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 });
}
}