A self-contained CTF challenge that forks Arbitrum One at a fixed block and runs GMX V1 keepers in the background. Players connect to a public RPC and interact with a live GMX V1 state to solve the challenge.
- Arbitrum One fork at block
355878386via Hardhat - Private RPC (port
8546) — full Hardhat methods, used internally by keepers - Public RPC (port
8545) — proxy that strips outhardhat_*,evm_*,anvil_*admin methods so players can't impersonate accounts or mine blocks - Keeper daemon that processes:
OrderBook.executeIncreaseOrder/executeDecreaseOrder(viaPositionManager)PositionRouter.executeDecreasePositions(decrease queue)
The challenge entry-point contract is in contracts/exp.sol.
pnpm install
cp .env.example .env # set ARB_RPC_URL to a real Arbitrum archive RPC
# Terminal 1 — start the fork
npm run rpc
# Terminal 2 — start the keeper daemon
npm run keeper
# Terminal 3 (optional) — public RPC proxy
npm run proxyOr, one-shot:
bash scripts/up.sh "https://your-arbitrum-rpc"Only the public RPC port is exposed; the private RPC and keepers run inside the container.
docker build -t nht-ctf .
docker run --rm -p 8545:8545 -e ARB_RPC_URL="https://your-arbitrum-rpc/" nht-ctfOr via compose:
export ARB_RPC_URL="https://your-arbitrum-rpc/"
docker compose up --buildcontracts/exp.sol Challenge / exploit entry-point contract
hardhat.config.ts Fork config, networks, solidity profile
flag.txt Placeholder flag (replace before deploying)
scripts/
up.sh One-shot launcher (rpc + proxy + keeper)
run-fork-rpc.sh Start the private fork RPC
public-rpc-proxy.ts JSON-RPC proxy that blocks admin methods
keeper-daemon.ts GMX V1 keeper loop
fund-player.ts Fund the player address with ETH/USDC
check-fork-health.ts Sanity-check the fork is alive
check-vault-wbtc-reserve.ts Win-condition probe
docker-entrypoint.sh Container boot script
lib/
gmx-v1-abis.ts ABIs used by the keeper
gmx-v1-arbitrum.ts Mainnet addresses
keepers/orderbook.ts OrderBook keeper
keepers/position-router.ts PositionRouter keeper
Dockerfile / docker-compose.yml
- Do not expose the private RPC (
8546) publicly — the public proxy is what players should hit. - Keeper config (addresses, intervals) lives at the top of
scripts/keeper-daemon.ts— no CLI args. - Fund the player before letting them in: edit
scripts/fund-player.tsthennpm run fund. - Replace
flag.txtwith your real flag before deployment.
- Hardhat 3 + viem toolbox
- Solidity 0.8.28
- TypeScript / Node 22 LTS
- pnpm
MIT