UNPKG

mev-inspect

Version:

A JS port of 'mev-inspect-py' optimised for ease of use.

342 lines 11.1 kB
import { Contract } from 'ethcall'; import { ZeroAddress } from 'ethers'; import vaultAbi from '../../abi/balancerV2Vault.js'; function isValidSwap(event) { return event.name === 'Swap'; } function isValidDeposit(event) { const deltas = event.values.deltas; if (!deltas) { return false; } return (event.name === 'PoolBalanceChanged' && deltas.every((delta) => delta >= 0)); } function isValidWithdrawal(event) { const deltas = event.values.deltas; if (!deltas) { return false; } return (event.name === 'PoolBalanceChanged' && deltas.every((delta) => delta <= 0)); } function isValidTransfer(event) { return event.name === 'InternalBalanceChanged'; } const VAULT_ADDRESS = '0xba12222222228d8ba445958a75a0704d566bf2c8'; function getPoolCalls(id) { const contract = new Contract(VAULT_ADDRESS, vaultAbi); return [contract.getPoolTokens(id)]; } function processPoolCalls(result) { const [poolTokens] = result; if (!poolTokens) { return null; } const tokens = poolTokens.tokens; const assets = tokens.map((token) => token.toLowerCase()); return { assets, factoryAddress: VAULT_ADDRESS, }; } function parseSwap(pool, event, transfers, allEvents) { const { transactionFrom, transactionHash: hash, transactionIndex, gasUsed, logIndex, address, blockHash, blockNumber, } = event; const { assetIn, assetOut, amountIn, amountOut } = getSwapValues(event); const vault = event.address.toLowerCase(); const { from, to } = getClusterInputOutput(vault, logIndex, allEvents, transfers); return { contract: { address: pool.address, protocol: { abi: 'BalancerV2', factory: pool.factory, }, }, block: { hash: blockHash, number: blockNumber, }, transaction: { from: transactionFrom.toLowerCase(), hash, index: transactionIndex, gasUsed, }, event: { logIndex, address: address.toLowerCase(), }, from, to, assetIn: { type: 'erc20', address: assetIn, }, amountIn, assetOut: { type: 'erc20', address: assetOut, }, amountOut, metadata: {}, }; } function parseDeposit(pool, event) { const { values, transactionFrom, transactionHash: hash, transactionIndex, gasUsed, logIndex, address, blockHash, blockNumber, } = event; const depositor = values.liquidityProvider.toLowerCase(); const assets = values.tokens.map((token) => token.toLowerCase()); const amounts = values.deltas; return { contract: { address: pool.address, protocol: { abi: 'BalancerV2', factory: pool.factory, }, }, block: { hash: blockHash, number: blockNumber, }, transaction: { from: transactionFrom.toLowerCase(), hash, index: transactionIndex, gasUsed, }, event: { address: address.toLowerCase(), logIndex, }, depositor, assets: assets.map((asset) => { return { type: 'erc20', address: asset, }; }), amounts, metadata: {}, }; } function parseWithdrawal(pool, event) { const { values, transactionFrom, transactionHash: hash, transactionIndex, gasUsed, logIndex, address, blockHash, blockNumber, } = event; const withdrawer = values.liquidityProvider.toLowerCase(); const assets = values.tokens.map((token) => token.toLowerCase()); const amounts = values.deltas.map((value) => -value); return { contract: { address: pool.address, protocol: { abi: 'BalancerV2', factory: pool.factory, }, }, block: { hash: blockHash, number: blockNumber, }, transaction: { from: transactionFrom.toLowerCase(), hash, index: transactionIndex, gasUsed, }, event: { address: address.toLowerCase(), logIndex, }, withdrawer, assets: assets.map((asset) => { return { type: 'erc20', address: asset, }; }), amounts, metadata: {}, }; } function parseTransfer(event) { const { values, transactionFrom, transactionHash: hash, transactionIndex, gasUsed, logIndex, address, blockHash, blockNumber, } = event; const user = values.user.toLowerCase(); const token = values.token.toLowerCase(); const delta = values.delta; const vault = event.address.toLowerCase(); const from = delta > 0 ? vault : user; const to = delta > 0 ? user : vault; const value = delta > 0 ? delta : -delta; return { block: { hash: blockHash, number: blockNumber, }, transaction: { from: transactionFrom.toLowerCase(), hash, index: transactionIndex, gasUsed, }, event: { logIndex, address: address.toLowerCase(), }, asset: token, from, to, value, }; } function getClusterInputOutput(vault, logIndex, allEvents, transfers) { function isVaultSwap(event) { return (event.classifier.type === 'swap' && event.classifier.protocol === 'BalancerV2'); } const emptyInputOutput = { from: ZeroAddress, to: ZeroAddress, }; const sortedEvents = [...allEvents]; sortedEvents.sort((a, b) => a.logIndex - b.logIndex); // Go back from current event until not a Balancer V2 swap found let startIndex = allEvents.findIndex((event) => event.logIndex === logIndex); while (startIndex > 0 && isVaultSwap(allEvents[startIndex - 1])) { startIndex--; } const startSwap = allEvents[startIndex]; // Go forward from current event until not a Balancer V2 swap found let endIndex = allEvents.findIndex((event) => event.logIndex === logIndex); while (endIndex < allEvents.length - 1 && isVaultSwap(allEvents[endIndex + 1])) { endIndex++; } const endSwap = allEvents[endIndex]; // Go forward from there until a swap found let endTransferIndex = endIndex; while (endTransferIndex < allEvents.length - 1 && allEvents[endTransferIndex + 1].classifier.type === 'transfer') { endTransferIndex++; } // Make sure there is at least one transfer if (endIndex === endTransferIndex) { // Theoretically possible, but not economically viable // Either way, w/o transfers, we can't deduct the sender return emptyInputOutput; } const endTransfer = allEvents[endTransferIndex]; // Make sure all swaps in cluster form a valid chain for (let i = startIndex; i < endIndex; i++) { const swap = allEvents[i]; const nextSwap = allEvents[i + 1]; if (swap.classifier.type !== 'swap' || nextSwap.classifier.type !== 'swap') { return emptyInputOutput; } const { assetOut: swapAssetOut, amountOut: swapAmountOut } = getSwapValues(swap); const { assetIn: nextSwapAssetIn, amountIn: nextSwapAmountIn } = getSwapValues(nextSwap); if (swapAssetOut !== nextSwapAssetIn) { return emptyInputOutput; } if (swapAmountOut !== nextSwapAmountIn) { return emptyInputOutput; } } // Get cluster transfers, inflows, outflows const clusterTransfers = transfers.filter((transfer) => transfer.event.logIndex > endSwap.logIndex && transfer.event.logIndex <= endTransfer.logIndex); const clusterInflows = clusterTransfers.filter((transfer) => transfer.to === vault); const clusterOutflows = clusterTransfers.filter((transfer) => transfer.from === vault); // Get first inflow, last outflow const firstInflow = clusterInflows[0]; const lastOutflow = clusterOutflows[clusterOutflows.length - 1]; // Make sure first inflow matches first swap in a cluster if (clusterInflows.length > 0) { const { assetIn, amountIn } = getSwapValues(startSwap); if (firstInflow.asset !== assetIn || (firstInflow.value !== amountIn && firstInflow.event.address !== vault) || firstInflow.to !== vault) { return emptyInputOutput; } } // Make sure last outflow matches last swap in a cluster if (clusterOutflows.length > 0) { const { assetOut, amountOut } = getSwapValues(endSwap); if (lastOutflow.asset !== assetOut || (lastOutflow.value !== amountOut && lastOutflow.event.address !== vault) || lastOutflow.from !== vault) { return emptyInputOutput; } } // Get input/output const clusterFrom = clusterInflows.length === 0 ? clusterOutflows.length === 0 ? ZeroAddress : lastOutflow.to : firstInflow.from; const clusterTo = clusterOutflows.length === 0 ? clusterInflows.length === 0 ? ZeroAddress : firstInflow.from : lastOutflow.to; const from = logIndex === startSwap.logIndex ? clusterFrom : vault; const to = logIndex === endSwap.logIndex ? clusterTo : vault; return { from, to, }; } function getSwapValues(swap) { const assetIn = swap.values.tokenIn.toLowerCase(); const assetOut = swap.values.tokenOut.toLowerCase(); const amountIn = swap.values.amountIn; const amountOut = swap.values.amountOut; return { assetIn, assetOut, amountIn, amountOut, }; } const CLASSIFIERS = { swap: { type: 'swap', protocol: 'BalancerV2', abi: vaultAbi, isValid: isValidSwap, parse: parseSwap, pool: { getCalls: getPoolCalls, processCalls: processPoolCalls, }, }, liquidityDeposit: { type: 'liquidity_deposit', protocol: 'BalancerV2', abi: vaultAbi, isValid: isValidDeposit, parse: parseDeposit, pool: { getCalls: getPoolCalls, processCalls: processPoolCalls, }, }, liquidityWithdrawal: { type: 'liquidity_withdrawal', protocol: 'BalancerV2', abi: vaultAbi, isValid: isValidWithdrawal, parse: parseWithdrawal, pool: { getCalls: getPoolCalls, processCalls: processPoolCalls, }, }, transfer: { type: 'transfer', abi: vaultAbi, isValid: isValidTransfer, parse: parseTransfer, }, }; export default CLASSIFIERS; //# sourceMappingURL=balancerV2.js.map