Skip to content

feat: add MPP to server protocol detection, settlement, and omni-challenge#147

Merged
badjer merged 16 commits intomainfrom
feature/phase3-sdk-mpp
Apr 2, 2026
Merged

feat: add MPP to server protocol detection, settlement, and omni-challenge#147
badjer merged 16 commits intomainfrom
feature/phase3-sdk-mpp

Conversation

@badjer
Copy link
Copy Markdown
Contributor

@badjer badjer commented Apr 1, 2026

Summary

  • detectProtocol() now detects Authorization: Payment header as MPP
  • ProtocolSettlement.buildRequestBody() handles MPP credential format (parses base64/JSON MppCredential, extracts amount for settle)
  • buildMppChallenge() creates Tempo MPP challenge from payment options
  • serializeMppHeader() formats WWW-Authenticate: Payment header
  • omniChallengeMcpError() includes mpp field in JSON-RPC error data (code -32042)
  • omniChallengeHttpResponse() includes WWW-Authenticate header when MPP available
  • New MppChallengeData type exported from @atxp/server
  • 13 new tests, 152 total passing

Replaces #3 (which was written against a stale main before Phase 1/2 changes).

Test plan

  • Server tests: 152/152 passing
  • Express tests: 10/10 passing
  • Typecheck clean
  • Integration tested via npm link into accounts + LLM

🤖 Generated with Claude Code

badjer and others added 10 commits April 1, 2026 16:13
…mni-challenge

- detectProtocol() now detects Authorization: Payment header as MPP
- ProtocolSettlement.buildRequestBody() handles MPP credential format
  (parses base64/JSON MppCredential, extracts amount for settle)
- buildMppChallenge() creates Tempo MPP challenge from payment options
- serializeMppHeader() formats WWW-Authenticate: Payment header
- omniChallengeMcpError() includes mpp field in JSON-RPC error data
- omniChallengeHttpResponse() includes WWW-Authenticate header when MPP
- buildOmniChallenge() accepts optional mppChallengeId
- New MppChallengeData type exported from @atxp/server

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review feedback fixes:
1. Re-export MPPChallenge from @atxp/mpp as MppChallengeData (no type duplication)
2. Add @atxp/mpp as dependency + tsconfig reference for @atxp/server
3. Document PATHUSD_DECIMALS constant (6 decimals assumption)
4. Fail explicitly on malformed MPP credentials (no silent { raw } fallback)
5. Safe amount extraction via String() instead of unsafe cast
6. Update detectProtocol docstring to document MPP detection
7. Fix stale comment in atxpExpress.ts ("X402 or ATXP" → "X402 or MPP")
8. Escape double-quotes in serializeMppHeader values
9. Move verify/mpp test to correct describe('verify') block
10. Add serializeMppHeader → parseMPPHeader round-trip test
11. Add tempo_moderato testnet test case for buildMppChallenge

Also: regenerate package-lock.json to fix stale nested @atxp/common@0.10.12
(was causing pre-existing typecheck failures across all workspace packages).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SDK middleware (atxpExpress):
- New resolveIdentity() extracts user identity from OAuth Bearer token
  (preferred) or wallet address in payment credential (fallback)
- handleProtocolCredential passes sourceAccountId to settle calls via
  SettlementContext for payment recording/reconciliation in auth

ProtocolSettlement:
- buildRequestBody for X402 and MPP now includes optional sourceAccountId
- SettlementContext type updated with sourceAccountId documentation

Identity resolution priority:
1. OAuth sub from Authorization: Bearer (available for X402 since it uses
   a separate header; available in MCP sessions for MPP)
2. Wallet address from MPP credential (chain:address format)
3. For X402 without OAuth: auth resolves from Permit2 payer address

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Log warn (not debug) when Bearer token is present but check fails
   (indicates config problem, not just missing token)
2. Identity log changed to debug level (avoids wallet addresses in info)
3. Pass context to verify (not just settle) — enables account-level
   checks during verification
4. Add test: MPP settle includes sourceAccountId from wallet credential
5. Add test: X402 settle without OAuth has no sourceAccountId
6. Add unit tests: sourceAccountId included in settle body when context
   provides it (both X402 and MPP)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- buildRequestBody for MPP: remove dead amount extraction (auth gets
  amount from credential.challenge.request.amount via mppx now)
- resolveIdentity: parse DID source string (did:pkh:eip155:<chainId>:<addr>)
  instead of old {chain, address} object format
- Update all MPP test credentials to standard format:
  challenge as object (id, method, intent, request),
  payload with type discriminator (transaction/hash/proof),
  source as DID string

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

1. Wrap verify call in try/catch — malformed credentials return 400
   instead of uncaught 500
2. Move ProtocolSettlement creation to atxpExpress closure — shared
   across all requests (stateless, no per-request allocation needed)
3. Document that X402 context.paymentRequirements is undefined in Express
   middleware (auth handles gracefully — facilitator can verify without them)
4. Re-export MPPChallenge directly instead of type alias (cleaner naming)
5. Pass settlement instance to handleProtocolCredential

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Add TODO comment on settlement-after-200 failure (known limitation —
   needs retry queue for production, payment is lost if settle fails)
2. Add comment explaining base_sepolia → base normalization in X402
   challenges (X402 spec behavior)
3. Replace flaky setTimeout(50) in Express tests with promise-based
   approach (all tests now await settle completion via explicit resolve)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
requirePayment now uses omniChallengeMcpError instead of paymentRequiredError.
MCP errors now include X402 payment requirements and MPP challenge data
alongside the existing ATXP-MCP payment request URL.

This enables X402 and MPP protocol handlers in the client to detect and
respond to challenges — previously they only saw ATXP-MCP data and the
client always fell back to the ATXP flow regardless of feature flags.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server:
- omniChallengeMcpError now uses -32042 (MPP_ERROR_CODE) instead of
  -30402 (PAYMENT_REQUIRED_ERROR_CODE)
- error.data contains both ATXP-MCP fields (paymentRequestId, etc.)
  and MPP fields (data.mpp) — standard MPP clients detect -32042,
  ATXP clients detect paymentRequestId in the same data

Client:
- atxpFetcher accepts BOTH -32042 (new omni) and -30402 (legacy) for
  backwards compatibility with old servers
- mcpJson parser checks both error codes

New constant: OMNI_PAYMENT_ERROR_CODE = -32042 exported from @atxp/common

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the client receives a -32042 MCP error with omni-challenge data,
it now checks for X402/MPP data in error.data BEFORE falling back to
the ATXP-MCP payment flow.

New method buildSyntheticResponseFromMcpError() constructs a synthetic
HTTP 402 Response from the MCP error data so existing protocol handlers
(X402ProtocolHandler, MPPProtocolHandler) can detect and handle the
challenge using their standard detection logic:
- X402: x402Version in body
- MPP: WWW-Authenticate: Payment header
- Protocol flag selects preferred handler when multiple match

Falls back to ATXP-MCP flow (handlePaymentRequestError) if no protocol
handler matches — backwards compatible with servers that only emit
ATXP-MCP challenges.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@badjer badjer force-pushed the feature/phase3-sdk-mpp branch from ba40e87 to fc5afe7 Compare April 2, 2026 21:56
protocolHandlers now defaults to [X402ProtocolHandler, MPPProtocolHandler]
instead of empty array. Developers no longer need to manually configure
protocol support — all protocols work out of the box.

ATXP-MCP doesn't need a handler — it's the native MCP payment flow that
runs as the fallback when no protocol handler matches.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@badjer badjer force-pushed the feature/phase3-sdk-mpp branch from fc5afe7 to 875deb7 Compare April 2, 2026 21:57
badjer and others added 5 commits April 2, 2026 15:44
The type re-export `export type { MPPChallenge as MppChallengeData }`
from @atxp/mpp caused CI failures because tsc --noEmit couldn't resolve
the cross-package type through rollup's dts output + project references.

Fix: define MppChallengeData inline (mirrors MPPChallenge from @atxp/mpp).
Remove project reference to atxp-mpp in tsconfig (only imported
MPP_ERROR_CODE at runtime, which resolves fine).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@badjer badjer merged commit 0acd5ed into main Apr 2, 2026
1 check passed
@badjer badjer deleted the feature/phase3-sdk-mpp branch April 2, 2026 23:14
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