1use crate::env::ICP_LEDGER;
2use crate::ic::DecodeCandid;
3use crate::ledger::types::icp::{BlockIndexed, Blocks};
4use crate::ledger::utils::account_identifier_equal;
5use candid::{Func, Principal};
6use futures::future::join_all;
7use ic_cdk::call::{Call, CallResult};
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(ICP_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(ICP_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(ICP_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(ICP_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
200type QueryBlocksResult = Result<Blocks, String>;
201
202async fn blocks_since(
212 ledger_canister_id: Principal,
213 start: BlockIndex,
214 length: u64,
215) -> QueryBlocksResult {
216 let response = query_blocks(ledger_canister_id, &GetBlocksArgs { start, length })
220 .await
221 .map_err(|e| {
222 format!(
223 "Query blocks {} with length {} failed: {:?}",
224 start, length, e
225 )
226 })?;
227
228 let blocks: Blocks = response
229 .blocks
230 .into_iter()
231 .enumerate()
232 .map(|(index, block)| (start + (index as u64), block))
233 .collect();
234
235 if response.archived_blocks.is_empty() {
236 Ok(blocks)
237 } else {
238 async fn get_blocks_from_archive(range: ArchivedBlockRange) -> Result<Blocks, String> {
239 let args = GetBlocksArgs {
240 start: range.start,
241 length: range.length,
242 };
243 let func: Func = range.callback.into();
244
245 let block_result = Call::bounded_wait(func.principal, &func.method)
246 .with_arg(args)
247 .await
248 .decode_candid::<GetBlocksResult>()?;
249
250 match block_result {
251 Err(e) => Err(format!("Query blocks from archive failed: {:?}", e)),
252 Ok(blocks_range) => Ok(blocks_range
253 .blocks
254 .into_iter()
255 .enumerate()
256 .map(|(index, block)| (range.start + (index as u64), block))
257 .collect()),
258 }
259 }
260
261 let mut order_archived_blocks = response.archived_blocks;
263 order_archived_blocks.sort_by(|a, b| a.start.cmp(&b.start));
264
265 let futures: Vec<_> = order_archived_blocks
267 .into_iter()
268 .map(get_blocks_from_archive)
269 .collect();
270
271 let archive_responses: Vec<QueryBlocksResult> = join_all(futures).await;
272
273 let results = archive_responses
274 .into_iter()
275 .collect::<Result<Vec<Blocks>, String>>()?;
276
277 Ok(results.into_iter().flatten().chain(blocks).collect())
278 }
279}