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'; import type { StatsContext } from './helpers/stats'; const ZERO = 0n; async function getKraikenTotalSupply(context: StatsContext) { 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, }); });