mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-22 22:57:55 +00:00
842dc9d8c2
- 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
170 lines
4.0 KiB
TypeScript
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)}`;
|
|
}
|
|
}
|