degenbot: Rust pool maths behind a Python MEV bot
Building blocks for Uniswap (V2, V3, V4), Curve V1, Solidly V2, Balancer V2 & Aave V3 arbitrage and liquidation bots on EVM-compatible blockchains
At a glance
- What is it?
- degenbot is an MIT licensed library set that puts Uniswap V2, V3 and V4, Curve V1, Solidly V2, Balancer V2 and Aave V3 pool logic in Rust and exposes it to Python through a thin PyO3 layer, so an arbitrage or liquidation bot can be written without reimplementing swap maths. It suits a quant developer who wants correct pool state, and it is alpha software with a strict ownership split between the two languages.
- Who is it for?
- degenbot fits a quant developer who wants to write an arbitrage or liquidation bot against several DEX protocols without reimplementing swap maths, event decoding and pool state, because the Rust core owns all of that and the Python layer is a thin driver.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One Rust core, two equally first-class consumers
degenbot is described as a Rust MEV-bot core with a first-class Python driver shell. The project makes a point of there being two equally first-class consumers sharing one Rust core, and the two are named in the README.
The pure-Rust consumer adds the crate with cargo add degenbot, which is the umbrella crate re-exporting the cores, and builds a fully functional MEV bot in Rust only. The README notes the umbrella crate is a git or path dependency until the workspace is published to crates.io. The Python-driven consumer drives the same Rust core from Python through a thin PyO3 layer that translates Python calls into Rust calls.
The division of labour is stated with unusual precision. The Rust core is the engine and Python is a driver shell, not a co-implementation. Pool and token state, swap maths, event decoding, solvers, the pump loop and swap encoding all live in the Rust core crates. The Python layer provides the user-facing API, orchestration and configuration.
The architectural vision is written up in a separate document, docs/adr/ADR-005-polars-inspired-three-layer-architecture.md, and the README is explicit that the crate sources are the last word when the documents and the code disagree.
The audience is a developer building a bot against these protocols, and the classes exist as building blocks for the lessons published by BowTiedDevil on Degen Code.
What actually lives in Rust, and what stays in Python
The Overview section lists the ownership boundary in detail, and it is worth reading because it decides how you extend the library.
The Rust core owns everything performance-critical and stateful: pool and token state, swap maths, event decoding, solvers, the pump loop, and swap encoding. It also owns the operator-facing infrastructure, which is the longer list: the settlement-arbitrage engine and pump loop, the in-process revm simulation engine, on-chain price readers, the database-aware pool and Aave updaters, EIP-1559 transaction signing and submission, and the websocket and HTTP pub-sub.
Python remains the user-facing API, the configuration, and the registry driver, and the Rust degenbot-db crate owns the SQLite schema, the file lifecycle and the read and write operations. Python consumers use a stable typed mirror in degenbot.db, and there is no Python SQLAlchemy session or ORM layer anywhere in the project.
Two design decisions follow from this and are called out in the README. The core crates contain no PyO3 code at all, so a pure-Rust bot runs without any Python machinery, and there is an in-repo proof of that in a standalone consumer example. And architectural decisions, specifically state ownership, the FFI topology and the schema cutover, are recorded as ADRs, with ADR-003, ADR-005 and ADR-010 named.
For a user, the practical consequence is that anything that mutates pool state happens in Rust. Python asks the core to do it. That is what makes the two consumer paths equivalent rather than one being a wrapper around the other.
Installing the package and the toolchain it expects
The installation requirements are Python 3.12 or later, and a package management tool such as pip or uv. The package is on PyPI:
pip install degenbotThe from-source route is a clone and a bootstrap command:
git clone https://github.com/BowTiedDevil/degenbot.git
cd degenbot
just bootstrap # or: pip install -e . for release-equivalent defaultsThe just recipe suggests a justfile-based workflow, and the repository does carry a justfile plus a run_bot.sh script.
The build itself is worth understanding because it explains why this is not a pure Python package. The build backend is maturin with a version requirement of 1.0 or later but below 2.0, and maturin is the Rust tool that produces Python wheels from a Rust crate. The version is dynamic and supplied by maturin at build time from the FFI binding crate's package version, which is inherited from the workspace package definition in rust/Cargo.toml. The comment in pyproject.toml calls this single-source-of-truth versioning, ADR-009, and notes that maturin normalises the semver form to PEP 440, so 0.6.0-alpha.3 becomes 0.6.0a3.
The Python-side runtime dependencies are short and ordinary: pydantic around 2.13, pydantic-settings around 2.15, tenacity around 9.1, and tomlkit around 0.15. So the Python layer is thin in dependencies as well as in code, and the heavy lifting is the compiled extension.
The repository also carries a rust-toolchain.toml, a devcontainer setup, a .devcontainer directory, a .envrc.example, a tests.env.example and a uv.lock, so the intended development environment is a pinned Rust toolchain plus uv, either in a container or on the host.
The Bot class, and why pools cannot be constructed directly
The Bot class is described as the central session object for all degenbot operations. It manages connections and registries, and it provides factory methods for creating pools and tokens. The quick start builds it from explicit settings:
bot = degenbot.Bot(
chain_id=1,
node=RPC_URL,
database=":memory:",
)The example notes that the Bot constructs the RPC provider from the node endpoint and enforces that its eth_chainId matches the chain_id, failing fast rather than silently talking to the wrong chain, and that no manual provider registration is needed. The in-memory database is for the example, and a persistent database path is the production default.
The more interesting constraint is in the advanced section. Pool classes are Python companions over Rust-owned pool state, and direct construction is impossible because any constructor call raises a TypeError. A pool comes into being only by registering in a Bot's Rust state, and the recommended path is Bot.build_pool() in production, with make_*_pool test helpers available in the tests.
That is a deliberate design and it has consequences. You cannot mock a pool by constructing it, and you cannot have a pool that exists outside a session, so the unit of composition is the Bot rather than the pool. For testing, that means the test helpers or a simulation environment rather than a mock.
I/O-free pools, the simulation harness and the settlement engine
The I/O-free architecture is the other concept that decides how you write against this library, and the README's example is short enough to be worth reading in full.
pool = bot.build_pool("0x8ad599c3A0ff1De082011EFDDc58f1908EB6e6D8")
token = bot.build_erc20token("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") # WETHThe comment above it says pools are I/O-free and that all data is injected at construction, so the pool can be printed and used to calculate a swap with no network calls at all. That is what makes a deterministic test of a strategy possible: the maths is separated from the fetching.
The operator-facing side is heavier, and it is where the strategy actually runs. There is a settlement-arbitrage engine and pump loop, an in-process revm simulation engine for running the EVM locally, on-chain price readers including a Chainlink price feed integration, database-aware pool and Aave updaters, EIP-1559 transaction signing and submission, and websocket and HTTP pub-sub.
For anyone debugging rather than trading, the README points at a specific document, docs/architecture/investigation-harness.md, which covers the simulation oracle driver, the per-contract scaffolder and the path-fixture toolkit, for debugging a failing settlement-arbitrage path or building your own simulation harness. The repository also carries a tier3-oracle directory and a contract_reference directory, plus a GLOSSARY.md.
The published protocol coverage is Uniswap V2, V3 and V4, Curve V1 StableSwap, Solidly V2, Balancer V2 weighted pools, and Aave V3, and the repository topics also list Aerodrome and SushiSwap, which are Solidly and Uniswap forks respectively.
Where degenbot is the wrong tool
The first limitation is the build and the operational surface. A Python package that is really a compiled Rust extension, with a maturin build, a pinned Rust toolchain and a justfile, is a different maintenance commitment from a pip install of a pure Python library. A team whose deployment pipeline installs wheels from PyPI will find that working from source requires cargo, and a team without Rust expertise will hit that wall during a dependency upgrade rather than at the start.
The second limitation is the state of the software. The release tags are alpha, v0.6.0a14, v0.6.0a13 and v0.6.0a12, and the classifiers say Development Status:: 4 - Beta. The version is derived from a Rust workspace literal and normalised by maturin, which means the version you see is a moving target across two toolchains. A production service that pins a degenbot version is pinning an alpha.
The third is the nature of the tool itself. This is a set of building blocks for arbitrage and liquidation bots, not a bot. The settlement engine, the pump loop, the solvers and the transaction submission are all there, and the README points to an investigation harness for debugging a failing path, which is a candid admission that a failing settlement path is a normal state to be in. The strategy is still yours, and the correctness of a live arbitrage depends on things this library cannot check for you, such as whether your opportunity is real once you account for the gas you will pay and the transactions that will beat you to it.
The alternative worth comparing is writing the pool maths yourself against the protocol contracts directly, using a library such as web3.py for the JSON-RPC and ABI handling. The difference in approach is that degenbot has already solved the part that is genuinely fiddly and version-sensitive, namely the per-protocol swap maths and the token and pool state that goes with it and it records its architectural decisions in the ADR process. What you give up is the ability to read every line of the logic, and what you get is a maintained set of pool implementations plus a simulation harness you would otherwise have to build. For a quant who already has audited pool maths, the library is redundant; for a quant who does not, it is the difference between a week and a month.
Editorial conclusion
degenbot fits a quant developer who wants to write an arbitrage or liquidation bot against several DEX protocols without reimplementing swap maths, event decoding and pool state, because the Rust core owns all of that and the Python layer is a thin driver. It is the wrong choice for a team that does not want a Rust toolchain and a compiled extension in its Python environment, for anyone who needs a production record of the strategy's own reliability, and for anyone who needs a settled answer rather than a simulation harness. Before building on it, read the ADR log, since state ownership, FFI topology and the schema cutover are recorded there and the crate sources are stated to be the last word, and check the alpha version tags, because the release line is still moving through prereleases.
Frequently asked questions
What is degenbot?
It is a set of building blocks for arbitrage and liquidation bots on EVM-compatible blockchains, with Uniswap V2, V3 and V4, Curve V1, Solidly V2, Balancer V2 and Aave V3 integrations. The pool and swap logic lives in Rust and is exposed to Python through a thin PyO3 binding, and a pure-Rust consumer path exists as well.
How do I install degenbot?
With Python 3.12 or later, run pip install degenbot for the PyPI release, or clone the repository, change into the degenbot directory and run just bootstrap, or pip install -e . for release-equivalent defaults. The package builds with maturin, so the Rust toolchain is required when working from source.
Why can I not construct a degenbot pool directly?
Pool classes are Python companions over Rust-owned pool state, so any direct constructor call raises a TypeError. A pool comes into being only by registering in a Bot's Rust state, and the recommended path is Bot.build_pool(), with make_*_pool helpers available in the tests.
What is in the degenbot Rust core?
The core owns pool and token state, swap maths, event decoding, solvers, the pump loop, swap encoding, the settlement-arbitrage engine, the in-process revm simulation engine, on-chain price readers, database-aware updaters, EIP-1559 signing and submission, and websocket and HTTP pub-sub. Python owns the user-facing API, configuration and orchestration.
What licence is degenbot released under?
The project is licensed under the MIT licence, with the licence file at the repository root. The release line is still alpha, with tags such as v0.6.0a14, and the last push was on 2026-10-22.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bowtieddevil-degenbot)Was this useful?