feat: wallet P&L with average cost basis tracking (#156) (#158)

This commit is contained in:
johba 2026-02-19 16:22:23 +01:00
parent 76b2635e63
commit 66106077ba
4 changed files with 139 additions and 3 deletions

View file

@ -259,12 +259,21 @@ export const recenters = onchainTable('recenters', t => ({
vwapTick: t.integer(), // nullable
}));
// Holders - track Kraiken token holders
// Holders - track Kraiken token holders with cost basis for P&L
export const holders = onchainTable(
'holders',
t => ({
address: t.hex().primaryKey(),
balance: t.bigint().notNull(),
// Cost basis tracking (updated on swaps only, not wallet-to-wallet transfers)
totalEthSpent: t
.bigint()
.notNull()
.$default(() => 0n), // cumulative ETH spent buying KRK
totalTokensAcquired: t
.bigint()
.notNull()
.$default(() => 0n), // cumulative KRK received from buys
}),
table => ({
addressIdx: index().on(table.address),

View file

@ -14,6 +14,10 @@ import { validateContractVersion } from './helpers/version';
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' as const;
// Pool address for detecting swaps (buys/sells)
// Computed deterministically from Uniswap V3 factory + WETH + Kraiken + 1% fee
const POOL_ADDRESS = (process.env.POOL_ADDRESS || '0x1f69cbfc7d3529a4fb4eadf18ec5644b2603b5ab').toLowerCase() as `0x${string}`;
// Track if version has been validated
let versionValidated = false;
@ -77,9 +81,25 @@ ponder.on('Kraiken:Transfer', async ({ event, context }) => {
const oldBalance = toHolder?.balance ?? 0n;
const newBalance = oldBalance + value;
// Detect buy: tokens coming FROM the pool = user bought KRK
const isBuy = from.toLowerCase() === POOL_ADDRESS;
let ethSpentDelta = 0n;
if (isBuy && value > 0n) {
// Approximate ETH cost using current price from stats
const currentPrice = statsData.currentPriceWei ?? 0n;
if (currentPrice > 0n) {
ethSpentDelta = (value * currentPrice) / 10n ** 18n;
}
}
if (toHolder) {
await context.db.update(holders, { address: to }).set({
balance: newBalance,
...(isBuy && {
totalEthSpent: (toHolder.totalEthSpent ?? 0n) + ethSpentDelta,
totalTokensAcquired: (toHolder.totalTokensAcquired ?? 0n) + value,
}),
});
// If this was a new holder (balance was 0), increment count
if (oldBalance === 0n) {
@ -90,6 +110,8 @@ ponder.on('Kraiken:Transfer', async ({ event, context }) => {
await context.db.insert(holders).values({
address: to,
balance: newBalance,
totalEthSpent: ethSpentDelta,
totalTokensAcquired: isBuy ? value : 0n,
});
holderCountDelta += 1;
}