We have pre-built a template for this tutorial hosted on near.dev called "Token Contract in AssemblyScript". You can try running the application right away to see the code interacting with the blockchain — just click Open in Gitpod.
In this tutorial we’ll build this application from scratch.
heads up
The intention of this tutorial is to get you up to speed as quickly as possible on the platform but please be aware that *AssemblyScript is recommended for non-financial use cases only*.
ERC-20 standard
The ERC-20 standard is one of Ethereum‘s most popular standards. It defines how custom tokens should be built. This is the same standard which was used to issue most of the ICOs in 2017 and 2018.
Specifically, any new token must follow next interface:
totalSupply(): uint256
– View function that returns the total token supply.balanceOf(owner: address): uint256
– View function that returns the account balance of another account with addressowner
.transfer(to: address, value: uint256)
– Sendvalue
amount of tokens to addressto
.transferFrom(from: address, to: address, value: uint256)
– Sendvalue
amount of tokens from addressfrom
to addressto
.approve(spender: address, value: uint256)
– Allowspender
to withdraw from your account, multiple times, up to thevalue
amount. If this function is called again it overwrites the current allowance withvalue
allowance(owner: address, spender: address): uint256
– View function, returns the amount whichspender
is still allowed to withdraw fromowner
.
Note, NEAR currently doesn’t have native uint256
/uint128
so for this tutorial we going to use u64. The support is coming in a few weeks.
You can read the official ERC-20 Token Standard here.
Building basic token
If you haven’t done so already …
In a new browser tab or window
Open Examples
Select "Token Contract in AssemblyScript"
Click Open in gitpod
Let’s start by defining number of tokens (non-divisible units) our token will have. This is a decision point for the developer, and here we will assume we going to have 1,000,000
.
This way we can implement totalSupply
function:
In the file
assembly/main.ts
- You will find the following lines of code
(note: there may be other code and comments in the file as well)
let balances = new PersistentMap<string, u64>("b:");
let approves = new PersistentMap<string, u64>("a:");
let TOTAL_SUPPLY: u64 = 1000000;
export function totalSupply(): string {
return TOTAL_SUPPLY.toString();
}
The PersistentMap
is needed to keep track of the current and previously recorded balances and approvals.
We also need some way to initialize our contract to award all these tokens to initial owner. This also goes into how to change storage in the smart contract.
Also in the same file
assembly/main.ts
- You will find the following lines of code
(note: there may be other code and comments in the file as well)
export function init(initialOwner: string): void {
logging.log("initialOwner: " + initialOwner);
assert(storage.get<string>("init") == null, "Already initialized token supply");
balances.set(initialOwner, TOTAL_SUPPLY);
storage.set<string>("init", "done");
}
In example above we have a storage
object that is accessible by this contract to store data. It’s just a key-value storage. You can see the full implementation of the Storage
class in the near-sdk-as
source here.
Now that it’s initialized, we can check the balance of users.
Also in the same file
assembly/main.ts
- You will find the following lines of code
(note: there may be other code and comments in the file as well)
export function balanceOf(tokenOwner: string): u64 {
logging.log("balanceOf: " + tokenOwner);
if (!balances.contains(tokenOwner)) {
return 0;
}
let result = balances.getSome(tokenOwner);
return result;
}
Let’s build harder part, transferring money from current user to somebody else.
Also in the same file
assembly/main.ts
- You will find the following lines of code
(note: there may be other code and comments in the file as well)
export function transfer(to: string, tokens: u64): boolean {
logging.log("transfer from: " + context.sender + " to: " + to + " tokens: " + tokens.toString());
let fromAmount = balanceOf(context.sender);
assert(fromAmount >= tokens, "not enough tokens on account");
balances.set(context.sender, fromAmount - tokens);
balances.set(to, balanceOf(to) + tokens);
return true;
}
Note, this is not a view function and it can fail, so we need to return boolean
to indicate if it was successful. We first check the balance of context.sender
, which is the user that executed given transaction. If there is not enough money on the balance, we return false
. Otherwise, subtract value
from the balance of sender and increment balance of to
.
You can see the full implementation of the Context
class in the near-sdk-as
source here.
You’ll notice that we’ve also implemented transferFrom
, approve
and allowance
in the sample.
Once you deploy the contract you can run the following commands in the browser console.
To initialize the balance
contract.init({initialOwner: walletAccount.getAccountId()}
To check your balance
contract.balanceOf({tokenOwner: walletAccount.getAccountId()})
To transfer funds, e.g. to Bob
contract.transfer({to: 'bob.near', tokens: '1000'})
Ask it on StackOverflow!