Skip to Content
DocumentOperatorRun a relayer node
Operate

Register, run, manage, and exit a zkScatter relayer.

A relayer matches orders off-chain, generates the counterparty half-proof, and submits settleAuth transactions on-chain in exchange for a basis-point fee. This page covers the SDK side — operator on-chain actions and HTTP-API expectations. The matcher logic itself lives in zk-relayer/ of the monorepo.

Operator lifecycle

  1. Register

    Post a bond, set a fee, publish your URL.

  2. Operate

    Run a matcher service, expose the standard HTTP endpoints, settle matched orders.

  3. Update

    Change URL, fee, profile metadata as needed.

  4. Exit

    Request exit, wait the cool-down, withdraw the bond.

Register

import { registerRelayer, MAX_RELAYER_FEE_BPS, } from "@zkscatter/sdk/relayer"; await registerRelayer( network.contracts.relayerRegistry, { url: "https://relayer.example.com", feeBps: 30, // 0.30 %, must be ≤ MAX_RELAYER_FEE_BPS bondEth: "1.0", // sent as msg.value }, signer, );

The contract enforces a minimum bond — read registry.minBond() first. The bond is returned in full on exit (after the 7-day cool-down); the protocol does not slash it. Apps still favour higher-bonded operators because bond size signals operational commitment. (SDK note: the parameter is named bondEth for legacy reasons. The SDK supports both bond modes: in native mode (bondToken == address(0)) it forwards the bond via msg.value; in ERC20 mode (non-zero bondToken) the operator must approve the registry for the bond amount first, and the SDK then calls register / addRelayerBond with msg.value = 0. The TON-bond migration is a separate PR; until it lands, the deployed bond asset is whatever the contract was deployed with.)

Update info

import { updateRelayerInfo } from "@zkscatter/sdk/relayer"; await updateRelayerInfo( network.contracts.relayerRegistry, { url: "https://new.example.com", feeBps: 25 }, signer, ); // Both `registerRelayer` and `updateRelayerInfo` validate `feeBps` // against `MAX_RELAYER_FEE_BPS` (500 = 5%) before prompting the wallet.

URL changes propagate to apps on their next loadRelayersWithApiInfo refresh — typically within 60s.

Add bond

import { addRelayerBond } from "@zkscatter/sdk/relayer"; await addRelayerBond(network.contracts.relayerRegistry, "0.5", signer);

More bond = stronger commitment signal, which apps factor into their own ranking. There’s no on-chain ranking and no on-chain slashing; the bond is locked collateral that returns in full on exit (after the 7-day cool-down). Reputation enforcement happens off-chain via the planned Dispute Registry — see whitepaper §10.2.

Read your status

import { loadOperatorRow } from "@zkscatter/sdk/relayer"; const row = await loadOperatorRow( network.contracts.relayerRegistry, account, readProvider, ); // row.status: "active" | "cooldown" | "offline" | "unregistered" // row.exitRequestedAt + EXIT_COOLDOWN_SECONDS = unix seconds when bond becomes withdrawable

Surface this in your operator dashboard.

Exit

import { requestRelayerExit, executeRelayerExit, EXIT_COOLDOWN_SECONDS, } from "@zkscatter/sdk/relayer"; await requestRelayerExit(network.contracts.relayerRegistry, signer); // wait EXIT_COOLDOWN_SECONDS (7 days) for any outstanding obligations await executeRelayerExit(network.contracts.relayerRegistry, signer); // bond returned to operator address

While in cool-down your relayer should stop accepting new orders but continue honoring matched ones — otherwise the bond is at risk.

HTTP API contract

Apps using the SDK’s RelayerClient expect these endpoints:

EndpointPurposeNotes
GET /api/infoIdentity + fee + capabilitiesUsed for liveness probe
POST /api/ordersSubmit signed orderBody: { order, signature, feeMode? }
GET /api/orders/:addressOrders for a maker (active + history)?status=&limit=&offset= for filtering
GET /api/orders/:address/:nonceSingle order detail
DELETE /api/orders/:address/:nonceCancel an active orderx-cancel-signature header

Claims are surfaced as the claims field on RelayerOrder. There is no separate /api/claims endpoint — apps derive claim entries from their pending order list.

Reference implementation: zk-relayer/src/http/ in the monorepo.

/api/info shape

{ name: string, version: string, address: string, // operator address fee: number, // bps, must match on-chain orderCount: number, settlement: string, // contract address, must match the chain it's serving profile?: RelayerProfile, }

profile will be sanitized client-side by sanitizeProfile — keep it honest, no HTML, https URLs only.

Operating posture

  • Run two replicas behind a load balancer — health checks should return non-200 the moment matching is impaired so apps switch to another relayer.
  • Surface latency in /api/info — apps can use this for routing.
  • Pre-warm Poseidon and the prover zkey on boot — cold-start match latency tanks user experience.
  • Watch reorg depth — submit settlements only after enough confirmations on the leaves you’re proving against, otherwise your proofs go stale.

Common errors

`Bond too low` on register

Below the contract minimum. Read registry.minBond() first.

`Already registered`

The signer’s address is already in the registry. Use updateRelayerInfo or exit first.

`executeExit` reverts with `Cooldown`

Less than 7 days since requestExit. Wait or query loadOperatorRow for exitAvailableAt.

Last updated on