Skip to main content

Notes

A note is a record of shielded value the protocol tracks for one specific account. Your balance in the pool is never a single number: it is a set of notes, and each note can be spent independently. Deposits create notes, transfers consume notes and create new ones, and withdrawals consume notes to release value back to a public address.

Anatomy of a note

commitment The note's on-chain identifier: Poseidon(precommitment, label), where precommitment = Poseidon(noteAddressHash, tokenId, value, metadata).
value The amount, in wei or the smallest unit of the asset.
tokenId The asset address. ETH uses the placeholder 0xeeee…eeee, while an ERC20 note carries the token's contract address.
label The deposit lineage tag, Poseidon(precommitment, depositSecret), carried forward through transfers.
ownerAddress The EVM address that controls this note, or 0x0…0 for non-owned recipient notes pending an out-of-band claim.
noteSecret A 32-byte secret. It feeds noteAddressHash = Poseidon(ownerAddress, noteSecret), which flows into the precommitment and commitment. The nullifier is Poseidon(privateNullifyingKey, commitment) and is not derived from noteSecret.
noteAddressHash Poseidon(ownerAddress, noteSecret). Binds the note to a specific recipient.
isOwned Whether the local session controls this note: true for normal notes, false for draft recipient notes the session generated as a sender but hasn't claimed (for example, notes awaiting out-of-band delivery).
status One of INACTIVE, PENDING, ACTIVE, SPENT, REJECTED, or EXITED. See NoteManager.
createdAtBlock The on-chain block.timestamp when the commitment was inserted, as hex. Despite the name it is a timestamp, not a block number. State-proof witnesses recompute the timestamped leaf from it (see State tree).
spentAtBlock The block number when the nullifier was published, as hex. Null while the note is unspent.
txHash The hash of the note-creation transaction.
depositSecret An optional secret, bound into label = Poseidon(precommitment, depositSecret) at deposit time. It is present only on the note that originated its lineage.

Note lifecycle

  1. Created when a note enters the system through a deposit, as a transact output (either change or a recipient note), or through an import.
  2. INACTIVE for draft and unclaimed notes the session knows about but cannot yet spend.
  3. PENDING immediately after creation: a deposit waits for ASP attestation, while a transact output waits for chain inclusion.
  4. ACTIVE once attested or confirmed, at which point the note is spendable.
  5. SPENT when spending the note through a transact or withdraw publishes its nullifier on-chain.
  6. EXITED after a successful ragequit.
  7. REJECTED if the ASP refuses to attest, which is recoverable via ragequit.

Valid status transitions

NoteManager.updateNoteStatus enforces VALID_STATUS_TRANSITIONS and throws InvalidStatusTransitionError for any edge not listed below:

FromAllowed to
INACTIVEINACTIVE, PENDING, EXITED
PENDINGPENDING, ACTIVE, EXITED, REJECTED
ACTIVEACTIVE, SPENT, EXITED, REJECTED
SPENTSPENT
EXITEDEXITED
REJECTEDREJECTED, EXITED

When a note transitions to SPENT or EXITED, spentAtBlock is set if it is currently unset: spentAtBlock ??= currentBlock ?? bigintToHex(syncCursor).

Before spending: verify the selected inputs against PoolVault.spentNullifiers(nullifier). This catches notes that are already spent on-chain even when local state is stale after a partial flow. See Failure modes.

Why notes (and not balances)?

Tracking value as notes rather than one balance is what makes the privacy properties work.

  • Per-spend privacy: each spend reveals only a fresh nullifier, computed from the spender's private nullifying key and the note's commitment, so the chain learns that some note was spent but not which one.
  • Composability: the transact_NxM circuit family takes N input notes and produces M outputs, which lets you split and merge value freely.
  • Selective disclosure: because every note carries its own secret, handing an auditor one note's secret reveals exactly that payment and nothing else (see Selective disclosure).