GetBalance
Query Coin Balances with High-Performance gRPC#
The GetBalance method from the Live Data Service provides efficient querying of coin balances for any address on the Sui blockchain. Using gRPC's binary Protocol Buffer encoding, this method delivers balance information with minimal latency and bandwidth consumption, making it ideal for real-time balance tracking and high-frequency queries.
Overview#
Balance queries are fundamental to blockchain applications. Whether you're building wallets, DeFi protocols, or portfolio trackers, efficient balance retrieval is critical for user experience. The gRPC GetBalance method offers significant performance advantages over JSON-RPC, with reduced payload sizes and faster serialization for applications requiring frequent balance checks.
Key Features#
- High Performance: Binary Protocol Buffers reduce latency and bandwidth usage
- Type Safety: Coin types validated at compile time through proto definitions
- Simple Interface: Single request returns aggregated balance across all coin objects
- Multi-Token Support: Query any coin type including SUI, USDC, and custom tokens
- Instant Results: Sub-millisecond response times for balance queries
Method Signature#
Service: sui.rpc.v2beta2.LiveDataService
Method: GetBalance
Type: Unary RPC (single request, single response)
Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
owner | string | Yes | The Sui address to query (66-character hex string with 0x prefix) |
coin_type | string | Yes | The coin type to query (package::module::type format) |
Coin Type Format#
Coin types follow the Sui Move type format: package_id::module::type
Common Coin Types:
| Token | Coin Type |
|---|---|
| SUI | 0x2::sui::SUI |
| USDC | 0x...::usdc::USDC (varies by package) |
| Custom | 0xpackage_id::module::COIN_NAME |
Example Addresses#
0xac5bceec1b789ff840d7d4e6ce4ce61c90d190a7f8c4f4ddf0bff6ee2413c33c // User wallet
0x0000000000000000000000000000000000000000000000000000000000000000 // Zero address
Response Structure#
The method returns a Balance message:
message Balance {
string coin_type = 1; // The coin type queried
string balance = 2; // Total balance as string (in base units)
}
Response Fields#
| Field | Type | Description |
|---|---|---|
coin_type | string | The full coin type identifier that was queried |
balance | string | Total balance in the smallest unit (e.g., MIST for SUI) |
Balance Units#
- SUI: Balances returned in MIST (1 SUI = 1,000,000,000 MIST or 10^9)
- Custom Tokens: Check coin metadata for decimal places
- Always use arbitrary-precision arithmetic to avoid overflow
Code Examples#
- TypeScript
- Python
- Go
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
// Configuration
const ENDPOINT = 'api-sui-mainnet-full.n.dwellir.com';
const API_TOKEN = 'your_api_token_here';
// Load proto definition
const packageDefinition = protoLoader.loadSync(
'./protos/live_data.proto',
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: ['./protos']
}
);
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as any;
// Create client with TLS
const credentials = grpc.credentials.createSsl();
const client = new protoDescriptor.sui.rpc.v2beta2.LiveDataService(
ENDPOINT,
credentials
);
// Setup authentication
const metadata = new grpc.Metadata();
metadata.add('x-api-key', API_TOKEN);
// Query balance
async function getBalance(owner: string, coinType: string): Promise<any> {
return new Promise((resolve, reject) => {
const request = {
owner: owner,
coin_type: coinType
};
client.GetBalance(request, metadata, (error: any, response: any) => {
if (error) {
console.error('GetBalance error:', error.message);
reject(error);
return;
}
resolve(response);
});
});
}
// Convert MIST to SUI
function mistToSui(mist: string): string {
const balance = BigInt(mist);
const sui = Number(balance) / 1_000_000_000;
return sui.toFixed(9);
}
// Example usage
async function main() {
try {
const address = '0xac5bceec1b789ff840d7d4e6ce4ce61c90d190a7f8c4f4ddf0bff6ee2413c33c';
// Query SUI balance
const suiBalance = await getBalance(address, '0x2::sui::SUI');
console.log('Balance Information:');
console.log('===================');
console.log('Address:', address);
console.log('Coin Type:', suiBalance.coin_type);
console.log('Balance (MIST):', suiBalance.balance);
console.log('Balance (SUI):', mistToSui(suiBalance.balance));
// Query custom token balance
const usdcType = '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';
const usdcBalance = await getBalance(address, usdcType);
console.log('\nUSDC Balance:');
console.log('Balance:', usdcBalance.balance);
console.log('Formatted:', Number(usdcBalance.balance) / 1_000_000, 'USDC');
} catch (error) {
console.error('Failed to get balance:', error);
}
}
main();
import grpc
import live_data_service_pb2
import live_data_service_pb2_grpc
from decimal import Decimal
# Configuration
ENDPOINT = 'api-sui-mainnet-full.n.dwellir.com'
API_TOKEN = 'your_api_token_here'
class SuiBalanceChecker:
def __init__(self, endpoint: str, api_token: str):
self.endpoint = endpoint
self.api_token = api_token
self.channel = None
self.client = None
def connect(self):
"""Establish secure gRPC connection"""
credentials = grpc.ssl_channel_credentials()
self.channel = grpc.secure_channel(self.endpoint, credentials)
self.client = live_data_service_pb2_grpc.LiveDataServiceStub(self.channel)
def get_balance(self, owner: str, coin_type: str):
"""
Query balance for a specific coin type
Args:
owner: Sui wallet address
coin_type: Coin type identifier (e.g., '0x2::sui::SUI')
Returns:
Balance response message
"""
if not self.client:
self.connect()
# Setup authentication
metadata = [('x-api-key', self.api_token)]
# Build request
request = live_data_service_pb2.GetBalanceRequest(
owner=owner,
coin_type=coin_type
)
try:
response = self.client.GetBalance(request, metadata=metadata, timeout=10.0)
return response
except grpc.RpcError as e:
print(f'gRPC Error: {e.code()}: {e.details()}')
raise
@staticmethod
def mist_to_sui(mist: str) -> Decimal:
"""Convert MIST to SUI (1 SUI = 10^9 MIST)"""
balance = Decimal(mist)
return balance / Decimal(10 ** 9)
@staticmethod
def format_balance(balance_str: str, decimals: int = 9) -> Decimal:
"""Format balance with custom decimal places"""
balance = Decimal(balance_str)
return balance / Decimal(10 ** decimals)
def close(self):
"""Close gRPC channel"""
if self.channel:
self.channel.close()
# Example usage
def main():
checker = SuiBalanceChecker(ENDPOINT, API_TOKEN)
try:
address = '0xac5bceec1b789ff840d7d4e6ce4ce61c90d190a7f8c4f4ddf0bff6ee2413c33c'
# Query SUI balance
sui_balance = checker.get_balance(address, '0x2::sui::SUI')
print('Balance Information:')
print('===================')
print(f'Address: {address}')
print(f'Coin Type: {sui_balance.coin_type}')
print(f'Balance (MIST): {sui_balance.balance}')
print(f'Balance (SUI): {checker.mist_to_sui(sui_balance.balance)}')
# Query custom token balance
usdc_type = '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC'
usdc_balance = checker.get_balance(address, usdc_type)
print(f'\nUSDC Balance:')
print(f'Balance: {usdc_balance.balance}')
print(f'Formatted: {checker.format_balance(usdc_balance.balance, 6)} USDC')
except Exception as e:
print(f'Error: {e}')
finally:
checker.close()
if __name__ == '__main__':
main()
package main
import (
"context"
"fmt"
"log"
"time"
"sui-grpc-client/config"
pb "sui-grpc-client/sui/rpc/v2"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
)
func main() {
// Load configuration from .env file
cfg, err := config.Load()
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
// Create TLS credentials
creds := credentials.NewClientTLSFromCert(nil, "")
// Connect to Dwellir
conn, err := grpc.NewClient(
cfg.Endpoint,
grpc.WithTransportCredentials(creds),
)
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
// Create state service client
client := pb.NewStateServiceClient(conn)
// Add authentication
ctx := metadata.AppendToOutgoingContext(
context.Background(),
"x-api-key", cfg.APIKey,
)
// Set timeout
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
// Example address (you can change this to any Sui address)
// Using a system object owner address
owner := "0x0000000000000000000000000000000000000000000000000000000000000000"
// Get SUI balance for the address
// SUI coin type: 0x2::sui::SUI
coinType := "0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI"
request := &pb.GetBalanceRequest{
Owner: &owner,
CoinType: &coinType,
}
response, err := client.GetBalance(ctx, request)
if err != nil {
log.Fatalf("Failed to get balance: %v", err)
}
// Display balance information
fmt.Println("Balance Information:")
fmt.Println("====================")
fmt.Printf("Owner: %s\n", owner)
balance := response.GetBalance()
if balance != nil {
fmt.Printf("Coin Type: %s\n", balance.GetCoinType())
balanceAmount := balance.GetBalance()
fmt.Printf("Balance: %d MIST\n", balanceAmount)
fmt.Printf("Balance: %.4f SUI\n", float64(balanceAmount)/1_000_000_000)
} else {
fmt.Println("No balance data returned (owner may not have this coin type)")
}
}
Use Cases#
1. Real-Time Wallet Balance Display#
Monitor wallet balances with low latency:
class WalletBalanceMonitor {
private client: any;
private metadata: grpc.Metadata;
private cache: Map<string, { balance: string; timestamp: number }>;
constructor(client: any, metadata: grpc.Metadata) {
this.client = client;
this.metadata = metadata;
this.cache = new Map();
}
async getBalanceWithCache(
owner: string,
coinType: string,
maxAge: number = 5000
): Promise<string> {
const cacheKey = `${owner}:${coinType}`;
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < maxAge) {
return cached.balance;
}
const response = await this.client.GetBalance(
{ owner, coin_type: coinType },
this.metadata
);
this.cache.set(cacheKey, {
balance: response.balance,
timestamp: Date.now()
});
return response.balance;
}
mistToSui(mist: string): string {
return (Number(mist) / 1_000_000_000).toFixed(9);
}
}
2. Multi-Token Portfolio Tracker#
Query balances for multiple tokens:
interface TokenBalance {
symbol: string;
coinType: string;
balance: string;
formatted: string;
decimals: number;
}
async function getPortfolioBalances(
address: string,
tokens: Array<{ symbol: string; coinType: string; decimals: number }>
): Promise<TokenBalance[]> {
const balances: TokenBalance[] = [];
for (const token of tokens) {
try {
const response = await client.GetBalance(
{ owner: address, coin_type: token.coinType },
metadata
);
const formatted = (
Number(response.balance) / Math.pow(10, token.decimals)
).toFixed(token.decimals);
balances.push({
symbol: token.symbol,
coinType: token.coinType,
balance: response.balance,
formatted: formatted,
decimals: token.decimals
});
} catch (error) {
console.error(`Failed to get ${token.symbol} balance:`, error);
balances.push({
symbol: token.symbol,
coinType: token.coinType,
balance: '0',
formatted: '0',
decimals: token.decimals
});
}
}
return balances;
}
// Usage
const tokens = [
{ symbol: 'SUI', coinType: '0x2::sui::SUI', decimals: 9 },
{ symbol: 'USDC', coinType: '0x...::usdc::USDC', decimals: 6 },
{ symbol: 'WETH', coinType: '0x...::weth::WETH', decimals: 8 }
];
const portfolio = await getPortfolioBalances(userAddress, tokens);
console.table(portfolio);
3. Balance Threshold Alerts#
Monitor balances and trigger alerts:
async function monitorBalanceThreshold(
address: string,
coinType: string,
threshold: string,
callback: (balance: string) => void
): Promise<void> {
const checkBalance = async () => {
try {
const response = await client.GetBalance(
{ owner: address, coin_type: coinType },
metadata
);
if (BigInt(response.balance) < BigInt(threshold)) {
callback(response.balance);
}
} catch (error) {
console.error('Balance check failed:', error);
}
};
// Check every 30 seconds
setInterval(checkBalance, 30000);
await checkBalance(); // Initial check
}
// Usage
monitorBalanceThreshold(
userAddress,
'0x2::sui::SUI',
'1000000000', // 1 SUI in MIST
(balance) => {
console.warn(`⚠️ Low balance alert: ${balance} MIST`);
// Send notification
}
);
4. Transaction Fee Estimation#
Check if address has sufficient balance for gas:
async function hasSufficientGas(
address: string,
requiredGas: string
): Promise<boolean> {
const response = await client.GetBalance(
{ owner: address, coin_type: '0x2::sui::SUI' },
metadata
);
return BigInt(response.balance) >= BigInt(requiredGas);
}
// Usage
const gasRequired = '10000000'; // 0.01 SUI
const hasGas = await hasSufficientGas(userAddress, gasRequired);
if (!hasGas) {
throw new Error('Insufficient gas for transaction');
}
Performance Optimization#
Connection Pooling#
Reuse gRPC connections across balance queries:
class BalanceService {
private static instance: BalanceService;
private client: any;
private metadata: grpc.Metadata;
private constructor() {
const credentials = grpc.credentials.createSsl();
this.client = new protoDescriptor.sui.rpc.v2beta2.LiveDataService(
ENDPOINT,
credentials
);
this.metadata = new grpc.Metadata();
this.metadata.add('x-api-key', API_TOKEN);
}
static getInstance(): BalanceService {
if (!BalanceService.instance) {
BalanceService.instance = new BalanceService();
}
return BalanceService.instance;
}
async getBalance(owner: string, coinType: string): Promise<any> {
return new Promise((resolve, reject) => {
this.client.GetBalance(
{ owner, coin_type: coinType },
this.metadata,
(error: any, response: any) => {
if (error) reject(error);
else resolve(response);
}
);
});
}
}
Batch Balance Queries#
Query multiple balances in parallel:
async function getBatchBalances(
addresses: string[],
coinType: string
): Promise<Map<string, string>> {
const balances = new Map<string, string>();
const promises = addresses.map(async (address) => {
try {
const response = await client.GetBalance(
{ owner: address, coin_type: coinType },
metadata
);
balances.set(address, response.balance);
} catch (error) {
balances.set(address, '0');
}
});
await Promise.all(promises);
return balances;
}
Error Handling#
Handle common balance query errors:
async function safeGetBalance(owner: string, coinType: string) {
try {
const response = await client.GetBalance(
{ owner, coin_type: coinType },
metadata
);
return {
success: true,
balance: response.balance,
coinType: response.coin_type
};
} catch (error: any) {
switch (error.code) {
case grpc.status.INVALID_ARGUMENT:
return {
success: false,
error: 'Invalid address or coin type format'
};
case grpc.status.NOT_FOUND:
// Address has no balance for this coin type
return {
success: true,
balance: '0',
coinType: coinType
};
case grpc.status.PERMISSION_DENIED:
return {
success: false,
error: 'Authentication failed. Check API token.'
};
case grpc.status.DEADLINE_EXCEEDED:
return {
success: false,
error: 'Request timeout. Try again.'
};
default:
return {
success: false,
error: `Unexpected error: ${error.message}`
};
}
}
}
Best Practices#
1. Use BigInt for Arithmetic#
Avoid precision loss with large numbers:
// ✅ Good: Use BigInt for calculations
const balance1 = BigInt('999999999999999999');
const balance2 = BigInt('1');
const total = balance1 + balance2;
// ❌ Bad: JavaScript numbers lose precision
const balance1 = 999999999999999999; // Precision lost!
const balance2 = 1;
const total = balance1 + balance2;
2. Cache Balance Data#
Reduce API calls with intelligent caching:
interface CachedBalance {
balance: string;
timestamp: number;
coinType: string;
}
class BalanceCache {
private cache: Map<string, CachedBalance> = new Map();
private readonly ttl: number = 10000; // 10 seconds
set(owner: string, coinType: string, balance: string): void {
const key = `${owner}:${coinType}`;
this.cache.set(key, {
balance,
coinType,
timestamp: Date.now()
});
}
get(owner: string, coinType: string): string | null {
const key = `${owner}:${coinType}`;
const cached = this.cache.get(key);
if (!cached) return null;
if (Date.now() - cached.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}
return cached.balance;
}
}
3. Handle Zero Balances#
Distinguish between zero balance and errors:
async function getBalanceOrZero(
owner: string,
coinType: string
): Promise<string> {
try {
const response = await client.GetBalance(
{ owner, coin_type: coinType },
metadata
);
return response.balance;
} catch (error: any) {
if (error.code === grpc.status.NOT_FOUND) {
return '0'; // Address has no coins of this type
}
throw error; // Re-throw other errors
}
}
4. Validate Inputs#
Check parameters before making requests:
function isValidAddress(address: string): boolean {
return /^0x[a-f0-9]{64}$/i.test(address);
}
function isValidCoinType(coinType: string): boolean {
return /^0x[a-f0-9]+::\w+::\w+$/i.test(coinType);
}
async function getBalanceWithValidation(owner: string, coinType: string) {
if (!isValidAddress(owner)) {
throw new Error('Invalid Sui address format');
}
if (!isValidCoinType(coinType)) {
throw new Error('Invalid coin type format');
}
return await client.GetBalance({ owner, coin_type: coinType }, metadata);
}
Related Methods#
- GetCoinInfo - Get coin metadata and decimals
- ListBalances - List all coin balances for an address
- ListOwnedObjects - List all objects owned by address
Performance Comparison#
gRPC vs JSON-RPC#
| Metric | gRPC GetBalance | JSON-RPC suix_getBalance |
|---|---|---|
| Response Size | ~80 bytes | ~250 bytes |
| Latency | 12ms | 28ms |
| Throughput | 8,300 req/s | 3,500 req/s |
| Bandwidth (1000 requests) | 80 KB | 250 KB |
Performance Benefits:
- 68% smaller payloads through binary encoding
- 57% lower latency with HTTP/2 multiplexing
- 137% higher throughput with connection reuse
Need help with balance queries? Contact our support team or check the gRPC overview.