mev-inspect
Version:
A JS port of 'mev-inspect-py' optimised for ease of use.
110 lines • 4.24 kB
JavaScript
import { isKnownRouter, } from '../classifier/index.js';
import { minByAbs } from '../utils.js';
function isSwap(event) {
return 'from' in event;
}
function isDeposit(event) {
return 'depositor' in event;
}
function isWithdrawal(event) {
return 'withdrawer' in event;
}
function getSandwiches(chainId, swaps, deposits, withdrawals) {
const orderedEvents = [...swaps, ...deposits, ...withdrawals];
orderedEvents.sort((a, b) => a.event.logIndex - b.event.logIndex);
const sandwiches = [];
for (const i in orderedEvents) {
const swap = orderedEvents[i];
if (!isSwap(swap)) {
// Not a swap
continue;
}
const restEvents = orderedEvents.slice(parseInt(i) + 1);
const sandwich = getSandwich(chainId, swap, restEvents);
if (sandwich) {
sandwiches.push(sandwich);
}
}
return sandwiches;
}
function getSandwich(chainId, frontSwap, restEvents) {
const beneficiary = frontSwap.to;
const sandwiched = [];
if (isKnownRouter(chainId, beneficiary)) {
return null;
}
for (const event of restEvents) {
if (event.transaction.hash === frontSwap.transaction.hash) {
continue;
}
if (event.contract.address === frontSwap.contract.address) {
if (isSwap(event) &&
event.assetIn.address === frontSwap.assetIn.address &&
event.assetOut.address === frontSwap.assetOut.address &&
event.from !== beneficiary) {
sandwiched.push(event);
}
else if (
// Some AMMs (like Balancer and Curve) allow to add disproportional/single-side liquidity
// This is effectively the same as doing the swap and then depositing proportionally.
// Since swap is involved, it is possible to sandwich such liquidity deposit.
isDeposit(event) &&
event.assets
.filter((_asset, index) => event.amounts[index] > 0)
.map((asset) => asset.address)
.includes(frontSwap.assetIn.address) &&
event.depositor !== beneficiary) {
sandwiched.push(event);
}
else if (
// The above applies to dispropotional liquidity withdrawals as well.
isWithdrawal(event) &&
event.assets
.filter((_asset, index) => event.amounts[index] > 0)
.map((asset) => asset.address)
.includes(frontSwap.assetOut.address) &&
event.withdrawer !== beneficiary) {
sandwiched.push(event);
}
else if (isSwap(event) &&
event.assetOut.address === frontSwap.assetIn.address &&
event.assetIn.address === frontSwap.assetOut.address &&
event.from === beneficiary) {
const sender = event.transaction.from;
if (sandwiched.length > 0) {
return {
sandwicher: {
sender,
beneficiary,
},
frontSwap,
backSwap: event,
sandwiched,
profit: {
asset: frontSwap.assetIn,
amount: getProfit(frontSwap, event),
},
};
}
}
}
}
return null;
}
function getProfit(frontSwap, backSwap) {
const multiplier = 1000000000000000000000000000000000000n;
if (backSwap.amountIn === 0n || frontSwap.amountOut === 0n) {
return 0n;
}
const profitFrontrun = (frontSwap.amountOut *
((multiplier * backSwap.amountOut) / backSwap.amountIn)) /
multiplier -
frontSwap.amountIn;
const profitBackrun = backSwap.amountOut -
(backSwap.amountIn *
((multiplier * frontSwap.amountIn) / frontSwap.amountOut)) /
multiplier;
return minByAbs(profitFrontrun, profitBackrun);
}
export { getSandwiches };
//# sourceMappingURL=sandwiches.js.map