suix_queryTransactionBlocks
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.
Parameters​
Parameter | Type | Required | Description |
---|---|---|---|
filter | object | Yes | Query criteria to filter transactions |
options | object | No | Options for controlling response content |
cursor | string | No | Pagination cursor from previous response |
limit | number | No | Maximum number of transactions to return (default: 50, max: 100) |
descendingOrder | boolean | No | Return results in descending order by sequence number (default: false) |
Filter Object​
The filter object supports various query types:
Filter Type | Description | Example |
---|---|---|
FromAddress | Transactions sent from specific address | { "FromAddress": "0x123..." } |
ToAddress | Transactions sent to specific address | { "ToAddress": "0x456..." } |
InputObject | Transactions that used specific object as input | { "InputObject": "0x789..." } |
ChangedObject | Transactions that modified specific object | { "ChangedObject": "0xabc..." } |
FromAndToAddress | Transactions between two addresses | { "FromAndToAddress": { "from": "0x123...", "to": "0x456..." } } |
TransactionKind | Transactions of specific type | { "TransactionKind": "ProgrammableTransaction" } |
MoveFunction | Transactions calling specific Move function | { "MoveFunction": { "package": "0x2", "module": "pay", "function": "split" } } |
Options Object​
Field | Type | Default | Description |
---|---|---|---|
showInput | boolean | false | Include transaction input data |
showRawInput | boolean | false | Include raw BCS-encoded input |
showEffects | boolean | false | Include transaction effects |
showEvents | boolean | false | Include events emitted |
showObjectChanges | boolean | false | Include object changes |
showBalanceChanges | boolean | false | Include balance changes |
showRawEffects | boolean | false | Include raw BCS-encoded effects |
Returns​
Returns a paginated response containing matching transactions.
Field | Type | Description |
---|---|---|
data | array | Array of transaction blocks matching the query |
nextCursor | string | Cursor for next page (null if no more pages) |
hasNextPage | boolean | Whether more pages are available |
Each transaction in the data array follows the same structure as sui_getTransactionBlock
responses.
Code Examples​
- cURL
- JavaScript
- Python
# Query transactions from a specific address
curl -X POST https://sui-mainnet.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://sui-mainnet.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://sui-mainnet.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
}'
import { SuiClient } from '@mysten/sui.js/client';
const client = new SuiClient({
url: 'https://sui-mainnet.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);
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://sui-mainnet.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
)
Response Example​
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"data": [
{
"digest": "0x8c123c0b23c456789abcdef0123456789abcdef0123456789abcdef0123456789a",
"transaction": {
"data": {
"messageVersion": "v1",
"transaction": {
"kind": "ProgrammableTransaction",
"inputs": [
{
"type": "pure",
"valueType": "address",
"value": "0x1a2b3c4d5e6f789012345678901234567890123456789012345678901234567890"
}
],
"transactions": [
{
"TransferObjects": [
[{ "Input": 0 }],
{ "Input": 1 }
]
}
]
},
"sender": "0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f",
"gasData": {
"payment": [
{
"objectId": "0x5d3c87e88bc566e3f10c66e0275a366001ffa8b86142adc78c744de6afffeb34",
"version": 31823924,
"digest": "HpSeCiMLG53N9FcHDrRTxwGhc4RVJa1seZhXYJ7KFpJe"
}
],
"owner": "0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f",
"price": "750",
"budget": "7500000"
}
},
"txSignatures": [
"AIXfGNvFvVwrVlOJQCAjNZ1mJ7QfOdxzQDQmHgdEGurBTGEpTKyKtIfxWq0YyL9JcF2JHGvglq3SqfX9VgwjJ+AONmH8/BbLSvdNuKLJAMjnafcTq4JxFRZGBKPhZCDn5w=="
]
},
"effects": {
"messageVersion": "v1",
"status": { "status": "success" },
"executedEpoch": "251",
"gasUsed": {
"computationCost": "750000",
"storageCost": "2340000",
"storageRebate": "1956000",
"nonRefundableStorageFee": "19764"
},
"transactionDigest": "0x8c123c0b23c456789abcdef0123456789abcdef0123456789abcdef0123456789a",
"created": [],
"mutated": [
{
"owner": {
"AddressOwner": "0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f"
},
"reference": {
"objectId": "0x5d3c87e88bc566e3f10c66e0275a366001ffa8b86142adc78c744de6afffeb34",
"version": 31823925,
"digest": "7dBx2Q5KDhz9AjZgWwHqGEKhWu9sF4fE2yNzYtGqHx8P"
}
}
],
"gasObject": {
"owner": {
"AddressOwner": "0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f"
},
"reference": {
"objectId": "0x5d3c87e88bc566e3f10c66e0275a366001ffa8b86142adc78c744de6afffeb34",
"version": 31823925,
"digest": "7dBx2Q5KDhz9AjZgWwHqGEKhWu9sF4fE2yNzYtGqHx8P"
}
},
"dependencies": []
},
"events": [],
"balanceChanges": [
{
"owner": {
"AddressOwner": "0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f"
},
"coinType": "0x2::sui::SUI",
"amount": "-1134000"
}
],
"timestampMs": "1703097600000",
"checkpoint": "18523456"
}
],
"nextCursor": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"hasNextPage": true
}
}
Common Use Cases​
1. Wallet Transaction History​
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​
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​
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​
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​
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​
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​
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​
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
}
};
}
Related Methods​
- sui_getTransactionBlock - Get single transaction details
- sui_executeTransactionBlock - Execute transaction blocks
- suix_getAllBalances - Get all coin balances
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.