Skip to content

Commit ebd81f5

Browse files
authored
feat: MUSD-248 create money-account-balance-service (#8428)
## Explanation Adds `MoneyAccountBalanceService` to `@metamask/money-account-controller` to support the MetaMask Money feature (mUSD stablecoin and musdSHFvd Veda vault shares). The service exposes five messenger actions: fetching mUSD and musdSHFvd ERC-20 balances via RPC, reading the Veda Accountant contract exchange rate, computing mUSD-equivalent vault share value, and fetching vault APY from Veda's Seven Seas REST API. All on-chain reads use `@ethersproject/contracts` with a `Web3Provider` resolved through `NetworkController` messenger actions. ## Key design decisions - **TanStack Query caching**: RPC calls use a 30-second staleTime; the Veda APY endpoint uses 5 minutes. `getMusdEquivalentValue` delegates to the two underlying cached queries and performs BigInt arithmetic over cached results rather than issuing its own `fetchQuery`, avoiding cache coordination issues. - **`VedaResponseValidationError` excluded from retry policy**: Malformed API responses are a deterministic failure — retrying them wastes quota. A custom error class signals the service policy to skip retry for validation failures. - **Loose Superstruct validation**: The Veda APY schema uses `type()` rather than `object()` so unknown fields don't cause validation failures, guarding against future API additions without breaking the service. - **`@metamask/utils` promoted to production dependency**: Previously a dev dependency, it is now required at runtime for hex/BigInt utilities used in balance and exchange rate computation. ## References - [MUSD-248: Create MoneyAccountBalanceService to fetch users Veda Vault balances](https://consensyssoftware.atlassian.net/browse/MUSD-248) <!-- 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 --> ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] 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** > Adds a new cached data service that performs on-chain RPC reads and external REST fetches; correctness and error-handling/caching behavior could impact displayed balances/APY, but changes are largely additive and isolated to a new package. > > **Overview** > Introduces a new package, `@metamask/money-account-balance-service`, implementing `MoneyAccountBalanceService` (a `BaseDataService`) that exposes messenger actions to fetch mUSD and vault-share ERC-20 balances, read the Veda Accountant exchange rate, compute mUSD-equivalent vault-share value, and fetch/normalize vault APY from Veda’s Seven Seas REST API (with Superstruct response validation and a non-retryable `VedaResponseValidationError`). > > Wires the new package into repo metadata (adds to `README` package list/dependency graph, `CODEOWNERS`, `teams.json`, and `yarn.lock`) and includes full package scaffolding (build/test/typedoc configs, changelog, and extensive Jest coverage including caching and error-path behavior). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2deb59b. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent e4a520c commit ebd81f5

21 files changed

Lines changed: 1622 additions & 0 deletions

.github/CODEOWNERS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
## Earn Team
4141
/packages/earn-controller @MetaMask/earn
42+
/packages/money-account-balance-service @MetaMask/earn
4243

4344
## Social AI Team
4445
/packages/ai-controllers @MetaMask/social-ai
@@ -146,6 +147,8 @@
146147
/packages/delegation-controller/CHANGELOG.md @MetaMask/delegation @MetaMask/core-platform
147148
/packages/earn-controller/package.json @MetaMask/earn @MetaMask/core-platform
148149
/packages/earn-controller/CHANGELOG.md @MetaMask/earn @MetaMask/core-platform
150+
/packages/money-account-balance-service/package.json @MetaMask/earn @MetaMask/core-platform
151+
/packages/money-account-balance-service/CHANGELOG.md @MetaMask/earn @MetaMask/core-platform
149152
/packages/eip-5792-middleware/package.json @MetaMask/wallet-integrations @MetaMask/core-platform
150153
/packages/eip-5792-middleware/CHANGELOG.md @MetaMask/wallet-integrations @MetaMask/core-platform
151154
/packages/eip1193-permission-middleware/package.json @MetaMask/wallet-integrations @MetaMask/core-platform

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Each package in this repository has its own README where you can find installati
6565
- [`@metamask/message-manager`](packages/message-manager)
6666
- [`@metamask/messenger`](packages/messenger)
6767
- [`@metamask/messenger-cli`](packages/messenger-cli)
68+
- [`@metamask/money-account-balance-service`](packages/money-account-balance-service)
6869
- [`@metamask/money-account-controller`](packages/money-account-controller)
6970
- [`@metamask/multichain-account-service`](packages/multichain-account-service)
7071
- [`@metamask/multichain-api-middleware`](packages/multichain-api-middleware)
@@ -151,6 +152,7 @@ linkStyle default opacity:0.5
151152
message_manager(["@metamask/message-manager"]);
152153
messenger(["@metamask/messenger"]);
153154
messenger_cli(["@metamask/messenger-cli"]);
155+
money_account_balance_service(["@metamask/money-account-balance-service"]);
154156
money_account_controller(["@metamask/money-account-controller"]);
155157
multichain_account_service(["@metamask/multichain-account-service"]);
156158
multichain_api_middleware(["@metamask/multichain-api-middleware"]);
@@ -350,6 +352,10 @@ linkStyle default opacity:0.5
350352
message_manager --> base_controller;
351353
message_manager --> controller_utils;
352354
message_manager --> messenger;
355+
money_account_balance_service --> base_data_service;
356+
money_account_balance_service --> controller_utils;
357+
money_account_balance_service --> messenger;
358+
money_account_balance_service --> network_controller;
353359
money_account_controller --> accounts_controller;
354360
money_account_controller --> base_controller;
355361
money_account_controller --> keyring_controller;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
### Added
11+
12+
- Add `MoneyAccountBalanceService` data service ([#8428](https://github.com/MetaMask/core/pull/8428))
13+
- Fetch mUSD ERC-20 balance via RPC (`getMusdBalance`)
14+
- Fetch musdSHFvd vault share balance via RPC (`getMusdSHFvdBalance`)
15+
- Fetch Veda Accountant exchange rate via RPC (`getExchangeRate`)
16+
- Compute mUSD-equivalent value of vault share holdings (`getMusdEquivalentValue`)
17+
- Fetch vault APY from the Veda performance REST API (`getVaultApy`)
18+
19+
[Unreleased]: https://github.com/MetaMask/core/
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 MetaMask
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# `@metamask/money-account-balance-service`
2+
3+
Data service for fetching Money account balances via on-chain RPC reads, the Veda Accountant exchange rate, and the Veda vault APY from Veda's REST API.
4+
5+
## Installation
6+
7+
`yarn add @metamask/money-account-balance-service`
8+
9+
or
10+
11+
`npm install @metamask/money-account-balance-service`
12+
13+
## Contributing
14+
15+
This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/core/blob/main/README.md).
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* For a detailed explanation regarding each configuration property and type check, visit:
3+
* https://jestjs.io/docs/configuration
4+
*/
5+
6+
const merge = require('deepmerge');
7+
const path = require('path');
8+
9+
const baseConfig = require('../../jest.config.packages');
10+
11+
const displayName = path.basename(__dirname);
12+
13+
module.exports = merge(baseConfig, {
14+
displayName,
15+
16+
coverageThreshold: {
17+
global: {
18+
branches: 100,
19+
functions: 100,
20+
lines: 100,
21+
statements: 100,
22+
},
23+
},
24+
});
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
{
2+
"name": "@metamask/money-account-balance-service",
3+
"version": "0.0.0",
4+
"description": "Data service for fetching Money account balances, exchange rates, and vault APY",
5+
"keywords": [
6+
"Ethereum",
7+
"MetaMask"
8+
],
9+
"homepage": "https://github.com/MetaMask/core/tree/main/packages/money-account-balance-service#readme",
10+
"bugs": {
11+
"url": "https://github.com/MetaMask/core/issues"
12+
},
13+
"license": "MIT",
14+
"repository": {
15+
"type": "git",
16+
"url": "https://github.com/MetaMask/core.git"
17+
},
18+
"files": [
19+
"dist/"
20+
],
21+
"sideEffects": false,
22+
"main": "./dist/index.cjs",
23+
"types": "./dist/index.d.cts",
24+
"exports": {
25+
".": {
26+
"import": {
27+
"types": "./dist/index.d.mts",
28+
"default": "./dist/index.mjs"
29+
},
30+
"require": {
31+
"types": "./dist/index.d.cts",
32+
"default": "./dist/index.cjs"
33+
}
34+
},
35+
"./package.json": "./package.json"
36+
},
37+
"publishConfig": {
38+
"access": "public",
39+
"registry": "https://registry.npmjs.org/"
40+
},
41+
"scripts": {
42+
"build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references",
43+
"build:all": "ts-bridge --project tsconfig.build.json --verbose --clean",
44+
"build:docs": "typedoc",
45+
"changelog:update": "../../scripts/update-changelog.sh @metamask/money-account-balance-service",
46+
"changelog:validate": "../../scripts/validate-changelog.sh @metamask/money-account-balance-service",
47+
"messenger-action-types:check": "tsx ../../packages/messenger-cli/src/cli.ts --check",
48+
"messenger-action-types:generate": "tsx ../../packages/messenger-cli/src/cli.ts --generate",
49+
"since-latest-release": "../../scripts/since-latest-release.sh",
50+
"test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
51+
"test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
52+
"test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
53+
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
54+
},
55+
"dependencies": {
56+
"@ethersproject/contracts": "^5.7.0",
57+
"@ethersproject/providers": "^5.7.0",
58+
"@metamask/base-data-service": "^0.1.1",
59+
"@metamask/controller-utils": "^11.20.0",
60+
"@metamask/messenger": "^1.1.1",
61+
"@metamask/metamask-eth-abis": "^3.1.1",
62+
"@metamask/network-controller": "^30.0.1",
63+
"@metamask/superstruct": "^3.1.0",
64+
"@metamask/utils": "^11.9.0"
65+
},
66+
"devDependencies": {
67+
"@metamask/auto-changelog": "^6.0.0",
68+
"@ts-bridge/cli": "^0.6.4",
69+
"@types/jest": "^29.5.14",
70+
"deepmerge": "^4.2.2",
71+
"jest": "^29.7.0",
72+
"nock": "^13.3.1",
73+
"ts-jest": "^29.2.5",
74+
"tsx": "^4.20.5",
75+
"typedoc": "^0.25.13",
76+
"typedoc-plugin-missing-exports": "^2.0.0",
77+
"typescript": "~5.3.3"
78+
},
79+
"engines": {
80+
"node": "^18.18 || >=20"
81+
}
82+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Hex } from '@metamask/utils';
2+
3+
export const VEDA_PERFORMANCE_API_BASE_URL = 'https://api.sevenseas.capital';
4+
5+
export const VEDA_API_NETWORK_NAMES: Record<Hex, string> = {
6+
'0xa4b1': 'arbitrum',
7+
};
8+
9+
export const DEFAULT_VEDA_API_NETWORK_NAME = VEDA_API_NETWORK_NAMES['0xa4b1'];
10+
11+
/**
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.
15+
*/
16+
export const ACCOUNTANT_ABI = [
17+
{
18+
inputs: [],
19+
name: 'getRate',
20+
outputs: [{ internalType: 'uint256', name: 'rate', type: 'uint256' }],
21+
stateMutability: 'view',
22+
type: 'function',
23+
},
24+
] as const;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export class VedaResponseValidationError extends Error {
2+
constructor(message?: string) {
3+
super(message ?? 'Malformed response received from Veda API');
4+
this.name = 'VedaResponseValidationError';
5+
}
6+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export { MoneyAccountBalanceService } from './money-account-balance-service';
2+
export type {
3+
MoneyAccountBalanceServiceActions,
4+
MoneyAccountBalanceServiceEvents,
5+
MoneyAccountBalanceServiceMessenger,
6+
} from './money-account-balance-service';
7+
export type {
8+
MoneyAccountBalanceServiceGetMusdBalanceAction,
9+
MoneyAccountBalanceServiceGetMusdSHFvdBalanceAction,
10+
MoneyAccountBalanceServiceGetExchangeRateAction,
11+
MoneyAccountBalanceServiceGetMusdEquivalentValueAction,
12+
MoneyAccountBalanceServiceGetVaultApyAction,
13+
} from './money-account-balance-service-method-action-types';
14+
export type {
15+
ExchangeRateResponse,
16+
MusdEquivalentValueResponse,
17+
NormalizedVaultApyResponse,
18+
} from './response.types';

0 commit comments

Comments
 (0)