Send ERC20 tokens with Javascript and ethers.js

Learn ERC20 tokens and ABI

Updated on April 18, 2020 at 11:50 am by Mehmet Egemen Albayrak

ERC20 is a standard for Ethereum contracts to create tokens living on the Ethereum chain. Each ERC20 token is a smart contract consist of some functions defined in its ABI. I don’t know if you are familiar with the Ethereum terminology so I’ll explain these alien words to you briefly.

ABI

ABI, Application Binary Interface is a pretty common concept in Computer Science and its applications. Usually referred to in OS development, an ABI is a binary interface that provides functions that you can call. In Ethereum an ABI is a description of what a smart contract can store and do. Calling these functions from ABI is done by send bytecodes to Ethereum Virtual Machine. Then the Ethereum Virtual machine, acting as a worldwide computer, will store and manipulate the data smart contract is referring to. An example and most common ABI for ERC20 tokens is:

[
    {
        "constant": true,
        "inputs": [],
        "name": "name",
        "outputs": [
            {
                "name": "",
                "type": "string"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_spender",
                "type": "address"
            },
            {
                "name": "_value",
                "type": "uint256"
            }
        ],
        "name": "approve",
        "outputs": [
            {
                "name": "",
                "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": "_value",
                "type": "uint256"
            }
        ],
        "name": "transferFrom",
        "outputs": [
            {
                "name": "",
                "type": "bool"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "decimals",
        "outputs": [
            {
                "name": "",
                "type": "uint8"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [
            {
                "name": "_owner",
                "type": "address"
            }
        ],
        "name": "balanceOf",
        "outputs": [
            {
                "name": "balance",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "symbol",
        "outputs": [
            {
                "name": "",
                "type": "string"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_to",
                "type": "address"
            },
            {
                "name": "_value",
                "type": "uint256"
            }
        ],
        "name": "transfer",
        "outputs": [
            {
                "name": "",
                "type": "bool"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [
            {
                "name": "_owner",
                "type": "address"
            },
            {
                "name": "_spender",
                "type": "address"
            }
        ],
        "name": "allowance",
        "outputs": [
            {
                "name": "",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "payable": true,
        "stateMutability": "payable",
        "type": "fallback"
    },
    {
        "anonymous": false,
        "inputs": [
            {
                "indexed": true,
                "name": "owner",
                "type": "address"
            },
            {
                "indexed": true,
                "name": "spender",
                "type": "address"
            },
            {
                "indexed": false,
                "name": "value",
                "type": "uint256"
            }
        ],
        "name": "Approval",
        "type": "event"
    },
    {
        "anonymous": false,
        "inputs": [
            {
                "indexed": true,
                "name": "from",
                "type": "address"
            },
            {
                "indexed": true,
                "name": "to",
                "type": "address"
            },
            {
                "indexed": false,
                "name": "value",
                "type": "uint256"
            }
        ],
        "name": "Transfer",
        "type": "event"
    }
]

Please check all events and functions to know what an ERC20 smart contract can do. We will use this ABI in JSON format for sending and learning the balance of tokens. You can use it for many other things, like listening to any balance change of an account. I believe the best way to learn is practicing so let’s dive into the ethers.js.

Setup Ethers.js

First, we need to install the library from npm. We can do it by running this code in console while in the project directory. For earlier npm versions you should run npm install ethers --save and for the latest npm install ethers will suffice for installing the library and saving it to your package.json as a dependency.

Second, we need to require ethers in our code, type the ABI, select the provider, connect to contract and send TRYB. A TRYB is a BiLira co. ERC20 token, of the place I work. You can use any contract address for a different token.

const ethers = require('ethers')

const TRYB_CONTRACT_ADDRESS = '0x3196638becfba39ac0c7e5094ebf915203e46c6f'
const TRYB_SENDER_PRIVATE_KEY = 'write_here_sender_wallet_private_key'

// rinkeby is a test network
const provider = ethers.getDefaultProvider('rinkeby')
const trybSenderWallet = new ethers.Wallet(TRYB_SENDER_PRIVATE_KEY, provider)

// We used the ABI we saw from the beginning of the article
const abi = `[
    {
        "constant": true,
        "inputs": [],
        "name": "name",
        "outputs": [
            {
                "name": "",
                "type": "string"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_spender",
                "type": "address"
            },
            {
                "name": "_value",
                "type": "uint256"
            }
        ],
        "name": "approve",
        "outputs": [
            {
                "name": "",
                "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": "_value",
                "type": "uint256"
            }
        ],
        "name": "transferFrom",
        "outputs": [
            {
                "name": "",
                "type": "bool"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "decimals",
        "outputs": [
            {
                "name": "",
                "type": "uint8"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [
            {
                "name": "_owner",
                "type": "address"
            }
        ],
        "name": "balanceOf",
        "outputs": [
            {
                "name": "balance",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "symbol",
        "outputs": [
            {
                "name": "",
                "type": "string"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_to",
                "type": "address"
            },
            {
                "name": "_value",
                "type": "uint256"
            }
        ],
        "name": "transfer",
        "outputs": [
            {
                "name": "",
                "type": "bool"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [
            {
                "name": "_owner",
                "type": "address"
            },
            {
                "name": "_spender",
                "type": "address"
            }
        ],
        "name": "allowance",
        "outputs": [
            {
                "name": "",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "payable": true,
        "stateMutability": "payable",
        "type": "fallback"
    },
    {
        "anonymous": false,
        "inputs": [
            {
                "indexed": true,
                "name": "owner",
                "type": "address"
            },
            {
                "indexed": true,
                "name": "spender",
                "type": "address"
            },
            {
                "indexed": false,
                "name": "value",
                "type": "uint256"
            }
        ],
        "name": "Approval",
        "type": "event"
    },
    {
        "anonymous": false,
        "inputs": [
            {
                "indexed": true,
                "name": "from",
                "type": "address"
            },
            {
                "indexed": true,
                "name": "to",
                "type": "address"
            },
            {
                "indexed": false,
                "name": "value",
                "type": "uint256"
            }
        ],
        "name": "Transfer",
        "type": "event"
    }
]`

const contract = new ethers.Contract(TRYB_CONTRACT_ADDRESS, abi, trybSenderWallet);

const receiverWallet = 'write_address_of_the_wallet_which_receive_ERC20_token'

// We send 0.01 TRYB
const howMuchTokens = ethers.utils.parseUnits('0.01', 6)
async function init() {
    await contract.transfer(receiverWallet, howMuchTokens) 
    console.log(`Sent ${ethers.utils.formatUnits(howMuchTokens, 6)} TRYB to
        address ${receiverWallet}
        `)
}
init()

Fancy devs are using async IIFEs for executing async code at the end. My habit is as you see above till top-level await is accepted as a proposal.

We need the private key for sending tokens as you can guess or you know. parseUnits function turns an ether to wei, increasing orders of magnitude. In our case we said to the computer that turn our ether to wei for a 6 decimal point ERC20 token as it’s written in token’s smart contract, ethers.js requires this to proceed. We sent tokens to the receiver wallet from the sender wallet by executing the contract. Then we logged the operation to the console for information purposes. You need await before contract.transfer() function otherwise it won’t work.

Did you read the ABI as I said before? If so, you will recongnize that transfer function as in contract.transfer() is from token’s ABI! And look at the parameters, they are as described in the ABI! So basically you can use ABI functions in that format with ethers.js as they are described in the ABI. You will see this better with our next example.

Check ERC20 token balance in a wallet

How do we know that the receiver wallet received the ERC20 tokens? We check a wallet’s balance. As you read in the ABI(hmm?) there is also a “balanceOf” function that allows you to check the balance of a wallet. Let’s check our receiver wallet’s balance:

const ethers = require('ethers')

const TRYB_RECEIVER_PKEY = 'the_receiver_wallet_private_key'
const TRYB_CONTRACT_ADDRESS = '0x3196638becfba39ac0c7e5094ebf915203e46c6f'

const provider = ethers.getDefaultProvider('rinkeby')

const getTRYBBalance = async (wallet) => {
  const abi = [
    {
      name: 'balanceOf',
      type: 'function',
      inputs: [
        {
          name: '_owner',
          type: 'address',
        },
      ],
      outputs: [
        {
          name: 'balance',
          type: 'uint256',
        },
      ],
      constant: true,
      payable: false,
    },
  ];
  const contract = new ethers.Contract(TRYB_CONTRACT_ADDRESS, abi, wallet);
  const balance = await contract.balanceOf(wallet.address)
  return balance
}

async function init() {
  const receiverWallet = new ethers.Wallet(TRYB_RECEIVER_PKEY, provider)
  const receiverTRYBBalance = await getTRYBBalance(receiverWallet)
  console.log('Reciever balance: ', ethers.utils.formatUnits(receiverTRYBBalance, 6) + ' TRYB', receiverWallet.address)  
}
init()

As you can see here in the “abi” variable, you don’t have to define all ABI. You can just use some part of it according to your needs. our “getTRYBBalance” function calls the “balanceOf(walletAddress)” function from the ABI. The usage can be derived from the ABI if you follow the inputs. There is a very important thing in this ABI. The “constant: true” part. If you don’t do that, you contract will want gas from you because it will think it’s a mutating function. By doing “constant: true” you tell to Ethereum Virtual Machine that you are doing a read-only operation so gas is not needed.

We used “rinkeby” testnet for our code. For operating on the ETH mainnet you should use const provider = ethers.getDefaultProvider() instead of const provider = ethers.getDefaultProvider('rinkeby').

That’s it! You learned how to send and check balance of an ERC20 token. You can apply this article to thousands of ERC20 tokens. I am planning to write more about blockchain, finance and machine learning of them in the future so you can subscribe if you would like to. See ya!

 

 

blockchainJavascript abierc20ethereumethers

Leave a Reply

Your email address will not be published. Required fields are marked *

Great! You want to be a part of our community.  With the form below, you can subscribe to quality content.