iota_client/api/
consolidation.rs

1// Copyright 2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    api::message_builder::ClientMessageBuilder,
6    node::{OutputType, OutputsOptions},
7    Client, Result,
8};
9use bee_message::constants::INPUT_OUTPUT_COUNT_MAX;
10use crypto::keys::slip10::Seed;
11use std::ops::Range;
12
13/// Function to consolidate all funds from a range of addresses to the address with the lowest index in that range
14/// Returns the address to which the funds got consolidated, if any were available
15pub async fn consolidate_funds(
16    client: &Client,
17    seed: &Seed,
18    account_index: usize,
19    address_range: Range<usize>,
20) -> Result<String> {
21    let addresses = client
22        .get_addresses(seed)
23        .with_account_index(account_index)
24        .with_range(address_range.clone())
25        .finish()
26        .await?;
27    let consolidation_address = addresses[0].clone();
28
29    let mut last_transfer_index = address_range.start;
30    let offset = address_range.start;
31    'consolidation: loop {
32        let mut message_ids = Vec::new();
33        // Iterate over addresses reversed so the funds end up on the first address in the range
34        for (index, address) in addresses.iter().enumerate().rev() {
35            // add the offset so the index matches the address index also for higher start indexes
36            let index = index + offset;
37            // We request the different output types separated so we don't get problems with the dust protection,
38            // since the maximum is 100 dust and when we add the signature locked single outptus first we will always
39            // have > 1 Mi for the output
40            let signature_locked_outputs = client
41                .get_address()
42                .outputs(
43                    address,
44                    OutputsOptions {
45                        include_spent: false,
46                        output_type: Some(OutputType::SignatureLockedSingle),
47                    },
48                )
49                .await?;
50            let dust_allowance_outputs = client
51                .get_address()
52                .outputs(
53                    address,
54                    OutputsOptions {
55                        include_spent: false,
56                        output_type: Some(OutputType::SignatureLockedDustAllowance),
57                    },
58                )
59                .await?;
60
61            let mut output_with_metadata = Vec::new();
62
63            for out in signature_locked_outputs.iter().chain(dust_allowance_outputs.iter()) {
64                let output_metadata = client.get_output(out).await?;
65                let (amount, _output_address, _check_treshold) =
66                    ClientMessageBuilder::get_output_amount_and_address(&output_metadata.output)?;
67                output_with_metadata.push((out.clone(), amount));
68            }
69
70            if !output_with_metadata.is_empty() {
71                // If we reach the same index again
72                if last_transfer_index == index {
73                    if output_with_metadata.len() < 2 {
74                        break 'consolidation;
75                    }
76                } else {
77                    last_transfer_index = index;
78                }
79            }
80
81            let outputs_chunks = output_with_metadata.chunks(INPUT_OUTPUT_COUNT_MAX);
82
83            for chunk in outputs_chunks {
84                let mut message_builder = client.message().with_seed(seed);
85                let mut total_amount = 0;
86                for (input, amount) in chunk {
87                    message_builder = message_builder.with_input(input.clone());
88                    total_amount += amount;
89                }
90
91                let message = message_builder
92                    .with_input_range(index..index + 1)
93                    .with_output(&consolidation_address, total_amount)?
94                    .with_initial_address_index(0)
95                    .finish()
96                    .await?;
97                message_ids.push(message.id().0);
98            }
99        }
100
101        if message_ids.is_empty() {
102            break 'consolidation;
103        }
104        // Wait for txs to get confirmed so we don't create conflicting txs
105        for message_id in message_ids {
106            let _ = client.retry_until_included(&message_id, None, None).await?;
107        }
108    }
109    Ok(consolidation_address)
110}