import { ponder } from "ponder:registry";
import schema from "ponder:schema";
// Helper function to create unique IDs
function createTransferId(event: any) {
return `${event.transaction.hash}:${event.log.logIndex}`;
}
function createBalanceId(account: string, contract: string, chain: string) {
return `${account}:${contract}:${chain}`;
}
function createStatsId(contract: string, chain: string) {
return `${contract}:${chain}`;
}
// Index USDC transfers on Ethereum
ponder.on("UsdcMainnet:Transfer", async ({ event, context }) => {
const { from, to, value } = event.args;
const transferId = createTransferId(event);
// Insert transfer record
await context.db.insert(schema.transfer).values({
id: transferId,
from,
to,
amount: value,
contract: event.log.address,
chain: "ethereum",
blockNumber: event.block.number,
timestamp: Number(event.block.timestamp),
});
// Update balances for both accounts (if not mint/burn)
const zeroAddress = "0x0000000000000000000000000000000000000000";
if (from !== zeroAddress) {
await updateBalance(context, from, event.log.address, "ethereum", -value, event.block.timestamp);
}
if (to !== zeroAddress) {
await updateBalance(context, to, event.log.address, "ethereum", value, event.block.timestamp);
}
// Update token statistics
await updateTokenStats(context, event.log.address, "ethereum", value, event.block.timestamp);
});
// Index USDC transfers on Polygon
ponder.on("UsdcPolygon:Transfer", async ({ event, context }) => {
const { from, to, value } = event.args;
const transferId = createTransferId(event);
await context.db.insert(schema.transfer).values({
id: transferId,
from,
to,
amount: value,
contract: event.log.address,
chain: "polygon",
blockNumber: event.block.number,
timestamp: Number(event.block.timestamp),
});
const zeroAddress = "0x0000000000000000000000000000000000000000";
if (from !== zeroAddress) {
await updateBalance(context, from, event.log.address, "polygon", -value, event.block.timestamp);
}
if (to !== zeroAddress) {
await updateBalance(context, to, event.log.address, "polygon", value, event.block.timestamp);
}
await updateTokenStats(context, event.log.address, "polygon", value, event.block.timestamp);
});
// Index USDC transfers on Base
ponder.on("UsdcBase:Transfer", async ({ event, context }) => {
const { from, to, value } = event.args;
const transferId = createTransferId(event);
await context.db.insert(schema.transfer).values({
id: transferId,
from,
to,
amount: value,
contract: event.log.address,
chain: "base",
blockNumber: event.block.number,
timestamp: Number(event.block.timestamp),
});
const zeroAddress = "0x0000000000000000000000000000000000000000";
if (from !== zeroAddress) {
await updateBalance(context, from, event.log.address, "base", -value, event.block.timestamp);
}
if (to !== zeroAddress) {
await updateBalance(context, to, event.log.address, "base", value, event.block.timestamp);
}
await updateTokenStats(context, event.log.address, "base", value, event.block.timestamp);
});
// Helper function to update account balances
async function updateBalance(
context: any,
account: string,
contract: string,
chain: string,
change: bigint,
blockTimestamp: bigint
) {
const balanceId = createBalanceId(account, contract, chain);
// Try to get existing balance
const existingBalance = await context.db.find(schema.balance, {
id: balanceId
});
if (existingBalance) {
// Update existing balance
const oldBalance = existingBalance.balance;
const newBalance = oldBalance + change;
// Handle cases where we're processing historical data from a start block
// and don't have the complete balance history
if (newBalance < 0n && change < 0n) {
// This suggests the account had a balance before our start block
// Initialize with the absolute value of the change (minimum needed balance)
const initializedBalance = -change;
await context.db.update(schema.balance, {
id: balanceId
}).set({
balance: initializedBalance + change, // This will equal initializedBalance - abs(change) = 0
lastUpdated: Number(blockTimestamp),
});
return; // Exit early after handling this case
} else if (newBalance < 0n) {
throw new Error(`Balance would become negative for account ${account}. Old: ${oldBalance}, Change: ${change}`);
}
await context.db.update(schema.balance, {
id: balanceId
}).set({
balance: newBalance,
lastUpdated: Number(blockTimestamp),
});
// Update holder count based on balance changes
if (oldBalance === 0n && newBalance > 0n) {
// New holder (balance went from 0 to positive)
await updateHolderCount(context, contract, chain, 1, blockTimestamp);
} else if (oldBalance > 0n && newBalance === 0n) {
// Holder exit (balance went from positive to 0)
await updateHolderCount(context, contract, chain, -1, blockTimestamp);
}
} else {
// Create new balance record
if (change < 0n) {
// Account is sending tokens but has no balance record
// This means they had tokens before our start block
// Initialize with the absolute value of the change, then subtract it
const initializedBalance = -change;
await context.db.insert(schema.balance).values({
id: balanceId,
account,
contract,
chain,
balance: 0n, // After the transfer, balance will be 0
lastUpdated: Number(blockTimestamp),
});
// Account just exited its position – adjust holder count
await updateHolderCount(context, contract, chain, -1, blockTimestamp);
} else {
// Normal case: receiving tokens to a new account
await context.db.insert(schema.balance).values({
id: balanceId,
account,
contract,
chain,
balance: change,
lastUpdated: Number(blockTimestamp),
});
// If this is a new holder with positive balance, increment holder count
if (change > 0n) {
await updateHolderCount(context, contract, chain, 1, blockTimestamp);
}
}
}
}
// Helper function to update holder count
async function updateHolderCount(
context: any,
contract: string,
chain: string,
change: number,
blockTimestamp: bigint
) {
const statsId = createStatsId(contract, chain);
const existingStats = await context.db.find(schema.tokenStats, {
id: statsId
});
if (existingStats) {
await context.db.update(schema.tokenStats, {
id: statsId
}).set({
totalHolders: Math.max(0, existingStats.totalHolders + change),
lastUpdated: Number(blockTimestamp),
});
} else {
// Create new token stats record for the first holder event
await context.db.insert(schema.tokenStats).values({
id: statsId,
contract,
chain,
totalHolders: Math.max(0, change),
totalTransfers: 0n,
totalVolume: 0n,
lastUpdated: Number(blockTimestamp),
});
}
}
// Helper function to update token statistics
async function updateTokenStats(
context: any,
contract: string,
chain: string,
volume: bigint,
blockTimestamp: bigint
) {
const statsId = createStatsId(contract, chain);
const existingStats = await context.db.find(schema.tokenStats, {
id: statsId
});
if (existingStats) {
await context.db.update(schema.tokenStats, {
id: statsId
}).set({
totalTransfers: existingStats.totalTransfers + 1n,
totalVolume: existingStats.totalVolume + volume,
lastUpdated: Number(blockTimestamp),
});
} else {
await context.db.insert(schema.tokenStats).values({
id: statsId,
contract,
chain,
totalHolders: 0,
totalTransfers: 1n,
totalVolume: volume,
lastUpdated: Number(blockTimestamp),
});
}
}