UNPKG

flashloan-profit-calculator

Version:

A library for analyzing flashloan transactions and calculating profits

98 lines (97 loc) 4.42 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.detectedProfitTakers = void 0; exports.resetProfitTakers = resetProfitTakers; exports.calculateProfitByToken = calculateProfitByToken; exports.formatProfitsWithDecimals = formatProfitsWithDecimals; exports.getTokenUsdPriceAtTimestamp = getTokenUsdPriceAtTimestamp; const transferProcessor_1 = require("./transferProcessor"); const constants_1 = require("./constants"); const transferProcessor_2 = require("./transferProcessor"); const utils_1 = require("./utils"); // Global variable to store detected profit takers exports.detectedProfitTakers = new Set(); /** * Reset detected profit takers (call this when starting new analysis) */ function resetProfitTakers() { exports.detectedProfitTakers.clear(); } /** * Calculates the total profit for each token */ async function calculateProfitByToken() { const totalProfit = []; for (const token in transferProcessor_1.revenueBalanceChanges) { let index = 0; // Calculate revenue const revenue = Object.values(transferProcessor_1.revenueBalanceChanges[token]).reduce((sum, balance) => { if (balance.type === "Revenue" || balance.type === "Cost") { index++; return sum + balance.amount; } const address = Object.keys(transferProcessor_1.revenueBalanceChanges[token])[index]; // get totalAddressParticipation let totalParticipation = 0; let totalToCount = 0; let totalFromCount = 0; for (const token in transferProcessor_1.addressParticipation[address]) { totalParticipation += transferProcessor_1.addressParticipation[address][token].participation; totalToCount += transferProcessor_1.addressParticipation[address][token].toCount; totalFromCount += transferProcessor_1.addressParticipation[address][token].fromCount; } // The address is a profit taker if it receives a token transfer without sending any tokens // i.e. It is not a swap if (totalParticipation % 2 === 1 && totalToCount !== totalFromCount && !transferProcessor_1.revenueBalanceChanges[address] && address !== constants_1.NULL_ADDRESS) { console.log(`${address} is a profit taker`); exports.detectedProfitTakers.add(address.toLowerCase()); index++; return sum + balance.amount; } index++; return sum; }, 0n); // Calculate cost const cost = Object.values(transferProcessor_1.costBalanceChanges[token]).reduce((sum, balance) => (balance.type === "Cost" ? sum + balance.amount : sum), 0n); const profit = revenue + cost; const price = await getTokenUsdPriceAtTimestamp("ethereum", // TODO: Make this configurable token, transferProcessor_2.blockTimestamp); totalProfit.push({ token, profit, usd: price ?? 0 }); } return totalProfit; } /** * Format profits for display with decimal conversion */ async function formatProfitsWithDecimals(profits) { return Promise.all(profits.map(async ({ token, profit }) => { const metadata = await (0, transferProcessor_1.getTokenMetadata)(token); return { token: token, symbol: metadata.symbol ?? token, decimals: metadata.decimals ?? 18, profit: Number(profit) / 10 ** (metadata.decimals ?? 18), }; })); } async function getTokenUsdPriceAtTimestamp(coingeckoId, contract, timestamp) { const from = timestamp; const to = timestamp + 3600; // +1 hour const url = contract === constants_1.ETH_ADDRESS ? `https://api.coingecko.com/api/v3/coins/ethereum/market_chart/range?vs_currency=usd&from=${from}&to=${to}&precision=2` : `https://api.coingecko.com/api/v3/coins/${coingeckoId}/contract/${contract}/market_chart/range?vs_currency=usd&from=${from}&to=${to}&precision=2`; const resp = await fetch(url, { headers: { "x-cg-demo-api-key": utils_1.COINGECKO_API_KEY ?? "", }, }); const data = await resp.json(); if (!data.prices || data.prices.length === 0) return null; // Use the first price in the range return data.prices[0][1]; }