flashloan-profit-calculator
Version:
A library for analyzing flashloan transactions and calculating profits
180 lines (179 loc) • 6.74 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateBuilderAddress = exports.blockTimestamp = exports.addressParticipation = exports.costBalanceChanges = exports.revenueBalanceChanges = void 0;
exports.initializeBalanceChanges = initializeBalanceChanges;
exports.trackAddressParticipation = trackAddressParticipation;
exports.processTransfer = processTransfer;
exports.processTransferLog = processTransferLog;
exports.isBuilder = isBuilder;
exports.setupProcessor = setupProcessor;
exports.resetState = resetState;
const alchemy_1 = require("../services/alchemy");
// State management
exports.revenueBalanceChanges = {};
exports.costBalanceChanges = {};
exports.addressParticipation = {};
// Configuration variables
let builderAddress = "";
let contractAddress = "";
let senderAddress = "";
exports.blockTimestamp = 0;
/**
* Update the builder address by fetching block information
*/
const updateBuilderAddress = async (blockNumber) => {
const block = await alchemy_1.alchemy.core.getBlock(blockNumber);
builderAddress = block.miner;
exports.blockTimestamp = block.timestamp;
};
exports.updateBuilderAddress = updateBuilderAddress;
/**
* Initialize balance changes for a token and address
*/
function initializeBalanceChanges(token, address, isRevenue, isSenderOrContract) {
if (!exports.revenueBalanceChanges[token]) {
exports.revenueBalanceChanges[token] = {};
}
if (!exports.costBalanceChanges[token]) {
exports.costBalanceChanges[token] = {};
}
if (!exports.revenueBalanceChanges[token][address]) {
exports.revenueBalanceChanges[token][address] = {
amount: 0n,
type: isSenderOrContract ? "Revenue" : "TokenTransfer",
};
}
if (!exports.costBalanceChanges[token][address]) {
exports.costBalanceChanges[token][address] = {
amount: 0n,
type: isSenderOrContract ? "Cost" : "TokenTransfer",
};
}
}
/**
* Track address participation in transfers
*/
function trackAddressParticipation(from, to, token) {
// Initialize address data if not exists
if (!exports.addressParticipation[from])
exports.addressParticipation[from] = {};
if (!exports.addressParticipation[to])
exports.addressParticipation[to] = {};
// Initialize token data for addresses if not exists
if (!exports.addressParticipation[from][token]) {
exports.addressParticipation[from][token] = {
toCount: 0,
fromCount: 0,
participation: 0,
};
}
if (!exports.addressParticipation[to][token]) {
exports.addressParticipation[to][token] = {
toCount: 0,
fromCount: 0,
participation: 0,
};
}
// Update counts
exports.addressParticipation[from][token].fromCount++;
exports.addressParticipation[from][token].participation++;
exports.addressParticipation[to][token].toCount++;
exports.addressParticipation[to][token].participation++;
}
/**
* Process a transfer of tokens between addresses
*/
function processTransfer(token, from, to, amount) {
trackAddressParticipation(from, to, token);
// Initialize balance changes
const isFromSenderOrContract = from.toLowerCase() === senderAddress ||
from.toLowerCase() === contractAddress;
const isToSenderOrContract = to.toLowerCase() === senderAddress || to.toLowerCase() === contractAddress;
initializeBalanceChanges(token, to, true, isToSenderOrContract);
initializeBalanceChanges(token, from, false, isFromSenderOrContract);
// Update balances
if (to !== "0x0") {
exports.revenueBalanceChanges[token][to].amount += amount;
}
if (isBuilder(to)) {
exports.revenueBalanceChanges[token][to].amount *= -1n;
exports.revenueBalanceChanges[token][to].type = "Cost";
}
else {
exports.costBalanceChanges[token][from].amount -= amount;
}
}
/**
* Process a transfer log with error handling
*/
function processTransferLog(log) {
try {
console.log(`Processing ${log.name} log from ${log.raw.address}`);
const token = log.raw.address.toLowerCase();
if (log.name === "Transfer") {
try {
const from = typeof log.inputs[0].value === "string"
? log.inputs[0].value.toLowerCase()
: log.inputs[0].value.toString().toLowerCase();
const to = typeof log.inputs[1].value === "string"
? log.inputs[1].value.toLowerCase()
: log.inputs[1].value.toString().toLowerCase();
const amount = BigInt(log.inputs[2].value);
// console.log(`Transfer: ${from} -> ${to}, amount: ${amount.toString()}`);
processTransfer(token, from, to, amount);
}
catch (error) {
console.error(`Error processing Transfer log: ${error}`);
}
}
// Wrapped tokens deduct from the sender's balance during withdrawals but it's not logged
// So we need to manually deduct the cost from the sender's balance
if (log.name === "Withdrawal") {
try {
const from = typeof log.inputs[0].value === "string"
? log.inputs[0].value.toLowerCase()
: log.inputs[0].value.toString().toLowerCase();
const amount = BigInt(log.inputs[1].value);
console.log(`Withdrawal: ${from}, amount: ${amount.toString()}`);
// Treat it like a WETH transfer out
processTransfer(token, from, "0x0", amount);
}
catch (error) {
console.error(`Error processing Withdrawal log: ${error}`);
}
}
}
catch (error) {
console.error(`Failed to process log: ${error}`);
}
}
/**
* Check if an address is a builder
*/
function isBuilder(address) {
return address.toLowerCase() === builderAddress.toLowerCase();
}
/**
* Setup the processor with contract and sender addresses
*/
function setupProcessor(contract, sender) {
contractAddress = contract.toLowerCase();
senderAddress = sender.toLowerCase();
}
/**
* Reset all state
*/
function resetState() {
for (const key of Object.keys(exports.revenueBalanceChanges)) {
delete exports.revenueBalanceChanges[key];
}
for (const key of Object.keys(exports.costBalanceChanges)) {
delete exports.costBalanceChanges[key];
}
for (const key of Object.keys(exports.addressParticipation)) {
delete exports.addressParticipation[key];
}
builderAddress = "";
contractAddress = "";
senderAddress = "";
}