Back to Learn
applications

ERC-20 Token

Build a complete ERC-20 token contract following the standard interface.

ERC-20 Token

ERC-20 is the standard interface for fungible tokens on Ethereum. Any contract following this standard is an ERC-20 token.

Core Functions

- totalSupply() - Returns total token supply

- balanceOf(address) - Gets account balance

- transfer(to, amount) - Transfers tokens

- approve(spender, amount) - Approves spending

- allowance(owner, spender) - Checks allowance

- transferFrom(from, to, amount) - Transfers on behalf

Storage Structure

``rust

#[storage]

pub struct Erc20 {

balances: StorageMap,

allowances: StorageMap>,

total_supply: StorageU256,

}

`

Events

- Transfer(from, to, amount)

- Approval(owner, spender, amount)`

Code Example

#![cfg_attr(not(feature = "export-abi"), no_main)]
extern crate alloc;

use stylus_sdk::prelude::*;
use stylus_sdk::{evm, msg};
use stylus_sdk::storage::{StorageMap, StorageU256};
use alloy_primitives::{Address, U256};
use alloy_sol_types::sol;

sol! {
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);

    error InsufficientBalance(uint256 available, uint256 required);
    error InsufficientAllowance(uint256 available, uint256 required);
}

#[derive(SolidityError)]
pub enum Erc20Error {
    InsufficientBalance(InsufficientBalance),
    InsufficientAllowance(InsufficientAllowance),
}

#[storage]
#[entrypoint]
pub struct Erc20 {
    balances: StorageMap<Address, U256>,
    allowances: StorageMap<Address, StorageMap<Address, U256>>,
    total_supply: StorageU256,
}

#[public]
impl Erc20 {
    pub fn total_supply(&self) -> U256 {
        self.total_supply.get()
    }

    pub fn balance_of(&self, account: Address) -> U256 {
        self.balances.get(account)
    }

    pub fn transfer(&mut self, to: Address, amount: U256) -> Result<bool, Erc20Error> {
        let sender = msg::sender();
        self._transfer(sender, to, amount)?;
        Ok(true)
    }

    pub fn approve(&mut self, spender: Address, amount: U256) -> bool {
        let owner = msg::sender();
        self.allowances.setter(owner).insert(spender, amount);
        evm::log(Approval { owner, spender, value: amount });
        true
    }

    pub fn allowance(&self, owner: Address, spender: Address) -> U256 {
        self.allowances.getter(owner).get(spender)
    }

    pub fn transfer_from(
        &mut self,
        from: Address,
        to: Address,
        amount: U256
    ) -> Result<bool, Erc20Error> {
        let spender = msg::sender();
        let current_allowance = self.allowance(from, spender);

        if current_allowance < amount {
            return Err(Erc20Error::InsufficientAllowance(InsufficientAllowance {
                available: current_allowance,
                required: amount,
            }));
        }

        self.allowances.setter(from).insert(spender, current_allowance - amount);
        self._transfer(from, to, amount)?;
        Ok(true)
    }

    fn _transfer(&mut self, from: Address, to: Address, amount: U256) -> Result<(), Erc20Error> {
        let from_balance = self.balances.get(from);

        if from_balance < amount {
            return Err(Erc20Error::InsufficientBalance(InsufficientBalance {
                available: from_balance,
                required: amount,
            }));
        }

        self.balances.insert(from, from_balance - amount);
        self.balances.insert(to, self.balances.get(to) + amount);

        evm::log(Transfer { from, to, value: amount });
        Ok(())
    }

    pub fn mint(&mut self, to: Address, amount: U256) {
        self.balances.insert(to, self.balances.get(to) + amount);
        self.total_supply.set(self.total_supply.get() + amount);
        evm::log(Transfer { from: Address::ZERO, to, value: amount });
    }
}

Key Points

  • Implements standard ERC-20 interface
  • balances mapping stores token balances
  • allowances nested mapping for approvals
  • Transfer and Approval events required
  • Internal _transfer helper for code reuse