tax rate, version and compose (#70)

resolves #67

Co-authored-by: johba <johba@harb.eth>
Reviewed-on: https://codeberg.org/johba/harb/pulls/70
This commit is contained in:
johba 2025-10-07 19:26:08 +02:00
parent d8ca557eb6
commit 6cbb1781ce
40 changed files with 1243 additions and 213 deletions

View file

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

View file

@ -0,0 +1,5 @@
# Auto-generated files
src/taxRates.ts
# Generated TypeScript definitions
src/__generated__/

View file

@ -14,6 +14,8 @@ Shared TypeScript helpers used by the landing app, txnBot, and other services to
- `src/queries/` - GraphQL operations that target the Ponder schema.
- `src/__generated__/graphql.ts` - Codegen output consumed throughout the stack.
- `src/abis.ts` - Contract ABIs imported directly from `onchain/out/` forge artifacts. Single source of truth for all ABI consumers.
- `src/taxRates.ts` - Generated from `onchain/src/Stake.sol` by `scripts/sync-tax-rates.mjs`; never edit by hand.
- `src/version.ts` - Version validation system tracking `KRAIKEN_LIB_VERSION` and `COMPATIBLE_CONTRACT_VERSIONS` for runtime dependency checking.
## GraphQL Code Generation
- Schema source points to the Ponder GraphQL endpoint for the active environment.

View file

@ -4,7 +4,7 @@ import tsParser from "@typescript-eslint/parser";
export default [
{
files: ["src/**/*.ts"],
ignores: ["src/tests/**/*", "src/__generated__/**/*"],
ignores: ["src/tests/**/*", "src/__generated__/**/*", "src/taxRates.ts"],
languageOptions: {
parser: tsParser,
parserOptions: { project: "./tsconfig.json" }

View file

@ -45,6 +45,11 @@
"types": "./dist/abis.d.ts",
"require": "./dist/abis.js",
"import": "./dist/abis.js"
},
"./version": {
"types": "./dist/version.d.ts",
"require": "./dist/version.js",
"import": "./dist/version.js"
}
},
"files": [

View file

@ -22,3 +22,5 @@ export { KRAIKEN_ABI, STAKE_ABI, ABIS } from './abis.js';
// Backward compatible aliases
export { KRAIKEN_ABI as KraikenAbi, STAKE_ABI as StakeAbi } from './abis.js';
export { KRAIKEN_LIB_VERSION, COMPATIBLE_CONTRACT_VERSIONS, isCompatibleVersion, getVersionMismatchError } from './version.js';

View file

@ -1,3 +1,10 @@
/**
* AUTO-GENERATED FILE DO NOT EDIT
*
* Generated by scripts/sync-tax-rates.mjs from onchain/src/Stake.sol.
* Run `node scripts/sync-tax-rates.mjs` after modifying the contract TAX_RATES array.
*/
export interface TaxRateOption {
index: number;
year: number;
@ -18,22 +25,42 @@ export const TAX_RATE_OPTIONS: TaxRateOption[] = [
{ index: 9, year: 50, daily: 0.13699, decimal: 0.5 },
{ index: 10, year: 60, daily: 0.16438, decimal: 0.6 },
{ index: 11, year: 80, daily: 0.21918, decimal: 0.8 },
{ index: 12, year: 100, daily: 0.27397, decimal: 1.0 },
{ index: 12, year: 100, daily: 0.27397, decimal: 1 },
{ index: 13, year: 130, daily: 0.35616, decimal: 1.3 },
{ index: 14, year: 180, daily: 0.49315, decimal: 1.8 },
{ index: 15, year: 250, daily: 0.68493, decimal: 2.5 },
{ index: 16, year: 320, daily: 0.87671, decimal: 3.2 },
{ index: 17, year: 420, daily: 1.15068, decimal: 4.2 },
{ index: 18, year: 540, daily: 1.47945, decimal: 5.4 },
{ index: 19, year: 700, daily: 1.91781, decimal: 7.0 },
{ index: 19, year: 700, daily: 1.91781, decimal: 7 },
{ index: 20, year: 920, daily: 2.52055, decimal: 9.2 },
{ index: 21, year: 1200, daily: 3.28767, decimal: 12.0 },
{ index: 22, year: 1600, daily: 4.38356, decimal: 16.0 },
{ index: 23, year: 2000, daily: 5.47945, decimal: 20.0 },
{ index: 24, year: 2600, daily: 7.12329, decimal: 26.0 },
{ index: 25, year: 3400, daily: 9.31507, decimal: 34.0 },
{ index: 26, year: 4400, daily: 12.05479, decimal: 44.0 },
{ index: 27, year: 5700, daily: 15.61644, decimal: 57.0 },
{ index: 28, year: 7500, daily: 20.54795, decimal: 75.0 },
{ index: 29, year: 9700, daily: 26.57534, decimal: 97.0 },
{ index: 21, year: 1200, daily: 3.28767, decimal: 12 },
{ index: 22, year: 1600, daily: 4.38356, decimal: 16 },
{ index: 23, year: 2000, daily: 5.47945, decimal: 20 },
{ index: 24, year: 2600, daily: 7.12329, decimal: 26 },
{ index: 25, year: 3400, daily: 9.31507, decimal: 34 },
{ index: 26, year: 4400, daily: 12.05479, decimal: 44 },
{ index: 27, year: 5700, daily: 15.61644, decimal: 57 },
{ index: 28, year: 7500, daily: 20.54795, decimal: 75 },
{ index: 29, year: 9700, daily: 26.57534, decimal: 97 }
];
/**
* Checksum of the contract TAX_RATES array (first 16 chars of SHA-256).
* Used for runtime validation to ensure kraiken-lib is in sync with deployed contracts.
*
* To validate at runtime:
* 1. Read TAX_RATES array from the Stake contract
* 2. Compute checksum: sha256(values.join(',')).slice(0, 16)
* 3. Compare with TAX_RATES_CHECKSUM
*
* If mismatch: Run `node scripts/sync-tax-rates.mjs` and rebuild kraiken-lib.
*/
export const TAX_RATES_CHECKSUM = '1e37f2312ef082e9';
/**
* Array of raw year values (matches contract uint256[] TAX_RATES exactly).
* Used for runtime validation without needing to parse options.
*/
export const TAX_RATES_RAW = [1, 3, 5, 8, 12, 18, 24, 30, 40, 50, 60, 80, 100, 130, 180, 250, 320, 420, 540, 700, 920, 1200, 1600, 2000, 2600, 3400, 4400, 5700, 7500, 9700];

View file

@ -6,4 +6,20 @@ describe('taxRates', () => {
expect(TAX_RATE_OPTIONS.length).toBeGreaterThan(0);
expect(TAX_RATE_OPTIONS[0]).toEqual(expect.objectContaining({ index: 0, year: 1, decimal: 0.01 }));
});
test('tax rate options have required fields', () => {
TAX_RATE_OPTIONS.forEach((option, idx) => {
expect(option.index).toBe(idx);
expect(option.year).toBeGreaterThan(0);
expect(option.daily).toBeGreaterThan(0);
expect(option.decimal).toBeGreaterThan(0);
expect(option.decimal).toBe(option.year / 100);
});
});
test('tax rates are in ascending order', () => {
for (let i = 1; i < TAX_RATE_OPTIONS.length; i++) {
expect(TAX_RATE_OPTIONS[i].year).toBeGreaterThan(TAX_RATE_OPTIONS[i - 1].year);
}
});
});

View file

@ -0,0 +1,66 @@
/**
* Protocol version compatibility tracking.
*
* The Kraiken contract exposes a VERSION constant that must be checked
* at runtime by indexers and frontends to ensure data compatibility.
*/
/**
* Current version this library is built for.
* Should match the deployed Kraiken contract VERSION.
*/
export const KRAIKEN_LIB_VERSION = 1;
/**
* List of Kraiken contract versions this library is compatible with.
*
* - Indexers MUST validate contract.VERSION is in this list at startup
* - Frontends SHOULD validate indexer version matches KRAIKEN_LIB_VERSION
*
* Version History:
* - v1: Initial deployment (30-tier TAX_RATES, index-based staking)
*/
export const COMPATIBLE_CONTRACT_VERSIONS = [1];
/**
* Validates if a contract version is compatible with this library.
*/
export function isCompatibleVersion(contractVersion: number): boolean {
return COMPATIBLE_CONTRACT_VERSIONS.includes(contractVersion);
}
/**
* Error message generator for version mismatches.
*/
export function getVersionMismatchError(contractVersion: number, context: 'ponder' | 'frontend'): string {
const compatibleVersions = COMPATIBLE_CONTRACT_VERSIONS.join(', ');
const instructions =
context === 'ponder'
? [
'1. Check if contract was upgraded',
'2. Update COMPATIBLE_CONTRACT_VERSIONS in kraiken-lib/src/version.ts',
'3. Run: ./scripts/build-kraiken-lib.sh',
'4. Run: rm -rf services/ponder/.ponder/',
'5. Restart Ponder for full re-index',
]
: [
'1. Contact administrator - indexer may need updating',
'2. Try refreshing the page',
'3. Check if contract was recently upgraded',
];
const lines = [
'╔════════════════════════════════════════════════════════════╗',
`║ ❌ CRITICAL: VERSION MISMATCH (${context})`.padEnd(61) + '║',
'╠════════════════════════════════════════════════════════════╣',
`║ Contract VERSION: ${contractVersion}`.padEnd(61) + '║',
`║ Library VERSION: ${KRAIKEN_LIB_VERSION}`.padEnd(61) + '║',
`║ Compatible versions: ${compatibleVersions}`.padEnd(61) + '║',
'║'.padEnd(61) + '║',
'║ 📋 Required Actions:'.padEnd(61) + '║',
...instructions.map(line => `${line}`.padEnd(61) + '║'),
'╚════════════════════════════════════════════════════════════╝',
];
return lines.join('\n');
}