mppx 0.11.0 → 0.12.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 (41) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +22 -1
  3. package/dist/cli/agent.d.ts +3 -0
  4. package/dist/cli/agent.js +20 -0
  5. package/dist/cli/cli.d.ts +1 -1
  6. package/dist/cli/cli.js +135 -135
  7. package/dist/cli/plugins/index.js +12 -12
  8. package/dist/cli/validate/payment.js +29 -12
  9. package/dist/evm/server/Methods.d.ts +164 -3
  10. package/dist/evm/server/Methods.js +50 -2
  11. package/dist/internal/version.d.ts +2 -2
  12. package/dist/internal/version.js +1 -1
  13. package/dist/server/Mppx.d.ts +5 -1
  14. package/dist/server/Mppx.js +120 -49
  15. package/dist/server/internal/html/compose.main.gen.d.ts +1 -1
  16. package/dist/server/internal/html/serviceWorker.gen.d.ts +1 -1
  17. package/dist/stripe/server/Methods.js +2 -4
  18. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  19. package/dist/tempo/internal/currencies.d.ts +19 -0
  20. package/dist/tempo/internal/currencies.js +41 -0
  21. package/dist/tempo/internal/defaults.d.ts +5 -3
  22. package/dist/tempo/internal/defaults.js +6 -2
  23. package/dist/tempo/internal/fee-payer.d.ts +5 -4
  24. package/dist/tempo/internal/fee-payer.js +7 -5
  25. package/dist/tempo/server/Charge.d.ts +12 -1
  26. package/dist/tempo/server/Charge.js +34 -32
  27. package/dist/tempo/server/Methods.d.ts +5003 -40
  28. package/dist/tempo/server/Methods.js +104 -13
  29. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  30. package/dist/tempo/server/internal/html.gen.js +1 -1
  31. package/dist/tempo/session/server/ChannelStore.d.ts +2 -0
  32. package/dist/tempo/session/server/ChannelStore.js +12 -3
  33. package/dist/tempo/session/server/CredentialVerification.d.ts +2 -0
  34. package/dist/tempo/session/server/CredentialVerification.js +22 -13
  35. package/dist/tempo/session/server/RequestState.d.ts +6 -0
  36. package/dist/tempo/session/server/RequestState.js +11 -4
  37. package/dist/tempo/session/server/Session.js +8 -1
  38. package/dist/tempo/session/server/Settlement.d.ts +2 -0
  39. package/dist/tempo/session/server/Settlement.js +13 -8
  40. package/dist/tempo/subscription/KeyAuthorization.d.ts +70 -8
  41. package/package.json +3 -3
@@ -189,6 +189,8 @@ export type VerifyAndAcceptVoucherParameters = {
189
189
  methodDetails: SessionMethodDetails;
190
190
  /** Minimum allowed voucher delta in raw units. */
191
191
  minVoucherDelta: bigint;
192
+ /** Whether this presentation must advance the accepted voucher. */
193
+ requireAdvance?: boolean | undefined;
192
194
  /** Server channel store. */
193
195
  store: ChannelStore;
194
196
  /** Signed cumulative voucher to verify and accept. */
@@ -224,9 +224,11 @@ export async function validateVoucher(parameters) {
224
224
  }
225
225
  /** Verifies a cumulative voucher and returns a session receipt after store reconciliation. */
226
226
  export async function verifyAndAcceptVoucher(parameters) {
227
- const { store, challenge, channel, voucher, channelState } = parameters;
227
+ const { store, challenge, channel, voucher, channelState, requireAdvance } = parameters;
228
228
  const validation = await validateVoucher(parameters);
229
- if (validation.alreadyAccepted)
229
+ if (validation.alreadyAccepted) {
230
+ if (requireAdvance)
231
+ throw new DeltaTooSmallError({ reason: 'voucher does not add new funds for this request' });
230
232
  return createSessionReceipt({
231
233
  challengeId: challenge.id,
232
234
  channelId: voucher.channelId,
@@ -234,7 +236,14 @@ export async function verifyAndAcceptVoucher(parameters) {
234
236
  spent: channel.spent,
235
237
  units: channel.units,
236
238
  });
237
- const updated = await store.updateChannel(voucher.channelId, (current) => acceptVoucherStateUpdate({ channelState, current, voucher }));
239
+ }
240
+ const updated = await store.updateChannel(voucher.channelId, (current) => {
241
+ if (requireAdvance &&
242
+ current !== null &&
243
+ voucher.cumulativeAmount <= current.highestVoucherAmount)
244
+ throw new DeltaTooSmallError({ reason: 'voucher does not add new funds for this request' });
245
+ return acceptVoucherStateUpdate({ channelState, current, voucher });
246
+ });
238
247
  if (!updated)
239
248
  throw new ChannelNotFoundError({ reason: 'channel not found' });
240
249
  return createSessionReceipt({
@@ -79,6 +79,8 @@ export type BroadcastCredentialPayloadParameters = {
79
79
  onSessionSettlement?: OnSessionSettlement | undefined;
80
80
  /** Discriminated session credential payload to verify. */
81
81
  payload: SessionCredentialPayload;
82
+ /** Whether an open or voucher credential must add new funds for this request. */
83
+ requireVoucherAdvance?: boolean | undefined;
82
84
  /** Server-side channel store. */
83
85
  store: ChannelStore.ChannelStore;
84
86
  };
@@ -1,5 +1,5 @@
1
1
  import { isAddress, isAddressEqual, zeroAddress, } from 'viem';
2
- import { AmountExceedsDepositError, ChannelClosedError, ChannelNotFoundError, InsufficientBalanceError, InvalidSignatureError, VerificationFailedError, } from '../../../Errors.js';
2
+ import { AmountExceedsDepositError, ChannelClosedError, ChannelNotFoundError, DeltaTooSmallError, InsufficientBalanceError, InvalidSignatureError, VerificationFailedError, } from '../../../Errors.js';
3
3
  import * as Chain from '../precompile/Chain.js';
4
4
  import { readChannelClosedReceiptFields } from '../precompile/Chain.js';
5
5
  import * as Channel from '../precompile/Channel.js';
@@ -513,18 +513,26 @@ async function handleOpenCredential(parameters) {
513
513
  const { descriptor, state } = result;
514
514
  assertSameDescriptor(descriptor, payload.descriptor);
515
515
  validateChannelState(state, request.amount);
516
- const updated = await store.updateChannel(channelId, (current) => ChannelStore.openChannelState({
517
- authorizedSigner: authorizedSigner(descriptor),
518
- chainId,
519
- channelId,
520
- current,
521
- descriptor,
522
- escrow,
523
- expiringNonceHash: result.expiringNonceHash,
524
- cumulativeAmount,
525
- signature: payload.signature,
526
- state,
527
- }));
516
+ const updated = await store.updateChannel(channelId, (current) => {
517
+ if (parameters.requireVoucherAdvance &&
518
+ current !== null &&
519
+ cumulativeAmount <= current.highestVoucherAmount)
520
+ throw new DeltaTooSmallError({
521
+ reason: 'voucher does not add new funds for this request',
522
+ });
523
+ return ChannelStore.openChannelState({
524
+ authorizedSigner: authorizedSigner(descriptor),
525
+ chainId,
526
+ channelId,
527
+ current,
528
+ descriptor,
529
+ escrow,
530
+ expiringNonceHash: result.expiringNonceHash,
531
+ cumulativeAmount,
532
+ signature: payload.signature,
533
+ state,
534
+ });
535
+ });
528
536
  if (!updated)
529
537
  throw new VerificationFailedError({ reason: 'failed to create channel' });
530
538
  return createSessionReceipt({
@@ -618,6 +626,7 @@ async function handleVoucherCredential(parameters) {
618
626
  return ChannelStore.verifyAndAcceptVoucher({
619
627
  store,
620
628
  minVoucherDelta,
629
+ requireAdvance: parameters.requireVoucherAdvance,
621
630
  challenge,
622
631
  channel,
623
632
  voucher,
@@ -18,6 +18,10 @@ export type ResolveSessionSnapshotParameters = {
18
18
  channelId: Hex | undefined;
19
19
  /** Payment fields the reusable channel must match before it is advertised. */
20
20
  expected?: SessionSnapshotPaymentFields | undefined;
21
+ /** Minimum raw-unit increase required when the next voucher must advance. */
22
+ minimumVoucherAdvance?: bigint | undefined;
23
+ /** Whether the next voucher must advance beyond the accepted cumulative amount. */
24
+ requireVoucherAdvance?: boolean | undefined;
21
25
  /** Server channel store. */
22
26
  store: ChannelStore.ChannelStore;
23
27
  };
@@ -149,12 +153,14 @@ export type ResolveSessionPaymentRequestParameters = {
149
153
  decimals: number;
150
154
  defaultFeePayer?: viem_Account | undefined;
151
155
  getClient: ResolveRequestChainIdParameters['getClient'];
156
+ minVoucherDelta?: string | undefined;
152
157
  parameterChainId?: number | undefined;
153
158
  parameterEscrowContract?: Address | undefined;
154
159
  parameterFeePayer?: ParameterFeePayer;
155
160
  request: SessionPaymentRequestInput;
156
161
  resolveChannelId?: ResolveSessionChannelId | undefined;
157
162
  source?: string | undefined;
163
+ sseEnabled?: boolean | undefined;
158
164
  store: ChannelStore.ChannelStore;
159
165
  };
160
166
  /** Inputs used to resolve shared context for credential verification. */
@@ -47,7 +47,7 @@ export async function resolveSessionChannelId(parameters) {
47
47
  }
48
48
  /** Builds server bootstrap hints for a reusable precompile session channel. */
49
49
  export async function resolveSessionSnapshot(parameters) {
50
- const { amount, channelId, expected, store } = parameters;
50
+ const { amount, channelId, expected, minimumVoucherAdvance = 0n, requireVoucherAdvance = false, store, } = parameters;
51
51
  if (!channelId)
52
52
  return undefined;
53
53
  const channel = await store.getChannel(ChannelStore.normalizeChannelId(channelId));
@@ -63,7 +63,11 @@ export async function resolveSessionSnapshot(parameters) {
63
63
  return undefined;
64
64
  if (expected && !matchesSnapshotPaymentFields(channel, expected))
65
65
  return undefined;
66
- const requiredCumulative = channel.spent + amount;
66
+ const spendBoundary = channel.spent + amount;
67
+ const advanceBoundary = requireVoucherAdvance
68
+ ? channel.highestVoucherAmount + (minimumVoucherAdvance > 0n ? minimumVoucherAdvance : 1n)
69
+ : 0n;
70
+ const requiredCumulative = spendBoundary > advanceBoundary ? spendBoundary : advanceBoundary;
67
71
  return {
68
72
  acceptedCumulative: channel.highestVoucherAmount.toString(),
69
73
  chainId: channel.chainId,
@@ -158,7 +162,7 @@ export async function resolveRequestChainId(parameters) {
158
162
  }
159
163
  /** Resolves request-time TIP-1034 details and server bootstrap hints for a challenge. */
160
164
  export async function resolveSessionPaymentRequest(parameters) {
161
- const { capturedRequest, credential, decimals, defaultFeePayer, getClient, parameterChainId, parameterEscrowContract, parameterFeePayer, request, resolveChannelId, source, store, } = parameters;
165
+ const { capturedRequest, credential, decimals, defaultFeePayer, getClient, minVoucherDelta, parameterChainId, parameterEscrowContract, parameterFeePayer, request, resolveChannelId, source, sseEnabled, store, } = parameters;
162
166
  const chainId = await resolveRequestChainId({
163
167
  getClient,
164
168
  parameterChainId,
@@ -172,6 +176,7 @@ export async function resolveSessionPaymentRequest(parameters) {
172
176
  const escrowContract = resolveRequestEscrowContract(request.escrowContract, parameterEscrowContract);
173
177
  const operator = resolveRequestOperator(request.operator);
174
178
  const requestAmount = parseUnits(request.amount, decimals);
179
+ const isContentRequest = capturedRequest ? isSessionContentRequest(capturedRequest) : false;
175
180
  const channelId = await resolveSessionChannelId({
176
181
  capturedRequest,
177
182
  credential,
@@ -181,7 +186,7 @@ export async function resolveSessionPaymentRequest(parameters) {
181
186
  store,
182
187
  });
183
188
  const sessionSnapshot = await resolveSessionSnapshot({
184
- amount: capturedRequest && !isSessionContentRequest(capturedRequest) ? 0n : requestAmount,
189
+ amount: capturedRequest && !isContentRequest ? 0n : requestAmount,
185
190
  channelId,
186
191
  expected: {
187
192
  chainId,
@@ -189,6 +194,8 @@ export async function resolveSessionPaymentRequest(parameters) {
189
194
  escrowContract,
190
195
  recipient: readChallengeAddress(request.recipient, 'recipient'),
191
196
  },
197
+ minimumVoucherAdvance: parseUnits(minVoucherDelta ?? '0', decimals),
198
+ requireVoucherAdvance: Boolean(capturedRequest) && !sseEnabled && isContentRequest,
192
199
  store,
193
200
  });
194
201
  const { operator: _operator, ...requestWithoutOperator } = request;
@@ -26,7 +26,7 @@ import { broadcastCredentialPayload, validateCredentialPayload } from './Credent
26
26
  import { requireSessionCredentialPayload } from './CredentialVerification.js';
27
27
  import { resolveCredentialVerificationContext, resolveSessionChannelId, resolveSessionSnapshot, resolveSessionPaymentRequest, } from './RequestState.js';
28
28
  import { respondToSessionCredential } from './RequestState.js';
29
- import { applyVerifiedHttpAccounting, chargeSessionChannel, } from './Settlement.js';
29
+ import { applyVerifiedHttpAccounting, chargeSessionChannel, shouldApplyVerifiedHttpAccounting, } from './Settlement.js';
30
30
  import { isSettlementDue, maybeSettleScheduled } from './Settlement.js';
31
31
  import { resolveSettlementSchedule, } from './Settlement.js';
32
32
  import * as Ws from './Ws.js';
@@ -305,6 +305,11 @@ export function session(p) {
305
305
  minVoucherDelta: context.minVoucherDelta,
306
306
  onSessionSettlement,
307
307
  payload,
308
+ requireVoucherAdvance: shouldApplyVerifiedHttpAccounting({
309
+ capturedRequest: envelope?.capturedRequest,
310
+ payloadAction: payload.action,
311
+ sseEnabled: Boolean(parameters.sse),
312
+ }),
308
313
  store,
309
314
  });
310
315
  return applyVerifiedHttpAccounting({
@@ -372,11 +377,13 @@ export function session(p) {
372
377
  decimals,
373
378
  defaultFeePayer: feePayer,
374
379
  getClient,
380
+ minVoucherDelta: parameters.minVoucherDelta,
375
381
  parameterChainId: parameters.chainId,
376
382
  parameterEscrowContract: parameters.escrowContract,
377
383
  parameterFeePayer: configuredFeePayer,
378
384
  request,
379
385
  resolveChannelId: parameters.resolveChannelId,
386
+ sseEnabled: Boolean(parameters.sse),
380
387
  store,
381
388
  });
382
389
  return {
@@ -138,6 +138,8 @@ export type ApplyVerifiedHttpAccountingParameters = {
138
138
  /** Runs optional server settlement policy after a successful content charge. */
139
139
  settleCharged: SettleChargedSessionChannel;
140
140
  };
141
+ /** Returns whether an accepted credential will authorize and charge an HTTP content response. */
142
+ export declare function shouldApplyVerifiedHttpAccounting(parameters: Pick<ApplyVerifiedHttpAccountingParameters, 'capturedRequest' | 'payloadAction' | 'sseEnabled'>): boolean;
141
143
  /** Applies the default HTTP content charge after a session credential has been accepted. */
142
144
  export declare function applyVerifiedHttpAccounting(parameters: ApplyVerifiedHttpAccountingParameters): Promise<SessionReceipt>;
143
145
  /** Atomically deducts spend from a channel and maps store failures to typed session errors. */
@@ -46,7 +46,7 @@ export function resolveCredentialFeePayer(parameters) {
46
46
  return requestFeePayer;
47
47
  if (typeof feePayer === 'object')
48
48
  return feePayer;
49
- return typeof feePayer === 'string' ? true : undefined;
49
+ return feePayer === true || typeof feePayer === 'string' ? true : undefined;
50
50
  }
51
51
  /** Converts a public settlement schedule into raw-unit thresholds. */
52
52
  export function resolveSettlementSchedule(schedule, decimals) {
@@ -113,16 +113,21 @@ export async function markSettlementComplete(parameters) {
113
113
  }
114
114
  : current);
115
115
  }
116
- /** Applies the default HTTP content charge after a session credential has been accepted. */
117
- export async function applyVerifiedHttpAccounting(parameters) {
118
- const { capturedRequest, payloadAction, receipt, sseEnabled } = parameters;
116
+ /** Returns whether an accepted credential will authorize and charge an HTTP content response. */
117
+ export function shouldApplyVerifiedHttpAccounting(parameters) {
118
+ const { capturedRequest, payloadAction, sseEnabled } = parameters;
119
119
  if (!capturedRequest)
120
- return receipt;
120
+ return false;
121
121
  if (payloadAction !== 'open' && payloadAction !== 'voucher')
122
- return receipt;
122
+ return false;
123
123
  if (sseEnabled && capturedRequest.method === 'POST')
124
- return receipt;
125
- if (!isSessionContentRequest(capturedRequest))
124
+ return false;
125
+ return isSessionContentRequest(capturedRequest);
126
+ }
127
+ /** Applies the default HTTP content charge after a session credential has been accepted. */
128
+ export async function applyVerifiedHttpAccounting(parameters) {
129
+ const { receipt, sseEnabled } = parameters;
130
+ if (!shouldApplyVerifiedHttpAccounting(parameters))
126
131
  return receipt;
127
132
  const requestAmount = parameters.getRequestAmount();
128
133
  const charged = await parameters.charge(receipt.channelId, requestAmount);
@@ -74,7 +74,7 @@ export declare function signSubscriptionKeyAuthorization(parameters: {
74
74
  };
75
75
  type: 'p256';
76
76
  account?: undefined;
77
- init?: undefined;
77
+ config?: undefined;
78
78
  inner?: undefined;
79
79
  keyId?: undefined;
80
80
  metadata?: undefined;
@@ -89,7 +89,7 @@ export declare function signSubscriptionKeyAuthorization(parameters: {
89
89
  };
90
90
  type: 'secp256k1';
91
91
  account?: undefined;
92
- init?: undefined;
92
+ config?: undefined;
93
93
  inner?: undefined;
94
94
  keyId?: undefined;
95
95
  metadata?: undefined;
@@ -103,25 +103,87 @@ export declare function signSubscriptionKeyAuthorization(parameters: {
103
103
  authenticatorData: Hex.Hex;
104
104
  clientDataJSON: string;
105
105
  };
106
- signature: {
107
- r: bigint;
108
- s: bigint;
109
- yParity?: number | undefined;
110
- };
111
106
  publicKey: {
112
107
  prefix: number;
113
108
  x: bigint;
114
109
  y: bigint;
115
110
  };
111
+ signature: {
112
+ r: bigint;
113
+ s: bigint;
114
+ yParity?: number | undefined;
115
+ };
116
116
  type: 'webAuthn';
117
117
  account?: undefined;
118
- init?: undefined;
118
+ config?: undefined;
119
119
  inner?: undefined;
120
120
  keyId?: undefined;
121
121
  prehash?: undefined;
122
122
  signatures?: undefined;
123
123
  userAddress?: undefined;
124
124
  version?: undefined;
125
+ } | {
126
+ account: import("ox/Address").Address;
127
+ config: {
128
+ owners: readonly {
129
+ owner: import("ox/Address").Address;
130
+ weight: number;
131
+ }[];
132
+ salt: Hex.Hex;
133
+ threshold: number;
134
+ version: bigint;
135
+ };
136
+ signatures: readonly ({
137
+ prehash: boolean;
138
+ publicKey: {
139
+ prefix: number;
140
+ x: bigint;
141
+ y: bigint;
142
+ };
143
+ signature: {
144
+ r: bigint;
145
+ s: bigint;
146
+ yParity?: number | undefined;
147
+ };
148
+ type: 'p256';
149
+ metadata?: undefined;
150
+ } | {
151
+ signature: {
152
+ r: bigint;
153
+ s: bigint;
154
+ yParity: number;
155
+ };
156
+ type: 'secp256k1';
157
+ metadata?: undefined;
158
+ prehash?: undefined;
159
+ publicKey?: undefined;
160
+ } | {
161
+ metadata: {
162
+ authenticatorData: Hex.Hex;
163
+ clientDataJSON: string;
164
+ };
165
+ publicKey: {
166
+ prefix: number;
167
+ x: bigint;
168
+ y: bigint;
169
+ };
170
+ signature: {
171
+ r: bigint;
172
+ s: bigint;
173
+ yParity?: number | undefined;
174
+ };
175
+ type: 'webAuthn';
176
+ prehash?: undefined;
177
+ })[];
178
+ type: 'multisig';
179
+ inner?: undefined;
180
+ keyId?: undefined;
181
+ metadata?: undefined;
182
+ prehash?: undefined;
183
+ publicKey?: undefined;
184
+ signature?: undefined;
185
+ userAddress?: undefined;
186
+ version?: undefined;
125
187
  };
126
188
  readonly address: `0x${string}`;
127
189
  readonly chainId: bigint;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mppx",
3
3
  "type": "module",
4
- "version": "0.11.0",
4
+ "version": "0.12.0",
5
5
  "main": "./dist/index.js",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/wevm/mppx#readme",
@@ -167,7 +167,7 @@
167
167
  "express": ">=5",
168
168
  "hono": ">=4.12.25",
169
169
  "next": ">=16.2.6",
170
- "viem": ">=2.54.0"
170
+ "viem": ">=2.57.1"
171
171
  },
172
172
  "peerDependenciesMeta": {
173
173
  "@modelcontextprotocol/sdk": {
@@ -204,7 +204,7 @@
204
204
  "dependencies": {
205
205
  "@stripe/stripe-js": "9.13.0",
206
206
  "eventsource-parser": "3.1.1",
207
- "ox": "0.14.33",
207
+ "ox": "0.14.45",
208
208
  "structured-headers": "2.0.3",
209
209
  "zod": "^4.4.3"
210
210
  },