You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何使用web3.js循环执行多笔代币转账交易?

解决web3.js循环发送交易时的「Transaction known」/「duplicate transaction」错误

这个问题我之前帮不少开发者排查过,核心就是nonce没有正确同步——当你在循环里快速发送多笔交易时,前一笔还处于pending(未挖矿确认)状态,此时如果直接用默认的web3.eth.getTransactionCount()获取nonce,拿到的可能是已确认交易的计数,而不是包含pending交易的最新值,导致多笔交易使用了同一个nonce,触发节点的重复交易检测。

下面给你两种最实用的解决方案,根据你的业务场景选就行:

方案1:等待上一笔交易确认后再发送下一笔(最安全)

这种方式是每发送完一笔交易,等待它被区块链确认(至少1个区块),再获取最新的nonce发送下一笔。优点是完全不会出现nonce重复问题,缺点是速度慢,适合交易数量不多、对安全性要求高的场景。

代码示例

const Web3 = require('web3');
const web3 = new Web3('你的节点提供商URL,比如Infura/Alchemy');

async function sendBatchTransactions(privateKey, toAddresses, tokenAmount) {
  const senderAccount = web3.eth.accounts.privateKeyToAccount(privateKey);
  // 初始获取包含pending交易的nonce
  let currentNonce = await web3.eth.getTransactionCount(senderAccount.address, 'pending');

  for (const toAddr of toAddresses) {
    // 构建交易对象(这里以ETH转账为例,ERC20代币需要调整data和gasLimit)
    const txConfig = {
      to: toAddr,
      value: web3.utils.toWei(tokenAmount, 'ether'),
      gasLimit: 21000, // ERC20转账建议设为200000左右
      gasPrice: await web3.eth.getGasPrice(),
      nonce: currentNonce
    };

    // 签名交易
    const signedTx = await senderAccount.signTransaction(txConfig);
    // 发送并等待交易确认
    const txReceipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);

    console.log(`交易 ${txReceipt.transactionHash} 已确认,区块号:${txReceipt.blockNumber}`);
    // 更新nonce为已确认交易的nonce+1,或者重新获取pending状态的nonce更保险
    currentNonce = await web3.eth.getTransactionCount(senderAccount.address, 'pending');
  }
}

方案2:手动维护nonce值(速度更快)

如果你需要快速批量发送交易,不想等每笔确认,可以手动维护nonce:先获取当前账户包含pending交易的最新nonce,然后每发送一笔就手动递增nonce,不需要等上一笔确认。但要注意处理交易失败的情况——如果某笔交易被revert,后续的交易因为nonce连续,会被节点卡住,需要重试或重置nonce。

代码示例

const Web3 = require('web3');
const web3 = new Web3('你的节点提供商URL');

async function sendBatchTransactionsFast(privateKey, toAddresses, tokenAmount) {
  const senderAccount = web3.eth.accounts.privateKeyToAccount(privateKey);
  // 初始获取pending状态的nonce
  let currentNonce = await web3.eth.getTransactionCount(senderAccount.address, 'pending');
  const transactionPromises = [];

  for (const toAddr of toAddresses) {
    const txConfig = {
      to: toAddr,
      value: web3.utils.toWei(tokenAmount, 'ether'),
      gasLimit: 21000,
      gasPrice: await web3.eth.getGasPrice(),
      nonce: currentNonce++ // 手动递增nonce
    };

    const signedTx = await senderAccount.signTransaction(txConfig);
    // 发送交易并监听状态
    const txPromise = web3.eth.sendSignedTransaction(signedTx.rawTransaction)
      .on('transactionHash', hash => console.log(`已发送交易:${hash}`))
      .on('error', err => {
        console.error(`交易失败:${err.message}`);
        // 这里可以添加重试逻辑:比如重新发送该nonce的交易,或者记录下来后续处理
        // 注意:如果是nonce重复错误,说明之前的交易已经被节点接收,不需要重试
        if (!err.message.includes('Transaction known') && !err.message.includes('duplicate')) {
          // 其他错误,比如gas不足,可以重新签名发送同一nonce的交易
        }
      });

    transactionPromises.push(txPromise);
  }

  // 等待所有交易完成
  await Promise.all(transactionPromises);
}

关键注意事项

  • 获取nonce必须加'pending'参数web3.eth.getTransactionCount(address, 'pending')会返回包含已发送但未确认交易的nonce计数,这是避免重复的核心。
  • ERC20转账要调整交易参数:如果是代币转账,需要在txConfig里添加data字段(编码transfer方法),并且把gasLimit设为足够高的值(比如200000),否则会因为gas不足导致交易失败。
  • 处理交易失败后的nonce重置:如果用方案2,一旦某笔交易失败且不是重复错误,需要重新获取最新的pending nonce,避免后续交易卡住。

内容的提问来源于stack exchange,提问作者Harsh

火山引擎 最新活动