Historical Data Queries
Methods for querying archival blockchain data including past transactions, blocks, and account history
Historical Data Queries
Historical data methods allow you to query past blockchain state, including archived transactions, blocks, and account history. These methods are essential for analytics, auditing, and understanding transaction history.
Overview
Historical queries fetch data from the blockchain's archived state. Unlike current state queries, these methods access:
- Past transactions - Detailed information about completed transactions
- Historical blocks - Block data from any point in chain history
- Transaction signatures - Lists of signatures for specific accounts
- Account history - How accounts have changed over time
When to Use Historical Methods
Use these methods when you need:
- Transaction verification - Confirming past payments or operations
- Analytics and reporting - Building dashboards with historical trends
- Auditing - Compliance and record-keeping requirements
- Blockchain exploration - Building explorers and analysis tools
Don't use these methods for:
- Real-time account balances (use Current State Methods)
- Live transaction monitoring (use WebSocket subscriptions)
- Pre-transaction validation (use current state queries)
Core Methods
getTransaction
Retrieves detailed information about a confirmed transaction by its signature.
const { Connection, PublicKey } = require('@solana/web3.js');
const connection = new Connection(
'https://nexus.fortiblox.com/rpc',
{
commitment: 'confirmed',
httpHeaders: {
'X-API-Key': process.env.FORTIBLOX_API_KEY
}
}
);
async function getTransactionDetails(signature) {
const transaction = await connection.getTransaction(signature, {
maxSupportedTransactionVersion: 0,
commitment: 'confirmed'
});
if (!transaction) {
console.log('Transaction not found');
return null;
}
console.log('Transaction Details:', {
slot: transaction.slot,
blockTime: new Date(transaction.blockTime * 1000),
fee: transaction.meta.fee,
success: transaction.meta.err === null,
preBalances: transaction.meta.preBalances,
postBalances: transaction.meta.postBalances,
logMessages: transaction.meta.logMessages
});
return transaction;
}
// Calculate transaction cost
async function getTransactionCost(signature) {
const tx = await connection.getTransaction(signature);
if (!tx || tx.meta.err !== null) {
return null;
}
const fee = tx.meta.fee;
const balanceChanges = tx.meta.postBalances.map((post, i) =>
post - tx.meta.preBalances[i]
);
return {
fee: fee / 1e9, // Convert to SOL
netChange: balanceChanges.map(change => change / 1e9)
};
}
// Example usage
const signature = '5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7';
getTransactionDetails(signature);from solana.rpc.api import Client
from datetime import datetime
import os
client = Client(
"https://nexus.fortiblox.com/rpc",
extra_headers={"X-API-Key": os.environ['FORTIBLOX_API_KEY']}
)
def get_transaction_details(signature):
"""Get detailed transaction information"""
response = client.get_transaction(
signature,
encoding="json",
max_supported_transaction_version=0,
commitment="confirmed"
)
if not response.value:
print("Transaction not found")
return None
tx = response.value
meta = tx.meta
print("Transaction Details:")
print(f" Slot: {tx.slot}")
print(f" Block Time: {datetime.fromtimestamp(tx.block_time)}")
print(f" Fee: {meta.fee} lamports")
print(f" Success: {meta.err is None}")
print(f" Pre-balances: {meta.pre_balances}")
print(f" Post-balances: {meta.post_balances}")
return tx
def get_transaction_cost(signature):
"""Calculate transaction cost and balance changes"""
response = client.get_transaction(signature)
if not response.value or response.value.meta.err:
return None
meta = response.value.meta
fee = meta.fee / 1e9 # Convert to SOL
balance_changes = [
(post - pre) / 1e9
for post, pre in zip(meta.post_balances, meta.pre_balances)
]
return {
"fee": fee,
"net_changes": balance_changes
}
# Example usage
signature = '5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7'
get_transaction_details(signature)curl -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: $FORTIBLOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getTransaction",
"params": [
"5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7",
{
"encoding": "json",
"maxSupportedTransactionVersion": 0,
"commitment": "confirmed"
}
]
}'Use Cases:
- Payment verification and receipts
- Transaction debugging and error analysis
- Building transaction explorers
- Compliance and audit trails
Performance Tips:
- Heavy method: 5 credits - Cache results for known transactions
- Use
jsonParsedencoding for easier data parsing - Include
maxSupportedTransactionVersion: 0for versioned transactions - Consider using
finalizedcommitment for historical data
getSignaturesForAddress
Returns transaction signatures for a given address, ordered from most recent to oldest.
async function getTransactionHistory(address, limit = 10) {
const publicKey = new PublicKey(address);
const signatures = await connection.getSignaturesForAddress(
publicKey,
{ limit },
'confirmed'
);
console.log(`Found ${signatures.length} transactions`);
for (const sig of signatures) {
console.log({
signature: sig.signature,
slot: sig.slot,
blockTime: new Date(sig.blockTime * 1000),
confirmationStatus: sig.confirmationStatus,
err: sig.err
});
}
return signatures;
}
// Paginate through transaction history
async function getAllTransactions(address, maxTransactions = 1000) {
const publicKey = new PublicKey(address);
let allSignatures = [];
let before = null;
const limit = 100; // Max per request
while (allSignatures.length < maxTransactions) {
const options = { limit };
if (before) {
options.before = before;
}
const signatures = await connection.getSignaturesForAddress(
publicKey,
options
);
if (signatures.length === 0) break;
allSignatures = allSignatures.concat(signatures);
before = signatures[signatures.length - 1].signature;
console.log(`Fetched ${allSignatures.length} transactions...`);
// Rate limiting - be nice to the API
await new Promise(resolve => setTimeout(resolve, 100));
}
return allSignatures;
}
// Get only successful transactions
async function getSuccessfulTransactions(address) {
const publicKey = new PublicKey(address);
const signatures = await connection.getSignaturesForAddress(publicKey);
const successful = signatures.filter(sig => sig.err === null);
console.log(`${successful.length} successful out of ${signatures.length} total`);
return successful;
}
// Example usage
getTransactionHistory('9B5XszUGdMaxCZ7uSQhPzdks5ZQSmWxrmzCSvtJ6Ns6g', 20);import time
def get_transaction_history(address, limit=10):
"""Get recent transaction signatures for an address"""
from solana.publickey import PublicKey
pubkey = PublicKey(address)
response = client.get_signatures_for_address(
pubkey,
limit=limit,
commitment="confirmed"
)
signatures = response.value
print(f"Found {len(signatures)} transactions")
for sig in signatures:
print({
"signature": sig.signature,
"slot": sig.slot,
"block_time": datetime.fromtimestamp(sig.block_time) if sig.block_time else None,
"confirmation_status": sig.confirmation_status,
"err": sig.err
})
return signatures
def get_all_transactions(address, max_transactions=1000):
"""Paginate through all transactions for an address"""
from solana.publickey import PublicKey
pubkey = PublicKey(address)
all_signatures = []
before = None
limit = 100
while len(all_signatures) < max_transactions:
options = {"limit": limit}
if before:
options["before"] = before
response = client.get_signatures_for_address(pubkey, **options)
signatures = response.value
if not signatures:
break
all_signatures.extend(signatures)
before = signatures[-1].signature
print(f"Fetched {len(all_signatures)} transactions...")
# Rate limiting
time.sleep(0.1)
return all_signatures
def get_successful_transactions(address):
"""Get only successful transactions"""
from solana.publickey import PublicKey
pubkey = PublicKey(address)
response = client.get_signatures_for_address(pubkey)
signatures = response.value
successful = [sig for sig in signatures if sig.err is None]
print(f"{len(successful)} successful out of {len(signatures)} total")
return successful
# Example usage
get_transaction_history('9B5XszUGdMaxCZ7uSQhPzdks5ZQSmWxrmzCSvtJ6Ns6g', 20)# Get recent signatures
curl -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: $FORTIBLOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getSignaturesForAddress",
"params": [
"9B5XszUGdMaxCZ7uSQhPzdks5ZQSmWxrmzCSvtJ6Ns6g",
{
"limit": 10
}
]
}'
# Paginate using before parameter
curl -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: $FORTIBLOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getSignaturesForAddress",
"params": [
"9B5XszUGdMaxCZ7uSQhPzdks5ZQSmWxrmzCSvtJ6Ns6g",
{
"limit": 100,
"before": "SIGNATURE_FROM_PREVIOUS_REQUEST"
}
]
}'Use Cases:
- Building transaction history displays
- Tracking account activity
- Finding specific transactions
- Calculating total received/sent amounts
Performance Tips:
- Heavy method: 5 credits - Paginate efficiently
- Default limit is 1000, but use smaller limits for faster responses
- Use
before/untilparameters for pagination - Cache results and only fetch new signatures since last check
Pagination Best Practice: Fetch signatures in batches of 100-1000, then use the last signature as the before parameter for the next batch.
getBlock
Returns complete information about a block at a given slot.
async function getBlockDetails(slot) {
const block = await connection.getBlock(slot, {
maxSupportedTransactionVersion: 0,
transactionDetails: 'full',
rewards: true
});
if (!block) {
console.log('Block not found (may not be finalized yet)');
return null;
}
console.log('Block Details:', {
blockhash: block.blockhash,
blockTime: new Date(block.blockTime * 1000),
blockHeight: block.blockHeight,
parentSlot: block.parentSlot,
transactionCount: block.transactions.length,
rewardsCount: block.rewards ? block.rewards.length : 0
});
return block;
}
// Get block with only signatures (faster, cheaper)
async function getBlockSignatures(slot) {
const block = await connection.getBlock(slot, {
transactionDetails: 'signatures',
rewards: false
});
if (!block) return null;
console.log(`Block ${slot} contains ${block.signatures.length} transactions`);
return block.signatures;
}
// Analyze block transactions
async function analyzeBlock(slot) {
const block = await connection.getBlock(slot, {
maxSupportedTransactionVersion: 0
});
if (!block) return null;
let totalFees = 0;
let successCount = 0;
let failureCount = 0;
block.transactions.forEach(tx => {
totalFees += tx.meta.fee;
if (tx.meta.err === null) {
successCount++;
} else {
failureCount++;
}
});
return {
slot,
totalTransactions: block.transactions.length,
successCount,
failureCount,
totalFees: totalFees / 1e9, // Convert to SOL
avgFeePerTx: (totalFees / block.transactions.length) / 1e9
};
}
// Example: Get current slot and its block
const currentSlot = await connection.getSlot();
getBlockDetails(currentSlot);def get_block_details(slot):
"""Get complete block information"""
response = client.get_block(
slot,
encoding="json",
max_supported_transaction_version=0,
transaction_details="full",
rewards=True
)
if not response.value:
print("Block not found (may not be finalized yet)")
return None
block = response.value
print("Block Details:")
print(f" Blockhash: {block.blockhash}")
print(f" Block Time: {datetime.fromtimestamp(block.block_time)}")
print(f" Block Height: {block.block_height}")
print(f" Parent Slot: {block.parent_slot}")
print(f" Transaction Count: {len(block.transactions)}")
return block
def get_block_signatures(slot):
"""Get only transaction signatures from a block"""
response = client.get_block(
slot,
transaction_details="signatures",
rewards=False
)
if not response.value:
return None
signatures = response.value.signatures
print(f"Block {slot} contains {len(signatures)} transactions")
return signatures
def analyze_block(slot):
"""Analyze block transactions"""
response = client.get_block(
slot,
max_supported_transaction_version=0
)
if not response.value:
return None
block = response.value
total_fees = 0
success_count = 0
failure_count = 0
for tx in block.transactions:
total_fees += tx.meta.fee
if tx.meta.err is None:
success_count += 1
else:
failure_count += 1
return {
"slot": slot,
"total_transactions": len(block.transactions),
"success_count": success_count,
"failure_count": failure_count,
"total_fees": total_fees / 1e9,
"avg_fee_per_tx": (total_fees / len(block.transactions)) / 1e9
}
# Example usage
current_slot = client.get_slot().value
get_block_details(current_slot)# Get full block details
curl -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: $FORTIBLOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlock",
"params": [
123456789,
{
"encoding": "json",
"maxSupportedTransactionVersion": 0,
"transactionDetails": "full",
"rewards": true
}
]
}'
# Get only signatures (faster)
curl -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: $FORTIBLOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlock",
"params": [
123456789,
{
"transactionDetails": "signatures",
"rewards": false
}
]
}'Use Cases:
- Block explorers and analytics
- Network performance analysis
- Transaction batch processing
- Validator reward tracking
Performance Tips:
- Heavy method: 5 credits - Use carefully
- Set
transactionDetails: 'signatures'if you don't need full transaction data - Set
rewards: falseif you don't need reward data - Only works with finalized or confirmed slots
getBlockTime
Returns the estimated production time of a block as a Unix timestamp.
async function getBlockTime(slot) {
const blockTime = await connection.getBlockTime(slot);
if (blockTime === null) {
console.log('Block time not available for this slot');
return null;
}
const date = new Date(blockTime * 1000);
console.log(`Block ${slot} produced at: ${date.toISOString()}`);
return blockTime;
}
// Calculate time between blocks
async function getBlockDuration(startSlot, endSlot) {
const startTime = await connection.getBlockTime(startSlot);
const endTime = await connection.getBlockTime(endSlot);
if (!startTime || !endTime) {
return null;
}
const duration = endTime - startTime;
const avgTimePerSlot = duration / (endSlot - startSlot);
console.log(`Duration: ${duration}s over ${endSlot - startSlot} slots`);
console.log(`Average: ${avgTimePerSlot.toFixed(2)}s per slot`);
return { duration, avgTimePerSlot };
}
// Example usage
const slot = 123456789;
getBlockTime(slot);def get_block_time(slot):
"""Get block production time"""
response = client.get_block_time(slot)
if response.value is None:
print("Block time not available for this slot")
return None
block_time = response.value
date = datetime.fromtimestamp(block_time)
print(f"Block {slot} produced at: {date.isoformat()}")
return block_time
def get_block_duration(start_slot, end_slot):
"""Calculate time between blocks"""
start_time = client.get_block_time(start_slot).value
end_time = client.get_block_time(end_slot).value
if not start_time or not end_time:
return None
duration = end_time - start_time
avg_time_per_slot = duration / (end_slot - start_slot)
print(f"Duration: {duration}s over {end_slot - start_slot} slots")
print(f"Average: {avg_time_per_slot:.2f}s per slot")
return {
"duration": duration,
"avg_time_per_slot": avg_time_per_slot
}
# Example usage
slot = 123456789
get_block_time(slot)curl -X POST https://nexus.fortiblox.com/rpc \
-H "X-API-Key: $FORTIBLOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlockTime",
"params": [123456789]
}'Use Cases:
- Converting slots to timestamps
- Time-based analytics
- Transaction timing analysis
- Block production monitoring
Performance Tips:
- Lightweight method (1 credit)
- Much faster than fetching full block
- Returns null for slots without confirmed blocks
Commitment Levels for Historical Data
When querying historical data, commitment level affects which data is accessible:
confirmed
- Best for: Recent historical queries (last few minutes)
- Availability: Supermajority confirmed transactions
- Speed: Fast
- Use case: Recent transaction lookups, near-real-time analytics
const tx = await connection.getTransaction(signature, {
commitment: 'confirmed'
});finalized
- Best for: Archival queries, compliance records
- Availability: Permanently confirmed transactions (32+ blocks old)
- Speed: Slightly slower
- Use case: Audit trails, long-term analytics, block explorers
const tx = await connection.getTransaction(signature, {
commitment: 'finalized'
});Recommendation: Use finalized for historical queries to ensure data permanence. Only use confirmed for very recent transactions where speed matters.
Querying Strategies
1. Efficient Pagination
async function fetchAllTransactions(address) {
const publicKey = new PublicKey(address);
const allTransactions = [];
let before = null;
const BATCH_SIZE = 100;
while (true) {
// Fetch batch of signatures
const options = { limit: BATCH_SIZE };
if (before) options.before = before;
const signatures = await connection.getSignaturesForAddress(
publicKey,
options
);
if (signatures.length === 0) break;
// Fetch full transaction details in parallel
const txPromises = signatures.map(sig =>
connection.getTransaction(sig.signature, {
maxSupportedTransactionVersion: 0
})
);
const transactions = await Promise.all(txPromises);
allTransactions.push(...transactions.filter(tx => tx !== null));
before = signatures[signatures.length - 1].signature;
console.log(`Fetched ${allTransactions.length} transactions...`);
// Rate limiting
await new Promise(resolve => setTimeout(resolve, 100));
}
return allTransactions;
}2. Time-Range Queries
async function getTransactionsInTimeRange(address, startTime, endTime) {
const publicKey = new PublicKey(address);
const transactions = [];
let before = null;
while (true) {
const options = { limit: 100 };
if (before) options.before = before;
const signatures = await connection.getSignaturesForAddress(
publicKey,
options
);
if (signatures.length === 0) break;
// Filter by time range
const inRange = signatures.filter(sig =>
sig.blockTime >= startTime && sig.blockTime <= endTime
);
if (inRange.length > 0) {
transactions.push(...inRange);
}
// Stop if we've passed the start time
if (signatures[signatures.length - 1].blockTime < startTime) {
break;
}
before = signatures[signatures.length - 1].signature;
}
return transactions;
}
// Get last 24 hours of transactions
const now = Math.floor(Date.now() / 1000);
const yesterday = now - 86400;
const recentTxs = await getTransactionsInTimeRange(
address,
yesterday,
now
);3. Parallel Batch Processing
async function processTransactionsBatch(signatures, batchSize = 10) {
const results = [];
for (let i = 0; i < signatures.length; i += batchSize) {
const batch = signatures.slice(i, i + batchSize);
const txPromises = batch.map(sig =>
connection.getTransaction(sig.signature, {
maxSupportedTransactionVersion: 0
}).catch(err => {
console.error(`Failed to fetch ${sig.signature}:`, err);
return null;
})
);
const batchResults = await Promise.all(txPromises);
results.push(...batchResults.filter(tx => tx !== null));
console.log(`Processed ${results.length}/${signatures.length}`);
// Rate limiting between batches
await new Promise(resolve => setTimeout(resolve, 200));
}
return results;
}Common Pitfalls
1. Not Handling Missing Blocks
// ❌ BAD - Assumes block always exists
const block = await connection.getBlock(slot);
const txCount = block.transactions.length;
// ✅ GOOD - Handles missing blocks
const block = await connection.getBlock(slot);
if (!block) {
console.log('Block not available (skipped or not finalized)');
return;
}
const txCount = block.transactions.length;2. Excessive Parallel Requests
// ❌ BAD - Floods API with 1000 concurrent requests
const signatures = await connection.getSignaturesForAddress(pubkey, { limit: 1000 });
const transactions = await Promise.all(
signatures.map(sig => connection.getTransaction(sig.signature))
);
// ✅ GOOD - Batches requests
async function fetchInBatches(signatures, batchSize = 10) {
const results = [];
for (let i = 0; i < signatures.length; i += batchSize) {
const batch = signatures.slice(i, i + batchSize);
const batchTxs = await Promise.all(
batch.map(sig => connection.getTransaction(sig.signature))
);
results.push(...batchTxs);
await new Promise(resolve => setTimeout(resolve, 100));
}
return results;
}3. Not Using Appropriate Transaction Details
// ❌ BAD - Fetches full data when only signatures needed
const block = await connection.getBlock(slot, {
transactionDetails: 'full' // Returns megabytes of data
});
const sigCount = block.transactions.length;
// ✅ GOOD - Fetches only what's needed
const block = await connection.getBlock(slot, {
transactionDetails: 'signatures' // Returns only signatures
});
const sigCount = block.signatures.length;Real-World Use Cases
Payment Verification System
async function verifyPayment(recipientAddress, expectedAmount, timeWindow = 300) {
const recipient = new PublicKey(recipientAddress);
const cutoffTime = Math.floor(Date.now() / 1000) - timeWindow;
// Get recent signatures
const signatures = await connection.getSignaturesForAddress(recipient, {
limit: 100
});
// Filter to recent transactions
const recentSigs = signatures.filter(sig =>
sig.blockTime >= cutoffTime && sig.err === null
);
// Check each transaction for the expected amount
for (const sig of recentSigs) {
const tx = await connection.getTransaction(sig.signature);
if (!tx) continue;
// Check balance changes
const balanceChanges = tx.meta.postBalances.map((post, i) =>
post - tx.meta.preBalances[i]
);
// Find if recipient received expected amount
const recipientIndex = tx.transaction.message.accountKeys.findIndex(
key => key.equals(recipient)
);
if (recipientIndex !== -1 && balanceChanges[recipientIndex] >= expectedAmount) {
return {
verified: true,
signature: sig.signature,
amount: balanceChanges[recipientIndex],
timestamp: sig.blockTime
};
}
}
return { verified: false };
}Transaction History Export
async function exportTransactionHistory(address, outputFormat = 'csv') {
const publicKey = new PublicKey(address);
const signatures = await getAllTransactions(address);
console.log(`Exporting ${signatures.length} transactions...`);
// Fetch full details in batches
const transactions = await processTransactionsBatch(signatures, 10);
// Format for export
const records = transactions.map(tx => ({
signature: tx.transaction.signatures[0],
slot: tx.slot,
timestamp: new Date(tx.blockTime * 1000).toISOString(),
fee: tx.meta.fee / 1e9,
success: tx.meta.err === null,
preBalance: tx.meta.preBalances[0] / 1e9,
postBalance: tx.meta.postBalances[0] / 1e9,
netChange: (tx.meta.postBalances[0] - tx.meta.preBalances[0]) / 1e9
}));
if (outputFormat === 'csv') {
return convertToCSV(records);
} else {
return JSON.stringify(records, null, 2);
}
}Analytics Dashboard Data
async function getAccountAnalytics(address, days = 30) {
const publicKey = new PublicKey(address);
const cutoffTime = Math.floor(Date.now() / 1000) - (days * 86400);
const signatures = await connection.getSignaturesForAddress(publicKey, {
limit: 1000
});
const recentSigs = signatures.filter(sig => sig.blockTime >= cutoffTime);
let totalReceived = 0;
let totalSent = 0;
let totalFees = 0;
let successCount = 0;
let failureCount = 0;
for (const sig of recentSigs) {
if (sig.err) {
failureCount++;
continue;
}
successCount++;
const tx = await connection.getTransaction(sig.signature);
if (!tx) continue;
totalFees += tx.meta.fee;
// Calculate net change for this address
const accountIndex = tx.transaction.message.accountKeys.findIndex(
key => key.equals(publicKey)
);
if (accountIndex !== -1) {
const change = tx.meta.postBalances[accountIndex] - tx.meta.preBalances[accountIndex];
if (change > 0) {
totalReceived += change;
} else {
totalSent += Math.abs(change);
}
}
}
return {
period: `${days} days`,
totalTransactions: recentSigs.length,
successCount,
failureCount,
totalReceived: totalReceived / 1e9,
totalSent: totalSent / 1e9,
totalFees: totalFees / 1e9,
avgFee: (totalFees / successCount) / 1e9
};
}Performance Optimization
Rate Limiting Best Practices
class RateLimitedClient {
constructor(connection, requestsPerSecond = 10) {
this.connection = connection;
this.minDelay = 1000 / requestsPerSecond;
this.lastRequest = 0;
}
async throttle() {
const now = Date.now();
const timeSinceLastRequest = now - this.lastRequest;
if (timeSinceLastRequest < this.minDelay) {
await new Promise(resolve =>
setTimeout(resolve, this.minDelay - timeSinceLastRequest)
);
}
this.lastRequest = Date.now();
}
async getTransaction(signature, options) {
await this.throttle();
return this.connection.getTransaction(signature, options);
}
}
const rateLimitedClient = new RateLimitedClient(connection, 10);Caching Strategy
class TransactionCache {
constructor(ttl = 3600000) {
this.cache = new Map();
this.ttl = ttl;
}
async getTransaction(connection, signature) {
const cached = this.cache.get(signature);
// Historical transactions never change, cache indefinitely
if (cached) {
return cached;
}
const tx = await connection.getTransaction(signature);
if (tx) {
this.cache.set(signature, tx);
}
return tx;
}
clear() {
this.cache.clear();
}
}
const txCache = new TransactionCache();