Standards

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

supplyis minted to the deployer (msg.sender) at construction. To distribute differently, extend with a custom distribution action.
owneris stored in the contract and used by mint/burn if those extensions are added.
ERC-20 complianceis verified at compile time — the Covenant compiler emits a lint error if any mandatory selector is missing from the generated ABI.

Key takeaways