Skip to content

feat: multi-chain MPP support (Solana + Tempo)#155

Merged
badjer merged 7 commits intomainfrom
mpp-solana-multichain
Apr 9, 2026
Merged

feat: multi-chain MPP support (Solana + Tempo)#155
badjer merged 7 commits intomainfrom
mpp-solana-multichain

Conversation

@badjer
Copy link
Copy Markdown
Contributor

@badjer badjer commented Apr 8, 2026

Summary

  • Multi-chain MPP challenges: buildMppChallenges() now returns an array of challenges (one per supported chain: Solana USDC + Tempo pathUSD) instead of a single Tempo-only challenge. OmniChallenge.mpp is now MppChallengeData[]. Both HTTP (WWW-Authenticate with multiple Payment values per RFC 7235) and MCP (data.mpp as array) formats support the array.
  • X-MPP-Payment header: Client sends MPP credentials via X-MPP-Payment instead of Authorization: Payment to avoid conflicting with the OAuth Authorization: Bearer token. Server detectProtocol() checks this header first, with fallback to standard Authorization: Payment.
  • Client forwards all challenges: MPPProtocolHandler extracts all challenges from headers/MCP errors and sends the full challenges[] array to accounts service, which picks the chain via ff:mpp-chain feature flag.

Packages changed

Package Changes
@atxp/server buildMppChallenges() (array), detectProtocol() checks x-mpp-payment, omni-challenge emits array
@atxp/mpp parseMPPHeaders(), parseMPPChallengesFromMCPError() handle arrays
@atxp/client Extracts all challenges, sends array to accounts, uses X-MPP-Payment header
@atxp/common AuthorizeParams.challenges field added
@atxp/express Forwards x-mpp-payment header to protocol detection

Test plan

  • Unit tests updated for array format (omniChallenge.test.ts)
  • paymentHeaders.test.ts updated for X-MPP-Payment
  • protocolHandler.test.ts updated for X-MPP-Payment
  • Verified E2E with local accounts + auth + SDK dev servers
  • Verify accounts service handles challenges[] array in /authorize/auto

🤖 Generated with Claude Code

badjer and others added 7 commits April 8, 2026 15:30
Server now emits an array of MPP challenges (one per supported chain)
instead of a single Tempo-only challenge. Client extracts all challenges
and forwards the array to accounts, which picks the chain via feature
flag. Uses X-MPP-Payment header to carry the MPP credential alongside
the OAuth Bearer token without conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shared core for building protocol-specific payment data from destination
sources. Used by both requirePayment() (MCP servers) and LLM callers.

- buildPaymentOptions: sources → X402 requirements + MPP challenges
- buildAuthorizeParamsFromSources: thin wrapper returning AuthorizeParams fields
- requirePayment refactored to use buildPaymentOptions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Standard MPP uses Authorization: Payment which conflicts with OAuth's
Authorization: Bearer. Instead of a custom header (X-MPP-Payment):

- Server signs user identity into MPP challenge opaque field (HMAC)
- Client sends standard Authorization: Payment (interop preserved)
- OAuth fetch skips Bearer when Payment is present
- Server recovers identity from opaque on retry (HMAC verified)

This preserves interop with external MPP clients/servers while solving
the OAuth header conflict for ATXP-to-ATXP flows.

Also: challenge ID uniqueness (random suffix), detectProtocol cleanup.

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

- Fix OAuth bypass to only skip challenge for ATXP/X402 (separate headers)
  or MPP when opaque identity verification succeeds; MPP with failed/missing
  opaque still gets OAuth challenge (#13)
- Extract parseCredentialBase64 helper for try-base64-then-raw-JSON pattern,
  use in atxpExpress.ts and protocol.ts (#14)
- Add opaqueIdentity unit tests: round-trip, tampered sig, wrong challengeId,
  missing opaque, malformed fields, different sub (#11)
- Align MppChallengeData.opaque type to Record<string, unknown> (#16)
- Reorder getSources after getExistingPaymentId check in requirePayment to
  avoid unnecessary fetch on idempotent path; extract fetchAllSources (#6)

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

- mppProtocolHandler: stop reporting challenge ID as transactionHash;
  use AuthorizeResult.context if available, empty string otherwise.
  Use context.network for settled chain when available.
- opaqueIdentity: derive HMAC key from ATXP_OPAQUE_KEY env var with
  random fallback for multi-instance deployments.
- oAuth: add inline comment referencing buildPaymentHeaders contract.
- omniChallenge.test: add tests for buildPaymentOptions and
  buildAuthorizeParamsFromSources covering multi-chain scenarios.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Changed buildMppChallenges fallback from 'pathUSD' to 'USDC' since
Tempo mainnet now uses USDC as the default stablecoin.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@badjer badjer merged commit 34a0c27 into main Apr 9, 2026
1 check passed
@badjer badjer deleted the mpp-solana-multichain branch April 9, 2026 02:08
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