A Uniswap V2 style liquidity pool written in Solidity, and a calculator that uses live reserves from Uniswap V2 on Ethereum mainnet to show price impact, sandwich attacks, arbitrage against Binance, and impermanent loss.
Swaps move along the curve x × y = k, so larger trades get a worse average price. The 0.3% fee stays in the pool.
| Received | - |
| Average price | - |
| Price impact | - |
| Fee paid to liquidity providers | - |
A bot buys just before a victim's swap and sells just after it. The victim's slippage tolerance limits how far the bot can move the price. The calculator finds the front-run size that maximises the bot's profit.
| Best front-run size | - |
| Bot profit after gas | - |
| Victim receives less by | - |
Chart: bot profit against front-run size. The dashed line is the largest front-run the victim's slippage limit allows.
When the pool price differs from Binance by more than the 0.3% fee, there is a trade that moves the pool back to the market price. Optimal input: (√(γ·x·y·P) − y) / γ, where γ = 0.997.
| Pool vs Binance | - |
| Trade | - |
| Profit before gas | - |
| Pool price after | - |
Compared with holding the two tokens, a liquidity provider's position is worth 2√r / (1 + r) − 1 less, where r is the price ratio. Trading fees offset this.
| Value if held | - |
| Value as liquidity provider, with fees | - |
| Impermanent loss | - |
| Fee APR needed to break even | - |
contracts/MiniPair.sol is a constant-product pool for two ERC-20 tokens with LP shares, a 0.3% fee, slippage and deadline checks, and reentrancy protection. The Hardhat tests use the same math library as this page: they plan a sandwich attack off-chain, run it against the contract, and check that the profit matches exactly.
function swapExactIn(address tokenIn, uint256 amountIn, uint256 minOut, address to, uint256 deadline)
external nonReentrant beforeDeadline(deadline) returns (uint256 amountOut)
{
bool zeroForOne;
if (tokenIn == address(token0)) zeroForOne = true;
else if (tokenIn != address(token1)) revert InvalidToken();
(uint256 rIn, uint256 rOut) = zeroForOne ? (reserve0, reserve1) : (reserve1, reserve0);
amountOut = getAmountOut(amountIn, rIn, rOut);
if (amountOut < minOut) revert InsufficientOutput(amountOut, minOut);
uint256 kBefore = uint256(reserve0) * reserve1;
IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
(zeroForOne ? token1 : token0).safeTransfer(to, amountOut);
_sync();
assert(uint256(reserve0) * reserve1 >= kBefore);
emit Swap(msg.sender, tokenIn, amountIn, amountOut, to);
}
Tests: 8 contract tests (Hardhat) and 4 tests of the JavaScript math.
Reserves from publicnode.com, prices from Binance. Not audited. Source: github.com/hpark236/cpamm-mev