A production-ready, high-performance Solana NFT transaction indexer built with TypeScript, Bun, and MongoDB
Features β’ Architecture β’ Integrations β’ Quick Start β’ API Documentation β’ Library Usage
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.
- π 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
- π 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
- β‘ 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
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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)β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The indexer integrates with several third-party services to provide comprehensive NFT indexing capabilities.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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 β
ββββββββββββββββββββββββββββββββββββββββββββββββ
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:
- Indexer sends
SubscribeRequestwith mint addresses to watch - Geyser maintains connection and pushes transactions as they're confirmed
- Each transaction includes: signature, slot, instructions, accountKeys
- Indexer parses SPL Token instructions to detect mints/transfers
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 collectionGET /v0/transactions/history?address={address}- Get transaction historyPOST /v0/transactions- Get transaction by signatures
Data Flow:
- Indexer fetches all assets in a collection
- For each asset, fetches transaction history since last processed signature
- Processes transactions in chronological order (oldest first)
- Handles pagination and rate limiting automatically
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:
- Indexer calls
Metaplex.findByMint(mintAddress) - SDK derives Metadata PDA from mint address
- Fetches on-chain metadata account from Solana
- Parses metadata structure (name, symbol, uri, creators)
- Optionally fetches off-chain JSON from uri field
- Extracts attributes/traits from metadata
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)
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: {...} };
});βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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
npm install solana-nft-indexergit clone <repo-url>
cd solana-indexer
bun install| 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 |
# 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>// 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);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);| 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 |
MIT License - see LICENSE for details
Built with β€οΈ by the Solana community