Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Degenbot

A Rust MEV-bot core with a first-class Python driver shell, for Uniswap (V2, V3, V4), Curve V1, Solidly V2, Balancer V2, and Aave V3 integrations on EVM-compatible blockchains.

Degenbot has two equally first-class consumers sharing one Rust core:

  • Pure-Rust MEV botcargo add degenbot (the umbrella crate re-exporting the cores; a git/path dependency until the workspace is published to crates.io) and build a fully functional MEV bot in Rust only.
  • Python-driven MEV bot — drive the same Rust core from Python through a thin PyO3 layer that translates Python calls into Rust calls.

The Rust core is the engine; Python is a driver shell, not a co-implementation. Pool/token state, swap math, event decoding, solvers, the pump loop, and swap encoding all live in Rust core crates; the Python layer provides the user-facing API, orchestration, and configuration. See docs/adr/ADR-005-polars-inspired-three-layer-architecture.md for the architectural vision.

Contents

Debugging a failing settlement-arbitrage path, or building your own simulation harness? See INVESTIGATIONS.md — the simulation oracle driver, the per-contract scaffolder, and the path-fixture toolkit.

Overview

Degenbot abstracts the implementation details of Uniswap liquidity pools and their underlying ERC-20 tokens into a set of Rust core crates exposed to Python through a thin PyO3 binding layer. The Rust core owns all performance-critical and stateful logic — pool/token state, swap math, event decoding, solvers, the pump loop, and swap encoding — while the Python companion provides the user-facing API, docstrings, and I/O orchestration.

As of the 0.6.x series the Rust core also owns the operator-facing infrastructure: the settlement-arbitrage engine and pump loop, the in-process revm simulation engine, on-chain price readers, the DB-aware pool/Aave updaters, EIP-1559 transaction signing and submission, and WS/HTTP pub-sub. Python still owns the user-facing API, config, and registries, and — until the ADR-010 0.7 cutover — the SQLAlchemy ORM with its Alembic-stamped session; the Rust degenbot-db crate already owns the schema DDL and file operations behind it (see degenbot database cutover / degenbot database heal).

These classes serve as building blocks for the lessons published by BowTiedDevil on Degen Code.

Architecture: The Python-Rust Split

Ownership is strict, which is what makes both consumption paths first-class: the Rust core owns everything stateful and performance-critical — pool/token state, swap math, event decoding, solvers, the pump loop, and swap encoding — while the Python side owns the user-facing API, orchestration, and configuration. The core crates contain no PyO3 code at all, so a pure-Rust bot (cargo add degenbot) runs without any Python machinery; an in-repo proof is rust/crates/degenbot/examples/standalone_consumer.rs. Architectural decisions — state ownership (ADR-003), FFI topology (ADR-005), schema cutover (ADR-010) — are recorded in the ADR design log, with the crate sources as the last word.

Installation

Requirements

  • Python 3.12+
  • pip, uv, or similar package management tool

From PyPI

pip install degenbot

From Source

git clone https://github.com/BowTiedDevil/degenbot.git
cd degenbot
uv sync  # or: pip install -e .

Quick Start

Modern Bot-Based Approach (Recommended)

The Bot class is the central session object for all degenbot operations. It manages connections, registries, and provides factory methods for creating pools and tokens:

# Initialize Bot from config file or explicit settings
bot = degenbot.Bot(
    config=DegenbotConfig(
        default_chain_id=1,
        rpc={1: RPC_URL},
        database={"path": "~/.config/degenbot/degenbot.db"},
    )
)

# Bot constructs the RPC provider from config and enforces its
# eth_chainId matches default_chain_id (fail-fast). No manual provider
# registration is needed.
# Create pools and tokens through Bot (I/O-free when possible)
pool = bot.build_pool("0x8ad599c3A0ff1De082011EFDDc58f1908EB6e6D8")
token = bot.build_erc20token("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")  # WETH

# Pools are I/O-free - all data injected at construction
print(f"Pool: {pool.name}")
print(f"Token: {token}")

# Calculate swaps without any network calls
amount_out = pool.calculate_tokens_out_from_tokens_in(
    token_in=pool.token0,
    token_in_quantity=10**18,
)
print(f"Output: {amount_out}")

Direct Pool Construction (Advanced)

Pool classes are Python companions over Rust-owned pool state — direct construction is impossible (any constructor call raises TypeError); a pool comes into being only by registering in a Bot's Rust state. Use Bot.build_pool() in production (or the make_*_pool test helpers in tests):

# Do NOT do this — the constructor always raises TypeError:
try:
    degenbot.UniswapV3Pool("0x8ad599c3A0ff1De082011EFDDc58f1908EB6e6D8")  # ← BROKEN!
    raise AssertionError("direct construction of a pool should be impossible")
except TypeError:
    pass

# Instead, always use Bot to construct pools (registers in Rust state,
# returns the Python companion wrapper):
pool = bot.build_pool("0x8ad599c3A0ff1De082011EFDDc58f1908EB6e6D8")

Core Concepts

I/O-Free Architecture

Degenbot pools follow an I/O-free architecture where on-chain data is fetched at construction time and injected into pool objects. After construction, pools are pure calculation objects with no network dependencies. For the Uniswap V2/V3/V4 families (including Aerodrome and Balancer), Bot.build_pool() performs that full choreography on the Rust side; Curve pools and token metadata use the equivalent Python-side builders. Either way, the pool you receive needs no network access at construction time.

Benefits:

  • Testability: Easy to create test fixtures with mocked data
  • Performance: Swap calculations are pure math, no network calls
  • Reliability: No async complexity in pool logic
  • State Management: Pools can be snapshotted, pickled, and restored

Current status: all pool types (V2, V3, V4, Aerodrome, Camelot, Balancer, Curve) are fully I/O-free — no pool class carries provider-dependent methods. For the Uniswap V2/V3/V4 families (including Aerodrome and Balancer), Bot.build_pool() performs the full fetch-and-register choreography and bot.update(pool) refreshes state from chain; Curve pools and token metadata use the remaining Python builders. Either way, state changes enter a pool only as a validated external_update() message — the pool itself never does I/O.

The Bot Class

Bot is the central session object that owns all runtime state:

import degenbot
from degenbot.config import DegenbotConfig

# Bot manages connections, registries, and provides factory methods
bot = degenbot.Bot(
    config=DegenbotConfig(
        default_chain_id=1,
        rpc={1: RPC_URL},
        database={"path": ":memory:"},
    )
)
# The RPC provider is built from config; eth_chainId is enforced to equal
# default_chain_id at construction.
bot.provider  # the chain's AlloyProvider (chain_id enforced at construction)
bot.chain_id  # 1
# All pool/token creation flows through Bot
pool = bot.build_pool("0x8ad599c3A0ff1De082011EFDDc58f1908EB6e6D8")
token = bot.build_erc20token("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")

# Bot provides token utilities with caching
balance = bot.get_token_balance(token, "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
approval = bot.get_token_approval(token, owner="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", spender="0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45")

Bot properties:

  • bot.chain_id - the configured chain ID for this single-chain session
  • bot.provider / bot.get_provider() - the chain's AlloyProvider (chain_id enforced at construction)
  • bot.pools - PoolRegistry for created pools
  • bot.tokens - TokenRegistry for created tokens
  • bot.managed_pools - ManagedPoolRegistry for V4 pools
  • bot.db - DatabaseSessionManager for state snapshots

Lifecycle & refresh: Bot is a context manager — with degenbot.Bot(config=...) as bot: (or an explicit, idempotent bot.close()) tears down the provider, the scoped DB session, and the Rust engine handles. bot.update(pool, block_number=...) is the canonical refresh entry point for the V2/V3/V4 families: it fetches current chain state from the Rust core and pushes pool.external_update() (returns True only when state changed). bot.release_python_state() drops the Python-side tracker/snapshot caches once the Rust engine owns canonical state. Builders are internal to Bot and not exposed publicly. All pool/token creation goes through Bot.build_pool().

Pool Types and Builders

build_pool(address) is the universal entry point that auto-resolves pool type from DB, registry, and on-chain probing:

Pool Type Method Supports
Uniswap V2 bot.build_pool(address) Standard AMM, Camelot, other forks
Uniswap V3 bot.build_pool(address) Full tick data, range orders
Uniswap V4 bot.build_managed_pool(address, pool_id=...) Singleton architecture with hooks
Curve V1 bot.build_pool(address) StableSwap, metapools, lending pools

When build_pool is called, the pool type is auto-resolved — in order, from the pool registry, the database, and (as a last resort) on-chain probing of the pool contract.

External Updates

Pools receive state updates via external_update() — a pure-logic method that validates the update and transitions pool state. I/O never touches the pool itself: for the V2/V3/V4 families bot.update(pool) fetches current reserves/slot0/liquidity from the Rust core (Curve/Balancer refresh runs through the remaining Python builders), constructs the family's ExternalUpdate message, and pushes it to the pool:

# Builder fetches state from chain (I/O), constructs update, pushes to pool
update = UniswapV2PoolExternalUpdate(
    block_number=block_number,
    reserves_token0=reserves0,
    reserves_token1=reserves1,
)
pool.external_update(update)  # Pure logic — no I/O

# Pool.simulate_swap() previews swaps without state change
# Pool.calculate_tokens_out_from_tokens_in() is pure math after construction

Supported Protocols

DEXs (Automated Market Makers)

Protocol Versions Chains
Uniswap V2, V3, V4 Ethereum, Base
Aerodrome V2, V3 Base
PancakeSwap V2, V3 Ethereum, Base
SushiSwap V2, V3 Ethereum, Base
Curve V1 Ethereum
Solidly V2 Ethereum, Base
Balancer V2 Ethereum
Camelot V2 Arbitrum
SwapBased V2 Base

Lending Protocols

Protocol Features
Aave V3 Supply, Borrow, Withdraw, Repay, Liquidation, E-Mode, GHO

Infrastructure

Feature Description
Chainlink Price Feeds Oracle price data
Anvil Forking Local forked blockchain for testing

Examples

The following examples demonstrate the recommended Bot-based approach for pool and token construction.

Using the Bot Class (Recommended)

All pool and token creation should flow through the Bot class for proper registry management and I/O handling:

import degenbot
from degenbot.config import DegenbotConfig

# Initialize Bot (handles config, connections, registries)
bot = degenbot.Bot(
    config=DegenbotConfig(
        default_chain_id=1,
        rpc={1: RPC_URL},
        database={"path": ":memory:"},
    )
)
# Build tokens (fetches from DB/RPC, cached in registry)
weth = bot.build_erc20token("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
usdc = bot.build_erc20token("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")

# Build pools (fetches all state from DB/RPC, returns I/O-free pool objects)
v3_pool = bot.build_pool("0x8ad599c3A0ff1De082011EFDDc58f1908EB6e6D8")
v2_pool = bot.build_pool("0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc")
curve_pool = bot.build_pool("0xbEbc44782C7db0a1A60Cb6fe97d0b483032FF1C7")  # 3Crv

# Universal builder -- auto-resolves pool type
pool = bot.build_pool("0x8ad599c3A0ff1De082011EFDDc58f1908EB6e6D8")  # V3, detected automatically

# Token utilities with automatic caching
balance = bot.get_token_balance(usdc, "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
approval = bot.get_token_approval(usdc, owner="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", spender="0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45")

# Pools are I/O-free after construction - pure calculations
amount_out = v3_pool.calculate_tokens_out_from_tokens_in(
    token_in=v3_pool.token0,
    token_in_quantity=1000_000000,  # 1000 USDC
)

Uniswap V2 Liquidity Pools

V2 pools use the constant-product invariant (x·y=k) with directional fees:

# `lp` is the WBTC/WETH V2 pool; in production it comes from
# `lp = bot.build_pool('0xBb2b8038a1640196FbE3e38816F3e67Cba72D940')`.
# An off-line test fixture built the same pool here so the math below
# runs without RPC.
assert lp.token0.symbol == 'WBTC'
assert lp.token1.symbol == 'WETH'
assert lp.reserves_token0 == 10732489743
assert lp.reserves_token1 == 2056834999904002274711

# V2 directional fees (may differ per direction)
assert lp.fee_token0 == Fraction(3, 1000)
assert lp.fee_token1 == Fraction(3, 1000)

# Calculate swap outputs - pure math, no I/O
assert lp.calculate_tokens_out_from_tokens_in(
    token_in=lp.token1,
    token_in_quantity=1*10**18
) == 5199789

assert lp.calculate_tokens_in_from_tokens_out(
    token_out=lp.token0,
    token_out_quantity=5199789
) == 999999992817074189

# Pools are I/O-free: updates flow through external_update()
# The builder (internal to Bot) fetches state and pushes updates
update = UniswapV2PoolExternalUpdate(
    block_number=100,
    reserves_token0=10732455184,
    reserves_token1=2056841643098872755548,
)
lp.external_update(update)

# Reserves are updated in-place
assert lp.reserves_token0 == 10732455184
assert lp.reserves_token1 == 2056841643098872755548

Uniswap V3 Liquidity Pools

V3 pools use concentrated liquidity with tick-based positions. The V3 pool uses a sparse tick data fetcher for on-demand liquidity loading:

# `lp` is the WBTC/WETH 0.3% V3 pool; in production it comes from
# `lp = bot.build_pool('0xCBCdF9626bC03E24f779434178A73a0B4bad62eD')`.
# An off-line test fixture built the same pool here so the math below
# runs without RPC.
assert lp.token0.symbol == 'WBTC'
assert lp.token1.symbol == 'WETH'
assert lp.fee == 3000
assert lp.liquidity == 544425151051415575
assert lp.sqrt_price_x96 == 34048891009198980752047510166697902
assert lp.tick == 259432

# Calculate inputs and outputs - pure math, no I/O
assert lp.calculate_tokens_out_from_tokens_in(
    token_in=lp.token1,
    token_in_quantity=1*10**18
) == 5398169

# Tick bitmap and tick data are injected at construction
assert 0 in lp.tick_bitmap
assert 0 in lp.tick_data

Uniswap V4 Liquidity Pools

V4 uses a singleton pool manager with hooks. Pools are identified by pool_id instead of address:

# `lp` is the ETH/USDC 0.05% V4 pool; in production it comes from
# `lp = bot.build_managed_pool('<poolManager>', pool_id='0x<...>')` — V4 pools
# are identified by (pool manager, pool id) rather than an address.
# An off-line test fixture built the same pool here so the math below
# runs without RPC.
assert lp.token0.symbol == 'ETH'
assert lp.token1.symbol == 'USDC'
assert lp.liquidity == 60429069420043934
assert lp.sqrt_price_x96 == 4220772448119892035402666
assert lp.tick == -196812

# V4 features: hooks, protocol fees, dynamic LP fees
assert lp.active_hooks == frozenset()
assert lp.pool_key == UniswapV4PoolKey(
    currency0='0x0000000000000000000000000000000000000000',
    currency1='0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
    fee=500,
    tick_spacing=10,
    hooks='0x0000000000000000000000000000000000000000',
)

Forking With Anvil

The AnvilFork class is used to launch a fork with anvil from the Foundry toolkit. The fork subprocess is spawned and driven by the Rust core; the Python AnvilFork is a thin companion over it. The object provides a provider attribute — an AlloyProvider — which can be used to communicate with the fork like any typical RPC client.

>>> fork = degenbot.AnvilFork(fork_url='http://localhost:8545')
>>> fork.provider.chain_id
1
>>> fork.provider.block_number
22675736

# The `AnvilFork` instance also exposes HTTP and WS endpoints that can be used to make a
# separate connection from a remote machine.
>>> from degenbot.provider import AlloyProvider
>>> _prov = AlloyProvider(fork.http_url)
>>> _prov.is_connected()
True

# The fork can be reset to a specific block (defaults to the latest block).
>>> fork.reset(block_number=22_675_800)
>>> fork.provider.block_number
22675800

# A different endpoint or start block needs a NEW fork — `reset` cannot retarget
# the fork URL. An "imaginary" block after a historical transaction (anvil
# `--fork-transaction-hash`, see the [Anvil reference](https://getfoundry.sh/anvil/reference/))
# is a constructor option:
>>> fork = degenbot.AnvilFork(
    fork_url='http://localhost:8545',
    fork_transaction_hash='0xc16e63e693a2748559c0fd653ade195be426472dddc5bfa3fcc769c4c88c249c',
)

# Blocks can be manually mined
>>> fork.mine()

# Byte code can be set for an arbitrary address.
>>> fork.set_code(
    address='0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
    code=bytes.fromhex('45')
)
>>> fork.provider.get_code('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')
b'\x45'

Anvil Options

The Anvil client offers many options; the most common ones are exposed as AnvilFork constructor options. For fine-grained control, pass any raw anvil flag through the anvil_opts argument (a list of strings, e.g. anvil_opts=['--optimism'] or anvil_opts=['--hardfork=london']) — they are appended after all managed options.

Curve StableSwap Pools (I/O-Free)

Curve pools follow the same I/O-free architecture: the Bot resolves metapool detection, lending-token identification, and all on-chain inputs before the pool runs pure math:

# `tripool` is Curve's 3Crv pool; in production it comes from
# `tripool = bot.build_pool('0xbEbc44782C7db0a1A60Cb6fe97d0b483032FF1C7')`.
# An off-line test fixture built the same pool here so the math below
# runs without RPC.
assert [t.symbol for t in tripool.tokens] == ['DAI', 'USDC', 'USDT']
assert tripool.a_coefficient == 2000
assert tripool.fee == 4000000

# For lending pools (cTokens), rates are resolved before calculation;
# get_dy() resolves all on-chain inputs upfront, then computes with pure math

Balancer V2 Weighted Pools

Balancer V2 weighted pools use the weighted product invariant with configurable token weights and a singleton Vault architecture. The math libraries are ported from the Balancer V2 Solidity monorepo with exact integer-level matching against on-chain results.

from degenbot.balancer.pools import BalancerV2Pool

# `weighted_pool` is the real mainnet "80 BAL 20 WETH" pool, built off-line by
# the fixture above so the math below runs without RPC. In production the same
# object comes from:
#     weighted_pool = bot.build_pool('0x5c6Ee304399DBdB9C8Ef030aB642B10820DB8F56')
assert isinstance(weighted_pool, BalancerV2Pool)
assert weighted_pool.address == '0x5c6Ee304399DBdB9C8Ef030aB642B10820DB8F56'
assert weighted_pool.vault == '0xBA12222222228d8Ba445958a75a0704d566BF2C8'
assert [t.symbol for t in weighted_pool.tokens] == ['BAL', 'WETH']
assert weighted_pool.fee == Fraction(1, 100)                 # 1% swap fee
assert weighted_pool.weights == (8 * 10**17, 2 * 10**17)    # 80 BAL / 20 WETH

# Swap math is pure after construction — no I/O
amount_out = weighted_pool.calculate_tokens_out_from_tokens_in(
    token_in=weighted_pool.tokens[1],   # WETH in
    token_out=weighted_pool.tokens[0],  # BAL out
    token_in_quantity=10**18,
)
assert amount_out == 61874980427000000  # ≈ 0.0619 BAL per WETH at 80/20 + 1% fee

amount_in = weighted_pool.calculate_tokens_in_from_tokens_out(
    token_in=weighted_pool.tokens[1],   # WETH in
    token_out=weighted_pool.tokens[0],  # BAL out
    token_out_quantity=100 * 10**18,
)
assert amount_in == 1616565737428323232324

Contract addresses and broken pool filters are centralized in degenbot.balancer.deployments:

from degenbot.balancer.deployments import (
    BALANCER_V2_VAULT_ADDRESS,
    BALANCERQUERIES_CONTRACT_ADDRESS,
    BROKEN_BALANCER_V2_POOLS,
)

# Canonical Vault + BalancerQueries addresses
assert BALANCER_V2_VAULT_ADDRESS == '0xBA12222222228d8Ba445958a75a0704d566BF2C8'
assert BALANCERQUERIES_CONTRACT_ADDRESS == '0xE39B5e3B6D74016b2F6A9673D7d7493B6DF549d5'

# BROKEN_BALANCER_V2_POOLS is a frozenset of pools with swaps disabled on-chain.
# Filter before constructing:
broken = '0x753BD6a5bF0b14ae7e5d2877e5cD6a3398aA2AAB'  # YUME/WETH 1/99
assert broken in BROKEN_BALANCER_V2_POOLS
assert weighted_pool.address not in BROKEN_BALANCER_V2_POOLS  # 80 BAL 20 WETH is healthy

Balancer V2 Stable Pools

Balancer V2 stable pools (MetaStablePool and ComposableStablePool) use the StableSwap invariant with rate caching. The math libraries are ported from deployed contracts with exact integer-level matching against on-chain results.

Two pool shapes share the same BalancerV2StablePool interface:

  • MetaStablePool — a 2-token stable pool with no BPT token and near-static rates: exact swap math needs no rate provider and no extra I/O.
  • ComposableStablePool — a multi-token stable pool that includes its own BPT token; time-varying rates (e.g., bb-a-* yield tokens) require a live BalancerRateProvider, and without one a swap call raises StaleRateResult (the approximate result is still readable on the exception).
from degenbot.balancer.stable_pools import BalancerV2StablePool
from degenbot.exceptions.pool import StaleRateResult

# Both pools were built off-line by the fixture above so the math runs
# without RPC; production obtains the same objects via `bot.build_pool(address)`.

# MetaStablePool: 2-token, no BPT, near-static rates — exact swap math
# needs no rate provider and no live RPC.
assert isinstance(meta_pool, BalancerV2StablePool)
assert [t.symbol for t in meta_pool.tokens] == ['wstETH', 'WETH']
assert meta_pool.fee == Fraction(4, 10000)        # 0.04%
assert meta_pool.amp == 50_000

amount_out = meta_pool.calculate_tokens_out_from_tokens_in(
    token_in=meta_pool.tokens[1],   # WETH in
    token_out=meta_pool.tokens[0],  # wstETH out
    token_in_quantity=10**18,
)
assert amount_out == 908727110808623404  # ≈ 0.9087 wstETH per WETH @ 1.1 rate

# ComposableStablePool: time-varying rates require a live rate provider;
# without one the call raises StaleRateResult (the approximate result is
# still readable on the exception).
assert isinstance(comp_pool, BalancerV2StablePool)
assert [t.symbol for t in comp_pool.tokens] == ['TUSD', '50TUSD50USDC', 'USDC']
assert comp_pool.fee == Fraction(3, 10000)         # 0.03%

try:
    comp_pool.calculate_tokens_out_from_tokens_in(
        token_in=comp_pool.tokens[0],   # TUSD in
        token_out=comp_pool.tokens[2],  # USDC out
        token_in_quantity=10**18,
    )
except StaleRateResult as e:
    # StaleRateResult wraps the approximate result so callers can still read it
    assert e.amount_in == 10**18
    assert e.amount_out == 1001103

Uniswap Arbitrage

Optimal arbitrage amounts for a cyclic pool sequence are computed by the Rust ArbitrageEngine (EVM-exact U512 solve), driven through EngineRegistry. The older Python cycle/solver classes are retired — the Rust engine is the sole solve surface:

from degenbot.arbitrage.engine_registry import EngineRegistry

# EngineRegistry is the one canonical entry point: it runs the pre-pump
# startup ritual (subscribe -> backfill from snapshot -> verify config) and
# registers cyclic paths against a Bot's shared BotState. The Rust engine
# owns the EVM-exact U512 solve and re-solves affected paths on each block.
registry = EngineRegistry(bot=bot)
# In production, `registry.start(node_http, node_ws)` runs the startup
# ritual (subscribe, snapshot, verify config) and returns BEFORE resume();
# after attaching the result consumer, `registry.engine.resume()` is the
# single gate after which one result batch per block flows.
path_id = registry.register_path(
    pools_and_zfos=[(v2_pool, True), (v3_pool, False)],
)
# The registered path is inspectable immediately (a solved snapshot of its
# hops); profitable solves surface in the next `latest_results()` batch.
solved_path = registry.engine.inspect_path(path_id)
assert solved_path["path_id"] == path_id

Swap Encoding & On-Chain Execution

A profitable solve goes straight from the Rust engine to a submitted transaction — there is no Python encoding layer. After a path solves:

  1. Encode — the Rust core emits the per-hop calldata (V2 swap(), V3 swap(), V4 PoolManager swap(), Curve exchange() / exchange_underlying()) and composes it into the cmd-executor contract envelope.
  2. Submit — the Rust submission layer signs (EIP-1559) and sends the transaction; dry-run mode (the default) stops after solving and simulates in-process without submitting.

Running the Settlement-Arbitrage Bot

The end-to-end settlement-arbitrage bot — the flagship Rust-core-driven workload — is a thin Python driver over the degenbot.runner package: the example owns only CLI parsing + SIGINT handling, while BotRunner owns the session and the Rust engine handshake.

# Dry run (default): solves, simulates in-process, renders profit lines — nothing is submitted
uv run python examples/eth_settlement_arbitrage_v2_v3_v4_rust.py \\
  --node-http "https://eth-mainnet.example.com" \\
  --node-ws "wss://eth-mainnet.example.com/ws"

# Restrict to one 3-hop permutation (overrides the driver's default path filter)
uv run python examples/eth_settlement_arbitrage_v2_v3_v4_rust.py --permutation V2-V3-V4

# Live mode: signs and submits real transactions (operator key via env)
uv run python examples/eth_settlement_arbitrage_v2_v3_v4_rust.py --live

Endpoints and operator keys come from examples/mainnet.env + the OS env: DEGENBOT_RPC_HTTP_CHAINID_1 / DEGENBOT_RPC_WS_CHAINID_1 (CLI --node-http / --node-ws take precedence), OPERATOR_ADDRESS / OPERATOR_PRIVATE_KEY in live mode, and optional EXECUTOR_CONTRACT_ADDRESS overrides. BotRunner performs the driver-side startup handshake, after which the Rust core owns the hot loop — event decode, per-block re-solve, in-process simulation, encoding, submission — and the Python driver owns config, result rendering, and dispatch policy. With --operator-socket PATH, the bot also hosts an OperatorServer that the degenbot path add / degenbot path discover CLI commands target to steer the live path set without a restart (protocol + design in docs/architecture/operator-add-path-surface.md).

Bot API Reference

The Bot class is the primary entry point for degenbot usage. Access factories, registries, and utilities through Bot.

Initialization

import degenbot
from degenbot.config import DegenbotConfig

# With explicit config
bot = degenbot.Bot(
    config=DegenbotConfig(
        default_chain_id=1,
        rpc={
            1: RPC_URL,
        },
        database={"path": "~/.config/degenbot/degenbot.db"},
    )
)
# The RPC provider is built from the config and its eth_chainId is enforced
# to equal default_chain_id at construction — no manual registration needed.

Universal Pool Builder

# Universal builder — auto-resolves pool type from DB, registry, or on-chain probing
pool = bot.build_pool(
    "0x8ad599c3A0ff1De082011EFDDc58f1908EB6e6D8",
    state_block=18900000,  # Optional, defaults to current block
)
# For V4 pools, use build_managed_pool with the PoolManager address + pool_id
pool = bot.build_managed_pool(
    "0x...",  # PoolManager address
    pool_id="0x...",
)

Pool Construction by Type

# V2 pool (auto-detected from factory)
pool = bot.build_pool(
    "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc",
    state_block=18900000,  # Optional, defaults to current block
)

# V3 pool (auto-detected from factory)
pool = bot.build_pool(
    "0x8ad599c3A0ff1De082011EFDDc58f1908EB6e6D8",
)

# Curve pool (auto-detected from on-chain probing)
pool = bot.build_pool(
    "0xbEbc44782C7db0a1A60Cb6fe97d0b483032FF1C7",
)
# V4 pool (singleton architecture with pool_id)
pool = bot.build_managed_pool(
    "0x...",  # PoolManager address
    pool_id="0x...",
    state_view_address="0x...",
    tokens=["0x...", "0x..."],
    fee=500,
    tick_spacing=10,
)

Token Factory

# ERC-20 token (fetches name, symbol, decimals from DB/RPC)
token = bot.build_erc20token("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")

# Token lookup (from registry if cache hit)
token = bot.get_token("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")

Token Utilities (With Caching)

# Get balance at block (cached per-bot)
balance = bot.get_token_balance(token, "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
balance_at_block = bot.get_token_balance(token, "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", block_identifier=10000000)

# Get approval amount (cached)
approval = bot.get_token_approval(token, owner="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", spender="0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45")

# Get total supply (cached)
total_supply = bot.get_token_total_supply(token)

# Get native ETH balance
eth_balance = bot.get_ether_balance(address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")

Accessing Bot Components

# RPC provider (built from config; chain_id enforced at construction)
provider = bot.provider

# Registries (check if already created)
existing_pool = bot.pools.get(chain_id=1, pool_address="0x8ad599c3A0ff1De082011EFDDc58f1908EB6e6D8")
existing_token = bot.tokens.get(token_address="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", chain_id=1)

# Database session
with bot.db() as session:
    # SQLAlchemy operations
    pass

Chainlink Price Feeds

Chainlink price feeds provide reliable oracle data for various assets. The ChainlinkPriceContract class simplifies access to these feeds.

# Load the price feed for ETH/USD
# decimals can be provided to avoid a live RPC call
assert price_feed.address == '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419'
assert price_feed.decimals == 8

# price_feed.price requires a Bot instance with RPC access for live data

CLI Reference

Degenbot provides a command-line interface for managing blockchain data and pool state.

Installation

The CLI is installed automatically with the package:

pip install degenbot
degenbot --help

Commands

Database Management

# Back up the database
degenbot database backup

# Reset database (creates fresh schema; hidden command, --force skips the prompt)
degenbot database reset --force

# Upgrade database schema to the latest Alembic revision
degenbot database upgrade [--force]

# Compact database to reclaim space
degenbot database compact

# Inspect schema ownership (Alembic vs Rust) + preview the cutover
degenbot database cutover --dry-run

# One-way cutover from Alembic to Rust schema ownership (ADR-010)
degenbot database cutover [--force]

# Out-of-place heal: rebuild a stale Alembic DB at the Rust head schema (ADR-011)
degenbot database heal [--dry-run]

Pool State Management

# Update pool metadata and liquidity positions for all active exchanges
# (--verify-chunk/--verify-all add pre-commit on-chain-truth gates: a
# divergence rolls the chunk back and does NOT advance last_update_block)
degenbot pool update [--chunk SIZE] [--to-block BLOCK] [--verify-chunk/--no-verify-chunk] [--verify-all/--no-verify-all]

# Verify one V3/V4 pool's DB state against on-chain truth at a given block
degenbot pool verify --rpc-url URL --chain 1 --block 18900000 --pool 0x... --family v3|v4 [--pool-manager 0x...]

# Activate an exchange for tracking
degenbot exchange activate base_uniswap_v3

# Deactivate an exchange
degenbot exchange deactivate base_uniswap_v3

# Steer a running bot (started with --operator-socket) without restarting it
degenbot path add --socket /path/to/operator.sock --hop V2:0xPoolAddr [--hop V3:0xPoolAddr] [--direction zfo|ozf]
degenbot path discover --socket /path/to/operator.sock [--bound N]

Supported exchanges:

  • Base: base_aerodrome_v2, base_aerodrome_v3, base_pancakeswap_v2, base_pancakeswap_v3, base_sushiswap_v2, base_sushiswap_v3, base_swapbased_v2, base_uniswap_v2, base_uniswap_v3, base_uniswap_v4
  • Ethereum: ethereum_pancakeswap_v2, ethereum_pancakeswap_v3, ethereum_sushiswap_v2, ethereum_sushiswap_v3, ethereum_uniswap_v2, ethereum_uniswap_v3, ethereum_uniswap_v4

Aave State Management

# Update Aave V3 positions for all active markets
degenbot aave update [--chunk SIZE] [--to-block BLOCK] [--verify-chunk/--no-verify-chunk] [--dry-run]

# Activate an Aave market
degenbot aave activate ethereum_aave_v3

# Deactivate an Aave market
degenbot aave deactivate ethereum_aave_v3

# Show a user's position in a market
degenbot aave position show <ADDRESS> [--market MARKET] [--chain-id CHAIN_ID]

# Scan all users in a market for liquidation risk (market-wide; no single address)
degenbot aave position risk [--market MARKET] [--chain-id CHAIN_ID] [--threshold 1.1] [--limit N] [--show-positions]

# Show market state
degenbot aave market show [--chain-id CHAIN_ID] [--name NAME]

Block Identifiers

Commands accepting --to-block support the following formats:

Format Example Description
latest latest Latest block
latest:-N latest:-64 N blocks before latest (default)
safe:+N safe:128 N blocks after safe block
Number 18900000 Specific block number

Configuration

Environment Variables

Variable Values Description
DEGENBOT_DEBUG 1, true, yes Enable debug-level logging output
DEGENBOT_DEBUG_FUNCTION_CALLS 1, true, yes Enable function call trace logging
DEGENBOT_RPC_HTTP_CHAINID_<ID> any HTTP(S) URL HTTP RPC endpoint for chain <ID>; overrides config.toml [rpc]
DEGENBOT_RPC_WS_CHAINID_<ID> any WS(S) URL WebSocket endpoint for chain <ID>; overrides config.toml [ws]
DEGENBOT_DEBUG=1 python my_script.py

Configuration File

Degenbot uses a TOML configuration file located at ~/.config/degenbot/config.toml:

# The chain this Bot session targets (required). One Bot per chain — see ADR-006.
# Must match a chain ID key in [rpc]; the connected RPC's eth_chainId is
# enforced to match at construction (fail-fast)
default_chain_id = 1

[rpc]
# Chain ID to HTTP RPC endpoint mapping
1 = "https://eth-mainnet.example.com"
8453 = "https://base-mainnet.example.com"

[ws]
# Chain ID to WebSocket endpoint mapping — the settlement-arbitrage pump
# subscribes to newHeads over WS, so a bot run needs this
1 = "wss://eth-mainnet.example.com/ws"
8453 = "wss://base-mainnet.example.com/ws"

[database]
# SQLite database path (optional, defaults to platform-specific location)
path = "/path/to/degenbot.db"

default_chain_id (required) selects the single chain this Bot targets — a Bot refuses to construct without it, and the connected RPC's eth_chainId is enforced to match it at construction. (Per-chain RPC/WS endpoints can also be supplied via the DEGENBOT_RPC_{HTTP,WS}_CHAINID_<ID> env vars above — the cascade is CLI flags > OS env > config.toml.) A [deployments] table may additionally carry a user overlay on the shipped pool-type/deployment registry (src/degenbot/registry/deployments.json is the single source of truth).

The Rust Core (degenbot_rs Rust crate, degenbot._ffi Python module)

The Rust core is the engine of degenbot — it owns all performance-critical and stateful logic. Python reaches it through the degenbot._ffi extension module, a thin PyO3 binding layer (rust/crates/degenbot-python/) that translates Python calls into Rust calls with no business logic of its own. The underlying core crates are pyo3-free by default and are consumable directly from pure Rust through the umbrella degenbot crate — currently via a git/path dependency (the workspace sets publish = false); the in-repo proof is rust/crates/degenbot/examples/standalone_consumer.rs, gated by just test-standalone.

The extension is built automatically during installation using maturin (or uv sync, which invokes maturin under the hood).

Available Functions

Tick Math

Uniswap V3 tick-to-price conversions (Q96 fixed point):

from degenbot.uniswap.v3_libraries import get_sqrt_ratio_at_tick, get_tick_at_sqrt_ratio

# Convert tick to sqrt price (Q96)
sqrt_price = get_sqrt_ratio_at_tick(253320)
assert sqrt_price == 25082941840919119221697001330704483

# Convert the full sqrt price back into the tick — exact round-trip
assert get_tick_at_sqrt_ratio(sqrt_price) == 253320

ABI Decoding

High-performance ABI decoding for contract data:

from degenbot._ffi.abi import decode, decode_single, encode

# Encode then decode multiple values
types = ["address", "uint256", "uint256"]
data = encode(types, ["0x0000000000000000000000000000000000000001", 100, 200])
values = decode(types, data)  # Returns list of decoded values
assert values == ["0x0000000000000000000000000000000000000001", 100, 200]

# Decode a single value
address = decode_single("address", data[:32])
assert address == "0x0000000000000000000000000000000000000001"

Address Utilities

EIP-55 checksummed address conversion:

from degenbot import get_checksum_address

checksummed = get_checksum_address("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
assert checksummed == "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"

ABI Encoding & Selectors

Encode function calls and compute selectors:

from degenbot.contract import encode_function_call, get_function_selector, decode_return_data
# Get a 4-byte function selector
selector = get_function_selector("transfer(address,uint256)")
assert selector == "0xa9059cbb"

# Encode a function call (selector + encoded args)
calldata = encode_function_call(
    "transfer(address,uint256)",
    ["0x0000000000000000000000000000000000000001", "100"],
)
assert calldata[:4].hex() == "a9059cbb"

# Decode the same calldata body back out
values = decode_return_data(calldata[4:], ["address", "uint256"])
assert values == ["0x0000000000000000000000000000000000000001", "100"]

Provider Classes

bot.provider is the normal way to reach chain data. The extension also exposes the raw synchronous/async RPC provider classes directly for the cases where the Bot conveniences don't fit:

# Create provider with connection pooling
provider = AlloyProvider(RPC_URL)

# Contract interaction
contract = Contract(
    "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
    provider_url=RPC_URL,
)

# Query blockchain
block_number = provider.get_block_number()
chain_id = provider.get_chain_id()
logs = provider.get_logs(
    from_block=block_number - 10,
    to_block=block_number,
    addresses=["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"],
)
result = contract.call(
    "balanceOf(address)",
    ["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"],
    block_number,
)

provider.close()

Async Provider

The extension also includes async wrappers for use with asyncio:

from degenbot._ffi.contract import AsyncContract
from degenbot._ffi.provider import AsyncAlloyProvider

# Create an async provider
async_provider = await AsyncAlloyProvider.create(
    rpc_url="https://eth-mainnet.example.com",
    max_retries=10,
    max_blocks_per_request=5000,
)

# Async contract interaction (built via `create`; `from_provider` wraps an existing provider)
async_contract = await AsyncContract.create("0x...", provider_url="https://...")
result = await async_contract.call("balanceOf(address)", ["0x..."])

# Batch multiple contract calls
results = await async_contract.batch_call(
    [("balanceOf(address)", ["0x..."]), ("totalSupply()", [])],
)

Log Filtering

from degenbot._ffi.provider import LogFilter

# Build a log filter
log_filter = LogFilter(
    from_block=1000000,
    to_block=1000100,
    addresses=["0x0000000000000000000000000000000000000001"],
    topics=[["0x0000000000000000000000000000000000000000000000000000000000000001"]],
)

AlloyProvider also exposes pub-subsubscribe_blocks(), subscribe_logs(...), subscribe_pending_transactions(), and friends return an async-iterable AlloySubscription (the primitive the settlement-arbitrage pump consumes) — plus offline modes (AlloyProvider.offline_from_json_file(path) / offline_from_json_string(s)) that answer from recorded RPC fixtures for deterministic tests, and opt-in transport-level rate limiting (requests_per_second + burst constructor args).

Engine and Dispatch Surface

Advanced drivers can also reach the engine directly through the degenbot._ffi module — the settlement-arbitrage engine (ArbitrageEngine), the shared Bot state handle, plus the I/O, submission, and price seams — instead of going through the degenbot.* conveniences. Its type stubs (src/degenbot/_ffi/*.pyi) are the reference surface.

Why Rust for the Hot Path

The MEV workload — per-block re-solve of hundreds of cyclic paths, EVM-exact revm simulation, ABI decode, tick math, and swap encoding — is latency-bound at the CPython boundary, so the pump loop runs in Rust with the GIL released around each PyO3 crossing. Per-operation microbenchmarks are not tracked in-repo.

Build Requirements

The extension is pre-built in published packages. For source builds:

  • A recent stable Rust toolchain (CI tracks @stable)
  • maturin (installed automatically with uv sync)
# Build the extension (same as `just build-rust-extension`)
cargo build -p degenbot_rs --features extension-module --manifest-path rust/Cargo.toml

# Or use the justfile
just dev  # Build and install Python extension

Documentation

Additional documentation is available in the docs/ directory:

  • Architecture: High-level architectural patterns
  • Architecture Decision Records: the 34-ADR design log for the Python→Rust migration (three-layer architecture, per-chain Bot, schema retention/cutover, registration-verify lifecycle, executor grammar, …)
  • Execution Strategy: the user-owned ExecutionStrategy seam (ADR-025)
  • Aave V3: Comprehensive control flow diagrams and amount transformations for Aave operations
  • CLI: Detailed CLI command reference (aave.md, database.md, pool.md)
  • Logging: Controlling RUST_LOG / DEGENBOT_DEBUG tracing, the env-gated hard/loud diagnostics, and debug-named diagnostics

Contract Reference

Verified Solidity source code for all supported protocols is in contract_reference/:

Protocol Path Contents
Uniswap V2 contract_reference/uniswap/V2/ Factory, Pair, ERC20, SafeMath, Math, UQ112x112
Uniswap V3 contract_reference/uniswap/V3/ Factory, Pool, Oracle, Tick, TickBitmap, SqrtPriceMath, SwapMath, TickMath, FullMath, Position, etc.
Uniswap V4 contract_reference/uniswap/V4/ PoolManager, Pool, Hooks, TickBitmap, SqrtPriceMath, SwapMath, ProtocolFeeLibrary, LPFeeLibrary, ERC6909, etc.
Aave V3 contract_reference/aave/ Pool (10 revisions), AToken (5 revisions), VariableDebtToken, GhoVariableDebtToken (6 revisions), GhoDiscountRateStrategy, AaveOracle, stkAAVE, RewardsController

Useful when auditing, or when you need to understand the exact on-chain behavior of a supported protocol. See contract_reference/README.md for the full index.

Contributing

Contributions are welcome! Please submit issues and pull requests to the GitHub repository.

Development Setup

git clone https://github.com/BowTiedDevil/degenbot.git
cd degenbot
uv sync

# Run the full gate: standalone-Rust smoke + cargo workspace + full pytest
just test

# Individual tracks:
just test-rust    # cargo workspace + just test-standalone
just test-python  # uv run pytest

License

This code is published under a permissive MIT license. See LICENSE for details.

Donation

If you find this code valuable, please fund continuing development by donating to 0xADAf500b965545C8A766CD9Cdeb3BF3FBef073e5 on any EVM compatible chain.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

degenbot-0.6.0a7.tar.gz (2.8 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

degenbot-0.6.0a7-cp312-abi3-win_amd64.whl (13.6 MB view details)

Uploaded CPython 3.12+Windows x86-64

degenbot-0.6.0a7-cp312-abi3-manylinux_2_28_x86_64.whl (13.5 MB view details)

Uploaded CPython 3.12+manylinux: glibc 2.28+ x86-64

degenbot-0.6.0a7-cp312-abi3-manylinux_2_28_aarch64.whl (12.8 MB view details)

Uploaded CPython 3.12+manylinux: glibc 2.28+ ARM64

degenbot-0.6.0a7-cp312-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl (25.2 MB view details)

Uploaded CPython 3.12+macOS 10.12+ universal2 (ARM64, x86-64)macOS 10.12+ x86-64macOS 11.0+ ARM64

File details

Details for the file degenbot-0.6.0a7.tar.gz.

File metadata

  • Download URL: degenbot-0.6.0a7.tar.gz
  • Upload date:
  • Size: 2.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for degenbot-0.6.0a7.tar.gz
Algorithm Hash digest
SHA256 3a2154e62752940892f46ce7d19ac2b1e2d6885eb5ba8b76b75ef2139a11b528
MD5 1aad4bbab32b7a556af3162483ef7f3d
BLAKE2b-256 5fd17e6e19cc09eea5a3d470908d9ff505ed22917f3e101ff5799575512efc03

See more details on using hashes here.

Provenance

The following attestation bundles were made for degenbot-0.6.0a7.tar.gz:

Publisher: publish-to-pypi.yaml on BowTiedDevil/degenbot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file degenbot-0.6.0a7-cp312-abi3-win_amd64.whl.

File metadata

  • Download URL: degenbot-0.6.0a7-cp312-abi3-win_amd64.whl
  • Upload date:
  • Size: 13.6 MB
  • Tags: CPython 3.12+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for degenbot-0.6.0a7-cp312-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 7d4a48f5605d395bea304e77ee87b44f710b5b64de7a6b4bea7aa2faea1498d3
MD5 03b680f38b78c211802a03d0d43e7062
BLAKE2b-256 f9558c209ebc1fed0736f40e095401d9cbca79453306bd72ecac2a4bcec1688f

See more details on using hashes here.

Provenance

The following attestation bundles were made for degenbot-0.6.0a7-cp312-abi3-win_amd64.whl:

Publisher: publish-to-pypi.yaml on BowTiedDevil/degenbot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file degenbot-0.6.0a7-cp312-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for degenbot-0.6.0a7-cp312-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 3d6b29331269188edc42305c81db83737dff9bab0dfb5bd113ce9d66ab275ca0
MD5 2884f46c278c1d3d62c16e3efcc11c6a
BLAKE2b-256 b1187cbf5e970538d5d09741f3a7e8d003321595eb37a462d6fe463f6458101b

See more details on using hashes here.

Provenance

The following attestation bundles were made for degenbot-0.6.0a7-cp312-abi3-manylinux_2_28_x86_64.whl:

Publisher: publish-to-pypi.yaml on BowTiedDevil/degenbot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file degenbot-0.6.0a7-cp312-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for degenbot-0.6.0a7-cp312-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 b71562f9fac9a4f76d09a39d3e7719a02e6d3694f00110725495216cdf13a0d6
MD5 8662cc474ca48df817707860012b07e9
BLAKE2b-256 41260e63de673ba67a9d01a8e00624df63b8188c88ca568b1086cf9f0f635884

See more details on using hashes here.

Provenance

The following attestation bundles were made for degenbot-0.6.0a7-cp312-abi3-manylinux_2_28_aarch64.whl:

Publisher: publish-to-pypi.yaml on BowTiedDevil/degenbot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file degenbot-0.6.0a7-cp312-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for degenbot-0.6.0a7-cp312-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 390d633fd055be29b0e4d28bba064500ee4c162cea60c8ee3e4ce58cf85a7a04
MD5 8abe4ab674ca04038e51e5141d8f01d2
BLAKE2b-256 e7b96d1bcf0b92317ed7aa26a712c8da426a87d84d26294dc928615975c16793

See more details on using hashes here.

Provenance

The following attestation bundles were made for degenbot-0.6.0a7-cp312-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:

Publisher: publish-to-pypi.yaml on BowTiedDevil/degenbot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.6.0a7 This release

5 files

0.5.0

2 files

0.4.3

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

0.2.4

2 files

0.2.3

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page