How to Create Your Own Token in Solidity – Easy Example

In the previous article of this Solidity crash course, we saw some of the basic elements of Solidity concerning smart contracts.

In this article, we’ll be going even further with the examples and explanations, helping us understand why, when, and how are specific elements of Solidity used in a certain way.

Also, as we continue our exploration of Solidity, we’ll introduce new concepts and clarify their use. I can imagine you’re anxious to start, so let’s go!

Subcurrency Example

In this example, we’ll take a look at a bit more complex example of a smart contract (see docs), which showcases a very basic form of cryptocurrency.

The contract provides its creator with the functionality of creating new crypto coins (some may call them “shitcoins” ;)).

This example of a smart contract is implemented in a way that enables any party to send coins (message sender) to any other party (message receiver) without being registered with a username and password. The only factor that identifies a user is an Ethereum keypair. 

I’ll always break down more extended examples like the current one into smaller, more manageable chunks, followed by an explanation of what each chunk does.

This way, it will be very easy to follow and understand what is going on in the source code.

💡 On a personal note, I’m aware that larger blocks of unfamiliar code can sometimes feel overwhelming at first sight, especially when you’re not used to reading source code written by another author. When that’s the case, my word of advice is: just give yourself time and you’ll understand everything. Usually, at first, you’ll understand the code, and then you’ll get the idea behind the code. 

Here is our simple cryptocurrency smart contract:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;

contract Coin 
{
    // The keyword "public" makes variables
    // accessible from other contracts.
    address public minter;
    mapping (address => uint) public balances;

    // Events allow clients to react to specific
    // contract changes you declare.
    event Sent(address from, address to, uint amount);

    // Constructor code is only run when the contract
    // is created.
    constructor() 
    {
        minter = msg.sender;
    }

    // Sends an amount of newly created coins to an address
    // Can only be called by the contract creator.
    function mint(address receiver, uint amount) public {
        require(msg.sender == minter);
        balances[receiver] += amount;
    }

    // Errors allow you to provide information about
    // why an operation failed. They are returned
    // to the caller of the function.
    error InsufficientBalance(uint requested, uint available);

    // Sends a number of existing coins
    // from any caller to an address
    function send(address receiver, uint amount) public {
        if (amount > balances[msg.sender])
            revert InsufficientBalance({
                requested: amount,
                available: balances[msg.sender]
            });

        balances[msg.sender] -= amount;
        balances[receiver] += amount;
        emit Sent(msg.sender, receiver, amount);
    }
}

The Magic – Line by Line

As before, our first line starts with a license declaration, setting the license to GPL 3.0. 

The line pragma solidity ^0.8.4; instructs the compiler to allow the source compilation, not below the version 0.8.4 and strictly below 0.9 (the ^ symbol), i.e. the upper version limit is set to the first major version.

🌍 Learn More: You can learn more about the Solidity file layout and pragma in this article on the Finxter blog.

The line contract Coin starts the code block for our contract. I’ve put the opening curly brace on the line below. The closing curly brace will end the code block for our contract, and all our contract source code will fit in between.

The line address public minter; declares a variable named minter which will store the coin minter’s address. The variable’s visibility is defined as public, making it accessible from other contracts.

The line mapping (address => uint) public balances; declares a mapping object between two variables of type address and type uint, i.e. unsigned integer. We could perceive a mapping object as a dict object in Python, where we use an address as an indexing element, and the uint object is the result that gets retrieved via the index reference. The mapping object’s visibility is also set to public.

The line event Sent(address from, address to, uint amount); declares an event that enables reactions to specific contract changes. In general, events are convenience interfaces with the EVM logging facilities, which are commonly used in debugging.

On the next line, we have a constructor block, which is executed when a contract is created. A constructor block can contain any amount of code, but in our case, it only has one line, assigning the sender/account address to the variable minter:

constructor() 
{
     minter = msg.sender;
}

In the following block, we have a mint() function that “creates” new coins (out of thin air) and sends (assigns) them to a receiver. Specifically, the function only increases the amount available to a receiver. Only the contract creator is allowed to mint the coins, and we have ensured such behavior by using a convenience function require, which checks if the requirement is satisfied.

The require function is a convenience function, and it will create an error without any message, or it will throw an error of type Error(string) if a string is passed to it. The require function is commonly used to ensure valid conditions that cannot be detected until the execution time, i.e. conditions received as function arguments or return values originating from calls to external contracts.

function mint(address receiver, uint amount) public 
{
    require(msg.sender == minter);
    balances[receiver] += amount;
}

The line error InsufficientBalance(uint requested, uint available); declares a custom error object used below with the require function. In case, the error gets invoked, we will see the error details showing the values of all received parameters, i.e. requested and available.

In the following line, we have function send(address receiver, uint amount), a function which will implement the logic for sending the specified amount to the receiver, and it will simultaneously deduct the same amount from the sender, given the amount being sent does not surpass the available amount on the sender’s account.

This behavior is implemented by the checking logic, which reverts the operation with our custom InsufficientBalance error. The use of named parameters requested and available assigned with amount and balances[msg.sender] is also introduced:

if (amount > balances[msg.sender])
    revert InsufficientBalance({
        requested: amount,
        available: balances[msg.sender]
    });
balances[msg.sender] -= amount;
balances[receiver] += amount;

The final line emit Sent(msg.sender, receiver, amount); reacts to a contract change (as we previously mentioned in our initial explanation) by sending a signal, i.e. a Sent event.

The Sent event carries the information about the message sender, the message receiver, and the amount deducted from the message sender / added to the message receiver.

Events

By using a line emit Sent(msg.sender, receiver, amount); we have declared an event, which is emitted when the send function executes the last line.

Web applications and other Ethereum clients can catch this type of event emitted on the blockchain at a very low cost (in terms of used gas).

When an event is emitted, the listener (defined as a Javascript code that will follow shortly) will receive the event, i.e. the arguments msg.sender, receiver, and amount which are passed to the parameters from, to, and amount, which we’ve defined when we declared our event Sent.

These arguments enable us an insight into the execution of the transaction.

The Javascript code for our event listener is also available from the GitHub repository:

Coin.Sent().watch({}, '', function(error, result) {
    if (!error) {
        console.log("Coin transfer: " + result.args.amount +
            " coins were sent from " + result.args.from +
            " to " + result.args.to + ".");
        console.log("Balances now:\n" +
            "Sender: " + Coin.balances.call(result.args.from) +
            "Receiver: " + Coin.balances.call(result.args.to));
    }
})

Since there are still some prerequisites in terms of the knowledge needed to set up our local development environment and create and use the listener, we will postpone the process and use the code above in one of the future articles.

Now, let’s just remember that the key library in use is the web3.js library – it is the main JavaScript library to interact with the Ethereum blockchain.

Conclusion

In this article, we looked at a second one, a more complex example of a smart contract, simulating a currency subcontract, which showed some of the common operations in coin minting and sending.

  • First, we encouraged ourselves to take a first look at someone else’s source code and survive in the process.
  • Second, we got to know the code very closely, as it got friendlier and tamer with each step.
  • Third, we learned how to deliberately make, errr… define an error and use it to make our code even more stable and trustworthy.
  • Fourth, we produced a streak of lightning at the end of our smart contract by emitting the Sent event and by doing so signaled that everything went fine.

What’s Next?

This tutorial is part of our extended Solidity documentation with videos and more accessible examples and explanations. You can navigate the series here (all links open in a new tab):


Learn Solidity Course

Solidity is the programming language of the future.

It gives you the rare and sought-after superpower to program against the “Internet Computer”, i.e., against decentralized Blockchains such as Ethereum, Binance Smart Chain, Ethereum Classic, Tron, and Avalanche – to mention just a few Blockchain infrastructures that support Solidity.

In particular, Solidity allows you to create smart contracts, i.e., pieces of code that automatically execute on specific conditions in a completely decentralized environment. For example, smart contracts empower you to create your own decentralized autonomous organizations (DAOs) that run on Blockchains without being subject to centralized control.

NFTs, DeFi, DAOs, and Blockchain-based games are all based on smart contracts.

This course is a simple, low-friction introduction to creating your first smart contract using the Remix IDE on the Ethereum testnet – without fluff, significant upfront costs to purchase ETH, or unnecessary complexity.