29

I created some custom token in Ropsten testnet using this guide: https://steemit.com/ethereum/@maxnachamkin/how-to-create-your-own-ethereum-token-in-an-hour-erc20-verified

I can send it to other accounts using MetaMask, but can't figure out how to do it in node.js using web3, ethereumjs-tx and Web3 JavaScript app API.

My code at the moment looks like this:

var count = web3.eth.getTransactionCount("0x26...");
var abiArray = JSON.parse(fs.readFileSync('mycoin.json', 'utf-8'));
var contractAddress = "0x8...";
var contract = web3.eth.contract(abiArray).at(contractAddress);
var rawTransaction = {
    "from": "0x26...",
    "nonce": web3.toHex(count),
    "gasPrice": "0x04e3b29200",
    "gasLimit": "0x7458",
    "to": contractAddress,
    "value": "0x0",
    "data": contract.transfer("0xCb...", 10, {from: "0x26..."}),
    "chainId": 0x03
};

var privKey = new Buffer('fc3...', 'hex');
var tx = new Tx(rawTransaction);

tx.sign(privKey);
var serializedTx = tx.serialize();

web3.eth.sendRawTransaction('0x' + serializedTx.toString('hex'), function(err, hash) {
    if (!err)
        console.log(hash);
    else
        console.log(err);
});

In this case code just stops at contract.transfer("0xCb...", 10, {from: "0x26..."}) part and request is just pending. Couldn't find any guide for doing similar things. Found some code here:

Web3 sending custom tokens by using the transfer function. Need to set the from account

And here:

How to transfer ERC20 tokens using web3js

But still got stucked, don't know what I'm missing.

9
  • Well looking at the web3 documentation you can try contract.methods. transfer("0xCb...", 10).send({from : XX},function(){dostuff})instead of sending sendRawTransaction. Aug 21, 2017 at 14:54
  • You should use getData to generate the raw transaction data, like this "data": contract.transfer.getData("0xCb...", 10, {from: "0x26..."}),.
    – Ismael
    Aug 21, 2017 at 23:51
  • 1
    This answer explain how to use getData ethereum.stackexchange.com/a/12932
    – Ismael
    Aug 21, 2017 at 23:53
  • @TomasNavickas were you using web3.js v0.20.x? Can you kindly post a complete example as the answer and marked as solved. Thank you.
    – Trav L
    Aug 31, 2017 at 14:11
  • @TomasNavickas This works, but it's sending wei (ether) instead of the actual token. Any idea why?
    – Viper
    Oct 29, 2017 at 4:33

6 Answers 6

24

I'm using web3.js version: 0.20.1 in node.js express application. I'm running Parity in Virtualbox Ubuntu machine.

The correct code looks like following:

var count = web3.eth.getTransactionCount("0x26...");
var abiArray = JSON.parse(fs.readFileSync('mycoin.json', 'utf-8'));
var contractAddress = "0x8...";
var contract = web3.eth.contract(abiArray).at(contractAddress);
var rawTransaction = {
    "from": "0x26...",
    "nonce": web3.toHex(count),
    "gasPrice": "0x04e3b29200",
    "gasLimit": "0x7458",
    "to": contractAddress,
    "value": "0x0",
    "data": contract.transfer.getData("0xCb...", 10, {from: "0x26..."}),
    "chainId": 0x03
};

var privKey = new Buffer('fc3...', 'hex');
var tx = new Tx(rawTransaction);

tx.sign(privKey);
var serializedTx = tx.serialize();

web3.eth.sendRawTransaction('0x' + serializedTx.toString('hex'), function(err, hash) {
    if (!err)
        console.log(hash);
    else
        console.log(err);
});
3
  • Can u please share the tutorial of using web3.js for ether and token transfers Apr 12, 2018 at 7:29
  • What does the Buffer do in this case? Why is the var privKey not just assigned as a string containing the private key? Jul 20, 2018 at 15:01
  • I would imagine a generic IERC20 ABI can be used?
    – Dominic
    Jan 13, 2022 at 17:03
14

I found many answers which were out of date or were missing important information. Here's what finally worked for me in my node.js project using web3 version 1.0.0-beta.26. Note that this is for Ethereum Main Net. To use the Robsten test net, change the chainId to 0x03

// Get private stuff from my .env file
import {my_privkey, infura_api_key} from '../.env'

// Need access to my path and file system
import path from 'path'
var fs = require('fs');

// Ethereum javascript libraries needed
import Web3 from 'Web3'
var Tx = require('ethereumjs-tx');

// Rather than using a local copy of geth, interact with the ethereum blockchain via infura.io
const web3 = new Web3(Web3.givenProvider || `https://mainnet.infura.io/` + infura_api_key)

// Create an async function so I can use the "await" keyword to wait for things to finish
const main = async () => {
  // This code was written and tested using web3 version 1.0.0-beta.26
  console.log(`web3 version: ${web3.version}`)

  // Who holds the token now?
  var myAddress = "0x97...";

  // Who are we trying to send this token to?
  var destAddress = "0x4f...";

  // If your token is divisible to 8 decimal places, 42 = 0.00000042 of your token
  var transferAmount = 1;

  // Determine the nonce
  var count = await web3.eth.getTransactionCount(myAddress);
  console.log(`num transactions so far: ${count}`);

  // This file is just JSON stolen from the contract page on etherscan.io under "Contract ABI"
  var abiArray = JSON.parse(fs.readFileSync(path.resolve(__dirname, './tt3.json'), 'utf-8'));

  // This is the address of the contract which created the ERC20 token
  var contractAddress = "0xe6...";
  var contract = new web3.eth.Contract(abiArray, contractAddress, { from: myAddress });

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

  // I chose gas price and gas limit based on what ethereum wallet was recommending for a similar transaction. You may need to change the gas price!
  var rawTransaction = {
      "from": myAddress,
      "nonce": "0x" + count.toString(16),
      "gasPrice": "0x003B9ACA00",
      "gasLimit": "0x250CA",
      "to": contractAddress,
      "value": "0x0",
      "data": contract.methods.transfer(destAddress, transferAmount).encodeABI(),
      "chainId": 0x01
  };

  // Example private key (do not use): 'e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109'
  // The private key must be for myAddress
  var privKey = new Buffer(my_privkey, 'hex');
  var tx = new Tx(rawTransaction);
  tx.sign(privKey);
  var serializedTx = tx.serialize();

  // Comment out these three lines if you don't really want to send the TX right now
  console.log(`Attempting to send signed tx:  ${serializedTx.toString('hex')}`);
  var receipt = await web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'));
  console.log(`Receipt info:  ${JSON.stringify(receipt, null, '\t')}`);

  // The balance may not be updated yet, but let's check
  balance = await contract.methods.balanceOf(myAddress).call();
  console.log(`Balance after send: ${balance}`);
}

main();

Note that sometimes the send will "fail" because the tx wasn't mined within 50 blocks. In my copy of web3 source code, I changed TIMEOUTBLOCK from 50 to 500 so I wouldn't have to deal with that.

Note that this code does not have error handling - you may want to add some.

10
  • So one would need to get hold and manage the abi json for every singe ERC-20 that needs to be handled?
    – djskinner
    Dec 13, 2017 at 16:32
  • Or is it possible to use a 'standard ERC20 token ABI' as this module suggests is possible? github.com/danfinlay/human-standard-token-abi
    – djskinner
    Dec 13, 2017 at 16:44
  • To call a function on any contract, all you need is the contract's address and the signature of the function. ERC20 defines several functions. As long as the contract at the address defines a public function with the same signature as the one you're calling, you can call it. Dec 13, 2017 at 19:05
  • I'm getting this error: Error: Returned error: invalid sender
    – Nishchit
    Dec 24, 2017 at 14:39
  • 1
    @zero_cool no, it has to be larger than the previous nonce. That's why we call getTransactionCount. Jun 1, 2018 at 9:33
4

If we just want send a transaction with erc20 method transfer,we can build a Contract Object by using minABI and the contract address,such as the follow code:

let minABI = [
// transfer
{
    "constant": false,
    "inputs": [
        {
            "name": "_to",
            "type": "address"
        },
        {
            "name": "_value",
            "type": "uint256"
        }
    ],
    "name": "transfer",
    "outputs": [
        {
            "name": "success",
            "type": "bool"
        }
    ],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
}
];
let contractAddres="put the erc20 contract address here";

let contract = await new web3.eth.Contract(minABI, contractAddr);

contract.methods.transfer("your account", "amount of erc20 tokens you want transfer").send({
        from: "your account"
    });

and this is work for me, hope this will help. :D

1
  • Thanks this is better than the other answers, you don't need and usually don't have the ABI of a public ERC20 token..
    – Dominic
    Jan 13, 2022 at 17:12
2

You can check this working example with my token: https://github.com/religion-counter/onlyone/blob/main/helper-scripts/send-onlyone.js

Also you can contribute to the repo if you are interested.

// Helper script that sends ONLYONE token to target addresses specified in targets.txt
// Target index - index in targets.txt file is specified as an argument - process.argv.splice(2)[0]

var fs = require('fs')

var targetAccounts = JSON.parse(fs.readFileSync('targets.txt', 'utf-8'));

var myAddress = JSON.parse(fs.readFileSync("my-address.json", 'utf-8'));
var targetIndex = Number(process.argv.splice(2)[0]);

console.log(`Sending ONLYONE to target ${targetIndex}.`);

async function sendOnlyone(fromAddress, toAddress) {

    var Tx = require('ethereumjs-tx').Transaction;
    var Web3 = require('web3');
    var web3 = new Web3(new Web3.providers.HttpProvider('https://bsc-dataseed.binance.org/'));

    var amount = web3.utils.toHex(10);
    var privateKey = Buffer.from(myAddress.privateKey, 'hex');
    var abiArray = JSON.parse(JSON.parse(fs.readFileSync('onlyone-abi.json','utf-8')));
    var contractAddress = '0xb899db682e6d6164d885ff67c1e676141deaaa40'; // ONLYONE address
    var contract = new web3.eth.Contract(abiArray, contractAddress, {from: fromAddress});
    var Common = require('ethereumjs-common').default;
    var BSC_FORK = Common.forCustomChain(
        'mainnet',
        {
        name: 'Binance Smart Chain Mainnet',
        networkId: 56,
        chainId: 56,
        url: 'https://bsc-dataseed.binance.org/'
        },
        'istanbul',
    );

    var count = await web3.eth.getTransactionCount(myAddress);

    var rawTransaction = {
        "from":myAddress,
        "gasPrice":web3.utils.toHex(5000000000),
        "gasLimit":web3.utils.toHex(210000),
        "to":contractAddress,"value":"0x0",
        "data":contract.methods.transfer(toAddress, amount).encodeABI(),
        "nonce":web3.utils.toHex(count)
    };

    var transaction = new Tx(rawTransaction, {'common':BSC_FORK});
    transaction.sign(privateKey)

    var result = await web3.eth.sendSignedTransaction('0x' + transaction.serialize().toString('hex'));
    return result;
}

sendOnlyone(myAddress, targetAccounts[targetIndex]);

1

Came across this documentation in one of the projects I'm using in my dapp: https://developers.fortmatic.com/docs/smart-contract-functions

The documentation explains pretty thoroughly what to do step-by-step to send ERC20 token transfers for both pre-1.0 (0.20.x) and post-1.0 web3 versions.

Here's a preview of what the 0.20.x version of web3 code looks like.

// Initialize provider
import Fortmatic from 'fortmatic';
import Web3 from 'web3';

const fm = new Fortmatic('YOUR_API_KEY');
window.web3 = new Web3(fm.getProvider()); // Can replace with MetaMask web3 provider

// Get the contract ABI from compiled smart contract json
const erc20TokenContractAbi = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"tokens","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"withdrawEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"_totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"tokenOwner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"acceptOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"uint256"}],"name":"safeSub","outputs":[{"name":"c","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"uint256"}],"name":"safeDiv","outputs":[{"name":"c","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"tokens","type":"uint256"},{"name":"data","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"uint256"}],"name":"safeMul","outputs":[{"name":"c","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"newOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"tokenAddress","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transferAnyERC20Token","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"tokenOwner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"uint256"}],"name":"safeAdd","outputs":[{"name":"c","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"tokens","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tokenOwner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"tokens","type":"uint256"}],"name":"Approval","type":"event"}];

// Create contract object
const tokenContract = web3.eth.contract(erc20TokenContractAbi);
// Instantiate contract
const tokenContractInstance = tokenContract.at('0x8EBC7785b83506AaA295Bd9174e6A7Ad5681fb80');

const toAddress = '0xE0cef4417a772512E6C95cEf366403839b0D6D6D';
// Calculate contract compatible value for transfer with proper decimal points using BigNumber
const tokenDecimals = web3.toBigNumber(18);
const tokenAmountToTransfer = web3.toBigNumber(100);
const calculatedTransferValue = web3.toHex(tokenAmountToTransfer.mul(web3.toBigNumber(10).pow(tokenDecimals)));

// Call contract function (non-state altering) to get total token supply
tokenContractInstance.totalSupply.call(function(error, result) {
  if (error) throw error;
  console.log(result);
});

// Get user account wallet address first
web3.eth.getAccounts(function(error, accounts) {
  if (error) throw error;
  // Send ERC20 transaction with web3
  tokenContractInstance.transfer.sendTransaction(toAddress, calculatedTransferValue, {from: accounts[0]}, function(error, txnHash) {
    if (error) throw error;
    console.log(txnHash);
  });
});
0

This is how to do it with ethers.js. I don't know why most of the other answers are using an import of an unspecified ABI json when ERC20 follows the same interface for any token, and you're unlikely to have the ABI of a public token.

Note in this example I'm assuming the amount needs to be multiplied by a power of 6 (utils.parseUnits(n, 6)) which applies to USDT and USDC for example. Other ERC20 tokens can vary.

import { Contract, providers, utils } from 'ethers'

const erc20abi = [
  /**
   * @dev Returns the amount of tokens in existence.
   */
  'function totalSupply() external view returns (uint256)',

  /**
   * @dev Returns the amount of tokens owned by `account`.
   */
  'function balanceOf(address account) external view returns (uint256)',

  /**
   * @dev Moves `amount` tokens from the caller's account to `recipient`.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * Emits a {Transfer} event.
   */
  'function transfer(address recipient, uint256 amount) external returns (bool)',

  /**
   * @dev Returns the remaining number of tokens that `spender` will be
   * allowed to spend on behalf of `owner` through {transferFrom}. This is
   * zero by default.
   *
   * This value changes when {approve} or {transferFrom} are called.
   */
  'function allowance(address owner, address spender) external view returns (uint256)',

  /**
   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * IMPORTANT: Beware that changing an allowance with this method brings the risk
   * that someone may use both the old and the new allowance by unfortunate
   * transaction ordering. One possible solution to mitigate this race
   * condition is to first reduce the spender's allowance to 0 and set the
   * desired value afterwards:
   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
   *
   * Emits an {Approval} event.
   */
  'function approve(address spender, uint256 amount) external returns (bool)',

  /**
   * @dev Moves `amount` tokens from `sender` to `recipient` using the
   * allowance mechanism. `amount` is then deducted from the caller's
   * allowance.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * Emits a {Transfer} event.
   */
  'function transferFrom(address sender, address recipient, uint256 amount) external returns (bool)',

  /**
   * @dev Emitted when `value` tokens are moved from one account (`from`) to
   * another (`to`).
   *
   * Note that `value` may be zero.
   */
  'event Transfer(address indexed from, address indexed to, uint256 value)',

  /**
   * @dev Emitted when the allowance of a `spender` for an `owner` is set by
   * a call to {approve}. `value` is the new allowance.
   */
  'event Approval(address indexed owner, address indexed spender, uint256 value)',
]

export async function approveERC20(
  provider: providers.Web3Provider,
  tokenAddr: string,
  spenderAddr: string,
  amount: string
) {
  const signer = provider.getSigner()
  const contract = new Contract(tokenAddr, erc20abi, signer)
  contract.approve(spenderAddr, utils.parseUnits(amount, 6))
}

export async function transferERC20(
  provider: providers.Web3Provider,
  tokenAddr: string,
  recipientAddr: string,
  amount: string
) {
  const signer = provider.getSigner()
  const contract = new Contract(tokenAddr, erc20abi, signer)
  contract.transfer(recipientAddr, utils.parseUnits(amount, 6))
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.