@metamask-previews/subscription-controller 10.0.1-preview-ff2ab0583 → 10.0.1-preview-c4789fdf4
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.
- package/CHANGELOG.md +7 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/subscription-delegation/SubscriptionDelegationService.d.ts.map +1 -1
- package/dist/subscription-delegation/SubscriptionDelegationService.js +9 -4
- package/dist/subscription-delegation/SubscriptionDelegationService.js.map +1 -1
- package/dist/subscription-delegation/caveats.d.ts +30 -2
- package/dist/subscription-delegation/caveats.d.ts.map +1 -1
- package/dist/subscription-delegation/caveats.js +49 -9
- package/dist/subscription-delegation/caveats.js.map +1 -1
- package/dist/subscription-delegation/fingerprint.d.ts +1 -0
- package/dist/subscription-delegation/fingerprint.d.ts.map +1 -1
- package/dist/subscription-delegation/fingerprint.js +10 -6
- package/dist/subscription-delegation/fingerprint.js.map +1 -1
- package/dist/subscription-delegation/types.d.ts +1 -1
- package/dist/subscription-delegation/types.d.ts.map +1 -1
- package/dist/subscription-delegation/types.js.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Changed
|
|
11
11
|
|
|
12
|
+
- Restrict cash-subscription delegations created by `SubscriptionDelegationService:prepareDelegation` so the ERC-20 `transfer` recipient must be the pricing chain `paymentAddress` (subscription treasury), via an `AllowedCalldataEnforcer` caveat. ([#10427](https://github.com/MetaMask/core/pull/10427))
|
|
13
|
+
- The caveat pins calldata from offset 0 to the `transfer(address,uint256)` selector followed by the ABI-encoded treasury address, matching the terms CHOMP expects.
|
|
14
|
+
- `prepareDelegation` throws when the pricing `paymentAddress` is not a valid address.
|
|
15
|
+
- Stored delegations without a matching recipient caveat are no longer reused; a new delegation is created and signed instead.
|
|
16
|
+
- Stop adding the `ValueLteEnforcer` caveat to cash-subscription delegations created by `SubscriptionDelegationService:prepareDelegation`, because CHOMP only accepts `ERC20PeriodTransferEnforcer` and `AllowedCalldataEnforcer` for this intent type. ([#10427](https://github.com/MetaMask/core/pull/10427))
|
|
17
|
+
- Stored delegations that still include a `ValueLteEnforcer` caveat are no longer reused; a new delegation is created and signed instead.
|
|
18
|
+
- Add `@ethersproject/abi` `^5.7.0` as a dependency ([#10427](https://github.com/MetaMask/core/pull/10427))
|
|
12
19
|
- Bump `@metamask/transaction-controller` from `^72.0.0` to `^72.0.1` ([#10462](https://github.com/MetaMask/core/pull/10462))
|
|
13
20
|
|
|
14
21
|
## [10.0.1]
|
package/dist/constants.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ export declare enum SubscriptionDelegationServiceErrorMessage {
|
|
|
66
66
|
MissingMoneyAccountVaultConfig = "Money Account vault configuration is missing or invalid",
|
|
67
67
|
DelegationContractsNotFound = "Subscription delegation contracts were not found for the configured chain",
|
|
68
68
|
PricingConfigurationNotFound = "Subscription delegation pricing configuration was not found",
|
|
69
|
+
InvalidRecipientAddress = "Subscription delegation recipient must be a valid 20-byte address",
|
|
69
70
|
InsufficientBalance = "Money Account balance is insufficient for the subscription funding requirement",
|
|
70
71
|
ChompRejectedDelegation = "CHOMP rejected the subscription delegation",
|
|
71
72
|
ChompMissingDelegationHash = "CHOMP verify response did not include a delegation hash",
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,2BAA2B,CAAC;AAEvD;;GAEG;AACH,eAAO,MAAM,oBAAoB;aAE/B,aAAa,EAAE,eAAe;aAE9B,kBAAkB,EAAE,oBAAoB;aAExC,wBAAwB,EAAE,0BAA0B;aAEpD,sBAAsB,EAAE,wBAAwB;aAEhD,oBAAoB,EAAE,sBAAsB;aAE5C,KAAK,EAAE,OAAO;CACN,CAAC;AAEX,oBAAY,GAAG;IACb,GAAG,QAAQ;IACX,GAAG,QAAQ;IACX,GAAG,QAAQ;CACZ;AAED,KAAK,YAAY,GAAG;IAClB,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAcF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,YAAY,CAKjD;AAED,oBAAY,kCAAkC;IAC5C,qBAAqB,wDAAmD;IACxE,iBAAiB,oDAA+C;IAChE,yBAAyB,yEAAoE;IAC7F,6CAA6C,8FAAyF;IACtI,sBAAsB,0DAAqD;IAC3E,oBAAoB,qDAAgD;IACpE,qCAAqC,2EAAsE;IAC3G,mCAAmC,sGAAiG;IACpI,iBAAiB,oDAA+C;CACjE;AAED,oBAAY,+BAA+B;IACzC,wBAAwB,gCAAgC;IACxD,mBAAmB,2BAA2B;IAC9C,0BAA0B,kCAAkC;IAC5D,4BAA4B,oCAAoC;IAChE,iCAAiC,2CAA2C;IAC5E,mCAAmC,6CAA6C;IAChF,sBAAsB,+GAA+G;IACrI,+BAA+B,yCAAyC;IACxE,iCAAiC,2CAA2C;IAC5E,qCAAqC,8CAA8C;IACnF,uBAAuB,gCAAgC;IACvD,0BAA0B,oCAAoC;IAC9D,gCAAgC,yCAAyC;IACzE,mBAAmB,2BAA2B;IAC9C,kBAAkB,0BAA0B;IAC5C,2BAA2B,qCAAqC;CACjE;AAED,oBAAY,yCAAyC;IACnD,aAAa,kEAAkE;IAC/E,eAAe,oEAAoE;IACnF,sBAAsB,6EAA6E;IACnG,2BAA2B,8EAA8E;IACzG,gBAAgB,wFAAwF;IACxG,4BAA4B,gDAAgD;IAC5E,kBAAkB,gEAAgE;IAClF,8BAA8B,4DAA4D;IAC1F,2BAA2B,8EAA8E;IACzG,4BAA4B,gEAAgE;IAC5F,mBAAmB,mFAAmF;IACtG,uBAAuB,+CAA+C;IACtE,0BAA0B,4DAA4D;IACtF,2BAA2B,mFAAmF;CAC/G;AAED,eAAO,MAAM,wBAAwB,QAAiB,CAAC;AAEvD,eAAO,MAAM,4BAA4B,EAIpC,MAAM,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,2BAA2B,CAAC;AAEvD;;GAEG;AACH,eAAO,MAAM,oBAAoB;aAE/B,aAAa,EAAE,eAAe;aAE9B,kBAAkB,EAAE,oBAAoB;aAExC,wBAAwB,EAAE,0BAA0B;aAEpD,sBAAsB,EAAE,wBAAwB;aAEhD,oBAAoB,EAAE,sBAAsB;aAE5C,KAAK,EAAE,OAAO;CACN,CAAC;AAEX,oBAAY,GAAG;IACb,GAAG,QAAQ;IACX,GAAG,QAAQ;IACX,GAAG,QAAQ;CACZ;AAED,KAAK,YAAY,GAAG;IAClB,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAcF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,YAAY,CAKjD;AAED,oBAAY,kCAAkC;IAC5C,qBAAqB,wDAAmD;IACxE,iBAAiB,oDAA+C;IAChE,yBAAyB,yEAAoE;IAC7F,6CAA6C,8FAAyF;IACtI,sBAAsB,0DAAqD;IAC3E,oBAAoB,qDAAgD;IACpE,qCAAqC,2EAAsE;IAC3G,mCAAmC,sGAAiG;IACpI,iBAAiB,oDAA+C;CACjE;AAED,oBAAY,+BAA+B;IACzC,wBAAwB,gCAAgC;IACxD,mBAAmB,2BAA2B;IAC9C,0BAA0B,kCAAkC;IAC5D,4BAA4B,oCAAoC;IAChE,iCAAiC,2CAA2C;IAC5E,mCAAmC,6CAA6C;IAChF,sBAAsB,+GAA+G;IACrI,+BAA+B,yCAAyC;IACxE,iCAAiC,2CAA2C;IAC5E,qCAAqC,8CAA8C;IACnF,uBAAuB,gCAAgC;IACvD,0BAA0B,oCAAoC;IAC9D,gCAAgC,yCAAyC;IACzE,mBAAmB,2BAA2B;IAC9C,kBAAkB,0BAA0B;IAC5C,2BAA2B,qCAAqC;CACjE;AAED,oBAAY,yCAAyC;IACnD,aAAa,kEAAkE;IAC/E,eAAe,oEAAoE;IACnF,sBAAsB,6EAA6E;IACnG,2BAA2B,8EAA8E;IACzG,gBAAgB,wFAAwF;IACxG,4BAA4B,gDAAgD;IAC5E,kBAAkB,gEAAgE;IAClF,8BAA8B,4DAA4D;IAC1F,2BAA2B,8EAA8E;IACzG,4BAA4B,gEAAgE;IAC5F,uBAAuB,sEAAsE;IAC7F,mBAAmB,mFAAmF;IACtG,uBAAuB,+CAA+C;IACtE,0BAA0B,4DAA4D;IACtF,2BAA2B,mFAAmF;CAC/G;AAED,eAAO,MAAM,wBAAwB,QAAiB,CAAC;AAEvD,eAAO,MAAM,4BAA4B,EAIpC,MAAM,EAAE,CAAC"}
|
package/dist/constants.js
CHANGED
|
@@ -90,6 +90,7 @@ export var SubscriptionDelegationServiceErrorMessage;
|
|
|
90
90
|
SubscriptionDelegationServiceErrorMessage["MissingMoneyAccountVaultConfig"] = "Money Account vault configuration is missing or invalid";
|
|
91
91
|
SubscriptionDelegationServiceErrorMessage["DelegationContractsNotFound"] = "Subscription delegation contracts were not found for the configured chain";
|
|
92
92
|
SubscriptionDelegationServiceErrorMessage["PricingConfigurationNotFound"] = "Subscription delegation pricing configuration was not found";
|
|
93
|
+
SubscriptionDelegationServiceErrorMessage["InvalidRecipientAddress"] = "Subscription delegation recipient must be a valid 20-byte address";
|
|
93
94
|
SubscriptionDelegationServiceErrorMessage["InsufficientBalance"] = "Money Account balance is insufficient for the subscription funding requirement";
|
|
94
95
|
SubscriptionDelegationServiceErrorMessage["ChompRejectedDelegation"] = "CHOMP rejected the subscription delegation";
|
|
95
96
|
SubscriptionDelegationServiceErrorMessage["ChompMissingDelegationHash"] = "CHOMP verify response did not include a delegation hash";
|
package/dist/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,CAAC,MAAM,cAAc,GAAG,wBAAwB,CAAC;AAEvD;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,6BAA6B;IAC7B,aAAa,EAAE,eAAe;IAC9B,8BAA8B;IAC9B,kBAAkB,EAAE,oBAAoB;IACxC,uCAAuC;IACvC,wBAAwB,EAAE,0BAA0B;IACpD,wBAAwB;IACxB,sBAAsB,EAAE,wBAAwB;IAChD,uBAAuB;IACvB,oBAAoB,EAAE,sBAAsB;IAC5C,QAAQ;IACR,KAAK,EAAE,OAAO;CACN,CAAC;AAEX,MAAM,CAAN,IAAY,GAIX;AAJD,WAAY,GAAG;IACb,kBAAW,CAAA;IACX,kBAAW,CAAA;IACX,kBAAW,CAAA;AACb,CAAC,EAJW,GAAG,KAAH,GAAG,QAId;AAMD,MAAM,QAAQ,GAA8B;IAC1C,GAAG,EAAE;QACH,kBAAkB,EAAE,6CAA6C;KAClE;IACD,GAAG,EAAE;QACH,kBAAkB,EAAE,6CAA6C;KAClE;IACD,GAAG,EAAE;QACH,kBAAkB,EAAE,yCAAyC;KAC9D;CACF,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,GAAQ;IACjC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,MAAM,CAAN,IAAY,kCAUX;AAVD,WAAY,kCAAkC;IAC5C,mHAAwE,CAAA;IACxE,2GAAgE,CAAA;IAChE,wIAA6F,CAAA;IAC7F,iLAAsI,CAAA;IACtI,sHAA2E,CAAA;IAC3E,+GAAoE,CAAA;IACpE,sJAA2G,CAAA;IAC3G,+KAAoI,CAAA;IACpI,2GAAgE,CAAA;AAClE,CAAC,EAVW,kCAAkC,KAAlC,kCAAkC,QAU7C;AAED,MAAM,CAAN,IAAY,+BAiBX;AAjBD,WAAY,+BAA+B;IACzC,2FAAwD,CAAA;IACxD,iFAA8C,CAAA;IAC9C,+FAA4D,CAAA;IAC5D,mGAAgE,CAAA;IAChE,+GAA4E,CAAA;IAC5E,mHAAgF,CAAA;IAChF,wKAAqI,CAAA;IACrI,2GAAwE,CAAA;IACxE,+GAA4E,CAAA;IAC5E,sHAAmF,CAAA;IACnF,0FAAuD,CAAA;IACvD,iGAA8D,CAAA;IAC9D,4GAAyE,CAAA;IACzE,iFAA8C,CAAA;IAC9C,+EAA4C,CAAA;IAC5C,mGAAgE,CAAA;AAClE,CAAC,EAjBW,+BAA+B,KAA/B,+BAA+B,QAiB1C;AAED,MAAM,CAAN,IAAY,
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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","sourcesContent":["import { SUBSCRIPTION_STATUSES } from './types.js';\n\nexport const controllerName = 'SubscriptionController';\n\n/**\n * API cancellation reason values and their client-facing text.\n */\nexport const CANCELLATION_REASONS = {\n // Costs more than it's worth\n TOO_EXPENSIVE: 'too_expensive',\n // I wasn't using the benefits\n NOT_USING_BENEFITS: 'not_using_benefits',\n // The benefits weren't what I expected\n BENEFITS_NOT_AS_EXPECTED: 'benefits_not_as_expected',\n // Something didn't work\n SOMETHING_DID_NOT_WORK: 'something_did_not_work',\n // Unhappy with support\n UNHAPPY_WITH_SUPPORT: 'unhappy_with_support',\n // Other\n OTHER: 'other',\n} as const;\n\nexport enum Env {\n DEV = 'dev',\n UAT = 'uat',\n PRD = 'prd',\n}\n\ntype EnvUrlsEntry = {\n subscriptionApiUrl: string;\n};\n\nconst ENV_URLS: Record<Env, EnvUrlsEntry> = {\n dev: {\n subscriptionApiUrl: 'https://subscription.dev-api.cx.metamask.io',\n },\n uat: {\n subscriptionApiUrl: 'https://subscription.uat-api.cx.metamask.io',\n },\n prd: {\n subscriptionApiUrl: 'https://subscription.api.cx.metamask.io',\n },\n};\n\n/**\n * Validates and returns correct environment endpoints\n *\n * @param env - environment field\n * @returns the correct environment url\n * @throws on invalid environment passed\n */\nexport function getEnvUrls(env: Env): EnvUrlsEntry {\n if (!ENV_URLS[env]) {\n throw new Error('invalid environment configuration');\n }\n return ENV_URLS[env];\n}\n\nexport enum SubscriptionControllerErrorMessage {\n UserAlreadySubscribed = `${controllerName} - User is already subscribed`,\n UserNotSubscribed = `${controllerName} - User is not subscribed`,\n SubscriptionProductsEmpty = `${controllerName} - Subscription products array cannot be empty`,\n PaymentTokenAddressAndSymbolRequiredForCrypto = `${controllerName} - Payment token address and symbol are required for crypto payment`,\n PaymentMethodNotCrypto = `${controllerName} - Payment method is not crypto`,\n ProductPriceNotFound = `${controllerName} - Product price not found`,\n SubscriptionNotValidForCryptoApproval = `${controllerName} - Subscription is not valid for crypto approval`,\n CryptoApprovalRequiresShieldApprove = `${controllerName} - Crypto approval is only supported for Shield ERC-20 approve transactions`,\n LinkRewardsFailed = `${controllerName} - Failed to link rewards`,\n}\n\nexport enum SubscriptionServiceErrorMessage {\n FailedToGetSubscriptions = 'Failed to get subscriptions',\n FailedToGetBenefits = 'Failed to get benefits',\n FailedToCancelSubscription = 'Failed to cancel subscription',\n FailedToUncancelSubscription = 'Failed to uncancel subscription',\n FailedToStartSubscriptionWithCard = 'Failed to start subscription with card',\n FailedToStartSubscriptionWithCrypto = 'Failed to start subscription with crypto',\n InvalidCryptoAuthCombo = 'Crypto subscription requires exactly one of rawTransaction (erc20_approval) or delegationHash (delegation)',\n FailedToUpdatePaymentMethodCard = 'Failed to update payment method card',\n FailedToUpdatePaymentMethodCrypto = 'Failed to update payment method crypto',\n FailedToGetSubscriptionsEligibilities = 'Failed to get subscriptions eligibilities',\n FailedToSubmitUserEvent = 'Failed to submit user event',\n FailedToAssignUserToCohort = 'Failed to assign user to cohort',\n FailedToSubmitSponsorshipIntents = 'Failed to submit sponsorship intents',\n FailedToLinkRewards = 'Failed to link rewards',\n FailedToGetPricing = 'Failed to get pricing',\n FailedToGetBillingPortalUrl = 'Failed to get billing portal url',\n}\n\nexport enum SubscriptionDelegationServiceErrorMessage {\n InvalidAmount = 'Subscription delegation amount must be a non-negative integer',\n InvalidDecimals = 'Subscription delegation decimals must be a non-negative integer',\n InvalidTrialPeriodDays = 'Subscription delegation trial period days must be a non-negative integer',\n InvalidMinimumFundingCycles = 'Subscription delegation minimum funding cycles must be a positive integer',\n LossyAmountScale = 'Subscription delegation amount cannot be scaled to token decimals without remainder',\n UnsupportedRecurringInterval = 'Unsupported subscription recurring interval',\n UnsupportedProduct = 'Subscription delegation is only supported for Money Account',\n MissingMoneyAccountVaultConfig = 'Money Account vault configuration is missing or invalid',\n DelegationContractsNotFound = 'Subscription delegation contracts were not found for the configured chain',\n PricingConfigurationNotFound = 'Subscription delegation pricing configuration was not found',\n InvalidRecipientAddress = 'Subscription delegation recipient must be a valid 20-byte address',\n InsufficientBalance = 'Money Account balance is insufficient for the subscription funding requirement',\n ChompRejectedDelegation = 'CHOMP rejected the subscription delegation',\n ChompMissingDelegationHash = 'CHOMP verify response did not include a delegation hash',\n ChompDelegationHashMismatch = 'CHOMP verify response delegation hash does not match the locally computed hash',\n}\n\nexport const DEFAULT_POLLING_INTERVAL = 5 * 60 * 1_000; // 5 minutes\n\nexport const ACTIVE_SUBSCRIPTION_STATUSES = [\n SUBSCRIPTION_STATUSES.active,\n SUBSCRIPTION_STATUSES.trialing,\n SUBSCRIPTION_STATUSES.provisional,\n] as string[];\n"]}
|
|
@@ -1 +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;
|
|
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;AA4B3D;;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;AAoBF;;;;;;;;;;;;;;;;;;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,CAoKzC;CAsJF"}
|
|
@@ -20,12 +20,13 @@ const MESSENGER_EXPOSED_METHODS = [
|
|
|
20
20
|
const DELEGATION_FRAMEWORK_VERSION = '1.3.0';
|
|
21
21
|
function resolveEnforcers(chainId) {
|
|
22
22
|
const contracts = DELEGATOR_CONTRACTS[DELEGATION_FRAMEWORK_VERSION]?.[hexToNumber(chainId)];
|
|
23
|
-
if (!contracts?.
|
|
23
|
+
if (!contracts?.ERC20PeriodTransferEnforcer ||
|
|
24
|
+
!contracts.AllowedCalldataEnforcer) {
|
|
24
25
|
throw new Error(`${SubscriptionDelegationServiceErrorMessage.DelegationContractsNotFound}: ${chainId}`);
|
|
25
26
|
}
|
|
26
27
|
return {
|
|
27
|
-
valueLte: contracts.ValueLteEnforcer,
|
|
28
28
|
erc20TokenPeriodTransfer: contracts.ERC20PeriodTransferEnforcer,
|
|
29
|
+
allowedCalldata: contracts.AllowedCalldataEnforcer,
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
/**
|
|
@@ -109,7 +110,7 @@ export class SubscriptionDelegationService {
|
|
|
109
110
|
throw new Error(SubscriptionDelegationServiceErrorMessage.UnsupportedProduct);
|
|
110
111
|
}
|
|
111
112
|
const skipChomp = Boolean(request.skipChompInteractions);
|
|
112
|
-
const { chainId, delegateAddress, enforcers, price, token } = await this.#resolveConfiguration(request.product, request.recurringInterval);
|
|
113
|
+
const { chainId, delegateAddress, paymentAddress, enforcers, price, token, } = await this.#resolveConfiguration(request.product, request.recurringInterval);
|
|
113
114
|
if (request.checkBalance) {
|
|
114
115
|
const { hasSufficientBalance } = await this.#compareMoneyAccountBalance(request.payerAddress, price);
|
|
115
116
|
if (!hasSufficientBalance) {
|
|
@@ -136,6 +137,7 @@ export class SubscriptionDelegationService {
|
|
|
136
137
|
request,
|
|
137
138
|
chainId,
|
|
138
139
|
delegateAddress,
|
|
140
|
+
paymentAddress,
|
|
139
141
|
tokenAddress: token.address,
|
|
140
142
|
periodAmount,
|
|
141
143
|
periodDuration,
|
|
@@ -162,6 +164,7 @@ export class SubscriptionDelegationService {
|
|
|
162
164
|
const unsigned = buildUnsignedSubscriptionDelegation({
|
|
163
165
|
delegateAddress,
|
|
164
166
|
delegatorAddress: request.payerAddress,
|
|
167
|
+
recipientAddress: paymentAddress,
|
|
165
168
|
enforcers,
|
|
166
169
|
tokenAddress: token.address,
|
|
167
170
|
periodAmount,
|
|
@@ -217,10 +220,11 @@ export class SubscriptionDelegationService {
|
|
|
217
220
|
disposition: 'created',
|
|
218
221
|
};
|
|
219
222
|
}
|
|
220
|
-
async #findReusableDelegation({ request, chainId, delegateAddress, tokenAddress, periodAmount, periodDuration, nowSeconds, isTrialDeferred, enforcers, }) {
|
|
223
|
+
async #findReusableDelegation({ request, chainId, delegateAddress, paymentAddress, tokenAddress, periodAmount, periodDuration, nowSeconds, isTrialDeferred, enforcers, }) {
|
|
221
224
|
const matches = makeMatchesSubscriptionDelegation({
|
|
222
225
|
delegatorAddress: request.payerAddress,
|
|
223
226
|
delegateAddress,
|
|
227
|
+
recipientAddress: paymentAddress,
|
|
224
228
|
chainId,
|
|
225
229
|
tokenAddress,
|
|
226
230
|
periodAmount,
|
|
@@ -255,6 +259,7 @@ export class SubscriptionDelegationService {
|
|
|
255
259
|
return {
|
|
256
260
|
chainId,
|
|
257
261
|
delegateAddress: chain.delegateAddress,
|
|
262
|
+
paymentAddress: chain.paymentAddress,
|
|
258
263
|
enforcers,
|
|
259
264
|
price,
|
|
260
265
|
token,
|
|
@@ -1 +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"]}
|
|
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 (\n !contracts?.ERC20PeriodTransferEnforcer ||\n !contracts.AllowedCalldataEnforcer\n ) {\n throw new Error(\n `${SubscriptionDelegationServiceErrorMessage.DelegationContractsNotFound}: ${chainId}`,\n );\n }\n\n return {\n erc20TokenPeriodTransfer: contracts.ERC20PeriodTransferEnforcer,\n allowedCalldata: contracts.AllowedCalldataEnforcer,\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 paymentAddress: 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 {\n chainId,\n delegateAddress,\n paymentAddress,\n enforcers,\n price,\n 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 paymentAddress,\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 recipientAddress: paymentAddress,\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 paymentAddress,\n tokenAddress,\n periodAmount,\n periodDuration,\n nowSeconds,\n isTrialDeferred,\n enforcers,\n }: {\n request: PrepareSubscriptionDelegationRequest;\n chainId: Hex;\n delegateAddress: Hex;\n paymentAddress: 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 recipientAddress: paymentAddress,\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 paymentAddress: chain.paymentAddress,\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"]}
|
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
import type { SignedDelegation } from '@metamask/authenticated-user-storage';
|
|
2
2
|
import type { Hex } from '@metamask/utils';
|
|
3
3
|
import type { SubscriptionDelegationEnforcers } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Calldata offset of the `AllowedCalldata` prefix. Must match CHOMP's
|
|
6
|
+
* expected terms, which pin the selector together with the recipient.
|
|
7
|
+
* ref: https://github.com/consensys-vertical-apps/va-mmcx-chomp-api/blob/28b72ff16cb5d1b89a7a8ebeb0049a53015a516b/src/intent/delegation-validation.service.ts#L244
|
|
8
|
+
*/
|
|
9
|
+
export declare const TRANSFER_CALLDATA_PREFIX_START_INDEX = 0;
|
|
10
|
+
/**
|
|
11
|
+
* Encodes the ERC-20 `transfer(address,uint256)` selector followed by the
|
|
12
|
+
* ABI-encoded recipient (36 bytes), i.e. calldata up to the amount argument.
|
|
13
|
+
*
|
|
14
|
+
* @param recipient - Transfer recipient address.
|
|
15
|
+
* @returns The lowercased hex calldata prefix.
|
|
16
|
+
*/
|
|
17
|
+
export declare function encodeTransferToCalldataPrefix(recipient: Hex): Hex;
|
|
4
18
|
export type UnsignedSubscriptionDelegation = Omit<SignedDelegation, 'signature'>;
|
|
5
19
|
export type BuildSubscriptionCaveatsParams = {
|
|
6
20
|
enforcers: SubscriptionDelegationEnforcers;
|
|
7
21
|
delegateAddress: Hex;
|
|
22
|
+
/**
|
|
23
|
+
* Subscription treasury (pricing chain `paymentAddress`); the only allowed
|
|
24
|
+
* ERC-20 `transfer` recipient.
|
|
25
|
+
*/
|
|
26
|
+
recipientAddress: Hex;
|
|
8
27
|
tokenAddress: Hex;
|
|
9
28
|
periodAmount: bigint;
|
|
10
29
|
periodDuration: number;
|
|
@@ -12,17 +31,26 @@ export type BuildSubscriptionCaveatsParams = {
|
|
|
12
31
|
};
|
|
13
32
|
/**
|
|
14
33
|
* Builds the caveat list for a cash-subscription delegation:
|
|
15
|
-
* `
|
|
34
|
+
* `ERC20TokenPeriodTransfer(...)` then `AllowedCalldata` pinning the
|
|
35
|
+
* `transfer` recipient to the subscription treasury.
|
|
36
|
+
*
|
|
37
|
+
* `ValueLte(0)` is intentionally omitted: CHOMP rejects cash-subscription
|
|
38
|
+
* delegations whose enforcers are not exactly `ERC20PeriodTransferEnforcer`
|
|
39
|
+
* and `AllowedCalldataEnforcer`. Neither enforcer restricts native value, so
|
|
40
|
+
* this relies on the settlement token's `transfer` being non-payable. Re-add
|
|
41
|
+
* `ValueLte(0)` once CHOMP accepts it.
|
|
42
|
+
* ref: https://github.com/consensys-vertical-apps/va-mmcx-chomp-api/blob/28b72ff16cb5d1b89a7a8ebeb0049a53015a516b/src/intent/delegation-validation.service.ts#L174
|
|
16
43
|
*
|
|
17
44
|
* @param params - Enforcer addresses, parties, and period terms.
|
|
18
45
|
* @param params.enforcers - Delegation Framework enforcer addresses.
|
|
46
|
+
* @param params.recipientAddress - Subscription treasury address.
|
|
19
47
|
* @param params.tokenAddress - Subscription settlement token.
|
|
20
48
|
* @param params.periodAmount - Maximum token amount per period.
|
|
21
49
|
* @param params.periodDuration - Period length in seconds.
|
|
22
50
|
* @param params.startDate - Unix timestamp when transfers may begin.
|
|
23
51
|
* @returns Caveats in enforcer order.
|
|
24
52
|
*/
|
|
25
|
-
export declare function buildSubscriptionCaveats({ enforcers, tokenAddress, periodAmount, periodDuration, startDate, }: BuildSubscriptionCaveatsParams): SignedDelegation['caveats'];
|
|
53
|
+
export declare function buildSubscriptionCaveats({ enforcers, recipientAddress, tokenAddress, periodAmount, periodDuration, startDate, }: BuildSubscriptionCaveatsParams): SignedDelegation['caveats'];
|
|
26
54
|
export type BuildUnsignedSubscriptionDelegationParams = BuildSubscriptionCaveatsParams & {
|
|
27
55
|
delegatorAddress: Hex;
|
|
28
56
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"caveats.d.ts","sourceRoot":"","sources":["../../src/subscription-delegation/caveats.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"caveats.d.ts","sourceRoot":"","sources":["../../src/subscription-delegation/caveats.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAO7E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAG3C,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,YAAY,CAAC;AAMlE;;;;GAIG;AACH,eAAO,MAAM,oCAAoC,IAAI,CAAC;AAEtD;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,GAAG,GAAG,GAAG,CAWlE;AAED,MAAM,MAAM,8BAA8B,GAAG,IAAI,CAC/C,gBAAgB,EAChB,WAAW,CACZ,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC3C,SAAS,EAAE,+BAA+B,CAAC;IAC3C,eAAe,EAAE,GAAG,CAAC;IACrB;;;OAGG;IACH,gBAAgB,EAAE,GAAG,CAAC;IACtB,YAAY,EAAE,GAAG,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,SAAS,GACV,EAAE,8BAA8B,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAqB9D;AAED,MAAM,MAAM,yCAAyC,GACnD,8BAA8B,GAAG;IAC/B,gBAAgB,EAAE,GAAG,CAAC;IACtB;;OAEG;IACH,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ,CAAC;AAEJ;;;;;GAKG;AACH,wBAAgB,mCAAmC,CACjD,MAAM,EAAE,yCAAyC,GAChD,8BAA8B,CAYhC"}
|
|
@@ -1,24 +1,56 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { defaultAbiCoder, Interface } from '@ethersproject/abi';
|
|
2
|
+
import { ROOT_AUTHORITY, createAllowedCalldataTerms, createERC20TokenPeriodTransferTerms, } from '@metamask/delegation-core';
|
|
3
|
+
import { bytesToHex, remove0x } from '@metamask/utils';
|
|
4
|
+
import { SubscriptionDelegationServiceErrorMessage } from '../constants.js';
|
|
5
|
+
const ERC20_INTERFACE = new Interface([
|
|
6
|
+
'function transfer(address to, uint256 amount)',
|
|
7
|
+
]);
|
|
8
|
+
/**
|
|
9
|
+
* Calldata offset of the `AllowedCalldata` prefix. Must match CHOMP's
|
|
10
|
+
* expected terms, which pin the selector together with the recipient.
|
|
11
|
+
* ref: https://github.com/consensys-vertical-apps/va-mmcx-chomp-api/blob/28b72ff16cb5d1b89a7a8ebeb0049a53015a516b/src/intent/delegation-validation.service.ts#L244
|
|
12
|
+
*/
|
|
13
|
+
export const TRANSFER_CALLDATA_PREFIX_START_INDEX = 0;
|
|
14
|
+
/**
|
|
15
|
+
* Encodes the ERC-20 `transfer(address,uint256)` selector followed by the
|
|
16
|
+
* ABI-encoded recipient (36 bytes), i.e. calldata up to the amount argument.
|
|
17
|
+
*
|
|
18
|
+
* @param recipient - Transfer recipient address.
|
|
19
|
+
* @returns The lowercased hex calldata prefix.
|
|
20
|
+
*/
|
|
21
|
+
export function encodeTransferToCalldataPrefix(recipient) {
|
|
22
|
+
let encodedRecipient;
|
|
23
|
+
try {
|
|
24
|
+
encodedRecipient = defaultAbiCoder.encode(['address'], [recipient]);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new Error(`${SubscriptionDelegationServiceErrorMessage.InvalidRecipientAddress}: ${recipient}`);
|
|
28
|
+
}
|
|
29
|
+
return `${ERC20_INTERFACE.getSighash('transfer')}${remove0x(encodedRecipient)}`;
|
|
30
|
+
}
|
|
3
31
|
/**
|
|
4
32
|
* Builds the caveat list for a cash-subscription delegation:
|
|
5
|
-
* `
|
|
33
|
+
* `ERC20TokenPeriodTransfer(...)` then `AllowedCalldata` pinning the
|
|
34
|
+
* `transfer` recipient to the subscription treasury.
|
|
35
|
+
*
|
|
36
|
+
* `ValueLte(0)` is intentionally omitted: CHOMP rejects cash-subscription
|
|
37
|
+
* delegations whose enforcers are not exactly `ERC20PeriodTransferEnforcer`
|
|
38
|
+
* and `AllowedCalldataEnforcer`. Neither enforcer restricts native value, so
|
|
39
|
+
* this relies on the settlement token's `transfer` being non-payable. Re-add
|
|
40
|
+
* `ValueLte(0)` once CHOMP accepts it.
|
|
41
|
+
* ref: https://github.com/consensys-vertical-apps/va-mmcx-chomp-api/blob/28b72ff16cb5d1b89a7a8ebeb0049a53015a516b/src/intent/delegation-validation.service.ts#L174
|
|
6
42
|
*
|
|
7
43
|
* @param params - Enforcer addresses, parties, and period terms.
|
|
8
44
|
* @param params.enforcers - Delegation Framework enforcer addresses.
|
|
45
|
+
* @param params.recipientAddress - Subscription treasury address.
|
|
9
46
|
* @param params.tokenAddress - Subscription settlement token.
|
|
10
47
|
* @param params.periodAmount - Maximum token amount per period.
|
|
11
48
|
* @param params.periodDuration - Period length in seconds.
|
|
12
49
|
* @param params.startDate - Unix timestamp when transfers may begin.
|
|
13
50
|
* @returns Caveats in enforcer order.
|
|
14
51
|
*/
|
|
15
|
-
export function buildSubscriptionCaveats({ enforcers, tokenAddress, periodAmount, periodDuration, startDate, }) {
|
|
52
|
+
export function buildSubscriptionCaveats({ enforcers, recipientAddress, tokenAddress, periodAmount, periodDuration, startDate, }) {
|
|
16
53
|
return [
|
|
17
|
-
{
|
|
18
|
-
enforcer: enforcers.valueLte,
|
|
19
|
-
terms: createValueLteTerms({ maxValue: 0n }),
|
|
20
|
-
args: '0x',
|
|
21
|
-
},
|
|
22
54
|
{
|
|
23
55
|
enforcer: enforcers.erc20TokenPeriodTransfer,
|
|
24
56
|
terms: createERC20TokenPeriodTransferTerms({
|
|
@@ -29,6 +61,14 @@ export function buildSubscriptionCaveats({ enforcers, tokenAddress, periodAmount
|
|
|
29
61
|
}),
|
|
30
62
|
args: '0x',
|
|
31
63
|
},
|
|
64
|
+
{
|
|
65
|
+
enforcer: enforcers.allowedCalldata,
|
|
66
|
+
terms: createAllowedCalldataTerms({
|
|
67
|
+
startIndex: TRANSFER_CALLDATA_PREFIX_START_INDEX,
|
|
68
|
+
value: encodeTransferToCalldataPrefix(recipientAddress),
|
|
69
|
+
}),
|
|
70
|
+
args: '0x',
|
|
71
|
+
},
|
|
32
72
|
];
|
|
33
73
|
}
|
|
34
74
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"caveats.js","sourceRoot":"","sources":["../../src/subscription-delegation/caveats.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"caveats.js","sourceRoot":"","sources":["../../src/subscription-delegation/caveats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEhE,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,mCAAmC,GACpC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGvD,OAAO,EAAE,yCAAyC,EAAE,MAAM,iBAAiB,CAAC;AAG5E,MAAM,eAAe,GAAG,IAAI,SAAS,CAAC;IACpC,+CAA+C;CAChD,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,oCAAoC,GAAG,CAAC,CAAC;AAEtD;;;;;;GAMG;AACH,MAAM,UAAU,8BAA8B,CAAC,SAAc;IAC3D,IAAI,gBAAwB,CAAC;IAC7B,IAAI,CAAC;QACH,gBAAgB,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,GAAG,yCAAyC,CAAC,uBAAuB,KAAK,SAAS,EAAE,CACrF,CAAC;IACJ,CAAC;IAED,OAAO,GAAG,eAAe,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC,EAAS,CAAC;AACzF,CAAC;AAqBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,wBAAwB,CAAC,EACvC,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,SAAS,GACsB;IAC/B,OAAO;QACL;YACE,QAAQ,EAAE,SAAS,CAAC,wBAAwB;YAC5C,KAAK,EAAE,mCAAmC,CAAC;gBACzC,YAAY;gBACZ,YAAY;gBACZ,cAAc;gBACd,SAAS;aACV,CAAC;YACF,IAAI,EAAE,IAAI;SACX;QACD;YACE,QAAQ,EAAE,SAAS,CAAC,eAAe;YACnC,KAAK,EAAE,0BAA0B,CAAC;gBAChC,UAAU,EAAE,oCAAoC;gBAChD,KAAK,EAAE,8BAA8B,CAAC,gBAAgB,CAAC;aACxD,CAAC;YACF,IAAI,EAAE,IAAI;SACX;KACF,CAAC;AACJ,CAAC;AAWD;;;;;GAKG;AACH,MAAM,UAAU,mCAAmC,CACjD,MAAiD;IAEjD,MAAM,IAAI,GACR,MAAM,CAAC,IAAI;QACX,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAEpE,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,eAAe;QAChC,SAAS,EAAE,MAAM,CAAC,gBAAgB;QAClC,SAAS,EAAE,cAAc;QACzB,OAAO,EAAE,wBAAwB,CAAC,MAAM,CAAC;QACzC,IAAI;KACL,CAAC;AACJ,CAAC","sourcesContent":["import { defaultAbiCoder, Interface } from '@ethersproject/abi';\nimport type { SignedDelegation } from '@metamask/authenticated-user-storage';\nimport {\n ROOT_AUTHORITY,\n createAllowedCalldataTerms,\n createERC20TokenPeriodTransferTerms,\n} from '@metamask/delegation-core';\nimport { bytesToHex, remove0x } from '@metamask/utils';\nimport type { Hex } from '@metamask/utils';\n\nimport { SubscriptionDelegationServiceErrorMessage } from '../constants.js';\nimport type { SubscriptionDelegationEnforcers } from './types.js';\n\nconst ERC20_INTERFACE = new Interface([\n 'function transfer(address to, uint256 amount)',\n]);\n\n/**\n * Calldata offset of the `AllowedCalldata` prefix. Must match CHOMP's\n * expected terms, which pin the selector together with the recipient.\n * ref: https://github.com/consensys-vertical-apps/va-mmcx-chomp-api/blob/28b72ff16cb5d1b89a7a8ebeb0049a53015a516b/src/intent/delegation-validation.service.ts#L244\n */\nexport const TRANSFER_CALLDATA_PREFIX_START_INDEX = 0;\n\n/**\n * Encodes the ERC-20 `transfer(address,uint256)` selector followed by the\n * ABI-encoded recipient (36 bytes), i.e. calldata up to the amount argument.\n *\n * @param recipient - Transfer recipient address.\n * @returns The lowercased hex calldata prefix.\n */\nexport function encodeTransferToCalldataPrefix(recipient: Hex): Hex {\n let encodedRecipient: string;\n try {\n encodedRecipient = defaultAbiCoder.encode(['address'], [recipient]);\n } catch {\n throw new Error(\n `${SubscriptionDelegationServiceErrorMessage.InvalidRecipientAddress}: ${recipient}`,\n );\n }\n\n return `${ERC20_INTERFACE.getSighash('transfer')}${remove0x(encodedRecipient)}` as Hex;\n}\n\nexport type UnsignedSubscriptionDelegation = Omit<\n SignedDelegation,\n 'signature'\n>;\n\nexport type BuildSubscriptionCaveatsParams = {\n enforcers: SubscriptionDelegationEnforcers;\n delegateAddress: Hex;\n /**\n * Subscription treasury (pricing chain `paymentAddress`); the only allowed\n * ERC-20 `transfer` recipient.\n */\n recipientAddress: Hex;\n tokenAddress: Hex;\n periodAmount: bigint;\n periodDuration: number;\n startDate: number;\n};\n\n/**\n * Builds the caveat list for a cash-subscription delegation:\n * `ERC20TokenPeriodTransfer(...)` then `AllowedCalldata` pinning the\n * `transfer` recipient to the subscription treasury.\n *\n * `ValueLte(0)` is intentionally omitted: CHOMP rejects cash-subscription\n * delegations whose enforcers are not exactly `ERC20PeriodTransferEnforcer`\n * and `AllowedCalldataEnforcer`. Neither enforcer restricts native value, so\n * this relies on the settlement token's `transfer` being non-payable. Re-add\n * `ValueLte(0)` once CHOMP accepts it.\n * ref: https://github.com/consensys-vertical-apps/va-mmcx-chomp-api/blob/28b72ff16cb5d1b89a7a8ebeb0049a53015a516b/src/intent/delegation-validation.service.ts#L174\n *\n * @param params - Enforcer addresses, parties, and period terms.\n * @param params.enforcers - Delegation Framework enforcer addresses.\n * @param params.recipientAddress - Subscription treasury address.\n * @param params.tokenAddress - Subscription settlement token.\n * @param params.periodAmount - Maximum token amount per period.\n * @param params.periodDuration - Period length in seconds.\n * @param params.startDate - Unix timestamp when transfers may begin.\n * @returns Caveats in enforcer order.\n */\nexport function buildSubscriptionCaveats({\n enforcers,\n recipientAddress,\n tokenAddress,\n periodAmount,\n periodDuration,\n startDate,\n}: BuildSubscriptionCaveatsParams): SignedDelegation['caveats'] {\n return [\n {\n enforcer: enforcers.erc20TokenPeriodTransfer,\n terms: createERC20TokenPeriodTransferTerms({\n tokenAddress,\n periodAmount,\n periodDuration,\n startDate,\n }),\n args: '0x',\n },\n {\n enforcer: enforcers.allowedCalldata,\n terms: createAllowedCalldataTerms({\n startIndex: TRANSFER_CALLDATA_PREFIX_START_INDEX,\n value: encodeTransferToCalldataPrefix(recipientAddress),\n }),\n args: '0x',\n },\n ];\n}\n\nexport type BuildUnsignedSubscriptionDelegationParams =\n BuildSubscriptionCaveatsParams & {\n delegatorAddress: Hex;\n /**\n * Optional salt for tests. When omitted, a random 32-byte salt is generated.\n */\n salt?: Hex;\n };\n\n/**\n * Builds an unsigned root cash-subscription delegation.\n *\n * @param params - Delegation parties, enforcers, and period terms.\n * @returns An unsigned delegation ready for signing.\n */\nexport function buildUnsignedSubscriptionDelegation(\n params: BuildUnsignedSubscriptionDelegationParams,\n): UnsignedSubscriptionDelegation {\n const salt =\n params.salt ??\n bytesToHex(globalThis.crypto.getRandomValues(new Uint8Array(32)));\n\n return {\n delegate: params.delegateAddress,\n delegator: params.delegatorAddress,\n authority: ROOT_AUTHORITY,\n caveats: buildSubscriptionCaveats(params),\n salt,\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../src/subscription-delegation/fingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAK/E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../src/subscription-delegation/fingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAK/E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAM3C,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,YAAY,CAAC;AAGlE,MAAM,MAAM,iCAAiC,GAAG;IAC9C,gBAAgB,EAAE,GAAG,CAAC;IACtB,eAAe,EAAE,GAAG,CAAC;IACrB,gBAAgB,EAAE,GAAG,CAAC;IACtB,OAAO,EAAE,GAAG,CAAC;IACb,YAAY,EAAE,GAAG,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,+BAA+B,CAAC;CAC5C,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAErE;AAED;;;;;;;;;GASG;AACH,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,iCAAiC,GAC1C,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAgFxC;AA8BD;;;;;;;;;GASG;AACH,wBAAgB,wCAAwC,CACtD,OAAO,EAAE,SAAS,kBAAkB,EAAE,EACtC,SAAS,EAAE,+BAA+B,GACzC,kBAAkB,GAAG,SAAS,CAYhC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { decodeAllowedCalldataTerms, decodeERC20TokenPeriodTransferTerms, } from '@metamask/delegation-core';
|
|
2
|
+
import { encodeTransferToCalldataPrefix, TRANSFER_CALLDATA_PREFIX_START_INDEX, } from './caveats.js';
|
|
2
3
|
import { CASH_SUBSCRIPTION_DELEGATION_TYPE } from './types.js';
|
|
3
4
|
/**
|
|
4
5
|
* Case-insensitive hex equality.
|
|
@@ -37,18 +38,21 @@ export function makeMatchesSubscriptionDelegation(expected) {
|
|
|
37
38
|
if (!equalsIgnoreCase(entry.metadata.tokenAddress, expected.tokenAddress)) {
|
|
38
39
|
return false;
|
|
39
40
|
}
|
|
41
|
+
// CHOMP requires exactly these two enforcers, so stored delegations with
|
|
42
|
+
// extra caveats (e.g. a legacy `ValueLte`) cannot be reused.
|
|
40
43
|
const { caveats } = entry.signedDelegation;
|
|
41
|
-
if (caveats.length
|
|
44
|
+
if (caveats.length !== 2) {
|
|
42
45
|
return false;
|
|
43
46
|
}
|
|
44
|
-
const valueLteCaveat = caveats.find((caveat) => equalsIgnoreCase(caveat.enforcer, expected.enforcers.valueLte));
|
|
45
47
|
const periodCaveat = caveats.find((caveat) => equalsIgnoreCase(caveat.enforcer, expected.enforcers.erc20TokenPeriodTransfer));
|
|
46
|
-
|
|
48
|
+
const allowedCalldataCaveat = caveats.find((caveat) => equalsIgnoreCase(caveat.enforcer, expected.enforcers.allowedCalldata));
|
|
49
|
+
if (!periodCaveat || !allowedCalldataCaveat) {
|
|
47
50
|
return false;
|
|
48
51
|
}
|
|
49
52
|
try {
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
53
|
+
const calldataTerms = decodeAllowedCalldataTerms(allowedCalldataCaveat.terms);
|
|
54
|
+
if (calldataTerms.startIndex !== TRANSFER_CALLDATA_PREFIX_START_INDEX ||
|
|
55
|
+
!equalsIgnoreCase(calldataTerms.value, encodeTransferToCalldataPrefix(expected.recipientAddress))) {
|
|
52
56
|
return false;
|
|
53
57
|
}
|
|
54
58
|
const periodTerms = decodeERC20TokenPeriodTransferTerms(periodCaveat.terms);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/subscription-delegation/fingerprint.ts"],"names":[],"mappings":"AACA,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/subscription-delegation/fingerprint.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DelegationResponse } from '@metamask/authenticated-user-storage';\nimport {\n decodeAllowedCalldataTerms,\n decodeERC20TokenPeriodTransferTerms,\n} from '@metamask/delegation-core';\nimport type { Hex } from '@metamask/utils';\n\nimport {\n encodeTransferToCalldataPrefix,\n TRANSFER_CALLDATA_PREFIX_START_INDEX,\n} from './caveats.js';\nimport type { SubscriptionDelegationEnforcers } from './types.js';\nimport { CASH_SUBSCRIPTION_DELEGATION_TYPE } from './types.js';\n\nexport type SubscriptionDelegationFingerprint = {\n delegatorAddress: Hex;\n delegateAddress: Hex;\n recipientAddress: Hex;\n chainId: Hex;\n tokenAddress: Hex;\n periodAmount: bigint;\n periodDuration: number;\n /**\n * Current unix timestamp in seconds. Used to classify a stored period\n * `startDate` as immediately redeemable (`<= now`) vs trial-deferred (`> now`).\n */\n nowSeconds: number;\n /**\n * When true, only a still-deferred period start (`> nowSeconds`) matches.\n * When false, only an immediately redeemable start (`<= nowSeconds`) matches.\n */\n isTrialDeferred: boolean;\n enforcers: SubscriptionDelegationEnforcers;\n};\n\n/**\n * Case-insensitive hex equality.\n *\n * @param left - First hex value.\n * @param right - Second hex value.\n * @returns Whether the values are equal ignoring case.\n */\nexport function equalsIgnoreCase(left: string, right: string): boolean {\n return left.toLowerCase() === right.toLowerCase();\n}\n\n/**\n * Builds a predicate that matches a stored AUS delegation to the semantic\n * cash-subscription fingerprint. Salt is ignored so a previously signed\n * equivalent permission can be reused. Period `startDate` is compared only\n * as trial-deferred (`> nowSeconds`) vs immediately redeemable (`<= nowSeconds`)\n * so a positive-length trial cannot reuse a live permission and vice versa.\n *\n * @param expected - Semantic fields that must match.\n * @returns Predicate over {@link DelegationResponse}.\n */\nexport function makeMatchesSubscriptionDelegation(\n expected: SubscriptionDelegationFingerprint,\n): (entry: DelegationResponse) => boolean {\n return (entry) => {\n if (entry.metadata.type !== CASH_SUBSCRIPTION_DELEGATION_TYPE) {\n return false;\n }\n if (\n !equalsIgnoreCase(\n entry.signedDelegation.delegator,\n expected.delegatorAddress,\n )\n ) {\n return false;\n }\n if (\n !equalsIgnoreCase(\n entry.signedDelegation.delegate,\n expected.delegateAddress,\n )\n ) {\n return false;\n }\n if (!equalsIgnoreCase(entry.metadata.chainIdHex, expected.chainId)) {\n return false;\n }\n if (!equalsIgnoreCase(entry.metadata.tokenAddress, expected.tokenAddress)) {\n return false;\n }\n\n // CHOMP requires exactly these two enforcers, so stored delegations with\n // extra caveats (e.g. a legacy `ValueLte`) cannot be reused.\n const { caveats } = entry.signedDelegation;\n if (caveats.length !== 2) {\n return false;\n }\n\n const periodCaveat = caveats.find((caveat) =>\n equalsIgnoreCase(\n caveat.enforcer,\n expected.enforcers.erc20TokenPeriodTransfer,\n ),\n );\n const allowedCalldataCaveat = caveats.find((caveat) =>\n equalsIgnoreCase(caveat.enforcer, expected.enforcers.allowedCalldata),\n );\n if (!periodCaveat || !allowedCalldataCaveat) {\n return false;\n }\n\n try {\n const calldataTerms = decodeAllowedCalldataTerms(\n allowedCalldataCaveat.terms,\n );\n if (\n calldataTerms.startIndex !== TRANSFER_CALLDATA_PREFIX_START_INDEX ||\n !equalsIgnoreCase(\n calldataTerms.value,\n encodeTransferToCalldataPrefix(expected.recipientAddress),\n )\n ) {\n return false;\n }\n\n const periodTerms = decodeERC20TokenPeriodTransferTerms(\n periodCaveat.terms,\n );\n const storedStartDate = Number(periodTerms.startDate);\n const isStoredDeferred = storedStartDate > expected.nowSeconds;\n if (expected.isTrialDeferred !== isStoredDeferred) {\n return false;\n }\n\n return (\n equalsIgnoreCase(periodTerms.tokenAddress, expected.tokenAddress) &&\n periodTerms.periodAmount === expected.periodAmount &&\n Number(periodTerms.periodDuration) === expected.periodDuration\n );\n } catch {\n return false;\n }\n };\n}\n\n/**\n * Reads the ERC-20 period-transfer `startDate` from a stored delegation.\n *\n * @param entry - Stored AUS delegation.\n * @param enforcers - Enforcer addresses used to locate the period caveat.\n * @returns Unix timestamp in seconds, or `undefined` when the caveat is\n * missing or malformed.\n */\nfunction getSubscriptionDelegationStartDate(\n entry: DelegationResponse,\n enforcers: SubscriptionDelegationEnforcers,\n): number | undefined {\n const periodCaveat = entry.signedDelegation.caveats.find((caveat) =>\n equalsIgnoreCase(caveat.enforcer, enforcers.erc20TokenPeriodTransfer),\n );\n if (!periodCaveat) {\n return undefined;\n }\n\n try {\n return Number(\n decodeERC20TokenPeriodTransferTerms(periodCaveat.terms).startDate,\n );\n } catch {\n return undefined;\n }\n}\n\n/**\n * Returns the fingerprint match with the latest period `startDate`.\n *\n * Equal `startDate`s keep list order. A delegation whose `startDate` cannot\n * be read sorts last.\n *\n * @param matches - Fingerprint-matching AUS delegations, in list order.\n * @param enforcers - Enforcer addresses used to read `startDate`.\n * @returns The preferred match, or `undefined` when `matches` is empty.\n */\nexport function pickLatestMatchingSubscriptionDelegation(\n matches: readonly DelegationResponse[],\n enforcers: SubscriptionDelegationEnforcers,\n): DelegationResponse | undefined {\n const byStartDateDescending = [...matches].sort((left, right) => {\n const leftStart =\n getSubscriptionDelegationStartDate(left, enforcers) ??\n Number.NEGATIVE_INFINITY;\n const rightStart =\n getSubscriptionDelegationStartDate(right, enforcers) ??\n Number.NEGATIVE_INFINITY;\n return rightStart - leftStart;\n });\n\n return byStartDateDescending[0];\n}\n"]}
|
|
@@ -66,7 +66,7 @@ export type MoneyAccountBalanceCheckResult = {
|
|
|
66
66
|
* Delegation Framework enforcers used by cash-subscription delegations.
|
|
67
67
|
*/
|
|
68
68
|
export type SubscriptionDelegationEnforcers = {
|
|
69
|
-
valueLte: Hex;
|
|
70
69
|
erc20TokenPeriodTransfer: Hex;
|
|
70
|
+
allowedCalldata: Hex;
|
|
71
71
|
};
|
|
72
72
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/subscription-delegation/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,iCAAiC,EAAG,mBAA4B,CAAC;AAE9E;;;;;;;;GAQG;AACH,MAAM,MAAM,oCAAoC,GAAG;IACjD,OAAO,EAAE,OAAO,aAAa,CAAC,kBAAkB,CAAC;IACjD,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,YAAY,EAAE,GAAG,CAAC;IAClB;;;OAGG;IACH,gBAAgB,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,8BAA8B,GAAG;IAC3C,cAAc,EAAE,GAAG,CAAC;IACpB,WAAW,EAAE,SAAS,GAAG,QAAQ,CAAC;CACnC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,+BAA+B,GAAG,IAAI,CAChD,oCAAoC,EACpC,SAAS,GAAG,mBAAmB,GAAG,cAAc,CACjD,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,8BAA8B,GAAG;IAC3C,oBAAoB,EAAE,OAAO,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/subscription-delegation/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,iCAAiC,EAAG,mBAA4B,CAAC;AAE9E;;;;;;;;GAQG;AACH,MAAM,MAAM,oCAAoC,GAAG;IACjD,OAAO,EAAE,OAAO,aAAa,CAAC,kBAAkB,CAAC;IACjD,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,YAAY,EAAE,GAAG,CAAC;IAClB;;;OAGG;IACH,gBAAgB,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,8BAA8B,GAAG;IAC3C,cAAc,EAAE,GAAG,CAAC;IACpB,WAAW,EAAE,SAAS,GAAG,QAAQ,CAAC;CACnC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,+BAA+B,GAAG,IAAI,CAChD,oCAAoC,EACpC,SAAS,GAAG,mBAAmB,GAAG,cAAc,CACjD,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,8BAA8B,GAAG;IAC3C,oBAAoB,EAAE,OAAO,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,wBAAwB,EAAE,GAAG,CAAC;IAC9B,eAAe,EAAE,GAAG,CAAC;CACtB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/subscription-delegation/types.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,mBAA4B,CAAC","sourcesContent":["import type { Hex } from '@metamask/utils';\n\nimport { PRODUCT_TYPES } from '../types.js';\nimport type { RecurringInterval } from '../types.js';\n\n/**\n * Storage / CHOMP metadata type for cash-subscription delegations.\n *\n * Defined locally so this package does not depend on an unreleased\n * `@metamask/chomp-api-service` intent type. Production CHOMP intent\n * registration still requires a follow-up chomp-api-service release that\n * accepts `'cash-subscription'`.\n */\nexport const CASH_SUBSCRIPTION_DELEGATION_TYPE = 'cash-subscription' as const;\n\n/**\n * Request to prepare a cash-subscription delegation.\n *\n * The service resolves all amount, token, delegate, and trial-duration fields\n * from authoritative pricing held by `SubscriptionController`.\n *\n * Only Money Account Plus is supported; Shield continues to use ERC-20\n * approval rather than delegation.\n */\nexport type PrepareSubscriptionDelegationRequest = {\n product: typeof PRODUCT_TYPES.MONEY_ACCOUNT_PLUS;\n recurringInterval: RecurringInterval;\n payerAddress: Hex;\n /**\n * Whether the user selected the pricing trial. Pricing `trialPeriodDays`\n * only affects the delegation start date when this is true.\n */\n isTrialRequested: boolean;\n /**\n * When true, gates preparation on a sufficient Money Account balance\n * (`unitAmount × minBillingCyclesForBalance` in mUSD) before side effects.\n */\n checkBalance?: boolean;\n /**\n * When true, skips CHOMP verify/intent interactions. Required for alpha\n * until `@metamask/chomp-api-service` accepts `'cash-subscription'` intent\n * metadata. Defaults to false (production path; unsupported until that\n * follow-up release).\n */\n skipChompInteractions?: boolean;\n};\n\n/**\n * Result of {@link SubscriptionDelegationService.prepareDelegation}.\n */\nexport type PreparedSubscriptionDelegation = {\n delegationHash: Hex;\n disposition: 'created' | 'reused';\n};\n\n/**\n * Request to check whether a Money Account holds enough convertible mUSD value\n * to cover the subscription funding requirement.\n */\nexport type MoneyAccountBalanceCheckRequest = Pick<\n PrepareSubscriptionDelegationRequest,\n 'product' | 'recurringInterval' | 'payerAddress'\n>;\n\n/**\n * Result of {@link SubscriptionDelegationService.checkMoneyAccountBalance}.\n * `balance` and `requiredBalance` are mUSD base units (6 decimals).\n */\nexport type MoneyAccountBalanceCheckResult = {\n hasSufficientBalance: boolean;\n balance: string;\n requiredBalance: string;\n};\n\n/**\n * Delegation Framework enforcers used by cash-subscription delegations.\n */\nexport type SubscriptionDelegationEnforcers = {\n
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/subscription-delegation/types.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,mBAA4B,CAAC","sourcesContent":["import type { Hex } from '@metamask/utils';\n\nimport { PRODUCT_TYPES } from '../types.js';\nimport type { RecurringInterval } from '../types.js';\n\n/**\n * Storage / CHOMP metadata type for cash-subscription delegations.\n *\n * Defined locally so this package does not depend on an unreleased\n * `@metamask/chomp-api-service` intent type. Production CHOMP intent\n * registration still requires a follow-up chomp-api-service release that\n * accepts `'cash-subscription'`.\n */\nexport const CASH_SUBSCRIPTION_DELEGATION_TYPE = 'cash-subscription' as const;\n\n/**\n * Request to prepare a cash-subscription delegation.\n *\n * The service resolves all amount, token, delegate, and trial-duration fields\n * from authoritative pricing held by `SubscriptionController`.\n *\n * Only Money Account Plus is supported; Shield continues to use ERC-20\n * approval rather than delegation.\n */\nexport type PrepareSubscriptionDelegationRequest = {\n product: typeof PRODUCT_TYPES.MONEY_ACCOUNT_PLUS;\n recurringInterval: RecurringInterval;\n payerAddress: Hex;\n /**\n * Whether the user selected the pricing trial. Pricing `trialPeriodDays`\n * only affects the delegation start date when this is true.\n */\n isTrialRequested: boolean;\n /**\n * When true, gates preparation on a sufficient Money Account balance\n * (`unitAmount × minBillingCyclesForBalance` in mUSD) before side effects.\n */\n checkBalance?: boolean;\n /**\n * When true, skips CHOMP verify/intent interactions. Required for alpha\n * until `@metamask/chomp-api-service` accepts `'cash-subscription'` intent\n * metadata. Defaults to false (production path; unsupported until that\n * follow-up release).\n */\n skipChompInteractions?: boolean;\n};\n\n/**\n * Result of {@link SubscriptionDelegationService.prepareDelegation}.\n */\nexport type PreparedSubscriptionDelegation = {\n delegationHash: Hex;\n disposition: 'created' | 'reused';\n};\n\n/**\n * Request to check whether a Money Account holds enough convertible mUSD value\n * to cover the subscription funding requirement.\n */\nexport type MoneyAccountBalanceCheckRequest = Pick<\n PrepareSubscriptionDelegationRequest,\n 'product' | 'recurringInterval' | 'payerAddress'\n>;\n\n/**\n * Result of {@link SubscriptionDelegationService.checkMoneyAccountBalance}.\n * `balance` and `requiredBalance` are mUSD base units (6 decimals).\n */\nexport type MoneyAccountBalanceCheckResult = {\n hasSufficientBalance: boolean;\n balance: string;\n requiredBalance: string;\n};\n\n/**\n * Delegation Framework enforcers used by cash-subscription delegations.\n */\nexport type SubscriptionDelegationEnforcers = {\n erc20TokenPeriodTransfer: Hex;\n allowedCalldata: Hex;\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask-previews/subscription-controller",
|
|
3
|
-
"version": "10.0.1-preview-
|
|
3
|
+
"version": "10.0.1-preview-c4789fdf4",
|
|
4
4
|
"description": "Handle user subscription",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Ethereum",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
+
"@ethersproject/abi": "^5.7.0",
|
|
53
54
|
"@metamask/authenticated-user-storage": "^4.1.0",
|
|
54
55
|
"@metamask/base-controller": "^10.0.0",
|
|
55
56
|
"@metamask/base-data-service": "^2.0.0",
|