This tutorial is part 2 of a series of tutorials to teach you how to create your first ICON DAPP from the ground up with little to no knowledge. This article assumes you have completed Part 1: Tools & Environment, which taught you basics from Python language to using ICON SDK and T-Bears, it is highly recommended that you finish part 1 before reading on.

Level: Intermediate


Programming Concepts ★★☆☆☆

Python ★★★☆☆

T-Bears (Local Emulated ICON Node) ★★★★★

SCORE (Smart Contract) ☆☆☆☆

What is a Smart Contract

ICON Smart Contract — SCORE Overview

SCORE (Smart Contract on Reliable Environment) is a smart contract running on ICON network. A contract is a software that resides at a specific address on the blockchain and executed on ICON nodes. They are building blocks for DApp (Decentralized App). SCORE defines and exports interfaces, so that other SCORE can invoke its functions. The code is written in python, and is uploaded as compressed binary data on the blockchain.

• Deployed SCORE can be updated. SCORE address remains the same after update.

• SCORE code size is limited to about 64 KB (actually bounded by the maximum stepLimit value during its deploy transaction) after compression.

• SCORE must follow sandbox policy — file system access or network API calls are prohibited.

In short, a SCORE is essentially ICON’s version of a smart contract. Let’s first go over some of these points,

  1. SCOREs can be updated. If you’re familiar with Ethereum smart contracts, they are immutable objects that cannot be altered (though there are clever tricks with contract managing contracts or upgradable smart contracts with ENS, but we’ll not get into those here). On the ICON network, smart contracts can be updated, SCORE address will not change after update. Updated SCOREs will go through the same auditing process as initial deployment.
  2. 64KB in terms of lines of raw code is about 65,000 lines, in terms of compressed byte code is orders of magnitude more. If a smart contract is that long, it’s probably done wrong.
  3. SCORE sandbox policy
  • Deterministic: All nodes forming consensus on the transaction must agree upon the same result.
  • No randomness: eg. using a random function where results are unpredictable.
  • No deviations from the environment, eg. no external network calls that can produce different results from different connections. No system calls where results may differ from node to node.
  • No long-running operations that can disrupt forming consensus.

Other Limitations

  • The maximum number of calls, interface calls and ICX transfer/send requests is 1024 in one transaction.
  • Declaring member variables that are not managed by states is prohibited.

State Transitions

A SCORE’s state transitions are logged in the State DB

A SCORE during its initial deployment, a State DB (leveldb) is initialized to record all its state changes. What is a state change exactly? Let’s use a real example, say Alice transfers 500 ICX to Bob, if the request is valid and verified, Alice’s ICX balance decreases by 500 and Bob’s balance increases by 500, this is a state change in balance.

Data Formats

VarDB, DictDB, ArrayDB are utility classes wrapping the State DB. A key can be a number or characters, and value_type can be int, str, Address, and bytes. If the key does not exist, these classes return 0 when value_type is int, return "" when str, return None when the value_type is Address or bytes. VarDB can be used to store simple key-value state, and DictDB behaves more like python dict. DictDB does not maintain order, whereas ArrayDB, which supports length and iterator, maintains order.

We will learn more about these data formats through actual implementation, for specs and usage examples, visit: SCORE VarDB, DictDB, ArrayDB

SCORE Coding

from iconservice import *

TAG = 'FirstScore'

class FirstScore(IconScoreBase):

def __init__(self, db: IconScoreDatabase) -> None:

def on_install(self) -> None:

def on_update(self) -> None:

def hello(self) -> str:
Logger.debug(f'Hello, world!', TAG)
return "Hello"
# def receive_funds is removed because it was our custom method not part of the generated template.

We see that all SCORE classes must inherit from the highest parent class ‘IconScoreBase’, this is an interface that lays out the foundation of a SCORE. In our generated template, each function’s default behavior is to call the same function from its parent class, ie. IconScoreBase, via the super() function. These are the basic minimum required functions in order to deploy the SCORE.


This function is called when the contract is loaded to each node, the initialization of data for the SCORE.


This function is called when the contract is deployed for the first time and never again after, including contract updates. State DB is initialized in this step, all subsequent state transitions will be logged to the State DB.

@external (external decorator)

Functions decorated with @external can be called from outside the contract. This is also how you expose SCORE functions to tbears scoreapi, so developers know which functions they can call to interact with your SCORE. We can also specify this to read only with @external(readonly=True), and the decorated function will have read-only access to the State DB.

@payable (payable decorator)

Remember we implemented a ‘receive_fund” function with a @payable decorator? Only functions with @payable decorator are permitted to receive incoming ICX coins. If ICX coins are passed to a non-payable function, that transaction will fail.

@eventlog (eventlog decorator)

Functions with @eventlog decorator will include logs in its txresult as ‘eventlogs’. You can also see the logs in ‘events’ tab on the live tracker.

Function hello is a dummy function that simply outputs “Hello”, but notice how the function’s return value is type hinted with str. Let’s explain this,

Type Hints

Type hinting is highly recommended for the input parameters and return value. When querying SCORE APIs, API specification is generated based on its type hints. If type hints are not given, only function names will return, this is not a good practice as function names don’t always suggest its type.

Possible data types for function parameters are int, str, bytes, bool, Address. List and Dict

Returning types can be int, str, bytes, bool, Address, List, Dict.

During our tbears init call, two other files were also generated, namely and package.json. This tells Python to treat the directory as a package with our base settings for the SCORE.

We have covered the basic anatomy of a SCORE smart contract, let’s learn more through actual implementation now.

Exercise - Enhance Hello World SCORE

# my_first_score/
def name(self) -> str:
return "HelloWorld"

def hello(self) -> str:
return f'Hello, {self.msg.sender}. My name is {}'

We declared a new methodname, it is a public method (@external) and it is read only. This returns the SCORE name of your choice.

We also modified the hello function to greet you back. msg is a built-in property that holds two information, msg.sender and msg.value. Sender is the wallet executing this transaction and value is the amount of ICX the sender attempts to transfer.

We also declared the function with return type hint of str, this is good practice and will be generated in the API spec, so we will know the function returns a string instead of being just a function name.

Your file should look like this, save it.

We learned in the last tutorial, all SCORE updates need to be deployed again with ‘-m update’ parameter.

# Update the SCORE in our localhost node
$ tbears deploy my_first_score -m update -o cx4f1ac3681a51dbbca949c5b411e6a6dadcfd6d2b

As usual, always check txresult to see if the transaction is successfully performed.

$ tbears txresult 0x68a09e20431961ac1be74efc1f546a70730d2e84b5d729ec425f62698498d634

Let’s try the same call.json, and see what we get

$ tbears call call.json

And we should get a nice greeting back from the contract.

response : {
"jsonrpc": "2.0",
"result": "Hello, hxe7af5fcfd8dfc67530a01a0e403882687528dfcb. My name is HelloWorld",
"id": 1

Deploying a Contract to Testnet

So far we have been working on the localhost node, we’ll eventually need to deploy the contract to the real ICON network for everyone to use.

In order to deploy our contract to the testnet, unlike local transactions, we’ll need valid signatures to make requests. This is done via providing our keystore to sign the transactions.

To make things more clear, let’s initiate a brand new SCORE template.

$ cd ..
$ mkdir SCORE_testnet && cd SCORE_testnet
$ tbears init my_testnet_score TestnetScore

Let’s make a new wallet for testnet use, instructions are the same from Part 1, you can either use the SDK or ICONex to create the wallet, we’ll use the SDK here.

# Enter interactive mode
$ python
# Create a wallet
>>> from iconsdk.wallet.wallet import KeyWallet
>>> wallet = KeyWallet.create()
# Check the wallet address
>>> wallet.get_address()
# Now let's create a keystore
>>>'./iconkeystore3', '@icon333')
>>> exit()

It is important to note that your ICX balances will reside on different networks, depending on where the transactions came from, so always make sure which node you’re on, ie. localhost or testnet.

The wallet is newly created so it has 0 balance across all nodes, let’s populate it with some ICX on ICX Testnet Faucet with the wallet address we just printed.

Now check the balance, this time we specify the network API.

# Check localhost first
$ tbears balance -u hx5638ee91e18574a1f0a29b4813578389f0e142a7
# balance in decimal: 0 # Then check testnet
$ tbears balance -u hx5638ee91e18574a1f0a29b4813578389f0e142a7
# balance in decimal: 20000000000000000000

The faucet app made the transfer on the testnet, so naturally your balances are reflected there. Now our data is on testnet, so we’re able to check transactions through the live tracker:

Now let’s reconfigure our default config file and point our URI to the testnet, so we don’t need to specify the network API on every call.

# tbears_cli_config.json# Change uri from"uri": "",# to "uri": "",

Our default stepLimit was also too low for testnet deployment, we’ll need a slightly higher stepLimit. You can use this tool to play with the settings you need: Unit Converter

Before we make any changes, let’s take a closer look at step calculation, I found this fairly confusing, particularly having to convert between all the units from super long decimals and hex, setting stepLimit was a bit of a guess work. We briefly covered step calculation in part 1 on a more theoretical level, with a formula. Let’s look into it and understand clearly how to properly configure stepLimit.

Step Calculation

Step = max ( [ ( ∑ βiSi+ C ), C ] )

This formula says, number of steps required has two scenarios,

Scenario 1: the transaction involves some SCORE operations, each operation has a different weight and cost. The formula ∑ βiSi+ C will sum up all the cost of these operations + a minimum fee of 100,000 steps.

Scenario 2: the transaction is a pure ICX transfer with no SCORE operations, so there’s only a minimum fee of 100,000 steps.

The formula will pick the higher value of the two scenarios as your step cost.

Let's further examine this with numbers,

In the first example, we assumed our SCORE is 512 Bytes, the size of Contract Set (size of generated/updated smart contract code in Bytes) is 512. We’d multiply by its weight of 30000 as our first SCORE operation cost. Second part is Contract Create (number of times to call the smart contract code generation function), in this case it’s one time only, so the cost is 1 times its weight of 1,000,000,000. Then we add the minimum transaction step cost which is 100,000. This operation falls in scenario 1 and will be picked by the system as the transaction cost.

In the second example, similar calculations were applied. In this example, we only made a contract call (a SCORE invoking an external function in another SCORE) which has a weight of 25,000.

The SCORE operations involved in transaction fee calculation can be found in the ICON Transaction Fee Yellowpaper

Now we have a fairly good idea how much each transaction might cost, we can set the stepLimits accordingly. For a contract deployment it’ll cost ~1 billion steps, let’s give it a bit of room with 1.5 billion steps as stepLimit. From our Unit Coverter, this is 0x59682f00 in hex value.

*Note the stepLimit in ‘deploy’ is using unit ‘step’ not ‘loop’, and it takes a hex value, so from the converter you should use Step to Hex.

We also know that ICX transfers right now cost 100,000 steps, let’s also give it a bit of a buffer with 200,000 stepLimit. This is 0x30d40 in hex value.

Open the default config file again,

# tbears_cli_config.json# Change deploy stepLimit to 
"stepLimit": "0x59682f00",
# Change transfer stepLimit to
"stepLimit": "0x200000"
# save the file

Units Revisited, in Words

I’m not sure if it’s just me or if people get confused by big numbers, let’s translate these numbers into words see if that helps,

1 quintillion (1 with 18 zeroes) loops = 1 ICX

100 million steps = 1 ICX

1 step = 10 billion loops

Ok that didn’t really help, whatevers let’s move on.

Now we the configurations ready, with URL pointing to testnet and estimated stepLimits, we’re ready to deploy.

# Deploy contract to testnet$ tbears deploy my_testnet_score -k iconkeystore3

Check transaction to see if everything went smoothly,

$ tbears txresult 0x3e23091f9f17afafdd241f7730bbbb64543dab75732b574865ee6a06a0ff61d3

Let’s also check the transaction result on the live tracker this time,

Step Limit is as we set it, 1.5 billion steps and actual steps consumed by the transaction was around 1 billion, also very close to our estimate.

Actual TxFee is simply Step used by Txt * Step Price which is ~10 ICX. Step Price here is the minimum 100,000 steps which is 0.00000001 ICX. When running on mainnet, ICX is the real ICX exchange rate, to get the transaction cost in USD value ($6.778 USD in this example).

You can click ‘Contract Created’ to see our the SCORE address, copy this down, this is where our SCORE lives on testnet. Let’s try to invoke the hello function on testnet, create a new call.json file.

# call.json# Change# iconkeyscore 3 address"from": "hx5638ee91e18574a1f0a29b4813578389f0e142a7"# to the scoreAddress you just copied, ie. SCORE address on testnet "to": "cxfe1cf1c1230cd32b0a95a54ce87e5b9009044343",

Execute it,

$ tbears call call.json

The keystore address we use doesn’t really matter in this case, as this is a read-only operation that doesn’t require a keystore file. But for other operations that involve ICX transactions, we will need to use an actual keystore to sign the transaction.

We should get a nice greeting back from the testnet node!

response : {
"jsonrpc": "2.0",
"result": "Hello, hx5638ee91e18574a1f0a29b4813578389f0e142a7. My name is HelloWorld",
"id": 1

At this point you should be comfortable with SCORE development. We have discussed SCORE in principle, basic syntax and its limitations. We have tried to deploy our SCORE to different networks, looked into step limits, step calculation and other configurations. We are now ready to build a more complicated SCORE project.

Before our next exercise, let’s spend a few minutes to learn about token standards, because we’ll be building our own token, we should understand what they are first.

ERC-20 should be a familiar term even for non-devs, you should all have at one point invested in an ICO with ETH and received custom ERC-20 tokens in return. This includes ICON presale, which was conducted on the Ethereum network, with ERC-20 ICX tokens.

What is ERC-20 exactly?

ERC-20 is a technical standard for smart contracts, there are only 6 functions in the standard. totalSupply for total token supply. balanceOf to get account balance of another account. transfer to transfer tokens directly with a value. transferFrom to transfer from an address to another. approve to allow balance withdrawl from your account. allowance that checks how much left can still be withdrawn.

This simple specification defines pretty much all your needs in a custom token, it has a finite supply, facilitates token transfers and keeps balance information.

Shortcomings of ERC-20

ERC-20 works pretty well, for the most part. It was (and still is?) the de facto token specification for almost all token sales, but what are the shortcomings?

There are minor shortcomings such as not being able to handle transactions through a receiver contract, taking 2 step process for transfers when only 1 is needed etc. These are however minor problems compared to the biggest shortcoming that can cause catastrophic economic losses — sending tokens to a contract that does not allow tokens to be withdrawn or reverting the action.

What is the fix? ERC-223.

ERC-223 addresses this specific problem by enforcing thetransferfunction to throw an error on invalid transfers and cancel the transaction so no funds are lost. The transfer function in ERC-223 also checks to see whether the receiving address is a contract, if it is, it will assume there’s a tokenFallback method to call it back. We’ll learn more about this soon in our crowdsale exercise.

Why did we just explain ERC-20 and ERC-223? Because ICON has its own token standard, the ICON Token Standard IRC-2. The standard adopts ERC-223 standard, along with its fallback mechanism. This is likely going to be the token standard for all upcoming ICON ICOs that plan to raise funds in ICX, in return you’d get custom IRC-2 tokens at a predefined ratio, pretty much like ETH<->ERC-20 token. The custom IRC-2 tokens can then be re-used by third parties or exchanges, just like how all other tokens work.

There’s also another token standard, ICON IRC-3 NFTs, which is based on ERC-721, you can read more here: ICON Non-Fungible Token Standard

Now let’s build our custom IRC-2 token.

Exercise — Creating a Sample Token (IRC-2) & Sample Crowdsale

We’ll start with our custom token SCORE implementation,

# Make a new directory, we'll work from a fresh SCORE template
$ cd ..
$ mkdir SCORE_token && cd SCORE_token
# Initialize a new SCORE project
$ tbears init my_sample_token MySampleToken

Referring to the IRC-2 spec, we’re required of a few methods, name, symbol, decimals, totalSupply, balanceOf, and transfer. We’ll first define the token standard interface to stay IRC-2 compliant, subsequently our custom token class will derive from this interface so we’ll be required to implement and override all these abstract methods that have no implementation bodies.

# my_sample_token/my_sample_token.pyfrom iconservice import *

TAG = 'MySampleToken'
class TokenStandard(ABC):
def name(self) -> str:

def symbol(self) -> str:

def decimals(self) -> int:

def totalSupply(self) -> int:

def balanceOf(self, _owner: Address) -> int:

def transfer(self, _to: Address, _value: int, _data: bytes=None):

Now let’s implement our custom token ‘MySampleToken’ class which derives from IconScoreBase so as usual, we’ll implement __init__, on_install and on_update. we need 3 class variables, balance information, total supply of the token and decimal places the token uses.

class MySampleToken(IconScoreBase, TokenStandard):    # Declare some class variables that we'll need
_BALANCES = 'balances'
_TOTAL_SUPPLY = 'total_supply'
_DECIMALS = 'decimals'

def __init__
(self, db: IconScoreDatabase) -> None:
# Remember we have 3 types of wrappers for State DB
# VarDB is for simple key value, DictDB for
# dictionaries and ArrayDB when you need to maintain order
self._total_supply = VarDB(self._TOTAL_SUPPLY, db, value_type=int)
self._decimals = VarDB(self._DECIMALS, db, value_type=int)
self._balances = DictDB(self._BALANCES, db, value_type=int)

def on_install(self, initialSupply: int, decimals: int) -> None:
# Token's total supply is using {decimals} places, so
# actual supply will need to be multiplied by 10^decimals
total_supply = initialSupply * 10 ** decimals
Logger.debug(f'on_install: total_supply={total_supply}', TAG)
# Set the total supply and decimals of this contract
self._balances[self.msg.sender] = total_supply

def on_update(self) -> None:

Now let’s implement those required methods of IRC-2 standard

# my_sample_token/my_sample_token.pyclass MySampleToken(IconScoreBase, TokenStandard):
def name(self) -> str:
return "MySampleToken"
# Symbol is the ticker symbol you normally see on exchanges
def symbol(self) -> str:
return "MST"

def decimals(self) -> int:
return self._decimals.get()

def totalSupply(self) -> int:
return self._total_supply.get()

def balanceOf(self, _owner: Address) -> int:
return self._balances[_owner]

def transfer(self, _to: Address, _value: int, _data: bytes=None):
if _data is None:
_data = b'None'
self._transfer(self.msg.sender, _to, _value, _data)
def _transfer(self, _from: Address, _to: Address, _value: int, _data: bytes): # check to see if the sender has enough balance
if self._balances[_from] < _value:
revert("Out of balance")

# substract the sending value from sender balance
self._balances[_from] = self._balances[_from] - _value
# add balance to recipient address
self._balances[_to] = self._balances[_to] + _value

# if recipient is a contract, we'll handle differently
# with tokenFallback method, this method will be defined
# in our actual crowdsale contract.
if _to
recipient_score = self.create_interface_score(_to, TokenFallbackInterface)
recipient_score.tokenFallback(_from, _value, _data)
self.Transfer(_from, _to, _value, _data)
Logger.debug(f'Transfer({_from}, {_to}, {_value}, {_data})', TAG)
# Log the transfer data event
def Transfer(self, _from: Address, _to: Address, _value: int, _data: bytes):

All these methods should be fairly self-explanatory, but notice transfer function has a generic implementation that calls another protected method _transfer function. In the_transfer function we checked to see if sender has enough balance to request such transfer, we can also do various other checks to ensure validity of the transaction. We also checked to see if recipient is a SCORE contract, if it is, we will handle through recipient’s tokenFallback which is defined in the crowdsale contract.

my_sample_token/ full version


We still need to make a few more configurations before we deploy the SCORE, open up your default tbears_cli_config.json and replace the entire file with the following content

# tbears_cli_config.json
"uri": "",
"nid": "0x3",
"keyStore": null,
"from": "hxe7af5fcfd8dfc67530a01a0e403882687528dfcb",
"to": "cx0000000000000000000000000000000000000000",
"stepLimit": "0x3000000",
"deploy": {
"contentType": "zip",
"mode": "install",
"scoreParams": {
"initialSupply": "0x2fb60ce0",
"decimals": "0x12"

Here we set the initial supply to 800,460,000 (use Unit Converter), remember the unit here is in ICX, so you should remove 18 zeroes from converted unit. Decimals is commonly 18 places so we’ll set the same.

Now we can deploy,

$ tbears deploy my_sample_token -k keystore_test1

as usual, you should always check the txresult to see if its successful

$ tbears txresult 0x16d7aaa8b69f5d73e7afdfe312b3f3f96bb3b178f6aa15d1f2bf7d77485a823d

We now have our custom IRC-2 token! Copy the scoreAddress from the result, let’s check to see if total supply is set correctly, create a file gettotalsupply.json

"jsonrpc": "2.0",
"method": "icx_call",
"params": {
"from": "hxe7af5fcfd8dfc67530a01a0e403882687528dfcb",
"to": "cx4bb1165dc33a6b7e70d1d5f46d33286095c5a56b",
"dataType": "call",
"data": {
"method": "totalSupply"
"id": 1

then call with,

$ tbears call gettotalsupply.json


response : {
"jsonrpc": "2.0",
"result": "0x2961fff8ca4a62327800000",
"id": 1

Again using the Unit Converter, this is 800,460,000,000,000,000,000,000,000 with 18 ‘decimals’, which is 800,460,000 (MST) just like we set it.

You can follow earlier procedures to deploy the same SCORE to testnet, remember to deploy using a keystore with enough balance there, you’ll be able to see everything on the live tracker like this,

populate balanceOf with the keystore address you used to deploy the contract

The wallet you used to deploy the code should have the total supply as a custom token (MST), you can also verify this through your ICONex by “Add token”, paste the token contract address to the “Address” field and the rest should populate automatically.

Create a Crowdsale Contract

# Create a new SCORE template
$ cd ..
$ mkdir SCORE_crowdsale && cd SCORE_crowdsale
$ tbears init my_sample_crowdsale MySampleCrowdsale

paste the entire code into my_sample_crowdsale/


The code should be fairly easy to understand, with our typical SCORE initialization and several functions to check crowdsale status and to manage transfers. However, people are likely slightly confused with the operation flow and interactions with our token SCORE contract. Let’s illustrate the entire workflow visually,

You can deploy the code locally to test first, then deploy to testnet. We need to pass in 3 parameters to fulfill our deployment (on_install), fundingGoalInICX, tokenScore, and durationInBlocks. Create the testnet config,

# tbears_cli_config_testnet.json
"uri": "",
"nid": "0x3",
"keyStore": null,
"from": "hx5638ee91e18574a1f0a29b4813578389f0e142a7",
"to": "cx0000000000000000000000000000000000000000",
"deploy": {
"stepLimit": "0x77359400",
"contentType": "zip",
"mode": "install",
"scoreParams": {
"fundingGoalInIcx": "0x2fb60ce0",
"tokenScore": "cx3d929fd4a9c32ccba8834a72503d27197627517e",
"durationInBlocks": "0x1f4"
"txresult": {},
"transfer": {
"stepLimit": "0x300000"

deploy to testnet,

$ tbears deploy my_sample_crowdsale -k iconkeystore3 -c tbears_cli_config_testnet.json

Now your crowdsale SCORE is deployed, but not yet live!

We’ll first transfer an initial amount of MST tokens from the MySampleToken owner to the crowdsale contract, which will trigger the crowdsale to start. In this example, since our funding goal is set at exactly the same amount as the total supply and the exchange rate is 1:1, so we’ll send the total supply over.

The crowdsale is triggered to go live and start receiving funds, let’s test with a couple of contributions,

If our crowdsale started fine, the funds should be sent in successfully.

You should receive equal amount of MST tokens in your contribution wallet, remember to first add the MST token to your contribution wallet so you can access it.

You can also check the total_joiner_count we implemented, each unique contribution will increment the counter, to see how many people participated in our crowdsale.

That’s it for SCORE! We went in-depth covering SCORE architecture, data format, syntax and best practices. We talked about SCORE sandbox policies and limitations. We also covered ICON’s token standard IRC-2, and created our own token MST and a crowdsale contract. At this point, you should have a fairly strong technical understanding of ICON development, in the next tutorial, let’s build a real ICON DAPP!

Level Up!

Programming Concepts ★★★☆☆

Python ★★★★

ICON SDK ★★★★★

T-Bears (Local Emulated ICON Node) ★★★★★

SCORE (Smart Contract) ★★★★★

Follow me on Twitter for most up-to-date ICON related content: