@metamask-previews/money-account-upgrade-controller 3.0.2-preview-00a181c → 3.0.2-preview-a302de5c3
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 +13 -0
- package/dist/MoneyAccountUpgradeController-method-action-types.cjs.map +1 -1
- package/dist/MoneyAccountUpgradeController-method-action-types.d.cts +11 -0
- package/dist/MoneyAccountUpgradeController-method-action-types.d.cts.map +1 -1
- package/dist/MoneyAccountUpgradeController-method-action-types.d.mts +11 -0
- package/dist/MoneyAccountUpgradeController-method-action-types.d.mts.map +1 -1
- package/dist/MoneyAccountUpgradeController-method-action-types.mjs.map +1 -1
- package/dist/MoneyAccountUpgradeController.cjs +212 -41
- package/dist/MoneyAccountUpgradeController.cjs.map +1 -1
- package/dist/MoneyAccountUpgradeController.d.cts +94 -18
- package/dist/MoneyAccountUpgradeController.d.cts.map +1 -1
- package/dist/MoneyAccountUpgradeController.d.mts +94 -18
- package/dist/MoneyAccountUpgradeController.d.mts.map +1 -1
- package/dist/MoneyAccountUpgradeController.mjs +213 -42
- package/dist/MoneyAccountUpgradeController.mjs.map +1 -1
- package/dist/errors.cjs +13 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +8 -0
- package/dist/errors.d.cts.map +1 -1
- package/dist/errors.d.mts +8 -0
- package/dist/errors.d.mts.map +1 -1
- package/dist/errors.mjs +11 -0
- package/dist/errors.mjs.map +1 -1
- package/dist/index.cjs +2 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,8 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Add a public `sync()` method that re-evaluates the bootstrap gates against live state, for clients whose `isEnabled` hook reads client-only signals (onboarding, preferences) that should also re-trigger the bootstrap ([#10072](https://github.com/MetaMask/core/pull/10072))
|
|
13
|
+
- Add `MissingMoneyAccountVaultConfigError`, reported through the `onBootstrapError` hook (once per controller lifetime) when the enable flag is on but `moneyAccountVaultConfig` is unserved or malformed ([#10072](https://github.com/MetaMask/core/pull/10072))
|
|
14
|
+
|
|
10
15
|
### Changed
|
|
11
16
|
|
|
17
|
+
- **BREAKING:** The controller now owns its bootstrap: it subscribes to `RemoteFeatureFlagController:stateChanged` and `KeyringController:stateChanged`, gates on an unlocked wallet with an HD keyring, parses the `moneyAccountVaultConfig` remote feature flag, and runs a serialized bootstrap that re-checks its gates across `await` points and re-runs when the vault config changes ([#10072](https://github.com/MetaMask/core/pull/10072))
|
|
18
|
+
- `init()` is now the no-argument lifecycle entry point (subscribe and first sync), to be called once after all controllers and services the messenger reaches are constructed. The former `init({ chainId, boringVaultAddress })` config-arming routine is internal and reads the chain id and boring vault from the flag; clients that called it must remove that orchestration.
|
|
19
|
+
- The constructor requires a `hooks` option carrying the client-specific parts of the bootstrap: `isEnabled(remoteFeatureFlags)` (required — version-gated flag evaluation depends on the client version, and clients may add gates such as a basic-functionality toggle), plus optional `isEligible()` (an async pre-bootstrap gate, e.g. fail-closed geolocation), `ensureChainConfigured(vaultConfig)` (client-specific network adding), and `onBootstrapError(error)`.
|
|
20
|
+
- The messenger must now allow the `RemoteFeatureFlagController:getState` and `KeyringController:getState` actions and the `RemoteFeatureFlagController:stateChanged` and `KeyringController:stateChanged` events.
|
|
21
|
+
- **BREAKING:** `upgradeAccount()` now waits for the in-flight bootstrap chain to settle (including runs scheduled while waiting) instead of throwing, and throws a new not-bootstrapped error message when no bootstrap has armed a config or the wallet is locked. Scheduling a bootstrap for a changed vault config — or `isEnabled` flipping off — disarms the previous config, so an upgrade can never sign against a superseded vault, including after a failed re-bootstrap ([#10072](https://github.com/MetaMask/core/pull/10072))
|
|
22
|
+
- The armed config is re-checked before every step; if it is disarmed or superseded while the sequence is running, `upgradeAccount()` throws an upgrade-aborted error before the next step signs anything and records nothing
|
|
23
|
+
- An `onBootstrapError` hook that throws is contained: the failed bootstrap is still forgotten and retried on the next trigger, and the throw does not escape `init()` or `sync()`
|
|
24
|
+
- Add `@metamask/money-account-utils` and `@metamask/remote-feature-flag-controller` as dependencies ([#10072](https://github.com/MetaMask/core/pull/10072))
|
|
12
25
|
- Bump `@metamask/authenticated-user-storage` from `^3.0.1` to `^3.0.2` ([#9972](https://github.com/MetaMask/core/pull/9972))
|
|
13
26
|
- Bump `@metamask/chomp-api-service` from `^4.0.0` to `^4.0.1` ([#9972](https://github.com/MetaMask/core/pull/9972))
|
|
14
27
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MoneyAccountUpgradeController-method-action-types.cjs","sourceRoot":"","sources":["../src/MoneyAccountUpgradeController-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { MoneyAccountUpgradeController } from './MoneyAccountUpgradeController.js';\n\n/**\n * Runs each step in the upgrade sequence in order. A step that reports\n * `'already-done'` is skipped without performing any action; a step that\n * reports `'completed'` has performed its action. An error thrown by any\n * step halts the sequence and is re-thrown wrapped in a\n * {@link MoneyAccountUpgradeStepError} that records which step failed (the\n * original error is preserved as `cause`).\n *\n * A run that completes is recorded in state (keyed by lowercased address,\n * fingerprinted against the active config); subsequent calls for a\n * recorded account return immediately without running any steps. If the\n * active config no longer matches the recorded fingerprint, the sequence\n * re-runs.\n *\n * @param address - The Money Account address to upgrade.\n */\nexport type MoneyAccountUpgradeControllerUpgradeAccountAction = {\n type: `MoneyAccountUpgradeController:upgradeAccount`;\n handler: MoneyAccountUpgradeController['upgradeAccount'];\n};\n\n/**\n * Union of all MoneyAccountUpgradeController action types.\n */\nexport type MoneyAccountUpgradeControllerMethodActions =\n MoneyAccountUpgradeControllerUpgradeAccountAction;\n"]}
|
|
1
|
+
{"version":3,"file":"MoneyAccountUpgradeController-method-action-types.cjs","sourceRoot":"","sources":["../src/MoneyAccountUpgradeController-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { MoneyAccountUpgradeController } from './MoneyAccountUpgradeController.js';\n\n/**\n * Runs each step in the upgrade sequence in order. A step that reports\n * `'already-done'` is skipped without performing any action; a step that\n * reports `'completed'` has performed its action. An error thrown by any\n * step halts the sequence and is re-thrown wrapped in a\n * {@link MoneyAccountUpgradeStepError} that records which step failed (the\n * original error is preserved as `cause`).\n *\n * A run that completes is recorded in state (keyed by lowercased address,\n * fingerprinted against the active config); subsequent calls for a\n * recorded account return immediately without running any steps. If the\n * active config no longer matches the recorded fingerprint, the sequence\n * re-runs.\n *\n * A call that arrives while the bootstrap chain is still in flight —\n * including runs scheduled while waiting — waits for it to settle rather\n * than failing, so the upgrade always runs against the latest armed\n * config. Scheduling a bootstrap for a changed vault config disarms the\n * previous one, so it throws when no bootstrap has armed a config (feature\n * disabled or the last bootstrap failed) or when the wallet is locked.\n *\n * The armed config is re-checked before every step: if a sync disarms or\n * supersedes it while the sequence is running, the sequence aborts before\n * the next step signs anything, and nothing is recorded.\n *\n * @param address - The Money Account address to upgrade.\n */\nexport type MoneyAccountUpgradeControllerUpgradeAccountAction = {\n type: `MoneyAccountUpgradeController:upgradeAccount`;\n handler: MoneyAccountUpgradeController['upgradeAccount'];\n};\n\n/**\n * Union of all MoneyAccountUpgradeController action types.\n */\nexport type MoneyAccountUpgradeControllerMethodActions =\n MoneyAccountUpgradeControllerUpgradeAccountAction;\n"]}
|
|
@@ -17,6 +17,17 @@ import type { MoneyAccountUpgradeController } from "./MoneyAccountUpgradeControl
|
|
|
17
17
|
* active config no longer matches the recorded fingerprint, the sequence
|
|
18
18
|
* re-runs.
|
|
19
19
|
*
|
|
20
|
+
* A call that arrives while the bootstrap chain is still in flight —
|
|
21
|
+
* including runs scheduled while waiting — waits for it to settle rather
|
|
22
|
+
* than failing, so the upgrade always runs against the latest armed
|
|
23
|
+
* config. Scheduling a bootstrap for a changed vault config disarms the
|
|
24
|
+
* previous one, so it throws when no bootstrap has armed a config (feature
|
|
25
|
+
* disabled or the last bootstrap failed) or when the wallet is locked.
|
|
26
|
+
*
|
|
27
|
+
* The armed config is re-checked before every step: if a sync disarms or
|
|
28
|
+
* supersedes it while the sequence is running, the sequence aborts before
|
|
29
|
+
* the next step signs anything, and nothing is recorded.
|
|
30
|
+
*
|
|
20
31
|
* @param address - The Money Account address to upgrade.
|
|
21
32
|
*/
|
|
22
33
|
export type MoneyAccountUpgradeControllerUpgradeAccountAction = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MoneyAccountUpgradeController-method-action-types.d.cts","sourceRoot":"","sources":["../src/MoneyAccountUpgradeController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,6BAA6B,EAAE,4CAA2C;AAExF
|
|
1
|
+
{"version":3,"file":"MoneyAccountUpgradeController-method-action-types.d.cts","sourceRoot":"","sources":["../src/MoneyAccountUpgradeController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,6BAA6B,EAAE,4CAA2C;AAExF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,iDAAiD,GAAG;IAC9D,IAAI,EAAE,8CAA8C,CAAC;IACrD,OAAO,EAAE,6BAA6B,CAAC,gBAAgB,CAAC,CAAC;CAC1D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,0CAA0C,GACpD,iDAAiD,CAAC"}
|
|
@@ -17,6 +17,17 @@ import type { MoneyAccountUpgradeController } from "./MoneyAccountUpgradeControl
|
|
|
17
17
|
* active config no longer matches the recorded fingerprint, the sequence
|
|
18
18
|
* re-runs.
|
|
19
19
|
*
|
|
20
|
+
* A call that arrives while the bootstrap chain is still in flight —
|
|
21
|
+
* including runs scheduled while waiting — waits for it to settle rather
|
|
22
|
+
* than failing, so the upgrade always runs against the latest armed
|
|
23
|
+
* config. Scheduling a bootstrap for a changed vault config disarms the
|
|
24
|
+
* previous one, so it throws when no bootstrap has armed a config (feature
|
|
25
|
+
* disabled or the last bootstrap failed) or when the wallet is locked.
|
|
26
|
+
*
|
|
27
|
+
* The armed config is re-checked before every step: if a sync disarms or
|
|
28
|
+
* supersedes it while the sequence is running, the sequence aborts before
|
|
29
|
+
* the next step signs anything, and nothing is recorded.
|
|
30
|
+
*
|
|
20
31
|
* @param address - The Money Account address to upgrade.
|
|
21
32
|
*/
|
|
22
33
|
export type MoneyAccountUpgradeControllerUpgradeAccountAction = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MoneyAccountUpgradeController-method-action-types.d.mts","sourceRoot":"","sources":["../src/MoneyAccountUpgradeController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,6BAA6B,EAAE,4CAA2C;AAExF
|
|
1
|
+
{"version":3,"file":"MoneyAccountUpgradeController-method-action-types.d.mts","sourceRoot":"","sources":["../src/MoneyAccountUpgradeController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,6BAA6B,EAAE,4CAA2C;AAExF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,iDAAiD,GAAG;IAC9D,IAAI,EAAE,8CAA8C,CAAC;IACrD,OAAO,EAAE,6BAA6B,CAAC,gBAAgB,CAAC,CAAC;CAC1D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,0CAA0C,GACpD,iDAAiD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MoneyAccountUpgradeController-method-action-types.mjs","sourceRoot":"","sources":["../src/MoneyAccountUpgradeController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { MoneyAccountUpgradeController } from './MoneyAccountUpgradeController.js';\n\n/**\n * Runs each step in the upgrade sequence in order. A step that reports\n * `'already-done'` is skipped without performing any action; a step that\n * reports `'completed'` has performed its action. An error thrown by any\n * step halts the sequence and is re-thrown wrapped in a\n * {@link MoneyAccountUpgradeStepError} that records which step failed (the\n * original error is preserved as `cause`).\n *\n * A run that completes is recorded in state (keyed by lowercased address,\n * fingerprinted against the active config); subsequent calls for a\n * recorded account return immediately without running any steps. If the\n * active config no longer matches the recorded fingerprint, the sequence\n * re-runs.\n *\n * @param address - The Money Account address to upgrade.\n */\nexport type MoneyAccountUpgradeControllerUpgradeAccountAction = {\n type: `MoneyAccountUpgradeController:upgradeAccount`;\n handler: MoneyAccountUpgradeController['upgradeAccount'];\n};\n\n/**\n * Union of all MoneyAccountUpgradeController action types.\n */\nexport type MoneyAccountUpgradeControllerMethodActions =\n MoneyAccountUpgradeControllerUpgradeAccountAction;\n"]}
|
|
1
|
+
{"version":3,"file":"MoneyAccountUpgradeController-method-action-types.mjs","sourceRoot":"","sources":["../src/MoneyAccountUpgradeController-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { MoneyAccountUpgradeController } from './MoneyAccountUpgradeController.js';\n\n/**\n * Runs each step in the upgrade sequence in order. A step that reports\n * `'already-done'` is skipped without performing any action; a step that\n * reports `'completed'` has performed its action. An error thrown by any\n * step halts the sequence and is re-thrown wrapped in a\n * {@link MoneyAccountUpgradeStepError} that records which step failed (the\n * original error is preserved as `cause`).\n *\n * A run that completes is recorded in state (keyed by lowercased address,\n * fingerprinted against the active config); subsequent calls for a\n * recorded account return immediately without running any steps. If the\n * active config no longer matches the recorded fingerprint, the sequence\n * re-runs.\n *\n * A call that arrives while the bootstrap chain is still in flight —\n * including runs scheduled while waiting — waits for it to settle rather\n * than failing, so the upgrade always runs against the latest armed\n * config. Scheduling a bootstrap for a changed vault config disarms the\n * previous one, so it throws when no bootstrap has armed a config (feature\n * disabled or the last bootstrap failed) or when the wallet is locked.\n *\n * The armed config is re-checked before every step: if a sync disarms or\n * supersedes it while the sequence is running, the sequence aborts before\n * the next step signs anything, and nothing is recorded.\n *\n * @param address - The Money Account address to upgrade.\n */\nexport type MoneyAccountUpgradeControllerUpgradeAccountAction = {\n type: `MoneyAccountUpgradeController:upgradeAccount`;\n handler: MoneyAccountUpgradeController['upgradeAccount'];\n};\n\n/**\n * Union of all MoneyAccountUpgradeController action types.\n */\nexport type MoneyAccountUpgradeControllerMethodActions =\n MoneyAccountUpgradeControllerUpgradeAccountAction;\n"]}
|
|
@@ -10,11 +10,13 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
10
10
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
11
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
12
|
};
|
|
13
|
-
var _MoneyAccountUpgradeController_config, _MoneyAccountUpgradeController_steps;
|
|
13
|
+
var _MoneyAccountUpgradeController_instances, _MoneyAccountUpgradeController_config, _MoneyAccountUpgradeController_isEnabled, _MoneyAccountUpgradeController_isEligible, _MoneyAccountUpgradeController_ensureChainConfigured, _MoneyAccountUpgradeController_onBootstrapError, _MoneyAccountUpgradeController_initialized, _MoneyAccountUpgradeController_bootstrap, _MoneyAccountUpgradeController_bootstrappedConfig, _MoneyAccountUpgradeController_missingConfigReported, _MoneyAccountUpgradeController_steps, _MoneyAccountUpgradeController_reportBootstrapError, _MoneyAccountUpgradeController_isWalletReady, _MoneyAccountUpgradeController_areGatesOpen, _MoneyAccountUpgradeController_scheduleBootstrap, _MoneyAccountUpgradeController_forget, _MoneyAccountUpgradeController_reportMissingConfig, _MoneyAccountUpgradeController_applyVaultConfig;
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.MoneyAccountUpgradeController = exports.getDefaultMoneyAccountUpgradeControllerState = exports.controllerName = void 0;
|
|
16
16
|
const base_controller_1 = require("@metamask/base-controller");
|
|
17
17
|
const delegation_deployments_1 = require("@metamask/delegation-deployments");
|
|
18
|
+
const keyring_controller_1 = require("@metamask/keyring-controller");
|
|
19
|
+
const money_account_utils_1 = require("@metamask/money-account-utils");
|
|
18
20
|
const utils_1 = require("@metamask/utils");
|
|
19
21
|
const errors_js_1 = require("./errors.cjs");
|
|
20
22
|
const associate_address_js_1 = require("./steps/associate-address.cjs");
|
|
@@ -51,7 +53,24 @@ function getDefaultMoneyAccountUpgradeControllerState() {
|
|
|
51
53
|
exports.getDefaultMoneyAccountUpgradeControllerState = getDefaultMoneyAccountUpgradeControllerState;
|
|
52
54
|
const MESSENGER_EXPOSED_METHODS = ['upgradeAccount'];
|
|
53
55
|
/**
|
|
54
|
-
* Controller that
|
|
56
|
+
* Controller that owns the Money Account upgrade sequence and its own
|
|
57
|
+
* bootstrap.
|
|
58
|
+
*
|
|
59
|
+
* After {@link MoneyAccountUpgradeController.init} is called, the controller watches
|
|
60
|
+
* `RemoteFeatureFlagController` and `KeyringController` state and bootstraps
|
|
61
|
+
* itself when:
|
|
62
|
+
*
|
|
63
|
+
* 1. the client's `isEnabled` hook returns `true` for the current flags,
|
|
64
|
+
* 2. the wallet is unlocked with an HD keyring,
|
|
65
|
+
* 3. the client's `isEligible` hook (if any) resolves `true`, and
|
|
66
|
+
* 4. the `moneyAccountVaultConfig` flag parses.
|
|
67
|
+
*
|
|
68
|
+
* The bootstrap awaits the client's `ensureChainConfigured` hook and then
|
|
69
|
+
* fetches CHOMP service details to get the upgrade config. We recheck points
|
|
70
|
+
* 1–2 when awaiting in the bootstrap so a lock or an `isEnabled` will stop the process.
|
|
71
|
+
*
|
|
72
|
+
* The bootstrap re-runs whenever the vault config changes.
|
|
73
|
+
* `isEnabled` going `false` disables the controller.
|
|
55
74
|
*/
|
|
56
75
|
class MoneyAccountUpgradeController extends base_controller_1.BaseController {
|
|
57
76
|
/**
|
|
@@ -60,8 +79,10 @@ class MoneyAccountUpgradeController extends base_controller_1.BaseController {
|
|
|
60
79
|
* @param options - The options for constructing the controller.
|
|
61
80
|
* @param options.messenger - The messenger to use for inter-controller communication.
|
|
62
81
|
* @param options.state - The initial state, merged with the defaults.
|
|
82
|
+
* @param options.hooks - The client hooks for the bootstrap; see
|
|
83
|
+
* {@link MoneyAccountUpgradeControllerHooks}.
|
|
63
84
|
*/
|
|
64
|
-
constructor({ messenger, state, }) {
|
|
85
|
+
constructor({ messenger, state, hooks, }) {
|
|
65
86
|
super({
|
|
66
87
|
messenger,
|
|
67
88
|
metadata: moneyAccountUpgradeControllerMetadata,
|
|
@@ -71,55 +92,80 @@ class MoneyAccountUpgradeController extends base_controller_1.BaseController {
|
|
|
71
92
|
...state,
|
|
72
93
|
},
|
|
73
94
|
});
|
|
95
|
+
_MoneyAccountUpgradeController_instances.add(this);
|
|
74
96
|
_MoneyAccountUpgradeController_config.set(this, void 0);
|
|
97
|
+
_MoneyAccountUpgradeController_isEnabled.set(this, void 0);
|
|
98
|
+
_MoneyAccountUpgradeController_isEligible.set(this, void 0);
|
|
99
|
+
_MoneyAccountUpgradeController_ensureChainConfigured.set(this, void 0);
|
|
100
|
+
_MoneyAccountUpgradeController_onBootstrapError.set(this, void 0);
|
|
101
|
+
_MoneyAccountUpgradeController_initialized.set(this, false);
|
|
102
|
+
_MoneyAccountUpgradeController_bootstrap.set(this, void 0);
|
|
103
|
+
_MoneyAccountUpgradeController_bootstrappedConfig.set(this, void 0);
|
|
104
|
+
_MoneyAccountUpgradeController_missingConfigReported.set(this, false);
|
|
75
105
|
_MoneyAccountUpgradeController_steps.set(this, [
|
|
76
106
|
associate_address_js_1.associateAddressStep,
|
|
77
107
|
eip_7702_authorization_js_1.eip7702AuthorizationStep,
|
|
78
108
|
build_delegations_js_1.buildDelegationStep,
|
|
79
109
|
register_intents_js_1.registerIntentsStep,
|
|
80
110
|
]);
|
|
111
|
+
__classPrivateFieldSet(this, _MoneyAccountUpgradeController_isEnabled, hooks.isEnabled, "f");
|
|
112
|
+
__classPrivateFieldSet(this, _MoneyAccountUpgradeController_isEligible, hooks.isEligible ?? (async () => true), "f");
|
|
113
|
+
__classPrivateFieldSet(this, _MoneyAccountUpgradeController_ensureChainConfigured, hooks.ensureChainConfigured ?? (async () => undefined), "f");
|
|
114
|
+
__classPrivateFieldSet(this, _MoneyAccountUpgradeController_onBootstrapError, hooks.onBootstrapError ?? (() => undefined), "f");
|
|
81
115
|
this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
82
116
|
}
|
|
83
117
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* @param params.chainId - The chain to initialize for.
|
|
90
|
-
* @param params.boringVaultAddress - The Veda boring vault contract
|
|
91
|
-
* (vmUSD) for the given chain. Used as the withdrawal-side delegation
|
|
92
|
-
* token. Supplied by the consumer until the CHOMP service-details API
|
|
93
|
-
* exposes it.
|
|
118
|
+
* Start the controller's bootstrap: subscribe to the feature-flag and
|
|
119
|
+
* keyring triggers and run an initial sync. Call once, after all
|
|
120
|
+
* controllers and services the messenger reaches are constructed — this is
|
|
121
|
+
* the only method that may be called before the controller is bootstrapped,
|
|
122
|
+
* and it is the reason the constructor performs no messenger calls.
|
|
94
123
|
*/
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
throw new Error(`Delegation Framework ${DELEGATION_FRAMEWORK_VERSION} is not deployed on chain ${chainId}`);
|
|
99
|
-
}
|
|
100
|
-
const response = await this.messenger.call('ChompApiService:getServiceDetails', [chainId]);
|
|
101
|
-
const chain = response.chains[chainId];
|
|
102
|
-
if (!chain) {
|
|
103
|
-
throw new Error(`Chain ${chainId} not found in service details response`);
|
|
124
|
+
init() {
|
|
125
|
+
if (__classPrivateFieldGet(this, _MoneyAccountUpgradeController_initialized, "f")) {
|
|
126
|
+
return;
|
|
104
127
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
128
|
+
__classPrivateFieldSet(this, _MoneyAccountUpgradeController_initialized, true, "f");
|
|
129
|
+
this.messenger.subscribe('RemoteFeatureFlagController:stateChanged', () => this.sync());
|
|
130
|
+
this.messenger.subscribe('KeyringController:stateChanged', () => this.sync());
|
|
131
|
+
this.sync();
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Re-evaluate the bootstrap checks against live state and schedule a
|
|
135
|
+
* bootstrap run when they are open and the vault config is new. Runs on
|
|
136
|
+
* every feature-flag and keyring state change.
|
|
137
|
+
*
|
|
138
|
+
* If sync fails a failure is reported through `onBootstrapError` and retried
|
|
139
|
+
* on the next trigger.
|
|
140
|
+
*/
|
|
141
|
+
sync() {
|
|
142
|
+
try {
|
|
143
|
+
const { remoteFeatureFlags } = this.messenger.call('RemoteFeatureFlagController:getState');
|
|
144
|
+
if (!__classPrivateFieldGet(this, _MoneyAccountUpgradeController_isEnabled, "f").call(this, remoteFeatureFlags)) {
|
|
145
|
+
// Disarm so a later `upgradeAccount` cannot run against a config
|
|
146
|
+
// armed while the feature was still enabled. Re-enabling re-runs the
|
|
147
|
+
// bootstrap from scratch.
|
|
148
|
+
__classPrivateFieldSet(this, _MoneyAccountUpgradeController_config, undefined, "f");
|
|
149
|
+
__classPrivateFieldSet(this, _MoneyAccountUpgradeController_bootstrappedConfig, undefined, "f");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (!__classPrivateFieldGet(this, _MoneyAccountUpgradeController_instances, "m", _MoneyAccountUpgradeController_isWalletReady).call(this)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const vaultConfig = (0, money_account_utils_1.getMoneyAccountVaultConfig)(remoteFeatureFlags);
|
|
156
|
+
if (!vaultConfig) {
|
|
157
|
+
__classPrivateFieldGet(this, _MoneyAccountUpgradeController_instances, "m", _MoneyAccountUpgradeController_reportMissingConfig).call(this);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (__classPrivateFieldGet(this, _MoneyAccountUpgradeController_bootstrappedConfig, "f") &&
|
|
161
|
+
(0, money_account_utils_1.areMoneyAccountVaultConfigsEqual)(vaultConfig, __classPrivateFieldGet(this, _MoneyAccountUpgradeController_bootstrappedConfig, "f"))) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
__classPrivateFieldGet(this, _MoneyAccountUpgradeController_instances, "m", _MoneyAccountUpgradeController_scheduleBootstrap).call(this, vaultConfig);
|
|
108
165
|
}
|
|
109
|
-
|
|
110
|
-
|
|
166
|
+
catch (error) {
|
|
167
|
+
__classPrivateFieldGet(this, _MoneyAccountUpgradeController_instances, "m", _MoneyAccountUpgradeController_reportBootstrapError).call(this, error);
|
|
111
168
|
}
|
|
112
|
-
__classPrivateFieldSet(this, _MoneyAccountUpgradeController_config, {
|
|
113
|
-
chainId,
|
|
114
|
-
delegateAddress: chain.autoDepositDelegate,
|
|
115
|
-
musdTokenAddress: vedaProtocol.supportedTokens[0].tokenAddress,
|
|
116
|
-
boringVaultAddress,
|
|
117
|
-
vedaVaultAdapterAddress: vedaProtocol.adapterAddress,
|
|
118
|
-
delegatorImplAddress: contracts.EIP7702StatelessDeleGatorImpl,
|
|
119
|
-
erc20TransferAmountEnforcer: contracts.ERC20TransferAmountEnforcer,
|
|
120
|
-
redeemerEnforcer: contracts.RedeemerEnforcer,
|
|
121
|
-
valueLteEnforcer: contracts.ValueLteEnforcer,
|
|
122
|
-
}, "f");
|
|
123
169
|
}
|
|
124
170
|
/**
|
|
125
171
|
* Runs each step in the upgrade sequence in order. A step that reports
|
|
@@ -135,11 +181,31 @@ class MoneyAccountUpgradeController extends base_controller_1.BaseController {
|
|
|
135
181
|
* active config no longer matches the recorded fingerprint, the sequence
|
|
136
182
|
* re-runs.
|
|
137
183
|
*
|
|
184
|
+
* A call that arrives while the bootstrap chain is still in flight —
|
|
185
|
+
* including runs scheduled while waiting — waits for it to settle rather
|
|
186
|
+
* than failing, so the upgrade always runs against the latest armed
|
|
187
|
+
* config. Scheduling a bootstrap for a changed vault config disarms the
|
|
188
|
+
* previous one, so it throws when no bootstrap has armed a config (feature
|
|
189
|
+
* disabled or the last bootstrap failed) or when the wallet is locked.
|
|
190
|
+
*
|
|
191
|
+
* The armed config is re-checked before every step: if a sync disarms or
|
|
192
|
+
* supersedes it while the sequence is running, the sequence aborts before
|
|
193
|
+
* the next step signs anything, and nothing is recorded.
|
|
194
|
+
*
|
|
138
195
|
* @param address - The Money Account address to upgrade.
|
|
139
196
|
*/
|
|
140
197
|
async upgradeAccount(address) {
|
|
141
|
-
|
|
142
|
-
|
|
198
|
+
let bootstrap = __classPrivateFieldGet(this, _MoneyAccountUpgradeController_bootstrap, "f");
|
|
199
|
+
while (bootstrap) {
|
|
200
|
+
await bootstrap.catch(() => undefined);
|
|
201
|
+
// A newer run may have been chained on while we waited; wait for that
|
|
202
|
+
// one too, until the chain settles.
|
|
203
|
+
bootstrap = __classPrivateFieldGet(this, _MoneyAccountUpgradeController_bootstrap, "f") === bootstrap ? undefined : __classPrivateFieldGet(this, _MoneyAccountUpgradeController_bootstrap, "f");
|
|
204
|
+
}
|
|
205
|
+
// A lock does not disarm the config (unlocking must not cost a CHOMP
|
|
206
|
+
// re-fetch), so the wallet has to be checked here as well.
|
|
207
|
+
if (!__classPrivateFieldGet(this, _MoneyAccountUpgradeController_config, "f") || !__classPrivateFieldGet(this, _MoneyAccountUpgradeController_instances, "m", _MoneyAccountUpgradeController_areGatesOpen).call(this)) {
|
|
208
|
+
throw new Error('MoneyAccountUpgradeController is not bootstrapped: upgradeAccount() requires the feature flag on, the wallet unlocked, and a successful bootstrap');
|
|
143
209
|
}
|
|
144
210
|
const config = __classPrivateFieldGet(this, _MoneyAccountUpgradeController_config, "f");
|
|
145
211
|
const accountKey = address.toLowerCase();
|
|
@@ -149,6 +215,9 @@ class MoneyAccountUpgradeController extends base_controller_1.BaseController {
|
|
|
149
215
|
return;
|
|
150
216
|
}
|
|
151
217
|
for (const step of __classPrivateFieldGet(this, _MoneyAccountUpgradeController_steps, "f")) {
|
|
218
|
+
if (__classPrivateFieldGet(this, _MoneyAccountUpgradeController_config, "f") !== config) {
|
|
219
|
+
throw new Error('MoneyAccountUpgradeController upgrade aborted: the upgrade config was disarmed or superseded while the sequence was running');
|
|
220
|
+
}
|
|
152
221
|
try {
|
|
153
222
|
await step.run({
|
|
154
223
|
messenger: this.messenger,
|
|
@@ -169,7 +238,109 @@ class MoneyAccountUpgradeController extends base_controller_1.BaseController {
|
|
|
169
238
|
}
|
|
170
239
|
}
|
|
171
240
|
exports.MoneyAccountUpgradeController = MoneyAccountUpgradeController;
|
|
172
|
-
_MoneyAccountUpgradeController_config = new WeakMap(), _MoneyAccountUpgradeController_steps = new WeakMap()
|
|
241
|
+
_MoneyAccountUpgradeController_config = new WeakMap(), _MoneyAccountUpgradeController_isEnabled = new WeakMap(), _MoneyAccountUpgradeController_isEligible = new WeakMap(), _MoneyAccountUpgradeController_ensureChainConfigured = new WeakMap(), _MoneyAccountUpgradeController_onBootstrapError = new WeakMap(), _MoneyAccountUpgradeController_initialized = new WeakMap(), _MoneyAccountUpgradeController_bootstrap = new WeakMap(), _MoneyAccountUpgradeController_bootstrappedConfig = new WeakMap(), _MoneyAccountUpgradeController_missingConfigReported = new WeakMap(), _MoneyAccountUpgradeController_steps = new WeakMap(), _MoneyAccountUpgradeController_instances = new WeakSet(), _MoneyAccountUpgradeController_reportBootstrapError = function _MoneyAccountUpgradeController_reportBootstrapError(error) {
|
|
242
|
+
try {
|
|
243
|
+
__classPrivateFieldGet(this, _MoneyAccountUpgradeController_onBootstrapError, "f").call(this, error);
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// The hook is the client's error sink; there is nowhere else to send its
|
|
247
|
+
// own failure without recursing into it.
|
|
248
|
+
}
|
|
249
|
+
}, _MoneyAccountUpgradeController_isWalletReady = function _MoneyAccountUpgradeController_isWalletReady() {
|
|
250
|
+
const { isUnlocked, keyrings } = this.messenger.call('KeyringController:getState');
|
|
251
|
+
return (isUnlocked && keyrings.some((keyring) => keyring.type === keyring_controller_1.KeyringTypes.hd));
|
|
252
|
+
}, _MoneyAccountUpgradeController_areGatesOpen = function _MoneyAccountUpgradeController_areGatesOpen() {
|
|
253
|
+
const { remoteFeatureFlags } = this.messenger.call('RemoteFeatureFlagController:getState');
|
|
254
|
+
return __classPrivateFieldGet(this, _MoneyAccountUpgradeController_isEnabled, "f").call(this, remoteFeatureFlags) && __classPrivateFieldGet(this, _MoneyAccountUpgradeController_instances, "m", _MoneyAccountUpgradeController_isWalletReady).call(this);
|
|
255
|
+
}, _MoneyAccountUpgradeController_scheduleBootstrap = function _MoneyAccountUpgradeController_scheduleBootstrap(vaultConfig) {
|
|
256
|
+
__classPrivateFieldSet(this, _MoneyAccountUpgradeController_bootstrappedConfig, vaultConfig, "f");
|
|
257
|
+
// Scheduling means the served vault config no longer matches whatever is
|
|
258
|
+
// armed, so disarm now: until this run succeeds, `upgradeAccount` must
|
|
259
|
+
// wait for it (or refuse if it fails) rather than sign delegations
|
|
260
|
+
// against the superseded vault.
|
|
261
|
+
__classPrivateFieldSet(this, _MoneyAccountUpgradeController_config, undefined, "f");
|
|
262
|
+
const run = async () => {
|
|
263
|
+
// The gates were checked when this run was scheduled, but it may start
|
|
264
|
+
// much later, chained behind an in-flight bootstrap. Eligibility comes
|
|
265
|
+
// after the gates so a disabled client also skips the (possibly
|
|
266
|
+
// external) eligibility lookup.
|
|
267
|
+
if (!__classPrivateFieldGet(this, _MoneyAccountUpgradeController_instances, "m", _MoneyAccountUpgradeController_areGatesOpen).call(this) || !(await __classPrivateFieldGet(this, _MoneyAccountUpgradeController_isEligible, "f").call(this))) {
|
|
268
|
+
__classPrivateFieldGet(this, _MoneyAccountUpgradeController_instances, "m", _MoneyAccountUpgradeController_forget).call(this, vaultConfig);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
await __classPrivateFieldGet(this, _MoneyAccountUpgradeController_ensureChainConfigured, "f").call(this, vaultConfig);
|
|
272
|
+
// Configuring the chain can suspend for a while, so re-check the gates
|
|
273
|
+
// before the external CHOMP call.
|
|
274
|
+
if (!__classPrivateFieldGet(this, _MoneyAccountUpgradeController_instances, "m", _MoneyAccountUpgradeController_areGatesOpen).call(this)) {
|
|
275
|
+
__classPrivateFieldGet(this, _MoneyAccountUpgradeController_instances, "m", _MoneyAccountUpgradeController_forget).call(this, vaultConfig);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
await __classPrivateFieldGet(this, _MoneyAccountUpgradeController_instances, "m", _MoneyAccountUpgradeController_applyVaultConfig).call(this, vaultConfig);
|
|
279
|
+
};
|
|
280
|
+
const bootstrap = __classPrivateFieldGet(this, _MoneyAccountUpgradeController_bootstrap, "f")
|
|
281
|
+
? __classPrivateFieldGet(this, _MoneyAccountUpgradeController_bootstrap, "f").catch(() => undefined).then(run)
|
|
282
|
+
: run();
|
|
283
|
+
__classPrivateFieldSet(this, _MoneyAccountUpgradeController_bootstrap, bootstrap, "f");
|
|
284
|
+
bootstrap.catch((error) => {
|
|
285
|
+
__classPrivateFieldGet(this, _MoneyAccountUpgradeController_instances, "m", _MoneyAccountUpgradeController_forget).call(this, vaultConfig);
|
|
286
|
+
__classPrivateFieldGet(this, _MoneyAccountUpgradeController_instances, "m", _MoneyAccountUpgradeController_reportBootstrapError).call(this, error);
|
|
287
|
+
});
|
|
288
|
+
}, _MoneyAccountUpgradeController_forget = function _MoneyAccountUpgradeController_forget(vaultConfig) {
|
|
289
|
+
if (__classPrivateFieldGet(this, _MoneyAccountUpgradeController_bootstrappedConfig, "f") === vaultConfig) {
|
|
290
|
+
__classPrivateFieldSet(this, _MoneyAccountUpgradeController_bootstrappedConfig, undefined, "f");
|
|
291
|
+
}
|
|
292
|
+
}, _MoneyAccountUpgradeController_reportMissingConfig = function _MoneyAccountUpgradeController_reportMissingConfig() {
|
|
293
|
+
if (!__classPrivateFieldGet(this, _MoneyAccountUpgradeController_missingConfigReported, "f")) {
|
|
294
|
+
__classPrivateFieldSet(this, _MoneyAccountUpgradeController_missingConfigReported, true, "f");
|
|
295
|
+
__classPrivateFieldGet(this, _MoneyAccountUpgradeController_instances, "m", _MoneyAccountUpgradeController_reportBootstrapError).call(this, new errors_js_1.MissingMoneyAccountVaultConfigError());
|
|
296
|
+
}
|
|
297
|
+
}, _MoneyAccountUpgradeController_applyVaultConfig =
|
|
298
|
+
/**
|
|
299
|
+
* Fetches service details and validates the controller can operate on the
|
|
300
|
+
* vault's chain, arming the upgrade config `upgradeAccount` runs against.
|
|
301
|
+
* Resolves the Delegation Framework contract addresses for the chain from
|
|
302
|
+
* `@metamask/delegation-deployments`.
|
|
303
|
+
*
|
|
304
|
+
* @param vaultConfig - The vault config to arm; its `boringVault` is the
|
|
305
|
+
* withdrawal-side delegation token (vmUSD), supplied via the flag until
|
|
306
|
+
* the CHOMP service-details API exposes it.
|
|
307
|
+
*/
|
|
308
|
+
async function _MoneyAccountUpgradeController_applyVaultConfig(vaultConfig) {
|
|
309
|
+
const { chainId, boringVault: boringVaultAddress } = vaultConfig;
|
|
310
|
+
const contracts = delegation_deployments_1.DELEGATOR_CONTRACTS[DELEGATION_FRAMEWORK_VERSION][(0, utils_1.hexToNumber)(chainId)];
|
|
311
|
+
if (!contracts) {
|
|
312
|
+
throw new Error(`Delegation Framework ${DELEGATION_FRAMEWORK_VERSION} is not deployed on chain ${chainId}`);
|
|
313
|
+
}
|
|
314
|
+
const response = await this.messenger.call('ChompApiService:getServiceDetails', [chainId]);
|
|
315
|
+
const chain = response.chains[chainId];
|
|
316
|
+
if (!chain) {
|
|
317
|
+
throw new Error(`Chain ${chainId} not found in service details response`);
|
|
318
|
+
}
|
|
319
|
+
const { vedaProtocol } = chain.protocol;
|
|
320
|
+
if (!vedaProtocol) {
|
|
321
|
+
throw new Error(`vedaProtocol not found for chain ${chainId} in service details response`);
|
|
322
|
+
}
|
|
323
|
+
if (vedaProtocol.supportedTokens.length === 0) {
|
|
324
|
+
throw new Error(`No supported tokens found for vedaProtocol on chain ${chainId}`);
|
|
325
|
+
}
|
|
326
|
+
// A disarm (isEnabled flipping off) or a newer scheduled config during
|
|
327
|
+
// the CHOMP call supersedes this run: arming now would resurrect a config
|
|
328
|
+
// the controller just dropped, or briefly shadow the newer one.
|
|
329
|
+
if (__classPrivateFieldGet(this, _MoneyAccountUpgradeController_bootstrappedConfig, "f") !== vaultConfig) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
__classPrivateFieldSet(this, _MoneyAccountUpgradeController_config, {
|
|
333
|
+
chainId,
|
|
334
|
+
delegateAddress: chain.autoDepositDelegate,
|
|
335
|
+
musdTokenAddress: vedaProtocol.supportedTokens[0].tokenAddress,
|
|
336
|
+
boringVaultAddress,
|
|
337
|
+
vedaVaultAdapterAddress: vedaProtocol.adapterAddress,
|
|
338
|
+
delegatorImplAddress: contracts.EIP7702StatelessDeleGatorImpl,
|
|
339
|
+
erc20TransferAmountEnforcer: contracts.ERC20TransferAmountEnforcer,
|
|
340
|
+
redeemerEnforcer: contracts.RedeemerEnforcer,
|
|
341
|
+
valueLteEnforcer: contracts.ValueLteEnforcer,
|
|
342
|
+
}, "f");
|
|
343
|
+
};
|
|
173
344
|
/**
|
|
174
345
|
* Derives a stable fingerprint of the config fields that define what
|
|
175
346
|
* "upgraded" means for an account. A recorded upgrade is only trusted while
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MoneyAccountUpgradeController.cjs","sourceRoot":"","sources":["../src/MoneyAccountUpgradeController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AuthenticatedUserStorageServiceCreateDelegationAction,\n AuthenticatedUserStorageServiceListDelegationsAction,\n} from '@metamask/authenticated-user-storage';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n ChompApiServiceAssociateAddressAction,\n ChompApiServiceCreateIntentsAction,\n ChompApiServiceCreateUpgradeAction,\n ChompApiServiceGetAssociatedAddressesAction,\n ChompApiServiceGetIntentsByAddressAction,\n ChompApiServiceGetServiceDetailsAction,\n ChompApiServiceVerifyDelegationAction,\n} from '@metamask/chomp-api-service';\nimport type { DelegationControllerSignDelegationAction } from '@metamask/delegation-controller';\nimport { DELEGATOR_CONTRACTS } from '@metamask/delegation-deployments';\nimport type {\n KeyringControllerSignEip7702AuthorizationAction,\n KeyringControllerSignPersonalMessageAction,\n} from '@metamask/keyring-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n NetworkControllerFindNetworkClientIdByChainIdAction,\n NetworkControllerGetNetworkClientByIdAction,\n} from '@metamask/network-controller';\nimport { hexToNumber } from '@metamask/utils';\nimport type { Hex } from '@metamask/utils';\n\nimport { MoneyAccountUpgradeStepError } from './errors.js';\nimport type { MoneyAccountUpgradeControllerMethodActions } from './MoneyAccountUpgradeController-method-action-types.js';\nimport { associateAddressStep } from './steps/associate-address.js';\nimport { buildDelegationStep } from './steps/build-delegations.js';\nimport { eip7702AuthorizationStep } from './steps/eip-7702-authorization.js';\nimport { registerIntentsStep } from './steps/register-intents.js';\nimport type { Step } from './steps/step.js';\nimport type { UpgradeConfig } from './types.js';\n\n/**\n * The Delegation Framework deployment version we resolve contract addresses\n * against in `@metamask/delegation-deployments`.\n */\nconst DELEGATION_FRAMEWORK_VERSION = '1.3.0';\n\nexport const controllerName = 'MoneyAccountUpgradeController';\n\n/**\n * Record of a Money Account upgrade sequence that ran to completion.\n */\nexport type MoneyAccountUpgradeStatus = {\n /**\n * Fingerprint of the upgrade config the sequence completed under. The\n * record is only trusted while the active config produces the same\n * fingerprint — if the chain, CHOMP contracts, or Delegation Framework\n * version change, the sequence re-runs.\n */\n configFingerprint: string;\n /** Unix timestamp (in milliseconds) when the sequence completed. */\n completedAt: number;\n};\n\nexport type MoneyAccountUpgradeControllerState = {\n /**\n * Accounts whose upgrade sequence has fully completed, keyed by lowercased\n * account address.\n */\n upgradedAccounts: { [address: Hex]: MoneyAccountUpgradeStatus };\n};\n\nconst moneyAccountUpgradeControllerMetadata = {\n upgradedAccounts: {\n includeInDebugSnapshot: false,\n includeInStateLogs: false,\n persist: true,\n usedInUi: false,\n },\n} satisfies StateMetadata<MoneyAccountUpgradeControllerState>;\n\n/**\n * Constructs the default {@link MoneyAccountUpgradeController} state. This\n * allows consumers to provide a partial state object when initializing the\n * controller and also helps in constructing complete state objects for this\n * controller in tests.\n *\n * @returns The default {@link MoneyAccountUpgradeController} state.\n */\nexport function getDefaultMoneyAccountUpgradeControllerState(): MoneyAccountUpgradeControllerState {\n return {\n upgradedAccounts: {},\n };\n}\n\nconst MESSENGER_EXPOSED_METHODS = ['upgradeAccount'] as const;\n\nexport type MoneyAccountUpgradeControllerGetStateAction =\n ControllerGetStateAction<\n typeof controllerName,\n MoneyAccountUpgradeControllerState\n >;\n\nexport type MoneyAccountUpgradeControllerActions =\n | MoneyAccountUpgradeControllerGetStateAction\n | MoneyAccountUpgradeControllerMethodActions;\n\ntype AllowedActions =\n | AuthenticatedUserStorageServiceCreateDelegationAction\n | AuthenticatedUserStorageServiceListDelegationsAction\n | ChompApiServiceAssociateAddressAction\n | ChompApiServiceCreateIntentsAction\n | ChompApiServiceCreateUpgradeAction\n | ChompApiServiceGetAssociatedAddressesAction\n | ChompApiServiceGetIntentsByAddressAction\n | ChompApiServiceGetServiceDetailsAction\n | ChompApiServiceVerifyDelegationAction\n | DelegationControllerSignDelegationAction\n | KeyringControllerSignEip7702AuthorizationAction\n | KeyringControllerSignPersonalMessageAction\n | NetworkControllerFindNetworkClientIdByChainIdAction\n | NetworkControllerGetNetworkClientByIdAction;\n\nexport type MoneyAccountUpgradeControllerStateChangedEvent =\n ControllerStateChangedEvent<\n typeof controllerName,\n MoneyAccountUpgradeControllerState\n >;\n\nexport type MoneyAccountUpgradeControllerEvents =\n MoneyAccountUpgradeControllerStateChangedEvent;\n\ntype AllowedEvents = never;\n\nexport type MoneyAccountUpgradeControllerMessenger = Messenger<\n typeof controllerName,\n MoneyAccountUpgradeControllerActions | AllowedActions,\n MoneyAccountUpgradeControllerEvents | AllowedEvents\n>;\n\n/**\n * Controller that orchestrates the Money Account upgrade sequence.\n */\nexport class MoneyAccountUpgradeController extends BaseController<\n typeof controllerName,\n MoneyAccountUpgradeControllerState,\n MoneyAccountUpgradeControllerMessenger\n> {\n #config?: UpgradeConfig & { chainId: Hex };\n\n readonly #steps: Step[] = [\n associateAddressStep,\n eip7702AuthorizationStep,\n buildDelegationStep,\n registerIntentsStep,\n ];\n\n /**\n * Constructor for the MoneyAccountUpgradeController.\n *\n * @param options - The options for constructing the controller.\n * @param options.messenger - The messenger to use for inter-controller communication.\n * @param options.state - The initial state, merged with the defaults.\n */\n constructor({\n messenger,\n state,\n }: {\n messenger: MoneyAccountUpgradeControllerMessenger;\n state?: Partial<MoneyAccountUpgradeControllerState>;\n }) {\n super({\n messenger,\n metadata: moneyAccountUpgradeControllerMetadata,\n name: controllerName,\n state: {\n ...getDefaultMoneyAccountUpgradeControllerState(),\n ...state,\n },\n });\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Fetches service details and validates the controller can operate on the\n * given chain. Resolves the Delegation Framework contract addresses for the\n * chain from `@metamask/delegation-deployments`.\n *\n * @param params - The parameters for initialization.\n * @param params.chainId - The chain to initialize for.\n * @param params.boringVaultAddress - The Veda boring vault contract\n * (vmUSD) for the given chain. Used as the withdrawal-side delegation\n * token. Supplied by the consumer until the CHOMP service-details API\n * exposes it.\n */\n async init({\n chainId,\n boringVaultAddress,\n }: {\n chainId: Hex;\n boringVaultAddress: Hex;\n }): Promise<void> {\n const contracts =\n DELEGATOR_CONTRACTS[DELEGATION_FRAMEWORK_VERSION][hexToNumber(chainId)];\n if (!contracts) {\n throw new Error(\n `Delegation Framework ${DELEGATION_FRAMEWORK_VERSION} is not deployed on chain ${chainId}`,\n );\n }\n\n const response = await this.messenger.call(\n 'ChompApiService:getServiceDetails',\n [chainId],\n );\n\n const chain = response.chains[chainId];\n if (!chain) {\n throw new Error(`Chain ${chainId} not found in service details response`);\n }\n\n const { vedaProtocol } = chain.protocol;\n if (!vedaProtocol) {\n throw new Error(\n `vedaProtocol not found for chain ${chainId} in service details response`,\n );\n }\n\n if (vedaProtocol.supportedTokens.length === 0) {\n throw new Error(\n `No supported tokens found for vedaProtocol on chain ${chainId}`,\n );\n }\n\n this.#config = {\n chainId,\n delegateAddress: chain.autoDepositDelegate,\n musdTokenAddress: vedaProtocol.supportedTokens[0].tokenAddress,\n boringVaultAddress,\n vedaVaultAdapterAddress: vedaProtocol.adapterAddress,\n delegatorImplAddress: contracts.EIP7702StatelessDeleGatorImpl,\n erc20TransferAmountEnforcer: contracts.ERC20TransferAmountEnforcer,\n redeemerEnforcer: contracts.RedeemerEnforcer,\n valueLteEnforcer: contracts.ValueLteEnforcer,\n };\n }\n\n /**\n * Runs each step in the upgrade sequence in order. A step that reports\n * `'already-done'` is skipped without performing any action; a step that\n * reports `'completed'` has performed its action. An error thrown by any\n * step halts the sequence and is re-thrown wrapped in a\n * {@link MoneyAccountUpgradeStepError} that records which step failed (the\n * original error is preserved as `cause`).\n *\n * A run that completes is recorded in state (keyed by lowercased address,\n * fingerprinted against the active config); subsequent calls for a\n * recorded account return immediately without running any steps. If the\n * active config no longer matches the recorded fingerprint, the sequence\n * re-runs.\n *\n * @param address - The Money Account address to upgrade.\n */\n async upgradeAccount(address: Hex): Promise<void> {\n if (!this.#config) {\n throw new Error(\n 'MoneyAccountUpgradeController must be initialized via init() before upgradeAccount() can be called',\n );\n }\n const config = this.#config;\n\n const accountKey = address.toLowerCase() as Hex;\n const configFingerprint = computeConfigFingerprint(config);\n if (\n this.state.upgradedAccounts[accountKey]?.configFingerprint ===\n configFingerprint\n ) {\n return;\n }\n\n for (const step of this.#steps) {\n try {\n await step.run({\n messenger: this.messenger,\n address,\n ...config,\n });\n } catch (error) {\n throw new MoneyAccountUpgradeStepError(step.name, error);\n }\n }\n\n this.update((state) => {\n state.upgradedAccounts[accountKey] = {\n configFingerprint,\n completedAt: Date.now(),\n };\n });\n }\n}\n\n/**\n * Derives a stable fingerprint of the config fields that define what\n * \"upgraded\" means for an account. A recorded upgrade is only trusted while\n * the active config produces the same fingerprint.\n *\n * @param config - The active upgrade config.\n * @returns A canonical string over the config's identifying fields.\n */\nfunction computeConfigFingerprint(\n config: UpgradeConfig & { chainId: Hex },\n): string {\n return [\n DELEGATION_FRAMEWORK_VERSION,\n config.chainId,\n config.delegateAddress,\n config.musdTokenAddress,\n config.boringVaultAddress,\n config.vedaVaultAdapterAddress,\n config.delegatorImplAddress,\n config.erc20TransferAmountEnforcer,\n config.redeemerEnforcer,\n config.valueLteEnforcer,\n ]\n .map((value) => value.toLowerCase())\n .join('|');\n}\n"]}
|
|
1
|
+
{"version":3,"file":"MoneyAccountUpgradeController.cjs","sourceRoot":"","sources":["../src/MoneyAccountUpgradeController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AuthenticatedUserStorageServiceCreateDelegationAction,\n AuthenticatedUserStorageServiceListDelegationsAction,\n} from '@metamask/authenticated-user-storage';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n ChompApiServiceAssociateAddressAction,\n ChompApiServiceCreateIntentsAction,\n ChompApiServiceCreateUpgradeAction,\n ChompApiServiceGetAssociatedAddressesAction,\n ChompApiServiceGetIntentsByAddressAction,\n ChompApiServiceGetServiceDetailsAction,\n ChompApiServiceVerifyDelegationAction,\n} from '@metamask/chomp-api-service';\nimport type { DelegationControllerSignDelegationAction } from '@metamask/delegation-controller';\nimport { DELEGATOR_CONTRACTS } from '@metamask/delegation-deployments';\nimport { KeyringTypes } from '@metamask/keyring-controller';\nimport type {\n KeyringControllerGetStateAction,\n KeyringControllerSignEip7702AuthorizationAction,\n KeyringControllerSignPersonalMessageAction,\n KeyringControllerState,\n} from '@metamask/keyring-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport {\n areMoneyAccountVaultConfigsEqual,\n getMoneyAccountVaultConfig,\n} from '@metamask/money-account-utils';\nimport type { MoneyAccountVaultConfig } from '@metamask/money-account-utils';\nimport type {\n NetworkControllerFindNetworkClientIdByChainIdAction,\n NetworkControllerGetNetworkClientByIdAction,\n} from '@metamask/network-controller';\nimport type {\n FeatureFlags,\n RemoteFeatureFlagControllerGetStateAction,\n RemoteFeatureFlagControllerState,\n} from '@metamask/remote-feature-flag-controller';\nimport { hexToNumber } from '@metamask/utils';\nimport type { Hex } from '@metamask/utils';\n\nimport {\n MissingMoneyAccountVaultConfigError,\n MoneyAccountUpgradeStepError,\n} from './errors.js';\nimport type { MoneyAccountUpgradeControllerMethodActions } from './MoneyAccountUpgradeController-method-action-types.js';\nimport { associateAddressStep } from './steps/associate-address.js';\nimport { buildDelegationStep } from './steps/build-delegations.js';\nimport { eip7702AuthorizationStep } from './steps/eip-7702-authorization.js';\nimport { registerIntentsStep } from './steps/register-intents.js';\nimport type { Step } from './steps/step.js';\nimport type { UpgradeConfig } from './types.js';\n\n/**\n * The Delegation Framework deployment version we resolve contract addresses\n * against in `@metamask/delegation-deployments`.\n */\nconst DELEGATION_FRAMEWORK_VERSION = '1.3.0';\n\nexport const controllerName = 'MoneyAccountUpgradeController';\n\n/**\n * Record of a Money Account upgrade sequence that ran to completion.\n */\nexport type MoneyAccountUpgradeStatus = {\n /**\n * Fingerprint of the upgrade config the sequence completed under. The\n * record is only trusted while the active config produces the same\n * fingerprint — if the chain, CHOMP contracts, or Delegation Framework\n * version change, the sequence re-runs.\n */\n configFingerprint: string;\n /** Unix timestamp (in milliseconds) when the sequence completed. */\n completedAt: number;\n};\n\nexport type MoneyAccountUpgradeControllerState = {\n /**\n * Accounts whose upgrade sequence has fully completed, keyed by lowercased\n * account address.\n */\n upgradedAccounts: { [address: Hex]: MoneyAccountUpgradeStatus };\n};\n\nconst moneyAccountUpgradeControllerMetadata = {\n upgradedAccounts: {\n includeInDebugSnapshot: false,\n includeInStateLogs: false,\n persist: true,\n usedInUi: false,\n },\n} satisfies StateMetadata<MoneyAccountUpgradeControllerState>;\n\n/**\n * Constructs the default {@link MoneyAccountUpgradeController} state. This\n * allows consumers to provide a partial state object when initializing the\n * controller and also helps in constructing complete state objects for this\n * controller in tests.\n *\n * @returns The default {@link MoneyAccountUpgradeController} state.\n */\nexport function getDefaultMoneyAccountUpgradeControllerState(): MoneyAccountUpgradeControllerState {\n return {\n upgradedAccounts: {},\n };\n}\n\nconst MESSENGER_EXPOSED_METHODS = ['upgradeAccount'] as const;\n\nexport type MoneyAccountUpgradeControllerGetStateAction =\n ControllerGetStateAction<\n typeof controllerName,\n MoneyAccountUpgradeControllerState\n >;\n\nexport type MoneyAccountUpgradeControllerActions =\n | MoneyAccountUpgradeControllerGetStateAction\n | MoneyAccountUpgradeControllerMethodActions;\n\ntype AllowedActions =\n | AuthenticatedUserStorageServiceCreateDelegationAction\n | AuthenticatedUserStorageServiceListDelegationsAction\n | ChompApiServiceAssociateAddressAction\n | ChompApiServiceCreateIntentsAction\n | ChompApiServiceCreateUpgradeAction\n | ChompApiServiceGetAssociatedAddressesAction\n | ChompApiServiceGetIntentsByAddressAction\n | ChompApiServiceGetServiceDetailsAction\n | ChompApiServiceVerifyDelegationAction\n | DelegationControllerSignDelegationAction\n | KeyringControllerGetStateAction\n | KeyringControllerSignEip7702AuthorizationAction\n | KeyringControllerSignPersonalMessageAction\n | NetworkControllerFindNetworkClientIdByChainIdAction\n | NetworkControllerGetNetworkClientByIdAction\n | RemoteFeatureFlagControllerGetStateAction;\n\nexport type MoneyAccountUpgradeControllerStateChangedEvent =\n ControllerStateChangedEvent<\n typeof controllerName,\n MoneyAccountUpgradeControllerState\n >;\n\nexport type MoneyAccountUpgradeControllerEvents =\n MoneyAccountUpgradeControllerStateChangedEvent;\n\ntype AllowedEvents =\n | ControllerStateChangedEvent<'KeyringController', KeyringControllerState>\n | ControllerStateChangedEvent<\n 'RemoteFeatureFlagController',\n RemoteFeatureFlagControllerState\n >;\n\nexport type MoneyAccountUpgradeControllerMessenger = Messenger<\n typeof controllerName,\n MoneyAccountUpgradeControllerActions | AllowedActions,\n MoneyAccountUpgradeControllerEvents | AllowedEvents\n>;\n\n/**\n * These hooks must be provided by the client - they provide functions that required to bootstrap\n * the controller, which rely on client specific information.\n */\nexport type MoneyAccountUpgradeControllerHooks = {\n /**\n * Whether the Money Account feature is enabled for this client.\n *\n * The controller will call the function with the current state of\n * the remote feature flags. It gets caleld on every sync, and re-checked\n * when the bootstrap `awaits`.\n *\n * The isEnabled function should re-read any client state it depends\n * on (e.g. a version-gated flag, a \"basic functionality\" toggle) rather\n * than caching it. Returning `false` after a successful bootstrap disarms\n * the controller: `upgradeAccount` refuses to run until a later sync\n * re-bootstraps.\n */\n isEnabled: (remoteFeatureFlags: FeatureFlags) => boolean;\n\n /**\n * An asynchronous client gate checked once per bootstrap run, before any\n * network is added or external service is called — e.g. a fail-closed\n * geolocation check. A run skipped here is forgotten and retried on the\n * next sync trigger. If the function is not provided we assume the client is eligible.\n */\n isEligible?: () => Promise<boolean>;\n\n /**\n * Ensure the vault chain exists in the client's NetworkController before\n * the bootstrap validates it. Adding a network is client-specific, so\n * the controller awaits this before calling\n * `NetworkController:findNetworkClientIdByChainId` consumers. Defaults to a\n * no-op.\n */\n ensureChainConfigured?: (\n vaultConfig: MoneyAccountVaultConfig,\n ) => Promise<void>;\n\n /**\n * Called when a bootstrap run fails or cannot be scheduled. Receives a\n * {@link MissingMoneyAccountVaultConfigError} (once per controller\n * lifetime) when the enable flag is on but `moneyAccountVaultConfig` is\n * unserved or malformed.\n */\n onBootstrapError?: (error: unknown) => void;\n};\n\n/**\n * Controller that owns the Money Account upgrade sequence and its own\n * bootstrap.\n *\n * After {@link MoneyAccountUpgradeController.init} is called, the controller watches\n * `RemoteFeatureFlagController` and `KeyringController` state and bootstraps\n * itself when:\n *\n * 1. the client's `isEnabled` hook returns `true` for the current flags,\n * 2. the wallet is unlocked with an HD keyring,\n * 3. the client's `isEligible` hook (if any) resolves `true`, and\n * 4. the `moneyAccountVaultConfig` flag parses.\n *\n * The bootstrap awaits the client's `ensureChainConfigured` hook and then\n * fetches CHOMP service details to get the upgrade config. We recheck points\n * 1–2 when awaiting in the bootstrap so a lock or an `isEnabled` will stop the process.\n *\n * The bootstrap re-runs whenever the vault config changes.\n * `isEnabled` going `false` disables the controller.\n */\nexport class MoneyAccountUpgradeController extends BaseController<\n typeof controllerName,\n MoneyAccountUpgradeControllerState,\n MoneyAccountUpgradeControllerMessenger\n> {\n #config?: UpgradeConfig & { chainId: Hex };\n\n readonly #isEnabled: MoneyAccountUpgradeControllerHooks['isEnabled'];\n\n readonly #isEligible: NonNullable<\n MoneyAccountUpgradeControllerHooks['isEligible']\n >;\n\n readonly #ensureChainConfigured: NonNullable<\n MoneyAccountUpgradeControllerHooks['ensureChainConfigured']\n >;\n\n readonly #onBootstrapError: NonNullable<\n MoneyAccountUpgradeControllerHooks['onBootstrapError']\n >;\n\n #initialized = false;\n\n #bootstrap?: Promise<void>;\n\n #bootstrappedConfig?: MoneyAccountVaultConfig;\n\n #missingConfigReported = false;\n\n readonly #steps: Step[] = [\n associateAddressStep,\n eip7702AuthorizationStep,\n buildDelegationStep,\n registerIntentsStep,\n ];\n\n /**\n * Constructor for the MoneyAccountUpgradeController.\n *\n * @param options - The options for constructing the controller.\n * @param options.messenger - The messenger to use for inter-controller communication.\n * @param options.state - The initial state, merged with the defaults.\n * @param options.hooks - The client hooks for the bootstrap; see\n * {@link MoneyAccountUpgradeControllerHooks}.\n */\n constructor({\n messenger,\n state,\n hooks,\n }: {\n messenger: MoneyAccountUpgradeControllerMessenger;\n state?: Partial<MoneyAccountUpgradeControllerState>;\n hooks: MoneyAccountUpgradeControllerHooks;\n }) {\n super({\n messenger,\n metadata: moneyAccountUpgradeControllerMetadata,\n name: controllerName,\n state: {\n ...getDefaultMoneyAccountUpgradeControllerState(),\n ...state,\n },\n });\n\n this.#isEnabled = hooks.isEnabled;\n this.#isEligible = hooks.isEligible ?? (async (): Promise<boolean> => true);\n this.#ensureChainConfigured =\n hooks.ensureChainConfigured ?? (async (): Promise<void> => undefined);\n this.#onBootstrapError = hooks.onBootstrapError ?? ((): void => undefined);\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Start the controller's bootstrap: subscribe to the feature-flag and\n * keyring triggers and run an initial sync. Call once, after all\n * controllers and services the messenger reaches are constructed — this is\n * the only method that may be called before the controller is bootstrapped,\n * and it is the reason the constructor performs no messenger calls.\n */\n init(): void {\n if (this.#initialized) {\n return;\n }\n this.#initialized = true;\n\n this.messenger.subscribe('RemoteFeatureFlagController:stateChanged', () =>\n this.sync(),\n );\n this.messenger.subscribe('KeyringController:stateChanged', () =>\n this.sync(),\n );\n this.sync();\n }\n\n /**\n * Re-evaluate the bootstrap checks against live state and schedule a\n * bootstrap run when they are open and the vault config is new. Runs on\n * every feature-flag and keyring state change.\n *\n * If sync fails a failure is reported through `onBootstrapError` and retried\n * on the next trigger.\n */\n sync(): void {\n try {\n const { remoteFeatureFlags } = this.messenger.call(\n 'RemoteFeatureFlagController:getState',\n );\n\n if (!this.#isEnabled(remoteFeatureFlags)) {\n // Disarm so a later `upgradeAccount` cannot run against a config\n // armed while the feature was still enabled. Re-enabling re-runs the\n // bootstrap from scratch.\n this.#config = undefined;\n this.#bootstrappedConfig = undefined;\n return;\n }\n\n if (!this.#isWalletReady()) {\n return;\n }\n\n const vaultConfig = getMoneyAccountVaultConfig(remoteFeatureFlags);\n if (!vaultConfig) {\n this.#reportMissingConfig();\n return;\n }\n\n if (\n this.#bootstrappedConfig &&\n areMoneyAccountVaultConfigsEqual(vaultConfig, this.#bootstrappedConfig)\n ) {\n return;\n }\n\n this.#scheduleBootstrap(vaultConfig);\n } catch (error) {\n this.#reportBootstrapError(error);\n }\n }\n\n /**\n * Hand a bootstrap failure to the client's `onBootstrapError` hook. A hook\n * that throws must not break the controller: from a subscription it would\n * surface as an unhandled rejection, and from `init()` it would abort the\n * caller's startup.\n *\n * @param error - The failure to report.\n */\n #reportBootstrapError(error: unknown): void {\n try {\n this.#onBootstrapError(error);\n } catch {\n // The hook is the client's error sink; there is nowhere else to send its\n // own failure without recursing into it.\n }\n }\n\n /**\n * Whether the wallet can sign right now: unlocked with an HD keyring. The\n * keyring list matters because during a vault restore the unlock flips\n * before the keyrings land in state.\n *\n * @returns Whether the wallet is ready.\n */\n #isWalletReady(): boolean {\n const { isUnlocked, keyrings } = this.messenger.call(\n 'KeyringController:getState',\n );\n return (\n isUnlocked && keyrings.some((keyring) => keyring.type === KeyringTypes.hd)\n );\n }\n\n /**\n * Whether the synchronous bootstrap gates are open right now. Re-read from\n * live state on every call because the bootstrap re-validates them across\n * its `await` points.\n *\n * @returns Whether the bootstrap may proceed.\n */\n #areGatesOpen(): boolean {\n const { remoteFeatureFlags } = this.messenger.call(\n 'RemoteFeatureFlagController:getState',\n );\n return this.#isEnabled(remoteFeatureFlags) && this.#isWalletReady();\n }\n\n #scheduleBootstrap(vaultConfig: MoneyAccountVaultConfig): void {\n this.#bootstrappedConfig = vaultConfig;\n\n // Scheduling means the served vault config no longer matches whatever is\n // armed, so disarm now: until this run succeeds, `upgradeAccount` must\n // wait for it (or refuse if it fails) rather than sign delegations\n // against the superseded vault.\n this.#config = undefined;\n\n const run = async (): Promise<void> => {\n // The gates were checked when this run was scheduled, but it may start\n // much later, chained behind an in-flight bootstrap. Eligibility comes\n // after the gates so a disabled client also skips the (possibly\n // external) eligibility lookup.\n if (!this.#areGatesOpen() || !(await this.#isEligible())) {\n this.#forget(vaultConfig);\n return;\n }\n\n await this.#ensureChainConfigured(vaultConfig);\n\n // Configuring the chain can suspend for a while, so re-check the gates\n // before the external CHOMP call.\n if (!this.#areGatesOpen()) {\n this.#forget(vaultConfig);\n return;\n }\n\n await this.#applyVaultConfig(vaultConfig);\n };\n\n const bootstrap = this.#bootstrap\n ? this.#bootstrap.catch(() => undefined).then(run)\n : run();\n this.#bootstrap = bootstrap;\n\n bootstrap.catch((error) => {\n this.#forget(vaultConfig);\n this.#reportBootstrapError(error);\n });\n }\n\n /**\n * Forget a scheduled bootstrap that was skipped or failed, so the next\n * trigger re-runs it — but only if no newer config has been scheduled\n * meanwhile: a newer config supersedes this run, success or failure.\n *\n * @param vaultConfig - The config the abandoned run was scheduled with.\n */\n #forget(vaultConfig: MoneyAccountVaultConfig): void {\n if (this.#bootstrappedConfig === vaultConfig) {\n this.#bootstrappedConfig = undefined;\n }\n }\n\n /**\n * Report a served enable flag without a usable `moneyAccountVaultConfig` —\n * a flag misconfiguration that silently disables upgrades. Reported once\n * per controller lifetime; flag refreshes arrive continuously and would\n * otherwise spam.\n */\n #reportMissingConfig(): void {\n if (!this.#missingConfigReported) {\n this.#missingConfigReported = true;\n this.#reportBootstrapError(new MissingMoneyAccountVaultConfigError());\n }\n }\n\n /**\n * Fetches service details and validates the controller can operate on the\n * vault's chain, arming the upgrade config `upgradeAccount` runs against.\n * Resolves the Delegation Framework contract addresses for the chain from\n * `@metamask/delegation-deployments`.\n *\n * @param vaultConfig - The vault config to arm; its `boringVault` is the\n * withdrawal-side delegation token (vmUSD), supplied via the flag until\n * the CHOMP service-details API exposes it.\n */\n async #applyVaultConfig(vaultConfig: MoneyAccountVaultConfig): Promise<void> {\n const { chainId, boringVault: boringVaultAddress } = vaultConfig;\n\n const contracts =\n DELEGATOR_CONTRACTS[DELEGATION_FRAMEWORK_VERSION][hexToNumber(chainId)];\n if (!contracts) {\n throw new Error(\n `Delegation Framework ${DELEGATION_FRAMEWORK_VERSION} is not deployed on chain ${chainId}`,\n );\n }\n\n const response = await this.messenger.call(\n 'ChompApiService:getServiceDetails',\n [chainId],\n );\n\n const chain = response.chains[chainId];\n if (!chain) {\n throw new Error(`Chain ${chainId} not found in service details response`);\n }\n\n const { vedaProtocol } = chain.protocol;\n if (!vedaProtocol) {\n throw new Error(\n `vedaProtocol not found for chain ${chainId} in service details response`,\n );\n }\n\n if (vedaProtocol.supportedTokens.length === 0) {\n throw new Error(\n `No supported tokens found for vedaProtocol on chain ${chainId}`,\n );\n }\n\n // A disarm (isEnabled flipping off) or a newer scheduled config during\n // the CHOMP call supersedes this run: arming now would resurrect a config\n // the controller just dropped, or briefly shadow the newer one.\n if (this.#bootstrappedConfig !== vaultConfig) {\n return;\n }\n\n this.#config = {\n chainId,\n delegateAddress: chain.autoDepositDelegate,\n musdTokenAddress: vedaProtocol.supportedTokens[0].tokenAddress,\n boringVaultAddress,\n vedaVaultAdapterAddress: vedaProtocol.adapterAddress,\n delegatorImplAddress: contracts.EIP7702StatelessDeleGatorImpl,\n erc20TransferAmountEnforcer: contracts.ERC20TransferAmountEnforcer,\n redeemerEnforcer: contracts.RedeemerEnforcer,\n valueLteEnforcer: contracts.ValueLteEnforcer,\n };\n }\n\n /**\n * Runs each step in the upgrade sequence in order. A step that reports\n * `'already-done'` is skipped without performing any action; a step that\n * reports `'completed'` has performed its action. An error thrown by any\n * step halts the sequence and is re-thrown wrapped in a\n * {@link MoneyAccountUpgradeStepError} that records which step failed (the\n * original error is preserved as `cause`).\n *\n * A run that completes is recorded in state (keyed by lowercased address,\n * fingerprinted against the active config); subsequent calls for a\n * recorded account return immediately without running any steps. If the\n * active config no longer matches the recorded fingerprint, the sequence\n * re-runs.\n *\n * A call that arrives while the bootstrap chain is still in flight —\n * including runs scheduled while waiting — waits for it to settle rather\n * than failing, so the upgrade always runs against the latest armed\n * config. Scheduling a bootstrap for a changed vault config disarms the\n * previous one, so it throws when no bootstrap has armed a config (feature\n * disabled or the last bootstrap failed) or when the wallet is locked.\n *\n * The armed config is re-checked before every step: if a sync disarms or\n * supersedes it while the sequence is running, the sequence aborts before\n * the next step signs anything, and nothing is recorded.\n *\n * @param address - The Money Account address to upgrade.\n */\n async upgradeAccount(address: Hex): Promise<void> {\n let bootstrap = this.#bootstrap;\n while (bootstrap) {\n await bootstrap.catch(() => undefined);\n // A newer run may have been chained on while we waited; wait for that\n // one too, until the chain settles.\n bootstrap = this.#bootstrap === bootstrap ? undefined : this.#bootstrap;\n }\n // A lock does not disarm the config (unlocking must not cost a CHOMP\n // re-fetch), so the wallet has to be checked here as well.\n if (!this.#config || !this.#areGatesOpen()) {\n throw new Error(\n 'MoneyAccountUpgradeController is not bootstrapped: upgradeAccount() requires the feature flag on, the wallet unlocked, and a successful bootstrap',\n );\n }\n const config = this.#config;\n\n const accountKey = address.toLowerCase() as Hex;\n const configFingerprint = computeConfigFingerprint(config);\n if (\n this.state.upgradedAccounts[accountKey]?.configFingerprint ===\n configFingerprint\n ) {\n return;\n }\n\n for (const step of this.#steps) {\n if (this.#config !== config) {\n throw new Error(\n 'MoneyAccountUpgradeController upgrade aborted: the upgrade config was disarmed or superseded while the sequence was running',\n );\n }\n try {\n await step.run({\n messenger: this.messenger,\n address,\n ...config,\n });\n } catch (error) {\n throw new MoneyAccountUpgradeStepError(step.name, error);\n }\n }\n\n this.update((state) => {\n state.upgradedAccounts[accountKey] = {\n configFingerprint,\n completedAt: Date.now(),\n };\n });\n }\n}\n\n/**\n * Derives a stable fingerprint of the config fields that define what\n * \"upgraded\" means for an account. A recorded upgrade is only trusted while\n * the active config produces the same fingerprint.\n *\n * @param config - The active upgrade config.\n * @returns A canonical string over the config's identifying fields.\n */\nfunction computeConfigFingerprint(\n config: UpgradeConfig & { chainId: Hex },\n): string {\n return [\n DELEGATION_FRAMEWORK_VERSION,\n config.chainId,\n config.delegateAddress,\n config.musdTokenAddress,\n config.boringVaultAddress,\n config.vedaVaultAdapterAddress,\n config.delegatorImplAddress,\n config.erc20TransferAmountEnforcer,\n config.redeemerEnforcer,\n config.valueLteEnforcer,\n ]\n .map((value) => value.toLowerCase())\n .join('|');\n}\n"]}
|