Sui distinguishes between a Balance—a scalar amount of a coin type held by an address—and Coin objects, which are individual owned objects that sum to that balance. The JSON-RPC methods sui_getBalance and sui_getAllBalances return balances, while sui_getCoins enumerates the underlying coin objects. Metadata such as decimals and symbol comes from sui_getCoinMetadata, and decimals is required to convert raw integer balances into human-readable amounts. This guide explains the coin model, shows runnable Node.js examples using the Sui TypeScript SDK, and provides a reproducible results table to verify your endpoint's behavior.
Sui's Coin Model: Balance Versus Coin Objects
In Sui, a Balance is a scalar value representing how much of a specific coin type an address holds. It is not an object itself; it is a derived quantity that the network computes from the set of Coin objects owned by that address. The JSON-RPC method sui_getBalance returns this scalar for a given coin type, and sui_getAllBalances returns all balances for all coin types held by an address.
A Coin is an individual owned object of a specific coin type. Each Coin object has its own object ID, version, and a balance field. The sum of the balance fields of all Coin objects of a given coin type owned by an address equals the Balance returned by sui_getBalance. The method sui_getCoins enumerates these Coin objects, paginated by a cursor.
Confusing these two concepts leads to incorrect totals. If you sum the balances of Coin objects returned by sui_getCoins but miss some due to pagination, you will undercount. If you treat the Balance as an object ID, you will fail to construct valid transactions. The Sui documentation on coin concepts explains this distinction in detail.
- Balance: scalar amount per coin type, returned by
sui_getBalanceandsui_getAllBalances. - Coin object: individual owned object with its own ID and balance, enumerated by
sui_getCoins. - The sum of Coin object balances equals the Balance for that coin type.
coinObjectCountin the balance response indicates how many Coin objects back that balance.
Coin Type Identifiers and Why They Are the Query Key
Every coin on Sui is identified by a fully qualified Move type string, not by a symbol. For the native token, the coin type is 0x2::sui::SUI. For custom coins, the format is <packageId>::<module>::<struct>, for example 0x2::sui::SUI or 0x1234...::my_coin::MY_COIN. This string is the query key for all coin-related RPC methods.
Symbols such as "SUI" or "USDC" are metadata, not identifiers. Two different coin types could theoretically share a symbol, and symbols can change if the metadata is updated. Always use the coin type string when calling sui_getBalance, sui_getCoinMetadata, or sui_getCoins.
Coin type strings are network-specific. A coin type that exists on Sui Mainnet may not exist on Testnet or Devnet. When building applications, ensure the coin type is configurable or derived from the network context. The Sui JSON-RPC API reference lists the exact parameter formats for each method.
- Native token:
0x2::sui::SUI. - Custom coin:
<packageId>::<module>::<struct>. - Symbols are metadata; coin types are identifiers.
- Coin types differ across networks (Mainnet, Testnet, Devnet).
Reading Coin Metadata: Decimals, Symbol, and Name
The method sui_getCoinMetadata returns the metadata for a coin type: decimals, name, symbol, description, and iconUrl. The decimals field is critical because raw balances are integers. To convert a raw balance to a human-readable amount, divide by 10 raised to the power of decimals.
For example, if decimals is 9, a raw balance of 1,000,000,000 represents 1.0 SUI. If decimals is 6, a raw balance of 1,000,000 represents 1.0 USDC. Without the correct decimals, displayed amounts will be off by orders of magnitude.
Metadata may be missing for some coins, especially those with poorly formed or unregistered metadata. In such cases, sui_getCoinMetadata may return null or an error. Your application should handle missing metadata gracefully, perhaps by falling back to a default decimals value or displaying the raw amount with a warning.
decimalsis required for human-readable conversion:raw / 10^decimals.symbolandnameare for display only; do not use them as query keys.iconUrlpoints to an image; validate the URL before rendering.- Missing metadata is possible; handle
nullresponses.
Querying Balances: sui_getBalance and sui_getAllBalances
sui_getBalance takes an owner address and a coin type, and returns an object with coinType, coinObjectCount, totalBalance, and lockedBalance. The totalBalance is the sum of all Coin object balances for that coin type. The lockedBalance represents coins that are locked, for example due to vesting or staking, and are not immediately spendable.
sui_getAllBalances takes only an owner address and returns an array of such balance objects for all coin types held by that address. This is useful for portfolio views or when you do not know which coin types an address holds.
Both methods reflect the state of the node's current checkpoint. If a transaction was just submitted and not yet included in a checkpoint, the balance may not reflect it. For consistent reads, you can specify a checkpoint sequence number if the method supports it, or wait for the transaction to be finalized.
sui_getBalancereturnscoinType,coinObjectCount,totalBalance,lockedBalance.lockedBalancemust be excluded when computing spendable funds.sui_getAllBalancesreturns an array of balance objects for all coin types.- Reads reflect the node's current checkpoint; recent transactions may not be included.
Enumerating Coin Objects with sui_getCoins and Pagination
sui_getCoins returns a paginated list of Coin objects for a given owner and coin type. Each page includes a data array of Coin objects and a nextCursor field. To enumerate all Coin objects, you must repeatedly call the method with the cursor until nextCursor is null or hasNextPage is false.
The number of Coin objects returned by sui_getCoins may differ from the coinObjectCount in the balance response if some Coin objects are locked. Locked coins are still owned but may not be returned by sui_getCoins depending on the node's implementation. Always cross-check the sum of returned Coin object balances against the totalBalance to detect discrepancies.
Pagination is essential for addresses with many Coin objects. A single call may return only a limited number of objects, and the limit is provider-specific. The Sui TypeScript SDK provides helper methods that handle pagination automatically, but understanding the cursor mechanism is important for raw JSON-RPC usage.
sui_getCoinsreturnsdataandnextCursor; loop untilnextCursoris null.- The count may differ from
coinObjectCountif some coins are locked. - Sum the
balancefields of returned Coin objects to verify againsttotalBalance. - Page size limits vary by provider; do not assume a fixed maximum.
Runnable Example: Reading SUI Balance and Metadata
The following Node.js example uses the Sui TypeScript SDK to read the SUI balance for an address, fetch the SUI coin metadata, and convert the raw balance to a human-readable amount. It assumes you have installed @mysten/sui and have a Sui RPC endpoint URL.
Replace YOUR_RPC_URL with your provider's endpoint, such as an OnFinality Sui endpoint. The example uses getBalance, getCoinMetadata, and getCoins from the SDK's client. The SDK methods map directly to the JSON-RPC methods described above.
import { SuiClient, getFullnodeUrl } from '@mysten/sui/client';
const client = new SuiClient({ url: 'YOUR_RPC_URL' });
const address = '0xYOUR_ADDRESS';
const coinType = '0x2::sui::SUI';
async function main() {
// 1. Get balance for SUI
const balance = await client.getBalance({ owner: address, coinType });
console.log('Raw totalBalance:', balance.totalBalance);
console.log('coinObjectCount:', balance.coinObjectCount);
console.log('lockedBalance:', balance.lockedBalance);
// 2. Get coin metadata
const metadata = await client.getCoinMetadata({ coinType });
if (!metadata) {
console.error('Metadata not found for', coinType);
return;
}
console.log('Decimals:', metadata.decimals);
console.log('Symbol:', metadata.symbol);
// 3. Convert raw balance to human-readable
const humanReadable = Number(balance.totalBalance) / Math.pow(10, metadata.decimals);
console.log('Human-readable balance:', humanReadable, metadata.symbol);
// 4. Page through coin objects and sum
let cursor = null;
let sum = 0n;
let count = 0;
do {
const page = await client.getCoins({ owner: address, coinType, cursor });
for (const coin of page.data) {
sum += BigInt(coin.balance);
count++;
}
cursor = page.nextCursor;
} while (cursor);
console.log('Sum of coin objects:', sum.toString());
console.log('Number of coin objects:', count);
console.log('Matches totalBalance:', sum.toString() === balance.totalBalance);
}
main().catch(console.error);Runnable Example: Raw JSON-RPC Calls with curl
If you prefer raw JSON-RPC, you can use curl to call the same methods. The following example calls sui_getBalance and sui_getCoinMetadata for SUI on a given address. Replace YOUR_RPC_URL and 0xYOUR_ADDRESS with your endpoint and address.
The JSON-RPC 2.0 specification defines the request format: a jsonrpc version, a method, params, and an id. The Sui JSON-RPC API follows this specification. Responses include either a result or an error object.
curl -X POST YOUR_RPC_URL \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "sui_getBalance",
"params": ["0xYOUR_ADDRESS", "0x2::sui::SUI"]
}'
curl -X POST YOUR_RPC_URL \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "sui_getCoinMetadata",
"params": ["0x2::sui::SUI"]
}'Reproducible Results Table for Your Endpoint
To verify your endpoint's behavior and document your own measurements, fill in the following table with data from your RPC provider. Use a known address and coin type, and run the methods described above. This table is for your own records; it is not a benchmark provided by OnFinality.
Record the raw totalBalance, the decimals from metadata, the human-readable amount, the coinObjectCount, and whether the sum of Coin objects matches the totalBalance. If they do not match, investigate locked balances or pagination issues.
- Coin type: e.g.,
0x2::sui::SUI - Raw totalBalance: integer from
sui_getBalance - Decimals: from
sui_getCoinMetadata - Human-readable amount:
raw / 10^decimals - Coin object count: from
coinObjectCountor count ofsui_getCoinsresults - Sum of coin objects: sum of
balancefields fromsui_getCoins - Match: yes/no
Limitations and Tradeoffs in Coin Reads
Coin type strings are network-specific. A coin type that exists on Mainnet may not exist on Testnet. Hardcoding coin types can break when switching networks. Always make coin types configurable or derive them from the network context.
Decimals come from metadata, which may be missing or incorrect for poorly formed coins. If sui_getCoinMetadata returns null, you cannot reliably convert the raw balance to a human-readable amount. In such cases, display the raw amount or use a fallback decimals value with a clear warning.
lockedBalance must be excluded when computing spendable funds. The totalBalance includes locked coins, but only the difference between totalBalance and lockedBalance is immediately spendable. Failing to account for this can lead to failed transactions.
The RPC state reflects the node's current checkpoint. A read immediately after submitting a transaction may not include that transaction if it has not yet been checkpointed. For time-sensitive operations, poll until the transaction is finalized or use a checkpoint-specific read if supported.
- Coin types are network-specific; do not hardcode across networks.
- Missing metadata prevents accurate decimal conversion.
- Exclude
lockedBalancefor spendable funds. - Reads reflect the node's current checkpoint; recent transactions may lag.
Troubleshooting Common Coin Read Issues
If sui_getBalance returns a totalBalance of zero for an address you expect to hold funds, verify the coin type string. A typo in the package ID or module name will result in a zero balance. Also confirm the address is correct and that you are querying the right network.
If sui_getCoinMetadata returns null, the coin may not have metadata registered. Check the coin type string and try a different coin. If the metadata exists but decimals seems wrong, verify the coin type against the official source.
If the sum of Coin objects from sui_getCoins does not match totalBalance, check for locked coins. Some Coin objects may be locked and not returned by sui_getCoins. Also ensure you are paginating through all pages; a missed page will undercount.
If you receive a nextCursor that never becomes null, you may be in an infinite loop due to a provider bug or incorrect cursor handling. Always include a maximum iteration limit and log the cursor values for debugging.
- Zero balance: check coin type, address, and network.
- Null metadata: coin may lack metadata; handle gracefully.
- Sum mismatch: check for locked coins and complete pagination.
- Infinite pagination: add a max iteration limit and log cursors.
Next Steps: Integrating Coin Reads into Your Application
Now that you can read balances and metadata, you can build features such as portfolio trackers, payment flows, and token gates. For a broader overview of Sui RPC methods, see the Sui RPC guide (RPC Assistant). To understand how coin objects fit into the object model, read Reading Sui objects, dynamic fields, and pagination.
When querying transaction history, use Sui queryTransactionBlocks cursor pagination to handle large result sets. For understanding object versions and concurrency, see Sui object versions and Lamport ordering. To parse transaction effects and object changes, refer to Sui transaction effects and object changes.
For production deployments, consider using a reliable RPC provider. OnFinality offers Sui network access with RPC pricing and an API service. Explore more guides on the OnFinality Learn hub.
- Use coin reads for portfolio trackers, payments, and token gates.
- Handle pagination and locked balances correctly.
- Choose a reliable RPC provider for production.
- Explore more Sui guides on OnFinality Learn.