CC 充 值流程
CC 是 Rocky 的原生代币,作为 Daml 合约在 Canton 网络上发行。本文介绍用户如何把 CC 充值到 Rocky 交易账户,分为 DevNet(当前实现)与 MainNet(规划)两套流程。
涉及概念:Canton Party、Daml Holding 合约、Canton Bridge。如果不熟悉,请先阅读 Canton 网络介绍。
一、共同的核心:DepositIntent 合约
无论 DevNet 还是 MainNet,最终的"上链 → 入账"环节都走同一条管道:
DepositIntent (Daml 合约)
│
▼ canton-bridge consumer 监听到 Created event
NATS ledger.events.deposit_intent
│
▼ internal-ledger 消费
UPDATE ledger.accounts SET available = available + amount
WHERE user_id = $1 AND asset = 'CC'
DepositIntent 是 Rocky 自定义的 Daml 模板,由用户(或其代理)作为 signatory 创建:
template DepositIntent
with
user_party : Party -- signatory
exchange : Party -- observer (Rocky's app-provider)
asset : Text -- "CC", "USDC", ...
amount : Decimal
intent_id : Text -- UUIDv7 for idempotency
where
signatory user_party
observer exchange
...
两种环境的差异**只在"DepositIntent 怎么被创建"**这一步:
| 环节 | DevNet | MainNet(规划) |
|---|---|---|
| 触发方 | 前端 TopNav 一键充值按钮 | 用户钱包转账操作 |
| 实际转账 | 无(dev tool 模拟) | 真实的 Splice Holding 转账 |
| Signatory 权限 | shadow-user-party-id(共享) | 用户自己的 Party |
| 余额校验 | 不校验 | 必须有足够的 CC Holding |
二、DevNet 流程(当前实现)
2.1 用户视角
- 登录 Rocky → 点击右上角用户头像打开账户 Modal
- "CC 充值"区域输入金额(如
1000),点击"充值 CC" - ~1-3 秒后 modal 中"CC 余额"自动更新
2.2 API 调用链
Browser
POST /api/perp/deposits/cc {user_id, amount}
│
▼
mtc-exchange (Next.js BFF)
/api/perp/deposits/cc/route.ts
└─ 注入 asset: "CC",转发:
POST http://api-gateway/v1/deposits/seed {user_id, asset: "CC", amount}
│
▼
api-gateway (Rust, port 8080)
dev_tools::seed_deposit
└─ 调用 gRPC:
canton_bridge.SubmitDepositIntent({user_id_hex, asset, amount})
│
▼
canton-bridge (Rust, port 50071)
service::submit_deposit_intent
└─ 用 shadow_user_party_id 作为 signatory 创建 DepositIntent 合约:
Ledger API: CreateCommand(DepositIntent, {user_party, exchange, asset, amount})
│
▼
Canton Participant (DevNet)
把合约写入 Synchronizer,分发给 observer (Rocky app-provider)
│
▼
canton-bridge consumer (订阅 active contracts stream)
发现 DepositIntent Created event
└─ 发布 NATS: ledger.events.deposit_intent {user_id, asset, amount, intent_id}
│
▼
internal-ledger (Rust, port 50051)
chain_events::handle_deposit_intent
└─ INSERT idempotent + UPDATE ledger.accounts
SET available = available + amount WHERE user_id = ? AND asset = ?
2.3 关键代码位置
| 文件 | 作用 |
|---|---|
mtc-exchange/src/app/api/perp/deposits/cc/route.ts | 前端 BFF,注入 asset: "CC" |
rocky-backend/services/api-gateway/src/routes/dev_tools.rs | seed_deposit handler(devToolsOnly 在 BFF 侧) |
rocky-backend/services/canton-bridge/src/service.rs | submit_deposit_intent gRPC handler |
rocky-backend/services/canton-bridge/src/ledger/marshal.rs | build_deposit_intent_create Daml 命令构造 |
rocky-backend/services/canton-bridge/src/consumer.rs | active contracts 流消费 + NATS 发布 |
rocky-backend/services/internal-ledger/src/chain_events.rs | NATS 消费 + 更新 ledger.accounts |
2.4 DevNet 的简化点 / 不真实之处
- ⚠️ 不校验余额:用户可以无限"充值"。因为 shadow-user-party-id 没有任何 CC Holding 上限。
- ⚠️ 不真实转账:DepositIntent 直接由 backend 代用户创建,用户本身不持有任何 CC token。
- ⚠️ 不需要钱包:用户没有自己的 Splice wallet 实例。
- ✅ 路径真实:从
DepositIntent创建到ledger.accounts入账这段管道与 MainNet 流程一致。
2.5 验证 DevNet 充值是否成功
# 直接看 ledger.accounts 的 CC 行
docker exec rocky-backend-stack-postgres-1 psql -U rocky -d rocky -c \
"SELECT user_id, available FROM ledger.accounts WHERE asset = 'CC' ORDER BY available DESC"
或者前端 TopNav modal 实时显示。
三、MainNet 流程(规划,未实现)
3.1 用户视角
- 用户已通过 Splice / Canton wallet 持有 CC tokens(来自空投、OTC、二级市场购买)
- 在 Rocky 网页点击"充值 CC"
- Rocky 弹出指引:"请把 X CC 从你的钱包转入以下托管地址 / Party"
- 用户在自己的 Splice wallet 里执行转账(签名)
- canton-bridge 监听到转账事件 → 1-2 个区块确认后入账
- Rocky modal 显示"已入账 X CC"
3.2 关键差异
| 项目 | DevNet | MainNet |
|---|---|---|
| 真实持有方 | 无 | 用户自己的 Party |
| 触发方式 | 后端代用户创建 DepositIntent | 用户钱包发起转账 |
| 资产来源 | 凭 空(dev tool) | 用户自己持有的 CC Holding 合约 |
| 余额校验 | 无 | Daml 合约确保 from.amount >= transfer.amount |
| 入账延迟 | ~1-3s | 取决于 Canton Synchronizer 出块(通常 2-5s) |
| 失败回滚 | 不存在失败 | 用户钱包不足 / 签名失败时 transfer 不上链 |
3.3 MainNet 数据流(规划)
用户的 Splice Wallet
├─ 持有合约 1: Holding(party=alice, asset="CC", amount=1000)
│
▼ alice 在 wallet 里选择 "Transfer to Rocky"
Transfer Choice
├─ 输入金额 + Rocky 平台 party id(known constant)
├─ alice 用自己的 key 签名
▼
Canton Synchronizer
├─ 把 Holding 合约 archive
├─ 创建新 Holding(party=rocky_custody, asset="CC", amount=1000)
├─ 创建 DepositIntent(user_party=alice, exchange=rocky, asset="CC", amount=1000)
▼
canton-bridge consumer
└─ 同 DevNet:发布 NATS → internal-ledger 入账
⚠️ MainNet 流程涉及 Rocky 平台的 custody model(资金托管模型),还在设计中。详细参考仓库根目录的 Canton_Custody_Wallet_Architecture.md。
3.4 MainNet 必须解决的开放问题
- 钱包指引 UX:用户怎么知道往哪个 party 转?是 deeplink 还是手动复制?
- 金额精度:CC 的最小单位(Daml
Decimal默认 10 位精度),UI 上显示几位? - 手续费:链上 transfer 的 gas / synchronizer fee 谁出?平台 absorb 还是用户付?
- 失 败处理:转账上链了但 canton-bridge 故障没入账 → 重试 / 客服路径?
- 限额 / KYC:单笔 / 单日上限?是否需要 KYC 通过?
- 取消 / 退款:DepositIntent 创建后但还没入账时,能不能撤销?
这些问题会在迁移到 MainNet 前以单独 spec 解决。
四、与其它资产(USDC、MTC)的关系
Rocky 当前支持三种资产:
| 资产 | 类型 | 充值方式 |
|---|---|---|
| CC | Rocky 原生代币 | 本文档 |
| USDC | Splice 标准稳定币 | DevNet 用同一 seed 接口,MainNet 走 Splice 跨链桥 |
| MTC | Mining Token Credit(挖矿奖励) | 不充值,只能通过交易触发 MiningReward.Claim 获得 |
三者的入账管道都走 ledger.accounts (user_id, asset),但发行机制不同:
- CC: 平台铸造(MainNet 后由 Rocky
app-providerparty mint) - USDC: 跨链锚定
- MTC: 链上奖励合约(参见
MiningReward模板,详见后续 [挖矿奖励] 文档)
五、相关文档
- Canton 网络介绍
- 仓库根目录技术决策:
Canton_Custody_Wallet_Architecture.md— 托管钱包架构party-model-decision.md— Party 模型选型(影响 MainNet shadow vs real party)canton-privacy-arbitration-design.md— 隐私仲裁设计
- TODO: USDC 充值文档(DevNet seed + MainNet 跨链桥)
- TODO: MTC 挖矿奖励合约文档