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::{CallResult, RejectionCode};
7use ic_cdk::call;
8use ic_ledger_types::{
9 query_blocks, transfer, AccountIdentifier, ArchivedBlockRange, BlockIndex, GetBlocksArgs,
10 GetBlocksResult, Memo, Operation, Subaccount, Tokens, Transaction, TransferArgs,
11 TransferResult, DEFAULT_SUBACCOUNT,
12};
13
14pub const SUB_ACCOUNT: Subaccount = DEFAULT_SUBACCOUNT;
16
17pub fn principal_to_account_identifier(
26 principal: &Principal,
27 sub_account: &Subaccount,
28) -> AccountIdentifier {
29 AccountIdentifier::new(principal, sub_account)
30}
31
32pub async fn transfer_payment(
44 to: &Principal,
45 to_sub_account: &Subaccount,
46 memo: Memo,
47 amount: Tokens,
48 fee: Tokens,
49) -> CallResult<TransferResult> {
50 let account_identifier: AccountIdentifier = principal_to_account_identifier(to, to_sub_account);
51
52 let args = TransferArgs {
53 memo,
54 amount,
55 fee,
56 from_subaccount: Some(SUB_ACCOUNT),
57 to: account_identifier,
58 created_at_time: None,
59 };
60
61 transfer_token(args).await
62}
63
64pub async fn transfer_token(args: TransferArgs) -> CallResult<TransferResult> {
72 let ledger = Principal::from_text(LEDGER).unwrap();
73
74 transfer(ledger, args).await
75}
76
77pub async fn find_payment(
88 from: AccountIdentifier,
89 to: AccountIdentifier,
90 amount: Tokens,
91 block_index: BlockIndex,
92) -> Option<BlockIndexed> {
93 let ledger = Principal::from_text(LEDGER).unwrap();
94
95 let response = blocks_since(ledger, block_index, 1).await.unwrap();
98
99 fn payment_check(
100 transaction: &Transaction,
101 expected_from: AccountIdentifier,
102 expected_to: AccountIdentifier,
103 expected_amount: Tokens,
104 ) -> bool {
105 match &transaction.operation {
106 None => (),
107 Some(operation) => match operation {
108 Operation::Transfer {
109 from, to, amount, ..
110 } => {
111 return account_identifier_equal(expected_from, *from)
112 && account_identifier_equal(expected_to, *to)
113 && expected_amount.e8s() == amount.e8s();
114 }
115 Operation::Mint { .. } => (),
116 Operation::Burn { .. } => (),
117 Operation::Approve { .. } => (),
118 Operation::TransferFrom { .. } => (),
119 },
120 }
121
122 false
123 }
124
125 let block = response
126 .iter()
127 .find(|(_, block)| payment_check(&block.transaction, from, to, amount));
128
129 block.cloned()
130}
131
132pub async fn chain_length(block_index: BlockIndex) -> CallResult<u64> {
140 let ledger = Principal::from_text(LEDGER).unwrap();
141 let response = query_blocks(
142 ledger,
143 GetBlocksArgs {
144 start: block_index,
145 length: 1,
146 },
147 )
148 .await?;
149 Ok(response.chain_length)
150}
151
152pub async fn find_blocks_transfer(
162 block_index: BlockIndex,
163 length: u64,
164 account_identifiers: Vec<AccountIdentifier>,
165) -> Blocks {
166 let ledger = Principal::from_text(LEDGER).unwrap();
167
168 let response = blocks_since(ledger, block_index, length).await.unwrap();
171
172 fn valid_mission_control(
173 transaction: &Transaction,
174 account_identifiers: &[AccountIdentifier],
175 ) -> bool {
176 match &transaction.operation {
177 None => (),
178 Some(operation) => match operation {
179 Operation::Transfer { from, to, .. } => {
180 return account_identifiers.iter().any(|&account_identifier| {
181 account_identifier == *to || account_identifier == *from
182 });
183 }
184 Operation::Mint { .. } => (),
185 Operation::Burn { .. } => (),
186 Operation::Approve { .. } => (),
187 Operation::TransferFrom { .. } => (),
188 },
189 }
190
191 false
192 }
193
194 response
195 .into_iter()
196 .filter(|(_, block)| valid_mission_control(&block.transaction, &account_identifiers))
197 .collect()
198}
199
200async fn blocks_since(
210 ledger_canister_id: Principal,
211 start: BlockIndex,
212 length: u64,
213) -> CallResult<Blocks> {
214 let response = query_blocks(ledger_canister_id, GetBlocksArgs { start, length }).await?;
218
219 let blocks: Blocks = response
220 .blocks
221 .into_iter()
222 .enumerate()
223 .map(|(index, block)| (start + (index as u64), block))
224 .collect();
225
226 if response.archived_blocks.is_empty() {
227 Ok(blocks)
228 } else {
229 type FromArchiveResult = CallResult<Blocks>;
230
231 async fn get_blocks_from_archive(range: ArchivedBlockRange) -> FromArchiveResult {
232 let args = GetBlocksArgs {
233 start: range.start,
234 length: range.length,
235 };
236 let func: Func = range.callback.into();
237
238 let response: CallResult<(GetBlocksResult,)> =
239 call(func.principal, &func.method, (args,)).await;
240
241 match response {
242 Err(e) => Err(e),
243 Ok((block_result,)) => match block_result {
244 Err(_) => Err((
245 RejectionCode::Unknown,
246 "Block results cannot be decoded".to_string(),
247 )),
248 Ok(blocks_range) => Ok(blocks_range
249 .blocks
250 .into_iter()
251 .enumerate()
252 .map(|(index, block)| (range.start + (index as u64), block))
253 .collect()),
254 },
255 }
256 }
257
258 let mut order_archived_blocks = response.archived_blocks;
260 order_archived_blocks.sort_by(|a, b| a.start.cmp(&b.start));
261
262 let futures: Vec<_> = order_archived_blocks
264 .into_iter()
265 .map(get_blocks_from_archive)
266 .collect();
267
268 let archive_responses: Vec<FromArchiveResult> = join_all(futures).await;
269
270 let results = archive_responses
271 .into_iter()
272 .collect::<CallResult<Vec<Blocks>>>()?;
273
274 Ok(results.into_iter().flatten().chain(blocks).collect())
275 }
276}