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.

Tomas Navickas
  • 641
  • 1
  • 5
  • 10
  • Well looking at the web3 documentation you can try contract.methods. transfer("0xCb...", 10).send({from : XX},function(){dostuff})instead of sending sendRawTransaction. – Adrien Forbu Aug 21 '17 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 '17 at 23:51
  • 1
    This answer explain how to use getData https://ethereum.stackexchange.com/a/12932 – Ismael Aug 21 '17 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 '17 at 14:11
  • @TomasNavickas This works, but it's sending wei (ether) instead of the actual token. Any idea why? – Viper Oct 29 '17 at 04:33
  • @Viper Wei (ether) is used to pay transaction fee. Can you provide some transaction example that we could see what input data you are sending? – Tomas Navickas Oct 29 '17 at 18:06
  • @TomasNavickas I've added it as a new question here, thanks! https://ethereum.stackexchange.com/questions/29513/transfer-erc20-token-using-ropsten-infutra-with-web3 – Viper Oct 29 '17 at 18:37
  • What does the Buffer do in this case? Why is the var privKey not just assigned as a string containing the private key? – John Murphy Jul 20 '18 at 15:02

6 Answers6

25

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);
});
Tomas Navickas
  • 641
  • 1
  • 5
  • 10
15

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.

dacoinminster
  • 251
  • 2
  • 4
  • 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 '17 at 16:32
  • Or is it possible to use a 'standard ERC20 token ABI' as this module suggests is possible? https://github.com/danfinlay/human-standard-token-abi – djskinner Dec 13 '17 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. – Dennis Estenson Dec 13 '17 at 19:05
  • I'm getting this error: Error: Returned error: invalid sender – Nishchit Dec 24 '17 at 14:39
  • @dacoinminster, if I send ether then it works but if I'm sending the token then giving an error Error: Returned error: invalid sender

    Am I doing anything wrong?

    – Nishchit Dec 24 '17 at 14:51
  • For testnet (ropsnet) use chainId: hex(3) – Nishchit Dec 25 '17 at 11:22
  • can you use a random number for the nonce? – zero_cool Apr 06 '18 at 07:05
  • 1
    @zero_cool no, it has to be larger than the previous nonce. That's why we call getTransactionCount. – Paul Razvan Berg Jun 01 '18 at 09:33
  • I get an empty receipt info and my tokens not get transferred. I got the error "Transaction was not mined within750 seconds, please make sure your transaction was properly sent. Be aware that it might still be mined!" after 15 mins what to do? – Vasanth Kumar Jul 16 '18 at 07:40
  • What does the Buffer do in this case? Why is the var privKey not just assigned as a string containing the private key? – John Murphy Jul 20 '18 at 14:42
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

mingmingzi
  • 41
  • 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 '22 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);
  });
});
Evan H.
  • 171
  • 1
  • 4
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)) }

Dominic
  • 117
  • 6