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 overd6e38d2is the $500 minimum-deposit rule: the MIN_DEPOSIT/BelowMinimum contract enforcement plus the "Min. deposit $500" frontend validation. Working tree now equalsd6e38d2+ that feature. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
163 lines
5.3 KiB
TypeScript
163 lines
5.3 KiB
TypeScript
import { parseUnits } from "viem";
|
|
import { useWaitForTransactionReceipt, useWriteContract } from "wagmi";
|
|
import { hoodquidityVaultsAbi } from "@/lib/abi/HoodquidityVaults";
|
|
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";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { CountUp } from "@/components/CountUp";
|
|
import type { CampaignState } from "@/hooks/useCampaigns";
|
|
import type { usePosition } from "@/hooks/useCampaigns";
|
|
|
|
type Position = NonNullable<ReturnType<typeof usePosition>["position"]>;
|
|
|
|
export function PositionPanel({
|
|
campaign,
|
|
position,
|
|
onSettled,
|
|
}: {
|
|
campaign: CampaignState;
|
|
position: Position;
|
|
onSettled: () => void;
|
|
}) {
|
|
const deployment = getActiveDeployment();
|
|
const { writeContract, data: txHash, isPending, reset } = useWriteContract();
|
|
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
|
|
hash: txHash,
|
|
});
|
|
|
|
if (isSuccess) {
|
|
reset();
|
|
onSettled();
|
|
}
|
|
|
|
const busy = isPending || isConfirming;
|
|
const ended = campaign.status === "ended";
|
|
const id = BigInt(campaign.meta.campaignId);
|
|
|
|
const exit = () =>
|
|
writeContract({
|
|
address: deployment.vaults,
|
|
abi: hoodquidityVaultsAbi,
|
|
functionName: "withdraw",
|
|
args: [id, parseUnits(position.amountUsd.toString(), USD_DECIMALS)],
|
|
chainId: ACTIVE_CHAIN_ID,
|
|
});
|
|
|
|
const claim = () =>
|
|
writeContract({
|
|
address: deployment.vaults,
|
|
abi: hoodquidityVaultsAbi,
|
|
functionName: "claim",
|
|
args: [id],
|
|
chainId: ACTIVE_CHAIN_ID,
|
|
});
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="slabel">LP receipt</div>
|
|
<div className="flex gap-2">
|
|
{isGenesisLp(position.lpIndex) && <Badge tone="mint">Genesis LP</Badge>}
|
|
{position.boosted && <Badge tone="lime">1.5x boosted</Badge>}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2.5 text-sm">
|
|
<div className="leader">
|
|
<span className="text-fog">Deposited</span>
|
|
<span className="num font-semibold">{fmtUsd(position.amountUsd, false)}</span>
|
|
</div>
|
|
<div className="leader">
|
|
<span className="text-fog">Vault share</span>
|
|
<span className="num font-semibold">
|
|
{campaign.tvlUsd > 0
|
|
? `${((position.amountUsd / campaign.tvlUsd) * 100).toFixed(1)}%`
|
|
: "0%"}
|
|
</span>
|
|
</div>
|
|
<div className="leader">
|
|
<span className="text-fog">Rewards accrued</span>
|
|
<span className="num font-semibold text-mint">
|
|
{fmtUsd(position.pendingUsd, false)}
|
|
</span>
|
|
</div>
|
|
<div className="leader">
|
|
<span className="text-fog">Rewards claimed</span>
|
|
<span className="num font-semibold">{fmtUsd(position.claimedUsd, false)}</span>
|
|
</div>
|
|
{position.stockAmount > 0 && (
|
|
<div className="leader">
|
|
<span className="text-fog">Stock leg</span>
|
|
<span className="num font-semibold">
|
|
{position.stockAmount.toFixed(4)} {campaign.meta.symbol}
|
|
</span>
|
|
</div>
|
|
)}
|
|
<div className="leader">
|
|
<span className="text-fog">Points</span>
|
|
<span className="num font-semibold text-lime">
|
|
<CountUp
|
|
value={campaignPoints({
|
|
amountUsd: position.amountUsd,
|
|
depositedAt: position.depositedAt,
|
|
boosted: position.boosted,
|
|
})}
|
|
format={(n) => fmtNum(Math.floor(n))}
|
|
/>
|
|
</span>
|
|
</div>
|
|
<div className="leader">
|
|
<span className="text-fog">Full rewards unlock</span>
|
|
<span className="num font-semibold">
|
|
{ended ? "Unlocked" : fmtCountdown(campaign.end)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-3 pt-1">
|
|
{ended ? (
|
|
<>
|
|
<Button
|
|
className="flex-1"
|
|
disabled={busy || position.pendingUsd === 0}
|
|
onClick={claim}
|
|
>
|
|
{busy ? "Confirming..." : "Claim rewards"}
|
|
</Button>
|
|
<Button
|
|
className="flex-1"
|
|
variant="outline"
|
|
disabled={busy || position.amountUsd === 0}
|
|
onClick={exit}
|
|
>
|
|
Withdraw all
|
|
</Button>
|
|
</>
|
|
) : (
|
|
<Button
|
|
className="flex-1"
|
|
variant="danger"
|
|
disabled={busy || position.amountUsd === 0}
|
|
onClick={exit}
|
|
>
|
|
{busy ? "Confirming..." : "Exit early"}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
{!ended ? (
|
|
<p className="text-[11px] leading-relaxed text-fog-dim">
|
|
Early exit returns your principal minus a 0.1% fee. Accrued campaign
|
|
rewards are forfeited and redistributed to remaining LPs.
|
|
</p>
|
|
) : (
|
|
<p className="text-[11px] leading-relaxed text-fog-dim">
|
|
Reward payouts carry a 10% platform fee. Points have no monetary
|
|
value and feed future programs.
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|