description: Tutorial on how to query an ERC20 token smart contract with Go.

Querying an ERC20 Token Smart Contract

First create an ERC20 smart contract interface. This is just a contract with the function definitions of the functions that you can call.

  1. pragma solidity ^0.4.24;
  2. contract ERC20 {
  3. string public constant name = "";
  4. string public constant symbol = "";
  5. uint8 public constant decimals = 0;
  6. function totalSupply() public constant returns (uint);
  7. function balanceOf(address tokenOwner) public constant returns (uint balance);
  8. function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
  9. function transfer(address to, uint tokens) public returns (bool success);
  10. function approve(address spender, uint tokens) public returns (bool success);
  11. function transferFrom(address from, address to, uint tokens) public returns (bool success);
  12. event Transfer(address indexed from, address indexed to, uint tokens);
  13. event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
  14. }

Then compile the smart contract to the JSON ABI, and create a Go token package out of the ABI using abigen.

  1. solc --abi erc20.sol
  2. abigen --abi=erc20_sol_ERC20.abi --pkg=token --out=erc20.go

Assuming we already have Ethereum client set up as usual, we can now import the new token package into our application and instantiate it. In this example we’ll be using the Golem token.

  1. tokenAddress := common.HexToAddress("0xa74476443119A942dE498590Fe1f2454d7D4aC0d")
  2. instance, err := token.NewToken(tokenAddress, client)
  3. if err != nil {
  4. log.Fatal(err)
  5. }

We may now call any ERC20 method that we like. For example, we can query the token balance of a user.

  1. address := common.HexToAddress("0x0536806df512d6cdde913cf95c9886f65b1d3462")
  2. bal, err := instance.BalanceOf(&bind.CallOpts{}, address)
  3. if err != nil {
  4. log.Fatal(err)
  5. }
  6. fmt.Printf("wei: %s\n", bal) // "wei: 74605500647408739782407023"

We can also read the public variables of the ERC20 smart contract.

  1. name, err := instance.Name(&bind.CallOpts{})
  2. if err != nil {
  3. log.Fatal(err)
  4. }
  5. symbol, err := instance.Symbol(&bind.CallOpts{})
  6. if err != nil {
  7. log.Fatal(err)
  8. }
  9. decimals, err := instance.Decimals(&bind.CallOpts{})
  10. if err != nil {
  11. log.Fatal(err)
  12. }
  13. fmt.Printf("name: %s\n", name) // "name: Golem Network"
  14. fmt.Printf("symbol: %s\n", symbol) // "symbol: GNT"
  15. fmt.Printf("decimals: %v\n", decimals) // "decimals: 18"

We can do some simple math to convert the balance into a human readable decimal format.

  1. fbal := new(big.Float)
  2. fbal.SetString(bal.String())
  3. value := new(big.Float).Quo(fbal, big.NewFloat(math.Pow10(int(decimals))))
  4. fmt.Printf("balance: %f", value) // "balance: 74605500.647409"

See the same information on etherscan: https://etherscan.io/token/0xa74476443119a942de498590fe1f2454d7d4ac0d?a=0x0536806df512d6cdde913cf95c9886f65b1d3462

Full code

Commands

  1. solc --abi erc20.sol
  2. abigen --abi=erc20_sol_ERC20.abi --pkg=token --out=erc20.go

erc20.sol

  1. pragma solidity ^0.4.24;
  2. contract ERC20 {
  3. string public constant name = "";
  4. string public constant symbol = "";
  5. uint8 public constant decimals = 0;
  6. function totalSupply() public constant returns (uint);
  7. function balanceOf(address tokenOwner) public constant returns (uint balance);
  8. function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
  9. function transfer(address to, uint tokens) public returns (bool success);
  10. function approve(address spender, uint tokens) public returns (bool success);
  11. function transferFrom(address from, address to, uint tokens) public returns (bool success);
  12. event Transfer(address indexed from, address indexed to, uint tokens);
  13. event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
  14. }

contract_read_erc20.go

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "math"
  6. "math/big"
  7. "github.com/ethereum/go-ethereum/accounts/abi/bind"
  8. "github.com/ethereum/go-ethereum/common"
  9. "github.com/ethereum/go-ethereum/ethclient"
  10. token "./contracts_erc20" // for demo
  11. )
  12. func main() {
  13. client, err := ethclient.Dial("https://mainnet.infura.io")
  14. if err != nil {
  15. log.Fatal(err)
  16. }
  17. // Golem (GNT) Address
  18. tokenAddress := common.HexToAddress("0xa74476443119A942dE498590Fe1f2454d7D4aC0d")
  19. instance, err := token.NewToken(tokenAddress, client)
  20. if err != nil {
  21. log.Fatal(err)
  22. }
  23. address := common.HexToAddress("0x0536806df512d6cdde913cf95c9886f65b1d3462")
  24. bal, err := instance.BalanceOf(&bind.CallOpts{}, address)
  25. if err != nil {
  26. log.Fatal(err)
  27. }
  28. name, err := instance.Name(&bind.CallOpts{})
  29. if err != nil {
  30. log.Fatal(err)
  31. }
  32. symbol, err := instance.Symbol(&bind.CallOpts{})
  33. if err != nil {
  34. log.Fatal(err)
  35. }
  36. decimals, err := instance.Decimals(&bind.CallOpts{})
  37. if err != nil {
  38. log.Fatal(err)
  39. }
  40. fmt.Printf("name: %s\n", name) // "name: Golem Network"
  41. fmt.Printf("symbol: %s\n", symbol) // "symbol: GNT"
  42. fmt.Printf("decimals: %v\n", decimals) // "decimals: 18"
  43. fmt.Printf("wei: %s\n", bal) // "wei: 74605500647408739782407023"
  44. fbal := new(big.Float)
  45. fbal.SetString(bal.String())
  46. value := new(big.Float).Quo(fbal, big.NewFloat(math.Pow10(int(decimals))))
  47. fmt.Printf("balance: %f", value) // "balance: 74605500.647409"
  48. }

solc version used for these examples

  1. $ solc --version
  2. 0.4.24+commit.e67f0147.Emscripten.clang