ERC20
The ERC20 token standard is a specification for fungible tokens, a type of token where all the units are exactly equal to each other.
The ERC20.cairo
contract implements an approximation of EIP-20 in Cairo for StarkNet.
Interface
@contract_interface
namespace IERC20 {
func name() -> (name: felt) {
}
func symbol() -> (symbol: felt) {
}
func decimals() -> (decimals: felt) {
}
func totalSupply() -> (totalSupply: Uint256) {
}
func balanceOf(account: felt) -> (balance: Uint256) {
}
func allowance(owner: felt, spender: felt) -> (remaining: Uint256) {
}
func transfer(recipient: felt, amount: Uint256) -> (success: felt) {
}
func transferFrom(sender: felt, recipient: felt, amount: Uint256) -> (success: felt) {
}
func approve(spender: felt, amount: Uint256) -> (success: felt) {
}
}
ERC20 compatibility
Although StarkNet is not EVM compatible, this implementation aims to be as close as possible to the ERC20 standard, in the following ways:
-
It uses Cairo’s
uint256
instead offelt
. -
It returns
TRUE
as success. -
It accepts a
felt
argument fordecimals
in the constructor calldata with a max value of 2^8 (imitatinguint8
type). -
It makes use of Cairo’s short strings to simulate
name
andsymbol
.
But some differences can still be found, such as:
Usage
Use cases go from medium of exchange currency to voting rights, staking, and more.
Considering that the constructor method looks like this:
func constructor(
name: felt, // Token name as Cairo short string
symbol: felt, // Token symbol as Cairo short string
decimals: felt // Token decimals (usually 18)
initial_supply: Uint256, // Amount to be minted
recipient: felt // Address where to send initial supply to
) {
}
To create a token you need to deploy it like this:
erc20 = await starknet.deploy(
"openzeppelin/token/erc20/presets/ERC20.cairo",
constructor_calldata=[
str_to_felt("Token"), # name
str_to_felt("TKN"), # symbol
18, # decimals
(1000, 0), # initial supply
account.contract_address # recipient
]
)
As most StarkNet contracts, it expects to be called by another contract and it identifies it through get_caller_address
(analogous to Solidity’s this.address
).
This is why we need an Account contract to interact with it.
For example:
signer = MockSigner(PRIVATE_KEY)
amount = uint(100)
account = await starknet.deploy(
"contracts/Account.cairo",
constructor_calldata=[signer.public_key]
)
await signer.send_transaction(account, erc20.contract_address, 'transfer', [recipient_address, *amount])
Extensibility
ERC20 contracts can be extended by following the extensibility pattern.
The basic idea behind integrating the pattern is to import the requisite ERC20 methods from the ERC20 library and incorporate the extended logic thereafter.
For example, let’s say you wanted to implement a pausing mechanism.
The contract should first import the ERC20 methods and the extended logic from the Pausable library i.e. Pausable.pause
, Pausable.unpause
.
Next, the contract should expose the methods with the extended logic therein like this:
@external
func transfer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
recipient: felt, amount: Uint256
) -> (success: felt) {
Pausable.assert_not_paused(); // imported extended logic
return ERC20.transfer(recipient, amount); // imported library method
}
Note that extensibility does not have to be only library-based like in the above example.
For instance, an ERC20 contract with a pausing mechanism can define the pausing methods directly in the contract or even import the pausable
methods from the library and tailor them further.
Some other ways to extend ERC20 contracts may include:
-
Implementing a minting mechanism.
-
Creating a timelock.
-
Adding roles such as owner or minter.
For full examples of the extensibility pattern being used in ERC20 contracts, see Presets.
Presets
The following contract presets are ready to deploy and can be used as-is for quick prototyping and testing.
Each preset mints an initial supply which is especially necessary for presets that do not expose a mint
method.
ERC20 (basic)
The ERC20
preset offers a quick and easy setup for deploying a basic ERC20 token.
ERC20Mintable
The ERC20Mintable
preset allows the contract owner to mint new tokens.
ERC20Pausable
The ERC20Pausable
preset allows the contract owner to pause/unpause all state-modifying methods i.e.
transfer
, approve
, etc.
This preset proves useful for scenarios such as preventing trades until the end of an evaluation period and having an emergency switch for freezing all token transfers in the event of a large bug.
ERC20Upgradeable
The ERC20Upgradeable
preset allows the contract owner to upgrade a contract by deploying a new ERC20 implementation contract while also maintaining the contract’s state.
This preset proves useful for scenarios such as eliminating bugs and adding new features.
For more on upgradeability, see Contract upgrades.
API Specification
Methods
func name() -> (name: felt) {
}
func symbol() -> (symbol: felt) {
}
func decimals() -> (decimals: felt) {
}
func totalSupply() -> (totalSupply: Uint256) {
}
func balanceOf(account: felt) -> (balance: Uint256) {
}
func allowance(owner: felt, spender: felt) -> (remaining: Uint256) {
}
func transfer(recipient: felt, amount: Uint256) -> (success: felt) {
}
func transferFrom(sender: felt, recipient: felt, amount: Uint256) -> (success: felt) {
}
func approve(spender: felt, amount: Uint256) -> (success: felt) {
}
decimals
Returns the number of decimals the token uses - e.g.
8
means to divide the token amount by 100000000
to get its user representation.
Parameters: None.
Returns:
decimals: felt
totalSupply
Returns the amount of tokens in existence.
Parameters: None.
Returns:
totalSupply: Uint256
balanceOf
Returns the amount of tokens owned by account
.
Parameters:
account: felt
Returns:
balance: Uint256
allowance
Returns the remaining number of tokens that spender
will be allowed to spend on behalf of owner
through transferFrom
.
This is zero by default.
This value changes when approve
or transferFrom
are called.
Parameters:
owner: felt
spender: felt
Returns:
remaining: Uint256
transfer
Moves amount
tokens from the caller’s account to recipient
.
It returns 1
representing a bool if it succeeds.
Emits a Transfer event.
Parameters:
recipient: felt
amount: Uint256
Returns:
success: felt
transferFrom
Moves amount
tokens from sender
to recipient
using the allowance mechanism.
amount
is then deducted from the caller’s allowance.
It returns 1
representing a bool if it succeeds.
Emits a Transfer event.
Parameters:
sender: felt
recipient: felt
amount: Uint256
Returns:
success: felt
approve
Sets amount
as the allowance of spender
over the caller’s tokens.
It returns 1
representing a bool if it succeeds.
Emits an Approval event.
Parameters:
spender: felt
amount: Uint256
Returns:
success: felt
Events
func Transfer(from_: felt, to: felt, value: Uint256) {
}
func Approval(owner: felt, spender: felt, value: Uint256) {
}
Transfer (event)
Emitted when value
tokens are moved from one account (from_
) to another (to
).
Note that value
may be zero.
Parameters:
from_: felt
to: felt
value: Uint256
Approval (event)
Emitted when the allowance of a spender
for an owner
is set by a call to approve.
value
is the new allowance.
Parameters:
owner: felt
spender: felt
value: Uint256