ERC20
ERC20
The ERC-20 introduces a standard for Fungible Tokens, in other words, they have a property that makes each Token be exactly the same (in type and value) as another Token.
Here is an example of a simple implementation:
use starknet::ContractAddress;
#[starknet::interface]
trait IERC20<TContractState> {
fn get_name(self: @TContractState) -> felt252;
fn get_symbol(self: @TContractState) -> felt252;
fn get_decimals(self: @TContractState) -> u8;
fn get_total_supply(self: @TContractState) -> felt252;
fn balance_of(self: @TContractState, account: ContractAddress) -> felt252;
fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> felt252;
fn transfer(ref self: TContractState, recipient: ContractAddress, amount: felt252);
fn transfer_from(
ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: felt252
);
fn approve(ref self: TContractState, spender: ContractAddress, amount: felt252);
fn increase_allowance(ref self: TContractState, spender: ContractAddress, added_value: felt252);
fn decrease_allowance(
ref self: TContractState, spender: ContractAddress, subtracted_value: felt252
);
}
#[starknet::contract]
mod erc_20 {
use zeroable::Zeroable;
use starknet::get_caller_address;
use starknet::contract_address_const;
use starknet::ContractAddress;
#[storage]
struct Storage {
name: felt252,
symbol: felt252,
decimals: u8,
total_supply: felt252,
balances: LegacyMap::<ContractAddress, felt252>,
allowances: LegacyMap::<(ContractAddress, ContractAddress), felt252>,
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
Transfer: Transfer,
Approval: Approval,
}
#[derive(Drop, starknet::Event)]
struct Transfer {
from: ContractAddress,
to: ContractAddress,
value: felt252,
}
#[derive(Drop, starknet::Event)]
struct Approval {
owner: ContractAddress,
spender: ContractAddress,
value: felt252,
}
#[constructor]
fn constructor(
ref self: ContractState,
recipient: ContractAddress,
name: felt252,
decimals: u8,
initial_supply: felt252,
symbol: felt252
) {
self.name.write(name);
self.symbol.write(symbol);
self.decimals.write(decimals);
assert(!recipient.is_zero(), 'ERC20: mint to the 0 address');
self.total_supply.write(initial_supply);
self.balances.write(recipient, initial_supply);
self
.emit(
Event::Transfer(
Transfer {
from: contract_address_const::<0>(), to: recipient, value: initial_supply
}
)
);
}
#[external(v0)]
impl IERC20Impl of super::IERC20<ContractState> {
fn get_name(self: @ContractState) -> felt252 {
self.name.read()
}
fn get_symbol(self: @ContractState) -> felt252 {
self.symbol.read()
}
fn get_decimals(self: @ContractState) -> u8 {
self.decimals.read()
}
fn get_total_supply(self: @ContractState) -> felt252 {
self.total_supply.read()
}
fn balance_of(self: @ContractState, account: ContractAddress) -> felt252 {
self.balances.read(account)
}
fn allowance(
self: @ContractState, owner: ContractAddress, spender: ContractAddress
) -> felt252 {
self.allowances.read((owner, spender))
}
fn transfer(ref self: ContractState, recipient: ContractAddress, amount: felt252) {
let sender = get_caller_address();
self.transfer_helper(sender, recipient, amount);
}
fn transfer_from(
ref self: ContractState,
sender: ContractAddress,
recipient: ContractAddress,
amount: felt252
) {
let caller = get_caller_address();
self.spend_allowance(sender, caller, amount);
self.transfer_helper(sender, recipient, amount);
}
fn approve(ref self: ContractState, spender: ContractAddress, amount: felt252) {
let caller = get_caller_address();
self.approve_helper(caller, spender, amount);
}
fn increase_allowance(
ref self: ContractState, spender: ContractAddress, added_value: felt252
) {
let caller = get_caller_address();
self
.approve_helper(
caller, spender, self.allowances.read((caller, spender)) + added_value
);
}
fn decrease_allowance(
ref self: ContractState, spender: ContractAddress, subtracted_value: felt252
) {
let caller = get_caller_address();
self
.approve_helper(
caller, spender, self.allowances.read((caller, spender)) - subtracted_value
);
}
}
#[generate_trait]
impl StorageImpl of StorageTrait {
fn transfer_helper(
ref self: ContractState,
sender: ContractAddress,
recipient: ContractAddress,
amount: felt252
) {
assert(!sender.is_zero(), 'ERC20: transfer from 0');
assert(!recipient.is_zero(), 'ERC20: transfer to 0');
self.balances.write(sender, self.balances.read(sender) - amount);
self.balances.write(recipient, self.balances.read(recipient) + amount);
self.emit(Transfer { from: sender, to: recipient, value: amount });
}
fn spend_allowance(
ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: felt252
) {
let current_allowance = self.allowances.read((owner, spender));
}
}
fn approve_helper(
ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: felt252
) {
assert(!spender.is_zero(), 'ERC20: approve from 0');
self.allowances.write((owner, spender), amount);
self.emit(Approval { owner, spender, value: amount });
}
}
}
Try it out!
- Install the toolchain:
- For macOS and Linux, run our script:
curl -sL https://raw.githubusercontent.com/lambdaclass/cairo-by-example/main/build/installer.sh | bash -s 2.2.0
- For Windows and others, please see the official guide
- Run the example:
- Copy the example into a erc20.cairo file and run with:
%!s(<nil>) erc20.cairo