v0.1.0 — v0.1 wedge
0.1.0 — 2026-05-12
The v0.1 wedge: a local proxy + timeout-reconciliation engine that catches the canonical coinbase/x402#1062 symptom — buyer is debited on-chain but the facilitator/server thinks the payment failed. Verified end-to-end on real Base Sepolia + the live x402.org/facilitator (tx 0x116ccf73…ba52). 215 tests, GitHub Actions CI on Node 20 + 22, Apache-2.0.
Added
- Initial project scaffold (X402-2)
CLAUDE.mdoperating manual for Claude Code sessionsTESTING.mddefining test requirements per change typeSPEC.md,ARCHITECTURE.md,DECISIONS.mdskeletonsCONTRIBUTING.md,dogfood-notes.mdskeletons.env.exampleconfiguration template.github/PULL_REQUEST_TEMPLATE.mdenforcing test checklist- Apache 2.0 license
- Dogfood rig on Base Sepolia (X402-3): Hono +
x402-hono@1.2.0server (src/dogfood/app.ts,api/[...all].tsfor Vercel,scripts/dev-server.tsfor local), paying client viax402-fetch@1.2.0(scripts/dogfood-client.ts), env validation with mainnet-RPC guard (src/dogfood/config.ts), and 20 unit tests covering config validation and 402 challenge generation. Verified end-to-end against the realx402.org/facilitatoron Base Sepolia — paid GET returned 200 with on-chain settlement tx0x8b53a04d…b3428. - Public Vercel production deploy (X402-3) on the
v1branch alias atx402trace-dogfood-git-v1-fardinvahdats-projects.vercel.appserving/(static landing) and/api/weather(x402-gated). Second on-chain settlement against the realx402.org/facilitatorfrom this production endpoint: tx0xc5758bf2…6cbf. - Five-failure-mode reproduction harness for X402-4 (
scripts/failure-modes.ts,pnpm dogfood:failure-modes <n>). Captures verbatim server/client/facilitator error strings for: wrong chain ID, expiredvalidBefore, insufficient USDC, malformed signature, facilitator unavailable. Full transcripts + diagnoses indogfood-notes.md§ Failure modes. - Week-1 pain synthesis (X402-6) in
dogfood-notes.md§ Top painful moments: 9 ranked pains + 5 wedge candidates (A reconciliation, B inspect+doctor, C bazaar-check, D cross-facilitator, E proxy substrate) — pulls from X402-3 dogfood capture, X402-4 failure modes, X402-5 GitHub catalogue, and the Notion Validation evidence page. CLAUDE.md updated to point future sessions at the table for feature-scope work. - v0.1 wedge accepted (X402-7) — ADR-001 in
DECISIONS.md: local HTTP proxy substrate + timeout-reconciliation engine on top. Scope tightened for the 5-week timeline: Base Sepolia only, single facilitator profile (x402.org/facilitator),exactEVM scheme only, detect-and-notify (no auto-refund in v0.1). CLAUDE.md status updated from "tentative" to "accepted." Bazaar diagnostics, cross-facilitator drift, inspect/doctor/versions deferred to v0.2+. - v0.1 implementation spec (X402-8) filled in at
SPEC.md: problem, solution, user flow (x402trace proxy --upstream <url> --reconcile+ sampleRECONCILED ⚠ settled-but-server-thinks-notoutput), v0.1 scope/v0.2 stretch lists, 5 success-criteria checkboxes, explicit out-of-scope non-goals, and per-competitor differentiation against xpay / x402scan / x402lint / x402-watch / zauth / PaySentry. Linked from CLAUDE.md. - v0.1 architecture (X402-9) finalized in
ARCHITECTURE.md: 5-component decomposition (Proxysrc/proxy/, Decodersrc/decoder/, Chainsrc/chain/, Reconciliationsrc/reconciliation/, CLIsrc/cli/) with one-example data flow traced through every component for happy / rejected / timeout outcomes; TypeScript interface stubs ready to become source code (PaymentRequirements, PaymentPayload, FacilitatorResponse, PaymentExchange, ExchangeOutcome, ChainTransfer, ReconciliationResult); canonical JSONL record format (the file is the API); full configuration precedence table; v0.2 extension points named per feature so no v0.1 component is rewritten for v0.2. - Proxy core (X402-10) in
src/proxy/: forward HTTP proxy that captures every exchange to a JSONL log + in-process event bus. Five modules —proxy.ts(server + forwarding via fetch),types.ts(ProxyEvent/CapturedRequest/CapturedResponse/ExchangeOutcomediscriminated union +isLikelyX402Exchangeheuristic),event-bus.ts(subscribe-with-queue-and-drop-counter pub-sub),jsonl-sink.ts(append-only writer),id.ts(per-exchange UUIDs). Hop-by-hop headers stripped per RFC; non-UTF8 bodies base64-encoded; upstream timeout returns 502 +proxy.errorevent. Deliberately dumb — no x402 parsing here (that's X402-11). 18 new unit tests + a 4-test integration that pipes the dogfood client through the proxy at the X402-3 dogfood rig (mock-facilitator-backed) and asserts both the 402 and the paid 200 are captured to JSONL with the right discriminants.pnpm proxy --upstream <url>runs it; fullnpx x402trace proxyplumbing arrives in X402-14. - x402 message decoder (X402-11) in
src/decoder/: pure-function parsers that turn rawProxyEvents into typedDecodedEvents. Six modules —types.ts(PaymentRequirements,PaymentAuthorization,PaymentPayload,FacilitatorResponse,DecodedEventdiscriminated union),parse.ts(parseChallengeBody,parsePaymentHeader,parseSettlementHeader, version detection — usesx402/schemes'sexact.evm.decodePaymentfor the v1 fast path, hand-rolled fallback for v2),redact.ts(signature redaction by default,--log-secretsopt-in),format.ts(formatHumanandformatJson),decoder.ts(createDecoder()stream consumer),index.ts. Includessrc/decoder/schema.mddocumenting the JSONL contract. Handles both v1 (X-PAYMENT/X-PAYMENT-RESPONSE) and v2 (PAYMENT-SIGNATURE/PAYMENT-RESPONSE) header surfaces. 22 new unit tests covering v1 + v2 parsers, redaction, formatting + a 4-test integration that asserts the proxy→decoder→PaymentExchange pipeline produces the right structured events from a paid flow. Stdout sample of the readable output is captured indogfood-notes.md§ "Decoder readable-output sample" per acceptance criterion;pnpm decoder:demoreproduces it. - Base RPC client (X402-12) in
src/chain/: thin viem wrapper over Base Sepolia. Five modules —types.ts(ChainTransfer+VerifyTransferResult7-variant discriminated union:confirmed/pending/reverted/not_found/wrong_recipient/wrong_amount/wrong_token),abi.ts(Base Sepolia USDC address0x036CbD…CF7e+ Transfer + EIP-3009 AuthorizationUsed event ABIs),retry.ts(exponential backoff, 3 attempts default per ARCHITECTURE.md),client.ts(createChainClientwithverifyTransfer,getTransferByTxHash,subscribeUsdcTransfersAsyncIterable,getBlockNumber),index.ts. Read-only — never holds private keys, never broadcasts.subscribeUsdcTransfersenriches eachTransferevent with the matchingAuthorizationUsed.noncefrom the same tx so the reconciliation engine can match by EIP-3009 nonce. 16 unit tests with mocked transport covering all 7 verify variants + the retry helper + 4 e2e tests against live Base Sepolia (gated byX402_E2E=1; verified against the two real X402-3 settlement txs0x8b53a04d…b3428and0xc5758bf2…6cbf). - GitHub Actions CI (X402-18) —
.github/workflows/ci.ymlrunspnpm typecheck,pnpm lint,pnpm test,pnpm buildon every PR + push tov1/staging/main. Node 20 + 22 matrix, pnpm-store cached viaactions/setup-node@v4, 10-min job timeout, in-progress runs cancelled when a fresher commit lands on the same ref.pnpm testauto-skipstests/e2e/chain-live.test.ts(gated onX402_E2E=1), so the live-Base-Sepolia suite never burns testnet funds in CI. Companion.github/workflows/release.ymlis the X402-19 substrate: tag-triggered (v*.*.*), runs the same quality bar, builds, and has the npm-publish step commented out until X402-19 wires theNPM_TOKENsecret. The CI badge inREADME.md(placeholder since X402-16) lights up on the first run. - Unit-test coverage hardening (X402-17) — Week-4 risk-reduction pass on the decoder, reconciliation, and CLI. Per the ticket spec ("chase risk reduction, not coverage targets"), targeted the gaps that break silently: (a) the streaming decoder's dispatch logic (
createDecoder().decode()) had zero direct tests — newtests/unit/decoder-decoder.test.ts(11 tests) covers settlement-header parse errors, theoutcome.rawPaymentResponseHeaderfallback path, andproxy.errorpassthrough; (b) base64 + unicode edge cases onparsePaymentHeader/parseChallengeBody/parseSettlementHeadermirroring the coinbase/x402#865 surface area — CJK + emoji indescriptionandextra.nameround-trip, padding-stripped base64 tolerated, JSON primitives rejected, URL-safe base64 contract documented; (c) reconciliationextractErrorReasonpaths (non-JSON rawBody, JSON withouterror, missing rawBody), theunknownproxy outcome branch the X402-15 demo relies on, chain-transfer race conditions (arrival before pending join, duplicate transfers after match); (d)x402trace proxyenv-var-vs-flag precedence — extractedresolveProxyConfig(opts, env)as a pure function fromproxy-command.ts(small refactor for testability), then pinned the contract from ARCHITECTURE.md § Configuration with 28 unit tests acrossX402TRACE_UPSTREAM,X402TRACE_PORT,X402TRACE_LOG,X402TRACE_RECONCILE(including non-truthy strings),BASE_RPC_URL,X402TRACE_WATCH_TIMEOUT_MS,X402TRACE_UPSTREAM_TIMEOUT_MS. Pipeline: 215 tests / 4 e2e skipped (+57 vs the X402-16 baseline). Coverage: reconciliation 97% → 100% lines/branches/funcs, decoder 79% → 90%, src/cli 80.8% → 82%. - README rewrite (X402-16): replaced the pre-release skeleton with a 115-line README that hits all seven X402-16 required sections — elevator pitch, the problem (with the verbatim 502 the buyer sees), 30-second quickstart (
git clone→pnpm install→cp .env.example .env→./examples/e2e-timeout-reconciliation.sh), how-it-works (4 bullets + ASCII flow diagram of proxy → server → facilitator → chain → engine), CLI reference (links to--helprather than duplicating), v0.2 roadmap pulled from SPEC.md § 5, differentiation, contributing, license. Three badges added: Apache-2.0 license (live), GitHub Actions CI (placeholder — wires up automatically when X402-18 lands the workflow), npm version (placeholder — wires up automatically when X402-19 publishesx402trace@0.1.0). The captured X402-15 settlement tx0x116ccf73…ba52is linked from the quickstart's "what success looks like" output block; the asciinema cast is referenced by relative path for localasciinema playuntil X402-23 uploads it for an embeddable URL. - End-to-end testnet demo (X402-15) in
examples/e2e-timeout-reconciliation.sh+examples/README.md+ recorded asciinema cast atexamples/cast/e2e-timeout-reconciliation.cast. The flagship reproduction of the canonical #1062 scenario, runnable against real Base Sepolia + the realx402.org/facilitator. Choreography: dogfood server with newDEMO_SLEEP_MS=10000knob sleeps 10s during the x402-hono paymentMiddleware's protected handler — x402-hono verifies before the handler and settles after, so the post-handler /settle still broadcasts on-chain even though the proxy in front gave up at 5s.x402trace proxy --reconcile --upstream-timeout-ms 5000returns 502 to the client (the canonical "I thought it failed" signal), pends the exchange, watches Base Sepolia via the chain client'ssubscribeUsdcTransfers, matches the EIP-3009 nonce, and emitsRECONCILED ⚠ settled-but-server-thinks-notwith the live tx hash. Verified end-to-end on Base Sepolia 2026-05-12 with on-chain settlement tx0x116ccf73…ba52(block 41402768) — captured in the committed asciinema cast; reconcile gap from proxy timeout to chain-detected was 11.9 seconds. New--upstream-timeout-msCLI flag onx402trace proxyexposesProxyOptions.upstreamTimeoutMsto the surface. NewdemoSleepMs/demoFailAfterSleepfields onDogfoodConfigplusDEMO_SLEEP_MS/DEMO_FAIL_AFTER_SLEEPenv wiring inloadServerConfig(demoFailAfterSleepis documented but NOT used in the canonical demo because x402-hono skips /settle on 5xx responses). 9 new tests — 8 unit tests covering env validation (positive/negative/zero/non-numeric) and CLI help inclusion, plus 1 hermetic integration test (tests/integration/demo-timeout-reconciliation.test.ts) that reproduces the full demo storyline against the mock facilitator with a syntheticChainTransfer. Pipeline: 158 tests / 4 e2e skipped, typecheck + lint clean. README rewritten with a quick-demo hero section. - CLI binary (X402-14) in
src/cli/+src/cli.ts: the user-facing surface for v0.1, composing the four substrates (Proxy / Decoder / Chain / Reconciliation) into a singlex402tracecommand. Two subcommands —x402trace proxy --upstream <url> [--port 8402] [--log human|json] [--log-file <path>] [--log-secrets] [--reconcile] [--rpc-url <url>] [--watch-timeout-ms <n>]runs the live pipeline;x402trace inspect <jsonl-log-file> [--log human|json] [--watch-timeout-ms <n>]replays a captured log and re-runs reconciliation offline. Built oncommander@14. Modules —index.ts(commander dispatch +runClitestable entry),proxy-command.ts(live pipeline wiring; opens a secondJsonlSinkagainst the same log path forchain.transferandreconcile.resultrecords — closes the X402-13 deferred acceptance bullet "All reconciliation events written to a local JSONL log file"),inspect-command.ts(offline replay),replay.ts(JSONL reader driving a virtual-clock engine via the newengine.tick()sweep method),format-result.ts(human + JSON renderers forReconciliationResult— the canonicalRECONCILED ⚠ settled-but-server-thinks-notheadline per SPEC.md § 3),color.ts(TTY +NO_COLOR-aware ANSI helpers; nochalkdep),exit-codes.ts(0 success / 1 usage / 2 runtime per the X402-14 ticket). 28 new unit tests + 1 smoke test that drives the full proxy + decoder + JSONL pipeline against the dogfood rig and replays the captured log throughinspect. Pipeline: 149 tests / 4 e2e skipped, typecheck + lint clean.scripts/proxy.tsis now a thin shim that defers torunCli;pnpm proxyandpnpm x402traceboth work, andnpx x402tracewill work once published. - Timeout reconciliation engine (X402-13) in
src/reconciliation/: the headline feature of the v0.1 wedge. Four modules —types.ts(PendingExchange+ReconciliationResult4-variant discriminated union:settled_on_chain/not_settled/value_mismatch/recipient_mismatch),match.ts(purematchPendingAgainstTransferchecking(payer, payee, value, nonce)exact-equality; case-insensitive on addresses + nonce),engine.ts(createReconciliationEngine({watchTimeoutMs?, sweepIntervalMs?, now?}) → Enginewith three ingest methods for proxy / decoder / chain streams, in-memory pending-set with two-half-join semantics, periodic sweep fornot_settledafterwatchTimeoutMs(default 60_000), injectable clock for tests),index.ts. 17 unit tests covering the match function + engine lifecycle (rejected outcomes flag, paid outcomes don't, settled_on_chain emit, not_settled timeout, value/recipient mismatch, arrival-order independence) + 2-test integration that reproduces the canonical #1062 scenario end-to-end: mock facilitator rejects → engine emitssettled_on_chainwhen matching ChainTransfer arrives. Pipeline: 120 tests / 4 e2e skipped, no new deps. - Local mock x402 facilitator (X402-3) at
src/dogfood/mock-facilitator.tsimplementing v1POST /verifyandPOST /settlewith canned-success responses. Unblocks integration testing without on-chain USDC and provides the test harnessTESTING.mdcalls for.scripts/mock-facilitator.tsis the standalone entry;tests/integration/dogfood-paid-flow.test.tsasserts the full 402 → signed retry → 200 +X-PAYMENT-RESPONSEflow against it. - Shared
src/dogfood/http-adapter.tsfor mounting a Hono app on a Nodehttpserver (used bydev-server.ts,mock-facilitator.ts, and the integration test). Avoids a separate@hono/node-serverdep. - Minimal
tsconfig.json(strict ESM NodeNext),vitest.config.ts,eslint.config.js(flat), and.prettierrc.jsonscoped to what X402-3 needed. X402-10 will tighten and broaden these.
Changed
—
Deprecated
—
Removed
—
Fixed
—
Security
—