IFIFv2 Indexer
Comprehensive real-time indexing for IFIFv2 project contracts, providing advanced event tracking, multi-version support, and full multi-chain compatibility.
Overview
The IFIFv2 indexer processes all v2-specific events from IFIF contracts, transforming raw blockchain events into structured, queryable data. It extends the unified IFIF indexer with explicit support for v2 features, multi-chain event handling, and version-aware state management.
Architecture
File Structure
- Primary Implementation:
indexer/src/ifif-v2.ts- Contains all IFIFv2-specific event handlers - Unified Configuration: Uses merged ABIs (
ififAbi + ifiFv2Abi) inponder.config.ts - Factory Pattern: IFIFv2 contracts are dynamically indexed via factory deployment events
- Database Schema: Defined in
ponder.schema.tswith dedicated v2 tables
Features
🔍 Event Processing
- ConfigV2Updated: v2 configuration changes (privateSaleStartEpoch, purchaseHardCapPercent, purchaseSoftCap, minNFTWeight)
- FundClaimConfigUpdated: Fund claim period configuration (startEpoch, endEpoch, periodLength)
- DistributorConfigUpdated: Distributor and fee configuration changes (distributorFeePercent, projectOwnerFeePercent, platformFeePercent)
- ClaimConfigUpdated: Token claim configuration (startEpoch, endEpoch, sweepEpoch, periodLength)
- FundClaimed: Project owner fund claim tracking
- Sweept: Factory-initiated sweep of unclaimed funds and tokens
🔗 Project ID Resolution
All v2 event handlers automatically resolve projectId from the main project table using composite key lookup:
const projectRecord = await context.db.find(project, { id: projectAddress, chainId });
if (projectRecord && typeof projectRecord.projectId === "number") {
projectId = projectRecord.projectId;
}🌐 Multi-Chain & Version Support
- All event handlers and schema tables use
chainIdfor multi-chain compatibility - Composite primary keys include
chainIdfor accurate event tracking across chains - Unified contract configuration using merged ABIs (
mergeAbis([ififAbi, ifiFv2Abi])) - Factory-based dynamic contract indexing for both v1 and v2 deployments
- Cross-version compatibility through shared project schema
📊 Data Analytics
- v2-specific configuration and fund claim tracking
- Distributor and claim config analytics with fee breakdown
- Fund claim and sweep event monitoring with amount tracking
- Integration with daily metrics through shared
dailyMetrictable - Cross-chain analytics through consistent
chainIdusage - Real-time project state updates in unified
projecttable
🛡️ Data Integrity
- Duplicate event prevention using event ID as primary key
- Composite primary keys with
chainIdfor multi-chain safety - Transaction-level consistency with full block metadata
- Type-safe data validation using Ponder schema definitions
- Comprehensive error handling with project ID resolution fallbacks
- Atomic database operations for each event handler
Technical Implementation
Ponder Configuration
// ponder.config.ts
IFIF: {
chain: "anvil",
abi: mergeAbis([ififAbi, ifiFv2Abi]), // Unified ABI for v1 + v2 events
address: factory({
address: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
event: parseAbiItem("event Deployed(uint256 projectId, address project)"),
parameter: "project"
}),
startBlock: 0
}Event Handler Pattern
ponder.on("IFIF:ConfigV2Updated", async ({ event, context }) => {
const { minNFTWeight, privateSaleStartEpoch, purchaseHardCapPercent, purchaseSoftCap } = event.args.config_v2;
const eventId = event.id as Hash;
const chainId = context.chain.id;
const projectAddress = event.log.address as Address;
// Project ID resolution from main project table
let projectId = 0;
const projectRecord = await context.db.find(project, { id: projectAddress, chainId });
if (projectRecord && typeof projectRecord.projectId === "number") {
projectId = projectRecord.projectId;
}
await context.db.insert(projectV2Config).values({
id: eventId,
chainId,
projectId,
projectAddress,
privateSaleStartEpoch,
purchaseHardCapPercent,
purchaseSoftCap,
minNFTWeight,
updatedBy: event.transaction.from as Address,
timestamp: event.block.timestamp,
blockNumber: event.block.number,
transactionHash: event.transaction.hash,
});
});Schema Definition
All IFIFv2 tables use composite primary keys with chainId and event-specific identifiers:
// ponder.schema.ts
export const projectV2Config = onchainTable("projectV2Config", (t) => ({
id: t.hex().notNull(),
chainId: t.integer().notNull(),
projectId: t.integer().notNull(),
projectAddress: t.hex().notNull(),
// ... other fields
}), (t) => ({
pk: primaryKey({
columns: [t.id, t.chainId]
}),
}));Database Schema
projectV2Config Table
{
id: Hash, // Event ID (primary key)
chainId: number, // Chain ID for multi-chain support
projectId: number, // Project ID from factory
projectAddress: Address, // Project contract address
privateSaleStartEpoch: bigint,
purchaseHardCapPercent: bigint,
purchaseSoftCap: bigint,
minNFTWeight: bigint,
updatedBy: Address,
timestamp: bigint,
blockNumber: bigint,
transactionHash: Hash
}fundClaimConfig Table
{
id: Hash,
chainId: number,
projectId: number,
projectAddress: Address,
startEpoch: bigint,
endEpoch: bigint,
periodLength: bigint,
updatedBy: Address,
timestamp: bigint,
blockNumber: bigint,
transactionHash: Hash
}distributorConfig Table
{
id: Hash,
chainId: number,
projectId: number,
projectAddress: Address,
distributor: Address,
distributorFeePercent: bigint,
projectOwnerFeePercent: bigint,
platformFeePercent: bigint,
updatedBy: Address,
timestamp: bigint,
blockNumber: bigint,
transactionHash: Hash
}claimConfig Table
{
id: Hash,
chainId: number,
projectId: number,
projectAddress: Address,
startEpoch: bigint,
endEpoch: bigint,
sweepEpoch: bigint,
periodLength: bigint,
updatedBy: Address,
timestamp: bigint,
blockNumber: bigint,
transactionHash: Hash
}fundClaim Table
{
id: Hash,
chainId: number,
projectId: number,
projectAddress: Address,
claimer: Address,
amount: bigint,
timestamp: bigint,
blockNumber: bigint,
transactionHash: Hash
}sweep Table
{
id: Hash,
chainId: number,
projectId: number,
projectAddress: Address,
sweeper: Address,
fundAmount: bigint,
tokenAmount: bigint,
timestamp: bigint,
blockNumber: bigint,
transactionHash: Hash
}Primary Key Structure
All IFIFv2 tables use composite primary keys for multi-chain support:
pk: primaryKey({
columns: [t.id, t.chainId] // Event ID + Chain ID
})Cross-Table Relationships
- projectV2Config → project: Links via
projectAddressandchainId - fundClaimConfig → project: Links via
projectAddressandchainId - distributorConfig → project: Links via
projectAddressandchainId - claimConfig → project: Links via
projectAddressandchainId - fundClaim → project: Links via
projectAddressandchainId - sweep → project: Links via
projectAddressandchainId
GraphQL API
Core Queries
Get v2 Project Config
query ProjectV2Config($projectAddress: String!, $chainId: Int!) {
projectV2Config(
where: { projectAddress: $projectAddress, chainId: $chainId }
) {
id
projectId
privateSaleStartEpoch
purchaseHardCapPercent
purchaseSoftCap
minNFTWeight
updatedBy
timestamp
blockNumber
transactionHash
}
}Get Fund Claim Config
query FundClaimConfig($projectAddress: String!, $chainId: Int!) {
fundClaimConfig(
where: { projectAddress: $projectAddress, chainId: $chainId }
) {
id
startEpoch
endEpoch
periodLength
updatedBy
timestamp
blockNumber
transactionHash
}
}Get Distributor Config
query DistributorConfig($projectAddress: String!, $chainId: Int!) {
distributorConfig(
where: { projectAddress: $projectAddress, chainId: $chainId }
) {
id
distributor
distributorFeePercent
projectOwnerFeePercent
platformFeePercent
updatedBy
timestamp
blockNumber
transactionHash
}
}Get Claim Config
query ClaimConfig($projectAddress: String!, $chainId: Int!) {
claimConfig(
where: { projectAddress: $projectAddress, chainId: $chainId }
) {
id
startEpoch
endEpoch
sweepEpoch
periodLength
updatedBy
timestamp
blockNumber
transactionHash
}
}Get Fund Claims
query FundClaims($projectAddress: String!, $chainId: Int!) {
fundClaims(
where: { projectAddress: $projectAddress, chainId: $chainId }
orderBy: "timestamp"
orderDirection: "desc"
) {
items {
id
claimer
amount
timestamp
blockNumber
transactionHash
}
}
}Get Sweeps
query Sweeps($projectAddress: String!, $chainId: Int!) {
sweeps(
where: { projectAddress: $projectAddress, chainId: $chainId }
orderBy: "timestamp"
orderDirection: "desc"
) {
items {
id
sweeper
fundAmount
tokenAmount
timestamp
blockNumber
transactionHash
}
}
}Error Handling & Edge Cases
Project ID Resolution
// Fallback handling for project ID resolution
let projectId = 0;
const projectRecord = await context.db.find(project, { id: projectAddress, chainId });
if (projectRecord && typeof projectRecord.projectId === "number") {
projectId = projectRecord.projectId;
}
// projectId remains 0 if project not found (graceful degradation)FundClaimed Claimer Resolution
// Special handling for FundClaimed events
let claimer: Address = event.transaction.from as Address;
const projectRecord = await context.db.find(project, { id: projectAddress, chainId });
if (projectRecord && typeof projectRecord.projectId === "number") {
projectId = projectRecord.projectId;
claimer = projectRecord.owner; // Use project owner as claimer
}Factory Sweep Address
// Sweep events always use factory contract as sweeper
sweeper: context.contracts.Factory.address as AddressPerformance Optimizations
- Multi-Chain Support: All tables and queries use
chainIdfor cross-chain event tracking - Unified ABI Configuration: Merged ABIs reduce configuration complexity
- Factory Pattern: Dynamic contract indexing without manual address configuration
- Efficient Queries: Composite primary keys enable fast multi-chain lookups
- Type Safety: Full TypeScript integration and contract ABI validation
- Duplicate Prevention: Event ID-based deduplication with chain-specific uniqueness
- Project ID Caching: Efficient project resolution through main project table lookups
Integration Examples
v2 Project Dashboard
// Get complete v2 project data with all configurations
const v2ProjectData = await ponder.query(`
query V2Project($address: String!, $chainId: Int!) {
# Main project data (shared v1/v2)
project(where: { id: $address, chainId: $chainId }) {
id
projectId
version
stage
owner
distributor
totalPurchase
totalWeight
createdAt
lastUpdated
}
# V2-specific configurations
projectV2Config(where: { projectAddress: $address, chainId: $chainId }) {
privateSaleStartEpoch
purchaseHardCapPercent
purchaseSoftCap
minNFTWeight
updatedBy
timestamp
}
fundClaimConfig(where: { projectAddress: $address, chainId: $chainId }) {
startEpoch
endEpoch
periodLength
updatedBy
timestamp
}
distributorConfig(where: { projectAddress: $address, chainId: $chainId }) {
distributor
distributorFeePercent
projectOwnerFeePercent
platformFeePercent
updatedBy
timestamp
}
claimConfig(where: { projectAddress: $address, chainId: $chainId }) {
startEpoch
endEpoch
sweepEpoch
periodLength
updatedBy
timestamp
}
# V2 activity tracking
fundClaims(
where: { projectAddress: $address, chainId: $chainId }
orderBy: "timestamp"
orderDirection: "desc"
) {
items {
claimer
amount
timestamp
transactionHash
}
}
sweeps(
where: { projectAddress: $address, chainId: $chainId }
orderBy: "timestamp"
orderDirection: "desc"
) {
items {
sweeper
fundAmount
tokenAmount
timestamp
transactionHash
}
}
}
`);Multi-Chain v2 Analytics
// Get v2 metrics across all chains
const v2Analytics = await ponder.query(`
query V2Analytics {
# All v2 projects across chains
projects(where: { version: 2 }) {
items {
id
chainId
projectId
stage
totalPurchase
totalWeight
createdAt
}
}
# Total fund claims across all v2 projects
fundClaims(orderBy: "timestamp", orderDirection: "desc", limit: 100) {
items {
projectAddress
chainId
claimer
amount
timestamp
}
}
# Recent sweep activities
sweeps(orderBy: "timestamp", orderDirection: "desc", limit: 50) {
items {
projectAddress
chainId
fundAmount
tokenAmount
timestamp
}
}
}
`);Version Compatibility
Unified Project Tracking
The IFIFv2 indexer integrates seamlessly with the main IFIF indexer:
- Shares the unified
projecttable with version field - Uses factory deployment events for automatic discovery
- Maintains cross-version compatibility through merged ABIs
- Supports both v1 and v2 projects in the same database
Migration Path
Existing v1 projects continue to work unchanged:
- v1 events are handled by
ifif.ts - v2 events are handled by
ifif-v2.ts - Shared project lifecycle through unified
projecttable - No data migration required for v1 → v2 upgrades