feat: add ABI validation helpers for Ponder (#29)

resolves https://codeberg.org/johba/harb/issues/23

Co-authored-by: openhands <openhands@all-hands.dev>
Reviewed-on: https://codeberg.org/johba/harb/pulls/29
This commit is contained in:
johba 2025-09-30 20:02:43 +02:00
parent 0eaf91be13
commit 76d84341de
17 changed files with 2105 additions and 49 deletions

1
CLAUDE.md Symbolic link
View file

@ -0,0 +1 @@
AGENTS.md

View file

@ -19,6 +19,7 @@
"@graphql-codegen/typescript-operations": "^4.2.0",
"@graphql-typed-document-node/core": "^3.2.0",
"@types/jest": "^29.5.12",
"@types/node": "^24.6.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.2",
"typescript": "^5.4.3"
@ -2887,13 +2888,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.12.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.2.tgz",
"integrity": "sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==",
"version": "24.6.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.0.tgz",
"integrity": "sha512-F1CBxgqwOMc4GKJ7eY22hWhBVQuMYTtqI8L0FcszYcpYX0fzfDGpez22Xau8Mgm7O9fI+zA/TYIdq3tGWfweBA==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
"undici-types": "~7.13.0"
}
},
"node_modules/@types/stack-utils": {
@ -7904,9 +7905,9 @@
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz",
"integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==",
"dev": true,
"license": "MIT"
},

View file

@ -24,6 +24,7 @@
"@graphql-codegen/typescript-operations": "^4.2.0",
"@graphql-typed-document-node/core": "^3.2.0",
"@types/jest": "^29.5.12",
"@types/node": "^24.6.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.2",
"typescript": "^5.4.3"

25
kraiken-lib/src/abis.ts Normal file
View file

@ -0,0 +1,25 @@
/**
* Contract ABIs extracted from forge build artifacts
*
* These ABIs are the single source of truth, generated from onchain/out/
* To update: run `forge build` in onchain/ directory
*/
const KraikenForgeOutput = require('../../onchain/out/Kraiken.sol/Kraiken.json');
const StakeForgeOutput = require('../../onchain/out/Stake.sol/Stake.json');
/**
* Kraiken ERC20 token contract ABI
*/
export const KraikenAbi = KraikenForgeOutput.abi;
/**
* Stake (Harberger tax staking) contract ABI
*/
export const StakeAbi = StakeForgeOutput.abi;
// Re-export for convenience
export const ABIS = {
Kraiken: KraikenAbi,
Stake: StakeAbi,
} as const;

View file

@ -22,3 +22,5 @@ export {
getSnatchList,
isPositionDelinquent,
} from "./helpers";
export { KraikenAbi, StakeAbi, ABIS } from "./abis";

View file

@ -5,7 +5,9 @@
"declaration": true,
"outDir": "./dist",
"skipLibCheck": true,
"types": []
"resolveJsonModule": true,
"esModuleInterop": true,
"types": ["node"]
},
"include": [
"src/**/*"

View file

@ -1432,12 +1432,12 @@
resolved "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.0.36.tgz"
integrity sha512-b7bq23s4fgBB76n34m2b3RBf6M369B0Z9uRR8aHTMd8kZISRkmDEpPD8hhpYvDFzr3bJCPES96cm3Q6qRNDbQw==
"@types/node@*", "@types/node@>=13":
version "20.12.2"
resolved "https://registry.npmjs.org/@types/node/-/node-20.12.2.tgz"
integrity sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==
"@types/node@*", "@types/node@^24.6.0", "@types/node@>=13":
version "24.6.0"
resolved "https://registry.npmjs.org/@types/node/-/node-24.6.0.tgz"
integrity sha512-F1CBxgqwOMc4GKJ7eY22hWhBVQuMYTtqI8L0FcszYcpYX0fzfDGpez22Xau8Mgm7O9fI+zA/TYIdq3tGWfweBA==
dependencies:
undici-types "~5.26.4"
undici-types "~7.13.0"
"@types/stack-utils@^2.0.0":
version "2.0.3"
@ -2387,6 +2387,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
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:
version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
@ -4257,10 +4262,10 @@ unc-path-regex@^0.1.2:
resolved "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz"
integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
undici-types@~7.13.0:
version "7.13.0"
resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz"
integrity sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==
unixify@^1.0.0:
version "1.0.0"

View file

@ -11,6 +11,7 @@
- Tooling: Foundry (`forge build`, `forge test`, `forge fmt`, `forge snapshot`), Anvil for local chain, Base Sepolia deployment script (`forge script ...BaseSepoliaDeploy`).
- Repo structure highlights: `src/` (core contracts), `test/helpers/` (Uniswap/Kraiken bases), `lib/uni-v3-lib` (math + JS setup), `script/` (deploy), `out/` (artifacts), config via `foundry.toml` & `remappings.txt`.
- Setup steps: clone repo, init/update submodules, install `lib/uni-v3-lib` dependencies (yarn), ensure Foundry installed.
- **ABI Architecture**: Contract ABIs are exported via `kraiken-lib/src/abis.ts`, which imports directly from `onchain/out/` (forge build artifacts). All consumers (ponder, web-app) import from kraiken-lib for type-safe, single-source-of-truth ABIs. Run `forge build` in `onchain/` to update ABIs across the stack.
## Strategy & Mechanics
- Outstanding supply excludes liquidity position balances; enforce 20% staking cap (~20k positions).

1
onchain/CLAUDE.md Symbolic link
View file

@ -0,0 +1 @@
AGENTS.md

1
services/ponder/CLAUDE.md Symbolic link
View file

@ -0,0 +1 @@
AGENTS.md

File diff suppressed because it is too large Load diff

View file

@ -7,17 +7,19 @@
"dev": "ponder dev",
"start": "ponder start",
"codegen": "ponder codegen",
"build": "tsc"
"build": "ponder codegen"
},
"dependencies": {
"@ponder/core": "^0.7.17",
"hono": "^4.5.0",
"ponder": "^0.13.1",
"kraiken-lib": "file:../../kraiken-lib",
"ponder": "^0.13.6",
"viem": "^2.21.0"
},
"devDependencies": {
"@types/node": "^20.11.30",
"esbuild": "^0.25.10",
"typescript": "^5.4.3"
"typescript": "^5.9.2"
},
"overrides": {
"esbuild": "^0.25.10",

View file

@ -9,6 +9,12 @@ declare module "ponder:schema" {
export * from "./ponder.schema.ts";
}
declare module "*.json" {
import type { Abi } from 'viem'
const value: { abi: Abi }
export default value
}
// This file enables type checking and editor autocomplete for this Ponder project.
// After upgrading, you may find that changes have been made to this file.
// If this happens, please commit the changes. Do not manually edit this file.

View file

@ -1,10 +1,20 @@
import { createConfig } from "ponder";
import type { Abi } from "viem";
import KraikenAbi from "./abis/Kraiken.json";
import StakeAbi from "./abis/Stake.json";
import { KraikenAbi, StakeAbi } from "kraiken-lib";
// Network configurations keyed by canonical environment name
const networks = {
type NetworkConfig = {
chainId: number;
rpc: string;
disableCache?: boolean;
contracts: {
kraiken: string;
stake: string;
startBlock: number;
};
};
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",
@ -33,7 +43,7 @@ const networks = {
startBlock: 26038614,
},
},
} as const;
};
// Select network based on environment variable
const NETWORK = (process.env.PONDER_NETWORK as keyof typeof networks) || "BASE_SEPOLIA_LOCAL_FORK";
@ -57,18 +67,18 @@ export default createConfig({
[NETWORK]: {
id: selectedNetwork.chainId,
rpc: selectedNetwork.rpc,
disableCache: selectedNetwork.disableCache,
disableCache: selectedNetwork.disableCache ?? false,
},
},
contracts: {
Kraiken: {
abi: KraikenAbi.abi as Abi,
abi: KraikenAbi satisfies Abi,
chain: NETWORK,
address: selectedNetwork.contracts.kraiken as `0x${string}`,
startBlock: selectedNetwork.contracts.startBlock,
},
Stake: {
abi: StakeAbi.abi as Abi,
abi: StakeAbi satisfies Abi,
chain: NETWORK,
address: selectedNetwork.contracts.stake as `0x${string}`,
startBlock: selectedNetwork.contracts.startBlock,

View file

@ -0,0 +1,19 @@
import type { Abi } from 'viem'
/**
* Helper function to ensure an imported ABI matches the viem Abi type at compile time
* @param abi The ABI to validate
* @returns The validated ABI
*/
export function validateAbi<T extends Abi>(abi: T): T {
return abi
}
/**
* Helper function to ensure an imported contract ABI matches the viem Abi type at compile time
* @param contract The contract with an abi property to validate
* @returns The validated contract ABI
*/
export function validateContractAbi<T extends { abi: Abi }>(contract: T): T['abi'] {
return contract.abi
}

View file

@ -10,8 +10,7 @@ import {
type WaitForTransactionReceiptParameters,
getChainId
} from "@wagmi/core";
// import { HarbContract } from "@/contracts/harb";
import HarbJson from "@/assets/contracts/harb.json";
import { KraikenAbi } from "kraiken-lib";
import { type Abi, type Address } from "viem";
import { StakeContract } from "@/contracts/stake";
import {getChain} from "@/config"
@ -36,13 +35,13 @@ export let HarbContract = getHarbJson();
function getHarbJson(){
console.log("getHarbJson");
const chainId = getChainId(config as any);
console.log("chainId", chainId);
const chain = getChain(chainId)
return {abi: HarbJson, contractAddress: chain?.harb} as Contract;
return {abi: KraikenAbi as Abi, contractAddress: chain?.harb} as Contract;
}
export function setHarbContract(){

View file

@ -1,7 +1,7 @@
import { ref } from "vue";
import { config } from "@/wagmi";
import { readContract, writeContract, getChainId } from "@wagmi/core";
import StakeJson from "@/assets/contracts/stake.json";
import { StakeAbi } from "kraiken-lib";
import { type Abi, type Address } from "viem";
import {getChain} from "@/config"
import logger from "@/utils/logger";
@ -25,8 +25,8 @@ function getStakeJson(){
console.log("chainId", chainId);
const chain = getChain(chainId)
return {abi: StakeJson, contractAddress: chain?.stake} as Contract;
return {abi: StakeAbi as Abi, contractAddress: chain?.stake} as Contract;
}
export function setStakeContract(){