Deploying ERC-20 Tokens on Starknet with Caïro
The contract we are going to deploy is below. Give it the name erc20.cairo
#[starknet::contract]
mod cairo_token {
use starknet::ContractAddress;
use starknet::get_caller_address;
#[storage]
struct Storage {
owner: ContractAddress,
name: felt252,
symbol: felt252,
total_supply: u256,
decimal: u8,
balances: LegacyMap::<ContractAddress, u256>,
allowances: LegacyMap::<(ContractAddress, ContractAddress), u256>,
}
#[constructor]
fn constructor(ref self: ContractState, _owner:ContractAddress, _name: felt252, _symbol: felt252, _decimal: u8, _total_supply: u256) {
self.name.write(_name);
self.symbol.write(_symbol);
self.decimal.write(_decimal);
self.owner.write(_owner);
}
#[external(v0)]
#[generate_trait]
impl CairoTokenTraitImpl of CairoTokenTrait {
fn name(self: @ContractState) -> felt252 {
self.name.read()
}
fn owner(self: @ContractState) -> ContractAddress {
self.owner.read()
}
fn symbol(self: @ContractState) -> felt252 {
self.symbol.read()
}
fn totalSupply(self: @ContractState) -> u256 {
self.total_supply.read()
}
fn mint(ref self: ContractState, to: ContractAddress, amount: u256) {
assert(get_caller_address() == self.owner.read(), 'Invalid caller');
let new_total_supply = self.total_supply.read() + amount;
self.total_supply.write(new_total_supply);
let new_balance = self.balances.read(to) + amount;
self.balances.write(to, new_balance);
}
fn transfer(ref self: ContractState, to: ContractAddress, amount: u256){
let caller: ContractAddress = get_caller_address();
self._transfer(caller, to, amount);
}
fn transferFrom(ref self: ContractState, sender: ContractAddress, to: ContractAddress, amount: u256){
let caller = get_caller_address();
assert(self.allowances.read((sender, caller)) >= amount, 'No allowance');
self.allowances.write((sender, caller), self.allowances.read((sender, caller)) - amount);
self._transfer(sender, to, amount);
}
fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) {
let caller: ContractAddress = get_caller_address();
let mut prev_allowance: u256 = self.allowances.read((caller, spender));
self.allowances.write((caller, spender), prev_allowance + amount);
}
fn allowance(self: @ContractState, owner: ContractAddress, spender: ContractAddress) -> u256 {
self.allowances.read((owner, spender))
}
fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 {
self.balances.read(account)
}
}
#[generate_trait]
impl PrivateFunctions of CairoTokenPrivateFunctionsTrait {
fn _transfer(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256) {
assert(self.balances.read(sender) >= amount, 'Insufficient bal');
self.balances.write(recipient, self.balances.read(recipient) + amount);
self.balances.write(sender, self.balances.read(sender) - amount)
}
}
}
In the standard lib.cairo
file remove all the old code and replace it with the following:
mod erc20;
Updating Scarb.toml
Update the Scarb.toml
as follows:
[package]
name = "starknetmetaschool"
version = "0.1.0"
# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest
[dependencies]
starknet = ">=2.0.1"
[cairo]
sierra-replace-ids = true
[[target.starknet-contract]]
sierra = true
When we deploy our token we need to pass in arguments into the constructor such as the name and the symbol in hex format. We can do this with changing the parameters.
util.py
def str_to_felt(text):
if len(text) > 31:
raise Exception("Text length too long to convert to felt.")
print(int.from_bytes(text.encode(), "big"))
str_to_felt("Ian Token") #Token Name
str_to_felt("IAN") #Symbol
python3 util.py
1353632901796933100910
4800846
Deploy of account and keystore
account:
starkli account fetch $PUBLIC_KEY --output ~/.starkli-wallets/deployer/account.json
export STARKNET_ACCOUNT=~/.starkli-wallets/deployer/account.json
keystore:
starkli signer keystore from-key ~/.starkli-wallets/deployer/keystore.json
export STARKNET_ACCOUNT=~/.starkli-wallets/deployer/keystore.json
Build the token
scarb build
$ starkli declare target/dev/{PROJECT_NAME}_cairo_token.sierra.json
Enter keystore password: ****
Declaring Cairo 1 class: 0x03ec6710c7d768dd35e30040c062c9c732ec84e126c02d7bee3347e880896ad6
CASM class hash: 0x0082cc8b08adfeb6611caef16cc7e2c579088f6afbe836617af16dbcf96fd7cd
Contract declaration transaction: 0x0740775d7aebf44c406e9c60ec5fedc3e9361330a43b778dd84466f65a780711
Class hash declared:
0x03ec6710c7d768dd35e30040c062c9c732ec84e126c02d7bee3347e880896ad6
- Class hash =
0x03ec6710c7d768dd35e30040c062c9c732ec84e126c02d7bee3347e880896ad6
- Public Key =
0x078a8628a18bE44235c1AA9C9f2EB046BCb77FcA22aAa49A1AEcEa7CB614f9d8
- Token Name =
“Ian Token”
- Symbol =
“IAN”
- Decimals =
18
- Minted value =
1000000000000000
- Salt value =
0
starkli deploy 0x0030d73892336dcc072b7eb4f98650713e59a2e41f08965bedebf41765e4ddc7 0x078a8628a18bE44235c1AA9C9f2EB046BCb77FcA22aAa49A1AEcEa7CB614f9d8 1353632901796933100910 4800846 18 1000000000000000 0
Deploy the Token
$ starkli deploy 0x0030d73892336dcc072b7eb4f98650713e59a2e41f08965bedebf41765e4ddc7 0x078a8628a18bE44235c1AA9C9f2EB046BCb77FcA22aAa49A1AEcEa7CB614f9d8 1353632901796933100910 4800846 18 1000000000000000 0
Deploying class 0x0030d73892336dcc072b7eb4f98650713e59a2e41f08965bedebf41765e4ddc7 with salt 0x0264d20d7756c7bfca2ac65092a312517b8a3e61fdde9b1b2d24dea983d50f03...
The contract will be deployed at address 0x043c38caaa4d15cc11b4adda3617b18021adcdeba74a3eacce6aef68b8b05a8c
Contract deployment transaction: 0x040585c76018618fbcb98af9dcbaf02a95f581e471a7695d7cc789c84ca2a988
Contract deployed:
0x043c38caaa4d15cc11b4adda3617b18021adcdeba74a3eacce6aef68b8b05a8c