# Relayer HTTP API

Base URL: `http://localhost:8090` (local), TBD (hosted).

## Security model

**The relayer never touches the user's note.** All proof generation happens on the client. The relayer:

* exposes the commitment Merkle tree (so clients can build inclusion proofs)
* receives a fully-built Groth16 proof + the public-signal vector
* verifies the proof's `relayer` public input matches its own address
* verifies the supplied `recipient` / `fee` / `refund` match the proof's bound public inputs (the relayer can't tamper because the verifier would reject the proof on-chain otherwise)
* enforces its fee policy
* broadcasts the on-chain `withdraw` tx and pays gas

The user trusts the relayer to actually broadcast (or not censor). The relayer cannot steal, tamper with destination/fee, or learn the user's note. See [`threat-model.md`](/protocol-reference/threat-model.md).

***

## `GET /status`

Health, fee policy, and the relayer's own address (which the client binds into the proof).

**Response:**

```json
{
  "network": "testnet",
  "feeBps": 50,
  "feeMinNanoTon": "50000000",
  "relayerAddress": "EQ..."
}
```

## `POST /note/check`

Check whether a deposit is on-chain and / or whether a nullifier has been spent.

The client derives `commitment` and `nullifierHash` locally from its note and sends only those, never the note itself.

**Request:**

```json
{
  "pool": "EQ...",
  "commitment": "0x...",       // optional — required to check "deposited?"
  "nullifierHash": "0x..."     // optional — required to check "spent?"
}
```

At least one of `commitment` and `nullifierHash` must be present.

**Response:**

```json
{
  "deposited": true,
  "leafIndex": 17,
  "timestamp": 1747084800,
  "spent": false
}
```

## `POST /withdraw`

Broadcast a client-built withdrawal.

**Request** (`BroadcastPayload`):

```json
{
  "pool": "EQ...pool-address...",
  "proof": {
    "pi_a": [...],
    "pi_b": [[...], [...], [...]],
    "pi_c": [...],
    "protocol": "groth16",
    "curve": "bls12381"
  },
  "publicSignals": [
    "<root>",
    "<nullifierHash>",
    "<recipient as field>",
    "<relayer as field>",
    "<fee>",
    "<refund>"
  ],
  "recipient": "EQ...recipient...",
  "fee": "50000000",
  "refund": "0"
}
```

Validation done before broadcasting:

| Check                                                             | Reason                                                 |
| ----------------------------------------------------------------- | ------------------------------------------------------ |
| `pool` is in the deployments registry                             | Reject unknown / malicious pools                       |
| `publicSignals` has exactly 6 elements                            | Sanity                                                 |
| `publicSignals[3]` (relayer field) equals our own address         | Otherwise we'd be paying gas for another relayer's fee |
| `publicSignals[2]` (recipient field) equals `recipient` parameter | Cross-check                                            |
| `publicSignals[4]` (fee) equals `fee` parameter                   | Cross-check                                            |
| `publicSignals[5]` (refund) equals `refund` parameter             | Cross-check                                            |
| `fee >= policy_floor`, `fee < denomination`                       | Fee policy                                             |
| `nullifierHash` not in `withdrawals` table                        | Fail fast on double-spend                              |

**Response:**

```json
{
  "seqno": 1234,
  "fee": "50000000"
}
```

The on-chain pool independently verifies the Groth16 proof and rejects with `ERR_PROOF_INVALID` if the relayer somehow tampered with the message body.

## `GET /pools`

List all configured pools for the relayer's active network.

```json
{
  "network": "testnet",
  "pools": {
    "ton-1": {
      "address": "EQ...",
      "asset": "ton",
      "amount": "1",
      "denomination": "1000000000",
      "deployedSeqno": 12345
    }
  }
}
```

## `GET /tree/root?pool=<address>`

Current Merkle root for the named pool, as observed by the relayer's indexer.

```json
{ "root": "0x..." }
```

## `GET /tree/path?pool=<address>&commitment=0x...`

Merkle inclusion path for a specific commitment.

**Privacy note:** asking for a specific commitment's path reveals to the relayer which deposit you're about to withdraw. For maximum privacy, use `GET /tree/commitments` and build the tree locally.

**Response:**

```json
{
  "root": "0x...",
  "pathIndices": [0, 1, 0, ...],
  "pathElements": ["0x...", "0x...", ...]
}
```

## `GET /tree/commitments?pool=<address>&from=<leaf-index>&limit=<n>`

Stream the full ordered list of pool commitments. Paginated.

* `from` (default `0`): leaf index to start at
* `limit` (default `1000`, max `10000`): max rows per response

```json
{
  "pool": "EQ...",
  "from": 0,
  "count": 1000,
  "nextFrom": 1000,
  "commitments": [
    { "leafIndex": 0, "commitment": "0x..." },
    { "leafIndex": 1, "commitment": "0x..." },
    ...
  ]
}
```

`nextFrom` is `null` once the response is the last page. Clients use the union of pages to rebuild the on-chain Merkle tree locally and compute the inclusion path for their own commitment without revealing which one they care about.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.tonadocash.com/operations/relayer-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
