/** * Exchange rate service for fetching BTC/USD rates from Coinbase API * * Module-level cache to avoid excessive API calls */ const COINBASE_API_URL = "https://api.coinbase.com/v2/exchange-rates"; const CACHE_DURATION_MS = 60000; // 1 minute cache let cachedRate: number | null = null; let cacheTimestamp: number = 0; /** * Get the current BTC/USD exchange rate from Coinbase API * Uses caching to avoid excessive API calls * * @returns Promise resolving to BTC/USD exchange rate */ export async function getBtcUsdRate(): Promise { const now = Date.now(); // Return cached rate if still valid if (cachedRate && (now - cacheTimestamp) < CACHE_DURATION_MS) { console.debug(`Using cached BTC/USD rate: ${cachedRate}`, { cache_age_ms: now - cacheTimestamp, }); return cachedRate; } try { console.debug("Fetching BTC/USD rate from Coinbase API"); const response = await fetch(`${COINBASE_API_URL}?currency=BTC`, { method: "GET", headers: { "Accept": "application/json", }, signal: AbortSignal.timeout(10000), // 10 second timeout }); if (!response.ok) { throw new Error(`Coinbase API error: ${response.status} ${response.statusText}`); } const data = await response.json(); // Validate response structure if (!data?.data?.rates?.USD) { throw new Error("Invalid response format from Coinbase API"); } const rate = parseFloat(data.data.rates.USD); if (isNaN(rate) || rate <= 0) { throw new Error(`Invalid exchange rate received: ${data.data.rates.USD}`); } // Update cache cachedRate = rate; cacheTimestamp = now; console.info(`Fetched BTC/USD rate from Coinbase: ${rate}`, { rate }); return rate; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`Failed to fetch BTC/USD rate: ${errorMessage}`); // If we have a cached rate, use it as fallback if (cachedRate) { const cacheAge = now - cacheTimestamp; console.warn(`Using stale cached rate as fallback: ${cachedRate}`, { cache_age_ms: cacheAge, }); return cachedRate; } // If no cached rate available, throw error throw new Error(`Unable to fetch BTC/USD exchange rate: ${errorMessage}`); } } /** * Convert BTC amount to USD using current exchange rate */ export async function btcToUsd(btcAmount: number): Promise { const rate = await getBtcUsdRate(); return btcAmount * rate; } /** * Convert USD amount to BTC using current exchange rate */ export async function usdToBtc(usdAmount: number): Promise { const rate = await getBtcUsdRate(); return usdAmount / rate; } /** * Convert sats to USD using current exchange rate */ export async function satsToUsd(sats: number): Promise { const btcAmount = sats / 100000000; // Convert sats to BTC return btcToUsd(btcAmount); } /** * Convert USD to sats using current exchange rate */ export async function usdToSats(usdAmount: number): Promise { const btcAmount = await usdToBtc(usdAmount); return Math.floor(btcAmount * 100000000); // Convert BTC to sats } /** * Clear the cached exchange rate (useful for testing or forcing refresh) */ export function clearCache(): void { cachedRate = null; cacheTimestamp = 0; } /** * Get cache status for debugging */ export function getCacheStatus(): { rate: number | null; age_ms: number; is_valid: boolean } { const now = Date.now(); const age = now - cacheTimestamp; const isValid = cachedRate !== null && age < CACHE_DURATION_MS; return { rate: cachedRate, age_ms: age, is_valid: isValid, }; }