Skip to content

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) in ponder.config.ts
  • Factory Pattern: IFIFv2 contracts are dynamically indexed via factory deployment events
  • Database Schema: Defined in ponder.schema.ts with 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 chainId for multi-chain compatibility
  • Composite primary keys include chainId for 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 dailyMetric table
  • Cross-chain analytics through consistent chainId usage
  • Real-time project state updates in unified project table

🛡️ Data Integrity

  • Duplicate event prevention using event ID as primary key
  • Composite primary keys with chainId for 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

  • projectV2Configproject: Links via projectAddress and chainId
  • fundClaimConfigproject: Links via projectAddress and chainId
  • distributorConfigproject: Links via projectAddress and chainId
  • claimConfigproject: Links via projectAddress and chainId
  • fundClaimproject: Links via projectAddress and chainId
  • sweepproject: Links via projectAddress and chainId

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 Address

Performance Optimizations

  • Multi-Chain Support: All tables and queries use chainId for 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 project table 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 project table
  • No data migration required for v1 → v2 upgrades