Chapter 6 / 15
ERC-20 Token
Six lines versus one hundred — the token keyword.
The token keyword is syntactic sugar that expands to a fully
compliant ERC-20 implementation. Covenant generates transfer,
transferFrom, approve, allowance,
balanceOf, totalSupply, and the required events —
all audited and correct by construction.
my_token.cov — using the token keyword
token MyToken {
name: "My Token";
symbol: "MTK";
decimals: 18;
supply: 1_000_000_000; // minted to deployer
owner: msg.sender;
}Equivalent generated interface (for reference only)
// This is what the compiler produces — you never write this manually.
// Shown here so you understand what the token keyword expands to.
record MyToken {
name_: text = "My Token";
symbol_: text = "MTK";
decimals_: u8 = 18;
total_: u256 = 1_000_000_000 * (10 ** 18);
owner: address;
balances: map(address => u256);
allowances: map(address => map(address => u256));
event Transfer(from: address, to: address, value: u256);
event Approval(owner: address, spender: address, value: u256);
action transfer(to: address, amount: u256) -> bool { ... }
action transferFrom(from: address, to: address, amount: u256) -> bool { ... }
action approve(spender: address, amount: u256) -> bool { ... }
view balanceOf(who: address) -> u256 { ... }
view allowance(owner: address, spender: address) -> u256 { ... }
view totalSupply() -> u256 { ... }
view name() -> text { ... }
view symbol() -> text { ... }
view decimals() -> u8 { ... }
}Extending the token with custom logic
token GovernanceToken {
name: "Governance Token";
symbol: "GOV";
decimals: 18;
supply: 100_000_000;
owner: msg.sender;
}
// Extend with a minting cap
record GovernanceTokenMinter {
token: address;
cap: u256 = 200_000_000 * (10 ** 18);
minted: u256;
error CapExceeded(requested: u256, remaining: u256);
action mint(to: address, amount: u256) only(self.owner) {
let remaining = self.cap - self.minted;
if amount > remaining {
revert_with CapExceeded(amount, remaining);
}
self.minted += amount;
// call into the token contract
GovernanceToken(self.token).mint(to, amount);
}
}Annotations
supply | is minted to the deployer (msg.sender) at construction. To distribute differently, extend with a custom distribution action. |
owner | is stored in the contract and used by mint/burn if those extensions are added. |
| ERC-20 compliance | is verified at compile time — the Covenant compiler emits a lint error if any mandatory selector is missing from the generated ABI. |
Key takeaways
- Use
tokenfor any standard fungible token — six lines replaces ~100 lines of Solidity. - The generated contract is identical to a hand-written ERC-20 — no abstraction overhead.
- Extensions (minting, burning, permit) are added by composing a separate record.