Skip to content

Pool Indexer crate for uniswap-v3 liquidity from subgraphs#4422

Draft
AryanGodara wants to merge 3 commits into
mainfrom
aryan/pool-indexer-subgraph-mode
Draft

Pool Indexer crate for uniswap-v3 liquidity from subgraphs#4422
AryanGodara wants to merge 3 commits into
mainfrom
aryan/pool-indexer-subgraph-mode

Conversation

@AryanGodara
Copy link
Copy Markdown
Member

Description

Replaces our driver's third-party Uniswap V3 subgraph dependency with our
own pool-indexer service. This is the subgraph-bootstrap slice of
the larger #4349, to keep the scope focused. Along with some fixes surfaced during local testing.

Out of scope (deferred to a follow-up PRs):

  • Cold-seed-from-genesis (chains without a subgraph)
  • Multi-factory-per-network

Changes

New service: crates/pool-indexer/

  • Bootstraps pool / tick state from an existing Uniswap V3 subgraph
  • Follows head via eth_getLogs and persists incremental state to Postgres
  • Serves four HTTP routes consumed by the driver: /pools,
    /pools/by-ids, /pools/{addr}/ticks, /pools/ticks

Driver-side abstraction (same as original PR, already reviewed)

  • New V3PoolDataSource trait in liquidity-sources/src/uniswap_v3/mod.rs
  • Two impls: existing UniV3SubgraphClient (no behavior change) + new
    PoolIndexerClient
  • build_pool_data_source selects the impl based on the optional
    pool-indexer-url driver config; defaults to subgraph

Database migration: V110__pool_indexer_uniswap_v3.sql

  • 4 tables: pool_indexer_checkpoints, uniswap_v3_pools,
    uniswap_v3_pool_states, uniswap_v3_ticks
  • Partial indexes on IS NULL columns for the backfill hot path

E2E test

  • Single local_node_pool_indexer_driver_integration test exercising the
    driver → pool-indexer wiring. Uses inline alloy::sol! macros with
    embedded compiled bytecode for the mock V3 contracts.

Local Testing Findings

Summary of what was verified locally against Ink mainnet (chain 57073):

  1. Pool-indexer bootstraps from the Uniswap V3 subgraph: 1718 pools +
    8266 ticks loaded in ~30s via bearer auth.
  2. Live indexer catches up to finalized head (indexed_block gauge
    tracking, caught up to finalized block log).
  3. Driver picks pool-indexer over subgraph:
    INFO uniswap v3: using pool-indexer as data source url=....
  4. Driver exercises all four pool-indexer routes; counters confirmed
    via pool_indexer_api_requests.
  5. End-to-end quote via baseline + V3-only liquidity from pool-indexer:
    USDT0 ↔ WETH both directions returned HTTP 200 with internally
    consistent prices (WETH ≈ $2078–$2141).
    (Also consistent with current price, when I tested)

@AryanGodara AryanGodara self-assigned this May 18, 2026
@github-actions
Copy link
Copy Markdown

Reminder: Please update the DB Readme and comment whether migrations are reversible (include rollback scripts if applicable).

  • If creating new tables, update the tables list.
  • When adding a new index, consider using CREATE INDEX CONCURRENTLY for tables involved in the critical execution path.
  • For breaking changes, remember that during rollout k8s starts the new autopilot, runs the Flyway migration, and only then shuts down the old pod. That overlap means the previous version can still be processing requests on the migrated schema, so make it compatible first and ship the breaking DB change in the following release.

Caused by:

@AryanGodara AryanGodara force-pushed the aryan/pool-indexer-subgraph-mode branch from 5937458 to f10f0fc Compare May 18, 2026 09:19
Copy link
Copy Markdown
Contributor

@jmg-duarte jmg-duarte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quick first pass

Comment thread crates/chain/src/lib.rs

/// Kebab-case slug used in URLs and per-network configs (pool-indexer API
/// routes, DB database names, etc). Stable — other services parse it.
pub fn slug(&self) -> &'static str {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn slug(&self) -> &'static str {
pub fn as_str(&self) -> &'static str {

Comment thread crates/chain/src/lib.rs
pub fn slug(&self) -> &'static str {
match &self {
Self::Mainnet => "mainnet",
Self::Goerli => "goerli",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: goerli is no longer up, maybe we should remove this

Comment thread crates/configs/src/lib.rs
pub mod autopilot;
pub mod banned_users;
pub mod database;
pub(crate) mod deserialize_env;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me, this starts to hint that this should probably be a crate on its own, but at the same time IMO the configs crate should be removed and the configs moved to their "users"

Comment on lines +584 to +586
// TODO: model these two URLs as an enum (Graph / PoolIndexer / Both)
// once serde supports `deny_unknown_fields` with internally-tagged or
// flattened enum variants — https://github.com/serde-rs/serde/issues/1547.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still wonder if there's a better way even if we need to modify the config again

Comment thread database/README.md
Comment on lines +723 to +727
### Reversibility

We do not ship paired `down`/rollback scripts with our Flyway migrations. The release model (autopilot rolls forward, old pods drain after the new schema is in place) means rollback is handled by deploying a previous binary against the migrated schema, not by reversing the SQL. Migrations must therefore be **forward-compatible with the previous binary** for the duration of the rolling deployment.

`V110__pool_indexer_uniswap_v3.sql` is purely additive: it introduces four new tables (`pool_indexer_checkpoints`, `uniswap_v3_pools`, `uniswap_v3_pool_states`, `uniswap_v3_ticks`) and four partial indexes. Nothing in the rest of the schema is touched, so the previous binary continues to function unchanged during the overlap window. If we ever need to undo it, the rollback is a `DROP TABLE ... CASCADE` on those four tables — but with `pool-indexer-url` unset on all chains (the default), nothing reads from them anyway, so a forward-only rollout is the expected path.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a justification Claude wrote for you, I don't think this is necessary, especially in the readme

The drop instructions could be in the migration itself

/// Retry only when `should_retry(&err)` returns true. Permanent errors
/// (e.g. contract reverts, bad input) bail out immediately so callers don't
/// waste sleep budget on something that won't get better.
pub async fn retry_with_sleep_if<F, T, E, P>(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the autopilot has one very similar if not the same function

You could create another PR moving it out and pointing to this one

Or just remove it from the autopilot in this one

Comment on lines +1 to +4
#!/usr/bin/env bash
# Wipes the local test DB, re-applies migrations, and runs pool-indexer
# against config.local.toml (mainnet + Ink by default).
set -euo pipefail
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the purpose / usage of this script in the docs

path = "src/main.rs"

[dependencies]
alloy = { workspace = true, features = ["contract", "providers", "rpc-types", "sol-types"] }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you replace the broad usage of alloy with the separate crates? it enables this crate to be compiled earlier than alloy is available

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants