> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metalntwx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Notes and Spending

> How notes represent value, how spending is authorized, and how the pool tracks note commitments, spends, and input locks

## Notes and ownership

MIP410 represents shielded value as individual notes, each specifying an
asset, amount, and recipient address. It follows an unspent transaction output
(UTXO) model where spending consumes existing notes and creates new ones. All of a note's
contents are fixed when it is created, including the recipient address. A payment
consumes its input notes in full and creates a recipient note. If the total input
value exceeds the payment amount, the remainder returns to the sender in a newly created
change note.

The recipient address determines which spending key can authorize use of
the note. The circuit checks that the note's address is associated with the
key used to verify the spending authorization. A wallet's shielded balance
comes from the unspent notes it controls. Notes reserved by an initiated
but unresolved payment still exist. Their value is unavailable for another
spend while the hold is pending.

## Note contents

A note is defined by a set of private information, known as the note opening. It is
used to compute the note's public representation, called its note commitment.
The opening is encoded as eleven field elements, grouped below by purpose.

| Fields | Purpose |
| - | - |
| `amount` | The value represented by the note. |
| `asset_id` | The MIP20 token that the value belongs to. |
| `d` | The diversifier. Together with `pk_x` and `pk_y`, it forms the recipient address. See [Keys and Addresses](https://docs.metalntwx.com/learn/mip410/key-and-addresses.md#diversified-addresses). |
| `pk_x`, `pk_y` | The two coordinates of that address's curve point. |
| `secret` | Per-note randomness that makes otherwise identical notes produce different commitments. |
| `P`, `bitmap_0`, `bitmap_1`, `depth` | Four reserved elements, fixed to zero. |
| `shieldTimestamp` | A timestamp supplied at shielding and checked for freshness. New payment and change notes inherit the minimum timestamp of their input notes. |

The reserved elements of the note opening have no active note functionality.
The timestamp carries forward information from the oldest input timestamp. The [Shielded Note
reference](https://docs.metalntwx.com/reference/privacy/shielded-note.md) specifies the field encoding and
commitment order.

The note commitment is the Poseidon2 hash of the encoded opening, commonly
displayed as a hexadecimal string. The fields are hashed in this order:

```text theme={null}
commitment = Poseidon2(
    amount, asset_id, d, pk_x, pk_y, secret,
    0, 0, 0, 0, shieldTimestamp
)
```

Poseidon2 is a one-way hash function, so the commitment cannot be reversed
to recover the note opening.

## Merkle tree

MIP410 uses a Merkle tree as a canonical onchain record of note
commitments created through successful transactions. Before adding
a new commitment, the pool verifies the required proofs and transaction
checks, including public backing or conservation of value from spent
input notes. When a note is spent, a zero-knowledge proof establishes
that its commitment belongs to this tree without revealing which
commitment is being used. This proves that the note was recorded
through an accepted transaction.

The diagram shows part of the tree, with illustrative hexadecimal hash
previews:

![A partial commitment tree before append, with recorded notes A, B, C and an empty leaf. Hash previews are illustrative.](https://mintcdn.com/nexus-ed339086/CdWwGT0bcT9zD-Uw/images/mip410/commitment-tree-before.svg?fit=max\&auto=format\&n=CdWwGT0bcT9zD-Uw\&q=85\&s=e63cbf0f5adbaf4bce2c53a1ed4e11eb)

The note commitments are themselves hashes of the private note openings and
each parent is the hash of its two children in their left-right order. For
the branch shown above, construction begins with:

```text theme={null}
A = Poseidon2(note_A_opening)
B = Poseidon2(note_B_opening)
H_AB = Poseidon2(A, B)
H_CE = Poseidon2(C, EMPTY_LEAF)
parent = Poseidon2(H_AB, H_CE)
```

Appending a new commitment changes its leaf and the hashes on the path
above it. When commitment D fills the empty position, `H_CE` becomes `H_CD`,
and the changes propagate up to a new root. Commitments A, B, and C and their
positions remain unchanged, as does `H_AB`. The pool stores the new root as
part of the transaction's onchain state changes and the Merkle root commits
to the recorded leaves and their positions.

![Appending note D changes H\_CD and its path to the root; A, B, C, H\_AB and other subtrees remain unchanged. Hash previews are illustrative.](https://mintcdn.com/nexus-ed339086/CdWwGT0bcT9zD-Uw/images/mip410/commitment-tree-after.svg?fit=max\&auto=format\&n=CdWwGT0bcT9zD-Uw\&q=85\&s=46dc120e54391856e814ef2f421db89f)

Membership can be checked using one sibling hash at each level. Starting
with a leaf, the checker combines it with its sibling, hashes the pair, and
repeats the process up to the root. The sibling hashes and the leaf's position
form the Merkle path. Checking A requires B, `H_CD`, and the sibling hashes
at the omitted levels. If the calculated root matches an accepted root,
the path establishes that the commitment belongs to that tree.

Changing the note details e.g. amount changes its commitment, so the altered
note will fail to reconstruct the recorded root using that path.

## Merkle Path

When a note is created, the pool publishes its commitment and permanent
position, called its leaf index, in an onchain event. Proving that the note
belongs to the tree, such as when spending it, requires the sibling hashes.
Together with the leaf index, the sibling hashes form its Merkle path.

An offchain tree mirror reconstructs the commitment tree from the pool's past
commitment events and updates it as new commitments are appended. The note's
leaf index identifies its position in that tree, from which the mirror obtains
the sibling hashes needed for a proof. One shared tree supplies paths for many
notes without replaying the chain history for each transaction or maintaining
a separate set of hashes for every note.

## Nullifiers and spent notes

The commitment tree is an append-only Merkle tree and spending a note does not
remove its commitment from the tree. The pool maintains a spent set of nullifiers
identifying notes that have already been spent.
A nullifier is derived deterministically from the
[nullifier key](https://docs.metalntwx.com/learn/mip410/key-and-addresses.md#delegating-capabilities) and
the note's leaf index, so the same note always produces the same nullifier.

```text theme={null}
nullifier = Poseidon2(nullifier_key, leaf_index, DOMAIN_NULLIFIER)
```

`DOMAIN_NULLIFIER` is a fixed label in the hash calculation, called a domain
tag. Its purpose becomes clear when we introduce input locks below.

The zero-knowledge proof establishes that the nullifier was derived correctly
for the note being spent. At payment acceptance or unshielding, the pool checks
whether the nullifier is already in the spent set. If it is, the transaction
is rejected. Otherwise, once all transaction checks pass, the pool
records the nullifier as spent.

## Input locks and availability

Initiation reserves notes without spending them, so the pool needs to track
reservations separately from completed spends. The pool records an input lock
for each note reserved by an initiated payment. It rejects another initiation
or unshielding attempt using a locked note, even though that note has not yet
been spent.

```text theme={null}
input_lock_id = Poseidon2(nullifier_key, leaf_index, DOMAIN_LOCK)
```

An input lock ID is derived from the same nullifier key and leaf index,
using a different domain tag, `DOMAIN_LOCK`. This produces a separate
identifier that can be published at initiation without revealing the
note's nullifier.

Rejection or reclaim releases the locks so the notes become available again.
Acceptance leaves the locks marked and records the notes as spent.

## Payment effects and hold IDs

The payment effect specifies the state changes to apply if a payment is
accepted. It contains the input nullifiers to record as spent and the
recipient and change note commitments to append to the tree.

At initiation, the sender commits to these values by publishing their hash,
called the payment effect hash. This fixes the proposed result while keeping
the values hidden until acceptance.

```text theme={null}
payment_effect_hash = Poseidon2(
    DOMAIN_EFFECT, nullifier_1, nullifier_2,
    recipient_note_commitment, change_note_commitment
)
```

`DOMAIN_EFFECT` is the domain tag for this hash.

The pool uses this hash as the hold ID and records the payment's input locks,
recipient VASP binding commitment, payload commitment, and deadline under it.
The payload commitment binds the private payment record and identifiers to
this payment. These records let the pool track and resolve the pending payment.

At acceptance, the payment effect is disclosed and the pool checks that its
hash matches the hold ID before applying the state changes. This prevents
acceptance from substituting different input or output notes. [Privacy Guarantees](https://docs.metalntwx.com/learn/mip410/privacy-guarantees.md) explains what observers
can infer from these public records.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.