From e2d5c95916a61fa68e6d7395388f5dd493fa659e Mon Sep 17 00:00:00 2001 From: Will Sewell Date: Wed, 6 May 2026 15:37:06 +0100 Subject: [PATCH] fix(examples): skip simulateContract for bid to avoid stale RPC allowance error The bid flow approves the ERC20, waits for the receipt, then runs simulateContract (eth_call at block "latest") before writing the bid. On load-balanced RPCs like sepolia.base.org, the eth_call can be served by a node that hasn't yet seen the approval block, returning "transfer amount exceeds allowance" and forcing the user to click Commit a second time. The on-chain execution always sees the confirmed approval, so calling writeContractAsync directly is safe. Co-Authored-By: Claude Opus 4.7 --- .../nextjs-evm/src/app/hooks/use-sale-contract.ts | 8 +++++--- examples/framework/react-evm/src/hooks.ts | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/framework/nextjs-evm/src/app/hooks/use-sale-contract.ts b/examples/framework/nextjs-evm/src/app/hooks/use-sale-contract.ts index 9275892..64b4522 100644 --- a/examples/framework/nextjs-evm/src/app/hooks/use-sale-contract.ts +++ b/examples/framework/nextjs-evm/src/app/hooks/use-sale-contract.ts @@ -74,16 +74,18 @@ export const useSaleContract = (saleSpecificEntityID: Hex) => { purchasePermitResp.Signature, ] as const; + // Skip simulateContract for the bid: it runs an eth_call against block "latest", + // and load-balanced RPCs (e.g. sepolia.base.org) may route to a node that hasn't + // yet seen the approval block, causing a spurious "transfer amount exceeds allowance" + // revert. The on-chain execution sees the confirmed approval and succeeds. // TODO could also show an example of using the replaceBidWithPermit function instead of the replaceBidWithApproval function - const { request: bidRequest } = await simulateContract(config, { + const bidHash = await writeContractAsync({ address: saleContract, abi: settlementSaleAbi, functionName: "replaceBidWithApproval", args: bidArgs, }); - const bidHash = await writeContractAsync(bidRequest); - setTxHash(bidHash); }, [writeContractAsync, config, chainId, switchChainAsync], diff --git a/examples/framework/react-evm/src/hooks.ts b/examples/framework/react-evm/src/hooks.ts index 78e0c48..bf51b2f 100644 --- a/examples/framework/react-evm/src/hooks.ts +++ b/examples/framework/react-evm/src/hooks.ts @@ -74,16 +74,18 @@ export const useSaleContract = (saleSpecificEntityID: Hex) => { purchasePermitResp.Signature, ] as const; + // Skip simulateContract for the bid: it runs an eth_call against block "latest", + // and load-balanced RPCs (e.g. sepolia.base.org) may route to a node that hasn't + // yet seen the approval block, causing a spurious "transfer amount exceeds allowance" + // revert. The on-chain execution sees the confirmed approval and succeeds. // TODO could also show an example of using the replaceBidWithPermit function instead of the replaceBidWithApproval function - const { request: bidRequest } = await simulateContract(config, { + const bidHash = await writeContractAsync({ address: saleContract, abi: settlementSaleAbi, functionName: "replaceBidWithApproval", args: bidArgs, }); - const bidHash = await writeContractAsync(bidRequest); - setTxHash(bidHash); }, [writeContractAsync, config, chainId, switchChainAsync],