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/:nonceEvery 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.