Skip to Content
DocumentOperatorBond management
Operate

Top up, exit cool-down, and withdrawal mechanics for a relayer's bond.

Every active relayer has a bond locked in RelayerRegistry. Apps prefer routing to higher-bonded operators (signals operational commitment), and the bond is returned in full on exit — there is no on-chain slashing in this contract. Accountability for misbehaviour runs through the planned Dispute Registry + reputation indexer (whitepaper §10.2). This page covers operator-side bond mechanics.

Sizing the bond

The contract enforces minBond() — read it before registering:

import { ethers } from "ethers"; import { RELAYER_REGISTRY_IFACE } from "@zkscatter/sdk"; const registry = new ethers.Contract( network.contracts.relayerRegistry, RELAYER_REGISTRY_IFACE, readProvider, ); const min = await registry.minBond(); console.log(`min bond: ${ethers.formatEther(min)}`);

Practical sizing (over the minimum):

Volume tierRecommended bondRationale
HobbyistminBondJust to stay registered
Production2–3× minBondVisible commitment to apps doing operator filtering
Institutional5–10× minBondApps prefer high-bond operators; signals long-term commitment

The bond is fully refunded on exit (after the 7-day cool-down) — there is no slash path that reduces it. The “tier” guidance is purely about reputation signalling: a higher bond visibly distinguishes operators that intend to stay around.

Topping up

import { addRelayerBond } from "@zkscatter/sdk/relayer"; const tx = await addRelayerBond( network.contracts.relayerRegistry, "0.5", // amount as a decimal string in the bond asset signer, ); await tx.wait();

addBond accepts any positive amount. There’s no upper limit. (SDK note: the parameter name is bondEth for legacy reasons. If the registry was deployed in native-bond mode (bondToken == address(0)), the SDK funds the top-up via msg.value. If the registry was deployed with an ERC20 bond token (bondToken != address(0)), approve the bond token for at least the top-up amount first, then call addRelayerBond — the SDK submits with msg.value = 0. The TON-bond migration is a separate PR; the deployed bond asset is whatever the contract was deployed with.)

Accountability — reputation, not slashing

The contract has no slash() entry point and no governance hook to forfeit bond. Instead, accountability is wired through the planned Dispute Registry:

  1. Bad proof submissionsettleAuth with a proof that fails verification reverts on-chain. The relayer pays gas and gains nothing. No bond consequence; the failed tx is the penalty.
  2. Order theft — relayer received an order signed against them but routed it to another operator. Cryptographic evidence (signed orderHash + competing settlement) goes into the Dispute Registry; a public misbehaviour record drops the relayer’s reputation rating, which apps surface in their picker. User flow shifts to competitors → fees decline → effective economic penalty.
  3. Censoring / locking — measurable refusal to match an order, then submitting it after expiry to spoil the user’s nonce. Same dispute path: evidence on-chain, rating down, fees down.
  4. Going offline — liveness only. Apps route around to another operator. No reputation impact (offline is an availability issue, not misbehaviour).

The combined effect — public misbehaviour record + KYC’d legal identity (operators cannot anonymously re-register) + lost user flow — exceeds any slashable bond fraction the contract could plausibly take. See whitepaper §10.2 for the full rationale.

Exit cool-down

requestExit() flips you into a 7-day cool-down (EXIT_COOLDOWN_SECONDS):

  1. Stop accepting new orders

    Continue serving in-flight matches; reject newcomers with a 503 + “exiting” message in /api/info.

  2. Wait 7 days

    During this window, the registry still considers the operator active = true, so any matched orders that were accepted before requestExit() can still settle. New disputes filed during the cool-down still affect the off-chain reputation score, but do not reduce the bond.

  3. Execute exit

    executeRelayerExit(...) returns the full bond and deactivates the registry entry.

Pre-flight checks

Before executeExit, confirm:

import { loadOperatorRow, EXIT_COOLDOWN_SECONDS } from "@zkscatter/sdk/relayer"; const row = await loadOperatorRow( network.contracts.relayerRegistry, account, readProvider, ); if (row.status !== "cooldown") { throw new Error("not in cooldown"); } const unlockAt = row.exitRequestedAt + EXIT_COOLDOWN_SECONDS; if (Math.floor(Date.now() / 1000) < unlockAt) { throw new Error(`bond unlocks at ${new Date(unlockAt * 1000)}`); }

Re-registration

After executeExit, you can register(...) again with a fresh bond. Your prior Disputed events (when the Dispute Registry ships) are public history — large operators typically want to avoid an exit/re-enter cycle since rebuilding rating under a new legal identity is expensive (fresh CA attestation, fresh KYC process, fresh user trust-building).

Common errors

`Bond too low` on register

msg.value < minBond(). Top up the wallet with at least the minimum, plus gas.

`AlreadyRegistered`

Same address already in the registry. Use updateRelayerInfo or exit first.

`CooldownNotPassed` on exit

Less than 7 days since requestExit. Read loadOperatorRow().exitRequestedAt and wait.

`BondTransferFailed` on exit

The operator EOA can’t receive the bond asset (e.g. it’s a contract without a payable receive). Set up a payable receiver or hand off the transfer manually.

Last updated on