Skip to content

feat(buy-x402): go auto-dispatch front door and buyer UX hardening#686

Closed
OisinKyne wants to merge 1 commit into
mainfrom
worktree-buyer-funnel
Closed

feat(buy-x402): go auto-dispatch front door and buyer UX hardening#686
OisinKyne wants to merge 1 commit into
mainfrom
worktree-buyer-funnel

Conversation

@OisinKyne

Copy link
Copy Markdown
Contributor

Summary

Why

Our first-try payment success rate for agents buying x402 services sits around 20%. A big share of failures happen before the first paid byte: a buying model must correctly choose pay vs pay-agent vs buy, the right --type, the right endpoint shape, and a payment option on multi-currency offers — four coin-flips a weak model reliably fumbles. Every wrong pre-flight decision is derivable from the seller's 402 response, so the tooling should make those decisions itself.

What

  • New buy.py go <url> [--message …] front door. Probes the endpoint, classifies the offer from the 402 (accepts[].extra.agentModel → agent; bazaar messages-shaped body → chat inference; otherwise plain HTTP) and dispatches to the right flow with the correct path, method, streaming mode, and timeout. Never creates persistent state; points at buy for pre-authorized pools.
  • Multi-currency auto-select. Non-interactive runs (i.e. every agent) no longer hard-error on multi-option offers; the first option the wallet can afford is selected, announced loudly with the full option list, and --token/--network/--payment-option still override. TTY prompt behavior unchanged.
  • Human money units. --budget and --cost-cap accept atomic units (1500000), token units (1.5), and symbol/$ forms ('1.5 USDC', '$1.50'). The parsed interpretation is echoed before signing; a symbol contradicting the settlement asset aborts. Weak buyers passing dollars where micro-units were expected was a silent 1,000,000× budgeting error.
  • Auth-expiry countdown in status. Pools expired silently before; the first symptom was a failed spend weeks later.
  • Timeout sanity. Non-streaming inference pay defaults to 600 s (was 100 s — a client-side kill risks paying for a response still in flight, and a blind retry double-pays). Streaming (go/pay-agent) remains the recommended shape.
  • Less surface to reason over. refill/remove stubs and deprecated maintain dropped from usage(); SKILL.md restructured to lead with go.

Testing

  • python3 -m py_compile + logic sanity checks for _parse_money_amount, _offer_shape, _pool_expiry_horizon (all forms verified, including wrong-symbol and garbage rejection)
  • go test ./internal/embed/... ./internal/buy/... (embedded-skill guards) green
  • Flows pass decimal budgets to the Go CLI, not buy.py, so no flow depends on the old atomic-only parsing; buy.py is now strictly more permissive

🤖 Generated with Claude Code

@bussyjd

bussyjd commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Superseded by #690.

The buyer-funnel stack (#686#687#688) and the x402 v2 PAYMENT-SIGNATURE fix (#689) are integrated into a single branch in #690, with the forwardauth.go conflict resolved keep-both and the emergent v2-metrics blind spot fixed (successful PAYMENT-SIGNATURE payments were unmetered). Whole-module build + affected suites green.

Leaving this open for the author to close in favor of #690.

OisinKyne added a commit that referenced this pull request Jul 2, 2026
…des #686–689) (#690)

* feat(buy-x402): go auto-dispatch front door and buyer UX hardening

* feat(catalog): single-source buyer prompts, docs deep links, signing metadata

- new internal/buyprompts package is the one authoring point for how-to-buy
  copy; the 402 page, /api/services.json ('buy' block with callShape +
  prompts per buyer-software kind), the storefront, and 'obol sell info'
  all render it, so instructions can no longer drift between surfaces
  (the storefront was still advertising the removed --no-verify-identity flag)
- catalog entries gain openapiPath + docsPath (Scalar deep-link anchor);
  storefront shows API-docs links for every offer type, skill.md service
  details link the anchored docs
- x-payment-info always emits accepts[] with payTo, CAIP-2 network, atomic
  amount, and the asset's EIP-712 signing domain, so an OpenAPI-only client
  can construct a valid X-PAYMENT without a second fetch
- @scalar/api-reference bumped 1.34.0 -> 1.62.1; renovate custom manager +
  postUpgradeTasks now keep it current, with scripts/update-scalar-sri.sh
  refreshing the SRI hash inside the same renovate PR

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(x402): tolerant chat-path gateway, structured payment errors, funnel metrics

- HandleProxy rewrites bare POST /services/<name> (and /chat/completions,
  /v1) to /v1/chat/completions for agent/inference offers, so the most
  common wrong-path mistake from external buyers succeeds instead of
  paying into a 404; the 402 page's agent copy taught exactly that bare
  form until this change
- terminal payment failures return structured JSON {error, reason, hint,
  retriable}; the facilitator's invalidReason (previously discarded) now
  rides the re-issued 402 challenge in error + extensions.paymentFailure,
  and signature rejections state the expected EIP-712 domain
- legacy error phrases kept verbatim (flows/lib.sh greps for them)
- new funnel metrics: payment_failure_reasons_total{reason} (bounded
  6-value set) and upstream_failed_after_verify_total, so first-try buyer
  success is measurable per stage; docs/observability.md updated

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(x402): accept x402 v2 PAYMENT-SIGNATURE header in verifier

The ForwardAuth verifier advertises x402Version 2 in its 402 challenge but only
read the payment from the legacy v1 X-PAYMENT header. Spec-compliant x402 v2
buyers (agentcash, poncho, coinbase SDK >= v2) attach the payment under
PAYMENT-SIGNATURE, so their valid payment was silently ignored and the caller
re-challenged — no verify, no settle, no log. This blocked every third-party v2
client from paying obol endpoints; only the in-tree buyer (which sends X-PAYMENT)
worked.

- Read the payment from X-PAYMENT (v1) OR PAYMENT-SIGNATURE (v2).
- Decode via the canonical x402types.ToPaymentPayload helper instead of a local
  json.Unmarshal, keeping the envelope in lockstep with the SDK.
- Mirror the settlement receipt onto both X-PAYMENT-RESPONSE and PAYMENT-RESPONSE.
- Tests for the v2 header accept + settle + dual response header.

Claude-Session: https://claude.ai/code/session_01VquWN9UMaSHH7MHGcG8bw1

* fix(x402): meter v2 (PAYMENT-SIGNATURE) payments in the funnel

Post-#689 a spec-compliant x402 v2 payment arrives under PAYMENT-SIGNATURE, but
the funnel gate hadPayment only checked X-PAYMENT — so every successful v2
payment (exactly the cohort #689 unblocked) incremented none of the
success/charge/upstream counters. Gate on both headers.

Emergent defect of integrating #688 (X-PAYMENT-gated metrics) with #689
(v2 via PAYMENT-SIGNATURE).

---------

Co-authored-by: Oisín Kyne <oisin@obol.tech>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: bussyjd <145845+bussyjd@users.noreply.github.com>
Co-authored-by: Oisín Kyne <4981644+OisinKyne@users.noreply.github.com>
@OisinKyne OisinKyne closed this Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants