UNPKG

mev-inspect

Version:

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

206 lines 6.6 kB
import { Coder } from 'abi-coder'; import { Contract } from 'ethcall'; import erc721Abi from '../../abi/erc721.js'; import poolAbi from '../../abi/sudoswapV1.js'; import { nativeAsset } from '../index.js'; const EXPONENTIAL_CURVE = '0x432f962d8209781da23fb37b6b59ee15de7d9841'; const LINEAR_CURVE = '0x5b6ac51d9b1cede0068a1b26533cace807f883ee'; const PROTOCOL_FEE = 5000000000000000n; function isValid(event) { return event.name === 'SwapNFTInPair' || event.name === 'SwapNFTOutPair'; } function getPoolCalls(address) { const poolContract = new Contract(address, poolAbi); const factoryCall = poolContract.factory(); const tokenCall = poolContract.token(); const nftCall = poolContract.nft(); const bondingCurveCall = poolContract.bondingCurve(); const feeCall = poolContract.fee(); const deltaCall = poolContract.delta(); return [ factoryCall, tokenCall, nftCall, bondingCurveCall, feeCall, deltaCall, ]; } function processPoolCalls(results, _address, chainId) { const factory = results[0].toLowerCase(); const token = results[1]?.toLowerCase(); const nft = results[2]?.toLowerCase(); const bondingCurve = results[3].toLowerCase(); const fee = results[4]; const delta = results[5]; const type = bondingCurve === EXPONENTIAL_CURVE ? 'exponential' : bondingCurve === LINEAR_CURVE ? 'linear' : null; if (!nft) { return null; } if (!type) { return null; } return { factoryAddress: factory.toLowerCase(), asset: token || nativeAsset[chainId], collection: nft, metadata: { type, fee, delta, }, }; } function parse(pool, event, _chainId, allLogs) { const { name, transactionFrom, transactionHash: hash, transactionIndex, gasUsed, logIndex, blockHash, blockNumber, } = event; const { address, asset, collection, metadata } = pool; const fee = metadata.fee; const delta = metadata.delta; const poolType = metadata.type; const txLogs = allLogs.filter((log) => log.transactionHash === hash); const newSpotPriceEvent = getNewSpotPrice(logIndex, address, txLogs); if (!newSpotPriceEvent) { return null; } const nftTransfers = getNftTransfers(logIndex, newSpotPriceEvent.logIndex, collection, txLogs); if (nftTransfers.length !== 1) { return null; } const transfer = nftTransfers[0]; const swapIn = name === 'SwapNFTInPair'; const effectivePrice = getEffectivePrice(poolType, fee, delta, newSpotPriceEvent.value, swapIn); const from = swapIn ? transfer.from : transfer.to; const to = swapIn ? transfer.from : transfer.to; return { contract: { address, protocol: { abi: 'SudoswapV1', factory: pool.factory, }, }, block: { hash: blockHash, number: blockNumber, }, transaction: { from: transactionFrom.toLowerCase(), hash, index: transactionIndex, gasUsed, }, event: { address, logIndex, }, from, to, assetIn: swapIn ? { type: 'erc721', collection, id: transfer.id, } : { type: 'erc20', address: asset, }, amountIn: swapIn ? 1n : effectivePrice, assetOut: swapIn ? { type: 'erc20', address: asset, } : { type: 'erc721', collection, id: transfer.id, }, amountOut: swapIn ? effectivePrice : 1n, }; } function getNewSpotPrice(swapLogIndex, poolAddress, logs) { const poolLogs = logs.filter((log) => log.address.toLowerCase() === poolAddress); poolLogs.reverse(); const log = poolLogs.find((log) => log.logIndex < swapLogIndex); if (!log) { return null; } const sudoswapCoder = new Coder(poolAbi); try { const event = sudoswapCoder.decodeEvent(log.topics, log.data); const price = event.values.newSpotPrice; return { logIndex: log.logIndex, value: price, }; } catch (e) { return null; } } function getNftTransfers(swapLogIndex, newSpotPriceLogIndex, collection, logs) { const collectionLogs = logs.filter((log) => log.address.toLowerCase() === collection && log.logIndex > newSpotPriceLogIndex && log.logIndex < swapLogIndex); const nftCoder = new Coder(erc721Abi); return collectionLogs .map((log) => { try { const event = nftCoder.decodeEvent(log.topics, log.data); return event.name === 'Transfer' ? { from: event.values.from.toLowerCase(), to: event.values.to.toLowerCase(), id: event.values.tokenId, } : null; } catch (e) { return null; } }) .filter((item) => item !== null); } function getEffectivePrice(type, fee, delta, nextSpotPrice, swapIn) { const precisionMultiplier = 1000000000000000000n; const spotPrice = type === 'linear' ? swapIn ? nextSpotPrice + delta : nextSpotPrice - delta : swapIn ? (nextSpotPrice * delta) / precisionMultiplier : (nextSpotPrice * precisionMultiplier) / delta; return type === 'linear' ? swapIn ? (spotPrice * (precisionMultiplier - fee - PROTOCOL_FEE)) / precisionMultiplier : ((spotPrice + delta) * (precisionMultiplier + fee + PROTOCOL_FEE)) / precisionMultiplier : swapIn ? (spotPrice * (precisionMultiplier - fee - PROTOCOL_FEE)) / precisionMultiplier : (spotPrice * (precisionMultiplier + fee + PROTOCOL_FEE) * delta) / precisionMultiplier / precisionMultiplier; } const CLASSIFIER = { nftSwap: { type: 'nft_swap', protocol: 'SudoswapV1', abi: poolAbi, isValid, parse, pool: { getCalls: getPoolCalls, processCalls: processPoolCalls, }, }, }; export default CLASSIFIER; export { getEffectivePrice }; //# sourceMappingURL=sudoswapV1.js.map