如何在循环中运行async异步函数 解决web3批量mintNFT报错问题
错误原因
你遇到的replacement transaction underpriced错误核心是循环并行触发交易导致nonce重复:
- 你写的同步循环会同时执行所有
mintNFT函数,几乎同一时间调用web3.eth.getTransactionCount拿到的都是同一个最新nonce,导致多笔交易用了相同nonce提交 - 链上节点收到相同nonce的多笔交易时,会判定为你要替换已进入内存池的同nonce交易,要求后提交的交易gas价格比之前的至少高10%,你这里gas参数写死没有提价,所以直接抛出错误。
解决方案
方案1:异步串行执行(简单稳妥,适合中小批量)
改造mintNFT返回Promise,循环中等待上一笔交易提交完成再发下一笔,自己维护nonce避免重复查链:
// 改造后的mintNFT,支持传入预计算的nonce,返回Promise async function mintNFT(tokenURI, nonce) { const tx = { from: PUBLIC_KEY, to: contractAddress, nonce: nonce, gas: 500000, data: nftContract.methods.mintNFT(PUBLIC_KEY, tokenURI).encodeABI(), }; const signedTx = await web3.eth.accounts.signTransaction(tx, PRIVATE_KEY); return new Promise((resolve, reject) => { web3.eth.sendSignedTransaction(signedTx.rawTransaction, (err, hash) => { if (err) { console.log(`交易提交失败,tokenURI:${tokenURI},错误信息:`, err); reject(err); } else { console.log(`交易提交成功,哈希:${hash},tokenURI:${tokenURI}`); resolve(hash); } }) }) } // 批量串行mint逻辑 async function batchMint(total) { // 仅查一次初始nonce let currentNonce = await web3.eth.getTransactionCount(PUBLIC_KEY, "latest"); for (let i = 0; i < total; i++) { try { await mintNFT(`https://ipfs.io/ipfs/cid/${i}`, currentNonce); currentNonce++; // 可加短延迟避免节点限流,不需要可以删掉 await new Promise(resolve => setTimeout(resolve, 50)); } catch (e) { // 交易失败后重试当前笔,重新同步nonce避免错乱 console.log(`第${i}笔mint失败,正在重试`); i--; currentNonce = await web3.eth.getTransactionCount(PUBLIC_KEY, "pending"); } } console.log("所有交易提交完成"); } // 调用批量方法,传入要mint的总数 batchMint(10000);
方案2:预计算nonce并行执行(效率更高,适合大批量)
如果觉得串行速度慢,可以提前一次性分配所有交易的nonce,再并行提交:
async function batchMintParallel(total) { const startNonce = await web3.eth.getTransactionCount(PUBLIC_KEY, "latest"); const mintTasks = []; for (let i = 0; i < total; i++) { mintTasks.push(mintNFT(`https://ipfs.io/ipfs/cid/${i}`, startNonce + i)); } // 等待所有交易完成,不管成功失败都会返回结果 await Promise.allSettled(mintTasks); console.log("所有批次交易处理完成"); }
注意事项
- 大批量mint建议不要写死gas参数,可通过
web3.eth.getGasPrice()动态获取当前链上gas价格,避免拥堵时交易失败 - 如果遇到nonce持续错乱的情况,可调用
web3.eth.getTransactionCount(PUBLIC_KEY, "pending")获取包含内存池交易的最新nonce重新同步
内容的提问来源于stack exchange,提问作者satad227
相关产品推荐
相关产品推荐

