通过 JSON-RPC 读取 Sui 对象需要理解对象模型:每个对象都有 ID、版本和摘要,所有权可以是地址、对象、共享或不可变。使用 sui_getObject 进行单次读取,使用 suix_multiGetObjects 进行批量读取,使用 suix_getDynamicFields 分页浏览动态字段,使用 suix_getOwnedObjects 列出地址的主要对象。本指南解释了这些机制,提供了可运行的 curl 示例,并包含常见问题(如对象缺失和分页循环)的故障排查清单。
直接回答:如何正确读取 Sui 对象
要通过 JSON-RPC 读取 Sui 对象,首先必须理解 Sui 对象并非简单的键值对。每个对象由一个 32 字节的对象 ID 标识,具有一个每次写入时递增的版本整数,以及一个随每次写入而变化的摘要(哈希)。所有权可以是地址拥有、对象拥有、共享或不可变。要获取对象的当前状态,请使用对象 ID 调用 suix_getObject(或旧版 sui_getObject)。要一次请求获取多个对象,请使用 suix_multiGetObjects。要列出地址拥有的对象,请使用 suix_getOwnedObjects 并附带可选过滤器。动态字段(允许对象嵌套任意数据)通过 suix_getDynamicFields(用于分页浏览)和 suix_getDynamicFieldObject(用于获取单个字段)单独读取。分页基于游标,必须正确处理 nextCursor 和 hasNextPage 字段以避免循环。本指南通过可运行的示例和故障排查清单逐步介绍每种方法。
Sui 对象模型:ID、版本、摘要和所有权
在 Sui 中,一切都是对象。基于 Move 的模型将对象存储在以 32 字节 ObjectID 为键的全局映射中。每个对象都有一个 version(单调递增的整数)和一个 digest(对象内容和版本的哈希)。当对象被修改时,其版本递增,摘要也会改变。这个三元组——ID、版本、摘要——是所有读取的基础。所有权可以是四种类型之一:地址拥有(由单个地址控制)、对象拥有(由另一个对象拥有,支持层级结构)、共享(任何人都可访问,常用于共享状态)和不可变(无法修改,例如已发布的包)。
官方 Sui 文档 解释说,对象可以包装在其他对象内部,这会影响它们的可见性。例如,包装后的对象不再能通过其 ID 直接访问;它“隐藏”在父对象内部。当 getObject 调用返回未找到,即使对象在链上存在,这也是常见的困惑来源。
读取对象时,你会收到其当前状态,包括 data 布局(例如 moveObject 或 package)、所有者以及引用(ID、版本、摘要)。如果只请求引用(通过将 showContent 设置为 false),你会得到一个轻量级响应,这对于跟踪更改而不下载完整内容非常有用。
读取对象:sui_getObject 和 suix_multiGetObjects
读取单个对象的主要方法是 suix_getObject(旧版 sui_getObject 已弃用,但在许多节点上仍然有效)。它接受一个对象 ID 和一组显示选项(例如 showContent、showOwner、showType)。响应包括对象的 objectId、version、digest、owner 以及包含 Move 类型和字段的 data 字段。
对于批量读取,使用 suix_multiGetObjects 并传入 ID 数组和相同的显示选项。当需要一次往返获取多个对象时,这非常高效,可减少延迟和速率限制使用。请注意,响应顺序与请求顺序一致,如果对象不存在,对应的条目将为 null。
以下是一个使用公共 Sui RPC 端点的可运行 curl 示例(如有需要,请将 URL 替换为您自己的提供商)。该示例获取一个已知对象(一个 Sui 币)并打印响应:
curl -X POST https://fullnode.mainnet.sui.io:443 \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_getObject",
"params": [
"0x2::sui::SUI",
{
"showContent": true,
"showOwner": true,
"showType": true
}
]
}'列出拥有的对象:suix_getOwnedObjects 和过滤器
要枚举地址拥有的对象,请使用 suix_getOwnedObjects。此方法返回地址的“主要”拥有的对象——即由地址直接拥有且未包装在其他对象内部的对象。默认情况下,它不返回动态字段或币余额。要按类型或包过滤,请使用带有 StructType 或 Package 过滤器的 filter 参数。
例如,要列出地址拥有的所有 SUI 币,您可以按 0x2::coin::Coin<0x2::sui::SUI> 过滤。响应包含用于分页的游标。以下是一个 curl 示例:
curl -X POST https://fullnode.mainnet.sui.io:443 \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_getOwnedObjects",
"params": [
"0xYOUR_ADDRESS",
{
"filter": {
"StructType": "0x2::coin::Coin<0x2::sui::SUI>"
},
"options": {
"showContent": true,
"showOwner": true
},
"limit": 10
}
]
}'动态字段:读取嵌套数据
Sui 对象可以包含动态字段,这些字段是存储在对象本身上的键值对。这些字段允许灵活的数据结构,可以随时间添加或删除。动态字段不属于对象的固定模式;它们单独存储,必须使用专用方法读取。
要分页浏览父对象的所有动态字段,请使用 suix_getDynamicFields,并传入父对象 ID、游标和限制。响应包含动态字段名称列表(base58 编码)及其类型,以及用于分页的 nextCursor。要获取特定动态字段的值,请使用 suix_getDynamicFieldObject,并传入父 ID 和字段名称(作为包含 type 和 value 的 DynamicFieldName 对象)。
以下是一个分页浏览动态字段的示例:
curl -X POST https://fullnode.mainnet.sui.io:443 \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "suix_getDynamicFields",
"params": [
"0xPARENT_OBJECT_ID",
null, // cursor
10 // limit
]
}'使用游标分页:避免循环和数据缺失
Sui RPC 中的所有列表方法(例如 suix_getOwnedObjects、suix_getDynamicFields)都使用基于游标的分页。响应包含 nextCursor 字段和 hasNextPage 布尔值。要获取下一页,请在下一个请求中将 nextCursor 作为游标参数传递。如果 hasNextPage 为 false,则表示已到达末尾。
一个常见错误是重复使用相同的游标,导致无限循环。始终从响应中更新游标。另外,请注意 limit 参数受节点提供商的限制;默认值通常为 50,但可能有所不同。如果您设置的限制高于上限,节点可能会返回错误或静默截断。请查阅提供商的文档以了解确切限制。
以下是一个可填写的结果表格,用于记录您自己的分页结果:
- | 页数 | 使用的游标 | 返回的对象数 | nextCursor | hasNextPage |
- |------|-------------|------------------|------------|-------------|
- | 1 | null | [填写] | [填写] | [填写] |
- | 2 | [填写] | [填写] | [填写] | [填写] |
- | ... | ... | ... | ... | ... |
读取历史版本和归档节点
要读取对象的特定历史版本,您必须显式请求该版本。suix_getObject 方法接受一个可选的 version 参数(作为 SuiObjectDataOptions 的一部分?实际上,标准方法不支持版本;您需要使用 suix_tryGetPastObject 或类似方法)。在旧版 API 中,sui_getObject 不支持版本;您必须使用 sui_tryGetPastObject(现在是 suix_tryGetPastObject)来获取过去的版本。此方法需要对象 ID 和所需的版本号。
但是,过去的版本仅在存储完整历史的归档节点上可用。非归档节点的全节点将只拥有最新状态和有限的检查点历史。如果您请求的版本不可用,节点将返回错误。对于需要完整对象历史的应用,您必须连接到归档 RPC 提供商。OnFinality 提供 Sui 归档节点和历史数据,保留完整历史。
或者,您可以通过事件或使用 showPreviousTransaction 的 suix_getObject 来跟踪对象更改,以查看最后修改该对象的交易,但这不会提供完整历史。
故障排查清单:常见失败和修复
读取 Sui 对象时,您可能会遇到几个常见问题。使用此清单来诊断和修复它们:
- 对象未找到:如果
suix_getObject返回null或错误,则对象可能已被包装、删除或从未存在。检查对象 ID 是否有拼写错误。如果对象已被包装,则无法直接访问;您必须通过其父对象的动态字段读取它。 - 不可变对象语义:不可变对象(如包)永远不会改变。它们的版本始终为 1,摘要是恒定的。如果您期望版本递增,则可能正在查看错误的对象。
- 字段不存在:使用
suix_getDynamicFieldObject时,确保字段名称和类型与存储的键完全匹配。动态字段名称是 base58 编码且类型敏感的。不匹配将返回错误。 - 版本不可用:如果您在非归档节点上请求历史版本,则会收到错误。请使用归档节点或将查询调整为最新版本。
- 游标误用:始终使用上一个响应中的
nextCursor。如果您传递相同的游标,可能会无限循环。此外,确保正确处理hasNextPage。 - 分页限制上限:
limit参数受提供商限制。如果超过限制,节点可能会返回错误或截断结果。请查阅提供商的文档以了解最大限制。在 OnFinality,限制已记录在案,并可能因计划而异;请参阅 RPC 定价。
对象读取的限制和权衡
通过 RPC 读取 Sui 对象存在固有的限制。首先,suix_getOwnedObjects 仅返回主要拥有的对象;它不包括嵌套在动态字段中的对象或非直接拥有的币。要获得完整的清单,您必须递归遍历动态字段,这可能代价高昂。
其次,对象读取是时间点读取。要跟踪随时间的变化,您必须轮询对象的版本和摘要,或订阅事件。轮询可能对速率限制敏感;考虑使用 WebSocket 订阅进行实时更新。OnFinality 的 Sui WebSocket 事件订阅 指南说明了如何设置。
第三,JSON-RPC API 正在向类型化 SDK 迁移。在 Sui 2.0 中,许多原始方法正在被抽象 RPC 层的 SDK 方法所取代。此迁移目前不会破坏现有功能,但您应该计划更新代码。官方 Sui JSON-RPC 迁移指南 提供了详细信息。
最后,读取许多对象的成本可能会累积。使用 suix_multiGetObjects 进行批量读取比单独调用更高效。对于大规模数据提取,请考虑使用专门的数据服务或索引。
后续步骤和进一步阅读
既然您了解了如何读取 Sui 对象,就可以应用这些知识来构建资产跟踪器、库存系统或任何需要查询链上状态的应用。为了加深理解,请探索以下资源:
- Sui RPC 指南(RPC 助手) – 所有 Sui RPC 方法的快速参考。
- 使用 devInspectTransaction 模拟 Sui 交易 – 了解如何在不提交的情况下模拟交易。
- Sui RPC 延迟和性能 – 了解延迟因素以及如何优化您的调用。
- Sui 归档节点和历史数据 – 访问完整的对象历史。
- Sui WebSocket 事件订阅 – 获取对象更改的实时更新。
- OnFinality 学习中心 – 更多教程和指南。
- API 服务 – OnFinality 的托管 RPC 端点。
- RPC 定价 – 了解速率限制和成本。
- Sui 网络概览 – 一般 Sui 网络信息。