Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
570ba35
fix: Managed `removed` property from `MultichainAssetsController:acco…
gabrieledm Apr 15, 2026
273821c
fix: Managed `removed` property from `MultichainAssetsController:acco…
gabrieledm Apr 15, 2026
186ecd9
Merge branch 'fix/NEB-892_usdt-balance-not-updating-after-swap' of ht…
gabrieledm Apr 15, 2026
bc7bc40
fix: run `yarn lint:eslint --prune-suppressions`
gabrieledm Apr 15, 2026
0ecdfea
fix: format `eslint-suppressions.json`
gabrieledm Apr 15, 2026
fb458a9
Merge branch 'main' into fix/NEB-892_usdt-balance-not-updating-after-…
gabrieledm Apr 15, 2026
3d06532
Merge branch 'main' into fix/NEB-892_usdt-balance-not-updating-after-…
gabrieledm Apr 15, 2026
ab9e1a9
Merge branch 'main' into fix/NEB-892_usdt-balance-not-updating-after-…
gabrieledm Apr 15, 2026
e5bc4d4
fix: CHANGELOG structure for assets-controllers
gabrieledm Apr 15, 2026
f45136c
Merge branch 'main' into fix/NEB-892_usdt-balance-not-updating-after-…
gabrieledm Apr 15, 2026
90786b5
Merge branch 'main' into fix/NEB-892_usdt-balance-not-updating-after-…
gabrieledm Apr 15, 2026
21da602
Merge branch 'main' into fix/NEB-892_usdt-balance-not-updating-after-…
gabrieledm Apr 16, 2026
30c7435
Merge branch 'main' into fix/NEB-892_usdt-balance-not-updating-after-…
gabrieledm Apr 16, 2026
a11e400
Merge branch 'main' into fix/NEB-892_usdt-balance-not-updating-after-…
gabrieledm Apr 16, 2026
ef10345
fix: added clean up of stale assets also in MultichainAssetsRatesCont…
gabrieledm Apr 16, 2026
25e9e55
Merge branch 'fix/NEB-892_usdt-balance-not-updating-after-swap' of ht…
gabrieledm Apr 16, 2026
c5fa690
fix: run eslint:fix
gabrieledm Apr 16, 2026
53ec52e
fix: run eslint:fix
gabrieledm Apr 16, 2026
8f95f2f
Merge branch 'fix/NEB-892_usdt-balance-not-updating-after-swap' of ht…
gabrieledm Apr 16, 2026
28024ac
Merge branch 'main' into fix/NEB-892_usdt-balance-not-updating-after-…
gabrieledm Apr 16, 2026
6065a9b
Merge branch 'fix/NEB-892_usdt-balance-not-updating-after-swap' of ht…
gabrieledm Apr 16, 2026
c395c16
fix: TransactionMeta import
gabrieledm Apr 16, 2026
b2adaa5
Merge branch 'main' into fix/NEB-892_usdt-balance-not-updating-after-…
gabrieledm Apr 16, 2026
891fa43
fix: guard against empty `added` assets
gabrieledm Apr 16, 2026
d363b2d
Merge branch 'main' into fix/NEB-892_usdt-balance-not-updating-after-…
gabrieledm Apr 17, 2026
acdc947
Merge branch 'main' into fix/NEB-892_usdt-balance-not-updating-after-…
gabrieledm Apr 17, 2026
6195114
Merge branch 'main' into fix/NEB-892_usdt-balance-not-updating-after-…
gabrieledm Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,6 @@
},
"packages/assets-controllers/src/MultichainBalancesController/MultichainBalancesController.ts": {
"@typescript-eslint/no-misused-promises": {
"count": 2
},
"@typescript-eslint/prefer-nullish-coalescing": {
"count": 1
},
"no-restricted-syntax": {
Expand Down Expand Up @@ -2248,11 +2245,6 @@
"count": 1
}
},
"packages/transaction-pay-controller/src/utils/source-amounts.ts": {
"import-x/no-relative-packages": {
"count": 1
}
},
"packages/transaction-pay-controller/src/utils/transaction.ts": {
"no-restricted-syntax": {
"count": 2
Expand Down
1 change: 1 addition & 0 deletions packages/assets-controllers/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Used the `removed` property coming from `MultichainAssetsController:accountAssetListUpdated` event body to manage stale balances ([#8461](https://github.com/MetaMask/core/pull/8461))
- `MultichainAssetsController`: fungible `token:` assets from automatic detection are no longer added when Blockaid bulk scan fails, returns empty, or omits that address (previously fail open); an explicit non-malicious per-token result from `PhishingController:bulkScanTokens` is now required before add. ([#8400](https://github.com/MetaMask/core/pull/8400))
- Fix `AccountTrackerController` wiping existing balances on other chains when syncing accounts for a chain that has no state entry yet ([#8505](https://github.com/MetaMask/core/pull/8505))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,41 +150,43 @@ const setupController = ({
controller: MultichainAssetsRatesController;
messenger: RootMessenger;
updateSpy: jest.SpyInstance;
mockGetAssetsState: jest.Mock;
} => {
const messenger: RootMessenger = new Messenger({
namespace: MOCK_ANY_NAMESPACE,
});

messenger.registerActionHandler(
'MultichainAssetsController:getState',
() => ({
accountsAssets: {
account1: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501'],
account2: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501'],
account3: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501'],
account5: [
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
],
const mockGetAssetsState = jest.fn().mockImplementation(() => ({
accountsAssets: {
account1: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501'],
account2: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501'],
account3: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501'],
account5: [
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
],
},
assetsMetadata: {
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501': {
name: 'Solana',
symbol: 'SOL',
fungible: true,
iconUrl: 'https://example.com/solana.png',
units: [{ symbol: 'SOL', name: 'Solana', decimals: 9 }],
},
assetsMetadata: {
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501': {
name: 'Solana',
symbol: 'SOL',
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v':
{
name: 'USDC',
symbol: 'USDC',
fungible: true,
iconUrl: 'https://example.com/solana.png',
units: [{ symbol: 'SOL', name: 'Solana', decimals: 9 }],
iconUrl: 'https://example.com/usdc.png',
units: [{ symbol: 'USDC', name: 'USDC', decimals: 2 }],
},
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v':
{
name: 'USDC',
symbol: 'USDC',
fungible: true,
iconUrl: 'https://example.com/usdc.png',
units: [{ symbol: 'USDC', name: 'USDC', decimals: 2 }],
},
},
allIgnoredAssets: {},
}),
},
allIgnoredAssets: {},
}));
messenger.registerActionHandler(
'MultichainAssetsController:getState',
mockGetAssetsState,
);

messenger.registerActionHandler(
Expand Down Expand Up @@ -240,6 +242,7 @@ const setupController = ({
controller,
messenger,
updateSpy,
mockGetAssetsState,
};
};

Expand Down Expand Up @@ -524,6 +527,149 @@ describe('MultichainAssetsRatesController', () => {
});
});

it('removes stale rates and historical prices for assets no longer tracked by any account', async () => {
const removedAsset =
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:removed-token' as CaipAssetType;
const keptAsset =
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501' as CaipAssetType;
const { controller, messenger, mockGetAssetsState } = setupController({
config: {
state: {
conversionRates: {
[removedAsset]: {
rate: '10',
conversionTime: 1738539923277,
currency: 'swift:0/iso4217:USD',
},
[keptAsset]: {
rate: '202.11',
conversionTime: 1738539923277,
currency: 'swift:0/iso4217:USD',
},
},
historicalPrices: {
[removedAsset]: {
USD: {
intervals: fakeHistoricalPrices.historicalPrice.intervals,
updateTime: 1737542312,
expirationTime: 1737542312,
},
},
[keptAsset]: {
USD: {
intervals: fakeHistoricalPrices.historicalPrice.intervals,
updateTime: 1737542312,
expirationTime: 1737542312,
},
},
},
},
},
});

mockGetAssetsState.mockReturnValue({
accountsAssets: {
account1: [keptAsset],
},
assetsMetadata: {},
allIgnoredAssets: {},
});

messenger.publish('MultichainAssetsController:accountAssetListUpdated', {
assets: {
account1: {
added: [],
removed: [removedAsset],
},
},
});

await Promise.resolve();
await jestAdvanceTime({ duration: 10 });

expect(controller.state.conversionRates).toStrictEqual({
[keptAsset]: {
rate: '202.11',
conversionTime: 1738539923277,
currency: 'swift:0/iso4217:USD',
},
});
expect(controller.state.historicalPrices).toStrictEqual({
[keptAsset]: {
USD: {
intervals: fakeHistoricalPrices.historicalPrice.intervals,
updateTime: 1737542312,
expirationTime: 1737542312,
},
},
});
});

it('keeps rates for removed assets that are still tracked by another account', async () => {
const sharedAsset =
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:shared-token' as CaipAssetType;
const { controller, messenger, mockGetAssetsState } = setupController({
config: {
state: {
conversionRates: {
[sharedAsset]: {
rate: '77',
conversionTime: 1738539923277,
currency: 'swift:0/iso4217:USD',
},
},
historicalPrices: {
[sharedAsset]: {
USD: {
intervals: fakeHistoricalPrices.historicalPrice.intervals,
updateTime: 1737542312,
expirationTime: 1737542312,
},
},
},
},
},
});

mockGetAssetsState.mockReturnValue({
accountsAssets: {
account1: [],
account2: [sharedAsset],
},
assetsMetadata: {},
allIgnoredAssets: {},
});

messenger.publish('MultichainAssetsController:accountAssetListUpdated', {
assets: {
account1: {
added: [],
removed: [sharedAsset],
},
},
});

await Promise.resolve();
await jestAdvanceTime({ duration: 10 });

expect(controller.state.conversionRates).toStrictEqual({
[sharedAsset]: {
rate: '77',
conversionTime: 1738539923277,
currency: 'swift:0/iso4217:USD',
},
});
expect(controller.state.historicalPrices).toStrictEqual({
[sharedAsset]: {
USD: {
intervals: fakeHistoricalPrices.historicalPrice.intervals,
updateTime: 1737542312,
expirationTime: 1737542312,
},
},
});
});

it('handles partial or empty Snap responses gracefully', async () => {
const { controller, messenger } = setupController();

Expand Down
Loading
Loading