Docs

suix_queryTransactionBlocks - Query Tran...

Query and filter Sui transactions using suix_queryTransactionBlocks RPC method. Search by sender, recipient, transaction kind, time range with pagination support using Dwellir's high-performance Sui infrastructure.

Queries and filters transaction blocks on the Sui blockchain with support for various search criteria including sender, recipient, transaction type, and time-based filtering with pagination support.

Overview

The suix_queryTransactionBlocks method is a powerful transaction search and filtering tool that enables applications to find specific transactions based on multiple criteria. Unlike simple transaction lookups by digest, this method allows complex queries to build transaction histories, monitor specific addresses, track particular transaction types, and perform analytical queries. It's essential for wallets, explorers, analytics tools, and monitoring systems.

Request Parameters

Request
filterobject

Query criteria to filter transactions

optionsobject

Options for controlling response content

cursorstring

Pagination cursor from previous response

limitnumber

Maximum number of transactions to return (default: 50, max: 100)

descendingOrderboolean

Return results in descending order by sequence number (default: false)

Response Body

Response
dataarray

Array of transaction blocks matching the query

nextCursorstring

Cursor for next page (null if no more pages)

hasNextPageboolean

Whether more pages are available

Code Examples

Bash
# Query transactions from a specific address
curl -X POST https://api-sui-mainnet-full.n.dwellir.com/YOUR_API_KEY \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "suix_queryTransactionBlocks",
    "params": [
      {
        "FromAddress": "0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f"
      },
      {
        "showInput": true,
        "showEffects": true,
        "showEvents": true
      },
      null,
      20,
      true
    ],
    "id": 1
  }'

# Query transactions between two addresses
curl -X POST https://api-sui-mainnet-full.n.dwellir.com/YOUR_API_KEY \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "suix_queryTransactionBlocks",
    "params": [
      {
        "FromAndToAddress": {
          "from": "0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f",
          "to": "0x1a2b3c4d5e6f789012345678901234567890123456789012345678901234567890"
        }
      },
      {
        "showEffects": true,
        "showBalanceChanges": true
      },
      null,
      10,
      true
    ],
    "id": 1
  }'

# Query Move function calls
curl -X POST https://api-sui-mainnet-full.n.dwellir.com/YOUR_API_KEY \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "suix_queryTransactionBlocks",
    "params": [
      {
        "MoveFunction": {
          "package": "0x2",
          "module": "coin",
          "function": "split"
        }
      },
      {
        "showInput": true,
        "showEffects": true
      },
      null,
      50,
      true
    ],
    "id": 1
  }'
JavaScript
import { SuiClient } from '@mysten/sui.js/client';

const client = new SuiClient({ 
  url: 'https://api-sui-mainnet-full.n.dwellir.com/YOUR_API_KEY' 
});

// Query transactions from specific address
async function getTransactionsFromAddress(address, limit = 50) {
  const result = await client.queryTransactionBlocks({
    filter: { FromAddress: address },
    options: {
      showInput: true,
      showEffects: true,
      showEvents: true,
      showBalanceChanges: true
    },
    limit: limit,
    order: 'descending'
  });
  
  console.log(`Found ${result.data.length} transactions`);
  return result;
}

// Get transaction history with pagination
async function getCompleteTransactionHistory(address, maxPages = 10) {
  let allTransactions = [];
  let cursor = null;
  let pageCount = 0;
  
  while (pageCount < maxPages) {
    const result = await client.queryTransactionBlocks({
      filter: { FromAddress: address },
      options: {
        showEffects: true,
        showBalanceChanges: true
      },
      cursor: cursor,
      limit: 50,
      order: 'descending'
    });
    
    allTransactions.push(...result.data);
    
    console.log(`Page ${pageCount + 1}: ${result.data.length} transactions`);
    
    if (!result.hasNextPage) break;
    
    cursor = result.nextCursor;
    pageCount++;
  }
  
  return allTransactions;
}

// Query transactions between two addresses
async function getTransactionsBetweenAddresses(fromAddress, toAddress) {
  const result = await client.queryTransactionBlocks({
    filter: {
      FromAndToAddress: {
        from: fromAddress,
        to: toAddress
      }
    },
    options: {
      showInput: true,
      showEffects: true,
      showBalanceChanges: true
    },
    limit: 100,
    order: 'descending'
  });
  
  return result.data;
}

// Query specific Move function calls
async function getMoveCallTransactions(packageId, module, functionName) {
  const result = await client.queryTransactionBlocks({
    filter: {
      MoveFunction: {
        package: packageId,
        module: module,
        function: functionName
      }
    },
    options: {
      showInput: true,
      showEvents: true
    },
    limit: 50,
    order: 'descending'
  });
  
  return result.data;
}

// Advanced transaction analysis
async function analyzeAddressActivity(address, days = 30) {
  const transactions = await getCompleteTransactionHistory(address, 20);
  
  const analysis = {
    totalTransactions: transactions.length,
    successfulTransactions: 0,
    failedTransactions: 0,
    totalGasUsed: 0,
    uniqueRecipients: new Set(),
    transactionTypes: {},
    dailyActivity: {},
    averageGasPerTransaction: 0
  };
  
  const now = Date.now();
  const cutoffTime = now - (days * 24 * 60 * 60 * 1000);
  
  transactions.forEach(txn => {
    const timestamp = parseInt(txn.timestampMs || '0');
    
    // Skip transactions outside time window
    if (timestamp < cutoffTime) return;
    
    // Status analysis
    if (txn.effects?.status?.status === 'success') {
      analysis.successfulTransactions++;
    } else {
      analysis.failedTransactions++;
    }
    
    // Gas analysis
    if (txn.effects?.gasUsed) {
      const gasUsed = parseInt(txn.effects.gasUsed.computationCost) + 
                     parseInt(txn.effects.gasUsed.storageCost) - 
                     parseInt(txn.effects.gasUsed.storageRebate);
      analysis.totalGasUsed += gasUsed;
    }
    
    // Recipients analysis
    txn.balanceChanges?.forEach(change => {
      if (change.owner?.AddressOwner && change.owner.AddressOwner !== address) {
        analysis.uniqueRecipients.add(change.owner.AddressOwner);
      }
    });
    
    // Transaction type analysis
    const txType = txn.transaction?.data?.transaction?.kind || 'Unknown';
    analysis.transactionTypes[txType] = (analysis.transactionTypes[txType] || 0) + 1;
    
    // Daily activity
    const day = new Date(timestamp).toISOString().split('T')[0];
    analysis.dailyActivity[day] = (analysis.dailyActivity[day] || 0) + 1;
  });
  
  analysis.averageGasPerTransaction = analysis.totalGasUsed / 
    Math.max(1, analysis.successfulTransactions + analysis.failedTransactions);
  analysis.uniqueRecipients = analysis.uniqueRecipients.size;
  
  return analysis;
}

// Monitor recent transactions
async function monitorRecentTransactions(address, callback, intervalMs = 10000) {
  let lastSeenDigest = null;
  
  console.log(`Starting transaction monitoring for ${address}`);
  
  const checkForNewTransactions = async () => {
    try {
      const result = await client.queryTransactionBlocks({
        filter: { FromAddress: address },
        options: {
          showEffects: true,
          showBalanceChanges: true
        },
        limit: 10,
        order: 'descending'
      });
      
      if (result.data.length === 0) return;
      
      const latestDigest = result.data[0].digest;
      
      if (lastSeenDigest === null) {
        lastSeenDigest = latestDigest;
        return;
      }
      
      if (latestDigest !== lastSeenDigest) {
        // Find new transactions
        const newTransactions = [];
        for (const txn of result.data) {
          if (txn.digest === lastSeenDigest) break;
          newTransactions.push(txn);
        }
        
        if (newTransactions.length > 0) {
          callback(newTransactions);
          lastSeenDigest = latestDigest;
        }
      }
    } catch (error) {
      console.error('Error monitoring transactions:', error);
    }
  };
  
  // Initial check
  await checkForNewTransactions();
  
  // Set up interval
  const intervalId = setInterval(checkForNewTransactions, intervalMs);
  
  return () => {
    clearInterval(intervalId);
    console.log('Transaction monitoring stopped');
  };
}

// Usage examples
const address = '0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f';

// Get recent transactions
const recentTransactions = await getTransactionsFromAddress(address, 20);
console.log('Recent transactions:', recentTransactions.data.length);

// Analyze activity
const activity = await analyzeAddressActivity(address, 7); // Last 7 days
console.log('Address Activity Analysis:', activity);

// Monitor for new transactions
const stopMonitoring = await monitorRecentTransactions(address, (newTxns) => {
  console.log(`📩 ${newTxns.length} new transactions detected`);
  newTxns.forEach(txn => {
    console.log(`- ${txn.digest}: ${txn.effects?.status?.status || 'unknown'}`);
  });
});

// Stop monitoring after 5 minutes
setTimeout(stopMonitoring, 5 * 60 * 1000);
Python
import requests
import json
from datetime import datetime, timedelta
from typing import List, Dict, Any, Optional, Callable
from dataclasses import dataclass
from collections import defaultdict
import time

@dataclass
class TransactionFilter:
    from_address: Optional[str] = None
    to_address: Optional[str] = None
    input_object: Optional[str] = None
    changed_object: Optional[str] = None
    from_and_to: Optional[Dict[str, str]] = None
    transaction_kind: Optional[str] = None
    move_function: Optional[Dict[str, str]] = None

class SuiTransactionQueryClient:
    def __init__(self, rpc_url: str):
        self.rpc_url = rpc_url
    
    def _build_filter_dict(self, filter_obj: TransactionFilter) -> Dict[str, Any]:
        """Convert TransactionFilter to RPC filter format"""
        filter_dict = {}
        
        if filter_obj.from_address:
            filter_dict["FromAddress"] = filter_obj.from_address
        elif filter_obj.to_address:
            filter_dict["ToAddress"] = filter_obj.to_address
        elif filter_obj.input_object:
            filter_dict["InputObject"] = filter_obj.input_object
        elif filter_obj.changed_object:
            filter_dict["ChangedObject"] = filter_obj.changed_object
        elif filter_obj.from_and_to:
            filter_dict["FromAndToAddress"] = {
                "from": filter_obj.from_and_to["from"],
                "to": filter_obj.from_and_to["to"]
            }
        elif filter_obj.transaction_kind:
            filter_dict["TransactionKind"] = filter_obj.transaction_kind
        elif filter_obj.move_function:
            filter_dict["MoveFunction"] = filter_obj.move_function
        
        return filter_dict
    
    def query_transactions(
        self,
        transaction_filter: TransactionFilter,
        cursor: Optional[str] = None,
        limit: int = 50,
        descending: bool = True,
        show_all: bool = True
    ) -> Dict[str, Any]:
        """Query transactions with filters"""
        
        # Build options
        options = {}
        if show_all:
            options = {
                "showInput": True,
                "showEffects": True,
                "showEvents": True,
                "showObjectChanges": True,
                "showBalanceChanges": True
            }
        
        # Build filter
        filter_dict = self._build_filter_dict(transaction_filter)
        
        payload = {
            "jsonrpc": "2.0",
            "method": "suix_queryTransactionBlocks",
            "params": [
                filter_dict,
                options,
                cursor,
                limit,
                descending
            ],
            "id": 1
        }
        
        response = requests.post(
            self.rpc_url,
            headers={'Content-Type': 'application/json'},
            data=json.dumps(payload)
        )
        
        result = response.json()
        if 'error' in result:
            raise Exception(f"RPC Error: {result['error']}")
        
        return result['result']
    
    def get_all_transactions_paginated(
        self,
        transaction_filter: TransactionFilter,
        max_pages: int = 100,
        limit: int = 50
    ) -> List[Dict[str, Any]]:
        """Get all matching transactions using pagination"""
        
        all_transactions = []
        cursor = None
        page = 0
        
        while page < max_pages:
            result = self.query_transactions(
                transaction_filter,
                cursor=cursor,
                limit=limit,
                descending=True
            )
            
            all_transactions.extend(result['data'])
            print(f"Page {page + 1}: {len(result['data'])} transactions")
            
            if not result['hasNextPage']:
                break
            
            cursor = result['nextCursor']
            page += 1
        
        print(f"Total transactions retrieved: {len(all_transactions)}")
        return all_transactions
    
    def analyze_address_activity(
        self, 
        address: str, 
        days: int = 30
    ) -> Dict[str, Any]:
        """Comprehensive address activity analysis"""
        
        # Get transactions from the address
        filter_obj = TransactionFilter(from_address=address)
        transactions = self.get_all_transactions_paginated(
            filter_obj, 
            max_pages=20
        )
        
        # Filter by time range
        cutoff_time = datetime.now() - timedelta(days=days)
        cutoff_ms = int(cutoff_time.timestamp() * 1000)
        
        recent_transactions = [
            txn for txn in transactions
            if int(txn.get('timestampMs', '0')) > cutoff_ms
        ]
        
        analysis = {
            'address': address,
            'analysis_period_days': days,
            'total_transactions': len(recent_transactions),
            'successful_transactions': 0,
            'failed_transactions': 0,
            'total_gas_used': 0,
            'unique_recipients': set(),
            'transaction_types': defaultdict(int),
            'daily_activity': defaultdict(int),
            'hourly_activity': defaultdict(int),
            'gas_statistics': {
                'min': float('inf'),
                'max': 0,
                'avg': 0,
                'total': 0
            },
            'event_types': defaultdict(int),
            'balance_changes': defaultdict(float)
        }
        
        total_gas = 0
        gas_transactions = 0
        
        for txn in recent_transactions:
            # Status analysis
            status = txn.get('effects', {}).get('status', {}).get('status')
            if status == 'success':
                analysis['successful_transactions'] += 1
            else:
                analysis['failed_transactions'] += 1
            
            # Gas analysis
            gas_used = txn.get('effects', {}).get('gasUsed', {})
            if gas_used:
                computation = int(gas_used.get('computationCost', 0))
                storage = int(gas_used.get('storageCost', 0))
                rebate = int(gas_used.get('storageRebate', 0))
                
                total_gas_cost = computation + storage - rebate
                total_gas += total_gas_cost
                gas_transactions += 1
                
                # Update gas statistics
                gas_stats = analysis['gas_statistics']
                gas_stats['min'] = min(gas_stats['min'], total_gas_cost)
                gas_stats['max'] = max(gas_stats['max'], total_gas_cost)
                gas_stats['total'] += total_gas_cost
            
            # Recipients analysis
            balance_changes = txn.get('balanceChanges', [])
            for change in balance_changes:
                owner = change.get('owner', {})
                address_owner = owner.get('AddressOwner')
                
                if address_owner and address_owner != address:
                    analysis['unique_recipients'].add(address_owner)
                
                # Track balance changes by coin type
                coin_type = change.get('coinType', 'Unknown')
                amount = float(change.get('amount', 0))
                analysis['balance_changes'][coin_type] += amount
            
            # Transaction type analysis
            tx_data = txn.get('transaction', {}).get('data', {})
            tx_kind = tx_data.get('transaction', {}).get('kind', 'Unknown')
            analysis['transaction_types'][tx_kind] += 1
            
            # Event analysis
            events = txn.get('events', [])
            for event in events:
                event_type = event.get('type', 'Unknown')
                analysis['event_types'][event_type] += 1
            
            # Time-based analysis
            timestamp_ms = int(txn.get('timestampMs', 0))
            if timestamp_ms > 0:
                dt = datetime.fromtimestamp(timestamp_ms / 1000)
                day_key = dt.strftime('%Y-%m-%d')
                hour_key = dt.strftime('%H')
                
                analysis['daily_activity'][day_key] += 1
                analysis['hourly_activity'][hour_key] += 1
        
        # Finalize gas statistics
        if gas_transactions > 0:
            analysis['gas_statistics']['avg'] = total_gas / gas_transactions
        else:
            analysis['gas_statistics']['min'] = 0
        
        # Convert sets to counts
        analysis['unique_recipients'] = len(analysis['unique_recipients'])
        analysis['total_gas_used'] = total_gas
        
        # Convert defaultdicts to regular dicts for JSON serialization
        analysis['transaction_types'] = dict(analysis['transaction_types'])
        analysis['daily_activity'] = dict(analysis['daily_activity'])
        analysis['hourly_activity'] = dict(analysis['hourly_activity'])
        analysis['event_types'] = dict(analysis['event_types'])
        analysis['balance_changes'] = dict(analysis['balance_changes'])
        
        return analysis
    
    def find_transactions_between_addresses(
        self,
        from_address: str,
        to_address: str,
        limit: int = 100
    ) -> List[Dict[str, Any]]:
        """Find all transactions between two specific addresses"""
        
        filter_obj = TransactionFilter(
            from_and_to={"from": from_address, "to": to_address}
        )
        
        result = self.query_transactions(
            filter_obj,
            limit=limit,
            descending=True,
            show_all=True
        )
        
        return result['data']
    
    def track_object_interactions(
        self,
        object_id: str,
        interaction_type: str = "changed"
    ) -> List[Dict[str, Any]]:
        """Track all transactions that interacted with a specific object"""
        
        if interaction_type == "input":
            filter_obj = TransactionFilter(input_object=object_id)
        else:  # changed
            filter_obj = TransactionFilter(changed_object=object_id)
        
        transactions = self.get_all_transactions_paginated(filter_obj, max_pages=10)
        
        # Analyze interaction patterns
        interaction_analysis = {
            'object_id': object_id,
            'interaction_type': interaction_type,
            'total_interactions': len(transactions),
            'unique_addresses': set(),
            'interaction_timeline': [],
            'transaction_types': defaultdict(int)
        }
        
        for txn in transactions:
            # Track unique addresses
            sender = txn.get('transaction', {}).get('data', {}).get('sender')
            if sender:
                interaction_analysis['unique_addresses'].add(sender)
            
            # Timeline
            timestamp = int(txn.get('timestampMs', 0))
            if timestamp > 0:
                interaction_analysis['interaction_timeline'].append({
                    'timestamp': timestamp,
                    'digest': txn['digest'],
                    'sender': sender
                })
            
            # Transaction types
            tx_kind = txn.get('transaction', {}).get('data', {}).get('transaction', {}).get('kind', 'Unknown')
            interaction_analysis['transaction_types'][tx_kind] += 1
        
        interaction_analysis['unique_addresses'] = len(interaction_analysis['unique_addresses'])
        interaction_analysis['transaction_types'] = dict(interaction_analysis['transaction_types'])
        
        return transactions, interaction_analysis
    
    def monitor_transactions(
        self,
        transaction_filter: TransactionFilter,
        callback: Callable[[List[Dict[str, Any]]], None],
        interval_seconds: int = 30,
        max_duration_minutes: int = 60
    ):
        """Monitor for new transactions matching filter"""
        
        last_seen_digest = None
        start_time = time.time()
        max_duration_seconds = max_duration_minutes * 60
        
        print(f"Starting transaction monitoring (max {max_duration_minutes} minutes)")
        
        while (time.time() - start_time) < max_duration_seconds:
            try:
                result = self.query_transactions(
                    transaction_filter,
                    limit=10,
                    descending=True,
                    show_all=True
                )
                
                if not result['data']:
                    time.sleep(interval_seconds)
                    continue
                
                latest_digest = result['data'][0]['digest']
                
                if last_seen_digest is None:
                    last_seen_digest = latest_digest
                    print("Monitoring initialized")
                    time.sleep(interval_seconds)
                    continue
                
                if latest_digest != last_seen_digest:
                    # Find new transactions
                    new_transactions = []
                    for txn in result['data']:
                        if txn['digest'] == last_seen_digest:
                            break
                        new_transactions.append(txn)
                    
                    if new_transactions:
                        print(f"📩 {len(new_transactions)} new transactions detected")
                        callback(new_transactions)
                        last_seen_digest = latest_digest
                
                time.sleep(interval_seconds)
                
            except Exception as e:
                print(f"Error in monitoring: {e}")
                time.sleep(interval_seconds)
        
        print("Transaction monitoring completed")

# Usage examples
client = SuiTransactionQueryClient('https://api-sui-mainnet-full.n.dwellir.com/YOUR_API_KEY')

# Example 1: Get transactions from address
address = '0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f'
filter_obj = TransactionFilter(from_address=address)
result = client.query_transactions(filter_obj, limit=20)
print(f"Found {len(result['data'])} transactions from address")

# Example 2: Comprehensive activity analysis
analysis = client.analyze_address_activity(address, days=7)
print(f"\nActivity Analysis for last 7 days:")
print(f"Total transactions: {analysis['total_transactions']}")
print(f"Success rate: {analysis['successful_transactions'] / max(1, analysis['total_transactions']):.2%}")
print(f"Average gas per transaction: {analysis['gas_statistics']['avg']:.0f}")
print(f"Unique recipients: {analysis['unique_recipients']}")

# Example 3: Track Move function calls
move_filter = TransactionFilter(
    move_function={
        "package": "0x2",
        "module": "coin", 
        "function": "split"
    }
)
coin_split_txns = client.query_transactions(move_filter, limit=50)
print(f"\nFound {len(coin_split_txns['data'])} coin split transactions")

# Example 4: Monitor new transactions
def on_new_transactions(transactions):
    for txn in transactions:
        status = txn.get('effects', {}).get('status', {}).get('status', 'unknown')
        print(f"New transaction: {txn['digest'][:16]}... - {status}")

# Monitor for 2 minutes
client.monitor_transactions(
    TransactionFilter(from_address=address),
    on_new_transactions,
    interval_seconds=10,
    max_duration_minutes=2
)

Common Use Cases

1. Wallet Transaction History

JavaScript
async function buildWalletHistory(address, limit = 100) {
  const transactions = await getAllTransactionHistory(address, limit);
  
  const history = transactions.map(txn => {
    const timestamp = parseInt(txn.timestampMs || '0');
    const status = txn.effects?.status?.status || 'unknown';
    
    // Determine transaction direction and counterpart
    let direction = 'unknown';
    let counterpart = null;
    let amount = 0;
    let coinType = '0x2::sui::SUI';
    
    if (txn.balanceChanges) {
      const userChange = txn.balanceChanges.find(
        change => change.owner?.AddressOwner === address
      );
      
      if (userChange) {
        amount = Math.abs(parseInt(userChange.amount));
        coinType = userChange.coinType;
        direction = parseInt(userChange.amount) >= 0 ? 'received' : 'sent';
        
        // Find counterpart
        const counterpartChange = txn.balanceChanges.find(
          change => change.owner?.AddressOwner !== address && 
                   change.coinType === coinType
        );
        
        if (counterpartChange) {
          counterpart = counterpartChange.owner?.AddressOwner;
        }
      }
    }
    
    return {
      digest: txn.digest,
      timestamp: new Date(timestamp),
      status: status,
      direction: direction,
      counterpart: counterpart,
      amount: amount,
      coinType: coinType,
      gasUsed: txn.effects?.gasUsed,
      type: txn.transaction?.data?.transaction?.kind || 'Unknown'
    };
  });
  
  return history.sort((a, b) => b.timestamp - a.timestamp);
}

2. DeFi Protocol Analytics

JavaScript
async function analyzeDeFiProtocol(packageId, module, functions = []) {
  const protocolTransactions = [];
  
  // Query transactions for each function
  for (const functionName of functions) {
    const txns = await getMoveCallTransactions(packageId, module, functionName);
    
    txns.forEach(txn => {
      txn._functionCalled = functionName;
      protocolTransactions.push(txn);
    });
  }
  
  // Sort by timestamp
  protocolTransactions.sort((a, b) => 
    parseInt(b.timestampMs || '0') - parseInt(a.timestampMs || '0')
  );
  
  // Analyze usage patterns
  const analysis = {
    totalTransactions: protocolTransactions.length,
    uniqueUsers: new Set(),
    functionUsage: {},
    dailyVolume: {},
    gasMetrics: {
      total: 0,
      average: 0,
      min: Infinity,
      max: 0
    },
    errorRate: 0
  };
  
  let failedCount = 0;
  
  protocolTransactions.forEach(txn => {
    // User tracking
    const sender = txn.transaction?.data?.sender;
    if (sender) analysis.uniqueUsers.add(sender);
    
    // Function usage
    const func = txn._functionCalled;
    analysis.functionUsage[func] = (analysis.functionUsage[func] || 0) + 1;
    
    // Daily volume
    const timestamp = parseInt(txn.timestampMs || '0');
    if (timestamp > 0) {
      const day = new Date(timestamp).toISOString().split('T')[0];
      analysis.dailyVolume[day] = (analysis.dailyVolume[day] || 0) + 1;
    }
    
    // Gas analysis
    const gasUsed = txn.effects?.gasUsed;
    if (gasUsed) {
      const totalGas = parseInt(gasUsed.computationCost) + 
                      parseInt(gasUsed.storageCost) - 
                      parseInt(gasUsed.storageRebate);
      
      analysis.gasMetrics.total += totalGas;
      analysis.gasMetrics.min = Math.min(analysis.gasMetrics.min, totalGas);
      analysis.gasMetrics.max = Math.max(analysis.gasMetrics.max, totalGas);
    }
    
    // Error tracking
    if (txn.effects?.status?.status !== 'success') {
      failedCount++;
    }
  });
  
  analysis.uniqueUsers = analysis.uniqueUsers.size;
  analysis.gasMetrics.average = analysis.gasMetrics.total / protocolTransactions.length;
  analysis.errorRate = failedCount / protocolTransactions.length;
  
  return { transactions: protocolTransactions, analysis };
}

3. Address Relationship Mapping

JavaScript
async function mapAddressRelationships(centralAddress, depth = 2) {
  const relationships = {
    nodes: new Set([centralAddress]),
    edges: [],
    transactions: new Map()
  };
  
  const queue = [{ address: centralAddress, level: 0 }];
  const processed = new Set();
  
  while (queue.length > 0 && queue[0].level < depth) {
    const { address, level } = queue.shift();
    
    if (processed.has(address)) continue;
    processed.add(address);
    
    console.log(`Processing level ${level}: ${address}`);
    
    // Get transactions from this address
    const sentTransactions = await getCompleteTransactionHistory(address, 5);
    
    // Get transactions to this address
    const receivedResult = await client.queryTransactionBlocks({
      filter: { ToAddress: address },
      options: { showBalanceChanges: true },
      limit: 100,
      order: 'descending'
    });
    
    // Process sent transactions
    sentTransactions.forEach(txn => {
      txn.balanceChanges?.forEach(change => {
        const recipient = change.owner?.AddressOwner;
        
        if (recipient && recipient !== address && !relationships.nodes.has(recipient)) {
          relationships.nodes.add(recipient);
          relationships.edges.push({
            from: address,
            to: recipient,
            amount: Math.abs(parseInt(change.amount)),
            coinType: change.coinType,
            txDigest: txn.digest,
            timestamp: parseInt(txn.timestampMs || '0')
          });
          
          if (level + 1 < depth) {
            queue.push({ address: recipient, level: level + 1 });
          }
        }
      });
    });
    
    // Process received transactions
    receivedResult.data.forEach(txn => {
      const sender = txn.transaction?.data?.sender;
      
      if (sender && sender !== address && !relationships.nodes.has(sender)) {
        relationships.nodes.add(sender);
        
        const relevantChange = txn.balanceChanges?.find(
          change => change.owner?.AddressOwner === address
        );
        
        if (relevantChange) {
          relationships.edges.push({
            from: sender,
            to: address,
            amount: Math.abs(parseInt(relevantChange.amount)),
            coinType: relevantChange.coinType,
            txDigest: txn.digest,
            timestamp: parseInt(txn.timestampMs || '0')
          });
        }
        
        if (level + 1 < depth) {
          queue.push({ address: sender, level: level + 1 });
        }
      }
    });
  }
  
  return {
    nodes: Array.from(relationships.nodes),
    edges: relationships.edges,
    centralAddress,
    depth,
    totalNodes: relationships.nodes.size,
    totalEdges: relationships.edges.length
  };
}

4. Transaction Pattern Detection

JavaScript
class TransactionPatternDetector {
  constructor(client) {
    this.client = client;
    this.patterns = {
      MEV: [],
      arbitrage: [],
      liquidations: [],
      flashLoans: [],
      suspiciousActivity: []
    };
  }
  
  async detectPatterns(address, lookbackDays = 7) {
    const transactions = await getCompleteTransactionHistory(address, 50);
    
    // Filter recent transactions
    const cutoff = Date.now() - (lookbackDays * 24 * 60 * 60 * 1000);
    const recentTxns = transactions.filter(txn => 
      parseInt(txn.timestampMs || '0') > cutoff
    );
    
    await Promise.all([
      this.detectArbitragePatterns(recentTxns),
      this.detectMEVPatterns(recentTxns),
      this.detectSuspiciousActivity(recentTxns),
      this.detectFlashLoanPatterns(recentTxns)
    ]);
    
    return this.patterns;
  }
  
  async detectArbitragePatterns(transactions) {
    // Look for sequences of trades that result in profit
    const trades = transactions.filter(txn => 
      txn.events?.some(event => event.type.includes('Swap') || event.type.includes('Trade'))
    );
    
    for (let i = 0; i < trades.length - 1; i++) {
      const trade1 = trades[i];
      const trade2 = trades[i + 1];
      
      const timeDiff = parseInt(trade1.timestampMs) - parseInt(trade2.timestampMs);
      
      // Trades within 5 minutes might be arbitrage
      if (Math.abs(timeDiff) < 5 * 60 * 1000) {
        // Analyze balance changes to detect profit
        const profit = this.calculateProfitBetweenTrades(trade1, trade2);
        
        if (profit > 0) {
          this.patterns.arbitrage.push({
            trades: [trade1.digest, trade2.digest],
            profit: profit,
            timeSpan: Math.abs(timeDiff),
            confidence: this.calculateArbitrageConfidence(trade1, trade2)
          });
        }
      }
    }
  }
  
  async detectMEVPatterns(transactions) {
    // Look for transactions that appear to front-run or back-run others
    for (let i = 0; i < transactions.length; i++) {
      const txn = transactions[i];
      
      // Check if this transaction has unusually high gas price
      const gasPrice = parseInt(txn.transaction?.data?.gasData?.price || '0');
      
      if (gasPrice > 1500) { // Higher than typical 750
        // Check for related transactions in the same block/checkpoint
        const sameCheckpoint = transactions.filter(t => 
          t.checkpoint === txn.checkpoint && t.digest !== txn.digest
        );
        
        if (sameCheckpoint.length > 0) {
          this.patterns.MEV.push({
            frontRunTx: txn.digest,
            relatedTxs: sameCheckpoint.map(t => t.digest),
            gasPrice: gasPrice,
            checkpoint: txn.checkpoint,
            confidence: this.calculateMEVConfidence(txn, sameCheckpoint)
          });
        }
      }
    }
  }
  
  async detectSuspiciousActivity(transactions) {
    // Look for potential wash trading or circular transactions
    const addressCounts = new Map();
    const timeWindows = new Map();
    
    transactions.forEach(txn => {
      txn.balanceChanges?.forEach(change => {
        const addr = change.owner?.AddressOwner;
        if (addr) {
          addressCounts.set(addr, (addressCounts.get(addr) || 0) + 1);
        }
      });
      
      // Group by hour
      const hour = Math.floor(parseInt(txn.timestampMs || '0') / (60 * 60 * 1000));
      if (!timeWindows.has(hour)) {
        timeWindows.set(hour, []);
      }
      timeWindows.get(hour).push(txn);
    });
    
    // Check for addresses with excessive interactions
    for (const [address, count] of addressCounts) {
      if (count > 10) { // Threshold for suspicious activity
        this.patterns.suspiciousActivity.push({
          type: 'excessive_interactions',
          address: address,
          interactionCount: count,
          confidence: Math.min(count / 20, 1)
        });
      }
    }
    
    // Check for high-frequency trading in short windows
    for (const [hour, hourTxns] of timeWindows) {
      if (hourTxns.length > 20) {
        this.patterns.suspiciousActivity.push({
          type: 'high_frequency_trading',
          hour: new Date(hour * 60 * 60 * 1000).toISOString(),
          transactionCount: hourTxns.length,
          confidence: Math.min(hourTxns.length / 50, 1)
        });
      }
    }
  }
  
  calculateProfitBetweenTrades(trade1, trade2) {
    // Simplified profit calculation - would need more sophisticated analysis
    const balance1 = this.sumBalanceChanges(trade1.balanceChanges || []);
    const balance2 = this.sumBalanceChanges(trade2.balanceChanges || []);
    
    return balance1 + balance2; // Simplified
  }
  
  sumBalanceChanges(changes) {
    return changes.reduce((sum, change) => {
      if (change.coinType === '0x2::sui::SUI') {
        return sum + parseInt(change.amount || '0');
      }
      return sum;
    }, 0);
  }
  
  calculateArbitrageConfidence(trade1, trade2) {
    // Simple confidence calculation based on various factors
    let confidence = 0.5;
    
    // Close timing increases confidence
    const timeDiff = Math.abs(parseInt(trade1.timestampMs) - parseInt(trade2.timestampMs));
    if (timeDiff < 60000) confidence += 0.3; // Within 1 minute
    else if (timeDiff < 300000) confidence += 0.2; // Within 5 minutes
    
    // Multiple balance changes suggest complex trading
    const totalChanges = (trade1.balanceChanges?.length || 0) + (trade2.balanceChanges?.length || 0);
    if (totalChanges > 4) confidence += 0.2;
    
    return Math.min(confidence, 1);
  }
  
  calculateMEVConfidence(txn, relatedTxns) {
    let confidence = 0.3; // Base confidence for high gas price
    
    // More related transactions increase confidence
    confidence += Math.min(relatedTxns.length * 0.1, 0.5);
    
    // Success of high-gas transaction increases confidence
    if (txn.effects?.status?.status === 'success') {
      confidence += 0.2;
    }
    
    return Math.min(confidence, 1);
  }
}

Advanced Query Techniques

1. Time-Range Queries

JavaScript
async function queryTransactionsByTimeRange(address, startTime, endTime) {
  const allTransactions = await getCompleteTransactionHistory(address, 100);
  
  const filteredTransactions = allTransactions.filter(txn => {
    const timestamp = parseInt(txn.timestampMs || '0');
    return timestamp >= startTime && timestamp <= endTime;
  });
  
  // Group by day
  const dailyGroups = {};
  filteredTransactions.forEach(txn => {
    const day = new Date(parseInt(txn.timestampMs)).toISOString().split('T')[0];
    if (!dailyGroups[day]) {
      dailyGroups[day] = [];
    }
    dailyGroups[day].push(txn);
  });
  
  return {
    totalTransactions: filteredTransactions.length,
    timeRange: {
      start: new Date(startTime).toISOString(),
      end: new Date(endTime).toISOString()
    },
    dailyBreakdown: Object.entries(dailyGroups).map(([day, txns]) => ({
      date: day,
      count: txns.length,
      successful: txns.filter(t => t.effects?.status?.status === 'success').length
    }))
  };
}

2. Multi-Filter Queries

JavaScript
async function complexTransactionQuery(filters) {
  // Execute multiple queries in parallel
  const queryPromises = filters.map(async filter => {
    const result = await client.queryTransactionBlocks({
      filter: filter,
      options: {
        showEffects: true,
        showBalanceChanges: true
      },
      limit: 100,
      order: 'descending'
    });
    
    return {
      filter: filter,
      transactions: result.data,
      count: result.data.length
    };
  });
  
  const results = await Promise.all(queryPromises);
  
  // Find intersections and unions
  const allTransactions = new Map();
  const filterResults = new Map();
  
  results.forEach(result => {
    filterResults.set(JSON.stringify(result.filter), result);
    
    result.transactions.forEach(txn => {
      if (!allTransactions.has(txn.digest)) {
        allTransactions.set(txn.digest, {
          transaction: txn,
          matchingFilters: []
        });
      }
      
      allTransactions.get(txn.digest).matchingFilters.push(result.filter);
    });
  });
  
  return {
    individualResults: results,
    combinedResults: Array.from(allTransactions.values()),
    intersections: Array.from(allTransactions.values()).filter(
      item => item.matchingFilters.length > 1
    ),
    totalUniqueTransactions: allTransactions.size
  };
}

Performance Considerations

1. Efficient Pagination

JavaScript
class EfficientTransactionPaginator {
  constructor(client) {
    this.client = client;
    this.pageCache = new Map();
    this.maxCacheSize = 100;
  }
  
  async getPage(filter, cursor = null, limit = 50) {
    const cacheKey = `${JSON.stringify(filter)}_${cursor || 'start'}_${limit}`;
    
    if (this.pageCache.has(cacheKey)) {
      return this.pageCache.get(cacheKey);
    }
    
    const result = await this.client.queryTransactionBlocks({
      filter: filter,
      cursor: cursor,
      limit: limit,
      options: { showEffects: true },
      order: 'descending'
    });
    
    // Cache management
    if (this.pageCache.size >= this.maxCacheSize) {
      const firstKey = this.pageCache.keys().next().value;
      this.pageCache.delete(firstKey);
    }
    
    this.pageCache.set(cacheKey, result);
    return result;
  }
  
  async *iterateAllPages(filter, batchSize = 50) {
    let cursor = null;
    let hasMore = true;
    
    while (hasMore) {
      const page = await this.getPage(filter, cursor, batchSize);
      
      yield page.data;
      
      cursor = page.nextCursor;
      hasMore = page.hasNextPage;
      
      // Small delay to prevent overwhelming the RPC
      if (hasMore) {
        await new Promise(resolve => setTimeout(resolve, 100));
      }
    }
  }
}

2. Query Optimization

JavaScript
async function optimizeQueryPerformance(address, analysisDepth = 'shallow') {
  const queries = {
    shallow: {
      limit: 20,
      options: { showEffects: true }
    },
    medium: {
      limit: 100,
      options: { showEffects: true, showBalanceChanges: true }
    },
    deep: {
      limit: 500,
      options: {
        showInput: true,
        showEffects: true,
        showEvents: true,
        showBalanceChanges: true
      }
    }
  };
  
  const config = queries[analysisDepth] || queries.shallow;
  
  const startTime = Date.now();
  
  const result = await client.queryTransactionBlocks({
    filter: { FromAddress: address },
    ...config,
    order: 'descending'
  });
  
  const endTime = Date.now();
  
  return {
    transactions: result.data,
    performance: {
      queryTime: endTime - startTime,
      transactionCount: result.data.length,
      avgTimePerTransaction: (endTime - startTime) / result.data.length,
      analysisDepth: analysisDepth
    }
  };
}

Best Practices

1. Query Optimization

  • Use specific filters to reduce result sets and improve performance
  • Request only needed data through selective options configuration
  • Implement pagination properly for large datasets
  • Cache frequent queries to reduce API load

2. Performance Management

  • Batch related queries when possible
  • Use descending order for most recent transactions first
  • Implement rate limiting to avoid hitting API limits
  • Monitor query response times and adjust accordingly

3. Data Processing

  • Stream process large datasets instead of loading everything into memory
  • Use incremental updates for real-time monitoring
  • Implement proper error handling for network timeouts
  • Validate response data before processing

4. Analysis Strategies

  • Combine multiple filters for complex queries
  • Use time-based filtering to focus on relevant periods
  • Track patterns across transactions for behavioral analysis
  • Implement anomaly detection for security monitoring

5. User Experience

  • Show loading indicators for long-running queries
  • Implement progressive loading for better responsiveness
  • Provide meaningful error messages for failed queries
  • Cache results to improve repeat access performance

Need help? Contact our support team or check the Sui documentation.