suix_getAllBalances
Retrieves all coin balances for a specific address on the Sui blockchain, providing a comprehensive overview of all owned fungible tokens in a single request.
Overview
The suix_getAllBalances
method is essential for portfolio management and wallet applications. Unlike suix_getBalance
which queries a specific coin type, this method returns all coin types owned by an address, including native SUI tokens and any custom fungible tokens. This makes it perfect for displaying complete portfolio overviews, calculating total portfolio values, and discovering all assets owned by an address.
Parameters
Parameter | Type | Required | Description |
---|---|---|---|
owner | string | Yes | The Sui address to query (66-character hex string with 0x prefix) |
Returns
Returns an array of balance objects, one for each coin type owned by the address.
Field | Type | Description |
---|---|---|
coinType | string | The type of coin (e.g., "0x2::sui::SUI") |
coinObjectCount | number | Number of coin objects of this type |
totalBalance | string | Total balance across all coin objects (in base units) |
lockedBalance | object | Locked balance information (if applicable) |
Balance Array Structure
Each balance object in the array contains:
- coinType: Full type identifier for the coin
- coinObjectCount: How many individual coin objects make up this balance
- totalBalance: Aggregate balance in the smallest denomination
- lockedBalance: Any locked/staked amounts (optional)
Common Coin Types
- Native SUI:
0x2::sui::SUI
- USDC:
0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC
- Custom Tokens:
0x{package}::{module}::{type}
Code Examples
- cURL
- JavaScript
- Python
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_getAllBalances",
"params": [
"0xac5bceec1b789ff840d7d4e6ce4ce61c90d190a7f8c4f4ddf0bff6ee2413c33c"
],
"id": 1
}'
import { SuiClient } from '@mysten/sui.js/client';
const client = new SuiClient({
url: 'https://api-sui-mainnet-full.n.dwellir.com/YOUR_API_KEY'
});
async function getAllBalances(address) {
const balances = await client.getAllBalances({
owner: address
});
console.log(`Found ${balances.length} different coin types`);
// Process and format balances
const formattedBalances = balances.map(balance => {
const isSui = balance.coinType === '0x2::sui::SUI';
const decimals = isSui ? 9 : 6; // Assume 6 for others, should check metadata
const formattedAmount = Number(balance.totalBalance) / Math.pow(10, decimals);
return {
coinType: balance.coinType,
symbol: isSui ? 'SUI' : extractSymbol(balance.coinType),
totalBalance: balance.totalBalance,
formattedBalance: formattedAmount,
coinObjectCount: balance.coinObjectCount,
lockedBalance: balance.lockedBalance
};
});
return formattedBalances;
}
function extractSymbol(coinType) {
// Extract symbol from coin type string
const parts = coinType.split('::');
return parts[parts.length - 1] || 'UNKNOWN';
}
// Portfolio analysis functions
async function analyzePortfolio(address) {
const balances = await getAllBalances(address);
const analysis = {
totalAssets: balances.length,
totalCoinObjects: balances.reduce((sum, b) => sum + b.coinObjectCount, 0),
suiBalance: 0,
otherTokens: [],
largestHolding: null,
zeroBalances: []
};
let maxBalance = 0;
balances.forEach(balance => {
if (balance.coinType === '0x2::sui::SUI') {
analysis.suiBalance = balance.formattedBalance;
} else {
analysis.otherTokens.push({
symbol: balance.symbol,
balance: balance.formattedBalance,
coinType: balance.coinType
});
}
if (balance.formattedBalance > maxBalance) {
maxBalance = balance.formattedBalance;
analysis.largestHolding = {
symbol: balance.symbol,
balance: balance.formattedBalance,
coinType: balance.coinType
};
}
if (balance.formattedBalance === 0) {
analysis.zeroBalances.push(balance.coinType);
}
});
return analysis;
}
// Monitor portfolio changes
async function comparePortfolios(address, previousBalances) {
const currentBalances = await getAllBalances(address);
const changes = {
added: [],
removed: [],
increased: [],
decreased: [],
unchanged: []
};
const currentTypes = new Set(currentBalances.map(b => b.coinType));
const previousTypes = new Set(previousBalances.map(b => b.coinType));
// Find added coin types
currentTypes.forEach(type => {
if (!previousTypes.has(type)) {
const balance = currentBalances.find(b => b.coinType === type);
changes.added.push(balance);
}
});
// Find removed coin types
previousTypes.forEach(type => {
if (!currentTypes.has(type)) {
const balance = previousBalances.find(b => b.coinType === type);
changes.removed.push(balance);
}
});
// Compare existing balances
currentBalances.forEach(current => {
const previous = previousBalances.find(p => p.coinType === current.coinType);
if (previous) {
const currentAmount = Number(current.totalBalance);
const previousAmount = Number(previous.totalBalance);
if (currentAmount > previousAmount) {
changes.increased.push({
...current,
change: currentAmount - previousAmount,
changeFormatted: current.formattedBalance - previous.formattedBalance
});
} else if (currentAmount < previousAmount) {
changes.decreased.push({
...current,
change: previousAmount - currentAmount,
changeFormatted: previous.formattedBalance - current.formattedBalance
});
} else {
changes.unchanged.push(current);
}
}
});
return changes;
}
// Example usage
const address = '0xac5bceec1b789ff840d7d4e6ce4ce61c90d190a7f8c4f4ddf0bff6ee2413c33c';
// Get all balances
const balances = await getAllBalances(address);
console.log('All Balances:', balances);
// Analyze portfolio
const analysis = await analyzePortfolio(address);
console.log('Portfolio Analysis:', analysis);
// Display formatted portfolio
function displayPortfolio(balances) {
console.log('\n📊 Portfolio Overview:');
console.log('=' * 50);
balances.forEach(balance => {
const locked = balance.lockedBalance ?
` (${balance.lockedBalance.number} locked)` : '';
console.log(`${balance.symbol.padEnd(10)} ${balance.formattedBalance.toFixed(6).padStart(15)}${locked}`);
});
}
displayPortfolio(balances);
import requests
import json
from decimal import Decimal
from typing import List, Dict, Any, Optional
from dataclasses import dataclass
@dataclass
class CoinBalance:
coin_type: str
symbol: str
total_balance: str
formatted_balance: Decimal
coin_object_count: int
locked_balance: Optional[Dict[str, Any]] = None
class SuiPortfolioTracker:
def __init__(self, rpc_url: str):
self.rpc_url = rpc_url
self.coin_metadata_cache = {}
def get_all_balances(self, address: str) -> List[CoinBalance]:
"""Get all coin balances for an address"""
payload = {
"jsonrpc": "2.0",
"method": "suix_getAllBalances",
"params": [address],
"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']}")
balances = []
for balance_data in result['result']:
balance = self._process_balance(balance_data)
balances.append(balance)
return balances
def _process_balance(self, balance_data: Dict[str, Any]) -> CoinBalance:
"""Process raw balance data into CoinBalance object"""
coin_type = balance_data['coinType']
symbol = self._extract_symbol(coin_type)
decimals = self._get_decimals(coin_type)
total_balance = balance_data['totalBalance']
formatted_balance = Decimal(total_balance) / Decimal(10 ** decimals)
return CoinBalance(
coin_type=coin_type,
symbol=symbol,
total_balance=total_balance,
formatted_balance=formatted_balance,
coin_object_count=balance_data['coinObjectCount'],
locked_balance=balance_data.get('lockedBalance')
)
def _extract_symbol(self, coin_type: str) -> str:
"""Extract symbol from coin type"""
if coin_type == '0x2::sui::SUI':
return 'SUI'
# Try to extract from the coin type string
parts = coin_type.split('::')
if len(parts) >= 3:
return parts[-1].upper()
return 'UNKNOWN'
def _get_decimals(self, coin_type: str) -> int:
"""Get decimal places for coin type"""
# Cache metadata to avoid repeated calls
if coin_type in self.coin_metadata_cache:
return self.coin_metadata_cache[coin_type]
if coin_type == '0x2::sui::SUI':
decimals = 9
else:
# For demo purposes, assume 6 decimals for other tokens
# In production, you should fetch from sui_getCoinMetadata
decimals = 6
self.coin_metadata_cache[coin_type] = decimals
return decimals
def analyze_portfolio(self, address: str) -> Dict[str, Any]:
"""Analyze portfolio composition and metrics"""
balances = self.get_all_balances(address)
analysis = {
'address': address,
'total_assets': len(balances),
'total_coin_objects': sum(b.coin_object_count for b in balances),
'sui_balance': Decimal('0'),
'other_tokens': [],
'largest_holding': None,
'zero_balances': [],
'locked_tokens': [],
'portfolio_diversity': 0
}
max_balance = Decimal('0')
non_zero_count = 0
for balance in balances:
if balance.coin_type == '0x2::sui::SUI':
analysis['sui_balance'] = balance.formatted_balance
else:
analysis['other_tokens'].append({
'symbol': balance.symbol,
'balance': float(balance.formatted_balance),
'coin_type': balance.coin_type,
'object_count': balance.coin_object_count
})
if balance.formatted_balance > max_balance:
max_balance = balance.formatted_balance
analysis['largest_holding'] = {
'symbol': balance.symbol,
'balance': float(balance.formatted_balance),
'coin_type': balance.coin_type
}
if balance.formatted_balance == 0:
analysis['zero_balances'].append(balance.coin_type)
else:
non_zero_count += 1
if balance.locked_balance:
analysis['locked_tokens'].append({
'symbol': balance.symbol,
'locked_amount': balance.locked_balance.get('number', '0'),
'epoch_id': balance.locked_balance.get('epochId')
})
# Calculate portfolio diversity (non-zero assets / total assets)
analysis['portfolio_diversity'] = non_zero_count / len(balances) if balances else 0
return analysis
def compare_portfolios(
self,
address: str,
previous_balances: List[CoinBalance]
) -> Dict[str, List[Any]]:
"""Compare current portfolio with previous state"""
current_balances = self.get_all_balances(address)
current_types = {b.coin_type for b in current_balances}
previous_types = {b.coin_type for b in previous_balances}
changes = {
'added': [],
'removed': [],
'increased': [],
'decreased': [],
'unchanged': []
}
# Find new coin types
for balance in current_balances:
if balance.coin_type not in previous_types:
changes['added'].append({
'symbol': balance.symbol,
'balance': float(balance.formatted_balance),
'coin_type': balance.coin_type
})
# Find removed coin types
for balance in previous_balances:
if balance.coin_type not in current_types:
changes['removed'].append({
'symbol': balance.symbol,
'balance': float(balance.formatted_balance),
'coin_type': balance.coin_type
})
# Compare existing balances
for current in current_balances:
previous = next(
(b for b in previous_balances if b.coin_type == current.coin_type),
None
)
if previous:
current_amount = current.formatted_balance
previous_amount = previous.formatted_balance
if current_amount > previous_amount:
changes['increased'].append({
'symbol': current.symbol,
'current_balance': float(current_amount),
'previous_balance': float(previous_amount),
'change': float(current_amount - previous_amount),
'coin_type': current.coin_type
})
elif current_amount < previous_amount:
changes['decreased'].append({
'symbol': current.symbol,
'current_balance': float(current_amount),
'previous_balance': float(previous_amount),
'change': float(previous_amount - current_amount),
'coin_type': current.coin_type
})
else:
changes['unchanged'].append({
'symbol': current.symbol,
'balance': float(current_amount),
'coin_type': current.coin_type
})
return changes
def format_portfolio_report(self, address: str) -> str:
"""Generate a formatted portfolio report"""
analysis = self.analyze_portfolio(address)
report = f"""
Portfolio Report for {address}
{'=' * 80}
📊 Portfolio Overview:
- Total Assets: {analysis['total_assets']}
- Total Coin Objects: {analysis['total_coin_objects']}
- Portfolio Diversity: {analysis['portfolio_diversity']:.2%}
💰 SUI Balance: {analysis['sui_balance']:.6f} SUI
🪙 Other Tokens ({len(analysis['other_tokens'])}):
"""
for token in analysis['other_tokens']:
locked_info = ""
locked_token = next(
(lt for lt in analysis['locked_tokens'] if token['symbol'] == lt['symbol']),
None
)
if locked_token:
locked_info = f" (🔒 {locked_token['locked_amount']} locked)"
report += f" {token['symbol'].ljust(10)} {token['balance']:>15.6f}{locked_info}\n"
if analysis['largest_holding']:
largest = analysis['largest_holding']
report += f"\n🏆 Largest Holding: {largest['symbol']} ({largest['balance']:.6f})\n"
if analysis['zero_balances']:
report += f"\n⚠️ Zero Balance Assets: {len(analysis['zero_balances'])}\n"
if analysis['locked_tokens']:
report += f"\n🔒 Locked Tokens: {len(analysis['locked_tokens'])}\n"
for locked in analysis['locked_tokens']:
report += f" {locked['symbol']}: {locked['locked_amount']} (Epoch {locked['epoch_id']})\n"
return report
def track_portfolio_changes(self, address: str, interval_seconds: int = 300):
"""Track portfolio changes over time (simplified version)"""
import time
print(f"Starting portfolio tracking for {address}")
print(f"Checking every {interval_seconds} seconds...")
previous_balances = self.get_all_balances(address)
print("Initial portfolio loaded")
while True:
try:
time.sleep(interval_seconds)
current_balances = self.get_all_balances(address)
changes = self.compare_portfolios(address, previous_balances)
if any(changes.values()):
print(f"\n🔄 Portfolio changes detected at {time.ctime()}")
for change_type, items in changes.items():
if items:
print(f" {change_type.upper()}: {len(items)} items")
for item in items[:3]: # Show first 3 items
symbol = item.get('symbol', 'UNKNOWN')
print(f" - {symbol}: {item}")
previous_balances = current_balances
else:
print(".", end="", flush=True) # Progress indicator
except KeyboardInterrupt:
print("\nPortfolio tracking stopped")
break
except Exception as e:
print(f"\nError during tracking: {e}")
time.sleep(interval_seconds)
# Usage examples
tracker = SuiPortfolioTracker('https://api-sui-mainnet-full.n.dwellir.com/YOUR_API_KEY')
# Example 1: Get all balances
address = '0xac5bceec1b789ff840d7d4e6ce4ce61c90d190a7f8c4f4ddf0bff6ee2413c33c'
balances = tracker.get_all_balances(address)
print(f"Found {len(balances)} different coin types:")
for balance in balances:
locked_info = f" (🔒 {balance.locked_balance['number']} locked)" if balance.locked_balance else ""
print(f"{balance.symbol.ljust(10)} {balance.formatted_balance:>15.6f}{locked_info}")
# Example 2: Portfolio analysis
analysis = tracker.analyze_portfolio(address)
print("\nPortfolio Analysis:")
print(f"Total Assets: {analysis['total_assets']}")
print(f"SUI Balance: {analysis['sui_balance']:.6f}")
print(f"Other Tokens: {len(analysis['other_tokens'])}")
print(f"Portfolio Diversity: {analysis['portfolio_diversity']:.2%}")
# Example 3: Generate formatted report
report = tracker.format_portfolio_report(address)
print(report)
# Example 4: Compare with previous state (demo)
# previous_balances = [...] # Would be loaded from storage/database
# changes = tracker.compare_portfolios(address, previous_balances)
# print("Portfolio changes:", changes)
Response Example
{
"jsonrpc": "2.0",
"id": 1,
"result": [
{
"coinType": "0x1a98df3cad1701b8247cb716cef8f687adb657b5d28ffcb1ee91fd9973f70941::rmy::RMY",
"coinObjectCount": 1,
"totalBalance": "238543215486",
"lockedBalance": {}
},
{
"coinType": "0x2125f8f0af64238b2e23bec7897066fd5e5ea97969d7221fd67867a1e1e5592a::NaviReward::NAVIREWARD",
"coinObjectCount": 1,
"totalBalance": "7926000000000",
"lockedBalance": {}
},
...
]
}
Common Use Cases
1. Portfolio Dashboard
async function buildPortfolioDashboard(address) {
const balances = await client.getAllBalances({ owner: address });
const dashboard = {
totalAssets: balances.length,
nonZeroAssets: 0,
totalValue: 0, // Would need price data
assets: []
};
for (const balance of balances) {
const asset = {
symbol: extractSymbol(balance.coinType),
coinType: balance.coinType,
balance: balance.totalBalance,
objectCount: balance.coinObjectCount,
hasLockedBalance: !!balance.lockedBalance
};
if (parseInt(balance.totalBalance) > 0) {
dashboard.nonZeroAssets++;
}
dashboard.assets.push(asset);
}
// Sort by balance (highest first)
dashboard.assets.sort((a, b) =>
parseInt(b.balance) - parseInt(a.balance)
);
return dashboard;
}
2. Asset Discovery
async function discoverNewAssets(address, knownCoinTypes = []) {
const balances = await client.getAllBalances({ owner: address });
const newAssets = balances.filter(balance =>
!knownCoinTypes.includes(balance.coinType) &&
parseInt(balance.totalBalance) > 0
);
console.log(`Discovered ${newAssets.length} new assets:`);
for (const asset of newAssets) {
console.log(`- ${extractSymbol(asset.coinType)}: ${asset.totalBalance}`);
// Optionally fetch metadata for new assets
try {
const metadata = await client.getCoinMetadata({
coinType: asset.coinType
});
console.log(` Metadata: ${metadata?.name || 'Unknown'} (${metadata?.symbol || 'N/A'})`);
} catch (error) {
console.log(` Could not fetch metadata: ${error.message}`);
}
}
return newAssets;
}
3. Balance Monitoring with Alerts
class BalanceMonitor {
constructor(client) {
this.client = client;
this.thresholds = new Map();
this.lastBalances = new Map();
}
setThreshold(address, coinType, threshold) {
const key = `${address}:${coinType}`;
this.thresholds.set(key, threshold);
}
async checkBalances(address) {
const currentBalances = await this.client.getAllBalances({ owner: address });
const alerts = [];
for (const balance of currentBalances) {
const key = `${address}:${balance.coinType}`;
const threshold = this.thresholds.get(key);
const currentAmount = parseInt(balance.totalBalance);
const lastAmount = this.lastBalances.get(key) || 0;
// Check threshold alerts
if (threshold && currentAmount < threshold) {
alerts.push({
type: 'low_balance',
coinType: balance.coinType,
current: currentAmount,
threshold: threshold,
symbol: extractSymbol(balance.coinType)
});
}
// Check for significant changes
const percentChange = lastAmount > 0 ?
((currentAmount - lastAmount) / lastAmount) * 100 : 0;
if (Math.abs(percentChange) > 10) { // 10% change
alerts.push({
type: percentChange > 0 ? 'balance_increase' : 'balance_decrease',
coinType: balance.coinType,
current: currentAmount,
previous: lastAmount,
percentChange: percentChange,
symbol: extractSymbol(balance.coinType)
});
}
this.lastBalances.set(key, currentAmount);
}
return alerts;
}
}
// Usage
const monitor = new BalanceMonitor(client);
monitor.setThreshold(address, '0x2::sui::SUI', 1_000_000_000); // 1 SUI minimum
const alerts = await monitor.checkBalances(address);
alerts.forEach(alert => {
console.log(`Alert: ${alert.type} for ${alert.symbol}`);
});
4. Portfolio Value Calculator
async function calculatePortfolioValue(address, priceOracle) {
const balances = await client.getAllBalances({ owner: address });
let totalValue = 0;
const breakdown = [];
for (const balance of balances) {
const amount = parseInt(balance.totalBalance);
if (amount === 0) continue;
try {
// Get current price from oracle
const price = await priceOracle.getPrice(balance.coinType);
const decimals = await getDecimals(balance.coinType);
const formattedAmount = amount / Math.pow(10, decimals);
const value = formattedAmount * price;
totalValue += value;
breakdown.push({
symbol: extractSymbol(balance.coinType),
coinType: balance.coinType,
amount: formattedAmount,
price: price,
value: value,
percentage: 0 // Will be calculated after total
});
} catch (error) {
console.warn(`Could not get price for ${balance.coinType}:`, error.message);
breakdown.push({
symbol: extractSymbol(balance.coinType),
coinType: balance.coinType,
amount: amount / Math.pow(10, 9), // Assume 9 decimals
price: 0,
value: 0,
percentage: 0
});
}
}
// Calculate percentages
breakdown.forEach(item => {
item.percentage = totalValue > 0 ? (item.value / totalValue) * 100 : 0;
});
// Sort by value
breakdown.sort((a, b) => b.value - a.value);
return {
totalValue,
breakdown,
lastUpdated: new Date().toISOString()
};
}
Advanced Portfolio Management
1. Portfolio Rebalancing Suggestions
async function suggestRebalancing(address, targetAllocations) {
const balances = await client.getAllBalances({ owner: address });
const currentValue = await calculatePortfolioValue(address, priceOracle);
const suggestions = [];
for (const [coinType, targetPercent] of Object.entries(targetAllocations)) {
const current = currentValue.breakdown.find(b => b.coinType === coinType);
const currentPercent = current ? current.percentage : 0;
const difference = targetPercent - currentPercent;
if (Math.abs(difference) > 5) { // 5% threshold
const targetValue = (currentValue.totalValue * targetPercent) / 100;
const currentValue = current ? current.value : 0;
const adjustment = targetValue - currentValue;
suggestions.push({
coinType: coinType,
symbol: extractSymbol(coinType),
currentPercent: currentPercent,
targetPercent: targetPercent,
difference: difference,
action: adjustment > 0 ? 'buy' : 'sell',
amount: Math.abs(adjustment)
});
}
}
return suggestions;
}
2. Historical Balance Tracking
class PortfolioHistoryTracker {
constructor(client, storage) {
this.client = client;
this.storage = storage; // Database or local storage
}
async recordSnapshot(address) {
const balances = await this.client.getAllBalances({ owner: address });
const timestamp = Date.now();
const snapshot = {
address,
timestamp,
balances: balances.map(b => ({
coinType: b.coinType,
totalBalance: b.totalBalance,
coinObjectCount: b.coinObjectCount,
lockedBalance: b.lockedBalance
}))
};
await this.storage.saveSnapshot(snapshot);
return snapshot;
}
async getHistoricalData(address, days = 30) {
const snapshots = await this.storage.getSnapshots(address, days);
// Process into time series data
const timeSeries = {};
snapshots.forEach(snapshot => {
snapshot.balances.forEach(balance => {
if (!timeSeries[balance.coinType]) {
timeSeries[balance.coinType] = [];
}
timeSeries[balance.coinType].push({
timestamp: snapshot.timestamp,
balance: balance.totalBalance,
objectCount: balance.coinObjectCount
});
});
});
return timeSeries;
}
async generateGrowthReport(address, days = 30) {
const historyData = await this.getHistoricalData(address, days);
const report = {
period: days,
assets: {}
};
for (const [coinType, data] of Object.entries(historyData)) {
if (data.length < 2) continue;
const oldest = data[0];
const newest = data[data.length - 1];
const growth = parseInt(newest.balance) - parseInt(oldest.balance);
const growthPercent = parseInt(oldest.balance) > 0 ?
(growth / parseInt(oldest.balance)) * 100 : 0;
report.assets[coinType] = {
symbol: extractSymbol(coinType),
startBalance: oldest.balance,
endBalance: newest.balance,
absoluteGrowth: growth.toString(),
percentGrowth: growthPercent,
dataPoints: data.length
};
}
return report;
}
}
Performance Optimization
1. Efficient Portfolio Polling
class OptimizedPortfolioFetcher {
constructor(client) {
this.client = client;
this.cache = new Map();
this.cacheTimeout = 30000; // 30 seconds
}
async getBalancesWithCache(address) {
const cacheKey = address;
const cached = this.cache.get(cacheKey);
if (cached && (Date.now() - cached.timestamp) < this.cacheTimeout) {
return cached.balances;
}
const balances = await this.client.getAllBalances({ owner: address });
this.cache.set(cacheKey, {
balances,
timestamp: Date.now()
});
return balances;
}
async batchGetBalances(addresses) {
const promises = addresses.map(address =>
this.getBalancesWithCache(address).catch(error => ({
address,
error: error.message
}))
);
const results = await Promise.all(promises);
return results;
}
clearCache() {
this.cache.clear();
}
}
2. Smart Update Detection
async function detectPortfolioUpdates(address, lastKnownBalances) {
const currentBalances = await client.getAllBalances({ owner: address });
// Quick check - compare array lengths and coin types
if (currentBalances.length !== lastKnownBalances.length) {
return { hasUpdates: true, reason: 'coin_count_changed' };
}
const currentTypes = new Set(currentBalances.map(b => b.coinType));
const lastTypes = new Set(lastKnownBalances.map(b => b.coinType));
if (currentTypes.size !== lastTypes.size ||
![...currentTypes].every(type => lastTypes.has(type))) {
return { hasUpdates: true, reason: 'coin_types_changed' };
}
// Check individual balances
for (let i = 0; i < currentBalances.length; i++) {
const current = currentBalances[i];
const last = lastKnownBalances.find(b => b.coinType === current.coinType);
if (!last || current.totalBalance !== last.totalBalance) {
return {
hasUpdates: true,
reason: 'balance_changed',
changedCoin: current.coinType
};
}
}
return { hasUpdates: false };
}
Error Handling and Edge Cases
1. Comprehensive Error Handling
async function safeGetAllBalances(address) {
try {
// Validate address format
if (!/^0x[a-fA-F0-9]{64}$/.test(address)) {
throw new Error('Invalid Sui address format');
}
const balances = await client.getAllBalances({ owner: address });
// Validate response structure
if (!Array.isArray(balances)) {
throw new Error('Invalid response: expected array of balances');
}
// Filter out invalid entries
const validBalances = balances.filter(balance =>
balance.coinType &&
typeof balance.totalBalance === 'string' &&
typeof balance.coinObjectCount === 'number'
);
if (validBalances.length !== balances.length) {
console.warn(`Filtered ${balances.length - validBalances.length} invalid balance entries`);
}
return {
success: true,
balances: validBalances,
metadata: {
totalTypes: validBalances.length,
hasLockedBalances: validBalances.some(b => b.lockedBalance),
timestamp: Date.now()
}
};
} catch (error) {
return {
success: false,
error: error.message,
balances: [],
metadata: {
timestamp: Date.now()
}
};
}
}
2. Handle Edge Cases
function processBalanceEdgeCases(balances) {
const processed = {
valid: [],
zeroBalances: [],
dustBalances: [],
lockedOnly: [],
issues: []
};
balances.forEach((balance, index) => {
const amount = parseInt(balance.totalBalance);
// Check for zero balances
if (amount === 0) {
processed.zeroBalances.push(balance);
return;
}
// Check for dust (very small amounts)
if (amount < 1000) { // Less than 0.000001 for 9-decimal coins
processed.dustBalances.push(balance);
}
// Check locked-only balances
if (balance.lockedBalance &&
parseInt(balance.lockedBalance.number) >= amount) {
processed.lockedOnly.push(balance);
}
// Check for potential issues
if (balance.coinObjectCount === 0 && amount > 0) {
processed.issues.push({
index,
issue: 'positive_balance_zero_objects',
balance
});
}
if (balance.coinObjectCount > 100) {
processed.issues.push({
index,
issue: 'too_many_coin_objects',
balance
});
}
processed.valid.push(balance);
});
return processed;
}
Related Methods
- suix_getBalance - Get balance for specific coin type
- suix_getCoins - Get coin objects with filtering
- sui_multiGetObjects - Batch query multiple objects
Best Practices
1. Efficient Data Processing
- Filter zero balances when displaying portfolio views
- Cache coin metadata to avoid repeated RPC calls
- Use pagination for addresses with many coin types
- Implement progressive loading for better user experience
2. Portfolio Management
- Track historical snapshots for growth analysis
- Set up balance alerts for important thresholds
- Monitor locked balances for staking and DeFi positions
- Aggregate small balances to reduce UI clutter
3. Performance Optimization
- Cache results for frequently queried addresses
- Batch multiple address queries when possible
- Use WebSocket subscriptions for real-time updates
- Implement smart update detection to minimize unnecessary calls
4. Error Resilience
- Validate address format before making requests
- Handle network timeouts gracefully
- Retry failed requests with exponential backoff
- Filter invalid response data to prevent application errors
5. User Experience
- Display loading states during data fetching
- Show meaningful error messages to users
- Format balances according to coin decimals
- Highlight significant changes in portfolio values
Need help? Contact our support team or check the Sui documentation.