Building DApps with Ethereum: Creating, Deploying TNS Tokens

Share this article

Building and Deploying a TNS Token

In part 1 of this tutorial series on building DApps with Ethereum, we bootstrapped two versions of a local blockchain for development: a Ganache version, and a full private PoA version.

In this part, we’ll dive right into it and build and deploy our TNS token — the token users will use to vote on proposals in the Story DAO.

Prerequisites

Have a Ganache version up and running, as per the previous part. Alternatively, have any local version of a blockchain running if you’re not following along from the first part, but make sure you can connect to it with tools we’ll need.

We’ll assume you have a working private blockchain and the ability to type commands into its console and the operating system’s terminal via the Terminal app or, on Windows, an app like Git Bash, Console, CMD Prompt, Powershell, etc.

The Basic Dependencies

To develop our application, we can use one of several frameworks and starter kits at our disposal: Dapp, eth-utils, Populus, Embark … and so on. But we’ll go with the current king of the ecosystem, Truffle.

Install it with the following:

npm install -g truffle

This will make the truffle command available everywhere. Now we can start the project with truffle init.

Starting the Token

Let’s get right into it and build our token. It’ll be a somewhat standard cookie-cutter ERC20 token with a twist. (You’ll see which twist lower in this post.) First, we’ll pull in some dependencies. The OpenZeppelin libraries are battle-tested high quality solidity contracts usable for extending and building contracts from.

npm install openzeppelin-solidity

Next, let’s create a new token file:

truffle create contract TNSToken

The default template that truffle generates here is a little out of date, so let’s get it updated:

pragma solidity ^0.4.24;

contract TNStoken {
    constructor() public {

    }
}

Up until now, the constructor of the token contract was supposed to be called the same as the contract itself, but for clarity it was changed to constructor. It should also always have a modifier telling the compiler who is allowed to deploy and interact with this contract (public meaning everyone).

SafeMath

The only Zeppelin contract we’ll be using in this case is their SafeMath contract. In Solidity, we import contracts with the import keyword, while the compiler will generally not require a full path, only a relative one, like so:

pragma solidity ^0.4.24;

import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";

contract TNStoken {
    using SafeMath for uint256;
    constructor() public {

    }
}

So, what is SafeMath? Long ago, there was an issue of 184 billion bitcoins being created because of a math problem in code. To prevent issues even remotely similar to these (not that this one in particular is possible in Ethereum), the SafeMath library exists. When two numbers are of the MAX_INT size (i.e. the maximum possible number in an operating system), summing them up would make the value “wrap around” to zero, like a car’s odometer being reset to 0 after reaching 999999 kilometers. So the SafeMath library has functions like these:

/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
  c = a + b;
  assert(c >= a);
  return c;
}

This function prevents this issue: it checks whether the sum of two numbers is still bigger than each of the two operands.

While it’s not too easy to make such silly mistakes when writing Solidity contracts, it’s still better to be safe than sorry.

By using SafeMath for uint256, we replace the standard uint256 numbers in Solidity (256bit unsigned — a.k.a. positive-only — whole numbers) with these “safe” versions. Instead of summing numbers like this: sum = someBigNumber + someBiggerNumber, we’ll be summing them like this: sum = someBigNumber.add(someBiggerNumber), thereby being safe in our calculations.

ERC20 from Scratch

With our math made safe, we can create our token.

ERC20 is a standard with a well-defined interface, so for reference, let’s add it into the contract. Read about the token standards here.

So the functions that an ERC20 token should have are:

pragma solidity ^0.4.24;

import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";

contract ERC20 {
    function totalSupply() public view returns (uint256);
    function balanceOf(address who) public view returns (uint256);
    function transfer(address to, uint256 value) public returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    function allowance(address owner, address spender) public view returns (uint256);
    function transferFrom(address from, address to, uint256 value) public returns (bool);
    function approve(address spender, uint256 value) public returns (bool);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

contract TNStoken {

    using SafeMath for uint256;

    constructor() public {

    }
}

This might seem complex, but it’s actually very simple. This is a “directory” of functions our token needs to have, and we’ll build them one by one, explaining what each of them means. Consider the above an interface for our token. We’ll see how and why this is useful when we create the Story DAO application.

Basic balances

Let’s start. A token is actually just a “spreadsheet” in the Ethereum blockchain, like this:

| Name | Amount |
|:--|:--|
| Bruno | 4000 |
| Joe | 5000 |
| Anne | 0 |
| Mike | 300 |

So let’s create a mapping, which is essentially exactly like a spreadsheet in the contract:

mapping(address => uint256) balances;

According to the interface above, this needs to be accompanied by a balanceOf function, which can read this table:

function balanceOf(address _owner) public view returns (uint256) {
    return balances[_owner];
}

The function balanceOf accepts one argument: _owner is public (can be used by anyone), is a view function (meaning it’s free to use — does not require a transaction), and returns a uint256 number, the balance of the owner of the address sent in. Everyone’s balance of tokens is publicly readable.

Total supply

Knowing the total supply of the token is important for its users and for coin tracking applications, so let’s define a contract property (variable) to track this and another free function through which to read this:

uint256 totalSupply_;
function totalSupply() public view returns (uint256) {
    return totalSupply_;
}

Sending tokens

Next, let’s make sure the owner of a number of tokens can transfer them to someone else. We’ll also want to know when a transfer has occurred, so we’ll define a Transfer event as well. A Transfer event lets us listen for transfers in the blockchain via JavaScript, so that our applications can be aware of when these events are emitted instead of constantly manually checking if a transfer happened. Events are declared alongside variables in a contract, and emitted with the emit keyword. Let’s add the following into our contract now:

event Transfer(address indexed from, address indexed to, uint256 value);

function transfer(address _to, uint256 _value) public returns (bool) {
    require(_to != address(0));
    require(_value <= balances[msg.sender]);

    balances[msg.sender] = balances[msg.sender].sub(_value);
    balances[_to] = balances[_to].add(_value);
    emit Transfer(msg.sender, _to, _value);
    return true;
}

This function accepts two arguments: _to, which is the destination address which will receive the tokens, and value, which is the number of tokens. It’s important to remember that value is the number of the smallest units of the token, not the whole units. So if a token is declared to have 10 decimals, then in order to send one token you would send 10000000000. This level of granularity lets us transact minuscule amounts.

The function is public, meaning anyone can use it — both other contracts and users — and it returns true if the operation was successful.

The function then does some sanity checks. First, it checks that the destination address isn’t a null address. In other words, tokens must not be sent into oblivion. Next, it checks if the sender is even allowed to send this many tokens by comparing their balance (balances[msg.sender]) with the value passed in for sending. If any of these checks fail, the function will reject the transaction and fail. It will refund any tokens sent, but the gas spent on executing the function up until that point will have been spent.

The next two lines subtract the amount of tokens from the sender’s balance and add that amount to the destination’s balance. Then the event is emitted with emit, and some values are passed in: the sender, the recipient, and the amount. Any client subscribed to Transfer events on this contract will now be notified of this event.

Okay, now our token holders can send tokens around. Believe it or not, that’s all you need for a basic token. But we’re going beyond that and adding some more functionality.

Allowance

Sometimes a third party may be given permission to withdraw from another account’s balance. This is useful for games which might facilitate in-game purchases, decentralized exchanges, and more. We do this by building a multi-dimensional mapping called allowance, which stores all such permissions. Let’s add the following:

mapping (address => mapping (address => uint256)) internal allowed;
event Approval(address indexed owner, address indexed spender, uint256 value);

The event is there so that apps listening can know when someone has pre-approved spending of their balance by someone else — a useful feature, and part of the standard.

The mapping combines addresses with another mapping, which combines addresses with numbers. It basically forms a spreadsheet like this one:

Table of Values

So Bob’s balance may be spent by Mary up to 1000 tokens and Billy up to 50 tokens. Mary’s balance can be spent by Bob up to 750 tokens. Billy’s balance can be spent up to 300 tokens by Mary and 1500 by Joe.

Given that this mapping is internal, it can only be used by functions in this contract and contracts that use this contract as a base.

To approve someone else spending from your account, you call the approve function with the address of the person allowed to spend your tokens, the amount they are allowed to spend, and in the function you emit an Approval event:

function approve(address _spender, uint256 _value) public returns (bool) {
  allowed[msg.sender][_spender] = _value;
  emit Approval(msg.sender, _spender, _value);
  return true;
}

We also need a way to read how much a user can spend from another user’s account:

function allowance(address _owner, address _spender) public view returns (uint256) {
    return allowed[_owner][_spender];
}

So it’s another read only function (view) which means it’s free to execute. It simply reads the remaining withdrawable balance.

So how does one send for someone else? With a new transferFrom function:

function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
    require(_to != address(0));
    require(_value <= balances[_from]);
    require(_value <= allowed[_from][msg.sender]);

    balances[_from] = balances[_from].sub(_value);
    balances[_to] = balances[_to].add(_value);
    allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
    emit Transfer(_from, _to, _value);
    return true;
}

As before, there are sanity checks: the destination address must not be a null-address, so no sending tokens to a black hole. The value being transferred also needs to be less than or equal to not only the current balance of the account the value is being transferred from, but also the balance the message sender (the address initiating this transaction) is still allowed to spend for them.

Next, the balance is updated and the allowed balance is brought into sync with that before emitting the event about the Transfer.

Note: it’s possible for the token holder to spend tokens without the allowed mapping being updated. This can happen if the token holder sends tokens around manually using transfer. In that case, it’s possible that the holder will have fewer tokens than the allowance dictates a third party can spend for them.

With approvals and allowances in place, we can also create functions that let a token holder increase or decrease someone’s allowance, rather than overwrite the value entirely. Try doing this as an exercise, then refer to source code below for the solution.

function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
    allowed[msg.sender][_spender] = (
    allowed[msg.sender][_spender].add(_addedValue));
    emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
    return true;
}

function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
    uint oldValue = allowed[msg.sender][_spender];
    if (_subtractedValue > oldValue) {
        allowed[msg.sender][_spender] = 0;
    } else {
        allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
    }
    emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
    return true;
}

Constructor

So far, we’ve merely built a token “contract”. But what is this token? What’s it called? How many decimals does it have? How do we use it?

At the very beginning, we defined a constructor function. Now, let’s finish its body and add the attributes name, symbol and decimals:

string public name;
string public symbol;
uint8 public decimals;

constructor(string _name, string _symbol, uint8 _decimals, uint256 _totalSupply) public {
    name = _name;
    symbol = _symbol;
    decimals = _decimals;
    totalSupply_ = _totalSupply;
}

Doing it like this lets us reuse the contract later for other tokens of the same type. But seeing as we know exactly what we’re building, let’s hard-code those values:

string public name;
string public symbol;
uint8 public decimals;

constructor() public {
    name = "The Neverending Story Token;
    symbol = "TNS";
    decimals = 18;
    totalSupply_ = 100 * 10**6 * 10**18;
}

These details are read by the various Ethereum tools and platforms when displaying the token’s information. The constructor function is automatically called when a contract is deployed to an Ethereum network, so these values will be auto-configured at deploy-time.

A word about totalSupply_ = 100 * 10**6 * 10**18;: this is just a way to make it easier for humans to read the number. Since all transfers in Ethereum are done with the smallest unit of ether or token (including decimals) the smallest unit is 18 decimals deep into the decimal point. This is why a single TNS token is 1 * 10**18*. Furthermore, we want 100 million of them, so that’s 100 * 10**6 or 100*10*10*10*10*10*10. This makes the number much more readable than 100000000000000000000000000.

Alternative Development Flow

Alternatively, we can just extend the Zeppelin contract, modify some attributes, and we have our token. That’s what most people do, but when dealing with software that potentially handles millions of other people’s money, I personally tend to want to know exactly what I put in the code, so blind code reuse is minimal in my personal case.

pragma solidity ^0.4.24;

import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
import "../node_modules/openzeppelin-solidity/contracts/token/ERC827/ERC20Token.sol";

contract TNStoken is ERC20Token {
    using SafeMath for uint256;

    string public name;
    string public symbol;
    uint8 public decimals;
    uint256 totalSupply_;

    constructor() public {
        name = "The Neverending Story Token";
        symbol = "TNS";
        decimals = 18;
        totalSupply_ = 100 * 10**6 * 10**18;
    }
}

In this case, we use the is notation to declare that our token is ERC20Token. This makes our token extend the ERC20 contract, which in turn extends StandardToken, and so on …

Either way, our token is ready now. But who gets how many tokens to start with and how?

Initial Balance

Let’s give the maker of the contract all the tokens. Otherwise, the tokens won’t be sendable to anyone. Update the constructor by adding the following line to the end of it:

balances[msg.sender] = totalSupply_;

Token Locking

Seeing as we intend to use the tokens as voting power (i.e. how many tokens you lock during voting represents how powerful your vote is), we need a way to prevent users from sending them around after having voted, else our DAO would be susceptible to a Sybil attack — a single person with a million tokens could register 100 addresses and achieve the voting power of 100 million tokens by just sending them around to different addresses and re-voting with a new address. Thus, we’ll prevent transferring exactly as many tokens as a person has devoted to a vote, cumulatively for each vote on each proposal. That’s the twist we mentioned at the start of this post. Let’s add the following event into our contract:

event Locked(address indexed owner, uint256 indexed amount);

Then let’s add the locking methods:

function increaseLockedAmount(address _owner, uint256 _amount) onlyOwner public returns (uint256) {
    uint256 lockingAmount = locked[_owner].add(_amount);
    require(balanceOf(_owner) >= lockingAmount, "Locking amount must not exceed balance");
    locked[_owner] = lockingAmount;
    emit Locked(_owner, lockingAmount);
    return lockingAmount;
}

function decreaseLockedAmount(address _owner, uint256 _amount) onlyOwner public returns (uint256) {
    uint256 amt = _amount;
    require(locked[_owner] > 0, "Cannot go negative. Already at 0 locked tokens.");
    if (amt > locked[_owner]) {
        amt = locked[_owner];
    }
    uint256 lockingAmount = locked[_owner].sub(amt);
    locked[_owner] = lockingAmount;
    emit Locked(_owner, lockingAmount);
    return lockingAmount;
}

Each method makes sure that no illegal amount can be locked or unlocked, and then emits an event after altering the locked amount for a given address. Each function also returns the new amount that’s now locked for this user. This still doesn’t prevent sending, though. Let’s modify transfer and transferFrom:

function transfer(address _to, uint256 _value) public returns (bool) {
    require(_to != address(0));
    require(_value <= balances[msg.sender] - locked[msg.sender]); // <-- THIS LINE IS DIFFERENT
    // ...

function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
    require(_to != address(0));
    require(_value <= balances[_from] - locked[_from]);
    require(_value <= allowed[_from][msg.sender] - locked[_from]); // <-- THIS LINE IS DIFFERENT
    // ...

Finally, we need to know how many tokens are locked or unlocked for a user:

function getLockedAmount(address _owner) view public returns (uint256) {
    return locked[_owner];
}

function getUnlockedAmount(address _owner) view public returns (uint256) {
    return balances[_owner].sub(locked[_owner]);
}

That’s it: our token is now lockable from the outside, but only by the owner of the token contract (which will be the Story DAO we’ll build in the coming tutorials). Let’s make the token contract Ownable — i.e. allow it to have an owner. Import the Ownable contract with import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol"; and then change this line:

contract StoryDao {

… to be this:

contract StoryDao is Ownable {

Full Code

The full code of the token with comments for custom functions at this point looks like this:

pragma solidity ^0.4.24;

import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol";
import "../node_modules/openzeppelin-solidity/contracts/ownership/Ownable.sol";

contract TNStoken is Ownable {

    using SafeMath for uint256;

    mapping(address => uint256) balances;
    mapping(address => uint256) locked;
    mapping (address => mapping (address => uint256)) internal allowed;
    uint256 totalSupply_;

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    event Locked(address indexed owner, uint256 indexed amount);

    string public name;
    string public symbol;
    uint8 public decimals;

    constructor() public {
        name = "The Neverending Story Token";
        symbol = "TNS";
        decimals = 18;
        totalSupply_ = 100 * 10**6 * 10**18;
        balances[msg.sender] = totalSupply_;
    }

    /**
    @dev _owner will be prevented from sending _amount of tokens. Anything
beyond this amount will be spendable.
    */
    function increaseLockedAmount(address _owner, uint256 _amount) public onlyOwner returns (uint256) {
        uint256 lockingAmount = locked[_owner].add(_amount);
        require(balanceOf(_owner) >= lockingAmount, "Locking amount must not exceed balance");
        locked[_owner] = lockingAmount;
        emit Locked(_owner, lockingAmount);
        return lockingAmount;
    }

    /**
    @dev _owner will be allowed to send _amount of tokens again. Anything
remaining locked will still not be spendable. If the _amount is greater
than the locked amount, the locked amount is zeroed out. Cannot be neg.
    */
    function decreaseLockedAmount(address _owner, uint256 _amount) public onlyOwner returns (uint256) {
        uint256 amt = _amount;
        require(locked[_owner] > 0, "Cannot go negative. Already at 0 locked tokens.");
        if (amt > locked[_owner]) {
            amt = locked[_owner];
        }
        uint256 lockingAmount = locked[_owner].sub(amt);
        locked[_owner] = lockingAmount;
        emit Locked(_owner, lockingAmount);
        return lockingAmount;
    }

    function transfer(address _to, uint256 _value) public returns (bool) {
        require(_to != address(0));
        require(_value <= balances[msg.sender] - locked[msg.sender]);

        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(msg.sender, _to, _value);
        return true;
    }

    function approve(address _spender, uint256 _value) public returns (bool) {
        allowed[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
    }

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
        require(_to != address(0));
        require(_value <= balances[_from] - locked[_from]);
        require(_value <= allowed[_from][msg.sender] - locked[_from]);

        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
        emit Transfer(_from, _to, _value);
        return true;
    }

    function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
        allowed[msg.sender][_spender] = (
        allowed[msg.sender][_spender].add(_addedValue));
        emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
        return true;
    }

    function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
        uint oldValue = allowed[msg.sender][_spender];
        if (_subtractedValue > oldValue) {
            allowed[msg.sender][_spender] = 0;
        } else {
            allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
        }
        emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
        return true;
    }

    /**
    @dev Returns number of tokens the address is still prevented from using
    */
    function getLockedAmount(address _owner) public view returns (uint256) {
        return locked[_owner];
    }

    /**
    @dev Returns number of tokens the address is allowed to send
    */
    function getUnlockedAmount(address _owner) public view returns (uint256) {
        return balances[_owner].sub(locked[_owner]);
    }

    function balanceOf(address _owner) public view returns (uint256) {
        return balances[_owner];
    }

    function totalSupply() public view returns (uint256) {
        return totalSupply_;
    }

    function allowance(address _owner, address _spender) public view returns (uint256) {
        return allowed[_owner][_spender];
    }

}

Conclusion

This part helped us get through building a basic token that we’ll use as a participation/share token in The Neverending Story. While the token has utility, it is by its definition of being an asset that controls decisions of a greater body a security token. Be mindful of the difference.

In the next part of this series, we’ll learn how to compile, deploy and test this token.

Bruno SkvorcBruno Skvorc
View Author

Bruno is a blockchain developer and technical educator at the Web3 Foundation, the foundation that's building the next generation of the free people's internet. He runs two newsletters you should subscribe to if you're interested in Web3.0: Dot Leap covers ecosystem and tech development of Web3, and NFT Review covers the evolution of the non-fungible token (digital collectibles) ecosystem inside this emerging new web. His current passion project is RMRK.app, the most advanced NFT system in the world, which allows NFTs to own other NFTs, NFTs to react to emotion, NFTs to be governed democratically, and NFTs to be multiple things at once.

DAppethereumethereum-hubethereum-tutorialsThe Neverending StoryTNS token
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week
Loading form