Getting started · ~30 min end-to-end

From zero to a running relayer.

Six steps to a running relayer — but registration is gated. Your wallet must first be verified (zk-X509 certificate proof) and admin-approved (KYC). Do that onboarding in the registration wizard first; the steps below stand up the service it gates.

Live status

Where you are right now

Auto-checks of your wallet, RPC, and on-chain state. Service health needs your relayer URL.

FAIL
Operator wallet connected

Connect a wallet from the header to enable the rest of the checks.

SKIP
Wallet on the right chain

Connect a wallet to check it is on Sepolia (chainId 11155111).

RPC reachable

Pinging RPC…

SKIP
On-chain registration

Connect a wallet to read your registry status.

SKIP
Bond balance

Connect a wallet to read.

SKIP
Relayer service /health

Enter your relayer's public URL and ping it.

Before you start

What you will need

Gather these first. Each one is required — the wizard later confirms them automatically.

Operator wallet (with private key)

A dedicated EVM wallet whose private key the relayer process can read. Never reuse a personal/treasury key — operator keys sign settlement txs constantly.

Hardware wallets are not supported for the relayer process. Use a fresh hot wallet held only by the operator host.

Verified + admin-approved identity (2 gates)

RelayerRegistry.register() reverts unless this same wallet is both isVerified (you proved an accredited certificate to zk-X509) AND kycApproved (an admin signed off your KYC). Complete both in the /register wizard before you reach the register step.

Both gates bind to the wallet that registers — prove and register from the same key, or it reverts (NotVerified / NotKycApproved).

Bond + gas balance

On Sepolia testnet: at least 0.1 ETH bond plus a gas float (~0.05 ETH) on the operator wallet. Bond is recoverable on exit after the registry's exit cool-down.

EVM RPC endpoint

A reliable RPC URL (Alchemy/Infura/your own node). For production, a fallback URL is strongly recommended.

Archive support is not required; standard JSON-RPC is enough.

Server / host

Minimum 2 vCPU, 4 GB RAM, 50 GB SSD. Docker or Node.js 20+ runtime. Outbound network access to the RPC and inbound HTTPS for your public URL.

Public HTTPS URL

A reachable public URL (e.g. https://relayer.example.com) that you will publish on-chain. Clients route orders to this URL. Behind a reverse proxy with TLS termination.

Admin API key (optional but recommended)

A strong random secret (openssl rand -hex 32, ≥ 32 bytes). The relayer runs without one, but every /api/admin/* endpoint returns 403 until it is set, so pause/resume, fee updates, and sanctions management are inaccessible.

Step-by-step

The six steps

Each step is independent — finish one before moving to the next. Time estimates assume the prerequisites above are ready.

1
Clone the repo and install dependencies
~5 min

Each package in the monorepo is standalone (no root npm workspace) — install and build inside zk-relayer/.

git clone https://github.com/tokamak-network/scatter-dex.git
cd scatter-dex/zk-relayer
npm install
npm run build
2
Configure your .env
~5 min

Copy the example file and fill in the five required fields (RPC_URL, RELAYER_PRIVATE_KEY, and the three contract addresses). ADMIN_API_KEY is optional but recommended. Other fields have safe defaults.

cp .env.example .env
# then edit .env
FieldRequiredNote
RPC_URLrequiredYour EVM RPC endpoint.
RELAYER_PRIVATE_KEYrequiredOperator wallet key. Use RELAYER_PRIVATE_KEY_FILE for Docker secrets.
COMMITMENT_POOL_ADDRESSrequiredFrom contracts/deployments — must match the chain in RPC_URL.
PRIVATE_SETTLEMENT_ADDRESSrequiredFrom contracts/deployments.
FEE_VAULT_ADDRESSrequiredFrom contracts/deployments.
ADMIN_API_KEYoptionalopenssl rand -hex 32 (≥ 32 bytes). Without it, /api/admin/* returns 403.
RELAYER_PUBLIC_URLoptionalYour HTTPS URL. Recommended in production.
TOKEN_LISToptionalPer-token symbols for the treasury page.
MAX_GAS_PRICE_GWEIoptionalCap above which the relayer pauses settlement.
3
Start the relayer service
~2 min

Run the service locally first to verify your config. Once /health returns 200, you are ready to register.

# from zk-relayer/
npm start

# in another shell:
curl -s http://localhost:3002/health | jq

Expected: { "status": "healthy", "uptime": <seconds>, "checks": { "rpc": "ok", "db": "ok" } }. A 503 with status: "degraded" means one of the checks failed — inspect the response body.

4
Register on-chain (gated)
~3 min

Connect your operator wallet and publish your public URL plus per-trade fee; the bond is escrowed in the RelayerRegistry contract. register() reverts with NotVerified unless your wallet proved its certificate to zk-X509, and with NotKycApproved unless an admin approved your KYC — so do the wizard's identity steps (KYC → zk-X509 proof → admin approval) first. The /register wizard walks all of it, then builds the tx.

5
Verify the relayer is live
~3 min

Confirm registration on-chain, then check that orders can reach your endpoint. Your registry entry must list the same URL the service is exposing.

# inspect the relayer's self-reported state:
curl -s https://YOUR_PUBLIC_URL/api/info | jq

# expected shape:
# {
#   name: "ScatterDEX ZK Relayer", version: "0.1.0",
#   address: "0x...", fee: 30, orderCount: 0,
#   commitmentPool: "0x...", privateSettlement: "0x...",
#   profile: { name, description, ... }
# }
#
# cross-check on-chain registration via the leaderboard page or
# the RelayerRegistry contract directly.
6
Set up monitoring + alerts
~10 min

At minimum: poll /health from your monitoring stack and route alerts to a channel you actually watch. Phase 2 will add in-app webhooks; for now wire it externally.

  • · Poll GET /health every 30 s — alert on non-200.
  • · Tail process stdout for [tx-recovery] and [admin] lines.
  • · Track operator wallet ETH balance — alert below ~0.02 ETH.
  • · Watch the on-chain bond — slashing or self-exit changes it.
Glossary

Core concepts

Terms you will see repeatedly in this app, the docs, and the contracts.

Bond

ETH you escrow in RelayerRegistry when registering. Acts as a registration deposit. Recoverable in full on exit after the registry's exit cool-down — the current contract has no bond-slashing mechanism (a misbehaving relayer only loses gas on failed settle() attempts).

Fee bps

Your per-trade fee in basis points (1 bp = 0.01%). Published on-chain at registration; updatable via /profile. Default reference: 30 bps (0.30%).

Platform fee

A protocol-level skim on relayer claims, capped at 50% by contract (governance default sub-10%). What you see on /treasury is net of platform fee.

FeeVault

On-chain contract that holds your accrued fees per token until you claim. The /treasury page is a UI on top of this contract.

Half-proof / authorize flow

Each side of a trade submits an authorize.circom proof; the relayer combines them in settleAuth. You never hold witness data — only proofs and public signals.

Sanctions lists (two of them)

On-chain: SanctionsList contract — a blocklist of EVM addresses (e.g. OFAC SDN). Enforced by PrivateSettlement for claim/settle paths.
Operator-local: a JSON file (or admin-API-managed) blocklist of EdDSA pubkeys. Rejects matching orders before they reach the chain.

Exit cooldown

Time between requestExit and executeExit on the registry, set by governance (7 days by default). isActiveRelayer returns false as soon as requestExit is called, so settlement is blocked for the entire cooldown — plan to drain your queue before requesting exit.

Slashing — not implemented

Despite the term being common in relayer designs, the current RelayerRegistry has no slashing path. If/when it ships, it will appear on /profile.

When things go wrong

Troubleshooting

Common failures and the first thing to check.

+/health returns 503

One of the readiness checks failed. Look at the response body — checks.rpc means your RPC_URL is unreachable; checks.db means the DB_PATH is not writable. Fix the underlying cause; the service does not need a restart once the dependency is healthy.

+Registration tx reverts with InsufficientBond

Your operator wallet does not hold enough native token to cover the minimum bond plus gas. Top up and retry — the registry returns the failure cleanly without consuming the bond.

+Orders arrive but never settle

Check the relayer's stdout for [gas-guard] or [tx-recovery] lines. If gas-guard rejected the settlement (gasPrice above MAX_GAS_PRICE_GWEI), the SettlementWorker classifies the failure as 'unknown' and does not auto-retry — the order will likely be picked up by another relayer. Raise the cap in .env (then restart) or accept that some flow will route around you during fee spikes.

+Connecting my wallet fails with 'Admin auth is not configured'

The relayer enables wallet (SIWE) auth only when RELAYER_REGISTRY_ADDRESS is set in its .env — that's what binds admin access to your on-chain relayer record. Set it (then restart) and the wallet connect flow will issue a session.

+My public URL on-chain does not match my running service

Update via /profile or POST /api/admin/profile. The leaderboard and clients route to whatever the registry says — a stale URL silently drops traffic to you.

+Settlement fails with NullifierAlreadyUsed

The order has already been settled — usually by a competing relayer. This is normal under contention; the relayer drops the order from its queue and moves on.

Ready when your checklist is green.

Once the prerequisites are gathered and the service is up, the registration flow is a single tx.