feat(x402): public helpers for 402-emit + probe extra.name fix (1.3.4)#11
Merged
vvillait88 merged 2 commits intomainfrom May 5, 2026
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cleanup PR after the verify→settle fix chain (1.3.1 / 1.3.2 / 1.3.3). Two parallel goals:
extra.namemistake (which silently breaks every signature at the facilitator on base mainnet) can't recur.extra: {"name": "USDC"}regardless of network (correct on sepolia, wrong on mainnet — the actual USDC contract on Base mainnet returnsname() = "USD Coin", sepolia returns"USDC").New public API
Returns a list of plain dicts ready for the 402 body's
accepts[]. The helper:ResourceConfigfrom kwargs (so callers don't import the x402 type)server.build_payment_requirements(...)which fills inextrafrom the registered scheme metadata (sepolia gets"USDC", mainnet gets"USD Coin", future networks get whatever the registered scheme says)PaymentRequirementsto wire-shape viamodel_dump(by_alias=True, mode="json")Also promoted from private (no behavior change; just public stability):
coerce_resource_config(dict →ResourceConfig)coerce_payment_payload(dict →PaymentPayload/PaymentPayloadV1perx402Version)settle_result_to_json_bytes(PydanticSettleResponse→ JSON bytes viamodel_dump_json(by_alias=True); falls through tojson.dumpsfor plain dicts)Probe / examples / tests
discovery/probe.py:sample_x402_accept_for_networknow returns:eip155:8453→extra: {"name": "USD Coin", "version": "2"}(was"USDC", wrong)eip155:84532→extra: {"name": "USDC", "version": "2"}(matches the sepolia contract)examples/multi_rail_merchant.py+examples/api_provider.py: same fix, comment points atbuild_x402_accepts_for_402as the cleaner production pattern.tests/test_discovery.py: assertions updated; added explicit sepolia assertion.Tests
test_build_x402_accepts_for_402_returns_dicts_from_typed_requirements(passes typedResourceConfigto the server, returns wire-shape dicts withpayTo/maxTimeoutSeconds/extracamelCase)Doc updates
README.md+CLAUDE.md+core/docs/integrations/python-commerce.mdxall gain abuild_x402_accepts_for_402snippet.Test plan
v1.3.4, push tag → PyPI publish.core/store/uv.lockto 1.3.4 + refactorstore/routes/purchase.pyto use the new helper (folded into existing core PR #237).🤖 Generated with Claude Code