Step-by-step guide on issuing your NEP-5 token on NEO’s Private net using Go
This is a tutorial on setting up the environment for developing NEO smart contracts and NEO DApps using Go. If you don’t know what NEO, smart contracts and DApps are, you can refer to:
What is NEO, and what is GAS? — blockknight
NEO Smart Contract Introduction
What is NEO DApps?
Extra Reading
NEO Smart Economy — NEO DApps
NEO For Beginners — NEO News Today — NEOFANS
Awesome NEO
Introduction
Development Language
One of the advantages of developing on NEO’s Smart Contracts platform is that it supports a wide range of development languages.
NEO recommends C#, though it also supports Python, Java, Go, Ruby, and in the future Kotlin, C/C++, JavaScript, and others.
Despite it supports many languages, you will find that most of the existing projects are done using C#, Python, Java, and less on Go. Thus, I will attempt to document a simple tutorial, with reference to City of Zion’s resources, in hopes of attracting Go developers to give it a go (pun intended)!
For Gophers
Regardless of being a first-time or experienced developer, it is always encouraged to begin your journey with a Private net or Testnet.
Why?
Because deploying an NEP-5 token on the Mainnet will set you back by 490 GAS.
At the time of this writing (15th September 2018), the market price for a GAS token is $5.80, which totals up to $2842 and still a rather hefty bill for any startup.
But when the GAS price was at its all-time high, it was $95.87 per token and with just a Smart Contract deployment, it would have left a $47k dent in your budget!
Hence, it is always encouraged that you begin with your journey with on Private net or Testnet and only deploy on Mainnet once you are ready.
Requirements
For this tutorial, the required development environment is as below:
1. Operating System — Ubuntu Desktop 18.04.1 LTS
2. Git CLI — Git
3. Language — The Go Programming Language
4. Go Dependency Management Tool — Dep
5. Docker — Docker CE & Docker Compose
6. Editor — Visual Studio Code, Extension: Go
Other NEO Resources Used
- Go Smart Contract framework — neo-storm
- Pre-built neo-privnet-with-gas in Python and neo-scan images — neo-scan-docker
Important notes: Results may vary on NEO compiler and node version
In essence, there are several ways to run a PrivateNet, such as using neo-go or neo-privatenet-docker. However, in this tutorial, we will stick with neo-scan-docker by slipo. The neo-scan-docker is built from neo-python, so it will carry some “Python” components. Furthermore, it comes with neo-scan’s UI that allows you to analyze the Smart Contract deployment transactions.
* The installation method may vary depending on your operating system. Please refer to the links above for more info.
Setting up your environment
First, let’s set up your NEO Privatenet with neo-scan-docker.
1. Check that your Git, Docker and Docker Compose are in place.
richie@Redbuntu:~/Desktop$ sudo git --version
git version 2.17.1
richie@Redbuntu:~/Desktop$ sudo docker -v
Docker version 18.06.1-ce, build e68fc7a
richie@Redbuntu:~/Desktop$ sudo docker-compose -v
docker-compose version 1.22.0, build f46880fe
2. Clone slipo’s neo-scan-docker and load Docker image with docker-compose
. It may take a while to download the images, so be patient for the done
status to appear.
richie@Redbuntu:~/Desktop$ git clone https://github.com/slipo/neo-scan-docker.git
Cloning into 'neo-scan-docker'...
remote: Counting objects: 27, done.
remote: Total 27 (delta 0), reused 0 (delta 0), pack-reused 27
Unpacking objects: 100% (27/27), done.
richie@Redbuntu:~/Desktop$ cd neo-scan-docker/
richie@Redbuntu:~/Desktop/neo-scan-docker$ sudo docker-compose up
Starting neo-scan-docker_postgresql_1 ... done
Starting neo-privnet ... done
Starting neo-scan ... done
Attaching to neo-scan-docker_postgresql_1, neo-privnet, neo-scan
3. Add 127.0.0.1 neo-privnet
into your hosts file. Note that the location of your hosts file may vary on different operating systems.
Unix/Linux/macOS - /etc/hosts
Windows - C:\Windows\System32\Drivers\etc\hosts
4. Loadhttp://localhost:4000/
on your web browser and you should see neo-scan’s web UI by City of Zion.
5. Click on theWallet Addresses
and you should see a wallet address with pre-loaded NEO and GAS tokens. We will use it for Smart Contract deployments. You can refer to neo-privatenet for more information.
You can use the keypair with the initial NEO and GAS by importing the WIF key.Private/WIF key: KxDgvEKzgSBPPfuVfw67oPQBSjidEiqTHURKSDL1R7yGaGYAeYnr
Address: AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y
Script hash (for use with CheckWitness): b'#\xba\'\x03\xc52c\xe8\xd6\xe5"\xdc2 39\xdc\xd8\xee\xe9'
With that, your NEO Privatenet and neo-scan setup are done!
Next, let’s set up your neo-storm.
1.Input the following go version
, dep version
, and echo $GOPATH
into your Terminal to make sure Go, Dep, GOPATH are in place.
richie@Redbuntu:~$ go version
go version go1.10.1 linux/amd64
richie@Redbuntu:~$ dep version
dep:
version : devel
build date :
git hash :
go version : go1.8.3
go compiler : gc
platform : linux/amd64
richie@Redbuntu:~$ echo $GOPATH
/home/richie/go
2. Get neo-storm with go get
, retrieve dependencies with dep
, and install with make install
. *Ignore the warning messages
richie@Redbuntu:~$ go get -u github.com/CityOfZion/neo-storm
package github.com/CityOfZion/neo-storm: no Go files in /home/richie/go/src/github.com/CityOfZion/neo-storm
richie@Redbuntu:~$ cd $GOPATH/src/github.com/CityOfZion/neo-storm
richie@Redbuntu:~/go/src/github.com/CityOfZion/neo-storm$ sudo dep ensure -update
dep: WARNING: Unknown field in manifest: prune
richie@Redbuntu:~/go/src/github.com/CityOfZion/neo-storm$ sudo make install
installing project dependencies
dep: WARNING: Unknown field in manifest: prune
installing neo-storm framework
done installing, happy coding!
3. Now thatneo-storm
has been successfully installed, you should be able to test it.
richie@Redbuntu:~/go/src/github.com/CityOfZion/neo-storm$ neo-storm
NAME:
neo-storm - Neo smart contract framework for the Go programming languageUSAGE:
neo-storm [global options] command [command options] [arguments...]VERSION:
0.0.0COMMANDS:
compile compile a smart contract to an .avm file
testinvoke testinvoke a smart contract against a remote NEO RPC node
init initialize a new smart-contract in a directory with boiler plate code
help, h Shows a list of commands or help for one commandGLOBAL OPTIONS:
--help, -h show help
--version, -v print the version
4. “Done installing, happy coding!” — let’s give our first Smart Contract a go!
richie@Redbuntu:~/Desktop$ cd ~/Desktop
richie@Redbuntu:~/Desktop$ neo-storm init -n NeoStormTest
Successfully initialized smart contract [NeoStormTest]
richie@Redbuntu:~/Desktop$ cd NeoStormTest/
richie@Redbuntu:~/Desktop/NeoStormTest$ ls
main.go
richie@Redbuntu:~/Desktop/NeoStormTest$ cat main.go
package NeoStormTestimport "github.com/CityOfZion/neo-storm/interop/runtime"func Main(op string, args []interface{}) {
runtime.Notify("Hello world!")
Issuing your NEP-5 token contract
- To issue an NEP-5 token on NEO, we will have to adhere to its token standard. *For more advance NEP5(.1), you can refer here — it will allow the token to be listed on NEX.
NEP-5 Token Standard
Methods
-totalSupply()
-name()
-symbol()
-decimals()
-balanceOf(account)
-transfer(from, to, amount)
Events
-transfer(from, to, amount)NEP-5(.1) by NEX
Methods
-totalSupply()
-name()
-symbol()
-decimals()
-balanceOf(ctx, account)
-transfer(ctx, from, to, amount, callingScriptHash)
-transferFrom(ctx, from, to, amount)
-approve(ctx, owner, spender, amount, callingScriptHash)
-allowance(ctx, owner, spender)
2. Instead of rewriting a token contract, you can fork/clone my repository at — https://github.com/maplerichie/neo-go-nep5
richie@Redbuntu:~/Desktop$ git clone https://github.com/maplerichie/neo-go-nep5 MyFirstNeoGoContract
3. Launch your code editor (I use Visual Studio Code) and add~/Desktop/MyFirstNeoGoContract
into your workspace. There is also sample from neo-storm
examples folder at https://github.com/CityOfZion/neo-storm/tree/master/examples/token
4. Edit package
and play around with the NEP-5 attributes such asdecimals, multiplier, owner Address, Name, Symbol, TotalSupply
.
Package: package should change to package MyFirstNeoGoContract
as same to the folder name
Decimals: This sets the number of decimals used by the token or how much divisible is the token.
For example:
NEO’s decimal is 0
, which makes it a non-divisible token. Thus, it is not possible to transfer or receive NEO in decimals and must only be in whole numbers.
However, decimals are possible only on exchanges because they create their own decimal systems to facilitate trading standards.
Multiplier: This gives the value when with Decimals and TotalSupply are multiplied. The input format is10^decimals
For example:
If decimals = 6
, the multiplier valeue should be 10^6
, so multiplier = 1000000
.
Owner: This is your token issuer’s address. Here we will use the neo-privatenet address AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y
Name: Your NEP-5 token name in String
Symbol: Your NEP-5 token symbol. It is usually 3 characters long but not a must.
TotalSupply: This is your total token supply. Setting a maximum supply may prevent inflation.
5. Change the parameters and make sure to comply with the NEP-5 token standard. Then, proceed to compile the Go Smart Contract usingneo-storm
richie@Redbuntu:~/Desktop/MyFirstNeoGoContract$ neo-storm compile -i main.go -o main.avm
6. You will see main.avm
in the current directory. For now, neo-storm
does not perform checks on the NEP-5 token standard. In my experience, it will show an error if owner Address
is missed out.
7. We can now deploy the NEP-5 to a Privatenet Docker. To do so, we will first need to get the Privatenet Docker ID, then copy the main.avm
from host location and paste it onto the Privatenet Docker image, and finally attach to it to the running Docker.
richie@Redbuntu:~/Desktop/MyFirstNeoGoContract$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
69c60667835b slipoh/neo-scan "/bin/sh -c 'sleep 3…" 6 hours ago Up About a minute 0.0.0.0:4000->4000/tcp neo-scan
88b358cdd8ff cityofzion/neo-privatenet "/bin/bash /opt/run.…" 6 hours ago Up About a minute 0.0.0.0:20333-20336->20333-20336/tcp, 0.0.0.0:30333-30336->30333-30336/tcp neo-privnet
ab96dc398c62 postgres:10.1 "docker-entrypoint.s…" 6 hours ago Up About a minute 0.0.0.0:5432->5432/tcp neo-scan-docker_postgresql_1
richie@Redbuntu:~/Desktop/MyFirstNeoGoContract$ sudo docker cp main.avm 88b358cdd8ff:/neo-python
richie@Redbuntu:~/Desktop/MyFirstNeoGoContract$ sudo docker exec -it 88b358cdd8ff /bin/bash* Consensus nodes are running in screen sessions, check 'screen -ls'
* neo-python is installed in /neo-python, with a neo-privnet.wallet file in place
* You can use the alias 'neopy' in the shell to start neo-python's prompt.py with privnet settings
* Please report issues to https://github.com/CityOfZion/neo-privatenet-docker
8. On Docker, navigate to neo-python-cli
and unlock the wallet with the password coz
.
root@88b358cdd8ff:/neo-python# neopy
Privatenet useragent '/NEO:2.7.6/', nonce: 513759764
[I 180914 10:34:09 LevelDBBlockchain:114] Created Blockchain DB at /root/.neopython/Chains/privnet
[I 180914 10:34:09 NotificationDB:73] Created Notification DB At /root/.neopython/Chains/privnet_notif
NEO cli. Type 'help' to get startedneo> open wallet neo-privnet.wallet
[password]> ***
Opened wallet at neo-privnet.wallet
9. Before deploying the contract main.avm
, let’s take a look at the wallet balance through neo-scan
.
neo> wallet
[I 180914 11:01:22 UserWallet:538] Script hash b'#\xba\'\x03\xc52c\xe8\xd6\xe5"\xdc2 39\xdc\xd8\xee\xe9' <class 'bytes'>
Used 0.357 Gas
Used 0.357 Gas
Wallet {
"path": "neo-privnet.wallet",
"addresses": [
{
"version": 0,
"script_hash": "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
"frozen": false,
"votes": [],
"balances": {
"0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b": "100000000.0",
"0x602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7": "74589.9996"
},
"is_watch_only": false,
"tokens": [
"[a3640dd3c560c75528e5f861da5da98958d0d713] NXT2 : 2500000.00000000"
]
}
],
"height": 10294,
"percent_synced": 100,
"synced_balances": [
"[NEO]: 100000000.0 ",
"[NEOGas]: 74589.9996 ",
"[NXT2]: 2500000 "
],
"public_keys": [
{
"Address": "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
"Public Key": "031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a"
}
],
"tokens": [
{
"name": "NEX Template V2",
"symbol": "NXT2",
"decimals": 8,
"script_hash": "0xa3640dd3c560c75528e5f861da5da98958d0d713",
"contract_address": "AHao29nmHmJ82Fsj1qDgqtDc2rtQ8u2n2b"
}
],
"claims": {
"available": "0.0",
"unavailable": "7730.0"
}
}
10. It looks ready for deployment! Again, the wallet’s password iscoz
.
neo> import contract main.avm 0710 05 True False
contract properties: 1
Please fill out the following contract details:
[Contract Name] > MyFirstNEOToken
[Contract Version] > 1.0
[Contract Author] > Richie
[Contract Email] > babierichie@hotmail.com
[Contract Description] > First NEO Token
Creating smart contract....
Name: MyFirstNEOToken
Version: 1.0
Author: Richie
Email: babierichie@hotmail.com
Description: First NEO Token
Needs Storage: True
Needs Dynamic Invoke: False
{
"hash": "0x4e9b87c96158a8e2cc4b74794f727b38947e9862",
"script": "...",
"parameters": "0710",
"returntype": "05"
}
Used 500.0 Gas-------------------------------------------------------------------------------------------------------------------------------------
Test deploy invoke successful
Total operations executed: 11
Results:
[<neo.Core.State.ContractState.ContractState object at 0x7f79b510d278>]
Deploy Invoke TX GAS cost: 490.0
Deploy Invoke TX Fee: 0.0
-------------------------------------------------------------------------------------------------------------------------------------Enter your password to continue and deploy this contract
[password]> ***
[I 180914 11:03:33 Transaction:613] Verifying transaction: b'9d63e8977edb6448b7d9efa93e0ff0258324ab3b530d3848def4b29dd6569114'
Relayed Tx: 9d63e8977edb6448b7d9efa93e0ff0258324ab3b530d3848def4b29dd6569114
11. Verify the transaction with the hash provided. Note: The hash will different on your machine.
neo> tx 9d63e8977edb6448b7d9efa93e0ff0258324ab3b530d3848def4b29dd6569114
{
"txid": "0x9d63e8977edb6448b7d9efa93e0ff0258324ab3b530d3848def4b29dd6569114",
"type": "InvocationTransaction",
"version": 1,
"attributes": [],
"vout": [
{
"n": 0,
"asset": "0x602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
"value": "25493.9997",
"address": "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"
}
],
"vin": [
{
"txid": "475a091ccaa6330320a29884f5f8796a260bdba91352dd409d16787b37d8be26",
"vout": 0
}
],
"sys_fee": "4.9e-06",
"net_fee": "489.9999951",
"scripts": [
{
"invocation": "40510fa6630ef581108057ebe967cd306522466a32eaf068cf3717da1e640b866246c013033e3c878ebf9247a5a3500178b21b126b3af63ed07a86b0d2c4f34c89",
"verification": "21031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4aac"
}
],
"script": "...",
"gas": 49000000000,
"height": 10302,
"unspents": [
{
"n": 0,
"asset": "0x602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
"value": "25493.9997",
"address": "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"
}
]
}
12. After the transaction has shown up, check the wallet balance again. At Step 9, our GAS balance was 74589.9996 and now, the GAS balance is 74099.9996. As such, 490 GAS has been spent for the contract deployment!
neo> wallet
[I 180914 11:18:38 UserWallet:538] Script hash b'#\xba\'\x03\xc52c\xe8\xd6\xe5"\xdc2 39\xdc\xd8\xee\xe9' <class 'bytes'>
Used 0.357 Gas
Used 0.357 Gas
Wallet {
"path": "neo-privnet.wallet",
"addresses": [
{
"version": 0,
"script_hash": "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
"frozen": false,
"votes": [],
"balances": {
"0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b": "100000000.0",
"0x602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7": "74099.9995"
},
"is_watch_only": false,
"tokens": [
"[a3640dd3c560c75528e5f861da5da98958d0d713] NXT2 : 2500000.00000000"
]
}
],
"height": 10348,
"percent_synced": 100,
"synced_balances": [
"[NEO]: 100000000.0 ",
"[NEOGas]: 48605.9999 ",
"[NXT2]: 2500000 "
],
"public_keys": [
{
"Address": "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
"Public Key": "031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a"
}
],
"tokens": [
{
"name": "NEX Template V2",
"symbol": "NXT2",
"decimals": 8,
"script_hash": "0xa3640dd3c560c75528e5f861da5da98958d0d713",
"contract_address": "AHao29nmHmJ82Fsj1qDgqtDc2rtQ8u2n2b"
}
],
"claims": {
"available": "0.0",
"unavailable": "8652.0"
}
}
We can also look up the contract deployed.
neo> contract search My
Found 1 results for My{
"version": 0,
"code": {
"hash": "0x4e9b87c96158a8e2cc4b74794f727b38947e9862",
"script": "...",
"parameters": "0710",
"returntype": 5
},
"name": "MyFirstNEOToken",
"code_version": "1.0",
"author": "Richie",
"email": "babierichie@hotmail.com",
"description": "First NEO Token",
"properties": {
"storage": true,
"dynamic_invoke": false
}
}
13. Finally, we can try to invoke the token with contract hash using the wallet’s password coz
.
neo> testinvoke 0x4e9b87c96158a8e2cc4b74794f727b38947e9862 deploy []
There you go! You have now successfully issued your own NEP-5 token on your NEO Private net!
End of tutorial
It is hoped that this tutorial is simple enough for Gophers to follow and hope that any Go developers will NEO’s Discord to participate in its development together. You should definitely give it a go!!
Reach me at Discord LikKee.Richie#5647
if you really need my help :D
Author
Richie Chong, Malaysia
NEOMY Community Member
Proofreader
Aaron Hong, Malaysia