Jackpot Page
This example will guide you through displaying the jackpot's size, ticket price, odds of winning and a countdown to the next drawing along with a section for claiming winnings and the last jackpot results.
This references our Jackpot Demo Integration Github Repo. You can find the code for the demo integration here or view the live demo here.
Jackpot Page
This is the entry point for the jackpot page.
// /app/jackpot/page.tsx
import { JackpotComponent } from '@/components/jackpot-component';
export default function JackpotPage() {
return <JackpotComponent />;
}
Jackpot Component
This component will display the jackpot's size, ticket price, odds of winning and a countdown to the next drawing.
This component will also display a form to purchase tickets.
// /app/components/jackpot-component.tsx
'use client';
import { Card, CardContent } from '@/components/ui/card';
import { getUsersInfo } from '@/lib/contract';
import { useEffect, useState } from 'react';
import { useAccount } from 'wagmi';
import { BuyTickets } from './jackpot-components/buy-tickets';
import { Countdown } from './jackpot-components/countdown';
import { CurrentJackpot } from './jackpot-components/current-jackpot';
import { LastJackpot } from './jackpot-components/last-jackpot';
import { TicketPrice } from './jackpot-components/ticket-price';
import { WinningOdds } from './jackpot-components/winning-odds';
import { WithdrawWinnings } from './jackpot-components/withdraw-winnings';
export function JackpotComponent() {
const { address, isConnected } = useAccount();
const [winningsAvailable, setWinningsAvailable] = useState<number>(0);
useEffect(() => {
if (!address) return;
const fetchWinningsAvailable = async () => {
const usersInfo = await getUsersInfo(address);
if (!usersInfo) return;
const winningsAvailable = usersInfo.winningsClaimable;
setWinningsAvailable(Number(winningsAvailable));
};
fetchWinningsAvailable();
}, [address]);
return (
<div className="space-y-6">
{winningsAvailable > 0 && (
<WithdrawWinnings winningsAvailable={winningsAvailable} />
)}
<CurrentJackpot />
<Countdown />
<Card className="bg-white rounded-xl shadow-sm">
<CardContent className="p-6">
<div className="text-center">
<TicketPrice />
<WinningOdds />
{isConnected && address && (
<BuyTickets walletAddress={address} />
)}
</div>
</CardContent>
</Card>
{/* NOTE: Free RPC servers are not reliable for traversing old logs.
You will need to have a reliable RPC endpoint setup in /app/viem-client.ts */}
<LastJackpot />
</div>
);
}
Buy Tickets
This component will display the current jackpot's size.
// /app/components/jackpot-components/buy-tickets.tsx
import { BaseJackpotAbi } from '@/lib/abi';
import {
CONTRACT_ADDRESS,
ERC20_TOKEN_ADDRESS,
REFERRER_ADDRESS,
} from '@/lib/constants';
import { getTokenAllowance, getTokenBalance } from '@/lib/contract';
import { useEffect, useState } from 'react';
import { parseAbi } from 'viem';
import { useAccount, useWriteContract } from 'wagmi';
import { ConnectButton } from '../connect-button';
import { Button } from '../ui/button';
export function BuyTickets({
walletAddress,
}: {
walletAddress: `0x${string}`;
}) {
const { isConnected } = useAccount();
const { data, error, isError, isPending, writeContract } =
useWriteContract();
const [ticketCount, setTicketCount] = useState<number>(1);
const [walletFunded, setWalletFunded] = useState<boolean>(false);
const [allowance, setAllowance] = useState<number>(0);
const increment = () => setTicketCount((prev) => prev + 1);
const decrement = () => setTicketCount((prev) => (prev > 1 ? prev - 1 : 1));
useEffect(() => {
// Get the balance of the wallet so we can check
// if it's funded
const fetchWalletFunds = async () => {
try {
const balance = await getTokenBalance(walletAddress);
if (balance) {
setWalletFunded(balance >= ticketCount * 10 ** 6);
} else {
setWalletFunded(false);
}
} catch (error) {
console.error('Error fetching wallet funded status:', error);
}
};
// Fetch wallet funds every 5 seconds
const intervalFunds = setInterval(fetchWalletFunds, 5000);
fetchWalletFunds();
// Get the allowance of the wallet so we can check
// if it's approved to buy tickets
const fetchAllowance = async () => {
try {
const allowance = await getTokenAllowance(walletAddress);
if (allowance) {
setAllowance(allowance);
} else {
setAllowance(0);
}
} catch (error) {
console.error('Error fetching allowance:', error);
}
};
// Fetch allowance every 5 seconds
const intervalAllowance = setInterval(fetchAllowance, 5000);
fetchAllowance();
return () => {
clearInterval(intervalFunds);
clearInterval(intervalAllowance);
};
}, [walletAddress]);
const handleApproveToken = async () => {
try {
if (!walletAddress) {
throw new Error('Wallet not connected');
}
const approveAbi = [
'function approve(address spender, uint256 amount) returns (bool)',
];
// Approve the token to be spent by the contract
return writeContract?.({
abi: parseAbi(approveAbi),
address: ERC20_TOKEN_ADDRESS as `0x${string}`,
functionName: 'approve',
args: [CONTRACT_ADDRESS, walletAddress],
});
} catch (error) {
console.error('Error approving token:', error);
}
};
const handleBuyTicket = async () => {
try {
if (!walletAddress) {
throw new Error('Wallet not connected');
}
const ticketCost = BigInt(1) * BigInt(10 ** 6);
// This is YOUR wallet to collect referral fees
const referrerAddress = REFERRER_ADDRESS;
return writeContract?.({
abi: BaseJackpotAbi,
address: CONTRACT_ADDRESS as `0x${string}`,
functionName: 'purchaseTickets',
args: [referrerAddress, ticketCost, walletAddress],
});
} catch (error) {
console.error('Error buying ticket:', error);
}
};
return (
<div className="flex flex-col items-center">
<div className="flex items-center">
<button
onClick={decrement}
className="bg-emerald-500 text-white px-2 hover:bg-emerald-600"
>
-
</button>
<input
type="number"
value={ticketCount}
onChange={(e) =>
setTicketCount(Math.max(1, Number(e.target.value)))
}
className="mx-2 w-16 text-center border border-emerald-500 rounded"
min="1"
style={{ appearance: 'none', MozAppearance: 'textfield' }}
/>
<button
onClick={increment}
className="bg-emerald-500 text-white px-2 hover:bg-emerald-600"
>
+
</button>
</div>
{isConnected &&
walletFunded &&
allowance >= ticketCount * 10 ** 6 ? (
<Button
onClick={handleBuyTicket}
disabled={!isConnected || !walletFunded}
className="mt-2 w-full bg-emerald-500 hover:bg-emerald-600 text-white"
>
Buy Ticket
</Button>
) : isConnected &&
walletFunded &&
allowance < ticketCount * 10 ** 6 ? (
<Button
onClick={handleApproveToken}
disabled={!isConnected}
className="mt-2 w-full bg-emerald-500 hover:bg-emerald-600 text-white"
>
Approve USDC Token
</Button>
) : isConnected && !walletFunded ? (
<div className="mt-2 w-full bg-orange-500 hover:bg-orange-600 text-white">
Not enough USDC in wallet
</div>
) : (
<div className="mt-2 w-full bg-emerald-500 hover:bg-emerald-600 text-white">
<ConnectButton />
</div>
)}
</div>
);
}
Countdown
This component will display a countdown to the next drawing.
// /app/components/jackpot-components/countdown.tsx
import { getTimeRemaining } from '@/lib/contract';
import { useEffect, useState } from 'react';
import { Card, CardContent } from '../ui/card';
export function Countdown() {
const [timeRemaining, setTimeRemaining] = useState<string | null>(null);
useEffect(() => {
// Fetch time remaining
const fetchTimeRemaining = async () => {
const timeRemaining = await getTimeRemaining();
if (timeRemaining) {
setTimeRemaining(timeRemaining.toString());
}
};
fetchTimeRemaining();
// Update time remaining every second
const timer = setInterval(() => {
setTimeRemaining((prev) => {
if (!prev) return null;
const seconds = parseInt(prev, 10);
if (seconds <= 0) return '0';
return (seconds - 1).toString();
});
}, 1000);
return () => clearInterval(timer);
}, []);
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">
Time Remaining
</h2>
<p className="text-3xl font-bold">
{timeRemaining
? formatTime(parseInt(timeRemaining, 10))
: '--:--:--'}
</p>
</div>
</CardContent>
</Card>
);
}
const formatTime = (totalSeconds: number) => {
if (totalSeconds <= 0) return '00:00:00';
const hours = Math.floor(totalSeconds / 3600) % 24;
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(
2,
'0'
)}:${String(seconds).padStart(2, '0')}`;
};
Current Jackpot
This component will display the current jackpot's size.
import { getJackpotAmount } from '@/lib/contract';
import { useEffect, useState } from 'react';
import { Card, CardContent } from '../ui/card';
export function CurrentJackpot() {
const [jackpotAmount, setJackpotAmount] = useState<number | undefined>(
undefined
);
useEffect(() => {
const fetchJackpotAmount = async () => {
const jackpotAmount = await getJackpotAmount();
setJackpotAmount(jackpotAmount);
};
fetchJackpotAmount();
}, []);
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">
Current Jackpot
</h2>
<p className="text-4xl font-bold text-emerald-500">
{jackpotAmount
? `$${jackpotAmount.toFixed(2)}`
: 'Loading...'}
</p>
</div>
</CardContent>
</Card>
);
}
Last Jackpot
This component will display the last jackpot's results.
// /app/components/jackpot-components/last-jackpot.tsx
import { getLastJackpotResults } from '@/lib/contract';
import { useEffect, useState } from 'react';
import { zeroAddress } from 'viem';
import { Card, CardContent } from '../ui/card';
interface LastJackpotEvent {
time: number;
winner: string;
winningTicket: number;
winAmount: number;
ticketsPurchasedTotalBps: number;
}
export function LastJackpot() {
const [lastJackpot, setLastJackpot] = useState<
LastJackpotEvent | undefined
>(undefined);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchLastJackpot = async () => {
const lastJackpot = await getLastJackpotResults();
if (lastJackpot) {
const lastJackpotEvent = {
time: lastJackpot.time,
winner: lastJackpot.winner,
winningTicket: lastJackpot.winningTicket,
winAmount: lastJackpot.winAmount,
ticketsPurchasedTotalBps:
lastJackpot.ticketsPurchasedTotalBps,
};
setLastJackpot(lastJackpotEvent);
setIsLoading(false);
}
};
fetchLastJackpot();
}, []);
return (
<Card>
<CardContent>
<div className="flex flex-col items-center justify-center">
<h1 className="text-2xl font-bold">Last Jackpot</h1>
{lastJackpot && !isLoading ? (
<>
<div className="flex justify-between w-full">
<p className="text-lg text-emerald-500">
Winner:
</p>
<p className="text-lg text-emerald-500">
{lastJackpot.winner === zeroAddress
? 'LPs Won'
: lastJackpot.winner}
</p>
</div>
<div className="flex justify-between w-full">
<p className="text-lg text-emerald-500">
Win Amount:
</p>
<p className="text-lg text-emerald-500">
{(lastJackpot.winAmount / 10 ** 6).toFixed(
2
)}{' '}
USDC
</p>
</div>
</>
) : (
<p className="text-lg">Loading...</p>
)}
</div>
</CardContent>
</Card>
);
}
Ticket Price
This component will display the current ticket price.
// /app/components/jackpot-components/ticket-price.tsx
import { getTicketPrice } from '@/lib/contract';
import { useEffect, useState } from 'react';
export function TicketPrice() {
const [ticketPrice, setTicketPrice] = useState<string | null>(null);
useEffect(() => {
const fetchTicketPrice = async () => {
const price = await getTicketPrice();
setTicketPrice(price?.toString() || null);
};
fetchTicketPrice();
}, []);
return (
<div>
<h2 className="text-lg font-medium text-gray-500 mb-2">
Ticket Price
</h2>
<p className="text-2xl font-bold mb-4">{ticketPrice} USDC</p>
</div>
);
}
Winning Odds
This component will display the odds of winning.
// /app/components/jackpot-components/winning-odds.tsx
import { getJackpotOdds } from '@/lib/contract';
import { useEffect, useState } from 'react';
export function WinningOdds() {
const [jackpotOdds, setJackpotOdds] = useState<number | null>(null);
useEffect(() => {
const fetchJackpotOdds = async () => {
const odds = await getJackpotOdds();
setJackpotOdds(odds || null);
};
fetchJackpotOdds();
}, []);
return (
<p className="text-sm text-gray-500 mb-4">
Odds of winning: 1 in {Number(jackpotOdds).toFixed(2)}
</p>
);
}
Withdraw Winnings
This component will display the form to withdraw winnings.
import { BaseJackpotAbi } from '@/lib/abi';
import { CONTRACT_ADDRESS } from '@/lib/constants';
import { useWriteContract } from 'wagmi';
import { Button } from '../ui/button';
import { Card, CardContent } from '../ui/card';
export function WithdrawWinnings({
winningsAvailable,
}: {
winningsAvailable: number;
}) {
const { writeContract } = useWriteContract();
const handleWithdraw = async () => {
writeContract?.({
address: CONTRACT_ADDRESS as `0x${string}`,
abi: BaseJackpotAbi,
functionName: 'withdrawWinnings',
args: [],
});
};
return (
<Card className="bg-white rounded-xl shadow-sm">
<CardContent className="p-6">
<div className="flex flex-col items-center justify-center">
<h1 className="text-2xl font-bold">Withdraw Winnings</h1>
<p className="text-lg">
{(winningsAvailable / 10 ** 6).toFixed(2)} USDC
</p>
<Button
onClick={handleWithdraw}
className="mt-2 w-full bg-emerald-500 hover:bg-emerald-600 text-white"
>
Withdraw
</Button>
</div>
</CardContent>
</Card>
);
}
Last updated