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.
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
- How many eth_calls per token in one
getListingspass? - If supply grows 10×, what happens to cost per poll?
- What empty/wrong UI state appears under rate limit?
- After a purchase, how long can the grid stay stale?
- Which events would retire the scan if indexed?
What to take away from this part
- Listing discovery is a totalSupply scan — not an OpenSea index.
- N×RPC × 30s poll makes Infura quota and stale UI inevitable.
- 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
Rinkeby Testnet vs Mainnet Client Split
The marketplace SPA locks to Rinkeby Infura while collection mint clients harden on mainnet.…
Next in series
OpenSea-like Information Architecture
The Bare Crypto marketplace SPA deliberately mirrors OpenSea's mental model: Home, Marketplace, Viewer, Profile, and a mint form.
Related articles
BareNFTReserve: Escrow, RandomBuy, and Weak RNG
Owner-only createNewListing, buy, and randomBuy using keccak256(revealNonce, block.difficulty, msg.sender) % 3 — weak RNG, owner-operated marketplace ADR…