Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
d070106
prices v3 upgrade
bergarces Nov 11, 2025
6d1f9e5
only support evm tokens
bergarces Nov 11, 2025
30c4ebb
update references
bergarces Nov 12, 2025
63b470f
fix test for lint
bergarces Nov 12, 2025
87ee9ad
remove unused vars
bergarces Nov 12, 2025
6534f68
correct logic
bergarces Nov 12, 2025
a0a67cf
fixes
bergarces Nov 12, 2025
3c9e945
fetch all chains together
bergarces Nov 13, 2025
3a3fca8
update
bergarces Nov 14, 2025
4dee204
update references
bergarces Nov 14, 2025
06da73a
update
bergarces Nov 14, 2025
0b0654e
linting
bergarces Nov 14, 2025
23a0575
revert breaking changes
bergarces Nov 14, 2025
8949840
add native token
bergarces Nov 14, 2025
32bc01d
Merge branch 'main' into prices-v3-upgrade
bergarces Nov 14, 2025
87cde78
fix
bergarces Nov 14, 2025
d816194
refactor code
bergarces Nov 17, 2025
c9ba06a
Merge branch 'main' into prices-v3-upgrade
bergarces Nov 17, 2025
82ca47d
remove comment
bergarces Nov 17, 2025
e7540ee
fix test
bergarces Nov 17, 2025
00fbbad
tests
bergarces Nov 17, 2025
0346651
fix test
bergarces Nov 17, 2025
bdb8179
tests
bergarces Nov 18, 2025
af45e9e
tests fixed
bergarces Nov 18, 2025
689ba05
fallback
bergarces Nov 18, 2025
05e61b2
Merge branch 'main' into prices-v3-upgrade
bergarces Nov 18, 2025
51c0efd
removed overcautious check
bergarces Nov 18, 2025
2531f64
fixes for tests
bergarces Nov 18, 2025
ec44139
changelog
bergarces Nov 18, 2025
1310de4
missing check
bergarces Nov 18, 2025
16cf753
remove lowercase conversion
bergarces Nov 18, 2025
dcf4950
fix changelog
bergarces Nov 18, 2025
2ce7690
remove trailing space
bergarces Nov 18, 2025
cf12c8c
support object
bergarces Nov 18, 2025
158332d
fixes
bergarces Nov 18, 2025
0bbfd79
Merge branch 'main' into prices-v3-upgrade
bergarces Nov 18, 2025
d356e04
fix changelog
bergarces Nov 18, 2025
92aa023
changelog fix
bergarces Nov 18, 2025
6d7c0d8
Merge branch 'main' into prices-v3-upgrade
bergarces Nov 18, 2025
7b66c7a
update chains
bergarces Nov 19, 2025
655d573
commented chains
bergarces Nov 19, 2025
40ec088
Merge branch 'main' into prices-v3-upgrade
bergarces Nov 19, 2025
ad447d9
add fallback for v2
bergarces Nov 19, 2025
152da47
Merge branch 'main' into prices-v3-upgrade
bergarces Nov 19, 2025
e879757
remove console.log
bergarces Nov 19, 2025
c294836
coverage
bergarces Nov 19, 2025
197e722
update list
bergarces Nov 19, 2025
7cb1716
Merge branch 'main' into prices-v3-upgrade
bergarces Nov 19, 2025
f83c205
fix test
bergarces Nov 19, 2025
19323ad
put chains in order
bergarces Nov 19, 2025
e306214
Merge branch 'main' into prices-v3-upgrade
bergarces Nov 20, 2025
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: 8 additions & 0 deletions packages/assets-controllers/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- **BREAKING:** Update `spot-prices` endpoint to use Price API v3 ([#7119](https://github.com/MetaMask/core/pull/7119))
- Update `AbstractTokenPricesService.fetchTokenPrices` arguments and return type
- Update `CodefiTokenPricesServiceV2` list of supported currencies
- Update `TokenRatesController` to fetch prices by native currency instead of by chain
- Remove legacy polling code and unused events from `TokenRatesController`

## [90.0.0]

### Added
Expand Down
110 changes: 72 additions & 38 deletions packages/assets-controllers/src/CurrencyRateController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function buildMockTokenPricesService(
): AbstractTokenPricesService {
return {
async fetchTokenPrices() {
return {};
return [];
},
async fetchExchangeRates() {
return {};
Expand Down Expand Up @@ -1138,12 +1138,15 @@ describe('CurrencyRateController', () => {
// Mock fetchTokenPrices to return token prices
jest
.spyOn(tokenPricesService, 'fetchTokenPrices')
.mockImplementation(async ({ chainId }) => {
if (chainId === '0x1') {
return {
'0x0000000000000000000000000000000000000000': {
.mockImplementation(async ({ assets }) => {
// eslint-disable-next-line jest/no-conditional-in-test
if (assets.some((asset) => asset.chainId === '0x1')) {
return [
{
currency: 'usd',
tokenAddress: '0x0000000000000000000000000000000000000000',
chainId: assets[0].chainId,
assetId: 'xx:yy/aa:bb',
price: 2500.5,
pricePercentChange1d: 0,
priceChange1d: 0,
Expand All @@ -1163,13 +1166,16 @@ describe('CurrencyRateController', () => {
pricePercentChange7d: 100,
totalVolume: 100,
},
};
];
}
if (chainId === '0x89') {
return {
'0x0000000000000000000000000000000000001010': {
// eslint-disable-next-line jest/no-conditional-in-test
if (assets.some((asset) => asset.chainId === '0x89')) {
return [
{
currency: 'usd',
tokenAddress: '0x0000000000000000000000000000000000001010',
chainId: assets[0].chainId,
assetId: 'xx:yy/aa:bb',
price: 0.85,
pricePercentChange1d: 0,
priceChange1d: 0,
Expand All @@ -1189,9 +1195,9 @@ describe('CurrencyRateController', () => {
pricePercentChange7d: 100,
totalVolume: 100,
},
};
];
}
return {};
return [];
});

// Make crypto compare also fail by not mocking it (no nock setup)
Expand Down Expand Up @@ -1255,12 +1261,21 @@ describe('CurrencyRateController', () => {

const fetchTokenPricesSpy = jest
.spyOn(tokenPricesService, 'fetchTokenPrices')
.mockImplementation(async ({ chainId }) => {
if (chainId === '0x1' || chainId === '0xaa36a7') {
return {
'0x0000000000000000000000000000000000000000': {
.mockImplementation(async ({ assets }) => {
// eslint-disable-next-line jest/no-conditional-in-test
if (
assets.some(
(asset) =>
// eslint-disable-next-line jest/no-conditional-in-test
asset.chainId === '0x1' || asset.chainId === '0xaa36a7',
)
) {
return [
{
currency: 'usd',
tokenAddress: '0x0000000000000000000000000000000000000000',
chainId: assets[0].chainId,
assetId: 'xx:yy/aa:bb',
price: 2500.5,
pricePercentChange1d: 0,
priceChange1d: 0,
Expand All @@ -1280,9 +1295,9 @@ describe('CurrencyRateController', () => {
pricePercentChange7d: 100,
totalVolume: 100,
},
};
];
}
return {};
return [];
});

const controller = new CurrencyRateController({
Expand All @@ -1296,8 +1311,12 @@ describe('CurrencyRateController', () => {
// Should only call fetchTokenPrices once, using first matching chainId (line 255)
expect(fetchTokenPricesSpy).toHaveBeenCalledTimes(1);
expect(fetchTokenPricesSpy).toHaveBeenCalledWith({
chainId: '0x1', // First chainId with ETH as native currency
tokenAddresses: [],
assets: [
{
chainId: '0x1',
tokenAddress: '0x0000000000000000000000000000000000000000',
},
],
currency: 'usd',
});

Expand Down Expand Up @@ -1338,13 +1357,16 @@ describe('CurrencyRateController', () => {

jest
.spyOn(tokenPricesService, 'fetchTokenPrices')
.mockImplementation(async ({ chainId }) => {
if (chainId === '0x1') {
.mockImplementation(async ({ assets }) => {
// eslint-disable-next-line jest/no-conditional-in-test
if (assets.some((asset) => asset.chainId === '0x1')) {
// ETH succeeds
return {
'0x0000000000000000000000000000000000000000': {
return [
{
currency: 'usd',
tokenAddress: '0x0000000000000000000000000000000000000000',
chainId: assets[0].chainId,
assetId: 'xx:yy/aa:bb',
price: 2500.5,
pricePercentChange1d: 0,
priceChange1d: 0,
Expand All @@ -1364,7 +1386,7 @@ describe('CurrencyRateController', () => {
pricePercentChange7d: 100,
totalVolume: 100,
},
};
];
}
// POL fails
throw new Error('Failed to fetch POL price');
Expand Down Expand Up @@ -1427,7 +1449,7 @@ describe('CurrencyRateController', () => {
.mockRejectedValue(new Error('Price API failed'));

// Return empty object (no token price)
jest.spyOn(tokenPricesService, 'fetchTokenPrices').mockResolvedValue({});
jest.spyOn(tokenPricesService, 'fetchTokenPrices').mockResolvedValue([]);

const controller = new CurrencyRateController({
messenger,
Expand Down Expand Up @@ -1475,12 +1497,15 @@ describe('CurrencyRateController', () => {

const fetchTokenPricesSpy = jest
.spyOn(tokenPricesService, 'fetchTokenPrices')
.mockImplementation(async ({ chainId }) => {
if (chainId === '0x1') {
return {
'0x0000000000000000000000000000000000000000': {
.mockImplementation(async ({ assets }) => {
// eslint-disable-next-line jest/no-conditional-in-test
if (assets.some((asset) => asset.chainId === '0x1')) {
return [
{
currency: 'usd',
tokenAddress: '0x0000000000000000000000000000000000000000',
chainId: assets[0].chainId,
assetId: 'xx:yy/aa:bb',
price: 2500.5,
pricePercentChange1d: 0,
priceChange1d: 0,
Expand All @@ -1500,9 +1525,9 @@ describe('CurrencyRateController', () => {
pricePercentChange7d: 100,
totalVolume: 100,
},
};
];
}
return {};
return [];
});

const controller = new CurrencyRateController({
Expand All @@ -1517,8 +1542,12 @@ describe('CurrencyRateController', () => {
// Should only call fetchTokenPrices for ETH, not BNB (line 252: if chainIds.length > 0)
expect(fetchTokenPricesSpy).toHaveBeenCalledTimes(1);
expect(fetchTokenPricesSpy).toHaveBeenCalledWith({
chainId: '0x1',
tokenAddresses: [],
assets: [
{
chainId: '0x1',
tokenAddress: '0x0000000000000000000000000000000000000000',
},
],
currency: 'usd',
});

Expand Down Expand Up @@ -1562,10 +1591,11 @@ describe('CurrencyRateController', () => {

const fetchTokenPricesSpy = jest
.spyOn(tokenPricesService, 'fetchTokenPrices')
.mockResolvedValue({
'0x0000000000000000000000000000000000001010': {
.mockResolvedValue([
{
currency: 'usd',
tokenAddress: '0x0000000000000000000000000000000000001010',
chainId: '0x89',
price: 0.85,
pricePercentChange1d: 0,
priceChange1d: 0,
Expand All @@ -1585,7 +1615,7 @@ describe('CurrencyRateController', () => {
pricePercentChange7d: 100,
totalVolume: 100,
},
});
]);

const controller = new CurrencyRateController({
messenger,
Expand All @@ -1597,8 +1627,12 @@ describe('CurrencyRateController', () => {

// Should use Polygon's native token address (line 269)
expect(fetchTokenPricesSpy).toHaveBeenCalledWith({
chainId: '0x89',
tokenAddresses: [],
assets: [
{
chainId: '0x89',
tokenAddress: '0x0000000000000000000000000000000000001010',
},
],
currency: 'usd',
});

Expand Down
9 changes: 6 additions & 3 deletions packages/assets-controllers/src/CurrencyRateController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,15 @@ export class CurrencyRateController extends StaticIntervalPollingController<Curr
const nativeTokenAddress = getNativeTokenAddress(chainId);
// Pass empty array as fetchTokenPrices automatically includes the native token address
const tokenPrices = await this.#tokenPricesService.fetchTokenPrices({
chainId,
tokenAddresses: [],
assets: [{ chainId, tokenAddress: nativeTokenAddress }],
currency: currentCurrency,
});

const tokenPrice = tokenPrices[nativeTokenAddress];
const tokenPrice = tokenPrices.find(
(item) =>
item.tokenAddress.toLowerCase() ===
nativeTokenAddress.toLowerCase(),
);

return {
nativeCurrency,
Expand Down
Loading