Skip to content

Commit f7df669

Browse files
authored
feat: MUSD-681: (money-account-balance-service) replace constructor config with remote feature flag subscription (MetaMask#8742)
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> This PR updates the `@metamask/money-account-balance-service` to use a remote vault config instead of constructor args. ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> - [MUSD-681: Support remote vault config feature flag in Money Balance Service](https://consensyssoftware.atlassian.net/browse/MUSD-681) ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Breaking API changes shift vault configuration from constructor args to `RemoteFeatureFlagController` state and alter response shapes, which can break consumers and affect balance/APY lookups until flags are available. Runtime behavior now depends on async flag delivery and validation, adding new error paths and cache invalidation logic. > > **Overview** > Refactors `MoneyAccountBalanceService` to **stop taking vault config in the constructor** and instead `init()`/subscribe to `RemoteFeatureFlagController` (`getState` + `stateChange`) for a remote `moneyAccountVaultConfig`, throwing `VaultConfigNotAvailableError` until a valid config is present and routing malformed configs via `VaultConfigValidationError`. > > Updates on-chain/API fetching to use the new config shape (`boringVault`, `chainId`, `lensAddress`, `tellerAddress`, etc.), including switching `getMusdEquivalentValue` to call the Lens contract’s `balanceOfInAssets` and tightening `getVaultApy` to error when the chain ID lacks a `VEDA_API_NETWORK_NAMES` mapping (adds Monad). Adds corresponding structs/types, logging, exports, dependency on `@metamask/remote-feature-flag-controller`, and extensive test coverage for config lifecycle and cache invalidation. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 5e603a6. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 0c7feb0 commit f7df669

16 files changed

Lines changed: 910 additions & 185 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ linkStyle default opacity:0.5
375375
money_account_balance_service --> controller_utils;
376376
money_account_balance_service --> messenger;
377377
money_account_balance_service --> network_controller;
378+
money_account_balance_service --> remote_feature_flag_controller;
378379
money_account_controller --> accounts_controller;
379380
money_account_controller --> base_controller;
380381
money_account_controller --> keyring_controller;

packages/money-account-balance-service/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Add `VaultConfigNotAvailableError` and `VaultConfigValidationError` error classes for typed consumer error handling ([#8742](https://github.com/MetaMask/core/pull/8742))
13+
- Add `LENS_ABI` constant for the Arctic Architecture Lens contract ([#8742](https://github.com/MetaMask/core/pull/8742))
14+
1015
### Changed
1116

17+
- **BREAKING:** `MoneyAccountBalanceService` no longer accepts vault config via constructor. Vault config is now read from `RemoteFeatureFlagController` state. Add `@metamask/remote-feature-flag-controller` as a dependency and permit `RemoteFeatureFlagController:getState`action and `RemoteFeatureFlagController:stateChange` event on the service's messenger. Service methods throw `VaultConfigNotAvailableError` until a valid config is available. ([#8742](https://github.com/MetaMask/core/pull/8742))
18+
- **BREAKING:** `VaultConfig` fields have changed — `vaultAddress``boringVault`, `vaultChainId``chainId`; `underlyingTokenAddress` and `underlyingTokenDecimals` removed; `lensAddress` and `tellerAddress` added ([#8742](https://github.com/MetaMask/core/pull/8742))
19+
- **BREAKING:** `MusdEquivalentValueResponse` shape has changed — `musdSHFvdBalance`, `exchangeRate`, and `musdEquivalentValue` replaced by a single `balanceOfInAssets` field ([#8742](https://github.com/MetaMask/core/pull/8742))
20+
- Monad (`0x8f`) added to `VEDA_API_NETWORK_NAMES` ([#8742](https://github.com/MetaMask/core/pull/8742))
1221
- Bump `@metamask/messenger` from `^1.1.1` to `^1.2.0` ([#8632](https://github.com/MetaMask/core/pull/8632))
1322
- Bump `@metamask/network-controller` from `^30.0.1` to `^30.1.0` ([#8636](https://github.com/MetaMask/core/pull/8636))
1423

packages/money-account-balance-service/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"@metamask/messenger": "^1.2.0",
6161
"@metamask/metamask-eth-abis": "^3.1.1",
6262
"@metamask/network-controller": "^30.1.0",
63+
"@metamask/remote-feature-flag-controller": "^4.2.0",
6364
"@metamask/superstruct": "^3.1.0",
6465
"@metamask/utils": "^11.9.0"
6566
},

packages/money-account-balance-service/src/constants.ts

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,31 @@ import { Hex } from '@metamask/utils';
22

33
export const VEDA_PERFORMANCE_API_BASE_URL = 'https://api.sevenseas.capital';
44

5+
/**
6+
* The key under which vault config is stored in
7+
* `RemoteFeatureFlagController` state's `remoteFeatureFlags` map.
8+
*/
9+
export const VAULT_CONFIG_FEATURE_FLAG_KEY = 'moneyAccountVaultConfig';
10+
511
export const VEDA_API_NETWORK_NAMES: Record<Hex, string> = {
612
'0xa4b1': 'arbitrum',
13+
'0x8f': 'monad',
714
};
815

9-
export const DEFAULT_VEDA_API_NETWORK_NAME = VEDA_API_NETWORK_NAMES['0xa4b1'];
10-
1116
/**
12-
* Minimal ABI for the Veda Accountant's `getRate()` function (selector 0x679aefce).
13-
* Returns the exchange rate between vault shares (musdSHFvd) and the
14-
* underlying asset (mUSD) as a uint256.
17+
* Minimal ABI for the Veda Accountant contract. Covers:
18+
* - base (0x5001f3b5) — the underlying ERC20 base asset address
19+
* - getRate (0x679aefce) — exchange rate between vault shares and the
20+
* underlying asset (mUSD) as a uint256
1521
*/
1622
export const ACCOUNTANT_ABI = [
23+
{
24+
inputs: [],
25+
name: 'base',
26+
outputs: [{ internalType: 'contract ERC20', name: '', type: 'address' }],
27+
stateMutability: 'view',
28+
type: 'function',
29+
},
1730
{
1831
inputs: [],
1932
name: 'getRate',
@@ -22,3 +35,33 @@ export const ACCOUNTANT_ABI = [
2235
type: 'function',
2336
},
2437
] as const;
38+
39+
/**
40+
* Minimal ABI for the Arctic Architecture Lens contract.
41+
* Covers:
42+
* - balanceOf (0xf7888aec) — shares held by an account in a BoringVault
43+
* - balanceOfInAssets (0x789fd871) — share balance denominated in underlying assets
44+
*/
45+
export const LENS_ABI = [
46+
{
47+
inputs: [
48+
{ name: 'account', type: 'address' },
49+
{ name: 'boringVault', type: 'address' },
50+
],
51+
name: 'balanceOf',
52+
outputs: [{ name: 'shares', type: 'uint256' }],
53+
stateMutability: 'view',
54+
type: 'function',
55+
},
56+
{
57+
inputs: [
58+
{ name: 'account', type: 'address' },
59+
{ name: 'boringVault', type: 'address' },
60+
{ name: 'accountant', type: 'address' },
61+
],
62+
name: 'balanceOfInAssets',
63+
outputs: [{ name: 'assets', type: 'uint256' }],
64+
stateMutability: 'view',
65+
type: 'function',
66+
},
67+
] as const;

packages/money-account-balance-service/src/errors.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,34 @@ export class VedaResponseValidationError extends Error {
44
this.name = 'VedaResponseValidationError';
55
}
66
}
7+
8+
/**
9+
* Thrown when a public method is called but vault config has not yet been
10+
* loaded from RemoteFeatureFlagController, or the flag key is absent.
11+
* This is a transient condition — the service will recover once flags are
12+
* fetched and a valid config arrives.
13+
*/
14+
export class VaultConfigNotAvailableError extends Error {
15+
constructor() {
16+
super(
17+
'MoneyAccountBalanceService: vault config is not available. ' +
18+
'RemoteFeatureFlagController may not have fetched flags yet.',
19+
);
20+
this.name = 'VaultConfigNotAvailableError';
21+
}
22+
}
23+
24+
/**
25+
* Thrown when the vault config flag value is present but fails superstruct
26+
* validation. This surfaces to error monitoring service (e.g. Sentry) via the messenger's captureException
27+
* handler.
28+
*/
29+
export class VaultConfigValidationError extends Error {
30+
constructor(message?: string) {
31+
super(
32+
message ??
33+
'MoneyAccountBalanceService: vault config from remote feature flags is malformed.',
34+
);
35+
this.name = 'VaultConfigValidationError';
36+
}
37+
}

packages/money-account-balance-service/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,8 @@ export type {
1616
MusdEquivalentValueResponse,
1717
NormalizedVaultApyResponse,
1818
} from './response.types';
19+
export {
20+
VaultConfigNotAvailableError,
21+
VaultConfigValidationError,
22+
} from './errors';
23+
export type { VaultConfig } from './types';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { createProjectLogger, createModuleLogger } from '@metamask/utils';
2+
3+
export const projectLogger = createProjectLogger(
4+
'money-account-balance-service',
5+
);
6+
7+
export { createModuleLogger };

packages/money-account-balance-service/src/money-account-balance-service-method-action-types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { MoneyAccountBalanceService } from './money-account-balance-service
1010
*
1111
* @param accountAddress - The Money account's Ethereum address.
1212
* @returns The mUSD balance as a raw uint256 string.
13+
* @throws {@link VaultConfigNotAvailableError} if vault config has not been loaded.
1314
*/
1415
export type MoneyAccountBalanceServiceGetMusdBalanceAction = {
1516
type: `MoneyAccountBalanceService:getMusdBalance`;
@@ -22,6 +23,7 @@ export type MoneyAccountBalanceServiceGetMusdBalanceAction = {
2223
*
2324
* @param accountAddress - The Money account's Ethereum address.
2425
* @returns The musdSHFvd balance as a raw uint256 string.
26+
* @throws {@link VaultConfigNotAvailableError} if vault config has not been loaded.
2527
*/
2628
export type MoneyAccountBalanceServiceGetMusdSHFvdBalanceAction = {
2729
type: `MoneyAccountBalanceService:getMusdSHFvdBalance`;
@@ -36,6 +38,7 @@ export type MoneyAccountBalanceServiceGetMusdSHFvdBalanceAction = {
3638
* @param options - The options for the query.
3739
* @param options.staleTime - The stale time for the query. Defaults to 30 seconds.
3840
* @returns The exchange rate as a raw uint256 string.
41+
* @throws {@link VaultConfigNotAvailableError} if vault config has not been loaded.
3942
*/
4043
export type MoneyAccountBalanceServiceGetExchangeRateAction = {
4144
type: `MoneyAccountBalanceService:getExchangeRate`;
@@ -51,6 +54,7 @@ export type MoneyAccountBalanceServiceGetExchangeRateAction = {
5154
* @param accountAddress - The Money account's Ethereum address.
5255
* @returns The musdSHFvd balance, exchange rate, and computed
5356
* mUSD-equivalent value as raw uint256 strings.
57+
* @throws {@link VaultConfigNotAvailableError} if vault config has not been loaded.
5458
*/
5559
export type MoneyAccountBalanceServiceGetMusdEquivalentValueAction = {
5660
type: `MoneyAccountBalanceService:getMusdEquivalentValue`;
@@ -61,6 +65,7 @@ export type MoneyAccountBalanceServiceGetMusdEquivalentValueAction = {
6165
* Fetches the vault's APY and fee breakdown from the Veda performance REST API.
6266
*
6367
* @returns The normalized vault APY response.
68+
* @throws {@link VaultConfigNotAvailableError} if vault config has not been loaded.
6469
*/
6570
export type MoneyAccountBalanceServiceGetVaultApyAction = {
6671
type: `MoneyAccountBalanceService:getVaultApy`;

0 commit comments

Comments
 (0)