How to build an ICO on NEO with the NEX ICO smart contract template

This post dives into the neo-ico-template of the Neon Exchange (NEX), and how to use it as an ICO developer to configure, compile, deploy and test the ICO smart contract.

The whole process includes the following steps:

  • Adapting the ICO template to your needs
  • Compiling the contract template into NEO bytecode (.avm file)
  • Deploying the compiled smart contract using neo-python
  • Invoking methods on the deployed contract (deploy, mintTokens, balanceOf, …)
  • For testing purposes we will use a private network of the NEO blockchain

About the NEX ICO Template

The smart contract template was released mid-December, and introduced in a blog post on the official neon-exchange Medium. The template has three particularly interesting innovations:

  • Full NEP5(.1) compatibility
  • KYC/AML enforcement (optional): the ICO operators can choose to require participants to get verified before contributing. This works by whitelisting participant addresses before they can successfully invoke the smart contract method mintTokens
  • Simplified refund and rejection mechanism: the template includes several methods to avoid invalid ICO contributions and to automatically process refunds (eg. when a participant is not yet whitelisted, or contributes outside the ICO window).

You can find a more in-depth description of those in the official blog post.

Preparations: Python 3.5 + neo-python

Make sure you have Python 3.5 installed, you can get it from the official Python downloads page or the package manager of your choice (on macOS it should be installed by default, else use homebrew do get it). You can check your Python version by using “python3.5 -V”.

This guide uses neo-python, therefore set that up as first step. Clone the repository, setup a virtualenv and install the requirements. The whole process is covered as a detailed step-by-step guide in the neo-python README. Follow this and you’ll have neo-python ready to go in no time.

Configuring the ICO Template

This section shows how to adapt the template to your specific ICO. The first step is cloning the ICO template repository, setting the virtual environment and installing the dependencies:

$ git clone
$ cd neo-ico-template/
$ python3.5 -m venv venv
$ source venv/bin/activate
(venv) $ pip install -r requirements.txt

Now let’s dive into the configuration of the smart contract, in particular the name, symbol, ownership, supply and amount of tokens a participant gets for sending NEO or GAS.

All the important settings for the ICO can be configured in nex/token/


It is important to set the right owner, which has access to administrative methods of the contract.

  • For a live deployment you should create a wallet specifically for this ICO (in the neo-python prompt: “neo> create wallet ico.wallet”).
  • You need to get the “script hash” of the wallet and use this as value for the owner variable of the smart contract. When you open the wallet with neo-python and print the details (“neo> wallet”), it will print an info line with the script hash:
[I 171218 15:24:14 UserWallet:470] Script hash b'#\xba\'\x03\xc52c\xe8\xd6\xe5"\xdc2 39\xdc\xd8\xee\xe9' <class ‘bytes’>
  • For testing purposes you can just use the wallet provided with the privatenet Docker image, which has this script hash:
b'#\xba\'\x03\xc52c\xe8\xd6\xe5"\xdc2 39\xdc\xd8\xee\xe9'

Set the script hash as value for the “owner” variable in

Settings for token, supply, token value, caps and ICO time window

  • Change name of the token and the symbol.
  • Decide on the total amount, and the initial amount held by the owners, and set it in the variables “total_supply” and “initial_amount”.
  • Then set how many tokens a participant will receive when sending NEO or GAS, and set this in the variables “tokens_per_neo” and “tokens_per_gas”.
  • If there should be a cap for participants, you can define this with “max_exchange_limited_round”.
  • Finally, set the start time of the ICO and the end time, based on NEO block height. For testing purposes, set the block_sale_start variable to 1 and the end to 1 + 100000.


If you want to disable the KYC process, simply remove the KYC check at (“if not self.get_kyc_status(attachments.sender_addr, storage)”). See also the comments in the code.

If you want to enable users to send GAS in addition to NEO, you need to enable it by uncommenting the lines at

Testing and compiling the ICO template to NEO bytecode

While configuring and adapting the ICO template, it is immensely useful to continuously test the various methods. neo-python offers built-in functionality to build the smart contract and test method invocations with custom parameters.

For instance, let’s build the SC and test the “name” method. Open the neo-python prompt and use a command like this (replace path-to-template with the relative path to your template directory):

neo> build path-to-template/ test 0710 05 True False name []
LOADING AND SAVING! ../nex/neo-ico-template/
Trimming load self method
Trimming load self method
Saved output to path-to-template/ico_template.avm
[I 171219 14:21:29 EventHub:102] [test_mode][SmartContract.Execution.Success] [70b3f7098a9a3f2361a1f0c312e9842ec4ca6dc6] [b'NEX Template']
Calling path-to-template/ with arguments ['name', '[]']
Test deploy invoke successful
Used total of 462 operations
Result b'NEX Template'
Invoke TX gas cost: 0.001

This step also compiles the Python code to NEO bytecode, producing a file with the name “ico_template.avm” (located in the template directory, next to the file).

Alternatively you can also manually compile the Python smart contract code, you can read more about this in the template README.

Starting a Private Network of the NEO Blockchain

The next step is to start a private network of the NEO blockchain to deploy the compiled smart contract with neo-python.

Running a private network is easy by using Docker with this image: Just follow the instructions on the website to get it up and running (see also this post).

You can confirm that the private network is running with “docker ps”.

Now connect to the private network with neo-python. Starting with the “-p” argument makes it use the local private net:

$ python -p
NEO cli. Type 'help' to get started

The block count at the bottom of the terminal should be around 2115 and increasing. Download and open the privatenet wallet.json (password: coz, see also the Docker hub description), and rebuild the wallet:

neo> open wallet neo-privnet.wallet
[Password]> coz
Opened wallet at neo-privnet.wallet
neo> wallet rebuild

Now wait a few seconds, and verify that all the NEO and GAS is available by issuing the “wallet” command, which shows all the infos:

neo> wallet

Deploying the Smart Contract

Now we have a compiled smart contract, a private network of the blockchain running, and neo-python fully set up with a wallet with lots of NEO and GAS. The next step is deploying the smart contract with neo-python.

For this example I’ve copied the compiled smart contract (ico_template.avm file) into the neo-python directory, which makes it easier to deploy deploy it into the blockchain:

neo> import contract ico_template.avm 0710 05 True False
contract properties: 1
Please fill out the following contract details:
[Contract Name] > nex ico template
[Contract Version] > 1
[Contract Author] > nex
[Contract Email] > nex
[Contract Description] > nex
Creating smart contract....
Name: nex ico template
Version: 1
Author: nex
Email: nex
Description: nex
Needs Storage: True
Needs Dynamic Invoke: False
"returntype": "05",

Test deploy invoke successful
Total operations executed: 11
Results ['IOp Interface: <neo.Core.State.ContractState.ContractState object at 0x107981128> ']
Deploy Invoke TX gas cost: 490.0
Deploy Invoke TX Fee: 0.0
Enter your password to continue and deploy this contract
[password]> coz
[I 171218 12:59:31 Transaction:380] Verifying transaction: b'<transaction-hash>'

The command we used was “import contract ico_template.avm 0710 05 True False”, which means to import the file ico_template.avm with the parameter types 0710 (string and array) and return type byte-array. The parameters “True False” at the end mean “Need storage” (yes) and “Support dynamic invoke” (no). See also the neo docs about SC parameter types.

At this point, the contract is being deployed. Wait for a few blocks until it shows up on the blockchain:

neo> tx <transaction-hash>

Replace <transaction-hash> with the actual hash at the end of the previous step. Once the transaction is found, you can search for the smart contract to get more information. In this example we search for “nex” because we used that in the smart contract deployment step:

neo> contract search nex
Found 1 results for nex
self contract properties: 1
"email": "nex",
"properties": {
"dynamic_invoke": false,
"storage": true
"code_version": "nex",
"name": "nex",
"description": "nex",
"author": "nex",
"code": {
"returntype": 5,
"hash": "70b3f7098a9a3f2361a1f0c312e9842ec4ca6dc6",
"parameters": "0710"
"version": 0

In this case, the ICO smart contract has the hash 70b3f7098a9a3f2361a1f0c312e9842ec4ca6dc6, which we will need to use for all the method invocations.

Invoking Methods

The ICO smart contract has several methods, some of which have to be invoked by the owner and some which can be called by everyone.

This diagram provides a rough overview of the stages and relevant smart contract methods:

ICO stages and selected methods

Also take a look at NEP-5.1 and the Neon Exchange blog post for more information about the various available methods, including those not listed here in the diagram.

The first step is calling the “deploy” method of the smart contract, which stores the initial amount of tokens as “tokens in circulation” in the contract storage. With neo-python we use the command testinvoke with the smart contract hash to invoke the method deploy (without any arguments):

neo> testinvoke 70b3f7098a9a3f2361a1f0c312e9842ec4ca6dc6 deploy []

The empty brackets mean that there are no arguments to pass for the deploy method.

After the deploy method has been called and the transaction shows up on the blockchain, we can check the amount of tokens already in circulation with the circulation method:

neo> testinvoke 70b3f7098a9a3f2361a1f0c312e9842ec4ca6dc6 circulation []
[I 171218 16:11:31 EventHub:102] [test_mode][SmartContract.Storage.Get] [70b3f7098a9a3f2361a1f0c312e9842ec4ca6dc6] b'in_circulation' -> bytearray(b'\x00\xa01\xa9_\xe3\x00')
[I 171218 16:11:31 EventHub:102] [test_mode][SmartContract.Execution.Success] [70b3f7098a9a3f2361a1f0c312e9842ec4ca6dc6] [b'\x00\xa01\xa9_\xe3\x00']
Test invoke successful
Total operations: 969
Results ["ByteArray: bytearray(b'\\x00\\xa01\\xa9_\\xe3\\x00')"]
Invoke TX gas cost: 0.0
Invoke TX Fee: 0.001

In this case, the result is this byte array: b’\x00\xa01\xa9_\xe3\x00'. We can convert it into an integer with Python 3 like this:

>>> val = b'\x00\xa01\xa9_\xe3\x00'
>>> int.from_bytes(val, byteorder="little")

As you can see, the byte array represents the integer value 250000000000000, which is 2500000.00000000 tokens, the initial amount of 2.5m tokens for the owners specified in

The command crowdsale_available returns the remaining number of tokens available in this crowdsale:

neo> testinvoke 70b3f7098a9a3f2361a1f0c312e9842ec4ca6dc6 crowdsale_available []
Test invoke successful
Total operations: 1047
Results ['Integer: 750000000000000 ']
Invoke TX gas cost: 0.0
Invoke TX Fee: 0.001

In this case, all 7.5m tokens are still available.

There are a few special smart contract methods in this template: crowdsale_register and crowdsale_status.

The ICO operator has to whitelist addresses in order to allow them to participate. To whitelist an address like ATELFGeypfK15uwNvRdQe1zaD3vw38976L, you’d invoke crowdsale_register like this:

neo> testinvoke 70b3f7098a9a3f2361a1f0c312e9842ec4ca6dc6 crowdsale_register ["ATELFGeypfK15uwNvRdQe1zaD3vw38976L"]

After this transaction went through, you can verify the crowdsale status of the address with this command:

neo> testinvoke 70b3f7098a9a3f2361a1f0c312e9842ec4ca6dc6 crowdsale_status ["ATELFGeypfK15uwNvRdQe1zaD3vw38976L"]

Once an address is kyc_registered, users can participate in the ICO by invoking the mintTokens method with some attached NEO / GAS.

This is how that call would be issued from neo-python with 5 NEO attached:

neo> testinvoke 70b3f7098a9a3f2361a1f0c312e9842ec4ca6dc6 mintTokens [] --attach-neo=5

Once this call went through, you can check the balance with the standard NEP-5 method balanceOf:

neo> testinvoke 70b3f7098a9a3f2361a1f0c312e9842ec4ca6dc6 balanceOf ["ATELFGeypfK15uwNvRdQe1zaD3vw38976L"]

And this is it! We performed all the steps needed to run an ICO with the NEX ICO template. You can read up about further NEP-5 methods here.

Finally, in neo-python you’ll want to add this NEP-5 token to your wallet. You can do so with “import token <hash>”, and then you’ll see the balances in your wallet:

neo> import token 70b3f7098a9a3f2361a1f0c312e9842ec4ca6dc6

By the way, there will be an upcoming article which explains some of the nuances and pitfalls of the neo-boa smart contract compiler!

Thanks a lot to the people at Neon Exchange for open-sourcing such a great piece of software. It will be helpful for many projects to come!

If you have questions or feedback, reach out to the author via @metachris.