Skip to content

feat(commerce 1.3)!: kill x402 Solana, add MPP solana/charge + classify+extra_warnings + x402scan discovery#7

Merged
vvillait88 merged 12 commits intomainfrom
feat/x402-settle-facilitator-error
May 5, 2026
Merged

feat(commerce 1.3)!: kill x402 Solana, add MPP solana/charge + classify+extra_warnings + x402scan discovery#7
vvillait88 merged 12 commits intomainfrom
feat/x402-settle-facilitator-error

Conversation

@vvillait88
Copy link
Copy Markdown
Contributor

@vvillait88 vvillait88 commented May 4, 2026

Summary

Mirrors agentscore/node-commerce#8 on the Python side. Ships as 1.3.0 (next minor after 1.2.0).

1. Kill x402 Solana (BREAKING)

  • Drop x402-solana-* rails. Add mpp-solana-* rails (method=solana).
  • validate_x402_network_config now base-only (base_network: str, was {base, svm} object).
  • verify_x402_request takes single accepted_network: str.
  • Rename X402SolanaConfigSolanaMppConfig, X402SolanaRailConfigSolanaMppRailConfig.
  • pyproject x402[evm,fastapi]>=2.8,<3 (no svm extra).
  • Python parity gap (intentional): there is no Python equivalent of @solana/mpp, so create_mppx_server does not ship a Solana rail. Python merchants on Solana implement MPP solana/charge server-side themselves.

2. New helpers

  • classify_x402_settle_result collapses process_x402_settle failure phases (verify_failed / settle_failed / facilitator_error / no_requirements) to (status, code, message, next_steps). Mapping is identical to the node side: 400 / 503 / 503 / 500.
  • extra_warnings field on BuildAgentInstructionsInput; append per-order warnings on top of defaults.
  • Solana-network rejection branch in verify_x402_request: clients presenting an x402 credential on a solana:* network get a behavior-only hint pointing at the solana/charge rail.
  • process_x402_settle wraps facilitator throws as phase='facilitator_error' across all steps.

3. x402scan discovery (Layer 1)

  • build_well_known_x402 builder.
  • OpenAPI extension helpers (siwx_security_scheme, x_payment_info_extension, x_guidance_extension).

4. Internal-disclosure cleanup

  • Replaced threat-model rationale in source docstrings with behavior-only language (stripe_multichain/pi_cache.py, identity/types.py, identity/signer.py).
  • Genericized Martin Estate / martinestate.com / martin-estate-wine-commerce / "wine-purchase" baked into examples + tests + docstrings.
  • Removed pending_identity internal state name from agent-visible warnings.
  • Removed x402[evm,svm] references from CLAUDE.md (the upstream extra is gone; we depend on x402[evm]).

Test plan

  • 715 pytest tests passing (3 skipped)
  • ruff + ty all green
  • CodeQL + dependency + python audit scans green

Downstream impact

Once this lands and agentscore-commerce==1.3.0 publishes:

  • agentscore/core#230 will pass CI (currently red on agentscore-commerce[mppx,x402]>=1.3.0 resolution).
  • The store route's verify_x402_request + process_x402_settle + classify_x402_settle_result wiring depends on this SDK release.

🤖 Generated with Claude Code

vvillait88 and others added 3 commits May 4, 2026 11:21
…_result

Mirrors agentscore/node-commerce#8 in python-commerce.

Adds a fourth phase to ProcessX402SettleFailure: 'facilitator_error'.
The verify-stage calls (build_payment_requirements, enrich_extensions,
process_payment_request) are now wrapped in try/except. A facilitator
that raises (e.g. CDP rejecting solana:devnet because it only supports
mainnet) returns phase='facilitator_error' with a 'step' field instead
of bubbling up as an opaque 500. settle_failed semantics preserved.

Adds classify_x402_settle_result(result) that maps the tagged result to
a ClassifiedX402Error envelope with controlled status/code/message/next_steps:

  verify_failed       -> 400 payment_proof_invalid       / regenerate_payment_credential
  facilitator_error   -> 503 payment_provider_unavailable / try_different_rail
  settle_failed       -> 503 payment_provider_unavailable / retry_or_swap_method (retry_after_seconds: 10)
  no_requirements     -> 500 payment_internal_error      / contact_support

The returned envelope is intentionally facilitator-agnostic and never
carries raw error detail; merchants log result server-side and return
the classified envelope to the consumer.

Updates README drop-in example to use the helper. Bumps to 1.3.0.
708 tests pass at 95.46% coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t-info, x-guidance, siwx)

Mirrors agentscore/node-commerce@01010df.

- build_well_known_x402(): emits {version: 1, resources: ["METHOD /path"]} for /.well-known/x402
- x_payment_info_extension(): per-operation x-payment-info with fixed/dynamic price + protocols
- x_guidance_extension(): info.x-guidance prose blob
- siwx_security_scheme(): http bearer SIWX entry
- agentscore_security_schemes() now includes siwx alongside OperatorToken + WalletAddress
- /.well-known/x402 added to DEFAULT_DISCOVERY_PATHS

Smoke verified: spun up a stdlib http.server emitting all the new docs and ran
@agentcash/discovery against it. Output identical to the node smoke:

    Source:   openapi
    Routes:   3
      POST  /purchase  paid  0.10 USD  [x402, mpp]
      POST  /quote     paid  0.01-5.00 USD  [x402]
      GET   /whoami    siwx
    Guidance: 29 tokens

716 tests pass at 95.52% coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors agentscore/node-commerce@32ea663. Drops x402-Solana from python-commerce
since the upstream x402[svm] Python lib has the same InvalidAccountData problem
and no Python equivalent of @solana/mpp exists yet (pympp extras are [server,
tempo, stripe, redis, sqlite] — no solana).

So python-commerce drops x402 Solana for parity but does NOT add MPP Solana
server-side wiring. The accepted_methods / how_to_pay builders still emit the
solana/charge JSON shape so an agent-side @solana/mpp/client buyer can pay; Python
merchants must implement server-side verification themselves until a Python lib
ships.

Changes (BREAKING):
- Drop x402-solana-* rails, drop SVM from validation + x402_server
- validate_x402_network_config is now base-only; drop svm_network field
- verify_x402_request takes accepted_network: str (not {base, svm})
- Drop is_solana from VerifyX402RequestSuccess
- Rename x402_solana -> solana_mpp field in challenge builders + discovery
- Add mpp-solana-* rails to the registry
- Drop svm extra from x402 peer dep group
- Bump 1.3.0 -> 1.4.0

713 tests pass at 95.56% coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
vvillait88 added 6 commits May 4, 2026 17:35
…c cleanup

The 1.4.0 kill-x402-Solana commit renamed the public exports in
challenge/__init__.py but missed the underlying class definitions and
some example/test usages. This catches the gap so examples actually
import.

Renames:
- accepted_methods.X402SolanaConfig -> SolanaMppConfig
- how_to_pay.X402SolanaRailConfig -> SolanaMppRailConfig
- BuildAcceptedMethodsInput.solana_mpp now typed SolanaMppConfig
- HowToPayRails.solana_mpp now typed SolanaMppRailConfig
- examples/multi_rail_merchant.py + tests catch up to the rename

Other doc cleanup:
- examples/api_provider.py drops the dead x402 Solana accepts entry +
  the stale x402_svm_rail variable; advertises Solana via MPP rail
  directives in the WWW-Authenticate header (same path tempo uses)
- examples/multi_rail_merchant.py drops the dead `verified.is_solana`
  property (no longer on VerifyX402RequestSuccess); test-mode simulate
  now hardcoded to `network="base"` since x402 verify only runs on base
- identity/signer.py docstring clarifies Solana now goes through MPP
  (not x402), so this helper returns None for Solana payloads
- CLAUDE.md updates the api_provider description + drops `x402[evm,svm]`
  references (the upstream extra is gone; we depend on `x402[evm]`)
…gs hook

verify_x402_request gets a dedicated branch for incoming x402 credentials on a
solana:* network. Instead of the generic "Unsupported x402 network" message,
the response points the client at the `solana/charge` rail in the 402 challenge
so an agent on a stale x402 SVM client can recover with one re-sign.

Both the error message and next_steps stay behavior-only: no internal terminology,
no CLI vendor references, no protocol-handshake mechanics, no infra disclosure.

build_agent_instructions gets an `extra_warnings` field so merchants can append
per-order rail-availability notes on top of the SDK's protocol-footgun defaults
(without overriding them). Set `warnings` directly to override entirely;
`extra_warnings` is ignored in that case.

- agentscore_commerce/payment/x402_validation.py: new solana:* branch in the
  network-mismatch check
- agentscore_commerce/challenge/agent_instructions.py: BuildAgentInstructionsInput
  gets `extra_warnings`; default-warnings concat covers both rails + extras
- tests/test_lifted_helpers.py: assertion updated for the new Solana hint
- tests/test_challenge.py: two new tests for extra_warnings + the override case
Mirrors the node-commerce sweep on the python side:

- Removed "pending_identity" (internal order-state column value) from
  _TEMPO_WARNING + _X402_WARNING in agent_instructions.py and from the
  default llms.txt template; agents see "the order will not complete"
- Tightened the agentscore-pay tool description from "x402 on Base + Solana"
  to "x402 on Base and MPP on Tempo + Solana" so the rail-protocol mapping
  matches the 1.4.0 reality (Solana goes through MPP solana/charge)
- Genericized docstring examples in identity/a2a.py, identity/ucp.py,
  discovery/skill_md.py, challenge/order_receipt.py: "Martin Estate" +
  "agents.martinestate.com" + "wine-purchase" placeholders replaced with
  "Example Merchant" / "agents.example.com" / generic skills
- examples/multi_rail_merchant.py docstring renamed from "Martin-Estate-
  style" to "regulated-commerce" to describe the pattern, not the customer

Mintlify worked-example pages keep referencing Martin Estate by name;
that's allowed at the marketing/URL level per the disclosure-posture rules.
Public-package tests are part of the public surface (visible on the PyPI
sdist + GitHub source). The sessions / a2a / ucp / skill_md tests were
using "Martin Estate" / "https://agents.martinestate.com" / "martin-estate-
wine-commerce" as fixture values; that bakes a public reference customer's
domain into the SDK source. Swap to "Example Merchant" / "agents.example.com"
/ "example-merchant-commerce".
…string

Replace "Prevents agents from sending payment to an attacker-controlled
address and replaying the credential" with the behavior-only equivalent
("Validates the credential's deposit address against the addresses the
merchant has actually minted"). Mirrors the node-commerce pi-cache.ts
sweep.
…pi comment

The _CANONICAL_AGENTSCORE_API note explained why the URL was hardcoded in
threat-model terms ("Prevents a malicious merchant from emitting memory
pointing agents at their own phishing endpoints"). Behavior-only language
is enough; the canonical-URL choice stands without narrating the threat.
@vvillait88 vvillait88 changed the title feat(x402-settle): facilitator_error wrap + classify_x402_settle_result feat(commerce 1.4)!: kill x402 Solana, add MPP solana/charge + classify+extra_warnings + x402scan discovery May 5, 2026
Lands the cycle's commerce SDK changes (kill x402 Solana + add MPP
solana/charge + classify+extra_warnings + x402scan discovery + internal-
disclosure scrubs) as the next minor release after 1.2.0.
@vvillait88 vvillait88 changed the title feat(commerce 1.4)!: kill x402 Solana, add MPP solana/charge + classify+extra_warnings + x402scan discovery feat(commerce 1.3)!: kill x402 Solana, add MPP solana/charge + classify+extra_warnings + x402scan discovery May 5, 2026
…2_request

Mirrors the node-commerce defensive fix: `.lower().startswith('solana:')`
catches malformed credentials carrying uppercase or mixed-case CAIP-2
prefixes and routes them to the targeted Solana hint instead of the
generic 'Unsupported x402 network' fallback.
vvillait88 added a commit to agentscore/pay that referenced this pull request May 5, 2026
## Summary

Pairs with agentscore/node-commerce#8 + agentscore/python-commerce#7 +
agentscore/martin-estate#56.

\`--chain solana\` no longer registers \`@x402/svm\`'s
\`ExactSvmScheme\`. It now registers \`@solana/mpp/client\`'s \`charge\`
method on \`Mppx\` and pays via MPP \`solana/charge\`. Reason:
\`@x402/svm\`'s tx-builder omits the idempotent
\`createAssociatedTokenAccount\` instruction, so SPL transfers fail
against any payTo whose USDC ATA isn't pre-warmed (every
Stripe-multichain rotating deposit address). Upstream won't fix this per
x402-foundation/x402#1020 (rent-drain attack vector).

## Changes

- Drop \`@x402/svm\` dep
- Add \`@solana/mpp\` client peer dep
- \`wallet.chain === 'solana'\` now goes through \`payViaMpp\` (was
\`payViaX402\`)
- \`payViaMpp\` registers \`solanaCharge({signer, rpcUrl})\` for Solana
wallets, \`tempo({account})\` for Tempo
- \`Protocol\` is \`'x402'\` for base, \`'mpp'\` for Solana + Tempo
- \`payViaX402\` throws \`unsupported_rail\` for non-base chains
- Everything else unchanged (passport attach, \`--max-spend\`,
idempotency, retries, dry-run)

Agent UX is identical: \`agentscore-pay pay POST <url> --chain solana
...\` continues to work; pay handles the protocol switch transparently.

## Test plan

- [x] 352 tests pass (vitest)
- [x] Local dry-run shows \`protocol: mpp\` for \`--chain solana\`
- [x] Local live-run reaches simulate against martin-estate (broadcast
deferred on devnet RPC reliability)
- [ ] On-chain settle smoke (mainnet or alternate devnet RPC)
- [ ] Coordinate release with martin-estate#56 deploy + new
\`@agent-score/pay\` rc tag

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sentry-sdk 2.58.0 → 2.59.0
types-requests 2.33.0.20260408 → 2.33.0.20260503

Plus 5 transitive packages dropped from the lockfile (no longer reachable
from the resolved deps): construct, construct-typing, jsonalias, solana,
solders.

Tests: 715 passing, ruff + ty clean.
@vvillait88 vvillait88 merged commit e3ddf68 into main May 5, 2026
7 checks passed
@vvillait88 vvillait88 deleted the feat/x402-settle-facilitator-error branch May 5, 2026 10:36
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