Files
cryptowallet/apps/web/src/lib/balances/prices.ts
ZOMBIIIIIII a81e29807c add project
2026-04-08 14:11:27 +03:00

71 lines
1.8 KiB
TypeScript

const PRICE_CACHE_TTL_MS = 60_000;
const PRICE_REQUEST_TIMEOUT_MS = 10_000;
let cachedPrices: Record<string, number> | null = null;
let cachedAt = 0;
interface CoinGeckoPriceResponse {
[coinId: string]: {
usd?: number;
};
}
export async function fetchUsdPrices(coinIds: string[]): Promise<Record<string, number>> {
const uniqueCoinIds = Array.from(new Set(coinIds.filter(Boolean)));
if (!uniqueCoinIds.length) {
return {};
}
if (
cachedPrices &&
Date.now() - cachedAt < PRICE_CACHE_TTL_MS &&
uniqueCoinIds.every((coinId) => coinId in cachedPrices!)
) {
return uniqueCoinIds.reduce<Record<string, number>>((acc, coinId) => {
acc[coinId] = cachedPrices![coinId];
return acc;
}, {});
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), PRICE_REQUEST_TIMEOUT_MS);
try {
const url = new URL('https://api.coingecko.com/api/v3/simple/price');
url.searchParams.set('ids', uniqueCoinIds.join(','));
url.searchParams.set('vs_currencies', 'usd');
const response = await fetch(url.toString(), {
signal: controller.signal,
cache: 'no-store',
headers: {
Accept: 'application/json',
},
});
if (!response.ok) {
throw new Error(`CoinGecko returned ${response.status}`);
}
const payload = (await response.json()) as CoinGeckoPriceResponse;
const prices = uniqueCoinIds.reduce<Record<string, number>>((acc, coinId) => {
const price = payload[coinId]?.usd;
if (typeof price === 'number') {
acc[coinId] = price;
}
return acc;
}, {});
cachedPrices = {
...(cachedPrices ?? {}),
...prices,
};
cachedAt = Date.now();
return prices;
} finally {
clearTimeout(timeoutId);
}
}