diff --git a/.changeset/smooth-walls-glow.md b/.changeset/smooth-walls-glow.md new file mode 100644 index 00000000000..4597e44571e --- /dev/null +++ b/.changeset/smooth-walls-glow.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Ensure smart accounts are deployed before validating signatures diff --git a/packages/thirdweb/src/wallets/smart/index.ts b/packages/thirdweb/src/wallets/smart/index.ts index 27c95b84a06..66dc28a58b4 100644 --- a/packages/thirdweb/src/wallets/smart/index.ts +++ b/packages/thirdweb/src/wallets/smart/index.ts @@ -256,6 +256,11 @@ async function createSmartAccount( account, accountContract, }); + // the bundler and rpc might not be in sync, so while the bundler has a transaction hash for the deployment, + // the rpc might not have it yet, so we wait until the rpc confirms the contract is deployed + await confirmContractDeployment({ + accountContract, + }); } const originalMsgHash = hashMessage(message); @@ -344,6 +349,11 @@ async function createSmartAccount( account, accountContract, }); + // the bundler and rpc might not be in sync, so while the bundler has a transaction hash for the deployment, + // the rpc might not have it yet, so we wait until the rpc confirms the contract is deployed + await confirmContractDeployment({ + accountContract, + }); } const originalMsgHash = hashTypedData(typedData); @@ -607,3 +617,24 @@ async function _sendUserOp(args: { transactionHash: receipt.transactionHash, }; } + +async function confirmContractDeployment(args: { + accountContract: ThirdwebContract; +}) { + const { accountContract } = args; + const startTime = Date.now(); + const timeout = 60000; // wait 1 minute max + const { isContractDeployed } = await import( + "../../utils/bytecode/is-contract-deployed.js" + ); + let isDeployed = await isContractDeployed(accountContract); + while (!isDeployed) { + if (Date.now() - startTime > timeout) { + throw new Error( + "Timeout: Smart account deployment not confirmed after 1 minute", + ); + } + await new Promise((resolve) => setTimeout(resolve, 500)); + isDeployed = await isContractDeployed(accountContract); + } +} diff --git a/packages/thirdweb/src/wallets/smart/smart-wallet-dev.test.ts b/packages/thirdweb/src/wallets/smart/smart-wallet-dev.test.ts index f4616b3f1d4..b6e03672e2b 100644 --- a/packages/thirdweb/src/wallets/smart/smart-wallet-dev.test.ts +++ b/packages/thirdweb/src/wallets/smart/smart-wallet-dev.test.ts @@ -2,7 +2,6 @@ import { beforeAll, describe, expect, it } from "vitest"; import { TEST_CLIENT } from "../../../test/src/test-clients.js"; import { arbitrumSepolia } from "../../chains/chain-definitions/arbitrum-sepolia.js"; import { type ThirdwebContract, getContract } from "../../contract/contract.js"; - import { balanceOf } from "../../extensions/erc1155/__generated__/IERC1155/read/balanceOf.js"; import { claimTo } from "../../extensions/erc1155/drops/write/claimTo.js"; import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js"; @@ -62,6 +61,12 @@ describe.runIf(process.env.TW_SECRET_KEY).skip.sequential( expect(smartWalletAddress).toHaveLength(42); }); + it("can sign a msg", async () => { + await smartAccount.signMessage({ message: "hello world" }); + const isDeployed = await isContractDeployed(accountContract); + expect(isDeployed).toEqual(true); + }); + it("can execute a tx", async () => { const tx = await sendAndConfirmTransaction({ transaction: claimTo({