fuels-ts 类型生成实战:fuels typegen 为 Sway 合约、脚本与谓词生成强类型 API
本文以 demo-typegen 示例 为核心,讲解 fuels-ts 仓库中 fuels typegen 类型生成(Typegen)的完整工作流:如何从 Sway 合约、脚本(script)与谓词(predicate)编译产物中提取 ABI,生成 TypeScript 强类型客户端,并基于生成的类型完成部署、调用与转账等端到端操作。读完本篇,你可以独立在自己的项目里配置 forc build 与 fuels typegen 流水线,理解 --script、--predicate 等参数的作用,并掌握生成类型的实际调用方式。
一、demo-typegen 示例的结构
apps/demo-typegen 是 fuels-ts 仓库中一个独立的最小示例包,其 README 描述了它的基本组成:一个 Sway 程序加一个 TypeScript 项目。结合仓库实际目录,当前示例已扩展为三种 Sway 程序类型各一个,外加 TypeScript 侧的测试与消费代码:
apps/demo-typegen/
├── demo-contract/
│ ├── Forc.toml # Sway 项目清单
│ └── src/main.sw # Sway 合约
├── demo-script/
│ ├── Forc.toml
│ └── src/main.sw # Sway 脚本
├── demo-predicate/
│ ├── Forc.toml
│ └── src/main.sw # Sway 谓词
├── src/
│ └── demo.test.ts # 消费生成类型的测试(即 TypeScript 侧项目)
├── package.json # 构建与类型生成脚本
├── turbo.json # Turborepo 缓存配置
└── README.md
三个 Sway 程序都极其精简,正好覆盖 typegen 需要处理的三种程序形态:
合约(demo-contract/src/main.sw)——暴露一个回显函数:
contract;
abi DemoContract {
fn return_input(input: u64) -> u64;
}
impl DemoContract for Contract {
fn return_input(input: u64) -> u64 {
input
}
}
脚本(demo-script/src/main.sw)——main 直接返回常量:
script;
fn main() -> u8 {
10
}
谓词(demo-predicate/src/main.sw)——恒为真的签名校验逻辑:
predicate;
fn main() -> bool {
true
}
每个 Sway 项目都有配套的 Forc.toml,声明项目名(如 name = "demo-contract")、入口文件 entry = "main.sw" 与许可证,fuels typegen 后续正是依据这些名字去定位编译产物中的 ABI 文件。
二、构建流程:编译 Sway 并生成类型
README 给出的核心命令是:
pnpm build
并注明“详见 package.json 中的 scripts”。下面完整列出 package.json 中的脚本及其执行顺序,这是理解 typegen 流水线的关键。
"scripts": {
"pretest": "run-s build:forc build:types",
"build:forc": "run-p forc:*",
"forc:contract": "pnpm fuels-forc build -p demo-contract --release",
"forc:script": "pnpm fuels-forc build -p demo-script --release",
"forc:predicate": "pnpm fuels-forc build -p demo-predicate --release",
"build:types": "run-p types:*",
"types:contract": "pnpm fuels typegen -i demo-contract/out/release/demo-contract-abi.json -o src/contract-types",
"types:script": "pnpm fuels typegen -i demo-script/out/release/demo-script-abi.json -o src/script-types --script",
"types:predicate": "pnpm fuels typegen -i demo-predicate/out/release/demo-predicate-abi.json -o src/predicate-types --predicate"
}
流程分两个阶段串行执行(run-s 串行、run-p 并行):
2.1 阶段一:build:forc 编译 Sway 程序
build:forc 通过 run-p forc:* 并行触发三个 forc 编译任务,每个任务形如:
pnpm fuels-forc build -p demo-contract --release
参数说明:
-p demo-contract:指定要编译的 Sway 包(对应 Forc.toml 中的name);--release:以 release 模式编译,产物落在<包目录>/out/release/下,包含字节码(.bin)、ABI(<包名>-abi.json)、存储槽位(storage_slots.json)等。
fuels-forc 来自 devDependencies 中的 @internal/forc(workspace 内部包,internal/forc),作用是安装/调用与当前 fuels-ts 版本匹配的 Forc 编译器,保证工具链版本一致。
2.2 阶段二:build:types 生成 TypeScript 类型
编译完成后,build:types 并行运行三个 typegen 任务,每个任务对应一种程序类型:
pnpm fuels typegen -i demo-contract/out/release/demo-contract-abi.json -o src/contract-types
pnpm fuels typegen -i demo-script/out/release/demo-script-abi.json -o src/script-types --script
pnpm fuels typegen -i demo-predicate/out/release/demo-predicate-abi.json -o src/predicate-types --predicate
参数含义:
| 参数 | 含义 |
|---|---|
-i <path> |
输入 ABI 文件路径,即 forc 编译产物中的 <包名>-abi.json |
-o <dir> |
生成类型的输出目录 |
--script |
声明输入为脚本程序,生成脚本调用客户端 |
--predicate |
声明输入为谓词程序,生成谓词客户端 |
| (无标志) | 默认为合约类型,生成合约与合约工厂客户端 |
需要说明的是,README 中“类型生成在 src/generated-types 内”的描述对应早期单合约形态;从当前 package.json 的脚本看,实际输出已按程序类型分为三个目录:src/contract-types、src/script-types、src/predicate-types,与测试代码中的导入路径一致。
fuels typegen 命令的实现位于 packages/fuels/src/cli/commands/build/generateTypes.ts。从源码结构看,CLI 将 -i 指定的 ABI 路径交给 @fuel-ts/abi-typegen 包的 runTypegen 执行,按程序类型分发到不同的模板生成逻辑;若未显式指定路径,则通过 getABIPaths 从 Forc 项目目录中自动发现 *-abi.json。对于 script 与 predicate,它还会额外把 out 目录下所有 *-abi.json(loader ABI)一并纳入生成范围(见 generateTypes.ts),因为脚本与谓词除业务代码外还有独立的 loader 部分。类型模板与生成引擎主体在 packages/abi-typegen 包中。更完整的参数说明可参考官方文档 generating-types.md 与 using-generated-types.md。
三、使用生成的类型:从部署到调用
demo.test.ts 完整演示了三种生成类型的消费方式。它从 fuels 导入运行时能力,从生成目录导入强类型客户端:
import { toHex, Address, Wallet, FuelError, ErrorCode } from 'fuels';
import { expectToThrowFuelError, launchTestNode } from 'fuels/test-utils';
import storageSlots from '../demo-contract/out/release/demo-contract-storage_slots.json';
import { DemoContract, DemoContractFactory } from './contract-types';
import { DemoPredicate } from './predicate-types';
import type { DemoPredicateInputs } from './predicate-types/DemoPredicate';
import { DemoScript } from './script-types';
注意 storageSlots 直接来自 forc 编译产物(demo-contract/out/release/demo-contract-storage_slots.json),这解释了为什么类型生成与 forc 编译必须按 pretest 脚本中的顺序执行。
3.1 合约:工厂部署与实例调用
生成的合约类型包含两个关键导出:DemoContractFactory(负责部署)与 DemoContract(已部署合约实例)。测试中展示了三种等价的部署/调用路径:
方式一:带存储槽位部署(demo.test.ts):
const { waitForResult } = await DemoContractFactory.deploy(wallet, {
storageSlots,
});
const { contract } = await waitForResult();
expect(contract.id).toBeTruthy();
方式二:实例化工厂后部署,再调用函数:
const factory = new DemoContractFactory(wallet);
const deploy = await factory.deploy();
const { contract } = await deploy.waitForResult();
const contractId = contract.id;
const { waitForResult } = await contract.functions.return_input(1337).call();
const { value } = await waitForResult();
expect(value.toHex()).toEqual(toHex(1337));
方式三:跳过部署,用已知合约 ID 直接连接实例(demo.test.ts):
const contractInstance = new DemoContract(contractId, wallet);
const call2 = await contractInstance.functions.return_input(1337).call();
const { value: v2 } = await call2.waitForResult();
可以看到,return_input(u64) -> u64 的 Sway 签名被 typegen 映射为强类型的 contract.functions.return_input(1337) 调用链:functions 下按函数名组织、.call() 发起交易、waitForResult() 等待回执并解包返回值。类型层面的函数名、参数个数与返回类型都由 ABI 推导,调用侧无需手写 JSON 编码。
测试还验证了生成类型的错误边界:用无资金的钱包 simulate() 会抛出 INSUFFICIENT_FUNDS_OR_MAX_COINS 的 FuelError,而 dryRun() 不消耗资金、不会抛错(demo.test.ts),说明生成的合约客户端与 fuels 的 Provider/资金检查体系是打通的。
3.2 脚本:以类钱包方式执行 main
脚本类型的用法与合约显著不同——没有部署概念,直接用钱包构造实例并调用 main(demo.test.ts):
const script = new DemoScript(wallet);
const { waitForResult } = await script.functions.main().call();
const { value } = await waitForResult();
expect(value).toStrictEqual(10);
返回值 10 与 Sway 侧 fn main() -> u8 { 10 } 严格对应。
3.3 谓词:构造、注资与转出
谓词类型的使用分三步(demo.test.ts):
const receiver = Wallet.fromAddress(Address.fromRandom(), provider);
// 1. 按 ABI 定义构造谓词输入数据
const predicateData: DemoPredicateInputs = [];
const predicate = new DemoPredicate({ provider, data: predicateData });
// 2. 向谓词地址转入资金
const tx = await wallet.transfer(predicate.address, 200_000, await provider.getBaseAssetId());
const { isStatusSuccess } = await tx.wait();
// 3. 由谓词转出资金给接收方,验证签名成功
const tx2 = await predicate.transfer(receiver.address, 50_000, await provider.getBaseAssetId());
await tx2.wait();
expect((await receiver.getBalance()).toNumber()).toEqual(50_000);
这里 DemoPredicateInputs 是 typegen 依据 fn main() -> bool 签名推导出的输入数据类型;谓词客户端构造时需要 provider 与 data 两个字段,之后即可像普通账户一样发起 transfer。本示例谓词恒返回 true,因此转出必然成功;真实场景中 data 会承载签名、白名单地址等校验参数。
四、缓存与工程化要点
- turbo.json 声明了
pretest任务的inputs(Sway 源码)与outputs(out/release/**编译产物),Turborepo 可据此对编译与生成结果做增量缓存; - 依赖上该包仅依赖 workspace 内的
fuels(运行时)与@internal/forc(编译器工具链),见 package.json,说明整条 typegen 流水线都可用 fuels-ts 自带的 CLI 完成; - 运行测试即触发完整流程:
pnpm pretest先build:forc再build:types,随后 vitest 执行 demo.test.ts 中的用例。
五、小结
以 apps/demo-typegen 为骨架,fuels-ts 的类型生成工作流可以概括为三步:
fuels-forc build -p <包名> --release编译 Sway 程序,产出*-abi.json等文件;fuels typegen -i <abi.json> -o <输出目录>生成类型,按程序类型追加--script或--predicate标志;- 在 TypeScript 侧导入生成的工厂/实例类,以
functions.<方法名>(...).call()链完成部署、调用与转账。
这套机制把 Sway ABI 的编码细节封装在生成代码里,开发者获得的是带完整类型提示的调用 API,这也是 fuels-ts “先编译、后 typegen、再编码”标准开发流程的最小可运行范例。
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