Skip to main content
In this guide, you’ll read on-chain state for Bags tokens on Robinhood Chain: a single-call state snapshot via BagsLens, token discovery through the factory registry and events, pool ID derivation, and post-migration price reads. All of these are read-only — no wallet or gas required.

Prerequisites

Before starting, make sure you have:
The publicClient from Setup batches parallel readContract calls into single multicall3 requests. Prefer many small reads with Promise.all over hand-rolled multicall — batching handles it and keeps you under the public-RPC rate limit.

1. Read a Token’s State with BagsLens

BagsLens.getTokenState returns everything you need about a token in one eth_call. It’s the recommended entry point for any token view.
read-state.ts
The returned TokenState struct:
priceQuotePerToken freezes at migration — it is only valid while migrated is false. For migrated tokens, read the live price from the pool (see section 5).

Batch multiple tokens

For lists, use getTokenStates to fetch many tokens in one call:

2. Discover Tokens via the Factory Registry

BagsFactory maintains an append-only registry of every launch. Because it’s append-only, the tail of the list is the newest launches.
list-tokens.ts
Other registry lookups:

3. Read Immutable Token Info

Name, symbol, and metadata URI never change, so read them once and cache. Non-Bags ERC-20s revert on metadataURI, so treat that as absent.
token-info.ts

4. Discover via TokenCreated Events

You can also index launches directly from TokenCreated logs (useful for backfilling history or building a stream). Start no earlier than the protocol deploy block.
index-launches.ts
For a live feed, checkpoint the last scanned block and only query [lastScanned + 1, latest] on each poll rather than re-scanning from the deploy block every time.

Indexer notes

  • Trade events are self-contained. TokensBought and TokensSold embed the post-trade price, virtual reserves, and full fee breakdown (vault / creator / partner), so you can index curve trades without extra state reads per event.
  • Phase flip. Migrated on the curve is the switch point: stop consuming curve events and start consuming PoolManager Swap logs (filtered by the token’s poolId) and the hook’s HookFeeTaken.
  • Pool-phase volume. The hook takes 2% of the WETH leg on every pool swap, so gross WETH volume per swap = HookFeeTaken.amount x 50.
  • Fee claimers are not in events at launch. Call feeShare.getClaimers() after TokenCreated, and watch ClaimersUpdated — the list can change.
  • Upgrade monitoring. BagsFactory and BagsVault are UUPS proxies, and per-token curves/fee-shares point at two shared beacons. Watch Upgraded on the factory/vault proxies and on both beacons (BagsBondingCurveBeacon, BagsFeeShareBeacon — addresses in the Contracts Reference) to detect implementation changes.

5. Post-Migration Price from the Pool

After migration, read the live spot price from the pool’s slot0 via StateView. Never fall back to the frozen lens price for migrated tokens. Add the StateView fragment to abi/periphery.ts:
Then read slot0 and convert sqrtPriceX96 to ETH per whole token:
pool-price.ts
sqrtPriceX96 is a Q64.96 fixed-point number. The Number()-based conversion above is fine for display; for high-precision accounting, compute the square in BigInt before converting.

6. Pool ID Derivation

Prefer the poolId from getTokenState or the TokenCreated event. When you must derive it yourself, it’s keccak256(abi.encode(poolKey)) over the sorted key:
pool-id.ts
The derivation must match the factory byte-for-byte or every derived poolId is wrong. Cross-check your derived value against the on-chain poolId from getTokenState for a known token, and prefer the on-chain value whenever it’s available.

Next steps