Playbook

Listing Discovery by Scanning totalSupply (Listing Discovery By Scanning Total Supply)

getListings reads NFT totalSupply and fans out multiple RPC calls per tokenId. N×calls do not scale; 30s polls create stale listings and Infura rate-limit…

OpenSea-like Marketplace UI

Part 2 of 3

Bare Crypto marketplace SPA: OpenSea-like information architecture (Home, Marketplace, Viewer, Profile, mint form), listing discovery by scanning totalSupply, and the Rinkeby Infura vs mainnet mint-client split with shared utils — ADRs, Infura rate limits, and 30s-poll stale listing failures.

OpenSea-like marketplace UI architecture diagram

Without an indexer, every refresh is a chain scan

The Bare Crypto marketplace client finds open listings not via an event index or subgraph, but with totalSupply() plus a tokenId loop. Each step adds calls such as wasNftSold, isListingOpenByTokenId, and getLastListingByTokenId. As supply grows, cost grows linearly — worse with polling — and the Reserve container repeats the scan about every 30 seconds.

getListings(sold=false)
  maxToken ← NFT.totalSupply()
  for tokenId = maxToken … 1:
    wasSold?  open?  isListing?
    → maybe getListingInfo(tokenId)
         ↓
  N tokens × M RPC calls / poll
  poll every ~30s (Reserve / Profile)
         ↓
  Infura rate-limit → empty / stale grid

This part cuts into the discovery algorithm behind the OpenSea-like grid — and why it is an ADR topic.

Concepts, defined where they first appear

📦 totalSupply Scan
Discovering the listing set by walking tokenIds down from supply.

📦 N×RPC
Multiple eth_calls per token; multiplied again by poll frequency against a quota.

📦 Stale Listing
A sold or canceled listing still shown open — poll lag or partial failure.

📦 Infura Rate Limit
Per-project request budget; overages fail or slow calls.

A loop that works for a small Rinkeby collection is not a mainnet product feature.

Algorithmic truth

getListings reads totalSupply via getTokenSupply(), then walks maxToken down to 1. Each token may run sold/open/listing checks and optionally getListingInfo. Empty ranges still burn RPC.

Cost model (approx)
  cost ≈ totalSupply × calls_per_token × polls_per_minute
  UI cards may add metadata/IPFS reads on top

30-second polls and stale UI

Reserve runs getListings on mount and repeats with setInterval(..., 30000). Profile similarly rescans owned tokens ~every 30s; Viewer and cards use 10–15s intervals. A user can buy while the grid stays stale for a full poll — or go blank under rate limits.

Infura failure mode

The whole scan hits the read node (Rinkeby Infura HTTP/WSS). When quota trips, try/catch paths may return wasSold=false or empty strings; the grid goes quietly wrong. Baking keys into the repo worsens blast radius — docs never print project IDs; env + edge limits belong in the ADR.

ADR: Listing discovery
  Accepted (prototype): scan totalSupply client-side
  Rejected (then): subgraph / event indexer
  Consequence: rate limits, stale polls, O(supply) cost
  Follow-up: index Transfer/Listing events; paginate

Difference from OpenSea

OpenSea does not make every browser walk full supply; it has index + API. Bare Crypto copies the IA product reference, not the discovery infrastructure — a conscious (or at least retrospective) trade-off.

The mappings that get confused most often

❌ totalSupply scan = production discovery
✓ It is a prototype symptom of missing indexers

❌ 30s poll is a realtime market
✓ Poll is stale-listing and quota risk

❌ Swallowing RPC errors in try/catch is safe UI
✓ Silent wrong listings are worse

❌ Shipping Infura keys in the client bundle is fine
✓ Leak + shared quota; prefer env and proxy

A checklist for auditing your own system

  1. How many eth_calls per token in one getListings pass?
  2. If supply grows 10×, what happens to cost per poll?
  3. What empty/wrong UI state appears under rate limit?
  4. After a purchase, how long can the grid stay stale?
  5. Which events would retire the scan if indexed?

What to take away from this part

  1. Listing discovery is a totalSupply scan — not an OpenSea index.
  2. N×RPC × 30s poll makes Infura quota and stale UI inevitable.
  3. ADR: accept for prototype, follow with indexer — it will not silently scale.

Asking every tokenId is not a marketplace; it is a load test.

FAQ

Frequently asked questions

What is totalSupply Scan?

Discovering the listing set by walking tokenIds down from supply.

What is N×RPC?

Multiple eth_calls per token; multiplied again by poll frequency against a quota.

Is it true that "totalSupply scan = production discovery"?

It is a prototype symptom of missing indexers

What does this part lock in?

This part cuts into the discovery algorithm behind the OpenSea-like grid — and why it is an ADR topic. The Bare Crypto marketplace client finds open listings not via an event index or subgraph, but with `totalSupply()` plus a tokenId loop. Each step adds calls such as `wasNftSold`, `isListingOpenByTokenId`, and `getLastListingByTokenId`. As supply grows, cost grows linearly — worse with polling — and the Reserve container repeats the scan about every 30 seconds.

Engineering Principles Learned

  • Discovery cost must be visible against supply; hidden O(N) is not a product.
  • Poll interval is not an SLA; design it with stale and quota together.
  • Swallowing RPC errors turns wrong listings into features.

Continue reading

Continue reading

Next in series

Next in series

Related articles

Paylaş