06 — ERC-20 Token
A full ERC-20 compliant token in Covenant — about 60 lines of readable code.
contract ERC20Token {
field name: String
field symbol: String
field decimals: u8 = 18
field total_supply: u256
field balances: Map<Address, u256>
field allowances: Map<Address, Map<Address, u256>>
event Transfer(from: indexed Address, to: indexed Address, value: u256)
event Approval(owner: indexed Address, spender: indexed Address, value: u256)
error InsufficientBalance(available: u256, requested: u256)
error InsufficientAllowance(available: u256, requested: u256)
// Constructor-equivalent: init block runs once at deployment
init(name: String, symbol: String, initial_supply: u256) {
self.name = name;
self.symbol = symbol;
self.total_supply = initial_supply;
self.balances[msg.sender] = initial_supply;
emit Transfer(from: Address(0), to: msg.sender, value: initial_supply);
}
@view action name() -> String { return self.name; }
@view action symbol() -> String { return self.symbol; }
@view action decimals() -> u8 { return self.decimals; }
@view action totalSupply() -> u256 { return self.total_supply; }
@view
action balanceOf(account: Address) -> u256 {
return self.balances[account];
}
action transfer(to: Address, amount: u256) -> Bool {
_transfer(msg.sender, to, amount);
return true;
}
action approve(spender: Address, amount: u256) -> Bool {
self.allowances[msg.sender][spender] = amount;
emit Approval(owner: msg.sender, spender: spender, value: amount);
return true;
}
@view
action allowance(owner: Address, spender: Address) -> u256 {
return self.allowances[owner][spender];
}
action transferFrom(from: Address, to: Address, amount: u256) -> Bool {
let allowed = self.allowances[from][msg.sender];
require(allowed >= amount, InsufficientAllowance(allowed, amount));
self.allowances[from][msg.sender] -= amount;
_transfer(from, to, amount);
return true;
}
// Internal helper — not exposed in ABI
internal action _transfer(from: Address, to: Address, amount: u256) {
let bal = self.balances[from];
require(bal >= amount, InsufficientBalance(bal, amount));
self.balances[from] -= amount;
self.balances[to] += amount;
emit Transfer(from: from, to: to, value: amount);
}
}
Minting & burning extension
Add to the contract body:
action mint(to: Address, amount: u256) {
only(owner);
self.total_supply += amount;
self.balances[to] += amount;
emit Transfer(from: Address(0), to: to, value: amount);
}
action burn(amount: u256) {
let bal = self.balances[msg.sender];
require(bal >= amount, InsufficientBalance(bal, amount));
self.balances[msg.sender] -= amount;
self.total_supply -= amount;
emit Transfer(from: msg.sender, to: Address(0), value: amount);
}