The gateway authenticates AI clients, enforces per-project policy, admits payment, dispatches to the model provider, settles exact usage, and writes the ledger row every receipt is signed from. Five worker processes sit around it and do everything the request path must not: settle on-chain, credit top-ups, sign receipts, publish epoch roots, and expire ciphertext.
The division is deliberate. Nothing in the request path holds a wallet key, broadcasts a transaction, or signs a receipt.
Service shape
- A Rust service on axum.
GIWA_GATEWAY_BIND_ADDR is required and has no code default: a missing value is a startup configuration error. The shipped topologies bind port 8788 inside the container and publish it on loopback only.
- The router has no browser-session or cookie dependency. The console is a separate Next.js app; the gateway boundary accepts API keys only.
- In the default localnet the gateway is not published to the host at all.
For the route table, the 402 challenge shape, and the error taxonomy, see Gateway API.
Protocol adapters
Three native protocol adapters parse inbound requests into one canonical form and render provider responses back into the client’s protocol:
- OpenAI Chat Completions: the API shape OpenRouter-style SDKs default to.
- OpenAI Responses: the protocol Codex speaks.
- Anthropic Messages, with token counting: the protocol Claude Code speaks.
Parsing yields the two numbers pricing depends on: an input-token upper bound (a canonical JSON byte bound that never underestimates what the provider will bill) and an output-token cap (requested output clamped to the catalog maximum). The estimator identity is a fixed string, canonical-json-byte-upper-bound-v1, so a later dispute can name the exact bound function rather than argue about intent.
Auth: per-project tok_ keys
- Keys carry the
tok_ prefix and are scoped to one project.
- OpenAI routes accept exactly one
Authorization: Bearer tok_… header. Anthropic routes accept exactly one bearer header or exactly one x-api-key: tok_… header, never both.
- The gateway stores only a SHA-256 key hash plus an 8-character display suffix. Presented keys and idempotency keys are redacted at the execution boundary.
- All admission, pricing, and ledger state is per-project.
Policy enforcement
Project policy is a durable admission boundary in PostgreSQL, not an application-side read/check/write convention:
- Model allowlists: a policy revision can restrict which models a project may request. Denials are durable decisions, not best-effort checks.
- Lifetime spend caps: a policy revision can cap cumulative project spend, in exact 4-decimal USD.
- Policy revisions and admitted decisions are immutable. Every post-activation credit or x402 admission pins one decision, so a later policy edit cannot retroactively change what a request was admitted under.
Credit admission
The prepaid-credit path is a dark module with no HTTP route. A caller holds one per-project PostgreSQL session lease from admission through terminal settlement:
- Admission authorizes a conservative maximum derived from the same body-aware bounds used for x402 quoting, at the active model price snapshot.
- Dispatch runs the provider call under the lease’s idempotent authority.
- Settlement charges exact-decimal actual usage against that admission and releases the remainder. A provider error before billable work charges exactly
0.0000.
The invariant is never-overdraw: settlement can never exceed the admission, the admission can never exceed policy, and one admission funds exactly one request. The lease connection is marked close-on-drop, so an error can never return a locked backend to the pool for another request.
The other payment axis, x402, is documented separately in x402 Payments on GIWA.
The request ledger
Every request ends as exactly one row: status (ok, provider_error, or dispatch_unknown), token counts, and exact-decimal cost. A BEFORE UPDATE OR DELETE trigger raises giwa request ledger is append-only on any mutation, so history can never be rewritten in place. This table is the substrate every receipt is signed from.
GIWA_TEST_PROVIDER is a test-only fixture. With GIWA_TEST_PROVIDER=enabled (the local default) the gateway answers from a deterministic local provider fixture and a trivial echo catalog entry. The production-shaped topology sets it to disabled, and the gateway config rejects enabled in production. Nothing about the test provider is a claim about a real model provider.
The five workers
All five are bounded, single-authority processes. The polling loop is owned outside them, each accepts one bounded batch and exits, and none has a listener unless that listener is its whole job. A worker that cannot finish honestly exits nonzero, so a retry is visible rather than silent.
The x402 adapter: the payment boundary
The adapter is a separate process that owns everything x402-shaped, so the gateway never links an x402-rs crate. The framework version mismatch that forces the boundary is explained in x402 Payments on GIWA.
- Surface: port 8090, exactly four endpoints (
POST /v1/verify, POST /v1/settle, POST /v1/recover, GET /healthz), reached over a single bearer-authenticated local JSON interface. No x402-rs type crosses the seam.
- Sole chain and facilitator client: it is the only component that talks to the facilitator, the chain RPC, and the settlement journal. It decodes the Permit2 payment header, re-derives the authorization nonce from the request binding, checks the full requirement and witness, calls the facilitator, and decodes the settle calldata word by word before accepting chain evidence.
- Append-only journal: before its sole settlement side effect it writes a prepared record, so a lost response can never become a second broadcast, and recovery is read-only. The mechanics are in x402 Stage B.
The top-up reconciler: sends no transactions
The reconciler completes Stage A credit top-ups. Its design constraint is explicit: it is not a facilitator, not a middleware, not a wallet signer, and it sends no transaction. Its only write authority is a fenced set of ACL-granted database procedures.
- Startup lockdown: it rejects every RPC endpoint except the fixed internal one, checks chain 91342, the fixed genesis timestamp and hash, the recorded tUSD address and runtime code hash, and that the asset has 6 decimals. No public RPC, testnet, or arbitrary database URL is accepted.
- Credit condition: it credits only after observing the exact ERC-3009 evidence chain, which is one canonical
AuthorizationUsed log, exactly matching calldata, a successful receipt with exactly one matching Transfer log, and finality confirmed by a second block-hash re-read. The full condition list is in x402 Stage A.
- Failure honesty: if a dispatched top-up is absent after its authorization can no longer be used, the reconciler persists a deterministic final-negative observation before asking the database to abandon the intent. If the token says the nonce was consumed but logs are absent, it fails closed. It never resends, so recovery cannot duplicate a payment.
A fault-injection overlay kills and recreates this worker around a real settlement:
The attestation publisher: epoch Merkle roots
The publisher converts already verified, database-sealed receipt epochs into exactly one EAS transaction per epoch. This is what makes “1M or 100M requests, same on-chain cost” literal rather than rhetorical.
- Narrow authority: it never reads raw tables, consuming ACL-fenced eligibility and publication procedures instead. It never accepts a non-local endpoint, and never signs a replacement transaction once signed raw bytes are durable.
- Merkle construction: an epoch’s receipts are ordered canonically into a tree under the V1 merkle domain separators
TOKSCALE_RECEIPT_LEAF_V1 and TOKSCALE_RECEIPT_NODE_V1. The transaction carries the epoch summary commitment, and individual receipt proofs verify against the anchored root in a browser. See Receipt Attestation.
- Signing: one EIP-1559 raw transaction from a dedicated publisher key isolated from the deployer, facilitator, treasury, receipt issuer, and browser payer, so its transaction nonce is exclusively owned by this worker. Bounded batches of 25 by default, capped at 100, under short leases.
Epoch attestation is proven against the local EAS fixture only. Publishing the first Usage Root on public GIWA Sepolia is still ahead of us; see Live Evidence.
The receipt issuer: signs UsageReceiptV1
- It claims a bounded batch of terminal ledger rows under a lease, signs one EIP-712
UsageReceiptV1 per row, and releases or marks failures honestly.
- The signer key is a dedicated issuer key, separate from the published vector signer. Before any work it asserts the bound deployment network, so a misconfigured database stops the pass instead of signing into the wrong network.
- Receipt format: UsageReceiptV1.
Terminal retention: ciphertext expiry
- It expires encrypted protocol-terminal payloads after their retention window, and has no listener, no chain client, no gateway credential, and no direct table authority. Its dedicated no-inherit database role can invoke exactly two expiry procedures, and it validates that its own database URL targets that role.
- Each run is a bounded pass of 256 rows per call, at most 16 passes, then it exits. Keeping ciphertext expiry in a separate least-privilege poller means the encryption key material and the deletion authority never live in the gateway process.
Evidence
The gateway and all five workers are exercised by the sealed localnet harness, which waits on published database evidence rather than process-ready signals:
Next steps