UNPKG

blockchain-api

Version:

API utilities for interacting with the Exatechl2 blockchain

198 lines (160 loc) 5.78 kB
import { ethers } from 'ethers'; import { PAIR_ABI, PANCAKESWAP_ROUTER_ABI, CONTRACT_ADDRESSES } from './abis'; import { logger } from '../../utils/logger'; interface PriceHistoryItem { timestamp: number; usd: number; bnb: number; } let lastPrice = { usd: 0, bnb: 0, lastUpdated: 0, changePercent: 0 }; let priceHistory: PriceHistoryItem[] = []; const EXATECH_DECIMALS = 9; /** * * @returns */ export async function getTokenPrice() { try { const now = Date.now(); if (lastPrice.lastUpdated > 0 && (now - lastPrice.lastUpdated < 1 * 60 * 1000)) { return { usd: lastPrice.usd, bnb: lastPrice.bnb, changePercent: lastPrice.changePercent }; } const provider = new ethers.providers.JsonRpcProvider("https://bsc-dataseed1.binance.org"); const bnbPriceInUsd = await getBnbPriceInUsd(provider); logger.debug(`BNB price in USD: ${bnbPriceInUsd}`); const bnbPerExatech = await getExatechPriceInBnbViaRouter(provider); logger.debug(`EXATECH price in BNB via Router: ${bnbPerExatech}`); const usdPrice = bnbPerExatech * bnbPriceInUsd; logger.debug(`EXATECH final price: ${usdPrice} USD`); priceHistory.push({ timestamp: now, usd: usdPrice, bnb: bnbPerExatech }); if (priceHistory.length > 24) { priceHistory.shift(); } let changePercent = 0; if (priceHistory.length > 1) { const oldestPrice = priceHistory[0].usd; changePercent = ((usdPrice - oldestPrice) / oldestPrice) * 100; } lastPrice = { usd: usdPrice, bnb: bnbPerExatech, lastUpdated: now, changePercent }; return { usd: usdPrice, bnb: bnbPerExatech, changePercent }; } catch (error) { logger.error("Error fetching token price:", error); throw error; } } async function getBnbPriceInUsd(provider: ethers.providers.Provider): Promise<number> { const router = new ethers.Contract( CONTRACT_ADDRESSES.PANCAKESWAP_ROUTER, PANCAKESWAP_ROUTER_ABI, provider ); const oneBNB = ethers.utils.parseEther("1"); const bnbToUsdt = await router.getAmountsOut( oneBNB, [CONTRACT_ADDRESSES.WBNB, CONTRACT_ADDRESSES.USDT] ); return parseFloat(ethers.utils.formatUnits(bnbToUsdt[1], 18)); } async function getExatechPriceInBnbViaRouter(provider: ethers.providers.Provider): Promise<number> { try { const router = new ethers.Contract( CONTRACT_ADDRESSES.PANCAKESWAP_ROUTER, PANCAKESWAP_ROUTER_ABI, provider ); const exatechAmount = ethers.utils.parseUnits("1", EXATECH_DECIMALS); logger.debug(`Getting price for: ${exatechAmount.toString()} EXATECH baseunits (1 EXATECH)`); const amountsOut = await router.getAmountsOut( exatechAmount, [CONTRACT_ADDRESSES.EXATECH, CONTRACT_ADDRESSES.WBNB] ); const bnbAmount = parseFloat(ethers.utils.formatEther(amountsOut[1])); logger.debug(`Router estimates 1 EXATECH = ${bnbAmount} BNB`); return bnbAmount; } catch (error) { logger.error("Error getting price via router:", error); return getExatechPriceInBnbViaReserves(provider); } } async function getExatechPriceInBnbViaReserves(provider: ethers.providers.Provider): Promise<number> { try { logger.debug("Trying reserve method as fallback..."); const pairContract = new ethers.Contract( CONTRACT_ADDRESSES.EXATECH_BNB_PAIR, PAIR_ABI, provider ); const token0 = await pairContract.token0(); const token1 = await pairContract.token1(); logger.debug(`Pair tokens: token0=${token0}, token1=${token1}`); logger.debug(`EXATECH address: ${CONTRACT_ADDRESSES.EXATECH}`); const isExatechToken0 = token0.toLowerCase() === CONTRACT_ADDRESSES.EXATECH.toLowerCase(); logger.debug(`Is EXATECH token0? ${isExatechToken0}`); const reserves = await pairContract.getReserves(); let exatechReserve, bnbReserve; if (isExatechToken0) { exatechReserve = parseFloat(ethers.utils.formatUnits(reserves.reserve0, EXATECH_DECIMALS)); bnbReserve = parseFloat(ethers.utils.formatEther(reserves.reserve1)); logger.debug(`Reserves: EXATECH=${exatechReserve}, BNB=${bnbReserve}`); } else { exatechReserve = parseFloat(ethers.utils.formatUnits(reserves.reserve1, EXATECH_DECIMALS)); bnbReserve = parseFloat(ethers.utils.formatEther(reserves.reserve0)); logger.debug(`Reserves: BNB=${bnbReserve}, EXATECH=${exatechReserve}`); } logger.debug(`Exatech reserve: ${exatechReserve}, BNB reserve: ${bnbReserve}`); if (exatechReserve < 0.0001 || bnbReserve < 0.0001) { logger.error("Reserves too small for accurate calculation"); throw new Error("Reserves too small"); } return bnbReserve / exatechReserve; } catch (error) { logger.error("Error in reserve method:", error); throw error; } } /** * @returns */ export async function getFormattedTokenPrice() { try { const price = await getTokenPrice(); const priceStr = price.usd.toFixed(8); const changeStr = price.changePercent >= 0 ? `+${price.changePercent.toFixed(1)}%` : `${price.changePercent.toFixed(1)}%`; return { price: `$${priceStr}`, change: changeStr, isPositive: price.changePercent >= 0 }; } catch (error) { logger.error("Error formatting token price:", error); return { price: "N/A", change: "-", isPositive: false }; } }