revert: restore the d6e38d2 showcase, keep only the $500 minimum deposit
All checks were successful
deploy / deploy (push) Successful in 1m18s

Remove the client-side simulated vault and all its wiring (simVault.ts,
AppBoard/DepositPanel/PositionPanel/useCampaigns sim paths, the balance/
faucet changes) and restore the original local dev addresses. The only
change retained over d6e38d2 is the $500 minimum-deposit rule: the
MIN_DEPOSIT/BelowMinimum contract enforcement plus the "Min. deposit $500"
frontend validation. Working tree now equals d6e38d2 + that feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
maxgort 2026-07-12 21:41:12 +03:00
parent 27f01990f0
commit 399610bcc8
6 changed files with 56 additions and 370 deletions

View file

@ -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<Mode>("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({
<div className="ticks rounded-none border border-edge bg-surface p-4">
<div className="flex items-center justify-between text-[11px] uppercase tracking-wider text-fog-dim">
<span>Stock leg</span>
{HAS_LIVE_VAULT && (
<button
type="button"
className="num cursor-pointer text-fog transition-colors hover:text-mint"
onClick={() => setStockInput((Number(stockBalance) / 1e18).toString())}
>
Balance {(Number(stockBalance) / 1e18).toFixed(4)}
</button>
)}
<button
type="button"
className="num cursor-pointer text-fog transition-colors hover:text-mint"
onClick={() => setStockInput((Number(stockBalance) / 1e18).toString())}
>
Balance {(Number(stockBalance) / 1e18).toFixed(4)}
</button>
</div>
<div className="mt-2 flex items-center gap-3">
<input
@ -312,15 +274,13 @@ export function DepositPanel({
<div className="ticks rounded-none border border-edge bg-surface p-4">
<div className="flex items-center justify-between text-[11px] uppercase tracking-wider text-fog-dim">
<span>{dual ? "Stable leg" : "You deposit"}</span>
{HAS_LIVE_VAULT && (
<button
type="button"
className="num cursor-pointer text-fog transition-colors hover:text-mint"
onClick={() => setInput((Number(balance) / 10 ** USD_DECIMALS).toString())}
>
Balance {fmtUsd(Number(balance) / 10 ** USD_DECIMALS)}
</button>
)}
<button
type="button"
className="num cursor-pointer text-fog transition-colors hover:text-mint"
onClick={() => setInput((Number(balance) / 10 ** USD_DECIMALS).toString())}
>
Balance {fmtUsd(Number(balance) / 10 ** USD_DECIMALS)}
</button>
</div>
<div className="mt-2 flex items-center gap-3">
<input

View file

@ -1,9 +1,7 @@
import { useState } from "react";
import { parseUnits } from "viem";
import { useWaitForTransactionReceipt, useWriteContract } from "wagmi";
import { hoodquidityVaultsAbi } from "@/lib/abi/HoodquidityVaults";
import { ACTIVE_CHAIN_ID, HAS_LIVE_VAULT, getActiveDeployment, USD_DECIMALS } from "@/lib/contracts";
import { simWithdraw, simClaim } from "@/lib/simVault";
import { ACTIVE_CHAIN_ID, getActiveDeployment, USD_DECIMALS } from "@/lib/contracts";
import { fmtUsd, fmtCountdown, fmtNum } from "@/lib/format";
import { campaignPoints, isGenesisLp } from "@/lib/points";
import { Button } from "@/components/ui/button";
@ -24,7 +22,6 @@ export function PositionPanel({
onSettled: () => 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 (
<div className="space-y-4">

View file

@ -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;

View file

@ -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",
},

View file

@ -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<number, SimPosition>;
}
/** 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<void>((r) => setTimeout(r, ms));
// ------------------------------------------------------------ reward accrual
let timer: ReturnType<typeof setInterval> | 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<number, SimPosition> = { ...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);
}

View file

@ -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<number | null>(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() {
<div className="mt-5 space-y-2.5">
<div className="flex justify-between font-mono text-[10px] uppercase tracking-[0.08em]">
<span className="text-fog">Amount</span>
{HAS_LIVE_VAULT && (
<button
className="num cursor-pointer text-fog-dim transition-colors hover:text-mint"
onClick={() => setInput(balanceUsd > 0 ? String(balanceUsd) : "")}
>
Balance: {fmtUsd(balanceUsd)}
</button>
)}
<button
className="num cursor-pointer text-fog-dim transition-colors hover:text-mint"
onClick={() => setInput(balanceUsd > 0 ? String(balanceUsd) : "")}
>
Balance: {fmtUsd(balanceUsd)}
</button>
</div>
<div className="flex items-center gap-2 border border-edge-bright bg-surface py-1 pl-1 pr-3.5">
<input
@ -475,14 +432,12 @@ export default function AppBoard() {
{v.toLocaleString("en-US")}
</button>
))}
{HAS_LIVE_VAULT && (
<button
onClick={() => setInput(balanceUsd > 0 ? String(balanceUsd) : "")}
className="flex-1 cursor-pointer border border-edge bg-transparent py-1.5 font-mono text-[11px] text-fog transition-colors hover:border-mint hover:text-mint"
>
MAX
</button>
)}
<button
onClick={() => setInput(balanceUsd > 0 ? String(balanceUsd) : "")}
className="flex-1 cursor-pointer border border-edge bg-transparent py-1.5 font-mono text-[11px] text-fog transition-colors hover:border-mint hover:text-mint"
>
MAX
</button>
</div>
{isConnected && belowMin && (
<p className="num font-mono text-[10px] tracking-[0.04em] text-danger">
@ -558,11 +513,7 @@ export default function AppBoard() {
<Stat label="Total deposited" value={fmtUsd(totalDeposited)} accent />
<Stat label="Claimable rewards" value={fmtUsd(totalPending)} />
<Stat label="Open positions" value={String(openPositions.length)} />
{HAS_LIVE_VAULT ? (
<Stat label="Wallet balance" value={fmtUsd(balanceUsd)} />
) : (
<Stat label="Rewards claimed" value={fmtUsd(totalClaimed)} />
)}
<Stat label="Wallet balance" value={fmtUsd(balanceUsd)} />
</div>
{openPositions.length > 0 ? (