Skip to content

feat: TEMP - testing moving out snap historical price logic out from snaps #5938

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions packages/assets-controllers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"@metamask/polling-controller": "^13.0.0",
"@metamask/rpc-errors": "^7.0.2",
"@metamask/snaps-utils": "^9.4.0",
"@metamask/superstruct": "^3.1.0",
"@metamask/utils": "^11.2.0",
"@types/bn.js": "^5.1.5",
"@types/uuid": "^8.3.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,18 @@ import type {
import type { InternalAccount } from '@metamask/keyring-internal-api';
import { StaticIntervalPollingController } from '@metamask/polling-controller';
import type { HandleSnapRequest } from '@metamask/snaps-controllers';
import type {
SnapId,
AssetConversion,
OnAssetsConversionArguments,
OnAssetsConversionResponse,
OnAssetHistoricalPriceArguments,
OnAssetHistoricalPriceResponse,
HistoricalPriceIntervals,
import {
type SnapId,
type AssetConversion,
type OnAssetsConversionArguments,
type OnAssetsConversionResponse,
type OnAssetHistoricalPriceArguments,
type OnAssetHistoricalPriceResponse,
type HistoricalPriceIntervals,
} from '@metamask/snaps-sdk';
import { HandlerType } from '@metamask/snaps-utils';
import { object, array, tuple, number } from '@metamask/superstruct';
import { assertStruct } from '@metamask/utils';
import { Mutex } from 'async-mutex';
import type { Draft } from 'immer';

Expand All @@ -41,11 +43,26 @@ import type {
MultichainAssetsControllerState,
} from '../MultichainAssetsController';

const GetHistoricalPricesResponseStruct = object({
prices: array(tuple([number(), number()])),
marketCaps: array(tuple([number(), number()])),
totalVolumes: array(tuple([number(), number()])),
});

/**
* The name of the MultichainAssetsRatesController.
*/
const controllerName = 'MultichainAssetsRatesController';

type HistoricalPricesApiResponse = {
/** Array of price data points, each containing [timestamp, price] */
prices: [number, number][];
/** Array of market cap data points, each containing [timestamp, marketCap] */
marketCaps: [number, number][];
/** Array of total volume data points, each containing [timestamp, volume] */
totalVolumes: [number, number][];
};

// This is temporary until its exported from snap
type HistoricalPrice = {
intervals: HistoricalPriceIntervals;
Expand Down Expand Up @@ -383,14 +400,18 @@ export class MultichainAssetsRatesController extends StaticIntervalPollingContro
'AccountsController:getSelectedMultichainAccount',
);
try {
const historicalPricesResponse = await this.#handleSnapRequest({
snapId: selectedAccount?.metadata.snap?.id as SnapId,
handler: HandlerType.OnAssetHistoricalPrice,
params: {
from: asset,
to: currentCaipCurrency,
},
});
const historicalPricesResponse = await this.getHistoricalPriceFromApi(
asset,
this.#currentCurrency,
);
// const historicalPricesResponse = await this.#handleSnapRequest({
// snapId: selectedAccount?.metadata.snap?.id as SnapId,
// handler: HandlerType.OnAssetHistoricalPrice,
// params: {
// from: asset,
// to: currentCaipCurrency,
// },
// });

// skip state update if no historical prices are returned
if (!historicalPricesResponse) {
Expand Down Expand Up @@ -604,4 +625,89 @@ export class MultichainAssetsRatesController extends StaticIntervalPollingContro
},
}) as Promise<OnAssetsConversionResponse | OnAssetHistoricalPriceResponse>;
}

/**
* Fetches historical prices for a specific asset using the Price API.
*
* @param asset - The CAIP asset type to fetch historical prices for.
* @param currency - The currency to get prices in (defaults to current currency).
* @returns A promise that resolves with the historical price data in OnAssetHistoricalPriceResponse format.
*/
async getHistoricalPriceFromApi(
asset: CaipAssetType,
currency?: string,
): Promise<OnAssetHistoricalPriceResponse> {
const timePeriodsToFetch = ['1d', '7d', '1m', '3m', '1y', '1000y'];

try {
// For each time period, call the Price API to fetch the historical prices
const promises = timePeriodsToFetch.map(async (timePeriod) => {
try {
// https://price.api.cx.metamask.io/v3/historical-prices/solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501?timePeriod=1d&vsCurrency=usd
const url = `https://price.api.cx.metamask.io/v3/historical-prices/${asset}?timePeriod=${timePeriod}&vsCurrency=${currency}`;
console.log(
'MultichainAssetsRatesController.getHistoricalPriceFromApi (new internal logic gutted from snap) calling',
url,
);

const fetchResponse = await fetch(url);
if (!fetchResponse.ok) {
throw new Error(`HTTP error! status: ${fetchResponse.status}`);
}

const response: HistoricalPricesApiResponse =
await fetchResponse.json();

// Add assertion similar to snap
assertStruct(response, GetHistoricalPricesResponseStruct);
Comment on lines +661 to +662
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validation total is around 1s.
Do we need such strict validation in our snap or here? This is our own API & we know the shape. Also if the shape was incorrect, it would fail elsewhere


return {
timePeriod,
response,
};
} catch (error) {
console.warn(
`Error fetching historical prices for ${asset} to ${currency} with time period ${timePeriod}`,
error,
);
return {
timePeriod,
response: { prices: [] }, // Return empty prices on error
};
}
});

const wrappedHistoricalPrices = await Promise.all(promises);

const intervals =
wrappedHistoricalPrices.reduce<HistoricalPriceIntervals>(
(acc, { timePeriod, response }) => {
const iso8601Interval = `P${timePeriod.toUpperCase()}`;
acc[iso8601Interval] = response.prices.map((price) => [
price[0],
price[1].toString(),
]);
return acc;
},
{},
);

const now = Date.now();
const historicalPrice = {
intervals,
updateTime: now,
expirationTime: now + 1000 * 60, // 1 minute expiration
};

return {
historicalPrice,
};
} catch (error) {
console.error(
`Failed to fetch historical prices for asset: ${asset}`,
error,
);
return null;
}
}
}
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2630,6 +2630,7 @@ __metadata:
"@metamask/snaps-controllers": "npm:^12.3.1"
"@metamask/snaps-sdk": "npm:^7.1.0"
"@metamask/snaps-utils": "npm:^9.4.0"
"@metamask/superstruct": "npm:^3.1.0"
"@metamask/transaction-controller": "npm:^57.3.0"
"@metamask/utils": "npm:^11.2.0"
"@types/bn.js": "npm:^5.1.5"
Expand Down
Loading