diff --git a/services/ponder/ponder.schema.ts b/services/ponder/ponder.schema.ts
index eeaedf5..d31086f 100644
--- a/services/ponder/ponder.schema.ts
+++ b/services/ponder/ponder.schema.ts
@@ -280,6 +280,28 @@ export const holders = onchainTable(
})
);
+// Transaction history for wallet dashboard
+export const transactions = onchainTable(
+ 'transactions',
+ t => ({
+ id: t.text().primaryKey(), // txHash-logIndex
+ holder: t.hex().notNull(),
+ type: t.text().notNull(), // "buy" | "sell" | "stake" | "unstake" | "snatch_in" | "snatch_out"
+ tokenAmount: t.bigint().notNull(),
+ ethAmount: t
+ .bigint()
+ .notNull()
+ .$default(() => 0n),
+ timestamp: t.bigint().notNull(),
+ blockNumber: t.integer().notNull(),
+ txHash: t.hex().notNull(),
+ }),
+ table => ({
+ holderIdx: index().on(table.holder),
+ timestampIdx: index().on(table.timestamp),
+ })
+);
+
// Helper constants
export const STATS_ID = '0x01';
export const SECONDS_IN_HOUR = 3600;
diff --git a/services/ponder/src/kraiken.ts b/services/ponder/src/kraiken.ts
index 8158e22..1056920 100644
--- a/services/ponder/src/kraiken.ts
+++ b/services/ponder/src/kraiken.ts
@@ -1,6 +1,6 @@
import { ponder } from 'ponder:registry';
import { getLogger } from './helpers/logger';
-import { stats, holders, STATS_ID } from 'ponder:schema';
+import { stats, holders, transactions, STATS_ID } from 'ponder:schema';
import {
ensureStatsExists,
parseRingBuffer,
@@ -118,6 +118,29 @@ ponder.on('Kraiken:Transfer', async ({ event, context }) => {
}
}
+ // Record buy/sell transactions
+ if (!isSelfTransfer && from !== ZERO_ADDRESS && to !== ZERO_ADDRESS) {
+ const isBuy = from.toLowerCase() === POOL_ADDRESS;
+ const isSell = to.toLowerCase() === POOL_ADDRESS;
+
+ if (isBuy || isSell) {
+ const currentPrice = statsData.currentPriceWei ?? 0n;
+ const ethEstimate = currentPrice > 0n ? (value * currentPrice) / 10n ** 18n : 0n;
+ const txId = `${event.transaction.hash}-${event.log.logIndex}`;
+
+ await context.db.insert(transactions).values({
+ id: txId,
+ holder: isBuy ? to : from,
+ type: isBuy ? 'buy' : 'sell',
+ tokenAmount: value,
+ ethAmount: ethEstimate,
+ timestamp: event.block.timestamp,
+ blockNumber: Number(event.block.number),
+ txHash: event.transaction.hash,
+ });
+ }
+ }
+
// Update holder count if changed (with underflow protection)
if (holderCountDelta !== 0) {
const newHolderCount = statsData.holderCount + holderCountDelta;
diff --git a/services/ponder/src/stake.ts b/services/ponder/src/stake.ts
index 2f912ca..a89c6b0 100644
--- a/services/ponder/src/stake.ts
+++ b/services/ponder/src/stake.ts
@@ -1,5 +1,5 @@
import { ponder } from 'ponder:registry';
-import { positions, stats, STATS_ID, TAX_RATES } from 'ponder:schema';
+import { positions, transactions, stats, STATS_ID, TAX_RATES } from 'ponder:schema';
import {
ensureStatsExists,
getStakeTotalSupply,
@@ -55,6 +55,18 @@ ponder.on('Stake:PositionCreated', async ({ event, context }) => {
payout: ZERO,
});
+ // Record stake transaction
+ await context.db.insert(transactions).values({
+ id: `${event.transaction.hash}-${event.log.logIndex}`,
+ holder: event.args.owner as `0x${string}`,
+ type: 'stake',
+ tokenAmount: event.args.kraikenDeposit,
+ ethAmount: ZERO,
+ timestamp: event.block.timestamp,
+ blockNumber: Number(event.block.number),
+ txHash: event.transaction.hash,
+ });
+
await refreshOutstandingStake(context);
await markPositionsUpdated(context, event.block.timestamp);
});
@@ -77,6 +89,18 @@ ponder.on('Stake:PositionRemoved', async ({ event, context }) => {
stakeDeposit: ZERO,
});
+ // Record unstake transaction (could be voluntary unstake or snatch payout)
+ await context.db.insert(transactions).values({
+ id: `${event.transaction.hash}-${event.log.logIndex}`,
+ holder: position.owner,
+ type: 'unstake',
+ tokenAmount: event.args.kraikenPayout,
+ ethAmount: ZERO,
+ timestamp: event.block.timestamp,
+ blockNumber: Number(event.block.number),
+ txHash: event.transaction.hash,
+ });
+
await refreshOutstandingStake(context);
await markPositionsUpdated(context, event.block.timestamp);
diff --git a/web-app/src/components/TransactionHistory.vue b/web-app/src/components/TransactionHistory.vue
new file mode 100644
index 0000000..dc14c26
--- /dev/null
+++ b/web-app/src/components/TransactionHistory.vue
@@ -0,0 +1,306 @@
+
+
+ Transaction History
+ {{ transactions.length }}
+
+
+
+
+
+
+
+
+
+ Date
+ Type
+ Amount (KRK)
+ Value (ETH)
+ Tx
+
+
+
+ {{ formatDate(tx.timestamp) }}
+
+
+ {{ txTypeLabel(tx.type) }}
+
+
+ {{ formatKrk(tx.tokenAmount) }}
+ {{ tx.ethAmount !== '0' ? formatEth(tx.ethAmount) : '—' }}
+
+
+ {{ shortHash(tx.txHash) }} ↗
+
+
+