mev-inspect
Version:
A JS port of 'mev-inspect-py' optimised for ease of use.
206 lines • 6.6 kB
JavaScript
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