如何用 Predicate 为 fuels-ts 合约调用支付 Gas 费用
【免费下载链接】fuels-tsFuel Network Typescript SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-ts
在 fuels-ts 中,合约调用默认由发起调用的钱包支付 gas 费用。如果你的业务场景类似文档中的流动性池合约——用户只需提供要存入的资产,而费用由另一方承担——可以把一个 Predicate 配置为feePayerAccount,让 Predicate 地址上的基础资产来支付本次交易的手续费。本文基于仓库中的 Custom Contract Call 文档 和 AssembleTx 文档,给出两条可执行的配置路径。
适用前提
- 已有一个运行中的 Fuel 节点,可通过
Provider连接(示例代码中的LOCAL_NETWORK_URL); - 持有一个有余额的钱包私钥(示例中的
WALLET_PVT_KEY),用于部署合约、给 Predicate 转账; - 已完成合约和 Predicate 的编译,并通过 typegen 生成了对应的 TypeScript 类。文档示例使用了
LiquidityPool、LiquidityPoolFactory与ReturnTruePredicate三个生成类,Predicate 源码可参考仓库中的 return-true-predicate Sway 项目。
Predicate 的验证在链下完成,只有当 Predicate 返回true时,其地址上的资金才能被使用来支付费用;否则 SDK 会抛出验证错误。因此选择 Predicate 时,其验证逻辑必须在你执行合约调用时能够返回true。
准备:先给 Predicate 充值
Predicate 要支付 gas,前提是它的地址上有足够的基础资产。文档示例中用钱包向 Predicate 转了500_000单位的基础资产:
const provider = new Provider(LOCAL_NETWORK_URL); const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider); const predicate = new ReturnTruePredicate({ provider }); // 部署合约(省略部分:使用 configurableConstants 配置 TOKEN 资产 ID) const deploy = await LiquidityPoolFactory.deploy(wallet, { configurableConstants }); const { contract } = await deploy.waitForResult(); const contractId = contract.id.toB256(); // 给 Predicate 充值,供其支付后续交易的手续费 const res = await wallet.transfer(predicate.address, 500_000); await res.waitForResult();500_000是文档示例值,你需要按实际预估的手续费调整。Predicate 余额不足时无法完成支付,这一点在 Send And Spend Funds From Predicates 文档 中有对应的失败示例。
主路径:通过 assembleTxParams 指定费用支付方
大多数情况下只需要在调用链上加一个.assembleTxParams(),把feePayerAccount指向 Predicate,并用accountCoinQuantities声明合约需要的资产由哪个账户提供:
// 创建合约实例 const liquidityPoolContract = new LiquidityPool(contractId, wallet); // 执行合约调用,指定 Predicate 作为费用支付方 const { waitForResult } = await liquidityPoolContract.functions .deposit({ bits: wallet.address.toB256() }) .callParams({ forward: [1000, TestAssetId.A.value], // 存入 1000 单位的 TOKEN 资产 }) .assembleTxParams({ feePayerAccount: predicate, // 使用 Predicate 支付手续费 accountCoinQuantities: [ { amount: 1000, assetId: TestAssetId.A.value, account: wallet, changeOutputAccount: wallet, }, ], }) .call(); const { transactionResult: { isStatusSuccess }, } = await waitForResult(); console.log('isStatusSuccess', isStatusSuccess);各参数的作用(依据 AssembleTx 文档):
feePayerAccount: predicate:由该账户支付交易费用,SDK 内部用它估算并补足手续费所需的基础资产;accountCoinQuantities:声明除手续费外交易需要的资产。amount是不含手续费的所需数量;account提供这些资源,缺省为feePayerAccount;changeOutputAccount接收该资产花费后的找零,缺省为account;forward: [1000, TestAssetId.A.value]是合约deposit方法本身要求转入的资产,与accountCoinQuantities声明的是同一笔资产。
结果验证:waitForResult()返回后检查transactionResult.isStatusSuccess,为true表示交易上链成功。
可选路径:手动调用 assembleTx
需要更细粒度控制时(例如在提交前预置交易、获取交易 ID),可以跳过 SDK 自动的assembleTx,改为手动组装后再提交:
// 创建调用作用域 const scope = liquidityPoolContract.functions .deposit({ bits: wallet.address.toB256() }) .callParams({ forward: [1000, TestAssetId.A.value], }); // 取出交易请求 const request = await scope.getTransactionRequest(); // 手动调用 assembleTx 估算并填充交易 await provider.assembleTx({ request, feePayerAccount: predicate, // 使用 Predicate 作为费用支付方 accountCoinQuantities: [ { amount: 1000, assetId: TestAssetId.A.value, account: wallet, changeOutputAccount: wallet, }, ], }); // 提交交易,跳过 SDK 的自动 assembleTx 步骤 const response = await scope .fromRequest(request) .call({ skipAssembleTx: true }); const { transactionResult: { isStatusSuccess }, } = await response.waitForResult(); console.log('isStatusSuccess', isStatusSuccess);这条路径有两个关键点,缺一不可:
fromRequest(request):把手动修改过的交易请求交还给调用流程,保证你手动做的改动在提交时生效;call({ skipAssembleTx: true }):告诉 SDK 跳过自动assembleTx。如果不跳过,SDK 会重新估算交易,覆盖掉你手动指定的 Predicate 费用支付逻辑。
provider.assembleTx()还支持blockHorizon(gas 价格估算向前看的块数,默认10)、estimatePredicates(是否为 Predicate 估算 gas)、reserveGas(额外预留 gas)等参数,完整参数表见 AssembleTx 文档。
常见失败与限制
Predicate 余额不足:Predicate 要支付的手续费超过其持有的基础资产时,转账/调用会失败。Send And Spend Funds From Predicates 文档 给出了这类场景下 SDK 抛出的错误信息(文档示例):
Insufficient funds or too many small value coins. Consider combining UTXOs. For the following asset ID: '{baseAssetId}'.遇到该错误时,先给 Predicate 补充基础资产,再重新发起调用。
每个 assetId 只能有一个找零输出:Fuel 的 UTXO 模型中,交易会花费掉包含在内的全部 UTXO,找零(OutputChange)按assetId归到唯一账户。当同一资产来自多个账户时,只有changeOutputAccount指定的账户收到找零。如果你的调用涉及多个账户的同一资产,必须显式指定changeOutputAccount,否则会出现一个账户出资、另一个账户收回找零的预期外行为。
Predicate 验证失败:Predicate 返回false时,SDK 会直接抛出验证错误,交易不会上链。
参考文档
- 合约调用中指定 Predicate 支付费用的两种方式:custom-contract-calls.md
assembleTx参数与默认行为:assemble-tx.md- Predicate 的资金流转与验证失败现象:send-and-spend-funds-from-predicates.md
【免费下载链接】fuels-tsFuel Network Typescript SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-ts
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考