Skip to main content
This is an account-model token. Balances and transfers are visible on chain. Every transfer publishes (sender, recipient, amount). The participant graph is fully observable.Use this pattern for transparent registries (game economies, public reward programs) — not for private finance. For private payments, use Midnight’s native shielded primitives (sendShielded, mintShieldedToken, etc.) which keep balances and counterparties in ZK-committed UTXOs.
Authentication uses witness-derived keypairs, not ownPublicKey(). See Access Control for the rationale and Transfer for the building block this contract uses.

The Contract

How It Works

This ERC20 implementation combines the patterns from the basics tutorials. For detailed explanations, follow the linked sub-tutorials.

Authentication

Every state-changing circuit authenticates the caller by proving knowledge of a witness secret whose persistentHash equals a stored public key:
  • transfer, approve, burn → caller authenticates as a UserPublicKey
  • transferFrom → caller authenticates as the spender
  • mint, adminBurn → caller authenticates as the admin
See Access Control for why ownPublicKey() cannot be used here.

Initialization

The constructor seeds _admin and the sealed metadata fields (_name, _symbol, _decimals). sealed ledger fields can only be written from the constructor, so they cannot be modified after deployment — exactly the “set once, read forever” semantics ERC20 metadata wants.

Total supply invariant

sum(all balances) == _totalSupply is preserved: mint increases both, _burn decreases both, _transfer preserves both. See Overflow Protection.

Why no isKeyOrAddressZero / burnAddress helpers?

UserPublicKey is a Bytes<32> hash of a witness secret. There is no canonical “zero address” — the burn happens by deducting from a balance without crediting it elsewhere (_burn), and admin authority is enforced via the keypair check, not by checking for the default value.

Privacy trade-off

The keypair scheme authenticates the caller but does not hide them. _balances stores UserPublicKey → amount; every write reveals which key moved and by how much. For genuinely private tokens, use Midnight’s shielded primitives — they’re built on UTXOs and ZK commitments and don’t expose the participant graph.

Try It Yourself

1. Create project structure:
2. Save the contract at contracts/my-token.compact.3. Compile:
4. Deploy with the four constructor arguments (admin pubkey, name, symbol, decimals). The witness side is wired by your DApp:
  • getAdminSecret() — the admin’s private Bytes<32> (kept in DApp state)
  • getUserSecret() — each user’s private Bytes<32>
  • initialAdmin: AdminPublicKey — computed off chain as persistentHash([pad(32, "erc20:admin:v1"), adminSecretBytes])

What’s Next

NFT (ERC721)

Create unique, non-fungible tokens

Multi-Token (ERC1155)

Manage multiple token types in one contract