用途
ICRC Ledger 用于在 Internet Computer 上部署和调用 ICRC-1/ICRC-2 同质化代币账本,覆盖 ICP、ckBTC、ckETH 等代币。ICRC-1 定义同质化代币的转账、余额与元数据接口;ICRC-2 在其上扩展 approve/transferFrom(授权额度)机制,使第三方可按授权额度代为花费代币。
主要能力
- 转账(icrc1_transfer)、余额查询(icrc1_balance_of)。
- ICRC-2 授权流程:icrc2_approve 授权、icrc2_transfer_from 代扣。
- 手续费查询:所有 ICRC-1 账本暴露 icrc1_fee : () -> (nat) 查询,返回当前转账手续费;手续费以代币最小单位计价(ICP 为 e8s,1 ICP = 10⁸ e8s;ckBTC 为 satoshi;ckETH 为 wei)。各账本自设手续费,且运行时可变更。
- 元数据与标准查询:icrc1_decimals、icrc1_metadata、icrc1_supported_standards。
- 交易历史查询:通过 Index canister(ICP Index、ckBTC Index、ckETH Index)。
代币与 Canister ID
| 代币 | Ledger Canister ID | Decimals |
|---|---|---|
| ICP | ryjl3-tyaaa-aaaaa-aaaba-cai | 8 |
| ckBTC | mxzaz-hqaaa-aaaar-qaada-cai | 8 |
| ckETH | ss2fx-dyaaa-aaaar-qacoq-cai | 18 |
Index canister:ICP Index qhbym-qaaaa-aaaaa-aaafq-cai;ckBTC Index n5wcd-faaaa-aaaar-qaaea-cai;ckETH Index s3zol-vqaaa-aaaar-qacpa-cai。
依赖前提
- Motoko:在 mops.toml 中设置 core = "2.3.1"。
- Rust:Cargo.toml 中 ic-cdk = "0.19"、candid = "0.10"、icrc-ledger-types = "0.1"。
使用方式
资料提供了 Motoko 与 Rust 两种完整实现,包括类型定义(Account、TransferArg、TransferError、ApproveArg、ApproveError、TransferFromArg、TransferFromError、Value)与远程账本 actor 引用。核心调用步骤:
- 查询余额:调用 icrc1_balance_of,传入 { owner; subaccount }。
- 转账:调用 icrc1_transfer,设置 from_subaccount、to、amount、fee、memo、created_at_time。
- 授权:ICRC-2 场景下,代币所有者先调用 icrc2_approve,指定 spender 与 amount。
- 代扣:spender 调用 icrc2_transfer_from,从 from 账户划转到 to 账户。
部署
在 icp.yaml 中配置 canister,使用 pre-built 步骤从 GitHub release 拉取 ic-icrc1-ledger.wasm.gz(需先将 <RELEASE_TAG> 替换为最新 release tag),并在 init_args 中指定 candid 格式的初始化参数文件。
初始化参数(icrc1_ledger_init.args)包含 token_symbol、token_name、minting_account、transfer_fee、metadata、initial_balances、archive_options 与 feature_flags(如启用 icrc2 需设 icrc2 = true)。注意 minting_account 必须与 initial_balances 中任何 principal 不同;文件中不会展开如 $(icp identity principal) 的 shell 替换,需填入字面 principal 字符串。
部署命令:
icp network start -d
icp deploy icrc1_ledger
常见陷阱(资料明确列出)
- 假设所有账本手续费相同:各账本自设手续费(如 ICP = 10000 e8s,ckBTC = 10 satoshi),不可复制混用;手续费运行时可变更,需处理 BadFee { expected_fee }。
- 忘记在 transferFrom 前 approve:ICRC-2 transferFrom 在未授权时会以 InsufficientAllowance 拒绝,必须先 approve。
- 未处理 Err 变体:icrc1_transfer 返回 Result<Nat, TransferError>,错误变体包括 BadFee、BadBurn、InsufficientFunds、TooOld、CreatedInFuture、Duplicate、TemporarilyUnavailable、GenericError。
- 使用错误的 Account 格式:ICRC-1 Account 为 { owner: Principal; subaccount: ?Blob },subaccount 为 32 字节 blob,传 null 使用默认子账户(全零)。
- 遗漏 created_at_time:缺少该字段会失去去重保护,24 小时内两笔相同转账都会执行;应设为 Time.now()(Motoko)或 ic_cdk::api::time()(Rust)。
- 将 Canister ID 硬编码为文本:需使用 Principal.fromText(...)(Motoko)或 Principal::from_text(...)(Rust)。
- 从前端调用账本:ICRC-1 转账应经后端 canister 发起,避免重入并绕开业务逻辑。
- --argument-file / init_args.path 中的 shell 替换不生效:文件按字面读取,需改用 --argument 或预先用 envsubst / sed 生成文件。
- minting account 不能调用 icrc2_approve:若 minting_account 与 initial_balances 使用同一 principal,该 principal 调用 icrc2_approve 会触发 trap "the minting account cannot delegate mints";生产环境 minting account 通常为专用 minter canister。
- 与 minting account 的转账手续费为零:转入 minting account 为 burn,转出为 mint,均要求 fee = null(或 ?0);传入常规手续费会以 BadFee { expected_fee = 0 } 失败。
适用场景
在 Internet Computer 上开发涉及 ICP、ckBTC、ckETH 等 ICRC 代币的应用,需要进行余额查询、转账、授权代扣、手续费处理与账本部署时适用。
限制
资料未提供收费、价格、平台支持范围与效果方面的说明;除上述代币、Canister ID 与依赖版本外,其他代币与工具的细节暂未提供。示例代码中标注的两处安全警告指出:sendTokens 与 transferFrom 在生产环境需添加访问控制,否则任意调用者均可转账。