diff --git a/packages/thirdweb/src/contract/actions/compiler-metadata.ts b/packages/thirdweb/src/contract/actions/compiler-metadata.ts index 001190913cd..6c5a16cae80 100644 --- a/packages/thirdweb/src/contract/actions/compiler-metadata.ts +++ b/packages/thirdweb/src/contract/actions/compiler-metadata.ts @@ -37,7 +37,7 @@ export function formatCompilerMetadata(metadata: any): PublishedMetadata { ]; return { name, - abi: metadata.output.abi, + abi: metadata?.output?.abi || [], metadata, info, licenses, diff --git a/packages/thirdweb/src/contract/actions/get-bytecode.ts b/packages/thirdweb/src/contract/actions/get-bytecode.ts index ee6757db639..34b58c14e15 100644 --- a/packages/thirdweb/src/contract/actions/get-bytecode.ts +++ b/packages/thirdweb/src/contract/actions/get-bytecode.ts @@ -11,11 +11,11 @@ const BYTECODE_CACHE = new WeakMap, Promise>(); * @returns A Promise that resolves to the bytecode of the contract. * @example * ```ts - * import { getByteCode } from "thirdweb/contract"; - * const bytecode = await getByteCode(contract); + * import { getBytecode } from "thirdweb/contract"; + * const bytecode = await getBytecode(contract); * ``` */ -export function getByteCode( +export function getBytecode( contract: ThirdwebContract, ): Promise { if (BYTECODE_CACHE.has(contract)) { diff --git a/packages/thirdweb/src/contract/actions/resolve-abi.ts b/packages/thirdweb/src/contract/actions/resolve-abi.ts index 418e292903c..9d5d0edb81c 100644 --- a/packages/thirdweb/src/contract/actions/resolve-abi.ts +++ b/packages/thirdweb/src/contract/actions/resolve-abi.ts @@ -2,7 +2,7 @@ import { formatAbi, type Abi, parseAbi } from "abitype"; import type { ThirdwebContract } from "../index.js"; import { getChainIdFromChain } from "../../chain/index.js"; import { getClientFetch } from "../../utils/fetch.js"; -import { getByteCode } from "./get-bytecode.js"; +import { getBytecode } from "./get-bytecode.js"; import { download } from "../../storage/download.js"; import { extractIPFSUri } from "../../utils/bytecode/extractIPFS.js"; import { detectMethodInBytecode } from "../../utils/bytecode/detectExtension.js"; @@ -106,15 +106,21 @@ export async function resolveAbiFromContractApi( export async function resolveAbiFromBytecode( contract: ThirdwebContract, ): Promise { - const bytecode = await getByteCode(contract); + const bytecode = await getBytecode(contract); const ipfsUri = extractIPFSUri(bytecode); if (!ipfsUri) { - throw new Error("No IPFS URI found in bytecode"); + // just early exit if we can't find an IPFS URI + return []; + } + try { + const res = await download({ uri: ipfsUri, client: contract.client }); + const json = await res.json(); + // ABI is at `json.output.abi` + return json.output.abi; + } catch { + // if we can't resolve the ABI from the IPFS URI, return an empty array + return []; } - const res = await download({ uri: ipfsUri, client: contract.client }); - const json = await res.json(); - // ABI is at `json.output.abi` - return json.output.abi; } const PLUGINS_ABI = { @@ -252,7 +258,7 @@ export async function resolveCompositeAbiFromBytecode( ) { const [rootAbi, bytecode] = await Promise.all([ resolveAbiFromBytecode(contract), - getByteCode(contract), + getBytecode(contract), ]); // check if contract is plugin-pattern / dynamic diff --git a/packages/thirdweb/src/contract/index.ts b/packages/thirdweb/src/contract/index.ts index c737daadb17..a5dee929c52 100644 --- a/packages/thirdweb/src/contract/index.ts +++ b/packages/thirdweb/src/contract/index.ts @@ -13,7 +13,7 @@ export { export { formatCompilerMetadata } from "./actions/compiler-metadata.js"; -export { getByteCode } from "./actions/get-bytecode.js"; +export { getBytecode } from "./actions/get-bytecode.js"; // verification export { diff --git a/packages/thirdweb/src/contract/verification/index.ts b/packages/thirdweb/src/contract/verification/index.ts index ce8b7dd352c..0e4f4885163 100644 --- a/packages/thirdweb/src/contract/verification/index.ts +++ b/packages/thirdweb/src/contract/verification/index.ts @@ -109,7 +109,7 @@ export async function verifyContract( const encodedArgs = options.encodedConstructorArgs ? options.encodedConstructorArgs : await fetchConstructorParams({ - abi: compilerMetadata.metadata.output.abi, + abi: compilerMetadata?.metadata?.output?.abi || [], contract: options.contract, explorerApiUrl: options.explorerApiUrl, explorerApiKey: options.explorerApiKey, diff --git a/packages/thirdweb/src/storage/download.ts b/packages/thirdweb/src/storage/download.ts index 5818081f552..54e11ab0e02 100644 --- a/packages/thirdweb/src/storage/download.ts +++ b/packages/thirdweb/src/storage/download.ts @@ -27,7 +27,11 @@ export async function download(options: DownloadOptions) { options.requestTimeoutMs, ); - return getClientFetch(options.client)(resolveScheme(options), { + const res = await getClientFetch(options.client)(resolveScheme(options), { requestTimeoutMs, }); + if (!res.ok) { + throw new Error(`Failed to download file: ${res.statusText}`); + } + return res; } diff --git a/packages/thirdweb/src/utils/any-evm/is-eip155-enforced.test.ts b/packages/thirdweb/src/utils/any-evm/is-eip155-enforced.test.ts new file mode 100644 index 00000000000..04d745655e1 --- /dev/null +++ b/packages/thirdweb/src/utils/any-evm/is-eip155-enforced.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from "vitest"; +import { isEIP155Enforced } from "./is-eip155-enforced.js"; +import { TEST_CLIENT } from "../../../test/src/test-clients.js"; + +describe("isEIP155Enforced", () => { + it("should return true if EIP-155 is enforced", async () => { + // Call the isEIP155Enforced function with a chain that enforces EIP-155 + const result = await isEIP155Enforced({ + // optimism enforce eip155 + chain: 10, + client: TEST_CLIENT, + }); + + // Assert that the result is true + expect(result).toBe(true); + }); + + it("should return false if EIP-155 is not enforced", async () => { + // Call the isEIP155Enforced function with a chain that does not enforce EIP-155 + const result = await isEIP155Enforced({ + // ethereum does not enforce eip155 + chain: 1, + client: TEST_CLIENT, + }); + + // Assert that the result is false + expect(result).toBe(false); + }); +}); diff --git a/packages/thirdweb/src/utils/any-evm/is-eip155-enforced.ts b/packages/thirdweb/src/utils/any-evm/is-eip155-enforced.ts index 59360b48277..ec54818857a 100644 --- a/packages/thirdweb/src/utils/any-evm/is-eip155-enforced.ts +++ b/packages/thirdweb/src/utils/any-evm/is-eip155-enforced.ts @@ -1,8 +1,13 @@ -import type { Chain } from "../../chain/index.js"; +import { getChainIdFromChain, type Chain } from "../../chain/index.js"; import type { ThirdwebClient } from "../../client/client.js"; import { eth_sendRawTransaction } from "../../rpc/index.js"; import { getRpcClient } from "../../rpc/rpc.js"; +// it's OK to cache this forever because: +// 1. the results can't change +// 2. the total size can be max * boolean +const EIP_ENFORCED_CACHE = new Map(); + type IsEIP155EnforcedOptions = { chain: Chain; client: ThirdwebClient; @@ -22,6 +27,12 @@ type IsEIP155EnforcedOptions = { export async function isEIP155Enforced( options: IsEIP155EnforcedOptions, ): Promise { + const chainId = getChainIdFromChain(options.chain); + // cache because the result cannot change + if (EIP_ENFORCED_CACHE.has(chainId)) { + return EIP_ENFORCED_CACHE.get(chainId) as boolean; + } + let result = false; try { // TODO: Find a better way to check this. @@ -37,11 +48,11 @@ export async function isEIP155Enforced( const errorJson = JSON.stringify(e).toLowerCase(); if (matchError(errorMsg) || matchError(errorJson)) { - return true; + result = true; } - return false; } - return false; + EIP_ENFORCED_CACHE.set(chainId, result); + return result; } const ERROR_SUBSTRINGS_COMPOSITE = [ diff --git a/packages/thirdweb/src/utils/any-evm/keccack-id.bench.ts b/packages/thirdweb/src/utils/any-evm/keccack-id.bench.ts new file mode 100644 index 00000000000..5f9f128f2f6 --- /dev/null +++ b/packages/thirdweb/src/utils/any-evm/keccack-id.bench.ts @@ -0,0 +1,13 @@ +import { describe, bench } from "vitest"; +import { keccackId } from "./keccack-id.js"; +import { id } from "ethers6"; + +const input = "Hello, World!"; +describe("keccackId", () => { + bench("thirdweb", () => { + keccackId(input); + }); + bench("ethers", () => { + id(input); + }); +}); diff --git a/packages/thirdweb/src/utils/any-evm/keccack-id.test.ts b/packages/thirdweb/src/utils/any-evm/keccack-id.test.ts new file mode 100644 index 00000000000..83c9f23d88b --- /dev/null +++ b/packages/thirdweb/src/utils/any-evm/keccack-id.test.ts @@ -0,0 +1,12 @@ +import { describe, it, expect } from "vitest"; +import { keccackId } from "./keccack-id.js"; + +describe("keccackId", () => { + it("should return the keccak256 hash of the input", () => { + const input = "Hello, World!"; + const result = keccackId(input); + expect(result).toMatchInlineSnapshot( + `"0xacaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f"`, + ); + }); +}); diff --git a/packages/thirdweb/src/utils/any-evm/keccack-id.ts b/packages/thirdweb/src/utils/any-evm/keccack-id.ts index 741c63199f5..be8a1754548 100644 --- a/packages/thirdweb/src/utils/any-evm/keccack-id.ts +++ b/packages/thirdweb/src/utils/any-evm/keccack-id.ts @@ -1,4 +1,4 @@ -import { keccak256, toBytes, type Hex } from "viem"; +import { keccak256, type Hex, stringToBytes } from "viem"; /** * Calculates the keccak ID of the given input. @@ -10,6 +10,6 @@ import { keccak256, toBytes, type Hex } from "viem"; * const keccakId = keccackId(input); * ``` */ -export function keccackId(input: string | number | bigint | boolean): Hex { - return keccak256(toBytes(input)); +export function keccackId(input: string): Hex { + return keccak256(stringToBytes(input)); } diff --git a/packages/thirdweb/src/utils/bytecode/detectExtension.ts b/packages/thirdweb/src/utils/bytecode/detectExtension.ts index 079a56c4c3b..c91a01bda13 100644 --- a/packages/thirdweb/src/utils/bytecode/detectExtension.ts +++ b/packages/thirdweb/src/utils/bytecode/detectExtension.ts @@ -2,7 +2,7 @@ import type { AbiFunction } from "abitype"; import { getFunctionSelector } from "../../abi/lib/getFunctionSelector.js"; import { type ThirdwebContract } from "../../contract/contract.js"; -import { getByteCode } from "../../contract/actions/get-bytecode.js"; +import { getBytecode } from "../../contract/actions/get-bytecode.js"; export type DetectExtensionOptions = { contract: ThirdwebContract; @@ -25,7 +25,7 @@ export type DetectExtensionOptions = { export async function detectMethod( options: DetectExtensionOptions, ): Promise { - const bytecode = await getByteCode(options.contract); + const bytecode = await getBytecode(options.contract); return detectMethodInBytecode({ bytecode, method: options.method }); } diff --git a/packages/thirdweb/src/utils/bytecode/extractIPFS.test.ts b/packages/thirdweb/src/utils/bytecode/extractIPFS.test.ts index c9cfb9e5bc1..76454eb4488 100644 --- a/packages/thirdweb/src/utils/bytecode/extractIPFS.test.ts +++ b/packages/thirdweb/src/utils/bytecode/extractIPFS.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { getByteCode } from "../../contract/index.js"; +import { getBytecode } from "../../contract/index.js"; import { DOODLES_CONTRACT, USDC_CONTRACT, @@ -9,7 +9,7 @@ import { extractIPFSUri } from "./extractIPFS.js"; describe("extractIPFSUri", () => { it("works if ipfs is there", async () => { // get some bytecode - const bytecode = await getByteCode(DOODLES_CONTRACT); + const bytecode = await getBytecode(DOODLES_CONTRACT); // extract IPFS hash const ipfsHash = extractIPFSUri(bytecode); @@ -21,7 +21,7 @@ describe("extractIPFSUri", () => { it("does not work if ipfs is not there", async () => { // get some bytecode - const bytecode = await getByteCode(USDC_CONTRACT); + const bytecode = await getBytecode(USDC_CONTRACT); // extract IPFS hash const ipfsHash = extractIPFSUri(bytecode); diff --git a/packages/thirdweb/src/utils/bytecode/is-contract-deployed.ts b/packages/thirdweb/src/utils/bytecode/is-contract-deployed.ts index d403028cc0f..24f770138dd 100644 --- a/packages/thirdweb/src/utils/bytecode/is-contract-deployed.ts +++ b/packages/thirdweb/src/utils/bytecode/is-contract-deployed.ts @@ -1,5 +1,5 @@ import type { ThirdwebContract } from "../../contract/contract.js"; -import { getByteCode } from "../../contract/actions/get-bytecode.js"; +import { getBytecode } from "../../contract/actions/get-bytecode.js"; /** * Checks if a contract is deployed by verifying its bytecode. @@ -18,5 +18,5 @@ import { getByteCode } from "../../contract/actions/get-bytecode.js"; export async function isContractDeployed( contract: ThirdwebContract, ): Promise { - return (await getByteCode(contract)) !== "0x"; + return (await getBytecode(contract)) !== "0x"; } diff --git a/packages/thirdweb/src/utils/bytecode/resolveImplementation.ts b/packages/thirdweb/src/utils/bytecode/resolveImplementation.ts index d907b05d0ad..0be6eaed01a 100644 --- a/packages/thirdweb/src/utils/bytecode/resolveImplementation.ts +++ b/packages/thirdweb/src/utils/bytecode/resolveImplementation.ts @@ -3,7 +3,7 @@ import type { ThirdwebContract } from "../../index.js"; import { eth_getStorageAt, getRpcClient } from "../../rpc/index.js"; import { readContractRaw } from "../../transaction/actions/raw/raw-read.js"; import { extractMinimalProxyImplementationAddress } from "./extractMnimalProxyImplementationAddress.js"; -import { getByteCode } from "../../contract/actions/get-bytecode.js"; +import { getBytecode } from "../../contract/actions/get-bytecode.js"; // TODO: move to const exports export const AddressZero = "0x0000000000000000000000000000000000000000"; @@ -22,7 +22,7 @@ export async function resolveImplementation( contract: ThirdwebContract, ): Promise<{ address: string; bytecode: string }> { const [bytecode, beacon] = await Promise.all([ - getByteCode(contract), + getBytecode(contract), getBeaconFromStorageSlot(contract), ]); // check minimal proxy first synchronously @@ -31,7 +31,7 @@ export async function resolveImplementation( if (minimalProxyImplementationAddress) { return { address: minimalProxyImplementationAddress, - bytecode: await getByteCode({ + bytecode: await getBytecode({ ...contract, address: minimalProxyImplementationAddress, }), @@ -57,7 +57,7 @@ export async function resolveImplementation( ) { return { address: implementationAddress, - bytecode: await getByteCode({ + bytecode: await getBytecode({ ...contract, address: implementationAddress, }), diff --git a/packages/thirdweb/src/utils/fetch.ts b/packages/thirdweb/src/utils/fetch.ts index 48c98161001..a4c463aea57 100644 --- a/packages/thirdweb/src/utils/fetch.ts +++ b/packages/thirdweb/src/utils/fetch.ts @@ -41,21 +41,18 @@ export function getClientFetch(client: ThirdwebClient) { }); } - let abortController: AbortController | undefined; + const controller = new AbortController(); let abortTimeout: ReturnType | undefined; if (requestTimeoutMs) { - abortController = new AbortController(); - abortTimeout = setTimeout(abortController.abort, requestTimeoutMs); + abortTimeout = setTimeout(() => controller.abort(), requestTimeoutMs); } return fetch(url, { ...restInit, headers, - signal: abortController?.signal, + signal: controller?.signal, }).finally(() => { - if (abortTimeout) { - clearTimeout(abortTimeout); - } + clearTimeout(abortTimeout); }); } FETCH_CACHE.set(client, fetchWithHeaders);