EOS Crowdsale – Develop Crowdsale smart contract on EOS

Table of Contents

Read Time: 7 minutes

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 .

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

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 in the series:

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

At QuillHash, we understand the Potential of Blockchain and have a good team of developers who can develop any blockchain applications like Smart Contracts, dApps, DeFi, Stable Coins, DEX on the any Blockchain Platform like EthereumEOS and Hyperledger.

To be up to date with our work, Join Our Community :-

Telegram | Twitter | Facebook | LinkedIn

QuillAudits is a secure smart contract audits platform designed by QuillHash Technologies. It is a fully automated platform to verify smart contracts to check for security vulnerabilities through its superior manual review and automated tools. We conduct both smart contract audits and penetration tests to find potential security vulnerabilities which might harm the platform’s integrity.

To be up to date with our work, Join Our Community:

Telegram | Twitter | Facebook | LinkedIn


Related Articles

View All


$NUWA failed to rug on BSC and was front-run by the MEV bot 0x286E09932B8D096cbA3423d12965042736b8F850.

The bot made ~$110,000 in profit.

Are you concerned about your enterprise's security in Web 3.0? Look no further!

Let's delve deeper into and learn effective solutions to mitigate them. Our experts have covered unconventional approaches, from Zero- Trust Security Model to Bug Bounty Programmes.


Hey folks👋,

Web3 security is like a game of whack-a-mole, except the moles are hackers who keep popping up no matter how hard you hit them. 🤦‍♀️

But fear not; we've got some tips to keep your crypto safe⬇️⬇️

Unlock the power of Web3 for your enterprise with enhanced security measures!

💪🌐 Our latest blog post delves into the world of Web3-powered enterprises and how to ensure maximum security in this new frontier.🔒

Read part 1 of our series now: 🚀


Load More

Amidst FTX Saga, Hacker Swept More Than $25 Million in 2nd week of November

The contract reinvested (the earn function was not called) before the user pledged (depositAll function) without settling the reward, which means that when the user pledged, the contract did not settle the previous reward and instead conducted a new investment.

Become a Quiffiliate!
Join our mission to safeguard web3

Sounds Interesting, Right? All you have to do is:


Refer QuillAudits to Web3 projects for audits.


Earn rewards as we conclude the audits.


Thereby help us Secure web3 ecosystem.

Total Rewards Shared Out: $190K+