import { ponder } from "ponder:registry"; import { positions, stats, STATS_ID, TAX_RATES } from "ponder:schema"; import { ensureStatsExists, getStakeTotalSupply, parseRingBuffer, refreshOutstandingStake, serializeRingBuffer, updateHourlyData, checkBlockHistorySufficient, RING_BUFFER_SEGMENTS, } from "./helpers/stats"; const ZERO = 0n; async function getKraikenTotalSupply(context: Context) { return context.client.readContract({ abi: context.contracts.Kraiken.abi, address: context.contracts.Kraiken.address, functionName: "totalSupply", }); } function toShareRatio(share: bigint, stakeTotalSupply: bigint): number { if (stakeTotalSupply === 0n) return 0; return Number(share) / Number(stakeTotalSupply); } ponder.on("Stake:PositionCreated", async ({ event, context }) => { await ensureStatsExists(context, event.block.timestamp); const stakeTotalSupply = await getStakeTotalSupply(context); const shareRatio = toShareRatio(event.args.share, stakeTotalSupply); const totalSupplyInit = await getKraikenTotalSupply(context); await context.db.insert(positions).values({ id: event.args.positionId.toString(), owner: event.args.owner as `0x${string}`, share: shareRatio, taxRate: TAX_RATES[Number(event.args.taxRate)] || 0, kraikenDeposit: event.args.kraikenDeposit, stakeDeposit: event.args.kraikenDeposit, taxPaid: ZERO, snatched: 0, creationTime: event.block.timestamp, lastTaxTime: event.block.timestamp, status: "Active", createdAt: event.block.timestamp, totalSupplyInit, totalSupplyEnd: null, payout: ZERO, }); await refreshOutstandingStake(context); }); ponder.on("Stake:PositionRemoved", async ({ event, context }) => { await ensureStatsExists(context, event.block.timestamp); const positionId = event.args.positionId.toString(); const position = await context.db.find(positions, { id: positionId }); if (!position) return; const totalSupplyEnd = await getKraikenTotalSupply(context); await context.db.update(positions, { id: positionId }).set({ status: "Closed", closedAt: event.block.timestamp, totalSupplyEnd, payout: (position.payout ?? ZERO) + event.args.kraikenPayout, kraikenDeposit: ZERO, stakeDeposit: ZERO, }); await refreshOutstandingStake(context); if (checkBlockHistorySufficient(context, event)) { await updateHourlyData(context, event.block.timestamp); } }); ponder.on("Stake:PositionShrunk", async ({ event, context }) => { await ensureStatsExists(context, event.block.timestamp); const positionId = event.args.positionId.toString(); const position = await context.db.find(positions, { id: positionId }); if (!position) return; const stakeTotalSupply = await getStakeTotalSupply(context); const shareRatio = toShareRatio(event.args.newShares, stakeTotalSupply); await context.db.update(positions, { id: positionId }).set({ share: shareRatio, kraikenDeposit: BigInt(position.kraikenDeposit ?? ZERO) - event.args.kraikenPayout, stakeDeposit: BigInt(position.stakeDeposit ?? ZERO) - event.args.kraikenPayout, snatched: position.snatched + 1, payout: BigInt(position.payout ?? ZERO) + event.args.kraikenPayout, }); await refreshOutstandingStake(context); if (checkBlockHistorySufficient(context, event)) { await updateHourlyData(context, event.block.timestamp); } }); ponder.on("Stake:PositionTaxPaid", async ({ event, context }) => { await ensureStatsExists(context, event.block.timestamp); const positionId = event.args.positionId.toString(); const position = await context.db.find(positions, { id: positionId }); if (!position) return; const stakeTotalSupply = await getStakeTotalSupply(context); const shareRatio = toShareRatio(event.args.newShares, stakeTotalSupply); await context.db.update(positions, { id: positionId }).set({ taxPaid: BigInt(position.taxPaid ?? ZERO) + event.args.taxPaid, share: shareRatio, taxRate: TAX_RATES[Number(event.args.taxRate)] || position.taxRate, lastTaxTime: event.block.timestamp, }); // Only update ringbuffer if we have sufficient block history if (checkBlockHistorySufficient(context, event)) { const statsData = await context.db.find(stats, { id: STATS_ID }); if (statsData) { const ringBuffer = parseRingBuffer(statsData.ringBuffer as string[]); const pointer = statsData.ringBufferPointer ?? 0; const baseIndex = pointer * RING_BUFFER_SEGMENTS; ringBuffer[baseIndex + 3] = ringBuffer[baseIndex + 3] + event.args.taxPaid; await context.db.update(stats, { id: STATS_ID }).set({ ringBuffer: serializeRingBuffer(ringBuffer), totalTaxPaid: statsData.totalTaxPaid + event.args.taxPaid, }); } await updateHourlyData(context, event.block.timestamp); } else { // Insufficient history - update only totalTaxPaid without ringbuffer const statsData = await context.db.find(stats, { id: STATS_ID }); if (statsData) { await context.db.update(stats, { id: STATS_ID }).set({ totalTaxPaid: statsData.totalTaxPaid + event.args.taxPaid, }); } } await refreshOutstandingStake(context); }); ponder.on("Stake:PositionRateHiked", async ({ event, context }) => { const positionId = event.args.positionId.toString(); await context.db.update(positions, { id: positionId }).set({ taxRate: TAX_RATES[Number(event.args.newTaxRate)] || 0, }); });