Token Standards: ERC-20, ERC-721, ERC-1155
Implementing token standards for fungible tokens, NFTs, and multi-token contracts.
Token Standards: ERC-20, ERC-721, ERC-1155
Token standards define interfaces for creating and managing tokens on blockchains.
ERC-20: Fungible Tokens
ERC-20 is the standard for fungible tokens (interchangeable, like currency).
Core Functions
RUSTtrait ERC20 { fn total_supply(&self) -> u64; fn balance_of(&self, owner: &str) -> u64; fn transfer(&mut self, to: &str, amount: u64) -> Result<(), String>; fn transfer_from(&mut self, from: &str, to: &str, amount: u64) -> Result<(), String>; fn approve(&mut self, spender: &str, amount: u64) -> Result<(), String>; fn allowance(&self, owner: &str, spender: &str) -> u64; }
Events
Transfer(from, to, amount): Token transferApproval(owner, spender, amount): Approval granted
ERC-721: Non-Fungible Tokens (NFTs)
ERC-721 is the standard for NFTs (unique, non-interchangeable tokens).
Core Functions
RUSTtrait ERC721 { fn balance_of(&self, owner: &str) -> u64; fn owner_of(&self, token_id: u64) -> Option<String>; fn transfer_from(&mut self, from: &str, to: &str, token_id: u64) -> Result<(), String>; fn approve(&mut self, approved: &str, token_id: u64) -> Result<(), String>; fn get_approved(&self, token_id: u64) -> Option<String>; fn set_approval_for_all(&mut self, operator: &str, approved: bool) -> Result<(), String>; fn is_approved_for_all(&self, owner: &str, operator: &str) -> bool; fn safe_transfer_from(&mut self, from: &str, to: &str, token_id: u64) -> Result<(), String>; }
Metadata
NFTs include metadata (name, description, image URI) typically stored off-chain.
ERC-1155: Multi-Token Standard
ERC-1155 supports both fungible and non-fungible tokens in a single contract.
Core Functions
RUSTtrait ERC1155 { fn balance_of(&self, account: &str, token_id: u64) -> u64; fn balance_of_batch(&self, accounts: &[&str], token_ids: &[u64]) -> Vec<u64>; fn set_approval_for_all(&mut self, operator: &str, approved: bool) -> Result<(), String>; fn is_approved_for_all(&self, owner: &str, operator: &str) -> bool; fn safe_transfer_from( &mut self, from: &str, to: &str, token_id: u64, amount: u64, ) -> Result<(), String>; fn safe_batch_transfer_from( &mut self, from: &str, to: &str, token_ids: &[u64], amounts: &[u64], ) -> Result<(), String>; }
Implementation Example
RUSTuse std::collections::HashMap; struct ERC20Token { name: String, symbol: String, decimals: u8, total_supply: u64, balances: HashMap<String, u64>, allowances: HashMap<(String, String), u64>, } impl ERC20Token { fn new(name: String, symbol: String, initial_supply: u64) -> Self { let mut balances = HashMap::new(); balances.insert(String::from("owner"), initial_supply); ERC20Token { name, symbol, decimals: 18, total_supply: initial_supply, balances, allowances: HashMap::new(), } } fn transfer(&mut self, to: &str, amount: u64) -> Result<(), String> { let from = String::from("owner"); // Simplified self._transfer(&from, to, amount) } fn _transfer(&mut self, from: &str, to: &str, amount: u64) -> Result<(), String> { let from_balance = *self.balances.get(from).unwrap_or(&0); if from_balance < amount { return Err(String::from("Insufficient balance")); } *self.balances.entry(from.to_string()).or_insert(0) -= amount; *self.balances.entry(to.to_string()).or_insert(0) += amount; Ok(()) } fn approve(&mut self, spender: &str, amount: u64) -> Result<(), String> { let owner = String::from("owner"); self.allowances.insert((owner, spender.to_string()), amount); Ok(()) } fn transfer_from(&mut self, from: &str, to: &str, amount: u64) -> Result<(), String> { let spender = String::from("spender"); // Simplified let allowance = *self.allowances.get(&(from.to_string(), spender.clone())).unwrap_or(&0); if allowance < amount { return Err(String::from("Insufficient allowance")); } self.allowances.insert((from.to_string(), spender), allowance - amount); self._transfer(from, to, amount) } }
Use Cases
ERC-20
- Stablecoins: USDC, DAI
- Governance Tokens: UNI, COMP
- Utility Tokens: In-app currencies
ERC-721
- Digital Art: CryptoPunks, Bored Apes
- Collectibles: Trading cards, virtual items
- Identity: Domain names, certificates
ERC-1155
- Gaming: Multiple item types in one contract
- Marketplaces: Efficient batch transfers
- Hybrid: Mix of fungible and non-fungible
Best Practices
- Reentrancy Protection: Use checks-effects-interactions pattern
- Integer Overflow: Use checked arithmetic
- Access Control: Implement proper permissions
- Events: Emit events for all state changes
- Gas Optimization: Batch operations when possible
Security Considerations
- Overflow/Underflow: Use safe math libraries
- Reentrancy: Guard against recursive calls
- Access Control: Verify caller permissions
- Input Validation: Check all parameters
Code Examples
ERC-20 Implementation
Complete ERC-20 token implementation
use std::collections::HashMap;
struct ERC20Token {
name: String,
symbol: String,
total_supply: u64,
balances: HashMap<String, u64>,
allowances: HashMap<(String, String), u64>,
}
impl ERC20Token {
fn new(name: String, symbol: String, initial_supply: u64) -> Self {
let mut balances = HashMap::new();
balances.insert(String::from("owner"), initial_supply);
ERC20Token {
name,
symbol,
total_supply: initial_supply,
balances,
allowances: HashMap::new(),
}
}
fn total_supply(&self) -> u64 {
self.total_supply
}
fn balance_of(&self, owner: &str) -> u64 {
*self.balances.get(owner).unwrap_or(&0)
}
fn transfer(&mut self, from: &str, to: &str, amount: u64) -> Result<(), String> {
let balance = self.balance_of(from);
if balance < amount {
return Err(String::from("Insufficient balance"));
}
*self.balances.entry(from.to_string()).or_insert(0) -= amount;
*self.balances.entry(to.to_string()).or_insert(0) += amount;
Ok(())
}
fn approve(&mut self, owner: &str, spender: &str, amount: u64) -> Result<(), String> {
self.allowances.insert((owner.to_string(), spender.to_string()), amount);
Ok(())
}
fn allowance(&self, owner: &str, spender: &str) -> u64 {
*self.allowances.get(&(owner.to_string(), spender.to_string())).unwrap_or(&0)
}
fn transfer_from(&mut self, from: &str, to: &str, amount: u64) -> Result<(), String> {
let spender = String::from("spender"); // Simplified
let allowance = self.allowance(from, &spender);
if allowance < amount {
return Err(String::from("Insufficient allowance"));
}
self.approve(from, &spender, allowance - amount)?;
self.transfer(from, to, amount)
}
}
fn main() {
let mut token = ERC20Token::new(
String::from("MyToken"),
String::from("MTK"),
1000000,
);
println!("Token: {} ({})", token.name, token.symbol);
println!("Total supply: {}", token.total_supply());
println!("Owner balance: {}", token.balance_of("owner"));
// Transfer tokens
token.transfer("owner", "alice", 1000).unwrap();
println!("Alice balance: {}", token.balance_of("alice"));
// Approve and transfer_from
token.approve("alice", "bob", 500).unwrap();
println!("Allowance: {}", token.allowance("alice", "bob"));
}Explanation:
ERC-20 defines the standard interface for fungible tokens. Key features: transfer, approve (for delegated transfers), and balance tracking. This enables tokens to work with wallets, exchanges, and DeFi protocols.
ERC-721 NFT
Basic ERC-721 NFT implementation
use std::collections::HashMap;
struct ERC721Token {
name: String,
symbol: String,
token_owners: HashMap<u64, String>,
balances: HashMap<String, u64>,
token_approvals: HashMap<u64, String>,
operator_approvals: HashMap<(String, String), bool>,
next_token_id: u64,
}
impl ERC721Token {
fn new(name: String, symbol: String) -> Self {
ERC721Token {
name,
symbol,
token_owners: HashMap::new(),
balances: HashMap::new(),
token_approvals: HashMap::new(),
operator_approvals: HashMap::new(),
next_token_id: 1,
}
}
fn mint(&mut self, to: &str) -> u64 {
let token_id = self.next_token_id;
self.next_token_id += 1;
self.token_owners.insert(token_id, to.to_string());
*self.balances.entry(to.to_string()).or_insert(0) += 1;
token_id
}
fn balance_of(&self, owner: &str) -> u64 {
*self.balances.get(owner).unwrap_or(&0)
}
fn owner_of(&self, token_id: u64) -> Option<&String> {
self.token_owners.get(&token_id)
}
fn transfer_from(&mut self, from: &str, to: &str, token_id: u64) -> Result<(), String> {
let owner = self.owner_of(token_id)
.ok_or(String::from("Token does not exist"))?;
if owner != from {
return Err(String::from("Not the owner"));
}
// Remove from old owner
self.token_owners.insert(token_id, to.to_string());
*self.balances.entry(from.to_string()).or_insert(1) -= 1;
*self.balances.entry(to.to_string()).or_insert(0) += 1;
// Clear approval
self.token_approvals.remove(&token_id);
Ok(())
}
fn approve(&mut self, approved: &str, token_id: u64) -> Result<(), String> {
let owner = self.owner_of(token_id)
.ok_or(String::from("Token does not exist"))?;
self.token_approvals.insert(token_id, approved.to_string());
Ok(())
}
}
fn main() {
let mut nft = ERC721Token::new(
String::from("MyNFT"),
String::from("MNFT"),
);
// Mint NFTs
let token1 = nft.mint("alice");
let token2 = nft.mint("alice");
let token3 = nft.mint("bob");
println!("Minted tokens: {}, {}, {}", token1, token2, token3);
println!("Alice balance: {}", nft.balance_of("alice"));
println!("Bob balance: {}", nft.balance_of("bob"));
// Transfer NFT
nft.transfer_from("alice", "bob", token1).unwrap();
println!("After transfer - Alice balance: {}", nft.balance_of("alice"));
println!("After transfer - Bob balance: {}", nft.balance_of("bob"));
}Explanation:
ERC-721 defines the standard for NFTs. Each token has a unique ID and owner. NFTs can be transferred, approved for others to transfer, and tracked individually. This enables digital collectibles, art, and unique assets.
Exercises
Simple Token
Create a simple token with transfer functionality!
Starter Code:
use std::collections::HashMap;
struct Token {
balances: HashMap<String, u64>,
}
fn main() {
// Create token
// Transfer tokens
}