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.
- package/CHANGELOG.md +32 -0
- package/README.md +19 -0
- package/dist/Method.d.ts +2 -0
- package/dist/cli/Extension.d.ts +20 -0
- package/dist/cli/Extension.js +5 -0
- package/dist/cli/cli.js +137 -137
- package/dist/cli/config.d.ts +4 -0
- package/dist/cli/config.js +1 -0
- package/dist/cli/internal.d.ts +7 -1
- package/dist/cli/internal.js +51 -5
- package/dist/cli/plugins/index.js +12 -12
- package/dist/cli/plugins/plugin.d.ts +2 -2
- package/dist/cli/sessions/request.d.ts +1 -0
- package/dist/cli/sessions/request.js +1 -0
- package/dist/cli/validate/discovery.d.ts +3 -1
- package/dist/cli/validate/discovery.js +28 -1
- package/dist/cli/validate/helpers.d.ts +3 -2
- package/dist/cli/validate/helpers.js +3 -0
- package/dist/cli/validate/index.js +3 -1
- package/dist/cli/validate/payment.js +118 -59
- package/dist/client/Mppx.d.ts +32 -0
- package/dist/client/Mppx.js +180 -6
- package/dist/client/internal/Fetch.d.ts +6 -0
- package/dist/client/internal/Fetch.js +4 -2
- package/dist/evm/client/Charge.d.ts +2 -2
- package/dist/evm/client/Charge.js +1 -1
- package/dist/evm/server/Charge.d.ts +7 -2
- package/dist/evm/server/Charge.js +28 -2
- package/dist/evm/server/Methods.d.ts +1 -1
- package/dist/internal/version.d.ts +5 -0
- package/dist/internal/version.js +6 -0
- package/dist/server/Mppx.d.ts +7 -6
- package/dist/server/Mppx.js +84 -2
- package/dist/server/internal/html/config.js +1 -3
- package/dist/stripe/Methods.d.ts +4 -3
- package/dist/stripe/Methods.js +14 -11
- package/dist/stripe/client/Charge.d.ts +3 -3
- package/dist/stripe/client/Methods.d.ts +3 -3
- package/dist/stripe/internal/parse-units.d.ts +8 -0
- package/dist/stripe/internal/parse-units.js +20 -0
- package/dist/stripe/internal/payment-intent.d.ts +29 -0
- package/dist/stripe/internal/payment-intent.js +8 -0
- package/dist/stripe/server/Charge.d.ts +5 -3
- package/dist/stripe/server/Charge.js +17 -21
- package/dist/stripe/server/Methods.d.ts +6 -4
- package/dist/stripe/server/Methods.js +42 -14
- package/dist/stripe/server/internal/analytics.d.ts +6 -0
- package/dist/stripe/server/internal/analytics.js +16 -0
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/stripe/server/internal/record-payment.d.ts +1 -0
- package/dist/stripe/server/internal/record-payment.js +7 -5
- package/dist/tempo/client/Charge.d.ts +8 -2
- package/dist/tempo/client/Charge.js +19 -11
- package/dist/tempo/client/Subscription.js +6 -2
- package/dist/tempo/internal/fee-payer.d.ts +2 -0
- package/dist/tempo/internal/fee-payer.js +4 -0
- package/dist/tempo/server/Subscription.js +1 -0
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/session/client/CredentialState.d.ts +2 -0
- package/dist/tempo/session/client/CredentialState.js +9 -3
- package/dist/tempo/session/client/Session.d.ts +6 -0
- package/dist/tempo/session/client/Session.js +4 -1
- package/dist/tempo/session/client/SessionManager.d.ts +11 -0
- package/dist/tempo/session/client/SessionManager.js +23 -2
- package/dist/tempo/session/precompile/Chain.js +3 -0
- package/dist/tempo/subscription/KeyAuthorization.d.ts +7 -1
- package/dist/tempo/subscription/KeyAuthorization.js +21 -3
- package/dist/validation/core.d.ts +1 -0
- package/dist/validation/core.js +7 -2
- package/dist/viem/Client.d.ts +4 -0
- package/dist/viem/Client.js +13 -0
- package/dist/x402/client/Exact.d.ts +2 -2
- package/dist/x402/client/Exact.js +1 -1
- package/dist/x402/server/EvmCharge.d.ts +2 -0
- package/dist/x402/server/EvmCharge.js +34 -26
- package/package.json +1 -1
package/dist/client/Mppx.js
CHANGED
|
@@ -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
|
|
33
|
-
?
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
85
|
-
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
|
|
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
|
|
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
|
|
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,
|
|
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. */
|
package/dist/server/Mppx.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
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:
|
|
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:
|
|
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
|
};
|
package/dist/server/Mppx.js
CHANGED
|
@@ -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
|
-
|
|
343
|
-
|
|
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)}
|
package/dist/stripe/Methods.d.ts
CHANGED
|
@@ -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: {
|
package/dist/stripe/Methods.js
CHANGED
|
@@ -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.
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|