LP Pool Status
This component will display the status of the LP Pool.// /app/components/lp-pool-status.tsx
import { LiquidityComponent } from '@/components/lp-component';
export default function LiquidityPage() {
return <LiquidityComponent />;
}
Liquidity Component
This component will display the liquidity component.'use client';
import { getLpPoolStatus, getLpsInfo } from '@/lib/contract';
import { useEffect, useState, useCallback } from 'react';
import { useAccount } from 'wagmi';
import { LpDepositForm } from './lp-components/lp-deposit-form';
import { LpPoolStatus } from './lp-components/lp-pool-status';
import { UserLpBalances } from './lp-components/user-lp-balances';
interface LpState {
lpsInfo?: [bigint, bigint, bigint, boolean];
lpPoolStatus?: boolean;
isLoading: boolean;
error?: string;
}
export function LiquidityComponent() {
const { address, isConnected } = useAccount();
const [state, setState] = useState<LpState>({
isLoading: true,
});
const fetchLpPoolStatus = useCallback(async () => {
try {
const status = await getLpPoolStatus();
setState((prev) => ({ ...prev, lpPoolStatus: status }));
} catch (error) {
setState((prev) => ({
...prev,
error: 'Failed to fetch pool status',
}));
} finally {
setState((prev) => ({ ...prev, isLoading: false }));
}
}, []);
const fetchLpsInfo = useCallback(async () => {
if (!address) return;
setState((prev) => ({ ...prev, isLoading: true }));
try {
const info = await getLpsInfo(address as `0x${string}`);
setState((prev) => ({ ...prev, lpsInfo: info }));
} catch (error) {
setState((prev) => ({
...prev,
error: 'Failed to fetch LP info',
lpsInfo: undefined,
}));
} finally {
setState((prev) => ({ ...prev, isLoading: false }));
}
}, [address]);
useEffect(() => {
fetchLpPoolStatus();
return () => {
setState({ isLoading: true });
};
}, [fetchLpPoolStatus]);
useEffect(() => {
if (isConnected && address) {
fetchLpsInfo();
}
}, [isConnected, address, fetchLpsInfo]);
if (state.error) {
return <div className="text-red-500">{state.error}</div>;
}
return (
<div className="space-y-6">
{state.isLoading ? (
<div className="animate-pulse">Loading...</div>
) : (
<>
<LpPoolStatus poolStatus={state.lpPoolStatus ?? false} />
{state.lpPoolStatus && address && (
<LpDepositForm address={address} />
)}
{state.lpsInfo && state.lpsInfo[0] > BigInt(0) && (
<UserLpBalances lpInfo={state.lpsInfo} />
)}
</>
)}
</div>
);
}
Lp Pool Status
This component will display the status of the LP Pool. Whether it is open or closed.// /app/components/lp-components/lp-pool-status.tsx
import { Card, CardContent } from '../ui/card';
export function LpPoolStatus({
poolStatus,
}: {
poolStatus: boolean | undefined;
}) {
return (
<Card className="bg-white rounded-xl shadow-sm">
<CardContent className="p-6">
<div className="text-center">
<h2 className="text-lg font-medium text-gray-500 mb-2">
LP Pool Status
</h2>
<p className="text-4xl font-bold text-emerald-500">
{poolStatus ? 'Open' : 'Closed'}
</p>
</div>
</CardContent>
</Card>
);
}
Lp Deposit Form
This component will display the form to add & edit liquidity to the jackpot as well as set the initial risk percentage (Position in Range).// /app/components/lp-components/lp-deposit-form.tsx
import { Card, CardContent } from '@/components/ui/card';
import { CONTRACT_ADDRESS, ERC20_TOKEN_ADDRESS } from '@/lib/constants';
import {
getLpsInfo,
getMinLpDeposit,
getTokenAllowance,
getTokenBalance,
} from '@/lib/contract';
import { useEffect, useState } from 'react';
import { parseAbi } from 'viem';
import { useWriteContract } from 'wagmi';
import { DepositInput } from './lp-deposit-form/deposit-input';
import { FormButton } from './lp-deposit-form/form-button';
import { MinLpDeposit } from './lp-deposit-form/min-lp-deposit';
import { RiskPercentage } from './lp-deposit-form/risk-percentage';
export function LpDepositForm({ address }: { address: string }) {
const [state, setState] = useState({
walletAddress: undefined as `0x${string}` | undefined,
walletBalance: 0,
allowance: 0,
lpPrincipal: 0,
currentRiskPercentage: null as number | null,
newRiskPercentage: 10,
minLpDeposit: undefined as number | undefined,
walletFunded: false,
allowanceFunded: false,
depositAmount: 250,
tempDepositAmount: 250,
});
const { writeContract } = useWriteContract();
useEffect(() => {
setState((prev) => ({
...prev,
walletAddress: address as `0x${string}`,
}));
const fetchMinLpDeposit = async () => {
const minDeposit = await getMinLpDeposit();
if (minDeposit) {
setState((prev) => ({
...prev,
minLpDeposit: Number(minDeposit),
}));
}
};
fetchMinLpDeposit();
const fetchLpInfo = async () => {
if (!state.walletAddress) return;
const lpsInfo = await getLpsInfo(state.walletAddress);
if (lpsInfo) {
setState((prev) => ({
...prev,
currentRiskPercentage: Number(lpsInfo[2]),
lpPrincipal: Number(lpsInfo[0]),
}));
}
};
fetchLpInfo();
}, [address]);
const fetchWalletAndAllowance = async () => {
if (!state.walletAddress) return;
try {
const balance = await getTokenBalance(state.walletAddress);
setState((prev) => ({
...prev,
walletBalance: balance
? Number((balance / 10 ** 6).toFixed(0))
: 0,
}));
} catch (error) {
console.error('Error fetching wallet balance:', error);
}
try {
const allowance = await getTokenAllowance(address as `0x${string}`);
setState((prev) => ({
...prev,
allowance: allowance ? allowance / 10 ** 6 : 0,
}));
} catch (error) {
console.error('Error fetching allowance:', error);
}
};
useEffect(() => {
fetchWalletAndAllowance();
const interval = setInterval(fetchWalletAndAllowance, 30000);
return () => clearInterval(interval); // Cleanup interval on unmount
}, [state.walletAddress]);
const handleApprove = async () => {
const depositAmount = state.minLpDeposit;
try {
const lpDepositAbi = [
'function approve(address spender, uint256 amount) returns (bool)',
];
writeContract?.({
abi: parseAbi(lpDepositAbi),
address: ERC20_TOKEN_ADDRESS as `0x${string}`,
functionName: 'approve',
args: [CONTRACT_ADDRESS as `0x${string}`, depositAmount],
});
} catch (error) {
console.error('Error approving token:', error);
}
};
const handleDeposit = async () => {
const depositAmount = state.minLpDeposit;
const riskPercentage = state.currentRiskPercentage;
try {
if (depositAmount === 0) {
console.error('Deposit amount cannot be 0');
return;
}
if (!riskPercentage || riskPercentage <= 0) {
console.error('Risk percentage cannot be null');
return;
}
const lpDepositAbi = [
'function lpDeposit(uint256 amount, uint256 riskPercentage) returns (bool)',
];
writeContract?.({
abi: parseAbi(lpDepositAbi),
address: CONTRACT_ADDRESS as `0x${string}`,
functionName: 'lpDeposit',
args: [depositAmount, riskPercentage],
});
} catch (error) {
console.error('Error approving token:', error);
}
};
return (
<Card className="bg-white rounded-xl shadow-sm">
<CardContent className="p-6">
<div className="text-center">
<h2 className="text-lg font-medium text-gray-500 mb-2">
LP Deposit Form
</h2>
<form>
<DepositInput
walletBalance={state.walletBalance}
allowance={state.allowance}
setWalletFunded={(funded: boolean) =>
setState((prev) => ({
...prev,
walletFunded: funded,
}))
}
setAllowanceFunded={(funded: boolean) =>
setState((prev) => ({
...prev,
allowanceFunded: funded,
}))
}
setDepositAmount={(amount: number) =>
setState((prev) => ({
...prev,
depositAmount: amount,
}))
}
/>
{state.minLpDeposit && state.lpPrincipal === 0 && (
<MinLpDeposit minLpDeposit={state.minLpDeposit} />
)}
<RiskPercentage
newRiskPercentage={state.newRiskPercentage}
setNewRiskPercentage={(percentage: number) =>
setState((prev) => ({
...prev,
newRiskPercentage: percentage,
}))
}
/>
</form>
</div>
<div className="flex justify-center">
<FormButton
walletFunded={state.walletFunded}
allowanceFunded={state.allowanceFunded}
depositAmount={state.depositAmount}
tempDepositAmount={state.tempDepositAmount}
handleDeposit={handleDeposit}
handleApprove={handleApprove}
walletBalance={state.walletBalance}
/>
</div>
</CardContent>
</Card>
);
}
Deposit Input
This component will display the input for the deposit amount & risk percentage (Position in Range).// /app/components/lp-components/lp-deposit-form/deposit-input.tsx
import { useEffect, useState } from 'react';
export function DepositInput({
walletBalance,
allowance,
setWalletFunded,
setAllowanceFunded,
setDepositAmount,
}: {
walletBalance: number;
allowance: number;
setWalletFunded: (funded: boolean) => void;
setAllowanceFunded: (funded: boolean) => void;
setDepositAmount: (amount: number) => void;
}) {
const [tempDepositAmount, setTempDepositAmount] = useState<number>(0);
useEffect(() => {
setDepositAmount(tempDepositAmount);
// Ensure wallet and allowance funding states are updated based on tempDepositAmount
setWalletFunded(
walletBalance >= tempDepositAmount && tempDepositAmount > 0
);
setAllowanceFunded(
allowance >= tempDepositAmount && tempDepositAmount > 0
);
}, [tempDepositAmount, walletBalance, allowance]);
return (
<>
<label className="block text-left mb-1">Add USDC Amount</label>
<input
type="number"
className="mt-1 block w-full h-10 rounded-md border-2 border-gray-500 shadow-sm focus:border-indigo-500 focus:ring focus:ring-indigo-500 focus:ring-offset-0 sm:text-sm ps-2"
min={0}
value={tempDepositAmount}
onChange={(e) => {
setTempDepositAmount(Number(e.target.value));
if (
walletBalance >= Number(e.target.value) &&
Number(e.target.value) > 0
) {
setWalletFunded(true);
} else {
setWalletFunded(false);
}
if (
allowance >= Number(e.target.value) &&
Number(e.target.value) > 0
) {
setAllowanceFunded(true);
} else {
setAllowanceFunded(false);
}
}}
onBlur={(e) => {
setDepositAmount(tempDepositAmount);
}}
style={{
appearance: 'none',
MozAppearance: 'textfield',
}}
/>
</>
);
}
Min Lp Deposit
This component will display the minimum deposit amount for the current jackpot.// /app/components/lp-components/lp-deposit-form/min-lp-deposit.tsx
export function MinLpDeposit({ minLpDeposit }: { minLpDeposit: number }) {
return (
<div>
<p className="text-sm text-emerald-500">
Minimum Initial LP Deposit: ${minLpDeposit / 10 ** 6} USDC
</p>
</div>
);
}
Risk Percentage
This component will allow the user to adjust their risk percentage (Position in Range) for future jackpots.// /app/components/lp-components/lp-deposit-form/risk-percentage.tsx
export function RiskPercentage({
newRiskPercentage,
setNewRiskPercentage,
}: {
newRiskPercentage: number;
setNewRiskPercentage: (percentage: number) => void;
}) {
return (
<>
<label className="block text-left mb-1 mt-4">
Set Risk Percentage
</label>
<select
onChange={(e) => {
const value = e.target.value;
setNewRiskPercentage(
value === 'custom' ? 0 : Number(value)
);
}}
defaultValue={'10'}
className="mt-1 block w-full h-10 rounded-md border-2 border-gray-500 shadow-sm focus:border-indigo-500 focus:ring focus:ring-indigo-500 focus:ring-offset-0 sm:text-sm ps-2"
>
<option value="100">100%</option>
<option value="50">50%</option>
<option value="25">25%</option>
<option value="10">10%</option>
<option value="custom">Custom</option>
</select>
{newRiskPercentage === 0 && (
<input
type="number"
min={0}
max={100}
onChange={(e) => {
const customValue = Number(e.target.value);
if (!isNaN(customValue) && customValue > 0) {
setNewRiskPercentage(customValue);
}
}}
className="mt-1 block w-full h-10 rounded-md border-2 border-gray-500 shadow-sm focus:border-indigo-500 focus:ring focus:ring-indigo-500 focus:ring-offset-0 sm:text-sm ps-2"
placeholder="Enter custom risk percentage"
style={{
appearance: 'none',
MozAppearance: 'textfield',
}}
/>
)}
</>
);
}
Form Button
This component will display the button to add & edit liquidity to the jackpot as well as set the initial risk percentage (Position in Range).// /app/components/lp-components/lp-deposit-form/form-button.tsx
export function FormButton({
walletFunded,
allowanceFunded,
depositAmount,
tempDepositAmount,
handleDeposit,
handleApprove,
walletBalance,
}: {
walletFunded: boolean;
allowanceFunded: boolean;
depositAmount: number;
tempDepositAmount: number;
handleDeposit: () => void;
handleApprove: () => void;
walletBalance: number;
}) {
let buttonContent;
if (depositAmount === 0 || tempDepositAmount === 0) {
buttonContent = (
<button
className="mt-4 bg-red-500 text-white px-4 py-2 rounded-md"
disabled
>
Deposit Amount Cannot Be 0
</button>
);
} else if (walletFunded && allowanceFunded && tempDepositAmount > 0) {
buttonContent = (
<button
onClick={handleDeposit}
className="mt-4 bg-blue-500 text-white px-4 py-2 rounded-md"
>
Deposit
</button>
);
} else if (walletFunded && !allowanceFunded) {
buttonContent = (
<button
onClick={handleApprove}
className="mt-4 bg-blue-500 text-white px-4 py-2 rounded-md"
>
Need to Approve USDC
</button>
);
} else if (!walletFunded && tempDepositAmount > walletBalance) {
buttonContent = (
<button
className="mt-4 bg-red-500 text-white px-4 py-2 rounded-md"
disabled
>
Need to Fund Wallet
</button>
);
} else {
buttonContent = (
<button
className="mt-4 bg-gray-500 text-white px-4 py-2 rounded-md"
disabled
>
Enter Amount
</button>
);
}
return <>{buttonContent}</>;
}
Lp Balances
This component will display the user’s LP balances.- Principal - This is how much liquidity is deposited but not in range. Position in Range is 100%
- Position in Range - This is how much liquidity is in range. Position in Range is how much liquidity is at risk in the current jackpot.
- Risk Percentage - This is the risk percentage (Position in Range) for the current jackpot.
// /app/components/lp-components/lp-balances.tsx
export function LpBalances({
principal,
inRange,
currentRiskPercent,
}: {
principal: number;
inRange: number;
currentRiskPercent: number;
}) {
return (
<>
<div className="flex justify-between">
<p className="font-bold text-emerald-500">Principal:</p>
<p className="font-bold text-emerald-500">
{(principal / 10 ** 6).toFixed(2)}
</p>
</div>
<div className="flex justify-between">
<p className="font-bold text-emerald-500">Position In Range:</p>
<p className="font-bold text-emerald-500">
{(inRange / 10 ** 6).toFixed(2)}
</p>
</div>
<div className="flex justify-between">
<p className="font-bold text-emerald-500">Risk Percent:</p>
<p className="font-bold text-emerald-500">
{currentRiskPercent}%
</p>
</div>
</>
);
}
Adjust Risk Percentage
This component will allow the user to adjust their risk percentage (Position in Range) for future jackpots.// /app/components/lp-components/adjust-risk-percentage.tsx
export function AdjustRiskPercentage({
riskPercent,
tempRiskPercent,
setRiskPercent,
setTempRiskPercent,
}: {
riskPercent: number;
tempRiskPercent: number;
setRiskPercent: (riskPercent: number) => void;
setTempRiskPercent: (riskPercent: number) => void;
}) {
return (
<>
<label
htmlFor="riskPercentInput"
className="text-sm font-medium text-gray-700 w-1/2"
>
Adjust Risk %
</label>
<input
id="riskPercentInput"
type="number"
min="0"
max="100"
value={tempRiskPercent}
onChange={(e) => setTempRiskPercent(Number(e.target.value))}
onBlur={() => {
setRiskPercent(tempRiskPercent);
}}
className="mt-1 block w-full h-10 rounded-md border-2 border-gray-500 shadow-sm focus:border-indigo-500 focus:ring focus:ring-indigo-500 focus:ring-offset-0 sm:text-sm ps-2"
style={{
appearance: 'none',
MozAppearance: 'textfield',
}}
/>
</>
);
}
