flashloan-profit-calculator
Version:
A library for analyzing flashloan transactions and calculating profits
98 lines (97 loc) • 4.42 kB
JavaScript
;
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];
}