This project simulates a set of actors operating in a blockchain environment, in order to study how different kinds of users are affected by different kinds of MEV extraction strategies, as well as to what extent protection mechanisms such as encrypted or private mempools are suitable for them.
- Python 3.11 or newer
- Foundry with
forgeandanvilon yourPATH - Node.js and npm
Install Python dependencies:
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev,server]"Install frontend dependencies:
cd frontend
npm installStart the backend from the repository root:
uvicorn server.main:app --port 8000Start the frontend from frontend/:
npm run devThe Vite dev server proxies /scenarios, /simulation, and /ws to http://localhost:8000, so both processes should run at the same time during development.
The project has four main parts:
sim/: the simulation core.SimulationEnginemanages time, actors, mempools, blocks, and the Anvil lifecycle.Environmentexposes Web3 access, account funding, and Foundry deployment helpers to scenarios.server/: the FastAPI backend. It owns the application state, loads scenarios, starts and pauses runs, serves simulation snapshots, and streams events over WebSocket.contracts/: Foundry contracts and deployment scripts used by scenarios at runtime.frontend/: the React and Vite UI that consumes the REST and WebSocket APIs and renders the simulator state.
Scenarios are plugins discovered from the scenarios/ Python package. The backend walks that package, imports modules, and registers every concrete subclass of sim.scenario.Scenario.
Each scenario is responsible for three things:
deploy(env): deploy contracts and return the contract handles the scenario needs.actors(env, contracts): construct the actors that will submit transactions into the public mempool, private mempool, or bundles.capture_state(env, contracts): collect the state snapshot attached to block events and exposed through the API.
Scenario configuration is passed to /simulation/load as a JSON object. Concrete scenarios usually validate that config with a Pydantic model in their constructor, then translate user-facing values into the internal units they need.
The repository currently contains one scenario: UniswapV2SandwichScenario in scenarios/uniswap_v2_sandwich/.
It models a sandwich attack around a Uniswap V2-style AMM:
PublicTradersubmits swaps to the public mempool.PrivateTradersubmits swaps to the private mempool.Sandwicherwatches for profitable public order flow and submits a bundle intended to front-run and back-run a victim trade.
At load time, the scenario:
- deploys two test ERC-20 tokens,
- deploys a Uniswap V2 pair plus helper contracts,
- seeds the pool with initial liquidity,
- instantiates the three actors above.
On each captured state snapshot, the scenario records AMM reserves and tracked actor balances so the UI can show price movement and PnL over time.
The checked-in config knobs are:
trader_interval: seconds between trader actionstrader_amount: trade size in token unitsfront_run_amount: size of each sandwich leg in token unitsinitial_liquidity: initial liquidity on both sides of the pool
Example presets live in:
scenarios/uniswap_v2_sandwich/default_config.jsonscenarios/uniswap_v2_sandwich/test_config.json
Run the Python test suite from the repository root:
pytestThe test suite expects forge and anvil to be available. It compiles the contracts with Foundry and uses Anvil for integration-style tests.