Skip to content

add per-token net buy/sell flow endpoint - #237

Merged
moodysalem merged 2 commits into
mainfrom
EKU-2155-api-per-token-net-buy-sell-flow-across-all-ekubo-pools-for-swap-price-chart
Oct 26, 2026
Merged

moodysalem merged 2 commits into
mainfrom
EKU-2155-api-per-token-net-buy-sell-flow-across-all-ekubo-pools-for-swap-price-chart

Conversation

@moodysalem

Copy link
Copy Markdown
Member

Problem

The API only exposes gross, unsigned volume (/price/.../ohlc, /overview/volume). A price chart can't show whether traders were net buying or net selling a token, and a pair-level figure ignores the token's other pools.

Solution

New GET /tokens/{chainId}/{tokenAddress}/net-flow?interval=<s>&duration=<s>:

  • Same path parameters, interval (60–86400, default 900) and duration (3600–2592000, default 86400) as /tokens/{chainId}/{tokenAddress}/price-history; 400 when duration / interval > 1000.
  • Envelope { timestamp, start, end, interval, data }. The envelope times are unix seconds. Buckets use date_bin with the price history's origin, so they line up with its points.
  • Each row is { start, bought, sold, swap_count }. bought is the raw amount that left Ekubo pools to traders (the sum of -delta where the token's delta is negative), and sold is the raw amount traders paid in (the sum of positive deltas). Swap deltas are the pool's balance change: the indexer adds them to TVL and charges fees on the positive side.
  • Covers every pool on the chain holding the token, across all cores and extensions (TWAMM and limit order execution included). Empty buckets are omitted. An unknown token returns 200 with empty data.
  • A multi-hop route through the token adds to both bought and sold, which leaves net unchanged. This is documented in the route description.

Query

The query collects the token's pool ids into two arrays, one for pools where the token is token0 and one for token1. Each side is then a single index-only scan of swaps_pool_key_id_block_time_ohlc_idx (pool_key_id = ANY(...) plus the block_time range), which already carries delta0/delta1. No new index or migration.

A first version used a lateral join per pool. The planner flattened it, misestimated, and nested-looped every swap on the chain against the pool list: 12.4 s for Starknet ETH over 1 day, which has about 1,070 pools. With the array form the same token over 30 days (507k swaps) runs in about 1.0 s.

Verification

  • bun run format:check, bun run lint, bun run typecheck, bun test all pass.
  • New unit test for the window and bucket-limit helper.
  • New Postgres test for the SQL (bucketing, sign split, token0/token1 side, other chains, no pools), run against a local PostgreSQL 18 via TEST_PG_CONNECTION_STRING.
  • New contract cases for the chart's three ranges and an unknown token; openapi.json regenerated.

Against the production database with wrangler dev --remote --env prod (uncached requests):

Request Rows Swaps Time
/tokens/1/0x4c46…d0f/net-flow?interval=1800&duration=86400 (EKUBO, 23 pools) 20 98 0.35 s
/tokens/1/0x4c46…d0f/net-flow?interval=43200&duration=2592000 61 9,946 0.18 s
/tokens/1/0x0/net-flow?interval=43200&duration=2592000 (ETH) 61 95,434 0.18–0.47 s
/tokens/1/0xa0b8…eb48/net-flow?interval=43200&duration=2592000 (USDC) 61 159,907 0.25–0.32 s
/tokens/23448594291968334/0x49d3…dc7/net-flow?interval=43200&duration=2592000 (Starknet ETH) 61 507,071 0.97 s
/tokens/23448594291968334/0x4718…38d/net-flow?interval=43200&duration=2592000 (STRK) 61 1,169,314 1.4–1.6 s
/tokens/1/0xdead/net-flow 0 0 0.10 s
/tokens/1/0x0/net-flow?interval=60&duration=86400 400

Spot check: summing the swaps from /tokens/1/{EKUBO}/{other}/events across every EKUBO pair matches net-flow exactly in all 18 complete 30-minute buckets from 2026-10-25 14:00 to 2026-10-26 13:00. That window covered 92 swaps; bought, sold, swap count and net all match.

@moodysalem
moodysalem merged commit a2b396c into main Oct 26, 2026
1 check passed
@moodysalem
moodysalem deleted the EKU-2155-api-per-token-net-buy-sell-flow-across-all-ekubo-pools-for-swap-price-chart branch October 26, 2026 13:51
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.

1 participant