1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
use candid::{CandidType, Deserialize};

use crate::identity::CanisterId;

use super::CallError;

/// icp 类的账本罐子接口

// Ledger 标准
// name : () -> (Name) query;
// symbol : () -> (Symbol) query;
// decimals : () -> (Decimals) query;
// account_balance : (BinaryAccountBalanceArgs) -> (Tokens) query;
// transfer_fee : (record {}) -> (TransferFee) query;
// transfer : (TransferArgs) -> (Result_1);
// 下面的接口应该用不到
// archives : () -> (Archives) query;
// query_blocks : (GetBlocksArgs) -> (QueryBlocksResponse) query;
// query_encoded_blocks : (GetBlocksArgs) -> (QueryEncodedBlocksResponse) query;

// =================== 账本方法 ===================

// https://icscan.io/canister/ryjl3-tyaaa-aaaaa-aaaba-cai

//  ============== 查询名称 ==============
// name : () -> (Name) query;
// type Name = record { name : text };

#[derive(CandidType, Deserialize, Debug, Clone)]
pub struct LedgerName {
    name: String,
}
#[allow(unused)]
pub async fn name(canister_id: CanisterId) -> LedgerName {
    let _call_result: Result<(LedgerName,), CallError> =
        ic_cdk::call(canister_id, "name", ()).await;
    _call_result.unwrap().0
}
#[allow(unused)]
pub async fn name_by(canister_id: CanisterId) -> String {
    name(canister_id).await.name
}

//  ============== 查询符号 ==============
// symbol : () -> (Symbol) query;
// type Symbol = record { symbol : text };

#[derive(CandidType, Deserialize, Debug, Clone)]
pub struct LedgerSymbol {
    symbol: String,
}
#[allow(unused)]
pub async fn symbol(canister_id: CanisterId) -> LedgerSymbol {
    let _call_result: Result<(LedgerSymbol,), CallError> =
        ic_cdk::call(canister_id, "symbol", ()).await;
    _call_result.unwrap().0
}
#[allow(unused)]
pub async fn symbol_by(canister_id: CanisterId) -> String {
    symbol(canister_id).await.symbol
}

//  ============== 查询精度 ==============
// decimals : () -> (Decimals) query;
// type Decimals = record { decimals : nat32 };

#[derive(CandidType, Deserialize, Debug, Clone)]
pub struct LedgerDecimals {
    decimals: u32,
}
#[allow(unused)]
pub async fn decimals(canister_id: CanisterId) -> LedgerDecimals {
    let _call_result: Result<(LedgerDecimals,), CallError> =
        ic_cdk::call(canister_id, "decimals", ()).await;
    _call_result.unwrap().0
}
#[allow(unused)]
pub async fn decimals_by(canister_id: CanisterId) -> u32 {
    decimals(canister_id).await.decimals
}

//  ============== 查询余额 ==============
// account_balance : (BinaryAccountBalanceArgs) -> (Tokens) query;
// type BinaryAccountBalanceArgs = record { account : vec nat8 };
// type Tokens = record { e8s : nat64 };

#[derive(CandidType, Deserialize, Debug, Clone)]
pub struct LedgerBinaryAccountBalanceArgs {
    pub account: LedgerAccountIdentifier,
}
#[derive(CandidType, Deserialize, Debug, Clone)]
pub struct LedgerTokens {
    pub e8s: u64, // ICP 接口指定 8 位精度的数值
}
#[allow(unused)]
pub async fn account_balance(
    canister_id: CanisterId,
    args: LedgerBinaryAccountBalanceArgs,
) -> LedgerTokens {
    let _call_result: Result<(LedgerTokens,), CallError> =
        ic_cdk::call(canister_id, "account_balance", (args,)).await;

    _call_result.unwrap().0
}
#[allow(unused)]
pub async fn account_balance_by(canister_id: CanisterId, account: LedgerAccountIdentifier) -> u64 {
    account_balance(canister_id, LedgerBinaryAccountBalanceArgs { account })
        .await
        .e8s
}

//  ============== 查询转账费用 ==============
// transfer_fee : (record {}) -> (TransferFee) query;
// type TransferFee = record { transfer_fee : Tokens };

#[derive(CandidType, Deserialize, Debug, Clone)]
pub struct LedgerTransferFeeArg {}
#[derive(CandidType, Deserialize, Debug, Clone)]
pub struct LedgerTransferFee {
    // The fee to pay to perform a transfer
    pub transfer_fee: LedgerTokens,
}
#[allow(unused)]
pub async fn transfer_fee(
    canister_id: CanisterId,
    args: LedgerTransferFeeArg,
) -> LedgerTransferFee {
    let _call_result: Result<(LedgerTransferFee,), CallError> =
        ic_cdk::call(canister_id, "transfer_fee", (args,)).await;

    _call_result.unwrap().0
}
/// 查询转账费用, 简化参数
#[allow(unused)]
pub async fn transfer_fee_by(canister_id: CanisterId) -> u64 {
    transfer_fee(canister_id, LedgerTransferFeeArg {})
        .await
        .transfer_fee
        .e8s
}

//  ============== 转账 ==============
// transfer : (TransferArgs) -> (Result_1);
// type TimeStamp = record { timestamp_nanos : nat64 };
// type Tokens = record { e8s : nat64 };
// type TransferArgs = record {
//   to : vec nat8;
//   fee : Tokens;
//   memo : nat64;
//   from_subaccount : opt vec nat8;
//   created_at_time : opt TimeStamp;
//   amount : Tokens;
// };
// type Result_1 = variant { Ok : nat64; Err : TransferError_1 };
// type TransferError_1 = variant {
//   TxTooOld : record { allowed_window_nanos : nat64 };
//   BadFee : record { expected_fee : Tokens };
//   TxDuplicate : record { duplicate_of : nat64 };
//   TxCreatedInFuture;
//   InsufficientFunds : record { balance : Tokens };
// };

pub type LedgerMemo = u64; // 转账需要记录的标识码
#[derive(CandidType, Deserialize, Debug, Clone)]
pub struct LedgerTimestamp {
    pub timestamp_nanos: u64, // 时间戳, 纳秒
}
// 账户 ID 是长度为 32 的byte数组
// 前 4 位是大端法编码的后面 28 位数字的 CRC32 校验码
// The first 4 bytes is big-endian encoding of a CRC32 checksum of the last 28 bytes.
pub type LedgerAccountIdentifier = Vec<u8>;
// 子账户是任意长度为 32 的byte数组
// 使用子账户机制, 让一个用户 principal 控制大量的账本账户
// Ledger uses subaccounts to compute the source address, which enables one
// principal to control multiple ledger accounts.
pub type LedgerSubaccount = Vec<u8>;
// 转账参数
#[derive(CandidType, Deserialize, Debug, Clone)]
pub struct LedgerTransferArgs {
    // 调用者指定的使用的子账户地址
    // 如果没有, 则默认全 0 的子账户
    // The subaccount from which the caller wants to transfer funds.
    // If null, the ledger uses the default (all zeros) subaccount to compute the source address.
    pub from_subaccount: Option<LedgerSubaccount>,
    // 想要转给目标地址的数量
    pub amount: LedgerTokens,
    // 目标地址, 长度为 32 的byte数组, 转账成功, 目标地址的余额会增加 amount 的数量
    // The destination account. If the transfer is successful, the balance of this address increases by `amount`.
    pub to: LedgerAccountIdentifier,
    // 调用者必须支付的交易费, 必须是 10000 e8s
    // The amount that the caller pays for the transaction. Must be 10000 e8s.
    pub fee: LedgerTokens,
    // 交易标识码 u64的数字
    pub memo: LedgerMemo,
    // 请求的时间节点, 如果是空, 则默认 IC 系统当前时间
    // The point in time when the caller created this request. If null, the ledger uses current IC time as the timestamp.
    pub created_at_time: Option<LedgerTimestamp>,
}
pub type LedgerBlockIndex = u64; // 转账成功后返回的交易高度

// 转账可能出现的错误
#[derive(CandidType, Deserialize, Debug, Clone)]
pub enum LedgerTransferError {
    // 手续费不正确
    // The fee that the caller specified in the transfer request was not the one that ledger expects.
    // The caller can change the transfer fee to the `expected_fee` and retry the request.
    BadFee { expected_fee: LedgerTokens },
    // 余额不足
    // The account specified by the caller doesn't have enough funds.
    InsufficientFunds { balance: LedgerTokens },
    // 交易过期了, 请求时间太早了, 距离 IC 系统当前时间 24 小时内的请求可以被接受
    // The request is too old.
    // The ledger only accepts requests created within 24 hours window.
    // This is a non-recoverable error.
    TxTooOld { allowed_window_nanos: u64 },
    // 未来的交易, 指定交易时间在未来
    // The caller specified `created_at_time` that is too far in future.
    // The caller can retry the request later.
    TxCreatedInFuture,
    // 重复交易 // ! 猜测是通过交易请求时间判断是否重复的
    // The ledger has already executed the request.
    // `duplicate_of` field is equal to the index of the block containing the original transaction.
    TxDuplicate { duplicate_of: LedgerBlockIndex },
}
// 转账结果
pub type LedgerTransferResult = Result<LedgerBlockIndex, LedgerTransferError>;

#[allow(unused)]
pub async fn transfer(canister_id: CanisterId, args: LedgerTransferArgs) -> LedgerTransferResult {
    let _call_result: Result<(LedgerTransferResult,), CallError> =
        ic_cdk::call(canister_id, "transfer", (args,)).await;
    _call_result.unwrap().0
}
#[allow(unused)]
pub async fn transfer_by(
    canister_id: CanisterId,
    amount: u64,
    to: LedgerAccountIdentifier,
    fee: u64,
    memo: u64,
) -> LedgerTransferResult {
    transfer(
        canister_id,
        LedgerTransferArgs {
            from_subaccount: None,
            amount: LedgerTokens { e8s: amount },
            to,
            fee: LedgerTokens { e8s: fee },
            memo,
            created_at_time: None,
        },
    )
    .await
}