PrivacyPoolsContract stores all the state your application needs to construct valid witnesses, monitor pool activity, and verify that a coin has or has not been spent. Understanding this state model is essential when building deposit, withdrawal, or transfer flows — the getters described here are the primary read interface between your application and the pool.
State overview
| State | Getter | Purpose |
|---|---|---|
| Commitment leaves | get_commitments | The public commitment hashes for every leaf inserted into the Merkle tree |
| Leaf count | get_commitment_count | The total number of leaves currently stored in the tree |
| Leaf ephemeral keys | get_leaf_ephemeral(index) | The ephemeral BabyJubJub public key stored alongside each leaf, used for recipient-side shared-secret recovery |
| Merkle root | get_merkle_root | The current root of the commitment tree |
| Merkle frontier | get_pairwise_frontier | The pairwise LeanIMT insertion state used to compute the next root efficiently |
| Root history | is_known_root(root) | Determines whether a given root is still within the accepted history window |
| Nullifier state | is_nulifier_hash_consumed(hash) | Returns true if the nullifier hash has been permanently consumed by a prior transact call |
| Token balance | get_token_balance | The public token balance currently held by the pool contract |
| Public slot config | get_public_slot_config | The public input and output slot counts configured for the circuit |
Reading pool state for witness construction
Before you can generate a proof for a withdrawal or transfer, your application needs a consistent snapshot of the current tree. You must read at least the Merkle root, the full list of commitments, and the leaf count from the contract before building the witness.Read
get_merkle_root and get_commitments as close together as possible. Another transaction landing between your two reads could change the root. If the root you used to build your proof has aged out of history by the time your transaction is submitted, the contract will reject it — rebuild your witness with a fresher root.Root history
The contract does not require your proof to reference the most recent root. It accepts proofs built against any root that is still present in the root history ring buffer. This is important for applications where multiple users may be submitting transactions concurrently — a root that was current when you started building your proof may no longer be the latest root by the time your transaction lands, but it will still be accepted as long as it is within the history window.is_known_root getter lets you check whether a given root is still in the history buffer before submitting. If is_known_root returns false, your proof will be rejected and you need to rebuild your witness from a more recent root.
Commitment insertion
Every successfultransact call stores either zero output commitments or exactly two output commitments. The contract never stores exactly one commitment — a transaction that provides exactly one non-zero output commitment is rejected. This aligns the on-chain state model with the circuit’s two-output design.
Alongside each commitment leaf, the contract stores a leaf ephemeral key: a BabyJubJub public key that the recipient uses to recover the ECDH shared secret needed to decrypt the coin. You can fetch the ephemeral key for any leaf index using get_leaf_ephemeral(index).
If you are building a wallet that needs to scan for coins sent to a stealth address, iterate over all leaf indices using
get_commitment_count and get_leaf_ephemeral(index) to recover the shared secret for each leaf and check whether it corresponds to a coin owned by your user.Nullifier checks
When a coin is spent in atransact call, its nullifier hash is permanently recorded on-chain. The contract enforces the following process:
- The circuit publishes nullifier hashes in the public signals.
- Before verifying the proof, the contract checks every nullifier hash against stored nullifier state.
- Any nullifier hash that is already marked consumed causes the entire transaction to revert.
- After the proof verifies successfully, the contract marks all input nullifier hashes as consumed.
is_nulifier_hash_consumed(hash).
Token legs
Public token movement happens insidetransact as part of the same atomic transaction that verifies the proof and updates state. There are two possible directions:
| Token leg | Direction |
|---|---|
| Public deposit | Tokens move from the from address into the pool contract |
| Public withdrawal | Tokens move from the pool contract to the public Stellar account encoded in the public signals |