flashloan-profit-calculator
Version:
A library for analyzing flashloan transactions and calculating profits
122 lines (121 loc) • 4.97 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectFlashloan = detectFlashloan;
const weth_1 = require("../utils/weth");
// Common flashloan function signatures
const FLASHLOAN_SIGNATURES = {
AAVE_V2: "0x5cffe9de", // flashLoan(address[],uint256[],uint256[],address,bytes,uint16)
AAVE_V3: "0xb2a02ff1", // flashLoan(address,address[],uint256[],uint256[],address,bytes,uint16)
UNISWAP_V2: "0x0c49ccbe", // flash(address,uint256,uint256,bytes)
UNISWAP_V3: "0x490e6cbc", // flash(address,uint256,uint256,bytes)
DYDX: "0xb2a02ff1", // flashLoan(address,address[],uint256[],uint256[],address,bytes,uint16)
};
async function detectFlashloan(logs, trace, contractAddress) {
const detection = {
isFlashloan: false,
borrowedTokens: [],
borrowedAmounts: [],
repaidAmounts: [],
};
// Check for flashloan function calls in trace
for (const call of trace) {
if (call.input && call.input.length >= 10) {
const functionSig = call.input.slice(0, 10);
// Check against known flashloan signatures
if (functionSig === FLASHLOAN_SIGNATURES.AAVE_V2) {
detection.isFlashloan = true;
detection.protocol = "Aave V2";
break;
}
if (functionSig === FLASHLOAN_SIGNATURES.AAVE_V3) {
detection.isFlashloan = true;
detection.protocol = "Aave V3";
break;
}
if (functionSig === FLASHLOAN_SIGNATURES.UNISWAP_V2) {
detection.isFlashloan = true;
detection.protocol = "Uniswap V2";
break;
}
if (functionSig === FLASHLOAN_SIGNATURES.UNISWAP_V3) {
detection.isFlashloan = true;
detection.protocol = "Uniswap V3";
break;
}
if (functionSig === FLASHLOAN_SIGNATURES.DYDX) {
detection.isFlashloan = true;
detection.protocol = "dYdX";
break;
}
}
}
// If no flashloan signature found, check for borrow-repay pattern
if (!detection.isFlashloan) {
const borrowRepayPattern = await detectBorrowRepayPattern(logs, contractAddress);
if (borrowRepayPattern.isFlashloan) {
Object.assign(detection, borrowRepayPattern);
}
}
return detection;
}
async function detectBorrowRepayPattern(logs, contractAddress) {
const detection = {
isFlashloan: false,
borrowedTokens: [],
borrowedAmounts: [],
repaidAmounts: [],
};
// Group transfers by token
const tokenTransfers = new Map();
for (const log of logs) {
if (log.name !== "Transfer")
continue;
const token = log.raw.address;
const amount = BigInt(log.inputs[2].value);
if (!tokenTransfers.has(token)) {
tokenTransfers.set(token, { in: 0n, out: 0n });
}
const transfers = tokenTransfers.get(token);
if (!transfers)
continue;
// Check if transfer is to/from our contract
const toAddress = String(log.inputs[1].value).toLowerCase();
const fromAddress = String(log.inputs[0].value).toLowerCase();
const contractAddressLower = contractAddress.toLowerCase();
if (toAddress === contractAddressLower) {
transfers.in += amount;
}
else if (fromAddress === contractAddressLower) {
transfers.out += amount;
}
}
// Analyze borrow-repay pattern
for (const [token, transfers] of tokenTransfers) {
// If we received and sent back roughly the same amount, it's likely a flashloan
if (transfers.in > 0n && transfers.out > 0n) {
const isWethLike = await (0, weth_1.isWethLikeToken)(token);
// For WETH-like tokens, we expect exact matches
// For other tokens, we allow small differences due to fees
if (isWethLike) {
if (transfers.in === transfers.out) {
detection.isFlashloan = true;
detection.borrowedTokens.push(token);
detection.borrowedAmounts.push(transfers.in);
detection.repaidAmounts.push(transfers.out);
}
}
else {
// Allow 1% difference for fees
const difference = transfers.in - transfers.out;
const maxAllowedDifference = transfers.in / 100n;
if (difference >= 0n && difference <= maxAllowedDifference) {
detection.isFlashloan = true;
detection.borrowedTokens.push(token);
detection.borrowedAmounts.push(transfers.in);
detection.repaidAmounts.push(transfers.out);
}
}
}
}
return detection;
}