Overview¶
The Pool CLI provides commands for managing liquidity pool metadata and tracking liquidity positions across multiple DEX versions (Uniswap 2, 3, and 4) on Base and Ethereum mainnet. The main command pool update fetches blockchain events for pool discovery and liquidity updates, synchronizing database with current pool states.
Background: Pool Architecture¶
Uniswap V2 Architecture¶
V2 pools use a constant product AMM (x*y=k) formula. All liquidity is distributed evenly across entire price range. Pools are created by factories with a PairCreated event containing token addresses and pool address.
Uniswap V3 Architecture¶
V3 introduces concentrated liquidity - liquidity providers can allocate capital to custom price ranges using tick boundaries. This requires tracking tick-level liquidity with:
Tick bitmap: 256-bit words mapping initialized tick positions
Liquidity mapping: Net and gross liquidity at each tick for price traversal
Tick spacing: Constraints on allowed tick positions per fee tier
Pools emit Mint and Burn events for liquidity changes.
Uniswap V4 Architecture¶
V4 uses a centralized pool manager (instead of per-pool contracts) with hooks for custom logic:
PoolManager: Single contract managing all pools
PoolKey: Keccak256 hash identifying pools (currency0, currency1, fee, tickSpacing, hooks)
Hooks: Customizable behavior at pool boundaries
ModifyLiquidity: Single event type for liquidity operations
V4 uses separate ManagedPool database tables with PoolManager contract references.
Commands¶
The command vocabulary is Rust-owned: degenbot-cli declares it over degenbot-cli-core’s pool and exchange arms. The authoritative flag/exit-code reference is the Rust CLI page; the domain behaviour below is unchanged.
degenbot pool update¶
Update pool metadata and liquidity positions for all activated exchanges.
degenbot pool update [--chunk SIZE] [--to-block BLOCK] [--verify-chunk|--no-verify-chunk] [--verify-all|--no-verify-all] [--verify-all-interval BLOCKS]
Parameters¶
Parameter |
Default |
Description |
|---|---|---|
|
10,000 |
Maximum number of blocks to process per database commit |
|
|
Last block in update range. Format: |
Block Identifiers¶
Valid block tags: earliest, finalized, safe, latest, pending
Examples:
latest- Latest blocklatest:-64- 64 blocks before chain tip (default, ensures finality)safe:128- 128 blocks after last safe block12345678- Specific block number
Behavior¶
Identify active chains: Queries database for chains with active exchanges
Determine update range: Starts from
min(last_update_block + 1)across exchangesProcess in chunks: Iteratively processes blocks up to
chunk_sizeTrack progress: Displays progress bar showing blocks processed
Skip up-to-date chains: If no new blocks exist since last update, skips
Example Usage¶
degenbot pool update --to-block "latest:-128"
degenbot pool update --chunk 5000
Failure output¶
A failed run names the endpoint, the chain, and the requested block range.
When the failure is the RPC-connection class (a dropped or unreachable
transport — the backend connection task has stopped family), the report says
so directly, states that chunks already committed are kept, points at
rerunning from the recorded per-exchange cursors, and then lists the
per-exchange resume state (current at the requested target / behind with the
last committed block / never updated) read from exchanges.last_update_block
in the operator database. The updater commits per chunk, so this is the
authoritative outstanding-work record; there is no automatic transport retry
(retrying a dropped transport has partial-chunk correctness implications and
needs its own decision).
degenbot exchange activate¶
Activate an exchange for pool tracking. Creates the database entry if it does not exist; idempotent when already active.
degenbot exchange activate --chain <CHAIN> --name <NAME>
CHAIN is a chain slug (base, ethereum) or a numeric chain id; NAME is the
DEX slug (aerodrome_v2, uniswap_v3, uniswap_v4, …). ADR-051 D5 collapses
the retired per-(chain, DEX) click verbs onto this one data-driven pair.
V4 exchanges additionally create a pool_managers row.
degenbot exchange deactivate¶
Deactivate an exchange (its pools are not updated).
degenbot exchange deactivate --chain <CHAIN> --name <NAME>
Supported Exchanges¶
Base Mainnet¶
Aerodrome V2, V3
Pancakeswap V2, V3
Sushiswap V2, V3
Swapbased V2
Uniswap V2, V3, V4
Ethereum Mainnet¶
Pancakeswap V2, V3
Sushiswap V2, V3
Uniswap V2, V3, V4
Fee Structure¶
V2 Fees¶
Fixed fee per pool:
Uniswap V2: 0.3% (3/1000)
Sushiswap V2: 0.3% (3/1000)
Swapbased V2: 0.3% (3/1000)
Pancakeswap V2: 0.25% (25/10000)
Aerodrome V2: Variable (queried from factory via
getFee()), includes stable pair flag
V3 Fees¶
Tiered fee structure (basis points, denominator = 1,000,000):
500 bps (0.05%) - Stable pairs
3000 bps (0.3%) - Standard pairs
10000 bps (1.0%) - Exotic pairs
V4 Fees¶
Dynamic fee support:
Range: 0 to 1,000,000 bps (0% to 100%)
Specified per pool in PoolKey
Pool Discovery Events¶
V2: PairCreated Event¶
Event Hash: 0x0d3648bd0f6ba80134a33ba9275ac585d9d315f0ad8355cddefde31afa28d0e9
Used by: Uniswap V2, Pancakeswap V2, Sushiswap V2, Swapbased V2
V3: PoolCreated Event¶
Event Hash: 0x783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118
Used by: Uniswap V3, Pancakeswap V3, Sushiswap V3, Aerodrome V3. Aerodrome V3: Fetches fee from factory after discovery.
V4: PoolCreated Event¶
Event Hash: 0xdd466e674ea557f56295e2d0218a125ea4b4f0f6f3307b95f85e6110838d6438
Liquidity Update Processing¶
V3 Mint Event¶
Event Hash: 0x7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde
Processing: Adds liquidity_gross and liquidity_net across tick range. Updates tick bitmap at word boundaries.
V3 Burn Event¶
Event Hash: 0x0c396cd989a39f4459b5fa1aed6a9a8dcdbc45908acfd67e028cd568da98982c
Processing: Subtracts liquidity_gross and liquidity_net across tick range. Deletes position if balance reaches zero.
V4 ModifyLiquidity Event¶
Event Hash: 0xf208f4912782fd25c7f114ca3723a2d5dd6f3bcc3ac8db5af63baa85f711d5ec
Processing: Single event with signed liquidityDelta (positive for add, negative for remove). Updates managed pool tables.
Mock Pool Helpers¶
Mock pool classes (MockV3LiquidityPool, MockV4LiquidityPool) are lightweight pool implementations used to simulate liquidity updates and validate tick calculations without expensive operations (state locking, notifications, caching).
Simplifications:
No-op
_state_lock, empty_notify_subscribers(),_invalidate_range_cache_for_ticks()_initial_state_blockset toMAX_UINT256to skip in-range modificationsFull (non-sparse) liquidity mapping
Usage: Load state from database → create mock → process events chronologically → export validated mappings back to database.
Data Model Updates¶
The Rust degenbot-db schema owns the tables below. Python callers receive typed
row mirrors from degenbot.db; the updater writes through Rust-owned seams:
Table |
Fields Updated |
Notes |
|---|---|---|
|
|
After each chunk completes |
|
New rows created |
For token0/token1/currency0/currency1 |
|
New rows created |
V2/V3 pool metadata from factory events |
|
New rows created |
V4 pool metadata from manager events |
|
New rows created |
V4 manager metadata (on activate) |
|
|
Upsert from V3 liquidity events |
|
|
Upsert from V3 tick bitmap updates |
|
|
Upsert from V4 liquidity events |
|
|
Upsert from V4 tick bitmap updates |
Algorithm Details¶
Chunk Processing¶
Blocks are processed in chunks to limit memory usage and enable incremental commits:
Calculate
working_end_blockas minimum of:last_block(target block)working_start_block + chunk_size - 1All
exchange.last_update_blockvalues ahead of current chunk
Update exchanges where
last_update_block is Noneorlast_update_block + 1 == working_start_blockCommit changes and advance
working_start_block = working_end_block + 1Repeat until
working_end_block == last_block
Event Ordering¶
Events are processed in block number, then log index order to ensure chronological processing within blocks:
sorted(all_events, key=operator.itemgetter("blockNumber", "logIndex"))
Invariants enforced by assertions:
New event block ≥ last update block
Same block events must have increasing log index
SQLite Variable Limits¶
Upsert operations are chunked to stay below SQLite’s 32,766 variable limit per batch statement:
Liquidity positions: 30,000 rows per chunk (4 keys/row)
Initialization maps: 30,000 rows per chunk (3 keys/row)
Zero-bitmap words (all ticks uninitialized) are excluded from upserts to reduce database size.
Configuration¶
The command uses Web3 connections from degenbot config file. Each active chain must have an RPC endpoint configured.
Required Config¶
[rpc]
1 = "https://mainnet.example.com" # Ethereum mainnet
8453 = "https://base.example.com" # Base mainnet
Dependencies¶
Database: Rust
degenbot-dbowner, consumed through the stabledegenbot.dbPython mirrorBlockchain: Rust
degenbot-rpc/degenbot-pool-updaterfor RPC callsMath: Rust
degenbot-math+degenbot-pools(tick bitmap, tick math, liquidity math)