Robinhood Chain · 4663 · read from pool state

How it reads.

LiqNet reads every pool it knows about straight from storage, replays each pool's own swap arithmetic until it stops absorbing, and publishes two numbers per ticker: how far apart the venues are, and how much each one will take. This page is the whole method, including the rules that make the numbers smaller.

01

In plain terms

When you buy a tokenized share on this chain, nobody sells it to you. You draw it out of a pool.

A pool is one contract holding two things, say NVIDIA shares and dollars. A buyer puts dollars in and takes shares out, and the contract's arithmetic sets the rate. Every share that leaves makes the next one dearer, so a large order pays more per share than a small one. That difference is the cost to fill.

Most pools here do not spread their liquidity evenly. It sits in bands around the current price, deposited by whoever chose to. An order walks through the bands, paying a little more in each, until it is filled or the bands run out. Past the last band the pool has nothing left to sell at any price. LiqNet calls that point the wall, and publishes where it is.

The same share trades in many pools at once, on several exchanges, and none of them talk to each other. At any instant they quote different prices. The cheapest quote is often the shallowest pool, which is exactly why a price on its own is not enough.

02

What LiqNet is

An instrument, not an exchange. It reads and publishes. It holds no funds, deploys no contracts and never asks a wallet to sign anything.

The name is the number it reads. Every concentrated pool stores a liquidityNet at each initialized tick: how much liquidity begins or ends there. Add them up as you walk and you have the depth; reach the last one and you know where it runs dry.

It doesHow
Read every pool from chain stateFour geometries, four read paths. No price feed and no indexer on the request path.
Walk depth tick by tickThe pool's own swap loop, replayed in the same 256-bit integer arithmetic, until it fills or stops.
Test whether a spread can be takenBuy on one venue and sell on another in simulation, through both pools' real arithmetic.
Not executeNo router, no custody, no signature. A reading is an eth_call: nobody sees it, nothing moves.
03

The read path

"Pool" is four different things on this chain. Each keeps its price in a different place and walks its liquidity a different way, and reading one as another puts a quote tens of percent from the market. So each kind gets its own path, and a pool whose kind cannot be established is left out.

KindPrice fromDepth fromSeen on
v3slot0()tickBitmap(int16) → ticks(int24)Uniswap v3, Ramses, Aero, Up, Giga, PancakeSwap v3, SushiSwap v3
v4extsloadthe same structures, from computed storage slotsUniswap v4, Orvex
algebraglobalState()tickTable(int16)Alandale
v2getReserves()closed form, exactUniswap v2 and its forks

Read by position, not by shape

Forks disagree about how many words slot0() returns: Uniswap v3 sends seven, Aero and Up send six. A strict ABI decoder rejects the short ones and the venue vanishes from the book. LiqNet reads the returned words by index, so an extra or missing trailing field changes nothing.

// engine/abi.mjs
export function word(data, i) {
  const hex = data.slice(2 + i * 64, 2 + (i + 1) * 64);
  if (hex.length < 64) throw new Error(`word ${i} missing`);
  return BigInt('0x' + hex);
}

Three round trips, one block

A reading is three batches through Multicall3 at 0xcA11…CA11, all pinned to the same block: the heads of every pool (price, tick, liquidity, fee), then the bitmap words around each price, then every initialized tick those words point to. Multicall3's own getBlockNumber() rides in the first batch, so the block a reading names is the block it was read at.

Requests go out as concurrent single calls, never as JSON-RPC batches: the bundling already happened inside the eth_call, and a second layer of batching only queues them behind each other.

04

Pools with no address

In a v4-style exchange every pool lives inside one shared contract, the singleton. There is no pool address to call. Its state is reached through extsload, with the slot computed from the pool's 32-byte id. A great deal of this chain's depth sits there, and a tool that only knows pool contracts cannot see it.

// where a v4 pool's state lives
base = keccak256(poolId ‖ POOLS_SLOT)

base + 0   slot0       sqrtPriceX96 (160) · tick (24) · protocolFee (24) · lpFee (24)
base + 3   liquidity
base + 4   ticks       keccak256(int24 tick ‖ base + 4)       → liquidityNet in the high 128 bits
base + 5   tickBitmap  keccak256(int16 word ‖ base + 5)

The two singletons on this chain keep the pools mapping at different slots: 6 for Uniswap v4 at 0x8366…0951, 4 for Orvex at 0xd01c…4032.

Tick spacing is not in storage

It belongs to the pool's key, not its state, so it cannot be read back. Guess it wrong and the bitmap decodes into ticks that do not exist, or into none, which looks exactly like a pool that has run dry. The registry probes instead: for each candidate spacing it reads the bitmap around the price and checks that every tick it points to actually holds liquidity. The spacing whose ticks all check out is the one recorded.

The protocol fee is part of the fee

Some v4 pools charge a protocol fee on top of the LP fee. The swap uses the combination, protocol + lp − protocol·lp / 10⁶, separately for each direction. LiqNet reads both halves out of slot0 and uses the same formula the pool does.

05

Walking the seam

Liquidity sits between ticks. An order walks from the current price toward the next initialized tick, takes whatever that band holds, crosses the tick (adding or removing the liquidity that starts or ends there), and carries on. LiqNet runs that loop against the pool's own tick data, in BigInt, with the contract's own rounding. It is not a curve fitted to past trades.

// engine/simulate.mjs, the loop in outline
while (remaining > 0n) {
  const next = ticks[idx];                                  // next initialized tick ahead
  const target = next ? sqrtAtTick(next.t) : limit;
  const step = swapStep(sqrtP, clamp(target, limit), L, remaining, fee);
  remaining -= step.amountIn + step.feeAmount;
  out += step.amountOut;
  sqrtP = step.next;
  if (sqrtP !== target) break;                              // filled inside this band
  L = zeroForOne ? L - next.net : L + next.net;            // crossed: the band changes
  idx += zeroForOne ? -1 : 1;
}

The math (TickMath, SqrtPriceMath, SwapMath) is ported line for line, and the tests check every TickMath constant against two thousand random ticks and the known values at both ends of the range.

The ladder

Every ticker is drilled at the same six sizes, in dollars, plus any size you type:

$100   $1,000   $10,000   $50,000   $250,000   $1,000,000

Pools quoted in ether are walked in ether, at the price of the deepest USDG/WETH pool, read in the same batch. Nothing else prices the ether leg.

06

The wall

A walk stops in one of three places: the order is filled, the liquidity runs out, or the marginal price has moved 10% from where it started. The third is a rule, not a fact about the pool, and it is the same rule for every geometry.

Without it, a pool with one wide position would report that it "absorbs" any size at all, just at an absurd price, and constant-product pools never run out by construction. Ten percent is far past anything anyone fills on purpose. When you see runs dry, the pool genuinely had nothing left; when you see wall, it still had liquidity, but only at prices nobody should accept.

Depth to the wall is the dollars an order can put in before either happens. It is what the "takes" column means, and what the locked-versus-liquid table compares against reserves.

07

What counts in a spread

The spread is the gap, in basis points, between the cheapest and dearest venue at one block. Which venues belong in it is a decision. Here are the three rules. Each one makes the headline smaller.

An emptied pool is not a quote

A drained pool keeps the price it was abandoned at forever, and that number will happily sit in an aggregate looking like an opportunity. Only venues that can fill the smallest rung ($100) whole count toward the spread. The rest stay in the table, marked.

A ticker is not an identity

Several tokens on this chain answer to the same symbol. LiqNet resolves each ticker to the address whose money-paired pools hold the most, and lists the others it set aside, with what they hold.

Unknown geometry is dropped

A pool that answers none of the four read paths, or whose price disagrees with the indexer's by more than threefold both ways round, is excluded and recorded as unclassified. That is why the venue list is shorter than the list of exchange names on the chain.

The consensus spot, the number costs are measured against, is the median mid-price of the fillable venues.

08

The crossing

A spread is only interesting if it can be taken. At every rung, LiqNet buys on each fillable venue and tries selling exactly what came out on every other one, using the state it has already read. Both fees and both price impacts are inside the result.

Not inside it: gas, the chance that someone else takes it first, and the fact that both pools move the moment anyone acts. It measures a disagreement. It promises nothing, and most of the time it comes back empty.

09

The registry

Something has to say which pools exist. That is the only job an indexer does here. npm run canon walks GeckoTerminal and DexScreener once, offline, then asks the chain what each pool actually is: which read path answers, which way round its tokens are, what spacing its ticks use. The result is written to data/registry.json. From then on a page load is chain reads and nothing else, so another service's rate limit or outage cannot change a published number.

Reading the registry…

10

The API

The same endpoints the pages call. CORS is on, there is no key, and every response names the block it was read at.

GET /api/assay?ticker=NVDA
GET /api/assay?ticker=SPY&size=25000
GET /api/assay?ticker=0xd0601ce157db5bdc3162bbac2a2c8af5320d9eec&sizes=100,5000,1m

  blockNumber     the block every number was read at
  readMs          how long the read took
  spot            median mid of the fillable venues
  dispersionBps   cheapest to dearest, fillable venues only
  venues[]        per pool: kind, fee, spot, capacity, stop, ticks, the ladder, the curve
  best[]          the venue that gives the most shares at each rung
  crossing        the best buy-here, sell-there that survives, or null
  impostors[]     same-symbol tokens that were set aside
GET /api/board                  the watchlist at one block: spread, depth, best per rung
GET /api/compare?tickers=NVDA,AAPL,TSLA&size=25000   ranked by all-in cost
GET /api/venues                 every venue: wins, fills and median cost at each size
GET /api/tickers                what the registry knows
GET /api/registry?ticker=NVDA   the pools behind one ticker

Readings are cached for fifteen seconds so bursts are served from memory rather than the chain. A reading is only true for the block it names.

11

Reproduce it

Every figure has a command behind it. They are the same functions the server calls.

npm run assay NVDA          # one ticker, every venue, the whole ladder
npm run assay SPY 25k 1m    # at the sizes you name
npm run canon               # rebuild the registry: the only step that uses an indexer
npm test                    # the math, against the contract's own constants

Point LIQNET_RPC at any node for chain 4663 and the numbers come out the same, because they come out of the chain.

12

What it cannot see

Being plain about the edges is part of the method.

  • Hooks. A v4 pool can carry a hook that takes a toll inside the swap. Storage does not show it, so a hooked pool may pay less than its reading. LiqNet reads the pool's own arithmetic, hook excluded.
  • Dynamic fees. Several venues change their fee with volatility. LiqNet uses the fee the pool reports at the block it reads, which is the fee a swap at that block would pay, and no more than that.
  • Venues with no read path. Bin-based pools, stable-swap curves and off-chain market makers are listed as unclassified rather than priced.
  • Routing. The best venue at a size is a single pool taking the whole order. Splitting across pools can do better; that is a router's job, and LiqNet is not a router.
13

Glossary

TermWhat it means here
AssayOne complete reading of a ticker: every venue priced from its own state, the ladder walked, at one block.
SeamOne pool's liquidity, as an order would meet it.
BandThe stretch of price between two initialized ticks, with its own liquidity.
WallWhere a walk stops without filling: the marginal price moved 10%, or nothing was left.
Runs dryThe pool had no liquidity left at any price within reach.
Takes · depthThe dollars an order can put in before the wall.
FillableCan fill the smallest rung, $100, whole. Only fillable venues count toward a spread.
Cost over spotWhat you pay per share, fee and impact included, against the consensus spot, in basis points.
SpreadThe gap between the cheapest and dearest fillable venue at one block.
CrossingBuying on one venue and selling on another at the same block, published only if it survives both fees and both impacts.
Basis pointA hundredth of a percent. A thousand dollars at 30 bps costs three dollars more than spot.