fuels-rs Provider 链上查询实战:获取地址币、可花费资源与资产余额
本篇围绕 fuels-rs(Fuel Network 官方 Rust SDK)的链上查询能力展开:在通过 Provider 连接 fuel-core 节点后,如何查询某个地址的所有未花费币(coins)、满足金额要求的可花费资源(spendable resources)以及各资产的汇总余额。读完后你将能够直接使用 get_coins、get_spendable_resources、get_balances 三个核心查询接口完成余额类业务开发,并理解其底层的分页拉取、资源过滤与缓存重试机制。
Provider 与查询接口的总体关系
在 fuels-rs 中,所有与节点的交互都通过 Provider 完成。从 provider.rs 的源码结构看,Provider 内部封装了 CachedClient<RetryableClient>:内层 RetryableClient 负责按配置对请求进行重试,外层 CachedClient 对部分 RPC 结果做带 TTL 的缓存。这意味着文档中的三个查询方法不仅是简单的 RPC 转发,还自带重试与缓存能力。
连接节点的方式有两种(参见 connecting 总览):
Provider::connect(url):连接到一个已存在的节点(测试网或自建的fuel-core节点);setup_test_provider(...):SDK 内置的测试辅助函数,会启动一个带预置状态的短时本地节点,适合在测试中使用。
除本文涉及的三个查询接口外,Provider 还提供 chain_info、node_info、dry_run、latest_gas_price 等大量方法,更完整的 API 面可查阅 fuels crate 中 fuels::accounts::provider::Provider 的公开 API 文档。
搭建测试链:setup_test_provider
如果你要连接的是外部区块链(测试网或自建节点),可以跳过本节。对于本地验证,示例代码位于 examples/providers/src/lib.rs 的 query_the_blockchain 测试中,完整设置如下:
use fuels::prelude::*;
// Create a random signer
let wallet_signer = PrivateKeySigner::random(&mut rand::thread_rng());
// How many coins in our wallet.
let number_of_coins = 1;
// The amount/value in each coin in our wallet.
let amount_per_coin = 3;
let coins = setup_single_asset_coins(
wallet_signer.address(),
AssetId::zeroed(),
number_of_coins,
amount_per_coin,
);
let retry_config = RetryConfig::new(3, Backoff::Fixed(Duration::from_secs(2)))?;
let provider = setup_test_provider(coins.clone(), vec![], None, None)
.await?
.with_retry_config(retry_config);
这段代码各部分在源码中的含义如下:
-
setup_single_asset_coins(owner, asset_id, num_coins, amount_per_coin):定义在 fuels-test-helpers/src/lib.rs,为同一地址、同一资产 ID 生成num_coins个 UTXO,每个 UTXO 的数额为amount_per_coin。从源码看,它使用随机数据填充 UtxoId 并将区块高度固定为 0,即生成一批“测试预置币”。 -
AssetId::zeroed():这里预置的资产是零值资产 ID,与测试链的默认基础资产(base asset)对应。 -
setup_test_provider(coins, messages, node_config, chain_config):定义在 fuels-test-helpers/src/lib.rs。它把coins与messages写入本地测试节点的StateConfig(预置状态),启动一个本地FuelService,最后通过Provider::from(bound_address)建立连接。第二个参数是消息(message)预置列表,查询余额场景传vec![]即可;后两个参数可分别覆盖节点配置与链配置,传None时使用默认值(测试链配置会把交易大小上限提高到 10,000,000 字节)。 -
RetryConfig::new(3, Backoff::Fixed(Duration::from_secs(2))):为 Provider 配置“最多尝试 3 次、每次间隔固定 2 秒”的重试策略。Backoff的三种策略与重试机制实现见 retry_util.rs:Backoff::Fixed(d):每次重试间隔固定为d;Backoff::Linear(d):间隔随尝试次数线性增长(第 n 次等待d * (n + 1));Backoff::Exponential(d):间隔指数翻倍(第 n 次等待d * 2^n)。
从源码看,
RetryConfig默认值为“最多 1 次尝试 +Linear(10ms)间隔”,即默认不重试;max_attempts使用NonZeroU32保证必须大于 0。
查询地址的全部未花费币:get_coins
get_coins 返回某地址在指定资产 ID 下的所有未花费 UTXO(coin)对象,包括每个 coin 的 utxo_id、amount、owner、asset_id 等字段。示例(继承自文档 querying.md 与 examples/providers/src/lib.rs):
let consensus_parameters = provider.consensus_parameters().await?;
let coins = provider
.get_coins(
&wallet_signer.address(),
*consensus_parameters.base_asset_id(),
)
.await?;
assert_eq!(coins.len(), 1);
要点:
- 资产 ID 必须显式传入:
get_coins(from: &Address, asset_id: AssetId)不接受Option,因此示例先从consensus_parameters()取出链的base_asset_id()。consensus_parameters()本身走缓存客户端(见 provider.rs),重复调用开销较低。 - 分页是自动完成的:从 provider.rs 的实现看,
get_coins内部使用游标(cursor)循环调用底层coinsRPC,每批拉取NUM_RESULTS_PER_REQUEST = 100条(provider.rs 定义的常量),方向为PageDirection::Forward,直到某一批返回空结果为止。因此即使某地址持有成千上万个 UTXO,SDK 也会自动翻页并聚合成一个完整的Vec<Coin>返回给调用方。
适合场景:需要审计、展示或自行选择具体 UTXO(例如做自定义转账逻辑)时使用。
查询可花费资源:get_spendable_resources 与 ResourceFilter
get_spendable_resources 解决的是另一类问题:我不关心具体是哪几个 UTXO,只要节点帮我凑出总额不低于 amount 的一批可花费资源。其入参是 ResourceFilter,定义见 provider.rs:
#[derive(Default)]
pub struct ResourceFilter {
pub from: Address, // 资源所有者地址
pub asset_id: Option<AssetId>, // 目标资产,None 时回退到链的 base asset
pub amount: u128, // 需要凑够的最小总额
pub excluded_utxos: Vec<UtxoId>, // 需要排除的 UTXO ID 列表
pub excluded_message_nonces: Vec<Nonce>, // 需要排除的消息 nonce 列表
}
| 字段 | 类型 | 默认值 / 缺省行为 |
|---|---|---|
from |
Address |
必须指定,即资源所属地址 |
asset_id |
Option<AssetId> |
None 时解析为链共识参数中的 base asset ID |
amount |
u128 |
需要达到的最小总额 |
excluded_utxos |
Vec<UtxoId> |
空向量,表示不排除任何 UTXO |
excluded_message_nonces |
Vec<Nonce> |
空向量,表示不排除任何消息 |
文档给出的示例(examples/providers/src/lib.rs)只设置了所有者与金额,其余字段走默认值:
let filter = ResourceFilter {
from: wallet_signer.address(),
amount: 1,
..Default::default()
};
let spendable_resources = provider.get_spendable_resources(filter).await?;
assert_eq!(spendable_resources.len(), 1);
结合源码,这个调用的执行链路是(见 provider.rs):
filter.resource_queries()把ResourceFilter转换为内部查询参数;由于asset_id为None,spend_query会先读取共识参数,把资产 ID 解析为 base asset ID,金额为1;由于两个排除列表均为空,exclusion_query()返回None,即不带排除条件。- SDK 调用节点的
coins_to_spendRPC,由节点在 UTXO 与消息中挑选凑足金额的资源组合。文档注释明确指出:返回的 coin 数量会经过优化以防止尘埃(dust)积累,因此返回数量通常恰好是满足金额的最优组合(示例中预置了 1 个数额为 3 的 coin,查询金额为 1,节点返回这 1 个 UTXO)。 - 返回值是
Vec<CoinType>——CoinType同时覆盖 UTXO coin 与 L1 转入的消息(message)两类可花费资源,这比get_coins的Vec<Coin>覆盖面更广。
两个补充细节:
- coin-cache 特性:启用
coin-cache特性后(参见 provider.rs),get_spendable_resources会先执行extend_filter_with_cached,把本进程近期已提交交易中使用的 coin 自动追加到excluded_utxos/excluded_message_nonces,从而避免在节点状态尚未确认时重复选用“在途”资源。这解释了源码注释中“recently submitted coins will be ignored”的行为。 - 排除列表的用途:当你已经手工选定部分 UTXO 用于其他交易时,可把它们的
UtxoId放入excluded_utxos,让节点在凑资源时绕开这些币。
查询资产余额汇总:get_balances
get_balances 返回某地址所有资产的可花费余额汇总。它和 get_coins 的本质区别是:只返回数字(每个资产 ID 对应 UTXO 数额之和),不返回 UTXO 本身,因此开销和返回体积都小得多。示例(examples/providers/src/lib.rs):
let _balances = provider.get_balances(&wallet_signer.address()).await?;
返回值类型为 HashMap<String, u128>,key 是资产 ID 的十六进制字符串,value 是该资产的余额总和。
从 provider.rs 的实现看,该方法还处理了节点索引能力差异:
- 先通过
node_info()检查节点的indexation.balances标志位; - 若节点已启用余额索引,则按游标分页(每批 100 条)循环拉取,直到拉空为止;
- 若节点未启用余额索引,则回退为单次请求最多 9999 条的兜底查询。
因此同一方法在不同节点配置下行为略有差异,但接口对调用方保持一致。
此外还有两个更细粒度的余额接口,适合只关心单一资产的场景:
get_asset_balance(&Address, &AssetId) -> Result<u128>:查询某地址在某资产下的汇总余额(见 provider.rs);get_contract_asset_balance(&ContractId, &AssetId) -> Result<u64>:查询某合约在某资产下的余额(见 provider.rs)。
三个查询方法的选型小结
| 方法 | 返回 | 适用场景 |
|---|---|---|
get_coins |
Vec<Coin>:单资产下全部未花费 UTXO 明细 |
需要查看/自选具体 UTXO,做资产审计或自定义转账 |
get_spendable_resources |
Vec<CoinType>:由节点凑出的、总额 ≥ amount 的最优资源组合(含 UTXO 与消息) |
发交易前让节点自动凑资源,SDK 内部转账逻辑同样走此接口 |
get_balances |
HashMap<String, u128>:所有资产的余额总和 |
展示钱包余额、做余额充足性判断 |
三者均返回 Result,错误处理遵循 SDK 统一的 fuels 错误类型;在连接外部节点时,可通过 with_retry_config 为 Provider 附加重试策略以提升弱网下的查询稳定性。
延伸阅读
- Provider 的连接方式与节点选择:connecting 总览、外部节点连接
- 重试机制源码:retry_util.rs(含重试间隔与停止条件的单元测试)
- 查询相关端到端测试:examples/providers/src/lib.rs
- 余额与 coin 的账户侧视角:checking balances and coins
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00