Repository navigation
agents/x402 hardcodes the EVM scheme; docs claim Stellar/Sui/Aptos support that doesn't exist in the code #1863
Description
Problem
developers.cloudflare.com/agents/x402 states: "Supported networks include Base, Ethereum, Polygon, Optimism, Arbitrum, Avalanche, Solana, Aptos, Stellar, and Sui." In the actual implementation (packages/agents/src/mcp/x402.ts), only the EVM exact scheme is ever registered:
import { registerExactEvmScheme as registerServerEvmScheme } from "@x402/evm/exact/server"
import { registerExactEvmScheme as registerClientEvmScheme } from "@x402/evm/exact/client"withX402() calls registerServerEvmScheme(resourceServer) unconditionally — the only scheme registration in the file. withX402Client() calls registerClientEvmScheme(paymentClient, { signer: account }), same story. There's no Stellar, Solana, Aptos, or Sui import anywhere in the repo. @x402/core is scheme-agnostic and would accept a @x402/stellar (or similar) registration — the wrapper just never calls it, and gives no hook to swap it in.
Compounding type-level lock-in
X402Config.recipient is typed `0x${string}` (EVM address literal). X402ClientConfig.account is typed ClientEvmSigner (from @x402/evm). A Stellar G... address or signer won't typecheck as either field — this isn't just a missing runtime registration, the public config types actively reject non-EVM inputs before you'd even get to a registration-order fix.
What does work
X402Config.facilitator?: FacilitatorConfig is re-exported straight from @x402/core/server — custom facilitator URLs and bearer-token auth headers are not Coinbase-CDP-locked. Only the payment scheme is hardcoded, not the facilitator transport.
Repro
Pass a Stellar G... address as X402Config.recipient — TypeScript rejects it against the `0x${string}` literal type. Force past the type (as any) and the resource server still only has the EVM exact scheme registered, so a Stellar-scheme payment payload would never validate.
Proposed fix — additive, backward-compatible, verified end-to-end
Verified with a real Stellar implementation and a full MCP protocol round trip (initialize → tools/call → 402 → client builds a Stellar payment → retries → server verifies + settles → result), confirmed on-chain via Horizon. No existing EVM caller's behavior changes.
-
Server (
withX402) — alongside the existingregisterServerEvmScheme(resourceServer)call, unconditionally add:resourceServer.register("stellar:*", new ExactStellarScheme()); // from @x402/stellar/exact/server
buildPaymentRequirements()looks up the scheme matching the requested network, so this additional registration is inert for existing EVM configs. -
Client (
withX402Client) —accountis a single value and client registration is keyed by network, so a single signer can't be dual-registered. The backward-compatible fix is a runtime type guard —@x402/stellaralready exportsisClientStellarSignerfor exactly this:if (isClientStellarSigner(account)) { paymentClient.register("stellar:*", new ExactStellarScheme(account)); // from @x402/stellar/exact/client } else { registerClientEvmScheme(paymentClient, { signer: account }); // unchanged existing branch }
-
Types — widen
recipient: \0x${string}`→`0x${string}` | stringandaccount: ClientEvmSigner→ClientEvmSigner | ClientStellarSigner`. Widening a literal type to a union never breaks an existing call site.
Heads-up for whoever picks this up: the test mocks need a small, expected extension
Running a patch like this against packages/agents/src/x402-tests/x402.test.ts initially fails 17 of 33 tests with resourceServer.register is not a function. Root cause: @x402/evm's registration goes through a convenience function (registerExactEvmScheme) that the test file mocks as a no-op, so the mock resource server never needed a real .register() method. @x402/stellar has no equivalent convenience function, so the patch has to call .register() directly, which the existing mock object doesn't implement. Fix: add register: vi.fn() to the existing mock fixtures, plus vi.mock() declarations for the new imports, mirroring the existing @x402/evm mock pattern. No existing test's assertions need to change. With that scaffolding, all 33 original tests pass unchanged.
Acceptance criteria
-
withX402()registers both@x402/evmand@x402/stellarexact schemes unconditionally, no regression for EVM configs -
withX402Client()branches onisClientStellarSigner(account), falling through to the existing EVM branch unchanged when the signer isn't Stellar -
X402Config.recipient/X402ClientConfig.accountwidened to EVM-or-Stellar unions, confirmed non-breaking against this repo's own test suite - If maintainers prefer EVM-only scope instead: correct the docs' network list and point to
examples/x402/(raw@x402/core/@x402/hono, not EVM-locked) for anyone needing Stellar today
Activity
twzrd-sol commentedon Aug 11, 2026
Thanks for filing this — the docs/code mismatch is exactly the gap we hit trying to sell Solana mainnet through the Agents MCP surface (
withX402/paidTool/withX402Client).What we verified (2026-08-11):
packages/agents/src/mcp/x402.tsstill registers only@x402/evm(registerExactEvmSchemeon server + client);recipient/accountare still typed EVM-only. Foundation@x402/svm@2.21.0already exports the same twin registration shape (registerExactSvmSchemeonexact/server+exact/client), so this is registration + type-widen + network map, not inventing a new scheme — same shape as the Stellar approach here.Re #596: reading the thread, it reads less like "Solana unwanted" and more like CF having internal design constraints (per
threepointone) plus a real Workers → public Solana RPC 403 snag for the recent-blockhash fetch (peradlonymous). That RPC edge is real and worth designing for, not papering over.We put together a draft against
mainto make that concrete rather than just describe it: twzrd-sol#3 (in our fork, not opened here). Single commit, 38/38 tests,tsc --noEmitclean. A few things it does that seemed worth getting right given #596's history: SVM/EVM client-side scheme selection uses@solana/kit's ownisTransactionSigner()rather than a hand-rolled method check (a hand-rolled version we tried first missed one of the threeTransactionSignershapes and would've silently misrouted), and it adds an optional RPC override specifically for the Workers-egress-403 case.Not trying to race or claim this — happy to open it here as-is, adjust it to whatever shape you'd prefer, or just leave it as reference if something's already in flight. One thing that would help us not duplicate effort: is Solana-on-MCP intended to land as (a) additive to the current module, (b) deferred into the Monetization Gateway rewrite (#1989), or (c) internal-only for now? Also happy to offer our facilitator (
intel.twzrd.xyz) as a live Solana-mainnet verification target if useful for testing, the way other teams have offered theirs — not a requirement, just available.Thanks for tracking the honesty gap here either way.
feat(x402): register @x402/svm alongside @x402/evm in Agents MCP inte…
feat(examples): add Solana devnet payments to the x402 examples