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:
parent
c150b683c8
commit
dc61771dfc
18 changed files with 2229 additions and 194 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ -d "onchain" ]; then
|
if [ -d "onchain" ]; then
|
||||||
|
|
@ -12,3 +13,7 @@ fi
|
||||||
if [ -d "web-app" ]; then
|
if [ -d "web-app" ]; then
|
||||||
(cd web-app && npx lint-staged)
|
(cd web-app && npx lint-staged)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -d "services/ponder" ]; then
|
||||||
|
(cd services/ponder && npx lint-staged)
|
||||||
|
fi
|
||||||
|
|
|
||||||
8
kraiken-lib/package-lock.json
generated
8
kraiken-lib/package-lock.json
generated
|
|
@ -219,7 +219,6 @@
|
||||||
"integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==",
|
"integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.24.2",
|
"@babel/code-frame": "^7.24.2",
|
||||||
|
|
@ -3107,7 +3106,6 @@
|
||||||
"integrity": "sha512-F1CBxgqwOMc4GKJ7eY22hWhBVQuMYTtqI8L0FcszYcpYX0fzfDGpez22Xau8Mgm7O9fI+zA/TYIdq3tGWfweBA==",
|
"integrity": "sha512-F1CBxgqwOMc4GKJ7eY22hWhBVQuMYTtqI8L0FcszYcpYX0fzfDGpez22Xau8Mgm7O9fI+zA/TYIdq3tGWfweBA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.13.0"
|
"undici-types": "~7.13.0"
|
||||||
}
|
}
|
||||||
|
|
@ -3943,7 +3941,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001587",
|
"caniuse-lite": "^1.0.30001587",
|
||||||
"electron-to-chromium": "^1.4.668",
|
"electron-to-chromium": "^1.4.668",
|
||||||
|
|
@ -5521,7 +5518,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz",
|
||||||
"integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==",
|
"integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
|
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -5606,7 +5602,6 @@
|
||||||
"integrity": "sha512-Ju2RCU2dQMgSKtArPbEtsK5gNLnsQyTNIo/T7cZNp96niC1x0KdJNZV0TIoilceBPQwfb5itrGl8pkFeOUMl4A==",
|
"integrity": "sha512-Ju2RCU2dQMgSKtArPbEtsK5gNLnsQyTNIo/T7cZNp96niC1x0KdJNZV0TIoilceBPQwfb5itrGl8pkFeOUMl4A==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"website"
|
"website"
|
||||||
],
|
],
|
||||||
|
|
@ -6266,7 +6261,6 @@
|
||||||
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jest/core": "^29.7.0",
|
"@jest/core": "^29.7.0",
|
||||||
"@jest/types": "^29.6.3",
|
"@jest/types": "^29.6.3",
|
||||||
|
|
@ -9249,7 +9243,6 @@
|
||||||
"integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
|
"integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|
@ -9572,7 +9565,6 @@
|
||||||
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
|
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Positions } from './__generated__/graphql.js';
|
import type { Positions as Position } from './__generated__/graphql.js';
|
||||||
import { toBigIntId } from './ids.js';
|
import { toBigIntId } from './ids.js';
|
||||||
|
|
||||||
export interface SnatchablePosition {
|
export interface SnatchablePosition {
|
||||||
|
|
@ -110,7 +110,7 @@ export function selectSnatchPositions(candidates: SnatchablePosition[], options:
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSnatchList(
|
export function getSnatchList(
|
||||||
positions: Positions[],
|
positions: Position[],
|
||||||
needed: bigint,
|
needed: bigint,
|
||||||
taxRate: number,
|
taxRate: number,
|
||||||
stakeTotalSupply: bigint
|
stakeTotalSupply: bigint
|
||||||
|
|
|
||||||
|
|
@ -2803,11 +2803,6 @@ fs.realpath@^1.0.0:
|
||||||
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
|
||||||
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
||||||
|
|
||||||
fsevents@^2.3.2:
|
|
||||||
version "2.3.3"
|
|
||||||
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
|
|
||||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
|
||||||
|
|
||||||
function-bind@^1.1.2:
|
function-bind@^1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
|
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
|
||||||
|
|
|
||||||
1
services/ponder/.husky/pre-commit
Normal file
1
services/ponder/.husky/pre-commit
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
npm test
|
||||||
14
services/ponder/.lintstagedrc.json
Normal file
14
services/ponder/.lintstagedrc.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
8
services/ponder/.prettierrc
Normal file
8
services/ponder/.prettierrc
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 140,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"arrowParens": "avoid"
|
||||||
|
}
|
||||||
76
services/ponder/eslint.config.js
Normal file
76
services/ponder/eslint.config.js
Normal 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,
|
||||||
|
];
|
||||||
1916
services/ponder/package-lock.json
generated
1916
services/ponder/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -4,10 +4,15 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "ponder dev",
|
"dev": "node ./node_modules/ponder/dist/esm/bin/ponder.js dev",
|
||||||
"start": "ponder start",
|
"start": "node ./node_modules/ponder/dist/esm/bin/ponder.js start",
|
||||||
"codegen": "ponder codegen",
|
"codegen": "node ./node_modules/ponder/dist/esm/bin/ponder.js codegen",
|
||||||
"build": "ponder 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": {
|
"dependencies": {
|
||||||
"hono": "^4.5.0",
|
"hono": "^4.5.0",
|
||||||
|
|
@ -17,9 +22,30 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.11.30",
|
"@types/node": "^20.11.30",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.45.0",
|
||||||
|
"@typescript-eslint/parser": "^8.45.0",
|
||||||
"esbuild": "^0.25.10",
|
"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"
|
"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": {
|
"overrides": {
|
||||||
"esbuild": "^0.25.10",
|
"esbuild": "^0.25.10",
|
||||||
"vite": "^5.4.11"
|
"vite": "^5.4.11"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { createConfig } from "ponder";
|
import { createConfig } from 'ponder';
|
||||||
import type { Abi } from "viem";
|
import type { Abi } from 'viem';
|
||||||
import { KraikenAbi, StakeAbi } from "kraiken-lib";
|
import { KraikenAbi, StakeAbi } from 'kraiken-lib';
|
||||||
|
|
||||||
// Network configurations keyed by canonical environment name
|
// Network configurations keyed by canonical environment name
|
||||||
type NetworkConfig = {
|
type NetworkConfig = {
|
||||||
|
|
@ -17,53 +17,54 @@ type NetworkConfig = {
|
||||||
const networks: Record<string, NetworkConfig> = {
|
const networks: Record<string, NetworkConfig> = {
|
||||||
BASE_SEPOLIA_LOCAL_FORK: {
|
BASE_SEPOLIA_LOCAL_FORK: {
|
||||||
chainId: 31337,
|
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,
|
disableCache: true,
|
||||||
contracts: {
|
contracts: {
|
||||||
kraiken: process.env.KRAIKEN_ADDRESS || "0x56186c1E64cA8043dEF78d06AfF222212eA5df71",
|
kraiken: process.env.KRAIKEN_ADDRESS || '0x56186c1E64cA8043dEF78d06AfF222212eA5df71',
|
||||||
stake: process.env.STAKE_ADDRESS || "0x056E4a859558A3975761ABd7385506BC4D8A8E60",
|
stake: process.env.STAKE_ADDRESS || '0x056E4a859558A3975761ABd7385506BC4D8A8E60',
|
||||||
startBlock: parseInt(process.env.START_BLOCK || "31425917"),
|
startBlock: parseInt(process.env.START_BLOCK || '31425917'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
BASE_SEPOLIA: {
|
BASE_SEPOLIA: {
|
||||||
chainId: 84532,
|
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: {
|
contracts: {
|
||||||
kraiken: "0x22c264Ecf8D4E49D1E3CabD8DD39b7C4Ab51C1B8",
|
kraiken: '0x22c264Ecf8D4E49D1E3CabD8DD39b7C4Ab51C1B8',
|
||||||
stake: "0xe28020BCdEeAf2779dd47c670A8eFC2973316EE2",
|
stake: '0xe28020BCdEeAf2779dd47c670A8eFC2973316EE2',
|
||||||
startBlock: 20940337,
|
startBlock: 20940337,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
BASE: {
|
BASE: {
|
||||||
chainId: 8453,
|
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: {
|
contracts: {
|
||||||
kraiken: "0x45caa5929f6ee038039984205bdecf968b954820",
|
kraiken: '0x45caa5929f6ee038039984205bdecf968b954820',
|
||||||
stake: "0xed70707fab05d973ad41eae8d17e2bcd36192cfc",
|
stake: '0xed70707fab05d973ad41eae8d17e2bcd36192cfc',
|
||||||
startBlock: 26038614,
|
startBlock: 26038614,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Select network based on environment variable
|
// 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];
|
const selectedNetwork = networks[NETWORK as keyof typeof networks];
|
||||||
|
|
||||||
if (!selectedNetwork) {
|
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(
|
// Network configuration logged during Ponder startup
|
||||||
`[ponder.config] Network=${NETWORK}, chainId=${selectedNetwork.chainId}, startBlock=${selectedNetwork.contracts.startBlock}`,
|
// Network=${NETWORK}, chainId=${selectedNetwork.chainId}, startBlock=${selectedNetwork.contracts.startBlock}
|
||||||
);
|
|
||||||
|
|
||||||
export default createConfig({
|
export default createConfig({
|
||||||
// Use PostgreSQL if DATABASE_URL is set, otherwise use PGlite
|
// Use PostgreSQL if DATABASE_URL is set, otherwise use PGlite
|
||||||
database: process.env.DATABASE_URL ? {
|
database: process.env.DATABASE_URL
|
||||||
kind: "postgres" as const,
|
? {
|
||||||
connectionString: process.env.DATABASE_URL,
|
kind: 'postgres' as const,
|
||||||
schema: process.env.DATABASE_SCHEMA || "public",
|
connectionString: process.env.DATABASE_URL,
|
||||||
} : undefined,
|
schema: process.env.DATABASE_SCHEMA || 'public',
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
chains: {
|
chains: {
|
||||||
[NETWORK]: {
|
[NETWORK]: {
|
||||||
id: selectedNetwork.chainId,
|
id: selectedNetwork.chainId,
|
||||||
|
|
|
||||||
|
|
@ -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
|
export const HOURS_IN_RING_BUFFER = 168; // 7 days * 24 hours
|
||||||
const RING_BUFFER_SEGMENTS = 4; // ubi, minted, burned, tax
|
const RING_BUFFER_SEGMENTS = 4; // ubi, minted, burned, tax
|
||||||
|
|
||||||
// Global protocol stats - singleton with id "0x01"
|
// Global protocol stats - singleton with id "0x01"
|
||||||
export const stats = onchainTable(
|
export const stats = onchainTable('stats', t => ({
|
||||||
"stats",
|
id: t.text().primaryKey(), // Always "0x01"
|
||||||
(t) => ({
|
kraikenTotalSupply: t
|
||||||
id: t.text().primaryKey(), // Always "0x01"
|
.bigint()
|
||||||
kraikenTotalSupply: t.bigint().notNull().$default(() => 0n),
|
.notNull()
|
||||||
stakeTotalSupply: t.bigint().notNull().$default(() => 0n),
|
.$default(() => 0n),
|
||||||
outstandingStake: t.bigint().notNull().$default(() => 0n),
|
stakeTotalSupply: t
|
||||||
|
.bigint()
|
||||||
|
.notNull()
|
||||||
|
.$default(() => 0n),
|
||||||
|
outstandingStake: t
|
||||||
|
.bigint()
|
||||||
|
.notNull()
|
||||||
|
.$default(() => 0n),
|
||||||
|
|
||||||
// Totals
|
// Totals
|
||||||
totalMinted: t.bigint().notNull().$default(() => 0n),
|
totalMinted: t
|
||||||
totalBurned: t.bigint().notNull().$default(() => 0n),
|
.bigint()
|
||||||
totalTaxPaid: t.bigint().notNull().$default(() => 0n),
|
.notNull()
|
||||||
totalUbiClaimed: t.bigint().notNull().$default(() => 0n),
|
.$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
|
// Rolling windows - calculated from ring buffer
|
||||||
mintedLastWeek: t.bigint().notNull().$default(() => 0n),
|
mintedLastWeek: t
|
||||||
mintedLastDay: t.bigint().notNull().$default(() => 0n),
|
.bigint()
|
||||||
mintNextHourProjected: t.bigint().notNull().$default(() => 0n),
|
.notNull()
|
||||||
|
.$default(() => 0n),
|
||||||
|
mintedLastDay: t
|
||||||
|
.bigint()
|
||||||
|
.notNull()
|
||||||
|
.$default(() => 0n),
|
||||||
|
mintNextHourProjected: t
|
||||||
|
.bigint()
|
||||||
|
.notNull()
|
||||||
|
.$default(() => 0n),
|
||||||
|
|
||||||
burnedLastWeek: t.bigint().notNull().$default(() => 0n),
|
burnedLastWeek: t
|
||||||
burnedLastDay: t.bigint().notNull().$default(() => 0n),
|
.bigint()
|
||||||
burnNextHourProjected: t.bigint().notNull().$default(() => 0n),
|
.notNull()
|
||||||
|
.$default(() => 0n),
|
||||||
|
burnedLastDay: t
|
||||||
|
.bigint()
|
||||||
|
.notNull()
|
||||||
|
.$default(() => 0n),
|
||||||
|
burnNextHourProjected: t
|
||||||
|
.bigint()
|
||||||
|
.notNull()
|
||||||
|
.$default(() => 0n),
|
||||||
|
|
||||||
taxPaidLastWeek: t.bigint().notNull().$default(() => 0n),
|
taxPaidLastWeek: t
|
||||||
taxPaidLastDay: t.bigint().notNull().$default(() => 0n),
|
.bigint()
|
||||||
taxPaidNextHourProjected: t.bigint().notNull().$default(() => 0n),
|
.notNull()
|
||||||
|
.$default(() => 0n),
|
||||||
|
taxPaidLastDay: t
|
||||||
|
.bigint()
|
||||||
|
.notNull()
|
||||||
|
.$default(() => 0n),
|
||||||
|
taxPaidNextHourProjected: t
|
||||||
|
.bigint()
|
||||||
|
.notNull()
|
||||||
|
.$default(() => 0n),
|
||||||
|
|
||||||
ubiClaimedLastWeek: t.bigint().notNull().$default(() => 0n),
|
ubiClaimedLastWeek: t
|
||||||
ubiClaimedLastDay: t.bigint().notNull().$default(() => 0n),
|
.bigint()
|
||||||
ubiClaimedNextHourProjected: t.bigint().notNull().$default(() => 0n),
|
.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)
|
// Ring buffer state (flattened array of length HOURS_IN_RING_BUFFER * 4)
|
||||||
ringBufferPointer: t.integer().notNull().$default(() => 0),
|
ringBufferPointer: t
|
||||||
lastHourlyUpdateTimestamp: t.bigint().notNull().$default(() => 0n),
|
.integer()
|
||||||
ringBuffer: t
|
.notNull()
|
||||||
.jsonb()
|
.$default(() => 0),
|
||||||
.$type<string[]>()
|
lastHourlyUpdateTimestamp: t
|
||||||
.notNull()
|
.bigint()
|
||||||
.$default(() => Array(HOURS_IN_RING_BUFFER * RING_BUFFER_SEGMENTS).fill("0")),
|
.notNull()
|
||||||
})
|
.$default(() => 0n),
|
||||||
);
|
ringBuffer: t
|
||||||
|
.jsonb()
|
||||||
|
.$type<string[]>()
|
||||||
|
.notNull()
|
||||||
|
.$default(() => Array(HOURS_IN_RING_BUFFER * RING_BUFFER_SEGMENTS).fill('0')),
|
||||||
|
}));
|
||||||
|
|
||||||
// Individual staking positions
|
// Individual staking positions
|
||||||
export const positions = onchainTable(
|
export const positions = onchainTable(
|
||||||
"positions",
|
'positions',
|
||||||
(t) => ({
|
t => ({
|
||||||
id: t.text().primaryKey(), // Position ID from contract
|
id: t.text().primaryKey(), // Position ID from contract
|
||||||
owner: t.hex().notNull(),
|
owner: t.hex().notNull(),
|
||||||
share: t.real().notNull(), // Share as decimal (0-1)
|
share: t.real().notNull(), // Share as decimal (0-1)
|
||||||
taxRate: t.real().notNull(), // Tax rate as decimal (e.g., 0.01 for 1%)
|
taxRate: t.real().notNull(), // Tax rate as decimal (e.g., 0.01 for 1%)
|
||||||
kraikenDeposit: t.bigint().notNull(),
|
kraikenDeposit: t.bigint().notNull(),
|
||||||
stakeDeposit: t.bigint().notNull(),
|
stakeDeposit: t.bigint().notNull(),
|
||||||
taxPaid: t.bigint().notNull().$default(() => 0n),
|
taxPaid: t
|
||||||
snatched: t.integer().notNull().$default(() => 0),
|
.bigint()
|
||||||
|
.notNull()
|
||||||
|
.$default(() => 0n),
|
||||||
|
snatched: t
|
||||||
|
.integer()
|
||||||
|
.notNull()
|
||||||
|
.$default(() => 0),
|
||||||
creationTime: t.bigint().notNull(),
|
creationTime: t.bigint().notNull(),
|
||||||
lastTaxTime: 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(),
|
createdAt: t.bigint().notNull(),
|
||||||
closedAt: t.bigint(),
|
closedAt: t.bigint(),
|
||||||
totalSupplyInit: t.bigint().notNull(),
|
totalSupplyInit: t.bigint().notNull(),
|
||||||
totalSupplyEnd: t.bigint(),
|
totalSupplyEnd: t.bigint(),
|
||||||
payout: t.bigint().notNull().$default(() => 0n),
|
payout: t
|
||||||
|
.bigint()
|
||||||
|
.notNull()
|
||||||
|
.$default(() => 0n),
|
||||||
}),
|
}),
|
||||||
(table) => ({
|
table => ({
|
||||||
ownerIdx: index().on(table.owner),
|
ownerIdx: index().on(table.owner),
|
||||||
statusIdx: index().on(table.status),
|
statusIdx: index().on(table.status),
|
||||||
})
|
})
|
||||||
|
|
@ -75,11 +147,10 @@ export const positions = onchainTable(
|
||||||
|
|
||||||
// Constants for tax rates (matches subgraph)
|
// Constants for tax rates (matches subgraph)
|
||||||
export const TAX_RATES = [
|
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.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.21, 0.25, 0.29, 0.33, 0.37, 0.41, 0.45, 0.49, 0.53, 0.57,
|
0.73, 0.77, 0.81, 0.85, 0.89, 0.93, 0.97,
|
||||||
0.61, 0.65, 0.69, 0.73, 0.77, 0.81, 0.85, 0.89, 0.93, 0.97
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Helper constants
|
// Helper constants
|
||||||
export const STATS_ID = "0x01";
|
export const STATS_ID = '0x01';
|
||||||
export const SECONDS_IN_HOUR = 3600;
|
export const SECONDS_IN_HOUR = 3600;
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,29 @@
|
||||||
import { Hono } from "hono";
|
import { Hono } from 'hono';
|
||||||
import { cors } from "hono/cors";
|
import { cors } from 'hono/cors';
|
||||||
import { client, graphql } from "ponder";
|
import { client, graphql } from 'ponder';
|
||||||
import { db } from "ponder:api";
|
import { db } from 'ponder:api';
|
||||||
import schema from "ponder:schema";
|
import schema from 'ponder:schema';
|
||||||
|
|
||||||
const app = new Hono();
|
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({
|
app.use(
|
||||||
origin: allowedOrigins?.length ? allowedOrigins : "*",
|
'/*',
|
||||||
allowMethods: ["GET", "POST", "OPTIONS"],
|
cors({
|
||||||
allowHeaders: ["Content-Type", "Apollo-Require-Preflight"],
|
origin: allowedOrigins?.length ? allowedOrigins : '*',
|
||||||
}));
|
allowMethods: ['GET', 'POST', 'OPTIONS'],
|
||||||
|
allowHeaders: ['Content-Type', 'Apollo-Require-Preflight'],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// SQL endpoint
|
// SQL endpoint
|
||||||
app.use("/sql/*", client({ db, schema }));
|
app.use('/sql/*', client({ db, schema }));
|
||||||
|
|
||||||
// GraphQL endpoints
|
// GraphQL endpoints
|
||||||
app.use("/graphql", graphql({ db, schema }));
|
app.use('/graphql', graphql({ db, schema }));
|
||||||
app.use("/", graphql({ db, schema }));
|
app.use('/', graphql({ db, schema }));
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|
|
||||||
|
|
@ -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
|
* 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
|
* @returns The validated ABI
|
||||||
*/
|
*/
|
||||||
export function validateAbi<T extends Abi>(abi: T): T {
|
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
|
* @returns The validated contract ABI
|
||||||
*/
|
*/
|
||||||
export function validateContractAbi<T extends { abi: Abi }>(contract: T): T['abi'] {
|
export function validateContractAbi<T extends { abi: Abi }>(contract: T): T['abi'] {
|
||||||
return contract.abi
|
return contract.abi;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 RING_BUFFER_SEGMENTS = 4; // ubi, minted, burned, tax
|
||||||
export const MINIMUM_BLOCKS_FOR_RINGBUFFER = 100;
|
export const MINIMUM_BLOCKS_FOR_RINGBUFFER = 100;
|
||||||
|
|
@ -13,11 +18,11 @@ export function parseRingBuffer(raw?: string[] | null): bigint[] {
|
||||||
if (!raw || raw.length === 0) {
|
if (!raw || raw.length === 0) {
|
||||||
return makeEmptyRingBuffer();
|
return makeEmptyRingBuffer();
|
||||||
}
|
}
|
||||||
return raw.map((value) => BigInt(value));
|
return raw.map(value => BigInt(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serializeRingBuffer(values: bigint[]): string[] {
|
export function serializeRingBuffer(values: bigint[]): string[] {
|
||||||
return values.map((value) => value.toString());
|
return values.map(value => value.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeMetrics(ringBuffer: bigint[], pointer: number) {
|
function computeMetrics(ringBuffer: bigint[], pointer: number) {
|
||||||
|
|
@ -62,12 +67,7 @@ function computeMetrics(ringBuffer: bigint[], pointer: number) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeProjections(
|
function computeProjections(ringBuffer: bigint[], pointer: number, timestamp: bigint, metrics: ReturnType<typeof computeMetrics>) {
|
||||||
ringBuffer: bigint[],
|
|
||||||
pointer: number,
|
|
||||||
timestamp: bigint,
|
|
||||||
metrics: ReturnType<typeof computeMetrics>
|
|
||||||
) {
|
|
||||||
const startOfHour = (timestamp / BigInt(SECONDS_IN_HOUR)) * BigInt(SECONDS_IN_HOUR);
|
const startOfHour = (timestamp / BigInt(SECONDS_IN_HOUR)) * BigInt(SECONDS_IN_HOUR);
|
||||||
const elapsedSeconds = timestamp - startOfHour;
|
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 currentBlock = event.block.number;
|
||||||
const deployBlock = BigInt(context.network.contracts.Kraiken.startBlock);
|
const deployBlock = BigInt(context.network.contracts.Kraiken.startBlock);
|
||||||
const blocksSinceDeployment = Number(currentBlock - deployBlock);
|
const blocksSinceDeployment = Number(currentBlock - deployBlock);
|
||||||
|
|
||||||
if (blocksSinceDeployment < MINIMUM_BLOCKS_FOR_RINGBUFFER) {
|
if (blocksSinceDeployment < MINIMUM_BLOCKS_FOR_RINGBUFFER) {
|
||||||
context.log.warn(
|
context.logger.warn(
|
||||||
`Insufficient block history (only ${blocksSinceDeployment} blocks available, need ${MINIMUM_BLOCKS_FOR_RINGBUFFER})`
|
`Insufficient block history (only ${blocksSinceDeployment} blocks available, need ${MINIMUM_BLOCKS_FOR_RINGBUFFER})`
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -110,7 +110,7 @@ export function checkBlockHistorySufficient(context: any, event: any): boolean {
|
||||||
return true;
|
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 });
|
let statsData = await context.db.find(stats, { id: STATS_ID });
|
||||||
if (!statsData) {
|
if (!statsData) {
|
||||||
const { client, contracts } = context;
|
const { client, contracts } = context;
|
||||||
|
|
@ -118,7 +118,7 @@ export async function ensureStatsExists(context: any, timestamp?: bigint) {
|
||||||
try {
|
try {
|
||||||
return await fn();
|
return await fn();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`[stats.ensureStatsExists] Falling back for ${label}`, error);
|
context.logger.warn(`[stats.ensureStatsExists] Falling back for ${label}`, error);
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -129,38 +129,36 @@ export async function ensureStatsExists(context: any, timestamp?: bigint) {
|
||||||
client.readContract({
|
client.readContract({
|
||||||
abi: contracts.Kraiken.abi,
|
abi: contracts.Kraiken.abi,
|
||||||
address: contracts.Kraiken.address,
|
address: contracts.Kraiken.address,
|
||||||
functionName: "totalSupply",
|
functionName: 'totalSupply',
|
||||||
}),
|
}),
|
||||||
0n,
|
0n,
|
||||||
"Kraiken.totalSupply",
|
'Kraiken.totalSupply'
|
||||||
),
|
),
|
||||||
readWithFallback(
|
readWithFallback(
|
||||||
() =>
|
() =>
|
||||||
client.readContract({
|
client.readContract({
|
||||||
abi: contracts.Stake.abi,
|
abi: contracts.Stake.abi,
|
||||||
address: contracts.Stake.address,
|
address: contracts.Stake.address,
|
||||||
functionName: "totalSupply",
|
functionName: 'totalSupply',
|
||||||
}),
|
}),
|
||||||
0n,
|
0n,
|
||||||
"Stake.totalSupply",
|
'Stake.totalSupply'
|
||||||
),
|
),
|
||||||
readWithFallback(
|
readWithFallback(
|
||||||
() =>
|
() =>
|
||||||
client.readContract({
|
client.readContract({
|
||||||
abi: contracts.Stake.abi,
|
abi: contracts.Stake.abi,
|
||||||
address: contracts.Stake.address,
|
address: contracts.Stake.address,
|
||||||
functionName: "outstandingStake",
|
functionName: 'outstandingStake',
|
||||||
}),
|
}),
|
||||||
0n,
|
0n,
|
||||||
"Stake.outstandingStake",
|
'Stake.outstandingStake'
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
cachedStakeTotalSupply = stakeTotalSupply;
|
cachedStakeTotalSupply = stakeTotalSupply;
|
||||||
|
|
||||||
const currentHour = timestamp
|
const currentHour = timestamp ? (timestamp / BigInt(SECONDS_IN_HOUR)) * BigInt(SECONDS_IN_HOUR) : 0n;
|
||||||
? (timestamp / BigInt(SECONDS_IN_HOUR)) * BigInt(SECONDS_IN_HOUR)
|
|
||||||
: 0n;
|
|
||||||
|
|
||||||
await context.db.insert(stats).values({
|
await context.db.insert(stats).values({
|
||||||
id: STATS_ID,
|
id: STATS_ID,
|
||||||
|
|
@ -178,7 +176,7 @@ export async function ensureStatsExists(context: any, timestamp?: bigint) {
|
||||||
return statsData;
|
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 });
|
const statsData = await context.db.find(stats, { id: STATS_ID });
|
||||||
if (!statsData) return;
|
if (!statsData) return;
|
||||||
|
|
||||||
|
|
@ -197,9 +195,7 @@ export async function updateHourlyData(context: any, timestamp: bigint) {
|
||||||
|
|
||||||
if (currentHour > lastUpdate) {
|
if (currentHour > lastUpdate) {
|
||||||
const hoursElapsedBig = (currentHour - lastUpdate) / BigInt(SECONDS_IN_HOUR);
|
const hoursElapsedBig = (currentHour - lastUpdate) / BigInt(SECONDS_IN_HOUR);
|
||||||
const hoursElapsed = Number(hoursElapsedBig > BigInt(HOURS_IN_RING_BUFFER)
|
const hoursElapsed = Number(hoursElapsedBig > BigInt(HOURS_IN_RING_BUFFER) ? BigInt(HOURS_IN_RING_BUFFER) : hoursElapsedBig);
|
||||||
? BigInt(HOURS_IN_RING_BUFFER)
|
|
||||||
: hoursElapsedBig);
|
|
||||||
|
|
||||||
for (let h = 0; h < hoursElapsed; h++) {
|
for (let h = 0; h < hoursElapsed; h++) {
|
||||||
pointer = (pointer + 1) % HOURS_IN_RING_BUFFER;
|
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);
|
await ensureStatsExists(context);
|
||||||
|
|
||||||
if (cachedStakeTotalSupply !== null) {
|
if (cachedStakeTotalSupply !== null) {
|
||||||
|
|
@ -261,7 +257,7 @@ export async function getStakeTotalSupply(context: any): Promise<bigint> {
|
||||||
const totalSupply = await context.client.readContract({
|
const totalSupply = await context.client.readContract({
|
||||||
abi: context.contracts.Stake.abi,
|
abi: context.contracts.Stake.abi,
|
||||||
address: context.contracts.Stake.address,
|
address: context.contracts.Stake.address,
|
||||||
functionName: "totalSupply",
|
functionName: 'totalSupply',
|
||||||
});
|
});
|
||||||
cachedStakeTotalSupply = totalSupply;
|
cachedStakeTotalSupply = totalSupply;
|
||||||
await context.db.update(stats, { id: STATS_ID }).set({
|
await context.db.update(stats, { id: STATS_ID }).set({
|
||||||
|
|
@ -270,12 +266,12 @@ export async function getStakeTotalSupply(context: any): Promise<bigint> {
|
||||||
return totalSupply;
|
return totalSupply;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function refreshOutstandingStake(context: any) {
|
export async function refreshOutstandingStake(context: StatsContext) {
|
||||||
await ensureStatsExists(context);
|
await ensureStatsExists(context);
|
||||||
const outstandingStake = await context.client.readContract({
|
const outstandingStake = await context.client.readContract({
|
||||||
abi: context.contracts.Stake.abi,
|
abi: context.contracts.Stake.abi,
|
||||||
address: context.contracts.Stake.address,
|
address: context.contracts.Stake.address,
|
||||||
functionName: "outstandingStake",
|
functionName: 'outstandingStake',
|
||||||
});
|
});
|
||||||
|
|
||||||
await context.db.update(stats, { id: STATS_ID }).set({
|
await context.db.update(stats, { id: STATS_ID }).set({
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Import all event handlers
|
// Import all event handlers
|
||||||
import "./kraiken";
|
import './kraiken';
|
||||||
import "./stake";
|
import './stake';
|
||||||
|
|
||||||
// This file serves as the entry point for all indexing functions
|
// 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
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { ponder } from "ponder:registry";
|
import { ponder } from 'ponder:registry';
|
||||||
import { stats, STATS_ID } from "ponder:schema";
|
import { stats, STATS_ID } from 'ponder:schema';
|
||||||
import {
|
import {
|
||||||
ensureStatsExists,
|
ensureStatsExists,
|
||||||
parseRingBuffer,
|
parseRingBuffer,
|
||||||
|
|
@ -7,11 +7,11 @@ import {
|
||||||
updateHourlyData,
|
updateHourlyData,
|
||||||
checkBlockHistorySufficient,
|
checkBlockHistorySufficient,
|
||||||
RING_BUFFER_SEGMENTS,
|
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;
|
const { from, to, value } = event.args;
|
||||||
|
|
||||||
await ensureStatsExists(context, event.block.timestamp);
|
await ensureStatsExists(context, event.block.timestamp);
|
||||||
|
|
@ -69,7 +69,7 @@ ponder.on("Kraiken:Transfer", async ({ event, context }) => {
|
||||||
await updateHourlyData(context, event.block.timestamp);
|
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);
|
await ensureStatsExists(context, event.block.timestamp);
|
||||||
|
|
||||||
// Only update hourly data if we have sufficient block history
|
// Only update hourly data if we have sufficient block history
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { ponder } from "ponder:registry";
|
import { ponder } from 'ponder:registry';
|
||||||
import { positions, stats, STATS_ID, TAX_RATES } from "ponder:schema";
|
import { positions, stats, STATS_ID, TAX_RATES } from 'ponder:schema';
|
||||||
import {
|
import {
|
||||||
ensureStatsExists,
|
ensureStatsExists,
|
||||||
getStakeTotalSupply,
|
getStakeTotalSupply,
|
||||||
|
|
@ -9,15 +9,16 @@ import {
|
||||||
updateHourlyData,
|
updateHourlyData,
|
||||||
checkBlockHistorySufficient,
|
checkBlockHistorySufficient,
|
||||||
RING_BUFFER_SEGMENTS,
|
RING_BUFFER_SEGMENTS,
|
||||||
} from "./helpers/stats";
|
} from './helpers/stats';
|
||||||
|
import type { StatsContext } from './helpers/stats';
|
||||||
|
|
||||||
const ZERO = 0n;
|
const ZERO = 0n;
|
||||||
|
|
||||||
async function getKraikenTotalSupply(context: Context) {
|
async function getKraikenTotalSupply(context: StatsContext) {
|
||||||
return context.client.readContract({
|
return context.client.readContract({
|
||||||
abi: context.contracts.Kraiken.abi,
|
abi: context.contracts.Kraiken.abi,
|
||||||
address: context.contracts.Kraiken.address,
|
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);
|
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);
|
await ensureStatsExists(context, event.block.timestamp);
|
||||||
|
|
||||||
const stakeTotalSupply = await getStakeTotalSupply(context);
|
const stakeTotalSupply = await getStakeTotalSupply(context);
|
||||||
|
|
@ -44,7 +45,7 @@ ponder.on("Stake:PositionCreated", async ({ event, context }) => {
|
||||||
snatched: 0,
|
snatched: 0,
|
||||||
creationTime: event.block.timestamp,
|
creationTime: event.block.timestamp,
|
||||||
lastTaxTime: event.block.timestamp,
|
lastTaxTime: event.block.timestamp,
|
||||||
status: "Active",
|
status: 'Active',
|
||||||
createdAt: event.block.timestamp,
|
createdAt: event.block.timestamp,
|
||||||
totalSupplyInit,
|
totalSupplyInit,
|
||||||
totalSupplyEnd: null,
|
totalSupplyEnd: null,
|
||||||
|
|
@ -54,7 +55,7 @@ ponder.on("Stake:PositionCreated", async ({ event, context }) => {
|
||||||
await refreshOutstandingStake(context);
|
await refreshOutstandingStake(context);
|
||||||
});
|
});
|
||||||
|
|
||||||
ponder.on("Stake:PositionRemoved", async ({ event, context }) => {
|
ponder.on('Stake:PositionRemoved', async ({ event, context }) => {
|
||||||
await ensureStatsExists(context, event.block.timestamp);
|
await ensureStatsExists(context, event.block.timestamp);
|
||||||
|
|
||||||
const positionId = event.args.positionId.toString();
|
const positionId = event.args.positionId.toString();
|
||||||
|
|
@ -64,7 +65,7 @@ ponder.on("Stake:PositionRemoved", async ({ event, context }) => {
|
||||||
const totalSupplyEnd = await getKraikenTotalSupply(context);
|
const totalSupplyEnd = await getKraikenTotalSupply(context);
|
||||||
|
|
||||||
await context.db.update(positions, { id: positionId }).set({
|
await context.db.update(positions, { id: positionId }).set({
|
||||||
status: "Closed",
|
status: 'Closed',
|
||||||
closedAt: event.block.timestamp,
|
closedAt: event.block.timestamp,
|
||||||
totalSupplyEnd,
|
totalSupplyEnd,
|
||||||
payout: (position.payout ?? ZERO) + event.args.kraikenPayout,
|
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);
|
await ensureStatsExists(context, event.block.timestamp);
|
||||||
|
|
||||||
const positionId = event.args.positionId.toString();
|
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);
|
await ensureStatsExists(context, event.block.timestamp);
|
||||||
|
|
||||||
const positionId = event.args.positionId.toString();
|
const positionId = event.args.positionId.toString();
|
||||||
|
|
@ -151,7 +152,7 @@ ponder.on("Stake:PositionTaxPaid", async ({ event, context }) => {
|
||||||
await refreshOutstandingStake(context);
|
await refreshOutstandingStake(context);
|
||||||
});
|
});
|
||||||
|
|
||||||
ponder.on("Stake:PositionRateHiked", async ({ event, context }) => {
|
ponder.on('Stake:PositionRateHiked', async ({ event, context }) => {
|
||||||
const positionId = event.args.positionId.toString();
|
const positionId = event.args.positionId.toString();
|
||||||
await context.db.update(positions, { id: positionId }).set({
|
await context.db.update(positions, { id: positionId }).set({
|
||||||
taxRate: TAX_RATES[Number(event.args.newTaxRate)] || 0,
|
taxRate: TAX_RATES[Number(event.args.newTaxRate)] || 0,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue