Skip to main content
Stage A tops up a project’s prepaid credit balance with an exact, fixed amount, using the x402 exact scheme over ERC-3009. x402 on GIWA is a port, not a builtin: GIWA does not officially support x402, and Coinbase’s hosted facilitator does not support GIWA, so Tokscale runs a self-hosted, digest-pinned x402-rs facilitator (ghcr.io/x402-rs/x402-facilitator@sha256:89ab5c03…) and deployed the chain prerequisites itself.

Direct ERC-3009 on tUSD: no proxy

The payment asset is tUSD (“Tokscale Test USD”, ERC-20 with 6 decimals and an EIP-3009 subset), deployed on GIWA Sepolia at 0x58d5608e89b5c1c3b96481a199756b1a292061a9 (byte-verified, domain and metadata validated). Because the asset implements ERC-3009, the x402 exact flow needs no proxy contract. The client signs an ERC-3009 authorization for the eip3009 asset, and the facilitator calls transferWithAuthorization directly on the token. The ExactPermit2Proxy exists only on the separate Permit2 branch and plays no role here (see x402 Stage B).

The flow

1

Return the 402 challenge

The top-up route responds 402 with scheme=exact, network=eip155:91342, asset=tUSD, payTo=treasury, and the exact requested amount.
2

Sign the ERC-3009 authorization

The payer signs one transferWithAuthorization payload covering token, payer, treasury, amount, validity window, and nonce.
3

Verify with the facilitator

The gateway posts to the facilitator’s /verify. The facilitator exposes synchronous /verify and /settle only; every state transition is driven by Tokscale’s own lifecycle and reconciler.
4

Persist the intent, then settle

Only after a successful verify does the gateway persist the intent and call /settle. A /settle timeout leaves the intent dispatched. It is never a silent failure and never an immediate retry that could obscure the on-chain outcome.
5

Credit from chain evidence only

Credit is written only by the reconciler, and only from canonical chain evidence.

The durable intent lifecycle

Every top-up intent moves through a persisted state machine:
The rules that make it safe:
  • Intents are unique on (chain_id, asset, payer, authorization_nonce), with a stored hash of the full authorization payload. One authorization can fund one intent, once.
  • The credit insert and the confirmed -> credited transition commit in one database transaction.
  • An intent expires to abandoned only after valid_before plus the configured finality window with no matching on-chain authorization.
  • Flashblocks pending-tag polling gives ~200ms “payment seen” feedback only. It is never a credit trigger: preconfirmation is not finality.

The reconciler’s exactly-once credit rule

The reconciler proves one authorization, not merely a transfer. For each dispatched intent it requires final-chain evidence that:
  1. the expected token emitted AuthorizationUsed(payer, nonce) for the stored payer and nonce, and
  2. the successful token call decodes to the stored transferWithAuthorization fields: token, payer, treasury, amount, validity window, nonce, and authorization commitment.
It then persists the transaction hash, block number, and block hash, and after the configured confirmation depth re-reads the block and receipt before crediting, so a reorg cannot turn a Flashblocks or early receipt observation into credit. authorizationState is a useful negative check but proves neither payee nor amount by itself. Two consequences:
  • A plain transfer cannot mint credit. A generic same-value tUSD transfer to the treasury never credits a pending intent, because it carries no AuthorizationUsed and no matching authorization calldata.
  • The reconciler sends no transactions. It scans, matches, and writes credit rows. Settlement is the facilitator’s job; crediting is the reconciler’s.

Proof on public GIWA Sepolia

On 2026-07-23 the pinned facilitator ran a full exact round trip against public GIWA Sepolia (eip155:91342): it advertised exact for the network, rejected a wrong chain (unsupported_scheme), rejected a wrong asset and a tampered signature, accepted one valid ERC-3009 authorization, and settled it on-chain. The settlement is recorded in the committed deployment manifest:
  • Settlement transaction: 0xad1a2f7b…, 1.0 tUSD (1,000,000 base units), block 31,331,118
  • Replay of the same authorization: rejected, reverting with the ERC-3009 authorization-used selector 0xd309466d. No double settlement.
The on-chain evidence (bytecode hash, EIP-712 domain, and this authorization receipt) can be re-checked at any time:
Scope discipline: tUSD is a test-only asset and this run is evidence, not a payment service. Scheduled or production use still gates on a dedicated non-public RPC and approved key and treasury custody.

Crash recovery, proven by killing the reconciler

The crash harness drives the worst case on the local localnet topology: an authorization settles on-chain, then the reconciler container is stopped and force-recreated before it credits. The harness then asserts, against both the database and the chain:
  • pre-restart: the intent is dispatch_unknown with zero credit rows (settled but uncredited);
  • post-restart: the new reconciler instance reconciles exactly one canonical settlement into exactly one credit (scanned=1 claimed=1 confirmed=1);
  • a separate plain same-value tUSD transfer to the treasury (selector 0xa9059cbb, zero AuthorizationUsed logs) mints no credit.
The crash harness runs under one target:
The exact mechanism is proven once on public GIWA Sepolia, with the evidence above. The full product top-up path (402 route, intent lifecycle, reconciler, crash recovery) is proven on the local localnet topology. Dedicated RPC, treasury custody, and production operation remain external gates.

Next steps