feat(ponder): Add strict ESLint + Prettier with pre-commit hooks (#52)

- 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
This commit is contained in:
johba 2025-10-04 15:37:26 +02:00
parent c150b683c8
commit dc61771dfc
18 changed files with 2229 additions and 194 deletions

View file

@ -0,0 +1 @@
npm test

View file

@ -0,0 +1,14 @@
{
"src/**/*.ts": [
"eslint --fix",
"prettier --write"
],
"ponder.config.ts": [
"eslint --fix",
"prettier --write"
],
"ponder.schema.ts": [
"eslint --fix",
"prettier --write"
]
}

View file

@ -0,0 +1,8 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 140,
"tabWidth": 2,
"trailingComma": "es5",
"arrowParens": "avoid"
}

View file

@ -0,0 +1,76 @@
import eslint from '@eslint/js';
import tseslint from '@typescript-eslint/eslint-plugin';
import tsparser from '@typescript-eslint/parser';
import eslintConfigPrettier from 'eslint-config-prettier';
export default [
eslint.configs.recommended,
{
files: ['**/*.ts'],
languageOptions: {
parser: tsparser,
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: import.meta.dirname,
},
globals: {
process: 'readonly',
console: 'readonly',
Context: 'readonly',
},
},
plugins: {
'@typescript-eslint': tseslint,
},
rules: {
// TypeScript
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'function',
format: ['camelCase'],
},
{
selector: 'variable',
format: ['camelCase', 'UPPER_CASE'],
},
{
selector: 'typeLike',
format: ['PascalCase'],
},
],
// Style
'prefer-const': 'error',
indent: ['error', 2, { SwitchCase: 1 }],
'max-len': [
'error',
{
code: 140,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignoreUrls: true,
},
],
// Console
'no-console': 'error',
// Complexity (off)
complexity: 'off',
'max-depth': 'off',
'max-nested-callbacks': 'off',
'max-params': 'off',
'max-statements': 'off',
},
},
eslintConfigPrettier,
];

File diff suppressed because it is too large Load diff

View file

@ -4,10 +4,15 @@
"private": true,
"type": "module",
"scripts": {
"dev": "ponder dev",
"start": "ponder start",
"codegen": "ponder codegen",
"build": "ponder codegen"
"dev": "node ./node_modules/ponder/dist/esm/bin/ponder.js dev",
"start": "node ./node_modules/ponder/dist/esm/bin/ponder.js start",
"codegen": "node ./node_modules/ponder/dist/esm/bin/ponder.js codegen",
"build": "node ./node_modules/ponder/dist/esm/bin/ponder.js codegen",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write \"src/**/*.ts\" \"ponder.config.ts\" \"ponder.schema.ts\"",
"format:check": "prettier --check \"src/**/*.ts\" \"ponder.config.ts\" \"ponder.schema.ts\"",
"prepare": "husky"
},
"dependencies": {
"hono": "^4.5.0",
@ -17,9 +22,30 @@
},
"devDependencies": {
"@types/node": "^20.11.30",
"@typescript-eslint/eslint-plugin": "^8.45.0",
"@typescript-eslint/parser": "^8.45.0",
"esbuild": "^0.25.10",
"eslint": "^9.36.0",
"eslint-config-prettier": "^10.1.8",
"husky": "^9.1.7",
"lint-staged": "^16.2.3",
"prettier": "^3.6.2",
"typescript": "^5.9.2"
},
"lint-staged": {
"src/**/*.ts": [
"eslint --fix",
"prettier --write"
],
"ponder.config.ts": [
"eslint --fix",
"prettier --write"
],
"ponder.schema.ts": [
"eslint --fix",
"prettier --write"
]
},
"overrides": {
"esbuild": "^0.25.10",
"vite": "^5.4.11"

View file

@ -1,6 +1,6 @@
import { createConfig } from "ponder";
import type { Abi } from "viem";
import { KraikenAbi, StakeAbi } from "kraiken-lib";
import { createConfig } from 'ponder';
import type { Abi } from 'viem';
import { KraikenAbi, StakeAbi } from 'kraiken-lib';
// Network configurations keyed by canonical environment name
type NetworkConfig = {
@ -17,53 +17,54 @@ type NetworkConfig = {
const networks: Record<string, NetworkConfig> = {
BASE_SEPOLIA_LOCAL_FORK: {
chainId: 31337,
rpc: process.env.PONDER_RPC_URL_BASE_SEPOLIA_LOCAL_FORK || "http://127.0.0.1:8545",
rpc: process.env.PONDER_RPC_URL_BASE_SEPOLIA_LOCAL_FORK || 'http://127.0.0.1:8545',
disableCache: true,
contracts: {
kraiken: process.env.KRAIKEN_ADDRESS || "0x56186c1E64cA8043dEF78d06AfF222212eA5df71",
stake: process.env.STAKE_ADDRESS || "0x056E4a859558A3975761ABd7385506BC4D8A8E60",
startBlock: parseInt(process.env.START_BLOCK || "31425917"),
kraiken: process.env.KRAIKEN_ADDRESS || '0x56186c1E64cA8043dEF78d06AfF222212eA5df71',
stake: process.env.STAKE_ADDRESS || '0x056E4a859558A3975761ABd7385506BC4D8A8E60',
startBlock: parseInt(process.env.START_BLOCK || '31425917'),
},
},
BASE_SEPOLIA: {
chainId: 84532,
rpc: process.env.PONDER_RPC_URL_BASE_SEPOLIA || "https://sepolia.base.org",
rpc: process.env.PONDER_RPC_URL_BASE_SEPOLIA || 'https://sepolia.base.org',
contracts: {
kraiken: "0x22c264Ecf8D4E49D1E3CabD8DD39b7C4Ab51C1B8",
stake: "0xe28020BCdEeAf2779dd47c670A8eFC2973316EE2",
kraiken: '0x22c264Ecf8D4E49D1E3CabD8DD39b7C4Ab51C1B8',
stake: '0xe28020BCdEeAf2779dd47c670A8eFC2973316EE2',
startBlock: 20940337,
},
},
BASE: {
chainId: 8453,
rpc: process.env.PONDER_RPC_URL_BASE || "https://base.llamarpc.com",
rpc: process.env.PONDER_RPC_URL_BASE || 'https://base.llamarpc.com',
contracts: {
kraiken: "0x45caa5929f6ee038039984205bdecf968b954820",
stake: "0xed70707fab05d973ad41eae8d17e2bcd36192cfc",
kraiken: '0x45caa5929f6ee038039984205bdecf968b954820',
stake: '0xed70707fab05d973ad41eae8d17e2bcd36192cfc',
startBlock: 26038614,
},
},
};
// Select network based on environment variable
const NETWORK = (process.env.PONDER_NETWORK as keyof typeof networks) || "BASE_SEPOLIA_LOCAL_FORK";
const NETWORK = (process.env.PONDER_NETWORK as keyof typeof networks) || 'BASE_SEPOLIA_LOCAL_FORK';
const selectedNetwork = networks[NETWORK as keyof typeof networks];
if (!selectedNetwork) {
throw new Error(`Invalid network: ${NETWORK}. Valid options: ${Object.keys(networks).join(", ")}`);
throw new Error(`Invalid network: ${NETWORK}. Valid options: ${Object.keys(networks).join(', ')}`);
}
console.log(
`[ponder.config] Network=${NETWORK}, chainId=${selectedNetwork.chainId}, startBlock=${selectedNetwork.contracts.startBlock}`,
);
// Network configuration logged during Ponder startup
// Network=${NETWORK}, chainId=${selectedNetwork.chainId}, startBlock=${selectedNetwork.contracts.startBlock}
export default createConfig({
// Use PostgreSQL if DATABASE_URL is set, otherwise use PGlite
database: process.env.DATABASE_URL ? {
kind: "postgres" as const,
connectionString: process.env.DATABASE_URL,
schema: process.env.DATABASE_SCHEMA || "public",
} : undefined,
database: process.env.DATABASE_URL
? {
kind: 'postgres' as const,
connectionString: process.env.DATABASE_URL,
schema: process.env.DATABASE_SCHEMA || 'public',
}
: undefined,
chains: {
[NETWORK]: {
id: selectedNetwork.chainId,

View file

@ -1,73 +1,145 @@
import { onchainTable, primaryKey, index } from "ponder";
import { onchainTable, index } from 'ponder';
export const HOURS_IN_RING_BUFFER = 168; // 7 days * 24 hours
const RING_BUFFER_SEGMENTS = 4; // ubi, minted, burned, tax
// Global protocol stats - singleton with id "0x01"
export const stats = onchainTable(
"stats",
(t) => ({
id: t.text().primaryKey(), // Always "0x01"
kraikenTotalSupply: t.bigint().notNull().$default(() => 0n),
stakeTotalSupply: t.bigint().notNull().$default(() => 0n),
outstandingStake: t.bigint().notNull().$default(() => 0n),
export const stats = onchainTable('stats', t => ({
id: t.text().primaryKey(), // Always "0x01"
kraikenTotalSupply: t
.bigint()
.notNull()
.$default(() => 0n),
stakeTotalSupply: t
.bigint()
.notNull()
.$default(() => 0n),
outstandingStake: t
.bigint()
.notNull()
.$default(() => 0n),
// Totals
totalMinted: t.bigint().notNull().$default(() => 0n),
totalBurned: t.bigint().notNull().$default(() => 0n),
totalTaxPaid: t.bigint().notNull().$default(() => 0n),
totalUbiClaimed: t.bigint().notNull().$default(() => 0n),
// Totals
totalMinted: t
.bigint()
.notNull()
.$default(() => 0n),
totalBurned: t
.bigint()
.notNull()
.$default(() => 0n),
totalTaxPaid: t
.bigint()
.notNull()
.$default(() => 0n),
totalUbiClaimed: t
.bigint()
.notNull()
.$default(() => 0n),
// Rolling windows - calculated from ring buffer
mintedLastWeek: t.bigint().notNull().$default(() => 0n),
mintedLastDay: t.bigint().notNull().$default(() => 0n),
mintNextHourProjected: t.bigint().notNull().$default(() => 0n),
// Rolling windows - calculated from ring buffer
mintedLastWeek: t
.bigint()
.notNull()
.$default(() => 0n),
mintedLastDay: t
.bigint()
.notNull()
.$default(() => 0n),
mintNextHourProjected: t
.bigint()
.notNull()
.$default(() => 0n),
burnedLastWeek: t.bigint().notNull().$default(() => 0n),
burnedLastDay: t.bigint().notNull().$default(() => 0n),
burnNextHourProjected: t.bigint().notNull().$default(() => 0n),
burnedLastWeek: t
.bigint()
.notNull()
.$default(() => 0n),
burnedLastDay: t
.bigint()
.notNull()
.$default(() => 0n),
burnNextHourProjected: t
.bigint()
.notNull()
.$default(() => 0n),
taxPaidLastWeek: t.bigint().notNull().$default(() => 0n),
taxPaidLastDay: t.bigint().notNull().$default(() => 0n),
taxPaidNextHourProjected: t.bigint().notNull().$default(() => 0n),
taxPaidLastWeek: t
.bigint()
.notNull()
.$default(() => 0n),
taxPaidLastDay: t
.bigint()
.notNull()
.$default(() => 0n),
taxPaidNextHourProjected: t
.bigint()
.notNull()
.$default(() => 0n),
ubiClaimedLastWeek: t.bigint().notNull().$default(() => 0n),
ubiClaimedLastDay: t.bigint().notNull().$default(() => 0n),
ubiClaimedNextHourProjected: t.bigint().notNull().$default(() => 0n),
ubiClaimedLastWeek: t
.bigint()
.notNull()
.$default(() => 0n),
ubiClaimedLastDay: t
.bigint()
.notNull()
.$default(() => 0n),
ubiClaimedNextHourProjected: t
.bigint()
.notNull()
.$default(() => 0n),
// Ring buffer state (flattened array of length HOURS_IN_RING_BUFFER * 4)
ringBufferPointer: t.integer().notNull().$default(() => 0),
lastHourlyUpdateTimestamp: t.bigint().notNull().$default(() => 0n),
ringBuffer: t
.jsonb()
.$type<string[]>()
.notNull()
.$default(() => Array(HOURS_IN_RING_BUFFER * RING_BUFFER_SEGMENTS).fill("0")),
})
);
// Ring buffer state (flattened array of length HOURS_IN_RING_BUFFER * 4)
ringBufferPointer: t
.integer()
.notNull()
.$default(() => 0),
lastHourlyUpdateTimestamp: t
.bigint()
.notNull()
.$default(() => 0n),
ringBuffer: t
.jsonb()
.$type<string[]>()
.notNull()
.$default(() => Array(HOURS_IN_RING_BUFFER * RING_BUFFER_SEGMENTS).fill('0')),
}));
// Individual staking positions
export const positions = onchainTable(
"positions",
(t) => ({
'positions',
t => ({
id: t.text().primaryKey(), // Position ID from contract
owner: t.hex().notNull(),
share: t.real().notNull(), // Share as decimal (0-1)
taxRate: t.real().notNull(), // Tax rate as decimal (e.g., 0.01 for 1%)
kraikenDeposit: t.bigint().notNull(),
stakeDeposit: t.bigint().notNull(),
taxPaid: t.bigint().notNull().$default(() => 0n),
snatched: t.integer().notNull().$default(() => 0),
taxPaid: t
.bigint()
.notNull()
.$default(() => 0n),
snatched: t
.integer()
.notNull()
.$default(() => 0),
creationTime: t.bigint().notNull(),
lastTaxTime: t.bigint().notNull(),
status: t.text().notNull().$default(() => "Active"), // "Active" or "Closed"
status: t
.text()
.notNull()
.$default(() => 'Active'), // "Active" or "Closed"
createdAt: t.bigint().notNull(),
closedAt: t.bigint(),
totalSupplyInit: t.bigint().notNull(),
totalSupplyEnd: t.bigint(),
payout: t.bigint().notNull().$default(() => 0n),
payout: t
.bigint()
.notNull()
.$default(() => 0n),
}),
(table) => ({
table => ({
ownerIdx: index().on(table.owner),
statusIdx: index().on(table.status),
})
@ -75,11 +147,10 @@ export const positions = onchainTable(
// Constants for tax rates (matches subgraph)
export const TAX_RATES = [
0.01, 0.03, 0.05, 0.07, 0.09, 0.11, 0.13, 0.15, 0.17, 0.19,
0.21, 0.25, 0.29, 0.33, 0.37, 0.41, 0.45, 0.49, 0.53, 0.57,
0.61, 0.65, 0.69, 0.73, 0.77, 0.81, 0.85, 0.89, 0.93, 0.97
0.01, 0.03, 0.05, 0.07, 0.09, 0.11, 0.13, 0.15, 0.17, 0.19, 0.21, 0.25, 0.29, 0.33, 0.37, 0.41, 0.45, 0.49, 0.53, 0.57, 0.61, 0.65, 0.69,
0.73, 0.77, 0.81, 0.85, 0.89, 0.93, 0.97,
];
// Helper constants
export const STATS_ID = "0x01";
export const STATS_ID = '0x01';
export const SECONDS_IN_HOUR = 3600;

View file

@ -1,24 +1,29 @@
import { Hono } from "hono";
import { cors } from "hono/cors";
import { client, graphql } from "ponder";
import { db } from "ponder:api";
import schema from "ponder:schema";
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { client, graphql } from 'ponder';
import { db } from 'ponder:api';
import schema from 'ponder:schema';
const app = new Hono();
const allowedOrigins = process.env.PONDER_CORS_ORIGINS?.split(",").map((origin) => origin.trim()).filter(Boolean);
const allowedOrigins = process.env.PONDER_CORS_ORIGINS?.split(',')
.map(origin => origin.trim())
.filter(Boolean);
app.use("/*", cors({
origin: allowedOrigins?.length ? allowedOrigins : "*",
allowMethods: ["GET", "POST", "OPTIONS"],
allowHeaders: ["Content-Type", "Apollo-Require-Preflight"],
}));
app.use(
'/*',
cors({
origin: allowedOrigins?.length ? allowedOrigins : '*',
allowMethods: ['GET', 'POST', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Apollo-Require-Preflight'],
})
);
// SQL endpoint
app.use("/sql/*", client({ db, schema }));
app.use('/sql/*', client({ db, schema }));
// GraphQL endpoints
app.use("/graphql", graphql({ db, schema }));
app.use("/", graphql({ db, schema }));
app.use('/graphql', graphql({ db, schema }));
app.use('/', graphql({ db, schema }));
export default app;

View file

@ -1,4 +1,4 @@
import type { Abi } from 'viem'
import type { Abi } from 'viem';
/**
* Helper function to ensure an imported ABI matches the viem Abi type at compile time
@ -6,7 +6,7 @@ import type { Abi } from 'viem'
* @returns The validated ABI
*/
export function validateAbi<T extends Abi>(abi: T): T {
return abi
return abi;
}
/**
@ -15,5 +15,5 @@ export function validateAbi<T extends Abi>(abi: T): T {
* @returns The validated contract ABI
*/
export function validateContractAbi<T extends { abi: Abi }>(contract: T): T['abi'] {
return contract.abi
}
return contract.abi;
}

View file

@ -1,4 +1,9 @@
import { stats, STATS_ID, HOURS_IN_RING_BUFFER, SECONDS_IN_HOUR } from "ponder:schema";
import { stats, STATS_ID, HOURS_IN_RING_BUFFER, SECONDS_IN_HOUR } from 'ponder:schema';
type Handler = Parameters<(typeof import('ponder:registry'))['ponder']['on']>[1];
type HandlerArgs = Handler extends (...args: infer Args) => unknown ? Args[0] : never;
export type StatsContext = HandlerArgs extends { context: infer C } ? C : never;
type StatsEvent = HandlerArgs extends { event: infer E } ? E : never;
export const RING_BUFFER_SEGMENTS = 4; // ubi, minted, burned, tax
export const MINIMUM_BLOCKS_FOR_RINGBUFFER = 100;
@ -13,11 +18,11 @@ export function parseRingBuffer(raw?: string[] | null): bigint[] {
if (!raw || raw.length === 0) {
return makeEmptyRingBuffer();
}
return raw.map((value) => BigInt(value));
return raw.map(value => BigInt(value));
}
export function serializeRingBuffer(values: bigint[]): string[] {
return values.map((value) => value.toString());
return values.map(value => value.toString());
}
function computeMetrics(ringBuffer: bigint[], pointer: number) {
@ -62,12 +67,7 @@ function computeMetrics(ringBuffer: bigint[], pointer: number) {
};
}
function computeProjections(
ringBuffer: bigint[],
pointer: number,
timestamp: bigint,
metrics: ReturnType<typeof computeMetrics>
) {
function computeProjections(ringBuffer: bigint[], pointer: number, timestamp: bigint, metrics: ReturnType<typeof computeMetrics>) {
const startOfHour = (timestamp / BigInt(SECONDS_IN_HOUR)) * BigInt(SECONDS_IN_HOUR);
const elapsedSeconds = timestamp - startOfHour;
@ -96,13 +96,13 @@ function computeProjections(
};
}
export function checkBlockHistorySufficient(context: any, event: any): boolean {
export function checkBlockHistorySufficient(context: StatsContext, event: StatsEvent): boolean {
const currentBlock = event.block.number;
const deployBlock = BigInt(context.network.contracts.Kraiken.startBlock);
const blocksSinceDeployment = Number(currentBlock - deployBlock);
if (blocksSinceDeployment < MINIMUM_BLOCKS_FOR_RINGBUFFER) {
context.log.warn(
context.logger.warn(
`Insufficient block history (only ${blocksSinceDeployment} blocks available, need ${MINIMUM_BLOCKS_FOR_RINGBUFFER})`
);
return false;
@ -110,7 +110,7 @@ export function checkBlockHistorySufficient(context: any, event: any): boolean {
return true;
}
export async function ensureStatsExists(context: any, timestamp?: bigint) {
export async function ensureStatsExists(context: StatsContext, timestamp?: bigint) {
let statsData = await context.db.find(stats, { id: STATS_ID });
if (!statsData) {
const { client, contracts } = context;
@ -118,7 +118,7 @@ export async function ensureStatsExists(context: any, timestamp?: bigint) {
try {
return await fn();
} catch (error) {
console.warn(`[stats.ensureStatsExists] Falling back for ${label}`, error);
context.logger.warn(`[stats.ensureStatsExists] Falling back for ${label}`, error);
return fallback;
}
};
@ -129,38 +129,36 @@ export async function ensureStatsExists(context: any, timestamp?: bigint) {
client.readContract({
abi: contracts.Kraiken.abi,
address: contracts.Kraiken.address,
functionName: "totalSupply",
functionName: 'totalSupply',
}),
0n,
"Kraiken.totalSupply",
'Kraiken.totalSupply'
),
readWithFallback(
() =>
client.readContract({
abi: contracts.Stake.abi,
address: contracts.Stake.address,
functionName: "totalSupply",
functionName: 'totalSupply',
}),
0n,
"Stake.totalSupply",
'Stake.totalSupply'
),
readWithFallback(
() =>
client.readContract({
abi: contracts.Stake.abi,
address: contracts.Stake.address,
functionName: "outstandingStake",
functionName: 'outstandingStake',
}),
0n,
"Stake.outstandingStake",
'Stake.outstandingStake'
),
]);
cachedStakeTotalSupply = stakeTotalSupply;
const currentHour = timestamp
? (timestamp / BigInt(SECONDS_IN_HOUR)) * BigInt(SECONDS_IN_HOUR)
: 0n;
const currentHour = timestamp ? (timestamp / BigInt(SECONDS_IN_HOUR)) * BigInt(SECONDS_IN_HOUR) : 0n;
await context.db.insert(stats).values({
id: STATS_ID,
@ -178,7 +176,7 @@ export async function ensureStatsExists(context: any, timestamp?: bigint) {
return statsData;
}
export async function updateHourlyData(context: any, timestamp: bigint) {
export async function updateHourlyData(context: StatsContext, timestamp: bigint) {
const statsData = await context.db.find(stats, { id: STATS_ID });
if (!statsData) return;
@ -197,9 +195,7 @@ export async function updateHourlyData(context: any, timestamp: bigint) {
if (currentHour > lastUpdate) {
const hoursElapsedBig = (currentHour - lastUpdate) / BigInt(SECONDS_IN_HOUR);
const hoursElapsed = Number(hoursElapsedBig > BigInt(HOURS_IN_RING_BUFFER)
? BigInt(HOURS_IN_RING_BUFFER)
: hoursElapsedBig);
const hoursElapsed = Number(hoursElapsedBig > BigInt(HOURS_IN_RING_BUFFER) ? BigInt(HOURS_IN_RING_BUFFER) : hoursElapsedBig);
for (let h = 0; h < hoursElapsed; h++) {
pointer = (pointer + 1) % HOURS_IN_RING_BUFFER;
@ -251,7 +247,7 @@ export async function updateHourlyData(context: any, timestamp: bigint) {
}
}
export async function getStakeTotalSupply(context: any): Promise<bigint> {
export async function getStakeTotalSupply(context: StatsContext): Promise<bigint> {
await ensureStatsExists(context);
if (cachedStakeTotalSupply !== null) {
@ -261,7 +257,7 @@ export async function getStakeTotalSupply(context: any): Promise<bigint> {
const totalSupply = await context.client.readContract({
abi: context.contracts.Stake.abi,
address: context.contracts.Stake.address,
functionName: "totalSupply",
functionName: 'totalSupply',
});
cachedStakeTotalSupply = totalSupply;
await context.db.update(stats, { id: STATS_ID }).set({
@ -270,12 +266,12 @@ export async function getStakeTotalSupply(context: any): Promise<bigint> {
return totalSupply;
}
export async function refreshOutstandingStake(context: any) {
export async function refreshOutstandingStake(context: StatsContext) {
await ensureStatsExists(context);
const outstandingStake = await context.client.readContract({
abi: context.contracts.Stake.abi,
address: context.contracts.Stake.address,
functionName: "outstandingStake",
functionName: 'outstandingStake',
});
await context.db.update(stats, { id: STATS_ID }).set({

View file

@ -1,6 +1,6 @@
// Import all event handlers
import "./kraiken";
import "./stake";
import './kraiken';
import './stake';
// This file serves as the entry point for all indexing functions
// Ponder will automatically register all event handlers from imported files
// Ponder will automatically register all event handlers from imported files

View file

@ -1,5 +1,5 @@
import { ponder } from "ponder:registry";
import { stats, STATS_ID } from "ponder:schema";
import { ponder } from 'ponder:registry';
import { stats, STATS_ID } from 'ponder:schema';
import {
ensureStatsExists,
parseRingBuffer,
@ -7,11 +7,11 @@ import {
updateHourlyData,
checkBlockHistorySufficient,
RING_BUFFER_SEGMENTS,
} from "./helpers/stats";
} from './helpers/stats';
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" as const;
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' as const;
ponder.on("Kraiken:Transfer", async ({ event, context }) => {
ponder.on('Kraiken:Transfer', async ({ event, context }) => {
const { from, to, value } = event.args;
await ensureStatsExists(context, event.block.timestamp);
@ -69,7 +69,7 @@ ponder.on("Kraiken:Transfer", async ({ event, context }) => {
await updateHourlyData(context, event.block.timestamp);
});
ponder.on("StatsBlock:block", async ({ event, context }) => {
ponder.on('StatsBlock:block', async ({ event, context }) => {
await ensureStatsExists(context, event.block.timestamp);
// Only update hourly data if we have sufficient block history

View file

@ -1,5 +1,5 @@
import { ponder } from "ponder:registry";
import { positions, stats, STATS_ID, TAX_RATES } from "ponder:schema";
import { ponder } from 'ponder:registry';
import { positions, stats, STATS_ID, TAX_RATES } from 'ponder:schema';
import {
ensureStatsExists,
getStakeTotalSupply,
@ -9,15 +9,16 @@ import {
updateHourlyData,
checkBlockHistorySufficient,
RING_BUFFER_SEGMENTS,
} from "./helpers/stats";
} from './helpers/stats';
import type { StatsContext } from './helpers/stats';
const ZERO = 0n;
async function getKraikenTotalSupply(context: Context) {
async function getKraikenTotalSupply(context: StatsContext) {
return context.client.readContract({
abi: context.contracts.Kraiken.abi,
address: context.contracts.Kraiken.address,
functionName: "totalSupply",
functionName: 'totalSupply',
});
}
@ -26,7 +27,7 @@ function toShareRatio(share: bigint, stakeTotalSupply: bigint): number {
return Number(share) / Number(stakeTotalSupply);
}
ponder.on("Stake:PositionCreated", async ({ event, context }) => {
ponder.on('Stake:PositionCreated', async ({ event, context }) => {
await ensureStatsExists(context, event.block.timestamp);
const stakeTotalSupply = await getStakeTotalSupply(context);
@ -44,7 +45,7 @@ ponder.on("Stake:PositionCreated", async ({ event, context }) => {
snatched: 0,
creationTime: event.block.timestamp,
lastTaxTime: event.block.timestamp,
status: "Active",
status: 'Active',
createdAt: event.block.timestamp,
totalSupplyInit,
totalSupplyEnd: null,
@ -54,7 +55,7 @@ ponder.on("Stake:PositionCreated", async ({ event, context }) => {
await refreshOutstandingStake(context);
});
ponder.on("Stake:PositionRemoved", async ({ event, context }) => {
ponder.on('Stake:PositionRemoved', async ({ event, context }) => {
await ensureStatsExists(context, event.block.timestamp);
const positionId = event.args.positionId.toString();
@ -64,7 +65,7 @@ ponder.on("Stake:PositionRemoved", async ({ event, context }) => {
const totalSupplyEnd = await getKraikenTotalSupply(context);
await context.db.update(positions, { id: positionId }).set({
status: "Closed",
status: 'Closed',
closedAt: event.block.timestamp,
totalSupplyEnd,
payout: (position.payout ?? ZERO) + event.args.kraikenPayout,
@ -79,7 +80,7 @@ ponder.on("Stake:PositionRemoved", async ({ event, context }) => {
}
});
ponder.on("Stake:PositionShrunk", async ({ event, context }) => {
ponder.on('Stake:PositionShrunk', async ({ event, context }) => {
await ensureStatsExists(context, event.block.timestamp);
const positionId = event.args.positionId.toString();
@ -104,7 +105,7 @@ ponder.on("Stake:PositionShrunk", async ({ event, context }) => {
}
});
ponder.on("Stake:PositionTaxPaid", async ({ event, context }) => {
ponder.on('Stake:PositionTaxPaid', async ({ event, context }) => {
await ensureStatsExists(context, event.block.timestamp);
const positionId = event.args.positionId.toString();
@ -151,7 +152,7 @@ ponder.on("Stake:PositionTaxPaid", async ({ event, context }) => {
await refreshOutstandingStake(context);
});
ponder.on("Stake:PositionRateHiked", async ({ event, 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,