You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js Web3实现BSC链BEP-20代币转账无确认问题求助

BEP-20代币转账交易已获取哈希但无法确认问题排查

问题描述

我之前通过Node.js的Web3库在BSC(币安智能链)上实现BEP-20代币转账功能正常,代码清理后出现异常:提交交易后能获取到交易哈希(例如:0xd18ee37686b00d45edb9974d8a119534f3403052e3ed4908ac8ca80bde385416),但交易始终无法得到确认,该问题已困扰两天。(注:节点地址和私钥为示例)

相关代码

// Ethereum javascript libraries needed
var Web3 = require('web3')
var Tx = require("ethereumjs-tx").Transaction;

const Common = require('ethereumjs-common');
const mysql = require('mysql');

//KLUV Contract Address KLUV - BSC
const _ContractAddress_ = '0x3A68A9Cd188C324a45c06866eFD1C79605B66827';
const _ContractABI_ = JSON.parse('[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getunlockedAfter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lockedPeriodOver","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawLockedTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]');

//To Wallet Address
const _ToWalletAddress_ = '0x76D52CF51D9a4B8C84Bf01b7AA3f801c52A328D8';

//My MetaMask Wallet
const _FromWalletAddress_ = '0xDeBCBca7f21aB70DA4e7860a112FEB3d8Fd04db5';

//My MetaMask Private Key
const _FromWalletPrivateKey_ = 'f777980edfc1c92446XXXXXXXXXXXXXXXe615ffcccc0744f0a4054c8f924773d';

//Node URL
const BSCNode = 'https://nd-098-532-742.p2pify.com/super_secret_address';

const web3 = new Web3(new Web3.providers.HttpProvider(BSCNode));

const main = async () => {

    console.log(`web3 version: ${web3.version}`)

    // Who holds the token now?
    var senderWalletAddress = _FromWalletAddress_;

    // Who are we trying to send this token to?
    var receiverWalletAddress = _ToWalletAddress_;

    // The address of the contract which created MFIL
    var contractAddress = _ContractAddress_;
    var contract = new web3.eth.Contract(_ContractABI_, contractAddress, {
        from: senderWalletAddress
    });

    // How many tokens do I have before sending?
    var balance = await contract.methods.balanceOf(senderWalletAddress).call();
    console.log(`Balance before send: ${balance} sKLUV\n------------------------`);

    // Use Gwei for the unit of gas price
    var gasPriceGwei = 5;
    var gasLimit = 5000000;

    // .00000001 KLUV
    var transferAmount = 1;

    // Determine the nonce - This isn't always good, you need to keep track of it
    var count = await web3.eth.getTransactionCount(senderWalletAddress);
    console.log(`num transactions so far: ${count}`);

    /* Override count - especially if testing */
    count = 77;

    var rawTransaction = {
        "from": senderWalletAddress,
        "nonce": "0x" + count.toString(16),
        "gasPrice": web3.utils.toHex(gasPriceGwei * 1e9),
        "gasLimit": web3.utils.toHex(gasLimit),
        "to": contractAddress,
        "value": "0x0",
        "chainId": 56,
        "data": contract.methods.transfer(receiverWalletAddress, transferAmount).encodeABI(),
    };

    console.log(`Raw of Transaction: \n${JSON.stringify(rawTransaction, null, '\t')}\n------------------------`);

    // Create Buffer containing hex of private key
    var privKey = new Buffer.from(_FromWalletPrivateKey_, 'hex');

    /* Define What Block Chain we're using */
    const chain = Common.default.forCustomChain(
        'mainnet', {
            name: 'bnb',
            networkId: 56,
            chainId: 56
        },
        'petersburg'
    )

    /* generate transaction including my block chain */
    var tx = new Tx(rawTransaction, {common: chain});

    /* sign the transaction with my buffered private key */
    tx.sign(privKey);

    /* Serialize the result */
    var serializedTx = tx.serialize();

    /* Get Ready to send */
    console.log(`Attempting to send signed tx:  ${serializedTx.toString('hex')}\n------------------------`);

    /* Limit the number of confirms we're looking for */
    let numberOfConfirms = 5;
    web3.eth.transactionConfirmationBlocks = numberOfConfirms;

    /* Use Event Method so we can see where things are hanging up */

    web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'))
        .on("sending", (payload) => {
            console.log('sending ');
            console.log(payload);
        })
        .on("sent", (payload) => {
            console.log('Sent Payload');
            console.log(payload);
        })
        .on('transactionHash', (transactionHash) => {
            console.log('Received Transaction hash');
            console.log(transactionHash);
        })
        .on("receipt", (receipt) => {
            console.log('Received Receipt');
            console.log(`Receipt info: \n${JSON.stringify(receipt, null, '\t')}\n------------------------`);
        })
        .on("confirmation", (confNumber, receipt, latestBlockHash) => {
            console.log('Received Confirmation ', confNumber);
            if (confNumber == numberOfConfirms) {
                // The balance may not be updated yet, but let's check
                contract.methods.balanceOf(senderWalletAddress).call().then((balance) => {
                    console.log(`Balance after send: ${balance} sKLUV`);
                });

            }
            console.log(confNumber, receipt, latestBlockHash);
        })
        .on('error', err => {
            console.log('Error Sending Transaction');
            console.log(err.message);
        }).catch(reason => {
        console.log('Promise failed in catch');
        console.log(reason);
    });

};

main();

问题排查与解决建议

1. 移除硬编码的Nonce

代码中手动将count设置为77是核心问题:

  • 若该nonce已被使用,交易会直接失败;
  • 若比钱包当前真实nonce值大,交易会一直处于pending状态,直到前面的nonce对应的交易被处理。

解决方法:删除count = 77;这行代码,使用钱包的真实交易计数,建议加上'pending'参数以包含未确认的交易:

var count = await web3.eth.getTransactionCount(senderWalletAddress, 'pending');

2. 优化Gas参数配置

  • Gas Limit过高:硬编码的5000000远大于BEP-20转账所需(通常21000-50000足够),建议通过合约方法估算实际需要的gas:
    var gasLimit = await contract.methods.transfer(receiverWalletAddress, transferAmount).estimateGas({from: senderWalletAddress});
    
  • Gas Price过低:BSC主网当前基础gasPrice可能高于5Gwei,过低的gasPrice会导致矿工优先打包出价更高的交易,你的交易会一直pending。建议使用网络建议的gasPrice或手动提高:
    // 获取网络建议gasPrice
    var gasPriceGwei = await web3.eth.getGasPrice();
    // 或手动设置为10Gwei
    // var gasPriceGwei = web3.utils.toWei('10', 'gwei');
    

3. 修正链配置

代码中自定义链时使用了'petersburg',BSC主网对应的是'istanbul',更简单可靠的方式是直接使用预定义的BSC链配置:

const chain = Common.default.forChain('bnb');

4. 验证交易状态

拿到交易哈希后,去BSC区块浏览器查询交易详情,查看交易是处于pending还是失败状态,以及失败原因(如私钥错误、BNB余额不足、合约调用失败等),这能快速定位问题。


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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 10:15:40