使用Web3.js和私钥发送ERC-20代币时EVM回滚错误排查
问题分析与修复方案
你的代码存在多个核心错误,导致EVM回滚交易,以下是具体问题和修正方案:
核心错误点
- 交易目标地址错误:ERC20代币转账需要调用代币合约的
transfer方法,而非直接给接收方地址转ETH,因此to字段应填ERC20代币合约地址,不是接收人地址。 - Data字段完全误用:你直接将ABI数组赋值给
data,但data需要是编码后的合约方法调用数据,且当前ABI缺少transfer方法定义,无法编码转账逻辑。 - Value字段错误:
value字段用于转ETH,ERC20代币转账不需要转ETH,必须设为0,你当前填入的代币金额会导致尝试转ETH,大概率因余额不足或逻辑错误被回滚。 - 缺失
transfer方法ABI:你的最小ABI仅包含balanceOf,必须添加ERC20标准的transfer方法ABI才能编码转账调用。
修正后的代码
1. 更新最小ABI(添加transfer方法)
// 包含balanceOf和transfer的ERC20最小ABI const minABI = [ { "constant": true, "inputs": [{"name": "_owner", "type": "address"}], "name": "balanceOf", "outputs": [{"name": "balance", "type": "uint256"}], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ {"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"} ], "name": "transfer", "outputs": [{"name": "", "type": "bool"}], "payable": false, "stateMutability": "nonpayable", "type": "function" } ];
2. 修正sendToken函数
async function sendToken(tokenHolder, walletSecret, tokenContractAddress, toAddress, amount, tokenDecimals = 18) { const nonce = await web3.eth.getTransactionCount(tokenHolder); const gasPrice = await web3.eth.getGasPrice(); const gasLimit = 200000; // 编码transfer方法的调用数据 const contract = new web3.eth.Contract(minABI, tokenContractAddress); // 处理代币小数位数:如果是18位直接用toWei,其他位数需对应调整 const transferAmount = tokenDecimals === 18 ? web3.utils.toWei(amount, 'ether') : web3.utils.toBN(amount * Math.pow(10, tokenDecimals)).toString(); const data = contract.methods.transfer(toAddress, transferAmount).encodeABI(); const params = { from: tokenHolder, to: tokenContractAddress, // 目标地址改为代币合约地址 nonce: web3.utils.toHex(nonce), data: data, // 填入编码后的转账调用数据 value: web3.utils.toHex(0), // ERC20转账无需转ETH,设为0 gasPrice: web3.utils.toHex(gasPrice), gasLimit: web3.utils.toHex(gasLimit), }; const signedTx = await web3.eth.accounts.signTransaction(params, walletSecret); try { web3.eth.sendSignedTransaction(signedTx.rawTransaction) .on('transactionHash', (hash) => console.log("txHash", hash)) .on('receipt', (receipt) => console.log("receipt", receipt)) .on('confirmation', (confirmationNumber, receipt) => console.log("confirmationNumber", confirmationNumber, "receipt", receipt)) .on('error', console.error); } catch(error) { console.log(error); } }
3. 修正调用方式(新增代币合约地址参数)
sendToken( "0x0000_SENDER_PUBLIC_KEY", "0x0000_SENDER_SECRET_KEY", "0x0000_TOKEN_CONTRACT_ADDRESS", // 必填:ERC20代币合约地址 "0x0000_RECIPIENT_PUBLIC_KEY", "0.005", 18 // 可选:代币小数位数,默认18(如USDT需改为6) );
额外注意事项
- 确认发送方的ERC20代币余额足够覆盖转账金额
- 若代币合约有特殊逻辑(如转账手续费),需调整
gasLimit或预留足够余额 - 代币小数位数必须与实际合约一致,否则金额计算错误
内容的提问来源于stack exchange,提问作者Antonio Da Silva
相关产品推荐
相关产品推荐

