The entire skate-spots codebase was accidentally copied into src/src/ and public/public/ instead of src/ and public/ directly. This left the real src/ directory as blank MKStack template stubs, explaining why the landing page showed the default template instead of SkateSpots. Changes made: - Copied all files from src/src/ up to src/ (App.tsx, AppRouter.tsx, main.tsx, all pages, components, hooks, contexts, lib, i18n, test) - Copied all files from public/public/ up to public/ (images, manifest, etc.) - Deleted src/src/ and public/public/ nested directories - Removed HelloWorld.tsx template stub from pages/ - Retained the previously fixed index.css (Tailwind v3 directives) Full skate-spots feature set is now active: - All 30+ routes (map, events, spots, coaches, marketplace, wallet, etc.) - NWCProvider, LanguageProvider, PWAInstallPrompt, AmberCallbackHandler - Skate community relays (goskatespots.nostr1.com, wotr.relatr.xyz, etc.) - Outfit Variable font, Amber pre-check in main.tsx - All hooks (useSkateSpots, useCoaches, useZaps, etc.) - i18n support (en/es) Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
140 lines
3.7 KiB
TypeScript
140 lines
3.7 KiB
TypeScript
/**
|
|
* 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<number> {
|
|
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<number> {
|
|
const rate = await getBtcUsdRate();
|
|
return btcAmount * rate;
|
|
}
|
|
|
|
/**
|
|
* Convert USD amount to BTC using current exchange rate
|
|
*/
|
|
export async function usdToBtc(usdAmount: number): Promise<number> {
|
|
const rate = await getBtcUsdRate();
|
|
return usdAmount / rate;
|
|
}
|
|
|
|
/**
|
|
* Convert sats to USD using current exchange rate
|
|
*/
|
|
export async function satsToUsd(sats: number): Promise<number> {
|
|
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<number> {
|
|
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,
|
|
};
|
|
}
|