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 tier | Recommended bond | Rationale |
|---|---|---|
| Hobbyist | minBond | Just to stay registered |
| Production | 2–3× minBond | Visible commitment to apps doing operator filtering |
| Institutional | 5–10× minBond | Apps 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:
- Bad proof submission —
settleAuthwith a proof that fails verification reverts on-chain. The relayer pays gas and gains nothing. No bond consequence; the failed tx is the penalty. - 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.
- 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. - 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):
- Stop accepting new orders
Continue serving in-flight matches; reject newcomers with a 503 + “exiting” message in
/api/info. - Wait 7 days
During this window, the registry still considers the operator
active = true, so any matched orders that were accepted beforerequestExit()can still settle. New disputes filed during the cool-down still affect the off-chain reputation score, but do not reduce the bond. - 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.