mppx 0.6.25 → 0.6.26

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 (58) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/Method.d.ts +6 -4
  3. package/dist/Method.d.ts.map +1 -1
  4. package/dist/Method.js +2 -1
  5. package/dist/Method.js.map +1 -1
  6. package/dist/middlewares/internal/mppx.d.ts +3 -2
  7. package/dist/middlewares/internal/mppx.d.ts.map +1 -1
  8. package/dist/middlewares/internal/mppx.js +1 -0
  9. package/dist/middlewares/internal/mppx.js.map +1 -1
  10. package/dist/server/Mppx.d.ts +8 -3
  11. package/dist/server/Mppx.d.ts.map +1 -1
  12. package/dist/server/Mppx.js +4 -1
  13. package/dist/server/Mppx.js.map +1 -1
  14. package/dist/stripe/server/Charge.d.ts +1 -1
  15. package/dist/stripe/server/Charge.d.ts.map +1 -1
  16. package/dist/stripe/server/Methods.d.ts +1 -1
  17. package/dist/stripe/server/Methods.d.ts.map +1 -1
  18. package/dist/tempo/Methods.d.ts +3 -2
  19. package/dist/tempo/Methods.d.ts.map +1 -1
  20. package/dist/tempo/Methods.js +13 -4
  21. package/dist/tempo/Methods.js.map +1 -1
  22. package/dist/tempo/client/Subscription.d.ts +3 -2
  23. package/dist/tempo/client/Subscription.d.ts.map +1 -1
  24. package/dist/tempo/server/Charge.d.ts +1 -1
  25. package/dist/tempo/server/Charge.d.ts.map +1 -1
  26. package/dist/tempo/server/Methods.d.ts +2 -2
  27. package/dist/tempo/server/Methods.d.ts.map +1 -1
  28. package/dist/tempo/server/Session.d.ts +1 -1
  29. package/dist/tempo/server/Session.d.ts.map +1 -1
  30. package/dist/tempo/server/Subscription.d.ts +28 -12
  31. package/dist/tempo/server/Subscription.d.ts.map +1 -1
  32. package/dist/tempo/server/Subscription.js +82 -30
  33. package/dist/tempo/server/Subscription.js.map +1 -1
  34. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  35. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  36. package/dist/tempo/server/internal/html.gen.js +1 -1
  37. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  38. package/dist/tempo/subscription/KeyAuthorization.d.ts +3 -14
  39. package/dist/tempo/subscription/KeyAuthorization.d.ts.map +1 -1
  40. package/dist/tempo/subscription/KeyAuthorization.js +11 -20
  41. package/dist/tempo/subscription/KeyAuthorization.js.map +1 -1
  42. package/dist/tempo/subscription/Types.d.ts +2 -2
  43. package/dist/tempo/subscription/Types.d.ts.map +1 -1
  44. package/package.json +1 -1
  45. package/src/Method.ts +21 -5
  46. package/src/middlewares/internal/mppx.test.ts +24 -0
  47. package/src/middlewares/internal/mppx.ts +6 -2
  48. package/src/server/Mppx.ts +17 -4
  49. package/src/tempo/Methods.test.ts +17 -0
  50. package/src/tempo/Methods.ts +12 -4
  51. package/src/tempo/client/Subscription.test.ts +5 -7
  52. package/src/tempo/server/Subscription.test.ts +1 -4
  53. package/src/tempo/server/Subscription.ts +156 -67
  54. package/src/tempo/server/internal/html/package.json +1 -1
  55. package/src/tempo/server/internal/html.gen.ts +1 -1
  56. package/src/tempo/subscription/KeyAuthorization.test.ts +13 -4
  57. package/src/tempo/subscription/KeyAuthorization.ts +11 -20
  58. package/src/tempo/subscription/Types.ts +2 -2
@@ -41,6 +41,7 @@ import type {
41
41
  } from '../subscription/Types.js'
42
42
 
43
43
  type SubscriptionRequest = ReturnType<typeof Methods.subscription.schema.request.parse>
44
+ type RenewalHandler = NonNullable<subscription.Parameters['renew']>
44
45
 
45
46
  /**
46
47
  * Creates a Tempo subscription method for recurring TIP-20 token payments.
@@ -67,22 +68,25 @@ export function subscription<const parameters extends subscription.Parameters>(
67
68
  periodCount,
68
69
  periodUnit,
69
70
  subscriptionExpires,
70
- waitForConfirmation = true,
71
71
  } = parameters
72
72
 
73
- const store = SubscriptionStore.fromStore(rawStore, {
74
- activationTimeoutMs: parameters.activationTimeoutMs,
75
- renewalTimeoutMs: parameters.renewalTimeoutMs,
76
- })
77
- const { feePayer, recipient } = Account.resolve(parameters)
78
- const getClient = ClientResolver.getResolver({
79
- chain: tempo_chain,
80
- getClient: parameters.getClient,
81
- rpcUrl: defaults.rpcUrl,
73
+ const { recipient } = Account.resolve(parameters)
74
+ const context = createContext(parameters, {
75
+ store: rawStore,
76
+ options: {
77
+ activationTimeoutMs: parameters.activationTimeoutMs,
78
+ renewalTimeoutMs: parameters.renewalTimeoutMs,
79
+ },
82
80
  })
81
+ const { feePayer, feePayerPolicy, getClient, store, waitForConfirmation } = context
83
82
 
84
83
  type Defaults = subscription.DeriveDefaults<parameters>
85
- return Method.toServer<typeof Methods.subscription, Defaults>(Methods.subscription, {
84
+ const method = Method.toServer<
85
+ typeof Methods.subscription,
86
+ Defaults,
87
+ undefined,
88
+ subscription.Extensions
89
+ >(Methods.subscription, {
86
90
  defaults: {
87
91
  amount,
88
92
  currency,
@@ -94,6 +98,19 @@ export function subscription<const parameters extends subscription.Parameters>(
94
98
  recipient,
95
99
  subscriptionExpires,
96
100
  } as unknown as Defaults,
101
+ extensions: {
102
+ renew: (parameters) =>
103
+ renewWithContext({
104
+ context: {
105
+ ...context,
106
+ feePayerPolicy: parameters.feePayerPolicy
107
+ ? resolveFeePayerPolicy(context.feePayerPolicy, parameters.feePayerPolicy)
108
+ : context.feePayerPolicy,
109
+ waitForConfirmation: parameters.waitForConfirmation ?? context.waitForConfirmation,
110
+ },
111
+ subscriptionId: parameters.subscriptionId,
112
+ }),
113
+ },
97
114
 
98
115
  async authorize({ input, request }) {
99
116
  const resolved = await parameters.resolve({ input, request })
@@ -107,7 +124,7 @@ export function subscription<const parameters extends subscription.Parameters>(
107
124
  if (periodIndex > subscription.lastChargedPeriod) {
108
125
  const renew = resolveRenewalHandler({
109
126
  feePayer,
110
- feePayerPolicy: parameters.feePayerPolicy,
127
+ feePayerPolicy,
111
128
  getClient,
112
129
  parameters,
113
130
  store,
@@ -226,7 +243,9 @@ export function subscription<const parameters extends subscription.Parameters>(
226
243
  (declaredSource.chainId !== verified.source.chainId ||
227
244
  !isAddressEqual(declaredSource.address, verified.source.address))
228
245
  ) {
229
- throw new VerificationFailedError({ reason: 'credential source does not match signature' })
246
+ throw new VerificationFailedError({
247
+ reason: 'credential source does not match signature',
248
+ })
230
249
  }
231
250
 
232
251
  const activation = await store.activate({
@@ -240,7 +259,7 @@ export function subscription<const parameters extends subscription.Parameters>(
240
259
  auto: {
241
260
  challengeId: credential.challenge.id,
242
261
  feePayer,
243
- feePayerPolicy: parameters.feePayerPolicy,
262
+ feePayerPolicy,
244
263
  getClient,
245
264
  keyAuthorization: (credential.payload as SubscriptionCredentialPayload).signature,
246
265
  realm: credential.challenge.realm,
@@ -290,6 +309,19 @@ export function subscription<const parameters extends subscription.Parameters>(
290
309
  return activation.result.receipt
291
310
  },
292
311
  })
312
+ return method
313
+ }
314
+
315
+ // Access-key provisioning can cost around 4M gas.
316
+ const defaultFeePayerPolicy = {
317
+ maxGas: 5_000_000n,
318
+ maxTotalFee: 200_000_000_000_000_000n,
319
+ } satisfies Partial<FeePayer.Policy>
320
+
321
+ function resolveFeePayerPolicy(
322
+ ...policies: (Partial<FeePayer.Policy> | undefined)[]
323
+ ): Partial<FeePayer.Policy> {
324
+ return Object.assign({}, defaultFeePayerPolicy, ...policies)
293
325
  }
294
326
 
295
327
  function requestFromCaptured(capturedRequest: Method.CapturedRequest | undefined): Request {
@@ -702,25 +734,11 @@ function resolveRenewalHandler(parameters: {
702
734
  feePayer?: ReturnType<typeof Account.resolve>['feePayer'] | undefined
703
735
  feePayerPolicy?: Partial<FeePayer.Policy> | undefined
704
736
  getClient: (parameters: { chainId?: number | undefined }) => MaybePromise<ViemClient>
705
- parameters: {
706
- renew?:
707
- | ((parameters: {
708
- inFlightReference: string
709
- periodIndex: number
710
- subscription: SubscriptionRecord
711
- }) => Promise<subscription.RenewalResult>)
712
- | undefined
713
- }
737
+ parameters: Pick<createContext.Parameters, 'renew'>
714
738
  store: SubscriptionStore.SubscriptionStore
715
739
  subscription: SubscriptionRecord
716
740
  waitForConfirmation: boolean
717
- }):
718
- | ((parameters: {
719
- inFlightReference: string
720
- periodIndex: number
721
- subscription: SubscriptionRecord
722
- }) => Promise<subscription.RenewalResult>)
723
- | undefined {
741
+ }): RenewalHandler | undefined {
724
742
  const {
725
743
  feePayer,
726
744
  feePayerPolicy,
@@ -829,7 +847,7 @@ async function submitSubscriptionPayment(parameters: {
829
847
  // (without `feePayer: true`) so viem returns the chain's full
830
848
  // proof-inclusive gas estimate. With `feePayer: true` viem sets a
831
849
  // dummy sig + null feePayerSignature, dropping signature and key
832
- // authorization verification costs see chainConfig.js FIXME. We add
850
+ // authorization verification costs -- see chainConfig.js FIXME. We add
833
851
  // a small buffer for fee-payer overhead, then flip `feePayer = true`
834
852
  // and re-sign with the fee-payer-sponsored envelope.
835
853
  const prepared = await prepareTransactionRequest(client, {
@@ -895,31 +913,41 @@ function createSubscriptionId() {
895
913
  * Returns the renewal result if the subscription was overdue, or `null` if already current.
896
914
  */
897
915
  export async function renew(parameters: renew.Parameters): Promise<renew.Result | null> {
898
- const { store: rawStore, waitForConfirmation = true } = parameters
899
- const store = SubscriptionStore.fromStore(rawStore, {
900
- renewalTimeoutMs: parameters.renewalTimeoutMs,
916
+ const context = createContext(parameters, {
917
+ store: parameters.store,
918
+ options: {
919
+ renewalTimeoutMs: parameters.renewalTimeoutMs,
920
+ },
901
921
  })
902
- const getClient = ClientResolver.getResolver({
903
- chain: tempo_chain,
904
- getClient: parameters.getClient,
905
- rpcUrl: defaults.rpcUrl,
922
+
923
+ return renewWithContext({
924
+ context,
925
+ subscriptionId: parameters.subscriptionId,
906
926
  })
927
+ }
907
928
 
908
- const record = await store.get(parameters.subscriptionId)
929
+ async function renewWithContext(parameters: {
930
+ context: createContext.Return
931
+ subscriptionId: string
932
+ }): Promise<renew.Result | null> {
933
+ const { context, subscriptionId } = parameters
934
+ const record = await context.store.get(subscriptionId)
909
935
  if (!record) return null
910
936
  if (!isActive(record)) return null
911
- const active = await store.getByKey(record.lookupKey)
937
+ const active = await context.store.getByKey(record.lookupKey)
912
938
  if (active?.subscriptionId !== record.subscriptionId) return null
913
939
 
914
940
  const periodIndex = getPeriodIndex(record)
915
941
  if (periodIndex <= record.lastChargedPeriod) return null
916
942
 
917
943
  const renew = resolveRenewalHandler({
918
- getClient,
919
- parameters,
920
- store,
944
+ feePayer: context.feePayer,
945
+ feePayerPolicy: context.feePayerPolicy,
946
+ getClient: context.getClient,
947
+ parameters: context.parameters,
948
+ store: context.store,
921
949
  subscription: record,
922
- waitForConfirmation,
950
+ waitForConfirmation: context.waitForConfirmation,
923
951
  })
924
952
  if (!renew) return null
925
953
 
@@ -927,35 +955,41 @@ export async function renew(parameters: renew.Parameters): Promise<renew.Result
927
955
  expectedLookupKey: record.lookupKey,
928
956
  periodIndex,
929
957
  renew,
930
- store,
958
+ store: context.store,
931
959
  subscription: record,
932
960
  })
933
- return renewal?.status === 'renewed' ? renewal.result : null
961
+ if (renewal?.status !== 'renewed') return null
962
+
963
+ await context.parameters.hooks?.renewed?.({
964
+ periodIndex,
965
+ receipt: renewal.result.receipt,
966
+ subscription: renewal.result.subscription,
967
+ })
968
+ return renewal.result
934
969
  }
935
970
 
936
971
  export declare namespace renew {
937
972
  /** Parameters for renewing an overdue subscription outside the request path. */
938
- type Parameters = {
939
- /** The subscription to renew. */
940
- subscriptionId: string
941
- /** Billing callback — same signature as the `renew` hook on {@link subscription}. */
942
- renew?:
943
- | ((parameters: {
944
- /** Stable idempotency/reconciliation reference persisted before the renewal hook runs. */
945
- inFlightReference: string
946
- periodIndex: number
947
- subscription: SubscriptionRecord
948
- }) => Promise<subscription.RenewalResult>)
949
- | undefined
950
- /** Store containing subscription records. */
951
- store: Store.AtomicStore<Record<string, unknown>>
952
- /**
953
- * Milliseconds before an in-flight renewal lock can be replaced.
954
- * Keeps concurrent renewal safe while allowing recovery from abandoned attempts.
955
- */
956
- renewalTimeoutMs?: number | undefined
957
- waitForConfirmation?: boolean | undefined
958
- } & Client.getResolver.Parameters
973
+ type Parameters = Account.resolve.Parameters &
974
+ Client.getResolver.Parameters & {
975
+ /** The subscription to renew. */
976
+ subscriptionId: string
977
+ /** Billing callback -- same signature as the `renew` hook on {@link subscription}. */
978
+ renew?: subscription.Parameters['renew']
979
+ /** Store containing subscription records. */
980
+ store: Store.AtomicStore<Record<string, unknown>>
981
+ /**
982
+ * Milliseconds before an in-flight renewal lock can be replaced.
983
+ * Keeps concurrent renewal safe while allowing recovery from abandoned attempts.
984
+ */
985
+ renewalTimeoutMs?: number | undefined
986
+ /**
987
+ * Override the fee-payer policy for sponsored subscription payments.
988
+ * Useful when the access key renewal tx requires more gas than the default policy allows.
989
+ */
990
+ feePayerPolicy?: Partial<FeePayer.Policy> | undefined
991
+ waitForConfirmation?: boolean | undefined
992
+ }
959
993
 
960
994
  /** Renewal result returned by {@link renew}. */
961
995
  type Result = subscription.RenewalResult
@@ -977,6 +1011,23 @@ export declare namespace subscription {
977
1011
  subscription: SubscriptionRecord
978
1012
  }
979
1013
 
1014
+ /** Parameters for renewing through a configured `mppx.tempo.subscription` handler. */
1015
+ type RenewParameters = {
1016
+ /** The subscription to renew. */
1017
+ subscriptionId: string
1018
+ /**
1019
+ * Override the fee-payer policy for sponsored subscription payments.
1020
+ * Useful when the access key renewal tx requires more gas than the default policy allows.
1021
+ */
1022
+ feePayerPolicy?: Partial<FeePayer.Policy> | undefined
1023
+ waitForConfirmation?: boolean | undefined
1024
+ }
1025
+
1026
+ /** Handler extensions attached to `mppx.tempo.subscription`. */
1027
+ type Extensions = {
1028
+ renew: (parameters: RenewParameters) => Promise<renew.Result | null>
1029
+ }
1030
+
980
1031
  /** Request defaults supported by the subscription method. */
981
1032
  type Defaults = LooseOmit<
982
1033
  Method.RequestDefaults<typeof Methods.subscription>,
@@ -1071,3 +1122,41 @@ export declare namespace subscription {
1071
1122
  decimals: number
1072
1123
  }
1073
1124
  }
1125
+
1126
+ function createContext<const parameters extends createContext.Parameters>(
1127
+ parameters: parameters,
1128
+ options: createContext.Options,
1129
+ ) {
1130
+ const { feePayer } = Account.resolve(parameters)
1131
+ const store = SubscriptionStore.fromStore(options.store, options.options)
1132
+ const getClient = ClientResolver.getResolver({
1133
+ chain: tempo_chain,
1134
+ getClient: parameters.getClient,
1135
+ rpcUrl: defaults.rpcUrl,
1136
+ })
1137
+ return {
1138
+ feePayer,
1139
+ feePayerPolicy: resolveFeePayerPolicy(parameters.feePayerPolicy),
1140
+ getClient,
1141
+ parameters,
1142
+ store,
1143
+ waitForConfirmation: parameters.waitForConfirmation ?? true,
1144
+ }
1145
+ }
1146
+
1147
+ declare namespace createContext {
1148
+ type Parameters = Account.resolve.Parameters &
1149
+ Client.getResolver.Parameters & {
1150
+ feePayerPolicy?: Partial<FeePayer.Policy> | undefined
1151
+ hooks?: subscription.Parameters['hooks']
1152
+ renew?: subscription.Parameters['renew']
1153
+ waitForConfirmation?: boolean | undefined
1154
+ }
1155
+
1156
+ type Options = {
1157
+ store: Store.AtomicStore<Record<string, unknown>>
1158
+ options?: SubscriptionStore.fromStore.Options | undefined
1159
+ }
1160
+
1161
+ type Return = ReturnType<typeof createContext>
1162
+ }
@@ -3,7 +3,7 @@
3
3
  "private": true,
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "accounts": "0.8.10",
6
+ "accounts": "0.10.2",
7
7
  "mppx": "workspace:*",
8
8
  "viem": "2.47.5"
9
9
  }