1use crate::env::LEDGER;
2use crate::ledger::types::icp::{BlockIndexed, Blocks};
3use crate::ledger::utils::account_identifier_equal;
4use candid::{Func, Principal};
5use futures::future::join_all;
6use ic_cdk::api::call::{
7 CallResult as DeprecatedCallResult, RejectionCode as DeprecatedRejectionCode,
8};
9use ic_cdk::call;
10use ic_cdk::call::{CallResult, Error};
11use ic_ledger_types::{
12 query_blocks, transfer, AccountIdentifier, ArchivedBlockRange, BlockIndex, GetBlocksArgs,
13 GetBlocksResult, Memo, Operation, Subaccount, Tokens, Transaction, TransferArgs,
14 TransferResult, DEFAULT_SUBACCOUNT,
15};
16
17pub const SUB_ACCOUNT: Subaccount = DEFAULT_SUBACCOUNT;
19
20pub fn principal_to_account_identifier(
29 principal: &Principal,
30 sub_account: &Subaccount,
31) -> AccountIdentifier {
32 AccountIdentifier::new(principal, sub_account)
33}
34
35pub async fn transfer_payment(
47 to: &Principal,
48 to_sub_account: &Subaccount,
49 memo: Memo,
50 amount: Tokens,
51 fee: Tokens,
52) -> CallResult<TransferResult> {
53 let account_identifier: AccountIdentifier = principal_to_account_identifier(to, to_sub_account);
54
55 let args = TransferArgs {
56 memo,
57 amount,
58 fee,
59 from_subaccount: Some(SUB_ACCOUNT),
60 to: account_identifier,
61 created_at_time: None,
62 };
63
64 transfer_token(args).await
65}
66
67pub async fn transfer_token(args: TransferArgs) -> CallResult<TransferResult> {
75 let ledger = Principal::from_text(LEDGER).unwrap();
76
77 transfer(ledger, &args).await
78}
79
80pub async fn find_payment(
91 from: AccountIdentifier,
92 to: AccountIdentifier,
93 amount: Tokens,
94 block_index: BlockIndex,
95) -> Option<BlockIndexed> {
96 let ledger = Principal::from_text(LEDGER).unwrap();
97
98 let response = blocks_since(ledger, block_index, 1).await.unwrap();
101
102 fn payment_check(
103 transaction: &Transaction,
104 expected_from: AccountIdentifier,
105 expected_to: AccountIdentifier,
106 expected_amount: Tokens,
107 ) -> bool {
108 match &transaction.operation {
109 None => (),
110 Some(operation) => match operation {
111 Operation::Transfer {
112 from, to, amount, ..
113 } => {
114 return account_identifier_equal(expected_from, *from)
115 && account_identifier_equal(expected_to, *to)
116 && expected_amount.e8s() == amount.e8s();
117 }
118 Operation::Mint { .. } => (),
119 Operation::Burn { .. } => (),
120 Operation::Approve { .. } => (),
121 Operation::TransferFrom { .. } => (),
122 },
123 }
124
125 false
126 }
127
128 let block = response
129 .iter()
130 .find(|(_, block)| payment_check(&block.transaction, from, to, amount));
131
132 block.cloned()
133}
134
135pub async fn chain_length(block_index: BlockIndex) -> CallResult<u64> {
143 let ledger = Principal::from_text(LEDGER).unwrap();
144 let response = query_blocks(
145 ledger,
146 &GetBlocksArgs {
147 start: block_index,
148 length: 1,
149 },
150 )
151 .await?;
152 Ok(response.chain_length)
153}
154
155pub async fn find_blocks_transfer(
165 block_index: BlockIndex,
166 length: u64,
167 account_identifiers: Vec<AccountIdentifier>,
168) -> Blocks {
169 let ledger = Principal::from_text(LEDGER).unwrap();
170
171 let response = blocks_since(ledger, block_index, length).await.unwrap();
174
175 fn valid_mission_control(
176 transaction: &Transaction,
177 account_identifiers: &[AccountIdentifier],
178 ) -> bool {
179 match &transaction.operation {
180 None => (),
181 Some(operation) => match operation {
182 Operation::Transfer { from, to, .. } => {
183 return account_identifiers.iter().any(|&account_identifier| {
184 account_identifier == *to || account_identifier == *from
185 });
186 }
187 Operation::Mint { .. } => (),
188 Operation::Burn { .. } => (),
189 Operation::Approve { .. } => (),
190 Operation::TransferFrom { .. } => (),
191 },
192 }
193
194 false
195 }
196
197 response
198 .into_iter()
199 .filter(|(_, block)| valid_mission_control(&block.transaction, &account_identifiers))
200 .collect()
201}
202
203async fn blocks_since(
213 ledger_canister_id: Principal,
214 start: BlockIndex,
215 length: u64,
216) -> DeprecatedCallResult<Blocks> {
217 let response = query_blocks(ledger_canister_id, &GetBlocksArgs { start, length })
221 .await
222 .map_err(|err: Error| (DeprecatedRejectionCode::CanisterReject, err.to_string()))?;
223
224 let blocks: Blocks = response
225 .blocks
226 .into_iter()
227 .enumerate()
228 .map(|(index, block)| (start + (index as u64), block))
229 .collect();
230
231 if response.archived_blocks.is_empty() {
232 Ok(blocks)
233 } else {
234 type FromArchiveResult = DeprecatedCallResult<Blocks>;
235
236 async fn get_blocks_from_archive(range: ArchivedBlockRange) -> FromArchiveResult {
237 let args = GetBlocksArgs {
238 start: range.start,
239 length: range.length,
240 };
241 let func: Func = range.callback.into();
242
243 let response: DeprecatedCallResult<(GetBlocksResult,)> =
244 call(func.principal, &func.method, (args,)).await;
245
246 match response {
247 Err(e) => Err(e),
248 Ok((block_result,)) => match block_result {
249 Err(_) => Err((
250 DeprecatedRejectionCode::Unknown,
251 "Block results cannot be decoded".to_string(),
252 )),
253 Ok(blocks_range) => Ok(blocks_range
254 .blocks
255 .into_iter()
256 .enumerate()
257 .map(|(index, block)| (range.start + (index as u64), block))
258 .collect()),
259 },
260 }
261 }
262
263 let mut order_archived_blocks = response.archived_blocks;
265 order_archived_blocks.sort_by(|a, b| a.start.cmp(&b.start));
266
267 let futures: Vec<_> = order_archived_blocks
269 .into_iter()
270 .map(get_blocks_from_archive)
271 .collect();
272
273 let archive_responses: Vec<FromArchiveResult> = join_all(futures).await;
274
275 let results = archive_responses
276 .into_iter()
277 .collect::<DeprecatedCallResult<Vec<Blocks>>>()?;
278
279 Ok(results.into_iter().flatten().chain(blocks).collect())
280 }
281}