Files
pwap/shared/lib/priceOracle.ts
T
pezkuwichain 5749f9a379 feat: add HEZ/DOT pool support and fix user-facing token names
- Add HEZ/DOT pool to PoolDashboard
- Display DOT, ETH, BTC instead of wDOT, wETH, wBTC to users
- Update priceOracle with correct symbol mappings
- Fix lint errors in check_all_pools.mjs
- Extract MINTABLE_ASSETS to separate file for fast refresh
2026-02-06 11:06:28 +03:00

170 lines
4.0 KiB
TypeScript

/**
* Price Oracle Service - Fetches prices from CoinGecko
* USDT-based Hybrid Oracle AMM
*/
const COINGECKO_API = 'https://api.coingecko.com/api/v3';
// CoinGecko ID mappings
export const COINGECKO_IDS: Record<string, string> = {
'DOT': 'polkadot',
'ETH': 'ethereum',
'BTC': 'bitcoin',
'USDT': 'tether',
};
// Manual prices for tokens not on CoinGecko (in USD)
export const MANUAL_PRICES: Record<string, number> = {
'HEZ': 1.0, // Set your HEZ price
'PEZ': 0.10, // Set your PEZ price
'wHEZ': 1.0,
};
// Price cache
interface PriceCache {
prices: Record<string, number>;
timestamp: number;
}
let priceCache: PriceCache | null = null;
const CACHE_TTL = 60 * 1000; // 1 minute cache
/**
* Fetch prices from CoinGecko
*/
export async function fetchCoinGeckoPrices(): Promise<Record<string, number>> {
try {
const ids = [...new Set(Object.values(COINGECKO_IDS))].join(',');
const response = await fetch(
`${COINGECKO_API}/simple/price?ids=${ids}&vs_currencies=usd`
);
if (!response.ok) {
throw new Error(`CoinGecko API error: ${response.status}`);
}
const data = await response.json();
const prices: Record<string, number> = {};
for (const [symbol, cgId] of Object.entries(COINGECKO_IDS)) {
if (data[cgId]?.usd) {
prices[symbol] = data[cgId].usd;
}
}
return prices;
} catch (error) {
console.error('CoinGecko fetch failed:', error);
return {};
}
}
/**
* Get all token prices (CoinGecko + manual)
*/
export async function getAllPrices(): Promise<Record<string, number>> {
// Check cache
if (priceCache && Date.now() - priceCache.timestamp < CACHE_TTL) {
return priceCache.prices;
}
const coinGeckoPrices = await fetchCoinGeckoPrices();
const prices: Record<string, number> = {
...MANUAL_PRICES,
...coinGeckoPrices,
};
// USDT is always $1
prices['USDT'] = 1;
prices['wUSDT'] = 1;
priceCache = { prices, timestamp: Date.now() };
return prices;
}
/**
* Calculate swap using oracle prices
* All swaps go through USDT as base currency
*/
export async function calculateOracleSwap(
fromSymbol: string,
toSymbol: string,
fromAmount: number,
feePercent: number = 0.3
): Promise<{
toAmount: number;
rate: number;
fromPriceUsd: number;
toPriceUsd: number;
route: string[];
} | null> {
const prices = await getAllPrices();
const fromPrice = prices[fromSymbol];
const toPrice = prices[toSymbol];
if (!fromPrice || !toPrice) {
console.warn(`Price not found: ${fromSymbol}=$${fromPrice}, ${toSymbol}=$${toPrice}`);
return null;
}
// Calculate rate and output
const rate = fromPrice / toPrice;
const feeMultiplier = 1 - (feePercent / 100);
// Determine route
let route: string[];
let totalFee = feePercent;
if (fromSymbol === 'USDT' || fromSymbol === 'wUSDT') {
// Direct: USDT → X
route = [fromSymbol, toSymbol];
} else if (toSymbol === 'USDT' || toSymbol === 'wUSDT') {
// Direct: X → USDT
route = [fromSymbol, toSymbol];
} else {
// Multi-hop: X → USDT → Y (double fee)
route = [fromSymbol, 'USDT', toSymbol];
totalFee = feePercent * 2;
}
const actualFeeMultiplier = 1 - (totalFee / 100);
const toAmount = fromAmount * rate * actualFeeMultiplier;
return {
toAmount,
rate,
fromPriceUsd: fromPrice,
toPriceUsd: toPrice,
route,
};
}
/**
* Get exchange rate between two tokens
*/
export async function getExchangeRate(
fromSymbol: string,
toSymbol: string
): Promise<number | null> {
const prices = await getAllPrices();
const fromPrice = prices[fromSymbol];
const toPrice = prices[toSymbol];
if (!fromPrice || !toPrice) return null;
return fromPrice / toPrice;
}
/**
* Format USD price for display
*/
export function formatUsdPrice(price: number): string {
if (price >= 1000) {
return `$${price.toLocaleString('en-US', { maximumFractionDigits: 2 })}`;
} else if (price >= 1) {
return `$${price.toFixed(2)}`;
} else {
return `$${price.toFixed(4)}`;
}
}