Manage ERC20 tokens in Java with Web3j

In this article, we learn how to manage ERC20 (Fungible) tokens in Java with the Web3j library.

ERC20 is an Ethereum Smart Contract standard for implementing tokens in a compliant way, so it is easy to interact and integrate tokens with any application (dapps, wallets, exchanges, etc.).

For more details about ERC20, see the article ERC-20 Token Standard.

Prerequisites

To follow this tutorial, you need the following software installed on your computer:

Start ganache by running the command:

ganache-cli

Contract deployment

Before starting, we need an ERC20 token contract deployed on the Ethereum blockchain (Ganache in our case). There are many ways to do this (Read Develop your ERC-20 Tokens explained and OpenZeppelin Part 3: Token Standards). For the sake of this tutorial, we use the simplest solution using OpenZeppelin reusable contracts, Truffle and Ganache.

1. First create a project folder for our ERC20 called JVM and initialize a Truffle project

$ mkdir JVM
$ cd JVM
$ truffle init

2. Then we install the Open-Zeppelin Solidity library which contains a lot of high-quality, fully tested and audited reusable smart contracts

Import the OpenZeppelin smart contracts using npm packages.

$ npm init -y
$ npm install openzeppelin-solidity --save-exact

3. Create a contract file ./contracts/JavaToken.sol

The smart contract inherits all the functionality and rules from the reusable OpenZeppelin contract ERC20Mintable. We only need to configure the following constant variables as below:

// JavaToken.sol
pragma solidity ^0.5.8;

import "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol";

contract JavaToken is ERC20Mintable {
 string public constant name = "Java Token";
 string public constant symbol = "JVM";
 uint8 public constant decimals = 18;
}

4. Deploy the smart contract on our local Ganache network

We need to first complete the migration script. Create a file called ./migrations/2_deploy_contract.js, and add the code below to the file:

// 2_deploy_contract.js
const JavaToken = artifacts.require("./JavaToken.sol");

module.exports = function(deployer, network, accounts) {
 // Deploy the smart contract
 deployer.deploy(JavaToken, {from: accounts[0]}).then(function(instance) {
 // Mint 100 tokens
 return instance.mint(accounts[0], web3.utils.toBN("100"), {from: accounts[0]});
 });
};

The migration script deploys the contract, mints and distributes 100 JVM tokens to the deployer account (the Ganache first account).

The next step is to configure a connection to the Ganache network in order to deploy a smart contract. Change the file ./truffle-config.jsto the code below:

// truffle-config.js
module.exports = {
  networks: {
    development: {
      host: "localhost",
      port: 8545,
      network_id: "*"
    }
  }
};

To deploy the smart contracts on the Ganache network, run the command below (do not forget to start ganache-cli beforehand):

$ truffle migrate --network development

Compiling your contracts...
===========================
> Compiling ./contracts/JavaToken.sol
> Compiling ./contracts/Migrations.sol
> Compiling openzeppelin-solidity/contracts/access/Roles.sol
> Compiling openzeppelin-solidity/contracts/access/roles/MinterRole.sol
> Compiling openzeppelin-solidity/contracts/math/SafeMath.sol
> Compiling openzeppelin-solidity/contracts/token/ERC20/ERC20.sol
> Compiling openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol
> Compiling openzeppelin-solidity/contracts/token/ERC20/IERC20.sol
> Artifacts written to /home/gjeanmart/workspace/tutorials/kauri-content/java-erc20/JVM/build/contracts
> Compiled successfully using:
 - solc: 0.5.8+commit.23d335f2.Emscripten.clang


Starting migrations...
======================
> Network name: 'development'
> Network id: 1565778588423
> Block gas limit: 0x6691b7


1_initial_migration.js
======================

 Deploying 'Migrations'
 ----------------------
 > transaction hash: 0x6161e15461a5c748a532b2191600986b8798be4842e78791238e9e77af5e1310
 > Blocks: 0 Seconds: 0
 > contract address: 0xC59fC6035859Dd35871c5d9211a0713ed608d59D
 > block number: 6
 > block timestamp: 1565778631
 > account: 0xB0C24796Fc9212AB371c8Caf1ea3F33cC1d4c8a0
 > balance: 99.95438236
 > gas used: 261393
 > gas price: 20 gwei
 > value sent: 0 ETH
 > total cost: 0.00522786 ETH


 > Saving migration to chain.
 > Saving artifacts
 -------------------------------------
 > Total cost: 0.00522786 ETH


2_deploy_contract.js
====================

 Deploying 'JavaToken'
 ---------------------
 > transaction hash: 0xfc5b214cbd56ecd4d24795aacefe1fdaf27b1b223ebff78299fd47f302cb374c
 > Blocks: 0 Seconds: 0
 > contract address: 0x9339a071cB9C1E3BbBA50E5E298f234c5095f012
 > block number: 8
 > block timestamp: 1565778631
 > account: 0xB0C24796Fc9212AB371c8Caf1ea3F33cC1d4c8a0
 > balance: 99.92109908
 > gas used: 1622141
 > gas price: 20 gwei
 > value sent: 0 ETH
 > total cost: 0.03244282 ETH


 > Saving migration to chain.
 > Saving artifacts
 -------------------------------------
 > Total cost: 0.03244282 ETH


Summary
=======
> Total deployments: 2
> Final cost: 0.03767068 ETH

Note the contract address after the command completes.

If you wish to learn more about this step, I recommend reading the following articles about Truffle and Ganache for deploying smart contracts: Truffle: Smart Contract Compilation & Deployment and Truffle 101 - Development Tools for Smart Contracts

Load the contract with Web3j

Now we have deployed an ERC20 smart contract on our Ganache local blockchain, and we can start interacting with it in Java using the Web3j ERC20 utility class.

1. Create a new project and import the Web3j dependency

Create a new Maven project in your favorite IDE and import Web3j dependencies (core and contracts for EIP support):

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>io.kauri.tutorials.java-ethereum</groupId>
  <artifactId>java-erc20</artifactId>
  <version>0.0.1-SNAPSHOT</version>

  <properties>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.compiler.source>1.8</maven.compiler.source>
    <web3j.version>4.2.0</web3j.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.web3j</groupId>
      <artifactId>core</artifactId>
      <version>${web3j.version}</version>
    </dependency>
    <dependency>
      <groupId>org.web3j</groupId>
      <artifactId>contracts</artifactId>
      <version>${web3j.version}</version>
    </dependency>
  </dependencies>

</project>

2. Create a new class to load an ERC20 Smart Contract wrapper

ERC20 is a standard, so there is no need to manually generate a Smart Contract wrapper, Web3j includes it by default.

However, you do need to connect Web3j to an Ethereum blockchain and configure a wallet to sign transactions.

In this article, we connect to a local Ganache blockchain (exposed by default on http://localhost:8545) and use the first Ganache test account (read the Ganache startup logs to find this information) which received 100 JVM tokens during deployment.

// Connect Web3j to the Blockchain
String rpcEndpoint = "http://localhost:8545";
Web3j web3j = Web3j.build(new HttpService(rpcEndpoint));

// Prepare a wallet
String pk = "0x5bbbef76458bf30511c9ee6ed56783644eb339258d02656755c68098c4809130";
// Account address: 0x1583c05d6304b6651a7d9d723a5c32830f53a12f
Credentials credentials = Credentials.create(pk);

// Load the contract
String contractAddress = "0xe4F275cE131eF87Cb8bF425E02D9215055e9F875";
ERC20 javaToken = ERC20.load(contractAddress, web3j, credentials, new DefaultGasProvider());

For more complex scenarios, you can load the contract with a specific TransactionManager and Gas parameters using ERC20.load(contractAddress, web3j, transactionManager, gasPrice, gasLimit)

3. Get token information

Once we have loaded our ERC20 token contract, we can start requesting information stored on this contract such as the number of decimal or the balance of an account in JVM tokens.

The following code retrieves the information we configured in our contract earlier.

String symbol = javaToken.symbol().send();
String name = javaToken.name().send();
BigInteger decimal = javaToken.decimals().send();

System.out.println("symbol: " + symbol);
System.out.println("name: " + name);
System.out.println("decimal: " + decimal.intValueExact());

More importantly, we can retrieve the token balance of an account.

BigInteger balance1 = javaToken.balanceOf("0x1583c05d6304b6651a7d9d723a5c32830f53a12f").send();
System.out.println("balance (0x1583c05d6304b6651a7d9d723a5c32830f53a12f)="+balance1.toString());

BigInteger balance2 = javaToken.balanceOf("0x0db6b797e64666d4b36b13e5dc6fcd4661893ac8").send();
System.out.println("balance (0x0db6b797e64666d4b36b13e5dc6fcd4661893ac8)="+balance2.toString());

The account 0x1583c05d6304b6651a7d9d723a5c32830f53a12f is Ganache's first account, the one that deployed the contract and received 100 tokens during deployment. While 0x0db6b797e64666d4b36b13e5dc6fcd4661893ac8 represents Ganache's second account that didn't receive any token.

Transfer tokens

To interact with the token, the ERC20 class offers all the functionalities needed like transfer, transferFrom and approve.

For example, we can transfer 25 JVM tokens to 0x0db6b797e64666d4b36b13e5dc6fcd4661893ac8 by signing and sending a transaction from our account configured as Credentials (0x1583c05d6304b6651a7d9d723a5c32830f53a12f).

TransactionReceipt receipt = javaToken.transfer("0x0db6b797e64666d4b36b13e5dc6fcd4661893ac8", new BigInteger("25")).send();
System.out.println("Transaction hash: "+receipt.getTransactionHash());

BigInteger balance1 = javaToken.balanceOf("0x1583c05d6304b6651a7d9d723a5c32830f53a12f").send();
System.out.println("balance (0x1583c05d6304b6651a7d9d723a5c32830f53a12f)="+balance1.toString());

BigInteger balance2 = javaToken.balanceOf("0x0db6b797e64666d4b36b13e5dc6fcd4661893ac8").send();
System.out.println("balance (0x0db6b797e64666d4b36b13e5dc6fcd4661893ac8)="+balance2.toString());

Get notified of transfer events

Finally, we cover how to subscribe to specific events generated by the ERC20 contract so we can react to any activity from it.

You can retrieve specific events for a given transaction via the method getTransferEvents:

List<ERC20.TransferEventResponse> events = javaToken.getTransferEvents(receipt);
events.forEach(event
 -> System.out.println("from: " + event._from + ", to: " + event._to + ", value: " + event._value));

We can also add RxJava as a dependency to subscribe continuously to any new events via transferEventFlowable.

javaToken.transferEventFlowable(DefaultBlockParameterName.EARLIEST, DefaultBlockParameterName.LATEST)
 .subscribe(event
 -> System.out.println("from: " + event._from + ", to: " + event._to + ", value: " + event._value));

For more information about subscribing to events with Web3j, I highly recommend reading this article: Interacting with an Ethereum Smart Contract in Java