diff --git a/apps/web/src/components/DepositPanel.tsx b/apps/web/src/components/DepositPanel.tsx
index a72121e..c0da62a 100644
--- a/apps/web/src/components/DepositPanel.tsx
+++ b/apps/web/src/components/DepositPanel.tsx
@@ -17,7 +17,7 @@ import {
MIN_DEPOSIT_USD,
MIN_DEPOSIT_UNITS,
} from "@/lib/contracts";
-import { useSimVault, simFaucet, simApprove, simDeposit } from "@/lib/simVault";
+import { useSimVault, simApprove, simDeposit } from "@/lib/simVault";
import { slippageImpact } from "@/lib/math";
import { fmtPct, fmtUsd } from "@/lib/format";
import { Button } from "@/components/ui/button";
@@ -113,11 +113,10 @@ export function DepositPanel({
});
const stockInputNum = Number(stockInput) || 0;
- // No live vault: the funded demo wallet stands in, and the stock leg is
- // valued off the live Dexscreener price instead of the on-chain feed.
- const balance = HAS_LIVE_VAULT
- ? ((walletData?.[0]?.result as bigint | undefined) ?? 0n)
- : parseUnits((isConnected ? sim.balanceUsd : 0).toFixed(USD_DECIMALS), USD_DECIMALS);
+ // 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);
@@ -154,7 +153,7 @@ export function DepositPanel({
const creditUnits = dual ? amount + stockCreditUnits : amount;
const belowMin = creditUnits > 0n && creditUnits < MIN_DEPOSIT_UNITS;
- const needsUsdFaucet = amount > 0n && balance < amount;
+ const needsUsdFaucet = HAS_LIVE_VAULT && 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;
@@ -171,10 +170,8 @@ export function DepositPanel({
const runSim = async () => {
setSimBusy(true);
try {
- if (needsUsdFaucet) {
- await simFaucet();
- } else if (needsUsdApprove) {
- await simApprove(Number(input) || 0);
+ if (needsUsdApprove) {
+ await simApprove();
} else {
await simDeposit(campaign.meta.campaignId, depositUsd, campaign.status === "boost");
setInput("");
@@ -284,13 +281,15 @@ export function DepositPanel({
Stock leg
-
+ {HAS_LIVE_VAULT && (
+
+ )}
{dual ? "Stable leg" : "You deposit"}
-
+ {HAS_LIVE_VAULT && (
+
+ )}
approve ->
- * deposit -> claim/withdraw flow so the app behaves exactly like the live
- * version: balances move, positions appear in the portfolio, campaign depth
- * grows and rewards accrue over time. No funds move; this is presentation
- * state only.
+ * 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 {
@@ -19,25 +19,22 @@ export interface SimPosition {
}
interface SimState {
- balanceUsd: number;
allowanceUsd: number;
positions: Record
;
}
-/** A funded demo wallet, so the flow starts at approve -> deposit. */
-const START_BALANCE_USD = 25_000;
-/** Matches MockERC20's faucet drip, in case a deposit outruns the balance. */
-const FAUCET_USD = 10_000;
+/** 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-v1";
+const STORAGE_KEY = "hq-vault-state-v2";
function initial(): SimState {
- return { balanceUsd: START_BALANCE_USD, allowanceUsd: 0, positions: {} };
+ return { allowanceUsd: 0, positions: {} };
}
function load(): SimState {
@@ -93,14 +90,9 @@ function ensureAccrual() {
// ------------------------------------------------------------------- actions
-export async function simFaucet() {
+export async function simApprove() {
await wait(CONFIRM_MS);
- set({ ...state, balanceUsd: state.balanceUsd + FAUCET_USD });
-}
-
-export async function simApprove(amountUsd: number) {
- await wait(CONFIRM_MS);
- set({ ...state, allowanceUsd: amountUsd });
+ set({ ...state, allowanceUsd: APPROVE_MAX });
}
export async function simDeposit(campaignId: number, amountUsd: number, boosted: boolean) {
@@ -119,7 +111,6 @@ export async function simDeposit(campaignId: number, amountUsd: number, boosted:
: { amountUsd, weightedUsd: weight, claimedUsd: 0, pendingUsd: 0, depositedAt: now, boosted };
set({
...state,
- balanceUsd: Math.max(0, state.balanceUsd - amountUsd),
allowanceUsd: Math.max(0, state.allowanceUsd - amountUsd),
positions: { ...state.positions, [campaignId]: pos },
});
@@ -127,12 +118,10 @@ export async function simDeposit(campaignId: number, amountUsd: number, boosted:
export async function simWithdraw(campaignId: number) {
await wait(CONFIRM_MS);
- const prev = state.positions[campaignId];
- if (!prev) return;
+ if (!state.positions[campaignId]) return;
const positions = { ...state.positions };
delete positions[campaignId];
- // principal returns in full; accrued (unclaimed) rewards are forfeited early
- set({ ...state, balanceUsd: state.balanceUsd + prev.amountUsd, positions });
+ set({ ...state, positions });
}
export async function simClaim(campaignId: number) {
@@ -141,7 +130,6 @@ export async function simClaim(campaignId: number) {
if (!prev || prev.pendingUsd <= 0) return;
set({
...state,
- balanceUsd: state.balanceUsd + prev.pendingUsd,
positions: {
...state.positions,
[campaignId]: { ...prev, claimedUsd: prev.claimedUsd + prev.pendingUsd, pendingUsd: 0 },
diff --git a/apps/web/src/screens/AppBoard.tsx b/apps/web/src/screens/AppBoard.tsx
index c301034..d89b9e3 100644
--- a/apps/web/src/screens/AppBoard.tsx
+++ b/apps/web/src/screens/AppBoard.tsx
@@ -19,14 +19,7 @@ import {
} from "@/lib/contracts";
import { useCampaigns, usePositions, type CampaignState } from "@/hooks/useCampaigns";
import { useAllMarketData } from "@/hooks/useMarketData";
-import {
- useSimVault,
- simFaucet,
- simApprove,
- simDeposit,
- simWithdraw,
- simClaim,
-} from "@/lib/simVault";
+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";
@@ -110,10 +103,9 @@ export default function AppBoard() {
],
query: { enabled: HAS_LIVE_VAULT && !!address },
});
- // With no live vault, the funded demo wallet drives the same state machine.
- const balance = HAS_LIVE_VAULT
- ? ((walletData?.[0]?.result as bigint | undefined) ?? 0n)
- : parseUnits((isConnected ? sim.balanceUsd : 0).toFixed(USD_DECIMALS), USD_DECIMALS);
+ // 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);
@@ -137,7 +129,7 @@ export default function AppBoard() {
const busy = isPending || isConfirming || simBusy;
const belowMin = amount > 0n && amount < MIN_DEPOSIT_UNITS;
- const needsFaucet = amount > 0n && balance < amount;
+ const needsFaucet = HAS_LIVE_VAULT && amount > 0n && balance < amount;
const needsApprove = !needsFaucet && amount > 0n && allowance < amount;
const selPair = selected ? pairs?.[selected.meta.symbol] : undefined;
@@ -151,12 +143,9 @@ export default function AppBoard() {
if (!selected) return;
setSimBusy(true);
try {
- if (needsFaucet) {
- await simFaucet();
- say("hUSD minted to wallet");
- } else if (needsApprove) {
- await simApprove(amountUsd);
- say(`approved ${fmtUsd(amountUsd)} hUSD`);
+ if (needsApprove) {
+ await simApprove();
+ say("hUSD spending approved");
} else {
const boosted = selected.status === "boost";
setInput("");
@@ -243,6 +232,7 @@ 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"
@@ -456,12 +446,14 @@ export default function AppBoard() {
Amount
-
+ {HAS_LIVE_VAULT && (
+
+ )}
))}
-
+ {HAS_LIVE_VAULT && (
+
+ )}
{isConnected && belowMin && (
@@ -564,7 +558,11 @@ export default function AppBoard() {
-
+ {HAS_LIVE_VAULT ? (
+
+ ) : (
+
+ )}
{openPositions.length > 0 ? (