blockchain-api
Version:
API utilities for interacting with the Exatechl2 blockchain
198 lines (160 loc) • 5.78 kB
text/typescript
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
};
}
}