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
@@ -33,7 +33,9 @@ import * as SponsorBudget from './SponsorBudget.js';
33
33
  * ```
34
34
  */
35
35
  export function charge(parameters = {}) {
36
- const { machineTokenEnabled, amount, currency = defaults.resolveCurrency(parameters), decimals = defaults.decimals, description, externalId, feeToken: configuredFeeToken, feePayerPolicy, html, relay, sponsorBudget, splits, supportedModes, validateSender, waitForConfirmation = true, } = parameters;
36
+ const { machineTokenEnabled, allowedFeeTokens: configuredAllowedFeeTokens, amount, currency = defaults.resolveCurrency(parameters), decimals = defaults.decimals, description, externalId, feeToken: configuredFeeToken, feePayerPolicy, html, relay, sponsorBudget, splits, supportedModes, validateSender, waitForConfirmation = true, } = parameters;
37
+ if (configuredAllowedFeeTokens?.length === 0)
38
+ throw new Error('`allowedFeeTokens` must contain at least one token.');
37
39
  const storeKeyPrefix = parameters.storeKeyPrefix ?? '';
38
40
  const rawStore = (parameters.store ?? Store.memory());
39
41
  const store = Store.from(rawStore, { keyPrefix: storeKeyPrefix });
@@ -92,7 +94,12 @@ export function charge(parameters = {}) {
92
94
  Expires.assert(challenge.expires, challenge.id);
93
95
  if (isZeroAmount && payload.type !== 'proof')
94
96
  throw new MismatchError('Zero-amount challenges require a proof credential.', {});
97
+ // Request-level local sponsorship takes precedence over the hosted provider.
98
+ const localFeePayer = Account.is(request.feePayer) ? request.feePayer : feePayer;
99
+ const allowedFeeTokens = configuredAllowedFeeTokens ??
100
+ (localFeePayer || !remoteFeePayer ? FeePayer.defaultAllowedFeeTokens(chainId) : undefined);
95
101
  return {
102
+ allowedFeeTokens,
96
103
  amount,
97
104
  machineTokenEnabled,
98
105
  chainId,
@@ -208,7 +215,7 @@ export function charge(parameters = {}) {
208
215
  if (isFeePayerTx) {
209
216
  if (!machineTokenRoute)
210
217
  FeePayer.validateCalls(transaction.calls, { amount, currency, recipient }, { currency, expectedTransfers: transfers });
211
- FeePayer.assertAllowedFeeToken(transaction, FeePayer.defaultAllowedFeeTokens(chainId));
218
+ FeePayer.assertAllowedFeeToken(transaction, context.allowedFeeTokens);
212
219
  }
213
220
  else {
214
221
  await viem_call(client, FeePayer.simulationTransaction(transaction, { feePayer: false }));
@@ -305,6 +312,8 @@ export function charge(parameters = {}) {
305
312
  const chainId = await (async () => {
306
313
  if (request.chainId)
307
314
  return request.chainId;
315
+ if (parameters.chainId !== undefined)
316
+ return parameters.chainId;
308
317
  if (parameters.testnet)
309
318
  return defaults.chainId.testnet;
310
319
  return (await getClient({})).chain?.id;
@@ -402,12 +411,10 @@ export function charge(parameters = {}) {
402
411
  let finalHash;
403
412
  let reservation;
404
413
  try {
405
- const allowedFeeTokens = FeePayer.defaultAllowedFeeTokens(chainId);
406
- if (isFeePayerTx)
407
- FeePayer.assertAllowedFeeToken(transaction, allowedFeeTokens);
408
- const selectableFeeTokens = allowedFeeTokens;
414
+ const { allowedFeeTokens } = context;
409
415
  const completedTransaction = await (async () => {
410
416
  if (feePayerAccount && methodDetails?.feePayer !== false) {
417
+ const selectableFeeTokens = allowedFeeTokens ?? FeePayer.defaultAllowedFeeTokens(chainId);
411
418
  const completed = await FeePayer.preflightSponsorship({
412
419
  transaction,
413
420
  simulate: (request) => viem_call(client, request),
@@ -422,7 +429,7 @@ export function charge(parameters = {}) {
422
429
  }));
423
430
  const sponsored = FeePayer.prepareSponsoredTransaction({
424
431
  account: feePayerAccount,
425
- allowedFeeTokens,
432
+ allowedFeeTokens: selectableFeeTokens,
426
433
  challengeExpires: expires,
427
434
  chainId: chainId ?? client.chain.id,
428
435
  details: { amount, currency, recipient },
@@ -901,34 +908,29 @@ function toReceipt(receipt) {
901
908
  reference: transactionHash,
902
909
  };
903
910
  }
904
- /**
905
- * Asserts that at least one of the matched payment logs carries a
906
- * challenge-bound memo nonce (keccak256(challengeId)[0..6] in bytes 25–31).
907
- * Only checks logs that were matched by `assertTransferLogs`, not the
908
- * entire receipt — preventing unrelated dust transfers from satisfying
909
- * the binding.
910
- * @internal
911
- */
912
911
  function assertChallengeBoundMemo(matchedLogs, parameters) {
913
- const bound = matchedLogs.some((log) => {
914
- if (log.kind !== 'memo')
915
- return false;
916
- if (!Attribution.verifyServer(log.args.memo, parameters.realm))
917
- return false;
918
- return Attribution.verifyChallengeBinding(log.args.memo, parameters.challengeId);
919
- });
920
- if (!bound)
921
- throw new MismatchError('Payment verification failed: memo is not bound to this challenge.', {});
912
+ assertChallengeBoundMemos(matchedLogs.map((log) => (log.kind === 'memo' ? log.args.memo : undefined)), parameters);
922
913
  }
923
914
  function assertChallengeBoundCallMemo(matchedCalls, parameters) {
924
- const bound = matchedCalls.some((call) => {
925
- if (!call.memo)
926
- return false;
927
- const memo = call.memo;
928
- if (!Attribution.verifyServer(memo, parameters.realm))
929
- return false;
930
- return Attribution.verifyChallengeBinding(memo, parameters.challengeId);
931
- });
915
+ assertChallengeBoundMemos(matchedCalls.map((call) => call.memo), parameters);
916
+ }
917
+ /**
918
+ * Requires a challenge-bound attribution memo and rejects conflicting attribution
919
+ * on matched payment transfers. Otherwise, a split payment could authorize another
920
+ * challenge after the first challenge's replay marker expires. Unmatched transfers
921
+ * cannot establish the binding; plain transfers and non-MPP memos remain allowed.
922
+ * @internal
923
+ */
924
+ function assertChallengeBoundMemos(memos, parameters) {
925
+ let bound = false;
926
+ for (const memo of memos) {
927
+ if (!memo || !Attribution.isMppMemo(memo))
928
+ continue;
929
+ if (!Attribution.verifyServer(memo, parameters.realm) ||
930
+ !Attribution.verifyChallengeBinding(memo, parameters.challengeId))
931
+ throw new MismatchError('Payment verification failed: memo is not bound to this challenge.', {});
932
+ bound = true;
933
+ }
932
934
  if (!bound)
933
935
  throw new MismatchError('Payment verification failed: memo is not bound to this challenge.', {});
934
936
  }