A browser-based DeFi capture-the-flag platform. Each challenge puts you inside a live simulated Ethereum market with a specific goal: grow your portfolio, exploit a vulnerable contract, or manipulate the market before the clock runs out. Write JavaScript trigger scripts or Solidity contracts, watch your strategy play out in real time, and iterate until you win.
No prior blockchain setup required. Anvil, the engine, and the frontend all run inside one Docker
container or a single ./start.sh call.
Option A: Docker (recommended, no local dependencies needed)
git clone https://github.com/branover/defi-ctf.git
cd defi-ctf
docker compose -f docker/docker-compose.yml up --buildOpen http://localhost:5173 in your browser.
First build takes 3-5 minutes (downloads Foundry + npm packages). Subsequent starts are fast since Docker caches the dependency layers.
Option B: Local
./setup.sh # installs dependencies (Debian/Ubuntu/Kali)
./start.sh # starts Anvil, the engine, and the frontendOpen http://localhost:5173.
Each challenge runs a deterministic simulated market on a local Anvil chain. Bots trade every block with fixed strategies. Your job is to beat them, or find the bug they left in the contracts.
Pick a challenge on the landing page, click Play, and the platform:
- Resets the chain to a clean state
- Deploys tokens, pools, and any challenge-specific contracts
- Mints starting balances for you and the bots
- Starts mining blocks
Win by meeting the challenge goal before the block count hits zero. Restart instantly and try a different approach. The market is fully deterministic, so the same setup produces the same bot behavior every time.
New to DeFi? Click Tutorial on the landing page for an interactive walkthrough before diving into challenges.
Legitimate market strategies. Spot price inefficiencies, ride trends, provide liquidity, or execute TWAP orders within the rules of the market. Good starting point if you're new to DeFi.
Tactics that exploit bot behavior, thin liquidity, or predictable order flow. Front-run a clockwork whale, trigger a liquidation cascade, or pump a token and dump into the momentum buyers. No contract bugs involved, just information asymmetry.
Smart contract security challenges. Find and exploit a real vulnerability: reentrancy, flash loan attacks, uninitialized proxy ownership, arithmetic overflow, oracle manipulation, and more. Based on real historical hacks scaled down to a single challenge.
Buy, sell, wrap/unwrap ETH, and manage liquidity directly from the browser. Trade markers appear on the chart so you can see exactly where your transactions landed.
Write a script in the in-browser IDE. The script runs once to register triggers: callbacks that fire on every block, when price crosses a threshold, etc. The full SDK (swap, getPrice, getBalance, addLiquidity, contract calls) is available inside each trigger.
ETH vs WETH: Trading pools only accept ERC-20 tokens, not native ETH. Your starting balance is native ETH — call
wrapEth(amount)to convert it to WETH before trading in pools. CallunwrapEth(amount)to convert back. The win condition checks your total ETH balance, which includes any unwrapped WETH.
// Buy the dip, sell the recovery
const id = onPriceBelow("weth-usdc-uniswap", 2800, async (ctx) => {
const usdc = await getBalance("USDC");
await swap("weth-usdc-uniswap", "USDC", usdc * 80n / 100n);
removeTrigger(id);
});
onPriceAbove("weth-usdc-uniswap", 3100, async (ctx) => {
const weth = await getBalance("WETH");
if (weth > 0n) await swap("weth-usdc-uniswap", "WETH", weth);
});See docs/script-sdk.md for the full SDK reference.
Switch the IDE panel to Solidity mode to edit files in the per-challenge solve/ workspace.
Click Run Script to execute a forge script against the live chain, or Deploy to compile
and deploy a contract. Output streams in real time.
This is the main interface for smart contract exploit challenges.
Use forge and cast from a terminal against the same Anvil instance:
cd solve/
./env.sh && source .env # writes .env with all addresses for the running challenge
cast call $ADDR_VAULT "owner()" --rpc-url $RPC_URL
forge script script/Solve.s.sol --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcastSee docs/foundry-workflow.md for a full walkthrough.
Anvil (port 8545) <- EVM execution
^
Engine (port 3000) <- HTTP REST + WebSocket (challenge control, bots, triggers)
^
Frontend (port 5173) <- browser UI (charts, IDE, depth panel, trade log)
The engine mines blocks on a timer. Each block:
- Records prices and OHLCV candles for all pools
- Runs every bot's
tick()(deterministic, seeded) - Fires registered player triggers
- Checks the win condition
Start here as a player:
| Document | What it covers |
|---|---|
docs/examples.md |
Worked JS strategy examples — best starting point after the tutorial |
docs/script-sdk.md |
Full JS SDK reference (all functions, parameters, return types) |
docs/foundry-workflow.md |
Solidity workflow (forge script, deploy, env.sh) |
docs/challenges.md |
Challenge authoring — manifest schema, win conditions, contract types |
Reference and advanced:
| Document | What it covers |
|---|---|
docs/bots.md |
Bot personalities and config |
docs/http-api.md |
HTTP REST API reference |
docs/websocket-api.md |
WebSocket message protocol |
docs/smart-contracts.md |
Contract ABIs and custom deployment |
For challenge authors:
| Document | What it covers |
|---|---|
docs/challenges.md |
Authoring challenges (manifest schema) |
./setup.sh # first-time setup
./start.sh # start platform
./stop.sh # stop platform
./test/run-tests.sh # run all tests (API + WS regression)
./test/run-tests.sh --ui # also run Playwright UI tests
cd engine && npx tsc --noEmit # TypeScript type-check
cd frontend && npm run build # frontend build check
cd frontend && npm test # frontend unit tests (Vitest)- Create
challenges/<id>/manifest.json(seedocs/challenges.md) - Add a
challenges/<id>/README.mdwith description and hints - If new contracts are needed, add
.solfiles tocontracts/src/and runforge build - Restart the engine (challenges auto-load from the directory)
# Start (foreground)
docker compose -f docker/docker-compose.yml up --build
# Start (detached)
docker compose -f docker/docker-compose.yml up -d --build
# Stop
docker compose -f docker/docker-compose.yml down
# Run tests inside Docker
docker compose -f docker/docker-compose.yml --profile test run --rm testsManifests in challenges/ are mounted as a volume, so you can edit them and restart the
container without rebuilding the image.
Large changes go on feature branches with atomic commits, followed by a PR before merge. See CLAUDE.md for the full workflow.

