UNPKG

flashloan-profit-calculator

Version:

A library for analyzing flashloan transactions and calculating profits

122 lines (121 loc) 4.97 kB
"use strict"; 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; }