GUNZscope
v0.8.21 // EARLY ACCESS

Version History

Early Access · Public Development Log

GUNZscope is under active development. This page tracks every meaningful release since the first commit. Features ship fast — if something’s missing, request it.

v0.8.21

Current

May 7, 2026

  • components/account/admin/UsersTools.tsx — admin UI for the per-user feature flag system shipped in 412471e. Added a FLAGS column to the registered-users table with a toggleable INTEL pill (intelligence-early). Click flips the flag via PATCH /api/admin-proxy/users with the full next-state featureFlags array; pending state is keyed `${userId}:${flag}` so per-row toggles disable independently while the request is in flight. KNOWN_FEATURE_FLAGS is the in-file vocabulary — extend it to add more pills. Grid template-cols updated from 8 columns to 9 to fit the new 90px FLAGS cell
  • components/ui/VersionBadge.tsx — APP_VERSION bumped to v0.8.21

v0.8.20

May 5, 2026

  • lib/pricing/resolveHistoricalGunPrice.ts — same-day cost-basis stability. The function used to fall through to CoinGeckoService.getGunTokenPrice() on every same-day resolve (since the historical endpoint has no row for today yet), which meant re-enrichment intraday could shift purchasePriceUsd around with each request as GUN moved. Added a tryCache(date) check before the spot fetch — if a previously resolved same-day price exists with confidence !== "estimated", reuse it. First resolve still hits the live spot price, subsequent same-day resolves now return the cached value instead of drifting
  • components/nft-detail/SupplyCard.tsx — relabeled "Supply Intelligence" to "On-Chain Supply" with an inline ? tooltip explaining the data source caveat (tracks on-chain mints from HEX decodes and marketplace activity; in-game-earned-but-not-yet-decoded items are not included; actual total supply may be higher). Bracket descriptions, "in circulation" copy, "First/Last Minted" labels, and the serial-number line all updated to "on-chain" framing for consistency. SupplyTooltip is a local component with hover + click toggle, clipped-corner styling, fixed 208px width
  • app/[locale]/account/page.tsx — dropped overflow-hidden on the Language section card so the locale dropdown can render outside the card boundary instead of being clipped
  • components/ui/VersionBadge.tsx — APP_VERSION bumped to v0.8.20

v0.8.19

May 5, 2026

  • components/charts/AcquisitionTimeline.tsx — provenance trail geometry rewrite. Replaced the visx LinePath with curveBasis through 3 points (start, midpoint, end) with a hand-rolled SVG path: M start L (cx + dx*0.3, cy) C (cx + dx*0.5, cy), (cx + dx*0.5, originY), (cx + dx*0.7, originY) L origin. Control points sit at 50% X on each end of the curve segment, forcing horizontal tangents at both joins so the straight segments meet the cubic Bezier without visible kinks. Removed @visx/curve and LinePath imports
  • components/ui/VersionBadge.tsx — APP_VERSION bumped to v0.8.19

v0.8.17

Apr 23, 2026

  • /intelligence route scaffold — server component (metadata) + client component (logic) under app/[locale]/intelligence/. IntelligencePage.tsx reads ?address= from useSearchParams for the Your Collection overlay, renders PageHeader + HeroPulse + StatStrip + ActivityChart and SectionPlaceholder stubs for RareMoving / TopMovers / ScarcityComposition / ExploreDeeper / NotableDrops / ReleaseTimeline / IntelligenceFooter. Placeholders resolve in Commits 7‑9 of the intelligence buildout
  • /scarcity retired — middleware.ts emits a 301 redirect from /scarcity and any /{locale}/scarcity to the equivalent /intelligence path, preserving the full query string (?address=, sort, tab, etc). ?tab=opensea redirects to /market instead and strips the tab param. /scarcity removed from PROTECTED_PAGES so the redirect runs before the auth check. app/[locale]/scarcity/page.tsx + layout.tsx deleted; utils.ts and components/* retained because subsequent intelligence components re‑import them. app/[locale]/market/page.tsx "Scarcity Data" link retargeted to /intelligence
  • 5 intelligence API routes under app/api/intelligence/: /pulse (15s cache — mintsToday from live SupplyEvent, 7d/30d/all‑time from DailyItemMintCount rollup, itemsTracked / totalEvents / totalReleases aggregates, todayVs30dAvgPct delta), /activity (60s cache, range=30d|7d|all, aggregates daily mint+burn series from rollup, derives release annotations from DropEvent where newItemCount ≥ 50 capped at 10), /rare-moving (60s, ultra‑rare/rare/uncommon items with 7d activity, contextSnippet joined against DropEvent.itemNames), /movers (60s, top‑N items by 7d mint volume within a supply bracket, 7‑day sparkline array per item), /scarcity (60s, bracket composition + 7‑bucket granular distribution using the indexer’s denormalized activeMints column). All routes use prisma.$queryRaw for aggregations Prisma’s builder can’t express cleanly. None gate on auth — global metrics only, public per design intent
  • lib/intelligence/ helpers — format.ts (formatMints with <1M exact + ≥1M condensed, formatSupply always‑exact, formatRelativeDate, formatDurationSeconds, formatPercent + formatSignedPercent, anonymizeWallet first4+8×*+last4, formatShortDate, formatIsoDate), urls.ts (itemUrl/releaseUrl/compareUrl/intelligenceUrl canonical builders), rarity.ts (SupplyBracket vocabulary with bracketColor/bracketLabel helpers, ultra‑rare=coral, rare=purple, uncommon=lime, common=gray‑2), shape-detect.ts (CurvePoint/CurvePattern/ShapeResult types exported + detectShape/narrateShape throwing stubs; full implementation lands in the item detail page commit)
  • components/intelligence/ primitives — ItemBadge (clipped‑corner rarity pill, color from lib/intelligence/rarity), ScarcityBar (horizontal stacked bar proportional to item counts across brackets, supports optional labels below), Sparkline (inline SVG polyline with all‑zero and single‑point edge cases handled)
  • app/[locale]/intelligence/components/PageHeader.tsx — "N releases · N items · N events" bar pulled from /api/intelligence/pulse. HeroPulse.tsx renders the 48px mintsToday number with a delta pill against 30d avg, a lime live‑indicator dot, and a "Live / updated Ns ago" meta line that ticks client‑side via setInterval each second. StatStrip.tsx shows 7d / 30d / all‑time burns / items tracked in four columns with vertical dividers, collapses to 2×2 on mobile. All three share the pulse fetch via SWR dedup (single network request)
  • app/[locale]/intelligence/components/hooks/useIntelligenceFetch.ts — thin SWR wrapper with a default 60s refreshInterval, revalidateOnFocus off, dedupingInterval clamped to min(refreshInterval, 15s); returns { data, error, isLoading, refresh, updatedAt } with the response body’s updatedAt hoisted for the "last updated" meta lines
  • app/[locale]/intelligence/components/ActivityChart.tsx — visx‑based dual‑axis daily mint/burn chart (200px tall, full container width via @visx/responsive ParentSize). Left axis lime line = mints, right axis dashed coral = burns. Burns render only when the user toggles the overlay checkbox. Range pills (30d / 7d / all‑time) refetch /api/intelligence/activity?range=… with SWR keyed on the URL. Peak annotation labels the max mint day. DropEvent annotations render as lime‑ringed dots at the chart baseline with title tooltips; clicking routes to /intelligence/release/[id]. Hover state shows a vertical guide line, filled data point marker, and a foreignObject tooltip in the top corner. Reuses chartTheme from components/charts/theme.ts
  • app/globals.css — added --gs-coral (#FF6B4A) token plus --gs-coral-dim and --gs-coral-glow, wired through @theme inline as --color-gs-coral for Tailwind utilities. Used for ultra‑rare bracket, burns overlay, ITEM B in compare mode (Commit 12), and destructive accents
  • app/api/intelligence/pulse/route.ts — added totalEvents (all‑time rollup mints + all‑time rollup burns) and totalReleases (prisma.dropEvent.count()) to the response so PageHeader, HeroPulse, and StatStrip all source from the same endpoint without additional fetches. 7 parallel queries via Promise.all; warm cache response under 50ms
  • app/[locale]/intelligence/components/SectionPlaceholder.tsx — shared stub component that renders a clipped‑corner box with "[SectionName]" text at a configurable height. Commit 7‑9 components replace individual placeholders incrementally; seams are intentional so partial progress is reviewable

v0.8.16

Apr 20, 2026

  • indexer/src/aggregation/drops.ts — full rewrite. Replaces the stateful sliding‑window detector (recentNewItems[] + knownItemNames Set + Date.now()‑vs‑block‑timestamp pruneWindow) with a stateless DB‑backed query against game_items.firstMintAt. evaluateDrop early‑exits when isNewItem === false; otherwise runs detectClusterAtBlockTime which range‑scans game_items WHERE firstMintAt BETWEEN (anchor − 30min) AND anchor AND totalMints > 0. Threshold remains 3. Idempotency via DropEvent overlap query (where isOngoing AND detectedAt in window AND itemNames hasSome [...]). closeExpiredDrops exported for periodic invocation — not yet wired to a cron route
  • indexer/prisma/schema.prisma — added @@index([firstMintAt]) on GameItem; coexists with rescue‑branch @@index([tokenId]) on SupplyEvent. Matching index added to authoritative prisma/schema.prisma. Migration prisma/migrations/20260419000000_add_first_mint_at_index/migration.sql ships the CREATE INDEX statement
  • indexer/scripts/backfill-drop-events.ts — new. Reconstructs drop_events from historical game_items.firstMintAt by calling evaluateDrop for each first‑mint in ascending block‑time order, then calls closeExpiredDrops with the most‑recent block timestamp as anchor. --dry-run default; --apply with optional --i-understand-this-wipes-existing-drops for safe re‑runs. Production apply created 941 DropEvents from 3344 first‑mints; spans 2025‑03‑14 genesis through 2026‑03‑23. Row count is higher than the session‑collapsed estimate because the detector’s ongoing‑drop overlap query filters by detectedAt within the trailing 30‑minute window, so multi‑hour sustained drops (genesis catalog mint, 2025‑12‑23 Hexmas) fragment into multiple consecutive rows as the original drop’s detectedAt rolls out of the window. Per‑day biggest examples: Hexmas day biggest=79 items, Feral Beast biggest=25, Le BoBo biggest=42
  • indexer/src/ingestion/listener.ts — wired evaluateDrop into resolveAndLinkEvent’s mint branch. Captures the post‑increment db.gameItem.update return value; on totalMints === 1 (true first mint), if firstMintAt is null, runs an inline update to set firstMintAt + firstMintBlock to the current event’s timestamp/block before invoking evaluateDrop. Wrapped in try/catch so detector failures are non‑fatal to ingestion. Phase B rebuildAggregates would eventually re‑derive firstMintAt from MIN(supply_events.timestamp), but the detector needs the value at evaluation time
  • rescue commit 7daa303 — captured ~2,000 lines of Phase A/B indexer architecture that had been authored directly on the VPS and never pushed. Includes batched normalizer.ts (createMany 2000‑event batches with estimated‑timestamp extrapolation, no inline metadata or drop calls), resolve-pass.ts (GunzScan‑paginated metadata pass with adaptive rate‑limit handling and aggregate rebuild), gapfill.ts/gapfill-early.ts/resolve-early.ts/backfill-unlinked.ts operational utilities, and the Phase A → background listener → Phase B choreography in index.ts. Captured as a single rescue commit; reconciled with the drop detector via PR #30

v0.8.15

Apr 19, 2026

  • indexer/scripts/merge‑game‑item‑duplicate.ts — new parameterized migration: takes --canonical and --stale id flags; validates same contractAddress, rarity, and TRIM(itemName); reassigns every referencing SupplyEvent (gameItemId and burnGameItemId) onto canonical; sums totalMints/totalBurns/activeMints; merges firstMintAt/lastMintAt ranges and firstMintBlock/lastMintBlock ranges; ORs isActivelyMinting; ANDs isNew; recomputes supplyBracket from merged activeMints (thresholds inlined from CONFIG.BRACKETS: 10/50/200); preserves canonical’s other fields with stale fallback on null; deletes the stale row; single Prisma interactive transaction with 60s timeout; idempotent (re‑run with already‑merged ids exits 0)
  • Resolved Kestrel Templar Epic collision from v0.8.14 skip list — merged stale cmmv8twr (' Kestrel Templar', 2527 mints) into canonical cmmv905v ('Kestrel Templar', 1197 mints); 2527 SupplyEvents reassigned; canonical row now activeMints=3724, supplyBracket=common, firstMintAt=2025‑03‑14, lastMintAt=2026‑04‑06, isActivelyMinting=true, category='Sniper Rifle' (canonical normalized form won over stale 'SniperRifle'); 0 byte‑exact and 0 trim‑equivalent collisions remain in game_items

v0.8.14

Apr 19, 2026

  • Indexer itemName trim — classifyOTGItem in indexer/src/resolution/classifier.ts now applies .trim() to metadata.name before returning; on‑chain metadata occasionally shipped names with leading or trailing whitespace which the indexer passed through unmodified; all downstream writes (upsertMint, upsertBurn, drop detector, secondary cache) get the normalized value automatically
  • scripts/backfill‑trim‑item‑names.ts — one‑time migration normalized 57 of 58 whitespace game_items rows and 5 of 5 whitespace item_origin_items rows; raw SQL because the indexer Prisma schema does not declare ItemOriginItem; batches of 50 for the Prisma 7 transaction timeout cap; idempotent with --dry‑run default; 1 collision ( Kestrel Templar Epic vs Kestrel Templar Epic) logged and skipped for manual merge
  • BASE_LIMB_NAMES PHOSPHOR FURY correction — dropped trailing space on 'PHOSPHOR FURY' entry in lib/intelligence/baseItems.ts so the case‑sensitive Set lookup in isBaseLimb() continues to match the 4 post‑backfill rarity rows (166K combined active mints); without this change the Tranche B2 /scarcity base‑limb filter would have regressed the moment the backfill landed

v0.8.13

Apr 19, 2026

  • Scarcity rankings zero‑guard — OnChainSupplyTab filters out activeMints === 0 rows from the rankings table to match the zero‑guard already shipped on SupplyBadge and SupplyCard (commits 1320ddd, 187ff27); API route now also supports excludeZero query param (default true) so data.total and data.brackets reflect the filter
  • Base‑limb exclusion on /scarcity — /api/supply/rankings accepts excludeBase param (default true); imports BASE_LIMB_NAMES from lib/intelligence/baseItems (Phase 0 allowlist) and filters via Prisma where: { itemName: { notIn: [...BASE_LIMB_NAMES] } } on items, total, and groupBy; UI adds a "Show base limbs" toggle near the bracket‑filter chip with offset‑reset useEffect; default view drops 94 base‑limb rows (93 common, 1 rare) and ultra‑rare bracket falls from 10 inflated‑by‑coverage‑gaps to 3 genuine
  • Rarity vs Bracket disambiguation — both desktop column headers now render an InfoTooltip (components/ui/InfoTooltip) icon with copy clarifying that Rarity is in‑game cosmetic quality (Common/Uncommon/Rare/Epic, unrelated to mint count) and Bracket is mint‑count scarcity (Ultra‑Rare ≤ 10, Rare 11‑50, Uncommon 51‑200, Common 200+); mobile card view unchanged since it has no column headers

v0.8.12

Apr 17, 2026

  • item_origin_items casing fix — three write sites (applyMatchRules, itemOriginService.createItem, prisma/seed.ts) were lowercasing itemName on write while game_items stores canonical on‑chain casing; case‑sensitive JOINs between the two tables returned 0 rows; fix preserves canonical casing on every write path and switches itemOriginService.deleteItem to a case‑insensitive findFirst + delete‑by‑id so removals still work across casings
  • ItemOriginsContext.buildIndex — Map keys now lowercase on insert so getItemOrigin's existing .toLowerCase() read path keeps working regardless of DB casing; without this change the backfill would have caused every release chip and origin label to go blank against v0.8.11's build
  • scripts/backfill‑item‑origin‑casing.ts — one‑time data migration, idempotent, --dry‑run default; applies updates in Prisma batches of 50 since Prisma 7 BatchTransactionOptions has no timeout; production run aligned 594 of 621 rows to canonical casing (27 orphans with no GameItem match remain lowercase pending curation); case‑sensitive JOIN gi.itemName = ioi.itemName now resolves 594 names instead of 0

v0.8.11

Apr 17, 2026

  • Middleware cron bypass — added pathname.startsWith('/api/cron/') short‑circuit in middleware.ts API block; cron routes now reach their own verifyCronAuth() guard instead of being rejected with 401 by the Dynamic JWT verifier; matches existing pattern for /api/admin‑proxy/*, /api/admin/*, and /api/access/validate
  • Match rule application pipeline — new lib/intelligence/applyMatchRules.ts walks GameItem rows without a matching ItemOriginItem, evaluates ItemOriginMatchRule entries in priority order (prefix then contains), creates an ItemOriginItem per first‑match; idempotent via @@unique([itemName, quality])
  • scripts/apply‑match‑rules.ts — CLI wrapper with --dry‑run flag; loads .env.local for standalone runs; first production run mapped 172 distinct names across 19 of 30 seeded rules (halloween 72, hexmas 48, retro 14, legacy 7, black‑friday 5, enforcer 4, others)
  • Daily cron schedule — /api/cron/apply‑match‑rules route wraps applyMatchRules() with verifyCronAuth; vercel.json schedule "0 3 * * *" keeps ItemOriginItem rows in sync as new GameItem rows land
  • lib/intelligence/baseItems.ts — 25‑name curated allowlist of OTG base prosthetic limbs (18 arms + 7 legs); verified against GameItem table with exact DB casings preserved (Starter Arm, PainKilla, 'PHOSPHOR FURY ' with trailing space); BASE_LIMB_SET + isBaseLimb() helper for Phase 1 scarcity filtering
  • drop_events investigation — docs/DROP_EVENTS_INVESTIGATION.md documents why the indexer's DropEvent table is empty despite active mint activity; root cause: pruneWindow() in indexer/src/aggregation/drops.ts compares block timestamps against Date.now() instead of the latest entry timestamp, so every push is immediately pruned during backfill and catch‑up; two 5‑item qualifying clusters (2026‑02‑10 and 2026‑03‑23) were missed
  • Intelligence page coverage audit — docs/INTELLIGENCE_COVERAGE_FINDINGS.md quantifies pre‑Phase‑0 gap: 15.1% item coverage, 9.3% supply coverage; post‑Phase‑0: 94 base limbs tagged (2.8M supply separated), 760 non‑base items mapped (21.6%), 2761 orphans remaining

v0.8.10

Apr 8, 2026

  • Smart post‑login routing — HomeClient defers the social‑login redirect until UserProfileContext loads; returning Discord users with linked game wallets now route to /intelligence?address=<gameWallet> instead of being dumped on /account; non‑social wallet logins keep the existing direct‑to‑/intelligence path
  • gs_just_logged_in sessionStorage flag — set by HomeClient on both fresh‑wallet and already‑connected paths before pushing; consumed exactly once by the /account post‑login guard so intentional Profile‑nav clicks never trigger an auto‑redirect
  • /account post‑login guard — new useEffect in AccountContent reads the flag, finds the first non‑embedded portfolio address, and router.replace()s to /intelligence when present; postLoginRedirectRef ensures it fires at most once per page load
  • Focused onboarding view — new showFocusedOnboarding = isEmbeddedOnly && !hasGameWalletForTabs branch in /account; hides the full tab bar (identity/wallets/sharing/settings) and renders a centered welcome heading + GameWalletOnboarding card; welcomeName falls back through socialConnections.username → displayName → "player"
  • Auto‑revealing tabs — once the focused user pastes a game wallet, addPortfolioAddress + refreshProfile flips hasGameWalletForTabs, showFocusedOnboarding becomes false, and the full tabbed account UI appears on next render with no manual navigation
  • GameWalletOnboarding secondary actions — added two escape hatches under the paste‑address input: "Connect Wallet" calls useDynamicModals().setShowLinkNewWalletModal(true) for users who already have MetaMask, "Browse the Explorer" links to /explore for users who want to look around first

v0.8.8

Apr 5, 2026

  • Discord social login — Dynamic Labs OAuth integration; users sign in with Discord and receive an auto‑provisioned embedded wallet; social‑login users route to /account instead of /intelligence
  • Social connections API — /api/social/link, /api/social/unlink, /api/social/resolve, /api/social/connections routes for linking Discord (and future OAuth providers) to user profiles; SocialConnection Prisma model with unique provider+providerId constraint
  • Auto‑whitelist for social logins — /api/access/validate detects Bearer token with OAuth credentials and auto‑whitelists the embedded wallet address; eliminates manual whitelist step for Discord users
  • Embedded wallet retry — useAutoLogin retries up to 5s (10 × 500ms) while Dynamic Labs provisions the embedded wallet after OAuth completion; prevents race condition where wallet is null during provisioning
  • Social login routing — HomeClient detects OAuth credentials and routes social‑login users to /account (no game wallet yet) instead of /intelligence; skips email‑only validation flow for social users
  • SocialConnections component — account page shows linked social accounts with connect/disconnect UI; useSocialResolver hook for resolving wallet addresses to social identities
  • Game wallet onboarding banner — prompts social‑login users to link their GunzChain game wallet for full portfolio access
  • Privacy policy update — added Data Protection Contact section (privacy@gunzscope.xyz); linked contact email in Your Rights section

v0.8.7

Mar 29, 2026

  • i18n framework — next‑intl integrated with App Router; locale‑aware routing via app/[locale]/ structure; middleware composed with existing auth middleware; next/link and next/navigation swapped to locale‑aware wrappers across 24+ files
  • French translations — fr.json with full coverage: Navbar, PublicNav, StaticPageNav, Footer, homepage (hero, features, social proof, dashboard preview, error states); LanguageSwitcher component in navbar with flag icons
  • Homepage stats bar overhaul — replaced GUN Price with On‑Chain Events count and Platforms Unified metric; platform icons replaced with official Simple Icons brand SVGs (Avalanche, Solana, OpenSea); top/bottom bar stat positions swapped
  • Mint position query — NFT detail modal shows "Mint #X of Y" via GunzScan token instance API; position derived from mint event ordering
  • Per‑item normalized enrichment cache — write‑through cache layer keyed by contract+tokenId; enriched data persists independently of gallery‑level cache; reduces redundant re‑enrichment on revisits
  • Admin auth unification — 7 admin API routes (whitelist, waitlist, users, shares, revenue, referrals, item‑origins) consolidated to shared admin auth pattern; removed hardcoded admin wallet from shares route
  • Snapshot field fix — enrichment snapshot now captures purchasePriceUsd, purchasePriceUsdEstimated, transferDate, gunPriceAtTransfer fields that were previously dropped during snapshot serialization
  • Visual polish — film grain noise overlay texture; supply count badges gated behind authentication; rich text heart icon fix for i18n paired tags
  • Per‑item cache reads — loadEnrichedItems() fetches individual EnrichedItem rows from server; seedLocalCacheFromEnrichedItems() pre‑populates localStorage from per‑item rows instead of monolithic blob; orchestrator reads per‑item first, falls back to blob
  • Supply card rarity fix — SupplyCard now reads rarity from activeItem (token‑level) instead of group‑level nft.traits which never updated on token switch in mixed‑rarity groups
  • Modal classification fix — NFTDetailModal now uses getSpecificItemType() instead of raw OpenSea Class trait; Meatport items correctly show "Combat DJ" class and "Meatport" origin badge via new event release + prefix match rule

v0.8.6

Mar 24, 2026

  • GUNZscopeAchievements.sol — soulbound ERC‑721 contract (UUPS upgradeable) deployed to Avalanche Fuji testnet; proxy at 0x779d1bc9769944821c135D460944C31D134bc201
  • Cross‑contract verification — claim() reads PortfolioAttestationV2 view functions (getAttestationCount, getLatestAttestation, getAttestation, hasRegisteredHandle) via IPortfolioAttestation interface; 4 verification types: TIME, VALUE, ACTIVITY, COLLECTION
  • Soulbound enforcement — _update() override blocks all transfers except mint/burn; ERC721URIStorageUpgradeable for per‑token metadata URIs
  • 8 launch achievements registered: Pioneer (1+ attestation), Handle Holder (gsHandle), Rising Star (10K GUN), Whale (100K GUN), Collector (10+ items), Hoarder (50+ items), Diamond Hands (90d/3 att), Veteran (180d/6 att)
  • claimBatch(uint256[]) — multi‑achievement claim in single tx; isEligible() view function for gas‑free pre‑check
  • deploy‑achievements.ts — Ledger‑based deployment script using custom LedgerSigner (same pattern as upgrade‑mainnet‑ledger.ts); 10 Ledger confirmations (2 deploy + 8 achievement registrations)
  • 35 Hardhat tests covering all 4 achievement types, soulbound transfer blocking, batch claims, double‑claim prevention, edge cases
  • Participation economy design doc — 4‑layer architecture: identity (deployed) → SBT milestones (testnet) → dust/participation score (planned) → transferable token (future); inspired by Midnight night/dust model
  • Roadmap pages updated — /build‑games reputation card moved from "next" to "live"; Phase 1 items include SBT contract; /roadmap item 04 reflects actual implementation

v0.8.5

Mar 21, 2026

  • Gaming Asset Intelligence Indexer v2 — standalone Node.js process on DigitalOcean VPS (PM2‑managed); two‑phase architecture: Phase A bulk‑scrapes ERC‑721 Transfer events via eth_getLogs, Phase B resolves metadata via GunzScan pagination
  • Phase A: deferred metadata normalizer — createMany with skipDuplicates, estimated timestamps from reference block + 2s block time, adaptive chunk splitting for dense ranges (>20K events); 27.69M events captured across 16.5M blocks in ~5 hours
  • Phase B: GunzScan instance pagination — fetches /api/v2/tokens/{contract}/instances (50/page, 600ms delay), classifies via classifyOTGItem, batch‑links SupplyEvents to GameItems using WHERE tokenId IN (...) bulk updates; resumable cursor in indexer_cursors table
  • Parallel architecture — WebSocket listener (ethers.js WebSocketProvider) starts before Phase B; real‑time mint/burn capture runs concurrently with background metadata resolution; listener auto‑backfills gaps on reconnect
  • Self‑healing Phase B — runPhaseB() wraps resolvePass() in retry loop (100 max, exponential backoff 1‑5 min); GunzScan 429s trigger 30s backoff with 5 retries per page; process never crashes, just waits and resumes
  • Periodic aggregate rebuilds — every 200 pages (~10K tokens), rebuildAggregates() recomputes totalMints/totalBurns/activeMints/supplyBracket for all GameItems from linked SupplyEvent counts
  • Gap‑fill script (gapfill.ts) — standalone PM2 process re‑scanning blocks 1.47M–8.77M to recover events lost during Neon Postgres OOM period; createMany + skipDuplicates for idempotent fills
  • Neon Postgres scaling — autoscaling 1‑2 CU (was 0.25 CU); eliminated Postgres OOM errors on bulk inserts; DB_BATCH_SIZE tuned from 100→‑2000 rows per INSERT
  • Database index — idx_supply_event_token_id on (tokenId, contractAddress) for fast Phase B updateMany lookups across 27M+ rows
  • VPS infrastructure — 1GB swap added as OOM safety net; Node.js heap capped at 1024MB; two concurrent PM2 processes (gs‑indexer + gs‑gapfill) stable at ~400MB combined
  • ValueHeader layout — 7d change badge moved inline with market value (same row); new position % line below cost basis showing distance from cost basis with directional arrow and profit/loss coloring
  • Homepage stats bar — replaced hardcoded/low‑impact metrics (NFTs Tracked: 70, Blocks Scanned: 16M+, Tracking: 24/7) with live indexer data; hero bar now shows On‑Chain Events (28M+), Items Indexed (3,500+), Blocks Indexed (16.7M+) with count‑up animations; /api/stats/site extended with indexerCursor + gameItem.count() via parallel Promise.all; social proof section pulls live event count with static fallback

v0.8.4

Mar 22, 2026

  • calcPortfolio cost basis fix — was using currentGunPrice for cost basis calculation; now uses purchasePriceUsd (historical price at acquisition) with currentGunPrice × costGun as fallback only when no historical data exists
  • NFT summary cards rework — NFTHoldingsCard Face A: count + venue breakdown (decoded/traded/transferred/received); Face B: profit/underwater count, cost vs market value, liquidity reality check (listed/with sales/no data). GunSpentCard Face A: estimated market value + pricing source breakdown; Face B: ROI % with cost in vs value now
  • Transfer reclassification fix — removed mutually exclusive marketplacePriceGun guard from isGenuineTransfer (required undefined while resolvedVenue required defined); transferred items now resolve to sender’s original venue, purchase price, and date instead of being classified as purchases
  • transferDate field — new field on NFT type and cache; computed from transfer event timestamp; propagated through enrichment pipeline and cache write (purchasePriceUsdEstimated + transferDate added to modal pipeline cache merge)
  • Provenance trail on AcquisitionTimeline — ghost dot (r=3, 30% opacity) at original purchase date, dashed gray connector, main cyan dot at transferDate; dateExtent and lockPoints updated to include transferDate; tooltip shows "Bought Mar 5 → Transferred Mar 12"
  • GunBalanceCard layout — Face A: balance + USD value; Face B: current GUN price + LiquidityIndicator with position‑impact messaging; plain‑language depth explanations for non‑traders
  • PortfolioSummaryBar — passes market value (displayTotal) as totalValue to ValueHeader so cost basis subtitle shows correct % above/below
  • Enrichment venue resolution — when tracing transfer back to sender’s original purchase, store sender’s venue (e.g., opensea) instead of transfer
  • Sparkline color fix — use scalar cost basis for sparkline profit/loss coloring when per‑point historical data unavailable
  • Modal pipeline cache merge — cache write now preserves orchestrator fields (marketExitGun, comparableSalesMedian, etc.) instead of overwriting with pipeline‑only subset
  • Cache schema v29 — force transferDate re‑enrichment for all cached items
  • Attestation explorer — WALLETS column showing registered wallet count per attested user
  • Portfolio layout — removed wallet search bar from authenticated portfolio view; search remains on view‑only/explore flow

v0.8.3

Mar 21, 2026

  • Sparkline loading strategy — server snapshots are now primary sparkline source; localStorage demoted to offline fallback; skeleton shimmer during 800ms server fetch; GUN price sparkline as timeout fallback; one‑time localStorage cleanup of stale bootstrap data
  • Modal cache‑first read — reset effect pre‑populates itemPurchaseData and resolvedAcquisitions from localStorage cache synchronously, eliminating empty→cached data flash on modal open; resolvedAcquisitionsRef updated atomically
  • Enrichment retry — retry delay 2s→5s, max 2 retries (3 total passes), per‑attempt diagnostic logging; currentRetryNfts re‑filtered between attempts so only unresolved NFTs retry
  • Cache housekeeping — useHandleResolver 5‑min TTL + 500 max size; useSupplyData Map cleanup on >200 entries; priceHistory server Map 1000‑entry guard; waitlist gs_waitlist_id cleanup in useAutoLogin
  • GUN price sparkline tooltip — 4 decimal places for sub‑dollar values, "GUN" prefix in GUN price mode; redundant GUN% second line hidden when primary line IS the GUN price
  • Auth gating — AuthGate component (blur/replace/inline variants) with "VIEW ONLY · CONNECT WALLET TO UNLOCK" CTA; gates cost basis, P&L, GUN spent, insights, charts, acquisition details, position card, supply badges, liquidity indicator
  • Server‑side redaction — lib/server/redactCostBasis.ts strips 20+ cost/acquisition/P&L fields from API responses; lib/server/softAuth.ts provides non‑rejecting Bearer auth check; applied to /api/portfolio/snapshots, /api/portfolio/[wallet]/pnl, and all /api/supply/* endpoints
  • Portfolio snapshots — GET endpoint redacts totalCostBasisUsd, totalGunSpent, enrichmentPct for non‑owner requests; PnL endpoint short‑circuits with { redacted: true } before expensive RPC work
  • Supply endpoint gating — /api/supply/batch, /api/supply/item, /api/supply/rankings, /api/supply/drops return empty results for unauthenticated requests

v0.8.2

Mar 17, 2026

  • DexScreener liquidity integration — new data source for GUN token trading depth, volume, and sentiment; lib/api/dexscreener.ts fetches + aggregates across all GUN pairs with 60s in‑memory cache and stale fallback
  • /api/dex/gun‑liquidity route — GET endpoint returning GunLiquidityData or { available: false }; Cache‑Control: s‑maxage=60, stale‑while‑revalidate=30; always 200 (graceful degradation)
  • useGunLiquidity hook — SWR‑based with 60s polling and dedupingInterval; returns { data, loading, error }
  • LiquidityIndicator component — compact pill matching 7d price change badge styling (clipHex(4), color‑mix borders, font‑mono font‑semibold); classifies depth (deep/moderate/thin/minimal), activity, and sentiment
  • Player‑friendly tooltip — two‑part hover: plain‑language explanation of what liquidity means + data breakdown (volume, buy/sell ratio, pair count, primary market, market cap); no crypto jargon
  • GunBalanceCard integration — LiquidityIndicator rendered below USD value on both card faces (balance + price views)
  • Architecture updates — build‑games diagram Layer 1 expanded to 4‑column grid with Liquidity/DexScreener card; data source counts 5 → 6; README ASCII art + endpoint count 74 → 75; homepage pricing sources 4 → 5

v0.8.1

Mar 14, 2026

  • Cross‑wallet favorite labels — PinnedFavoritesRow now shows source wallet label (from portfolioAddresses) with title attr for full address; falls back to truncateAddress(), then "other wallet" for unresolved stubs
  • Favorite metadata now stores walletAddress — FavoritePinActions passes walletAddress into FavoriteButton metadata; usePinnedFavorites stub path reads fav.metadata.walletAddress instead of hardcoding "unknown"
  • walletLabels prop — PortfolioClient builds Map<string, string> from portfolioAddresses; threaded through PinnedFavoritesRow for label‑over‑address display
  • NFTGalleryGridCardProps.walletAddress — new optional prop threaded from NFTGallery → NFTGalleryGridCard → FavoritePinActions for metadata capture
  • Matrix decode entrance animation — CSS keyframes: matrix‑scanline (lime sweep), matrix‑card‑decode (clip‑path + scaleY + blur + brightness), matrix‑rain‑col (vertical lines), matrix‑label‑decode (letter‑spacing + blur flicker), matrix‑container‑enter (inward glow), matrix‑divider‑decode (scaleX sweep)
  • DecodeText component — character‑by‑character scramble effect using requestAnimationFrame interval; resolves left‑to‑right with randomized probability; 18‑frame cycle (~300ms)
  • MatrixRain component — generates N random vertical gradient lines with staggered delays/durations; positioned absolutely within matrix‑container
  • Animation gating — hasAnimated ref in PinnedFavoritesRow prevents replay on re‑renders; prefers‑reduced‑motion media query disables all matrix animations
  • Admin panel: Fees & Revenue tab — new AdminTab "fees" with FeesRevenueTab component; 3 sections: Revenue Streams (lime gradient, dynamic stats from /api/admin/revenue), User‑Facing Costs (purple gradient, static table), Operator Costs (warning gradient, static table with cache notes)
  • /api/admin/revenue route — GET endpoint, Bearer $ADMIN_SECRET auth; returns attestationCount, handleChangeCount, avaxCollected (placeholder values, TODO: wire to on‑chain events)
  • WalletAddressInput default border — border‑transparent → border‑white/[0.12]; focus: border‑[var(‑‑gs‑lime)]/40
  • useUserProfile event handler fix — gs:profile‑updated handler wrapped in queueMicrotask() to prevent "setState during render" warning when FavoriteButton dispatch triggers setProfile in WalletDropdown’s useUserProfile instance
  • UsersTools.tsx placeholder fix — \u2026 in JSX string attr rendered literally; wrapped in {""} expression for proper unicode interpretation
  • NFTGalleryPagination debug info — gated behind ?debug=1 URL param instead of NODE_ENV === "development"
  • usePinnedFavorites resolvedCacheRef — caches previously resolved NFT+wallet pairs so walletMap re‑fetches don’t downgrade full cards to stubs

v0.7.5

Mar 10, 2026

  • NFTDetailPositionCard text readability pass — row labels 9px/40% → 10px/50%, section headers 10px → 11px, GUN values 14px → 15px, USD conversions 50% → 60% opacity, arrows 30% → 40%, italic subtexts switched to text-white/50 + tracking-wide
  • Purple accent visibility — VIA SALES label, waterfall dropdown buttons, Reference Estimate header, warning icon all bumped to full opacity (were /60–/80)
  • Low-confidence waterfall dropdown: labels 20% → 30%, values 12px/40% → 13px/50%; full-confidence: labels 25% → 35%, values same bump
  • NFT description subtitle in modal: added tracking-wide letter spacing
  • Image lightbox — click NFT image in modal to open fixed z-[200] overlay; scroll-to-zoom (0.5×–5×, 0.15 step), click-to-toggle (1×/2×); bg-black/95 solid (no backdrop-blur to avoid GPU load); zoom % indicator at bottom; plain <img> tag to avoid Next.js Image fill constraints
  • Modal image size bump — 180×180/220×220 → 220×220/280×280 (mobile/desktop); container changed from aspect-square+max-w to explicit dimensions; parent switched to flex justify-center for proper centering
  • High-res image pipeline — imageHires field added to NFT type, CachedMetadataData, and avalanche.ts cache read path; /api/opensea/orders extracts image_url from maker_asset_bundle; threaded through getNFTListings → enrichment orchestrator → metadata cache (7d TTL); lightbox uses imageHires || image
  • Acquisition timeline tooltip — glass effect: background rgba(22,22,22,0.6), backdrop-blur 6px, border 12% white opacity

v0.7.4

Mar 10, 2026

  • /build-games landing page overhaul — reordered sections: "The Market" (13M players, 450K DAU, zero existing trackers) now leads as section 01 before Architecture; judges see the "why" before the "how"
  • GitHub repo link added to hero CTAs and bottom CTA section — links to Gunzilla-NYC/gunzscope for judge access to source code
  • Attestation explorer link — "View on Snowtrace" button in On-Chain Proof section links directly to the attestation contract on AVAX C-Chain
  • Market section copy refined — "OTG and GUNZscope Phase I is a sequenced strategic entry point, not the ceiling" replaces generic framing
  • Footer — CRYPTOHAKI text now links to /cryptohaki profile page

v0.7.3

Mar 8, 2026

  • CSS design token system — 12-step neutral scale (--gs-n-0 through --gs-n-11), 14 semantic aliases (--gs-bg, --gs-surface, --gs-card, --gs-text-*, --gs-border, --gs-status-*), 3 transition tokens (--gs-ease, --gs-dur-fast, --gs-dur-norm); enables single-point theme switching
  • Variable-driven corner-cut utilities — .cut-sm (4px), .cut-md (8px), .cut-lg (12px) classes backed by --gs-cut-* custom properties; replaces hardcoded clip-path polygons
  • Accessibility: scroll-behavior forced to auto in prefers-reduced-motion media query — smooth scrolling was still active for users with reduced motion enabled (WCAG 2.1 SC 2.3.3)
  • Compositing layer merge — grid-bg + scanlines collapsed into single .page-bg element across 8 page files; eliminates one full-viewport GPU texture per page (~2-4 MB VRAM savings)
  • IntersectionObserver auto-disconnect — observers in home page social proof, brand page, and build-games page now call unobserve() after element reveal; ~29 persistent observer callbacks eliminated post-animation
  • rAF-gated scroll handlers — Navbar, PublicNav, and ScrollToTopButton scroll listeners wrapped in requestAnimationFrame ticking gate with passive: true; caps setState calls at 1/frame (8× reduction on 120Hz displays)
  • content-visibility: auto on 3 below-fold home page sections (Features, Social Proof, Dashboard Preview) — browser skips layout+paint for offscreen content; ~200-400 DOM nodes deferred on initial load

v0.7.2

Mar 7, 2026

  • Wallet modal UX overhaul — "Connect Whitelisted Wallet" → "Get Started"; two lanes now labeled "View Only" (paste address) and "Full Access" (Dynamic connect) with badge indicators; description copy rewritten for clarity
  • Middleware view‑only bypass — /portfolio with ?address= query param now skips gs_session cookie validation; applies to both missing‑cookie and expired‑cookie paths in middleware.ts
  • connectionMode fix — PortfolioClient.tsx compares activeWalletData.address against primaryWallet.address (case‑insensitive); returns "view‑only" when viewing a different address than the connected wallet; fixes View Only badge, attestation button gating, and ShareDropdown signing controls
  • useIsViewOnly() hook — new selector in PortfolioContext.tsx; reads connectionMode from context; consumed by WalletIdentity.tsx (badge) and ShareDropdown.tsx (attestation gating)
  • Portfolio initializing hard timeout — 15s setTimeout in isPortfolioInitializing effect; fires regardless of gunPrice state; prevents permanent "Calculating…" when CoinGecko times out; existing 10s enrichment timeout preserved as inner guard
  • Error boundaries — app/global‑error.tsx (root‑level, wraps <html>/<body>) and app/portfolio/error.tsx (route‑level with brand styling); both show error digest + retry button
  • Body scroll lock — useEffect in app/page.tsx sets document.body.style.overflow="hidden" when showWalletModal is true; cleanup restores on unmount
  • NFTGalleryPagination "All N NFTs loaded" message gated behind process.env.NODE_ENV === "development"
  • E2E test suite — 57 Playwright specs across 5 files (wallet‑modal, view‑only‑flow, hard‑refresh, network‑edge‑cases, data‑integrity‑responsive); desktop‑chrome + mobile‑chrome (Pixel 7) projects; runs against BASE_URL || gunzscope.xyz
  • Tweet thread automation — compose‑tweet.mjs gains ‑‑version flag for targeting specific update entries; post‑tweet.mjs auto‑loads .env.local credentials; tweet.mjs interactive CLI with preview/edit/post flow

v0.7.1

Mar 5, 2026

  • Server‑side whitelist enforcement — middleware.ts verifies Bearer JWT (API routes) and gs_session cookie (page routes) against whitelist_entries via checkWhitelistEdge() on every request; 7 page routes + 26 API routes in matcher
  • Session cookie (gs_session) — lib/auth/sessionCookie.ts creates HS256‑signed JWT via jose SignJWT; set by /api/access/validate on 3 success paths (permanent, trial, promoted); cleared by /api/auth/logout POST; 7‑day maxAge, httpOnly + Secure + SameSite=Strict
  • Soft delete for whitelist — WhitelistEntry.isActive column; removeFromWhitelist() sets isActive=false instead of DELETE; banService.ts fixed to use update() not delete(); addToWhitelist() upserts with isActive=true for re‑activation
  • whitelistService.edge.ts — Edge‑compatible whitelist check via @neondatabase/serverless HTTP queries; predicate: isActive=true AND (expiresAt IS NULL OR expiresAt > NOW())
  • useAutoLogin whitelist pre‑check — calls /api/access/validate before /api/me; non‑whitelisted wallets get handleLogOut() immediately, preventing ghost profile creation
  • promoteFromWaitlist() fix — upsert update clause now sets isActive: true (survives prior admin soft‑delete)
  • KonamiOverlay trial‑expired UX — parses trialExpired from /api/access/konami 403 response; shows "Trial already used" instead of generic error
  • Navbar + WalletButton disconnect — fire‑and‑forget fetch("/api/auth/logout") before handleLogOut()
  • Ghost profile cleanup — 4 zero‑engagement UserProfile rows deleted; Digital Panoply whitelisted via direct DB upsert

v0.7.0

Mar 5, 2026

  • Portfolio Pins — FavoriteButton wired onto NFTGalleryGridCard image overlay (bottom‑right, opacity‑0 group‑hover:opacity‑100); PinButton component reads useUserProfile().favorites, PATCH /api/favorites/[id] toggles pinned; useNFTGalleryFilters partitions by pinnedRefIds Set, floats pinned to top preserving sort order within each group
  • isOwnPortfolio prop threaded NFTGalleryProps → NFTGalleryInner → NFTGalleryGridCard; computed in PortfolioClient via connectedWallets.includes(activeWalletData.address.toLowerCase())
  • Wishlist model — FavoriteItem extended with externalContract, externalTokenId, externalChain (String?), lastKnownValue (Float?), lastValueAt (DateTime?); type union includes "wishlist"
  • GET /api/favorites — listFavorites() splits by type !== "wishlist" vs type === "wishlist", returns { favorites, wishlist }
  • GET /api/favorites/refresh‑wishlist — updates lastValueAt for all wishlist items
  • AccountPanel Wishlist tab — TabId union extended; wishlistItems/favoriteItems computed from profile.favorites.filter(); star icon, lastKnownValue GUN display, lastValueAt date
  • Admin Users tab — listUsers() in userService with Prisma OR search (displayName, email, wallet address); GET /api/admin/users with verifyAdmin + isWhitelisted cross‑reference; UsersTools component with debounced search, 7‑column grid, whitelist dot indicator
  • Feature Requests GlitchLink in desktop navbar — gated behind hasWallet, positioned after ExperimentsDropdown
  • useUserProfile — togglePin() action with optimistic update; FavoriteItem interface gains pinned: boolean + wishlist fields; AddFavoriteInput gains optional externalContract/externalTokenId/externalChain
  • addFavorite() return type includes pinned: boolean; upsert create/update now persists wishlist fields
  • Display name fallback — UsersTools, ShareLeaderboard, admin shares page now show truncateAddress(primaryWallet) instead of raw email or "Anonymous"
  • shareService getShareLeaderboard() includes primaryWallet via wallets relation (orderBy isPrimary desc, take 1)
  • Duplicate cryptohaki UserProfile cleaned via raw SQL script (scripts/cleanup‑duplicate‑user.ts) with correct FK column mapping per table

v0.5.4

Mar 4, 2026

  • PortfolioSparkline component — new chart in ValueHeader showing % change from cost basis over time; dual‑clip split‑gradient fill (profit above zero, loss below), zero baseline dashed line, pulsing endpoint dot, edge‑aware hover tooltip as absolute div sibling
  • PortfolioSparkline colors — profit fill #A6F700 (brand lime), loss fill #B44AFF (soft orchid / ‑‑gs‑rarity‑epic); endpoint glyph + drop‑shadow glow match; tooltip % text uses same palette
  • ValueHeader 7d badge loss state — switched from var(‑‑gs‑loss) red to #B44AFF orchid (bg/border/text)
  • PnLScatterPlot refinements — floor cluster annotation (horizontal dashed line for dots < 5 GUN), simplified legend (4 items), zone label opacity 0.18 + fontSize 11, break‑even label rotation via atan2, break‑even dot treatment (white stroke within 5%)
  • PnLScatterPlot + AcquisitionTimeline margins normalized to { top: 16, right: 24, bottom: 32, left: 40 } in embedded mode
  • AcquisitionTimeline — removed vertical grid lines; lollipop stems converted to gradient opacity (5 linearGradient defs per venue, stemGradient() helper)
  • AcquisitionTimeline stem gradient fix — gradientUnits="userSpaceOnUse" with explicit pixel y1/y2 coordinates; default objectBoundingBox caused zero‑width line degenerate bounding box
  • AcquisitionTimeline horizontal crosshair — dashed line from hovered dot to Y‑axis on proximity lock
  • AcquisitionTimeline tooltip — added acquisition date (MMM DD, YYYY) and current USD value fields
  • AcquisitionTimeline stem bottom stopOpacity reduced from 0.25 to 0.10

v0.5.3

Mar 4, 2026

  • 40‑item performance audit across 5 phases — parallelization, caching, architecture, DRY, decomposition
  • Parallelized 6 critical paths: fetchGunPricesForDates (5‑concurrent batches), /api/leaderboard (Promise.all DB+price), floor‑drop/portfolio‑digest/whale‑tracker crons, avalanche.ts RPC calls
  • Cache‑Control headers on 6 API routes: /api/price/gun, /api/portfolio/[wallet]/pnl, /api/nft/pnl/[tokenId], /api/marketplace/purchases/*
  • Deferred Vercel Analytics + SpeedInsights after hydration via AnalyticsDeferred component
  • motion/react added to optimizePackageImports — tree‑shakes Framer Motion bundle
  • useGunPrice migrated to SWR — built‑in dedup, stale‑while‑revalidate, background refresh
  • Shared server‑side GUN price cache (lib/server/gunPrice.ts) — single in‑memory cache across all API routes
  • /api/scarcity — in‑memory cache + parallel mint‑count lookups
  • React.cache() on share/referral server pages for same‑request dedup
  • PortfolioContext — 14 prop‑drilled values lifted into context provider, eliminates drilling cascade
  • GalleryFilterContext — filter state moved from prop threading to dedicated context
  • WalletIdentity — removed redundant boolean guards
  • Deduplicated timeAgo, validateSlugLocally, slug constants into lib/utils/
  • OpenSea cache‑control helpers consolidated to app/api/opensea/cacheHelpers.ts
  • memo() on NFTGalleryControls + chart sub‑components (AcquisitionTimeline, PnLScatterPlot)
  • animate‑spin SVGs wrapped in <span> for GPU compositing
  • .map().filter() → single‑pass .reduce() in hot paths
  • Leaderboard DB‑level DISTINCT instead of JS dedup
  • Cron logAlert via after() for non‑blocking response
  • AdminPanel.tsx decomposed: 1,342 → 155 lines + 8 sub‑components in components/account/admin/
  • useNFTAcquisitionPipeline: types + candidates extracted to lib/hooks/acquisition/ (1,747 → 1,360 lines)
  • brand/page.tsx: 5 static sections extracted to app/brand/sections/ (1,682 → 195 lines)
  • PortfolioClient: PortfolioSummaryBar + Footer converted to dynamic() imports (5 total lazy‑loaded components)
  • Passive scroll listeners + startTransition for scroll‑driven state
  • 10s timeout on all CoinGecko fetches
  • /api/price/history restored to 24h revalidate cache

v0.5.2

Mar 2, 2026

  • /build‑games landing page — hero, stats bar (live from /api/stats/site), BuildVelocityChart, multi‑chain architecture diagram, 9‑card feature grid, on‑chain proof section (live attestation count), valuation waterfall, dashboard preview, roadmap CTA; layout matches homepage patterns (max‑w‑7xl, section‑number, section‑line)
  • /build‑games/roadmap — public fork of /strategy with admin gate removed; DocBadge, BuildVelocityChart, 6 phases, ScopeBar, vision banner; minimal nav with Logo + back link
  • PublicNav component — reusable nav for public pages (no Dynamic Labs dependency); Logo, VersionBadge, GlitchLink items, Login CTA, mobile hamburger, scroll backdrop blur; matches Navbar layout exactly
  • /explore now uses PublicNav instead of Navbar — anonymous visitors see Home + Onchain ID links instead of empty bar
  • /explore/attestation/[cid] viewer page — renders attestation metadata as branded UI instead of raw JSON; summary cards (wallet, value, items, block), merkle root with copy, holdings table (first 100 + expand), Autonomys DSN badge; getMetadataLink() extracts CID and links to viewer
  • Strategy page — added BuildVelocityChart section above vision banner
  • Homepage — Onchain ID link now uses GlitchLink (bracket + scramble effect on hover)
  • KonamiOverlay bug fixes — input bg‑black/60 → bg‑black/85 (bottom transparency fix); border‑1 → border‑2 with higher opacity (0.15 empty, 0.50 valid); input text‑white/90 → text‑[var(‑‑gs‑white)] + tracking‑wider + caret‑lime; focus‑within state on empty input
  • globals.css — input::selection override: rgba(166,247,0,0.3) instead of solid lime; fixes green flash on paste
  • DynamicProvider — .modal‑card bg rgba(22,22,22,0.5) → rgba(22,22,22,0.88); auth modal readability fix

v0.5.1

Mar 2, 2026

  • Nav restructure — Leaderboard, Scarcity, Market grouped under “Experiments” dropdown; new ExperimentsDropdown component with GlitchLink‑style trigger (useGlitchScramble, brackets, chevron), close‑on‑outside/Escape/route‑change
  • “Explore” renamed to “Onchain ID” across navbar (desktop GlitchLink + mobile menu) and home page
  • Explore page — Autonomys DSN changed from external link to static label; Avalanche link color → AVAX red (#E84142); Autonomys label color → brand blue (#4A7AFF); per‑row metadata links blue
  • Autonomys attestation count fix — isAutonomysURI() helper matches both direct gateway URLs (gateway.autonomys.xyz) and proxy URLs (/api/attestation/metadata/{cid}); tooltip now shows correct count
  • Admin notification badge on WalletDropdown — fetches /api/feature‑requests, counts status=open; red pill badge on trigger + inline badge next to Feature Requests nav item; admin‑only, refreshes on route change

v0.5.0

Mar 2, 2026

  • /api/market/listings — parallelized getActiveListingsDetailed + getCollectionSaleEvents via Promise.all; previously sequential
  • Module‑level collection sales cache in OpenSeaService.getCollectionSaleEvents — keyed by slug::afterDateMs, 30‑min TTL; findUsableSalesCache() cross‑key reuse (unfiltered 200‑entry cache serves 50‑entry unfiltered requests)
  • Module‑level GunzScan name cache in resolveTokenMetadata — keyed by contract:tokenId, 24h TTL, 500‑entry max; batch fetch only uncached tokens; logs hit/miss ratio
  • New lib/api/marketCache.ts — MarketReferencePrice / MarketReferencePriceCache interfaces; module‑level singleton with 5‑min TTL; populated by /api/market/listings, consumed by /api/market/reference‑prices
  • New /api/market/reference‑prices route — lightweight GET returning per‑item‑name floor prices from shared cache; no OpenSea calls; s‑maxage=300, stale‑while‑revalidate=60; returns empty if market data not cached
  • MarketReferencePriceData type added to lib/types.ts
  • applyValuationTables() — new optional 5th param marketReference?: MarketReferencePriceData; fills currentLowestListing from market bulk data only when per‑NFT enrichment hasn’t provided one (nft.currentLowestListing === undefined)
  • PortfolioClient.tsx — 4th parallel fetch for /api/market/reference‑prices alongside rarity/comparable/floor; passed to applyValuationTables()
  • NFTDetailPositionCard — expandable waterfall tier list in both Reference Estimate and Market Reality sections; builds waterfallTiers from currentLowestListing/comparableSalesMedian/rarityFloor/floorPrice; best tier marked, others behind toggle

v0.4.9

Mar 2, 2026

  • /api/attestation/status — now returns contractAvaxBalance (contract’s AVAX balance from collected fees) and attestFee (current fee in AVAX); batched via Promise.all with existing totalAttestations query
  • ATTESTATION_ABI extended with owner() view and withdraw() — new exports: withdrawFees(signer) calls contract.withdraw() and waits for receipt; getContractOwner(provider) reads on‑chain owner address
  • OnChainTools admin component — AVAX Earned + Current Fee rows in Live Status; Withdraw button with 5‑state flow (idle/switching/signing/confirming/success); chain switch via ensureAvalancheChain, signer from Dynamic’s primaryWallet.connector.getWalletClient(); tx hash links to Snowtrace on success
  • Admin panel auth fix — production showed empty whitelist/waitlist because NEXT_PUBLIC_ADMIN_SECRET was not set in Vercel; fetch callbacks use data.entries ?? [] which treats 401 as empty data silently

v0.4.8

Mar 1, 2026

  • PortfolioAttestation.sol v3 — attest() now takes explicit `address wallet` as first parameter instead of using msg.sender; enables delegation (wallet A pays, wallet B gets the attestation); require(wallet != address(0)) guard
  • UUPS proxy upgraded on C‑Chain: implementation 0x80A6C9661Fb0fEd1cCEBf568bCb709D548B98358; proxy address unchanged (0xEBE8FD7d40724Eb84d9C888ce88840577Cc79c16)
  • Frontend ABI + submitAttestation() updated to pass wallet param; usePortfolioAttestation hook passes tracked walletAddress (in‑game wallet) as attestation target
  • Attestation flow reordered: chain switch → get signer → fetch blockNumber from signer’s provider (fixes RPC mismatch causing estimateGas failures)
  • Contract ownership transferred to Ledger wallet (0x8ABF…488C); hardhat‑ledger plugin + upgrade script configured for future UUPS upgrades
  • Implementation contract verified on Snowtrace via Routescan API
  • Test suite expanded to 29 tests: delegation (alice pays for bob), multi‑payer, zero‑address rejection

v0.4.7

Mar 1, 2026

  • PortfolioAttestation.sol v2 — configurable attestFee (0.01 AVAX default), owner/withdraw/transferOwnership/setFee; attest() now payable with require(msg.value >= attestFee); totalFeesCollected counter; FeeUpdated + OwnerTransferred events
  • Contract redeployed to Avalanche C‑Chain: 0xf8f5aa3D940009987F02AD92e44A5434Bab748bf
  • Frontend contract layer — ABI updated with payable attest + attestFee() view; submitAttestation() reads fee from contract and sends as tx value; exported getAttestFee() for UI display
  • Metadata URI now routes through gunzscope.xyz/api/attestation/metadata/{cid} instead of gateway.autonomys.xyz — /api/attestation/metadata/[cid] proxy route with 24h immutable cache
  • Test suite expanded to 26 tests: fee enforcement, overpayment, owner withdraw with gas accounting, ownership transfer, fee update, zero‑address guard

v0.4.6

Mar 1, 2026

  • GUN Δ hollow dots on Cost vs Value scatter chart — NFTs without market valuation (no listing, comparable sale, rarity floor, or collection floor) now appear as hollow circles positioned via synthetic floor derived from GUN token appreciation: syntheticFloor = cost × (gunPrice / historicalGunUsd)
  • Hollow dots use profit/loss green/red coloring (not amber) with dashed stems to distinguish from filled market‑valued dots; fixed 4px radius, no quantity scaling
  • Grouped GUN Δ NFTs (×2, ×3) expanded into individual hollow dots with deterministic ±2% cost jitter to prevent visual stacking
  • Legend restructured with MARKET / GUN Δ sections separated by pipe dividers; each section shows filled vs hollow profit/loss indicators
  • portfolioInsights.ts — generateInsights now includes GUN Δ items in total unrealized P&L; added getGunDeltaPnlUsd() helper for Track A currency appreciation; new gun_delta insight type with isNeutral amber styling
  • Insight quantity counting uses nft.quantity ?? 1 instead of array length — grouped ×2 NFTs count as 2 individual items in "below cost basis" and totalTracked denominators
  • InsightsPanel.tsx — gun_delta icon (swap arrows SVG) with amber accent when isNeutral
  • portfolioInsights test suite expanded to 12 tests; added quantity>1 coverage

v0.4.5

Mar 1, 2026

  • Autonomys Auto Drive integration — attestation metadata uploaded to Autonomys DSN via @autonomys/auto‑drive SDK; /api/attestation/upload route accepts JSON payload, returns CID + gateway URL; usePortfolioAttestation uploads before on‑chain tx, falls back to inline data: URI if upload fails
  • On‑Chain Explorer — public /explore page queries PortfolioAttested events from C‑Chain contract; /api/attestation/events route with chunked queryFilter (49k block range per call, public RPC 50k limit) + 5‑min server‑side cache + stale fallback on error; useExplorer hook
  • Explorer UI — stats banner (attestations/wallets/GUN attested), desktop table + mobile cards, Snowtrace tx/address links, Autonomys gateway metadata links, loading skeletons, empty state with portfolio CTA
  • Navbar + sitemap + home page Explorer links — public page, no auth gate; added to isInApp check, desktop GlitchLink, mobile menu, sitemap at hourly/0.7
  • Exported ATTESTATION_ABI and getContractAddress() from lib/attestation/contract.ts for event query reuse

v0.4.4

Mar 1, 2026

  • Seaport v1.6 ABI fix — extractCostFromOrderFulfilled now tries v1.5 ABI first, falls back to v1.6 (which adds address recipient to OrderFulfilled non‑indexed data); previous v1.5‑only decode silently failed on GunzChain’s Seaport, causing tx.value fallback to assign total batch cost to every item
  • NFT detail cache schema v24→v25 — invalidates all stale entries with incorrect batch‑purchase prices
  • On‑chain portfolio attestation — usePortfolioAttestation hook builds Merkle tree of NFT holdings, submits root+totalValue to PortfolioAttestation contract on Avalanche C‑Chain; ensureAvalancheChain() handles wallet_switchEthereumChain / wallet_addEthereumChain
  • ShareDropdown attestation UI — "Attest On‑Chain" button with 7‑state flow (idle/building/switching‑chain/signing/confirming/success/error); existing attestation indicator; Snowtrace tx link on success
  • WalletIdentity passes Dynamic primaryWallet connector + isOwnWallet to ShareDropdown for attestation signing
  • Attestation status API — GET /api/attestation/status reads deployer GUN balance (GunzChain) + AVAX balance (C‑Chain) + totalAttestations from contract
  • AdminPanel OnChainTools — added C‑Chain AVAX balance display
  • Hardhat config — added avalanche mainnet network (chainId 43114) + Routescan etherscan verification; deploy:avalanche npm script

v0.4.3

Feb 28, 2026

  • Konami trial access — Konami code now grants 72‑hour trial whitelist instead of permanent access; WhitelistEntry gains expiresAt DateTime? field (null = permanent, non‑null = trial)
  • getWhitelistStatus() discriminated union — returns { status: permanent | trial | expired | none, expiresAt? }; replaces boolean isWhitelisted() in validate and waitlist/status endpoints
  • Konami API rewrite — checks existing whitelist status before granting; permanent → no‑op, active trial → return existing info, expired → 403 "trial already used", none → creates 72h trial entry + waitlist entry with threshold 1
  • joinWaitlistForTrial() — creates or updates waitlist entry with promotionThreshold: 1 (vs default 3); idempotent downgrade if already on waitlist
  • Tiered referral thresholds — DEFAULT_PROMOTION_THRESHOLD=3, TRIAL_PROMOTION_THRESHOLD=1, EXPIRED_TRIAL_PROMOTION_THRESHOLD=2
  • bumpExpiredTrialThreshold() — lazy idempotent upgrade 1→2 on expired trial detection; called from validate and waitlist/status endpoints
  • promoteFromWaitlist() upsert pattern — converts expired trial to permanent whitelist (sets expiresAt: null) when referral threshold met
  • Validate API trial‑aware responses — { trial: true, expiresAt } for active trials; { trialExpired: true, waitlisted: true } with threshold bump for expired
  • Home page trialExpired redirect — expired trial users redirect to /waitlist?address=xxx&trialExpired=true; active trial stores expiresAt in localStorage
  • KonamiOverlay messaging — "TRIAL ACCESS GRANTED" with "72 hours of full access activated" and "Refer 1 friend to keep it forever"
  • WaitlistClient trialExpired state — reads ?trialExpired=true URL param; shows "Trial Ended" header with dynamic referral count based on per‑entry promotionThreshold

v0.4.2

Feb 28, 2026

  • Ban/reset system — BanEntry model in Prisma; banService with isBanned/banAddress/unbanAddress/resetAddress/listBans; ban guards on /api/access/validate, /api/access/konami, /api/access/reconcile, /api/waitlist/status, and joinWaitlist(); banned users get 403 with { banned: true }
  • Admin PATCH /api/admin/whitelist — { address, action: "ban"|"unban"|"reset", reason? }; ban removes from whitelist + waitlist + blocks re‑enrollment; reset clears whitelist + waitlist without ban (user can rejoin); GET ?view=banned returns paginated ban list
  • AdminPanel ban/reset UI — Ban/Reset buttons on whitelist entries, Ban button on waitlist entries, dedicated Banned Users section with unban capability
  • WaitlistClient banned state — useWaitlist hook returns isBanned; banned users see "ACCESS REVOKED" page with support contact instead of join form
  • ReferralRedirect banned handling — email‑only validate returning { banned: true } shows revoked message instead of redirect
  • KonamiOverlay wallet flow fix — submitting state was never reset after successful wallet submission, permanently disabling the Confirm button in the handle phase; added setSubmitting(false) + initial handle availability check via /api/referral/check‑slug
  • Home page validation refactor — separated wallet and email validation into independent useEffect blocks; wasConnectedOnMount now tracks wallet only (email users always validate); emailValidatingRef prevents duplicate email validation calls
  • Attestation status API — GET /api/attestation/status route stub
  • Hardhat config — updated GunzChain testnet RPC URL; added Fuji (Avalanche C‑Chain testnet) network

v0.4.1

Feb 28, 2026

  • Hero redesign — split title into super‑label ("YOUR OTG" at text‑2xl‑3xl with 0.15em tracking) + dominant scramble headline (text‑6xl‑[104px]); removed "Arsenal" line; purple glow text‑shadow on OTG (40px blur, 0.3 opacity)
  • Scramble words updated — Intelligence, Lore, Legacy, Edge (was Intelligence, Dominance, Advantage, Edge)
  • Hero subtitle rewrite — "The tactical intelligence layer for Off The Grid / Start your legacy, analyze the market, dominate the meta."
  • CTA section hierarchy — "Early access — whitelist only" label above Connect Wallet button; Konami hint below at white/25 opacity; removed redundant "Connect here" link
  • KonamiOverlay v2 — "Ready Player Zero" title with rotating subtitles (3 variants, module‑level counter); custom input accepting EVM/Solana/email with type badge; onSubmit returns Promise<boolean>; confirmed state with "CLEARANCE CONFIRMED" card; auto‑proceeds to Dynamic auth after 2.5s via onProceed callback

v0.4.0

Feb 27, 2026

  • Konami code easter egg — useKonamiCode hook listens for ↑↑↓↓←→←→BA on home page; 2s inactivity timeout resets progress; triggers KonamiOverlay
  • KonamiOverlay tactical scan — full‑screen overlay with hex matrix rain (120ms refresh), horizontal scan line (2.5s linear sweep), corner bracket framing, 5‑line sequential reveal with motion/react stagger
  • Konami whitelist input — after scan animation completes, WalletAddressInput slides in with clip‑path ENTER button; validates via detectChain; ESC or backdrop click to dismiss
  • POST /api/access/konami — accepts { address, email }; attempts promoteFromWaitlist first, falls back to addToWhitelist with "Konami code" label; reconciles email+wallet if both provided

v0.3.9

Feb 27, 2026

  • Email waitlist gate — /api/access/validate accepts { email } alongside { address }; emails stored as email:user@example.com identifier in waitlist/referrer tables; deriveAutoSlug handles email: prefix (username before @)
  • Email‑to‑wallet reconciliation — new POST /api/access/reconcile endpoint; promoted email user connects wallet → whitelists wallet address with reconciled:{email} note; WaitlistClient detects wallet connection and auto‑reconciles
  • WaitlistClient email mode — identifier resolution from wallet OR ?email= search param; "Signed in as" email notice; promotion celebration shows Connect Wallet CTA instead of auto‑redirect for email users
  • useWaitlist type parameter — hook accepts type: "wallet" | "email"; fetches /api/waitlist/status with ?address= or ?email= accordingly
  • Email gate in app/page.tsx — email‑only Dynamic users (no wallet) now go through /api/access/validate instead of bypassing straight to /portfolio; non‑whitelisted emails redirect to /waitlist?email=
  • GlitchText container stability — glitch hover effect no longer resizes the login button; fixed by preserving container dimensions during letter scramble animation
  • SEO: robots.txt — blocks /api/, /admin/, /brand, /roadmap, /strategy; references sitemap.xml
  • SEO: dynamic sitemap — app/sitemap.ts with 13 public pages, priority‑weighted (1.0 home → 0.1 legal)
  • SEO: root layout metadata — Viewport export (themeColor #0A0A0A), metadataBase, title.template "%s | GUNZscope", default OG/Twitter cards, JSON‑LD WebApplication schema
  • SEO: page metadata — added Metadata exports to changelog, updates, credits, privacy, terms, cookies; created layout.tsx wrappers for client‑component pages (market, insanity); noindex on brand/roadmap/strategy
  • SEO: canonical URL on /portfolio layout to prevent ?address= duplicates
  • SEO: alt text on 4 NFT images in scarcity and market pages (was empty string)
  • Admin‑gated /strategy page — 6‑phase strategic roadmap; vertical timeline with phase nodes, pill‑tag items, market scope expansion bar
  • Admin panel Links tab — card grid linking to all admin/internal pages with colored dot indicators
  • Brand page Working Links section — Build Games landing preview link; Waitlist Flow section 06 with gate/state/promotion test panels

v0.3.8

Feb 26, 2026

  • Waitlist redirect fix — paste‑address flow (non‑Dynamic‑SDK users) now passes address as query param to /waitlist; WaitlistClient reads from primaryWallet?.address || searchParams.get(‘address’); previously redirected back to / because no Dynamic wallet was connected
  • Waitlist page Suspense boundary — wrapped WaitlistClient in <Suspense> in app/waitlist/page.tsx; required by Next.js for useSearchParams() during static prerender
  • Waitlist status API auth removed — GET /api/waitlist/status no longer requires Dynamic JWT; position and referral count are non‑sensitive; paste‑address users have no auth token
  • useWaitlist hook auth optional — getAuthToken() now called inside try block; token sent as Authorization header only when available; missing token no longer causes early return that left isLoading stuck at true
  • Reusable WalletAddressInput component — components/ui/WalletAddressInput.tsx; unified chain detection (GunzChain/Solana badge, validation border, hint text) across 5 input locations: home page, admin panel, account page, insanity mode, portfolio search
  • WalletAddressInput validateChain prop — when false, disables red border for non‑matching input; used by Handle Tools where slugs are valid input alongside wallet addresses
  • Admin panel column layout standardization — WhitelistTools, WaitlistTools, ShareLeaderboard all use same toolbar‑at‑top pattern with shrink‑0 pb‑3 mb‑3 border‑b divider; address + action button on same row
  • Stale .next cache diagnosis — dev server was serving compiled chunks with old error text "This address isn’t on the early access list yet." from cached .next/dev/ files while source had been updated

v0.3.7

Feb 23, 2026

  • Referral register GET handler — added try/catch around getReferrerByWallet() DB call; was the only API route handler without error wrapping, causing unhandled throws to return HTML 500 instead of JSON
  • useReferral client hook — added regRes.ok guard before .json() parse; non‑2xx responses now log status + body via console.warn("[Referral]") and show status code in error message instead of generic "Failed to load referral data"
  • Admin‑gated /roadmap page — app/roadmap/page.tsx; converted gunzscope‑blockchain‑architecture.html to React/Tailwind with brand CSS vars, clip‑path corners, proper typography; isAdminWallet() gate with redirect

v0.3.6

Feb 22, 2026

  • Updates page accordion refactor — extracted UPDATES data + UpdateEntry interface to lib/data/updates.ts; page.tsx slimmed from 449 → 48 lines, imports data and delegates to new UpdateTimeline client component
  • UpdateTimeline component — components/updates/UpdateTimeline.tsx; ‘use client’ accordion with useState‑based open set, CSS height transition (200ms ease‑in‑out), chevron rotation; tag:’current’ entry auto‑expanded, others collapsed, multiple can be open simultaneously
  • Push‑to‑main workflow updated — CLAUDE.md and docs/notes/push‑to‑main‑workflow.md now reference lib/data/updates.ts instead of app/updates/page.tsx for user‑facing update entries

v0.3.5

Feb 22, 2026

  • Card/modal market data unification — NFTDetailPositionCard Track B now uses same waterfall as deriveCardData(): marketExitGun → comparableSalesMedian → rarityFloor → currentLowestListing; previously modal only checked marketExitGun + computeMarketInputs (which excluded rarityFloor)
  • Tier‑confidence gating — new trackBIsSalesBased boolean on NFTCardData; gallery cards and list rows only show Track B MARKET line for sales‑based tiers (1‑4: EXACT, VIA SALES, VIA SKIN, VIA WEAPON); statistical proxies (tiers 5‑6: RARITY, FLOOR, LISTED, SIMILAR) suppressed from card display
  • Modal low‑confidence treatment — tiers 5‑6 render "Reference Estimate" card instead of "Market Reality": dimmer styling (opacity‑80), no VS COST row, no MARKET P&L percentage, warning text explaining it’s a proxy not sales data
  • exitTierLabel fallback chain — modal’s Track B exitTierLabel now falls back through VIA SALES → RARITY → LISTED when marketExitTierLabel is null, matching card behavior

v0.3.4

Feb 22, 2026

  • Dual‑track P&L card redesign — NFTDetailPositionCard restructured into two distinct cards: Track A ("Your Deal", lime border) shows GUN token appreciation since purchase; Track B ("Market Reality", purple border) shows market‑based P&L from comparable sales waterfall
  • Cost Basis merged into Track A — standalone COST BASIS section removed; cost basis row now lives inside the Track A card as the first data row, followed by TODAY’S VALUE row, providing a natural reading flow from "what you paid" → "what it’s worth" → "your P&L"
  • P&L hero treatment — both track cards use a border‑top divider to separate data rows from the P&L value, displayed in font‑display 20px bold with profit/loss coloring; italic subtext below explains the number in plain English
  • Label‑left / value‑right row pattern — all data rows use flex justify‑between with shrink‑0 labels (mono 9px uppercase gray) and right‑aligned values (display 14px semibold white tabular‑nums); arrow separator (→) between GUN and USD amounts
  • Track B confidence line — shows data quality indicator (green dot + tier label like "VIA SALES" + sample count), plus ABOVE/BELOW FLOOR pill when market exit differs from collection floor
  • Card styling standardized — bg‑[var(‑‑gs‑dark‑3)] with subtle border, 3px colored left border (lime for Track A, purple for Track B), p‑5 padding, rounded‑lg corners
  • Removed Observed Market Range section — NFTDetailObservedMarketRange component no longer rendered in modal; getPositionOnRange helper removed
  • Admin gate on /brand page — /brand now restricted to admin wallets using existing isAdminWallet() utility; non‑admin users redirected to /

v0.3.3

Feb 22, 2026

  • Tiered valuation waterfall (Track B) — 6‑tier Market Exit estimate per NFT: EXACT (same tokenId), VIA SALES (same baseName), VIA SKIN (same skinDesign), VIA WEAPON (same weapon), SIMILAR (deferred), FLOOR (collection floor)
  • Time‑weighted median — comparable sales weighted by recency (7d = 1.0, 7‑30d = 0.75, 30‑90d = 0.50, 90+ = 0.25); weighted‑median walk instead of simple median
  • Item name parser — new parseItemName() extracts skinDesign and weapon from "X for the Y" naming pattern, enabling Tier 3 and Tier 4 waterfall groupings
  • Pure valuation service — lib/portfolio/valuationService.ts walks waterfall with minimum‑sale‑count gates (1 for EXACT, 2 for all others), returns estimatedGun + tier + tierLabel
  • Waterfall data in comparable‑sales API — /api/opensea/comparable‑sales now returns waterfall (byTokenId, byName, bySkin, byWeapon) alongside existing items; backward‑compatible optional field
  • applyValuationTables enhanced — calls getMarketExitValuation() per NFT, writes marketExitGun, marketExitTier, marketExitTierLabel to NFT objects
  • Track B on gallery cards — grid cards (medium+) and list rows show "~592 GUN · VIA SALES" below existing P&L and ValuationLabel
  • Track B in modal QuickStats — 4th column "Market Exit" shows estimated GUN, USD conversion, tier label, and P&L vs cost basis; grid adapts 3→ 4 columns when data available
  • Scarcity tracking prep — useNFTEnrichmentOrchestrator now tracks max observed mint number per baseName in scarcityMapRef for future Tier 5 matching
  • NFT type extended — 3 new fields: marketExitGun, marketExitTier (1‑6), marketExitTierLabel

v0.3.2

Feb 21, 2026

  • Full‑pagination enrichment — enrichment now defers until all NFT pages are loaded (was firing per‑50‑item page, causing concurrent enrichment races and backward progress jumps on wallets with 50+ NFTs)
  • Generation‑guarded enrichment — startEnrichment increments a generation counter; all state updates (setProgress, setEnrichedNFTs, updateCallback, setIsEnriching) check gen === generationRef.current before writing, preventing stale enrichments from wallet switches
  • Enrichment diagnostic summary — console.info after completion logs total/cached/fresh/failed counts, date/costGUN/costUSD/listing resolution percentages, free transfer count, and venue breakdown
  • Incremental refresh — handleRefresh uses new invalidateListingPrices() instead of clearWalletCache(); only clears listingFetchedAt/currentLowestListing/currentHighestListing on each cached entry, preserving all acquisition data
  • Removed groupNFTsByMetadata import from PortfolioClient — handleLoadMoreNFTs now passes mergedNFTs (already grouped) directly to startEnrichment instead of re‑grouping per page
  • cumulativeBaseRef reset — new startEnrichment calls reset cumulativeBaseRef to 0 alongside generation bump, preventing stale cumulative offsets

v0.3.1

Feb 21, 2026

  • Server‑side GUN price history cache — new GunPriceHistory Prisma model stores confirmed historical GUN/USD rates in Neon PostgreSQL; shared across all users so the first person to resolve a date’s price populates it for everyone
  • Waterfall tier 2: server cache — resolveHistoricalGunPrice now checks the shared server table between localStorage and CoinGecko (3s timeout), with write‑through to localStorage on hit and fire‑and‑forget write‑back on CoinGecko/DefiLlama success
  • GET /api/gun‑price/history — public endpoint with CDN caching (1h fresh, 24h stale‑while‑revalidate); 404s cached for 5 min to avoid hammering DB for missing dates
  • POST /api/gun‑price/history — validated write endpoint with ATH guard, confidence‑based upgrade logic (won’t overwrite daily with estimated), rejects estimated prices from shared table
  • "Synced X ago" indicator — ValueHeader shows when the portfolio was last loaded from server cache, with staleness coloring (>24h = brighter gray)
  • Manual refresh button — spinning refresh icon next to the synced timestamp clears localStorage cache and re‑triggers full wallet fetch + enrichment
  • Refresh disabled during enrichment — button grays out and spins while NFT enrichment is active to prevent redundant requests

v0.3.0

Feb 21, 2026

  • Modal P&L reorganization — separated market valuation from GUN appreciation into two distinct P&L stories: market-based (via listings/sales/floor) when available, xGUN fallback otherwise
  • Unified P&L computation — QuickStats UNREALIZED and YOUR POSITION P&L now always agree (both use market-first, xGUN fallback)
  • Valuation method labels — QuickStats and YOUR POSITION show specific source: VIA LISTING, VIA SALES, VIA FLOOR, or GUN Δ
  • GUN Based Performance sub-section — when market data drives headline P&L, a separate section shows pure GUN token appreciation with explanatory narrative
  • CoinGecko fetch guard — modal acquisition pipeline skips redundant historical price fetches when enrichment data already provides confirmed USD values
  • Valuation method badges on gallery cards — 6-tier taxonomy (LISTED, SALES, RARITY, FLOOR, COST, UNLISTED) shown on NFT cards
  • Hidden redundant GUN @ line — cost basis section no longer shows "GUN @ $X.XXXX at time of purchase" when GUN Based Performance section is visible

v0.2.9

Feb 21, 2026

  • xGUN P&L formula — PnL now purely reflects GUN/USD price appreciation: Y = historicalGunPrice, Z = currentGunPrice, P&L = costGun × (Z‑Y). Removed market‑data waterfall (listing/comparable‑sales/rarity‑floor) from cards, modal, sort, and portfolio summary
  • Removed pnlSource labels — "vs listing" / "vs sales" / "vs floor" badges no longer appear on NFT cards since PnL is now single‑source
  • OpenSea event_timestamp fix — 4 parse sites were treating Unix seconds as milliseconds, producing dates in January 1970 and triggering $0.0776 fallback prices
  • Stale closure overwrite fix — async modal loadItemDetails captured resolvedAcquisitions at effect start time; cache‑rendered data was overwritten ~1s later. Fixed via resolvedAcquisitionsRef pattern in both NFTDetailModal and useNFTAcquisitionPipeline
  • Transfer chain tracing fix — buildCandidateFromHoldingRaw now uses senderAcquiredAtIso, senderVenue, and senderTxHash when using sender cost data, instead of the transfer date/venue
  • Transaction fee extraction — txFeeGun and senderTxFeeGun computed from receipt.gasUsed × receipt.gasPrice in avalanche.ts; propagated through ResolvedAcquisition, selectBestAcquisition, and all candidate builders
  • Gas fees display — YOUR POSITION section in NFTDetailModal shows purchase and transfer gas fees when available
  • Server cache hydration sanitization — PortfolioClient strips legacy purchasePriceUsd values from server‑cached NFTs when purchasePriceUsdEstimated !== false
  • CoinGecko ATH sanity check — /api/price/history rejects prices above $0.12 (GUN ATH ~$0.115) and logs a warning
  • Stale Next.js server cache bypass — /api/price/history temporarily switched to cache: 'no‑store' to purge incorrect CoinGecko historical data (revert to revalidate: 86400 after confirmation)
  • Enrichment trust guard — modal no longer overwrites confirmed purchasePriceUsd (purchasePriceUsdEstimated === false) with its own recomputation
  • Cache schema v24 — full client‑side re‑enrichment forced after CoinGecko data correction and server cache purge
  • MetaMask fallback — main page wallet connect falls back to MetaMask deep link when Dynamic SDK fails to trigger wallet
  • wGUN acquisition support — enrichment orchestrator handles wGUN‑based purchases for cost extraction
  • useNftPnL hook rewrite — portfolio summary P&L now uses xGUN formula instead of floor‑based calculation
  • PnL sort rewrite — useNFTGalleryFilters pnl‑desc sort uses xGUN unrealized USD gain with currentGunPrice threading

v0.2.8

Feb 19, 2026

  • Historical price CORS fix — new /api/price/history server‑side proxy routes CoinGecko historical price requests through the server, fixing silent CORS failure that left purchasePriceUsd undefined on all client‑side lookups
  • 14‑day sparkline — /api/price/gun now fetches 14d market_chart alongside 7d sparkline; PriceData type extended with sparkline14d; bootstrap and performance hooks prefer 14d data
  • Portfolio history backwards extension — bootstrapPortfolioHistory can now prepend synthetic points from the sparkline when the sparkline reaches further back than stored history, with 30‑min buffer gap and ~24‑point sampling
  • On‑chain cost extraction for transfers — acquisition pipeline now captures costGunFromChain for cross‑wallet transfers with wGUN payment, calculates USD from historical GUN price
  • Universal GUN→USD fallback — any item with finalPurchasePriceGun > 0 and no USD value gets historical price lookup as a last‑resort conversion
  • Transfer cost basis propagation — NFTDetailModal costBasisGun now falls through to traced original purchase price for TRANSFER acquisitions instead of always returning null
  • Enrichment cache invalidation — marketplace purchases (opensea, in_game_marketplace) with missing price are treated as incomplete and retried on next enrichment cycle
  • USD‑first acquisition card — NFTDetailAcquisitionCard shows $USD as primary line with GUN as secondary when USD is available, for both decode cost and purchase price sections
  • Chart tooltip dynamic positioning — BackdropChart tooltip now renders above the point when point is in lower half of chart, below when in upper half, with hoverY null‑check guard
  • ValueHeader pointer‑events — elements with [title] attribute now receive pointer‑events for native tooltip hover
  • Item origins expansion — new categories early_access, reward, content_pack; Pioneer Set, Player Zero Set, Prankster Set, Anarchist Set reclassified; Going Ape Shit, Hump for Dominance added to Aperil Fools
  • Empty wallet state — portfolios with 0 GUN and 0 NFTs show centered "Nothing Detected" message with inline search bar + Leaderboard/Market CTAs instead of empty $0.00 dashboard
  • CLAUDE.md — documented Production Whitelist API endpoints and admin workflow

v0.2.7

Feb 18, 2026

  • Item origin registry scaled to 35+ releases — added Enforcer BP, Pink Fury BP, Mr Fuckles BP, Hopper Pilot BP, Mad Biker BP, Neotokyo event, Trick Treat or Die event expansion, plus dozens of individual items
  • AIRDROP label — any NFT with a known origin and sub‑1‑GUN acquisition cost now displays "AIRDROP" instead of "0 GUN"; unknown‑origin items keep their raw price
  • Contains match rules — Don DeLulu CP and Mrs Crackhead Santa CP items now use fuzzy‑contains matching to catch blockchain name variations that exact‑match missed
  • ItemRelease description field — releases can now carry rich event descriptions (lore, mechanics, reward details); Trick, Treat or Die is the first with full event metadata
  • Trick, Treat or Die event badge — Halloween items now show the actual event name instead of generic "Halloween"
  • Loading messages refreshed — three new OTG‑themed quips in the portfolio loading rotation
  • Welcome popup streamlined — merged feedback paragraphs, removed redundant bug‑report CTA; single "Got it, let me explore" button

v0.2.6

Feb 18, 2026

  • Search bar validation — invalid addresses (e.g. trailing special characters) now show inline chain detection badge and hint text instead of silently failing; "Go" button properly gates on address validity
  • Wallet actions relocated — Watch and Portfolio buttons moved from search dropdown to the wallet identity bar, where users have context after viewing a wallet’s data
  • Item origin database — curated lookup table mapping NFT items to their release origin (Battle Pass, Content Pack, Event) with 26 releases catalogued
  • Navbar dropdown contrast — darker background and stronger shadow on wallet dropdown so it no longer blends into the page behind it

v0.2.5

Feb 18, 2026

  • Hook extraction — five custom hooks pulled from PortfolioClient (loading messages, chart milestone gating, portfolio snapshots, wallet search actions, multi‑wallet gallery), reducing component from 1,203 to 1,012 lines
  • Lazy‑load below‑fold — ChartInsightsRow and NFTGallery dynamically imported with skeleton placeholders for faster initial paint
  • Accessibility — skip‑to‑content link, aria‑live on metrics grid, ARIA tablist with roving tabIndex and arrow‑key navigation on chart tabs
  • Wallet switch race fix — request ID ref pattern on handleWalletSubmit discards stale responses when rapidly switching wallets
  • Gallery card stagger — first 24 NFT cards fade‑in with translateY stagger animation (30ms grid, 20ms list); cards beyond 24 render instantly

v0.2.4

Feb 17, 2026

  • Cost basis sparkline — dashed white line on the portfolio value chart shows historical cost basis alongside market value, visually revealing unrealized P&L gap
  • Star‑appear animation — chart dots fade in like stars appearing in a night sky as NFTs enrich, growing from tiny pinpoints to full size over ~10 seconds
  • Random dot stagger — new dots appear in randomized order across the chart (Fisher‑Yates shuffle) instead of chronologically, for a more organic night‑sky feel
  • "Under Active Dev" label — moved from chart tab headers into the Insights section for a cleaner chart UI

v0.2.3

Feb 17, 2026

  • Share image redesign — tactical HUD aesthetic OG image with dot‑grid background, corner brackets, gradient accent line, metric cards for GUN balance / NFTs / cost basis
  • Download portfolio image — new button in share dropdown fetches the OG image and saves it as PNG
  • Cost basis in share links — GUN spent on NFTs is now stored in share snapshots and displayed on the OG card
  • Chart zoom fix — zooming no longer pushes dots off‑screen; scales use base width so data positions stay stable
  • Zoom to cursor — Shift+scroll zooms toward the mouse pointer; zoom buttons auto‑scroll to the densest data cluster
  • Gallery performance — React.memo on card components + memoized card data prevents unnecessary re‑renders
  • Search debounce — 200ms debounce on gallery search input eliminates lag from keystroke‑driven re‑filtering
  • content‑visibility: auto on NFT cards — browser skips painting offscreen cards, reducing compositor work on large galleries
  • Enrichment speed — batch delay reduced from 800ms to 200ms, priority window expanded from 12 to 18 above‑fold NFTs

v0.2.2

Feb 17, 2026

  • Chart crossfade — Timeline and Cost‑vs‑Value charts stay mounted simultaneously, opacity crossfade via motion/react eliminates DOM rebuild flicker
  • Aligned chart dimensions — embedded Timeline now matches Scatter plot margins and height so content doesn’t shift during crossfade
  • Multi‑wallet NFT total fix — portfolio wallets that load after the primary wallet now correctly update the NFT count
  • NFT count includes duplicates — gallery item count sums quantities instead of unique token count
  • Holdings card cleanup — removed per‑category GUN amounts, combined Bought+Minted on one row, consistent vertical spacing
  • Navbar wallet dropdown — converted from slide‑out panel to positioned dropdown with spring animation, click‑outside and ESC‑key close
  • Navbar layout fix — three‑group flex prevents wallet address width changes from shifting navigation links
  • Transition demo — interactive comparison of four chart transition styles added to brand page

v0.2.1

Feb 17, 2026 · 6:30 AM EST

  • Spring‑physics animations site‑wide — all panels, modals, drawers, and accordions now use motion/react with consistent spring config
  • Custom green arrow cursor — replaces default pointer everywhere, zero‑lag tracking via direct mousemove transform
  • Drop‑panel close fix — resolved race condition where trigger button click re‑opened panel immediately after click‑outside closed it
  • Active‑state indicators — wallet‑switcher and share trigger icons stay green while their panels are open
  • Navbar layout stability — three‑group flex prevents wallet address from shifting navigation links
  • BreakdownDrawer accordion — smooth height expand/collapse replaces instant show/hide
  • UnlockBanner trust section — spring‑animated height reveal instead of instant toggle
  • WeaponLabDrawer exit animation — drawer now slides out instead of vanishing on close
  • ConnectPromptModal entrance — scale + fade spring animation replaces no‑animation mount

v0.2.0

Feb 16, 2026 · 5:48 AM EST

  • wGUN cost extraction fix — OpenSea offer fills now resolve acquisition cost from ERC‑20 receipt logs
  • Offer fill detection — NFTs acquired via pre‑signed OpenSea offers show "OpenSea (Offer)" as source
  • Hardcoded wGUN contract address as constant — no longer depends on env var for cost extraction

v0.1.9

Feb 16, 2026 · 3:21 AM EST

  • Bundle diet — removed 15 unused dependencies (210 packages), including Nivo, GSAP, tsparticles, force‑graph, reaviz
  • Replaced axios with native fetch across all API services (−30 KB)
  • Replaced framer‑motion with a single CSS keyframe for page transitions (−45 KB)
  • Code‑split 13 heavy components via next/dynamic — charts, modals, debug panels load on demand
  • Deferred PostHog initialization to after hydration for faster first paint
  • Added optimizePackageImports for visx, ethers, posthog‑js — tree‑shakes unused exports
  • AVIF image format enabled site‑wide (20‑50% smaller than WebP on supported browsers)
  • Converted barrel‑file imports to direct imports in key components for better tree‑shaking

v0.1.8

Feb 16, 2026 · 1:10 AM EST

  • Market page — search all active OpenSea listings, drill into individual items with buy links
  • Scarcity upgrades — quality badges, Best Deal sort, price range filter, cross‑links to Market
  • OpenSea listing coverage tripled — fetches up to 3,000 listings (was 1,000)
  • PnL scatter plot redesign — gradient stems, lighter grid, boosted zone labels, legend row, bordered data strip
  • Sqrt‑aware Y‑axis ticks — labels evenly spaced in visual space instead of bunching at the bottom
  • Chart height increase — both charts get more vertical breathing room
  • Fixed zoom height jump — switching between charts no longer causes jarring layout shift
  • Quality metadata pipeline — GunzScan rarity trait extracted and propagated end‑to‑end

v0.1.7

Feb 15, 2026 · 8:23 PM EST

  • Acquisition Timeline — log‑scale Y‑axis with curated tick marks for better dot distribution
  • Data‑driven dot entrance animation — new dots materialize as enrichment discovers them
  • Portfolio sparkline stability — snapshots only record after enrichment completes, eliminating jagged reloads
  • Backdrop sparkline clipping fix — increased top margin and enforced minimum container height
  • Hydration fix — loading text no longer mismatches between server and client renders
  • CSS shorthand/longhand conflict resolved in chart metadata card and insights border
  • Chart zoom no longer inflates container height — fixed‑height chart area with hidden scrollbar
  • ShareDropdown cleanup — removed dead code, simplified conditional logic
  • SimpleMetrics and usePortfolioSummaryData reduced by ~700 lines of dead code
  • PnLScatterPlot simplified — removed unused tooltip state and redundant computations

v0.1.6

Feb 14, 2026 · 8:57 PM EST

  • Social sharing — Share on X, Discord, or copy link with rich OG preview cards showing portfolio value, P&L, and NFT count
  • Valuation waterfall upgrade — per‑item listing > comparable sales median > rarity‑tier floor > cost basis
  • Insights panel expansion — unrealized P&L, most valuable, biggest loss (5 insight types total)
  • Acquisition Timeline chart — interactive visx timeline of NFT purchases by venue and date
  • P&L Scatter Plot promoted to main portfolio view (was insanity‑only)
  • All charts now use full valuation waterfall (listing > comparable > rarity > floor)
  • Chart visual overhaul — sqrt scales, glow effects, gradient zones, smarter axis formatting

v0.1.5

Feb 14, 2026 · 4:05 PM EST

  • NFT valuation waterfall: per‑item listings, rarity‑tier floors, comparable sales medians
  • Dual‑value display — cost basis vs market value side by side
  • Per‑item P&L with visx interactive charts
  • Feature request system with community voting, bug reports, and screenshot attachments
  • Collapsible request cards with lightbox image viewer
  • UXR welcome popup for new testers with onboarding guidance
  • Crosshair cursor performance: removed backdrop‑blur from overlays, cached DOM walks, targeted cursor rules
  • Display name support for wallet profiles
  • Portfolio history bootstrap with sparkline seeding
  • Hybrid portfolio: aggregated summary + per‑wallet gallery with SWITCH
  • Read‑only portfolio access via ?address= param — browse any wallet without logging in
  • Migrate from SQLite to Neon PostgreSQL — full read/write in production

v0.1.4

Feb 13, 2026 · 4:56 AM EST

  • Scramble‑decode loading text matching home hero animation
  • 10pm Easter egg — because someone had to
  • NFT Holdings sparkline toggle on first wallet search
  • Server‑side RPC proxy for reliable production wallet loading
  • View transitions with framer‑motion page animations
  • Wallet address help panel for new users
  • Auto‑populate credits from completed feature requests

v0.1.3

Feb 12, 2026 · 4:35 PM EST

  • NFT sparkline toggle with historical hover counts
  • Dynamic Labs SDK upgrade (4.59.1 → 4.61.2)
  • Crosshair cursor performance fix
  • UX polish: onboarding flow, nav, login gate, multi‑admin
  • Grouped NFT visual overhaul: dynamic rarity accents, mergeIntoGroups
  • Decode cost extraction fix for relayer‑submitted transactions

v0.1.2

Feb 11, 2026 · 10:37 PM EST

  • GunzScan API migration with infinite scroll
  • Ambient backdrop sparkline with smooth curves and overlay toggles
  • Auto‑load portfolio on wallet connect
  • Component decomposition: Navbar, PortfolioSummaryBar, scarcity, feature‑requests
  • Wallet dropdown enhancements + identity bar refactor
  • SEO metadata for all pages
  • Standardized API response types

v0.1.1

Feb 10, 2026 · 11:47 PM EST

  • Confidence indicator overhaul with enrichment reliability fixes
  • Insanity Mode toggle + clip‑corner card design
  • Sticky accent lines and container transparency polish
  • Email auth flow + adaptive onboarding
  • Scarcity page UX improvements
  • Disconnect UX and network switch visibility fixes

v0.1.0

Feb 9, 2026 · 7:20 PM EST

  • Public feature request and management system
  • Dynamic wallet onboarding with styled connect flow
  • Redesigned footer with social links
  • Leaderboard page with access gate and active wallet display
  • Nav glitch effect + gallery refactor

v0.0.3

Feb 5–8, 2026

  • NFTDetailModal decomposition (3,163 → 1,069 lines via 4 extracted hooks)
  • Portfolio context + hooks architecture refactor
  • useWalletDataFetcher, useNFTEnrichmentOrchestrator, useWalletAggregation hooks
  • WaffleChart composition visualization with stagger animation
  • Marketplace price enrichment pipeline
  • Portfolio three‑section layout with Simple/Detailed toggle

v0.0.2

Jan 31 – Feb 1, 2026

  • NFT P&L pipeline with historical prices, rarity floors, and comparable sales
  • Interactive rarity filter pills in NFT gallery
  • YOUR POSITION section in NFT detail modal
  • Floor price enrichment + metadata caching
  • Security vulnerability fixes (31 → 9)
  • Functional tier support from raw metadata
  • Native GUN balance fetch fix

v0.0.1

Genesis

Jan 19–22, 2026

  • Initial release — GUNZscope is born
  • Multi‑chain portfolio tracker for Off The Grid
  • NFT Armory/Lab feature with weapon compatibility
  • Acquisition truth layer using RPC‑only fingerprints
  • Progressive accounts implementation
  • OpenSea + in‑game marketplace data integration

Built for the Off The Grid community · Not affiliated with GUNZILLA Games

© 2026 GUNZscope
·Terms·Privacy·Credits··

Built with ♥ by CRYPTOHAKI for the Gunzilla community.