From 74f97626e002bafd9356d679c1f6eaec8d847590 Mon Sep 17 00:00:00 2001 From: gregfromstl Date: Tue, 17 Dec 2024 17:01:50 -0800 Subject: [PATCH] fix ens name resolution --- .../src/auth/parse-erc6492-signature.ts | 28 +- .../src/event/actions/get-events.test.ts | 246 ++++++++-------- .../thirdweb/src/event/actions/get-events.ts | 190 ++++++------- packages/thirdweb/src/event/prepare-event.ts | 74 ++--- .../src/extensions/ens/resolve-l2-name.ts | 12 +- .../thirdweb/src/rpc/actions/eth_getLogs.ts | 200 ++++++------- .../thirdweb/src/transaction/read-contract.ts | 268 +++++++++--------- pnpm-lock.yaml | 16 +- 8 files changed, 520 insertions(+), 514 deletions(-) diff --git a/packages/thirdweb/src/auth/parse-erc6492-signature.ts b/packages/thirdweb/src/auth/parse-erc6492-signature.ts index 0ac2f9572d7..c23f644f135 100644 --- a/packages/thirdweb/src/auth/parse-erc6492-signature.ts +++ b/packages/thirdweb/src/auth/parse-erc6492-signature.ts @@ -9,7 +9,7 @@ import type { Erc6492Signature } from "./types.js"; * @auth */ export type ParseErc6492SignatureReturnType = OneOf< - Erc6492Signature | { signature: Hex } + Erc6492Signature | { signature: Hex } >; /** @@ -29,19 +29,19 @@ export type ParseErc6492SignatureReturnType = OneOf< * @auth */ export function parseErc6492Signature( - signature: Hex, + signature: Hex, ): ParseErc6492SignatureReturnType { - if (!ox__WrappedSignature.validate(signature)) { - return { signature }; - } + if (!ox__WrappedSignature.validate(signature)) { + return { signature }; + } - const [address, data, originalSignature] = ox__AbiParameters.decode( - [{ type: "address" }, { type: "bytes" }, { type: "bytes" }], - signature, - ); - return { - address: ox__Address.checksum(address), - data, - signature: originalSignature, - }; + const [address, data, originalSignature] = ox__AbiParameters.decode( + [{ type: "address" }, { type: "bytes" }, { type: "bytes" }], + signature, + ); + return { + address: ox__Address.checksum(address), + data, + signature: originalSignature, + }; } diff --git a/packages/thirdweb/src/event/actions/get-events.test.ts b/packages/thirdweb/src/event/actions/get-events.test.ts index 3ccdeb876d4..17de12af7f9 100644 --- a/packages/thirdweb/src/event/actions/get-events.test.ts +++ b/packages/thirdweb/src/event/actions/get-events.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from "vitest"; import { FORK_BLOCK_NUMBER } from "../../../test/src/chains.js"; import { - DOODLES_CONTRACT, - USDT_CONTRACT, + DOODLES_CONTRACT, + USDT_CONTRACT, } from "../../../test/src/test-contracts.js"; import { transferEvent } from "../../extensions/erc721/__generated__/IERC721A/events/Transfer.js"; import { prepareEvent } from "../prepare-event.js"; @@ -11,136 +11,136 @@ import { getContractEvents } from "./get-events.js"; // skip this test suite if there is no secret key available to test with // TODO: remove reliance on secret key during unit tests entirely describe.runIf(process.env.TW_SECRET_KEY)("getEvents", () => { - it("should get all events", async () => { - const events = await getContractEvents({ - contract: USDT_CONTRACT, - fromBlock: FORK_BLOCK_NUMBER - 10n, - }); - expect(events.length).toBe(261); - }); + it("should get all events", async () => { + const events = await getContractEvents({ + contract: USDT_CONTRACT, + fromBlock: FORK_BLOCK_NUMBER - 10n, + }); + expect(events.length).toBe(261); + }); - // TODO: investigate why RPC returns 0 events here - it.skip("should get events for blockHash", async () => { - const BLOCK_HASH = - "0xb0ad5ee7b4912b50e5a2d7993796944653a4c0632c57740fe4a7a1c61e426324"; - const events = await getContractEvents({ - contract: USDT_CONTRACT, - blockHash: BLOCK_HASH, - }); + // TODO: investigate why RPC returns 0 events here + it.skip("should get events for blockHash", async () => { + const BLOCK_HASH = + "0xb0ad5ee7b4912b50e5a2d7993796944653a4c0632c57740fe4a7a1c61e426324"; + const events = await getContractEvents({ + contract: USDT_CONTRACT, + blockHash: BLOCK_HASH, + }); - expect(events.length).toBe(14); - }); + expect(events.length).toBe(14); + }); - it("should get events for blockRange", async () => { - const events = await getContractEvents({ - contract: USDT_CONTRACT, - blockRange: 10n, - }); - expect(events.length).toBe(241); + it("should get events for blockRange", async () => { + const events = await getContractEvents({ + contract: USDT_CONTRACT, + blockRange: 10n, + }); + expect(events.length).toBe(241); - const explicitEvents = await getContractEvents({ - contract: USDT_CONTRACT, - fromBlock: FORK_BLOCK_NUMBER - 9n, // 1 less than range as fromBlock and toBlock are inclusive - toBlock: FORK_BLOCK_NUMBER, - }); - expect(explicitEvents.length).toEqual(events.length); - }); + const explicitEvents = await getContractEvents({ + contract: USDT_CONTRACT, + fromBlock: FORK_BLOCK_NUMBER - 9n, // 1 less than range as fromBlock and toBlock are inclusive + toBlock: FORK_BLOCK_NUMBER, + }); + expect(explicitEvents.length).toEqual(events.length); + }); - it("should get events for blockRange using fromBlock and toBlock", async () => { - const eventsFromBlock = await getContractEvents({ - contract: USDT_CONTRACT, - fromBlock: FORK_BLOCK_NUMBER - 49n, - blockRange: 20n, - }); - expect(eventsFromBlock.length).toBe(412); + it("should get events for blockRange using fromBlock and toBlock", async () => { + const eventsFromBlock = await getContractEvents({ + contract: USDT_CONTRACT, + fromBlock: FORK_BLOCK_NUMBER - 49n, + blockRange: 20n, + }); + expect(eventsFromBlock.length).toBe(412); - const eventsToBlock = await getContractEvents({ - contract: USDT_CONTRACT, - toBlock: FORK_BLOCK_NUMBER - 30n, - blockRange: 20n, - }); - expect(eventsToBlock.length).toBe(eventsFromBlock.length); + const eventsToBlock = await getContractEvents({ + contract: USDT_CONTRACT, + toBlock: FORK_BLOCK_NUMBER - 30n, + blockRange: 20n, + }); + expect(eventsToBlock.length).toBe(eventsFromBlock.length); - const explicitEvents = await getContractEvents({ - contract: USDT_CONTRACT, - fromBlock: FORK_BLOCK_NUMBER - 49n, - toBlock: FORK_BLOCK_NUMBER - 30n, - }); - expect(explicitEvents.length).toEqual(eventsFromBlock.length); - }); + const explicitEvents = await getContractEvents({ + contract: USDT_CONTRACT, + fromBlock: FORK_BLOCK_NUMBER - 49n, + toBlock: FORK_BLOCK_NUMBER - 30n, + }); + expect(explicitEvents.length).toEqual(eventsFromBlock.length); + }); - it("should get individual events with signature", async () => { - const events = await getContractEvents({ - contract: USDT_CONTRACT, - fromBlock: FORK_BLOCK_NUMBER - 100n, - events: [ - prepareEvent({ - signature: "event Burn(address indexed burner, uint256 amount)", - }), - ], - }); - expect(events.length).toMatchInlineSnapshot("0"); - }); + it("should get individual events with signature", async () => { + const events = await getContractEvents({ + contract: USDT_CONTRACT, + fromBlock: FORK_BLOCK_NUMBER - 100n, + events: [ + prepareEvent({ + signature: "event Burn(address indexed burner, uint256 amount)", + }), + ], + }); + expect(events.length).toMatchInlineSnapshot("0"); + }); - it("should get specified events", async () => { - const events = await getContractEvents({ - contract: USDT_CONTRACT, - fromBlock: FORK_BLOCK_NUMBER - 10n, - events: [ - prepareEvent({ - signature: "event Burn(address indexed burner, uint256 amount)", - }), - prepareEvent({ - signature: { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "owner", - type: "address", - }, - { - indexed: true, - internalType: "address", - name: "spender", - type: "address", - }, - { - indexed: false, - internalType: "uint256", - name: "value", - type: "uint256", - }, - ], - name: "Approval", - type: "event", - }, - }), - ], - }); - expect(events.length).toMatchInlineSnapshot("9"); - }); + it("should get specified events", async () => { + const events = await getContractEvents({ + contract: USDT_CONTRACT, + fromBlock: FORK_BLOCK_NUMBER - 10n, + events: [ + prepareEvent({ + signature: "event Burn(address indexed burner, uint256 amount)", + }), + prepareEvent({ + signature: { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "spender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + }), + ], + }); + expect(events.length).toMatchInlineSnapshot("9"); + }); - it("should get individual events with extension", async () => { - const events = await getContractEvents({ - contract: DOODLES_CONTRACT, - fromBlock: FORK_BLOCK_NUMBER - 1000n, - events: [transferEvent()], - }); - expect(events.length).toBe(38); - }); + it("should get individual events with extension", async () => { + const events = await getContractEvents({ + contract: DOODLES_CONTRACT, + fromBlock: FORK_BLOCK_NUMBER - 1000n, + events: [transferEvent()], + }); + expect(events.length).toBe(38); + }); - it("should get individual events with filters", async () => { - const events = await getContractEvents({ - contract: DOODLES_CONTRACT, - fromBlock: FORK_BLOCK_NUMBER - 1000n, - events: [ - transferEvent({ - from: "0xB81965DdFdDA3923f292a47A1be83ba3A36B5133", - }), - ], - }); - expect(events.length).toBe(2); - }); + it("should get individual events with filters", async () => { + const events = await getContractEvents({ + contract: DOODLES_CONTRACT, + fromBlock: FORK_BLOCK_NUMBER - 1000n, + events: [ + transferEvent({ + from: "0xB81965DdFdDA3923f292a47A1be83ba3A36B5133", + }), + ], + }); + expect(events.length).toBe(2); + }); }); diff --git a/packages/thirdweb/src/event/actions/get-events.ts b/packages/thirdweb/src/event/actions/get-events.ts index 29032191ff2..515b1afd732 100644 --- a/packages/thirdweb/src/event/actions/get-events.ts +++ b/packages/thirdweb/src/event/actions/get-events.ts @@ -6,46 +6,46 @@ */ import type { - Abi, - AbiEvent, - ExtractAbiEvent, - ExtractAbiEventNames, + Abi, + AbiEvent, + ExtractAbiEvent, + ExtractAbiEventNames, } from "abitype"; import { resolveContractAbi } from "../../contract/actions/resolve-abi.js"; import type { ThirdwebContract } from "../../contract/contract.js"; import { eth_blockNumber } from "../../rpc/actions/eth_blockNumber.js"; import { - type GetLogsBlockParams, - type GetLogsParams, - eth_getLogs, + type GetLogsBlockParams, + type GetLogsParams, + eth_getLogs, } from "../../rpc/actions/eth_getLogs.js"; import { getRpcClient } from "../../rpc/rpc.js"; import type { Prettify } from "../../utils/type-utils.js"; import { type PreparedEvent, prepareEvent } from "../prepare-event.js"; -import { type ParseEventLogsResult, parseEventLogs } from "./parse-logs.js"; import { isAbiEvent } from "../utils.js"; +import { type ParseEventLogsResult, parseEventLogs } from "./parse-logs.js"; export type GetContractEventsOptionsDirect< - abi extends Abi, - abiEvents extends PreparedEvent[], - TStrict extends boolean, + abi extends Abi, + abiEvents extends PreparedEvent[], + TStrict extends boolean, > = { - contract: ThirdwebContract; - events?: abiEvents; - strict?: TStrict; + contract: ThirdwebContract; + events?: abiEvents; + strict?: TStrict; }; export type GetContractEventsOptions< - abi extends Abi, - abiEvents extends PreparedEvent[], - TStrict extends boolean, + abi extends Abi, + abiEvents extends PreparedEvent[], + TStrict extends boolean, > = Prettify< - GetContractEventsOptionsDirect & GetLogsBlockParams + GetContractEventsOptionsDirect & GetLogsBlockParams >; export type GetContractEventsResult< - abiEvents extends PreparedEvent[], - TStrict extends boolean, + abiEvents extends PreparedEvent[], + TStrict extends boolean, > = ParseEventLogsResult; /** @@ -97,91 +97,91 @@ export type GetContractEventsResult< * @contract */ export async function getContractEvents< - const abi extends Abi, - const abiEvents extends PreparedEvent[] = PreparedEvent< - ExtractAbiEvent> - >[], - const TStrict extends boolean = true, + const abi extends Abi, + const abiEvents extends PreparedEvent[] = PreparedEvent< + ExtractAbiEvent> + >[], + const TStrict extends boolean = true, >( - options: GetContractEventsOptions, + options: GetContractEventsOptions, ): Promise> { - const { contract, events, blockRange, ...restParams } = options; + const { contract, events, blockRange, ...restParams } = options; - const rpcRequest = getRpcClient(contract); + const rpcRequest = getRpcClient(contract); - if ( - restParams.blockHash && - (blockRange || restParams.fromBlock || restParams.toBlock) - ) { - throw new Error("Cannot specify blockHash and range simultaneously,"); - } + if ( + restParams.blockHash && + (blockRange || restParams.fromBlock || restParams.toBlock) + ) { + throw new Error("Cannot specify blockHash and range simultaneously,"); + } - const latestBlockNumber = await eth_blockNumber(rpcRequest); + const latestBlockNumber = await eth_blockNumber(rpcRequest); - // Compute toBlock and fromBlock if blockRange was passed - if (blockRange) { - const { fromBlock, toBlock } = restParams; + // Compute toBlock and fromBlock if blockRange was passed + if (blockRange) { + const { fromBlock, toBlock } = restParams; - // Make sure the inputs were properly defined - if ( - fromBlock !== undefined && - toBlock !== undefined && - BigInt(toBlock) - BigInt(fromBlock) !== BigInt(blockRange) - ) { - throw new Error( - "Incompatible blockRange with specified fromBlock and toBlock. Please only define fromBlock or toBlock when specifying blockRange.", - ); - } + // Make sure the inputs were properly defined + if ( + fromBlock !== undefined && + toBlock !== undefined && + BigInt(toBlock) - BigInt(fromBlock) !== BigInt(blockRange) + ) { + throw new Error( + "Incompatible blockRange with specified fromBlock and toBlock. Please only define fromBlock or toBlock when specifying blockRange.", + ); + } - if (fromBlock !== undefined) { - restParams.toBlock = BigInt(fromBlock) + BigInt(blockRange) - 1n; // Subtract one because toBlock is inclusive - } else if (toBlock !== undefined) { - restParams.fromBlock = BigInt(toBlock) - BigInt(blockRange) + 1n; // Add one because fromBlock is inclusive - } else { - // If no from or to block specified, use the latest block as the to block - restParams.toBlock = latestBlockNumber; - restParams.fromBlock = latestBlockNumber - BigInt(blockRange) + 1n; // Add one because fromBlock is inclusive - } - } + if (fromBlock !== undefined) { + restParams.toBlock = BigInt(fromBlock) + BigInt(blockRange) - 1n; // Subtract one because toBlock is inclusive + } else if (toBlock !== undefined) { + restParams.fromBlock = BigInt(toBlock) - BigInt(blockRange) + 1n; // Add one because fromBlock is inclusive + } else { + // If no from or to block specified, use the latest block as the to block + restParams.toBlock = latestBlockNumber; + restParams.fromBlock = latestBlockNumber - BigInt(blockRange) + 1n; // Add one because fromBlock is inclusive + } + } - let resolvedEvents = events ?? []; + let resolvedEvents = events ?? []; - // if we have an abi on the contract, we can encode the topics with it - if (!events?.length && !!contract) { - // if we have a contract *WITH* an abi we can use that - if (contract.abi?.length) { - // @ts-expect-error - we can't make typescript happy here, but we know this is an abi event - resolvedEvents = contract.abi - .filter(isAbiEvent) - .map((abiEvent) => prepareEvent({ signature: abiEvent })); - } else { - const runtimeAbi = await resolveContractAbi(contract); - // @ts-expect-error - we can't make typescript happy here, but we know this is an abi event - resolvedEvents = runtimeAbi - .filter(isAbiEvent) - .map((abiEvent) => prepareEvent({ signature: abiEvent })); - } - } + // if we have an abi on the contract, we can encode the topics with it + if (!events?.length && !!contract) { + // if we have a contract *WITH* an abi we can use that + if (contract.abi?.length) { + // @ts-expect-error - we can't make typescript happy here, but we know this is an abi event + resolvedEvents = contract.abi + .filter(isAbiEvent) + .map((abiEvent) => prepareEvent({ signature: abiEvent })); + } else { + const runtimeAbi = await resolveContractAbi(contract); + // @ts-expect-error - we can't make typescript happy here, but we know this is an abi event + resolvedEvents = runtimeAbi + .filter(isAbiEvent) + .map((abiEvent) => prepareEvent({ signature: abiEvent })); + } + } - const logsParams: GetLogsParams[] = - events && events.length > 0 - ? // if we have events passed in then we use those - events.map((e) => ({ - ...restParams, - address: contract?.address, - topics: e.topics, - })) - : // otherwise we want "all" events (aka not pass any topics at all) - [{ ...restParams, address: contract?.address }]; + const logsParams: GetLogsParams[] = + events && events.length > 0 + ? // if we have events passed in then we use those + events.map((e) => ({ + ...restParams, + address: contract?.address, + topics: e.topics, + })) + : // otherwise we want "all" events (aka not pass any topics at all) + [{ ...restParams, address: contract?.address }]; - const logs = await Promise.all( - logsParams.map((ethLogParams) => eth_getLogs(rpcRequest, ethLogParams)), - ); - const flattenLogs = logs - .flat() - .sort((a, b) => Number((a.blockNumber ?? 0n) - (b.blockNumber ?? 0n))); - return parseEventLogs({ - logs: flattenLogs, - events: resolvedEvents, - }); + const logs = await Promise.all( + logsParams.map((ethLogParams) => eth_getLogs(rpcRequest, ethLogParams)), + ); + const flattenLogs = logs + .flat() + .sort((a, b) => Number((a.blockNumber ?? 0n) - (b.blockNumber ?? 0n))); + return parseEventLogs({ + logs: flattenLogs, + events: resolvedEvents, + }); } diff --git a/packages/thirdweb/src/event/prepare-event.ts b/packages/thirdweb/src/event/prepare-event.ts index 32361d6cd74..d54299bd644 100644 --- a/packages/thirdweb/src/event/prepare-event.ts +++ b/packages/thirdweb/src/event/prepare-event.ts @@ -1,36 +1,36 @@ import { type AbiEvent, type ParseAbiItem, parseAbiItem } from "abitype"; -import * as ox__Hex from "ox/Hex"; import * as ox__AbiEvent from "ox/AbiEvent"; +import type * as ox__Hex from "ox/Hex"; import type { AbiEventParametersToPrimitiveTypes } from "./types.js"; import { isAbiEvent } from "./utils.js"; type ParseEvent = - // if the method IS an AbiEvent, return it - event extends AbiEvent - ? event - : event extends string // we now know we are in "string" territory - ? // if the string starts with `function` then we can parse it - event extends `event ${string}` - ? ParseAbiItem - : // do we have an ABI to check, check the length - AbiEvent - : // this means its neither have an AbiEvent NOR a string -> never - never; + // if the method IS an AbiEvent, return it + event extends AbiEvent + ? event + : event extends string // we now know we are in "string" territory + ? // if the string starts with `function` then we can parse it + event extends `event ${string}` + ? ParseAbiItem + : // do we have an ABI to check, check the length + AbiEvent + : // this means its neither have an AbiEvent NOR a string -> never + never; type EventFilters = - AbiEventParametersToPrimitiveTypes; + AbiEventParametersToPrimitiveTypes; export type PrepareEventOptions< - TSignature extends `event ${string}` | AbiEvent, + TSignature extends `event ${string}` | AbiEvent, > = { - signature: TSignature; - filters?: Readonly>>; + signature: TSignature; + filters?: Readonly>>; }; export type PreparedEvent = { - abiEvent: abiEvent; - hash: ox__Hex.Hex; - topics: ox__Hex.Hex[]; + abiEvent: abiEvent; + hash: ox__Hex.Hex; + topics: ox__Hex.Hex[]; }; /** @@ -47,24 +47,24 @@ export type PreparedEvent = { * @contract */ export function prepareEvent( - options: PrepareEventOptions, + options: PrepareEventOptions, ): PreparedEvent> { - const { signature } = options; - let resolvedSignature: ParseEvent; - if (isAbiEvent(signature)) { - resolvedSignature = signature as ParseEvent; - } else { - resolvedSignature = parseAbiItem(signature) as ParseEvent; - } + const { signature } = options; + let resolvedSignature: ParseEvent; + if (isAbiEvent(signature)) { + resolvedSignature = signature as ParseEvent; + } else { + resolvedSignature = parseAbiItem(signature) as ParseEvent; + } - const { topics } = ox__AbiEvent.encode( - resolvedSignature, - options.filters as ParseEvent, - ); - return { - abiEvent: resolvedSignature, - hash: ox__AbiEvent.getSelector(resolvedSignature), - // @ts-expect-error - TODO: investiagte why this complains, it works fine however - topics, - }; + const { topics } = ox__AbiEvent.encode( + resolvedSignature, + options.filters as ParseEvent, + ); + return { + abiEvent: resolvedSignature, + hash: ox__AbiEvent.getSelector(resolvedSignature), + // @ts-expect-error - TODO: investiagte why this complains, it works fine however + topics, + }; } diff --git a/packages/thirdweb/src/extensions/ens/resolve-l2-name.ts b/packages/thirdweb/src/extensions/ens/resolve-l2-name.ts index 87aea0fdd89..51c906621a9 100644 --- a/packages/thirdweb/src/extensions/ens/resolve-l2-name.ts +++ b/packages/thirdweb/src/extensions/ens/resolve-l2-name.ts @@ -1,8 +1,10 @@ import type { Address } from "abitype"; import * as ox__AbiParameters from "ox/AbiParameters"; +import * as ox__Bytes from "ox/Bytes"; import * as ox__Ens from "ox/Ens"; import * as ox__Hash from "ox/Hash"; -import type * as ox__Hex from "ox/Hex"; +import * as ox__Hex from "ox/Hex"; +import { keccak256 } from "viem"; import type { Chain } from "../../chains/types.js"; import type { ThirdwebClient } from "../../client/client.js"; import { getContract } from "../../contract/contract.js"; @@ -26,8 +28,11 @@ export type ResolveL2NameOptions = { */ const convertReverseNodeToBytes = (address: Address, chainId: number) => { const addressFormatted = address.toLocaleLowerCase() as Address; - const addressNode = ox__Hash.keccak256( - addressFormatted.substring(2) as ox__Hex.Hex, + // We temporarily need to use the raw hashing function from noble due to a bug in ox + const addressNode = ox__Hex.fromBytes( + ox__Hash.keccak256( + ox__Bytes.fromString(addressFormatted.slice(2) as string), + ), ); const cointype = (0x80000000 | chainId) >>> 0; @@ -42,6 +47,7 @@ const convertReverseNodeToBytes = (address: Address, chainId: number) => { [reverseNode, addressNode], ), ); + keccak256; return addressReverseNode; }; diff --git a/packages/thirdweb/src/rpc/actions/eth_getLogs.ts b/packages/thirdweb/src/rpc/actions/eth_getLogs.ts index d95d35d2a89..36e0f8b6c04 100644 --- a/packages/thirdweb/src/rpc/actions/eth_getLogs.ts +++ b/packages/thirdweb/src/rpc/actions/eth_getLogs.ts @@ -1,51 +1,51 @@ import type { Address } from "abitype"; import { - type BlockNumber, - type BlockTag, - type EIP1193RequestFn, - type EIP1474Methods, - type Hash, - type LogTopic, - type RpcLog, - formatLog, + type BlockNumber, + type BlockTag, + type EIP1193RequestFn, + type EIP1474Methods, + type Hash, + type LogTopic, + type RpcLog, + formatLog, } from "viem"; import { numberToHex } from "../../utils/encoding/hex.js"; export type GetLogsBlockParams = - | { - fromBlock?: BlockNumber | BlockTag; - toBlock?: BlockNumber | BlockTag; - blockHash?: never; - blockRange?: never; - } - | { - fromBlock?: never; - toBlock?: never; - blockHash?: Hash; - blockRange?: never; - } - | { - fromBlock?: BlockNumber | "latest"; - toBlock?: never; - blockRange: BlockNumber; - blockHash?: never; - } - | { - fromBlock?: never; - toBlock?: BlockNumber | "latest"; - blockRange: BlockNumber; - blockHash?: never; - } - | { - fromBlock?: never; - toBlock?: never; - blockRange: BlockNumber; - blockHash?: never; - }; + | { + fromBlock?: BlockNumber | BlockTag; + toBlock?: BlockNumber | BlockTag; + blockHash?: never; + blockRange?: never; + } + | { + fromBlock?: never; + toBlock?: never; + blockHash?: Hash; + blockRange?: never; + } + | { + fromBlock?: BlockNumber | "latest"; + toBlock?: never; + blockRange: BlockNumber; + blockHash?: never; + } + | { + fromBlock?: never; + toBlock?: BlockNumber | "latest"; + blockRange: BlockNumber; + blockHash?: never; + } + | { + fromBlock?: never; + toBlock?: never; + blockRange: BlockNumber; + blockHash?: never; + }; export type GetLogsParams = { - topics?: LogTopic[]; - address?: Address; + topics?: LogTopic[]; + address?: Address; } & GetLogsBlockParams; /** @@ -66,69 +66,69 @@ export type GetLogsParams = { * ``` */ export async function eth_getLogs( - request: EIP1193RequestFn, - params: GetLogsParams = {}, + request: EIP1193RequestFn, + params: GetLogsParams = {}, ) { - const topics = params.topics ?? []; + const topics = params.topics ?? []; - let logs: RpcLog[]; - // in the case we have a blockHash - if (params.blockHash) { - const param: { - address?: string | string[]; - topics: LogTopic[]; - blockHash: `0x${string}`; - } = { - topics, - blockHash: params.blockHash, - }; - if (params.address) { - param.address = params.address; - } - logs = await request({ - method: "eth_getLogs", - params: [param], - }); - } - // otherwise - else { - const param: { - address?: string | string[]; - topics?: LogTopic[]; - } & ( - | { - fromBlock?: BlockTag | `0x${string}`; - toBlock?: BlockTag | `0x${string}`; - blockHash?: never; - } - | { - fromBlock?: never; - toBlock?: never; - blockHash?: `0x${string}`; - } - ) = { topics }; - if (params.address) { - param.address = params.address; - } + let logs: RpcLog[]; + // in the case we have a blockHash + if (params.blockHash) { + const param: { + address?: string | string[]; + topics: LogTopic[]; + blockHash: `0x${string}`; + } = { + topics, + blockHash: params.blockHash, + }; + if (params.address) { + param.address = params.address; + } + logs = await request({ + method: "eth_getLogs", + params: [param], + }); + } + // otherwise + else { + const param: { + address?: string | string[]; + topics?: LogTopic[]; + } & ( + | { + fromBlock?: BlockTag | `0x${string}`; + toBlock?: BlockTag | `0x${string}`; + blockHash?: never; + } + | { + fromBlock?: never; + toBlock?: never; + blockHash?: `0x${string}`; + } + ) = { topics }; + if (params.address) { + param.address = params.address; + } - if (params.fromBlock) { - param.fromBlock = - typeof params.fromBlock === "bigint" - ? numberToHex(params.fromBlock) - : params.fromBlock; - } - if (params.toBlock) { - param.toBlock = - typeof params.toBlock === "bigint" - ? numberToHex(params.toBlock) - : params.toBlock; - } + if (params.fromBlock) { + param.fromBlock = + typeof params.fromBlock === "bigint" + ? numberToHex(params.fromBlock) + : params.fromBlock; + } + if (params.toBlock) { + param.toBlock = + typeof params.toBlock === "bigint" + ? numberToHex(params.toBlock) + : params.toBlock; + } - logs = await request({ - method: "eth_getLogs", - params: [param], - }); - } + logs = await request({ + method: "eth_getLogs", + params: [param], + }); + } - return logs.map((log) => formatLog(log)); + return logs.map((log) => formatLog(log)); } diff --git a/packages/thirdweb/src/transaction/read-contract.ts b/packages/thirdweb/src/transaction/read-contract.ts index 997e264245a..64510400d5c 100644 --- a/packages/thirdweb/src/transaction/read-contract.ts +++ b/packages/thirdweb/src/transaction/read-contract.ts @@ -1,73 +1,73 @@ import { - type Abi, - type AbiFunction, - type AbiParameter, - type AbiParametersToPrimitiveTypes, - type ExtractAbiFunctionNames, - parseAbiItem, + type Abi, + type AbiFunction, + type AbiParameter, + type AbiParametersToPrimitiveTypes, + type ExtractAbiFunctionNames, + parseAbiItem, } from "abitype"; -import * as ox__TransactionRequest from "ox/TransactionRequest"; import * as ox__AbiParameters from "ox/AbiParameters"; +import type * as ox__TransactionRequest from "ox/TransactionRequest"; import type { ThirdwebContract } from "../contract/contract.js"; import { isAbiFunction } from "./utils.js"; import type { PrepareTransactionOptions } from "./prepare-transaction.js"; import type { - BaseTransactionOptions, - ParamsOption, - ParseMethod, + BaseTransactionOptions, + ParamsOption, + ParseMethod, } from "./types.js"; import { eth_call } from "../rpc/actions/eth_call.js"; import { getRpcClient } from "../rpc/rpc.js"; import { encodeAbiParameters } from "../utils/abi/encodeAbiParameters.js"; import { - type PreparedMethod, - prepareMethod, + type PreparedMethod, + prepareMethod, } from "../utils/abi/prepare-method.js"; import type { Hex } from "../utils/encoding/hex.js"; export type ReadContractResult = // if the outputs are 0 length, return never, invalid case - outputs extends { length: 0 } - ? never - : outputs extends { length: 1 } - ? // if the outputs are 1 length, we'll always return the first element - AbiParametersToPrimitiveTypes[0] - : // otherwise we'll return the array - AbiParametersToPrimitiveTypes; + outputs extends { length: 0 } + ? never + : outputs extends { length: 1 } + ? // if the outputs are 1 length, we'll always return the first element + AbiParametersToPrimitiveTypes[0] + : // otherwise we'll return the array + AbiParametersToPrimitiveTypes; export type ReadContractOptions< - TAbi extends Abi = [], - TMethod extends - | AbiFunction - | string - | (( - contract: ThirdwebContract, - ) => Promise) = TAbi extends { length: 0 } - ? AbiFunction | string - : ExtractAbiFunctionNames, - TPreparedMethod extends PreparedMethod< - ParseMethod - > = PreparedMethod>, + TAbi extends Abi = [], + TMethod extends + | AbiFunction + | string + | (( + contract: ThirdwebContract, + ) => Promise) = TAbi extends { length: 0 } + ? AbiFunction | string + : ExtractAbiFunctionNames, + TPreparedMethod extends PreparedMethod< + ParseMethod + > = PreparedMethod>, > = BaseTransactionOptions< - Omit< - ox__TransactionRequest.TransactionRequest, - | "from" - | "to" - | "data" - | "value" - | "accessList" - | "gas" - | "gasPrice" - | "maxFeePerGas" - | "maxPriorityFeePerGas" - | "nonce" - > & { - method: TMethod | TPreparedMethod; - from?: string; - } & ParamsOption & - Omit, - TAbi + Omit< + ox__TransactionRequest.TransactionRequest, + | "from" + | "to" + | "data" + | "value" + | "accessList" + | "gas" + | "gasPrice" + | "maxFeePerGas" + | "maxPriorityFeePerGas" + | "nonce" + > & { + method: TMethod | TPreparedMethod; + from?: string; + } & ParamsOption & + Omit, + TAbi >; /** @@ -129,99 +129,99 @@ export type ReadContractOptions< * ``` */ export async function readContract< - const TAbi extends Abi, - const TMethod extends TAbi extends { - length: 0; - } - ? - | AbiFunction - | `function ${string}` - | ((contract: ThirdwebContract) => Promise) - : ExtractAbiFunctionNames, - const TPreparedMethod extends PreparedMethod< - ParseMethod - > = PreparedMethod>, + const TAbi extends Abi, + const TMethod extends TAbi extends { + length: 0; + } + ? + | AbiFunction + | `function ${string}` + | ((contract: ThirdwebContract) => Promise) + : ExtractAbiFunctionNames, + const TPreparedMethod extends PreparedMethod< + ParseMethod + > = PreparedMethod>, >( - options: ReadContractOptions, + options: ReadContractOptions, ): Promise> { - type ParsedMethod_ = ParseMethod; - type PreparedMethod_ = PreparedMethod; - const { contract, method, params } = options; + type ParsedMethod_ = ParseMethod; + type PreparedMethod_ = PreparedMethod; + const { contract, method, params } = options; - const resolvePreparedMethod = async () => { - if (Array.isArray(method)) { - return method as PreparedMethod_; - } - if (isAbiFunction(method)) { - return prepareMethod(method as ParsedMethod_) as PreparedMethod_; - } + const resolvePreparedMethod = async () => { + if (Array.isArray(method)) { + return method as PreparedMethod_; + } + if (isAbiFunction(method)) { + return prepareMethod(method as ParsedMethod_) as PreparedMethod_; + } - if (typeof method === "function") { - return prepareMethod( - // @ts-expect-error - we're sure it's a function - (await method(contract)) as ParsedMethod_, - ) as PreparedMethod_; - } - // if the method starts with the string `function ` we always will want to try to parse it - if (typeof method === "string" && method.startsWith("function ")) { - // @ts-expect-error - method *is* string in this case - const abiItem = parseAbiItem(method); - if (abiItem.type === "function") { - return prepareMethod(abiItem as ParsedMethod_) as PreparedMethod_; - } - throw new Error(`"method" passed is not of type "function"`); - } - // check if we have a "abi" on the contract - if (contract.abi && contract.abi?.length > 0) { - // extract the abiFunction from it - const abiFunction = contract.abi?.find( - (item) => item.type === "function" && item.name === method, - ); - // if we were able to find it -> return it - if (abiFunction) { - return prepareMethod(abiFunction as ParsedMethod_) as PreparedMethod_; - } - } - throw new Error(`Could not resolve method "${method}".`); - }; + if (typeof method === "function") { + return prepareMethod( + // @ts-expect-error - we're sure it's a function + (await method(contract)) as ParsedMethod_, + ) as PreparedMethod_; + } + // if the method starts with the string `function ` we always will want to try to parse it + if (typeof method === "string" && method.startsWith("function ")) { + // @ts-expect-error - method *is* string in this case + const abiItem = parseAbiItem(method); + if (abiItem.type === "function") { + return prepareMethod(abiItem as ParsedMethod_) as PreparedMethod_; + } + throw new Error(`"method" passed is not of type "function"`); + } + // check if we have a "abi" on the contract + if (contract.abi && contract.abi?.length > 0) { + // extract the abiFunction from it + const abiFunction = contract.abi?.find( + (item) => item.type === "function" && item.name === method, + ); + // if we were able to find it -> return it + if (abiFunction) { + return prepareMethod(abiFunction as ParsedMethod_) as PreparedMethod_; + } + } + throw new Error(`Could not resolve method "${method}".`); + }; - // resolve in parallel - const [resolvedPreparedMethod, resolvedParams] = await Promise.all([ - resolvePreparedMethod(), - typeof params === "function" ? params() : params, - ]); + // resolve in parallel + const [resolvedPreparedMethod, resolvedParams] = await Promise.all([ + resolvePreparedMethod(), + typeof params === "function" ? params() : params, + ]); - let encodedData: Hex; + let encodedData: Hex; - // if we have no inputs, we know it's just the signature - if (resolvedPreparedMethod[1].length === 0) { - encodedData = resolvedPreparedMethod[0]; - } else { - // we do a "manual" concat here to avoid the overhead of the "concatHex" function - // we can do this because we know the specific formats of the values - encodedData = (resolvedPreparedMethod[0] + - encodeAbiParameters( - resolvedPreparedMethod[1], - // @ts-expect-error - TODO: fix this type issue - resolvedParams, - ).slice(2)) as `${(typeof resolvedPreparedMethod)[0]}${string}`; - } + // if we have no inputs, we know it's just the signature + if (resolvedPreparedMethod[1].length === 0) { + encodedData = resolvedPreparedMethod[0]; + } else { + // we do a "manual" concat here to avoid the overhead of the "concatHex" function + // we can do this because we know the specific formats of the values + encodedData = (resolvedPreparedMethod[0] + + encodeAbiParameters( + resolvedPreparedMethod[1], + // @ts-expect-error - TODO: fix this type issue + resolvedParams, + ).slice(2)) as `${(typeof resolvedPreparedMethod)[0]}${string}`; + } - const rpcRequest = getRpcClient({ - chain: contract.chain, - client: contract.client, - }); + const rpcRequest = getRpcClient({ + chain: contract.chain, + client: contract.client, + }); - const result = await eth_call(rpcRequest, { - data: encodedData, - to: contract.address, - from: options.from, - }); - // use the prepared method to decode the result - const decoded = ox__AbiParameters.decode(resolvedPreparedMethod[2], result); - if (Array.isArray(decoded) && decoded.length === 1) { - return decoded[0]; - } + const result = await eth_call(rpcRequest, { + data: encodedData, + to: contract.address, + from: options.from, + }); + // use the prepared method to decode the result + const decoded = ox__AbiParameters.decode(resolvedPreparedMethod[2], result); + if (Array.isArray(decoded) && decoded.length === 1) { + return decoded[0]; + } - return decoded as ReadContractResult; + return decoded as ReadContractResult; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 725980f907a..d01d9db94b1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23467,8 +23467,8 @@ snapshots: '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.7.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) eslint-plugin-react: 7.37.2(eslint@8.57.0) eslint-plugin-react-hooks: 5.1.0(eslint@8.57.0) @@ -23487,7 +23487,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0): + eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0(supports-color@8.1.1) @@ -23499,7 +23499,7 @@ snapshots: is-glob: 4.0.3 stable-hash: 0.0.4 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -23524,18 +23524,18 @@ snapshots: - bluebird - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.14.1(eslint@8.57.0)(typescript@5.7.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -23546,7 +23546,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.16.0 is-glob: 4.0.3