💙 Are you a DApp? We're helping DApps reach out to more users with our promotion service. Contact us now!
View all posts

EOS Smart Contract Development (Part — 5): Develop Crowdsale smart contract on EOS

Article written by quillhash.com and brought to you by EOS GO

In this article, we are going to discuss how basic crowdsale works and how can we develop an EOS Crowdsale smart contract. We will try to understand in what ways EOS smart contract differs from Ethereum smart contract in such basic contracts. Also, why do we need inter contract communication to manage crowdsale in EOS and how we can do it.


Before reading this, we would recommend you to read the previous articles in this series so as to have an in-depth understanding of eosio.token system smart contract. So let’s get started!

How basic crowdsale works:

Crowdsale is required in blockchain space to have faith while raising funds, unlike traditional revenue-raising methods. An initial coin offering (ICO), or digital token crowdsale, is a method of blockchain-based crowdfunding based on the exchange of a project’s new and unique cryptocurrency tokens for established cryptocurrencies like ETH, EOS, etc. Now, Crowdsale smart contract defines the rules and makes sure that there is transparency while raising these funds.

Now we will discuss EOS Crowdsale:

In this article, we are going to develop a crowdsale contract in EOS. QUI token crowdsale would be there in exchange for SYS token. The smart contract will have the following action functions:

  • To initialize the crowdsale with the name of the recipient (an account which will manage the crowdsale, pause the campaign or withdraw the SYS collected )

ACTION init(eosio::name recipient, eosio::time_point_sec start, eosio::time_point_sec finish);

  • To buy the QUI tokens for the account from. It is redirected to handle_investment method where token transfer is managed & multi-index table and states are managed. Whenever transfer action of eosio.token is called, it gets invoked.

ACTION buyquill(eosio::name from, eosio::name to, eosio::asset quantity, std::string memo);

  • To transfer the tokens from account from to the account to. The eosio.token contract holding all the QUI balances gets updated.

ACTION transfer(eosio::name from, eosio::name to, eosio::asset quantity, std::string memo);

  • To withdraw all the SYS once the goal is achieved or the finish time set while initialising is passed by. It can be called by the recipient only.

ACTION withdraw()

  • To avoid any discrepancies that may be present and for safety measure, crowdsale can be paused by the recipient at any moment of time. It acts as a toggle function so can be unpaused by calling the same action.

ACTION pause();

We will be issuing QUI token in exchange for SYS token of eosio.token contract after applying the rates. One can configure for the rates that apply after the crowdsale gets over.

Now, one main thing to note in this is that unlike ethereum the smart contract does not have its own address, but is deployed on particular EOS account address, say Alice. So, any transfer of tokens one does, is upon that address only. For convenience sake, we use the same name as contract for the address where account is deployed i.e. for crowdsale smart contract, we have crowdsale named account address.

Terminology used:

Code - Refers to an account_name where a contract has been published. We also define the scope of the contract in the constructor itself.

Now, let's first quickly revise what are Multi-Index Tables. They are a way to cache state and/or data in RAM for fast access. Multi-index table supports CRUD operations i.e. create, read, update and delete operations, something which the blockchain doesn't support (it only supports create and read.)

The multi-index table we will be using in crowdsale will be to hold the deposits of each account and its SYS and QUI holdings. The primary key would be the account name.

Conceptually, Multi-index tables are stored on the EOSIO RAM cache. Smart contracts using a multi-index table reserve a partition of the RAM cache and accesses to each partition is controlled using tablename, code, and scope.

In the code, when we emplace or modify the row in the table, we need to specify the ram payer as first argument payer.

deposits.emplace( payer, [&]( auto& deposit ) {
	deposit.account = investor;
	deposit.tokens = entire_tokens;

Note, in inter-contract communication, we cannot charge RAM to other accounts during notify normally. That is pretty obvious else anyone will make anyone pay for the ram.

Also, one more important note, you can always upgrade your smart contract but you can’t change the fields of the table.

Now, let's learn about inter contract communications and what is the need for it?

Inter contract is the communication between 2 external smart contracts. eosio.token contract is the system contract that holds all our tokens QUI, SYS, etc.

Any transfer of tokens or issuance of new tokens is done by this contract only. We have learned in detail about the contract in the previous article.

The crowdsale contract needs to issue QUI tokens in exchange for SYS tokens. To have that strategy, we require to have a communication in between 2 contracts, whenever transfer action is called upon we need to have transfer of SYS from participant to the recipient (of crowdsale) and also issue new QUI tokens to the participant.

In order to notify both the account from and account to, that there is some transfer between them, the standard eosio.token contract uses the following in its transfer action:

require_recipient( from );
require_recipient( to );

Now, we need to listen to these receipts of eosio.token contract. So, a custom dispatcher is made that handles QUI transfers from eosio.token contract. Hence, when transfer action of eosio.token is called then the transfer action of crowdsale is invoked as a result which handles the calculation of QUI as per the rate and transfers QUI tokens back to the same participant account. It is done using an instance of the action sender by giving the required permissions.

A custom dispatcher code snippet is shown below:

extern "C" void apply(uint64_t receiver, uint64_t code, uint64_t action)
    if (code == eosio::name("eosio.token").value && action == eosio::name("transfer").value) // handle actions from eosio.token contract
	    eosio::execute_action(eosio::name(receiver), eosio::name(code), &crowdsale::transfer;
    else if (code == receiver) // for other direct actions
	    switch (action)
		    EOSIO_DISPATCH_HELPER(crowdsaler, (init)(transfer)(pause)(rate)(checkgoal)(withdraw));

Here, we are listening from the eosio.token code (account where eosio.token contract is deployed default) and its transfer action. We invoke crowdsale’s transfer function when condition is satisfied using execute_action. If this condition is not met, and any crowdsale action is called explicitly, the EOSIO_DISPATCH_HELPER is used. Note, we have not done any changes in standard eosio.token contract. When you deploy it on Jungle testnet or any other testnet, you wont need to deploy eosio.token as such, it being a system contract. Before deploying on testnet, you need to create an account and have some EOS (from faucet) and then buy some ram required for deploying and updating the tables. It seems you need about 10 times the size of your contract for ram-allocation. The EOS required can be calculated from https://www.eosrp.io/#calc .

Now let's get back to the contract. For the issuance of QUI tokens to the same participant account action sender is used. An action constructor has 4 parameters:

  1. Permission level struct
  2. Contract to call
  3. Action to be called (initialised using eosio::name)
  4. Data to be passed to the action

So, issue action of eosio.token needs to be invoked. It is shown in the code snippet below:

eosio::action issue_action = eosio::action(
    eosio::permission_level(this->_self, "active"_n),
    issue{to, quantity, memo});

Similarly, we make an action construct in crowdsale for transfer action of eosio.token.

eosio::action transfer_action = eosio::action(
    eosio::permission_level(_self, eosio::name("active")),
    eosio::name("eosio.token"), // name of the contract
    transfer{from, to, quantity, memo});

Now, the inter-contract communication is established, we need to write the logic of crowdsale actions to do basic functionalities like init, pause, rate, checkgoal, withdraw and transfer.

Now, in order to test if all the actions are working as expected, we use the eosfactory. The test cases for all basic actions are written in python. The installation instructions can be found at:


The complete smart contract of EOS Crowdsale along with the test cases can be found at https://github.com/Quillhash/Crowdsale_EOS

So, we learned here how inter-contract communication is so amazing. We can listen to the transactions of 1 contract and apply our logic to do another one. Please note, we cannot as such change the fields of standard eosio.token i.e. you cannot take EOS from anyone’s wallet or issue new tokens, its just if they are giving you the EOS you can provide QUI back to them in exchange from another contract, say quilltoken. That's why we try not to change much in token contracts and keep it the same as standard eosio.token only.


We went full path from writing EOS crowdsale smart contract and learnt about eosio.token smart contract to have our own token and transfer tokens out to other accounts using inter-contract communication. We also did see about EOS Jungle Testnet (which is almost identical to Mainnet) and the major differences between EOS and Ethereum smart contract.

Stay tuned for the next part…

~~Part 1 — EOS Smart Contract Development~~

~~Part 2 — EOS Smart Contracts Audit checklist to Keep In mind Before Development~~

~~Part 3- Understanding fundamental concepts for writing dApps on EOS.~~

~~Part 4- Analysing EOS standard token contract.~~

~~Part 5- Develop a basic crowd sale application with EOS~~

Part 6- EOS for high performance dApps — Games on EOS!

Disclaimer: The views expressed by the author above do not necessarily represent the views of EOS GO. EOS GO is a community where EOS GO Blog being a platform for authors to express their diverse ideas and perspectives. To learn more about EOS GO and EOS, please join us on our social medias.

EOS GO Telegram - EOS News Channel - Twitter

EOS GO is funded by EOS ASIA and powered by YOU. Join the community and begin contributing to the movement by adding eos go to your name and joining the EOS GO telegram group.