diff --git a/apps/web/src/hooks/useCampaigns.ts b/apps/web/src/hooks/useCampaigns.ts index 7bb18c7..295f0f5 100644 --- a/apps/web/src/hooks/useCampaigns.ts +++ b/apps/web/src/hooks/useCampaigns.ts @@ -2,6 +2,8 @@ import { useAccount, useReadContracts } from "wagmi"; import { hoodquidityVaultsAbi } from "@/lib/abi/HoodquidityVaults"; import { ACTIVE_CHAIN_ID, + CAMPAIGN_SEED, + HAS_LIVE_VAULT, getActiveDeployment, USD_DECIMALS, type MarketMeta, @@ -30,6 +32,35 @@ export function rewardsRemainingUsd(c: CampaignState, nowSec: number): number { const scale = 10 ** USD_DECIMALS; +/** How far into the launch schedule the seeded campaigns sit: 2d 4h, so a + * 14-day campaign reads ~11d 20h remaining, past the 1-day boost window. */ +const SEED_ELAPSED_SEC = 187_200; + +/** Build a campaign row from the static seed, for the no-live-vault build. */ +function seedCampaign(meta: MarketMeta, now: number): CampaignState { + const s = CAMPAIGN_SEED[meta.campaignId]; + const start = now - SEED_ELAPSED_SEC; + const end = start + s.durationDays * 86_400; + const boostWindow = s.boostWindowDays * 86_400; + let status: CampaignState["status"] = "active"; + if (now < start) status = "upcoming"; + else if (now < start + boostWindow) status = "boost"; + else if (now >= end) status = "ended"; + return { + meta, + tvlUsd: s.tvlUsd, + targetDepthUsd: s.targetDepthUsd, + rewardBudgetUsd: s.rewardBudgetUsd, + start, + end, + boostWindow, + boostBps: s.boostBps, + durationDays: s.durationDays, + activeLPs: s.activeLPs, + status, + }; +} + export function useCampaigns() { const deployment = getActiveDeployment(); @@ -41,10 +72,21 @@ export function useCampaigns() { args: [BigInt(m.campaignId)], chainId: ACTIVE_CHAIN_ID, })), - query: { refetchInterval: 15_000 }, + query: { enabled: HAS_LIVE_VAULT, refetchInterval: 15_000 }, }); const now = Math.floor(Date.now() / 1000); + + // Showcase build with no reachable vault: render the seeded launch set. + if (!HAS_LIVE_VAULT) { + return { + campaigns: deployment.markets.map((m) => seedCampaign(m, now)), + isLoading: false, + error: null, + deployment, + }; + } + const campaigns: CampaignState[] | undefined = data?.map((res, i) => { const meta = deployment.markets[i]; const c = res.result as @@ -122,7 +164,7 @@ export function usePositions() { chainId: ACTIVE_CHAIN_ID, } as const, ]), - query: { enabled: !!address, refetchInterval: 15_000 }, + query: { enabled: HAS_LIVE_VAULT && !!address, refetchInterval: 15_000 }, }); const positions = deployment.markets.map((meta, i) => { @@ -174,7 +216,7 @@ export function usePosition(campaignId: number) { chainId: ACTIVE_CHAIN_ID, }, ], - query: { enabled: !!address, refetchInterval: 15_000 }, + query: { enabled: HAS_LIVE_VAULT && !!address, refetchInterval: 15_000 }, }); const raw = data?.[0]?.result as diff --git a/apps/web/src/lib/contracts.ts b/apps/web/src/lib/contracts.ts index 0324ed1..4008829 100644 --- a/apps/web/src/lib/contracts.ts +++ b/apps/web/src/lib/contracts.ts @@ -98,6 +98,36 @@ const DEPLOYMENTS: Record = { */ export const ACTIVE_CHAIN_ID = import.meta.env.DEV ? 31337 : 421614; +/** + * True only when a real vault contract is reachable on the active chain + * (anvil in dev). The public showcase build has no live vault yet, so the + * campaign hooks fall back to the seed below instead of hanging on reads. + */ +export const HAS_LIVE_VAULT = DEPLOYMENTS[ACTIVE_CHAIN_ID] != null; + +export interface CampaignSeed { + targetDepthUsd: number; + rewardBudgetUsd: number; + durationDays: number; + boostWindowDays: number; + boostBps: number; + tvlUsd: number; + activeLPs: number; +} + +/** + * Seed campaign parameters mirroring script/Deploy.s.sol's launch set. Used + * when HAS_LIVE_VAULT is false so the showcase renders realistic campaigns + * without an on-chain vault. Keyed by campaignId. + */ +export const CAMPAIGN_SEED: Record = { + 0: { targetDepthUsd: 1_000_000, rewardBudgetUsd: 50_000, durationDays: 14, boostWindowDays: 1, boostBps: 15_000, tvlUsd: 5_000, activeLPs: 1 }, + 1: { targetDepthUsd: 750_000, rewardBudgetUsd: 40_000, durationDays: 14, boostWindowDays: 1, boostBps: 15_000, tvlUsd: 0, activeLPs: 0 }, + 2: { targetDepthUsd: 500_000, rewardBudgetUsd: 25_000, durationDays: 21, boostWindowDays: 1, boostBps: 15_000, tvlUsd: 0, activeLPs: 0 }, + 3: { targetDepthUsd: 2_000_000, rewardBudgetUsd: 60_000, durationDays: 30, boostWindowDays: 1, boostBps: 15_000, tvlUsd: 0, activeLPs: 0 }, + 4: { targetDepthUsd: 400_000, rewardBudgetUsd: 35_000, durationDays: 14, boostWindowDays: 1, boostBps: 15_000, tvlUsd: 0, activeLPs: 0 }, +}; + export function getActiveDeployment(): Deployment { return DEPLOYMENTS[ACTIVE_CHAIN_ID] ?? local; } diff --git a/apps/web/src/screens/AppBoard.tsx b/apps/web/src/screens/AppBoard.tsx index eb69f4f..f89bc65 100644 --- a/apps/web/src/screens/AppBoard.tsx +++ b/apps/web/src/screens/AppBoard.tsx @@ -9,7 +9,7 @@ import { import { useAppKit } from "@reown/appkit/react"; import { hoodquidityVaultsAbi } from "@/lib/abi/HoodquidityVaults"; import { mockERC20Abi } from "@/lib/abi/MockERC20"; -import { ACTIVE_CHAIN_ID, getActiveDeployment, USD_DECIMALS } from "@/lib/contracts"; +import { ACTIVE_CHAIN_ID, HAS_LIVE_VAULT, getActiveDeployment, USD_DECIMALS } from "@/lib/contracts"; import { useCampaigns, usePositions, type CampaignState } from "@/hooks/useCampaigns"; import { useAllMarketData } from "@/hooks/useMarketData"; import { feeApr, rewardApr } from "@/lib/math"; @@ -91,7 +91,7 @@ export default function AppBoard() { chainId: ACTIVE_CHAIN_ID, }, ], - query: { enabled: !!address }, + query: { enabled: HAS_LIVE_VAULT && !!address }, }); const balance = (walletData?.[0]?.result as bigint | undefined) ?? 0n; const allowance = (walletData?.[1]?.result as bigint | undefined) ?? 0n;