apps/api listens on 127.0.0.1:8787 behind nginx, and is served publicly at
https://api.ctrlarcz.xyz and under /api/ on the app’s own origin.
The routes
Signed requests
The four relayer-funded routes spend the operator’s money, so an anonymous caller must not be able to reach them. Each request carries three headers:Sign the raw body bytes you actually send, not a re-serialized copy.
keccak256 of
{"a":1} and { "a": 1 } are different, and the server hashes what arrived.Quotas
Authentication says who is calling; the quota bounds what that buys.- 50 units per address per day. A gasless claim is 1 unit.
- 2000 units per day across the whole process. Addresses are free to mint, so a per-address cap alone lets an attacker multiply spend across many wallets. The global ceiling caps the relayer’s total exposure regardless.
- Over either limit the route answers
429 daily quota exceeded.
Public routes
GET /api/health
relayer is null when no relayer key is configured. On Arc the USDC balance is
also the gas balance, so one number covers both. low: true is a warning, not an
error: the API is healthy, its wallet is not, and box deploys, announcements and
gasless claims stop when it runs out.
GET and POST /api/cosign
GET returns { address }, the public address the browser locks into each new
box. POST is The Machine: it reads the box’s real policy from chain, runs the
firewall, and returns a signature or a veto.
GET /api/verified-recipients
?sender=0x...&chainId=5042002. Everyone this sender has completed a protected
transfer to, which feeds the firewall’s known-good list.
Public and unauthenticated on purpose: it is derived from public events and anyone
can read the same logs. Making the browser sign for it would buy nothing and add a
wallet prompt to the send form’s first render.
GET /api/announcements
?fromBlock=...&chainId=.... Raw stealth announcements, for clients that would
rather not run a chunked eth_getLogs scan themselves.
The server cannot tell which of these belong to the caller, and neither can anyone
else without the viewing key. Recognition happens on the client with
recognizeAnnouncements. See Stealth boxes.
Relayer routes
POST /api/gasless-claim
msg.sender, so
the sponsor is a relayer and cannot redirect the funds.
claim() raises as typed errors are
encoded in error.kind. See
Errors and result shapes.
POST /api/relay/create and /api/relay/announce
Deploy a box, and announce a stealth box, on the payer’s behalf. Both exist for privacy rather than convenience: paying for your own box deploy puts your wallet on the deployment transaction, which is the one link the design otherwise removes.create validates the policy before it signs anything: the co-signer named must be
this server, the mode must be PUSH or PULL, and the expiry must be in the future.
announce refuses a box that does not exist.
POST /api/relay/gas
Seeds a fresh address with enough USDC to send one transaction. A stealth address has no gas by definition, and on Arc gas is USDC, so without this a payer can create a box they cannot afford to sweep.POST /api/investigate
A reasoned second opinion on a recipient, on top of the rule engine.deep is off (no API key), budget (past the daily ceiling) or ran. Two
properties are worth stating plainly:
- It can only ever tighten a verdict, never weaken one.
clampVerdictbounds the advisory by the rule engine’s own result, so a model that says “looks fine” cannot unblock a lookalike. - Running out degrades the advisory, not the check. Past 1000 model calls a day
the route still answers with the rule verdict and a
nulladvisory, which is exactly how it behaves with no API key at all. The risk check that carries it never fails because a budget ran out.
CORS
CORS_ORIGINS is a comma-separated allowlist and it is fail-closed: an empty
list allows no browser origin. It does not mean “any”. A native client sends no
Origin header and is always allowed.
Running the apps locally means adding http://localhost:5173 to it, or every
relayer-funded flow answers 403 from a page that otherwise looks like it is
working.
Next
- The veto model: The co-signer.
- Outcome shapes: Errors and result shapes.
- Running it: Local development.