FortiBlox LogoFortiBlox Docs
NexusRPC NodesRPC Guides

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 jsonParsed encoding for easier data parsing
  • Include maxSupportedTransactionVersion: 0 for versioned transactions
  • Consider using finalized commitment 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/until parameters 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: false if 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();

Additional Resources