Skip to content

feat(x402): public helpers for 402-emit + probe extra.name fix (1.3.4)#11

Merged
vvillait88 merged 2 commits intomainfrom
feat/x402-public-helpers-and-probe-fix
May 5, 2026
Merged

feat(x402): public helpers for 402-emit + probe extra.name fix (1.3.4)#11
vvillait88 merged 2 commits intomainfrom
feat/x402-public-helpers-and-probe-fix

Conversation

@vvillait88
Copy link
Copy Markdown
Contributor

Summary

Cleanup PR after the verify→settle fix chain (1.3.1 / 1.3.2 / 1.3.3). Two parallel goals:

  1. Lift up the boilerplate every Python merchant inlined for emitting x402 challenges so the extra.name mistake (which silently breaks every signature at the facilitator on base mainnet) can't recur.
  2. Fix the discovery probe + examples + tests that hardcoded extra: {"name": "USDC"} regardless of network (correct on sepolia, wrong on mainnet — the actual USDC contract on Base mainnet returns name() = "USD Coin", sepolia returns "USDC").

New public API

from agentscore_commerce.payment import build_x402_accepts_for_402

x402_accepts = build_x402_accepts_for_402(
    x402_server,
    network="eip155:8453",
    price=f"${total_usd}",
    pay_to=os.environ["TREASURY_BASE_RECIPIENT"],
    max_timeout_seconds=300,
)

Returns a list of plain dicts ready for the 402 body's accepts[]. The helper:

  • Builds a typed ResourceConfig from kwargs (so callers don't import the x402 type)
  • Calls server.build_payment_requirements(...) which fills in extra from the registered scheme metadata (sepolia gets "USDC", mainnet gets "USD Coin", future networks get whatever the registered scheme says)
  • Dumps each Pydantic PaymentRequirements to wire-shape via model_dump(by_alias=True, mode="json")
  • Falls through for dict-shaped requirements (older x402 / test stubs)

Also promoted from private (no behavior change; just public stability):

  • coerce_resource_config (dict → ResourceConfig)
  • coerce_payment_payload (dict → PaymentPayload / PaymentPayloadV1 per x402Version)
  • settle_result_to_json_bytes (Pydantic SettleResponse → JSON bytes via model_dump_json(by_alias=True); falls through to json.dumps for plain dicts)

Probe / examples / tests

  • discovery/probe.py: sample_x402_accept_for_network now returns:
    • eip155:8453extra: {"name": "USD Coin", "version": "2"} (was "USDC", wrong)
    • eip155:84532extra: {"name": "USDC", "version": "2"} (matches the sepolia contract)
  • examples/multi_rail_merchant.py + examples/api_provider.py: same fix, comment points at build_x402_accepts_for_402 as the cleaner production pattern.
  • tests/test_discovery.py: assertions updated; added explicit sepolia assertion.

Tests

  • New: test_build_x402_accepts_for_402_returns_dicts_from_typed_requirements (passes typed ResourceConfig to the server, returns wire-shape dicts with payTo/maxTimeoutSeconds/extra camelCase)
  • Full suite: 724 passed, 3 skipped, 95.16% coverage.

Doc updates

  • README.md + CLAUDE.md + core/docs/integrations/python-commerce.mdx all gain a build_x402_accepts_for_402 snippet.

Test plan

  • CI green.
  • Tag v1.3.4, push tag → PyPI publish.
  • Bump core/store/uv.lock to 1.3.4 + refactor store/routes/purchase.py to use the new helper (folded into existing core PR #237).

🤖 Generated with Claude Code

vvillait88 and others added 2 commits May 5, 2026 08:53
…ix (1.3.4)

Lifts up the boilerplate every Python merchant currently inlines for emitting
x402 challenges and ports the discovery probe to match the actual on-chain
USDC contract names.

New public exports
- build_x402_accepts_for_402(server, *, network, price, pay_to, ...) -> list[dict]
  Wraps server.build_payment_requirements() so merchants don't import x402's
  ResourceConfig type, don't model_dump(by_alias=True) each Pydantic
  PaymentRequirements, and don't hardcode extra.name (which differs by network:
  base mainnet USDC returns "USD Coin", base sepolia returns "USDC" — wrong
  value silently breaks every signature at the facilitator). Falls through for
  dict-shaped requirements (older x402 / test stubs).
- coerce_resource_config / coerce_payment_payload / settle_result_to_json_bytes
  Promoted from private. Used internally by process_x402_settle but useful to
  callers that need the same coerce on a custom verify+settle path.

Probe / examples / tests
- discovery/probe.py: sample_x402_accept_for_network now returns
  extra: {"name": "USD Coin", "version": "2"} for eip155:8453 and
  extra: {"name": "USDC", "version": "2"} for eip155:84532, matching the
  actual contract name() values. Was previously hardcoded "USDC" for both
  networks (correct only on sepolia).
- examples/multi_rail_merchant.py + examples/api_provider.py: same fix; comment
  points at build_x402_accepts_for_402 as the cleaner production pattern.
- tests/test_discovery.py: assertions updated to match the contract-derived
  values; added new sepolia-specific assertion.

Tests: 724 passed, 95.16% coverage. Lint + ty clean.
Doc updates: CLAUDE.md, README.md, mintlify python-commerce.mdx all gain a
build_x402_accepts_for_402 snippet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… inline

Replaces the hand-rolled x402-base accept (with its conditional ``extra.name``
based on network) with one call to ``build_x402_accepts_for_402``. The helper
fills in the right ``extra.name`` from the registered scheme metadata.

Solana stays inline because it goes through MPP ``solana/charge``, not x402's
exact scheme. ``api_provider.py`` keeps the inline pattern (smallest possible
example) with a comment pointing at the helper as the production pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vvillait88 vvillait88 merged commit c13bb38 into main May 5, 2026
7 checks passed
@vvillait88 vvillait88 deleted the feat/x402-public-helpers-and-probe-fix branch May 5, 2026 15:59
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.

1 participant