mppx 0.12.0 → 0.13.1

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 (108) hide show
  1. package/CHANGELOG.md +55 -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/utils.d.ts +11 -1
  11. package/dist/cli/utils.js +15 -2
  12. package/dist/cli/validate/challenge.d.ts +2 -0
  13. package/dist/cli/validate/challenge.js +19 -4
  14. package/dist/cli/validate/discovery.d.ts +1 -0
  15. package/dist/cli/validate/discovery.js +10 -1
  16. package/dist/cli/validate/payment.js +34 -12
  17. package/dist/client/Methods.d.ts +0 -2
  18. package/dist/client/Methods.js +0 -2
  19. package/dist/client/Mppx.js +1 -0
  20. package/dist/client/index.d.ts +1 -1
  21. package/dist/client/index.js +1 -1
  22. package/dist/client/internal/Fetch.d.ts +2 -0
  23. package/dist/client/internal/Fetch.js +8 -2
  24. package/dist/client/internal/MethodChallenge.d.ts +1 -0
  25. package/dist/evm/Types.js +1 -1
  26. package/dist/evm/server/Charge.js +5 -0
  27. package/dist/integrations/x402/Evm.js +4 -3
  28. package/dist/internal/HeaderCodec.d.ts +2 -2
  29. package/dist/internal/version.d.ts +2 -2
  30. package/dist/internal/version.js +1 -1
  31. package/dist/mcp/client/McpClient.js +4 -7
  32. package/dist/middlewares/elysia.d.ts +11 -8
  33. package/dist/middlewares/elysia.js +67 -14
  34. package/dist/middlewares/express.js +13 -6
  35. package/dist/middlewares/hono.js +4 -4
  36. package/dist/middlewares/nextjs.js +9 -5
  37. package/dist/proxy/internal/Headers.js +65 -0
  38. package/dist/proxy/services/stripe.js +1 -0
  39. package/dist/server/Mppx.d.ts +13 -1
  40. package/dist/server/Mppx.js +85 -44
  41. package/dist/server/NodeListener.js +71 -6
  42. package/dist/server/Request.d.ts +11 -1
  43. package/dist/server/Request.js +132 -13
  44. package/dist/server/Transport.d.ts +14 -1
  45. package/dist/server/Transport.js +1 -0
  46. package/dist/stripe/server/Methods.d.ts +10 -3
  47. package/dist/stripe/server/Methods.js +17 -2
  48. package/dist/stripe/server/internal/html.gen.js +1 -1
  49. package/dist/tempo/Methods.d.ts +1 -0
  50. package/dist/tempo/Methods.js +1 -0
  51. package/dist/tempo/client/Charge.js +17 -13
  52. package/dist/tempo/client/Methods.d.ts +1 -5
  53. package/dist/tempo/client/Methods.js +10 -5
  54. package/dist/tempo/client/ResolveAccount.d.ts +3 -0
  55. package/dist/tempo/client/index.d.ts +0 -3
  56. package/dist/tempo/client/index.js +0 -2
  57. package/dist/tempo/internal/fee-payer.d.ts +6 -3
  58. package/dist/tempo/internal/fee-payer.js +23 -6
  59. package/dist/tempo/internal/machine-token-charge.d.ts +23 -0
  60. package/dist/tempo/internal/machine-token-charge.js +2 -1
  61. package/dist/tempo/legacy/index.d.ts +1 -3
  62. package/dist/tempo/legacy/index.js +1 -3
  63. package/dist/tempo/server/Charge.js +75 -46
  64. package/dist/tempo/server/Methods.d.ts +55 -10
  65. package/dist/tempo/server/Subscription.d.ts +1 -1
  66. package/dist/tempo/server/Subscription.js +31 -4
  67. package/dist/tempo/server/internal/html.gen.js +1 -1
  68. package/dist/tempo/server/internal/transport.js +28 -11
  69. package/dist/tempo/session/client/ChannelOps.js +5 -0
  70. package/dist/tempo/session/client/Session.d.ts +1 -0
  71. package/dist/tempo/session/client/Session.js +7 -4
  72. package/dist/tempo/session/client/SessionManager.js +3 -0
  73. package/dist/tempo/session/client/Transports.d.ts +8 -1
  74. package/dist/tempo/session/client/Transports.js +12 -3
  75. package/dist/tempo/session/precompile/Chain.js +16 -1
  76. package/dist/tempo/session/precompile/Protocol.d.ts +2 -0
  77. package/dist/tempo/session/precompile/Voucher.d.ts +16 -0
  78. package/dist/tempo/session/precompile/Voucher.js +48 -0
  79. package/dist/tempo/session/server/ChannelStore.d.ts +20 -2
  80. package/dist/tempo/session/server/ChannelStore.js +25 -4
  81. package/dist/tempo/session/server/CredentialVerification.d.ts +4 -1
  82. package/dist/tempo/session/server/CredentialVerification.js +34 -6
  83. package/dist/tempo/session/server/MeteredStream.d.ts +3 -2
  84. package/dist/tempo/session/server/MeteredStream.js +85 -29
  85. package/dist/tempo/session/server/Session.d.ts +2 -1
  86. package/dist/tempo/session/server/Session.js +2 -1
  87. package/dist/tempo/session/server/Settlement.d.ts +21 -0
  88. package/dist/tempo/session/server/Settlement.js +92 -17
  89. package/dist/tempo/session/server/Sse.d.ts +3 -1
  90. package/dist/tempo/session/server/Sse.js +105 -32
  91. package/dist/tempo/session/server/Transports.d.ts +24 -19
  92. package/dist/tempo/session/server/Transports.js +169 -58
  93. package/dist/tempo/session/server/Ws.d.ts +5 -1
  94. package/dist/tempo/session/server/Ws.js +75 -20
  95. package/dist/tempo/subscription/Store.d.ts +2 -0
  96. package/dist/tempo/subscription/Store.js +3 -0
  97. package/dist/validation/core.js +2 -1
  98. package/dist/x402/server/EvmCharge.d.ts +3 -1
  99. package/dist/x402/server/EvmCharge.js +14 -1
  100. package/package.json +6 -6
  101. package/dist/tempo/legacy/client/ChannelOps.d.ts +0 -69
  102. package/dist/tempo/legacy/client/ChannelOps.js +0 -168
  103. package/dist/tempo/legacy/client/Session.d.ts +0 -185
  104. package/dist/tempo/legacy/client/Session.js +0 -279
  105. package/dist/tempo/legacy/client/SessionManager.d.ts +0 -100
  106. package/dist/tempo/legacy/client/SessionManager.js +0 -706
  107. package/dist/tempo/legacy/client/index.d.ts +0 -7
  108. 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))) {
@@ -541,7 +576,7 @@ export function charge(parameters = {}) {
541
576
  throw new VerificationFailedError({
542
577
  reason: 'Broadcast transaction hash does not match the signed transaction',
543
578
  });
544
- return toReceipt(receipt);
579
+ return toReceipt(receipt.transactionHash, fundingCurrency);
545
580
  }
546
581
  // Optimistic path: broadcast without waiting for confirmation
547
582
  // (simulation above already ran). The returned receipt assumes
@@ -558,12 +593,7 @@ export function charge(parameters = {}) {
558
593
  throw new VerificationFailedError({
559
594
  reason: 'Sponsor budget reservation ownership was lost after broadcast',
560
595
  });
561
- return {
562
- method: 'tempo',
563
- status: 'success',
564
- timestamp: new Date().toISOString(),
565
- reference,
566
- };
596
+ return toReceipt(reference, fundingCurrency);
567
597
  }
568
598
  catch (error) {
569
599
  if (!broadcastAttempted) {
@@ -694,6 +724,8 @@ function decodeTransferCall(call, currency) {
694
724
  return null;
695
725
  }
696
726
  async function assertTransferLogs(receipt, parameters) {
727
+ if (receipt.status !== 'success')
728
+ throw new Error(`Transaction reverted: ${receipt.transactionHash}`);
697
729
  const logs = getTransferLogEffects(receipt);
698
730
  const used = new Set();
699
731
  const matched = [];
@@ -896,16 +928,13 @@ async function isActiveAccessKey(client, parameters) {
896
928
  }
897
929
  }
898
930
  /** @internal */
899
- function toReceipt(receipt) {
900
- const { status, transactionHash } = receipt;
901
- if (status !== 'success') {
902
- throw new Error(`Transaction reverted: ${transactionHash}`);
903
- }
931
+ function toReceipt(reference, fundingCurrency) {
904
932
  return {
933
+ ...(fundingCurrency ? { fundingCurrency } : {}),
905
934
  method: 'tempo',
906
935
  status: 'success',
907
936
  timestamp: new Date().toISOString(),
908
- reference: transactionHash,
937
+ reference,
909
938
  };
910
939
  }
911
940
  function assertChallengeBoundMemo(matchedLogs, parameters) {