suix_getCoins - Get Coin Objects with Fi...
Retrieve and filter coin objects on Sui blockchain using suix_getCoins RPC method. Query specific coin types, paginate results, and access detailed coin object data with Dwellir's high-performance Sui infrastructure.
Retrieves coin objects owned by an address with optional filtering by coin type and pagination support, providing detailed information about individual coin objects rather than aggregated balances.
Overview
The suix_getCoins method is essential for applications that need to work with individual coin objects rather than just aggregate balances. Unlike suix_getBalance which returns totals, this method returns actual coin objects that can be used in transactions, split, merged, or transferred. It supports filtering by coin type and pagination for efficient data retrieval, making it perfect for transaction construction, coin management, and detailed portfolio analysis.
Code Examples
Common Use Cases
1. Transaction Coin Selection
async function buildTransaction(sender, recipient, amount, coinType = '0x2::sui::SUI') {
// Select appropriate coins for the transaction
const coinSelection = await selectCoinsForAmount(sender, coinType, amount);
const txb = new TransactionBlock();
if (coinSelection.coinCount === 1) {
// Simple case: use single coin
const [coin] = coinSelection.selectedCoins;
if (coinSelection.change > 0) {
// Split coin if change is needed
const [splitCoin] = txb.splitCoins(coin.coinObjectId, [amount]);
txb.transferObjects([splitCoin], recipient);
} else {
// Transfer entire coin
txb.transferObjects([coin.coinObjectId], recipient);
}
} else {
// Multiple coins: merge first, then transfer
const [primaryCoin, ...coinsToMerge] = coinSelection.selectedCoins;
if (coinsToMerge.length > 0) {
txb.mergeCoins(
primaryCoin.coinObjectId,
coinsToMerge.map(c => c.coinObjectId)
);
}
const [splitCoin] = txb.splitCoins(primaryCoin.coinObjectId, [amount]);
txb.transferObjects([splitCoin], recipient);
}
return txb;
}2. Coin Consolidation
async function consolidateCoins(owner, coinType, maxCoinsPerTx = 10) {
const coins = await getAllCoinsWithPagination(owner, coinType);
if (coins.length <= 1) {
console.log('No consolidation needed');
return [];
}
// Sort coins by balance (keep largest as primary)
coins.sort((a, b) => parseInt(b.balance) - parseInt(a.balance));
const transactions = [];
const primaryCoin = coins[0];
// Group remaining coins into batches
for (let i = 1; i < coins.length; i += maxCoinsPerTx - 1) {
const batch = coins.slice(i, i + maxCoinsPerTx - 1);
const txb = new TransactionBlock();
txb.mergeCoins(
primaryCoin.coinObjectId,
batch.map(coin => coin.coinObjectId)
);
transactions.push({
transaction: txb,
description: `Merge ${batch.length} coins into primary coin`,
coinsToMerge: batch.map(c => c.coinObjectId)
});
}
return transactions;
}3. Coin Splitting Strategy
async function splitCoinOptimally(owner, coinType, targetSizes) {
const coins = await getAllCoinsWithPagination(owner, coinType);
if (coins.length === 0) {
throw new Error('No coins available for splitting');
}
// Find the largest coin that can accommodate all splits
const totalTargetAmount = targetSizes.reduce((sum, size) => sum + size, 0);
const suitableCoin = coins.find(coin =>
parseInt(coin.balance) >= totalTargetAmount
);
if (!suitableCoin) {
throw new Error('No single coin large enough for all splits');
}
const txb = new TransactionBlock();
// Split the coin into target sizes
const splitCoins = txb.splitCoins(
suitableCoin.coinObjectId,
targetSizes
);
return {
transaction: txb,
sourceCoin: suitableCoin.coinObjectId,
targetSizes: targetSizes,
splitCoins: splitCoins,
remainingBalance: parseInt(suitableCoin.balance) - totalTargetAmount
};
}4. Portfolio Rebalancing
async function rebalancePortfolio(owner, targetAllocations) {
const portfolioCoins = {};
// Get coins for each type in target allocation
for (const coinType of Object.keys(targetAllocations)) {
portfolioCoins[coinType] = await getAllCoinsWithPagination(owner, coinType);
}
// Calculate current balances
const currentBalances = {};
let totalValue = 0;
for (const [coinType, coins] of Object.entries(portfolioCoins)) {
const balance = coins.reduce((sum, coin) => sum + parseInt(coin.balance), 0);
currentBalances[coinType] = balance;
totalValue += balance; // Simplified - would need price conversion
}
// Calculate rebalancing requirements
const rebalancingActions = [];
for (const [coinType, targetPercent] of Object.entries(targetAllocations)) {
const currentBalance = currentBalances[coinType] || 0;
const targetBalance = (totalValue * targetPercent) / 100;
const difference = targetBalance - currentBalance;
if (Math.abs(difference) > totalValue * 0.01) { // 1% threshold
rebalancingActions.push({
coinType,
currentBalance,
targetBalance,
difference,
action: difference > 0 ? 'buy' : 'sell',
amount: Math.abs(difference)
});
}
}
return {
currentBalances,
targetAllocations,
totalValue,
rebalancingActions,
needsRebalancing: rebalancingActions.length > 0
};
}Advanced Coin Management
1. Gas-Efficient Coin Strategy
class GasOptimizedCoinManager {
constructor(client) {
this.client = client;
this.gasPrice = 750; // MIST per gas unit
this.baseTransactionCost = 1000000; // Base transaction cost
}
async calculateConsolidationValue(owner, coinType) {
const coins = await getAllCoinsWithPagination(owner, coinType);
if (coins.length <= 1) return null;
// Storage cost saved per coin consolidated
const storageRefundPerCoin = 988000; // Approximate MIST
const potentialSavings = (coins.length - 1) * storageRefundPerCoin;
// Gas cost of consolidation
const gasUnitsPerMerge = 1000; // Approximate
const totalGasCost = gasUnitsPerMerge * (coins.length - 1) * this.gasPrice;
const netBenefit = potentialSavings - totalGasCost;
return {
coinCount: coins.length,
potentialSavings,
consolidationCost: totalGasCost,
netBenefit,
worthConsolidating: netBenefit > 0,
breakEvenCoinCount: Math.ceil(totalGasCost / storageRefundPerCoin) + 1
};
}
async optimizeCoinSelection(owner, coinType, amount) {
const coins = await getAllCoinsWithPagination(owner, coinType);
// Sort by balance descending for greedy selection
coins.sort((a, b) => parseInt(b.balance) - parseInt(a.balance));
// Try different selection strategies
const strategies = [
this.greedySelection(coins, amount),
this.exactFitSelection(coins, amount),
this.minimizeChangeSelection(coins, amount)
];
// Evaluate each strategy's gas efficiency
const evaluatedStrategies = strategies.map(strategy => {
if (!strategy.coins) return { ...strategy, efficiency: 0 };
const mergeOperations = Math.max(0, strategy.coins.length - 1);
const gasCost = (this.baseTransactionCost + mergeOperations * 500) * this.gasPrice;
return {
...strategy,
gasCost,
efficiency: amount / gasCost, // Amount per gas unit
wasteRatio: strategy.change / amount
};
});
// Select the most efficient strategy
return evaluatedStrategies
.filter(s => s.coins && s.coins.length > 0)
.reduce((best, current) =>
current.efficiency > best.efficiency ? current : best
);
}
greedySelection(coins, amount) {
const selected = [];
let total = 0;
for (const coin of coins) {
selected.push(coin);
total += parseInt(coin.balance);
if (total >= amount) break;
}
return {
strategy: 'greedy',
coins: total >= amount ? selected : null,
total,
change: Math.max(0, total - amount),
success: total >= amount
};
}
exactFitSelection(coins, amount) {
// Try to find exact or near-exact match
const exactMatch = coins.find(coin => parseInt(coin.balance) === amount);
if (exactMatch) {
return {
strategy: 'exact',
coins: [exactMatch],
total: amount,
change: 0,
success: true
};
}
// Find smallest coin that covers the amount
const singleCoin = coins.find(coin => parseInt(coin.balance) >= amount);
if (singleCoin) {
return {
strategy: 'single',
coins: [singleCoin],
total: parseInt(singleCoin.balance),
change: parseInt(singleCoin.balance) - amount,
success: true
};
}
return { strategy: 'exact', coins: null, success: false };
}
minimizeChangeSelection(coins, amount) {
// Use dynamic programming to minimize change
let bestSelection = null;
let minChange = Infinity;
// Try combinations up to 5 coins (gas efficiency limit)
for (let maxCoins = 1; maxCoins <= Math.min(5, coins.length); maxCoins++) {
const result = this.findOptimalCombination(coins, amount, maxCoins);
if (result.success && result.change < minChange) {
minChange = result.change;
bestSelection = result;
}
}
return bestSelection || { strategy: 'minimize', coins: null, success: false };
}
findOptimalCombination(coins, amount, maxCoins, index = 0, current = []) {
if (current.length > maxCoins) return { success: false };
const currentTotal = current.reduce((sum, coin) => sum + parseInt(coin.balance), 0);
if (currentTotal >= amount) {
return {
strategy: 'minimize',
coins: [...current],
total: currentTotal,
change: currentTotal - amount,
success: true
};
}
if (index >= coins.length) return { success: false };
// Try including current coin
const withCoin = this.findOptimalCombination(
coins, amount, maxCoins, index + 1, [...current, coins[index]]
);
// Try without current coin
const withoutCoin = this.findOptimalCombination(
coins, amount, maxCoins, index + 1, current
);
// Return better result
if (!withCoin.success) return withoutCoin;
if (!withoutCoin.success) return withCoin;
return withCoin.change < withoutCoin.change ? withCoin : withoutCoin;
}
}2. Coin Health Monitoring
class CoinHealthMonitor {
constructor(client) {
this.client = client;
this.thresholds = {
fragmentationWarning: 10,
fragmentationCritical: 20,
dustThreshold: 1000000, // 0.001 SUI
consolidationThreshold: 5
};
}
async assessCoinHealth(owner, coinType) {
const coins = await getAllCoinsWithPagination(owner, coinType);
if (coins.length === 0) {
return { status: 'no_coins', message: 'No coins found' };
}
const totalBalance = coins.reduce((sum, coin) => sum + parseInt(coin.balance), 0);
const avgBalance = totalBalance / coins.length;
const balances = coins.map(coin => parseInt(coin.balance)).sort((a, b) => b - a);
// Calculate fragmentation metrics
const fragmentationScore = coins.length / Math.sqrt(avgBalance);
const dustCoins = coins.filter(coin => parseInt(coin.balance) < this.thresholds.dustThreshold);
const largestCoin = Math.max(...balances);
const smallestCoin = Math.min(...balances);
const balanceDistribution = this.calculateBalanceDistribution(balances);
// Determine health status
let status, recommendations = [];
if (coins.length === 1) {
status = 'optimal';
} else if (coins.length <= this.thresholds.consolidationThreshold) {
status = 'good';
} else if (coins.length <= this.thresholds.fragmentationWarning) {
status = 'warning';
recommendations.push('Consider consolidating coins to reduce gas costs');
} else {
status = 'critical';
recommendations.push('Urgent: High fragmentation detected');
recommendations.push('Consolidate coins to optimize gas usage');
}
// Add dust-specific recommendations
if (dustCoins.length > 0) {
recommendations.push(`${dustCoins.length} dust coins detected (< 0.001 SUI)`);
if (dustCoins.length >= 3) {
recommendations.push('Consolidate dust coins to recover storage deposits');
}
}
return {
status,
coinCount: coins.length,
totalBalance,
avgBalance,
largestCoin,
smallestCoin,
dustCoins: dustCoins.length,
fragmentationScore,
balanceDistribution,
recommendations,
healthScore: this.calculateHealthScore(coins.length, fragmentationScore, dustCoins.length)
};
}
calculateBalanceDistribution(balances) {
const total = balances.reduce((sum, b) => sum + b, 0);
const distribution = [];
for (let i = 0; i < Math.min(5, balances.length); i++) {
distribution.push({
rank: i + 1,
balance: balances[i],
percentage: (balances[i] / total) * 100
});
}
return distribution;
}
calculateHealthScore(coinCount, fragmentationScore, dustCount) {
let score = 100;
// Penalize for too many coins
if (coinCount > 5) score -= (coinCount - 5) * 5;
// Penalize for fragmentation
score -= Math.min(40, fragmentationScore * 2);
// Penalize for dust
score -= dustCount * 3;
return Math.max(0, Math.min(100, score));
}
}Performance Optimization Strategies
1. Intelligent Caching
class SmartCoinCache {
constructor(client, cacheTimeout = 30000) {
this.client = client;
this.cache = new Map();
this.cacheTimeout = cacheTimeout;
}
generateCacheKey(owner, coinType, cursor, limit) {
return `${owner}:${coinType || 'all'}:${cursor || 'start'}:${limit}`;
}
async getCoinsWithCache(owner, coinType, cursor, limit) {
const key = this.generateCacheKey(owner, coinType, cursor, limit);
const cached = this.cache.get(key);
if (cached && (Date.now() - cached.timestamp) < this.cacheTimeout) {
return cached.data;
}
const result = await this.client.getCoins({
owner,
coinType,
cursor,
limit
});
// Only cache successful results
if (result.data) {
this.cache.set(key, {
data: result,
timestamp: Date.now()
});
}
return result;
}
invalidateOwnerCache(owner) {
// Remove all cached entries for a specific owner
for (const [key] of this.cache) {
if (key.startsWith(`${owner}:`)) {
this.cache.delete(key);
}
}
}
getCacheStats() {
return {
size: this.cache.size,
keys: Array.from(this.cache.keys())
};
}
}2. Batch Processing for Multiple Owners
async function batchProcessOwners(owners, coinType = null) {
const batchSize = 5; // Process 5 owners concurrently
const results = new Map();
for (let i = 0; i < owners.length; i += batchSize) {
const batch = owners.slice(i, i + batchSize);
const batchPromises = batch.map(async owner => {
try {
const coins = await getAllCoinsWithPagination(owner, coinType);
return { owner, coins, success: true };
} catch (error) {
return { owner, error: error.message, success: false };
}
});
const batchResults = await Promise.all(batchPromises);
batchResults.forEach(result => {
results.set(result.owner, result);
});
console.log(`Processed batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(owners.length / batchSize)}`);
// Rate limiting between batches
if (i + batchSize < owners.length) {
await new Promise(resolve => setTimeout(resolve, 200));
}
}
return results;
}Error Handling and Edge Cases
1. Robust Error Handling
async function robustGetCoins(owner, coinType = null, maxRetries = 3) {
let attempt = 0;
let lastError;
while (attempt < maxRetries) {
try {
// Validate inputs
if (!/^0x[a-fA-F0-9]{64}$/.test(owner)) {
throw new Error('Invalid owner address format');
}
if (coinType && !/^0x[a-fA-F0-9]+::\w+::\w+$/.test(coinType)) {
throw new Error('Invalid coin type format');
}
const result = await client.getCoins({
owner,
coinType,
limit: 50
});
// Validate response structure
if (!result.data || !Array.isArray(result.data)) {
throw new Error('Invalid response structure');
}
// Validate coin objects
const validCoins = result.data.filter(coin => {
return coin.coinObjectId &&
coin.coinType &&
coin.balance &&
!isNaN(parseInt(coin.balance));
});
if (validCoins.length !== result.data.length) {
console.warn(`Filtered ${result.data.length - validCoins.length} invalid coins`);
}
return {
...result,
data: validCoins
};
} catch (error) {
attempt++;
lastError = error;
console.warn(`Attempt ${attempt} failed:`, error.message);
if (attempt < maxRetries) {
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw new Error(`Failed after ${maxRetries} attempts: ${lastError?.message}`);
}Related Methods
- suix_getBalance - Get total balance for specific coin type
- suix_getAllBalances - Get all coin balances for address
- sui_multiGetObjects - Batch query multiple objects
Best Practices
1. Efficient Pagination
- Use appropriate page sizes: 20-50 items for UI, larger for batch processing
- Implement cursor-based navigation for better user experience
- Cache pagination results to avoid repeated queries
- Handle incomplete pages gracefully at the end of datasets
2. Transaction Optimization
- Select minimal coins needed for transactions to reduce gas costs
- Prefer single coin transactions when possible
- Consolidate fragmented coins during low network activity
- Consider gas costs when selecting between multiple viable coin combinations
3. Performance Management
- Cache frequently accessed data with appropriate TTL
- Batch multiple owner queries to reduce API load
- Use filtering to reduce unnecessary data transfer
- Implement retry logic with exponential backoff
4. User Experience
- Show loading states during pagination
- Display meaningful coin information (balance, type, age)
- Provide consolidation suggestions for fragmented portfolios
- Handle edge cases like zero balances gracefully
5. Security Considerations
- Validate all inputs before making RPC calls
- Handle rate limiting appropriately
- Don't expose sensitive coin selection logic to untrusted code
- Implement proper error boundaries to prevent application crashes
Need help? Contact our support team or check the Sui documentation.
suix_getCoinMetadata - Get Coin Metadata
Get comprehensive metadata for any coin type on Sui blockchain including decimals, symbol, name, and icon URL using Dwellir's high-performance Sui RPC infrastructure.
suix_getCommitteeInfo - Get Validator Co...
Get detailed validator committee information on Sui blockchain. Query committee composition, validator details, and governance data with Dwellir's high-performance Sui RPC infrastructure.