> For the complete documentation index, see [llms.txt](https://docs.tonadocash.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tonadocash.com/protocol-reference/anonymity-mining.md).

# Anonymity mining

Anonymity mining (AM) pays TONADO to users with commitments in a TONado pool's merkle tree. The reward subsidizes the anonymity set: more deposits sitting longer in the pool = better privacy for every other user. AM exists because privacy guarantees scale with the size + age of the active deposit set, and a token incentive is the cleanest way to keep that set healthy.

## TL;DR

* One `AmDistributor` contract per pool denomination.
* Each claim pays `denomination × amRateBps / 10000` TONADO (default 1% of denomination; hard-capped at 5%).
* Claim is a ZK proof - proves "I own a commitment in the tree with root R" without revealing which commitment.
* The AM nullifier is **cryptographically distinct** from the withdraw nullifier. A deposit can be both withdrawn AND used for one AM claim; neither blocks the other.

## Why AM does not break privacy

The withdraw flow's privacy property is "the on-chain transaction doesn't reveal which deposit funded which withdrawal." AM must preserve this. Two concrete sub-properties:

1. **An AM claim doesn't link to a specific deposit.** The ZK proof binds only `(merkleRoot, amNullifierHash, recipient, relayer, fee, refund)` - same shape as withdraw. The merkle root is one of many the distributor accepts; the AM nullifier hash is per-deposit but computed from a domain-separated hash.
2. **An AM claim doesn't link to the eventual withdraw and vice versa.** This is the critical design choice:

   * Withdraw nullifier hash = `Poseidon(1)(nullifier)` - single input.
   * AM nullifier hash = `Poseidon(2)(nullifier, AM_DOMAIN_TAG)` - two input.

   These are different hash functions (different Poseidon arity → different permutation domain). They cannot collide for any `nullifier`. The on-chain pool keeps its own `nullifiers` dict; the AmDistributor keeps a separate `amNullifiers` dict. The two never interact.

   `AM_DOMAIN_TAG` is a fixed constant baked into the circuit. Current value: 8 repetitions of `0xDEADC0DE` (224 bits). Distinct from any plausible random `secret` (depositor-sampled 248-bit value) - but also a constant the ceremony freezes; post-ceremony changes invalidate the proving key.

## Files

* [`apps/contracts/circuits/amClaim.circom`](https://github.com/tonadocash/monorepo/blob/main/apps/contracts/circuits/amClaim.circom) - the circuit
* [`apps/contracts/contracts/anonymity-mining/am_distributor.tolk`](https://github.com/tonadocash/monorepo/blob/main/apps/contracts/contracts/anonymity-mining/am_distributor.tolk) - on-chain distributor
* [`apps/contracts/contracts/verifier_am.tolk`](https://github.com/tonadocash/monorepo/blob/main/apps/contracts/contracts/verifier_am.tolk) - **STUB** placeholder verifier until ceremony
* [`packages/core/src/amDistributor.ts`](https://github.com/tonadocash/monorepo/blob/main/packages/core/src/amDistributor.ts) - SDK wrapper + `computeAmNullifierHash` helper
* [`apps/contracts/tests/am-distributor.test.ts`](https://github.com/tonadocash/monorepo/blob/main/apps/contracts/tests/am-distributor.test.ts) - 9 sandbox tests

## Circuit shape

Public signals (declaration order - wire-encoded into the proof):

```
root
amNullifierHash
recipient
relayer
fee
refund         // always 0 on the AM path; included for shape parity
```

Private inputs:

```
nullifier, secret
pathElements[levels]
pathIndices[levels]
```

Constraints:

1. `commitment = Poseidon(nullifier, secret)` (same `CommitmentHasher` the withdraw circuit uses)
2. MerkleProof verifies `commitment` is at the claimed `pathIndices` under `root`
3. `amNullifierHash === Poseidon(2)(nullifier, AM_DOMAIN_TAG)`
4. `recipient/relayer/fee/refund` bound to proof via dummy squares (non-malleability - same pattern as withdraw)

Levels = 20 (same as withdraw + merge - shared off-chain tree).

## On-chain claim flow

```
                  ┌─────────────────────┐
   User           │   AmDistributor     │
   (or relayer)   │   contract          │
                  └──────────┬──────────┘
        │ Claim(root, amNullifierHash,
        │       recipient, relayer, fee, refund, proof)
        ▼
  ┌──────────────────────────────┐
  │ 1. !paused                   │
  │ 2. dict-check root in        │
  │    knownRoots                │
  │ 3. dict-check                │
  │    amNullifierHash NOT in    │
  │    amNullifiers              │
  │ 4. amgroth16VerifyPayload(   │
  │       proof, payload)        │
  │ 5. Mark amNullifierHash      │
  │ 6. Emit AmClaimLog           │
  │ 7. Send (reward - fee)       │
  │    TONADO to recipient       │
  │ 8. Send fee TONADO to        │
  │    relayer                   │
  └──────────────────────────────┘
```

`knownRoots` is curated by governance - `AdminAddRoot(root)` adds a new root as the underlying pool grows. Old roots stay valid indefinitely so historical deposits can still claim.

## Lifecycle

| Phase                      | Status  | Note                                                                                      |
| -------------------------- | ------- | ----------------------------------------------------------------------------------------- |
| Design + first-pass code   | ✓       | Circuit + contract + SDK + 9 sandbox tests                                                |
| Trusted-setup ceremony     | Pending | Run `pnpm circuits:compile` + multi-party MPC; see [ceremony.md](/operations/ceremony.md) |
| Verifier export            | Pending | `pnpm verifier:export` replaces `verifier_am.tolk` with the ceremony-derived verifier     |
| Deploy AM Distributors     | Pending | One per pool (denomination-aware); deploy script TBD                                      |
| Initial AM-reserve funding | Pending | Governance schedules releases from the 30% reserve vault                                  |

**Mainnet checklist:**

* [ ] Refuse to deploy `am_distributor.tolk` if `verifier_am.tolk` still says "STUB" at the top.
* [ ] Add `amClaim_final.zkey` to `deploy/ceremony/MAINNET_ATTESTATION.txt` alongside the existing `withdraw_final.zkey` and `merkleUpdate_final.zkey` pins.
* [ ] Confirm the `AM_DOMAIN_TAG` constant in the circuit matches the constant in `am_distributor.tolk` byte-for-byte.

## See also

* [tokenomics.md](/protocol-reference/tokenomics.md) - where AM fits in the broader token model
* [circuit-spec.md](/protocol-reference/circuit-spec.md) - all circuits at a glance
* [contracts-spec.md](/protocol-reference/contracts-spec.md) - distributor contract surface
* [../how-it-works/commitments-and-nullifiers.md](/how-it-works/commitments-and-nullifiers.md) - why nullifiers prevent double-claim
