# Transact circuit (NxM)

> The transact circuit is the NxM family of spend proofs. A proof states that the prover spent N input [notes](/concepts/notes) whose [nullifiers](/concepts/commitments-and-nullifiers) are now in the public set, produced M output notes whose commitments are now in the public set, and conserved value both globally (input value must equal output value plus the public `amountOut`) and per deposit label (the outputs of a label never exceed its inputs).

The transact circuit is the NxM family of spend proofs. A proof states that the prover spent N input [notes](/concepts/notes) whose [nullifiers](/concepts/commitments-and-nullifiers) are now in the public set, produced M output notes whose commitments are now in the public set, and conserved value both globally (input value must equal output value plus the public `amountOut`) and per deposit label (the outputs of a label never exceed its inputs).

## Variants

The protocol ships 25 variants, `transact_NxM` for every N and M from 1 to 5. The SDK picks the smallest variant that fits the call's input and output counts.

## Tree depths

`stateMaxDepth` 22. Bounds the state-tree membership proofs for input commitments.
`keystoreMaxDepth` 18. Bounds the keystore-tree membership proof for the spender's auth policy.
`associationSetMaxDepth` 18. Bounds the ASP root membership proofs for input labels.

If the SDK's witness depth doesn't match the circuit's compiled depth, proving fails with "Too many values for input signal" errors.

## Output composition rule

The circuit itself does not fix an output count per label. It enforces only global conservation, per-label conservation (the outputs of a label never exceed its inputs), and that every output label is inherited from some input.

The familiar one-recipient-plus-one-change shape is an SDK convention layered on top: each **distinct deposit label** spent emits one recipient note and one change note, no matter how many input notes carry that label, because multiple inputs with the same label aggregate into a single recipient-and-change pair. Under that convention, spending one label produces 2 outputs, which fits the 1x2 or 2x2 variants. Spending two labels produces 4 outputs, which fits 2x4, 3x4, and the larger variants. Spending three labels would need 6 outputs, and no variant goes that high, so such a cycle must be split across transactions.

## Public inputs

-   The N nullifiers, which become the spent markers.
-   The M output commitments.
-   The state root, keystore root, and ASP root used in the witness.
-   `amountOut` (the public withdrawal amount, 0 for a pure transfer).
-   `tokenIdOut` (the token being withdrawn).
-   `context` (a commitment to the withdrawal details).

## Private inputs

The spender supplies one set of account inputs per proof: ownerAddress, privateNullifyingKey, privateRevocableKey, the [keystore](/concepts/keystore-tree) membership proof (keystoreLeafIndex, keystoreSiblings, keystoreTreeDepth), and a single tokenId shared by all notes in the call.

Each input note then contributes its value, label, noteSecret, timestamp, a [state-tree](/concepts/state-tree) membership proof, and an ASP-tree membership proof. The timestamp is the block timestamp recorded when the commitment was inserted, and it matters because the state-tree leaf is `Poseidon(COMMITMENT_LEAF_TAG, commitment, timestamp)`, a three-input hash with a domain-separation tag. The input commitment itself is not a signal, because the circuit recomputes it from these fields.

Finally, each output contributes a noteAddressHash (the precomputed Poseidon hash of ownerAddress and noteSecret), an outputValue, and an outputLabel.

## Verifier routing

A `TransactVerifierRouter` contract holds one verifier address per (N, M) pair. Submitting to [`PoolVault`](/protocol/contracts/pool-vault)`.transact` routes to the appropriate verifier.

Source: `v2-monorepo/packages/circuits/circuits/templates/Transact.circom` and `circuits/main/transact/transact_NxM.circom`
