LP Deposit Page
This page will display a form to add & edit liquidity to the jackpot. It also lets users adjust their risk percentage (Position in Range) for the current jackpot as well as withdraw their liquidity.
This references our Jackpot Demo Integration Github Repo. You can find the code for the demo integration here.
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',
}}
/>
</>
);
}
Last updated