# Running a relayer

A relayer is what gives users privacy. Without it, the wallet paying for the withdrawal transaction is on-chain and linkable to the depositor. The relayer's job is to submit the withdrawal transaction from a wallet *unrelated* to the user's depositor wallet, in exchange for a small fee.

This page covers running a relayer for development. For the HTTP API reference, see [Relayer HTTP API](/operations/relayer-api.md).

## What the relayer does and doesn't see

|                 |                                                                                                                                                                    |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Sees**        | A pre-built Groth16 proof, the public signals (root, nullifier hash, recipient field, relayer field, fee, refund), the recipient TON address, the fee, the refund. |
| **Doesn't see** | The user's note. The user's `(nullifier, secret)`. Which specific deposit in the pool the withdrawal corresponds to.                                               |

The proof generation happens entirely on the user's machine. The relayer just submits.

## Architecture

```
                  Toncenter / liteclient
                          │ (poll)
                          ▼
                     indexerWorker (bg)  ──►  Postgres (deposits, withdrawals, sync_cursor)
                                                       │
                                                       ▼
                                                  TreeMirror (lazy in-process)
                                                       │
                                                       ▼
                                  Express  ──►  TornadoOperations  ──►  Pool contract (withdraw tx)
```

Two processes:

* **The REST server** (`pnpm relayer:dev`): accepts withdraw requests, validates them, signs and broadcasts.
* **The indexer worker** (`pnpm --filter @tonado/relayer run worker:indexer`): polls Toncenter for pool events and persists them to Postgres. The REST server reads from Postgres to build Merkle proofs and answer note-status queries.

## Running locally

```bash
# 1. Dependencies (postgres + redis)
docker compose up -d postgres redis

# 2. Configure
cp .env.example .env
$EDITOR .env
# at minimum: RELAYER_WALLET_MNEMONIC, TONCENTER_API_KEY

# 3. Migrate the DB
pnpm --filter @tonado/relayer run migrate

# 4. (Optional) Build
pnpm --filter @tonado/relayer run build

# 5. Start the REST server
pnpm relayer:dev
# or after build: pnpm --filter @tonado/relayer run start

# 6. In a second terminal, start the indexer worker
pnpm --filter @tonado/relayer run worker:indexer
```

Health check:

```bash
curl http://localhost:8090/status
```

You should see something like:

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

## Required environment

| Variable                  | Purpose                                                                                                                    |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `RELAYER_WALLET_MNEMONIC` | 24-word mnemonic of the wallet that signs withdraw transactions. **The highest-value secret in the operator-side system.** |
| `RELAYER_WALLET_VERSION`  | `v4R2` (default) or `v5R1`.                                                                                                |
| `RELAYER_FEE_BPS`         | Basis points charged. Default 50 (0.5%).                                                                                   |
| `RELAYER_FEE_MIN_TON`     | Minimum fee floor in TON. Default 0.05.                                                                                    |
| `TONCENTER_API_KEY`       | Without this you'll hit Toncenter rate limits.                                                                             |
| `POSTGRES_URL`            | Postgres connection string. Default `postgres://localhost:5432/tonado`.                                                    |
| `REDIS_URL`               | Redis connection string. Default `redis://localhost:6379`.                                                                 |

## Fee policy

```
fee = max(RELAYER_FEE_MIN_TON, denomination * RELAYER_FEE_BPS / 10000)
```

The relayer rejects any withdrawal whose declared fee is below this. The fee is paid in the same asset as the pool: native TON for native pools, jettons for jetton pools (minus a small TON top-up for gas).

## What the relayer cannot do (security model)

A malicious relayer **cannot**:

* Generate a proof for a different recipient: the relayer doesn't have the nullifier or secret.
* Substitute its own recipient: recipient is bound to the proof as a public input.
* Inflate the fee: fee is bound to the proof.
* Broadcast someone else's proof and collect that fee: the relayer field is bound; the on-chain verifier rejects.
* Steal funds in any other way.

A malicious relayer **can**:

* Refuse to broadcast (censor): the user switches relayers or self-relays.
* Log timing + IP and attempt deanonymization correlation: the user uses multiple relayers, Tor, randomized delays.

See [Threat model](/protocol-reference/threat-model.md) for the full attacker analysis.

## Running in production

```bash
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
```

For production:

* **Keep the relayer mnemonic in a secrets manager** (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager). Never in `.env`.
* **Provision Postgres with backups + point-in-time recovery.**
* **Configure DDoS protection** in front of the REST API (e.g., Cloudflare).
* **Rate-limit** `/note/check` and `/withdraw` per source IP.
* **Set up balance alerts** on the relayer wallet. Running out of TON kills the relayer.
* **Withdrawal caps per hour** to limit blast radius if the relayer is compromised.
* **A cold reserve wallet** that periodically sweeps excess fees out of the hot relayer wallet.

The full operations checklist is in [Mainnet launch plan](/operations/mainnet-launch-plan.md) phase 4.

## Multi-relayer

The TONado Cash protocol is **relayer-agnostic**: any wallet can act as a relayer, and users can choose among multiple relayers per withdrawal. Running multiple independent relayers decentralizes the censorship surface and the operational risk.

A relayer registry (similar to Tornado-EVM's) is on the roadmap but not in v1. For now, users discover relayers out-of-band (project channels, public URLs) and configure them per-request.


---

# 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/for-developers/running-a-relayer.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.
