使用本页,可以在创建、上架或索引代币之前,了解代币在这条链上是什么。RPL 是 Rand 的代币标准,自链 14 起引入。代币是一种屏蔽原生资产:账本注册表中的一个条目,余额以普通票据的形式存放在 RAND 所用的同一棵承诺树中。它不是合约。如需逐步创建,请参阅部署 RPL 代币。
原因在于结构。Call 只写入一份回执,因此 ERC-20 式合约无处保存余额;而公开的余额表会让代币成为屏蔽链上唯一的透明资产。RPL 转而让代币成为账本中的一等对象。链 20 加入了 RPL-2(v0.6.8):程序可以持有公开状态单元和公开金库,并可以成为自身代币的铸造权限方(见下文);个人持有的余额仍然是票据。链 21(fullnode v0.8.0,自 2026-10-12 起上线)在金库之外又增加了两类公开的代币持有方:RPL-3 perps,即永续合约引擎,其唯一的市场 BTC-PERP 以 zUSD 作抵押;以及多签账户,即按资产公开记录余额的 M-of-N Dilithium2 账户(交易)。
代币是一个注册表条目
TokenRegistry(crates/randprotocol-core/src/ledger/tokens.rs)是所有代币共用的一张表,无论原生还是桥接。桥正是通过这张注册表解析资产的索引。每个条目包含:
| 字段 | 规则 |
|---|---|
id | 资产 id,32 字节,内容寻址 |
index | u32,票据的 asset 字所携带的值;连续编号,按注册顺序从 1 起分配。索引 0 是 RAND,从不注册 |
name | 1 至 32 字节的 UTF-8,允许空格,不可更改 |
symbol | 1 至 12 个 ASCII 可打印字符字节(0x21..=0x7e,不含空白),不可更改 |
decimals | 0 至 9,不可更改 |
authority | 铸造权限方,见下文 |
mint_nonce | Key 权限方的重放计数器 |
total_supply | u64,使用带检查的算术;溢出会使铸造被拒绝。自链 15 起,它被限制在 2^63 以下(见下文) |
registered_at | 注册时的区块高度 |
小数位数上限为 9,因为金额是 u64:若取 18 位小数,u64 只能容纳 18.4 个整枚代币,而 9 正是 RAND 自身的精度。桥接代币无论其源币种使用多少位小数,一律以 8 位小数注册。
注册表由创世文件中的 tokens 部分开启,正如 bridge 和 aggregation 由各自的部分开启。没有它,所有代币操作都会在任何其他检查之前被拒绝(TokenError::Disabled)。链 21 的创世文件含有该部分,并列出一个代币 zUSD 及其七个备付来源,这一配置自链 14 起经每次切换沿用至今;链上其余代币均通过交易产生,且没有任何一个会跨切换保留:链 20 创世之后注册的十一个代币(durian.market 的 DUR 及其 LP 份额、示例程序代币、RUSD)因均无桥接支撑,已在链 21 切换时连同其票据一并丢弃。(链 14 的创世文件没有列出任何代币。)开关打开后,tokens_root(以 blake3("rand-token-leaf-1", bincode(TokenInfo)) 叶子按索引顺序构成的默克尔根)会折叠进状态根。在没有开启任何后续功能的链上,该根的域为 rand-state-4;链 20 的根还纳入了水龙头与保证金队列状态、实时的 gas 价格以及程序状态根,以 rand-state-8 为域做哈希,并再由已准入的验证者集合包裹一层;链 21 的根则加入 perps 状态(rand-state-9)和多签注册表(rand-state-multisig-1)(共识)。
含有 tokens 部分的链还要求每个创世 alloc 票据都带有其打开信息(pk、time、r),节点会重新计算承诺,并拒绝不匹配的文件。承诺位于资产 0,除非打开信息指向同一创世文件所列代币的索引:链 21 有三个这样的票据,分别为索引 1 的 10、89.002999 和 0.999 zUSD(链 20 有两个,10 和 89);并且对每个列出的代币,创世票据之和必须等于其备付来源起始的 locked。一旦票据的 asset 字可以指向某个代币,未打开的创世承诺就会成为向池中未经检查地放入任意资产的无备付票据的唯一途径。
资产 id、索引与 rpl1…
一个代币有两个名称,RPC 两者都会给出。
- 资产 id 为 32 字节,与程序 id 一样是内容寻址的。原生代币的 id 为
blake3("rand-rpl-asset", bincode(name, symbol, decimals, authority, initial, salt)),其中salt是创建者选定的 32 字节,initial是完整的初始铸造(如有)。桥接代币的 id 为blake3("rand-rpl-asset", bincode(name, symbol, 8, salt))。注册一个已存在的 id 会被拒绝(TokenError::AlreadyRegistered)。 - 索引是票据实际携带的值,因为 32 字节的 id 放不进一个字。它是一种简写,仅在本链上有意义。
- 文本形式
rpl1…是 bech32m(BIP-350),人类可读部分为rpl,覆盖 32 个 id 字节:52 个数据字符加 6 个字符的校验和,共 62 个字符。凡是提到代币之处,它都与 64 位十六进制 id 并列显示,凡是查询代币之处也都接受它;十六进制仍然被接受。解析是严格的:校验和错误、前缀错误、载荷不是恰好 32 字节,或大小写混用,都会被拒绝,因此一个笔误不会解析到另一个代币。
**符号不是唯一的。**与 ERC-20 和 SPL 一样,代币的身份是其资产 id,而绝不是它的名称或代码。
rand_bridgeAssetId 计算、rand_getAssets 按每条备付行输出的旧式按 (chain, token) 计的值,是针对桥的传输格式的一个更窄的标识符,并非代币自身的 id。
铸造权限
| 权限方 | 谁可以发行份额 | 说明 |
|---|---|---|
None | 注册之后无人可以 | 固定供应:必须有初始铸造,且这就是全部供应 |
Key(PublicKey) | 某一把 Dilithium2 密钥的持有者 | 之后可凭签名再次铸造;可以把权限移交给另一把密钥,或放弃该权限 |
Bridge { backings } | 仅限桥的锁定与释放 | 绝不直接铸造;仅通过桥的治理操作存在 |
Program(ProgramId) | 仅限该程序的 Invoke(RPL-2) | 在带有 program_state 部分的链上(链 20 和 21):注册时不含初始铸造,因此其供应从零开始,只能由该程序的证明所担保的状态转移来铸造或销毁(rand token create --program <id>)。在没有该部分的链上,注册时会被拒绝 |
指定 Bridge 的 RegisterToken 会被拒绝(TokenError::AuthorityNotAllowed),指定 Program 并带有初始铸造,或在没有 program_state 的链上这样做,同样会被拒绝。Key 权限方恰好是一把 Dilithium2 公钥:任何其他字节长度的密钥都会被拒绝(TokenError::BadAuthorityKey),铸造中接收方的 ML-KEM 密钥也同样按其长度受限。两者要么是共识状态,要么是按固定费用购买的区块空间,因此都不可任意庞大。
创建代币
创建是无需许可的。Action::RegisterToken { name, symbol, decimals, authority, initial, salt, index } 搭载在普通的 RAND 手续费捆绑包上,支付 BUNDLE_BASE 加上注册表的 registration_fee。注册费是一个创世参数,限定在 1 RAND 至 10,000 RAND 之间,因此垃圾代币总要付出代价。在链 20 上它为 1 RAND,所以一次注册花费 1.001 RAND,并且链 20 会将其销毁,自链 15 起的每条链都是如此:提议者只保留捆绑包的基础费用(见下文)。
index 指明创建者为其封装初始票据的注册表索引。如果另一笔注册先占用了该索引,交易会被拒绝(TokenError::IndexMismatch),而不是让票据搁浅;创建者需在下一个索引处重新证明。
被铸造的票据由链计算,而非由提交者选定:note_commitment(recipient.pk, MINT_FROM, amount, index, time, r)。金额和接收方的 pk 在线路上是公开的,因此即便密封信封是乱码,接收方也可以根据交易自身的字段重建该票据。桥的存款有同样的防护。
为什么 id 绑定整个初始铸造
RegisterToken 是未签名的。交易绑定阻止已生成证明的副本被篡改,但对新副本可以指定什么则不置一词。倘若 id 只覆盖初始金额,待处理注册的观察者就可以提交一个接收方不同的副本,抢先提交并以同一 id 注册,从而取走固定供应代币的全部供应。由于 id 覆盖整个初始铸造(金额、接收方、r、time 和信封),被改向的副本会哈希为一个不同的资产 id:一个符号相同的不同代币,而非唯一的符号本就允许这种情况。原始交易仍会落在下一个索引,代价是一次重新证明。
转账与 RAND 使用同一种捆绑包
自 chain 14 起,每笔转移价值的交易恰好携带一个捆绑包,即固定的 4 入 4 出隐藏资产形态(交易与操作)。槽位 0–1 承载一种私有资产,即 RAND 或任一 RPL 代币,电路并不知道是哪一种。槽位 2–3 始终承载 RAND。任何资产的转账都是 Action::None,也就是普通 RAND 支付所用的操作。不存在代币转账操作,也没有任何公开字段指明代币。
观察者在一笔代币转账中看到的,与一笔 RAND 支付所公布的完全相同:一个锚点、四个作废符、四个承诺、RAND 的 fee、burn_a = burn_r = burn_asset = 0、一个 time、四个信封密文以及捆绑包证明。自 chain 17(拆分授权)起,每个捆绑包还携带一个 auth_commit 和第二个更小的授权证明,对每种资产同样如此。在 chain 20 上,每个信封恰为 1,860 字节,并为加密备注(rand send --memo)预留了空间,无论是否发送备注。观察者看不到发送方、接收方、金额、移动的是哪种资产、哪些槽位是占位项,也看不到找零。
钱包在槽位 0–1 中最多花费两张该代币的票据,在槽位 2–3 中最多花费两张用于支付手续费的 RAND 票据。手续费始终是 RAND,因此持有代币却没有 RAND 的持有者会在生成证明之前被拒绝。成本与一笔 RAND 支付相同(手续费)。自 chain 17 起,这意味着在发送方机器上生成两个 STARK 证明;在 chain 20 上的实测为:支出授权(tier 10,约 7 秒,约 1.37 MB)和捆绑包(tier 14,约 100 秒,约 1.5 MB),在网络上合计约 2.85 MB,每个证明都在该链单个证明 4 MiB 的上限之内。chain 21 的证明属于约束集 9,上限为 6 MiB,此处尚未实测。在 chain 17 之前,一笔转账只携带捆绑包证明,按当时的实测约为 1.42 MB。
铸造与授权方
TokenMint { asset, amount, recipient, r, time, envelope, nonce, signature } 用于增发由 Key 授权的代币。SetAuthority { asset, new, nonce, signature } 把授权方权限移交给另一把密钥;当 new 缺省时则放弃该权限,放弃之后任何密钥都不能再铸造该代币。对授权方不是 Key 的代币执行这两种操作都会被拒绝(TokenError::NotKeyAuthority)。
授权方签署 blake3("rand-rpl-mint-1" | "rand-rpl-authority-1", chain_id ‖ asset_id ‖ nonce ‖ body)。nonce 必须等于该代币的 mint_nonce,随后该值递增,因此已签名的铸造既不能在本链上重放,也不能带到另一条链上。铸造的 body 包含信封的摘要:仅有承诺并不覆盖信封,若没有该摘要,第三方就可以用一个无意义的信封重新包装一笔已广播的铸造并耗掉该 nonce。
两种操作都搭载在一个支付 BUNDLE_BASE 的 RAND 手续费捆绑包上。支付手续费的钱包与授权铸造的密钥是分开的,可以属于不同的人。
销毁
Action::TokenBurn { asset, amount } 搭载在同一个捆绑包上:捆绑包的 burn_asset 必须等于 asset,burn_a 必须等于 amount,burn_r 为 0。代币通过资产槽位离开池,RAND 手续费则通过同一证明的槽位 2–3 离开。total_supply 恰好减少 amount。Bridge 授权的代币在此处会被拒绝(TokenError::BridgedToken);它通过桥销毁离开。
手续费
每个 RPL 操作都以 RAND 支付。
| 操作 | 手续费下限 | chain 21 上(与 chain 20 相同) |
|---|---|---|
任何资产的转账(Action::None) | BUNDLE_BASE | 0.001 RAND |
RegisterToken | BUNDLE_BASE + registration_fee | 1.001 RAND,其中 1 RAND 被销毁 |
TokenMint | BUNDLE_BASE | 0.001 RAND |
SetAuthority | BUNDLE_BASE | 0.001 RAND |
TokenBurn | BUNDLE_BASE | 0.001 RAND |
chain 21 的创世配置中有一个 gas 部分,chain 20 也有,它只为程序调用和 invoke 定价。这些下限是固定的:任何 RPL 操作都不按 gas 或字节数付费。在 chain 21 上,每笔下限费用都被销毁,提议者保留小费(手续费);注册的 1 RAND 已经被销毁。
哪些是公开的,哪些是隐藏的
| 公开 | 隐藏 | |
|---|---|---|
| 注册 | 名称、符号、小数位数、授权方、资产 id、初始铸造的金额与接收方 | 无;代币的供应量因此从第一个区块起就可审计 |
| 后续铸造 | 金额、接收方的屏蔽地址、新票据的各字段 | 谁支付了 RAND 手续费 |
| 转账 | 没有任何信息表明有代币发生了移动 | 发送方、接收方、金额、哪种资产 |
| 销毁 | 资产与金额 | 谁持有被销毁的票据 |
| 授权方变更 | 新密钥,或已放弃的事实 | — |
因此 total_supply 始终可审计:它等于注册与每次铸造所增加的量,减去每次销毁。持有者之间的转账不泄露任何信息,包括易手的是代币而非 RAND 这一事实。
披露的方式与 RAND 相同。票据明文携带资产,因此单笔交易密钥(rand tx-key <hash>)或查看密钥打开代币票据的方式与打开 RAND 票据完全一样,rand_checkTransaction 和 rand_getViewingNotes 在其披露的票据中返回 asset。给定这样的密钥,randscan 会依据注册表解析索引,并将该转账显示为具名代币;同一笔交易的公开页面则不显示其中任何内容。
桥接代币
Bridge 授权的代币仍然是 RPL 代币:同样的注册表条目、同样的隐藏转账、同样的销毁机制。它的供应量只通过源链上的锁定(BridgeAttest)或释放(BridgeBurn)而变动。一个桥接代币可以有多个储备,每个储备是一个 (chain, token) 对,带有各自的源链小数位数、锁定数量和铸造上限,而 total_supply 始终等于其各储备已锁定数量之和。桥接代币的注册以及储备的添加,只能在桥的抗量子守护者达到法定人数的情况下进行。
在 chain 21 上,注册表的第一个代币是 zUSD(“Shielded USD”),索引 1,8 位小数,在创世文件中列出了七个储备(Ethereum、Binance Smart Chain 和 Solana 上的 USDT 与 USDC,以及 Tron 上的 USDT)。它保留了在 chain 14 上的资产 id,当时它在高度 256 通过交易注册:chain 15 到 21 都在创世配置中以相同的名称、符号和盐值列出它。它在 chain 21 开始时有 100.001999 zUSD,分在三张创世票据中(10、89.002999 和 0.999),由锁定在 Ethereum 上的 22 USDT、Binance Smart Chain 上的 23.001999、Tron 上的 30 以及 Solana 上的 25 作为储备,承接自 chain 20 在高度 814,702 时的桥状态。chain 20 开始时有 99 zUSD,分在两张票据中(10 和 89),承接自 chain 19。chain 19 开始时有 10 zUSD,由 Solana 上的 10 USDT 作为储备;chain 18 则是 Tron 上锁定的 9 USDT 与 Solana 上的 1 USDT。其总供应量等于各储备所锁定的数量,并随每次存入与赎回而变动。其 id 为 rpl1xtj6k2x8strx8c2d5fjs50ltztck5ykms4ve7nmzmstf6fhn0v0spelqtx。桥介绍了储备、释放单位、铸造上限、暂停与联署。
zUSD 是一篮子资产,而不是七个相互独立的债权。 它的各个储备按面值汇集,因此持有者承担的是所有已列出储备的风险,而不仅是自己所存入那种币的风险:已经脱锚的储备可以按面值存入,再销毁 zUSD 以换取健康的储备,直到健康的储备被抽干。限制损失的是滚动铸造窗口(在 chain 21 上与 chain 20 相同,所有储备合计每 24 小时 4,000 zUSD,另有每个储备 100,000)以及实际锁定的托管资产。该窗口限制的是速率,并为暂停铸造留出时间,但并不限定总量。储备只能在抗量子守护者达到法定人数时列入,且在铸造被暂停期间不能列入。
桥的费用,以 zUSD 计(自链 20 起)。 链 20 的创世配置设定了 bridge.fees,链 21 原样沿用:存入收取 10 bps,销毁收取 10 bps,各自向下取整到对应抵押资产的整数释放单位(6 位小数的 USDT 为 100 个基本单位),以 zUSD 票据(note)的形式支付给创世配置指定的国库地址(v0.6.8,fullnode docs/bridge.md §25)。一笔 g 的存入会追加两张票据:存入者的 g − fee 和国库的 fee;存入 1 USDT,存入者得到 0.999 zUSD。一笔 amount 的销毁将 fee 作为国库票据保留,并在源链上释放 amount − fee;守护者签署的是这一释放金额,rand_getBridgeBurn 返回 amount、release_amount 和 fee。费用票据是被发行且有抵押支持的,因此 total_supply 仍等于各抵押资产 locked 之和:一笔存入使两者都增加 g,一笔销毁使两者都减少 amount − fee。费用票据不带信封。它的每一个字段都是公开的(金额来自交易和创世费率,资产和 time,由证明摘要或销毁交易 id 派生的盲化因子,以及来自创世配置的所有者),因此国库的钱包可以从公开字段重建它(票据)。BRIDGE_BURN_FEE,即销毁时向提议者支付的 0.01 RAND,保持不变。链 14 至 19 在 Rand 上没有桥费用。
自链 15 起的注册表
自链 15 起,创世配置开启了链 14 所没有的三条注册表规则,链 21 的创世配置与链 20 一样,设定了相同的三个值。每一项都已提交到创世哈希和代币根中。
| 创世字段 | 链 21(与链 20 相同) | 作用 |
|---|---|---|
tokens.max_tokens | 4096 | 注册表容量不会超过此值;达到上限时,RegisterToken 和 RegisterBridgedToken 会被以 RegistryFull 拒绝。ListBacking 不新增代币,不受影响 |
tokens.burn_registration_fee | true | 注册的 registration_fee 被销毁,而不是支付给提议者,提议者保留 fee − registration_fee。rand_getSupply 将其计入 burned 和 registration_fees_burned(供应量) |
tokens.bound_note_value | true | 数额达到或超过 2^63 的 TokenMint、初始铸造或桥存入会被以 AmountTooLarge 拒绝;会使代币 total_supply 达到或超过 2^63 的则以 SupplyTooLarge 拒绝 |
设置票据面值上限,是因为捆绑电路对每个金额都做 63 位范围检查:面值达到或超过 2^63 的票据可以被承诺,却永远无法花费,同时还计入代币的供应量。无论创世配置如何设定,每个节点在所有链上也都会在准入时拒绝此类金额。
RPC 与 CLI
| 方法 | 参数 | 结果 |
|---|---|---|
rand_getTokens | [from_index, limit],可选,默认为 0 和 1000,上限钳制为 1000 | { enabled, registration_fee, next_index, max_tokens, burn_registration_fee, bound_note_value, tokens },按索引升序,从 from_index 起按范围读取 |
rand_getToken | [token]:索引、64 位十六进制 id 或 rpl1… | 一个代币的行,或 null;格式错误的 id 返回 -32602 |
rand_getTokenSupply | [token],形式相同 | { total_supply, backings };原生代币的 backings 为空 |
这些结果中的每个金额(total_supply、registration_fee、抵押资产的 locked、mint_cap_per_day 和 minted_today)都是十进制字符串,因为 JSON 数字在超过 2⁵³ 后不再精确。index、next_index、mint_day 和 max_tokens 是普通整数(在没有上限的链上 max_tokens 为 null);burn_registration_fee 和 bound_note_value 是布尔值。一行中的 authority 为 {"kind":"none"}、{"kind":"key","key","address"}、{"kind":"bridge","backings":[…]} 或 {"kind":"program","program"}。详细说明见 JSON-RPC。
隐私。 无论调用者关心哪个代币,rand_getTokens 的开销都相同。rand_getToken 和 rand_getTokenSupply 会让节点知道调用者关心哪个代币:对区块浏览器无妨,对即将发送的钱包则不合适。因此钱包会从完整列表中解析每个 --asset,数字索引完全不会到达节点。
| 命令 | 作用 |
|---|---|
rand token create | 在注册表的下一个索引处注册代币,固定供应量或由 Key 授权 |
rand token mint | 增发由 Key 授权的代币,由其授权密钥签名 |
rand token set-authority | 将授权移交给另一个密钥(--new-key)或放弃授权(--renounce) |
rand token burn <ASSET> <AMOUNT> | 销毁此钱包持有的数量 |
rand token info <ASSET> | 一个代币的完整行 |
rand token list | 注册表的一页 |
rand send <TO> <AMOUNT> --asset <INDEX|rpl1…|hex> | 在一个捆绑中发送代币;不带 --asset 时发送 RAND。它会在生成证明前打印接收方的指纹和金额,并询问 send? [y/N](--yes 跳过该询问) |
rand asset-balance [INDEX] | 此钱包持有的某一资产数量,或每个资产一行 |
rand token register-bridged, rand token list-backing | 桥的两个上架操作;每个都需要一个 PQ 守护者法定人数文件 |
自 v0.5.10 起,rand send --asset、rand token mint 和 rand token burn 的金额以代币的显示单位计,按其注册表行自身的 decimals 读取:6 位小数代币的 1.5 等于 1,500,000 个最小单位,小数位数超过代币所拥有的会被拒绝。send 的确认信息会同时打印两者(1.500000 USDX (1500000 units))。rand token create 的 --fixed-supply 和 --initial 仍取整的最小单位,rand bridge-burn 亦然,rand asset-balance 打印的是最小单位。链上的金额始终是最小单位的整数。标志和默认值见命令行。
与 ERC-20 和 SPL 的差异
| ERC-20 / SPL | RPL | |
|---|---|---|
| 余额 | 公开的映射或账户 | 屏蔽票据(shielded note);只有密钥持有者能看到 |
| 转账事件 | 公开,包含发送方、接收方和金额 | 无;转账不发布任何可区分的信息 |
| 每个代币的代码 | 可行:税费、rebase、钩子、冻结 | 无;每个代币遵循相同的固定规则 |
| 由程序持有 | 是 | 自链 20 起(RPL-2):程序的公开金库,由一次调用的销毁字段充入,并以链计算的票据支付 |
| 由多签账户或永续合约引擎持有 | 是 | 在链 21 上(v0.8.0):多签账户的按资产余额,与金库一样公开,以及永续合约引擎的 zUSD 抵押品;永续合约的支付是链计算的票据,与金库相同 |
| 金额 | 最高 256 位(ERC-20),64 位(SPL) | u64,最多 9 位小数 |
| 接收 | ERC-20 无需设置;SPL 需要关联代币账户 | 无需设置;屏蔽地址可接收任何资产 |
| 转账成本 | 普通的 EVM 或 SVM 执行 | 由发送方证明:共享的隐藏资产捆绑,自链 17 起还有其旁的一个小型授权证明 |
| 授权 | approve / transferFrom | 尚未支持 |
| 元数据 | 在某些实现中可变 | 在链上且不可变 |
| 冻结、黑名单 | 某些代币可行 | 按设计不在范围内 |
已知限制
- 没有授权额度。
approve和transferFrom在 RPL 规范中被设计为专用的授权额度账户,尚未实现。 - 程序的持有量是公开的。 自链 20 起,程序可以在其金库中持有代币并铸造自己的代币(RPL-2),但金库余额是公开的,与代币的供应量一样;只有票据是私密的。链 20 之前完全没有程序状态,链 21 的多签账户和永续合约引擎以同样的公开方式持有代币。
- 费用仅限 RAND。 代币不能支付自己的费用;只持有代币的钱包无法转账。
u64金额,最多 9 位小数。 18 位小数的源币种以较低精度表示;桥以 8 位小数和每个抵押资产的释放单位来处理这一点。- 每笔转账都要生成证明。 在发送方的机器上约需 100 秒和数 GB 内存;代币的路径并不比 RAND 的更便宜。
- 原生代币没有冻结、黑名单或暂停,这是有意的决定。桥的铸造暂停只适用于桥接的铸造。
- 按代币查询会泄露关注点。
rand_getToken、rand_getTokenSupply和区块浏览器的/tokens/:id会让服务器知道查询的是哪个代币。