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
use candid::{CandidType, Deserialize};

use crate::identity::{CanisterId, UserId};

use super::CallError;

/// ICRC1 标准接口

// ICRC1 标准
// icrc1_supported_standards : () -> (vec StandardRecord) query;
// icrc1_name : () -> (text) query;
// icrc1_symbol : () -> (text) query;
// icrc1_decimals : () -> (nat8) query;
// icrc1_total_supply : () -> (nat) query;
// icrc1_balance_of : (Account) -> (nat) query;
// icrc1_fee : () -> (nat) query;
// icrc1_transfer : (TransferArg) -> (Result);
// 下面的接口应该用不到
// icrc1_metadata : () -> (vec record { text; MetadataValue }) query;
// icrc1_minting_account : () -> (opt Account) query;

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

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

//  ============== 查询支持的标准 ==============
// icrc1_supported_standards : () -> (vec StandardRecord) query;
// type StandardRecord = record { url : text; name : text };

#[derive(CandidType, Deserialize, Debug, Clone)]
pub struct ICRC1SupportedStandard {
    name: String, // ICRC-1
    url: String,  // https://github.com/dfinity/ICRC-1
}
pub type ICRC1SupportedStandards = Vec<ICRC1SupportedStandard>;
#[allow(unused)]
pub async fn icrc1_supported_standards(canister_id: CanisterId) -> ICRC1SupportedStandards {
    let _call_result: Result<(ICRC1SupportedStandards,), CallError> =
        ic_cdk::call(canister_id, "icrc1_supported_standards", ()).await;
    _call_result.unwrap().0
}
#[allow(unused)]
pub async fn icrc1_supported_standards_by(canister_id: CanisterId) -> Vec<String> {
    icrc1_supported_standards(canister_id)
        .await
        .iter()
        .map(|s| s.name.clone())
        .collect()
}

//  ============== 查询名称 ==============
// icrc1_name : () -> (text) query;

#[allow(unused)]
pub async fn icrc1_name(canister_id: CanisterId) -> String {
    let _call_result: Result<(String,), CallError> =
        ic_cdk::call(canister_id, "icrc1_name", ()).await;
    _call_result.unwrap().0
}

//  ============== 查询符号 ==============
// icrc1_symbol : () -> (text) query;

#[allow(unused)]
pub async fn icrc1_symbol(canister_id: CanisterId) -> String {
    let _call_result: Result<(String,), CallError> =
        ic_cdk::call(canister_id, "icrc1_symbol", ()).await;
    _call_result.unwrap().0
}

//  ============== 查询精度 ==============
// icrc1_decimals : () -> (nat8) query;

#[allow(unused)]
pub async fn icrc1_decimals(canister_id: CanisterId) -> u8 {
    let _call_result: Result<(u8,), CallError> =
        ic_cdk::call(canister_id, "icrc1_decimals", ()).await;
    _call_result.unwrap().0
}

//  ============== 查询总供应量 ==============
// icrc1_decimals : () -> (nat8) query;

pub type ICRC1TotalSupply = candid::Nat;
#[allow(unused)]
pub async fn icrc1_total_supply(canister_id: CanisterId) -> ICRC1TotalSupply {
    let _call_result: Result<(ICRC1TotalSupply,), CallError> =
        ic_cdk::call(canister_id, "icrc1_total_supply", ()).await;
    _call_result.unwrap().0
}

//  ============== 查询余额 ==============
// icrc1_balance_of : (Account) -> (nat) query;
// type Account = record { owner : principal; subaccount : opt vec nat8 };

pub type ICRC1Subaccount = Vec<u8>;

#[derive(CandidType, Deserialize, Debug, Clone)]
pub struct ICRC1Account {
    owner: UserId,
    subaccount: Option<ICRC1Subaccount>,
}
pub type ICRC1Balance = candid::Nat;
#[allow(unused)]
pub async fn icrc1_balance_of(canister_id: CanisterId, account: ICRC1Account) -> ICRC1Balance {
    let _call_result: Result<(ICRC1Balance,), CallError> =
        ic_cdk::call(canister_id, "icrc1_balance_of", ()).await;
    _call_result.unwrap().0
}
#[allow(unused)]
pub async fn icrc1_balance_of_by(
    canister_id: CanisterId,
    owner: UserId,
    subaccount: Option<ICRC1Subaccount>,
) -> ICRC1Balance {
    icrc1_balance_of(canister_id, ICRC1Account { owner, subaccount }).await
}

//  ============== 查询交易费用 ==============
// icrc1_fee : () -> (nat) query;

pub type ICRC1Fee = candid::Nat;
#[allow(unused)]
pub async fn icrc1_fee(canister_id: CanisterId) -> ICRC1Fee {
    let _call_result: Result<(ICRC1Fee,), CallError> =
        ic_cdk::call(canister_id, "icrc1_fee", ()).await;
    _call_result.unwrap().0
}

//  ============== 转账 ==============
// icrc1_transfer : (TransferArg) -> (Result);
// type TransferArg = record {
//     to : Account;
//     fee : opt nat;
//     memo : opt vec nat8;
//     from_subaccount : opt vec nat8;
//     created_at_time : opt nat64;
//     amount : nat;
// };
// type Result = variant { Ok : nat; Err : TransferError };
// type TransferError = variant {
//     GenericError : record { message : text; error_code : nat };
//     TemporarilyUnavailable;
//     BadBurn : record { min_burn_amount : nat };
//     Duplicate : record { duplicate_of : nat };
//     BadFee : record { expected_fee : nat };
//     CreatedInFuture : record { ledger_time : nat64 };
//     TooOld;
//     InsufficientFunds : record { balance : nat };
// };

// 转账参数
#[derive(CandidType, Deserialize, Debug, Clone)]
pub struct ICRC1TransferArgs {
    // 调用者指定的使用的子账户地址
    pub from_subaccount: Option<ICRC1Subaccount>,
    // 想要转给目标地址的数量
    pub amount: candid::Nat,
    // 目标地址
    pub to: ICRC1Account,
    // 交易费
    pub fee: Option<candid::Nat>,
    // 交易标识码
    pub memo: Option<Vec<u8>>,
    // 请求的时间节点
    pub created_at_time: Option<u64>,
}
// 转账可能出现的错误
#[derive(CandidType, Deserialize, Debug, Clone)]
pub enum ICRC1TransferError {
    GenericError {
        error_code: candid::Nat,
        message: String,
    },
    TemporarilyUnavailable, // ? 临时不可用?
    BadBurn {
        min_burn_amount: candid::Nat,
    },
    Duplicate {
        duplicate_of: candid::Nat,
    },
    BadFee {
        expected_fee: candid::Nat,
    },
    CreatedInFuture {
        ledger_time: u64,
    },
    TooOld,
    InsufficientFunds {
        balance: candid::Nat,
    },
}
// 转账结果
pub type ICRC1TransferResult = Result<candid::Nat, ICRC1TransferError>;
#[allow(unused)]
pub async fn icrc1_transfer(
    canister_id: CanisterId,
    args: ICRC1TransferArgs,
) -> ICRC1TransferResult {
    let _call_result: Result<(ICRC1TransferResult,), CallError> =
        ic_cdk::call(canister_id, "icrc1_transfer", (args,)).await;
    _call_result.unwrap().0
}
#[allow(unused)]
pub async fn icrc1_transfer_by(
    canister_id: CanisterId,
    amount: candid::Nat,
    owner: UserId,
    subaccount: Option<ICRC1Subaccount>,
) -> ICRC1TransferResult {
    icrc1_transfer(
        canister_id,
        ICRC1TransferArgs {
            from_subaccount: None,
            amount,
            to: ICRC1Account { owner, subaccount },
            fee: None,
            memo: None,
            created_at_time: None,
        },
    )
    .await
}