mppx 0.12.0 → 0.13.2

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 (110) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/Method.d.ts +7 -1
  3. package/dist/Method.js +7 -2
  4. package/dist/Receipt.d.ts +2 -0
  5. package/dist/Receipt.js +2 -0
  6. package/dist/cli/cli.d.ts +8 -0
  7. package/dist/cli/cli.js +134 -136
  8. package/dist/cli/plugins/index.js +12 -14
  9. package/dist/cli/plugins/tempo.js +9 -2
  10. package/dist/cli/sessions/request.d.ts +1 -1
  11. package/dist/cli/sessions/request.js +6 -8
  12. package/dist/cli/utils.d.ts +11 -1
  13. package/dist/cli/utils.js +15 -2
  14. package/dist/cli/validate/challenge.d.ts +2 -0
  15. package/dist/cli/validate/challenge.js +19 -4
  16. package/dist/cli/validate/discovery.d.ts +1 -0
  17. package/dist/cli/validate/discovery.js +10 -1
  18. package/dist/cli/validate/payment.js +34 -12
  19. package/dist/client/Methods.d.ts +0 -2
  20. package/dist/client/Methods.js +0 -2
  21. package/dist/client/Mppx.js +1 -0
  22. package/dist/client/index.d.ts +1 -1
  23. package/dist/client/index.js +1 -1
  24. package/dist/client/internal/Fetch.d.ts +2 -0
  25. package/dist/client/internal/Fetch.js +22 -7
  26. package/dist/client/internal/MethodChallenge.d.ts +1 -0
  27. package/dist/evm/Types.js +1 -1
  28. package/dist/evm/server/Charge.js +5 -0
  29. package/dist/integrations/x402/Evm.js +4 -3
  30. package/dist/internal/HeaderCodec.d.ts +2 -2
  31. package/dist/internal/version.d.ts +2 -2
  32. package/dist/internal/version.js +1 -1
  33. package/dist/mcp/client/McpClient.js +4 -7
  34. package/dist/middlewares/elysia.d.ts +11 -8
  35. package/dist/middlewares/elysia.js +67 -14
  36. package/dist/middlewares/express.js +13 -6
  37. package/dist/middlewares/hono.js +4 -4
  38. package/dist/middlewares/nextjs.js +9 -5
  39. package/dist/proxy/internal/Headers.js +65 -0
  40. package/dist/proxy/services/stripe.js +1 -0
  41. package/dist/server/Mppx.d.ts +14 -2
  42. package/dist/server/Mppx.js +85 -44
  43. package/dist/server/NodeListener.js +71 -6
  44. package/dist/server/Request.d.ts +11 -1
  45. package/dist/server/Request.js +132 -13
  46. package/dist/server/Transport.d.ts +14 -1
  47. package/dist/server/Transport.js +1 -0
  48. package/dist/stripe/server/Methods.d.ts +10 -3
  49. package/dist/stripe/server/Methods.js +17 -2
  50. package/dist/stripe/server/internal/html.gen.js +1 -1
  51. package/dist/tempo/Methods.d.ts +1 -0
  52. package/dist/tempo/Methods.js +1 -0
  53. package/dist/tempo/client/Charge.js +17 -13
  54. package/dist/tempo/client/Methods.d.ts +1 -5
  55. package/dist/tempo/client/Methods.js +10 -5
  56. package/dist/tempo/client/ResolveAccount.d.ts +3 -0
  57. package/dist/tempo/client/index.d.ts +0 -3
  58. package/dist/tempo/client/index.js +0 -2
  59. package/dist/tempo/internal/fee-payer.d.ts +6 -3
  60. package/dist/tempo/internal/fee-payer.js +23 -6
  61. package/dist/tempo/internal/machine-token-charge.d.ts +23 -0
  62. package/dist/tempo/internal/machine-token-charge.js +2 -1
  63. package/dist/tempo/legacy/index.d.ts +1 -3
  64. package/dist/tempo/legacy/index.js +1 -3
  65. package/dist/tempo/server/Charge.js +87 -53
  66. package/dist/tempo/server/Methods.d.ts +55 -10
  67. package/dist/tempo/server/Subscription.d.ts +1 -1
  68. package/dist/tempo/server/Subscription.js +31 -4
  69. package/dist/tempo/server/internal/html.gen.js +1 -1
  70. package/dist/tempo/server/internal/transport.js +28 -11
  71. package/dist/tempo/session/client/ChannelOps.js +5 -0
  72. package/dist/tempo/session/client/Session.d.ts +1 -0
  73. package/dist/tempo/session/client/Session.js +7 -4
  74. package/dist/tempo/session/client/SessionManager.js +3 -0
  75. package/dist/tempo/session/client/Transports.d.ts +8 -1
  76. package/dist/tempo/session/client/Transports.js +12 -3
  77. package/dist/tempo/session/precompile/Chain.js +16 -1
  78. package/dist/tempo/session/precompile/Protocol.d.ts +2 -0
  79. package/dist/tempo/session/precompile/Voucher.d.ts +16 -0
  80. package/dist/tempo/session/precompile/Voucher.js +48 -0
  81. package/dist/tempo/session/server/ChannelStore.d.ts +20 -2
  82. package/dist/tempo/session/server/ChannelStore.js +25 -4
  83. package/dist/tempo/session/server/CredentialVerification.d.ts +4 -1
  84. package/dist/tempo/session/server/CredentialVerification.js +34 -6
  85. package/dist/tempo/session/server/MeteredStream.d.ts +3 -2
  86. package/dist/tempo/session/server/MeteredStream.js +85 -29
  87. package/dist/tempo/session/server/Session.d.ts +2 -1
  88. package/dist/tempo/session/server/Session.js +2 -1
  89. package/dist/tempo/session/server/Settlement.d.ts +21 -0
  90. package/dist/tempo/session/server/Settlement.js +92 -17
  91. package/dist/tempo/session/server/Sse.d.ts +3 -1
  92. package/dist/tempo/session/server/Sse.js +105 -32
  93. package/dist/tempo/session/server/Transports.d.ts +24 -19
  94. package/dist/tempo/session/server/Transports.js +169 -58
  95. package/dist/tempo/session/server/Ws.d.ts +5 -1
  96. package/dist/tempo/session/server/Ws.js +75 -20
  97. package/dist/tempo/subscription/Store.d.ts +2 -0
  98. package/dist/tempo/subscription/Store.js +3 -0
  99. package/dist/validation/core.js +2 -1
  100. package/dist/x402/server/EvmCharge.d.ts +3 -1
  101. package/dist/x402/server/EvmCharge.js +14 -1
  102. package/package.json +6 -6
  103. package/dist/tempo/legacy/client/ChannelOps.d.ts +0 -69
  104. package/dist/tempo/legacy/client/ChannelOps.js +0 -168
  105. package/dist/tempo/legacy/client/Session.d.ts +0 -185
  106. package/dist/tempo/legacy/client/Session.js +0 -279
  107. package/dist/tempo/legacy/client/SessionManager.d.ts +0 -100
  108. package/dist/tempo/legacy/client/SessionManager.js +0 -706
  109. package/dist/tempo/legacy/client/index.d.ts +0 -7
  110. package/dist/tempo/legacy/client/index.js +0 -5
@@ -1,6 +1,6 @@
1
1
  import * as SignatureEnvelope from 'ox/tempo/SignatureEnvelope';
2
2
  import { decodeFunctionData, formatUnits, keccak256, parseEventLogs, } from 'viem';
3
- import { getTransactionReceipt, sendRawTransaction, sendRawTransactionSync, signTransaction, verifyTypedData, call as viem_call, } from 'viem/actions';
3
+ import { getTransaction, getTransactionReceipt, sendRawTransaction, sendRawTransactionSync, signTransaction, verifyTypedData, call as viem_call, } from 'viem/actions';
4
4
  import { Abis, Actions, Transaction } from 'viem/tempo';
5
5
  import { tempo as tempo_chain } from 'viem/tempo/chains';
6
6
  import { PaymentError, VerificationFailedError } from '../../Errors.js';
@@ -116,6 +116,43 @@ export function charge(parameters = {}) {
116
116
  supportedModes,
117
117
  };
118
118
  }
119
+ /** Validates a recognized payment route and retains its funding metadata. */
120
+ function validatePaymentRoute(calls, context, transfers, isFeePayerTx = false) {
121
+ const { amount, currency, machineTokenEnabled, recipient } = context;
122
+ const machineRoute = machineTokenEnabled
123
+ ? MachineTokenCharge.matchRoute({
124
+ calls,
125
+ chainId: context.chainId ?? context.client.chain?.id,
126
+ currency,
127
+ transfers,
128
+ })
129
+ : undefined;
130
+ const matchedCalls = machineRoute?.transfers ??
131
+ assertTransferCalls(calls, {
132
+ currency,
133
+ exactCount: isFeePayerTx,
134
+ transfers,
135
+ });
136
+ assertChallengeBoundCallMemo(matchedCalls, {
137
+ challengeId: context.challenge.id,
138
+ realm: context.challenge.realm,
139
+ });
140
+ if (machineRoute)
141
+ return {
142
+ settlementSenders: [machineRoute.settlementSender],
143
+ fundingCurrency: machineRoute.calls[0].to,
144
+ };
145
+ try {
146
+ const fundingCurrency = FeePayer.validateCalls(calls, { amount, currency, recipient }, { currency, expectedTransfers: transfers });
147
+ return { settlementSenders: [], fundingCurrency };
148
+ }
149
+ catch (error) {
150
+ if (isFeePayerTx)
151
+ throw error;
152
+ // Unrecognized routes remain valid for unsponsored payments, without funding metadata.
153
+ return { settlementSenders: [], fundingCurrency: undefined };
154
+ }
155
+ }
119
156
  async function validateHashCredential(credential, payload, context) {
120
157
  const { amount, chainId, challenge, client, currency, methodDetails, recipient, settlementSenders, supportedModes, } = context;
121
158
  if (supportedModes && !supportedModes.includes('push'))
@@ -141,7 +178,7 @@ export function charge(parameters = {}) {
141
178
  challengeId: challenge.id,
142
179
  realm: challenge.realm,
143
180
  });
144
- return { receipt: toReceipt(receipt), sender, transfers };
181
+ return { receipt, sender, transfers };
145
182
  }
146
183
  async function validateProofCredential(credential, payload, context) {
147
184
  const { chainId, challenge, client, isZeroAmount } = context;
@@ -181,7 +218,7 @@ export function charge(parameters = {}) {
181
218
  return { sender: source.address };
182
219
  }
183
220
  async function validateTransactionCredential(credential, payload, request, context) {
184
- const { machineTokenEnabled, amount, chainId, challenge, client, currency, methodDetails, recipient, requestAllowsFeePayer, supportedModes, } = context;
221
+ const { amount, chainId, challenge, client, methodDetails, recipient, requestAllowsFeePayer, supportedModes, } = context;
185
222
  if (supportedModes && !supportedModes.includes('pull'))
186
223
  throw new MismatchError('Transaction credentials are not supported for this challenge.', {});
187
224
  const serializedTransaction = payload.signature;
@@ -190,40 +227,30 @@ export function charge(parameters = {}) {
190
227
  const transaction = Transaction.deserialize(serializedTransaction);
191
228
  if (!transaction.signature || !transaction.from)
192
229
  throw new MismatchError('Transaction must be signed by the sender before fee payer co-signing.', {});
230
+ if (credential.source) {
231
+ const source = Proof.parsePkhSource(credential.source);
232
+ const resolvedChainId = challenge.request.methodDetails?.chainId ?? chainId ?? client.chain?.id;
233
+ if (!source || source.chainId !== resolvedChainId)
234
+ throw new MismatchError('Transaction credential source is invalid.', {});
235
+ if (!TempoAddress.isEqual(source.address, transaction.from))
236
+ throw new MismatchError('Transaction credential source does not match the sender.', {});
237
+ }
193
238
  const isFeePayerTx = methodDetails?.feePayer === true &&
194
239
  requestAllowsFeePayer &&
195
240
  !!(Account.is(request.feePayer) ? request.feePayer : feePayer || remoteFeePayer);
196
241
  const transfers = getExpectedTransfers({ amount, methodDetails, recipient });
197
- const machineTokenRoute = machineTokenEnabled
198
- ? MachineTokenCharge.matchRoute({
199
- calls: transaction.calls ?? [],
200
- chainId: chainId ?? client.chain?.id,
201
- currency,
202
- transfers,
203
- })
204
- : undefined;
205
- const matchedCalls = machineTokenRoute?.transfers ??
206
- assertTransferCalls(transaction.calls ?? [], {
207
- currency,
208
- exactCount: isFeePayerTx,
209
- transfers,
210
- });
211
- assertChallengeBoundCallMemo(matchedCalls, {
212
- challengeId: challenge.id,
213
- realm: challenge.realm,
214
- });
242
+ const { settlementSenders, fundingCurrency } = validatePaymentRoute(transaction.calls ?? [], context, transfers, isFeePayerTx);
215
243
  if (isFeePayerTx) {
216
- if (!machineTokenRoute)
217
- FeePayer.validateCalls(transaction.calls, { amount, currency, recipient }, { currency, expectedTransfers: transfers });
218
244
  FeePayer.assertAllowedFeeToken(transaction, context.allowedFeeTokens);
219
245
  }
220
246
  else {
221
247
  await viem_call(client, FeePayer.simulationTransaction(transaction, { feePayer: false }));
222
248
  }
223
249
  return {
250
+ fundingCurrency,
224
251
  isFeePayerTx,
225
252
  serializedTransaction,
226
- settlementSenders: machineTokenRoute ? [machineTokenRoute.settlementSender] : [],
253
+ settlementSenders,
227
254
  transaction,
228
255
  transfers,
229
256
  };
@@ -244,7 +271,7 @@ export function charge(parameters = {}) {
244
271
  return { details: { mode: 'proof', sender }, type: 'proof' };
245
272
  }
246
273
  case 'transaction': {
247
- const { isFeePayerTx, serializedTransaction, settlementSenders, transaction, transfers } = await validateTransactionCredential(credential, context.payload, request, context);
274
+ const { fundingCurrency, isFeePayerTx, serializedTransaction, settlementSenders, transaction, transfers, } = await validateTransactionCredential(credential, context.payload, request, context);
248
275
  return {
249
276
  details: {
250
277
  mode: 'pull',
@@ -252,6 +279,7 @@ export function charge(parameters = {}) {
252
279
  serializedTransaction,
253
280
  transfers,
254
281
  },
282
+ fundingCurrency,
255
283
  isFeePayerTx,
256
284
  serializedTransaction,
257
285
  settlementSenders,
@@ -383,7 +411,19 @@ export function charge(parameters = {}) {
383
411
  reason: 'Transaction hash has already been used',
384
412
  });
385
413
  }
386
- return receipt;
414
+ // Only final hash receipts need this lookup; validation stays receipt-only.
415
+ const transaction = await getTransaction(client, { hash }).catch(() => undefined);
416
+ let fundingCurrency;
417
+ if (transaction && TempoAddress.isEqual(transaction.from, validated.details.sender)) {
418
+ try {
419
+ fundingCurrency = validatePaymentRoute(transaction.calls ??
420
+ (transaction.to ? [{ to: transaction.to, data: transaction.input }] : []), context, validated.details.transfers).fundingCurrency;
421
+ }
422
+ catch {
423
+ // Unrecognized calls do not invalidate an already verified payment.
424
+ }
425
+ }
426
+ return toReceipt(receipt.transactionHash, fundingCurrency);
387
427
  }
388
428
  case 'proof': {
389
429
  if (proofStore && !(await tryClaimProof(proofStore, challenge.id, replayExpires))) {
@@ -391,15 +431,10 @@ export function charge(parameters = {}) {
391
431
  reason: 'Proof credential has already been used',
392
432
  });
393
433
  }
394
- return {
395
- method: 'tempo',
396
- status: 'success',
397
- timestamp: new Date().toISOString(),
398
- reference: challenge.id,
399
- };
434
+ return toReceipt(challenge.id);
400
435
  }
401
436
  case 'transaction': {
402
- const { isFeePayerTx, serializedTransaction, settlementSenders, transaction, transfers } = validated;
437
+ const { fundingCurrency, isFeePayerTx, serializedTransaction, settlementSenders, transaction, transfers, } = validated;
403
438
  // Pre-broadcast dedup: catch exact byte-for-byte replays early.
404
439
  const hash = keccak256(serializedTransaction);
405
440
  if (!(await tryClaimHash(store, hash, replayExpires))) {
@@ -409,6 +444,9 @@ export function charge(parameters = {}) {
409
444
  }
410
445
  let broadcastAttempted = false;
411
446
  let finalHash;
447
+ // Set only after this request claims the final hash, so cleanup never
448
+ // releases a marker that another request holds.
449
+ let claimedFinalHash;
412
450
  let reservation;
413
451
  try {
414
452
  const { allowedFeeTokens } = context;
@@ -475,11 +513,13 @@ export function charge(parameters = {}) {
475
513
  // Broadcast the same canonical envelope so replay keys match the node reference.
476
514
  const serializedTransaction_final = await Transaction.serialize(Transaction.deserialize(completedTransaction.serializedTransaction));
477
515
  finalHash = keccak256(serializedTransaction_final);
478
- if (finalHash.toLowerCase() !== hash.toLowerCase() &&
479
- !(await tryClaimHash(store, finalHash, replayExpires)))
480
- throw new VerificationFailedError({
481
- reason: 'Transaction hash has already been used',
482
- });
516
+ if (finalHash.toLowerCase() !== hash.toLowerCase()) {
517
+ if (!(await tryClaimHash(store, finalHash, replayExpires)))
518
+ throw new VerificationFailedError({
519
+ reason: 'Transaction hash has already been used',
520
+ });
521
+ claimedFinalHash = finalHash;
522
+ }
483
523
  if (isFeePayerTx) {
484
524
  const sponsor = completedTransaction.sponsor;
485
525
  if (!sponsor)
@@ -541,7 +581,7 @@ export function charge(parameters = {}) {
541
581
  throw new VerificationFailedError({
542
582
  reason: 'Broadcast transaction hash does not match the signed transaction',
543
583
  });
544
- return toReceipt(receipt);
584
+ return toReceipt(receipt.transactionHash, fundingCurrency);
545
585
  }
546
586
  // Optimistic path: broadcast without waiting for confirmation
547
587
  // (simulation above already ran). The returned receipt assumes
@@ -558,19 +598,14 @@ export function charge(parameters = {}) {
558
598
  throw new VerificationFailedError({
559
599
  reason: 'Sponsor budget reservation ownership was lost after broadcast',
560
600
  });
561
- return {
562
- method: 'tempo',
563
- status: 'success',
564
- timestamp: new Date().toISOString(),
565
- reference,
566
- };
601
+ return toReceipt(reference, fundingCurrency);
567
602
  }
568
603
  catch (error) {
569
604
  if (!broadcastAttempted) {
570
605
  if (reservation)
571
606
  await SponsorBudget.release(sponsorBudgetStore, reservation);
572
- if (finalHash && finalHash.toLowerCase() !== hash.toLowerCase())
573
- await releaseHashUse(store, finalHash);
607
+ if (claimedFinalHash)
608
+ await releaseHashUse(store, claimedFinalHash);
574
609
  await releaseHashUse(store, hash);
575
610
  }
576
611
  throw error;
@@ -694,6 +729,8 @@ function decodeTransferCall(call, currency) {
694
729
  return null;
695
730
  }
696
731
  async function assertTransferLogs(receipt, parameters) {
732
+ if (receipt.status !== 'success')
733
+ throw new Error(`Transaction reverted: ${receipt.transactionHash}`);
697
734
  const logs = getTransferLogEffects(receipt);
698
735
  const used = new Set();
699
736
  const matched = [];
@@ -896,16 +933,13 @@ async function isActiveAccessKey(client, parameters) {
896
933
  }
897
934
  }
898
935
  /** @internal */
899
- function toReceipt(receipt) {
900
- const { status, transactionHash } = receipt;
901
- if (status !== 'success') {
902
- throw new Error(`Transaction reverted: ${transactionHash}`);
903
- }
936
+ function toReceipt(reference, fundingCurrency) {
904
937
  return {
938
+ ...(fundingCurrency ? { fundingCurrency } : {}),
905
939
  method: 'tempo',
906
940
  status: 'success',
907
941
  timestamp: new Date().toISOString(),
908
- reference: transactionHash,
942
+ reference,
909
943
  };
910
944
  }
911
945
  function assertChallengeBoundMemo(matchedLogs, parameters) {