From 481e020d66e1910488a4cee80eefb040b304d78f Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Wed, 22 Apr 2026 10:46:48 +0900 Subject: [PATCH] [#928] Switch from MCap to FDV (price * max supply) on /airdrop and /token - Add PLOT_MAX_SUPPLY = 1,000,000 constant - Cron: store FDV (price * 1M) in mcap_usd column instead of MCap - useTokenInfo: compute FDV (price * max supply) instead of MCap - /token page: label "FDV" instead of "Market Cap" - Status API: rename currentMcap -> currentFdv in response - CampaignHero/MilestoneTrack: update StatusData interfaces - WeeklySnapshots: label "FDV" instead of "MCap" FDV = PLOT USD price * 1,000,000 (max supply). No Mint Club MCap API. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/contracts/constants.ts | 3 +++ package.json | 2 +- src/app/api/airdrop/status/route.ts | 14 +++++++------- src/app/api/cron/airdrop-price/route.ts | 11 ++++++----- src/app/token/page.tsx | 6 +++--- src/components/airdrop/CampaignHero.tsx | 2 +- src/components/airdrop/MilestoneTrack.tsx | 2 +- src/components/airdrop/WeeklySnapshots.tsx | 2 +- src/hooks/useTokenInfo.ts | 8 ++++---- 9 files changed, 27 insertions(+), 23 deletions(-) diff --git a/lib/contracts/constants.ts b/lib/contracts/constants.ts index 92b6be0f..d15c7cf9 100644 --- a/lib/contracts/constants.ts +++ b/lib/contracts/constants.ts @@ -47,6 +47,9 @@ export const PLOT_TOKEN = (IS_TESTNET /** Human-readable label for the reserve token */ export const RESERVE_LABEL = "PLOT"; +/** PLOT max supply (bonding curve cap) — used for FDV calculation */ +export const PLOT_MAX_SUPPLY = 1_000_000; + // --------------------------------------------------------------------------- // Supported Zap input tokens (Base) // --------------------------------------------------------------------------- diff --git a/package.json b/package.json index b3a5cfb9..67bd4b56 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plotlink", - "version": "0.1.34", + "version": "0.1.35", "private": true, "workspaces": [ "packages/*" diff --git a/src/app/api/airdrop/status/route.ts b/src/app/api/airdrop/status/route.ts index d89a14e8..b2bcb994 100644 --- a/src/app/api/airdrop/status/route.ts +++ b/src/app/api/airdrop/status/route.ts @@ -41,28 +41,28 @@ export async function GET() { } const totalParticipants = uniqueAddresses.size; - // Milestone status - const currentMcap = latestPrice?.mcap_usd ?? 0; + // Milestone status — mcap_usd column now stores FDV (price × max supply) + const currentFdv = latestPrice?.mcap_usd ?? 0; const milestones = { bronze: { mcap: AIRDROP_CONFIG.MILESTONES.BRONZE.mcap, pct: AIRDROP_CONFIG.MILESTONES.BRONZE.pct, - reached: currentMcap >= AIRDROP_CONFIG.MILESTONES.BRONZE.mcap, + reached: currentFdv >= AIRDROP_CONFIG.MILESTONES.BRONZE.mcap, }, silver: { mcap: AIRDROP_CONFIG.MILESTONES.SILVER.mcap, pct: AIRDROP_CONFIG.MILESTONES.SILVER.pct, - reached: currentMcap >= AIRDROP_CONFIG.MILESTONES.SILVER.mcap, + reached: currentFdv >= AIRDROP_CONFIG.MILESTONES.SILVER.mcap, }, gold: { mcap: AIRDROP_CONFIG.MILESTONES.GOLD.mcap, pct: AIRDROP_CONFIG.MILESTONES.GOLD.pct, - reached: currentMcap >= AIRDROP_CONFIG.MILESTONES.GOLD.mcap, + reached: currentFdv >= AIRDROP_CONFIG.MILESTONES.GOLD.mcap, }, diamond: { mcap: AIRDROP_CONFIG.MILESTONES.DIAMOND.mcap, pct: AIRDROP_CONFIG.MILESTONES.DIAMOND.pct, - reached: currentMcap >= AIRDROP_CONFIG.MILESTONES.DIAMOND.mcap, + reached: currentFdv >= AIRDROP_CONFIG.MILESTONES.DIAMOND.mcap, }, }; @@ -72,7 +72,7 @@ export async function GET() { timeRemainingDays: Math.ceil(remainingMs / (1000 * 60 * 60 * 24)), timeElapsedPercent: totalMs > 0 ? Math.min(100, Math.round((elapsedMs / totalMs) * 100)) : 0, poolAmount: AIRDROP_CONFIG.POOL_AMOUNT, - currentMcap, + currentFdv, latestPriceUsd: latestPrice?.price_usd ?? null, milestones, totalPointsEarned, diff --git a/src/app/api/cron/airdrop-price/route.ts b/src/app/api/cron/airdrop-price/route.ts index c7b2b746..1c3f34af 100644 --- a/src/app/api/cron/airdrop-price/route.ts +++ b/src/app/api/cron/airdrop-price/route.ts @@ -10,7 +10,7 @@ import { formatUnits } from "viem"; import { createServerClient } from "../../../../../lib/supabase"; import { getPlotUsdPrice } from "../../../../../lib/usd-price"; import { publicClient } from "../../../../../lib/rpc"; -import { PLOT_TOKEN } from "../../../../../lib/contracts/constants"; +import { PLOT_TOKEN, PLOT_MAX_SUPPLY } from "../../../../../lib/contracts/constants"; import { erc20Abi } from "../../../../../lib/price"; function verifyCron(req: Request): boolean { @@ -67,13 +67,14 @@ export async function GET(req: Request) { return NextResponse.json({ skipped: true, reason: "Supply fetch failed" }, { status: 200 }); } - const mcapUsd = priceUsd * supplyFormatted; + // Store FDV (price × max supply) instead of MCap for milestone tracking + const fdvUsd = priceUsd * PLOT_MAX_SUPPLY; const { error } = await supabase.from("pl_daily_prices").insert({ recorded_at: todayUtc, price_usd: priceUsd, supply: supplyFormatted, - mcap_usd: mcapUsd, + mcap_usd: fdvUsd, // column stores FDV (price × max supply) }); if (error) { @@ -81,6 +82,6 @@ export async function GET(req: Request) { return NextResponse.json({ error: "Insert failed" }, { status: 500 }); } - console.info(`[airdrop-price] Snapshot recorded: date=${todayUtc} price=${priceUsd} supply=${supplyFormatted} mcap=${mcapUsd}`); - return NextResponse.json({ recorded: true, date: todayUtc, priceUsd, supply: supplyFormatted, mcapUsd }); + console.info(`[airdrop-price] Snapshot recorded: date=${todayUtc} price=${priceUsd} supply=${supplyFormatted} fdv=${fdvUsd}`); + return NextResponse.json({ recorded: true, date: todayUtc, priceUsd, supply: supplyFormatted, fdvUsd }); } diff --git a/src/app/token/page.tsx b/src/app/token/page.tsx index 1a40ed06..fbd2a036 100644 --- a/src/app/token/page.tsx +++ b/src/app/token/page.tsx @@ -119,7 +119,7 @@ export default function TokenPage() {

Token Information

- {/* Stats Grid — Price + Market Cap */} + {/* Stats Grid — Price + FDV */}
Price
@@ -142,12 +142,12 @@ export default function TokenPage() { )}
-
Market Cap
+
FDV
{tokenInfoLoading ? (
) : tokenInfo ? (
- ${formatNumber(tokenInfo.marketCap)} + ${formatNumber(tokenInfo.fdv)}
) : (
diff --git a/src/components/airdrop/CampaignHero.tsx b/src/components/airdrop/CampaignHero.tsx index 3ec33b31..7f9fa321 100644 --- a/src/components/airdrop/CampaignHero.tsx +++ b/src/components/airdrop/CampaignHero.tsx @@ -9,7 +9,7 @@ interface StatusData { timeRemainingDays: number; timeElapsedPercent: number; poolAmount: number; - currentMcap: number; + currentFdv: number; latestPriceUsd: number | null; milestones: { bronze: { mcap: number; pct: number; reached: boolean }; diff --git a/src/components/airdrop/MilestoneTrack.tsx b/src/components/airdrop/MilestoneTrack.tsx index f5840386..95be3670 100644 --- a/src/components/airdrop/MilestoneTrack.tsx +++ b/src/components/airdrop/MilestoneTrack.tsx @@ -6,7 +6,7 @@ import { formatUsdValue } from "../../../lib/usd-price"; interface StatusData { poolAmount: number; - currentMcap: number; + currentFdv: number; latestPriceUsd: number | null; milestones: { bronze: { mcap: number; pct: number; reached: boolean }; diff --git a/src/components/airdrop/WeeklySnapshots.tsx b/src/components/airdrop/WeeklySnapshots.tsx index 3735a6f8..d6b463d0 100644 --- a/src/components/airdrop/WeeklySnapshots.tsx +++ b/src/components/airdrop/WeeklySnapshots.tsx @@ -93,7 +93,7 @@ export function WeeklySnapshots() { {snap.newReferrals}
- MCap:{" "} + FDV:{" "} {formatUsdValue(snap.mcapStart)} → {formatUsdValue(snap.mcapEnd)} {" "} diff --git a/src/hooks/useTokenInfo.ts b/src/hooks/useTokenInfo.ts index 901e541e..8d5eb3cc 100644 --- a/src/hooks/useTokenInfo.ts +++ b/src/hooks/useTokenInfo.ts @@ -3,7 +3,7 @@ import { useQuery } from "@tanstack/react-query"; import { formatEther } from "viem"; import { browserClient } from "../../lib/rpc"; -import { PLOT_TOKEN, MCV2_BOND, HUNT } from "../../lib/contracts/constants"; +import { PLOT_TOKEN, MCV2_BOND, HUNT, PLOT_MAX_SUPPLY } from "../../lib/contracts/constants"; const USDC_ADDRESS = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" as const; const ONEINCH_SPOT_PRICE_AGGREGATOR = "0x00000000000D6FFc74A8feb35aF5827bf57f6786" as const; @@ -44,7 +44,7 @@ const ERC20_SUPPLY_ABI = [ export interface TokenInfo { price: number; - marketCap: number; + fdv: number; totalSupply: number; priceChange24h: number | null; } @@ -88,7 +88,7 @@ export function useTokenInfo() { ]); const totalSupply = Number(formatEther(supply)); - const marketCap = price * totalSupply; + const fdv = price * PLOT_MAX_SUPPLY; // 24h price change via block diff (~43200 blocks = 1 day on Base @ 2s) let priceChange24h: number | null = null; @@ -115,7 +115,7 @@ export function useTokenInfo() { // Token may not have existed 24h ago } - return { price, marketCap, totalSupply, priceChange24h } satisfies TokenInfo; + return { price, fdv, totalSupply, priceChange24h } satisfies TokenInfo; }, staleTime: 5 * 60 * 1000, refetchInterval: 5 * 60 * 1000,