diff --git a/apps/web/src/components/DepositPanel.tsx b/apps/web/src/components/DepositPanel.tsx index c0da62a..86b8fde 100644 --- a/apps/web/src/components/DepositPanel.tsx +++ b/apps/web/src/components/DepositPanel.tsx @@ -11,13 +11,11 @@ import { hoodquidityVaultsAbi } from "@/lib/abi/HoodquidityVaults"; import { mockERC20Abi } from "@/lib/abi/MockERC20"; import { ACTIVE_CHAIN_ID, - HAS_LIVE_VAULT, getActiveDeployment, USD_DECIMALS, MIN_DEPOSIT_USD, MIN_DEPOSIT_UNITS, } from "@/lib/contracts"; -import { useSimVault, simApprove, simDeposit } from "@/lib/simVault"; import { slippageImpact } from "@/lib/math"; import { fmtPct, fmtUsd } from "@/lib/format"; import { Button } from "@/components/ui/button"; @@ -42,11 +40,9 @@ export function DepositPanel({ const { address, isConnected } = useAccount(); const { open: openConnectModal } = useAppKit(); const deployment = getActiveDeployment(); - const sim = useSimVault(); const [mode, setMode] = useState("single"); const [input, setInput] = useState(""); const [stockInput, setStockInput] = useState(""); - const [simBusy, setSimBusy] = useState(false); const stockToken = campaign.meta.stockToken; const symbol = campaign.meta.symbol; @@ -109,25 +105,17 @@ export function DepositPanel({ chainId: ACTIVE_CHAIN_ID, }, ], - query: { enabled: HAS_LIVE_VAULT && !!address }, + query: { enabled: !!address }, }); - const stockInputNum = Number(stockInput) || 0; - // No live vault: a funded mainnet wallet is assumed (no faucet, no balance - // gate); the stock leg is valued off the live Dexscreener price, and only - // the vault allowance is tracked so approve -> deposit stays real. const balance = (walletData?.[0]?.result as bigint | undefined) ?? 0n; - const allowance = HAS_LIVE_VAULT - ? ((walletData?.[1]?.result as bigint | undefined) ?? 0n) - : parseUnits((isConnected ? sim.allowanceUsd : 0).toFixed(USD_DECIMALS), USD_DECIMALS); + const allowance = (walletData?.[1]?.result as bigint | undefined) ?? 0n; const stockBalance = (walletData?.[2]?.result as bigint | undefined) ?? 0n; const stockAllowance = (walletData?.[3]?.result as bigint | undefined) ?? 0n; const stockQuote = (walletData?.[4]?.result as bigint | undefined) ?? 0n; /** USD value of the entered stock leg (or unit price when input empty). */ - const stockValueUsd = HAS_LIVE_VAULT - ? Number(stockQuote) / 10 ** USD_DECIMALS - : (pair?.priceUsd ?? 0) * (stockAmount > 0n ? stockInputNum : 1); + const stockValueUsd = Number(stockQuote) / 10 ** USD_DECIMALS; const { writeContract, data: txHash, isPending, reset } = useWriteContract(); const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ @@ -140,25 +128,18 @@ export function DepositPanel({ onSettled(); } - const busy = isPending || isConfirming || simBusy; + const busy = isPending || isConfirming; const dual = mode === "dual"; // combined credit (stable leg + valued stock leg) in stable base units - const stockCreditUnits = - stockAmount > 0n - ? HAS_LIVE_VAULT - ? stockQuote - : parseUnits(stockValueUsd.toFixed(USD_DECIMALS), USD_DECIMALS) - : 0n; - const creditUnits = dual ? amount + stockCreditUnits : amount; + const creditUnits = dual ? amount + (stockAmount > 0n ? stockQuote : 0n) : amount; const belowMin = creditUnits > 0n && creditUnits < MIN_DEPOSIT_UNITS; - const needsUsdFaucet = HAS_LIVE_VAULT && amount > 0n && balance < amount; + const needsUsdFaucet = amount > 0n && balance < amount; const needsUsdApprove = !needsUsdFaucet && amount > 0n && allowance < amount; - // stock leg faucet/approve only apply on-chain; sim values it directly - const needsStockFaucet = HAS_LIVE_VAULT && dual && stockAmount > 0n && stockBalance < stockAmount; + const needsStockFaucet = dual && stockAmount > 0n && stockBalance < stockAmount; const needsStockApprove = - HAS_LIVE_VAULT && dual && !needsStockFaucet && stockAmount > 0n && stockAllowance < stockAmount; + dual && !needsStockFaucet && stockAmount > 0n && stockAllowance < stockAmount; const ready = dual ? stockAmount > 0n : amount > 0n; @@ -167,26 +148,9 @@ export function DepositPanel({ ? slippageImpact(PREVIEW_TRADE, pair.liquidityUsd + campaign.tvlUsd, depositUsd) : null; - const runSim = async () => { - setSimBusy(true); - try { - if (needsUsdApprove) { - await simApprove(); - } else { - await simDeposit(campaign.meta.campaignId, depositUsd, campaign.status === "boost"); - setInput(""); - setStockInput(""); - onSettled(); - } - } finally { - setSimBusy(false); - } - }; - const act = () => { if (!isConnected) return openConnectModal?.(); if (belowMin) return; // guarded; the button is disabled below the minimum - if (!HAS_LIVE_VAULT) return void runSim(); if (needsUsdFaucet) { writeContract({ address: deployment.usd, @@ -281,15 +245,13 @@ export function DepositPanel({
Stock leg - {HAS_LIVE_VAULT && ( - - )} +
{dual ? "Stable leg" : "You deposit"} - {HAS_LIVE_VAULT && ( - - )} +
void; }) { const deployment = getActiveDeployment(); - const [simBusy, setSimBusy] = useState(false); const { writeContract, data: txHash, isPending, reset } = useWriteContract(); const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash: txHash, @@ -35,19 +32,11 @@ export function PositionPanel({ onSettled(); } - const busy = isPending || isConfirming || simBusy; + const busy = isPending || isConfirming; const ended = campaign.status === "ended"; const id = BigInt(campaign.meta.campaignId); - const campaignId = campaign.meta.campaignId; - const exit = () => { - if (!HAS_LIVE_VAULT) { - setSimBusy(true); - simWithdraw(campaignId) - .then(onSettled) - .finally(() => setSimBusy(false)); - return; - } + const exit = () => writeContract({ address: deployment.vaults, abi: hoodquidityVaultsAbi, @@ -55,16 +44,8 @@ export function PositionPanel({ args: [id, parseUnits(position.amountUsd.toString(), USD_DECIMALS)], chainId: ACTIVE_CHAIN_ID, }); - }; - const claim = () => { - if (!HAS_LIVE_VAULT) { - setSimBusy(true); - simClaim(campaignId) - .then(onSettled) - .finally(() => setSimBusy(false)); - return; - } + const claim = () => writeContract({ address: deployment.vaults, abi: hoodquidityVaultsAbi, @@ -72,7 +53,6 @@ export function PositionPanel({ args: [id], chainId: ACTIVE_CHAIN_ID, }); - }; return (
diff --git a/apps/web/src/hooks/useCampaigns.ts b/apps/web/src/hooks/useCampaigns.ts index 58ea4b1..295f0f5 100644 --- a/apps/web/src/hooks/useCampaigns.ts +++ b/apps/web/src/hooks/useCampaigns.ts @@ -8,7 +8,6 @@ import { USD_DECIMALS, type MarketMeta, } from "@/lib/contracts"; -import { useSimVault } from "@/lib/simVault"; export interface CampaignState { meta: MarketMeta; @@ -64,7 +63,6 @@ function seedCampaign(meta: MarketMeta, now: number): CampaignState { export function useCampaigns() { const deployment = getActiveDeployment(); - const sim = useSimVault(); const { data, isLoading, error } = useReadContracts({ contracts: deployment.markets.map((m) => ({ @@ -79,19 +77,10 @@ export function useCampaigns() { const now = Math.floor(Date.now() / 1000); - // Showcase build with no reachable vault: render the seeded launch set, - // folding in any simulated deposits so depth and LP counts move live. + // Showcase build with no reachable vault: render the seeded launch set. if (!HAS_LIVE_VAULT) { return { - campaigns: deployment.markets.map((m) => { - const c = seedCampaign(m, now); - const sp = sim.positions[m.campaignId]; - if (sp && sp.amountUsd > 0) { - c.tvlUsd += sp.amountUsd; - c.activeLPs += 1; - } - return c; - }), + campaigns: deployment.markets.map((m) => seedCampaign(m, now)), isLoading: false, error: null, deployment, @@ -156,7 +145,6 @@ export function useCampaigns() { export function usePositions() { const { address } = useAccount(); const deployment = getActiveDeployment(); - const sim = useSimVault(); const zero = "0x0000000000000000000000000000000000000000" as const; const { data, isLoading, refetch } = useReadContracts({ @@ -179,23 +167,6 @@ export function usePositions() { query: { enabled: HAS_LIVE_VAULT && !!address, refetchInterval: 15_000 }, }); - if (!HAS_LIVE_VAULT) { - const positions = deployment.markets.map((meta) => { - const sp = address ? sim.positions[meta.campaignId] : undefined; - return { - meta, - amountUsd: sp?.amountUsd ?? 0, - claimedUsd: sp?.claimedUsd ?? 0, - pendingUsd: sp?.pendingUsd ?? 0, - boosted: sp?.boosted ?? false, - depositedAt: sp?.depositedAt ?? 0, - lpIndex: sp && sp.amountUsd > 0 ? 1 : 0, - stockAmount: 0, - }; - }); - return { positions, isLoading: false, refetch }; - } - const positions = deployment.markets.map((meta, i) => { const raw = data?.[i * 2]?.result as | { @@ -227,7 +198,6 @@ export function usePositions() { export function usePosition(campaignId: number) { const { address } = useAccount(); const deployment = getActiveDeployment(); - const sim = useSimVault(); const { data, isLoading, refetch } = useReadContracts({ contracts: [ @@ -249,28 +219,6 @@ export function usePosition(campaignId: number) { query: { enabled: HAS_LIVE_VAULT && !!address, refetchInterval: 15_000 }, }); - if (!HAS_LIVE_VAULT) { - const sp = address ? sim.positions[campaignId] : undefined; - return { - isLoading: false, - refetch, - position: - sp && sp.amountUsd > 0 - ? { - amountUsd: sp.amountUsd, - weighted: sp.weightedUsd, - claimedUsd: sp.claimedUsd, - depositedAt: sp.depositedAt, - lpIndex: 1, - stockAmount: 0, - stockCreditUsd: 0, - pendingUsd: sp.pendingUsd, - boosted: sp.boosted, - } - : undefined, - }; - } - const raw = data?.[0]?.result as | { amount: bigint; diff --git a/apps/web/src/lib/contracts.ts b/apps/web/src/lib/contracts.ts index 4a965e7..6fe0527 100644 --- a/apps/web/src/lib/contracts.ts +++ b/apps/web/src/lib/contracts.ts @@ -33,15 +33,15 @@ interface Deployment { /** anvil (local dev) deployment, chainId 31337 */ const local: Deployment = { - vaults: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", - usd: "0x5FbDB2315678afecb367f032d93F642f64180aa3", + vaults: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed", + usd: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE", markets: [ { symbol: "NVDA", name: "NVIDIA Stock Token", campaignId: 0, - stockToken: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", - priceFeed: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", + stockToken: "0x59b670e9fA9D0A427751Af201D676719a970857b", + priceFeed: "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1", dexToken: XSTOCK.NVDA, risk: "Medium", }, @@ -49,8 +49,8 @@ const local: Deployment = { symbol: "TSLA", name: "Tesla Stock Token", campaignId: 1, - stockToken: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", - priceFeed: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", + stockToken: "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f", + priceFeed: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", dexToken: XSTOCK.TSLA, risk: "Medium", }, @@ -58,8 +58,8 @@ const local: Deployment = { symbol: "AAPL", name: "Apple Stock Token", campaignId: 2, - stockToken: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", - priceFeed: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", + stockToken: "0x09635F643e140090A9A8Dcd712eD6285858ceBef", + priceFeed: "0xc5a5C42992dECbae36851359345FE25997F5C42d", dexToken: XSTOCK.AAPL, risk: "Low", }, @@ -67,8 +67,8 @@ const local: Deployment = { symbol: "SPY", name: "SPDR S&P 500 Token", campaignId: 3, - stockToken: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", - priceFeed: "0x9A676e781A523b5d0C0e43731313A708CB607508", + stockToken: "0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E", + priceFeed: "0xc3e53F4d16Ae77Db1c982e75a937B9f60FE63690", dexToken: XSTOCK.SPY, risk: "Low", }, @@ -76,8 +76,8 @@ const local: Deployment = { symbol: "AIB", name: "AI Basket Token", campaignId: 4, - stockToken: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1", - priceFeed: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE", + stockToken: "0x9E545E3C0baAB3E08CdfD552C960A1050f373042", + priceFeed: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9", dexBasket: [XSTOCK.NVDA, XSTOCK.META, XSTOCK.GOOGL, XSTOCK.AMZN], risk: "High", }, diff --git a/apps/web/src/lib/simVault.ts b/apps/web/src/lib/simVault.ts deleted file mode 100644 index 59292c1..0000000 --- a/apps/web/src/lib/simVault.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { useSyncExternalStore } from "react"; - -/** - * Client-side vault used when no on-chain vault is reachable (the showcase - * build). It mirrors the shape and timing of the real approve -> deposit -> - * claim/withdraw flow so the app behaves exactly like the live version: - * positions appear in the portfolio, campaign depth grows and rewards accrue - * over time. There is no wallet balance or faucet — you approve once, then - * deposit, exactly like a funded mainnet wallet. No funds move. - */ - -export interface SimPosition { - amountUsd: number; - weightedUsd: number; - claimedUsd: number; - pendingUsd: number; - depositedAt: number; // unix seconds - boosted: boolean; -} - -interface SimState { - allowanceUsd: number; - positions: Record; -} - -/** A single approve covers any later deposit, like an infinite allowance. */ -const APPROVE_MAX = 1e15; -/** Block-confirmation feel for each action. */ -const CONFIRM_MS = 1_100; -/** Reward accrual tuned to the campaign APRs the UI advertises (~5,000%). */ -const DEMO_APR = 50; -const SECONDS_PER_YEAR = 31_536_000; -const TICK_MS = 2_000; -const STORAGE_KEY = "hq-vault-state-v2"; - -function initial(): SimState { - return { allowanceUsd: 0, positions: {} }; -} - -function load(): SimState { - try { - const raw = typeof localStorage !== "undefined" && localStorage.getItem(STORAGE_KEY); - if (raw) return JSON.parse(raw) as SimState; - } catch { - /* ignore */ - } - return initial(); -} - -let state: SimState = load(); -const listeners = new Set<() => void>(); - -function persist() { - try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); - } catch { - /* ignore */ - } -} - -function set(next: SimState) { - state = next; - persist(); - listeners.forEach((l) => l()); -} - -const wait = (ms: number) => new Promise((r) => setTimeout(r, ms)); - -// ------------------------------------------------------------ reward accrual - -let timer: ReturnType | null = null; -function ensureAccrual() { - if (timer || typeof setInterval === "undefined") return; - timer = setInterval(() => { - const ids = Object.keys(state.positions); - if (ids.length === 0) return; - let changed = false; - const positions: Record = { ...state.positions }; - for (const key of ids) { - const id = Number(key); - const p = positions[id]; - if (p.amountUsd <= 0) continue; - const rate = (DEMO_APR / SECONDS_PER_YEAR) * (p.boosted ? 1.5 : 1); - positions[id] = { ...p, pendingUsd: p.pendingUsd + p.amountUsd * rate * (TICK_MS / 1000) }; - changed = true; - } - if (changed) set({ ...state, positions }); - }, TICK_MS); -} - -// ------------------------------------------------------------------- actions - -export async function simApprove() { - await wait(CONFIRM_MS); - set({ ...state, allowanceUsd: APPROVE_MAX }); -} - -export async function simDeposit(campaignId: number, amountUsd: number, boosted: boolean) { - await wait(CONFIRM_MS); - const now = Math.floor(Date.now() / 1000); - const prev = state.positions[campaignId]; - const weight = amountUsd * (boosted ? 1.5 : 1); - const pos: SimPosition = prev - ? { - ...prev, - amountUsd: prev.amountUsd + amountUsd, - weightedUsd: prev.weightedUsd + weight, - depositedAt: now, - boosted: prev.boosted || boosted, - } - : { amountUsd, weightedUsd: weight, claimedUsd: 0, pendingUsd: 0, depositedAt: now, boosted }; - set({ - ...state, - allowanceUsd: Math.max(0, state.allowanceUsd - amountUsd), - positions: { ...state.positions, [campaignId]: pos }, - }); -} - -export async function simWithdraw(campaignId: number) { - await wait(CONFIRM_MS); - if (!state.positions[campaignId]) return; - const positions = { ...state.positions }; - delete positions[campaignId]; - set({ ...state, positions }); -} - -export async function simClaim(campaignId: number) { - await wait(CONFIRM_MS); - const prev = state.positions[campaignId]; - if (!prev || prev.pendingUsd <= 0) return; - set({ - ...state, - positions: { - ...state.positions, - [campaignId]: { ...prev, claimedUsd: prev.claimedUsd + prev.pendingUsd, pendingUsd: 0 }, - }, - }); -} - -// --------------------------------------------------------------------- hook - -function subscribe(cb: () => void) { - listeners.add(cb); - ensureAccrual(); - return () => listeners.delete(cb); -} -function getSnapshot() { - return state; -} - -export function useSimVault(): SimState { - return useSyncExternalStore(subscribe, getSnapshot, getSnapshot); -} diff --git a/apps/web/src/screens/AppBoard.tsx b/apps/web/src/screens/AppBoard.tsx index d89b9e3..98f4bcb 100644 --- a/apps/web/src/screens/AppBoard.tsx +++ b/apps/web/src/screens/AppBoard.tsx @@ -19,7 +19,6 @@ import { } from "@/lib/contracts"; import { useCampaigns, usePositions, type CampaignState } from "@/hooks/useCampaigns"; import { useAllMarketData } from "@/hooks/useMarketData"; -import { useSimVault, simApprove, simDeposit, simWithdraw, simClaim } from "@/lib/simVault"; import { feeApr, rewardApr } from "@/lib/math"; import { fmtUsd, fmtAprPct, fmtCountdown } from "@/lib/format"; import { cn } from "@/lib/utils"; @@ -48,10 +47,8 @@ export default function AppBoard() { const { campaigns, isLoading } = useCampaigns(); const { data: pairs } = useAllMarketData(deployment.markets); const { positions, refetch: refetchPositions } = usePositions(); - const sim = useSimVault(); const [tab, setTab] = useState<"campaigns" | "portfolio">("campaigns"); - const [simBusy, setSimBusy] = useState(false); const [risk, setRisk] = useState<(typeof RISKS)[number]>("All"); const [selectedId, setSelectedId] = useState(null); const [input, setInput] = useState(""); @@ -103,12 +100,8 @@ export default function AppBoard() { ], query: { enabled: HAS_LIVE_VAULT && !!address }, }); - // No live vault: a funded mainnet wallet is assumed (no faucet, no balance - // gate); only the vault allowance is tracked so approve -> deposit is real. const balance = (walletData?.[0]?.result as bigint | undefined) ?? 0n; - const allowance = HAS_LIVE_VAULT - ? ((walletData?.[1]?.result as bigint | undefined) ?? 0n) - : parseUnits((isConnected ? sim.allowanceUsd : 0).toFixed(USD_DECIMALS), USD_DECIMALS); + const allowance = (walletData?.[1]?.result as bigint | undefined) ?? 0n; const balanceUsd = Number(balance) / 10 ** USD_DECIMALS; const { writeContract, data: txHash, isPending, reset } = useWriteContract({ @@ -127,9 +120,9 @@ export default function AppBoard() { lastAction.current = ""; }, [isSuccess, reset, refetchWallet, refetchPositions]); - const busy = isPending || isConfirming || simBusy; + const busy = isPending || isConfirming; const belowMin = amount > 0n && amount < MIN_DEPOSIT_UNITS; - const needsFaucet = HAS_LIVE_VAULT && amount > 0n && balance < amount; + const needsFaucet = amount > 0n && balance < amount; const needsApprove = !needsFaucet && amount > 0n && allowance < amount; const selPair = selected ? pairs?.[selected.meta.symbol] : undefined; @@ -139,30 +132,11 @@ export default function AppBoard() { : 0; const selApr = selFee + selReward; - const runSim = async () => { - if (!selected) return; - setSimBusy(true); - try { - if (needsApprove) { - await simApprove(); - say("hUSD spending approved"); - } else { - const boosted = selected.status === "boost"; - setInput(""); - await simDeposit(selected.meta.campaignId, amountUsd, boosted); - say(`deposited ${fmtUsd(amountUsd)} -> ${selected.meta.symbol} / hUSD`); - } - } finally { - setSimBusy(false); - } - }; - const act = () => { if (!selected) return; if (!isConnected) return openConnectModal?.(); if (amount === 0n) return say("enter an amount to deposit"); if (belowMin) return say(`minimum deposit is $${MIN_DEPOSIT_USD}`); - if (!HAS_LIVE_VAULT) return void runSim(); if (needsFaucet) { lastAction.current = "hUSD minted to wallet"; writeContract({ @@ -194,13 +168,6 @@ export default function AppBoard() { }; const claim = (campaignId: number) => { - if (!HAS_LIVE_VAULT) { - setSimBusy(true); - simClaim(campaignId) - .then(() => say("rewards claimed -> wallet")) - .finally(() => setSimBusy(false)); - return; - } lastAction.current = "rewards claimed -> wallet"; writeContract({ address: deployment.vaults, @@ -212,13 +179,6 @@ export default function AppBoard() { }; const withdraw = (campaignId: number, posAmountUsd: number) => { - if (!HAS_LIVE_VAULT) { - setSimBusy(true); - simWithdraw(campaignId) - .then(() => say("position closed · principal returned")) - .finally(() => setSimBusy(false)); - return; - } lastAction.current = "position closed · principal returned"; writeContract({ address: deployment.vaults, @@ -232,7 +192,6 @@ export default function AppBoard() { const openPositions = positions.filter((p) => p.amountUsd > 0); const totalDeposited = openPositions.reduce((s, p) => s + p.amountUsd, 0); const totalPending = openPositions.reduce((s, p) => s + p.pendingUsd, 0); - const totalClaimed = openPositions.reduce((s, p) => s + p.claimedUsd, 0); const primaryLabel = !isConnected ? "Connect wallet" @@ -446,14 +405,12 @@ export default function AppBoard() {
Amount - {HAS_LIVE_VAULT && ( - - )} +
))} - {HAS_LIVE_VAULT && ( - - )} +
{isConnected && belowMin && (

@@ -558,11 +513,7 @@ export default function AppBoard() { - {HAS_LIVE_VAULT ? ( - - ) : ( - - )} +

{openPositions.length > 0 ? (