Solana Adapters
This guide covers how to build adapters for Solana-based protocols using Subsquid's Solana processor.
Overview
Solana adapters use Subsquid's data processing framework to efficiently index on-chain data from the Solana blockchain. The adapter scans blocks, filters instructions, and extracts relevant data for downstream processing.
Setting up the Data Source
Importing Dependencies
First, import the necessary components:
// Import core types and classes from Subsquid's Solana processor package
import { DataSourceBuilder, SolanaRpcClient } from '@subsquid/solana-stream';
// Import instruction definitions for the programs you want to listen to
import * as tokenProgram from './abi/TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
// Import shared types and config validation from Absinthe's common package
import { validateEnv } from './utils/validateEnv';
import { AbsintheApiClient, HOURS_TO_MS } from '@absinthe/common';
import { SplTransfersProcessor } from './BatchProcessor';
Environment Configuration
Load and validate your environment configuration:
// Load and validate environment variables (including ABS_CONFIG)
const env = validateEnv();
const { splTransfersProtocol, baseConfig } = env;
Building the Processor
Create the Solana data source processor:
export const processor = new DataSourceBuilder()
// Set the Subsquid gateway URL for this chain (for fast block data)
.setGateway(splTransfersProtocol.gatewayUrl)
// Set the fallback RPC endpoint (for on-demand data, e.g., account states)
.setRpc(
splTransfersProtocol.rpcUrl == null
? undefined
: {
client: new SolanaRpcClient({
url: splTransfersProtocol.rpcUrl,
// rateLimit: 100 // requests per sec
}),
strideConcurrency: 10,
},
)
// Define the block range to scan, based on config
.setBlockRange({ from: splTransfersProtocol.fromBlock })
// Specify which fields to extract for blocks, transactions, instructions, and token balances
.setFields({
block: {
timestamp: true,
},
transaction: {
signatures: true,
},
instruction: {
programId: true,
accounts: true,
data: true,
},
tokenBalance: {
preAmount: true,
postAmount: true,
preOwner: true,
postOwner: true,
},
})
// Listen for specific instructions from the Token program
.addInstruction({
where: {
programId: [tokenProgram.programId], // executed by the SPL Token program
d1: [tokenProgram.instructions.transfer.d1], // first 8 bytes equal to transfer discriminator
isCommitted: true, // were successfully committed
},
include: {
innerInstructions: true, // inner instructions
transaction: true, // transaction that executed the given instruction
transactionTokenBalances: true, // all token balance records of executed transaction
},
})
.build();
Setting up the API Client
Configure the Absinthe API client for sending processed data:
// Instantiate the Absinthe API client for sending processed data
const apiClient = new AbsintheApiClient({
baseUrl: baseConfig.absintheApiUrl,
apiKey: baseConfig.absintheApiKey,
});
Chain Configuration
Prepare chain metadata for downstream use:
const chainConfig = {
chainArch: splTransfersProtocol.chainArch,
networkId: splTransfersProtocol.chainId,
chainShortName: splTransfersProtocol.chainShortName,
chainName: splTransfersProtocol.chainName,
};
Running the Processor
Finally, instantiate and run your custom processor:
// Calculate window duration for balance flushing
const WINDOW_DURATION_MS = baseConfig.balanceFlushIntervalHours * HOURS_TO_MS;
// Instantiate your custom processor class
const splTransfersProcessor = new SplTransfersProcessor(
splTransfersProtocol,
WINDOW_DURATION_MS,
apiClient,
env.baseConfig,
chainConfig,
);
// Start the processor
splTransfersProcessor.run();
Key Components
DataSourceBuilder
The core engine that scans blocks, filters instructions, and extracts data from the Solana blockchain.
SolanaRpcClient
Provides fallback RPC functionality for on-demand data retrieval, such as account states.
Field Selection
Configure which fields to extract from:
- Blocks: timestamp and other block header fields
- Transactions: signatures and transaction metadata
- Instructions: program ID, accounts, and instruction data
- Token Balances: pre/post amounts and owners
Instruction Filtering
Use discriminators to filter for specific instruction types from target programs. This ensures you only process relevant on-chain activities.
Understanding the SplTransfersProcessor Implementation
The SplTransfersProcessor
class is the core business logic that handles the actual processing of Solana SPL token transfer data. Here's what it does:
Class Overview
The processor is designed to monitor SPL token transfers on Solana and convert them into standardized transaction records for the Absinthe platform.
Key Responsibilities
1. Batch Processing Architecture- Uses Subsquid's batch processor to efficiently handle blocks of data
- Processes multiple blocks in batches to optimize performance
- Maintains protocol state across batch processing cycles
- Monitors instructions from the SPL Token program
- Specifically filters for
transfer
instruction discriminators - Extracts token balance changes from transaction token balance records
- Only processes transfers for tracked tokens (configured in
TRACKED_TOKENS
) - Currently tracks USDC(Mint) transfers (
EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
)
- Converts raw Solana token balance changes into standardized transaction records
- Calculates display amounts using token decimals (e.g., USDC has 6 decimals)
- Extracts relevant metadata: transaction hash, block number, timestamp, user address
5. Data Flow Processing The processor follows this flow for each batch: Initialize protocol states for tracking For each block in the batch: Scan all instructions Filter for SPL token transfer instructions Extract token balance changes Convert to standardized transaction format Send processed transactions to Absinthe API
6. State Management- Maintains protocol state per contract address
- Accumulates transactions within each batch
- Handles balance windows for time-weighted calculations
- Comprehensive error handling for batch processing failures
- Detailed logging for debugging and monitoring
- Graceful failure recovery to prevent data loss
Token Balance Processing Logic
The processor examines token balance records from each transaction to identify:
- Net changes: Difference between pre and post amounts
- Token metadata: Mint address, decimals, owner
- Value calculations: Converting raw amounts to human-readable values
Only processes balances where the mint address matches tracked tokens and there's an actual balance change (pre ≠ post).
Integration with Absinthe Platform
The processed transactions are formatted according to Absinthe's schema and include:
- Event classification (transaction type)
- User identification (token owner)
- Value calculations in USD
- Gas and fee information
- Blockchain metadata (block, transaction hash)
This standardized format allows the Absinthe platform to consistently handle transaction data across different blockchain protocols.