> ## Documentation Index
> Fetch the complete documentation index at: https://docs.immutable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Indexer

Query Immutable Chain data—NFTs, tokens, activities, and orders—via APIs and webhooks, without running your own infrastructure.

## Why Use Indexer?

> [accordion] Zero Infrastructure
>
> Query blockchain data via simple API calls. No need to run nodes, databases, or indexing pipelines.

> [accordion] Real-Time Data
>
> Data indexed within seconds of on-chain confirmation. Webhooks push updates instantly.

> [accordion] Rich Queries
>
> Filter by owner, collection, attributes, activity type, and more. Get exactly the data you need.

> [accordion] Battle-Tested Scale
>
> Handles millions of queries daily across the Immutable ecosystem.

## Data Available

| Category | Examples |
| - | - |
| **NFTs** | Ownership, metadata, attributes, images |
| **Tokens** | ERC-20 balances, transfers |
| **Activities** | Mints, transfers, burns, sales |
| **Collections** | Stats, metadata, floor prices |
| **Orders** | Active listings and bids |

## Integration Patterns

Choose the right pattern for your use case:

### Polling vs Webhooks

| Pattern | Best For | Trade-offs |
| - | - | - |
| **Polling** | Player-driven requests, low volume | Simple, but may hit rate limits |
| **Webhooks** | Server sync, real-time updates | Efficient, requires server infrastructure |

> [tab] Polling
>
> Use polling when:
>
> * Player opens inventory (fetch their NFTs)
> * Player views an item (fetch metadata)
> * Player searches marketplace (query listings)
>
> ```typescript theme={null}
> import { BlockchainData } from '@imtbl/blockchain-data';
> import { Environment } from '@imtbl/config';
>
> const indexer = new BlockchainData({
>   baseConfig: {
>     environment: Environment.SANDBOX,
>     publishableKey: 'YOUR_PUBLISHABLE_KEY',
>   },
> });
> ```
>
> ```typescript theme={null}
> // Player opens inventory → fetch their NFTs
> async function loadInventory(address: string) {
>   const { result } = await indexer.listNFTsByAccountAddress({
>     chainName: 'imtbl-zkevm-testnet',
>     accountAddress: address,
>   });
>   return result;
> }
> ```

> [tab] Webhooks
>
> Use webhooks when:
>
> * Syncing to your database
> * Triggering game events on sales/transfers
> * Real-time leaderboards or activity feeds
>
> Configure webhooks in [Hub](https://hub.immutable.com).
>
> | Event | Trigger |
> | - | - |
> | `imtbl_zkevm_activity_mint` | NFT minted |
> | `imtbl_zkevm_activity_transfer` | NFT transferred |
> | `imtbl_zkevm_activity_burn` | NFT burned |
> | `imtbl_zkevm_activity_sale` | NFT sold |
> | `imtbl_zkevm_order_updated` | Order status changed |

### Caching Strategy

Reduce API calls with smart caching:

```typescript theme={null}
const CACHE_TTL = {
  nftMetadata: 24 * 60 * 60 * 1000, // 24 hours - rarely changes
  nftOwnership: 30 * 1000,          // 30 seconds - changes on transfers
  collectionStats: 5 * 60 * 1000,   // 5 minutes - aggregated data
  activities: 0,                     // No cache - always fresh
};

class IndexerCache {
  private cache = new Map<string, { data: any; expires: number }>();

  async get<T>(key: string, ttl: number, fetcher: () => Promise<T>): Promise<T> {
    const cached = this.cache.get(key);
    if (cached && cached.expires > Date.now()) {
      return cached.data as T;
    }
    
    const data = await fetcher();
    this.cache.set(key, { data, expires: Date.now() + ttl });
    return data;
  }
}

// Usage
const cache = new IndexerCache();

async function getNFTMetadata(contractAddress: string, tokenId: string) {
  return cache.get(
    `nft:${contractAddress}:${tokenId}`,
    CACHE_TTL.nftMetadata,
    () => indexer.getNFT({ chainName: 'imtbl-zkevm-testnet', contractAddress, tokenId })
  );
}
```

### Pagination

For large result sets, paginate efficiently:

```typescript theme={null}
async function getAllNFTs(contractAddress: string): Promise<NFT[]> {
  const allNFTs: NFT[] = [];
  let cursor: string | undefined;
  
  do {
    const { result, page } = await indexer.listNFTs({
      chainName: 'imtbl-zkevm-testnet',
      contractAddress,
      pageSize: 200, // Max page size
      pageCursor: cursor,
    });
    
    allNFTs.push(...result);
    cursor = page.nextCursor;
  } while (cursor);
  
  return allNFTs;
}
```

> [!WARNING]
>
> Full collection syncs can be slow. Use webhooks for keeping a database in sync rather than periodic full fetches.

### Rate Limit Handling

```typescript theme={null}
async function fetchWithRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error: any) {
      if (error.status === 429 && i < maxRetries - 1) {
        // Rate limited - exponential backoff
        await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
        continue;
      }
      throw error;
    }
  }
  throw new Error('Max retries exceeded');
}
```

| Tier | Rate Limit |
| - | - |
| Standard | 50 req/sec |
| Enterprise | Custom |

## Common Patterns

### Player Inventory

```typescript theme={null}
async function getPlayerInventory(address: string, contractAddress?: string) {
  const { result } = await indexer.listNFTsByAccountAddress({
    chainName: 'imtbl-zkevm-testnet',
    accountAddress: address,
    contractAddress, // Optional: filter to specific collection
  });
  
  return result.map(nft => ({
    id: nft.token_id,
    name: nft.name,
    image: nft.image,
    attributes: nft.attributes,
  }));
}
```

### Activity Feed

```typescript theme={null}
async function getRecentActivity(contractAddress: string) {
  const { result } = await indexer.listActivities({
    chainName: 'imtbl-zkevm-testnet',
    contractAddress,
    activityType: 'sale', // or 'mint', 'transfer', 'burn'
    pageSize: 20,
  });
  
  return result.map(activity => ({
    type: activity.activity_type,
    from: activity.from,
    to: activity.to,
    tokenId: activity.token_id,
    timestamp: activity.indexed_at,
    price: activity.details?.amount,
  }));
}
```

### Collection Stats

```typescript theme={null}
async function getCollectionOverview(contractAddress: string) {
  const collection = await indexer.getCollection({
    chainName: 'imtbl-zkevm-testnet',
    contractAddress,
  });
  
  return {
    name: collection.name,
    totalSupply: collection.total_supply,
    holders: collection.distinct_owners,
    // Note: floor price comes from Orderbook, not Indexer
  };
}
```

## Webhook Integration

### Verifying Webhooks

Always verify webhook signatures:

```typescript theme={null}
import crypto from 'crypto';

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// In your webhook handler
app.post('/webhooks/immutable', (req, res) => {
  const signature = req.headers['x-immutable-signature'];
  const isValid = verifyWebhookSignature(
    JSON.stringify(req.body),
    signature,
    process.env.WEBHOOK_SECRET
  );
  
  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process the webhook
  handleWebhookEvent(req.body);
  res.status(200).send('OK');
});
```

### Database Sync Pattern

```typescript theme={null}
// When receiving a transfer webhook
async function handleTransfer(data: TransferEvent) {
  const { contract_address, token_id, from, to } = data;
  
  await db.nft.update({
    where: { contractAddress: contract_address, tokenId: token_id },
    data: { owner: to },
  });
  
  // Optionally fetch fresh data to ensure consistency
  const nft = await indexer.getNFT({
    chainName: 'imtbl-zkevm-testnet',
    contractAddress: contract_address,
    tokenId: token_id,
  });
  
  await db.nft.update({
    where: { contractAddress: contract_address, tokenId: token_id },
    data: { 
      owner: nft.owner,
      metadata: nft.metadata,
    },
  });
}
```

> [!NOTE]
>
> Webhooks are available to partners with a managed relationship. [Contact us](https://www.immutable.com/contact) to enable webhooks.

## Base URLs

| Environment | URL |
| - | - |
| Testnet | `https://api.sandbox.immutable.com` |
| Mainnet | `https://api.immutable.com` |

## Next Steps

> [card] [Metadata Search](https://docs.immutable.com/docs/products/indexer/metadata-search.md)
>
> Filter NFTs by attributes

> [card] [Build a Marketplace](https://docs.immutable.com/docs/guides/build-a-marketplace.md)
>
> Use the Indexer in your marketplace

> [card] [Orderbook](https://docs.immutable.com/docs/products/orderbook/overview.md)
>
> Build trading functionality

> [card] [Contracts](https://docs.immutable.com/docs/products/asset-contracts/overview.md)
>
> Understand NFT contract structure


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.