This project explores building a MEV bot targeting decentralized exchanges on Arbitrum.
- Smart contracts live in the
./contractsdirectory. They are compiled and tested using Foundry. - Foundry configuration is contained in
foundry.tomlat the repository root. Run tests withforge test. - Bot implementation will be written in Go. The entry point is under
cmd/botand connects to an Arbitrum node to watch transactions and events.
-
Install Foundry using:
curl -L https://foundry.paradigm.xyz | bash source ~/.bashrc && foundryup
After cloning the repo, run
forge install foundry-rs/forge-stdto fetch the testing libraries such asforge-std. Thelibdirectory is excluded from version control, so you may need to run this command whenever you clone a fresh repository. -
Download Go dependencies with
go mod downloadso the Go tests can compile. -
Run
make testto execute both the Solidity and Go tests. Behind the scenes this runsforge testandgo test ./.... -
Build the Go bot with
make buildwhich simply callsgo build ./cmd/bot. Utility packages underpkg/keep the bot logic modular. For example,ethutil.ConnectClientprovides a simple wrapper for creating Ethereum RPC clients. Thewatcherpackage offers utilities for observing chain activity.BlockWatcherlogs new block headers only whenDEBUG=1is set to reduce noise and automatically reconnects if the websocket drops.EventWatcherbehaves the same way and subscribes only to profitable trade events and Uniswap V2Syncevents. When aTradeExecutedevent is observed the bot prints the input and profit so profitable trades are logged in real time.Syncevents are decoded to log pool price updates, allowing the bot to track market movements without processing every log on the chain. -
Start the bot with
make run(ormake run-devto run usinggo run). The bot automatically loads environment variables from a.envfile if present usinggodotenv. SetDEBUG=1to enable more verbose logging with file and line numbers. Use a WebSocket RPC URL (e.g.wss://...) so the block and event watchers can subscribe to notifications. HTTP endpoints will only log an error and no events will be seen. The bot will look for aPAIRSenvironment variable specifying pairs to monitor for arbitrage, formatted as"addr1,addr2;addr3,addr4". SetFACTORIESto a comma separated list of factory addresses to automatically capturePairCreatedandPoolCreatedevents and grow the set of pools scanned for opportunities. When bothREGISTRY_ADDRESSandPRIVATE_KEYare configured, each discovered pool and its tokens are registered on-chain automatically. If only one of these variables is set the registry client is disabled. The bot waits for each registration transaction to be mined so the local cache only stores confirmed entries. -
Deploy contracts using
make deploy. By default this deploys theRegistrycontract withforge create. PassCONTRACT=path:Nameto deploy a different contract.RPC_URLandPRIVATE_KEYmust be set in the environment. -
Go contract bindings are generated automatically when running
make build(and thusmake run). Thegenerate-bindingstarget can still be called manually if desired. It runsscripts/generate_bindings.shwhich usesabigento create Go packages undercmd/bot/bindings. Ifabigenisn't installed the script simply skips generation. To install it run:
go install github.com/ethereum/go-ethereum/cmd/abigen@latest
8. Interact with the on-chain registry using `make registry-cli`. Pass
commands such as `ARGS="tokens"`, `ARGS="add-token 0x..."`, or
`ARGS="add-pool 0x..."`. When only a pool address is supplied the CLI
queries the pool for its tokens and registers everything automatically.
The tool uses the same `RPC_URL`, `REGISTRY_ADDRESS`, and `PRIVATE_KEY`
environment variables as the bot.
- Interact with the on-chain registry using
make registry-cli. Pass commands such asARGS="tokens",ARGS="add-token 0x...", orARGS="add-pool 0x...". When only a pool address is supplied the CLI queries the pool for its tokens and registers everything automatically. The tool uses the sameRPC_URL,REGISTRY_ADDRESS, andPRIVATE_KEYenvironment variables as the bot.
8. Interact with the on-chain registry using `make registry-cli`. Pass
commands such as `ARGS="tokens"`, `ARGS="add-token 0x..."`, or
`ARGS="add-pool 0x..."`. When only a pool address is supplied the CLI
queries the pool for its tokens and registers everything automatically.
The tool uses the same `RPC_URL`, `REGISTRY_ADDRESS`, and `PRIVATE_KEY`
environment variables as the bot.
- Interact with the on-chain registry using
make registry-cli. Pass commands such asARGS="tokens",ARGS="add-token 0x...", orARGS="add-pool 0x...". When only a pool address is supplied the CLI queries the pool for its tokens and registers everything automatically. The tool uses the sameRPC_URL,REGISTRY_ADDRESS, andPRIVATE_KEYenvironment variables as the bot.
8. Interact with the on-chain registry using `make registry-cli`. Pass
commands such as `ARGS="tokens"`, `ARGS="add-token 0x..."`, or
`ARGS="add-pool 0x..."`. When only a pool address is supplied the CLI
queries the pool for its tokens and registers everything automatically.
The tool uses the same `RPC_URL`, `REGISTRY_ADDRESS`, and `PRIVATE_KEY`
environment variables as the bot.
9. Debug the bot using `make debug` which launches it under the
[Delve](https://github.com/go-delve/delve) debugger. Install
`dlv` with `go install github.com/go-delve/delve/cmd/dlv@latest` if
it's not already available. Pass additional arguments with `ARGS`.
The repo now includes a `Registry` contract that stores token, exchange and pool metadata using library based diamond storage. It forms the on-chain
configuration for the bot and demonstrates how components remain modular.
The registry exposes helper getters like `getTokenCount`, `getExchangeCount` and
`getPoolCount` so the bot can query how many entries exist without fetching all
data.
`BatchExecutor` provides a simple multicall style contract that allows the bot
to execute a series of encoded calls within a single transaction. This makes it
easy to chain swaps across multiple pools when seeking arbitrage opportunities.
`ArbitrageCalculator` implements a small library that estimates the most
profitable trade size between two constant product pools. It helps the bot
determine how much input to supply when performing cross-exchange arbitrage.
`ArbitrageExecutor` is a simple contract that uses this calculator to execute
an arbitrage across two constant product pools. The executor transfers tokens
between the pairs and performs the swaps, returning any profit to the caller.
`TriangularArbitrageCalculator` and `TriangularArbitrageExecutor` extend this
idea to three pools. The calculator searches for the most profitable input
amount when swapping through a cycle of three constant product pairs. The
executor performs the swaps atomically, enabling triangular arbitrage across
multiple DEXes.
`MultiArbitrageExecutor` further generalizes this to an arbitrary number of
pools. It now accepts per‑pool fee settings so different exchanges like Uniswap
V3 or Algebra can be mixed within a single cycle. Provide parallel arrays of fee
numerators and denominators when calling `execute` to support directional fees.
An example test demonstrates a three‑pair arbitrage executing successfully.
Run both Solidity and Go tests with:
```bash
forge test -vv
go test ./...
Alternatively you can just run make test which wraps both commands.
The goal is to watch Arbitrum events for arbitrage opportunities across many DEXes.
Market now records both discovered pools and the tokens they trade. Pool and
token addresses are collected from factory events in real time and can be
listed via the bot's API in future iterations. A simple NonceManager utility
helps avoid transaction conflicts when submitting many arbitrage attempts.
A lightweight Vue 3 front end scaffold lives in web/. It uses Vite,
Tailwind, Pinia, Viem, and Vue Router to visualize saved tokens and pools.
Install dependencies with make web-install and run make web-dev to launch
the UI. Build the production bundle with make web-build. Set VITE_API_URL
to the bot's API address when running the dev server, e.g.
VITE_API_URL=http://localhost:8080 npm run dev.
The Go bot exposes a small HTTP API on port 8080 that the frontend uses. It
provides GET /tokens and GET /pools to list the current market. POST /tokens
adds a single token, while POST /pools accepts just a pool address and
automatically fetches its tokens before updating the registry when a registry
address and key are configured.
A sample .env.sample file is provided containing environment variables used by
the Go bot, such as RPC_URL and PRIVATE_KEY. Copy it to .env and adjust the
values as needed. RPC_URL defaults to the public Arbitrum RPC endpoint if left
unset. MARKET_CACHE controls where discovered pools are cached locally and
should point to a SQLite database file such as market.db. REGISTRY_ADDRESS
specifies an on-chain registry contract to persist newly discovered pools and
tokens. Cached pools include their token addresses so the bot can resync them to
the registry on startup. When syncing the registry,
the bot queries existing entries to avoid duplicate transactions and logs the
hash of each successful addToken or addPool call. Runtime state tracks which
tokens and pools are already registered so newly discovered addresses are only
submitted once.
You can build a container image containing the bot binary:
# or simply `make docker`
docker build -t mev-bot .Run the container with your environment variables, for example:
docker run --rm -e RPC_URL=https://arb1.arbitrum.io/rpc mev-botTo check Go test coverage you can run:
go test ./... -coverTo calculate Solidity test coverage, run:
forge coverageSee .env.sample for the full list of environment variables understood by the
bot.