Search code examples
node.jsethereumweb3jserc20

Sending ERC-20 tokens fails. Transaction succeeds, but no tokens are sent


When I run the following code, it accurately gets the token balance for both addresses and the transaction even goes through (I can see it on the testnet), although no tokens are sent.

I've tried a variety of things including replacing the signed transaction piece with this:

await contract.methods.transfer(toAddress, 100000).send({
  from: fromAddress
});

but that fails with an unknown account error.

My Code for sending tokens:

const Web3 = require("web3");
const { hdkey } = require("ethereumjs-wallet");
const bip39 = require("bip39");
const token = require("./token.json");

const mnemonic = "12 word phrase";
const provider = "https://apis.ankr.com/.../binance/full/test";

(async() => {
    try {
        const seed = await bip39.mnemonicToSeed(mnemonic);
        const root = hdkey.fromMasterSeed(seed);
        const web3 = new Web3(provider);
        const addrNode = root.derivePath(`m/44'/60'/0'/0/0`);
        const wallet = addrNode.getWallet();
        // #0 in the hdwallet, the owner of the tokens
        const fromAddress = wallet.getAddressString();
        // #1 in the hdwallet, already has a token balance
        const toAddress = "0x...";

        const contract = new web3.eth.Contract(token.abi, token.contract_address);

        let fromAddressBalance = await contract.methods.balanceOf(fromAddress).call();
        let toAddressBalance = await contract.methods.balanceOf(toAddress).call();

        console.log(`Pre Transaction: Sender: ${fromAddressBalance} TOKENS / Wallet: ${toAddressBalance} TOKENS`);

        // token has 3 decimal places, this is 100.000
        const encodedABI = contract.methods.transfer(toAddress, 100000).encodeABI();
        const tx = {
            from: fromAddress,
            to: toAddress,
            gas: 2000000,
            value: 0x0,
            data: encodedABI
          }; 
        const signed = await web3.eth.accounts.signTransaction(tx, wallet.privateKey.toString("hex"));
        const trans = await web3.eth.sendSignedTransaction(signed.rawTransaction);

        fromAddressBalance = await contract.methods.balanceOf(fromAddress).call();
        toAddressBalance = await contract.methods.balanceOf(toAddress).call();
    
        console.log(`Post Transaction: Sender: ${fromAddressBalance} TOKENS / Wallet: ${toAddressBalance} TOKENS`);
    } catch (err) {
        console.error(err.stack);
    }
    process.exit();
})();

Solution

  • There were a few things wrong that once fixed resolved my issue. I haven't gone back through and tested which did it, or if all were required, but wanted to leave this for future explorers.

    • I was creating a wallet with ethereum-js-wallet, and then using it with web3. You have to let web3 know about the wallet.
            const account = web3.eth.accounts.privateKeyToAccount(privateKey);
            web3.eth.accounts.wallet.create();
            web3.eth.accounts.wallet.add(account);
    
    const privateKey = `0x${wallet.privateKey.toString("hex")}`;
    
    • This ended up not mattering, and not being the correct way to transfer, but it's still a good note that I was sending a signed transaction to a 3rd party, NOT to the contract, the correct way to send the transaction would have been
            const tx = {
                from: fromAddress,
                to: token.contract_address,
                gas: 2000000,
                value: 0x0,
                data: encodedABI
              }; 
    

    Ultimately, I needed to take advantage of the contract's transfer method vs signing/sending a transaction

            const result = await contract.methods.transfer(toAddress, 100).send({
                from: fromAddress,
                gas: 2000000
            });