@metamask/subscription-controller 9.0.1 → 10.0.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 (52) hide show
  1. package/CHANGELOG.md +52 -1
  2. package/dist/SubscriptionController.d.ts.map +1 -1
  3. package/dist/SubscriptionController.js +9 -1
  4. package/dist/SubscriptionController.js.map +1 -1
  5. package/dist/SubscriptionService-structs.d.ts +150 -36
  6. package/dist/SubscriptionService-structs.d.ts.map +1 -1
  7. package/dist/SubscriptionService-structs.js +46 -12
  8. package/dist/SubscriptionService-structs.js.map +1 -1
  9. package/dist/SubscriptionService.d.ts.map +1 -1
  10. package/dist/SubscriptionService.js +7 -1
  11. package/dist/SubscriptionService.js.map +1 -1
  12. package/dist/constants.d.ts +27 -0
  13. package/dist/constants.d.ts.map +1 -1
  14. package/dist/constants.js +34 -0
  15. package/dist/constants.js.map +1 -1
  16. package/dist/index.d.ts +11 -4
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +6 -3
  19. package/dist/index.js.map +1 -1
  20. package/dist/selectors.d.ts +41 -1
  21. package/dist/selectors.d.ts.map +1 -1
  22. package/dist/selectors.js +65 -0
  23. package/dist/selectors.js.map +1 -1
  24. package/dist/subscription-delegation/SubscriptionDelegationService-method-action-types.d.ts +49 -0
  25. package/dist/subscription-delegation/SubscriptionDelegationService-method-action-types.d.ts.map +1 -0
  26. package/dist/subscription-delegation/SubscriptionDelegationService-method-action-types.js +6 -0
  27. package/dist/subscription-delegation/SubscriptionDelegationService-method-action-types.js.map +1 -0
  28. package/dist/subscription-delegation/SubscriptionDelegationService.d.ts +97 -0
  29. package/dist/subscription-delegation/SubscriptionDelegationService.d.ts.map +1 -0
  30. package/dist/subscription-delegation/SubscriptionDelegationService.js +306 -0
  31. package/dist/subscription-delegation/SubscriptionDelegationService.js.map +1 -0
  32. package/dist/subscription-delegation/amount.d.ts +49 -0
  33. package/dist/subscription-delegation/amount.d.ts.map +1 -0
  34. package/dist/subscription-delegation/amount.js +84 -0
  35. package/dist/subscription-delegation/amount.js.map +1 -0
  36. package/dist/subscription-delegation/caveats.d.ts +40 -0
  37. package/dist/subscription-delegation/caveats.d.ts.map +1 -0
  38. package/dist/subscription-delegation/caveats.js +51 -0
  39. package/dist/subscription-delegation/caveats.js.map +1 -0
  40. package/dist/subscription-delegation/fingerprint.d.ts +53 -0
  41. package/dist/subscription-delegation/fingerprint.d.ts.map +1 -0
  42. package/dist/subscription-delegation/fingerprint.js +109 -0
  43. package/dist/subscription-delegation/fingerprint.js.map +1 -0
  44. package/dist/subscription-delegation/types.d.ts +72 -0
  45. package/dist/subscription-delegation/types.d.ts.map +1 -0
  46. package/dist/subscription-delegation/types.js +10 -0
  47. package/dist/subscription-delegation/types.js.map +1 -0
  48. package/dist/types.d.ts +113 -17
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/types.js +39 -0
  51. package/dist/types.js.map +1 -1
  52. package/package.json +18 -9
@@ -0,0 +1,49 @@
1
+ /**
2
+ * This file is auto generated.
3
+ * Do not edit manually.
4
+ */
5
+ import type { SubscriptionDelegationService } from './SubscriptionDelegationService.js';
6
+ /**
7
+ * Checks whether the Money Account holds enough convertible mUSD value to
8
+ * cover pricing `unitAmount × minBillingCyclesForBalance`.
9
+ *
10
+ * @param request - Payer address and pricing amount fields.
11
+ * @returns Balance comparison in mUSD base units (6 decimals).
12
+ */
13
+ export type SubscriptionDelegationServiceCheckMoneyAccountBalanceAction = {
14
+ type: `SubscriptionDelegationService:checkMoneyAccountBalance`;
15
+ handler: SubscriptionDelegationService['checkMoneyAccountBalance'];
16
+ };
17
+ /**
18
+ * Prepares a cash-subscription delegation and returns its hash.
19
+ *
20
+ * Reuses a stored AUS delegation that matches the semantic fingerprint when
21
+ * one exists (ensuring a CHOMP intent is active for its hash, unless
22
+ * `skipChompInteractions` is true). Reuse classifies period `startDate` as
23
+ * trial-deferred (`> now`) vs immediately redeemable, matching creation.
24
+ * When several records match, the latest period `startDate` is reused
25
+ * so a `forceNew` replacement is preferred over an older equivalent
26
+ * permission.
27
+ * If there is no match, builds, signs, optionally verifies with CHOMP,
28
+ * persists, and optionally registers a new delegation.
29
+ *
30
+ * When `skipChompInteractions` is true (required for alpha), CHOMP verify
31
+ * and intent calls are skipped; the returned hash is computed locally. The
32
+ * default CHOMP-enabled path requires a follow-up chomp-api-service release
33
+ * that accepts `'cash-subscription'` intent metadata.
34
+ *
35
+ * @param request - Authoritative pricing and payer details for the delegation.
36
+ * @param forceNew - Whether to create a replacement instead of reusing a
37
+ * matching stored delegation.
38
+ * @returns The delegation hash (CHOMP-verified unless skipped) and whether it
39
+ * was created or reused.
40
+ */
41
+ export type SubscriptionDelegationServicePrepareDelegationAction = {
42
+ type: `SubscriptionDelegationService:prepareDelegation`;
43
+ handler: SubscriptionDelegationService['prepareDelegation'];
44
+ };
45
+ /**
46
+ * Union of all SubscriptionDelegationService action types.
47
+ */
48
+ export type SubscriptionDelegationServiceMethodActions = SubscriptionDelegationServiceCheckMoneyAccountBalanceAction | SubscriptionDelegationServicePrepareDelegationAction;
49
+ //# sourceMappingURL=SubscriptionDelegationService-method-action-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubscriptionDelegationService-method-action-types.d.ts","sourceRoot":"","sources":["../../src/subscription-delegation/SubscriptionDelegationService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAExF;;;;;;GAMG;AACH,MAAM,MAAM,2DAA2D,GAAG;IACxE,IAAI,EAAE,wDAAwD,CAAC;IAC/D,OAAO,EAAE,6BAA6B,CAAC,0BAA0B,CAAC,CAAC;CACpE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,MAAM,oDAAoD,GAAG;IACjE,IAAI,EAAE,iDAAiD,CAAC;IACxD,OAAO,EAAE,6BAA6B,CAAC,mBAAmB,CAAC,CAAC;CAC7D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,0CAA0C,GAClD,2DAA2D,GAC3D,oDAAoD,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * This file is auto generated.
3
+ * Do not edit manually.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=SubscriptionDelegationService-method-action-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubscriptionDelegationService-method-action-types.js","sourceRoot":"","sources":["../../src/subscription-delegation/SubscriptionDelegationService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { SubscriptionDelegationService } from './SubscriptionDelegationService.js';\n\n/**\n * Checks whether the Money Account holds enough convertible mUSD value to\n * cover pricing `unitAmount × minBillingCyclesForBalance`.\n *\n * @param request - Payer address and pricing amount fields.\n * @returns Balance comparison in mUSD base units (6 decimals).\n */\nexport type SubscriptionDelegationServiceCheckMoneyAccountBalanceAction = {\n type: `SubscriptionDelegationService:checkMoneyAccountBalance`;\n handler: SubscriptionDelegationService['checkMoneyAccountBalance'];\n};\n\n/**\n * Prepares a cash-subscription delegation and returns its hash.\n *\n * Reuses a stored AUS delegation that matches the semantic fingerprint when\n * one exists (ensuring a CHOMP intent is active for its hash, unless\n * `skipChompInteractions` is true). Reuse classifies period `startDate` as\n * trial-deferred (`> now`) vs immediately redeemable, matching creation.\n * When several records match, the latest period `startDate` is reused\n * so a `forceNew` replacement is preferred over an older equivalent\n * permission.\n * If there is no match, builds, signs, optionally verifies with CHOMP,\n * persists, and optionally registers a new delegation.\n *\n * When `skipChompInteractions` is true (required for alpha), CHOMP verify\n * and intent calls are skipped; the returned hash is computed locally. The\n * default CHOMP-enabled path requires a follow-up chomp-api-service release\n * that accepts `'cash-subscription'` intent metadata.\n *\n * @param request - Authoritative pricing and payer details for the delegation.\n * @param forceNew - Whether to create a replacement instead of reusing a\n * matching stored delegation.\n * @returns The delegation hash (CHOMP-verified unless skipped) and whether it\n * was created or reused.\n */\nexport type SubscriptionDelegationServicePrepareDelegationAction = {\n type: `SubscriptionDelegationService:prepareDelegation`;\n handler: SubscriptionDelegationService['prepareDelegation'];\n};\n\n/**\n * Union of all SubscriptionDelegationService action types.\n */\nexport type SubscriptionDelegationServiceMethodActions =\n | SubscriptionDelegationServiceCheckMoneyAccountBalanceAction\n | SubscriptionDelegationServicePrepareDelegationAction;\n"]}
@@ -0,0 +1,97 @@
1
+ import type { AuthenticatedUserStorageServiceCreateDelegationAction, AuthenticatedUserStorageServiceListDelegationsAction } from '@metamask/authenticated-user-storage';
2
+ import type { ChompApiServiceCreateIntentsAction, ChompApiServiceGetIntentsByAddressAction, ChompApiServiceVerifyDelegationAction } from '@metamask/chomp-api-service';
3
+ import type { DelegationControllerSignDelegationAction } from '@metamask/delegation-controller';
4
+ import type { Messenger } from '@metamask/messenger';
5
+ import type { MoneyAccountBalanceServiceFetchBalanceWithFallbackAction } from '@metamask/money-account-balance-service';
6
+ import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller';
7
+ import type { SubscriptionControllerGetPricingAction } from '../SubscriptionController-method-action-types.js';
8
+ import type { SubscriptionDelegationServiceMethodActions } from './SubscriptionDelegationService-method-action-types.js';
9
+ import type { MoneyAccountBalanceCheckRequest, MoneyAccountBalanceCheckResult, PrepareSubscriptionDelegationRequest, PreparedSubscriptionDelegation } from './types.js';
10
+ /**
11
+ * The name of the {@link SubscriptionDelegationService}, used to namespace the
12
+ * service's actions and events.
13
+ */
14
+ export declare const serviceName = "SubscriptionDelegationService";
15
+ /**
16
+ * Actions that {@link SubscriptionDelegationService} exposes to other consumers.
17
+ */
18
+ export type SubscriptionDelegationServiceActions = SubscriptionDelegationServiceMethodActions;
19
+ /**
20
+ * Actions from other messengers that {@link SubscriptionDelegationServiceMessenger} calls.
21
+ */
22
+ type AllowedActions = AuthenticatedUserStorageServiceListDelegationsAction | AuthenticatedUserStorageServiceCreateDelegationAction | ChompApiServiceVerifyDelegationAction | ChompApiServiceCreateIntentsAction | ChompApiServiceGetIntentsByAddressAction | DelegationControllerSignDelegationAction | MoneyAccountBalanceServiceFetchBalanceWithFallbackAction | RemoteFeatureFlagControllerGetStateAction | SubscriptionControllerGetPricingAction;
23
+ /**
24
+ * Events that {@link SubscriptionDelegationService} exposes to other consumers.
25
+ */
26
+ export type SubscriptionDelegationServiceEvents = never;
27
+ type AllowedEvents = never;
28
+ /**
29
+ * The messenger which is restricted to actions and events accessed by
30
+ * {@link SubscriptionDelegationService}.
31
+ */
32
+ export type SubscriptionDelegationServiceMessenger = Messenger<typeof serviceName, SubscriptionDelegationServiceActions | AllowedActions, SubscriptionDelegationServiceEvents | AllowedEvents>;
33
+ /**
34
+ * Options for constructing {@link SubscriptionDelegationService}.
35
+ */
36
+ export type SubscriptionDelegationServiceOptions = {
37
+ messenger: SubscriptionDelegationServiceMessenger;
38
+ };
39
+ /**
40
+ * Stateless orchestrator for cash-subscription delegation setup.
41
+ *
42
+ * Owns the workflow: size periodic caveats → sign → CHOMP verify → persist to
43
+ * Authenticated User Storage → register CHOMP intent. Returns a verified
44
+ * `delegationHash` for `SubscriptionController.startSubscriptionWithCrypto`.
45
+ *
46
+ * Alpha callers must pass `skipChompInteractions: true` until a follow-up
47
+ * `@metamask/chomp-api-service` release accepts `'cash-subscription'` intent
48
+ * metadata. The CHOMP-enabled path (`skipChompInteractions` unset/false)
49
+ * remains dormant and is not production-ready without that package support.
50
+ *
51
+ * Each call resolves the Money Account chain from remote feature flags, then
52
+ * resolves its price, payment token, and delegate from `SubscriptionController`
53
+ * pricing. The pricing `delegateAddress` is used as the delegation `delegate`.
54
+ *
55
+ * Does not own subscription state; `SubscriptionController` does not depend on
56
+ * this service. Only Money Account Plus is supported.
57
+ */
58
+ export declare class SubscriptionDelegationService {
59
+ #private;
60
+ readonly name: typeof serviceName;
61
+ constructor(options: SubscriptionDelegationServiceOptions);
62
+ /**
63
+ * Checks whether the Money Account holds enough convertible mUSD value to
64
+ * cover pricing `unitAmount × minBillingCyclesForBalance`.
65
+ *
66
+ * @param request - Payer address and pricing amount fields.
67
+ * @returns Balance comparison in mUSD base units (6 decimals).
68
+ */
69
+ checkMoneyAccountBalance(request: MoneyAccountBalanceCheckRequest): Promise<MoneyAccountBalanceCheckResult>;
70
+ /**
71
+ * Prepares a cash-subscription delegation and returns its hash.
72
+ *
73
+ * Reuses a stored AUS delegation that matches the semantic fingerprint when
74
+ * one exists (ensuring a CHOMP intent is active for its hash, unless
75
+ * `skipChompInteractions` is true). Reuse classifies period `startDate` as
76
+ * trial-deferred (`> now`) vs immediately redeemable, matching creation.
77
+ * When several records match, the latest period `startDate` is reused
78
+ * so a `forceNew` replacement is preferred over an older equivalent
79
+ * permission.
80
+ * If there is no match, builds, signs, optionally verifies with CHOMP,
81
+ * persists, and optionally registers a new delegation.
82
+ *
83
+ * When `skipChompInteractions` is true (required for alpha), CHOMP verify
84
+ * and intent calls are skipped; the returned hash is computed locally. The
85
+ * default CHOMP-enabled path requires a follow-up chomp-api-service release
86
+ * that accepts `'cash-subscription'` intent metadata.
87
+ *
88
+ * @param request - Authoritative pricing and payer details for the delegation.
89
+ * @param forceNew - Whether to create a replacement instead of reusing a
90
+ * matching stored delegation.
91
+ * @returns The delegation hash (CHOMP-verified unless skipped) and whether it
92
+ * was created or reused.
93
+ */
94
+ prepareDelegation(request: PrepareSubscriptionDelegationRequest, forceNew?: boolean): Promise<PreparedSubscriptionDelegation>;
95
+ }
96
+ export {};
97
+ //# sourceMappingURL=SubscriptionDelegationService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubscriptionDelegationService.d.ts","sourceRoot":"","sources":["../../src/subscription-delegation/SubscriptionDelegationService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,qDAAqD,EACrD,oDAAoD,EACrD,MAAM,sCAAsC,CAAC;AAC9C,OAAO,KAAK,EACV,kCAAkC,EAClC,wCAAwC,EACxC,qCAAqC,EACtC,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,wCAAwC,EAAE,MAAM,iCAAiC,CAAC;AAGhG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,wDAAwD,EAAE,MAAM,yCAAyC,CAAC;AAKxH,OAAO,KAAK,EAAE,yCAAyC,EAAE,MAAM,0CAA0C,CAAC;AAK1G,OAAO,KAAK,EAAE,sCAAsC,EAAE,MAAM,kDAAkD,CAAC;AAqB/G,OAAO,KAAK,EAAE,0CAA0C,EAAE,MAAM,wDAAwD,CAAC;AACzH,OAAO,KAAK,EACV,+BAA+B,EAC/B,8BAA8B,EAC9B,oCAAoC,EACpC,8BAA8B,EAE/B,MAAM,YAAY,CAAC;AAGpB;;;GAGG;AACH,eAAO,MAAM,WAAW,kCAAkC,CAAC;AAyB3D;;GAEG;AACH,MAAM,MAAM,oCAAoC,GAC9C,0CAA0C,CAAC;AAE7C;;GAEG;AACH,KAAK,cAAc,GACf,oDAAoD,GACpD,qDAAqD,GACrD,qCAAqC,GACrC,kCAAkC,GAClC,wCAAwC,GACxC,wCAAwC,GACxC,wDAAwD,GACxD,yCAAyC,GACzC,sCAAsC,CAAC;AAE3C;;GAEG;AACH,MAAM,MAAM,mCAAmC,GAAG,KAAK,CAAC;AAExD,KAAK,aAAa,GAAG,KAAK,CAAC;AAE3B;;;GAGG;AACH,MAAM,MAAM,sCAAsC,GAAG,SAAS,CAC5D,OAAO,WAAW,EAClB,oCAAoC,GAAG,cAAc,EACrD,mCAAmC,GAAG,aAAa,CACpD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,oCAAoC,GAAG;IACjD,SAAS,EAAE,sCAAsC,CAAC;CACnD,CAAC;AAmBF;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,6BAA6B;;IACxC,QAAQ,CAAC,IAAI,EAAE,OAAO,WAAW,CAAe;IAIhD,YAAY,OAAO,EAAE,oCAAoC,EAOxD;IAED;;;;;;OAMG;IACG,wBAAwB,CAC5B,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,8BAA8B,CAAC,CAMzC;IA+BD;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACG,iBAAiB,CACrB,OAAO,EAAE,oCAAoC,EAC7C,QAAQ,UAAQ,GACf,OAAO,CAAC,8BAA8B,CAAC,CA4JzC;CAkJF"}
@@ -0,0 +1,306 @@
1
+ import { hashDelegation } from '@metamask/delegation-core';
2
+ import { DELEGATOR_CONTRACTS } from '@metamask/delegation-deployments';
3
+ import { getMoneyAccountVaultConfig, MUSD_DECIMALS, } from '@metamask/money-account-utils';
4
+ import { add0x, hexToNumber } from '@metamask/utils';
5
+ import { SubscriptionDelegationServiceErrorMessage } from '../constants.js';
6
+ import { CRYPTO_AUTH_METHODS, PAYMENT_TYPES, PRODUCT_TYPES } from '../types.js';
7
+ import { assertPositiveInteger, calculatePeriodAmount, getDelegationStartDate, getPeriodDuration, } from './amount.js';
8
+ import { buildUnsignedSubscriptionDelegation } from './caveats.js';
9
+ import { equalsIgnoreCase, makeMatchesSubscriptionDelegation, pickLatestMatchingSubscriptionDelegation, } from './fingerprint.js';
10
+ import { CASH_SUBSCRIPTION_DELEGATION_TYPE } from './types.js';
11
+ /**
12
+ * The name of the {@link SubscriptionDelegationService}, used to namespace the
13
+ * service's actions and events.
14
+ */
15
+ export const serviceName = 'SubscriptionDelegationService';
16
+ const MESSENGER_EXPOSED_METHODS = [
17
+ 'prepareDelegation',
18
+ 'checkMoneyAccountBalance',
19
+ ];
20
+ const DELEGATION_FRAMEWORK_VERSION = '1.3.0';
21
+ function resolveEnforcers(chainId) {
22
+ const contracts = DELEGATOR_CONTRACTS[DELEGATION_FRAMEWORK_VERSION]?.[hexToNumber(chainId)];
23
+ if (!contracts?.ValueLteEnforcer || !contracts.ERC20PeriodTransferEnforcer) {
24
+ throw new Error(`${SubscriptionDelegationServiceErrorMessage.DelegationContractsNotFound}: ${chainId}`);
25
+ }
26
+ return {
27
+ valueLte: contracts.ValueLteEnforcer,
28
+ erc20TokenPeriodTransfer: contracts.ERC20PeriodTransferEnforcer,
29
+ };
30
+ }
31
+ /**
32
+ * Stateless orchestrator for cash-subscription delegation setup.
33
+ *
34
+ * Owns the workflow: size periodic caveats → sign → CHOMP verify → persist to
35
+ * Authenticated User Storage → register CHOMP intent. Returns a verified
36
+ * `delegationHash` for `SubscriptionController.startSubscriptionWithCrypto`.
37
+ *
38
+ * Alpha callers must pass `skipChompInteractions: true` until a follow-up
39
+ * `@metamask/chomp-api-service` release accepts `'cash-subscription'` intent
40
+ * metadata. The CHOMP-enabled path (`skipChompInteractions` unset/false)
41
+ * remains dormant and is not production-ready without that package support.
42
+ *
43
+ * Each call resolves the Money Account chain from remote feature flags, then
44
+ * resolves its price, payment token, and delegate from `SubscriptionController`
45
+ * pricing. The pricing `delegateAddress` is used as the delegation `delegate`.
46
+ *
47
+ * Does not own subscription state; `SubscriptionController` does not depend on
48
+ * this service. Only Money Account Plus is supported.
49
+ */
50
+ export class SubscriptionDelegationService {
51
+ name = serviceName;
52
+ #messenger;
53
+ constructor(options) {
54
+ this.#messenger = options.messenger;
55
+ this.#messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
56
+ }
57
+ /**
58
+ * Checks whether the Money Account holds enough convertible mUSD value to
59
+ * cover pricing `unitAmount × minBillingCyclesForBalance`.
60
+ *
61
+ * @param request - Payer address and pricing amount fields.
62
+ * @returns Balance comparison in mUSD base units (6 decimals).
63
+ */
64
+ async checkMoneyAccountBalance(request) {
65
+ const { price } = await this.#resolveConfiguration(request.product, request.recurringInterval);
66
+ return this.#compareMoneyAccountBalance(request.payerAddress, price);
67
+ }
68
+ async #compareMoneyAccountBalance(payerAddress, price) {
69
+ assertPositiveInteger(price.minBillingCyclesForBalance, SubscriptionDelegationServiceErrorMessage.InvalidMinimumFundingCycles);
70
+ const periodAmount = calculatePeriodAmount({
71
+ unitAmount: price.unitAmount,
72
+ unitDecimals: price.unitDecimals,
73
+ tokenDecimals: MUSD_DECIMALS,
74
+ });
75
+ const requiredBalance = periodAmount * BigInt(price.minBillingCyclesForBalance);
76
+ const { totalBalance } = await this.#messenger.call('MoneyAccountBalanceService:fetchBalanceWithFallback', payerAddress);
77
+ return {
78
+ hasSufficientBalance: BigInt(totalBalance) >= requiredBalance,
79
+ balance: totalBalance,
80
+ requiredBalance: requiredBalance.toString(),
81
+ };
82
+ }
83
+ /**
84
+ * Prepares a cash-subscription delegation and returns its hash.
85
+ *
86
+ * Reuses a stored AUS delegation that matches the semantic fingerprint when
87
+ * one exists (ensuring a CHOMP intent is active for its hash, unless
88
+ * `skipChompInteractions` is true). Reuse classifies period `startDate` as
89
+ * trial-deferred (`> now`) vs immediately redeemable, matching creation.
90
+ * When several records match, the latest period `startDate` is reused
91
+ * so a `forceNew` replacement is preferred over an older equivalent
92
+ * permission.
93
+ * If there is no match, builds, signs, optionally verifies with CHOMP,
94
+ * persists, and optionally registers a new delegation.
95
+ *
96
+ * When `skipChompInteractions` is true (required for alpha), CHOMP verify
97
+ * and intent calls are skipped; the returned hash is computed locally. The
98
+ * default CHOMP-enabled path requires a follow-up chomp-api-service release
99
+ * that accepts `'cash-subscription'` intent metadata.
100
+ *
101
+ * @param request - Authoritative pricing and payer details for the delegation.
102
+ * @param forceNew - Whether to create a replacement instead of reusing a
103
+ * matching stored delegation.
104
+ * @returns The delegation hash (CHOMP-verified unless skipped) and whether it
105
+ * was created or reused.
106
+ */
107
+ async prepareDelegation(request, forceNew = false) {
108
+ if (request.product !== PRODUCT_TYPES.MONEY_ACCOUNT_PLUS) {
109
+ throw new Error(SubscriptionDelegationServiceErrorMessage.UnsupportedProduct);
110
+ }
111
+ const skipChomp = Boolean(request.skipChompInteractions);
112
+ const { chainId, delegateAddress, enforcers, price, token } = await this.#resolveConfiguration(request.product, request.recurringInterval);
113
+ if (request.checkBalance) {
114
+ const { hasSufficientBalance } = await this.#compareMoneyAccountBalance(request.payerAddress, price);
115
+ if (!hasSufficientBalance) {
116
+ throw new Error(SubscriptionDelegationServiceErrorMessage.InsufficientBalance);
117
+ }
118
+ }
119
+ const periodAmount = calculatePeriodAmount({
120
+ unitAmount: price.unitAmount,
121
+ unitDecimals: price.unitDecimals,
122
+ tokenDecimals: token.decimals,
123
+ });
124
+ const periodDuration = getPeriodDuration(request.recurringInterval);
125
+ const nowSeconds = Math.floor(Date.now() / 1000);
126
+ const startDate = getDelegationStartDate({
127
+ nowSeconds,
128
+ trialPeriodDays: request.isTrialRequested
129
+ ? price.trialPeriodDays
130
+ : undefined,
131
+ });
132
+ const isTrialDeferred = startDate > nowSeconds;
133
+ const reusable = forceNew
134
+ ? undefined
135
+ : await this.#findReusableDelegation({
136
+ request,
137
+ chainId,
138
+ delegateAddress,
139
+ tokenAddress: token.address,
140
+ periodAmount,
141
+ periodDuration,
142
+ nowSeconds,
143
+ isTrialDeferred,
144
+ enforcers,
145
+ });
146
+ if (reusable) {
147
+ if (!skipChomp) {
148
+ await this.#ensureIntent({
149
+ account: request.payerAddress,
150
+ chainId,
151
+ delegationHash: reusable.metadata.delegationHash,
152
+ allowance: reusable.metadata.allowance,
153
+ tokenSymbol: reusable.metadata.tokenSymbol,
154
+ tokenAddress: reusable.metadata.tokenAddress,
155
+ });
156
+ }
157
+ return {
158
+ delegationHash: reusable.metadata.delegationHash,
159
+ disposition: 'reused',
160
+ };
161
+ }
162
+ const unsigned = buildUnsignedSubscriptionDelegation({
163
+ delegateAddress,
164
+ delegatorAddress: request.payerAddress,
165
+ enforcers,
166
+ tokenAddress: token.address,
167
+ periodAmount,
168
+ periodDuration,
169
+ startDate,
170
+ });
171
+ const signature = (await this.#messenger.call('DelegationController:signDelegation', { delegation: unsigned, chainId }));
172
+ const signedDelegation = { ...unsigned, signature };
173
+ const delegationHash = hashDelegation({
174
+ ...unsigned,
175
+ salt: BigInt(unsigned.salt),
176
+ signature,
177
+ });
178
+ if (!skipChomp) {
179
+ const verifyResult = await this.#messenger.call('ChompApiService:verifyDelegation', {
180
+ signedDelegation,
181
+ chainId,
182
+ });
183
+ if (!verifyResult.valid) {
184
+ throw new Error(`${SubscriptionDelegationServiceErrorMessage.ChompRejectedDelegation}: ${verifyResult.errors?.join(', ') ?? 'unknown error'}`);
185
+ }
186
+ if (!verifyResult.delegationHash) {
187
+ throw new Error(SubscriptionDelegationServiceErrorMessage.ChompMissingDelegationHash);
188
+ }
189
+ if (!equalsIgnoreCase(verifyResult.delegationHash, delegationHash)) {
190
+ throw new Error(SubscriptionDelegationServiceErrorMessage.ChompDelegationHashMismatch);
191
+ }
192
+ }
193
+ const allowance = add0x(periodAmount.toString(16));
194
+ await this.#messenger.call('AuthenticatedUserStorageService:createDelegation', {
195
+ signedDelegation,
196
+ metadata: {
197
+ delegationHash,
198
+ chainIdHex: chainId,
199
+ allowance,
200
+ tokenSymbol: token.symbol,
201
+ tokenAddress: token.address,
202
+ type: CASH_SUBSCRIPTION_DELEGATION_TYPE,
203
+ },
204
+ });
205
+ if (!skipChomp) {
206
+ await this.#createIntent({
207
+ account: request.payerAddress,
208
+ chainId,
209
+ delegationHash,
210
+ allowance,
211
+ tokenSymbol: token.symbol,
212
+ tokenAddress: token.address,
213
+ });
214
+ }
215
+ return {
216
+ delegationHash,
217
+ disposition: 'created',
218
+ };
219
+ }
220
+ async #findReusableDelegation({ request, chainId, delegateAddress, tokenAddress, periodAmount, periodDuration, nowSeconds, isTrialDeferred, enforcers, }) {
221
+ const matches = makeMatchesSubscriptionDelegation({
222
+ delegatorAddress: request.payerAddress,
223
+ delegateAddress,
224
+ chainId,
225
+ tokenAddress,
226
+ periodAmount,
227
+ periodDuration,
228
+ nowSeconds,
229
+ isTrialDeferred,
230
+ enforcers,
231
+ });
232
+ const existingDelegations = await this.#messenger.call('AuthenticatedUserStorageService:listDelegations');
233
+ return pickLatestMatchingSubscriptionDelegation(existingDelegations.filter(matches), enforcers);
234
+ }
235
+ async #resolveConfiguration(product, recurringInterval) {
236
+ const { remoteFeatureFlags } = this.#messenger.call('RemoteFeatureFlagController:getState');
237
+ const vaultConfig = getMoneyAccountVaultConfig(remoteFeatureFlags);
238
+ if (!vaultConfig) {
239
+ throw new Error(SubscriptionDelegationServiceErrorMessage.MissingMoneyAccountVaultConfig);
240
+ }
241
+ const { chainId } = vaultConfig;
242
+ const enforcers = resolveEnforcers(chainId);
243
+ const pricing = await this.#messenger.call('SubscriptionController:getPricing');
244
+ const price = pricing.products
245
+ .find((entry) => entry.name === product)
246
+ ?.prices?.find((entry) => entry.interval === recurringInterval);
247
+ const paymentMethod = pricing.paymentMethods.find((entry) => entry.type === PAYMENT_TYPES.byCrypto &&
248
+ entry.cryptoAuthMethod === CRYPTO_AUTH_METHODS.DELEGATION &&
249
+ entry.products?.includes(product) === true);
250
+ const chain = paymentMethod?.chains?.find((entry) => entry.chainId === chainId);
251
+ const token = chain?.tokens[0];
252
+ if (!price || !chain?.delegateAddress || !token) {
253
+ throw new Error(SubscriptionDelegationServiceErrorMessage.PricingConfigurationNotFound);
254
+ }
255
+ return {
256
+ chainId,
257
+ delegateAddress: chain.delegateAddress,
258
+ enforcers,
259
+ price,
260
+ token,
261
+ };
262
+ }
263
+ /**
264
+ * Ensures an active CHOMP intent exists for the given delegation hash,
265
+ * registering one when missing or revoked.
266
+ *
267
+ * @param params - Intent identity and metadata.
268
+ * @param params.account - Delegator / payer address.
269
+ * @param params.chainId - Chain ID of the delegation.
270
+ * @param params.delegationHash - Hash of the stored delegation.
271
+ * @param params.allowance - Period allowance stored with the delegation.
272
+ * @param params.tokenSymbol - Payment token symbol.
273
+ * @param params.tokenAddress - Payment token address.
274
+ */
275
+ async #ensureIntent(params) {
276
+ const existingIntents = await this.#messenger.call('ChompApiService:getIntentsByAddress', params.account);
277
+ const hasActiveIntent = existingIntents.some((intent) => equalsIgnoreCase(intent.delegationHash, params.delegationHash) &&
278
+ intent.status === 'active');
279
+ if (hasActiveIntent) {
280
+ return;
281
+ }
282
+ await this.#createIntent(params);
283
+ }
284
+ async #createIntent(params) {
285
+ // Published `@metamask/chomp-api-service` only types intent metadata as
286
+ // `'cash-deposit' | 'cash-withdrawal'`. The dormant production path still
287
+ // passes `'cash-subscription'`; a follow-up chomp-api-service release must
288
+ // accept that discriminator before this path is production-ready. Alpha
289
+ // callers must use `skipChompInteractions: true` so this method is not
290
+ // reached.
291
+ await this.#messenger.call('ChompApiService:createIntents', [
292
+ {
293
+ account: params.account,
294
+ delegationHash: params.delegationHash,
295
+ chainId: params.chainId,
296
+ metadata: {
297
+ allowance: params.allowance,
298
+ tokenSymbol: params.tokenSymbol,
299
+ tokenAddress: params.tokenAddress,
300
+ type: CASH_SUBSCRIPTION_DELEGATION_TYPE,
301
+ },
302
+ },
303
+ ]);
304
+ }
305
+ }
306
+ //# sourceMappingURL=SubscriptionDelegationService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubscriptionDelegationService.js","sourceRoot":"","sources":["../../src/subscription-delegation/SubscriptionDelegationService.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DelegationResponse,\n AuthenticatedUserStorageServiceCreateDelegationAction,\n AuthenticatedUserStorageServiceListDelegationsAction,\n} from '@metamask/authenticated-user-storage';\nimport type {\n ChompApiServiceCreateIntentsAction,\n ChompApiServiceGetIntentsByAddressAction,\n ChompApiServiceVerifyDelegationAction,\n} from '@metamask/chomp-api-service';\nimport type { DelegationControllerSignDelegationAction } from '@metamask/delegation-controller';\nimport { hashDelegation } from '@metamask/delegation-core';\nimport { DELEGATOR_CONTRACTS } from '@metamask/delegation-deployments';\nimport type { Messenger } from '@metamask/messenger';\nimport type { MoneyAccountBalanceServiceFetchBalanceWithFallbackAction } from '@metamask/money-account-balance-service';\nimport {\n getMoneyAccountVaultConfig,\n MUSD_DECIMALS,\n} from '@metamask/money-account-utils';\nimport type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller';\nimport { add0x, hexToNumber } from '@metamask/utils';\nimport type { Hex } from '@metamask/utils';\n\nimport { SubscriptionDelegationServiceErrorMessage } from '../constants.js';\nimport type { SubscriptionControllerGetPricingAction } from '../SubscriptionController-method-action-types.js';\nimport { CRYPTO_AUTH_METHODS, PAYMENT_TYPES, PRODUCT_TYPES } from '../types.js';\nimport type {\n ProductPrice,\n ProductType,\n PricingCryptoPaymentMethod,\n RecurringInterval,\n TokenPaymentInfo,\n} from '../types.js';\nimport {\n assertPositiveInteger,\n calculatePeriodAmount,\n getDelegationStartDate,\n getPeriodDuration,\n} from './amount.js';\nimport { buildUnsignedSubscriptionDelegation } from './caveats.js';\nimport {\n equalsIgnoreCase,\n makeMatchesSubscriptionDelegation,\n pickLatestMatchingSubscriptionDelegation,\n} from './fingerprint.js';\nimport type { SubscriptionDelegationServiceMethodActions } from './SubscriptionDelegationService-method-action-types.js';\nimport type {\n MoneyAccountBalanceCheckRequest,\n MoneyAccountBalanceCheckResult,\n PrepareSubscriptionDelegationRequest,\n PreparedSubscriptionDelegation,\n SubscriptionDelegationEnforcers,\n} from './types.js';\nimport { CASH_SUBSCRIPTION_DELEGATION_TYPE } from './types.js';\n\n/**\n * The name of the {@link SubscriptionDelegationService}, used to namespace the\n * service's actions and events.\n */\nexport const serviceName = 'SubscriptionDelegationService';\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'prepareDelegation',\n 'checkMoneyAccountBalance',\n] as const;\n\nconst DELEGATION_FRAMEWORK_VERSION = '1.3.0';\n\nfunction resolveEnforcers(chainId: Hex): SubscriptionDelegationEnforcers {\n const contracts =\n DELEGATOR_CONTRACTS[DELEGATION_FRAMEWORK_VERSION]?.[hexToNumber(chainId)];\n\n if (!contracts?.ValueLteEnforcer || !contracts.ERC20PeriodTransferEnforcer) {\n throw new Error(\n `${SubscriptionDelegationServiceErrorMessage.DelegationContractsNotFound}: ${chainId}`,\n );\n }\n\n return {\n valueLte: contracts.ValueLteEnforcer,\n erc20TokenPeriodTransfer: contracts.ERC20PeriodTransferEnforcer,\n };\n}\n\n/**\n * Actions that {@link SubscriptionDelegationService} exposes to other consumers.\n */\nexport type SubscriptionDelegationServiceActions =\n SubscriptionDelegationServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link SubscriptionDelegationServiceMessenger} calls.\n */\ntype AllowedActions =\n | AuthenticatedUserStorageServiceListDelegationsAction\n | AuthenticatedUserStorageServiceCreateDelegationAction\n | ChompApiServiceVerifyDelegationAction\n | ChompApiServiceCreateIntentsAction\n | ChompApiServiceGetIntentsByAddressAction\n | DelegationControllerSignDelegationAction\n | MoneyAccountBalanceServiceFetchBalanceWithFallbackAction\n | RemoteFeatureFlagControllerGetStateAction\n | SubscriptionControllerGetPricingAction;\n\n/**\n * Events that {@link SubscriptionDelegationService} exposes to other consumers.\n */\nexport type SubscriptionDelegationServiceEvents = never;\n\ntype AllowedEvents = never;\n\n/**\n * The messenger which is restricted to actions and events accessed by\n * {@link SubscriptionDelegationService}.\n */\nexport type SubscriptionDelegationServiceMessenger = Messenger<\n typeof serviceName,\n SubscriptionDelegationServiceActions | AllowedActions,\n SubscriptionDelegationServiceEvents | AllowedEvents\n>;\n\n/**\n * Options for constructing {@link SubscriptionDelegationService}.\n */\nexport type SubscriptionDelegationServiceOptions = {\n messenger: SubscriptionDelegationServiceMessenger;\n};\n\ntype SubscriptionIntentParams = {\n account: Hex;\n chainId: Hex;\n delegationHash: Hex;\n allowance: Hex;\n tokenSymbol: string;\n tokenAddress: Hex;\n};\n\ntype ResolvedSubscriptionDelegationConfig = {\n chainId: Hex;\n delegateAddress: Hex;\n enforcers: SubscriptionDelegationEnforcers;\n price: ProductPrice;\n token: TokenPaymentInfo;\n};\n\n/**\n * Stateless orchestrator for cash-subscription delegation setup.\n *\n * Owns the workflow: size periodic caveats → sign → CHOMP verify → persist to\n * Authenticated User Storage → register CHOMP intent. Returns a verified\n * `delegationHash` for `SubscriptionController.startSubscriptionWithCrypto`.\n *\n * Alpha callers must pass `skipChompInteractions: true` until a follow-up\n * `@metamask/chomp-api-service` release accepts `'cash-subscription'` intent\n * metadata. The CHOMP-enabled path (`skipChompInteractions` unset/false)\n * remains dormant and is not production-ready without that package support.\n *\n * Each call resolves the Money Account chain from remote feature flags, then\n * resolves its price, payment token, and delegate from `SubscriptionController`\n * pricing. The pricing `delegateAddress` is used as the delegation `delegate`.\n *\n * Does not own subscription state; `SubscriptionController` does not depend on\n * this service. Only Money Account Plus is supported.\n */\nexport class SubscriptionDelegationService {\n readonly name: typeof serviceName = serviceName;\n\n readonly #messenger: SubscriptionDelegationServiceMessenger;\n\n constructor(options: SubscriptionDelegationServiceOptions) {\n this.#messenger = options.messenger;\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Checks whether the Money Account holds enough convertible mUSD value to\n * cover pricing `unitAmount × minBillingCyclesForBalance`.\n *\n * @param request - Payer address and pricing amount fields.\n * @returns Balance comparison in mUSD base units (6 decimals).\n */\n async checkMoneyAccountBalance(\n request: MoneyAccountBalanceCheckRequest,\n ): Promise<MoneyAccountBalanceCheckResult> {\n const { price } = await this.#resolveConfiguration(\n request.product,\n request.recurringInterval,\n );\n return this.#compareMoneyAccountBalance(request.payerAddress, price);\n }\n\n async #compareMoneyAccountBalance(\n payerAddress: Hex,\n price: ProductPrice,\n ): Promise<MoneyAccountBalanceCheckResult> {\n assertPositiveInteger(\n price.minBillingCyclesForBalance,\n SubscriptionDelegationServiceErrorMessage.InvalidMinimumFundingCycles,\n );\n\n const periodAmount = calculatePeriodAmount({\n unitAmount: price.unitAmount,\n unitDecimals: price.unitDecimals,\n tokenDecimals: MUSD_DECIMALS,\n });\n const requiredBalance =\n periodAmount * BigInt(price.minBillingCyclesForBalance);\n\n const { totalBalance } = await this.#messenger.call(\n 'MoneyAccountBalanceService:fetchBalanceWithFallback',\n payerAddress,\n );\n\n return {\n hasSufficientBalance: BigInt(totalBalance) >= requiredBalance,\n balance: totalBalance,\n requiredBalance: requiredBalance.toString(),\n };\n }\n\n /**\n * Prepares a cash-subscription delegation and returns its hash.\n *\n * Reuses a stored AUS delegation that matches the semantic fingerprint when\n * one exists (ensuring a CHOMP intent is active for its hash, unless\n * `skipChompInteractions` is true). Reuse classifies period `startDate` as\n * trial-deferred (`> now`) vs immediately redeemable, matching creation.\n * When several records match, the latest period `startDate` is reused\n * so a `forceNew` replacement is preferred over an older equivalent\n * permission.\n * If there is no match, builds, signs, optionally verifies with CHOMP,\n * persists, and optionally registers a new delegation.\n *\n * When `skipChompInteractions` is true (required for alpha), CHOMP verify\n * and intent calls are skipped; the returned hash is computed locally. The\n * default CHOMP-enabled path requires a follow-up chomp-api-service release\n * that accepts `'cash-subscription'` intent metadata.\n *\n * @param request - Authoritative pricing and payer details for the delegation.\n * @param forceNew - Whether to create a replacement instead of reusing a\n * matching stored delegation.\n * @returns The delegation hash (CHOMP-verified unless skipped) and whether it\n * was created or reused.\n */\n async prepareDelegation(\n request: PrepareSubscriptionDelegationRequest,\n forceNew = false,\n ): Promise<PreparedSubscriptionDelegation> {\n if (request.product !== PRODUCT_TYPES.MONEY_ACCOUNT_PLUS) {\n throw new Error(\n SubscriptionDelegationServiceErrorMessage.UnsupportedProduct,\n );\n }\n\n const skipChomp = Boolean(request.skipChompInteractions);\n\n const { chainId, delegateAddress, enforcers, price, token } =\n await this.#resolveConfiguration(\n request.product,\n request.recurringInterval,\n );\n\n if (request.checkBalance) {\n const { hasSufficientBalance } = await this.#compareMoneyAccountBalance(\n request.payerAddress,\n price,\n );\n if (!hasSufficientBalance) {\n throw new Error(\n SubscriptionDelegationServiceErrorMessage.InsufficientBalance,\n );\n }\n }\n\n const periodAmount = calculatePeriodAmount({\n unitAmount: price.unitAmount,\n unitDecimals: price.unitDecimals,\n tokenDecimals: token.decimals,\n });\n const periodDuration = getPeriodDuration(request.recurringInterval);\n const nowSeconds = Math.floor(Date.now() / 1000);\n const startDate = getDelegationStartDate({\n nowSeconds,\n trialPeriodDays: request.isTrialRequested\n ? price.trialPeriodDays\n : undefined,\n });\n const isTrialDeferred = startDate > nowSeconds;\n\n const reusable = forceNew\n ? undefined\n : await this.#findReusableDelegation({\n request,\n chainId,\n delegateAddress,\n tokenAddress: token.address,\n periodAmount,\n periodDuration,\n nowSeconds,\n isTrialDeferred,\n enforcers,\n });\n if (reusable) {\n if (!skipChomp) {\n await this.#ensureIntent({\n account: request.payerAddress,\n chainId,\n delegationHash: reusable.metadata.delegationHash,\n allowance: reusable.metadata.allowance,\n tokenSymbol: reusable.metadata.tokenSymbol,\n tokenAddress: reusable.metadata.tokenAddress,\n });\n }\n return {\n delegationHash: reusable.metadata.delegationHash,\n disposition: 'reused',\n };\n }\n\n const unsigned = buildUnsignedSubscriptionDelegation({\n delegateAddress,\n delegatorAddress: request.payerAddress,\n enforcers,\n tokenAddress: token.address,\n periodAmount,\n periodDuration,\n startDate,\n });\n\n const signature = (await this.#messenger.call(\n 'DelegationController:signDelegation',\n { delegation: unsigned, chainId },\n )) as Hex;\n\n const signedDelegation = { ...unsigned, signature };\n\n const delegationHash = hashDelegation({\n ...unsigned,\n salt: BigInt(unsigned.salt),\n signature,\n });\n\n if (!skipChomp) {\n const verifyResult = await this.#messenger.call(\n 'ChompApiService:verifyDelegation',\n {\n signedDelegation,\n chainId,\n },\n );\n\n if (!verifyResult.valid) {\n throw new Error(\n `${SubscriptionDelegationServiceErrorMessage.ChompRejectedDelegation}: ${\n verifyResult.errors?.join(', ') ?? 'unknown error'\n }`,\n );\n }\n\n if (!verifyResult.delegationHash) {\n throw new Error(\n SubscriptionDelegationServiceErrorMessage.ChompMissingDelegationHash,\n );\n }\n if (!equalsIgnoreCase(verifyResult.delegationHash, delegationHash)) {\n throw new Error(\n SubscriptionDelegationServiceErrorMessage.ChompDelegationHashMismatch,\n );\n }\n }\n\n const allowance: Hex = add0x(periodAmount.toString(16));\n\n await this.#messenger.call(\n 'AuthenticatedUserStorageService:createDelegation',\n {\n signedDelegation,\n metadata: {\n delegationHash,\n chainIdHex: chainId,\n allowance,\n tokenSymbol: token.symbol,\n tokenAddress: token.address,\n type: CASH_SUBSCRIPTION_DELEGATION_TYPE,\n },\n },\n );\n\n if (!skipChomp) {\n await this.#createIntent({\n account: request.payerAddress,\n chainId,\n delegationHash,\n allowance,\n tokenSymbol: token.symbol,\n tokenAddress: token.address,\n });\n }\n\n return {\n delegationHash,\n disposition: 'created',\n };\n }\n\n async #findReusableDelegation({\n request,\n chainId,\n delegateAddress,\n tokenAddress,\n periodAmount,\n periodDuration,\n nowSeconds,\n isTrialDeferred,\n enforcers,\n }: {\n request: PrepareSubscriptionDelegationRequest;\n chainId: Hex;\n delegateAddress: Hex;\n tokenAddress: Hex;\n periodAmount: bigint;\n periodDuration: number;\n nowSeconds: number;\n isTrialDeferred: boolean;\n enforcers: SubscriptionDelegationEnforcers;\n }): Promise<DelegationResponse | undefined> {\n const matches = makeMatchesSubscriptionDelegation({\n delegatorAddress: request.payerAddress,\n delegateAddress,\n chainId,\n tokenAddress,\n periodAmount,\n periodDuration,\n nowSeconds,\n isTrialDeferred,\n enforcers,\n });\n\n const existingDelegations = await this.#messenger.call(\n 'AuthenticatedUserStorageService:listDelegations',\n );\n\n return pickLatestMatchingSubscriptionDelegation(\n existingDelegations.filter(matches),\n enforcers,\n );\n }\n\n async #resolveConfiguration(\n product: ProductType,\n recurringInterval: RecurringInterval,\n ): Promise<ResolvedSubscriptionDelegationConfig> {\n const { remoteFeatureFlags } = this.#messenger.call(\n 'RemoteFeatureFlagController:getState',\n );\n const vaultConfig = getMoneyAccountVaultConfig(remoteFeatureFlags);\n if (!vaultConfig) {\n throw new Error(\n SubscriptionDelegationServiceErrorMessage.MissingMoneyAccountVaultConfig,\n );\n }\n\n const { chainId } = vaultConfig;\n const enforcers = resolveEnforcers(chainId);\n const pricing = await this.#messenger.call(\n 'SubscriptionController:getPricing',\n );\n const price = pricing.products\n .find((entry) => entry.name === product)\n ?.prices?.find((entry) => entry.interval === recurringInterval);\n const paymentMethod = pricing.paymentMethods.find(\n (entry): entry is PricingCryptoPaymentMethod =>\n entry.type === PAYMENT_TYPES.byCrypto &&\n entry.cryptoAuthMethod === CRYPTO_AUTH_METHODS.DELEGATION &&\n entry.products?.includes(product) === true,\n );\n const chain = paymentMethod?.chains?.find(\n (entry) => entry.chainId === chainId,\n );\n const token = chain?.tokens[0];\n if (!price || !chain?.delegateAddress || !token) {\n throw new Error(\n SubscriptionDelegationServiceErrorMessage.PricingConfigurationNotFound,\n );\n }\n\n return {\n chainId,\n delegateAddress: chain.delegateAddress,\n enforcers,\n price,\n token,\n };\n }\n\n /**\n * Ensures an active CHOMP intent exists for the given delegation hash,\n * registering one when missing or revoked.\n *\n * @param params - Intent identity and metadata.\n * @param params.account - Delegator / payer address.\n * @param params.chainId - Chain ID of the delegation.\n * @param params.delegationHash - Hash of the stored delegation.\n * @param params.allowance - Period allowance stored with the delegation.\n * @param params.tokenSymbol - Payment token symbol.\n * @param params.tokenAddress - Payment token address.\n */\n async #ensureIntent(params: SubscriptionIntentParams): Promise<void> {\n const existingIntents = await this.#messenger.call(\n 'ChompApiService:getIntentsByAddress',\n params.account,\n );\n\n const hasActiveIntent = existingIntents.some(\n (intent) =>\n equalsIgnoreCase(intent.delegationHash, params.delegationHash) &&\n intent.status === 'active',\n );\n\n if (hasActiveIntent) {\n return;\n }\n\n await this.#createIntent(params);\n }\n\n async #createIntent(params: SubscriptionIntentParams): Promise<void> {\n // Published `@metamask/chomp-api-service` only types intent metadata as\n // `'cash-deposit' | 'cash-withdrawal'`. The dormant production path still\n // passes `'cash-subscription'`; a follow-up chomp-api-service release must\n // accept that discriminator before this path is production-ready. Alpha\n // callers must use `skipChompInteractions: true` so this method is not\n // reached.\n await this.#messenger.call('ChompApiService:createIntents', [\n {\n account: params.account,\n delegationHash: params.delegationHash,\n chainId: params.chainId,\n metadata: {\n allowance: params.allowance,\n tokenSymbol: params.tokenSymbol,\n tokenAddress: params.tokenAddress,\n type: CASH_SUBSCRIPTION_DELEGATION_TYPE as\n | 'cash-deposit'\n | 'cash-withdrawal',\n },\n },\n ]);\n }\n}\n"]}
@@ -0,0 +1,49 @@
1
+ import type { RecurringInterval } from '../types.js';
2
+ /**
3
+ * Rescales a plan fee from pricing `unitDecimals` into token base units.
4
+ *
5
+ * Uses a 1:1 numeric mapping (ADR 0057): the period amount is one billing
6
+ * interval's fee, not `fee × minimumFundingCycles`.
7
+ *
8
+ * @param params - Amount and decimal inputs.
9
+ * @param params.unitAmount - Fee in pricing minor units (non-negative integer).
10
+ * @param params.unitDecimals - Decimals of `unitAmount`.
11
+ * @param params.tokenDecimals - Decimals of the payment token.
12
+ * @returns The period amount in token base units.
13
+ * @throws If inputs are not non-negative integers, or downscaling would lose precision.
14
+ */
15
+ export declare function calculatePeriodAmount({ unitAmount, unitDecimals, tokenDecimals, }: {
16
+ unitAmount: number;
17
+ unitDecimals: number;
18
+ tokenDecimals: number;
19
+ }): bigint;
20
+ /**
21
+ * Plan-scoped ERC-20 period duration in seconds (ADR 0057).
22
+ *
23
+ * - Monthly: 28 days (minimum Stripe monthly invoice gap).
24
+ * - Yearly: 365 days.
25
+ *
26
+ * @param recurringInterval - Subscription billing interval.
27
+ * @returns Period duration in seconds.
28
+ */
29
+ export declare function getPeriodDuration(recurringInterval: RecurringInterval): number;
30
+ /**
31
+ * Computes the ERC20TokenPeriodTransfer `startDate` for a subscription
32
+ * delegation, optionally deferred by a pricing trial.
33
+ *
34
+ * @param params - Clock and optional trial length.
35
+ * @param params.nowSeconds - Current unix timestamp in seconds.
36
+ * @param params.trialPeriodDays - Optional non-negative trial length in days.
37
+ * Defaults to `0` (no offset) when omitted.
38
+ * @returns Unix timestamp when the first period transfer may begin.
39
+ */
40
+ export declare function getDelegationStartDate({ nowSeconds, trialPeriodDays, }: {
41
+ nowSeconds: number;
42
+ trialPeriodDays?: number;
43
+ }): number;
44
+ /**
45
+ * @param value - Candidate number.
46
+ * @param message - Error message when invalid.
47
+ */
48
+ export declare function assertPositiveInteger(value: number, message: string): void;
49
+ //# sourceMappingURL=amount.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"amount.d.ts","sourceRoot":"","sources":["../../src/subscription-delegation/amount.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIrD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,UAAU,EACV,YAAY,EACZ,aAAa,GACd,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,MAAM,CA6BT;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,iBAAiB,EAAE,iBAAiB,GACnC,MAAM,CAUR;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,UAAU,EACV,eAAmB,GACpB,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,MAAM,CAMT;AAYD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAI1E"}