How To Create Token and Initial Coin Offering Contracts Using Truffle + Zeppelin Solidity

Update: this blog post has been updated for OpenZeppelin Solidity version v2.0. I have tested this blog post with the following dependencies:

- node@10.11.0
- truffle@v4.1.14
- ganache-cli@v6.1.8
- zeppelin-solidity@2.0.0

Token contracts are hot. Token crowd sales aka Initial Coin Offers(ICO) are hotter. There have been a lot going in terms of ICOs lately in the crypto world and if you would like to code one yourself, look no further. In this blog post I am going to go through the steps to create your own and only token as well as the ICO contract for it. We will also use the help of some clever tools.

Caveat: You should not actually release your ICO with the code found here. It is for demo purposes only. But it is a good start.

For this tutorial I am going to be using Truffle and Zeppelin Solidity. Truffle is the de facto framework for creating smart contracts and decentralized applications. Zeppelin Solidity is a library that has extensive and well tested smart contracts that adhere to security best practices. In the smart contract world where a small bug can cost you money, it is good not to reinvent the wheel when there are trusted solutions out there.

We will also use ganache-cli as the blockchain node because it is fast and developer friendly.

Side note: If you want to actually create the contract on the Ethereum testnet or main net you would have to use something like Geth or Parity.

Moving on: provided that you have npm and node installed, from terminal type the commands:

$ npm install -g ganache-cli
$ npm install -g truffle
$ mkdir my-ico && cd my-ico
$ truffle init
$ npm install openzeppelin-solidity@2.0.0

A lot of magic happened with those commands above. But in essence it is the setup that will get you up and running with creating and deploying your ICO smart contract.

This adds the openzeppelin-solidity folder to node_modules and in it you will find all smart contract templates from the OpenZeppelin Solidity library.

First step is to create the token contract.

touch contracts/GustavoCoin.sol

And for the code:

pragma solidity 0.4.24;

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

contract GustavoCoin is ERC20Mintable {
  string public name = "GUSTAVO COIN";
  string public symbol = "GUS";
  uint8 public decimals = 18;
}

That is a straightforward token contract. We practically let Zeppelin Solidity smart contract templates do all the heavy duty for us. Here we reference MintableToken . The idea is to have a token where the supply is controlled by an owner who can emit tokens and assign them. For a better look at what this contract does, check it at node_modules/zeppelin-solidity/contracts/token/ERC20/MintableToken.sol .

The following step is to create the Crowdsale contract.

touch contracts/GustavoCoinCrowdsale.sol

We are basically going to inherit the contract at node_modules/zeppelin-solidity/contracts/crowdsale/emission/MintedCrowdsale.sol and node_modules/zeppelin-solidity/contracts/crowdsale/validation/TimedCrowdsale.sol. We end up with only the code below.

pragma solidity 0.4.24;
import './GustavoCoin.sol';
import 'openzeppelin-solidity/contracts/crowdsale/emission/MintedCrowdsale.sol';
import 'openzeppelin-solidity/contracts/crowdsale/validation/TimedCrowdsale.sol';

contract GustavoCoinCrowdsale is TimedCrowdsale, MintedCrowdsale {
  constructor
    (
      uint256 _openingTime,
      uint256 _closingTime,
      uint256 _rate,
      address _wallet,
      ERC20Mintable _token
    )
    public
    Crowdsale(_rate, _wallet, _token)
    TimedCrowdsale(_openingTime, _closingTime) {
    }
}

Beautiful, huh? We are using the secure contracts provided by Zeppelin Solidity to our advantage. Note that GustavoCoinCrowdsale inherits from TimedCrowdsale and MintedCrowdsale. In order to deploy GustavoCoinCrowdsale , we must give a few parameters to its constructor function as per the Crowdsale and TimeCrowdsale contracts, i.e. openingTime and closingTime timestamps, the rate of token per ether rate, the token address itself and the wallet address of the contract owner(s).

Let’s deploy this contract. Open a new terminal tab and run

$ ganache-cli

It will run ganache-cli. We are using it for our development needs.

First, let’s add this to truffle.js file. It is for configuration purposes:

truffle.js:
module.exports = {
    networks: {
        development: {
            host: "localhost",
            port: 8545,
            network_id: "*" // Match any network id
        }
    }
};

Then, go on (you may need to create it) to the file migrations/2_deploy_contracts.js and modify it to this:

const GustavoCoinCrowdsale = artifacts.require('./GustavoCoinCrowdsale.sol');
const GustavoCoin = artifacts.require('./GustavoCoin.sol');

module.exports = function(deployer, network, accounts) {
    const openingTime = web3.eth.getBlock('latest').timestamp + 2; // two secs in the future
    const closingTime = openingTime + 86400 * 20; // 20 days
    const rate = new web3.BigNumber(1000);
    const wallet = accounts[1];

    return deployer
        .then(() => {
            return deployer.deploy(GustavoCoin);
        })
        .then(() => {
            return deployer.deploy(
                GustavoCoinCrowdsale,
                openingTime,
                closingTime,
                rate,
                wallet,
                GustavoCoin.address
            );
        });
};

Note that we are not deploying GustavoCoin . This is because once GustavoCoinCrowdsale deploys it will create GustavoCoin . Now back to the terminal tab where you installed Truffle, run the commands:

$ truffle compile
$ truffle migrate

If you happen to look at the ganache-cli tab you will see that the contract was deployed successfully.

Alright, let’s buy some GUS tokens.

Run

$ truffle console

This will open truffle console and we are going to use the web3.js API to interact with the deployed contract.

// The account that will buy GUS tokens. It will show differently in your ganache-cli console
> purchaser = web3.eth.accounts[2]
'0xddac5d057c79facd674bc95dfd9104076fd34d6b'

// The address of the GUS token instance that was created when the crowdsale contract was deployed
// assign the result of GustavoCoinCrowdsale.deployed() to the variable crowdsale
> GustavoCoinCrowdsale.deployed().then(inst => { crowdsale = inst })
undefined

> crowdsale.token().then(addr => { tokenAddress = addr } )
undefined

> tokenAddress
'0x87a784686ef69304ac0cb1fcb845e03c82f4ce16'

> gustavoCoinInstance = GustavoCoin.at(tokenAddress)
...

// add minter role to crowdsale so it is able to mint tokens during crowdsale
> gustavoCoinInstance.addMinter(crowdsale.address)
...

// now check the number of GUS tokens purchaser has. It should be 0

> gustavoCoinInstance.balanceOf(purchaser).then(balance => balance.toString(10))
'0'

// Buying GUS tokens

> GustavoCoinCrowdsale.deployed().then(inst => inst.sendTransaction({ from: purchaser, value: web3.toWei(5, "ether")}))
{ tx: '0x68aa48e1f0d0248835378caa1e5b2051be35a5ff1ded82878683e6072c0a0cfc',
receipt:
{ transactionHash: '0x68aa48e1f0d0248835378caa1e5b2051be35a5ff1ded82878683e6072c0a0cfc',
transactionIndex: 0,
blockHash: '0xb48ceed99cf6ddd4f081a99474113c4c16ecf61f76625a6559f1686698ee7d57',
   blockNumber: 5,
   gasUsed: 68738,
   cumulativeGasUsed: 68738,
   contractAddress: null,
   logs: [] },
  logs: [] }
undefined

// Check the amount of GUS tokens for purchaser again. It should have some now.

> gustavoCoinInstance.balanceOf(purchaser).then(balance => purchaserGusTokenBalance = balance.toString(10))
'5000000000000000000000'

// When we created our token we made it with 18 decimals, which the same as what ether has. That's a lot of zeros, let's display without the decimals:

> web3.fromWei(purchaserGusTokenBalance, "ether")
'5000'

Yay! It worked.

Where to go after here you ask?

You could test this code on the Ethereum testnet, or you could create a web app that would allow users to interact with the CrowdsaleToken contract, you could add more functionality to the contract itself such as deadline, token cap, etc. Unleash your inner token creator.

The Ethereum community grows by the day and though there are not many tools out there yet, the ones such as Truffle and Zeppelin Solidity do a great job at improving developer’s experience.

I am curious to know what you will be coding from here.

By the way, here is the repo containing the code for this blog post.

I want to leave my gratitude for Francisco Giordano for reviewing the code used in this blog post.