HTTP API

Five small services, all open source and self-hostable, all JSON over HTTP, with CORS preflight so browser apps can call them directly. They do not require the user's identity secret. Path checks only establish consistency with a supplied root. The programs check roots against their on-chain history; off-chain verification trusts the source configured by its backend. An indexer also sees wallet-specific requests, so consider local witness construction or self-hosting when that privacy matters.

Indexer

GET/roots/:tree

Recent Merkle roots for registry or census (decimal field elements, newest last) plus nextIndex.

GET/path/:tree/:leafIndex

An inclusion path: index, 26 siblings, and the root it hashes to. The SDK checks it before use. Asking for a path does tell the indexer which leaf you care about; see /leaves.

GET/leaves/:tree?from=&limit=

A page of leaves in append order (up to 5,000). The SDK's localWitness rebuilds the tree from them and computes the path itself, so the request reveals no selected leaf. The dashboard uses this while the tree is small; other wallet-specific requests made in the same flow can still reveal the wallet.

GET/registry/binding/:wallet

Leaf index and commitment for a registered wallet, 404 otherwise.

GET/census/latest?wallet=&mint=

The newest snapshot for a (wallet, mint) pair: leaf index, amount, slot.

GET/pockets/:appIdHex

Live pocket state rebuilt from events: address, session expiry, revoked flag, pending change, last sweep destination.

GET/activity/:pocket?limit=

Recent pocket events (SessionOpened, Filled, SweptHome, ChangeProposed…), newest first.

GET/stats

Registration, snapshot, pocket and activity counts — the anonymity-set numbers the UI shows.

GET/health

Leaf counts plus the poller's last tick, last error and whether the webhook is enabled.

POST/webhook

Event ingestion for hosted deployments (Helius raw-transaction webhook array, or { logs: [], signature }). Pocket events require a transaction signature for deduplication. Off unless WEBHOOK_SECRET is set and sent in Authorization; failed transactions are dropped, events are attributed by program-id framing, tree roots are checked against the on-chain ring when RPC_URL configures that source, and the store refuses anything that does not chain — without mutating.

Relayer

POST/register

{ walletPubkey, messageHex, signatureHex, commitmentHex } — wraps the user's signature in a transaction and pays. Rate-limited per IP and wallet; requires the wallet to hold a minimum balance.

POST/snapshot

{ wallet, holding } — cranks a census snapshot (one per holding per minute).

POST/open-session

The full login proof plus session key, home and policy — builds and pays pocket.open_session. Pocket creation counts against a rolling daily budget and may be limited to partner origins (RELAYER_ALLOWED_ORIGINS).

POST/owner-action

{ pocket, action: sweep | revoke | propose | cancel, proof…, tokenPairs? } — the owner-proof paths that work even without a session. Build the proof's action digest using the pocket's current owner_action_nonce; a consumed nonce cannot be reused. For sweeps, pairs carry the mint so missing home token accounts are created in the same transaction.

Solver

POST/intent

A signed intent (wire shape in @ghostlink/common). The solver re-verifies the session signature locally, values both legs with the pinned price feeds and refuses to deliver more than the input is worth, quotes a route, and submits precompile + fill_begin + route + fill_end as one transaction. Returns the signature or a structured refusal.

Actions (Blinks)

GET/actions/fund/:appIdHex

Solana Actions metadata wallets unfurl; the destination pocket is recomputed from the app id, never taken from the request, and returned as pocket so the SDK's verifyFunding can refuse an endpoint that names any other address. This GET check does not validate the transaction returned by the later POST request.

POST/actions/fund/:appIdHex?amount= | ?mint=&rawAmount=

Returns the unsigned deposit transaction (SOL or SPL) for the user's wallet to simulate and sign. The reference service requires an initialized pocket with the correct program owner, discriminator and app ID. Callers must still trust the endpoint or validate the returned transaction's destination, amounts and instructions before signing it.

Watcher

No HTTP surface. Watches pocket transactions, notifies on change proposals and large fills (retrying deliveries), and — given a fee-payer keypair — cranks execute_change once a proposal's timelock has elapsed. Set WATCHER_STATE_PATH to retain its cursor and pending proposals across restarts.

Services read configuration, RPC URLs and any required fee-payer keypair paths from the environment. Wire types live in packages/common/src/api.ts — one import on both sides of every call.