Skip to Content
SDK Referencerelayer
Relayer

Registry, HTTP client, operator actions, profile sanitization.

import { /* ... */ } from "@zkscatter/sdk/relayer";

Discovery

import { loadActiveRelayers, loadRelayersWithApiInfo } from "@zkscatter/sdk/relayer"; // On-chain only — fast. const onChain = await loadActiveRelayers(network.contracts.relayerRegistry, provider); // RelayerOnChain[]: { address, url, fee, bond, registeredAt, exitRequestedAt, active } // Combined: on-chain + live /api/info probe in parallel (default 3s timeout). const full = await loadRelayersWithApiInfo( network.contracts.relayerRegistry, provider, { probeTimeoutMs: 3000 }, ); // RelayerInfo[]: RelayerOnChain + { online, api?: RelayerApiInfo }

RelayerClient

const client = new RelayerClient("https://relayer.example.com", { fetchImpl: globalThis.fetch, // optional override timeoutMs: 5000, // default 5_000 }); await client.getInfo(); // /api/info await client.submitOrder(order, signature, "cover_taker"); await client.getOrders(account); // active orders for an account await client.getOrderHistory(account, { limit: 50, offset: 0 }); await client.getOrderDetail(account, nonce); // single order await client.cancelOrder(account, nonce, signature); // DELETE /api/orders/:address/:nonce

Every method accepts an optional trailing AbortSignal — wire it to a standard AbortController for per-call cancellation. The client wraps each call in its own timeout (timeoutMs) so an explicit signal is only needed for user-driven cancellation.

Submit order

import type { OrderData, FeeMode } from "@zkscatter/sdk/relayer"; // Wire-format payload — amounts/maxFee/nonce already serialized for // transport. `claims` is the same distribution committed in the // authorize proof's claimsRoot. const order: OrderData = { maker: account, sellToken: tokenA, buyToken: tokenB, sellAmount: amountIn.toString(), buyAmount: amountOut.toString(), maxFee: feeBps, // number, basis points expiry: expiryUnixSeconds, // number nonce: Number(nonce), // number claims, // [{ claimHash, amount, releaseDelay }] }; await client.submitOrder(order, signature, "cover_taker"); // → { status: "queued" | "matched" | "settled", txHash?, nonce? }

signature is a relayer-protocol-defined string committing to the order payload — the relayer verifies it before queueing. Match the exact format the relayer’s /api/info advertises.

Operator actions

import { registerRelayer, updateRelayerInfo, addRelayerBond, requestRelayerExit, executeRelayerExit, loadOperatorRow, EXIT_COOLDOWN_SECONDS, MAX_RELAYER_FEE_BPS, } from "@zkscatter/sdk/relayer"; await registerRelayer( network.contracts.relayerRegistry, { url: "https://relayer.example.com", feeBps: 30, bondEth: "1.0" }, signer, ); await addRelayerBond(network.contracts.relayerRegistry, "0.5", signer); await updateRelayerInfo( network.contracts.relayerRegistry, { url: "https://new.example.com", feeBps: 25 }, signer, ); await requestRelayerExit(network.contracts.relayerRegistry, signer); // wait EXIT_COOLDOWN_SECONDS (7 days) await executeRelayerExit(network.contracts.relayerRegistry, signer);

Operator row

const row = await loadOperatorRow( network.contracts.relayerRegistry, account, provider, ); // { // url, feeBps, bond, bondEth, // registeredAt, // 0 if never registered // exitRequestedAt, // 0 if not in cooldown; else add EXIT_COOLDOWN_SECONDS for unlock time // active, status, // "active" | "cooldown" | "offline" | "unregistered" // }

Use this to drive an operator dashboard — single read, derived status.

Profile sanitization

import { sanitizeProfile, type RelayerProfile } from "@zkscatter/sdk/relayer"; const safe = sanitizeProfile(untrustedJson); // undefined if invalid; otherwise: // { name, description, logoUrl, contact, socialX, website, updatedAt }

sanitizeProfile enforces:

  • length caps (no DoS through giant strings)
  • URL scheme allowlist (https:// only)
  • character class filters on display fields

Always run untrusted relayer profiles through this before rendering.

Common errors

Probe times out for every relayer

Default timeout is 3s. Bump probeTimeoutMs for cross-region setups, or pre-filter by lastHeartbeat from the shared orderbook.

`submitOrder` returns `status: "rejected"`

Inspect the response body — common reasons are nonce reuse, EdDSA signature mismatch, fee below relayer minimum, or expired order.

`registerRelayer` reverts with `Bond too low`

The contract enforces a minimum bond. Read the registry’s minBond() view first.

Last updated on