Token Transfer Events Post

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 == 0x0 minus the sum where to == 0x0 equals the current total_supply.
  • Per-address balance: For any address addr, the sum of amounts where to == addr minus the sum where from == addr equals that address’s public balance. (Private balances are shielded; PRIV acts 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_supply and per-address public balances from public logs even when private counterparties remain shielded
  • Keeps 0x0 exclusively for mints and burns, so total_supply calculations 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:

  1. Asset contractTransfer for the underlying token
  2. Vault (shares) contractTransfer for 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_publicTransfer(0x0, to, shares)
issue_public_to_public transfer_public_to_public(from, vault, max_assets) [+ optional refund] _mint_to_publicTransfer(0x0, to, shares)
withdraw_public_to_public transfer_public_to_public(vault, to, assets) _burn_publicTransfer(from, 0x0, shares)
redeem_public_to_public transfer_public_to_public(vault, to, assets) _burn_publicTransfer(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_VALUE represents the private side so both invariants hold (supply and per-address balance) while keeping private counterparties hidden.
4 Likes