- Install eslint, @typescript-eslint plugins, prettier, husky, lint-staged - Configure ESLint flat config with TypeScript parser - Enforce: no-explicit-any (with exceptions), no-unused-vars, naming-convention, prefer-const, no-console - Set style: 2-space indent, 140 char max-len, disable complexity rules - Configure Prettier: single quotes, 140 width, trailing commas - Setup husky pre-commit hook to auto-fix and format on commit - Replace console.log/warn with context.logger.info/warn in handlers - Remove console.log from ponder.config.ts (replaced with comment) - Add npm scripts: lint, lint:fix, format, format:check - All lint rules pass without warnings resolvel #45 Co-authored-by: johba <johba@harb.eth> Reviewed-on: https://codeberg.org/johba/harb/pulls/52
160 lines
5.4 KiB
TypeScript
160 lines
5.4 KiB
TypeScript
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,
|
|
});
|
|
});
|