mppx 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +19 -0
  3. package/dist/Method.d.ts +2 -0
  4. package/dist/cli/Extension.d.ts +20 -0
  5. package/dist/cli/Extension.js +5 -0
  6. package/dist/cli/cli.js +137 -137
  7. package/dist/cli/config.d.ts +4 -0
  8. package/dist/cli/config.js +1 -0
  9. package/dist/cli/internal.d.ts +7 -1
  10. package/dist/cli/internal.js +51 -5
  11. package/dist/cli/plugins/index.js +12 -12
  12. package/dist/cli/plugins/plugin.d.ts +2 -2
  13. package/dist/cli/sessions/request.d.ts +1 -0
  14. package/dist/cli/sessions/request.js +1 -0
  15. package/dist/cli/validate/discovery.d.ts +3 -1
  16. package/dist/cli/validate/discovery.js +28 -1
  17. package/dist/cli/validate/helpers.d.ts +3 -2
  18. package/dist/cli/validate/helpers.js +3 -0
  19. package/dist/cli/validate/index.js +3 -1
  20. package/dist/cli/validate/payment.js +118 -59
  21. package/dist/client/Mppx.d.ts +32 -0
  22. package/dist/client/Mppx.js +180 -6
  23. package/dist/client/internal/Fetch.d.ts +6 -0
  24. package/dist/client/internal/Fetch.js +4 -2
  25. package/dist/evm/client/Charge.d.ts +2 -2
  26. package/dist/evm/client/Charge.js +1 -1
  27. package/dist/evm/server/Charge.d.ts +7 -2
  28. package/dist/evm/server/Charge.js +28 -2
  29. package/dist/evm/server/Methods.d.ts +1 -1
  30. package/dist/internal/version.d.ts +5 -0
  31. package/dist/internal/version.js +6 -0
  32. package/dist/server/Mppx.d.ts +7 -6
  33. package/dist/server/Mppx.js +84 -2
  34. package/dist/server/internal/html/config.js +1 -3
  35. package/dist/stripe/Methods.d.ts +4 -3
  36. package/dist/stripe/Methods.js +14 -11
  37. package/dist/stripe/client/Charge.d.ts +3 -3
  38. package/dist/stripe/client/Methods.d.ts +3 -3
  39. package/dist/stripe/internal/parse-units.d.ts +8 -0
  40. package/dist/stripe/internal/parse-units.js +20 -0
  41. package/dist/stripe/internal/payment-intent.d.ts +29 -0
  42. package/dist/stripe/internal/payment-intent.js +8 -0
  43. package/dist/stripe/server/Charge.d.ts +5 -3
  44. package/dist/stripe/server/Charge.js +17 -21
  45. package/dist/stripe/server/Methods.d.ts +6 -4
  46. package/dist/stripe/server/Methods.js +42 -14
  47. package/dist/stripe/server/internal/analytics.d.ts +6 -0
  48. package/dist/stripe/server/internal/analytics.js +16 -0
  49. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  50. package/dist/stripe/server/internal/html.gen.js +1 -1
  51. package/dist/stripe/server/internal/record-payment.d.ts +1 -0
  52. package/dist/stripe/server/internal/record-payment.js +7 -5
  53. package/dist/tempo/client/Charge.d.ts +8 -2
  54. package/dist/tempo/client/Charge.js +19 -11
  55. package/dist/tempo/client/Subscription.js +6 -2
  56. package/dist/tempo/internal/fee-payer.d.ts +2 -0
  57. package/dist/tempo/internal/fee-payer.js +4 -0
  58. package/dist/tempo/server/Subscription.js +1 -0
  59. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  60. package/dist/tempo/server/internal/html.gen.js +1 -1
  61. package/dist/tempo/session/client/CredentialState.d.ts +2 -0
  62. package/dist/tempo/session/client/CredentialState.js +9 -3
  63. package/dist/tempo/session/client/Session.d.ts +6 -0
  64. package/dist/tempo/session/client/Session.js +4 -1
  65. package/dist/tempo/session/client/SessionManager.d.ts +11 -0
  66. package/dist/tempo/session/client/SessionManager.js +23 -2
  67. package/dist/tempo/session/precompile/Chain.js +3 -0
  68. package/dist/tempo/subscription/KeyAuthorization.d.ts +7 -1
  69. package/dist/tempo/subscription/KeyAuthorization.js +21 -3
  70. package/dist/validation/core.d.ts +1 -0
  71. package/dist/validation/core.js +7 -2
  72. package/dist/viem/Client.d.ts +4 -0
  73. package/dist/viem/Client.js +13 -0
  74. package/dist/x402/client/Exact.d.ts +2 -2
  75. package/dist/x402/client/Exact.js +1 -1
  76. package/dist/x402/server/EvmCharge.d.ts +2 -0
  77. package/dist/x402/server/EvmCharge.js +34 -26
  78. package/package.json +1 -1
@@ -1,8 +1,11 @@
1
1
  import * as AttestationClient from '../attestation/Client.js';
2
+ import * as Constants from '../Constants.js';
2
3
  import * as Expires from '../Expires.js';
3
4
  import * as AcceptPayment from '../internal/AcceptPayment.js';
4
5
  import * as Fetch from './internal/Fetch.js';
6
+ import * as MethodChallenge from './internal/MethodChallenge.js';
5
7
  import * as Transport from './Transport.js';
8
+ const preparedPaymentMethods = new WeakMap();
6
9
  /**
7
10
  * Creates a client-side payment handler from an array of methods.
8
11
  *
@@ -28,9 +31,12 @@ export function create(config) {
28
31
  const { attestation, maxPaymentRetries, onChallenge, orderChallenges, polyfill = true, acceptPaymentPolicy = polyfill && typeof globalThis.location !== 'undefined'
29
32
  ? 'same-origin'
30
33
  : 'always', transport = Transport.http(), } = config;
31
- const rawFetch = config.fetch ?? globalThis.fetch;
32
- const attestedFetch = attestation
33
- ? createAttestedFetch(rawFetch, attestation)
34
+ const rawFetch = Fetch.unwrapFetch(config.fetch ?? globalThis.fetch);
35
+ const attestationSigner = attestation
36
+ ? createAttestationSigner(attestation)
37
+ : undefined;
38
+ const attestedFetch = attestationSigner
39
+ ? AttestationClient.wrapFetch(rawFetch, attestationSigner)
34
40
  : rawFetch;
35
41
  const methods = config.methods.flat();
36
42
  const acceptPayment = AcceptPayment.resolve(methods, config.paymentPreferences);
@@ -117,7 +123,7 @@ export function create(config) {
117
123
  throw error;
118
124
  }
119
125
  });
120
- return Object.freeze({
126
+ const prepared = Object.freeze({
121
127
  challenge: selectedChallenge,
122
128
  challenges: challengeSnapshots,
123
129
  createCredential: createPreparedCredential,
@@ -127,6 +133,8 @@ export function create(config) {
127
133
  return transport.setCredential(request, credential, { challenge: transportChallenge });
128
134
  },
129
135
  });
136
+ preparedPaymentMethods.set(prepared, selectedMethod);
137
+ return prepared;
130
138
  }
131
139
  catch (error) {
132
140
  await events.emit('payment.failed', createPaymentFailedPayload({
@@ -139,6 +147,51 @@ export function create(config) {
139
147
  throw error;
140
148
  }
141
149
  }
150
+ async function prepareRequest(input, init, options) {
151
+ const { maxRedirects = 20, ...paymentOptions } = options ?? {};
152
+ const preparedHttp = await prepareHttpRequest({
153
+ acceptPayment: acceptPayment.header,
154
+ acceptPaymentPolicy,
155
+ fetch: rawFetch,
156
+ init,
157
+ input,
158
+ maxRedirects,
159
+ signer: attestationSigner,
160
+ });
161
+ const requestInit = requestToInit(preparedHttp.replayRequest, preparedHttp.body);
162
+ if (!(await transport.isPaymentRequired(preparedHttp.response, requestInit)))
163
+ throw new Error('Response does not require payment.');
164
+ const payment = (await preparePayment(preparedHttp.response, {
165
+ ...paymentOptions,
166
+ request: requestInit,
167
+ }));
168
+ const paymentMethod = preparedPaymentMethods.get(payment) ?? payment.method;
169
+ const createRequestCredential = memoizeCreateCredential(async (context) => {
170
+ if (MethodChallenge.has(paymentMethod))
171
+ await MethodChallenge.handle(paymentMethod, {
172
+ challenge: payment.challenge,
173
+ context,
174
+ fetch: attestedFetch,
175
+ input: preparedHttp.replayRequest,
176
+ });
177
+ return payment.createCredential(context);
178
+ });
179
+ return Object.freeze({
180
+ ...payment,
181
+ createCredential: createRequestCredential,
182
+ request: preparedHttp.request,
183
+ response: preparedHttp.response,
184
+ redirects: preparedHttp.redirects,
185
+ async pay(context) {
186
+ const credential = await createRequestCredential(context);
187
+ const paidInit = payment.setCredential(requestToInit(preparedHttp.replayRequest, preparedHttp.body), credential);
188
+ return attestedFetch(preparedHttp.replayRequest.url, {
189
+ ...paidInit,
190
+ redirect: 'manual',
191
+ });
192
+ },
193
+ });
194
+ }
142
195
  return {
143
196
  fetch,
144
197
  rawFetch,
@@ -150,6 +203,7 @@ export function create(config) {
150
203
  onPaymentFailed,
151
204
  onPaymentResponse,
152
205
  preparePayment,
206
+ prepareRequest: prepareRequest,
153
207
  async createCredential(response, context, options) {
154
208
  const prepared = await preparePayment(response, options);
155
209
  return prepared.createCredential(context);
@@ -173,11 +227,131 @@ export function create(config) {
173
227
  export function restore() {
174
228
  Fetch.restore();
175
229
  }
176
- function createAttestedFetch(fetch, signers) {
230
+ function createAttestationSigner(signers) {
177
231
  const values = Object.values(signers);
178
232
  if (values.length === 0)
179
233
  throw new TypeError('Mppx client attestation must configure at least one signer.');
180
- return AttestationClient.wrapFetch(fetch, AttestationClient.composeSigners(...values));
234
+ return AttestationClient.composeSigners(...values);
235
+ }
236
+ const redirectStatuses = new Set([301, 302, 303, 307, 308]);
237
+ const bodyHeaders = [
238
+ 'content-encoding',
239
+ 'content-language',
240
+ 'content-length',
241
+ 'content-location',
242
+ 'content-type',
243
+ 'transfer-encoding',
244
+ ];
245
+ const crossOriginHeaders = [
246
+ 'authorization',
247
+ 'cookie',
248
+ 'cookie2',
249
+ 'host',
250
+ 'payment-authorization',
251
+ 'payment-signature',
252
+ 'proxy-authorization',
253
+ 'x-payment',
254
+ ];
255
+ async function prepareHttpRequest(parameters) {
256
+ const { acceptPayment, acceptPaymentPolicy, fetch, init, input, maxRedirects, signer } = parameters;
257
+ if (!Number.isInteger(maxRedirects) || maxRedirects < 0)
258
+ throw new TypeError('maxRedirects must be a non-negative integer.');
259
+ let request = new Request(input, { ...init, redirect: 'manual' });
260
+ const explicitAcceptPayment = request.headers.has(Constants.Headers.acceptPayment);
261
+ let body = await replayBody(request, init?.body);
262
+ const redirects = [];
263
+ for (;;) {
264
+ request = withAcceptPayment(request, acceptPayment, explicitAcceptPayment, acceptPaymentPolicy);
265
+ const sentRequest = signer ? await signer.sign(request.clone()) : request.clone();
266
+ const response = await fetch(sentRequest);
267
+ if (response.type === 'opaqueredirect' || response.status === 0)
268
+ throw new Error('prepareRequest requires a runtime that exposes manual redirect responses.');
269
+ if (!redirectStatuses.has(response.status))
270
+ return {
271
+ body,
272
+ replayRequest: request,
273
+ request: sentRequest,
274
+ response,
275
+ redirects: Object.freeze(redirects),
276
+ };
277
+ const location = response.headers.get('location');
278
+ if (!location)
279
+ return {
280
+ body,
281
+ replayRequest: request,
282
+ request: sentRequest,
283
+ response,
284
+ redirects: Object.freeze(redirects),
285
+ };
286
+ if (redirects.length >= maxRedirects) {
287
+ await response.body?.cancel();
288
+ throw new Error(`Payment request exceeded ${maxRedirects} redirects.`);
289
+ }
290
+ const from = new URL(request.url);
291
+ const to = new URL(location, from);
292
+ if (from.protocol === 'https:' && to.protocol !== 'https:') {
293
+ await response.body?.cancel();
294
+ throw new Error(`Payment request refused HTTPS downgrade redirect to ${to.href}`);
295
+ }
296
+ const headers = new Headers(request.headers);
297
+ let method = request.method;
298
+ const switchesToGet = ((response.status === 301 || response.status === 302) && method === 'POST') ||
299
+ (response.status === 303 && method !== 'GET' && method !== 'HEAD');
300
+ if (switchesToGet) {
301
+ method = 'GET';
302
+ body = undefined;
303
+ for (const header of bodyHeaders)
304
+ headers.delete(header);
305
+ }
306
+ if (from.origin !== to.origin)
307
+ for (const header of [...headers.keys()]) {
308
+ const value = headers.get(header) ?? '';
309
+ if (crossOriginHeaders.includes(header) || value.startsWith('Payment '))
310
+ headers.delete(header);
311
+ }
312
+ redirects.push(Object.freeze({ from: from.href, status: response.status, to: to.href }));
313
+ await response.body?.cancel();
314
+ request = new Request(to, requestInit(request, headers, method, body));
315
+ }
316
+ }
317
+ function requestToInit(request, body) {
318
+ return requestInit(request, new Headers(request.headers), request.method, body);
319
+ }
320
+ async function replayBody(request, suppliedBody) {
321
+ if (typeof suppliedBody === 'string')
322
+ return suppliedBody;
323
+ if (!request.body)
324
+ return undefined;
325
+ const accept = request.headers.get('accept')?.toLowerCase() ?? '';
326
+ if (request.headers.has('mcp-method') ||
327
+ (accept.includes('application/json') && accept.includes('text/event-stream')))
328
+ return request.clone().text();
329
+ return request.clone().arrayBuffer();
330
+ }
331
+ function withAcceptPayment(request, acceptPayment, explicit, policy) {
332
+ if (explicit)
333
+ return request;
334
+ const headers = new Headers(request.headers);
335
+ headers.delete(Constants.Headers.acceptPayment);
336
+ if (acceptPayment && Fetch.shouldInjectForPolicy(request, policy))
337
+ headers.set(Constants.Headers.acceptPayment, acceptPayment);
338
+ return new Request(request, { headers });
339
+ }
340
+ function requestInit(request, headers, method, body) {
341
+ return {
342
+ ...(body ? { body } : {}),
343
+ cache: request.cache,
344
+ credentials: request.credentials,
345
+ headers,
346
+ integrity: request.integrity,
347
+ keepalive: request.keepalive,
348
+ method,
349
+ mode: request.mode,
350
+ redirect: 'manual',
351
+ referrer: request.referrer,
352
+ referrerPolicy: request.referrerPolicy,
353
+ signal: request.signal,
354
+ };
181
355
  }
182
356
  function memoizeCreateCredential(createCredential) {
183
357
  let promise;
@@ -168,6 +168,12 @@ export declare function normalizeHeaders(headers: unknown): Record<string, strin
168
168
  */
169
169
  export declare function createEventDispatcher<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[], response = Response>(): ClientEventDispatcher<methods, response>;
170
170
  /** @internal */
171
+ /** @internal */
172
+ export declare function unwrapFetch(fetch: typeof globalThis.fetch): typeof globalThis.fetch;
173
+ /** @internal */
171
174
  export declare function validateCredentialHeaderValue(credential: string): void;
175
+ /** @internal */
176
+ /** @internal */
177
+ export declare function shouldInjectForPolicy(input: RequestInfo | URL, policy: NonNullable<from.Config['acceptPaymentPolicy']>): boolean;
172
178
  export {};
173
179
  //# sourceMappingURL=Fetch.d.ts.map
@@ -558,7 +558,8 @@ function getCallerHeaders(input, headers) {
558
558
  return new Headers(input instanceof Request ? input.headers : undefined);
559
559
  }
560
560
  /** @internal */
561
- function unwrapFetch(fetch) {
561
+ /** @internal */
562
+ export function unwrapFetch(fetch) {
562
563
  let current = fetch;
563
564
  while (current[MPPX_FETCH_WRAPPER]) {
564
565
  current = current[MPPX_FETCH_WRAPPER];
@@ -620,7 +621,8 @@ async function resolveChallengeOrder(candidates, orderChallenges) {
620
621
  return orderChallenges ? orderChallenges(candidates) : candidates;
621
622
  }
622
623
  /** @internal */
623
- function shouldInjectForPolicy(input, policy) {
624
+ /** @internal */
625
+ export function shouldInjectForPolicy(input, policy) {
624
626
  if (policy === 'always')
625
627
  return true;
626
628
  if (policy === 'never')
@@ -81,8 +81,8 @@ export declare namespace charge {
81
81
  signTypedData?: (parameters: any) => Promise<`0x${string}`>;
82
82
  };
83
83
  type Parameters = {
84
- /** Account used to sign EVM charge credentials. */
85
- account: Account;
84
+ /** Account used to sign credentials. May instead be supplied to `createCredential` after payment preparation. */
85
+ account?: Account | undefined;
86
86
  /** EIP-3009 token domain metadata for custom currencies. */
87
87
  authorization?: Types.AuthorizationConfig | undefined;
88
88
  /** Optional token decimals used to parse `maxAmount` when currency metadata is not provided. */
@@ -34,7 +34,7 @@ export function charge(parameters) {
34
34
  context,
35
35
  });
36
36
  const account = (context?.account ?? parameters.account);
37
- if (!account.signTypedData)
37
+ if (!account?.signTypedData)
38
38
  throw new Error('EVM authorization requires a typed-data signer.');
39
39
  const request = challenge.request;
40
40
  assertPolicy(parameters, request);
@@ -1,5 +1,6 @@
1
1
  import type * as Credential from '../../Credential.js';
2
2
  import * as Method from '../../Method.js';
3
+ import * as ServerTransport from '../../server/Transport.js';
3
4
  import * as X402 from '../../x402/server/EvmCharge.js';
4
5
  import * as Assets from '../Assets.js';
5
6
  import * as Methods from '../Methods.js';
@@ -8,11 +9,15 @@ import * as Types from '../Types.js';
8
9
  * Creates an EVM charge server method.
9
10
  *
10
11
  * Speaks the native Payment-auth `evm/charge` wire format.
12
+ * Supports non-mutating `validateCredential` checks before settlement through
13
+ * `broadcastCredential`. Validation checks the signature and payment terms, and
14
+ * checks the facilitator when using facilitator-backed settlement. Custom
15
+ * settlers remain responsible for chain-state checks and replay protection.
11
16
  */
12
- export declare function charge(parameters: charge.NativeConfig): Method.Server<typeof Methods.charge, charge.Defaults>;
17
+ export declare function charge(parameters: charge.NativeConfig): Method.Server<typeof Methods.charge, charge.Defaults, ServerTransport.Http>;
13
18
  export declare namespace charge {
14
19
  type Parameters = NativeConfig;
15
- type Native = Method.Server<typeof Methods.charge, Defaults>;
20
+ type Native = Method.Server<typeof Methods.charge, Defaults, ServerTransport.Http>;
16
21
  type NativeConfig = BaseConfig & CurrencyConfig & RecipientConfig & Method.ComposableHooks<typeof Methods.charge>;
17
22
  type BaseConfig = {
18
23
  /** EIP-3009 token domain metadata. Required for raw addresses and viem tokens; inferred for known assets. */
@@ -22,7 +22,7 @@ export function charge(parameters) {
22
22
  recipient: config.recipient,
23
23
  },
24
24
  transport,
25
- async verify({ credential }) {
25
+ async validate({ credential }) {
26
26
  const payload = credential.payload;
27
27
  const request = credential.challenge.request;
28
28
  const chainId = request.methodDetails.chainId;
@@ -70,11 +70,34 @@ export function charge(parameters) {
70
70
  if (credential.source && credential.source !== source) {
71
71
  throw new VerificationFailedError({ reason: 'EVM authorization source mismatch' });
72
72
  }
73
- const settled = await config.settle({
73
+ const authorization = {
74
74
  credential,
75
75
  payload,
76
76
  request,
77
77
  source,
78
+ };
79
+ await config.validate?.(authorization);
80
+ return {
81
+ challenge: credential.challenge,
82
+ credential,
83
+ details: { payer: getAddress(payload.from) },
84
+ intent: Methods.charge.intent,
85
+ method: Methods.charge.name,
86
+ request: credential.challenge.request,
87
+ source,
88
+ };
89
+ },
90
+ async broadcast({ credential }) {
91
+ const payload = credential.payload;
92
+ const request = credential.challenge.request;
93
+ const settled = await config.settle({
94
+ credential,
95
+ payload,
96
+ request,
97
+ source: Types.toSource({
98
+ address: getAddress(payload.from),
99
+ chainId: request.methodDetails.chainId,
100
+ }),
78
101
  });
79
102
  return Receipt.from({
80
103
  method: Types.paymentMethod,
@@ -133,6 +156,9 @@ function resolveConfig(config) {
133
156
  decimals,
134
157
  recipient: getAddress(recipient),
135
158
  settle,
159
+ validate: !config.settle && x402.facilitator
160
+ ? (parameters) => X402.verifyWithFacilitator(x402, parameters)
161
+ : undefined,
136
162
  x402,
137
163
  };
138
164
  }
@@ -67,7 +67,7 @@ export declare function evm<const parameters extends evm.Parameters>(parameters:
67
67
  }[] | undefined;
68
68
  }>>;
69
69
  };
70
- }, charge_.Defaults, undefined, {}, string | undefined>];
70
+ }, charge_.Defaults, import("../../server/Transport.js").Http, {}, string | undefined>];
71
71
  export declare namespace evm {
72
72
  type Parameters = charge_.Parameters;
73
73
  /** Creates an EVM `charge` server method. */
@@ -0,0 +1,5 @@
1
+ /** Current mppx package version. */
2
+ export declare const version = "0.10.0";
3
+ /** Canonical SDK identifier for this mppx release. */
4
+ export declare const sdkIdentifier = "mppx/0.10.0";
5
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1,6 @@
1
+ // Generated by scripts/sync-version.ts.
2
+ /** Current mppx package version. */
3
+ export const version = '0.10.0';
4
+ /** Canonical SDK identifier for this mppx release. */
5
+ export const sdkIdentifier = `mppx/${version}`;
6
+ //# sourceMappingURL=version.js.map
@@ -134,15 +134,16 @@ export type Mppx<methods extends Methods = Methods, transport extends Transport.
134
134
  onPaymentFailed(handler: ServerEventHandler<FlattenMethods<methods>, transport, 'payment.failed'>): Unsubscribe;
135
135
  /** Register a handler for successful verified payments. */
136
136
  onPaymentSuccess(handler: ServerEventHandler<FlattenMethods<methods>, transport, 'payment.success'>): Unsubscribe;
137
- } & (transport extends Transport.Http ? {
137
+ } & (transport extends Transport.Http | Transport.Mcp | Transport.McpSdk ? {
138
138
  /**
139
139
  * Combines multiple method handlers into a single route handler that presents
140
- * all methods to the client via multiple `WWW-Authenticate` headers.
140
+ * all methods through HTTP headers or an MCP payment-required challenge list.
141
141
  *
142
142
  * Each entry is a `[method, options]` tuple where `method` is one of the
143
143
  * server methods passed to `Mppx.create()`, looked up by `name`+`intent`.
144
144
  *
145
- * Only available on HTTP transports.
145
+ * Available on HTTP, MCP JSON-RPC, and MCP SDK transports. MCP handlers
146
+ * accept the transport's input and return its challenge and receipt types.
146
147
  * No-credential authorize hooks run in entry order; the first 200 response
147
148
  * wins, and earlier hooks may have already run side effects.
148
149
  *
@@ -174,7 +175,7 @@ export type Mppx<methods extends Methods = Methods, transport extends Transport.
174
175
  * })
175
176
  * ```
176
177
  */
177
- compose(...entries: ComposeEntry<FlattenMethods<methods>>[]): ComposedHandler;
178
+ compose(...entries: ComposeEntry<FlattenMethods<methods>>[]): ComposedHandler<transport>;
178
179
  } : {}) & Handlers<FlattenMethods<methods>, transport> & {
179
180
  /**
180
181
  * Generate Challenge objects for registered methods without going through
@@ -364,7 +365,7 @@ declare namespace MethodFn {
364
365
  };
365
366
  }
366
367
  /** A configured handler — the return value of e.g. `mppx.charge({ ... })`. @internal */
367
- type ConfiguredHandler = ((input: Request) => Promise<MethodFn.Response<Transport.Http>>) & {
368
+ type ConfiguredHandler<transport extends Transport.AnyTransport = Transport.Http> = ((input: Transport.InputOf<transport>) => Promise<MethodFn.Response<transport>>) & {
368
369
  _internal: {
369
370
  _method: Method.AnyServer;
370
371
  description?: string | undefined;
@@ -377,7 +378,7 @@ type ConfiguredHandler = ((input: Request) => Promise<MethodFn.Response<Transpor
377
378
  _stableBinding?: Method.StableBindingFn<Method.Method> | undefined;
378
379
  };
379
380
  };
380
- type ComposedHandler = ((input: Request) => Promise<MethodFn.Response<Transport.Http>>) & {
381
+ type ComposedHandler<transport extends Transport.AnyTransport = Transport.Http> = ((input: Transport.InputOf<transport>) => Promise<MethodFn.Response<transport>>) & {
381
382
  _internal?: {
382
383
  offers: readonly ConfiguredHandler['_internal'][];
383
384
  };
@@ -7,6 +7,7 @@ import * as Errors from '../Errors.js';
7
7
  import * as Expires from '../Expires.js';
8
8
  import * as AcceptPayment from '../internal/AcceptPayment.js';
9
9
  import * as Env from '../internal/env.js';
10
+ import * as Mcp from '../Mcp.js';
10
11
  import * as PaymentRequest from '../PaymentRequest.js';
11
12
  import * as x402_Header from '../x402/Header.js';
12
13
  import * as x402_Types from '../x402/Types.js';
@@ -70,6 +71,7 @@ export function create(config) {
70
71
  serverEvents.on('payment.success', (async (ctx) => {
71
72
  if (ctx.method.name === mi.name && ctx.method.intent === mi.intent) {
72
73
  await mi.onPaymentSuccess({
74
+ challenge: ctx.challenge,
73
75
  input: ctx.input,
74
76
  receipt: ctx.receipt,
75
77
  request: ctx.request,
@@ -339,8 +341,9 @@ export function create(config) {
339
341
  }
340
342
  const verifyCredentialFn = broadcastCredentialFn;
341
343
  function composeFn(...entries) {
342
- if (transport.name !== 'http')
343
- throw new Error('compose() only supports HTTP transport');
344
+ const isMcp = transport.name === 'mcp' || transport.name === 'mcp-sdk';
345
+ if (transport.name !== 'http' && !isMcp)
346
+ throw new Error('compose() only supports HTTP and MCP transports');
344
347
  if (entries.length === 0)
345
348
  throw new Error('compose() requires at least one entry');
346
349
  const configured = entries.map(([methodOrKey, options]) => {
@@ -352,8 +355,15 @@ export function create(config) {
352
355
  const handlerFn = handlers[key];
353
356
  if (!handlerFn)
354
357
  throw new Error(`No handler for "${key}". Is this method in your methods array?`);
358
+ const method = handlerFn._method;
359
+ if (isMcp && method?.transport && method.transport.name !== transport.name)
360
+ throw new Error('MCP compose() requires methods using the configured MCP transport');
361
+ if (isMcp && method?.canOffer)
362
+ throw new Error('MCP compose() does not support HTTP canOffer hooks');
355
363
  return handlerFn(options);
356
364
  });
365
+ if (isMcp)
366
+ return composeMcpHandlers(configured, transport);
357
367
  return composeHandlers(configured, undefined, selectOffers);
358
368
  }
359
369
  function onChallengeCreated(handler) {
@@ -1657,6 +1667,78 @@ function getConfiguredOffers(handler) {
1657
1667
  return internal.offers;
1658
1668
  return [internal];
1659
1669
  }
1670
+ /** Dispatches one MCP credential or gathers unsigned offers in their configured order. */
1671
+ function composeMcpHandlers(handlers, transport) {
1672
+ return async (input) => {
1673
+ let credential;
1674
+ try {
1675
+ const parsed = transport.getCredential(input);
1676
+ credential = parsed ? hydrateCredentialMeta(parsed) : null;
1677
+ }
1678
+ catch {
1679
+ // Let the method emit the normal malformed-credential error and retry challenge.
1680
+ return handlers[0](input);
1681
+ }
1682
+ if (credential) {
1683
+ const candidates = handlers.filter((handler) => {
1684
+ const internal = handler._internal;
1685
+ return (internal.name === credential.challenge.method &&
1686
+ internal.intent === credential.challenge.intent);
1687
+ });
1688
+ const match = candidates.find((handler) => {
1689
+ const internal = handler._internal;
1690
+ try {
1691
+ const mismatch = internal._stableBinding
1692
+ ? getRequestBindingMismatch(getStableBinding(internal._canonicalRequest, internal._stableBinding), getStableBinding(credential.challenge.request, internal._stableBinding))
1693
+ : getPinnedRequestBindingMismatch(internal._canonicalRequest, credential.challenge.request);
1694
+ return !mismatch && opaqueValuesMatch(internal.meta, credential.challenge.meta);
1695
+ }
1696
+ catch {
1697
+ return false;
1698
+ }
1699
+ });
1700
+ // Verification and settlement belong to exactly one handler, including failures.
1701
+ return (match ?? candidates[0] ?? handlers[0])(input);
1702
+ }
1703
+ const challenges = [];
1704
+ let first;
1705
+ for (const handler of handlers) {
1706
+ const result = await handler(input);
1707
+ if (result.status === 200)
1708
+ return result;
1709
+ const response = result.challenge;
1710
+ const error = 'code' in response ? response : response.error;
1711
+ // Request-hook and configuration failures must retain their original error semantics.
1712
+ if (!error || error.code !== Mcp.paymentRequiredCode)
1713
+ return result;
1714
+ const data = error.data;
1715
+ if (!data?.challenges?.length)
1716
+ return result;
1717
+ first ??= response;
1718
+ challenges.push(...data.challenges);
1719
+ }
1720
+ if (!first)
1721
+ throw new Error('compose() requires at least one handler');
1722
+ if ('code' in first) {
1723
+ const { McpError } = await import('@modelcontextprotocol/sdk/types.js');
1724
+ return {
1725
+ status: 402,
1726
+ challenge: new McpError(first.code, 'Payment Required', {
1727
+ ...first.data,
1728
+ challenges,
1729
+ }),
1730
+ };
1731
+ }
1732
+ return {
1733
+ status: 402,
1734
+ challenge: {
1735
+ jsonrpc: first.jsonrpc,
1736
+ id: first.id,
1737
+ error: { ...first.error, data: { ...first.error.data, challenges } },
1738
+ },
1739
+ };
1740
+ };
1741
+ }
1660
1742
  function isComposedHandlerMetadata(internal) {
1661
1743
  return 'offers' in internal && !('_canonicalRequest' in internal);
1662
1744
  }
@@ -106,9 +106,7 @@ export function render(options) {
106
106
  ${data.challenge.description
107
107
  ? `data-description="${sanitize(data.challenge.description)}"`
108
108
  : ''}
109
- ${data.challenge.expires
110
- ? `data-expires="${sanitize(data.challenge.expires)}"`
111
- : ''}
109
+ ${data.challenge.expires ? `data-expires="${sanitize(data.challenge.expires)}"` : ''}
112
110
  ${data.challenge.expires ? `data-expires-label="${sanitize(text.expires)}"` : ''}
113
111
  >
114
112
  ${sanitize(data.label)}
@@ -1,4 +1,5 @@
1
1
  import * as z from '../zod.js';
2
+ import * as PaymentIntent from './internal/payment-intent.js';
2
3
  /**
3
4
  * Stripe charge intent for one-time payments via Shared Payment Tokens (SPTs).
4
5
  *
@@ -22,7 +23,7 @@ export declare const charge: {
22
23
  externalId: z.ZodMiniOptional<z.ZodMiniString<string>>;
23
24
  metadata: z.ZodMiniOptional<z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>>;
24
25
  networkId: z.ZodMiniString<string>;
25
- paymentIntentOptions: z.ZodMiniOptional<z.ZodMiniObject<{
26
+ paymentIntentOptions: z.ZodMiniOptional<z.ZodMiniUnion<readonly [z.ZodMiniObject<{
26
27
  customer: z.ZodMiniOptional<z.ZodMiniString<string>>;
27
28
  hooks: z.ZodMiniOptional<z.ZodMiniObject<{
28
29
  inputs: z.ZodMiniObject<{
@@ -33,7 +34,7 @@ export declare const charge: {
33
34
  }, z.core.$strip>>;
34
35
  metadata: z.ZodMiniOptional<z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>>;
35
36
  receipt_email: z.ZodMiniOptional<z.ZodMiniString<string>>;
36
- }, z.core.$strip>>;
37
+ }, z.core.$strip>, z.ZodMiniCustom<PaymentIntent.ResolveOptions, PaymentIntent.ResolveOptions>]>>;
37
38
  paymentMethodTypes: z.ZodMiniArray<z.ZodMiniString<string>>;
38
39
  recipient: z.ZodMiniOptional<z.ZodMiniString<string>>;
39
40
  }, z.core.$strip>, z.ZodMiniTransform<{
@@ -55,7 +56,7 @@ export declare const charge: {
55
56
  externalId?: string | undefined;
56
57
  metadata?: Record<string, string> | undefined;
57
58
  networkId: string;
58
- paymentIntentOptions?: {
59
+ paymentIntentOptions?: PaymentIntent.ResolveOptions | {
59
60
  customer?: string | undefined;
60
61
  hooks?: {
61
62
  inputs: {
@@ -1,6 +1,6 @@
1
- import { parseUnits } from 'viem';
2
1
  import * as Method from '../Method.js';
3
2
  import * as z from '../zod.js';
3
+ import { parseUnits } from './internal/parse-units.js';
4
4
  import * as PaymentIntent from './internal/payment-intent.js';
5
5
  /**
6
6
  * Stripe charge intent for one-time payments via Shared Payment Tokens (SPTs).
@@ -25,18 +25,21 @@ export const charge = Method.from({
25
25
  externalId: z.optional(z.string()),
26
26
  metadata: z.optional(z.record(z.string(), z.string())),
27
27
  networkId: z.string(),
28
- paymentIntentOptions: z.optional(PaymentIntent.Schema),
28
+ paymentIntentOptions: z.optional(PaymentIntent.InputSchema),
29
29
  paymentMethodTypes: z.array(z.string()).check(z.minLength(1)),
30
30
  recipient: z.optional(z.string()),
31
- }), z.transform(({ amount, decimals, metadata, networkId, paymentIntentOptions: _, paymentMethodTypes, ...rest }) => ({
32
- ...rest,
33
- amount: parseUnits(amount, decimals).toString(),
34
- methodDetails: {
35
- networkId,
36
- paymentMethodTypes,
37
- ...(metadata !== undefined && { metadata }),
38
- },
39
- }))),
31
+ }), z.transform(({ amount, decimals, metadata, networkId, paymentIntentOptions, paymentMethodTypes, ...rest }) => {
32
+ void paymentIntentOptions;
33
+ return {
34
+ ...rest,
35
+ amount: parseUnits(amount, decimals).toString(),
36
+ methodDetails: {
37
+ networkId,
38
+ paymentMethodTypes,
39
+ ...(metadata !== undefined && { metadata }),
40
+ },
41
+ };
42
+ })),
40
43
  },
41
44
  });
42
45
  //# sourceMappingURL=Methods.js.map