diff --git a/.changeset/green-books-drop.md b/.changeset/green-books-drop.md new file mode 100644 index 00000000000..4c7b4d77be1 --- /dev/null +++ b/.changeset/green-books-drop.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Auto resolve zksync bytecode diff --git a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx index 36c1a111eed..6198fbfb897 100644 --- a/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx +++ b/apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx @@ -37,7 +37,6 @@ import { } from "thirdweb/deploys"; import { useActiveAccount, useActiveWalletChain } from "thirdweb/react"; import { upload } from "thirdweb/storage"; -import { isZkSyncChain } from "thirdweb/utils"; import { FormHelperText, FormLabel, Heading, Text } from "tw-components"; import { useCustomFactoryAbi, useFunctionParamsFromABI } from "../hooks"; import { addContractToMultiChainRegistry } from "../utils"; @@ -386,10 +385,6 @@ export const CustomContractForm: React.FC = ({ throw new Error("no chain"); } - const compilerType = (await isZkSyncChain(walletChain)) - ? "zksolc" - : "solc"; - let _contractURI = ""; if (hasContractURI && params.contractMetadata) { @@ -454,7 +449,6 @@ export const CustomContractForm: React.FC = ({ deployMetadata: m, initializeParams: params.moduleData[m.name], })), - compilerType, }); }, }); diff --git a/apps/dashboard/src/components/contract-components/contract-publish-form/index.tsx b/apps/dashboard/src/components/contract-components/contract-publish-form/index.tsx index c0b67d2338e..de2aa7cffb6 100644 --- a/apps/dashboard/src/components/contract-components/contract-publish-form/index.tsx +++ b/apps/dashboard/src/components/contract-components/contract-publish-form/index.tsx @@ -92,7 +92,9 @@ export function ContractPublishForm(props: { customFactoryAddresses: props.publishMetadata.factoryDeploymentData?.customFactoryInput ?.customFactoryAddresses || {}, - params: props.publishMetadata.customFactoryInput?.params || [], + params: + props.publishMetadata.factoryDeploymentData?.customFactoryInput + ?.params || [], }, }, constructorParams: props.publishMetadata.constructorParams || {}, diff --git a/apps/dashboard/src/components/contract-components/contract-table/cells/description.tsx b/apps/dashboard/src/components/contract-components/contract-table/cells/description.tsx index 91ee2857458..ef16ab7ef90 100644 --- a/apps/dashboard/src/components/contract-components/contract-table/cells/description.tsx +++ b/apps/dashboard/src/components/contract-components/contract-table/cells/description.tsx @@ -17,8 +17,7 @@ export const ContractDescriptionCell: React.FC< } > - {deployMetadataResultQuery.data?.latestPublishedContractMetadata - ?.publishedMetadata.description || + {deployMetadataResultQuery.data?.description || (!deployMetadataResultQuery.isFetching ? "First Version" : "None")} diff --git a/apps/dashboard/src/components/contract-components/shared/contract-id-image.tsx b/apps/dashboard/src/components/contract-components/shared/contract-id-image.tsx index efa6eec04fc..cbe5f8120a7 100644 --- a/apps/dashboard/src/components/contract-components/shared/contract-id-image.tsx +++ b/apps/dashboard/src/components/contract-components/shared/contract-id-image.tsx @@ -18,9 +18,7 @@ export const ContractIdImage: React.FC = ({ }) => { const deployMetadataResultQuery = useFetchDeployMetadata(contractId); - const logo = - deployMetadataResultQuery.data?.latestPublishedContractMetadata - ?.publishedMetadata.logo; + const logo = deployMetadataResultQuery.data?.logo; const img = deployMetadataResultQuery.data?.image !== "custom" diff --git a/packages/thirdweb/src/contract/actions/compiler-metadata.ts b/packages/thirdweb/src/contract/actions/compiler-metadata.ts index ebe1c091980..941e9ecba64 100644 --- a/packages/thirdweb/src/contract/actions/compiler-metadata.ts +++ b/packages/thirdweb/src/contract/actions/compiler-metadata.ts @@ -30,11 +30,10 @@ export type CompilerMetadata = { export function formatCompilerMetadata( // biome-ignore lint/suspicious/noExplicitAny: TODO: fix later metadata: any, - compilerType?: "solc" | "zksolc", ): CompilerMetadata { let meta = metadata; - if (compilerType === "zksolc") { - meta = metadata.source_metadata || meta; + if ("source_metadata" in metadata) { + meta = metadata.source_metadata; } const compilationTarget = meta.settings.compilationTarget; const targets = Object.keys(compilationTarget); diff --git a/packages/thirdweb/src/contract/deployment/publisher.ts b/packages/thirdweb/src/contract/deployment/publisher.ts index f8b7b1e91d2..d0c79a868cb 100644 --- a/packages/thirdweb/src/contract/deployment/publisher.ts +++ b/packages/thirdweb/src/contract/deployment/publisher.ts @@ -24,7 +24,6 @@ export async function fetchPublishedContractMetadata(options: { contractId: string; publisher?: string; version?: string; - compilerType?: "solc" | "zksolc"; }): Promise { const cacheKey = `${options.contractId}-${options.publisher}-${options.version}`; return withCache( @@ -34,7 +33,6 @@ export async function fetchPublishedContractMetadata(options: { publisherAddress: options.publisher || THIRDWEB_DEPLOYER, contractId: options.contractId, version: options.version, - compilerType: options.compilerType, }); if (!publishedContract.publishMetadataUri) { throw new Error( @@ -44,7 +42,6 @@ export async function fetchPublishedContractMetadata(options: { const data = await fetchDeployMetadata({ client: options.client, uri: publishedContract.publishMetadataUri, - compilerType: options.compilerType, }); return data; }, @@ -214,7 +211,6 @@ type FetchPublishedContractOptions = { contractId: string; version?: string; client: ThirdwebClient; - compilerType?: "solc" | "zksolc"; }; /** diff --git a/packages/thirdweb/src/contract/deployment/utils/bootstrap.ts b/packages/thirdweb/src/contract/deployment/utils/bootstrap.ts index 1da6c8c24b6..3f8e48a8b7d 100644 --- a/packages/thirdweb/src/contract/deployment/utils/bootstrap.ts +++ b/packages/thirdweb/src/contract/deployment/utils/bootstrap.ts @@ -1,5 +1,8 @@ import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js"; -import type { FetchDeployMetadataResult } from "../../../utils/any-evm/deploy-metadata.js"; +import { + type FetchDeployMetadataResult, + fetchBytecodeFromCompilerMetadata, +} from "../../../utils/any-evm/deploy-metadata.js"; import { isZkSyncChain } from "../../../utils/any-evm/zksync/isZkSyncChain.js"; import type { ClientAndChainAndAccount } from "../../../utils/types.js"; import { type ThirdwebContract, getContract } from "../../contract.js"; @@ -27,7 +30,6 @@ export async function getOrDeployInfraForPublishedContract( constructorParams?: Record; publisher?: string; version?: string; - compilerType?: "solc" | "zksolc"; }, ): Promise<{ cloneFactoryContract: ThirdwebContract; @@ -41,7 +43,6 @@ export async function getOrDeployInfraForPublishedContract( constructorParams, publisher, version, - compilerType, } = args; if (await isZkSyncChain(chain)) { @@ -55,14 +56,17 @@ export async function getOrDeployInfraForPublishedContract( contractId, publisher, version, - compilerType, }); const implementationContract = await zkDeployContractDeterministic({ chain, client, account, abi: compilerMetadata.abi, - bytecode: compilerMetadata.bytecode, + bytecode: await fetchBytecodeFromCompilerMetadata({ + compilerMetadata, + client, + chain, + }), params: constructorParams, }); return { diff --git a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts index 2be090bd2a0..9b20921068c 100644 --- a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts +++ b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts @@ -13,7 +13,10 @@ import { resolveMethod } from "../../transaction/resolve-method.js"; import { encodeAbiParameters } from "../../utils/abi/encodeAbiParameters.js"; import { normalizeFunctionParams } from "../../utils/abi/normalizeFunctionParams.js"; import { getAddress } from "../../utils/address.js"; -import type { CompilerMetadata } from "../../utils/any-evm/deploy-metadata.js"; +import { + type CompilerMetadata, + fetchBytecodeFromCompilerMetadata, +} from "../../utils/any-evm/deploy-metadata.js"; import type { FetchDeployMetadataResult } from "../../utils/any-evm/deploy-metadata.js"; import { isZkSyncChain } from "../../utils/any-evm/zksync/isZkSyncChain.js"; import type { Hex } from "../../utils/encoding/hex.js"; @@ -33,7 +36,6 @@ export type DeployPublishedContractOptions = { version?: string; implementationConstructorParams?: Record; salt?: string; - compilerType?: "solc" | "zksolc"; }; /** @@ -93,14 +95,12 @@ export async function deployPublishedContract( version, implementationConstructorParams, salt, - compilerType, } = options; const deployMetadata = await fetchPublishedContractMetadata({ client, contractId, publisher, version, - compilerType, }); return deployContractfromDeployMetadata({ @@ -111,7 +111,6 @@ export async function deployPublishedContract( initializeParams: contractParams, implementationConstructorParams, salt, - compilerType, }); } @@ -130,7 +129,6 @@ export type DeployContractfromDeployMetadataOptions = { initializeParams?: Record; }[]; salt?: string; - compilerType?: "solc" | "zksolc"; }; /** @@ -148,7 +146,6 @@ export async function deployContractfromDeployMetadata( implementationConstructorParams, modules, salt, - compilerType, } = options; switch (deployMetadata?.deployType) { case "standard": { @@ -182,7 +179,6 @@ export async function deployContractfromDeployMetadata( client, })), publisher: deployMetadata.publisher, - compilerType, }); const initializeTransaction = await getInitializeTransaction({ @@ -272,7 +268,11 @@ async function directDeploy(options: { account, client, chain, - bytecode: compilerMetadata.bytecode, + bytecode: await fetchBytecodeFromCompilerMetadata({ + compilerMetadata, + client, + chain, + }), abi: compilerMetadata.abi, params: contractParams, salt, @@ -286,7 +286,11 @@ async function directDeploy(options: { account, client, chain, - bytecode: compilerMetadata.bytecode, + bytecode: await fetchBytecodeFromCompilerMetadata({ + compilerMetadata, + client, + chain, + }), abi: compilerMetadata.abi, constructorParams: contractParams, salt, diff --git a/packages/thirdweb/src/extensions/thirdweb/write/publish.test.ts b/packages/thirdweb/src/extensions/thirdweb/write/publish.test.ts index b4e09e9f5fa..9ec5d612128 100644 --- a/packages/thirdweb/src/extensions/thirdweb/write/publish.test.ts +++ b/packages/thirdweb/src/extensions/thirdweb/write/publish.test.ts @@ -85,7 +85,6 @@ describe.runIf(process.env.TW_SECRET_KEY)("publishContract", () => { uri: logs?.[0]?.args.publishedContract.publishMetadataUri ?? "", }); expect(publishedData.abi).toBeDefined(); - expect(publishedData.bytecode).toBeDefined(); expect(publishedData.version).toBe("0.0.1"); expect(publishedData.changelog).toBe("Initial release"); expect(publishedData.name).toBe("CatAttackNFT"); diff --git a/packages/thirdweb/src/utils/any-evm/compute-published-contract-deploy-info.ts b/packages/thirdweb/src/utils/any-evm/compute-published-contract-deploy-info.ts index efebf8cc050..77203b08371 100644 --- a/packages/thirdweb/src/utils/any-evm/compute-published-contract-deploy-info.ts +++ b/packages/thirdweb/src/utils/any-evm/compute-published-contract-deploy-info.ts @@ -7,7 +7,10 @@ import { encodeAbiParameters } from "../abi/encodeAbiParameters.js"; import { normalizeFunctionParams } from "../abi/normalizeFunctionParams.js"; import { ensureBytecodePrefix } from "../bytecode/prefix.js"; import type { Hex } from "../encoding/hex.js"; -import type { FetchDeployMetadataResult } from "./deploy-metadata.js"; +import { + type FetchDeployMetadataResult, + fetchBytecodeFromCompilerMetadata, +} from "./deploy-metadata.js"; import { getInitBytecodeWithSalt } from "./get-init-bytecode-with-salt.js"; /** @@ -52,7 +55,11 @@ export async function computeDeploymentInfoFromMetadata(args: { client: args.client, chain: args.chain, abi: args.contractMetadata.abi, - bytecode: args.contractMetadata.bytecode, + bytecode: await fetchBytecodeFromCompilerMetadata({ + compilerMetadata: args.contractMetadata, + client: args.client, + chain: args.chain, + }), constructorParams: args.constructorParams, salt: args.salt, }); diff --git a/packages/thirdweb/src/utils/any-evm/deploy-metadata.ts b/packages/thirdweb/src/utils/any-evm/deploy-metadata.ts index 701c3d6d394..7b832ba99e8 100644 --- a/packages/thirdweb/src/utils/any-evm/deploy-metadata.ts +++ b/packages/thirdweb/src/utils/any-evm/deploy-metadata.ts @@ -1,14 +1,16 @@ import type { Abi } from "abitype"; +import type { Chain } from "../../chains/types.js"; import type { ThirdwebClient } from "../../client/client.js"; import { formatCompilerMetadata } from "../../contract/actions/compiler-metadata.js"; import { download } from "../../storage/download.js"; import type { Hex } from "../encoding/hex.js"; +import { withCache } from "../promise/withCache.js"; import type { Prettify } from "../type-utils.js"; +import { isZkSyncChain } from "./zksync/isZkSyncChain.js"; type FetchDeployMetadataOptions = { uri: string; client: ThirdwebClient; - compilerType?: "solc" | "zksolc"; }; export type FetchDeployMetadataResult = Partial & @@ -23,41 +25,21 @@ export type FetchDeployMetadataResult = Partial & export async function fetchDeployMetadata( options: FetchDeployMetadataOptions, ): Promise { - const isZksolc = options.compilerType === "zksolc"; const rawMeta: RawCompilerMetadata = await download({ uri: options.uri, client: options.client, }).then((r) => r.json()); - if ( - isZksolc && - (!rawMeta.compilers?.zksolc || rawMeta.compilers?.zksolc.length === 0) - ) { - throw new Error(`No zksolc metadata found for contract: ${rawMeta.name}`); - } - - const metadataUri = isZksolc - ? rawMeta.compilers.zksolc[0].metadataUri - : rawMeta.metadataUri; - const bytecodeUri = isZksolc - ? rawMeta.compilers.zksolc[0].bytecodeUri - : rawMeta.bytecodeUri; - const [deployBytecode, parsedMeta] = await Promise.all([ - download({ uri: bytecodeUri, client: options.client }).then( - (res) => res.text() as Promise, - ), - fetchAndParseCompilerMetadata({ - client: options.client, - uri: metadataUri, - compilerType: options.compilerType, - }), - ]); + const metadataUri = rawMeta.metadataUri; + const parsedMeta = await fetchAndParseCompilerMetadata({ + client: options.client, + uri: metadataUri, + }); return { ...rawMeta, ...parsedMeta, version: rawMeta.version, - bytecode: deployBytecode, name: rawMeta.name, }; } @@ -84,10 +66,42 @@ async function fetchAndParseCompilerMetadata( } return { ...metadata, - ...formatCompilerMetadata(metadata, options.compilerType), + ...formatCompilerMetadata(metadata), }; } +export async function fetchBytecodeFromCompilerMetadata(options: { + compilerMetadata: FetchDeployMetadataResult; + client: ThirdwebClient; + chain: Chain; +}) { + const { compilerMetadata, client, chain } = options; + return withCache( + async () => { + const isZksolc = await isZkSyncChain(chain); + const bytecodeUri = isZksolc + ? compilerMetadata.compilers?.zksolc[0]?.bytecodeUri + : compilerMetadata.bytecodeUri; + + if (!bytecodeUri) { + throw new Error( + `No bytecode URI found in compiler metadata for ${compilerMetadata.name} on chain ${chain.name}`, + ); + } + const deployBytecode = await download({ + uri: bytecodeUri, + client, + }).then((res) => res.text() as Promise); + + return deployBytecode; + }, + { + cacheKey: `bytecode:${compilerMetadata.name}:${chain.id}`, + cacheTime: 24 * 60 * 60 * 1000, + }, + ); +} + // types type RawCompilerMetadata = { @@ -96,8 +110,8 @@ type RawCompilerMetadata = { bytecodeUri: string; // biome-ignore lint/suspicious/noExplicitAny: TODO: fix later analytics?: any; - // biome-ignore lint/suspicious/noExplicitAny: TODO: fix later - [key: string]: any; + version?: string; + [key: string]: unknown; }; type ParsedCompilerMetadata = { @@ -146,10 +160,7 @@ type ParsedCompilerMetadata = { }; export type CompilerMetadata = Prettify< - RawCompilerMetadata & - ParsedCompilerMetadata & { - bytecode: Hex; - } + RawCompilerMetadata & ParsedCompilerMetadata >; export type ExtendedMetadata = { diff --git a/packages/thirdweb/src/utils/any-evm/zksync/isZkSyncChain.ts b/packages/thirdweb/src/utils/any-evm/zksync/isZkSyncChain.ts index 54360eff765..91a13b1f0c3 100644 --- a/packages/thirdweb/src/utils/any-evm/zksync/isZkSyncChain.ts +++ b/packages/thirdweb/src/utils/any-evm/zksync/isZkSyncChain.ts @@ -6,23 +6,26 @@ export async function isZkSyncChain(chain: Chain) { return false; } - const stack = await getChainStack(chain.id).catch(() => { - // fall back to checking against these zksync chain-ids - if ( - chain.id === 324 || - chain.id === 300 || - chain.id === 302 || - chain.id === 11124 || - chain.id === 282 || // cronos zkevm testnet - chain.id === 388 // cronos zkevm mainnet - ) { - return "zksync-stack"; - } - - return ""; - }); + // check known zksync chain-ids first + if ( + chain.id === 324 || + chain.id === 300 || + chain.id === 302 || + chain.id === 11124 || + chain.id === 282 || // cronos zkevm testnet + chain.id === 388 // cronos zkevm mainnet + ) { + return true; + } - return stack === "zksync-stack"; + // fallback to checking the stack on rpc + try { + const stack = await getChainStack(chain.id); + return stack === "zksync-stack"; + } catch { + // If the network check fails, assume it's not a ZkSync chain + return false; + } } async function getChainStack(chainId: number): Promise { diff --git a/packages/thirdweb/src/wallets/smart/smart-wallet-zksync.test.ts b/packages/thirdweb/src/wallets/smart/smart-wallet-zksync.test.ts index 3d4acc86f2b..9f87b6313c9 100644 --- a/packages/thirdweb/src/wallets/smart/smart-wallet-zksync.test.ts +++ b/packages/thirdweb/src/wallets/smart/smart-wallet-zksync.test.ts @@ -27,7 +27,8 @@ const contract = getContract({ client, }); -describe.runIf(process.env.TW_SECRET_KEY).skip( +// TODO run this on every CI run, needs proper zk fork setup +describe.runIf(process.env.TW_SECRET_KEY).todo( "SmartWallet zksync tests", { retry: 0, @@ -112,5 +113,28 @@ describe.runIf(process.env.TW_SECRET_KEY).skip( }); expect(tx.transactionHash.length).toBe(66); }); + + it("should send a transaction on Creator Testnet", async () => { + const abstractSmartWallet = smartWallet({ + chain: defineChain(4654), + gasless: true, + }); + const account = await abstractSmartWallet.connect({ + client: TEST_CLIENT, + personalAccount, + }); + const tx = await sendTransaction({ + transaction: prepareTransaction({ + chain: defineChain(4654), + client: TEST_CLIENT, + to: account.address, + value: BigInt(0), + data: "0x", + }), + account: account, + }); + console.log(tx.transactionHash); + expect(tx.transactionHash.length).toBe(66); + }); }, );