Creating ERC20 Supply

The standard interface implemented by tokens built on Starknet comes from the popular token standard on Ethereum called ERC20. EIP20, from which ERC20 contracts are derived, does not specify how tokens are created. This guide will go over strategies for creating both a fixed and dynamic token supply.

Fixed Supply

Let’s say we want to create a token named MyToken with a fixed token supply. We can achieve this by setting the token supply in the constructor which will execute upon deployment.

#[starknet::contract]
mod MyToken {
    use starknet::ContractAddress;
    use openzeppelin::token::erc20::ERC20;

    #[storage]
    struct Storage {}

    #[constructor]
    fn constructor(
        self: @ContractState,
        initial_supply: u256,
        recipient: ContractAddress
    ) {
        let name = 'MyToken';
        let symbol = 'MTK';

        let mut unsafe_state = ERC20::unsafe_new_contract_state();
        ERC20::InternalImpl::initializer(ref unsafe_state, name, symbol);
        ERC20::InternalImpl::_mint(ref unsafe_state, recipient, initial_supply);
    }
}

In the constructor, we’re first grabbing the ERC20 state and calling the ERC20 initializer to set the token name and symbol. Next, we’re calling the internal _mint function which creates initial_supply of tokens and allocates them to recipient. Since the internal _mint is not exposed in our contract, it will not be possible to create any more tokens. In other words, we’ve implemented a fixed token supply!

Dynamic Supply

ERC20 contracts with a dynamic supply include a mechanism for creating or destroying tokens. Let’s make a few changes to the almighty MyToken contract and create a minting mechanism.

#[starknet::contract]
mod MyToken {
    use starknet::ContractAddress;
    use openzeppelin::token::erc20::ERC20;

    #[storage]
    struct Storage {}

    #[constructor]
    fn constructor(self: @ContractState) {
        let name = 'MyToken';
        let symbol = 'MTK';

        let mut unsafe_state = ERC20::unsafe_new_contract_state();
        ERC20::InternalImpl::initializer(ref unsafe_state, name, symbol);
    }

    #[external(v0)]
    fn mint(
        self: @ContractState,
        recipient: ContractAddress,
        amount: u256
    ) {
        // This function is NOT protected which means
        // ANYONE can mint tokens
        let mut unsafe_state = ERC20::unsafe_new_contract_state();
        ERC20::InternalImpl::_mint(ref unsafe_state, recipient, amount);
    }
}

The exposed mint above will create amount tokens and allocate them to recipient. We now have our minting mechanism!

There is, however, a big problem. mint does not include any restrictions on who can call this function. For the sake of good practices, let’s implement a simple permissioning mechanism with Ownable.

#[starknet::contract]
mod MyToken {
    use starknet::ContractAddress;
    use openzeppelin::access::ownable::Ownable;
    use openzeppelin::token::erc20::ERC20;

    #[storage]
    struct Storage {}

    #[constructor]
    fn constructor(self: @ContractState, owner: ContractAddress) {
        // Set contract owner
        let mut unsafe_ownable = Ownable::unsafe_new_contract_state();
        Ownable::InternalImpl::initializer(ref unsafe_ownable, owner);

        // Initialize ERC20
        let name = 'MyToken';
        let symbol = 'MTK';

        let mut unsafe_erc20 = ERC20::unsafe_new_contract_state();
        ERC20::InternalImpl::initializer(ref unsafe_erc20, name, symbol);
    }

    #[external(v0)]
    fn mint(
        self: @ContractState,
        recipient: ContractAddress,
        amount: u256
    ) {
        // Set permissions with Ownable
        let unsafe_ownable = Ownable::unsafe_new_contract_state();
        Ownable::InternalImpl::assert_only_owner(@unsafe_ownable);

        // Mint tokens if called by the contract owner
        let mut unsafe_erc20 = ERC20::unsafe_new_contract_state();
        ERC20::InternalImpl::_mint(ref unsafe_erc20, recipient, amount);
    }
}

In the constructor, we pass the owner address to set the owner of the MyToken contract. The mint function includes assert_only_owner which will ensure that only the contract owner can call this function. Now, we have a protected ERC20 minting mechanism to create a dynamic token supply.

For a more thorough explanation of permission mechanisms, see Access Control.