Sui 区分 Balance(余额)——地址持有的某种代币类型的标量数量——和 Coin 对象(代币对象),后者是构成该余额的各个独立拥有的对象。JSON-RPC 方法 sui_getBalance 和 sui_getAllBalances 返回余额,而 sui_getCoins 枚举底层的代币对象。小数位和符号等元数据来自 sui_getCoinMetadata,其中 decimals 是将原始整数余额转换为人类可读金额所必需的。本指南解释了代币模型,展示了使用 Sui TypeScript SDK 的可运行 Node.js 示例,并提供了一个可复现的结果表,用于验证你的端点行为。
Sui 的代币模型:余额与代币对象
在 Sui 中,Balance(余额)是一个标量值,表示某个地址持有多少特定代币类型。它本身不是对象;它是网络根据该地址拥有的 Coin 对象集合计算出的派生量。JSON-RPC 方法 sui_getBalance 返回给定代币类型的这个标量,而 sui_getAllBalances 返回地址持有的所有代币类型的所有余额。
Coin(代币对象)是特定代币类型的单个拥有对象。每个 Coin 对象都有自己的对象 ID、版本和一个 balance 字段。地址拥有的给定代币类型的所有 Coin 对象的 balance 字段之和等于 sui_getBalance 返回的 Balance。方法 sui_getCoins 枚举这些 Coin 对象,并通过游标分页。
混淆这两个概念会导致总数错误。如果你对 sui_getCoins 返回的 Coin 对象余额求和,但因分页而遗漏了一些,就会少算。如果你把 Balance 当作对象 ID,就无法构建有效的交易。Sui 文档中关于代币概念的部分详细解释了这一区别。
- Balance:每种代币类型的标量数量,由
sui_getBalance和sui_getAllBalances返回。 - Coin 对象:具有自己 ID 和余额的单个拥有对象,由
sui_getCoins枚举。 - Coin 对象余额之和等于该代币类型的 Balance。
- 余额响应中的
coinObjectCount表示支撑该余额的 Coin 对象数量。
代币类型标识符及其作为查询键的原因
Sui 上的每个代币都由完全限定的 Move 类型字符串标识,而不是由符号标识。对于原生代币,代币类型是 0x2::sui::SUI。对于自定义代币,格式为 <packageId>::<module>::<struct>,例如 0x2::sui::SUI 或 0x1234...::my_coin::MY_COIN。这个字符串是所有代币相关 RPC 方法的查询键。
诸如 "SUI" 或 "USDC" 之类的符号是元数据,不是标识符。理论上两个不同的代币类型可以共享一个符号,而且如果元数据更新,符号也可能改变。在调用 sui_getBalance、sui_getCoinMetadata 或 sui_getCoins 时,始终使用代币类型字符串。
代币类型字符串是网络特定的。存在于 Sui 主网上的代币类型可能不存在于测试网或开发网上。在构建应用程序时,确保代币类型可配置或从网络上下文派生。Sui JSON-RPC API 参考列出了每个方法的确切参数格式。
- 原生代币:
0x2::sui::SUI。 - 自定义代币:
<packageId>::<module>::<struct>。 - 符号是元数据;代币类型是标识符。
- 代币类型因网络而异(主网、测试网、开发网)。
读取代币元数据:小数位、符号和名称
方法 sui_getCoinMetadata 返回代币类型的元数据:decimals、name、symbol、description 和 iconUrl。decimals 字段至关重要,因为原始余额是整数。要将原始余额转换为人类可读的金额,需除以 10 的 decimals 次方。
例如,如果 decimals 为 9,原始余额 1,000,000,000 表示 1.0 SUI。如果 decimals 为 6,原始余额 1,000,000 表示 1.0 USDC。如果没有正确的小数位,显示的金额会相差几个数量级。
某些代币的元数据可能缺失,尤其是那些元数据格式不良或未注册的代币。在这种情况下,sui_getCoinMetadata 可能返回 null 或错误。你的应用程序应优雅地处理缺失的元数据,例如回退到默认的小数位值或显示原始金额并给出警告。
decimals是人类可读转换所必需的:raw / 10^decimals。symbol和name仅用于显示;不要将它们用作查询键。iconUrl指向图像;渲染前请验证 URL。- 元数据可能缺失;处理
null响应。
查询余额:sui_getBalance 和 sui_getAllBalances
sui_getBalance 接受所有者地址和代币类型,并返回一个包含 coinType、coinObjectCount、totalBalance 和 lockedBalance 的对象。totalBalance 是该代币类型所有 Coin 对象余额的总和。lockedBalance 表示被锁定的代币,例如由于归属或质押,不能立即花费。
sui_getAllBalances 仅接受所有者地址,并返回该地址持有的所有代币类型的此类余额对象数组。这对于投资组合视图或当你不知道地址持有哪些代币类型时很有用。
这两个方法都反映节点当前检查点的状态。如果交易刚刚提交但尚未包含在检查点中,余额可能不会反映它。为了获得一致的读取,如果方法支持,你可以指定检查点序列号,或者等待交易最终确定。
sui_getBalance返回coinType、coinObjectCount、totalBalance、lockedBalance。- 计算可花费资金时必须排除
lockedBalance。 sui_getAllBalances返回所有代币类型的余额对象数组。- 读取反映节点当前检查点;最近的交易可能未包含在内。
使用 sui_getCoins 和分页枚举代币对象
sui_getCoins 返回给定所有者和代币类型的分页 Coin 对象列表。每页包含一个 Coin 对象的 data 数组和一个 nextCursor 字段。要枚举所有 Coin 对象,你必须使用游标重复调用该方法,直到 nextCursor 为 null 或 hasNextPage 为 false。
如果某些 Coin 对象被锁定,sui_getCoins 返回的 Coin 对象数量可能与余额响应中的 coinObjectCount 不同。锁定的代币仍然被拥有,但根据节点的实现,可能不会由 sui_getCoins 返回。始终将返回的 Coin 对象余额之和与 totalBalance 交叉核对,以检测差异。
对于拥有许多 Coin 对象的地址,分页至关重要。单次调用可能只返回有限数量的对象,且限制因提供商而异。Sui TypeScript SDK 提供了自动处理分页的辅助方法,但理解游标机制对于原始 JSON-RPC 使用很重要。
sui_getCoins返回data和nextCursor;循环直到nextCursor为 null。- 如果某些代币被锁定,计数可能与
coinObjectCount不同。 - 对返回的 Coin 对象的
balance字段求和,以验证totalBalance。 - 页面大小限制因提供商而异;不要假设固定的最大值。
可运行示例:读取 SUI 余额和元数据
以下 Node.js 示例使用 Sui TypeScript SDK 读取地址的 SUI 余额,获取 SUI 代币元数据,并将原始余额转换为人类可读的金额。它假设你已经安装了 @mysten/sui 并拥有 Sui RPC 端点 URL。
将 YOUR_RPC_URL 替换为你的提供商的端点,例如 OnFinality Sui 端点。该示例使用 SDK 客户端中的 getBalance、getCoinMetadata 和 getCoins。SDK 方法直接映射到上述 JSON-RPC 方法。
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);可运行示例:使用 curl 的原始 JSON-RPC 调用
如果你更喜欢原始 JSON-RPC,可以使用 curl 调用相同的方法。以下示例为给定地址上的 SUI 调用 sui_getBalance 和 sui_getCoinMetadata。将 YOUR_RPC_URL 和 0xYOUR_ADDRESS 替换为你的端点和地址。
JSON-RPC 2.0 规范定义了请求格式:jsonrpc 版本、method、params 和 id。Sui JSON-RPC API 遵循此规范。响应包含 result 或 error 对象。
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"]
}'针对你的端点的可复现结果表
要验证你的端点行为并记录你自己的测量结果,请使用来自你的 RPC 提供商的数据填写下表。使用已知地址和代币类型,并运行上述方法。此表仅供你自己记录;它不是 OnFinality 提供的基准。
记录原始 totalBalance、元数据中的 decimals、人类可读金额、coinObjectCount,以及 Coin 对象之和是否与 totalBalance 匹配。如果不匹配,请调查锁定余额或分页问题。
- 代币类型:例如
0x2::sui::SUI - 原始 totalBalance:来自
sui_getBalance的整数 - 小数位:来自
sui_getCoinMetadata - 人类可读金额:
raw / 10^decimals - 代币对象数量:来自
coinObjectCount或sui_getCoins结果的数量 - 代币对象之和:来自
sui_getCoins的balance字段之和 - 是否匹配:是/否
代币读取的局限性和权衡
代币类型字符串是网络特定的。存在于主网上的代币类型可能不存在于测试网上。硬编码代币类型在切换网络时可能会失效。始终使代币类型可配置或从网络上下文派生。
小数位来自元数据,对于格式不良的代币,元数据可能缺失或不正确。如果 sui_getCoinMetadata 返回 null,你无法可靠地将原始余额转换为人类可读的金额。在这种情况下,显示原始金额或使用带有明确警告的回退小数位值。
计算可花费资金时必须排除 lockedBalance。totalBalance 包括锁定的代币,但只有 totalBalance 和 lockedBalance 之间的差额可以立即花费。未能考虑到这一点可能导致交易失败。
RPC 状态反映节点当前检查点。提交交易后立即读取可能不包含该交易(如果它尚未被检查点)。对于时间敏感的操作,轮询直到交易最终确定,或者如果支持,使用特定检查点的读取。
- 代币类型是网络特定的;不要跨网络硬编码。
- 缺失元数据会妨碍准确的小数位转换。
- 可花费资金需排除
lockedBalance。 - 读取反映节点当前检查点;最近的交易可能滞后。
排查常见的代币读取问题
如果 sui_getBalance 对你预期持有资金的地址返回 totalBalance 为零,请验证代币类型字符串。包 ID 或模块名称中的拼写错误将导致余额为零。还要确认地址正确,并且你查询的是正确的网络。
如果 sui_getCoinMetadata 返回 null,该代币可能没有注册元数据。检查代币类型字符串并尝试其他代币。如果元数据存在但 decimals 似乎错误,请对照官方来源验证代币类型。
如果来自 sui_getCoins 的 Coin 对象之和与 totalBalance 不匹配,请检查是否有锁定的代币。某些 Coin 对象可能被锁定,不会由 sui_getCoins 返回。还要确保你分页遍历了所有页面;遗漏一页会导致少算。
如果你收到一个永远不会变为 null 的 nextCursor,你可能由于提供商错误或游标处理不当而陷入无限循环。始终包含最大迭代限制,并记录游标值以便调试。
- 余额为零:检查代币类型、地址和网络。
- 元数据为 null:代币可能缺少元数据;优雅处理。
- 总和不匹配:检查锁定的代币并完成分页。
- 无限分页:添加最大迭代限制并记录游标。
下一步:将代币读取集成到你的应用程序中
现在你可以读取余额和元数据,你可以构建诸如投资组合跟踪器、支付流程和代币门控等功能。有关 Sui RPC 方法的更广泛概述,请参阅 Sui RPC 指南(RPC Assistant)。要了解代币对象如何融入对象模型,请阅读读取 Sui 对象、动态字段和分页。
在查询交易历史时,使用 Sui queryTransactionBlocks 游标分页来处理大型结果集。要理解对象版本和并发,请参阅 Sui 对象版本和 Lamport 排序。要解析交易效果和对象变更,请参考 Sui 交易效果和对象变更。
对于生产部署,考虑使用可靠的 RPC 提供商。OnFinality 提供 Sui 网络访问,以及 RPC 定价和 API 服务。在 OnFinality Learn 中心探索更多指南。
- 将代币读取用于投资组合跟踪器、支付和代币门控。
- 正确处理分页和锁定余额。
- 为生产选择可靠的 RPC 提供商。
- 在 OnFinality Learn 上探索更多 Sui 指南。