Token Scanner renders two side‑by‑side tables (Trending Tokens and New Tokens) with infinite scrolling, real‑time updates via WebSocket, and performant virtualization.
- Live demo: overfuse.github.io/token-pages
- Two tables: Trending and New, full‑height layout
- Infinite scroll (server pagination), virtualization for >1000 rows
- Real‑time updates (tick and pair‑stats) with price flash feedback
- Local debounced updates and reordering to maintain sort intent
- Per‑table filters (chain, volume, age, exclude honeypots) and realtime toggle
- Essential columns: Token, Exchange, Price, Market Cap, Volume, Price Changes (5m/1h/6h/24h), Age, Buys/Sells, Liquidity, Audit badges
- Visual style inspired by DefiLlama protocol rankings
- React + Vite + TypeScript
- Tailwind CSS
- @tanstack/react-query for pagination/fetching
- @tanstack/react-table for table logic
- react-virtuoso for virtualization
- Zustand (per‑table store) for rows/filters/realtime + WS handling
- Install + start dev server
yarn install
yarn dev
- Build and preview the build
yarn build
yarn preview
- Unit tests cover the core state and runtime logic:
- Store: merging scanner pages, price/mcap tick updates, pair‑stats audit updates
- Hook: per‑row subscribe/unsubscribe on mount/unmount
- Run tests:
yarn test
# or
yarn test:watch
You will have to use a no-cors extension from the chrome web store during development
https://chromewebstore.google.com/detail/allow-cors-access-control/
- or any other extension with similar functionality.
Enable CORS for REST API https://api-rs.dexcelerate.com
and WebSocket wss://api-rs.dexcelerate.com/ws
;
- Per‑table Zustand stores:
createScannerStore(ws)
- State:
filters
,realtimeEnabled
,rowsById
(Map),rows
(array),sort
metadata - Actions:
setFilters
,setRealtime
,onScannerPairs
,onTick
,onPairStats
,flush
- Debounced
flush
batches updates and reorders locally before committingrows
- State:
- Data fetching:
useScannerQuery
(- Paginates
GET /scanner
and forwards pages to the store viaonScannerPairs
)
- Paginates
- Real‑time updates:
useScannerUpdates
- Connects a per‑table WebSocket, subscribes to scanner filter and per‑row
pair
/pair-stats
tick
re‑computes price and market cap;pair-stats
updates audit fields- Debounced store flush reorders locally to maintain the current ranking intent
- Connects a per‑table WebSocket, subscribes to scanner filter and per‑row
- Table rendering:
ScannerTable
+react-virtuoso
+@tanstack/react-table
- Stable row keys, memoized header/components, semantic table HTML
- Price flashing via transient cell state
- UI components
Filters
per table; token cell with chain/protocol icons,AuditBadge
set- Number formatting with
numeral
src/stores/scannerStore.ts
: Zustand store per tablesrc/hooks/useScannerQuery.ts
: React Query -> store forwardingsrc/hooks/useScannerUpdates.ts
: WebSocket wiring + subscriptionssrc/components/ScannerTable.tsx
: Virtualized tablesrc/components/Filters.tsx
: Per‑table filters + realtime togglesrc/lib/api.ts
,src/lib/ws.ts
: REST/WS clients (see CORS proxy tip above)src/lib/types.ts
: Shared types and filter presets
- Virtualization keeps DOM size small;
rowsById
Map allows in‑place merges - Debounced
flush
batches UI updates to minimize re‑renders - Local reordering preserves the active ranking (volume/age/etc.) between server pages and WS ticks
- Local reordering vs server ordering: The UI reorders locally on debounced flush. Under very high churn, server‑driven ordering may be more consistent.
- Token/media assets: Token icons and some metadata are not fetched from external services; rows render gracefully without them.
- Token icons omitted: Skipped due to time constraints. Potential follow‑up: integrate CoinGecko or a token metadata service.
- Sorting breadth: Broad, arbitrary sorting modes are not implemented to keep focus on “Trending” (volume) and “New” (age), avoiding UX confusion.
- Schema validation: Runtime validation of API responses (e.g., with zod) is not included; types assume well‑formed responses.