Skip to main content

HTTP APIs

zk-pad runs three kinds of off-chain service. Anyone can run their own instance of each.

ServicePurposeContract
RelayerSubmits signed FeeVault actions and pays the gaspackages/sdk/API.md + packages/sdk/src/api.ts (zod, normative)
AttestorSocial-account commitments, OAuth, BindOwner signaturessame
IndexerRead API for the dApp: tokens, trades, candles, holders, activityservices/indexer/API.md + services/indexer/src/api/types.ts
Source of truth

These pages mirror packages/sdk/API.md, services/indexer/API.md and the code. If a page and the zod schemas or types.ts disagree, the code wins.

Conventions​

  • JSON in and out (content-type: application/json).
  • Integers that can exceed 2^53 (amounts, nonces, deadlines, delays, gas) are decimal strings. Addresses and byte strings are 0x hex. Timestamps are unix seconds.
  • Errors are non-2xx responses with { "error": string, "code"?: string }. Codes: bad_request, bad_signature, fee_too_low, expired, simulation_failed, rate_limited, screened, not_found, unauthorized, unsupported, unavailable (503, retry), internal, and payment_required / payment_failed (402, paid attestor handle lookups).
  • CORS: * for GET; allow-listed origins for POST (relayer and attestor).
  • Request bodies are size-limited (413 bad_request): 64 KiB on the relayer, 16 KiB on the attestor.
  • /v1/metrics on the relayer and the attestor is operator-only (Authorization: Bearer <METRICS_TOKEN>, 404 when unset), and the indexer's GraphQL is operator-only as well. Even aggregate counters polled every few seconds line up with on-chain events.

Privacy rules​

  • Services never log IPs, beneficiary ids, recipients or signatures. Only aggregate counters are kept. No analytics.
  • Clients send no cookies (credentials: 'omit') and no referrer.
  • Every endpoint must work over Tor / OHTTP. Nothing depends on the client IP except coarse in-memory rate limiting.
  • Beneficiary data is served as a fetch-everything dump (GET /v1/accounts on the relayer, GET /v1/beneficiaries on the indexer). There is deliberately no per-id endpoint, so a server cannot learn which id a visitor cares about.
  • Nothing account-specific in URLs, which end up in proxy and CDN access logs: relayer quotes carry no amount, tx status is a POST, OAuth starts are a POST, and ids only travel in POST bodies.
  • No id-specific RPC reads. Clients take nonces and balances from GET /v1/accounts (RelayerClient.accountNonces / findAccount) instead of nonces(id) / accountOf(id) / balanceOf(id, …) against a third-party RPC, and the attestor answers logins from its own event-sourced index. A per-id eth_call would link the caller's IP to the id at the moment it acts.