Logo Coinbase

Send ERC20 tokens using Web3 library with Query & Transact

March 4, 2022

How to manually sign and send a transaction by interacting with an ERC20 token’s smart contract, using Query & Transact and the Web3 library

by Vijith Bheemireddi

One of the various methods to interface with the Ethereum blockchain is using the Web3 JavaScript library. Web3.js is a set of libraries that let you use HTTP, IPC, or WebSocket to communicate with a local or remote Ethereum node.

This tutorial will show you how to manually sign and send a transaction by interacting with the ERC20 token’s smart contract, using an Ethereum Query & Transact node and the Web3 library. We are going to use Ethereum’s Ropsten testnet network in this guide so that you can follow along with testnet funds, which can be requested via the Ropsten Testnet Faucet. We'll utilize Node.js and some simple JavaScript code in this example. Because the Web3 library is so large (see here), we'll only go through the techniques we'll need for this lesson. 

Prerequisites:

Before we begin, if you are new to Ethereum and do not have a solid understanding of wallets, accounts, and transactions, I strongly recommend giving this a read. Please engage in this process at your own risk, and do your own research. The steps in this post use testnet funds to help those learning explore the process with no value at stake. Do not use mainnet funds for learning. 

Additionally, we need to make sure we have all the necessary libraries, an Ethereum Account address, and an ERC20 token contract address (smart contract token) to begin our activity.

Nodejs: https://nodejs.org/en/download/

Install Web3 package:

npm install web3

Coinbase Cloud Ethereum Query & Transact Node on the Bison Trails platform: https://app.bisontrails.co/qt

Ropsten Ethereum testnet token faucet, to use for gas fees: https://faucet.dimensions.network/

ERC20 token:You can either use an existing ERC20 token that you have in your wallet, or generate one using a smart contract, or create one following the directions in this link

I will be using DEMO Token (Contract address: 0x4134aA5373ACAfc36337bF515713a943927B06e5) in this example.

Anatomy of an Ethereum transaction

Now that you have all the prerequisites ready, let's begin coding.

For this tutorial, we will only need a single javascript file to send an ERC20 token from your wallet account to another Ethereum Ropsten wallet account. 

We need to set the following variables:

  1. Create your Web3 constructor (Web3)

  2. Create your web3 instance: Here we use a Coinbase Cloud Query and Transact Node on the Bison Trails platform, with its Access Keys for private access.

  3. privateKey: This is the private key for your Ethereum Ropsten wallet. Store it as Environment variable $YOUR_PRIVATE_KEY

  4. Set TokenAddress, fromAddress, and toAddresses: Here we interact with the smart contract of the ERC20 token which is the TokenAddress itself. We will transfer the ERC20 token from the fromAddress to the toAddress.

  5. contractABI: ABI stands for Application Binary Interface. The interface has become the standard mechanism for encoding and decoding data into and out of machine (Binary) code. It's used to encode Solidity contract calls for the EVM in Ethereum, as well as how to read data out of transactions backwards. In our ABI, for simplicity we are only using the transfer method, but you can use the standard ERC20 ABIs which offer other methods (such as BalanceOf, totalSupply, etc.) pertaining to your use case. 

  6. Create your contract object to interact with smart contracts on the blockchain: When you create a new contract object you give it the json interface of the respective smart contract, and web3 will auto-convert all calls into low-level ABI calls over RPC for you.

  7. Amount: The number of ERC20 tokens needs to be given in Wei, but we can use the Web3 toWei utility to convert units. I am sending 1 DEMO Token unit, so I convert the unit to WEI and then to HEX. 

  8. Transaction data: The data part of the transaction  invokes the smart contract’s method (Transfer). We send this as part of data in our transaction. 

const Web3 = require('web3')

const Web3js = new Web3(new Web3.providers.HttpProvider("https://{username}:{password}@bisontrailsURL"))

const privateKey = process.env.YOUR_PRIVATE_KEY //Your Private key environment variable

let tokenAddress = '0x4134aa5373acafc36337bf515713a943927b06e5' // Demo Token contract address

let toAddress = ''// where to send it

let fromAddress = ''// Your address

let contractABI = [

   {

       'constant': false,

       'inputs': [

           {

               'name': '_to',

               'type': 'address'

           },

           {

               'name': '_value',

               'type': 'uint256'

           }

       ],

       'name': 'transfer',

       'outputs': [

           {

               'name': '',

               'type': 'bool'

           }

       ],

   }

]

let contract = new Web3js.eth.Contract(contractABI, tokenAddress, { from: fromAddress })

let amount = Web3js.utils.toHex(Web3js.utils.toWei("1")); //1 DEMO Token

let data = contract.methods.transfer(toAddress, amount).encodeABI()

Now, we create the transaction. I am putting this under sendErcToken() function. To create the transaction, we need to invoke a sign transaction method:

 web3.eth.accounts.signTransaction(tx, privateKey)

Here, we have to define the transaction object with parameters such as: toAddress, fromAddress, number of tokens to send, and the gas limit.

Note that since we’re interacting with smart contracts, the toAddress will be the tokenAddress, and we will need to have the smart contract execute the method to transfer our tokens to the destination wallet address. Additionally, your wallet’s private key should be provided, as we sign the transaction with your private key in the wallet where the ERC20 tokens are stored.

function sendErcToken() {

   let txObj = {

       gas: Web3js.utils.toHex(100000),

       "to": tokenAddress,

       "value": "0x00",

       "data": data,

       "from": fromAddress

   }

   Web3js.eth.accounts.signTransaction(txObj, privateKey, (err, signedTx) => {

       if (err) {

           return callback(err)

       } else {

           console.log(signedTx)

           return Web3js.eth.sendSignedTransaction(signedTx.rawTransaction, (err, res) => {

               if (err) {

                   console.log(err)

               } else {

                   console.log(res)

               }

           })

       }

   })

}

So our complete erc20transfer.js file will look like this:

const Web3 = require('web3')

const Web3js = new Web3(new Web3.providers.HttpProvider("https://{username}:{password}@bisontrailsURL"))

const privateKey = process.env.YOUR_PRIVATE_KEY //Your Private key environment variable

let tokenAddress = '0x4134aa5373acafc36337bf515713a943927b06e5' // Demo Token contract address

let toAddress = '' // where to send it

let fromAddress = '' // your wallet

let contractABI = [

   // transfer

   {

       'constant': false,

       'inputs': [

           {

               'name': '_to',

               'type': 'address'

           },

           {

               'name': '_value',

               'type': 'uint256'

           }

       ],

       'name': 'transfer',

       'outputs': [

           {

               'name': '',

               'type': 'bool'

           }

       ],

       'type': 'function'

   }

]

let contract = new Web3js.eth.Contract(contractABI, tokenAddress, { from: fromAddress })

let amount = Web3js.utils.toHex(Web3js.utils.toWei("1")); //1 DEMO Token

let data = contract.methods.transfer(toAddress, amount).encodeABI()

sendErcToken()

function sendErcToken() {

   let txObj = {

       gas: Web3js.utils.toHex(100000),

       "to": tokenAddress,

       "value": "0x00",

       "data": data,

       "from": fromAddress

   }

   Web3js.eth.accounts.signTransaction(txObj, privateKey, (err, signedTx) => {

       if (err) {

           return callback(err)

       } else {

           console.log(signedTx)

           return Web3js.eth.sendSignedTransaction(signedTx.rawTransaction, (err, res) => {

               if (err) {

                   console.log(err)

               } else {

                   console.log(res)

               }

           })

       }

   })

}

Your result will contain the messageHash, the v-r-s values (the values for the transaction’s signature), and the transaction hash, which you can check on Ropsten Etherscan.