ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

fuels-rs 合约调用中的同交易自定义资产转账:`add_custom_asset()` 用法与底层实现

2026/9/10 15:25:47 拓冰建站 浏览量
fuels-rs 合约调用中的同交易自定义资产转账:`add_custom_asset()` 用法与底层实现 fuels-rs 合约调用中的同交易自定义资产转账add_custom_asset()用法与底层实现【免费下载链接】fuels-rsFuel Network Rust SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-rs本篇文章聚焦 Fuel Network 官方 Rust SDKfuels-rs在合约调用contract call过程中随调用一同转账自定义资产的能力。核心 API 是add_custom_asset()它允许你在同一笔交易里为合约调用额外指定“资产 ID 金额 目标地址”实现合约方法与资产转移的原子性提交。读完本文你将掌握add_custom_asset()的完整调用形态、to参数与None的语义差别理解 SDK 是如何把“自定义资产”编译成链上交易的 Coin 输入/输出并能用仓库自带的端到端测试来验证余额变化。一、为什么需要“随合约调用转账资产”在 Fuel 上资产是“携带在调用frame/call中”被转交的。日常的合约调用默认只处理基础资产base asset即AssetId::zeroed()可类比原生燃料币并通过 CallParameters 配置随调用转发的金额。但业务场景往往不止于此例如在调用某个合约方法的同时把某种自定义代币非基础资产直接支付给第三方地址在同一笔交易中完成“合约状态更新 资产划转”保证二者要么同时成功、要么同时失败借助签名聚合让调用与转账共用同一个交易、同一组输入签名避免二次签名与竞态。这正是 docs/src/calling-contracts/custom-asset-transfer.md 所介绍的特性SDK 允许在发起合约调用时在同一笔交易内指定要转移的资产asset ID、数量amount与目标地址destination address。实现该能力的方法就是add_custom_asset()。二、add_custom_asset()快速上手原文档引用的完整示例位于仓库的 examples/contracts/src/lib.rs摘录核心调用片段对应文档中的add_custom_assets锚点// ANCHOR: add_custom_assets let amount 1000; let _ contract_instance .methods() .initialize_counter(42) .add_custom_asset(AssetId::zeroed(), amount, Some(some_addr)) .call() .await?; // ANCHOR_END: add_custom_assets示例所在测试函数的完整上下文包含钱包与合约的搭建如下便于你直接理解运行前提#[tokio::test] async fn custom_assets_example() - Result() { use fuels::prelude::*; setup_program_test!( Wallets(wallet, wallet_2), Abigen(Contract( name MyContract, project e2e/sway/contracts/contract_test )), Deploy( name contract_instance, contract MyContract, wallet wallet ) ); let some_addr: Address thread_rng().r#gen(); let amount 1000; let _ contract_instance .methods() .initialize_counter(42) .add_custom_asset(AssetId::zeroed(), amount, Some(some_addr)) .call() .await?; // ... Ok(()) }关键点解读调用链contract_instance.methods().合约方法(...)先选定要调用的合约方法此处为initialize_counter(42)随后在.call()之前链式调用.add_custom_asset(...)即“为这笔即将发送的合约调用附加一次资产转移”。参数三元组(asset_id, amount, to)分别是资产 ID、转移数量、目标地址。示例中的some_addr通过thread_rng().r#gen()生成的随机地址演示“把资产转给一个非当前钱包的第三方地址”的用法示例里用的资产是AssetId::zeroed()基础资产。需要注意的是示例同时展示了与add_custom_asset相邻但作用不同的两个方法.with_inputs(custom_inputs)与.with_outputs(custom_outputs)在 examples/contracts/src/lib.rs 的add_custom_inputs_outputs锚点中。它们分别对应手工指定完整的交易输入/输出这一更低层的能力详见 custom-inputs-outputs.md而add_custom_asset()是更高层的便捷封装。三、方法签名与参数语义add_custom_asset()定义在 packages/fuels-programs/src/calls/contract_call.rspub fn add_custom_asset(mut self, asset_id: AssetId, amount: u64, to: OptionAddress) { *self.custom_assets.entry((asset_id, to)).or_default() amount; }三个参数的含义如下参数类型含义asset_idAssetId要转移的资产 ID。基础资产为AssetId::zeroed()自定义资产为其各自的 32 字节 ID。amountu64转移数量。toOptionAddress目标地址。为Some(addr)时 SDK 会生成发往该地址的 Coin 输出为None时不会为这笔资产生成发往外部地址的 Coin 输出资产仍作为调用所需的输入随交易进入具体业务语义由合约与你的交易结构决定。可被重复调用并自动累加注意实现中self.custom_assets.entry((asset_id, to)).or_default() amount这一行内部用一个HashMap(AssetId, OptionAddress), u64保存所有自定义资产字段定义见同文件第 24 行键是(asset_id, to)的组合对同一(asset_id, to)多次调用add_custom_asset()数量会累加而不是覆盖。因此在同一笔调用中你可以连续声明多笔转账例如把两种不同资产同时转给同一目标见下文的端到端测试示例。四、底层实现自定义资产如何变成链上交易理解add_custom_asset需要把它放入调用处理与交易组装的主链路中观察。相关逻辑集中在 packages/fuels-programs/src/calls/utils.rs。1. 自定义资产被纳入“所需资产总额”的计算在预估这笔调用需要从钱包中取出多少资产时calculate_required_asset_amounts()见 utils.rs会把两类来源合并统计每个调用的CallParameters中通过call_parameters.amount()与asset_id()未指定时为基础资产表达的基础转发金额每个调用的custom_assets表里登记的自定义资产。两者按asset_id分组求和得到“这笔交易按资产种类分别需要多少余额”。这一步决定了 SDK 在构造输入[Input]时是否要为某种资产额外挑选未花费的 coinUTXO也直接影响了钱包的可用余额校验。2. 自定义资产驱动 Coin 输出的生成交易输出[Output]由get_transaction_inputs_outputs()utils.rs统一编排其中自定义资产对应的“汇款”动作由generate_custom_outputs()utils.rs完成fn generate_custom_outputs(calls: [ContractCall]) - VecOutput { calls .iter() .flat_map(|call| call.custom_assets) .group_by(|custom| (custom.0.0, custom.0.1)) .into_iter() .filter_map(|(asset_id_address, groups_w_same_asset_id_address)| { let total_amount_in_group groups_w_same_asset_id_address .map(|(_, amount)| amount) .sum::u64(); asset_id_address .1 .map(|address| Output::coin(address, total_amount_in_group, asset_id_address.0)) }) .collect::Vec_() }该函数清晰揭示了几个实现事实先按(asset_id, 目标地址)分组再求和即使你在多个调用中声明了相同的“资产 地址”最终也只生成一个Coin 输出金额取总和链上更省空间filter_map过滤掉to None的条目只有目标地址为Some(address)的自定义资产才会生成Output::coin(address, amount, asset_id)形式的 Coin 输出直接把amount个asset_id资产打进address顺序约定自定义输出被放在交易输出的靠前位置在函数内部先收集custom_outputs随后才拼接合约输出与找零输出而自定义输入/输出在get_transaction_inputs_outputs()中同样被显式注释为“应置于其他输入输出之前”见 utils.rs这与 Fuel 交易中输入输出按类型排序的规范一致。3. 调用对象的生命周期每次构造合约方法调用时ContractCall会以custom_assets: Default::default()初始化空表见 packages/fuels-programs/src/calls/call_handler.rs 与 packages/fuels-programs/src/calls/utils.rs随后由你在构建期通过add_custom_asset()逐步登记最终在交易组装阶段被消费。也就是说add_custom_asset是**构建期build-time**设置必须发生在.call()之前。五、用仓库端到端测试验证真实转账仓库在 e2e/tests/contracts.rs 中提供了test_add_custom_assets端到端测试可直接用于验证本特性的完整行为。其核心思路与断言如下1. 构造多资产钱包测试为两个钱包分别准备三种资产的 coin每种num_coins: 1、初始coin_amount: 100_000基础资产AssetId::zeroed()以及两种自定义资产AssetId::from([3u8; 32])与AssetId::from([1u8; 32])见 e2e/tests/contracts.rs。2. 在同一调用中附加两笔自定义资产转账let amount_1 5000; let amount_2 3000; let response contract_instance .methods() .get(5, 6) .add_custom_asset(asset_id_1, amount_1, Some(wallet_2.address())) .add_custom_asset(asset_id_2, amount_2, Some(wallet_2.address())) .call() .await?; assert_eq!(response.value, 11);这里清晰地演示了“合约方法照常执行 多种自定义资产一并转移”的组合get(5, 6)返回 11 不受影响同时两笔不同资产的转账在同一笔交易内完成。3. 余额断言let balance_asset_1 wallet_1.get_asset_balance(asset_id_1).await?; let balance_asset_2 wallet_1.get_asset_balance(asset_id_2).await?; assert_eq!(balance_asset_1, (initial_amount - amount_1) as u128); assert_eq!(balance_asset_2, (initial_amount - amount_2) as u128); let balance_asset_1 wallet_2.get_asset_balance(asset_id_1).await?; let balance_asset_2 wallet_2.get_asset_balance(asset_id_2).await?; assert_eq!(balance_asset_1, (initial_amount amount_1) as u128); assert_eq!(balance_asset_2, (initial_amount amount_2) as u128);转出方wallet_1的两种资产各减少对应数量转入方wallet_2的两种资产各增加对应数量——这从链上余额角度验证了Output::coin(address, total_amount, asset_id)确实按预期产出并上链。需要补充说明的是测试通过WalletsConfig::new_multiple_assets(...)让钱包预先持有足够余额这是因为燃料机制要求交易的每一种输入资产都必须有对应的 UTXO 作为来源。六、适用边界与相关主题指引在使用add_custom_asset()时建议结合以下几点判断是否适用需要“按交易原子转移给第三方”时优先使用它若目标只是“给合约本身转发某资产”通常用CallParameters更直接见 call-params.md。需要完全掌控输入输出结构例如自定义签名输入、手工输出类型时可退而使用 with_inputs/with_outputs并通过add_signer为交易补充签名示例见 examples/contracts/src/lib.rs。变量数量输出variable output是另一种与资产转移相关的输出类型适合“数量在构造时未知”的转账场景见 variable-outputs.md而本特性要求你在构建期就明确指定amount。参考路径速查特性文档docs/src/calling-contracts/custom-asset-transfer.md示例代码examples/contracts/src/lib.rs核心实现packages/fuels-programs/src/calls/contract_call.rs输入输出组装packages/fuels-programs/src/calls/utils.rs调用构建初始化packages/fuels-programs/src/calls/call_handler.rs端到端验证e2e/tests/contracts.rs示例所用 Sway 合约e2e/sway/contracts/contract_test/src/main.sw以上链接覆盖“文档 → 示例 → SDK 实现 → 合约 → 测试”的完整证据链你可以顺着任一环节在仓库中继续深入阅读。【免费下载链接】fuels-rsFuel Network Rust SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-rs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考