Skip to content

feat(solana)!: route --chain solana through MPP solana/charge#17

Merged
vvillait88 merged 9 commits intomainfrom
feat/solana-mpp
May 5, 2026
Merged

feat(solana)!: route --chain solana through MPP solana/charge#17
vvillait88 merged 9 commits intomainfrom
feat/solana-mpp

Conversation

@vvillait88
Copy link
Copy Markdown
Contributor

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 --chain solana ...` continues to work; pay handles the protocol switch transparently.

Test plan

  • 352 tests pass (vitest)
  • Local dry-run shows `protocol: mpp` for `--chain solana`
  • 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

vvillait88 and others added 8 commits May 4, 2026 14:59
Pairs with agentscore/node-commerce#8 + agentscore/python-commerce#7 +
agentscore/martin-estate#56.

x402 Solana via @x402/svm omitted the idempotent ATA-create instruction in the
buyer's SPL transfer tx, so payments failed against any payTo whose USDC ATA
wasn't pre-warmed (every Stripe-multichain rotating address). Per upstream
x402-foundation/x402#1020 ATA pre-creation is the seller's responsibility by
design and won't be fixed in @x402/svm. MPP solana/charge bakes the
createAssociatedTokenIdempotent instruction into the buyer's tx, so it works
against any payTo.

Changes:
- Drop @x402/svm dep + ExactSvmScheme registration
- Add @solana/mpp client peer dep
- Route wallet.chain === 'solana' through payViaMpp (was payViaX402)
- payViaMpp registers solanaCharge({signer, rpcUrl}) when wallet.chain is solana,
  tempo({account}) when wallet.chain is tempo
- Protocol determination: 'x402' for base, 'mpp' for solana + tempo
- payViaX402 throws unsupported_rail for non-base chains
- All else unchanged (passport attach, max-spend, idempotency, retries, etc.)

352 tests pass. Architecture validated end-to-end against local martin-estate
through the simulate step (broadcast deferred on devnet RPC reliability; not a
code bug).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User-visible surfaces (CLI description in --help / --mcp / --llms,
README rails table, agent-guide intro) were still claiming x402 on
Solana via @x402/svm. Solana actually routes through MPP `solana/charge`
via @solana/mpp/client; clarify that everywhere the user can see.

- src/cli.ts:88 description: x402 on Base + Solana, MPP on Tempo
  -> x402 on Base, MPP on Tempo + Solana
- README rails table: solana row now MPP `solana/charge` via mppx +
  @solana/mpp/client (was x402 SPL Token via @x402/svm)
- src/commands/agent-guide.ts intro mirrors the new rail topology
- .claude/CLAUDE.md chain-to-protocol routing maps `solana` to
  `solanaCharge` from @solana/mpp/client; drops ExactSvmScheme
- tests/integration/README.md future-test description updated
Public-package source + README must describe behavior, not exact defense
parameters or named-customer business domains.

- README.md: drop the exact scrypt KDF parameters (`N=131072, r=8, p=1`)
  from the user-facing security note; the algorithm name is enough
- src/commands/identity.ts: rate-limit retry suggestion no longer surfaces
  the "<= 1s" window granularity; points at the Retry-After header instead
- src/commands/agent-guide.ts: regulated-commerce examples no longer name
  "wine" specifically; the regulated categories (age-restricted, jurisdiction-
  restricted, compliance-gated) are sufficient to convey the gating need
The `sessions create --context` describe text + the example block both
used "wine purchase" as the canonical context value. Replace with a
neutral "checkout" so the CLI manifest (visible to every LLM agent
consuming `agentscore-pay --llms`) doesn't bake a customer's business
domain into the public surface.
The Solana-rail explanation under ## Rails carried "the x402 SVM scheme had
a known idempotent-ATA-create gap that the upstream maintainers won't fix".
That's upstream finger-pointing in user-facing README copy. Replace with the
factual statement (Solana payments use MPP `solana/charge` via the Solana
Foundation package); the protocol choice stands without narration of the
upstream gap.
The agent-guide note for `limits set` framed the local-only nature of the
limits in threat-model terms ("they protect against runaway agent loops,
not malicious merchants"). Restate as behavior-only: "the merchant cannot
read or override them."
The npm package description still claimed "x402 (Base, Solana) and MPP
(Tempo)". Solana is MPP `solana/charge`, not x402, since 1.4. Match the
README and cli.ts wording: "x402 (Base) and MPP (Tempo, Solana)."
Ships the cycle's pay changes (drop @x402/svm, route --chain solana
through MPP `solana/charge` via @solana/mpp/client, dual-audience output
updates, internal-disclosure scrubs) so the trusted-publishing workflow
fires on tag push.

Per pay versioning policy, pay stays on 0.X until coordinated launch.
vvillait88 added a commit to agentscore/node-commerce that referenced this pull request May 5, 2026
…fy+extraWarnings + x402scan discovery (#8)

## Summary

Major release pulling together everything that landed on this branch
beyond the original facilitator-error fix. Ships as `1.3.0` (next minor
after `1.2.0`).

**1. Kill x402 Solana, add MPP `solana/charge` (BREAKING)**
- Drop `x402-solana-mainnet` / `x402-solana-devnet` rails from the
registry.
- Drop SVM from `validateX402NetworkConfig` (now base-only) and
`verifyX402Request` (single `acceptedNetwork: string`, was `{base, svm}`
object).
- Drop SVM from `createX402Server` rail union; drop the SVM extraction
branch in `extractPaymentSigner`.
- Add `mpp-solana-mainnet` / `mpp-solana-devnet` rails
(method=`solana`).
- Add `solana` rail to `createMppxServer`, wiring `@solana/mpp/server`
charge.
- Drop `@x402/svm` from devDependencies; add `@solana/mpp` +
`@solana/kit` as optional peer deps.
- Cross-rename: `x402_solana` → `solana_mpp` field across challenge
builders + discovery surfaces.

**2. New helpers**
- `classifyX402SettleResult` collapses `processX402Settle` failure
phases (`verify_failed` / `settle_failed` / `facilitator_error` /
`no_requirements`) to `(status, code, message, next_steps)`. Maps to 400
/ 503 / 503 / 500.
- `extraWarnings` field on `buildAgentInstructions` — append per-order
warnings on top of the SDK's default protocol-footgun set.
- Solana-network rejection branch in `verifyX402Request`: clients
presenting an x402 credential on a `solana:*` network get a
behavior-only hint pointing at the `solana/charge` rail.
Case-insensitive prefix match.
- `verifyX402Request` now wraps facilitator throws as `phase:
'facilitator_error'` cleanly across `build_requirements`,
`enrich_extensions`, and `process_payment_request` steps.

**3. Solana signer recovery in `extractPaymentSigner`**
- New 3-step MPP recovery order: (a) EVM DID
`did:pkh:eip155:<chain>:<addr>` source, (b) Solana DID
`did:pkh:solana:<genesis>:<addr>` source, (c) pull-mode tx decode that
reads the SPL `TransferChecked` authority via `@solana/kit` (optional
peer).
- Fixes the wallet-signer-match gap on Solana MPP: previously the
recovered signer was always null on Solana credentials because
`@solana/mpp/client` doesn't set `Credential.source`, which silently
weakened wallet-auth security relative to EVM. Now the gate's
`verifyWalletSignerMatch` enforces same-operator on Solana the same way
it does on EVM.
- Pull mode (`payload.type === 'transaction'`) covered. Push mode
(`payload.type === 'signature'`) returns null (RPC fetch deferred; pay
uses pull mode by default).
- Defensive bounds-check on lookup-table account indices (v0 txs).

**4. x402scan discovery (Layer 1)**
- New `buildWellKnownX402` builder for the `/.well-known/x402` endpoint.
- New OpenAPI extension helpers (`siwxSecurityScheme`,
`xPaymentInfoExtension`, `xGuidanceExtension`).

**5. Internal-disclosure cleanup**
- Replaced threat-model rationale in source comments with behavior-only
language across `core.ts`, `stripe-multichain/pi-cache.ts`,
`payment/x402_validation.ts`, `signer-match.test.ts`.
- Removed exact defense parameters from user-visible places.
- Genericized customer-domain attributions baked into examples /
docstrings / tests (Martin Estate / martinestate.com / wine-purchase →
Example Merchant / agents.example.com / product-purchase).
- Removed internal state name `pending_identity` from agent-visible
warnings; agents see "the order will not complete."
- Dropped upstream finger-pointing from README rails section.

## Test plan

- [x] 660 vitest tests passing (4 skipped)
- [x] eslint + typecheck clean
- [x] CodeQL + dependency scan green

## Downstream impact

Once this lands and `@agent-score/commerce@1.3.0` publishes:
- `agentscore/martin-estate#56` will pass CI (currently red on
`acceptedNetwork`, `extraWarnings`, `solana_mpp` RailKey).
- `agentscore/pay#17` consumes the new SDK shape via the rails registry.
- Solana wallet-signer-match enforcement turns on automatically for any
martin-estate Solana settle.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mppx 0.6.14 → 0.6.15
typescript-eslint 8.59.1 → 8.59.2

Held back:
- viem 2.48.7 (exact-pinned; 2.48.8 has a known regression)
- eslint 9 → 10 + @eslint/js 9 → 10 (major-bump separate decision)

Tests: 354 passing, lint + typecheck clean.
@vvillait88 vvillait88 merged commit 8caa045 into main May 5, 2026
6 checks passed
@vvillait88 vvillait88 deleted the feat/solana-mpp branch May 5, 2026 08:53
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