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:
parent
d8ca557eb6
commit
6cbb1781ce
40 changed files with 1243 additions and 213 deletions
|
|
@ -6,6 +6,7 @@ Ponder-based indexer that records Kraiken protocol activity and exposes the Grap
|
|||
- Track Kraiken token transfers and staking events to maintain protocol stats, hourly ring buffers, and position state.
|
||||
- Serve a GraphQL endpoint at `http://localhost:42069/graphql` for downstream consumers.
|
||||
- Support `BASE_SEPOLIA_LOCAL_FORK`, `BASE_SEPOLIA`, and `BASE` environments through a single TypeScript codebase.
|
||||
- **Tax Rate Handling**: Import `TAX_RATE_OPTIONS` from `kraiken-lib` (synced with Stake.sol via `scripts/sync-tax-rates.mjs`). The schema stores both `taxRateIndex` (source of truth) and `taxRate` decimal (for display). Event handlers extract the index from contract events and look up the decimal value.
|
||||
|
||||
## Key Files
|
||||
- `ponder.config.ts` - Network selection and contract bindings (update addresses after deployments).
|
||||
|
|
|
|||
0
services/ponder/ponder-env.d.ts
vendored
Executable file → Normal file
0
services/ponder/ponder-env.d.ts
vendored
Executable file → Normal file
|
|
@ -1,4 +1,5 @@
|
|||
import { onchainTable, index } from 'ponder';
|
||||
import { TAX_RATE_OPTIONS } from 'kraiken-lib/taxRates';
|
||||
|
||||
export const HOURS_IN_RING_BUFFER = 168; // 7 days * 24 hours
|
||||
const RING_BUFFER_SEGMENTS = 4; // ubi, minted, burned, tax
|
||||
|
|
@ -113,7 +114,8 @@ export const positions = onchainTable(
|
|||
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%)
|
||||
taxRate: t.real().notNull(), // Tax rate as decimal (e.g., 0.01 for 1%) - for display
|
||||
taxRateIndex: t.integer().notNull(), // Tax rate index from contract - source of truth
|
||||
kraikenDeposit: t.bigint().notNull(),
|
||||
stakeDeposit: t.bigint().notNull(),
|
||||
taxPaid: t
|
||||
|
|
@ -142,14 +144,13 @@ export const positions = onchainTable(
|
|||
table => ({
|
||||
ownerIdx: index().on(table.owner),
|
||||
statusIdx: index().on(table.status),
|
||||
taxRateIndexIdx: index().on(table.taxRateIndex),
|
||||
})
|
||||
);
|
||||
|
||||
// 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,
|
||||
];
|
||||
// Export decimal values for backward compatibility in event handlers
|
||||
// Maps index → decimal (e.g., TAX_RATES[0] = 0.01 for 1% yearly)
|
||||
export const TAX_RATES = TAX_RATE_OPTIONS.map(opt => opt.decimal);
|
||||
|
||||
// Helper constants
|
||||
export const STATS_ID = '0x01';
|
||||
|
|
|
|||
32
services/ponder/src/helpers/version.ts
Normal file
32
services/ponder/src/helpers/version.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { isCompatibleVersion, getVersionMismatchError, KRAIKEN_LIB_VERSION } from 'kraiken-lib/version';
|
||||
import type { Context } from 'ponder:registry';
|
||||
|
||||
/**
|
||||
* Validates that the deployed Kraiken contract version is compatible
|
||||
* with this indexer's kraiken-lib version.
|
||||
*
|
||||
* MUST be called at Ponder startup before processing any events.
|
||||
* Fails hard (process.exit) on mismatch to prevent indexing wrong data.
|
||||
*/
|
||||
export async function validateContractVersion(context: Context): Promise<void> {
|
||||
try {
|
||||
const contractVersion = await context.client.readContract({
|
||||
address: context.contracts.Kraiken.address,
|
||||
abi: context.contracts.Kraiken.abi,
|
||||
functionName: 'VERSION',
|
||||
});
|
||||
|
||||
const versionNumber = Number(contractVersion);
|
||||
|
||||
if (!isCompatibleVersion(versionNumber)) {
|
||||
console.error(getVersionMismatchError(versionNumber, 'ponder'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`✓ Contract version validated: v${versionNumber} (kraiken-lib v${KRAIKEN_LIB_VERSION})`);
|
||||
} catch (error) {
|
||||
console.error('Failed to read contract VERSION:', error);
|
||||
console.error('Ensure Kraiken contract has VERSION constant and is deployed correctly');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,10 +8,20 @@ import {
|
|||
checkBlockHistorySufficient,
|
||||
RING_BUFFER_SEGMENTS,
|
||||
} from './helpers/stats';
|
||||
import { validateContractVersion } from './helpers/version';
|
||||
|
||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' as const;
|
||||
|
||||
// Track if version has been validated
|
||||
let versionValidated = false;
|
||||
|
||||
ponder.on('Kraiken:Transfer', async ({ event, context }) => {
|
||||
// Validate version once at first event
|
||||
if (!versionValidated) {
|
||||
await validateContractVersion(context);
|
||||
versionValidated = true;
|
||||
}
|
||||
|
||||
const { from, to, value } = event.args;
|
||||
|
||||
await ensureStatsExists(context, event.block.timestamp);
|
||||
|
|
|
|||
|
|
@ -34,11 +34,13 @@ ponder.on('Stake:PositionCreated', async ({ event, context }) => {
|
|||
const shareRatio = toShareRatio(event.args.share, stakeTotalSupply);
|
||||
const totalSupplyInit = await getKraikenTotalSupply(context);
|
||||
|
||||
const taxRateIndex = Number(event.args.taxRate);
|
||||
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,
|
||||
taxRate: TAX_RATES[taxRateIndex] || 0,
|
||||
taxRateIndex,
|
||||
kraikenDeposit: event.args.kraikenDeposit,
|
||||
stakeDeposit: event.args.kraikenDeposit,
|
||||
taxPaid: ZERO,
|
||||
|
|
@ -115,10 +117,12 @@ ponder.on('Stake:PositionTaxPaid', async ({ event, context }) => {
|
|||
const stakeTotalSupply = await getStakeTotalSupply(context);
|
||||
const shareRatio = toShareRatio(event.args.newShares, stakeTotalSupply);
|
||||
|
||||
const taxRateIndex = Number(event.args.taxRate);
|
||||
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,
|
||||
taxRate: TAX_RATES[taxRateIndex] || position.taxRate,
|
||||
taxRateIndex,
|
||||
lastTaxTime: event.block.timestamp,
|
||||
});
|
||||
|
||||
|
|
@ -154,7 +158,9 @@ ponder.on('Stake:PositionTaxPaid', async ({ event, context }) => {
|
|||
|
||||
ponder.on('Stake:PositionRateHiked', async ({ event, context }) => {
|
||||
const positionId = event.args.positionId.toString();
|
||||
const taxRateIndex = Number(event.args.newTaxRate);
|
||||
await context.db.update(positions, { id: positionId }).set({
|
||||
taxRate: TAX_RATES[Number(event.args.newTaxRate)] || 0,
|
||||
taxRate: TAX_RATES[taxRateIndex] || 0,
|
||||
taxRateIndex,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue