Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

15 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸš€ Solana NFT Indexer

A production-ready, high-performance Solana NFT transaction indexer built with TypeScript, Bun, and MongoDB

TypeScript Bun MongoDB License

Features β€’ Architecture β€’ Integrations β€’ Quick Start β€’ API Documentation β€’ Library Usage


πŸ“– Overview

The Solana NFT Indexer is a real-time transaction monitoring system designed specifically for tracking NFT mints and transfers on the Solana blockchain. It uses a dual-strategy indexing approach:

  • Real-time Streaming: Via Yellowstone gRPC for low-latency transaction processing
  • Historical Backfill: Via Helius API for catching up on missed transactions

The indexer stores all transaction data in MongoDB and provides a comprehensive RESTful API for querying NFT ownership, transaction history, and metadata.


✨ Features

Core Functionality

  • πŸ”„ Real-time Transaction Monitoring - Subscribe to specific NFT collections via Geyser gRPC
  • πŸ“œ Historical Backfill - Catch up on missed transactions with Helius API polling
  • πŸ’Ύ MongoDB Persistence - Efficient storage with compound indexes for fast queries
  • πŸ” Transaction Categorization - Automatic detection of mints vs transfers
  • 🏷️ NFT Metadata Caching - 24-hour TTL cache for reduced API calls
  • 🎯 Multi-Collection Support - Track multiple NFT collections simultaneously

API & CLI

  • 🌐 RESTful API - Comprehensive HTTP endpoints with Elysia.js
  • πŸ’» CLI Interface - Command-line tools for managing the indexer
  • πŸ“Š Health Monitoring - Built-in health checks and status endpoints
  • πŸ”Œ WebSocket Ready - Real-time updates via streaming events

Developer Experience

  • ⚑ Bun Runtime - Fast startup and low memory footprint
  • πŸ”’ TypeScript - Full type safety with strict mode
  • πŸ“ Structured Logging - Pino logger with JSON output
  • πŸ›‘οΈ Error Handling - Comprehensive error recovery and retry logic
  • πŸ“¦ Importable Library - Use as a standalone app or import as a package

πŸ—οΈ Architecture

System Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Solana NFT Indexer System                        β”‚
β”‚                                                                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚                       Application Layer                            β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚   β”‚
β”‚  β”‚  β”‚    CLI     β”‚  β”‚   API      β”‚  β”‚ Orchestratorβ”‚  β”‚  Service  β”‚ β”‚   β”‚
β”‚  β”‚  β”‚   (Elysia)  β”‚  β”‚  (Elysia)  β”‚  β”‚    (Event   β”‚  β”‚  Layer    β”‚ β”‚   β”‚
β”‚  β”‚  β”‚            β”‚  β”‚            β”‚  β”‚    Emitter) β”‚  β”‚           β”‚ β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”˜   β”‚
β”‚           β”‚               β”‚                   β”‚                   β”‚       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚                     Service Layer (Business Logic)                β”‚ β”‚
β”‚  β”‚                                                                      β”‚ β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚ β”‚
β”‚  β”‚  β”‚  Streaming   β”‚  β”‚   Backfill   β”‚  β”‚    Metadata      β”‚     β”‚ β”‚
β”‚  β”‚  β”‚  Service     β”‚  β”‚   Service    β”‚  β”‚    Service       β”‚     β”‚ β”‚
β”‚  β”‚  β”‚ (Geyser gRPC)β”‚  β”‚  (Helius)    β”‚  β”‚  (Metaplex)      β”‚     β”‚ β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚            β”‚                   β”‚                   β”‚                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚              Processor Service (Transaction Pipeline)        β”‚    β”‚
β”‚  β”‚  β€’ Categorize transactions (mint vs transfer)                   β”‚    β”‚
β”‚  β”‚  β€’ Deduplicate and validate                                       β”‚    β”‚
β”‚  β”‚  β€’ Update ownership records                                       β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       Data Layer                                        β”‚
β”‚                                                                      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”‚
β”‚  β”‚   MongoDB    β”‚  β”‚   Models     β”‚  β”‚    Utils     β”‚              β”‚
β”‚  β”‚   (Mongoose)  β”‚  β”‚  (Schemas)   β”‚  β”‚  (Logger, etc)β”‚              β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”Œ External Integrations

The indexer integrates with several third-party services to provide comprehensive NFT indexing capabilities.

Integration Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                           Solana Blockchain                                 β”‚
β”‚                     (Source of NFT Transactions)                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                             β”‚
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚                                       β”‚
         β–Ό                                       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ”· Geyser gRPC       β”‚              β”‚  πŸ”΅ Helius API         β”‚
β”‚  (Real-time Stream)   β”‚              β”‚  (Historical Data)    β”‚
β”‚                      β”‚              β”‚                      β”‚
β”‚  β€’ SUBSCRIBE txs      β”‚              β”‚  β€’ GET /assets        β”‚
β”‚  β€’ Stream confirmationsβ”‚              β”‚  β€’ GET /transactions  β”‚
β”‚  β€’ Low latency        β”‚              β”‚  β€’ GET /signatures    β”‚
β”‚  β€’ gRPC protocol      β”‚              β”‚  β€’ REST API           β”‚
β”‚  β€’ Requires token     β”‚              β”‚  β€’ API key auth       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚                                     β”‚
           β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
           └────┴                           β”Œβ”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜
                β”‚                       β”‚
                β–Ό                       β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚   Solana NFT Indexer               β”‚
    β”‚                                    β”‚
    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
    β”‚  β”‚  Metaplex JS SDK          β”‚     β”‚
    β”‚  β”‚  (NFT Metadata)           β”‚     β”‚
    β”‚  β”‚                           β”‚     β”‚
    β”‚  β”‚  β€’ findByMint()           β”‚     β”‚
    β”‚  β”‚  β€’ findAllByOwner()       β”‚     β”‚
    β”‚  β”‚  β€’ parseMetadata()        β”‚     β”‚
    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
    β”‚             β”‚                       β”‚
    β”‚             β–Ό                       β”‚
    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
    β”‚  β”‚   Transaction Processor  β”‚     β”‚
    β”‚  β”‚                           β”‚     β”‚
    β”‚  β”‚  β€’ Parse instructions     β”‚     β”‚
    β”‚  β”‚  β€’ Extract mint/owner     β”‚     β”‚
    β”‚  β”‚  β€’ Detect tx type         β”‚     β”‚
    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
    β”‚             β”‚                       β”‚
    β”‚             β–Ό                       β”‚
    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
    β”‚  β”‚      MongoDB              β”‚     β”‚
    β”‚  β”‚                           β”‚     β”‚
    β”‚  β”‚  β€’ Transactions           β”‚     β”‚
    β”‚  β”‚  β€’ Token Ownership        β”‚     β”‚
    β”‚  β”‚  β€’ Collection State       β”‚     β”‚
    β”‚  β”‚  β€’ NFT Metadata Cache     β”‚     β”‚
    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚
               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           HTTP API (Elysia.js)               β”‚
β”‚                                              β”‚
β”‚  β€’ GET  /health                              β”‚
β”‚  β€’ GET  /indexer/transactions               β”‚
β”‚  β€’ GET  /indexer/ownerships                  β”‚
β”‚  β€’ POST /indexer/start                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Third-Party Service Details

1. πŸ”· Geyser (Yellowstone gRPC)

Purpose: Real-time transaction streaming

Communication Method: gRPC (bidirectional streaming)

How It Works:

Indexer ──[gRPC Subscribe]──▢ Geyser Server ◀───[Stream]──▢ Confirmed Txs
          β”‚                                      β”‚
          └───[Read token list]β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

API Used:

// @triton-one/yellowstone-grpc
import Client from '@triton-one/yellowstone-grpc';

// Subscribe to transactions for specific mints
const client = new Client(endpoint, authToken);
const stream = await client.subscribe({
  transactions: {
    accountInclude: [mint1, mint2, ...]
  }
});

// Stream emits confirmed transactions
stream.on('data', (transaction) => {
  // Process in real-time
});

Data Flow:

  1. Indexer sends SubscribeRequest with mint addresses to watch
  2. Geyser maintains connection and pushes transactions as they're confirmed
  3. Each transaction includes: signature, slot, instructions, accountKeys
  4. Indexer parses SPL Token instructions to detect mints/transfers

2. πŸ”΅ Helius API

Purpose: Historical transaction backfill and asset queries

Communication Method: HTTPS REST API

How It Works:

Indexer ──[GET /assets?collection=X]──▢ Helius ◀───[Asset List]β”€β”€β”€β”˜
          β”‚                                   β”‚
          └───[GET /signatures?address=X]──▢ Helius ◀───[Tx List]β”€β”€β”€β”€β”€β”˜

API Used:

// HTTP REST calls
const assets = await fetch(
  `https://api.helius.xyz/v0/assets?collectionAddress=${collection}`,
  { headers: { 'Authorization': `Bearer ${apiKey}` } }
);

const transactions = await fetch(
  `https://api.helius.xyz/v0/transactions/history?address=${mint}&before=${sig}`,
  { headers: { 'Authorization': `Bearer ${apiKey}` } }
);

Key Endpoints:

  • GET /v0/assets?collectionAddress={address} - Get all NFTs in collection
  • GET /v0/transactions/history?address={address} - Get transaction history
  • POST /v0/transactions - Get transaction by signatures

Data Flow:

  1. Indexer fetches all assets in a collection
  2. For each asset, fetches transaction history since last processed signature
  3. Processes transactions in chronological order (oldest first)
  4. Handles pagination and rate limiting automatically

3. 🟣 Metaplex JS SDK

Purpose: NFT metadata fetching and parsing

Communication Method: RPC calls to Solana RPC + HTTP for off-chain metadata

How It Works:

Indexer ──[Metaplex.findByMint()]──▢ Solana RPC ◀───[On-chain Metadata Account]β”€β”€β”€β”˜
                                       β”‚
                                       └───[HTTP GET metadata URI]──▢ Arweave/IPFS

API Used:

import { Umi } from '@metaplex-foundation/js';
import { publicKey } from '@solana/web3.js';

// Connect to Solana
const umi = Umi.create(process.env.RPC_ENDPOINT);

// Find NFT by mint
const nft = await findMetadataPda(umi);
const metadata = await fetchMetadata(umi, nft);

Data Flow:

  1. Indexer calls Metaplex.findByMint(mintAddress)
  2. SDK derives Metadata PDA from mint address
  3. Fetches on-chain metadata account from Solana
  4. Parses metadata structure (name, symbol, uri, creators)
  5. Optionally fetches off-chain JSON from uri field
  6. Extracts attributes/traits from metadata

4. 🟒 MongoDB (with Mongoose)

Purpose: Persistent storage for transactions, ownership, and metadata

Communication Method: MongoDB Wire Protocol (via Mongoose driver)

How It Works:

Indexer ──[Mongoose.connect()]──▢ MongoDB ◀───[Query/Insert]β”€β”€β”€β”˜
          β”‚                           β”‚
          └───[Model.create()]β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

API Used:

import mongoose from 'mongoose';

// Connect
await mongoose.connect(mongoUri);

// Define schema
const TransactionSchema = new Schema({
  signature: { type: String, unique: true },
  slot: Number,
  from_address: String,
  to_address: String,
  // ...
});

// Create document
await TransactionModel.create({
  signature: tx.signature,
  slot: tx.slot,
  // ...
});

// Query
const txs = await TransactionModel.find({ mint_address: address })
  .sort({ blockTime: -1 })
  .limit(100);

Data Models:

  • Transaction - Stores all NFT transactions
  • TokenOwnership - Current owner of each NFT
  • CollectionState - Indexing progress tracking
  • NFTMetadata - Cached metadata (24-hour TTL)

5. 🌐 Elysia.js

Purpose: HTTP API server framework

Communication Method: HTTP (serves client requests)

How It Works:

Client ──[GET /transactions]──▢ Elysia Server ◀───[Controller]───▢ MongoDB
         β”‚                                       β”‚
         └───[POST /start]β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

API Used:

import { Elysia, t } from 'elysia';

// Define schema
const TransactionSchema = t.Object({
  signature: t.String(),
  slot: t.Number(),
  from_address: t.String(),
  to_address: t.String(),
});

// Create route
app.get('/transactions', async ({ query }) => {
  const { limit = 50 } = query;
  const txs = await TransactionModel.find().limit(limit);
  return { transactions: txs, pagination: {...} };
});

Data Flow Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Transaction Lifecycle                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

1. NFT Minted on Solana
   β”‚
   β–Ό
2. Geyser Detects Transaction (real-time)
   └── gRPC stream β†’ TransactionProcessor
   β”‚
   β–Ό
3. Helius Backfill (if missed)
   └── HTTP GET /transactions β†’ TransactionProcessor
   β”‚
   β–Ό
4. TransactionProcessor Categorizes
   β”œβ”€β”€ SPL Token InitializeMint? β†’ MINT
   └── SPL Token Transfer? β†’ TRANSFER
   β”‚
   β–Ό
5. Metaplex Fetches Metadata
   β”œβ”€β”€ findMetadataPda() β†’ On-chain account
   └── fetchMetadata() β†’ Off-chain JSON (uri field)
   β”‚
   β–Ό
6. Data Stored in MongoDB
   β”œβ”€β”€ TransactionModel.create()
   β”œβ”€β”€ TokenOwnershipModel.updateOne()
   β”œβ”€β”€ NFTMetadataModel.create() (with TTL)
   └── CollectionStateModel.updateOne()
   β”‚
   β–Ό
7. Queryable via HTTP API
   └── Elysia GET /indexer/transactions β†’ MongoDB β†’ JSON Response

πŸš€ Quick Start

Installation as Library

npm install solana-nft-indexer

Installation as Standalone

git clone <repo-url>
cd solana-indexer
bun install

πŸ“š API Documentation

Endpoints

Category Endpoint Description
Health GET /health System health check
Indexer POST /indexer/start Start indexing service
Transactions GET /indexer/transactions List transactions (paginated)
Ownership GET /indexer/ownerships Get NFT ownership
Metadata GET /indexer/metadata/:mint Get NFT metadata

Example Usage

# Check health
curl http://localhost:3001/health

# Get recent transactions
curl http://localhost:3001/indexer/transactions?limit=10

# Get NFT metadata
curl http://localhost:3001/indexer/metadata/<MINT_ADDRESS>

πŸ“¦ Library Usage

Import as Package

// Import services
import {
  GeyserStreamingService,
  MetadataService,
  ProcessorService
} from 'solana-nft-indexer';

// Import types
import type {
  ParsedTransaction,
  NFTMetadata,
  EnrichedTransaction
} from 'solana-nft-indexer';

// Create streaming instance
const streamer = GeyserStreamingService.getInstance();
await streamer.connect();
await streamer.subscribe(['mint1', 'mint2']);

streamer.on('transaction', (tx) => {
  console.log('New transaction:', tx);
});

// Fetch metadata
const metadataService = MetadataService.getInstance(rpcUrl);
const nft = await metadataService.getNFTMetadata(mintAddress);

Direct Instantiation

import { ProcessorService } from 'solana-nft-indexer';

// Create processor for specific collection
const processor = new ProcessorService({
  collectionAddress: 'collection_mint_address',
  rpcEndpoint: 'https://your-rpc-url'
});

// Process transaction
await processor.processTransaction(transaction, assetMap);

βš™οΈ Configuration

Environment Variables

Variable Required Default Description
MONGODB_URI βœ… - MongoDB connection string
YELLOWSTONE_GRPC_ENDPOINT ❌ - Geyser gRPC endpoint
HELIUS_API_KEY βœ… - Helius API key
SOLANA_RPC_ENDPOINT βœ… - Solana RPC URL
COLLECTION_ADDRESS βœ… - Collection to index

πŸ“„ License

MIT License - see LICENSE for details


Built with ❀️ by the Solana community

About

πŸš€ Production-ready Solana NFT indexer with real-time Geyser gRPC streaming, Helius backfill, and MongoDB persistence. Built with TypeScript + Bun.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages