Historical Events
import { type Address } from 'viem';
import { base } from 'viem/chains';
import { getFeeBps } from '@/utils/contract';
import { CONTRACT_ADDRESS } from '@/utils/constants';
const API_URL = 'https://basescan.org';
const API_KEY = process.env.BASESCAN_API_KEY as string;
const PURCHASE_TICKET_TOPIC = "0xd72c70202ab87b3549553b1d4ceb2a632c83cb96fa2dfe65c30282862fe11ade";
const JACKPOT_RUN_TOPIC = "0x3208da215cdfa0c44cf3d81565b27f57d4c505bf1a48e40957e53aaf3ba2aa82";
const FEEBPS = await getFeeBps();
// fetch ticket purchases for all users
type TicketPurchaseInfo = { blockNumber: number; ticketsPurchased: number };
const fetchTicketPurchases = async (token: string) => {
const batchSize = 500; // Maximum allowed by the API
let allEvents: any[] = [];
let page = 1;
let hasMoreEvents = true;
while (hasMoreEvents) {
const urlParams = new URLSearchParams({
module: 'logs',
action: 'getLogs',
address: CONTRACT_ADDRESS,
topic0: PURCHASE_TICKET_TOPIC,
apikey: API_KEY,
page: page.toString(),
offset: batchSize.toString(),
});
const request = `${API_URL}?${urlParams}`;
const response = await fetch(request);
const body = await response?.json();
const request = `${API_URL}?${urlParams}`;
const response = await fetch(request);
const body = await response?.json();
if (body.status === '1' && body.result && body.result.length > 0) {
allEvents = allEvents.concat(body.result);
if (body.result.length < batchSize) {
hasMoreEvents = false;
} else {
page++;
// Add a small delay to avoid rate limiting
await new Promise((resolve) => setTimeout(resolve, 200));
}
} else {
hasMoreEvents = false;
}
}
const ticketPurchases = new Map<string, TicketPurchaseInfo[]>();
allEvents.forEach((event: any) => {
const userAddress = `0x${event.topics[1].substring(26, 66)}`;
let ticketsPurchased;
// The tickets purchased is the first 32 bytes of the data
ticketsPurchased =
parseInt(event.data.substring(0, 66), 16) /
10000 /
((100 - FEEBPS) / 100);
// The referrer address is the next 32 bytes, if you need to use it
const referrerAddress = `0x${event.data.substring(66, 130)}`;
const blockNumber = parseInt(event.blockNumber, 16);
if (!ticketPurchases.has(userAddress)) {
ticketPurchases.set(userAddress, []);
}
ticketPurchases.get(userAddress)?.push({ blockNumber, ticketsPurchased });
});
return ticketPurchases;
};
// Add this function to get the current block number
const getCurrentBlockNumber = async () => {
const urlParams = new URLSearchParams({
module: 'block',
action: 'getblocknobytime',
timestamp: Math.floor(Date.now() / 1000).toString(),
closest: 'before',
apikey: API_KEY,
});
const request = `${API_URL}?${urlParams}`;
const response = await fetch(request);
const body = await response?.json();
return parseInt(body.result);
};
// fetch jackpot info (winnerAddress, winAmount, ticketsPurchasedByWinner, blockNumber, transactionHash) for all jackpots
type JackpotInfo = {
winnerAddress: string;
winAmount: number;
ticketsPurchasedByWinner: number;
blockNumber: number;
transactionHash: string;
estimatedTimestamp: number;
};
const fetchJackpotInfo = async (token: string) => {
const urlParams = new URLSearchParams({
module: 'logs',
action: 'getLogs',
address: CONTRACT_ADDRESS,
topic0: JACKPOT_RUN_TOPIC,
apikey: API_KEY,
});
const request = `${API_URL}?${urlParams}`;
const response = await fetch(request);
const body = await response?.json();
const events = body.result;
const currentBlockNumber = await getCurrentBlockNumber();
const currentTimestamp = Math.floor(Date.now() / 1000);
const jackpotInfos: JackpotInfo[] = [];
events.forEach((event: any) => {
// field 1 [66:130]
const winner = event.data.substring(66, 130);
const winnerAddress =
parseInt(winner, 16) === 0
? 'LP_WINNER'
: `0x${winner.substring(24, 64)}`;
// field 3 [194:258]
const winAmount = parseInt(event.data.substring(194, 258), 16) / 10 ** 18;
// field 4 [258:322]
const ticketsPurchasedByWinner =
parseInt(event.data.substring(258, 322), 16) /
10000 /
((100 - FEEBPS) / 100);
const blockNumber = parseInt(event.blockNumber, 16);
const transactionHash = event.transactionHash;
const estimatedTimestamp =
currentTimestamp - (currentBlockNumber - blockNumber) * 2; // 2 seconds per block
const jackpotInfo = {
winnerAddress,
winAmount,
ticketsPurchasedByWinner,
blockNumber,
transactionHash,
estimatedTimestamp,
};
jackpotInfos.push(jackpotInfo);
});
return jackpotInfos;
};
// build jackpot history for a user address by fetching jackpot info and merging with user's ticket purchase count
type UserJackpotInfo = {
winnerAddress: string;
winAmount: number;
ticketsPurchasedByWinner: number;
blockNumber: number;
transactionHash: string;
ticketsPurchasedByUser: number;
estimatedTimestamp: number;
};
const buildUserJackpotHistory = async (
userAddress: string | undefined,
token: string
) => {
const jackpotInfos = await fetchJackpotInfo(token);
if (!userAddress) {
return jackpotInfos.map((info) => ({
...info,
ticketsPurchasedByUser: 0,
}));
}
const ticketPurchases = await fetchTicketPurchases(token);
const userTicketPurchases =
ticketPurchases.get(userAddress.toLowerCase()) || [];
const userJackpotHistory: UserJackpotInfo[] = [];
let prevBlock = 0;
jackpotInfos.forEach((jackpotInfo) => {
let ticketsPurchasedByUser = 0;
userTicketPurchases.forEach((userTicketPurchase) => {
if (
prevBlock <= userTicketPurchase.blockNumber &&
userTicketPurchase.blockNumber < jackpotInfo.blockNumber
) {
ticketsPurchasedByUser += userTicketPurchase.ticketsPurchased;
}
});
prevBlock = jackpotInfo.blockNumber;
userJackpotHistory.push({
...jackpotInfo,
ticketsPurchasedByUser,
});
});
return userJackpotHistory;
};
export { buildUserJackpotHistory, type UserJackpotInfo };
Last updated