UDT 代币
在 CKB 上发行和转移用户自定义代币(UDT)与 xUDT。
在 CKB 上发行、转移和查询同质化代币。用户自定义代币(User-Defined Token,简称 UDT)的数量存储在 Cell 的 data 字段中;ccc.udt.Udt 类自动处理编码、SSRI 执行以及旧版 xUDT 的降级兼容。
完成本指南后你将能够
- 转移 UDT 代币:自动完成输入选择和找零处理
- 铸造新代币:需要持有发行方权限(owner-mode)
- 读取链上元数据:查询符合 SSRI 规范的代币名称、符号、精度和图标
- 理解 xUDT 与 sUDT 的关系
以下示例均假设你已有一个已连接的 signer。请先阅读连接钱包(浏览器端)或 Node.js 后端(服务端)。
xUDT 与 sUDT
| sUDT | xUDT | |
|---|---|---|
| 标准 | CKB RFC 25 | CKB RFC 52 |
| 扩展支持 | 无 | 支持(owner-mode、RCE 规则) |
KnownScript 值 | —(CCC 中未收录) | ccc.KnownScript.XUdt |
| 生产环境 | 旧版 | 推荐 |
CCC 的 Udt 类同时支持两种标准。默认使用 SSRI 执行模型(链上脚本执行),对旧版 xUDT 代币自动降级为链下构造。
安装
npm install @ckb-ccc/cccUDT 支持已内置于主包中,无需额外安装。
导入
import { ccc } from "@ckb-ccc/ccc";Udt 类通过 ccc.udt.Udt 访问。
构造 Udt 实例
转移或铸造前,需先创建一个 Udt 对象,需要提供两项信息:
code——持有 UDT 脚本代码的 Cell 的OutPoint(cell dep)script——唯一标识该代币的 Type 脚本(其args通常为发行方的 Lock 哈希)
const type = await ccc.Script.fromKnownScript(
signer.client,
ccc.KnownScript.XUdt,
// args 唯一标识该代币(发行方 Lock 哈希)
"0xf8f94a13dfe1b87c10312fb9678ab5276eefbe1e0b2c62b4841b1f393494eff2",
);
const code = (
await signer.client.getCellDeps(
(await signer.client.getKnownScript(ccc.KnownScript.XUdt)).cellDeps,
)
)[0].outPoint;
const udt = new ccc.udt.Udt(code, type);
// 可选第三个参数:{ executor },用于 SSRI 执行。旧版 xUDT 可省略。转移代币
适用场景:将 UDT 代币从 signer 发送至另一个地址。整体流程与 CKB 转账的四步模式一致,额外增加了一个 UDT 专属的输入填充步骤:
udt.transfer 创建包含目标输出 Cell 的交易骨架,此时尚未平衡输入——后续两步会处理。
const receiver = await signer.getRecommendedAddress();
const { script: lock } = await ccc.Address.fromString(receiver, signer.client);
let { res: tx } = await udt.transfer(signer, [
{ to: lock, amount: ccc.fixedPointFrom(1) }, // 1 个代币单位
]);udt.completeBy 收集 signer 持有的 UDT Cell,直至代币余额足以覆盖输出。多余的 UDT 自动生成找零 Cell 返还。
tx = await udt.completeBy(tx, signer);UDT Cell 在链上存储同样需要 CKB 容量。此步骤添加 CKB 输入以覆盖所有输出 Cell 所需容量:
await tx.completeInputsByCapacity(signer);await tx.completeFeeBy(signer);
const txHash = await signer.sendTransaction(tx);
console.log("UDT transfer hash:", txHash); // "0x..." — 32 字节交易哈希铸造代币
适用场景:你是代币发行方,需要增发新的代币供应量。signer 须持有铸造权限(xUDT 中通常为 owner-mode——signer 的 Lock 哈希与 Type 脚本 args 匹配)。
铸造时无需提供已有的 UDT 输入,直接创建新的代币输出:
const { script: to } = await ccc.Address.fromString(receiver, signer.client);
// 铸造 1000 个代币
let { res: tx } = await udt.mint(signer, [
{ to, amount: ccc.fixedPointFrom(1000) },
]);
await tx.completeInputsByCapacity(signer);
await tx.completeFeeBy(signer);
const txHash = await signer.sendTransaction(tx);读取代币元数据(SSRI 代币)
适用场景:在 UI 中展示代币名称、符号、精度或图标。仅适用于在链上实现了 SSRI UDT 接口的代币:
const { res: name } = await udt.name();
const { res: symbol } = await udt.symbol();
const { res: decimals } = await udt.decimals();
const { res: icon } = await udt.icon();
console.log(`${name} (${symbol}), ${decimals} decimals`);对于未实现 SSRI UDT 接口的旧版 sUDT / xUDT 代币,上述方法返回 undefined。使用前请检查返回值。
API 参考
| 方法 | 说明 |
|---|---|
new ccc.udt.Udt(code, script) | 构造 UDT 实例 |
udt.transfer(signer, transfers, tx?) | 构建转账输出 |
udt.mint(signer, mints, tx?) | 构建铸造输出 |
udt.completeBy(tx, signer) | 填充 UDT 输入并生成找零 |
udt.completeChangeToLock(tx, signer, lock) | 填充 UDT 输入并使用指定 Lock 作为找零地址 |
udt.name() | 代币名称(仅 SSRI) |
udt.symbol() | 代币符号(仅 SSRI) |
udt.decimals() | 代币精度(仅 SSRI) |
udt.icon() | 代币图标 URI(仅 SSRI) |
常见问题
udt.completeBy 抛出"not enough UDT balance"错误
signer 持有的 UDT Cell 不足以覆盖转账金额。请检查 signer 的 UDT 余额,或向该地址充值更多代币。
udt.transfer 返回 { res: tx },res 是什么?
所有 UDT 方法返回 ssri.ExecutorResponse<T> 包装对象,实际的 Transaction 在其 .res 属性中。这一设计使 CCC 能够在结果旁附加 SSRI 执行的元数据。
udt.name() / udt.symbol() 返回 undefined
该代币未实现 SSRI UDT 元数据接口,旧版 sUDT / xUDT 代币均属于这种情况。需从链下注册表获取元数据。
向从未持有该代币的地址转账
直接转账即可。udt.transfer 会在收款方 Lock 下创建新的代币输出 Cell;收款方无需提前准备——新 Cell 所需的 CKB 容量由 signer 通过 completeInputsByCapacity 支付。
下一步
- 组装交易——了解 UDT 操作所基于的声明 → 填充 → 手续费 → 发送模式。
- Spore 协议——在 CKB 上创建非同质化数码物。
- Node.js 后端——从服务端脚本发行和转移代币。
最后更新于