mppx 0.9.1 → 0.9.3

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 (73) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +26 -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.d.ts +1 -0
  7. package/dist/cli/cli.js +135 -135
  8. package/dist/cli/config.d.ts +4 -0
  9. package/dist/cli/config.js +1 -0
  10. package/dist/cli/internal.d.ts +3 -0
  11. package/dist/cli/internal.js +15 -2
  12. package/dist/cli/plugins/evm.js +2 -0
  13. package/dist/cli/plugins/index.d.ts +1 -0
  14. package/dist/cli/plugins/index.js +13 -13
  15. package/dist/cli/plugins/plugin.d.ts +2 -2
  16. package/dist/cli/plugins/x402.d.ts +3 -0
  17. package/dist/cli/plugins/x402.js +61 -0
  18. package/dist/cli/sessions/request.d.ts +1 -0
  19. package/dist/cli/sessions/request.js +1 -0
  20. package/dist/cli/validate/payment.js +14 -8
  21. package/dist/evm/client/Charge.d.ts +2 -2
  22. package/dist/evm/client/Charge.js +1 -1
  23. package/dist/evm/server/Charge.d.ts +7 -2
  24. package/dist/evm/server/Charge.js +28 -2
  25. package/dist/evm/server/Methods.d.ts +1 -1
  26. package/dist/internal/version.d.ts +5 -0
  27. package/dist/internal/version.js +6 -0
  28. package/dist/middlewares/internal/mppx.js +17 -3
  29. package/dist/server/Mppx.d.ts +7 -6
  30. package/dist/server/Mppx.js +84 -2
  31. package/dist/server/internal/html/config.js +1 -3
  32. package/dist/stripe/Methods.d.ts +24 -5
  33. package/dist/stripe/Methods.js +14 -11
  34. package/dist/stripe/client/Charge.d.ts +23 -5
  35. package/dist/stripe/client/Methods.d.ts +23 -5
  36. package/dist/stripe/internal/constants.d.ts +4 -0
  37. package/dist/stripe/internal/constants.js +2 -0
  38. package/dist/stripe/internal/parse-units.d.ts +8 -0
  39. package/dist/stripe/internal/parse-units.js +20 -0
  40. package/dist/stripe/internal/payment-intent.d.ts +39 -1
  41. package/dist/stripe/internal/payment-intent.js +16 -1
  42. package/dist/stripe/internal/types.d.ts +4 -0
  43. package/dist/stripe/server/Charge.d.ts +25 -5
  44. package/dist/stripe/server/Charge.js +35 -23
  45. package/dist/stripe/server/Methods.d.ts +8 -0
  46. package/dist/stripe/server/Methods.js +54 -9
  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/hosted-fee-payer.d.ts +10 -0
  50. package/dist/stripe/server/internal/hosted-fee-payer.js +54 -0
  51. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  52. package/dist/stripe/server/internal/html.gen.js +1 -1
  53. package/dist/stripe/server/internal/record-payment.d.ts +4 -1
  54. package/dist/stripe/server/internal/record-payment.js +35 -5
  55. package/dist/tempo/client/Subscription.js +6 -2
  56. package/dist/tempo/internal/account.d.ts +1 -0
  57. package/dist/tempo/internal/fee-payer.d.ts +2 -0
  58. package/dist/tempo/internal/fee-payer.js +4 -0
  59. package/dist/tempo/internal/remote-fee-payer.d.ts +2 -0
  60. package/dist/tempo/server/Subscription.js +1 -0
  61. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  62. package/dist/tempo/server/internal/html.gen.js +1 -1
  63. package/dist/tempo/session/client/SessionManager.d.ts +5 -0
  64. package/dist/tempo/session/client/SessionManager.js +16 -2
  65. package/dist/tempo/session/precompile/Chain.js +3 -0
  66. package/dist/tempo/subscription/KeyAuthorization.d.ts +7 -1
  67. package/dist/tempo/subscription/KeyAuthorization.js +21 -3
  68. package/dist/viem/Client.js +1 -0
  69. package/dist/x402/client/Exact.d.ts +2 -2
  70. package/dist/x402/client/Exact.js +1 -1
  71. package/dist/x402/server/EvmCharge.d.ts +2 -0
  72. package/dist/x402/server/EvmCharge.js +34 -26
  73. package/package.json +1 -1
@@ -3,6 +3,7 @@ import { type Address } from 'viem';
3
3
  import * as Account from '../../../viem/Account.js';
4
4
  import * as Client from '../../../viem/Client.js';
5
5
  import { type ChannelStore } from '../client/ChannelStore.js';
6
+ import { type SessionContext } from '../client/CredentialState.js';
6
7
  import { session as sessionPlugin } from '../client/Session.js';
7
8
  import type { SessionReceipt } from '../precompile/Protocol.js';
8
9
  import { deserializeSnapshot as deserializeSessionSnapshot, serializeSnapshot as serializeSessionSnapshot } from '../Snapshot.js';
@@ -48,6 +49,8 @@ export type PaymentResponse = Response & {
48
49
  /** Local cumulative voucher authorization in raw token units. */
49
50
  cumulative: bigint;
50
51
  };
52
+ /** @internal Merges manager-wide context without allowing it to replace an operation's fields. */
53
+ export declare function mergeSessionCredentialContext(credentialContext: unknown, operationContext: SessionContext): SessionContext;
51
54
  /**
52
55
  * Creates a session manager that handles the full client payment lifecycle:
53
56
  * channel open, incremental vouchers, SSE streaming, and channel close.
@@ -78,6 +81,8 @@ export declare namespace sessionManager {
78
81
  escrow?: Address | undefined;
79
82
  /** Fetch implementation used for HTTP probes, management posts, and paid retries. */
80
83
  fetch?: typeof globalThis.fetch | undefined;
84
+ /** Base context supplied to every session credential created by the manager. */
85
+ credentialContext?: unknown;
81
86
  /** Maximum deposit in human-readable units (e.g. `'10'` for 10 tokens). Converted to raw units via `decimals`. */
82
87
  maxDeposit?: string | undefined;
83
88
  /**
@@ -10,7 +10,7 @@ import * as Client from '../../../viem/Client.js';
10
10
  import { charge as chargePlugin } from '../../client/Charge.js';
11
11
  import * as defaults from '../../internal/defaults.js';
12
12
  import { createChannelStore, entryKey } from '../client/ChannelStore.js';
13
- import { hydrateSessionSnapshot } from '../client/CredentialState.js';
13
+ import { hydrateSessionSnapshot, sessionContextSchema, } from '../client/CredentialState.js';
14
14
  import { session as sessionPlugin } from '../client/Session.js';
15
15
  import * as Channel from '../precompile/Channel.js';
16
16
  import { deserializeSessionReceipt } from '../precompile/Protocol.js';
@@ -60,6 +60,12 @@ function requestInitWithSessionHint(input, init, channelId) {
60
60
  },
61
61
  };
62
62
  }
63
+ /** @internal Merges manager-wide context without allowing it to replace an operation's fields. */
64
+ export function mergeSessionCredentialContext(credentialContext, operationContext) {
65
+ if (credentialContext === undefined)
66
+ return operationContext;
67
+ return { ...sessionContextSchema.parse(credentialContext), ...operationContext };
68
+ }
63
69
  function resolveSessionManagerConfig(parameters) {
64
70
  const decimals = parameters.decimals ?? 6;
65
71
  const WebSocket = parameters.webSocket ??
@@ -92,6 +98,9 @@ export function sessionManager(parameters) {
92
98
  });
93
99
  const getAccount = Account.getResolver({ account: parameters.account });
94
100
  const runtime = createSessionManagerRuntime();
101
+ const credentialContext = parameters.credentialContext === undefined
102
+ ? undefined
103
+ : mergeSessionCredentialContext(parameters.credentialContext, {});
95
104
  const receipts = createSessionReceiptCoordinator({
96
105
  getSocketSession: () => runtime.socketSession,
97
106
  });
@@ -248,7 +257,10 @@ export function sessionManager(parameters) {
248
257
  },
249
258
  });
250
259
  function createSessionCredential(challenge, context) {
251
- return method.createCredential({ challenge, context });
260
+ return method.createCredential({
261
+ challenge,
262
+ context: mergeSessionCredentialContext(credentialContext, context),
263
+ });
252
264
  }
253
265
  function updateSpentFromReceipt(receipt) {
254
266
  applySessionReceiptToRuntime({
@@ -535,6 +547,8 @@ export function sessionManager(parameters) {
535
547
  use.trackCreates = true;
536
548
  const liveHint = runtime.channel?.opened ? runtime.channel.channelId : undefined;
537
549
  let effectiveInit = requestInitWithSessionHint(input, init, liveHint);
550
+ if (credentialContext)
551
+ effectiveInit = { ...effectiveInit, context: credentialContext };
538
552
  // Stored channels may be stale, so retry once after evicting the resumed entry.
539
553
  let canRetryResumed = !previous.channel?.opened;
540
554
  async function retryWithoutResumed() {
@@ -318,6 +318,7 @@ async function prepareFeePayerCallTransaction(client, parameters) {
318
318
  account,
319
319
  calls: [{ to, data }],
320
320
  feePayer: true,
321
+ nonceKey: 'expiring',
321
322
  ...(feeToken ? { feeToken } : {}),
322
323
  });
323
324
  }
@@ -329,6 +330,8 @@ function sendPrecompileContractCall(client, parameters) {
329
330
  ...(account ? { account } : {}),
330
331
  to,
331
332
  data,
333
+ // Server processes can share a signer without sharing viem's concurrency detection.
334
+ nonceKey: 'expiring',
332
335
  ...(feePayer ? { feePayer } : {}),
333
336
  ...(feeToken ? { feeToken } : {}),
334
337
  });
@@ -1,3 +1,4 @@
1
+ import { Hex } from 'ox';
1
2
  import { KeyAuthorization } from 'ox/tempo';
2
3
  import { type Address } from 'viem';
3
4
  import type * as Methods from '../Methods.js';
@@ -42,6 +43,8 @@ export declare function getSubscriptionRpcAllowedCalls(request: Pick<Subscriptio
42
43
  readonly recipients: readonly [`0x${string}`];
43
44
  }];
44
45
  }];
46
+ /** Decodes the server-issued challenge identifier for use as a signed Tempo witness. */
47
+ export declare function getSubscriptionChallengeWitness(challengeId: string): Hex.Hex;
45
48
  /**
46
49
  * Creates and signs a Tempo key authorization for subscription payments when the account can sign
47
50
  * arbitrary hashes locally.
@@ -53,6 +56,7 @@ export declare function signSubscriptionKeyAuthorization(parameters: {
53
56
  hash: `0x${string}`;
54
57
  }) => Promise<`0x${string}`>) | undefined;
55
58
  };
59
+ challengeId: string;
56
60
  chainId: number;
57
61
  request: Pick<SubscriptionRequest, 'amount' | 'currency' | 'periodCount' | 'periodUnit' | 'recipient' | 'subscriptionExpires'>;
58
62
  }): Promise<{
@@ -96,7 +100,7 @@ export declare function signSubscriptionKeyAuthorization(parameters: {
96
100
  version?: undefined;
97
101
  } | {
98
102
  metadata: {
99
- authenticatorData: import("ox/Hex").Hex;
103
+ authenticatorData: Hex.Hex;
100
104
  clientDataJSON: string;
101
105
  };
102
106
  signature: {
@@ -133,6 +137,7 @@ export declare function signSubscriptionKeyAuthorization(parameters: {
133
137
  readonly recipients: readonly `0x${string}`[];
134
138
  }[];
135
139
  readonly type: "p256" | "secp256k1" | "webAuthn";
140
+ readonly witness: `0x${string}`;
136
141
  } | undefined>;
137
142
  /**
138
143
  * Verifies that a subscription credential contains a key authorization scoped to the requested
@@ -140,6 +145,7 @@ export declare function signSubscriptionKeyAuthorization(parameters: {
140
145
  */
141
146
  export declare function verifySubscriptionKeyAuthorization(parameters: {
142
147
  accessKey?: SubscriptionAccessKey | undefined;
148
+ challengeId: string;
143
149
  chainId: number;
144
150
  payload: SubscriptionCredentialPayload;
145
151
  request: SubscriptionRequest;
@@ -1,3 +1,4 @@
1
+ import { Base64, Hex } from 'ox';
1
2
  import { KeyAuthorization, SignatureEnvelope } from 'ox/tempo';
2
3
  import { isAddress, isAddressEqual } from 'viem';
3
4
  import { VerificationFailedError } from '../../Errors.js';
@@ -104,16 +105,29 @@ export function getSubscriptionRpcAllowedCalls(request) {
104
105
  },
105
106
  ];
106
107
  }
108
+ /** Decodes the server-issued challenge identifier for use as a signed Tempo witness. */
109
+ export function getSubscriptionChallengeWitness(challengeId) {
110
+ try {
111
+ const witness = Hex.fromBytes(Base64.toBytes(challengeId));
112
+ if (Hex.size(witness) !== 32)
113
+ throw new Error('invalid challenge id size');
114
+ return witness;
115
+ }
116
+ catch {
117
+ throw new VerificationFailedError({ reason: 'challenge id must encode 32 bytes' });
118
+ }
119
+ }
107
120
  /**
108
121
  * Creates and signs a Tempo key authorization for subscription payments when the account can sign
109
122
  * arbitrary hashes locally.
110
123
  */
111
124
  export async function signSubscriptionKeyAuthorization(parameters) {
112
- const { accessKey, account, chainId, request } = parameters;
125
+ const { accessKey, account, challengeId, chainId, request } = parameters;
113
126
  if (typeof account.sign !== 'function')
114
127
  return undefined;
115
128
  const authorization = createUnsignedAuthorization({
116
129
  accessKey,
130
+ challengeId,
117
131
  chainId,
118
132
  request,
119
133
  });
@@ -135,7 +149,7 @@ export async function signSubscriptionKeyAuthorization(parameters) {
135
149
  * token, recipient, amount, period, expiry, chain, and server-issued access key.
136
150
  */
137
151
  export function verifySubscriptionKeyAuthorization(parameters) {
138
- const { accessKey, chainId, payload, request } = parameters;
152
+ const { accessKey, challengeId, chainId, payload, request } = parameters;
139
153
  if (payload.type !== 'keyAuthorization') {
140
154
  throw new VerificationFailedError({ reason: 'invalid keyAuthorization payload' });
141
155
  }
@@ -146,6 +160,9 @@ export function verifySubscriptionKeyAuthorization(parameters) {
146
160
  authorization,
147
161
  chainId,
148
162
  });
163
+ if (authorization.witness?.toLowerCase() !== getSubscriptionChallengeWitness(challengeId)) {
164
+ throw new VerificationFailedError({ reason: 'keyAuthorization challenge mismatch' });
165
+ }
149
166
  assertAuthorizationExpiry(authorization, request);
150
167
  assertAuthorizationLimit(getSingleTokenLimit(authorization), request);
151
168
  assertAuthorizationScopes(authorization.scopes, request);
@@ -159,7 +176,7 @@ export function verifySubscriptionKeyAuthorization(parameters) {
159
176
  };
160
177
  }
161
178
  function createUnsignedAuthorization(parameters) {
162
- const { accessKey, chainId, request } = parameters;
179
+ const { accessKey, challengeId, chainId, request } = parameters;
163
180
  return KeyAuthorization.from({
164
181
  address: normalizeAddress(accessKey.accessKeyAddress, 'accessKeyAddress'),
165
182
  chainId: BigInt(chainId),
@@ -173,6 +190,7 @@ function createUnsignedAuthorization(parameters) {
173
190
  ],
174
191
  scopes: getSubscriptionScopes(request),
175
192
  type: accessKey.keyType,
193
+ witness: getSubscriptionChallengeWitness(challengeId),
176
194
  });
177
195
  }
178
196
  function deserializeAuthorization(signature) {
@@ -9,6 +9,7 @@ const knownTempoChains = {
9
9
  /** Creates an HTTP transport for a configured remote fee-payer service. */
10
10
  export function remoteFeePayerTransport(config, options = {}) {
11
11
  return http(config.url, {
12
+ fetchFn: config.fetch,
12
13
  fetchOptions: { headers: RemoteFeePayer.resolveHeaders(config) },
13
14
  ...options,
14
15
  });
@@ -28,8 +28,8 @@ export type Signer = Account & {
28
28
  signTypedData?: (parameters: any) => Promise<`0x${string}`>;
29
29
  };
30
30
  export type Config = {
31
- /** Account used to sign exact EVM payment payloads. */
32
- account: Account;
31
+ /** Account used to sign exact EVM payment payloads, unless supplied in credential context. */
32
+ account?: Account | undefined;
33
33
  /** Optional token decimals used to parse `maxAmount` when currency metadata is not provided. */
34
34
  decimals?: number | undefined;
35
35
  /** Optional maximum display-unit amount the client is willing to pay. */
@@ -8,7 +8,7 @@ import * as Types from '../Types.js';
8
8
  */
9
9
  export async function createCredential(parameters) {
10
10
  const account = (parameters.context?.account ?? parameters.config.account);
11
- if (!account.signTypedData)
11
+ if (!account?.signTypedData)
12
12
  throw new Error('x402 exact requires a typed-data signer.');
13
13
  const request = parameters.challenge.request;
14
14
  const accepted = Types.toPaymentRequirements(request);
@@ -52,6 +52,8 @@ export declare function resolveOptions(parameters: {
52
52
  export declare function createPath(config: ResolvedOptions): Path;
53
53
  /** Settles a verified EVM authorization through an x402 facilitator. */
54
54
  export declare function settleWithFacilitator(parameters: ResolvedOptions): SettleWithFacilitator;
55
+ /** Checks an EVM authorization with the facilitator without settling it. */
56
+ export declare function verifyWithFacilitator(parameters: ResolvedOptions, authorization: Parameters<SettleWithFacilitator>[0]): Promise<void>;
55
57
  export type SettleWithFacilitator = (parameters: {
56
58
  credential: Credential.Credential<Types.AuthorizationPayload>;
57
59
  payload: Types.AuthorizationPayload;
@@ -174,34 +174,11 @@ export function createPath(config) {
174
174
  }
175
175
  /** Settles a verified EVM authorization through an x402 facilitator. */
176
176
  export function settleWithFacilitator(parameters) {
177
- const { facilitator, maxTimeoutSeconds } = parameters;
177
+ const { facilitator } = parameters;
178
178
  if (!facilitator)
179
179
  throw new Error('EVM authorization x402 requires `facilitator`.');
180
- return async ({ payload, request }) => {
181
- const paymentRequirements = toPaymentRequirements(request, {
182
- ...parameters,
183
- maxTimeoutSeconds,
184
- });
185
- const paymentPayload = {
186
- accepted: paymentRequirements,
187
- payload: {
188
- authorization: {
189
- from: payload.from,
190
- nonce: payload.nonce,
191
- to: payload.to,
192
- validAfter: payload.validAfter,
193
- validBefore: payload.validBefore,
194
- value: payload.value,
195
- },
196
- signature: payload.signature,
197
- },
198
- x402Version: 2,
199
- };
200
- const verified = await facilitator.verify(paymentPayload, paymentRequirements);
201
- if (!verified.isValid)
202
- throw new VerificationFailedError({
203
- reason: verified.invalidMessage ?? verified.invalidReason ?? 'EVM facilitator verify failed',
204
- });
180
+ return async (authorization) => {
181
+ const { paymentPayload, paymentRequirements } = facilitatorPayment(parameters, authorization);
205
182
  const settled = await facilitator.settle(paymentPayload, paymentRequirements);
206
183
  if (!settled.success)
207
184
  throw new VerificationFailedError({
@@ -212,6 +189,37 @@ export function settleWithFacilitator(parameters) {
212
189
  };
213
190
  };
214
191
  }
192
+ /** Checks an EVM authorization with the facilitator without settling it. */
193
+ export async function verifyWithFacilitator(parameters, authorization) {
194
+ const { facilitator } = parameters;
195
+ if (!facilitator)
196
+ throw new Error('EVM authorization x402 requires `facilitator`.');
197
+ const { paymentPayload, paymentRequirements } = facilitatorPayment(parameters, authorization);
198
+ const verified = await facilitator.verify(paymentPayload, paymentRequirements);
199
+ if (!verified.isValid)
200
+ throw new VerificationFailedError({
201
+ reason: verified.invalidMessage ?? verified.invalidReason ?? 'EVM facilitator verify failed',
202
+ });
203
+ }
204
+ function facilitatorPayment(parameters, { payload, request }) {
205
+ const paymentRequirements = toPaymentRequirements(request, parameters);
206
+ const paymentPayload = {
207
+ accepted: paymentRequirements,
208
+ payload: {
209
+ authorization: {
210
+ from: payload.from,
211
+ nonce: payload.nonce,
212
+ to: payload.to,
213
+ validAfter: payload.validAfter,
214
+ validBefore: payload.validBefore,
215
+ value: payload.value,
216
+ },
217
+ signature: payload.signature,
218
+ },
219
+ x402Version: 2,
220
+ };
221
+ return { paymentPayload, paymentRequirements };
222
+ }
215
223
  /** Returns whether a credential was converted from an x402 payment payload. */
216
224
  export function isCredential(credential) {
217
225
  return credential[x402Credential] === true;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mppx",
3
3
  "type": "module",
4
- "version": "0.9.1",
4
+ "version": "0.9.3",
5
5
  "main": "./dist/index.js",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/wevm/mppx#readme",