Skip to content

Market Cap stat box renders blank while PLOT/USD price is loading #876

@realproject7

Description

@realproject7

Problem

On the story page (/story/[storylineId]), the first stat box (Market Cap) renders as an empty bordered box while the PLOT/USD price is loading. After a few seconds it eventually appears (e.g. "$68.84 +0.0%"), meaning all 3 price sources in the fallback chain are slow or failing before one finally succeeds.

Root Cause

Two issues:

1. Price API fallback chain is slow/unreliable

getPlotUsdPrice() in lib/usd-price.ts tries 3 sources sequentially with 3s timeouts each:

  1. Mint Club SDK — dynamic import + RPC call. Likely fails or times out (3s wasted)
  2. GeckoTerminal — HTTP fetch. May not list PLOT or may be rate-limited (3s wasted)
  3. CoinGecko — HTTP fetch. May not list PLOT without API key (3s wasted)

Worst case: 9 seconds of sequential timeouts before returning null. Even if one source eventually works, the user sees a blank box for several seconds.

Investigation needed:

  • Which source(s) actually succeed for the PLOT token?
  • Which ones consistently fail? (check server logs for [USD Price] source=X result=miss)
  • Are we hitting rate limits on GeckoTerminal/CoinGecko?
  • Is the Mint Club SDK import failing on the server side?
  • Is the 2-minute in-memory cache (CACHE_TTL) working, or is it being invalidated between requests (e.g. serverless cold starts)?

2. Loading state shows blank box

MarketCapBox (src/components/MarketCapBox.tsx:30) returns null while plotUsd is loading:

if (!plotUsd) return null;

The parent always renders the box container, so null content = empty bordered box.

Fix Plan

Part A: Diagnose & fix the price source reliability

  1. Add structured logging to fetchPlotUsdPrice() — log which source succeeded, how long each attempt took, and why failures occurred (timeout vs HTTP error vs missing data)
  2. Identify which sources actually work for PLOT token and remove/deprioritize dead sources
  3. Consider Promise.any() instead of sequential fallback — try all sources in parallel, use whichever responds first
  4. Extend cache TTL or add stale-while-revalidate — return stale cached price immediately while refreshing in background, especially important for serverless where in-memory cache is lost on cold starts
  5. Consider DB-backed cache as a last-resort fallback (store last known good price in Supabase) so there's always a value to show

Part B: Loading state fallback

Show "—" with label while loading, so the box is never blank:

if (!plotUsd) {
  return (
    <>
      <div className="text-foreground text-sm font-bold"></div>
      <div className="text-muted text-[9px]">Market Cap</div>
    </>
  );
}

Files

  • lib/usd-price.ts — price fetching logic, cache, fallback chain
  • src/components/MarketCapBox.tsx — loading state UI
  • src/hooks/usePlotUsdPrice.ts — client-side hook
  • src/app/api/tokens/plot-price/route.ts — API endpoint

Acceptance Criteria

  • Logs identify which price sources work/fail and why
  • Dead or consistently-failing sources are removed or deprioritized
  • Price loads fast enough that users rarely see the fallback state
  • Market Cap box never renders blank — shows "—" while loading
  • Cached price survives serverless cold starts (DB fallback or extended cache)

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent/T3Assigned to T3 builder agentbugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions