Skip to main content

suix_getCoins

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.

Parameters​

ParameterTypeRequiredDescription
ownerstringYesThe Sui address to query (66-character hex string with 0x prefix)
coinTypestringNoFilter by specific coin type (defaults to all types)
cursorstringNoPagination cursor from previous response
limitnumberNoMaximum number of coins to return (default: 50, max: 100)

Coin Type Filter​

  • All coins: Omit coinType parameter or pass null
  • Specific type: e.g., "0x2::sui::SUI" for native SUI
  • Custom tokens: e.g., "0xpackage::module::TYPE"

Pagination​

  • First request: Omit cursor parameter
  • Subsequent requests: Use nextCursor from previous response
  • Page size: Set limit (1-100, default 50)

Returns​

Returns a paginated response containing coin objects and pagination metadata.

FieldTypeDescription
dataarrayArray of coin objects
nextCursorstringCursor for next page (null if no more pages)
hasNextPagebooleanWhether more pages are available

Coin Object Structure​

Each coin object contains:

FieldTypeDescription
coinTypestringThe type of coin (e.g., "0x2::sui::SUI")
coinObjectIdstringUnique identifier of the coin object
versionstringCurrent version/sequence number
digeststringObject content digest
balancestringBalance of this specific coin object
previousTransactionstringPrevious transaction that modified this coin

Code Examples​

# Get all coins (first page)
curl -X POST https://sui-mainnet.dwellir.com/YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "suix_getCoins",
"params": [
"0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f",
null,
null,
50
],
"id": 1
}'

# Get SUI coins only
curl -X POST https://sui-mainnet.dwellir.com/YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "suix_getCoins",
"params": [
"0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f",
"0x2::sui::SUI",
null,
20
],
"id": 1
}'

# Get next page using cursor
curl -X POST https://sui-mainnet.dwellir.com/YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "suix_getCoins",
"params": [
"0xd77955e670601c2c2e6e8637e383695c166aac0a86b741c266bdfb23c2e3369f",
null,
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
50
],
"id": 1
}'

Response Example​

{
"jsonrpc": "2.0",
"id": 1,
"result": {
"data": [
{
"coinType": "0x2::sui::SUI",
"coinObjectId": "0x5d3c87e88bc566e3f10c66e0275a366001ffa8b86142adc78c744de6afffeb34",
"version": "31823924",
"digest": "HpSeCiMLG53N9FcHDrRTxwGhc4RVJa1seZhXYJ7KFpJe",
"balance": "1000000000",
"previousTransaction": "C2QKmxrYPNBJjNmLtZrzFt9tVfq7wo2JZBmc5nQKYFdt"
},
{
"coinType": "0x2::sui::SUI",
"coinObjectId": "0x7a8b9c0d1e2f3456789012345678901234567890123456789012345678901234",
"version": "18234567",
"digest": "8kRvNmPdY2zCQh7vWxEqGsKjFp9LtQbN5nMcAuDeF1vR",
"balance": "500000000",
"previousTransaction": "9mLkPqR7XzNy2bVcDwFeAsKjHp8LtQbN5nMcAuDeF1vR"
},
{
"coinType": "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC",
"coinObjectId": "0x3e4f5a6b7c8d9012345678901234567890123456789012345678901234567890",
"version": "12345678",
"digest": "4jNvPqR8YzCx5bWdEwGeAsKjHp9LtQbN5nMcAuDeF1vR",
"balance": "1000000",
"previousTransaction": "5oMpRqS9ZaDy6cXeExHfBtLkIq0MuRcO6oNdBvEgG2wS"
}
],
"nextCursor": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJvYmplY3RJZCI6IjB4M2U0ZjVhNmI3YzhkOTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwIn0.signature",
"hasNextPage": true
}
}

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

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.