news 2026/9/10 2:36:39

Fuel SDK 实战:使用 Predicate 发送与花费链上资产(Send and Spend Funds From Predicates)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Fuel SDK 实战:使用 Predicate 发送与花费链上资产(Send and Spend Funds From Predicates)

Fuel SDK 实战:使用 Predicate 发送与花费链上资产(Send and Spend Funds From Predicates)

【免费下载链接】fuels-tsFuel Network Typescript SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-ts

Predicate(谓词)是 Sway 中一类返回布尔值的特殊程序,它像一道"规则锁"一样守护链上资产——只有当交易满足谓词预设条件时,资产才允许被转移。本文以 fuels-ts 仓库中 send-and-spend-funds-from-predicates.md 为主线,结合仓库内真实 Sway 源码与 TS 测试片段,完整演示"向谓词地址转账、由谓词条件验证后花费资产、预构建交易、异常与验证失败处理"的整条链路。读完本文你将掌握:如何用forc build产物实例化谓词、如何为谓词充值并执行转移、如何用createTransfer预取交易 ID,以及两类典型失败场景的排查方法。

Predicate 如何"锁定"与"解锁"资产

在 Fuel 网络中,资产可以像发送到普通地址一样发送到 Predicate 的地址(该地址由谓词字节码与数据计算出的根root派生而来)。区别在于:要花掉这些资产,交易必须附上谓词字节码与数据,让链下先执行谓词逻辑,返回true才放行,返回false则整笔交易被拒绝。这一特性在 Predicates 概览 中有清晰阐述:谓词是纯函数、不依赖链上存储,只在收到参数后做出布尔决策,因此校验可以在"上链之前"完成,既降低网络拥堵,也让交易更便宜。

当谓词校验成功后资产才可用;否则 SDK 会抛出验证错误(见下文"Predicate 验证失败"一节)。

第一步:编写一个受控地址的 Sway Predicate

仓库中的示例谓词位于 apps/docs/sway/simple-predicate/src/main.sw,完整逻辑如下:

predicate; fn main(input_address: b256) -> bool { let valid_address = 0xfc05c23a8f7f66222377170ddcbfea9c543dff0dd2d2ba4d0478a4521423a9d4; input_address == valid_address }

它的含义非常直接:

  • 入口函数main接收一个b256类型的参数input_address
  • 函数体内硬编码了一个"合法地址"valid_address
  • 只有当input_address与该valid_address完全相等时返回true,否则返回false

也就是说,只有持有正确地址数据的人,才能触发这笔受控资产的转移。

第二步:编译并收集两大关键产物

在项目目录执行forc build编译上述谓词后,会得到两个必需工件:

  1. JSON ABI:描述main函数签名与参数类型,供 SDK 正确编码谓词数据;
  2. 谓词字节码(binary):需要附带在交易中供节点/链下执行。

此外,fuels-ts 的类型生成工具会根据 ABI 自动生成类型安全的谓词类(本仓库文档示例中为SimplePredicate),这样无需手写 ABI 与字节码的加载逻辑即可实例化。

第三步:在 SDK 中实例化谓词并传入数据

关键点:本示例main需要一个名为input_address、类型为B256的参数。该参数正是谓词数据(predicate data),需要在构造谓词时一并传入。代码见 transferring-assets.ts:

import { Provider, Wallet } from 'fuels'; const provider = new Provider(LOCAL_NETWORK_URL); const baseAssetId = await provider.getBaseAssetId(); const sender = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider); const receiver = Wallet.generate({ provider }); const inputAddress = '0xfc05c23a8f7f66222377170ddcbfea9c543dff0dd2d2ba4d0478a4521423a9d4'; const predicate = new SimplePredicate({ provider, data: [inputAddress], });

几个值得注意的实现细节:

  • 在底层 Predicate 实现 中,构造函数接收bytecodeabiprovider,以及可选的dataconfigurableConstantsdata会被编码成predicateData字节,供交易携带。
  • 谓词地址(predicate.address)由字节码 + 谓词数据共同推导:数据一旦改变,能花费资金的地址也随之改变。
  • 因此原文档特别提示:若想在实例化之后再修改谓词数据,或使用与构造时不同的数据,必须重新创建新的Predicate实例。这也正是示例代码把data: [inputAddress]放在构造函数里的原因。从源码结构看,谓词数据参与了predicateId/根的计算,所以"改数据 = 换一把新锁",必须新建实例。

关于钱包的创建与资助,可以参考仓库的 钱包使用指南 与 谓词实例化完整说明。

第四步:向谓词地址转入资产

有了可用的钱包(sender)后,就可以像给普通地址转账一样,向predicate.address转入资产,为后续的"条件花费"储备资金(见 transferring-assets.ts#L23-L39):

// 打算发送给谓词的金额 const amountToPredicate = 10_000_000; // 从 sender 钱包向谓词地址转入资产 const fundPredicateTx = await sender.transfer( predicate.address, amountToPredicate, baseAssetId, { gasLimit: 1000, } ); // 等待交易上链 await fundPredicateTx.waitForResult();

transfer的三个核心参数是:接收方地址(此处为谓词地址)、转账金额、以及资产 ID(通过provider.getBaseAssetId()获取的基础资产)。

第五步:让谓词"亲自"转出资产

谓词持有资金后,即可用它来验证一笔新的转账交易:直接调用predicate.transfer,把资金转给目标钱包(receiver),见 transferring-assets.ts#L46-L59:

// 从谓词转给接收钱包的金额 const amountToReceiver = 200; // 由谓词发起转账:此时谓词条件会被求值 const transferFromPredicateTx = await predicate.transfer( receiver.address, amountToReceiver, baseAssetId ); await transferFromPredicateTx.waitForResult();

同样地,transfer接收两个核心参数:接收方地址与转账金额。SDK 在提交前会把谓词字节码与数据填充进交易输入(对应 predicate.ts 中的输入填充逻辑),链下执行谓词main

  • 若传入的input_address等于硬编码的valid_address,谓词返回true,资金成功转到receiver
  • 若不等,谓词返回false,SDK 抛出PredicateVerificationFailed错误(详见下文)。

实现层面,Predicate重写了sendTransaction并在发送前用populateTransactionPredicates把字节码、谓词数据与 witness 写入交易请求,且发送时关闭了自动依赖估算(estimateTxDependencies: false)。transfer/createTransfer则定义在 Account 基类 中,供普通钱包与谓词共用同一套转账 API。

进阶:用createTransfer预构建交易并预取交易 ID

predicate.transfer是一步到位的便捷写法。若需要"先搭好交易、确认无误再提交",可以使用createTransfer。它返回一个ScriptTransactionRequest,随后用sendTransaction提交。预构建的最大好处是:在真正提交之前就能获知交易 ID。仓库 pre-stage.ts 演示了这一流程:

// 预创建一笔从谓词转出的交易 const transactionRequest = await predicate.createTransfer( receiver.address, amountToReceiver, baseAssetId, { gasLimit: 1000, } ); // 提交前即可获得交易 ID const chainId = await provider.getChainId(); const transactionId = transactionRequest.getTransactionId(chainId); // 提交交易并等待结果 const submitTransaction = await predicate.sendTransaction(transactionRequest); await submitTransaction.waitForResult();

示例后半段还会校验transactionId === submitTransaction.id,验证"预取 ID 与最终上链交易 ID 一致",这在需要先行签名、多步编排或离线审计的场景下非常实用。PredicateAccount的其他常用方法(如transfercreateTransfersendTransactionsimulateTransaction等)可进一步参考 Predicate 方法指南。

失败场景一:试图花费谓词的全部余额

直接转出谓词地址上的全部余额会失败,因为没有剩余资产去支付交易手续费(gas)。此时 SDK 会抛出类似如下报错(见 failure-not-enough-funds.ts):

Insufficient funds or too many small value coins. Consider combining UTXOs. For the following asset ID: '<baseAssetId>'.

这个报错提示了两种排查方向:要么为交易预留足够支付手续费的部分资产(即不要转出 100% 余额),要么合并零碎的 UTXO,避免因小额硬币过多而无法凑出可用的资金与手续费组合。示例中该错误通过safeExec(来自fuels/test-utils)捕获后与期望文案比对,以验证错误确实发生。

失败场景二:Predicate 条件不满足(验证失败)

回顾我们的谓词:只有当input_address与硬编码的valid_address相等时才返回true。因此,若在实例化时传入一个随机生成的、不同于valid_addressB256地址,谓词校验必然失败,整笔转账被拒绝。仓库 failure-returns-false.ts 展示了该场景的错误特征:

const inputAddress = getRandomB256(); // 故意用与 valid_address 不同的地址 // ...先给谓词充值,再尝试转账... // 捕获到的错误信息以如下片段开头: const errorMessage = `PredicateVerificationFailed`;

也就是说,SDK 抛出的错误以PredicateVerificationFailed开头。只要在测试或日志中看到这一关键字,即可确认是谓词条件未满足(返回false)导致交易被拒,而不是网络或余额问题——这能显著加速排查。

小结与延伸阅读

围绕"发送与花费谓词资产",核心链路可概括为四步:

  1. 编写 Sway 谓词(main返回布尔值),执行forc build得到 ABI 与字节码;
  2. 构造SimplePredicate(或任意生成的谓词类),把main所需的参数通过data传入;
  3. 用普通钱包向predicate.address转账充值;
  4. 调用predicate.transfer(或createTransfer+sendTransaction两步式),由谓词条件放行资产;注意留出手续费余额,否则会触发资金不足错误。

谓词机制的边界条件(数据不可变、纯函数、链下校验)使其天然适合做时间锁、多签、白名单地址、条件支付等"可编程资产锁"场景。想深入了解的朋友还可以继续阅读仓库中同一专题下的 Predicate 概览与调试技巧、实例化谓词、部署谓词、可配置常量 以及 自定义交易;对应 Sway 源码与 TypeScript 片段则分别位于 docs/sway/simple-predicate/src/main.sw 与 predicates/snippets/cookbook 目录下,可直接对照运行验证。

【免费下载链接】fuels-tsFuel Network Typescript SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-ts

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 2:36:15

SpringBoot+Vue健康饮食系统调试实战指南

简介&#xff1a;这是一套面向计算机专业本科生的Java全栈毕设实战项目&#xff0c;聚焦智能健康饮食场景&#xff0c;专为毕业设计、课程设计及期末大作业打造&#xff0c;兼顾SpringBoot后端开发与Vue前端工程化实践能力训练。资源包共353个文件&#xff0c;涵盖88个核心Java…

作者头像 李华
网站建设 2026/9/10 2:34:12

智慧工地安全帽反光衣检测:VOC与YOLO标注转换及YOLOv8训练实战

简介&#xff1a;这份智慧工地检测数据集来自真实工地监控摄像头&#xff0c;共3065张图像&#xff0c;覆盖多视角多场景抓拍&#xff0c;面向反光衣穿戴检测、安全帽佩戴检测与人员入侵告警等任务。压缩包共2000个文件&#xff0c;以XML&#xff08;VOC格式&#xff09;标注、…

作者头像 李华
网站建设 2026/9/10 2:31:37

Prophet时序预测实战:数据预处理、参数调优与结果解读

简介&#xff1a;本资源是一份面向Python初学者与数据分析从业者的Prophet时间序列预测入门实践脚本&#xff0c;聚焦业务场景下的快速建模与结果解读。资源核心为一个精简实用的prophet.py脚本&#xff0c;完整封装了数据加载、模型初始化、趋势与季节性拟合、未来365天预测及…

作者头像 李华
网站建设 2026/9/10 2:29:27

【YOLOv13多模态融合改进】| TGRS 2026 CIFusion 通道交互融合 全局通道自适应权重 + 双向残差模态互通,缓解模态互抑提升小目标精度

一、本文介绍 本文记录的是利用CIF通道交互融合模块改进YOLOv13的可见光-红外双模态目标检测。 CIF(Channel Interaction Fusion)通过RGB-红外特征通道拼接、全局通道注意力权重生成与双向残差跨模态交互结合,动态分配两类模态通道贡献,搭建可见光纹理、红外热辐射双向互…

作者头像 李华