Transfer Events on Aztec Token & Tokenized Vault
This post explains how Transfer events are emitted across the Token and Tokenized Vault contracts, where they fire, and why the design enables indexers to reconcile supply from public event data.
The Transfer Event
struct Transfer {
from: AztecAddress,
to: AztecAddress,
amount: u128,
}
Every public balance-changing operation emits a public Transfer(from, to, amount) event. Two sentinel addresses are used:
| Sentinel | Value | Meaning |
|---|---|---|
0x0 (AztecAddress::zero) |
Mint origin or burn destination | Standard ERC-20 convention for mints (from = 0) and burns (to = 0) |
PRIVATE_ADDRESS_MAGIC_VALUE |
sha224("PRIVATE_ADDRESS") → 0x1ea7e...3264 |
The counterpart of the balance change is a private note |
Why the Magic Value?
Invariants:
- Supply: The sum of all amounts where
from == 0x0minus the sum whereto == 0x0equals the currenttotal_supply. - Per-address balance: For any address
addr, the sum of amounts whereto == addrminus the sum wherefrom == addrequals that address’s public balance. (Private balances are shielded;PRIVacts as a sink/source for the private aggregate.)
Without a sentinel for the private side, we would need to expose an address when moving tokens between public and private. That would break privacy. Using a single, fixed magic value instead:
- Keeps the event log privacy-preserving for private counterparties
- Preserves the accountability invariants — indexers can compute
total_supplyand per-address public balances from public logs even when private counterparties remain shielded - Keeps
0x0exclusively for mints and burns, sototal_supplycalculations stay unambiguous
Token Contract: Where Events Are Emitted
Direct (User-Callable) Methods
| Method | Event | Rationale |
|---|---|---|
transfer_public_to_public(from, to, amount) |
Transfer(from, to, amount) |
Direct public→public move; both addresses are public |
transfer_public_to_commitment(from, commitment, amount) |
Transfer(from, PRIV, amount) |
Public balance decreases; recipient is a commitment (private) |
mint_to_public(to, amount) |
Via _mint_to_public |
Mint creates new supply and credits a public address |
mint_to_commitment(commitment, amount) |
Transfer(0x0, PRIV, amount) |
Mint to private; supply increases, recipient is private |
burn_public(from, amount) |
Via _burn_public |
Burn from public; supply decreases |
Internal Methods (Called by Self or Private Enqueues)
| Method | Event | Called By |
|---|---|---|
increase_public_balance_internal(to, amount) |
Transfer(PRIV, to, amount) |
transfer_private_to_public, transfer_private_to_public_with_commitment — private balance → public |
decrease_public_balance_internal(from, amount) |
Transfer(from, PRIV, amount) |
transfer_public_to_private — public balance → private |
increase_total_supply_internal(amount) |
Transfer(0x0, PRIV, amount) |
mint_to_private — mint to private |
decrease_total_supply_internal(amount) |
Transfer(PRIV, 0x0, amount) |
burn_private — burn from private |
_mint_to_public(to, amount) |
Transfer(0x0, to, amount) |
mint_to_public, deposit_public_to_public, issue_public_to_public, and vault settle methods |
_burn_public(from, amount) |
Transfer(from, 0x0, amount) |
burn_public, withdraw_public_to_public, redeem_public_to_public, and vault settle methods |
No Event
transfer_private_to_private— Pure private-to-private; no public balance change, no public event.transfer_private_to_commitment— Pure private-to-private; no public balance change, no public event.
Tokenized Vault: Where Events Are Emitted
The vault is a Token (shares). Its events follow the same rules. Asset transfers are on the asset contract; share mint/burn/transfer events are on the vault contract.
Asset vs Shares
Each vault operation usually involves:
- Asset contract —
Transferfor the underlying token - Vault (shares) contract —
Transferfor share mint/burn
Vault Public Methods (Direct Events)
| Method | Asset Events | Vault (Shares) Events |
|---|---|---|
deposit_public_to_public |
transfer_public_to_public(from, vault, assets) |
_mint_to_public → Transfer(0x0, to, shares) |
issue_public_to_public |
transfer_public_to_public(from, vault, max_assets) [+ optional refund] |
_mint_to_public → Transfer(0x0, to, shares) |
withdraw_public_to_public |
transfer_public_to_public(vault, to, assets) |
_burn_public → Transfer(from, 0x0, shares) |
redeem_public_to_public |
transfer_public_to_public(vault, to, assets) |
_burn_public → Transfer(from, 0x0, shares) |
Vault Settle Methods (Internal — Emit Share Events)
These are enqueued from private flows and emit share Transfer events when they run in public:
| Settle Method | Event | Reason |
|---|---|---|
settle_deposit_public_to_private_internal |
Transfer(0x0, PRIV, shares) |
Mint shares to private |
settle_deposit_private_to_private_internal |
Transfer(0x0, PRIV, shares) |
Mint shares to private |
settle_deposit_public_to_private_exact_internal |
Transfer(0x0, PRIV, max_shares) |
Mint shares to private |
settle_deposit_private_to_private_exact_internal |
Transfer(0x0, PRIV, max_shares) |
Mint shares to private |
settle_issue_public_to_private_internal |
Transfer(0x0, PRIV, shares) |
Mint shares to private |
settle_issue_private_to_private_exact_internal |
Transfer(0x0, PRIV, shares) |
Mint shares to private |
settle_withdraw_public_to_private_internal |
Via _burn_public |
Burn shares from public |
settle_withdraw_private_to_private_internal |
Transfer(PRIV, 0x0, shares) |
Burn shares from private |
settle_withdraw_private_to_public_exact_internal |
Transfer(PRIV, 0x0, shares) |
Burn shares from private |
settle_withdraw_private_to_private_exact_internal |
Transfer(PRIV, 0x0, shares) |
Burn shares from private |
settle_redeem_private_to_public_internal |
Transfer(PRIV, 0x0, shares) |
Burn shares from private |
settle_redeem_private_to_private_exact_internal |
Transfer(PRIV, 0x0, shares) |
Burn shares from private |
settle_redeem_public_to_private_exact_internal |
Via _burn_public |
Burn shares from public |
settle_deposit_private_to_public_internal |
Via _mint_to_public |
Mint shares to public |
settle_issue_private_to_public_exact_internal |
Via _mint_to_public |
Mint shares to public |
Asset events for private flows come from the underlying Token’s internal methods (transfer_private_to_public, transfer_public_to_private, etc.), which emit PRIV when the counterpart is private.
Quick Reference: Event Patterns
| Operation | from | to |
|---|---|---|
| Mint to public | 0x0 |
recipient |
| Mint to private | 0x0 |
PRIV |
| Public → public | sender | recipient |
| Public → private | sender | PRIV |
| Private → public | PRIV |
recipient |
| Private → private | (no event) | |
| Burn from public | sender | 0x0 |
| Burn from private | PRIV |
0x0 |
Summary
- Token: Events are emitted at every public balance change (direct or via internal helpers). Private-only moves (
transfer_private_to_private) emit nothing. - Vault: Same logic for shares; asset events are on the asset contract, share events on the vault.
- Magic value:
PRIVATE_ADDRESS_MAGIC_VALUErepresents the private side so both invariants hold (supply and per-address balance) while keeping private counterparties hidden.