Skip to main content

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​

ParameterTypeRequiredDescription
filterobjectYesQuery criteria to filter transactions
optionsobjectNoOptions for controlling response content
cursorstringNoPagination cursor from previous response
limitnumberNoMaximum number of transactions to return (default: 50, max: 100)
descendingOrderbooleanNoReturn results in descending order by sequence number (default: false)

Filter Object​

The filter object supports various query types:

Filter TypeDescriptionExample
FromAddressTransactions sent from specific address{ "FromAddress": "0x123..." }
ToAddressTransactions sent to specific address{ "ToAddress": "0x456..." }
InputObjectTransactions that used specific object as input{ "InputObject": "0x789..." }
ChangedObjectTransactions that modified specific object{ "ChangedObject": "0xabc..." }
FromAndToAddressTransactions between two addresses{ "FromAndToAddress": { "from": "0x123...", "to": "0x456..." } }
TransactionKindTransactions of specific type{ "TransactionKind": "ProgrammableTransaction" }
MoveFunctionTransactions calling specific Move function{ "MoveFunction": { "package": "0x2", "module": "pay", "function": "split" } }

Options Object​

FieldTypeDefaultDescription
showInputbooleanfalseInclude transaction input data
showRawInputbooleanfalseInclude raw BCS-encoded input
showEffectsbooleanfalseInclude transaction effects
showEventsbooleanfalseInclude events emitted
showObjectChangesbooleanfalseInclude object changes
showBalanceChangesbooleanfalseInclude balance changes
showRawEffectsbooleanfalseInclude raw BCS-encoded effects

Returns​

Returns a paginated response containing matching transactions.

FieldTypeDescription
dataarrayArray of transaction blocks matching the query
nextCursorstringCursor for next page (null if no more pages)
hasNextPagebooleanWhether more pages are available

Each transaction in the data array follows the same structure as sui_getTransactionBlock responses.

Code Examples​

# 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
}'

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
}
};
}

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.