Struct iota_client::client::Client

source ·
pub struct Client { /* private fields */ }
Expand description

An instance of the client using HORNET or Bee URI

Implementations§

Finishes the block with local PoW if needed. Without local PoW, it will finish the block with a 0 nonce.

Examples found in repository?
src/client/high_level.rs (line 295)
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
    pub async fn reattach_unchecked(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Get the Block object by the BlockID.
        let block = self.get_block(block_id).await?;
        let reattach_block = self.finish_block_builder(None, block.payload().cloned()).await?;

        // Post the modified
        let block_id = self.post_block_raw(&reattach_block).await?;
        // Get block if we use remote Pow, because the node will change parents and nonce
        let block = if self.get_local_pow() {
            reattach_block
        } else {
            self.get_block(&block_id).await?
        };
        Ok((block_id, block))
    }

    /// Promotes a block. The method should validate if a promotion is necessary through get_block. If not, the
    /// method should error out and should not allow unnecessary promotions.
    pub async fn promote(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        let metadata = self.get_block_metadata(block_id).await?;
        if metadata.should_promote.unwrap_or(false) {
            self.promote_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

    /// Promote a block without checking if it should be promoted
    pub async fn promote_unchecked(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Create a new block (zero value block) for which one tip would be the actual block.
        let mut tips = self.get_tips().await?;
        if let Some(tip) = tips.first_mut() {
            *tip = *block_id;
        }

        let promote_block = self.finish_block_builder(Some(Parents::new(tips)?), None).await?;

        let block_id = self.post_block_raw(&promote_block).await?;
        // Get block if we use remote Pow, because the node will change parents and nonce.
        let block = if self.get_local_pow() {
            promote_block
        } else {
            self.get_block(&block_id).await?
        };
        Ok((block_id, block))
    }
More examples
Hide additional examples
src/api/block_builder/mod.rs (line 401)
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
    pub async fn finish_block(self, payload: Option<Payload>) -> Result<Block> {
        // Do not replace parents with the latest tips if they are set explicitly,
        // necessary for block promotion.
        let final_block = self.client.finish_block_builder(self.parents, payload).await?;

        let block_id = self.client.post_block_raw(&final_block).await?;
        // Get block if we use remote PoW, because the node will change parents and nonce
        if self.client.get_local_pow() {
            Ok(final_block)
        } else {
            // Request block multiple times because the node maybe didn't process it completely in this time
            // or a node balancer could be used which forwards the request to different node than we published
            for time in 1..3 {
                if let Ok(block) = self.client.get_block(&block_id).await {
                    return Ok(block);
                }
                #[cfg(not(target_family = "wasm"))]
                tokio::time::sleep(std::time::Duration::from_millis(time * 50)).await;
                #[cfg(target_family = "wasm")]
                gloo_timers::future::TimeoutFuture::new((time * 50).try_into().unwrap()).await;
            }
            self.client.get_block(&block_id).await
        }
    }
src/node_api/core/routes.rs (line 179)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
    pub async fn post_block(&self, block: &Block) -> Result<BlockId> {
        let path = "api/core/v2/blocks";
        let local_pow = self.get_local_pow();
        let timeout = if local_pow {
            self.get_timeout()
        } else {
            self.get_remote_pow_timeout()
        };
        let block_dto = BlockDto::from(block);

        // fallback to local PoW if remote PoW fails
        let resp = match self
            .node_manager
            .post_request_json::<SubmitBlockResponse>(path, timeout, serde_json::to_value(block_dto)?, local_pow)
            .await
        {
            Ok(res) => res,
            Err(e) => {
                if let Error::NodeError(e) = e {
                    let fallback_to_local_pow = self.get_fallback_to_local_pow();
                    // hornet and bee return different error blocks
                    if (e == *"No available nodes with remote Pow"
                        || e.contains("proof of work is not enabled")
                        || e.contains("`Pow` not enabled"))
                        && fallback_to_local_pow
                    {
                        // Without this we get:within `impl Future<Output = [async output]>`, the trait `Send` is not
                        // implemented for `std::sync::RwLockWriteGuard<'_, NetworkInfo>`
                        {
                            let mut client_network_info =
                                self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                            // switch to local PoW
                            client_network_info.local_pow = true;
                        }
                        let block_res = self.finish_block_builder(None, block.payload().cloned()).await;
                        let block_with_local_pow = match block_res {
                            Ok(block) => {
                                // reset local PoW state
                                let mut client_network_info =
                                    self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                                client_network_info.local_pow = false;
                                block
                            }
                            Err(e) => {
                                // reset local PoW state
                                let mut client_network_info =
                                    self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                                client_network_info.local_pow = false;
                                return Err(e);
                            }
                        };
                        let block_dto = BlockDto::from(&block_with_local_pow);

                        self.node_manager
                            .post_request_json(path, timeout, serde_json::to_value(block_dto)?, true)
                            .await?
                    } else {
                        return Err(Error::NodeError(e));
                    }
                } else {
                    return Err(e);
                }
            }
        };

        Ok(BlockId::from_str(&resp.block_id)?)
    }

    /// Returns the BlockId of the submitted block.
    /// POST /api/core/v2/blocks
    pub async fn post_block_raw(&self, block: &Block) -> Result<BlockId> {
        let path = "api/core/v2/blocks";
        let local_pow = self.get_local_pow();
        let timeout = if local_pow {
            self.get_timeout()
        } else {
            self.get_remote_pow_timeout()
        };

        // fallback to local Pow if remote Pow fails
        let resp = match self
            .node_manager
            .post_request_bytes::<SubmitBlockResponse>(path, timeout, &block.pack_to_vec(), local_pow)
            .await
        {
            Ok(res) => res,
            Err(e) => {
                if let Error::NodeError(e) = e {
                    let fallback_to_local_pow = self.get_fallback_to_local_pow();
                    // hornet and bee return different error blocks
                    if (e == *"No available nodes with remote Pow"
                        || e.contains("proof of work is not enabled")
                        || e.contains("`Pow` not enabled"))
                        && fallback_to_local_pow
                    {
                        // Without this we get:within `impl Future<Output = [async output]>`, the trait `Send` is not
                        // implemented for `std::sync::RwLockWriteGuard<'_, NetworkInfo>`
                        {
                            let mut client_network_info =
                                self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                            // switch to local PoW
                            client_network_info.local_pow = true;
                        }
                        let block_res = self.finish_block_builder(None, block.payload().cloned()).await;
                        let block_with_local_pow = match block_res {
                            Ok(block) => {
                                // reset local PoW state
                                let mut client_network_info =
                                    self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                                client_network_info.local_pow = false;
                                block
                            }
                            Err(e) => {
                                // reset local PoW state
                                let mut client_network_info =
                                    self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                                client_network_info.local_pow = false;
                                return Err(e);
                            }
                        };
                        self.node_manager
                            .post_request_bytes(path, timeout, &block_with_local_pow.pack_to_vec(), true)
                            .await?
                    } else {
                        return Err(Error::NodeError(e));
                    }
                } else {
                    return Err(e);
                }
            }
        };

        Ok(BlockId::from_str(&resp.block_id)?)
    }

Calls the appropriate PoW function depending whether the compilation is for wasm or not.

Examples found in repository?
src/api/block_builder/pow.rs (line 19)
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    pub async fn finish_block_builder(&self, parents: Option<Parents>, payload: Option<Payload>) -> Result<Block> {
        if self.get_local_pow() {
            self.finish_pow(parents, payload).await
        } else {
            // Finish block without doing PoW.
            let parents = match parents {
                Some(parents) => parents,
                None => Parents::new(self.get_tips().await?)?,
            };
            let mut block_builder = BlockBuilder::new(parents);

            if let Some(p) = payload {
                block_builder = block_builder.with_payload(p);
            }

            Ok(block_builder.finish()?)
        }
    }

Function to consolidate all funds and native tokens from a range of addresses to the address with the lowest index in that range. Returns the address to which the funds got consolidated, if any were available

Get the inputs of a transaction for the given transaction id.

A generic send function for easily sending transaction or tagged data blocks.

Examples found in repository?
src/api/consolidation.rs (line 76)
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
    pub async fn consolidate_funds(
        &self,
        secret_manager: &SecretManager,
        address_builder_options: GetAddressesBuilderOptions,
    ) -> Result<String> {
        let token_supply = self.get_token_supply().await?;
        let mut last_transfer_index = address_builder_options.range.as_ref().unwrap_or(&(0..1)).start;
        // use the start index as offset
        let offset = last_transfer_index;

        let addresses = self
            .get_addresses(secret_manager)
            .set_options(address_builder_options)?
            .finish()
            .await?;

        let consolidation_address = addresses[0].clone();

        'consolidation: loop {
            let mut block_ids = Vec::new();
            // Iterate over addresses reversed so the funds end up on the first address in the range
            for (index, address) in addresses.iter().enumerate().rev() {
                let index = index as u32;
                // add the offset so the index matches the address index also for higher start indexes
                let index = index + offset;

                // Get output ids of outputs that can be controlled by this address without further unlock constraints
                let basic_output_ids = self
                    .basic_output_ids(vec![
                        QueryParameter::Address(address.to_string()),
                        QueryParameter::HasExpiration(false),
                        QueryParameter::HasTimelock(false),
                        QueryParameter::HasStorageDepositReturn(false),
                    ])
                    .await?;

                let basic_outputs_responses = self.get_outputs(basic_output_ids).await?;

                if !basic_outputs_responses.is_empty() {
                    // If we reach the same index again
                    if last_transfer_index == index {
                        if basic_outputs_responses.len() < 2 {
                            break 'consolidation;
                        }
                    } else {
                        last_transfer_index = index;
                    }
                }

                let outputs_chunks = basic_outputs_responses.chunks(INPUT_COUNT_MAX.into());

                for chunk in outputs_chunks {
                    let mut block_builder = self.block().with_secret_manager(secret_manager);
                    let mut total_amount = 0;
                    let mut total_native_tokens = NativeTokensBuilder::new();

                    for output_response in chunk {
                        block_builder = block_builder.with_input(UtxoInput::from(OutputId::new(
                            TransactionId::from_str(&output_response.metadata.transaction_id)?,
                            output_response.metadata.output_index,
                        )?))?;

                        let output = Output::try_from_dto(&output_response.output, token_supply)?;

                        if let Some(native_tokens) = output.native_tokens() {
                            total_native_tokens.add_native_tokens(native_tokens.clone())?;
                        }
                        total_amount += output.amount();
                    }

                    let consolidation_output = BasicOutputBuilder::new_with_amount(total_amount)?
                        .add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(
                            Address::try_from_bech32(&consolidation_address)?.1,
                        )))
                        .with_native_tokens(total_native_tokens.finish()?)
                        .finish_output(token_supply)?;

                    let block = block_builder
                        .with_input_range(index..index + 1)
                        .with_outputs(vec![consolidation_output])?
                        .with_initial_address_index(0)
                        .finish()
                        .await?;
                    block_ids.push(block.id());
                }
            }

            if block_ids.is_empty() {
                break 'consolidation;
            }
            // Wait for txs to get confirmed so we don't create conflicting txs
            for block_id in block_ids {
                let _ = self.retry_until_included(&block_id, None, None).await?;
            }
        }
        Ok(consolidation_address)
    }

Return a list of addresses from a secret manager regardless of their validity.

Examples found in repository?
src/api/consolidation.rs (line 35)
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
    pub async fn consolidate_funds(
        &self,
        secret_manager: &SecretManager,
        address_builder_options: GetAddressesBuilderOptions,
    ) -> Result<String> {
        let token_supply = self.get_token_supply().await?;
        let mut last_transfer_index = address_builder_options.range.as_ref().unwrap_or(&(0..1)).start;
        // use the start index as offset
        let offset = last_transfer_index;

        let addresses = self
            .get_addresses(secret_manager)
            .set_options(address_builder_options)?
            .finish()
            .await?;

        let consolidation_address = addresses[0].clone();

        'consolidation: loop {
            let mut block_ids = Vec::new();
            // Iterate over addresses reversed so the funds end up on the first address in the range
            for (index, address) in addresses.iter().enumerate().rev() {
                let index = index as u32;
                // add the offset so the index matches the address index also for higher start indexes
                let index = index + offset;

                // Get output ids of outputs that can be controlled by this address without further unlock constraints
                let basic_output_ids = self
                    .basic_output_ids(vec![
                        QueryParameter::Address(address.to_string()),
                        QueryParameter::HasExpiration(false),
                        QueryParameter::HasTimelock(false),
                        QueryParameter::HasStorageDepositReturn(false),
                    ])
                    .await?;

                let basic_outputs_responses = self.get_outputs(basic_output_ids).await?;

                if !basic_outputs_responses.is_empty() {
                    // If we reach the same index again
                    if last_transfer_index == index {
                        if basic_outputs_responses.len() < 2 {
                            break 'consolidation;
                        }
                    } else {
                        last_transfer_index = index;
                    }
                }

                let outputs_chunks = basic_outputs_responses.chunks(INPUT_COUNT_MAX.into());

                for chunk in outputs_chunks {
                    let mut block_builder = self.block().with_secret_manager(secret_manager);
                    let mut total_amount = 0;
                    let mut total_native_tokens = NativeTokensBuilder::new();

                    for output_response in chunk {
                        block_builder = block_builder.with_input(UtxoInput::from(OutputId::new(
                            TransactionId::from_str(&output_response.metadata.transaction_id)?,
                            output_response.metadata.output_index,
                        )?))?;

                        let output = Output::try_from_dto(&output_response.output, token_supply)?;

                        if let Some(native_tokens) = output.native_tokens() {
                            total_native_tokens.add_native_tokens(native_tokens.clone())?;
                        }
                        total_amount += output.amount();
                    }

                    let consolidation_output = BasicOutputBuilder::new_with_amount(total_amount)?
                        .add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(
                            Address::try_from_bech32(&consolidation_address)?.1,
                        )))
                        .with_native_tokens(total_native_tokens.finish()?)
                        .finish_output(token_supply)?;

                    let block = block_builder
                        .with_input_range(index..index + 1)
                        .with_outputs(vec![consolidation_output])?
                        .with_initial_address_index(0)
                        .finish()
                        .await?;
                    block_ids.push(block.id());
                }
            }

            if block_ids.is_empty() {
                break 'consolidation;
            }
            // Wait for txs to get confirmed so we don't create conflicting txs
            for block_id in block_ids {
                let _ = self.retry_until_included(&block_id, None, None).await?;
            }
        }
        Ok(consolidation_address)
    }
More examples
Hide additional examples
src/api/block_builder/input_selection/automatic.rs (lines 104-107)
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
224
225
226
227
228
229
    pub(crate) async fn get_inputs(&self, rent_structure: &RentStructure) -> Result<SelectedTransactionData> {
        log::debug!("[get_inputs]");

        let account_index = self.account_index;
        let mut gap_index = self.initial_address_index;
        let mut empty_address_count: u64 = 0;
        let mut cached_error = None;
        let token_supply = self.client.get_token_supply().await?;

        log::debug!("[get_inputs from utxo chains]");

        // First get inputs for utxo chains (Alias, Foundry, NFT outputs).
        let mut available_inputs = self.get_utxo_chains_inputs(self.outputs.iter()).await?;
        let required_inputs_for_sender_or_issuer = self.get_inputs_for_sender_and_issuer(&available_inputs).await?;

        let current_time = self.client.get_time_checked().await?;

        // Try to select inputs with required inputs for utxo chains alone before requesting more inputs from addresses.
        if let Ok(selected_transaction_data) = try_select_inputs(
            required_inputs_for_sender_or_issuer.clone(),
            available_inputs.clone(),
            self.outputs.clone(),
            self.custom_remainder_address,
            rent_structure,
            // Don't allow burning of native tokens during automatic input selection, because otherwise it
            // could lead to burned native tokens by accident.
            false,
            current_time,
            token_supply,
        ) {
            return Ok(selected_transaction_data);
        };

        log::debug!("[get_inputs from addresses]");

        // Then select inputs with outputs from addresses.
        let selected_transaction_data = 'input_selection: loop {
            // Get the addresses in the BIP path/index ~ path/index+20.
            let addresses = self
                .client
                .get_addresses(
                    self.secret_manager
                        .ok_or(crate::Error::MissingParameter("secret manager"))?,
                )
                .with_account_index(account_index)
                .with_range(gap_index..gap_index + ADDRESS_GAP_RANGE)
                .get_all()
                .await?;
            // Have public and internal addresses with the index ascending ordered.
            let mut public_and_internal_addresses = Vec::new();

            for index in 0..addresses.public.len() {
                public_and_internal_addresses.push((addresses.public[index].clone(), false));
                public_and_internal_addresses.push((addresses.internal[index].clone(), true));
            }

            // For each address, get the address outputs.
            let mut address_index = gap_index;

            for (index, (str_address, internal)) in public_and_internal_addresses.iter().enumerate() {
                let address_outputs = self.basic_address_outputs(str_address.to_string()).await?;

                // If there are more than 20 (ADDRESS_GAP_RANGE) consecutive empty addresses, then we stop
                // looking up the addresses belonging to the seed. Note that we don't
                // really count the exact 20 consecutive empty addresses, which is
                // unnecessary. We just need to check the address range,
                // (index * ADDRESS_GAP_RANGE, index * ADDRESS_GAP_RANGE + ADDRESS_GAP_RANGE), where index is
                // natural number, and to see if the outputs are all empty.
                if address_outputs.is_empty() {
                    // Accumulate the empty_address_count for each run of output address searching
                    empty_address_count += 1;
                } else {
                    // Reset counter if there is an output
                    empty_address_count = 0;

                    for output_response in address_outputs {
                        let output = Output::try_from_dto(&output_response.output, token_supply)?;
                        let address = Address::try_from_bech32(str_address)?.1;

                        // We can ignore the unlocked_alias_or_nft_address, since we only requested basic outputs
                        let (required_unlock_address, _unlocked_alias_or_nft_address) = output
                            .required_and_unlocked_address(
                                current_time,
                                &output_response.metadata.output_id()?,
                                false,
                            )?;
                        if required_unlock_address == address {
                            available_inputs.push(InputSigningData {
                                output,
                                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                                chain: Some(Chain::from_u32_hardened(vec![
                                    HD_WALLET_TYPE,
                                    self.coin_type,
                                    account_index,
                                    *internal as u32,
                                    address_index,
                                ])),
                                bech32_address: str_address.clone(),
                            });
                        }
                    }
                    let selected_transaction_data = match try_select_inputs(
                        required_inputs_for_sender_or_issuer.clone(),
                        available_inputs.clone(),
                        self.outputs.clone(),
                        self.custom_remainder_address,
                        rent_structure,
                        // Don't allow burning of native tokens during automatic input selection, because otherwise it
                        // could lead to burned native tokens by accident.
                        false,
                        current_time,
                        token_supply,
                    ) {
                        Ok(r) => r,
                        // for these errors, just try again in the next round with more addresses which might have more
                        // outputs.
                        Err(err @ crate::Error::NotEnoughBalance { .. }) => {
                            cached_error.replace(err);
                            continue;
                        }
                        Err(err @ crate::Error::NotEnoughNativeTokens { .. }) => {
                            cached_error.replace(err);
                            continue;
                        }
                        // Native tokens left, but no balance for the storage deposit for a remainder.
                        Err(err @ crate::Error::NoBalanceForNativeTokenRemainder) => {
                            cached_error.replace(err);
                            continue;
                        }
                        // Currently too many inputs, by scanning for more inputs, we might find some with more amount.
                        Err(err @ crate::Error::ConsolidationRequired { .. }) => {
                            cached_error.replace(err);
                            continue;
                        }
                        // Not enough balance for a remainder.
                        Err(crate::Error::BlockError(block_error)) => match block_error {
                            iota_types::block::Error::InvalidStorageDepositAmount { .. } => {
                                cached_error.replace(crate::Error::BlockError(block_error));
                                continue;
                            }
                            _ => return Err(block_error.into()),
                        },
                        Err(e) => return Err(e),
                    };

                    break 'input_selection selected_transaction_data;
                }

                // if we just processed an even index, increase the address index
                // (because the list has public and internal addresses)
                if index % 2 == 1 {
                    address_index += 1;
                }
            }

            gap_index += ADDRESS_GAP_RANGE;

            // The gap limit is 20 and use reference 40 here because there's public and internal addresses
            if empty_address_count >= (ADDRESS_GAP_RANGE * 2) as u64 {
                // returned last cached error
                return Err(cached_error.unwrap_or(Error::NoInputs));
            }
        };

        Ok(selected_transaction_data)
    }

Find all blocks by provided block IDs.

Retries (promotes or reattaches) a block for provided block id. Block should only be retried only if they are valid and haven’t been confirmed for a while.

Retries (promotes or reattaches) a block for provided block id until it’s included (referenced by a milestone). Default interval is 5 seconds and max attempts is 40. Returns the included block at first position and additional reattached blocks

Examples found in repository?
src/api/consolidation.rs (line 116)
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
    pub async fn consolidate_funds(
        &self,
        secret_manager: &SecretManager,
        address_builder_options: GetAddressesBuilderOptions,
    ) -> Result<String> {
        let token_supply = self.get_token_supply().await?;
        let mut last_transfer_index = address_builder_options.range.as_ref().unwrap_or(&(0..1)).start;
        // use the start index as offset
        let offset = last_transfer_index;

        let addresses = self
            .get_addresses(secret_manager)
            .set_options(address_builder_options)?
            .finish()
            .await?;

        let consolidation_address = addresses[0].clone();

        'consolidation: loop {
            let mut block_ids = Vec::new();
            // Iterate over addresses reversed so the funds end up on the first address in the range
            for (index, address) in addresses.iter().enumerate().rev() {
                let index = index as u32;
                // add the offset so the index matches the address index also for higher start indexes
                let index = index + offset;

                // Get output ids of outputs that can be controlled by this address without further unlock constraints
                let basic_output_ids = self
                    .basic_output_ids(vec![
                        QueryParameter::Address(address.to_string()),
                        QueryParameter::HasExpiration(false),
                        QueryParameter::HasTimelock(false),
                        QueryParameter::HasStorageDepositReturn(false),
                    ])
                    .await?;

                let basic_outputs_responses = self.get_outputs(basic_output_ids).await?;

                if !basic_outputs_responses.is_empty() {
                    // If we reach the same index again
                    if last_transfer_index == index {
                        if basic_outputs_responses.len() < 2 {
                            break 'consolidation;
                        }
                    } else {
                        last_transfer_index = index;
                    }
                }

                let outputs_chunks = basic_outputs_responses.chunks(INPUT_COUNT_MAX.into());

                for chunk in outputs_chunks {
                    let mut block_builder = self.block().with_secret_manager(secret_manager);
                    let mut total_amount = 0;
                    let mut total_native_tokens = NativeTokensBuilder::new();

                    for output_response in chunk {
                        block_builder = block_builder.with_input(UtxoInput::from(OutputId::new(
                            TransactionId::from_str(&output_response.metadata.transaction_id)?,
                            output_response.metadata.output_index,
                        )?))?;

                        let output = Output::try_from_dto(&output_response.output, token_supply)?;

                        if let Some(native_tokens) = output.native_tokens() {
                            total_native_tokens.add_native_tokens(native_tokens.clone())?;
                        }
                        total_amount += output.amount();
                    }

                    let consolidation_output = BasicOutputBuilder::new_with_amount(total_amount)?
                        .add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(
                            Address::try_from_bech32(&consolidation_address)?.1,
                        )))
                        .with_native_tokens(total_native_tokens.finish()?)
                        .finish_output(token_supply)?;

                    let block = block_builder
                        .with_input_range(index..index + 1)
                        .with_outputs(vec![consolidation_output])?
                        .with_initial_address_index(0)
                        .finish()
                        .await?;
                    block_ids.push(block.id());
                }
            }

            if block_ids.is_empty() {
                break 'consolidation;
            }
            // Wait for txs to get confirmed so we don't create conflicting txs
            for block_id in block_ids {
                let _ = self.retry_until_included(&block_id, None, None).await?;
            }
        }
        Ok(consolidation_address)
    }

Function to find inputs from addresses for a provided amount (useful for offline signing), ignoring outputs with additional unlock conditions

Find all outputs based on the requests criteria. This method will try to query multiple nodes if the request amount exceeds individual node limit.

Reattaches blocks for provided block id. Blocks can be reattached only if they are valid and haven’t been confirmed for a while.

Reattach a block without checking if it should be reattached

Examples found in repository?
src/client/high_level.rs (line 99)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
    pub async fn retry(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Get the metadata to check if it needs to promote or reattach
        let block_metadata = self.get_block_metadata(block_id).await?;
        if block_metadata.should_promote.unwrap_or(false) {
            self.promote_unchecked(block_id).await
        } else if block_metadata.should_reattach.unwrap_or(false) {
            self.reattach_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

    /// Retries (promotes or reattaches) a block for provided block id until it's included (referenced by a
    /// milestone). Default interval is 5 seconds and max attempts is 40. Returns the included block at first position
    /// and additional reattached blocks
    pub async fn retry_until_included(
        &self,
        block_id: &BlockId,
        interval: Option<u64>,
        max_attempts: Option<u64>,
    ) -> Result<Vec<(BlockId, Block)>> {
        log::debug!("[retry_until_included]");
        // Attachments of the Block to check inclusion state
        let mut block_ids = vec![*block_id];
        // Reattached Blocks that get returned
        let mut blocks_with_id = Vec::new();
        for _ in 0..max_attempts.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_MAX_AMOUNT) {
            #[cfg(target_family = "wasm")]
            gloo_timers::future::TimeoutFuture::new(
                (interval.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_INTERVAL) * 1000)
                    .try_into()
                    .unwrap(),
            )
            .await;

            #[cfg(not(target_family = "wasm"))]
            tokio::time::sleep(std::time::Duration::from_secs(
                interval.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_INTERVAL),
            ))
            .await;

            // Check inclusion state for each attachment
            let block_ids_len = block_ids.len();
            let mut conflicting = false;
            for (index, block_id_) in block_ids.clone().iter().enumerate() {
                let block_metadata = self.get_block_metadata(block_id_).await?;
                if let Some(inclusion_state) = block_metadata.ledger_inclusion_state {
                    match inclusion_state {
                        LedgerInclusionStateDto::Included | LedgerInclusionStateDto::NoTransaction => {
                            // if original block, request it so we can return it on first position
                            if block_id == block_id_ {
                                let mut included_and_reattached_blocks =
                                    vec![(*block_id, self.get_block(block_id).await?)];
                                included_and_reattached_blocks.extend(blocks_with_id);
                                return Ok(included_and_reattached_blocks);
                            } else {
                                // Move included block to first position
                                blocks_with_id.rotate_left(index);
                                return Ok(blocks_with_id);
                            }
                        }
                        // only set it as conflicting here and don't return, because another reattached block could
                        // have the included transaction
                        LedgerInclusionStateDto::Conflicting => conflicting = true,
                    };
                }
                // Only reattach or promote latest attachment of the block
                if index == block_ids_len - 1 {
                    if block_metadata.should_promote.unwrap_or(false) {
                        // Safe to unwrap since we iterate over it
                        self.promote_unchecked(block_ids.last().unwrap()).await?;
                    } else if block_metadata.should_reattach.unwrap_or(false) {
                        // Safe to unwrap since we iterate over it
                        let reattached = self.reattach_unchecked(block_ids.last().unwrap()).await?;
                        block_ids.push(reattached.0);
                        blocks_with_id.push(reattached);
                    }
                }
            }
            // After we checked all our reattached blocks, check if the transaction got reattached in another block
            // and confirmed
            if conflicting {
                let block = self.get_block(block_id).await?;
                if let Some(Payload::Transaction(transaction_payload)) = block.payload() {
                    let included_block = self.get_included_block(&transaction_payload.id()).await?;
                    let mut included_and_reattached_blocks = vec![(included_block.id(), included_block)];
                    included_and_reattached_blocks.extend(blocks_with_id);
                    return Ok(included_and_reattached_blocks);
                }
            }
        }
        Err(Error::TangleInclusionError(block_id.to_string()))
    }

    /// Function to find inputs from addresses for a provided amount (useful for offline signing), ignoring outputs with
    /// additional unlock conditions
    pub async fn find_inputs(&self, addresses: Vec<String>, amount: u64) -> Result<Vec<UtxoInput>> {
        // Get outputs from node and select inputs
        let mut available_outputs = Vec::new();

        for address in addresses {
            let basic_output_ids = self
                .basic_output_ids(vec![
                    QueryParameter::Address(address.to_string()),
                    QueryParameter::HasExpiration(false),
                    QueryParameter::HasTimelock(false),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?;

            available_outputs.extend(self.get_outputs(basic_output_ids).await?);
        }

        let mut basic_outputs = Vec::new();
        let current_time = self.get_time_checked().await?;
        let token_supply = self.get_token_supply().await?;

        for output_resp in available_outputs {
            let (amount, _) = ClientBlockBuilder::get_output_amount_and_address(
                &Output::try_from_dto(&output_resp.output, token_supply)?,
                None,
                current_time,
            )?;
            basic_outputs.push((
                UtxoInput::new(
                    TransactionId::from_str(&output_resp.metadata.transaction_id)?,
                    output_resp.metadata.output_index,
                )?,
                amount,
            ));
        }
        basic_outputs.sort_by(|l, r| r.1.cmp(&l.1));

        let mut total_already_spent = 0;
        let mut selected_inputs = Vec::new();
        for (_offset, output_wrapper) in basic_outputs
            .into_iter()
            // Max inputs is 128
            .take(INPUT_COUNT_MAX.into())
            .enumerate()
        {
            // Break if we have enough funds and don't create dust for the remainder
            if total_already_spent == amount || total_already_spent >= amount {
                break;
            }
            selected_inputs.push(output_wrapper.0.clone());
            total_already_spent += output_wrapper.1;
        }

        if total_already_spent < amount {
            return Err(crate::Error::NotEnoughBalance {
                found: total_already_spent,
                required: amount,
            });
        }

        Ok(selected_inputs)
    }

    /// Find all outputs based on the requests criteria. This method will try to query multiple nodes if
    /// the request amount exceeds individual node limit.
    pub async fn find_outputs(
        &self,
        output_ids: &[OutputId],
        addresses: &[String],
    ) -> Result<Vec<OutputWithMetadataResponse>> {
        let mut output_responses = self.get_outputs(output_ids.to_vec()).await?;

        // Use `get_address()` API to get the address outputs first,
        // then collect the `UtxoInput` in the HashSet.
        for address in addresses {
            // Get output ids of outputs that can be controlled by this address without further unlock constraints
            let basic_output_ids = self
                .basic_output_ids(vec![
                    QueryParameter::Address(address.to_string()),
                    QueryParameter::HasExpiration(false),
                    QueryParameter::HasTimelock(false),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?;

            output_responses.extend(self.get_outputs(basic_output_ids).await?);
        }

        Ok(output_responses.clone())
    }

    /// Reattaches blocks for provided block id. Blocks can be reattached only if they are valid and haven't been
    /// confirmed for a while.
    pub async fn reattach(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        let metadata = self.get_block_metadata(block_id).await?;
        if metadata.should_reattach.unwrap_or(false) {
            self.reattach_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

Promotes a block. The method should validate if a promotion is necessary through get_block. If not, the method should error out and should not allow unnecessary promotions.

Promote a block without checking if it should be promoted

Examples found in repository?
src/client/high_level.rs (line 97)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
    pub async fn retry(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Get the metadata to check if it needs to promote or reattach
        let block_metadata = self.get_block_metadata(block_id).await?;
        if block_metadata.should_promote.unwrap_or(false) {
            self.promote_unchecked(block_id).await
        } else if block_metadata.should_reattach.unwrap_or(false) {
            self.reattach_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

    /// Retries (promotes or reattaches) a block for provided block id until it's included (referenced by a
    /// milestone). Default interval is 5 seconds and max attempts is 40. Returns the included block at first position
    /// and additional reattached blocks
    pub async fn retry_until_included(
        &self,
        block_id: &BlockId,
        interval: Option<u64>,
        max_attempts: Option<u64>,
    ) -> Result<Vec<(BlockId, Block)>> {
        log::debug!("[retry_until_included]");
        // Attachments of the Block to check inclusion state
        let mut block_ids = vec![*block_id];
        // Reattached Blocks that get returned
        let mut blocks_with_id = Vec::new();
        for _ in 0..max_attempts.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_MAX_AMOUNT) {
            #[cfg(target_family = "wasm")]
            gloo_timers::future::TimeoutFuture::new(
                (interval.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_INTERVAL) * 1000)
                    .try_into()
                    .unwrap(),
            )
            .await;

            #[cfg(not(target_family = "wasm"))]
            tokio::time::sleep(std::time::Duration::from_secs(
                interval.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_INTERVAL),
            ))
            .await;

            // Check inclusion state for each attachment
            let block_ids_len = block_ids.len();
            let mut conflicting = false;
            for (index, block_id_) in block_ids.clone().iter().enumerate() {
                let block_metadata = self.get_block_metadata(block_id_).await?;
                if let Some(inclusion_state) = block_metadata.ledger_inclusion_state {
                    match inclusion_state {
                        LedgerInclusionStateDto::Included | LedgerInclusionStateDto::NoTransaction => {
                            // if original block, request it so we can return it on first position
                            if block_id == block_id_ {
                                let mut included_and_reattached_blocks =
                                    vec![(*block_id, self.get_block(block_id).await?)];
                                included_and_reattached_blocks.extend(blocks_with_id);
                                return Ok(included_and_reattached_blocks);
                            } else {
                                // Move included block to first position
                                blocks_with_id.rotate_left(index);
                                return Ok(blocks_with_id);
                            }
                        }
                        // only set it as conflicting here and don't return, because another reattached block could
                        // have the included transaction
                        LedgerInclusionStateDto::Conflicting => conflicting = true,
                    };
                }
                // Only reattach or promote latest attachment of the block
                if index == block_ids_len - 1 {
                    if block_metadata.should_promote.unwrap_or(false) {
                        // Safe to unwrap since we iterate over it
                        self.promote_unchecked(block_ids.last().unwrap()).await?;
                    } else if block_metadata.should_reattach.unwrap_or(false) {
                        // Safe to unwrap since we iterate over it
                        let reattached = self.reattach_unchecked(block_ids.last().unwrap()).await?;
                        block_ids.push(reattached.0);
                        blocks_with_id.push(reattached);
                    }
                }
            }
            // After we checked all our reattached blocks, check if the transaction got reattached in another block
            // and confirmed
            if conflicting {
                let block = self.get_block(block_id).await?;
                if let Some(Payload::Transaction(transaction_payload)) = block.payload() {
                    let included_block = self.get_included_block(&transaction_payload.id()).await?;
                    let mut included_and_reattached_blocks = vec![(included_block.id(), included_block)];
                    included_and_reattached_blocks.extend(blocks_with_id);
                    return Ok(included_and_reattached_blocks);
                }
            }
        }
        Err(Error::TangleInclusionError(block_id.to_string()))
    }

    /// Function to find inputs from addresses for a provided amount (useful for offline signing), ignoring outputs with
    /// additional unlock conditions
    pub async fn find_inputs(&self, addresses: Vec<String>, amount: u64) -> Result<Vec<UtxoInput>> {
        // Get outputs from node and select inputs
        let mut available_outputs = Vec::new();

        for address in addresses {
            let basic_output_ids = self
                .basic_output_ids(vec![
                    QueryParameter::Address(address.to_string()),
                    QueryParameter::HasExpiration(false),
                    QueryParameter::HasTimelock(false),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?;

            available_outputs.extend(self.get_outputs(basic_output_ids).await?);
        }

        let mut basic_outputs = Vec::new();
        let current_time = self.get_time_checked().await?;
        let token_supply = self.get_token_supply().await?;

        for output_resp in available_outputs {
            let (amount, _) = ClientBlockBuilder::get_output_amount_and_address(
                &Output::try_from_dto(&output_resp.output, token_supply)?,
                None,
                current_time,
            )?;
            basic_outputs.push((
                UtxoInput::new(
                    TransactionId::from_str(&output_resp.metadata.transaction_id)?,
                    output_resp.metadata.output_index,
                )?,
                amount,
            ));
        }
        basic_outputs.sort_by(|l, r| r.1.cmp(&l.1));

        let mut total_already_spent = 0;
        let mut selected_inputs = Vec::new();
        for (_offset, output_wrapper) in basic_outputs
            .into_iter()
            // Max inputs is 128
            .take(INPUT_COUNT_MAX.into())
            .enumerate()
        {
            // Break if we have enough funds and don't create dust for the remainder
            if total_already_spent == amount || total_already_spent >= amount {
                break;
            }
            selected_inputs.push(output_wrapper.0.clone());
            total_already_spent += output_wrapper.1;
        }

        if total_already_spent < amount {
            return Err(crate::Error::NotEnoughBalance {
                found: total_already_spent,
                required: amount,
            });
        }

        Ok(selected_inputs)
    }

    /// Find all outputs based on the requests criteria. This method will try to query multiple nodes if
    /// the request amount exceeds individual node limit.
    pub async fn find_outputs(
        &self,
        output_ids: &[OutputId],
        addresses: &[String],
    ) -> Result<Vec<OutputWithMetadataResponse>> {
        let mut output_responses = self.get_outputs(output_ids.to_vec()).await?;

        // Use `get_address()` API to get the address outputs first,
        // then collect the `UtxoInput` in the HashSet.
        for address in addresses {
            // Get output ids of outputs that can be controlled by this address without further unlock constraints
            let basic_output_ids = self
                .basic_output_ids(vec![
                    QueryParameter::Address(address.to_string()),
                    QueryParameter::HasExpiration(false),
                    QueryParameter::HasTimelock(false),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?;

            output_responses.extend(self.get_outputs(basic_output_ids).await?);
        }

        Ok(output_responses.clone())
    }

    /// Reattaches blocks for provided block id. Blocks can be reattached only if they are valid and haven't been
    /// confirmed for a while.
    pub async fn reattach(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        let metadata = self.get_block_metadata(block_id).await?;
        if metadata.should_reattach.unwrap_or(false) {
            self.reattach_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

    /// Reattach a block without checking if it should be reattached
    pub async fn reattach_unchecked(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Get the Block object by the BlockID.
        let block = self.get_block(block_id).await?;
        let reattach_block = self.finish_block_builder(None, block.payload().cloned()).await?;

        // Post the modified
        let block_id = self.post_block_raw(&reattach_block).await?;
        // Get block if we use remote Pow, because the node will change parents and nonce
        let block = if self.get_local_pow() {
            reattach_block
        } else {
            self.get_block(&block_id).await?
        };
        Ok((block_id, block))
    }

    /// Promotes a block. The method should validate if a promotion is necessary through get_block. If not, the
    /// method should error out and should not allow unnecessary promotions.
    pub async fn promote(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        let metadata = self.get_block_metadata(block_id).await?;
        if metadata.should_promote.unwrap_or(false) {
            self.promote_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

Returns the local time checked with the timestamp of the latest milestone, if the difference is larger than 5 minutes an error is returned to prevent locking outputs by accident for a wrong time.

Examples found in repository?
src/api/block_builder/transaction.rs (line 119)
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
    pub async fn sign_transaction(&self, prepared_transaction_data: PreparedTransactionData) -> Result<Payload> {
        log::debug!("[sign_transaction] {:?}", prepared_transaction_data);
        let secret_manager = self.secret_manager.ok_or(Error::MissingParameter("secret manager"))?;
        let unlocks = secret_manager
            .sign_transaction_essence(&prepared_transaction_data)
            .await?;
        let tx_payload = TransactionPayload::new(prepared_transaction_data.essence.clone(), unlocks)?;

        validate_transaction_payload_length(&tx_payload)?;

        let current_time = self.client.get_time_checked().await?;

        let conflict = verify_semantic(&prepared_transaction_data.inputs_data, &tx_payload, current_time)?;

        if conflict != ConflictReason::None {
            log::debug!("[sign_transaction] conflict: {conflict:?} for {:#?}", tx_payload);
            return Err(Error::TransactionSemantic(conflict));
        }

        Ok(Payload::from(tx_payload))
    }
More examples
Hide additional examples
src/client/high_level.rs (line 207)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
    pub async fn find_inputs(&self, addresses: Vec<String>, amount: u64) -> Result<Vec<UtxoInput>> {
        // Get outputs from node and select inputs
        let mut available_outputs = Vec::new();

        for address in addresses {
            let basic_output_ids = self
                .basic_output_ids(vec![
                    QueryParameter::Address(address.to_string()),
                    QueryParameter::HasExpiration(false),
                    QueryParameter::HasTimelock(false),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?;

            available_outputs.extend(self.get_outputs(basic_output_ids).await?);
        }

        let mut basic_outputs = Vec::new();
        let current_time = self.get_time_checked().await?;
        let token_supply = self.get_token_supply().await?;

        for output_resp in available_outputs {
            let (amount, _) = ClientBlockBuilder::get_output_amount_and_address(
                &Output::try_from_dto(&output_resp.output, token_supply)?,
                None,
                current_time,
            )?;
            basic_outputs.push((
                UtxoInput::new(
                    TransactionId::from_str(&output_resp.metadata.transaction_id)?,
                    output_resp.metadata.output_index,
                )?,
                amount,
            ));
        }
        basic_outputs.sort_by(|l, r| r.1.cmp(&l.1));

        let mut total_already_spent = 0;
        let mut selected_inputs = Vec::new();
        for (_offset, output_wrapper) in basic_outputs
            .into_iter()
            // Max inputs is 128
            .take(INPUT_COUNT_MAX.into())
            .enumerate()
        {
            // Break if we have enough funds and don't create dust for the remainder
            if total_already_spent == amount || total_already_spent >= amount {
                break;
            }
            selected_inputs.push(output_wrapper.0.clone());
            total_already_spent += output_wrapper.1;
        }

        if total_already_spent < amount {
            return Err(crate::Error::NotEnoughBalance {
                found: total_already_spent,
                required: amount,
            });
        }

        Ok(selected_inputs)
    }
src/api/block_builder/input_selection/manual.rs (line 39)
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
    pub(crate) async fn get_custom_inputs(
        &self,
        governance_transition: Option<HashSet<AliasId>>,
        rent_structure: &RentStructure,
        allow_burning: bool,
    ) -> Result<SelectedTransactionData> {
        log::debug!("[get_custom_inputs]");

        let mut inputs_data = Vec::new();
        let current_time = self.client.get_time_checked().await?;
        let token_supply = self.client.get_token_supply().await?;

        if let Some(inputs) = &self.inputs {
            for input in inputs {
                let output_response = self.client.get_output(input.output_id()).await?;
                let output = Output::try_from_dto(&output_response.output, token_supply)?;

                if !output_response.metadata.is_spent {
                    let (_output_amount, output_address) = ClientBlockBuilder::get_output_amount_and_address(
                        &output,
                        governance_transition.clone(),
                        current_time,
                    )?;

                    let bech32_hrp = self.client.get_bech32_hrp().await?;
                    let address_index_internal = match self.secret_manager {
                        Some(secret_manager) => {
                            match output_address {
                                Address::Ed25519(_) => Some(
                                    search_address(
                                        secret_manager,
                                        &bech32_hrp,
                                        self.coin_type,
                                        self.account_index,
                                        self.input_range.clone(),
                                        &output_address,
                                    )
                                    .await?,
                                ),
                                // Alias and NFT addresses can't be generated from a private key.
                                _ => None,
                            }
                        }
                        // Assuming default for offline signing.
                        None => Some((0, false)),
                    };

                    inputs_data.push(InputSigningData {
                        output,
                        output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                        chain: address_index_internal.map(|(address_index, internal)| {
                            Chain::from_u32_hardened(vec![
                                HD_WALLET_TYPE,
                                self.coin_type,
                                self.account_index,
                                internal as u32,
                                address_index,
                            ])
                        }),
                        bech32_address: output_address.to_bech32(&bech32_hrp),
                    });
                }
            }
        }

        let selected_transaction_data = try_select_inputs(
            inputs_data,
            Vec::new(),
            self.outputs.clone(),
            self.custom_remainder_address,
            rent_structure,
            allow_burning,
            current_time,
            token_supply,
        )?;

        Ok(selected_transaction_data)
    }
src/api/block_builder/input_selection/utxo_chains/mod.rs (line 301)
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
pub(crate) async fn get_alias_and_nft_outputs_recursively(
    client: &Client,
    utxo_chains: &mut Vec<(Address, OutputWithMetadataResponse)>,
) -> Result<()> {
    log::debug!("[get_alias_and_nft_outputs_recursively]");
    let current_time = client.get_time_checked().await?;
    let token_supply = client.get_token_supply().await?;

    let mut processed_alias_nft_addresses = std::collections::HashSet::new();

    // Add addresses for alias and nft outputs we already have
    for (_unlock_address, output_response) in utxo_chains.iter() {
        let output_id = OutputId::new(
            TransactionId::from_str(&output_response.metadata.transaction_id)?,
            output_response.metadata.output_index,
        )?;

        match Output::try_from_dto(&output_response.output, token_supply)? {
            Output::Alias(alias_output) => {
                processed_alias_nft_addresses.insert(Address::Alias(alias_output.alias_address(&output_id)));
            }
            Output::Nft(nft_output) => {
                processed_alias_nft_addresses.insert(Address::Nft(nft_output.nft_address(&output_id)));
            }
            _ => {}
        }
    }

    let mut processed_utxo_chains = Vec::new();

    // Make the outputs response optional, because we don't know it yet for new required outputs
    let mut utxo_chain_optional_response: Vec<(Address, Option<OutputWithMetadataResponse>)> =
        utxo_chains.iter_mut().map(|(a, o)| (*a, Some(o.clone()))).collect();

    // Get alias or nft addresses when needed or just add the input again
    while let Some((unlock_address, output_response)) = utxo_chain_optional_response.pop() {
        // Don't request outputs for addresses where we already have the output
        if processed_alias_nft_addresses.insert(unlock_address) {
            match unlock_address {
                Address::Alias(address) => {
                    let output_id = client.alias_output_id(*address.alias_id()).await?;
                    let output_response = client.get_output(&output_id).await?;
                    if let OutputDto::Alias(alias_output_dto) = &output_response.output {
                        let alias_output = AliasOutput::try_from_dto(alias_output_dto, token_supply)?;
                        // State transition if we add them to inputs
                        let alias_unlock_address = alias_output.state_controller_address();
                        // Add address to unprocessed_alias_nft_addresses so we get the required output there
                        // also
                        if alias_unlock_address.is_alias() || alias_unlock_address.is_nft() {
                            utxo_chain_optional_response.push((*alias_unlock_address, None));
                        }
                        processed_utxo_chains.push((*alias_unlock_address, output_response));
                    }
                }
                Address::Nft(address) => {
                    let output_id = client.nft_output_id(*address.nft_id()).await?;
                    let output_response = client.get_output(&output_id).await?;
                    if let OutputDto::Nft(nft_output) = &output_response.output {
                        let nft_output = NftOutput::try_from_dto(nft_output, token_supply)?;
                        let unlock_address = nft_output
                            .unlock_conditions()
                            .locked_address(nft_output.address(), current_time);
                        // Add address to unprocessed_alias_nft_addresses so we get the required output there also
                        if unlock_address.is_alias() || unlock_address.is_nft() {
                            utxo_chain_optional_response.push((*unlock_address, None));
                        }
                        processed_utxo_chains.push((*unlock_address, output_response));
                    }
                }
                _ => {}
            }
        }

        // Add if the output_response is available
        if let Some(output_response) = output_response {
            processed_utxo_chains.push((unlock_address, output_response));
        }
    }

    *utxo_chains = processed_utxo_chains;

    Ok(())
}
src/api/block_builder/input_selection/utxo_chains/automatic.rs (line 32)
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
    pub(crate) async fn get_utxo_chains_inputs(
        &self,
        outputs: impl Iterator<Item = &'a Output> + Clone,
    ) -> Result<Vec<InputSigningData>> {
        log::debug!("[get_utxo_chains_inputs]");
        let client = self.client;
        let bech32_hrp = client.get_bech32_hrp().await?;
        let current_time = self.client.get_time_checked().await?;
        let token_supply = client.get_token_supply().await?;

        let mut utxo_chains: Vec<(Address, OutputWithMetadataResponse)> = Vec::new();
        for output in outputs {
            match output {
                Output::Alias(alias_output) => {
                    // if the alias id is null then there can't be a previous output and it can also not be a
                    // governance transition
                    if !alias_output.alias_id().is_null() {
                        // Check if the transaction is a governance_transition, by checking if the new index is the same
                        // as the previous index
                        let output_id = client.alias_output_id(*alias_output.alias_id()).await?;
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Alias(alias_output_dto) = &output_response.output {
                            let alias_output = AliasOutput::try_from_dto(alias_output_dto, token_supply)?;

                            // A governance transition is identified by an unchanged State Index in next
                            // state.
                            if alias_output.state_index() == alias_output.state_index() {
                                utxo_chains.push((*alias_output.governor_address(), output_response));
                            } else {
                                utxo_chains.push((*alias_output.state_controller_address(), output_response));
                            }
                        }
                    }
                }
                Output::Nft(nft_output) => {
                    // If the id is null then this output creates it and we can't have a previous output
                    if !nft_output.nft_id().is_null() {
                        let output_id = client.nft_output_id(*nft_output.nft_id()).await?;
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Nft(nft_output) = &output_response.output {
                            let nft_output = NftOutput::try_from_dto(nft_output, token_supply)?;

                            let unlock_address = nft_output
                                .unlock_conditions()
                                .locked_address(nft_output.address(), current_time);

                            utxo_chains.push((*unlock_address, output_response));
                        }
                    }
                }
                Output::Foundry(foundry_output) => {
                    // if it's the first foundry output, then we can't have it as input
                    if let Ok(output_id) = client.foundry_output_id(foundry_output.id()).await {
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Foundry(foundry_output_dto) = &output_response.output {
                            let foundry_output = FoundryOutput::try_from_dto(foundry_output_dto, token_supply)?;
                            utxo_chains.push((Address::Alias(*foundry_output.alias_address()), output_response));
                        }
                    }
                }
                _ => {}
            }
        }

        // Get recursively owned alias or nft outputs
        get_alias_and_nft_outputs_recursively(self.client, &mut utxo_chains).await?;

        let mut utxo_chain_inputs = Vec::new();
        for (unlock_address, output_response) in utxo_chains {
            let address_index_internal = match self.secret_manager {
                Some(secret_manager) => {
                    match unlock_address {
                        Address::Ed25519(_) => Some(
                            search_address(
                                secret_manager,
                                &bech32_hrp,
                                self.coin_type,
                                self.account_index,
                                self.input_range.clone(),
                                &unlock_address,
                            )
                            .await?,
                        ),
                        // Alias and NFT addresses can't be generated from a private key
                        _ => None,
                    }
                }
                // Assuming default for offline signing
                None => Some((0, false)),
            };

            utxo_chain_inputs.push(InputSigningData {
                output: Output::try_from_dto(&output_response.output, token_supply)?,
                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                chain: address_index_internal.map(|(address_index, internal)| {
                    Chain::from_u32_hardened(vec![
                        HD_WALLET_TYPE,
                        self.coin_type,
                        self.account_index,
                        internal as u32,
                        address_index,
                    ])
                }),
                bech32_address: unlock_address.to_bech32(&bech32_hrp),
            });
        }

        Ok(utxo_chain_inputs)
    }
src/api/block_builder/input_selection/automatic.rs (line 79)
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
224
225
226
227
228
229
    pub(crate) async fn get_inputs(&self, rent_structure: &RentStructure) -> Result<SelectedTransactionData> {
        log::debug!("[get_inputs]");

        let account_index = self.account_index;
        let mut gap_index = self.initial_address_index;
        let mut empty_address_count: u64 = 0;
        let mut cached_error = None;
        let token_supply = self.client.get_token_supply().await?;

        log::debug!("[get_inputs from utxo chains]");

        // First get inputs for utxo chains (Alias, Foundry, NFT outputs).
        let mut available_inputs = self.get_utxo_chains_inputs(self.outputs.iter()).await?;
        let required_inputs_for_sender_or_issuer = self.get_inputs_for_sender_and_issuer(&available_inputs).await?;

        let current_time = self.client.get_time_checked().await?;

        // Try to select inputs with required inputs for utxo chains alone before requesting more inputs from addresses.
        if let Ok(selected_transaction_data) = try_select_inputs(
            required_inputs_for_sender_or_issuer.clone(),
            available_inputs.clone(),
            self.outputs.clone(),
            self.custom_remainder_address,
            rent_structure,
            // Don't allow burning of native tokens during automatic input selection, because otherwise it
            // could lead to burned native tokens by accident.
            false,
            current_time,
            token_supply,
        ) {
            return Ok(selected_transaction_data);
        };

        log::debug!("[get_inputs from addresses]");

        // Then select inputs with outputs from addresses.
        let selected_transaction_data = 'input_selection: loop {
            // Get the addresses in the BIP path/index ~ path/index+20.
            let addresses = self
                .client
                .get_addresses(
                    self.secret_manager
                        .ok_or(crate::Error::MissingParameter("secret manager"))?,
                )
                .with_account_index(account_index)
                .with_range(gap_index..gap_index + ADDRESS_GAP_RANGE)
                .get_all()
                .await?;
            // Have public and internal addresses with the index ascending ordered.
            let mut public_and_internal_addresses = Vec::new();

            for index in 0..addresses.public.len() {
                public_and_internal_addresses.push((addresses.public[index].clone(), false));
                public_and_internal_addresses.push((addresses.internal[index].clone(), true));
            }

            // For each address, get the address outputs.
            let mut address_index = gap_index;

            for (index, (str_address, internal)) in public_and_internal_addresses.iter().enumerate() {
                let address_outputs = self.basic_address_outputs(str_address.to_string()).await?;

                // If there are more than 20 (ADDRESS_GAP_RANGE) consecutive empty addresses, then we stop
                // looking up the addresses belonging to the seed. Note that we don't
                // really count the exact 20 consecutive empty addresses, which is
                // unnecessary. We just need to check the address range,
                // (index * ADDRESS_GAP_RANGE, index * ADDRESS_GAP_RANGE + ADDRESS_GAP_RANGE), where index is
                // natural number, and to see if the outputs are all empty.
                if address_outputs.is_empty() {
                    // Accumulate the empty_address_count for each run of output address searching
                    empty_address_count += 1;
                } else {
                    // Reset counter if there is an output
                    empty_address_count = 0;

                    for output_response in address_outputs {
                        let output = Output::try_from_dto(&output_response.output, token_supply)?;
                        let address = Address::try_from_bech32(str_address)?.1;

                        // We can ignore the unlocked_alias_or_nft_address, since we only requested basic outputs
                        let (required_unlock_address, _unlocked_alias_or_nft_address) = output
                            .required_and_unlocked_address(
                                current_time,
                                &output_response.metadata.output_id()?,
                                false,
                            )?;
                        if required_unlock_address == address {
                            available_inputs.push(InputSigningData {
                                output,
                                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                                chain: Some(Chain::from_u32_hardened(vec![
                                    HD_WALLET_TYPE,
                                    self.coin_type,
                                    account_index,
                                    *internal as u32,
                                    address_index,
                                ])),
                                bech32_address: str_address.clone(),
                            });
                        }
                    }
                    let selected_transaction_data = match try_select_inputs(
                        required_inputs_for_sender_or_issuer.clone(),
                        available_inputs.clone(),
                        self.outputs.clone(),
                        self.custom_remainder_address,
                        rent_structure,
                        // Don't allow burning of native tokens during automatic input selection, because otherwise it
                        // could lead to burned native tokens by accident.
                        false,
                        current_time,
                        token_supply,
                    ) {
                        Ok(r) => r,
                        // for these errors, just try again in the next round with more addresses which might have more
                        // outputs.
                        Err(err @ crate::Error::NotEnoughBalance { .. }) => {
                            cached_error.replace(err);
                            continue;
                        }
                        Err(err @ crate::Error::NotEnoughNativeTokens { .. }) => {
                            cached_error.replace(err);
                            continue;
                        }
                        // Native tokens left, but no balance for the storage deposit for a remainder.
                        Err(err @ crate::Error::NoBalanceForNativeTokenRemainder) => {
                            cached_error.replace(err);
                            continue;
                        }
                        // Currently too many inputs, by scanning for more inputs, we might find some with more amount.
                        Err(err @ crate::Error::ConsolidationRequired { .. }) => {
                            cached_error.replace(err);
                            continue;
                        }
                        // Not enough balance for a remainder.
                        Err(crate::Error::BlockError(block_error)) => match block_error {
                            iota_types::block::Error::InvalidStorageDepositAmount { .. } => {
                                cached_error.replace(crate::Error::BlockError(block_error));
                                continue;
                            }
                            _ => return Err(block_error.into()),
                        },
                        Err(e) => return Err(e),
                    };

                    break 'input_selection selected_transaction_data;
                }

                // if we just processed an even index, increase the address index
                // (because the list has public and internal addresses)
                if index % 2 == 1 {
                    address_index += 1;
                }
            }

            gap_index += ADDRESS_GAP_RANGE;

            // The gap limit is 20 and use reference 40 here because there's public and internal addresses
            if empty_address_count >= (ADDRESS_GAP_RANGE * 2) as u64 {
                // returned last cached error
                return Err(cached_error.unwrap_or(Error::NoInputs));
            }
        };

        Ok(selected_transaction_data)
    }

Create the builder to instantiate the IOTA Client.

Gets the network related information such as network_id and min_pow_score and if it’s the default one, sync it first and set the NetworkInfo.

Examples found in repository?
src/client/mod.rs (line 123)
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
    pub async fn get_protocol_parameters(&self) -> Result<ProtocolParameters> {
        Ok(self.get_network_info().await?.protocol_parameters)
    }

    /// Gets the protocol version of the node we're connecting to.
    pub async fn get_protocol_version(&self) -> Result<u8> {
        Ok(self.get_network_info().await?.protocol_parameters.protocol_version())
    }

    /// Gets the network name of the node we're connecting to.
    pub async fn get_network_name(&self) -> Result<String> {
        Ok(self.get_network_info().await?.protocol_parameters.network_name().into())
    }

    /// Gets the network id of the node we're connecting to.
    pub async fn get_network_id(&self) -> Result<u64> {
        Ok(self.get_network_info().await?.protocol_parameters.network_id())
    }

    /// Gets the bech32 HRP of the node we're connecting to.
    pub async fn get_bech32_hrp(&self) -> Result<String> {
        Ok(self.get_network_info().await?.protocol_parameters.bech32_hrp().into())
    }

    /// Gets the minimum pow score of the node we're connecting to.
    pub async fn get_min_pow_score(&self) -> Result<u32> {
        Ok(self.get_network_info().await?.protocol_parameters.min_pow_score())
    }

    /// Gets the below maximum depth of the node we're connecting to.
    pub async fn get_below_max_depth(&self) -> Result<u8> {
        Ok(self.get_network_info().await?.protocol_parameters.below_max_depth())
    }

    /// Gets the rent structure of the node we're connecting to.
    pub async fn get_rent_structure(&self) -> Result<RentStructure> {
        Ok(self
            .get_network_info()
            .await?
            .protocol_parameters
            .rent_structure()
            .clone())
    }

    /// Gets the token supply of the node we're connecting to.
    pub async fn get_token_supply(&self) -> Result<u64> {
        Ok(self.get_network_info().await?.protocol_parameters.token_supply())
    }
More examples
Hide additional examples
src/client/high_level.rs (line 347)
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
    pub async fn get_time_checked(&self) -> Result<u32> {
        let current_time = instant::SystemTime::now()
            .duration_since(instant::SystemTime::UNIX_EPOCH)
            .expect("time went backwards")
            .as_secs() as u32;

        let network_info = self.get_network_info().await?;

        if let Some(latest_ms_timestamp) = network_info.latest_milestone_timestamp {
            // Check the local time is in the range of +-5 minutes of the node to prevent locking funds by accident
            if !(latest_ms_timestamp - FIVE_MINUTES_IN_SECONDS..latest_ms_timestamp + FIVE_MINUTES_IN_SECONDS)
                .contains(&current_time)
            {
                return Err(Error::TimeNotSynced {
                    current_time,
                    milestone_timestamp: latest_ms_timestamp,
                });
            }
        }

        Ok(current_time)
    }

Gets the protocol parameters of the node we’re connecting to.

Examples found in repository?
src/node_api/core/routes.rs (line 291)
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
    pub async fn get_block(&self, block_id: &BlockId) -> Result<Block> {
        let path = &format!("api/core/v2/blocks/{block_id}");

        let resp = self
            .node_manager
            .get_request::<BlockResponse>(path, None, self.get_timeout(), false, true)
            .await?;

        match resp {
            BlockResponse::Json(dto) => Ok(Block::try_from_dto(&dto, &self.get_protocol_parameters().await?)?),
            BlockResponse::Raw(_) => Err(crate::Error::UnexpectedApiResponse),
        }
    }

    /// Finds a block by its BlockId. This method returns the given block raw data.
    /// GET /api/core/v2/blocks/{BlockId}
    pub async fn get_block_raw(&self, block_id: &BlockId) -> Result<Vec<u8>> {
        let path = &format!("api/core/v2/blocks/{block_id}");

        self.node_manager
            .get_request_bytes(path, None, self.get_timeout())
            .await
    }

    /// Returns the metadata of a block.
    /// GET /api/core/v2/blocks/{BlockId}/metadata
    pub async fn get_block_metadata(&self, block_id: &BlockId) -> Result<BlockMetadataResponse> {
        let path = &format!("api/core/v2/blocks/{block_id}/metadata");

        self.node_manager
            .get_request(path, None, self.get_timeout(), true, true)
            .await
    }

    // UTXO routes.

    /// Finds an output, as JSON, by its OutputId (TransactionId + output_index).
    /// GET /api/core/v2/outputs/{outputId}
    pub async fn get_output(&self, output_id: &OutputId) -> Result<OutputWithMetadataResponse> {
        let path = &format!("api/core/v2/outputs/{output_id}");

        self.node_manager
            .get_request(path, None, self.get_timeout(), false, true)
            .await
    }

    /// Finds an output, as raw bytes, by its OutputId (TransactionId + output_index).
    /// GET /api/core/v2/outputs/{outputId}
    pub async fn get_output_raw(&self, output_id: &OutputId) -> Result<Vec<u8>> {
        let path = &format!("api/core/v2/outputs/{output_id}");

        self.node_manager
            .get_request_bytes(path, None, self.get_timeout())
            .await
    }

    /// Get the metadata for a given `OutputId` (TransactionId + output_index).
    /// GET /api/core/v2/outputs/{outputId}/metadata
    pub async fn get_output_metadata(&self, output_id: &OutputId) -> Result<OutputMetadataResponse> {
        let path = &format!("api/core/v2/outputs/{output_id}/metadata");

        self.node_manager
            .get_request::<OutputMetadataResponse>(path, None, self.get_timeout(), false, true)
            .await
    }

    /// Gets all stored receipts.
    /// GET /api/core/v2/receipts
    pub async fn get_receipts(&self) -> Result<Vec<ReceiptDto>> {
        let path = &"api/core/v2/receipts";

        let resp = self
            .node_manager
            .get_request::<ReceiptsResponse>(path, None, DEFAULT_API_TIMEOUT, false, false)
            .await?;

        Ok(resp.receipts)
    }

    /// Gets the receipts by the given milestone index.
    /// GET /api/core/v2/receipts/{migratedAt}
    pub async fn get_receipts_migrated_at(&self, milestone_index: u32) -> Result<Vec<ReceiptDto>> {
        let path = &format!("api/core/v2/receipts/{milestone_index}");

        let resp = self
            .node_manager
            .get_request::<ReceiptsResponse>(path, None, DEFAULT_API_TIMEOUT, false, false)
            .await?;

        Ok(resp.receipts)
    }

    /// Gets the current treasury output.
    /// The treasury output contains all tokens from the legacy network that have not yet been migrated.
    /// GET /api/core/v2/treasury
    pub async fn get_treasury(&self) -> Result<TreasuryResponse> {
        let path = "api/core/v2/treasury";

        self.node_manager
            .get_request(path, None, DEFAULT_API_TIMEOUT, false, false)
            .await
    }

    /// Returns the block, as object, that was included in the ledger for a given TransactionId.
    /// GET /api/core/v2/transactions/{transactionId}/included-block
    pub async fn get_included_block(&self, transaction_id: &TransactionId) -> Result<Block> {
        let path = &format!("api/core/v2/transactions/{transaction_id}/included-block");

        let resp = self
            .node_manager
            .get_request::<BlockResponse>(path, None, self.get_timeout(), true, true)
            .await?;

        match resp {
            BlockResponse::Json(dto) => Ok(Block::try_from_dto(&dto, &self.get_protocol_parameters().await?)?),
            BlockResponse::Raw(_) => Err(crate::Error::UnexpectedApiResponse),
        }
    }

    /// Returns the block, as raw bytes, that was included in the ledger for a given TransactionId.
    /// GET /api/core/v2/transactions/{transactionId}/included-block
    pub async fn get_included_block_raw(&self, transaction_id: &TransactionId) -> Result<Vec<u8>> {
        let path = &format!("api/core/v2/transactions/{transaction_id}/included-block");

        self.node_manager
            .get_request_bytes(path, None, self.get_timeout())
            .await
    }

    // Milestones routes.

    /// Gets the milestone by the given milestone id.
    /// GET /api/core/v2/milestones/{milestoneId}
    pub async fn get_milestone_by_id(&self, milestone_id: &MilestoneId) -> Result<MilestonePayload> {
        let path = &format!("api/core/v2/milestones/{milestone_id}");

        let resp = self
            .node_manager
            .get_request::<MilestoneResponse>(path, None, self.get_timeout(), false, true)
            .await?;

        match resp {
            MilestoneResponse::Json(dto) => Ok(MilestonePayload::try_from_dto(
                &dto,
                &self.get_protocol_parameters().await?,
            )?),
            MilestoneResponse::Raw(_) => Err(crate::Error::UnexpectedApiResponse),
        }
    }

    /// Gets the milestone by the given milestone id.
    /// GET /api/core/v2/milestones/{milestoneId}
    pub async fn get_milestone_by_id_raw(&self, milestone_id: &MilestoneId) -> Result<Vec<u8>> {
        let path = &format!("api/core/v2/milestones/{milestone_id}");

        self.node_manager
            .get_request_bytes(path, None, self.get_timeout())
            .await
    }

    /// Gets all UTXO changes of a milestone by its milestone id.
    /// GET /api/core/v2/milestones/{milestoneId}/utxo-changes
    pub async fn get_utxo_changes_by_id(&self, milestone_id: &MilestoneId) -> Result<UtxoChangesResponse> {
        let path = &format!("api/core/v2/milestones/{milestone_id}/utxo-changes");

        self.node_manager
            .get_request(path, None, self.get_timeout(), false, false)
            .await
    }

    /// Gets the milestone by the given milestone index.
    /// GET /api/core/v2/milestones/{index}
    pub async fn get_milestone_by_index(&self, index: u32) -> Result<MilestonePayload> {
        let path = &format!("api/core/v2/milestones/by-index/{index}");

        let resp = self
            .node_manager
            .get_request::<MilestoneResponse>(path, None, self.get_timeout(), false, true)
            .await?;

        match resp {
            MilestoneResponse::Json(dto) => Ok(MilestonePayload::try_from_dto(
                &dto,
                &self.get_protocol_parameters().await?,
            )?),
            MilestoneResponse::Raw(_) => Err(crate::Error::UnexpectedApiResponse),
        }
    }
More examples
Hide additional examples
src/api/block_builder/transaction.rs (line 95)
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
    pub async fn prepare_transaction(&self) -> Result<PreparedTransactionData> {
        log::debug!("[prepare_transaction]");
        let rent_structure = self.client.get_rent_structure().await?;
        let token_supply = self.client.get_token_supply().await?;

        let mut governance_transition: Option<HashSet<AliasId>> = None;
        for output in &self.outputs {
            // Check if the outputs have enough amount to cover the storage deposit
            output.verify_storage_deposit(rent_structure.clone(), token_supply)?;
            if let Output::Alias(x) = output {
                if x.state_index() > 0 {
                    // Check if the transaction is a governance_transition, by checking if the new index is the same as
                    // the previous index
                    let output_id = self.client.alias_output_id(*x.alias_id()).await?;
                    let output_response = self.client.get_output(&output_id).await?;
                    if let OutputDto::Alias(output) = output_response.output {
                        // A governance transition is identified by an unchanged State Index in next state.
                        if x.state_index() == output.state_index {
                            let mut transitions = HashSet::new();
                            transitions.insert(AliasId::try_from(&output.alias_id)?);
                            governance_transition.replace(transitions);
                        }
                    }
                }
            }
        }

        // Input selection
        let selected_transaction_data = if self.inputs.is_some() {
            self.get_custom_inputs(governance_transition, &rent_structure, self.allow_burning)
                .await?
        } else {
            self.get_inputs(&rent_structure).await?
        };

        // Build transaction payload
        let inputs_commitment = InputsCommitment::new(selected_transaction_data.inputs.iter().map(|i| &i.output));

        let mut essence = RegularTransactionEssence::builder(self.client.get_network_id().await?, inputs_commitment);
        let inputs = selected_transaction_data
            .inputs
            .iter()
            .map(|i| {
                Ok(Input::Utxo(UtxoInput::new(
                    *i.output_metadata.transaction_id(),
                    i.output_metadata.output_index(),
                )?))
            })
            .collect::<Result<Vec<Input>>>()?;
        essence = essence.with_inputs(inputs);

        essence = essence.with_outputs(selected_transaction_data.outputs);

        // Add tagged data payload if tag set
        if let Some(index) = self.tag.clone() {
            let tagged_data_payload = TaggedDataPayload::new(index.to_vec(), self.data.clone().unwrap_or_default())?;
            essence = essence.with_payload(Payload::from(tagged_data_payload));
        }

        let regular_essence = essence.finish(&self.client.get_protocol_parameters().await?)?;

        validate_regular_transaction_essence_length(&regular_essence)?;

        let essence = TransactionEssence::Regular(regular_essence);

        Ok(PreparedTransactionData {
            essence,
            inputs_data: selected_transaction_data.inputs,
            remainder: selected_transaction_data.remainder,
        })
    }

Gets the protocol version of the node we’re connecting to.

Gets the network name of the node we’re connecting to.

Gets the network id of the node we’re connecting to.

Examples found in repository?
src/api/block_builder/transaction.rs (line 74)
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
    pub async fn prepare_transaction(&self) -> Result<PreparedTransactionData> {
        log::debug!("[prepare_transaction]");
        let rent_structure = self.client.get_rent_structure().await?;
        let token_supply = self.client.get_token_supply().await?;

        let mut governance_transition: Option<HashSet<AliasId>> = None;
        for output in &self.outputs {
            // Check if the outputs have enough amount to cover the storage deposit
            output.verify_storage_deposit(rent_structure.clone(), token_supply)?;
            if let Output::Alias(x) = output {
                if x.state_index() > 0 {
                    // Check if the transaction is a governance_transition, by checking if the new index is the same as
                    // the previous index
                    let output_id = self.client.alias_output_id(*x.alias_id()).await?;
                    let output_response = self.client.get_output(&output_id).await?;
                    if let OutputDto::Alias(output) = output_response.output {
                        // A governance transition is identified by an unchanged State Index in next state.
                        if x.state_index() == output.state_index {
                            let mut transitions = HashSet::new();
                            transitions.insert(AliasId::try_from(&output.alias_id)?);
                            governance_transition.replace(transitions);
                        }
                    }
                }
            }
        }

        // Input selection
        let selected_transaction_data = if self.inputs.is_some() {
            self.get_custom_inputs(governance_transition, &rent_structure, self.allow_burning)
                .await?
        } else {
            self.get_inputs(&rent_structure).await?
        };

        // Build transaction payload
        let inputs_commitment = InputsCommitment::new(selected_transaction_data.inputs.iter().map(|i| &i.output));

        let mut essence = RegularTransactionEssence::builder(self.client.get_network_id().await?, inputs_commitment);
        let inputs = selected_transaction_data
            .inputs
            .iter()
            .map(|i| {
                Ok(Input::Utxo(UtxoInput::new(
                    *i.output_metadata.transaction_id(),
                    i.output_metadata.output_index(),
                )?))
            })
            .collect::<Result<Vec<Input>>>()?;
        essence = essence.with_inputs(inputs);

        essence = essence.with_outputs(selected_transaction_data.outputs);

        // Add tagged data payload if tag set
        if let Some(index) = self.tag.clone() {
            let tagged_data_payload = TaggedDataPayload::new(index.to_vec(), self.data.clone().unwrap_or_default())?;
            essence = essence.with_payload(Payload::from(tagged_data_payload));
        }

        let regular_essence = essence.finish(&self.client.get_protocol_parameters().await?)?;

        validate_regular_transaction_essence_length(&regular_essence)?;

        let essence = TransactionEssence::Regular(regular_essence);

        Ok(PreparedTransactionData {
            essence,
            inputs_data: selected_transaction_data.inputs,
            remainder: selected_transaction_data.remainder,
        })
    }

Gets the bech32 HRP of the node we’re connecting to.

Examples found in repository?
src/utils.rs (line 125)
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
    pub async fn hex_to_bech32(&self, hex: &str, bech32_hrp: Option<&str>) -> crate::Result<String> {
        match bech32_hrp {
            Some(hrp) => Ok(hex_to_bech32(hex, hrp)?),
            None => Ok(hex_to_bech32(hex, &self.get_bech32_hrp().await?)?),
        }
    }

    /// Transforms an alias id to a bech32 encoded address
    pub async fn alias_id_to_bech32(&self, alias_id: AliasId, bech32_hrp: Option<&str>) -> crate::Result<String> {
        match bech32_hrp {
            Some(hrp) => Ok(alias_id_to_bech32(alias_id, hrp)),
            None => Ok(alias_id_to_bech32(alias_id, &self.get_bech32_hrp().await?)),
        }
    }

    /// Transforms an nft id to a bech32 encoded address
    pub async fn nft_id_to_bech32(&self, nft_id: NftId, bech32_hrp: Option<&str>) -> crate::Result<String> {
        match bech32_hrp {
            Some(hrp) => Ok(nft_id_to_bech32(nft_id, hrp)),
            None => Ok(nft_id_to_bech32(nft_id, &self.get_bech32_hrp().await?)),
        }
    }

    /// Transforms a hex encoded public key to a bech32 encoded address
    pub async fn hex_public_key_to_bech32_address(&self, hex: &str, bech32_hrp: Option<&str>) -> crate::Result<String> {
        match bech32_hrp {
            Some(hrp) => Ok(hex_public_key_to_bech32_address(hex, hrp)?),
            None => Ok(hex_public_key_to_bech32_address(hex, &self.get_bech32_hrp().await?)?),
        }
    }
More examples
Hide additional examples
src/api/address.rs (line 139)
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
    pub async fn finish(self) -> Result<Vec<String>> {
        let bech32_hrp = match self.bech32_hrp.clone() {
            Some(bech32_hrp) => bech32_hrp,
            None => match self.client {
                Some(client) => client.get_bech32_hrp().await?,
                None => SHIMMER_TESTNET_BECH32_HRP.to_string(),
            },
        };

        let addresses = self
            .secret_manager
            .generate_addresses(
                self.coin_type,
                self.account_index,
                self.range,
                self.internal,
                self.options.clone(),
            )
            .await?
            .into_iter()
            .map(|a| a.to_bech32(&bech32_hrp))
            .collect();

        Ok(addresses)
    }
    /// Consume the builder and get a vector of public addresses
    pub async fn get_raw(self) -> Result<Vec<Address>> {
        self.secret_manager
            .generate_addresses(
                self.coin_type,
                self.account_index,
                self.range,
                false,
                self.options.clone(),
            )
            .await
    }

    /// Consume the builder and get the vector of public and internal addresses bech32 encoded
    pub async fn get_all(self) -> Result<Bech32Addresses> {
        let bech32_hrp = match self.bech32_hrp.clone() {
            Some(bech32_hrp) => bech32_hrp,
            None => match self.client {
                Some(client) => client.get_bech32_hrp().await?,
                None => SHIMMER_TESTNET_BECH32_HRP.to_string(),
            },
        };
        let addresses = self.get_all_raw().await?;

        Ok(Bech32Addresses {
            public: addresses.public.into_iter().map(|a| a.to_bech32(&bech32_hrp)).collect(),
            internal: addresses
                .internal
                .into_iter()
                .map(|a| a.to_bech32(&bech32_hrp))
                .collect(),
        })
    }
src/api/block_builder/input_selection/manual.rs (line 54)
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
    pub(crate) async fn get_custom_inputs(
        &self,
        governance_transition: Option<HashSet<AliasId>>,
        rent_structure: &RentStructure,
        allow_burning: bool,
    ) -> Result<SelectedTransactionData> {
        log::debug!("[get_custom_inputs]");

        let mut inputs_data = Vec::new();
        let current_time = self.client.get_time_checked().await?;
        let token_supply = self.client.get_token_supply().await?;

        if let Some(inputs) = &self.inputs {
            for input in inputs {
                let output_response = self.client.get_output(input.output_id()).await?;
                let output = Output::try_from_dto(&output_response.output, token_supply)?;

                if !output_response.metadata.is_spent {
                    let (_output_amount, output_address) = ClientBlockBuilder::get_output_amount_and_address(
                        &output,
                        governance_transition.clone(),
                        current_time,
                    )?;

                    let bech32_hrp = self.client.get_bech32_hrp().await?;
                    let address_index_internal = match self.secret_manager {
                        Some(secret_manager) => {
                            match output_address {
                                Address::Ed25519(_) => Some(
                                    search_address(
                                        secret_manager,
                                        &bech32_hrp,
                                        self.coin_type,
                                        self.account_index,
                                        self.input_range.clone(),
                                        &output_address,
                                    )
                                    .await?,
                                ),
                                // Alias and NFT addresses can't be generated from a private key.
                                _ => None,
                            }
                        }
                        // Assuming default for offline signing.
                        None => Some((0, false)),
                    };

                    inputs_data.push(InputSigningData {
                        output,
                        output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                        chain: address_index_internal.map(|(address_index, internal)| {
                            Chain::from_u32_hardened(vec![
                                HD_WALLET_TYPE,
                                self.coin_type,
                                self.account_index,
                                internal as u32,
                                address_index,
                            ])
                        }),
                        bech32_address: output_address.to_bech32(&bech32_hrp),
                    });
                }
            }
        }

        let selected_transaction_data = try_select_inputs(
            inputs_data,
            Vec::new(),
            self.outputs.clone(),
            self.custom_remainder_address,
            rent_structure,
            allow_burning,
            current_time,
            token_supply,
        )?;

        Ok(selected_transaction_data)
    }
src/api/block_builder/input_selection/utxo_chains/automatic.rs (line 31)
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
    pub(crate) async fn get_utxo_chains_inputs(
        &self,
        outputs: impl Iterator<Item = &'a Output> + Clone,
    ) -> Result<Vec<InputSigningData>> {
        log::debug!("[get_utxo_chains_inputs]");
        let client = self.client;
        let bech32_hrp = client.get_bech32_hrp().await?;
        let current_time = self.client.get_time_checked().await?;
        let token_supply = client.get_token_supply().await?;

        let mut utxo_chains: Vec<(Address, OutputWithMetadataResponse)> = Vec::new();
        for output in outputs {
            match output {
                Output::Alias(alias_output) => {
                    // if the alias id is null then there can't be a previous output and it can also not be a
                    // governance transition
                    if !alias_output.alias_id().is_null() {
                        // Check if the transaction is a governance_transition, by checking if the new index is the same
                        // as the previous index
                        let output_id = client.alias_output_id(*alias_output.alias_id()).await?;
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Alias(alias_output_dto) = &output_response.output {
                            let alias_output = AliasOutput::try_from_dto(alias_output_dto, token_supply)?;

                            // A governance transition is identified by an unchanged State Index in next
                            // state.
                            if alias_output.state_index() == alias_output.state_index() {
                                utxo_chains.push((*alias_output.governor_address(), output_response));
                            } else {
                                utxo_chains.push((*alias_output.state_controller_address(), output_response));
                            }
                        }
                    }
                }
                Output::Nft(nft_output) => {
                    // If the id is null then this output creates it and we can't have a previous output
                    if !nft_output.nft_id().is_null() {
                        let output_id = client.nft_output_id(*nft_output.nft_id()).await?;
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Nft(nft_output) = &output_response.output {
                            let nft_output = NftOutput::try_from_dto(nft_output, token_supply)?;

                            let unlock_address = nft_output
                                .unlock_conditions()
                                .locked_address(nft_output.address(), current_time);

                            utxo_chains.push((*unlock_address, output_response));
                        }
                    }
                }
                Output::Foundry(foundry_output) => {
                    // if it's the first foundry output, then we can't have it as input
                    if let Ok(output_id) = client.foundry_output_id(foundry_output.id()).await {
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Foundry(foundry_output_dto) = &output_response.output {
                            let foundry_output = FoundryOutput::try_from_dto(foundry_output_dto, token_supply)?;
                            utxo_chains.push((Address::Alias(*foundry_output.alias_address()), output_response));
                        }
                    }
                }
                _ => {}
            }
        }

        // Get recursively owned alias or nft outputs
        get_alias_and_nft_outputs_recursively(self.client, &mut utxo_chains).await?;

        let mut utxo_chain_inputs = Vec::new();
        for (unlock_address, output_response) in utxo_chains {
            let address_index_internal = match self.secret_manager {
                Some(secret_manager) => {
                    match unlock_address {
                        Address::Ed25519(_) => Some(
                            search_address(
                                secret_manager,
                                &bech32_hrp,
                                self.coin_type,
                                self.account_index,
                                self.input_range.clone(),
                                &unlock_address,
                            )
                            .await?,
                        ),
                        // Alias and NFT addresses can't be generated from a private key
                        _ => None,
                    }
                }
                // Assuming default for offline signing
                None => Some((0, false)),
            };

            utxo_chain_inputs.push(InputSigningData {
                output: Output::try_from_dto(&output_response.output, token_supply)?,
                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                chain: address_index_internal.map(|(address_index, internal)| {
                    Chain::from_u32_hardened(vec![
                        HD_WALLET_TYPE,
                        self.coin_type,
                        self.account_index,
                        internal as u32,
                        address_index,
                    ])
                }),
                bech32_address: unlock_address.to_bech32(&bech32_hrp),
            });
        }

        Ok(utxo_chain_inputs)
    }
src/api/block_builder/input_selection/sender_issuer.rs (line 29)
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
    pub(crate) async fn get_inputs_for_sender_and_issuer(
        &self,
        utxo_chain_inputs: &[InputSigningData],
    ) -> Result<Vec<InputSigningData>> {
        log::debug!("[get_inputs_for_sender_and_issuer]");

        let mut required_inputs = Vec::new();
        let bech32_hrp = self.client.get_bech32_hrp().await?;
        let current_time = self.client.get_time_checked().await?;
        let token_supply = self.client.get_token_supply().await?;

        let required_sender_or_issuer_addresses =
            get_required_addresses_for_sender_and_issuer(&[], &self.outputs, current_time)?;

        for sender_or_issuer_address in required_sender_or_issuer_addresses {
            match sender_or_issuer_address {
                Address::Ed25519(_) => {
                    // Check if the address is derived from the seed
                    let (address_index, internal) = search_address(
                        self.secret_manager.ok_or(Error::MissingParameter("secret manager"))?,
                        &bech32_hrp,
                        self.coin_type,
                        self.account_index,
                        self.input_range.clone(),
                        &sender_or_issuer_address,
                    )
                    .await?;
                    let address_outputs = self
                        .basic_address_outputs(sender_or_issuer_address.to_bech32(&bech32_hrp))
                        .await?;

                    let mut found_output = false;
                    for output_response in address_outputs {
                        let output = Output::try_from_dto(&output_response.output, token_supply)?;

                        // We can ignore the unlocked_alias_or_nft_address, since we only requested basic outputs
                        let (required_unlock_address, _unlocked_alias_or_nft_address) = output
                            .required_and_unlocked_address(
                                current_time,
                                &output_response.metadata.output_id()?,
                                false,
                            )?;

                        if required_unlock_address == sender_or_issuer_address {
                            required_inputs.push(InputSigningData {
                                output,
                                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                                chain: Some(Chain::from_u32_hardened(vec![
                                    HD_WALLET_TYPE,
                                    self.coin_type,
                                    self.account_index,
                                    internal as u32,
                                    address_index,
                                ])),
                                bech32_address: sender_or_issuer_address.to_bech32(&bech32_hrp),
                            });
                            found_output = true;
                            break;
                        }
                    }

                    if !found_output {
                        return Err(Error::MissingInputWithEd25519Address);
                    }
                }
                Address::Alias(alias_address) => {
                    // Check if output is alias address.
                    let alias_id = alias_address.alias_id();

                    // check if already found or request new.
                    if !utxo_chain_inputs.iter().chain(required_inputs.iter()).any(|input| {
                        if let Output::Alias(alias_output) = &input.output {
                            alias_id == alias_output.alias_id()
                        } else {
                            false
                        }
                    }) {
                        let output_id = self.client.alias_output_id(*alias_id).await?;
                        let output_response = self.client.get_output(&output_id).await?;
                        if let OutputDto::Alias(alias_output_dto) = &output_response.output {
                            let alias_output = AliasOutput::try_from_dto(alias_output_dto, token_supply)?;
                            // State transition if we add them to inputs
                            let unlock_address = alias_output.state_controller_address();
                            let address_index_internal = match self.secret_manager {
                                Some(secret_manager) => {
                                    match unlock_address {
                                        Address::Ed25519(_) => Some(
                                            search_address(
                                                secret_manager,
                                                &bech32_hrp,
                                                self.coin_type,
                                                self.account_index,
                                                self.input_range.clone(),
                                                unlock_address,
                                            )
                                            .await?,
                                        ),
                                        // Alias and NFT addresses can't be generated from a private key
                                        _ => None,
                                    }
                                }
                                // Assuming default for offline signing
                                None => Some((0, false)),
                            };

                            required_inputs.push(InputSigningData {
                                output: Output::try_from_dto(&output_response.output, token_supply)?,
                                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                                chain: address_index_internal.map(|(address_index, internal)| {
                                    Chain::from_u32_hardened(vec![
                                        HD_WALLET_TYPE,
                                        self.coin_type,
                                        self.account_index,
                                        internal as u32,
                                        address_index,
                                    ])
                                }),
                                bech32_address: unlock_address.to_bech32(&bech32_hrp),
                            });
                        }
                    }
                }
                Address::Nft(nft_address) => {
                    // Check if output is nft address.
                    let nft_id = nft_address.nft_id();

                    // Check if already found or request new.
                    if !utxo_chain_inputs.iter().chain(required_inputs.iter()).any(|input| {
                        if let Output::Nft(nft_output) = &input.output {
                            nft_id == nft_output.nft_id()
                        } else {
                            false
                        }
                    }) {
                        let output_id = self.client.nft_output_id(*nft_id).await?;
                        let output_response = self.client.get_output(&output_id).await?;
                        if let OutputDto::Nft(nft_output) = &output_response.output {
                            let nft_output = NftOutput::try_from_dto(nft_output, token_supply)?;

                            let unlock_address = nft_output
                                .unlock_conditions()
                                .locked_address(nft_output.address(), current_time);

                            let address_index_internal = match self.secret_manager {
                                Some(secret_manager) => {
                                    match unlock_address {
                                        Address::Ed25519(_) => Some(
                                            search_address(
                                                secret_manager,
                                                &bech32_hrp,
                                                self.coin_type,
                                                self.account_index,
                                                self.input_range.clone(),
                                                unlock_address,
                                            )
                                            .await?,
                                        ),
                                        // Alias and NFT addresses can't be generated from a private key.
                                        _ => None,
                                    }
                                }
                                // Assuming default for offline signing.
                                None => Some((0, false)),
                            };

                            required_inputs.push(InputSigningData {
                                output: Output::try_from_dto(&output_response.output, token_supply)?,
                                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                                chain: address_index_internal.map(|(address_index, internal)| {
                                    Chain::from_u32_hardened(vec![
                                        HD_WALLET_TYPE,
                                        self.coin_type,
                                        self.account_index,
                                        internal as u32,
                                        address_index,
                                    ])
                                }),
                                bech32_address: unlock_address.to_bech32(&bech32_hrp),
                            });
                        }
                    }
                }
            }
        }

        // Check required Alias and NFT outputs with new added outputs.
        // No need to check for sender and issuer again, since these outputs already exist and we don't set new features
        // for them.
        let utxo_chain_inputs = self
            .get_utxo_chains_inputs(required_inputs.iter().map(|i| &i.output))
            .await?;
        required_inputs.extend(utxo_chain_inputs.into_iter());

        Ok(required_inputs)
    }

Gets the minimum pow score of the node we’re connecting to.

Examples found in repository?
src/api/block_builder/pow.rs (line 52)
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
    async fn finish_multi_threaded_pow(&self, parents: Option<Parents>, payload: Option<Payload>) -> Result<Block> {
        let pow_worker_count = self.pow_worker_count;
        let min_pow_score = self.get_min_pow_score().await?;
        let tips_interval = self.get_tips_interval();

        loop {
            let cancel = MinerCancel::new();
            let cancel_2 = cancel.clone();
            let payload_ = payload.clone();
            let parents = match &parents {
                Some(parents) => parents.clone(),
                None => Parents::new(self.get_tips().await?)?,
            };
            let time_thread = std::thread::spawn(move || Ok(pow_timeout(tips_interval, cancel)));
            let pow_thread = std::thread::spawn(move || {
                let mut client_miner = MinerBuilder::new().with_cancel(cancel_2);
                if let Some(worker_count) = pow_worker_count {
                    client_miner = client_miner.with_num_workers(worker_count);
                }
                do_pow(client_miner.finish(), min_pow_score, payload_, parents)
                    .map(|block| (block.nonce(), Some(block)))
            });

            let threads = vec![pow_thread, time_thread];

            for t in threads {
                match t.join().expect("failed to join threads.") {
                    Ok(res) => {
                        if res.0 != 0 {
                            if let Some(block) = res.1 {
                                return Ok(block);
                            }
                        }
                    }
                    Err(err) => {
                        return Err(err);
                    }
                }
            }
        }
    }

Gets the below maximum depth of the node we’re connecting to.

Gets the rent structure of the node we’re connecting to.

Examples found in repository?
src/api/block_builder/transaction.rs (line 38)
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
    pub async fn prepare_transaction(&self) -> Result<PreparedTransactionData> {
        log::debug!("[prepare_transaction]");
        let rent_structure = self.client.get_rent_structure().await?;
        let token_supply = self.client.get_token_supply().await?;

        let mut governance_transition: Option<HashSet<AliasId>> = None;
        for output in &self.outputs {
            // Check if the outputs have enough amount to cover the storage deposit
            output.verify_storage_deposit(rent_structure.clone(), token_supply)?;
            if let Output::Alias(x) = output {
                if x.state_index() > 0 {
                    // Check if the transaction is a governance_transition, by checking if the new index is the same as
                    // the previous index
                    let output_id = self.client.alias_output_id(*x.alias_id()).await?;
                    let output_response = self.client.get_output(&output_id).await?;
                    if let OutputDto::Alias(output) = output_response.output {
                        // A governance transition is identified by an unchanged State Index in next state.
                        if x.state_index() == output.state_index {
                            let mut transitions = HashSet::new();
                            transitions.insert(AliasId::try_from(&output.alias_id)?);
                            governance_transition.replace(transitions);
                        }
                    }
                }
            }
        }

        // Input selection
        let selected_transaction_data = if self.inputs.is_some() {
            self.get_custom_inputs(governance_transition, &rent_structure, self.allow_burning)
                .await?
        } else {
            self.get_inputs(&rent_structure).await?
        };

        // Build transaction payload
        let inputs_commitment = InputsCommitment::new(selected_transaction_data.inputs.iter().map(|i| &i.output));

        let mut essence = RegularTransactionEssence::builder(self.client.get_network_id().await?, inputs_commitment);
        let inputs = selected_transaction_data
            .inputs
            .iter()
            .map(|i| {
                Ok(Input::Utxo(UtxoInput::new(
                    *i.output_metadata.transaction_id(),
                    i.output_metadata.output_index(),
                )?))
            })
            .collect::<Result<Vec<Input>>>()?;
        essence = essence.with_inputs(inputs);

        essence = essence.with_outputs(selected_transaction_data.outputs);

        // Add tagged data payload if tag set
        if let Some(index) = self.tag.clone() {
            let tagged_data_payload = TaggedDataPayload::new(index.to_vec(), self.data.clone().unwrap_or_default())?;
            essence = essence.with_payload(Payload::from(tagged_data_payload));
        }

        let regular_essence = essence.finish(&self.client.get_protocol_parameters().await?)?;

        validate_regular_transaction_essence_length(&regular_essence)?;

        let essence = TransactionEssence::Regular(regular_essence);

        Ok(PreparedTransactionData {
            essence,
            inputs_data: selected_transaction_data.inputs,
            remainder: selected_transaction_data.remainder,
        })
    }

Gets the token supply of the node we’re connecting to.

Examples found in repository?
src/api/block_builder/mod.rs (line 166)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
    pub async fn with_output(mut self, address: &str, amount: u64) -> Result<ClientBlockBuilder<'a>> {
        let output = BasicOutputBuilder::new_with_amount(amount)?
            .add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(
                Address::try_from_bech32(address)?.1,
            )))
            .finish_output(self.client.get_token_supply().await?)?;
        self.outputs.push(output);
        if !OUTPUT_COUNT_RANGE.contains(&(self.outputs.len() as u16)) {
            return Err(crate::Error::BlockError(iota_types::block::Error::InvalidOutputCount(
                TryIntoBoundedU16Error::Truncated(self.outputs.len()),
            )));
        }
        Ok(self)
    }

    /// Set outputs to the builder
    pub fn with_outputs(mut self, outputs: Vec<Output>) -> Result<Self> {
        self.outputs.extend(outputs);
        if !OUTPUT_COUNT_RANGE.contains(&(self.outputs.len() as u16)) {
            return Err(crate::Error::BlockError(iota_types::block::Error::InvalidOutputCount(
                TryIntoBoundedU16Error::Truncated(self.outputs.len()),
            )));
        }
        Ok(self)
    }

    /// Set a transfer to the builder, address needs to be hex encoded
    pub async fn with_output_hex(mut self, address: &str, amount: u64) -> Result<ClientBlockBuilder<'a>> {
        let output = BasicOutputBuilder::new_with_amount(amount)?
            .add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(
                address.parse::<Ed25519Address>()?.into(),
            )))
            .finish_output(self.client.get_token_supply().await?)?;
        self.outputs.push(output);
        if !OUTPUT_COUNT_RANGE.contains(&(self.outputs.len() as u16)) {
            return Err(crate::Error::BlockError(iota_types::block::Error::InvalidOutputCount(
                TryIntoBoundedU16Error::Truncated(self.outputs.len()),
            )));
        }
        Ok(self)
    }

    /// Set a custom remainder address
    pub fn with_custom_remainder_address(mut self, address: &str) -> Result<Self> {
        let address = Address::try_from_bech32(address)?.1;
        self.custom_remainder_address.replace(address);
        Ok(self)
    }

    /// Set tagged_data to the builder
    pub fn with_tag(mut self, tag: Vec<u8>) -> Self {
        self.tag.replace(tag);
        self
    }

    /// Set data to the builder
    pub fn with_data(mut self, data: Vec<u8>) -> Self {
        self.data.replace(data);
        self
    }

    /// Set 1-8 custom parent block ids
    pub fn with_parents(mut self, parent_ids: Vec<BlockId>) -> Result<Self> {
        self.parents.replace(Parents::new(parent_ids)?);
        Ok(self)
    }

    /// Set multiple options from client block builder options type
    /// Useful for bindings
    pub async fn set_options(mut self, options: ClientBlockBuilderOptions) -> Result<ClientBlockBuilder<'a>> {
        if let Some(coin_type) = options.coin_type {
            self = self.with_coin_type(coin_type);
        }

        if let Some(account_index) = options.account_index {
            self = self.with_account_index(account_index);
        }

        if let Some(initial_address_index) = options.initial_address_index {
            self = self.with_initial_address_index(initial_address_index);
        }

        if let Some(inputs) = options.inputs {
            for input in inputs {
                self = self.with_input(UtxoInput::try_from(&input)?)?;
            }
        }

        if let Some(input_range) = options.input_range {
            self = self.with_input_range(input_range);
        }

        if let Some(output) = options.output {
            self = self
                .with_output(
                    &output.address,
                    output
                        .amount
                        .parse::<u64>()
                        .map_err(|_| Error::InvalidAmount(output.amount))?,
                )
                .await?;
        }

        if let Some(output_hex) = options.output_hex {
            self = self
                .with_output_hex(
                    &output_hex.address,
                    output_hex
                        .amount
                        .parse::<u64>()
                        .map_err(|_| Error::InvalidAmount(output_hex.amount))?,
                )
                .await?;
        }

        if let Some(outputs) = options.outputs {
            let token_supply = self.client.get_token_supply().await?;

            self = self.with_outputs(
                outputs
                    .iter()
                    .map(|o| Ok(Output::try_from_dto(o, token_supply)?))
                    .collect::<Result<Vec<Output>>>()?,
            )?;
        }

        if let Some(custom_remainder_address) = options.custom_remainder_address {
            self = self.with_custom_remainder_address(&custom_remainder_address)?;
        }

        if let Some(tag) = options.tag {
            self = self.with_tag(prefix_hex::decode(&tag)?);
        }

        if let Some(data) = options.data {
            self = self.with_data(prefix_hex::decode(&data)?);
        }

        if let Some(parents) = options.parents {
            self = self.with_parents(parents)?;
        }
        if let Some(allow_burning) = options.allow_burning {
            self = self.with_burning_allowed(allow_burning);
        }

        Ok(self)
    }
More examples
Hide additional examples
src/client/high_level.rs (line 208)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
    pub async fn find_inputs(&self, addresses: Vec<String>, amount: u64) -> Result<Vec<UtxoInput>> {
        // Get outputs from node and select inputs
        let mut available_outputs = Vec::new();

        for address in addresses {
            let basic_output_ids = self
                .basic_output_ids(vec![
                    QueryParameter::Address(address.to_string()),
                    QueryParameter::HasExpiration(false),
                    QueryParameter::HasTimelock(false),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?;

            available_outputs.extend(self.get_outputs(basic_output_ids).await?);
        }

        let mut basic_outputs = Vec::new();
        let current_time = self.get_time_checked().await?;
        let token_supply = self.get_token_supply().await?;

        for output_resp in available_outputs {
            let (amount, _) = ClientBlockBuilder::get_output_amount_and_address(
                &Output::try_from_dto(&output_resp.output, token_supply)?,
                None,
                current_time,
            )?;
            basic_outputs.push((
                UtxoInput::new(
                    TransactionId::from_str(&output_resp.metadata.transaction_id)?,
                    output_resp.metadata.output_index,
                )?,
                amount,
            ));
        }
        basic_outputs.sort_by(|l, r| r.1.cmp(&l.1));

        let mut total_already_spent = 0;
        let mut selected_inputs = Vec::new();
        for (_offset, output_wrapper) in basic_outputs
            .into_iter()
            // Max inputs is 128
            .take(INPUT_COUNT_MAX.into())
            .enumerate()
        {
            // Break if we have enough funds and don't create dust for the remainder
            if total_already_spent == amount || total_already_spent >= amount {
                break;
            }
            selected_inputs.push(output_wrapper.0.clone());
            total_already_spent += output_wrapper.1;
        }

        if total_already_spent < amount {
            return Err(crate::Error::NotEnoughBalance {
                found: total_already_spent,
                required: amount,
            });
        }

        Ok(selected_inputs)
    }
src/api/block_builder/transaction.rs (line 39)
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
    pub async fn prepare_transaction(&self) -> Result<PreparedTransactionData> {
        log::debug!("[prepare_transaction]");
        let rent_structure = self.client.get_rent_structure().await?;
        let token_supply = self.client.get_token_supply().await?;

        let mut governance_transition: Option<HashSet<AliasId>> = None;
        for output in &self.outputs {
            // Check if the outputs have enough amount to cover the storage deposit
            output.verify_storage_deposit(rent_structure.clone(), token_supply)?;
            if let Output::Alias(x) = output {
                if x.state_index() > 0 {
                    // Check if the transaction is a governance_transition, by checking if the new index is the same as
                    // the previous index
                    let output_id = self.client.alias_output_id(*x.alias_id()).await?;
                    let output_response = self.client.get_output(&output_id).await?;
                    if let OutputDto::Alias(output) = output_response.output {
                        // A governance transition is identified by an unchanged State Index in next state.
                        if x.state_index() == output.state_index {
                            let mut transitions = HashSet::new();
                            transitions.insert(AliasId::try_from(&output.alias_id)?);
                            governance_transition.replace(transitions);
                        }
                    }
                }
            }
        }

        // Input selection
        let selected_transaction_data = if self.inputs.is_some() {
            self.get_custom_inputs(governance_transition, &rent_structure, self.allow_burning)
                .await?
        } else {
            self.get_inputs(&rent_structure).await?
        };

        // Build transaction payload
        let inputs_commitment = InputsCommitment::new(selected_transaction_data.inputs.iter().map(|i| &i.output));

        let mut essence = RegularTransactionEssence::builder(self.client.get_network_id().await?, inputs_commitment);
        let inputs = selected_transaction_data
            .inputs
            .iter()
            .map(|i| {
                Ok(Input::Utxo(UtxoInput::new(
                    *i.output_metadata.transaction_id(),
                    i.output_metadata.output_index(),
                )?))
            })
            .collect::<Result<Vec<Input>>>()?;
        essence = essence.with_inputs(inputs);

        essence = essence.with_outputs(selected_transaction_data.outputs);

        // Add tagged data payload if tag set
        if let Some(index) = self.tag.clone() {
            let tagged_data_payload = TaggedDataPayload::new(index.to_vec(), self.data.clone().unwrap_or_default())?;
            essence = essence.with_payload(Payload::from(tagged_data_payload));
        }

        let regular_essence = essence.finish(&self.client.get_protocol_parameters().await?)?;

        validate_regular_transaction_essence_length(&regular_essence)?;

        let essence = TransactionEssence::Regular(regular_essence);

        Ok(PreparedTransactionData {
            essence,
            inputs_data: selected_transaction_data.inputs,
            remainder: selected_transaction_data.remainder,
        })
    }
src/api/block_builder/input_selection/manual.rs (line 40)
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
    pub(crate) async fn get_custom_inputs(
        &self,
        governance_transition: Option<HashSet<AliasId>>,
        rent_structure: &RentStructure,
        allow_burning: bool,
    ) -> Result<SelectedTransactionData> {
        log::debug!("[get_custom_inputs]");

        let mut inputs_data = Vec::new();
        let current_time = self.client.get_time_checked().await?;
        let token_supply = self.client.get_token_supply().await?;

        if let Some(inputs) = &self.inputs {
            for input in inputs {
                let output_response = self.client.get_output(input.output_id()).await?;
                let output = Output::try_from_dto(&output_response.output, token_supply)?;

                if !output_response.metadata.is_spent {
                    let (_output_amount, output_address) = ClientBlockBuilder::get_output_amount_and_address(
                        &output,
                        governance_transition.clone(),
                        current_time,
                    )?;

                    let bech32_hrp = self.client.get_bech32_hrp().await?;
                    let address_index_internal = match self.secret_manager {
                        Some(secret_manager) => {
                            match output_address {
                                Address::Ed25519(_) => Some(
                                    search_address(
                                        secret_manager,
                                        &bech32_hrp,
                                        self.coin_type,
                                        self.account_index,
                                        self.input_range.clone(),
                                        &output_address,
                                    )
                                    .await?,
                                ),
                                // Alias and NFT addresses can't be generated from a private key.
                                _ => None,
                            }
                        }
                        // Assuming default for offline signing.
                        None => Some((0, false)),
                    };

                    inputs_data.push(InputSigningData {
                        output,
                        output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                        chain: address_index_internal.map(|(address_index, internal)| {
                            Chain::from_u32_hardened(vec![
                                HD_WALLET_TYPE,
                                self.coin_type,
                                self.account_index,
                                internal as u32,
                                address_index,
                            ])
                        }),
                        bech32_address: output_address.to_bech32(&bech32_hrp),
                    });
                }
            }
        }

        let selected_transaction_data = try_select_inputs(
            inputs_data,
            Vec::new(),
            self.outputs.clone(),
            self.custom_remainder_address,
            rent_structure,
            allow_burning,
            current_time,
            token_supply,
        )?;

        Ok(selected_transaction_data)
    }
src/api/block_builder/input_selection/utxo_chains/mod.rs (line 302)
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
pub(crate) async fn get_alias_and_nft_outputs_recursively(
    client: &Client,
    utxo_chains: &mut Vec<(Address, OutputWithMetadataResponse)>,
) -> Result<()> {
    log::debug!("[get_alias_and_nft_outputs_recursively]");
    let current_time = client.get_time_checked().await?;
    let token_supply = client.get_token_supply().await?;

    let mut processed_alias_nft_addresses = std::collections::HashSet::new();

    // Add addresses for alias and nft outputs we already have
    for (_unlock_address, output_response) in utxo_chains.iter() {
        let output_id = OutputId::new(
            TransactionId::from_str(&output_response.metadata.transaction_id)?,
            output_response.metadata.output_index,
        )?;

        match Output::try_from_dto(&output_response.output, token_supply)? {
            Output::Alias(alias_output) => {
                processed_alias_nft_addresses.insert(Address::Alias(alias_output.alias_address(&output_id)));
            }
            Output::Nft(nft_output) => {
                processed_alias_nft_addresses.insert(Address::Nft(nft_output.nft_address(&output_id)));
            }
            _ => {}
        }
    }

    let mut processed_utxo_chains = Vec::new();

    // Make the outputs response optional, because we don't know it yet for new required outputs
    let mut utxo_chain_optional_response: Vec<(Address, Option<OutputWithMetadataResponse>)> =
        utxo_chains.iter_mut().map(|(a, o)| (*a, Some(o.clone()))).collect();

    // Get alias or nft addresses when needed or just add the input again
    while let Some((unlock_address, output_response)) = utxo_chain_optional_response.pop() {
        // Don't request outputs for addresses where we already have the output
        if processed_alias_nft_addresses.insert(unlock_address) {
            match unlock_address {
                Address::Alias(address) => {
                    let output_id = client.alias_output_id(*address.alias_id()).await?;
                    let output_response = client.get_output(&output_id).await?;
                    if let OutputDto::Alias(alias_output_dto) = &output_response.output {
                        let alias_output = AliasOutput::try_from_dto(alias_output_dto, token_supply)?;
                        // State transition if we add them to inputs
                        let alias_unlock_address = alias_output.state_controller_address();
                        // Add address to unprocessed_alias_nft_addresses so we get the required output there
                        // also
                        if alias_unlock_address.is_alias() || alias_unlock_address.is_nft() {
                            utxo_chain_optional_response.push((*alias_unlock_address, None));
                        }
                        processed_utxo_chains.push((*alias_unlock_address, output_response));
                    }
                }
                Address::Nft(address) => {
                    let output_id = client.nft_output_id(*address.nft_id()).await?;
                    let output_response = client.get_output(&output_id).await?;
                    if let OutputDto::Nft(nft_output) = &output_response.output {
                        let nft_output = NftOutput::try_from_dto(nft_output, token_supply)?;
                        let unlock_address = nft_output
                            .unlock_conditions()
                            .locked_address(nft_output.address(), current_time);
                        // Add address to unprocessed_alias_nft_addresses so we get the required output there also
                        if unlock_address.is_alias() || unlock_address.is_nft() {
                            utxo_chain_optional_response.push((*unlock_address, None));
                        }
                        processed_utxo_chains.push((*unlock_address, output_response));
                    }
                }
                _ => {}
            }
        }

        // Add if the output_response is available
        if let Some(output_response) = output_response {
            processed_utxo_chains.push((unlock_address, output_response));
        }
    }

    *utxo_chains = processed_utxo_chains;

    Ok(())
}
src/api/consolidation.rs (line 29)
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
    pub async fn consolidate_funds(
        &self,
        secret_manager: &SecretManager,
        address_builder_options: GetAddressesBuilderOptions,
    ) -> Result<String> {
        let token_supply = self.get_token_supply().await?;
        let mut last_transfer_index = address_builder_options.range.as_ref().unwrap_or(&(0..1)).start;
        // use the start index as offset
        let offset = last_transfer_index;

        let addresses = self
            .get_addresses(secret_manager)
            .set_options(address_builder_options)?
            .finish()
            .await?;

        let consolidation_address = addresses[0].clone();

        'consolidation: loop {
            let mut block_ids = Vec::new();
            // Iterate over addresses reversed so the funds end up on the first address in the range
            for (index, address) in addresses.iter().enumerate().rev() {
                let index = index as u32;
                // add the offset so the index matches the address index also for higher start indexes
                let index = index + offset;

                // Get output ids of outputs that can be controlled by this address without further unlock constraints
                let basic_output_ids = self
                    .basic_output_ids(vec![
                        QueryParameter::Address(address.to_string()),
                        QueryParameter::HasExpiration(false),
                        QueryParameter::HasTimelock(false),
                        QueryParameter::HasStorageDepositReturn(false),
                    ])
                    .await?;

                let basic_outputs_responses = self.get_outputs(basic_output_ids).await?;

                if !basic_outputs_responses.is_empty() {
                    // If we reach the same index again
                    if last_transfer_index == index {
                        if basic_outputs_responses.len() < 2 {
                            break 'consolidation;
                        }
                    } else {
                        last_transfer_index = index;
                    }
                }

                let outputs_chunks = basic_outputs_responses.chunks(INPUT_COUNT_MAX.into());

                for chunk in outputs_chunks {
                    let mut block_builder = self.block().with_secret_manager(secret_manager);
                    let mut total_amount = 0;
                    let mut total_native_tokens = NativeTokensBuilder::new();

                    for output_response in chunk {
                        block_builder = block_builder.with_input(UtxoInput::from(OutputId::new(
                            TransactionId::from_str(&output_response.metadata.transaction_id)?,
                            output_response.metadata.output_index,
                        )?))?;

                        let output = Output::try_from_dto(&output_response.output, token_supply)?;

                        if let Some(native_tokens) = output.native_tokens() {
                            total_native_tokens.add_native_tokens(native_tokens.clone())?;
                        }
                        total_amount += output.amount();
                    }

                    let consolidation_output = BasicOutputBuilder::new_with_amount(total_amount)?
                        .add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(
                            Address::try_from_bech32(&consolidation_address)?.1,
                        )))
                        .with_native_tokens(total_native_tokens.finish()?)
                        .finish_output(token_supply)?;

                    let block = block_builder
                        .with_input_range(index..index + 1)
                        .with_outputs(vec![consolidation_output])?
                        .with_initial_address_index(0)
                        .finish()
                        .await?;
                    block_ids.push(block.id());
                }
            }

            if block_ids.is_empty() {
                break 'consolidation;
            }
            // Wait for txs to get confirmed so we don't create conflicting txs
            for block_id in block_ids {
                let _ = self.retry_until_included(&block_id, None, None).await?;
            }
        }
        Ok(consolidation_address)
    }

returns the tips interval

Examples found in repository?
src/api/block_builder/pow.rs (line 53)
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
    async fn finish_multi_threaded_pow(&self, parents: Option<Parents>, payload: Option<Payload>) -> Result<Block> {
        let pow_worker_count = self.pow_worker_count;
        let min_pow_score = self.get_min_pow_score().await?;
        let tips_interval = self.get_tips_interval();

        loop {
            let cancel = MinerCancel::new();
            let cancel_2 = cancel.clone();
            let payload_ = payload.clone();
            let parents = match &parents {
                Some(parents) => parents.clone(),
                None => Parents::new(self.get_tips().await?)?,
            };
            let time_thread = std::thread::spawn(move || Ok(pow_timeout(tips_interval, cancel)));
            let pow_thread = std::thread::spawn(move || {
                let mut client_miner = MinerBuilder::new().with_cancel(cancel_2);
                if let Some(worker_count) = pow_worker_count {
                    client_miner = client_miner.with_num_workers(worker_count);
                }
                do_pow(client_miner.finish(), min_pow_score, payload_, parents)
                    .map(|block| (block.nonce(), Some(block)))
            });

            let threads = vec![pow_thread, time_thread];

            for t in threads {
                match t.join().expect("failed to join threads.") {
                    Ok(res) => {
                        if res.0 != 0 {
                            if let Some(block) = res.1 {
                                return Ok(block);
                            }
                        }
                    }
                    Err(err) => {
                        return Err(err);
                    }
                }
            }
        }
    }

returns if local pow should be used or not

Examples found in repository?
src/client/high_level.rs (line 300)
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
    pub async fn reattach_unchecked(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Get the Block object by the BlockID.
        let block = self.get_block(block_id).await?;
        let reattach_block = self.finish_block_builder(None, block.payload().cloned()).await?;

        // Post the modified
        let block_id = self.post_block_raw(&reattach_block).await?;
        // Get block if we use remote Pow, because the node will change parents and nonce
        let block = if self.get_local_pow() {
            reattach_block
        } else {
            self.get_block(&block_id).await?
        };
        Ok((block_id, block))
    }

    /// Promotes a block. The method should validate if a promotion is necessary through get_block. If not, the
    /// method should error out and should not allow unnecessary promotions.
    pub async fn promote(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        let metadata = self.get_block_metadata(block_id).await?;
        if metadata.should_promote.unwrap_or(false) {
            self.promote_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

    /// Promote a block without checking if it should be promoted
    pub async fn promote_unchecked(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Create a new block (zero value block) for which one tip would be the actual block.
        let mut tips = self.get_tips().await?;
        if let Some(tip) = tips.first_mut() {
            *tip = *block_id;
        }

        let promote_block = self.finish_block_builder(Some(Parents::new(tips)?), None).await?;

        let block_id = self.post_block_raw(&promote_block).await?;
        // Get block if we use remote Pow, because the node will change parents and nonce.
        let block = if self.get_local_pow() {
            promote_block
        } else {
            self.get_block(&block_id).await?
        };
        Ok((block_id, block))
    }
More examples
Hide additional examples
src/api/block_builder/pow.rs (line 18)
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    pub async fn finish_block_builder(&self, parents: Option<Parents>, payload: Option<Payload>) -> Result<Block> {
        if self.get_local_pow() {
            self.finish_pow(parents, payload).await
        } else {
            // Finish block without doing PoW.
            let parents = match parents {
                Some(parents) => parents,
                None => Parents::new(self.get_tips().await?)?,
            };
            let mut block_builder = BlockBuilder::new(parents);

            if let Some(p) = payload {
                block_builder = block_builder.with_payload(p);
            }

            Ok(block_builder.finish()?)
        }
    }
src/api/block_builder/mod.rs (line 405)
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
    pub async fn finish_block(self, payload: Option<Payload>) -> Result<Block> {
        // Do not replace parents with the latest tips if they are set explicitly,
        // necessary for block promotion.
        let final_block = self.client.finish_block_builder(self.parents, payload).await?;

        let block_id = self.client.post_block_raw(&final_block).await?;
        // Get block if we use remote PoW, because the node will change parents and nonce
        if self.client.get_local_pow() {
            Ok(final_block)
        } else {
            // Request block multiple times because the node maybe didn't process it completely in this time
            // or a node balancer could be used which forwards the request to different node than we published
            for time in 1..3 {
                if let Ok(block) = self.client.get_block(&block_id).await {
                    return Ok(block);
                }
                #[cfg(not(target_family = "wasm"))]
                tokio::time::sleep(std::time::Duration::from_millis(time * 50)).await;
                #[cfg(target_family = "wasm")]
                gloo_timers::future::TimeoutFuture::new((time * 50).try_into().unwrap()).await;
            }
            self.client.get_block(&block_id).await
        }
    }
src/node_api/core/routes.rs (line 147)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
    pub async fn post_block(&self, block: &Block) -> Result<BlockId> {
        let path = "api/core/v2/blocks";
        let local_pow = self.get_local_pow();
        let timeout = if local_pow {
            self.get_timeout()
        } else {
            self.get_remote_pow_timeout()
        };
        let block_dto = BlockDto::from(block);

        // fallback to local PoW if remote PoW fails
        let resp = match self
            .node_manager
            .post_request_json::<SubmitBlockResponse>(path, timeout, serde_json::to_value(block_dto)?, local_pow)
            .await
        {
            Ok(res) => res,
            Err(e) => {
                if let Error::NodeError(e) = e {
                    let fallback_to_local_pow = self.get_fallback_to_local_pow();
                    // hornet and bee return different error blocks
                    if (e == *"No available nodes with remote Pow"
                        || e.contains("proof of work is not enabled")
                        || e.contains("`Pow` not enabled"))
                        && fallback_to_local_pow
                    {
                        // Without this we get:within `impl Future<Output = [async output]>`, the trait `Send` is not
                        // implemented for `std::sync::RwLockWriteGuard<'_, NetworkInfo>`
                        {
                            let mut client_network_info =
                                self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                            // switch to local PoW
                            client_network_info.local_pow = true;
                        }
                        let block_res = self.finish_block_builder(None, block.payload().cloned()).await;
                        let block_with_local_pow = match block_res {
                            Ok(block) => {
                                // reset local PoW state
                                let mut client_network_info =
                                    self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                                client_network_info.local_pow = false;
                                block
                            }
                            Err(e) => {
                                // reset local PoW state
                                let mut client_network_info =
                                    self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                                client_network_info.local_pow = false;
                                return Err(e);
                            }
                        };
                        let block_dto = BlockDto::from(&block_with_local_pow);

                        self.node_manager
                            .post_request_json(path, timeout, serde_json::to_value(block_dto)?, true)
                            .await?
                    } else {
                        return Err(Error::NodeError(e));
                    }
                } else {
                    return Err(e);
                }
            }
        };

        Ok(BlockId::from_str(&resp.block_id)?)
    }

    /// Returns the BlockId of the submitted block.
    /// POST /api/core/v2/blocks
    pub async fn post_block_raw(&self, block: &Block) -> Result<BlockId> {
        let path = "api/core/v2/blocks";
        let local_pow = self.get_local_pow();
        let timeout = if local_pow {
            self.get_timeout()
        } else {
            self.get_remote_pow_timeout()
        };

        // fallback to local Pow if remote Pow fails
        let resp = match self
            .node_manager
            .post_request_bytes::<SubmitBlockResponse>(path, timeout, &block.pack_to_vec(), local_pow)
            .await
        {
            Ok(res) => res,
            Err(e) => {
                if let Error::NodeError(e) = e {
                    let fallback_to_local_pow = self.get_fallback_to_local_pow();
                    // hornet and bee return different error blocks
                    if (e == *"No available nodes with remote Pow"
                        || e.contains("proof of work is not enabled")
                        || e.contains("`Pow` not enabled"))
                        && fallback_to_local_pow
                    {
                        // Without this we get:within `impl Future<Output = [async output]>`, the trait `Send` is not
                        // implemented for `std::sync::RwLockWriteGuard<'_, NetworkInfo>`
                        {
                            let mut client_network_info =
                                self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                            // switch to local PoW
                            client_network_info.local_pow = true;
                        }
                        let block_res = self.finish_block_builder(None, block.payload().cloned()).await;
                        let block_with_local_pow = match block_res {
                            Ok(block) => {
                                // reset local PoW state
                                let mut client_network_info =
                                    self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                                client_network_info.local_pow = false;
                                block
                            }
                            Err(e) => {
                                // reset local PoW state
                                let mut client_network_info =
                                    self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                                client_network_info.local_pow = false;
                                return Err(e);
                            }
                        };
                        self.node_manager
                            .post_request_bytes(path, timeout, &block_with_local_pow.pack_to_vec(), true)
                            .await?
                    } else {
                        return Err(Error::NodeError(e));
                    }
                } else {
                    return Err(e);
                }
            }
        };

        Ok(BlockId::from_str(&resp.block_id)?)
    }

returns the fallback_to_local_pow

Examples found in repository?
src/node_api/core/routes.rs (line 164)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
    pub async fn post_block(&self, block: &Block) -> Result<BlockId> {
        let path = "api/core/v2/blocks";
        let local_pow = self.get_local_pow();
        let timeout = if local_pow {
            self.get_timeout()
        } else {
            self.get_remote_pow_timeout()
        };
        let block_dto = BlockDto::from(block);

        // fallback to local PoW if remote PoW fails
        let resp = match self
            .node_manager
            .post_request_json::<SubmitBlockResponse>(path, timeout, serde_json::to_value(block_dto)?, local_pow)
            .await
        {
            Ok(res) => res,
            Err(e) => {
                if let Error::NodeError(e) = e {
                    let fallback_to_local_pow = self.get_fallback_to_local_pow();
                    // hornet and bee return different error blocks
                    if (e == *"No available nodes with remote Pow"
                        || e.contains("proof of work is not enabled")
                        || e.contains("`Pow` not enabled"))
                        && fallback_to_local_pow
                    {
                        // Without this we get:within `impl Future<Output = [async output]>`, the trait `Send` is not
                        // implemented for `std::sync::RwLockWriteGuard<'_, NetworkInfo>`
                        {
                            let mut client_network_info =
                                self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                            // switch to local PoW
                            client_network_info.local_pow = true;
                        }
                        let block_res = self.finish_block_builder(None, block.payload().cloned()).await;
                        let block_with_local_pow = match block_res {
                            Ok(block) => {
                                // reset local PoW state
                                let mut client_network_info =
                                    self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                                client_network_info.local_pow = false;
                                block
                            }
                            Err(e) => {
                                // reset local PoW state
                                let mut client_network_info =
                                    self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                                client_network_info.local_pow = false;
                                return Err(e);
                            }
                        };
                        let block_dto = BlockDto::from(&block_with_local_pow);

                        self.node_manager
                            .post_request_json(path, timeout, serde_json::to_value(block_dto)?, true)
                            .await?
                    } else {
                        return Err(Error::NodeError(e));
                    }
                } else {
                    return Err(e);
                }
            }
        };

        Ok(BlockId::from_str(&resp.block_id)?)
    }

    /// Returns the BlockId of the submitted block.
    /// POST /api/core/v2/blocks
    pub async fn post_block_raw(&self, block: &Block) -> Result<BlockId> {
        let path = "api/core/v2/blocks";
        let local_pow = self.get_local_pow();
        let timeout = if local_pow {
            self.get_timeout()
        } else {
            self.get_remote_pow_timeout()
        };

        // fallback to local Pow if remote Pow fails
        let resp = match self
            .node_manager
            .post_request_bytes::<SubmitBlockResponse>(path, timeout, &block.pack_to_vec(), local_pow)
            .await
        {
            Ok(res) => res,
            Err(e) => {
                if let Error::NodeError(e) = e {
                    let fallback_to_local_pow = self.get_fallback_to_local_pow();
                    // hornet and bee return different error blocks
                    if (e == *"No available nodes with remote Pow"
                        || e.contains("proof of work is not enabled")
                        || e.contains("`Pow` not enabled"))
                        && fallback_to_local_pow
                    {
                        // Without this we get:within `impl Future<Output = [async output]>`, the trait `Send` is not
                        // implemented for `std::sync::RwLockWriteGuard<'_, NetworkInfo>`
                        {
                            let mut client_network_info =
                                self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                            // switch to local PoW
                            client_network_info.local_pow = true;
                        }
                        let block_res = self.finish_block_builder(None, block.payload().cloned()).await;
                        let block_with_local_pow = match block_res {
                            Ok(block) => {
                                // reset local PoW state
                                let mut client_network_info =
                                    self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                                client_network_info.local_pow = false;
                                block
                            }
                            Err(e) => {
                                // reset local PoW state
                                let mut client_network_info =
                                    self.network_info.write().map_err(|_| crate::Error::PoisonError)?;
                                client_network_info.local_pow = false;
                                return Err(e);
                            }
                        };
                        self.node_manager
                            .post_request_bytes(path, timeout, &block_with_local_pow.pack_to_vec(), true)
                            .await?
                    } else {
                        return Err(Error::NodeError(e));
                    }
                } else {
                    return Err(e);
                }
            }
        };

        Ok(BlockId::from_str(&resp.block_id)?)
    }

Returns the health of the node. GET /health

Returns the available API route groups of the node. GET /api/routes

Returns general information about the node. GET /api/core/v2/info

GET /api/core/v2/info endpoint

Examples found in repository?
src/node_manager/syncing.rs (line 82)
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
    pub(crate) async fn sync_nodes(
        sync: &Arc<RwLock<HashMap<Node, InfoResponse>>>,
        nodes: &HashSet<Node>,
        network_info: &Arc<RwLock<NetworkInfo>>,
        ignore_node_health: bool,
    ) -> Result<()> {
        log::debug!("sync_nodes");
        let mut healthy_nodes = HashMap::new();
        let mut network_nodes: HashMap<String, Vec<(InfoResponse, Node)>> = HashMap::new();

        for node in nodes {
            // Put the healthy node url into the network_nodes
            if let Ok(info) = Client::get_node_info(node.url.as_ref(), None).await {
                if info.status.is_healthy || ignore_node_health {
                    match network_nodes.get_mut(&info.protocol.network_name) {
                        Some(network_node_entry) => {
                            network_node_entry.push((info, node.clone()));
                        }
                        None => {
                            network_nodes.insert(info.protocol.network_name.clone(), vec![(info, node.clone())]);
                        }
                    }
                } else {
                    log::debug!("{} is not healthy: {:?}", node.url, info);
                }
            } else {
                log::error!("Couldn't get the node info from {}", node.url);
            }
        }

        // Get network_id with the most nodes
        let mut most_nodes = ("network_id", 0);
        for (network_id, node) in &network_nodes {
            if node.len() > most_nodes.1 {
                most_nodes.0 = network_id;
                most_nodes.1 = node.len();
            }
        }

        if let Some(nodes) = network_nodes.get(most_nodes.0) {
            if let Some((info, _node_url)) = nodes.first() {
                let mut network_info = network_info.write().map_err(|_| crate::Error::PoisonError)?;

                network_info.latest_milestone_timestamp = info.status.latest_milestone.timestamp;
                network_info.protocol_parameters = ProtocolParameters::try_from(info.protocol.clone())?;
            }

            for (info, node_url) in nodes {
                healthy_nodes.insert(node_url.clone(), info.clone());
            }
        }

        // Update the sync list.
        *sync.write().map_err(|_| crate::Error::PoisonError)? = healthy_nodes;

        Ok(())
    }

Returns tips that are ideal for attaching a block. GET /api/core/v2/tips

Examples found in repository?
src/api/block_builder/pow.rs (line 24)
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
    pub async fn finish_block_builder(&self, parents: Option<Parents>, payload: Option<Payload>) -> Result<Block> {
        if self.get_local_pow() {
            self.finish_pow(parents, payload).await
        } else {
            // Finish block without doing PoW.
            let parents = match parents {
                Some(parents) => parents,
                None => Parents::new(self.get_tips().await?)?,
            };
            let mut block_builder = BlockBuilder::new(parents);

            if let Some(p) = payload {
                block_builder = block_builder.with_payload(p);
            }

            Ok(block_builder.finish()?)
        }
    }

    /// Calls the appropriate PoW function depending whether the compilation is for wasm or not.
    pub async fn finish_pow(&self, parents: Option<Parents>, payload: Option<Payload>) -> Result<Block> {
        #[cfg(not(target_family = "wasm"))]
        let block = self.finish_multi_threaded_pow(parents, payload).await?;
        #[cfg(target_family = "wasm")]
        let block = self.finish_single_threaded_pow(parents, payload).await?;

        Ok(block)
    }

    /// Performs multi-threaded proof-of-work.
    ///
    /// Always fetches new tips after each tips interval elapses if no parents are provided.
    #[cfg(not(target_family = "wasm"))]
    async fn finish_multi_threaded_pow(&self, parents: Option<Parents>, payload: Option<Payload>) -> Result<Block> {
        let pow_worker_count = self.pow_worker_count;
        let min_pow_score = self.get_min_pow_score().await?;
        let tips_interval = self.get_tips_interval();

        loop {
            let cancel = MinerCancel::new();
            let cancel_2 = cancel.clone();
            let payload_ = payload.clone();
            let parents = match &parents {
                Some(parents) => parents.clone(),
                None => Parents::new(self.get_tips().await?)?,
            };
            let time_thread = std::thread::spawn(move || Ok(pow_timeout(tips_interval, cancel)));
            let pow_thread = std::thread::spawn(move || {
                let mut client_miner = MinerBuilder::new().with_cancel(cancel_2);
                if let Some(worker_count) = pow_worker_count {
                    client_miner = client_miner.with_num_workers(worker_count);
                }
                do_pow(client_miner.finish(), min_pow_score, payload_, parents)
                    .map(|block| (block.nonce(), Some(block)))
            });

            let threads = vec![pow_thread, time_thread];

            for t in threads {
                match t.join().expect("failed to join threads.") {
                    Ok(res) => {
                        if res.0 != 0 {
                            if let Some(block) = res.1 {
                                return Ok(block);
                            }
                        }
                    }
                    Err(err) => {
                        return Err(err);
                    }
                }
            }
        }
    }
More examples
Hide additional examples
src/client/high_level.rs (line 322)
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
    pub async fn promote_unchecked(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Create a new block (zero value block) for which one tip would be the actual block.
        let mut tips = self.get_tips().await?;
        if let Some(tip) = tips.first_mut() {
            *tip = *block_id;
        }

        let promote_block = self.finish_block_builder(Some(Parents::new(tips)?), None).await?;

        let block_id = self.post_block_raw(&promote_block).await?;
        // Get block if we use remote Pow, because the node will change parents and nonce.
        let block = if self.get_local_pow() {
            promote_block
        } else {
            self.get_block(&block_id).await?
        };
        Ok((block_id, block))
    }

Returns the BlockId of the submitted block. POST JSON to /api/core/v2/blocks

Returns the BlockId of the submitted block. POST /api/core/v2/blocks

Examples found in repository?
src/client/high_level.rs (line 298)
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
    pub async fn reattach_unchecked(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Get the Block object by the BlockID.
        let block = self.get_block(block_id).await?;
        let reattach_block = self.finish_block_builder(None, block.payload().cloned()).await?;

        // Post the modified
        let block_id = self.post_block_raw(&reattach_block).await?;
        // Get block if we use remote Pow, because the node will change parents and nonce
        let block = if self.get_local_pow() {
            reattach_block
        } else {
            self.get_block(&block_id).await?
        };
        Ok((block_id, block))
    }

    /// Promotes a block. The method should validate if a promotion is necessary through get_block. If not, the
    /// method should error out and should not allow unnecessary promotions.
    pub async fn promote(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        let metadata = self.get_block_metadata(block_id).await?;
        if metadata.should_promote.unwrap_or(false) {
            self.promote_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

    /// Promote a block without checking if it should be promoted
    pub async fn promote_unchecked(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Create a new block (zero value block) for which one tip would be the actual block.
        let mut tips = self.get_tips().await?;
        if let Some(tip) = tips.first_mut() {
            *tip = *block_id;
        }

        let promote_block = self.finish_block_builder(Some(Parents::new(tips)?), None).await?;

        let block_id = self.post_block_raw(&promote_block).await?;
        // Get block if we use remote Pow, because the node will change parents and nonce.
        let block = if self.get_local_pow() {
            promote_block
        } else {
            self.get_block(&block_id).await?
        };
        Ok((block_id, block))
    }
More examples
Hide additional examples
src/api/block_builder/mod.rs (line 403)
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
    pub async fn finish_block(self, payload: Option<Payload>) -> Result<Block> {
        // Do not replace parents with the latest tips if they are set explicitly,
        // necessary for block promotion.
        let final_block = self.client.finish_block_builder(self.parents, payload).await?;

        let block_id = self.client.post_block_raw(&final_block).await?;
        // Get block if we use remote PoW, because the node will change parents and nonce
        if self.client.get_local_pow() {
            Ok(final_block)
        } else {
            // Request block multiple times because the node maybe didn't process it completely in this time
            // or a node balancer could be used which forwards the request to different node than we published
            for time in 1..3 {
                if let Ok(block) = self.client.get_block(&block_id).await {
                    return Ok(block);
                }
                #[cfg(not(target_family = "wasm"))]
                tokio::time::sleep(std::time::Duration::from_millis(time * 50)).await;
                #[cfg(target_family = "wasm")]
                gloo_timers::future::TimeoutFuture::new((time * 50).try_into().unwrap()).await;
            }
            self.client.get_block(&block_id).await
        }
    }

Finds a block by its BlockId. This method returns the given block object. GET /api/core/v2/blocks/{BlockId}

Examples found in repository?
src/client/high_level.rs (line 85)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
    pub async fn find_blocks(&self, block_ids: &[BlockId]) -> Result<Vec<Block>> {
        let mut blocks = Vec::new();

        // Use a `HashSet` to prevent duplicate block_ids.
        let mut block_ids_to_query = HashSet::<BlockId>::new();

        // Collect the `BlockId` in the HashSet.
        for block_id in block_ids {
            block_ids_to_query.insert(*block_id);
        }

        // Use `get_block()` API to get the `Block`.
        for block_id in block_ids_to_query {
            let block = self.get_block(&block_id).await?;
            blocks.push(block);
        }
        Ok(blocks)
    }

    /// Retries (promotes or reattaches) a block for provided block id. Block should only be
    /// retried only if they are valid and haven't been confirmed for a while.
    pub async fn retry(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Get the metadata to check if it needs to promote or reattach
        let block_metadata = self.get_block_metadata(block_id).await?;
        if block_metadata.should_promote.unwrap_or(false) {
            self.promote_unchecked(block_id).await
        } else if block_metadata.should_reattach.unwrap_or(false) {
            self.reattach_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

    /// Retries (promotes or reattaches) a block for provided block id until it's included (referenced by a
    /// milestone). Default interval is 5 seconds and max attempts is 40. Returns the included block at first position
    /// and additional reattached blocks
    pub async fn retry_until_included(
        &self,
        block_id: &BlockId,
        interval: Option<u64>,
        max_attempts: Option<u64>,
    ) -> Result<Vec<(BlockId, Block)>> {
        log::debug!("[retry_until_included]");
        // Attachments of the Block to check inclusion state
        let mut block_ids = vec![*block_id];
        // Reattached Blocks that get returned
        let mut blocks_with_id = Vec::new();
        for _ in 0..max_attempts.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_MAX_AMOUNT) {
            #[cfg(target_family = "wasm")]
            gloo_timers::future::TimeoutFuture::new(
                (interval.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_INTERVAL) * 1000)
                    .try_into()
                    .unwrap(),
            )
            .await;

            #[cfg(not(target_family = "wasm"))]
            tokio::time::sleep(std::time::Duration::from_secs(
                interval.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_INTERVAL),
            ))
            .await;

            // Check inclusion state for each attachment
            let block_ids_len = block_ids.len();
            let mut conflicting = false;
            for (index, block_id_) in block_ids.clone().iter().enumerate() {
                let block_metadata = self.get_block_metadata(block_id_).await?;
                if let Some(inclusion_state) = block_metadata.ledger_inclusion_state {
                    match inclusion_state {
                        LedgerInclusionStateDto::Included | LedgerInclusionStateDto::NoTransaction => {
                            // if original block, request it so we can return it on first position
                            if block_id == block_id_ {
                                let mut included_and_reattached_blocks =
                                    vec![(*block_id, self.get_block(block_id).await?)];
                                included_and_reattached_blocks.extend(blocks_with_id);
                                return Ok(included_and_reattached_blocks);
                            } else {
                                // Move included block to first position
                                blocks_with_id.rotate_left(index);
                                return Ok(blocks_with_id);
                            }
                        }
                        // only set it as conflicting here and don't return, because another reattached block could
                        // have the included transaction
                        LedgerInclusionStateDto::Conflicting => conflicting = true,
                    };
                }
                // Only reattach or promote latest attachment of the block
                if index == block_ids_len - 1 {
                    if block_metadata.should_promote.unwrap_or(false) {
                        // Safe to unwrap since we iterate over it
                        self.promote_unchecked(block_ids.last().unwrap()).await?;
                    } else if block_metadata.should_reattach.unwrap_or(false) {
                        // Safe to unwrap since we iterate over it
                        let reattached = self.reattach_unchecked(block_ids.last().unwrap()).await?;
                        block_ids.push(reattached.0);
                        blocks_with_id.push(reattached);
                    }
                }
            }
            // After we checked all our reattached blocks, check if the transaction got reattached in another block
            // and confirmed
            if conflicting {
                let block = self.get_block(block_id).await?;
                if let Some(Payload::Transaction(transaction_payload)) = block.payload() {
                    let included_block = self.get_included_block(&transaction_payload.id()).await?;
                    let mut included_and_reattached_blocks = vec![(included_block.id(), included_block)];
                    included_and_reattached_blocks.extend(blocks_with_id);
                    return Ok(included_and_reattached_blocks);
                }
            }
        }
        Err(Error::TangleInclusionError(block_id.to_string()))
    }

    /// Function to find inputs from addresses for a provided amount (useful for offline signing), ignoring outputs with
    /// additional unlock conditions
    pub async fn find_inputs(&self, addresses: Vec<String>, amount: u64) -> Result<Vec<UtxoInput>> {
        // Get outputs from node and select inputs
        let mut available_outputs = Vec::new();

        for address in addresses {
            let basic_output_ids = self
                .basic_output_ids(vec![
                    QueryParameter::Address(address.to_string()),
                    QueryParameter::HasExpiration(false),
                    QueryParameter::HasTimelock(false),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?;

            available_outputs.extend(self.get_outputs(basic_output_ids).await?);
        }

        let mut basic_outputs = Vec::new();
        let current_time = self.get_time_checked().await?;
        let token_supply = self.get_token_supply().await?;

        for output_resp in available_outputs {
            let (amount, _) = ClientBlockBuilder::get_output_amount_and_address(
                &Output::try_from_dto(&output_resp.output, token_supply)?,
                None,
                current_time,
            )?;
            basic_outputs.push((
                UtxoInput::new(
                    TransactionId::from_str(&output_resp.metadata.transaction_id)?,
                    output_resp.metadata.output_index,
                )?,
                amount,
            ));
        }
        basic_outputs.sort_by(|l, r| r.1.cmp(&l.1));

        let mut total_already_spent = 0;
        let mut selected_inputs = Vec::new();
        for (_offset, output_wrapper) in basic_outputs
            .into_iter()
            // Max inputs is 128
            .take(INPUT_COUNT_MAX.into())
            .enumerate()
        {
            // Break if we have enough funds and don't create dust for the remainder
            if total_already_spent == amount || total_already_spent >= amount {
                break;
            }
            selected_inputs.push(output_wrapper.0.clone());
            total_already_spent += output_wrapper.1;
        }

        if total_already_spent < amount {
            return Err(crate::Error::NotEnoughBalance {
                found: total_already_spent,
                required: amount,
            });
        }

        Ok(selected_inputs)
    }

    /// Find all outputs based on the requests criteria. This method will try to query multiple nodes if
    /// the request amount exceeds individual node limit.
    pub async fn find_outputs(
        &self,
        output_ids: &[OutputId],
        addresses: &[String],
    ) -> Result<Vec<OutputWithMetadataResponse>> {
        let mut output_responses = self.get_outputs(output_ids.to_vec()).await?;

        // Use `get_address()` API to get the address outputs first,
        // then collect the `UtxoInput` in the HashSet.
        for address in addresses {
            // Get output ids of outputs that can be controlled by this address without further unlock constraints
            let basic_output_ids = self
                .basic_output_ids(vec![
                    QueryParameter::Address(address.to_string()),
                    QueryParameter::HasExpiration(false),
                    QueryParameter::HasTimelock(false),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?;

            output_responses.extend(self.get_outputs(basic_output_ids).await?);
        }

        Ok(output_responses.clone())
    }

    /// Reattaches blocks for provided block id. Blocks can be reattached only if they are valid and haven't been
    /// confirmed for a while.
    pub async fn reattach(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        let metadata = self.get_block_metadata(block_id).await?;
        if metadata.should_reattach.unwrap_or(false) {
            self.reattach_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

    /// Reattach a block without checking if it should be reattached
    pub async fn reattach_unchecked(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Get the Block object by the BlockID.
        let block = self.get_block(block_id).await?;
        let reattach_block = self.finish_block_builder(None, block.payload().cloned()).await?;

        // Post the modified
        let block_id = self.post_block_raw(&reattach_block).await?;
        // Get block if we use remote Pow, because the node will change parents and nonce
        let block = if self.get_local_pow() {
            reattach_block
        } else {
            self.get_block(&block_id).await?
        };
        Ok((block_id, block))
    }

    /// Promotes a block. The method should validate if a promotion is necessary through get_block. If not, the
    /// method should error out and should not allow unnecessary promotions.
    pub async fn promote(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        let metadata = self.get_block_metadata(block_id).await?;
        if metadata.should_promote.unwrap_or(false) {
            self.promote_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

    /// Promote a block without checking if it should be promoted
    pub async fn promote_unchecked(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Create a new block (zero value block) for which one tip would be the actual block.
        let mut tips = self.get_tips().await?;
        if let Some(tip) = tips.first_mut() {
            *tip = *block_id;
        }

        let promote_block = self.finish_block_builder(Some(Parents::new(tips)?), None).await?;

        let block_id = self.post_block_raw(&promote_block).await?;
        // Get block if we use remote Pow, because the node will change parents and nonce.
        let block = if self.get_local_pow() {
            promote_block
        } else {
            self.get_block(&block_id).await?
        };
        Ok((block_id, block))
    }
More examples
Hide additional examples
src/api/block_builder/mod.rs (line 411)
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
    pub async fn finish_block(self, payload: Option<Payload>) -> Result<Block> {
        // Do not replace parents with the latest tips if they are set explicitly,
        // necessary for block promotion.
        let final_block = self.client.finish_block_builder(self.parents, payload).await?;

        let block_id = self.client.post_block_raw(&final_block).await?;
        // Get block if we use remote PoW, because the node will change parents and nonce
        if self.client.get_local_pow() {
            Ok(final_block)
        } else {
            // Request block multiple times because the node maybe didn't process it completely in this time
            // or a node balancer could be used which forwards the request to different node than we published
            for time in 1..3 {
                if let Ok(block) = self.client.get_block(&block_id).await {
                    return Ok(block);
                }
                #[cfg(not(target_family = "wasm"))]
                tokio::time::sleep(std::time::Duration::from_millis(time * 50)).await;
                #[cfg(target_family = "wasm")]
                gloo_timers::future::TimeoutFuture::new((time * 50).try_into().unwrap()).await;
            }
            self.client.get_block(&block_id).await
        }
    }

Finds a block by its BlockId. This method returns the given block raw data. GET /api/core/v2/blocks/{BlockId}

Returns the metadata of a block. GET /api/core/v2/blocks/{BlockId}/metadata

Examples found in repository?
src/client/high_level.rs (line 95)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
    pub async fn retry(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Get the metadata to check if it needs to promote or reattach
        let block_metadata = self.get_block_metadata(block_id).await?;
        if block_metadata.should_promote.unwrap_or(false) {
            self.promote_unchecked(block_id).await
        } else if block_metadata.should_reattach.unwrap_or(false) {
            self.reattach_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

    /// Retries (promotes or reattaches) a block for provided block id until it's included (referenced by a
    /// milestone). Default interval is 5 seconds and max attempts is 40. Returns the included block at first position
    /// and additional reattached blocks
    pub async fn retry_until_included(
        &self,
        block_id: &BlockId,
        interval: Option<u64>,
        max_attempts: Option<u64>,
    ) -> Result<Vec<(BlockId, Block)>> {
        log::debug!("[retry_until_included]");
        // Attachments of the Block to check inclusion state
        let mut block_ids = vec![*block_id];
        // Reattached Blocks that get returned
        let mut blocks_with_id = Vec::new();
        for _ in 0..max_attempts.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_MAX_AMOUNT) {
            #[cfg(target_family = "wasm")]
            gloo_timers::future::TimeoutFuture::new(
                (interval.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_INTERVAL) * 1000)
                    .try_into()
                    .unwrap(),
            )
            .await;

            #[cfg(not(target_family = "wasm"))]
            tokio::time::sleep(std::time::Duration::from_secs(
                interval.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_INTERVAL),
            ))
            .await;

            // Check inclusion state for each attachment
            let block_ids_len = block_ids.len();
            let mut conflicting = false;
            for (index, block_id_) in block_ids.clone().iter().enumerate() {
                let block_metadata = self.get_block_metadata(block_id_).await?;
                if let Some(inclusion_state) = block_metadata.ledger_inclusion_state {
                    match inclusion_state {
                        LedgerInclusionStateDto::Included | LedgerInclusionStateDto::NoTransaction => {
                            // if original block, request it so we can return it on first position
                            if block_id == block_id_ {
                                let mut included_and_reattached_blocks =
                                    vec![(*block_id, self.get_block(block_id).await?)];
                                included_and_reattached_blocks.extend(blocks_with_id);
                                return Ok(included_and_reattached_blocks);
                            } else {
                                // Move included block to first position
                                blocks_with_id.rotate_left(index);
                                return Ok(blocks_with_id);
                            }
                        }
                        // only set it as conflicting here and don't return, because another reattached block could
                        // have the included transaction
                        LedgerInclusionStateDto::Conflicting => conflicting = true,
                    };
                }
                // Only reattach or promote latest attachment of the block
                if index == block_ids_len - 1 {
                    if block_metadata.should_promote.unwrap_or(false) {
                        // Safe to unwrap since we iterate over it
                        self.promote_unchecked(block_ids.last().unwrap()).await?;
                    } else if block_metadata.should_reattach.unwrap_or(false) {
                        // Safe to unwrap since we iterate over it
                        let reattached = self.reattach_unchecked(block_ids.last().unwrap()).await?;
                        block_ids.push(reattached.0);
                        blocks_with_id.push(reattached);
                    }
                }
            }
            // After we checked all our reattached blocks, check if the transaction got reattached in another block
            // and confirmed
            if conflicting {
                let block = self.get_block(block_id).await?;
                if let Some(Payload::Transaction(transaction_payload)) = block.payload() {
                    let included_block = self.get_included_block(&transaction_payload.id()).await?;
                    let mut included_and_reattached_blocks = vec![(included_block.id(), included_block)];
                    included_and_reattached_blocks.extend(blocks_with_id);
                    return Ok(included_and_reattached_blocks);
                }
            }
        }
        Err(Error::TangleInclusionError(block_id.to_string()))
    }

    /// Function to find inputs from addresses for a provided amount (useful for offline signing), ignoring outputs with
    /// additional unlock conditions
    pub async fn find_inputs(&self, addresses: Vec<String>, amount: u64) -> Result<Vec<UtxoInput>> {
        // Get outputs from node and select inputs
        let mut available_outputs = Vec::new();

        for address in addresses {
            let basic_output_ids = self
                .basic_output_ids(vec![
                    QueryParameter::Address(address.to_string()),
                    QueryParameter::HasExpiration(false),
                    QueryParameter::HasTimelock(false),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?;

            available_outputs.extend(self.get_outputs(basic_output_ids).await?);
        }

        let mut basic_outputs = Vec::new();
        let current_time = self.get_time_checked().await?;
        let token_supply = self.get_token_supply().await?;

        for output_resp in available_outputs {
            let (amount, _) = ClientBlockBuilder::get_output_amount_and_address(
                &Output::try_from_dto(&output_resp.output, token_supply)?,
                None,
                current_time,
            )?;
            basic_outputs.push((
                UtxoInput::new(
                    TransactionId::from_str(&output_resp.metadata.transaction_id)?,
                    output_resp.metadata.output_index,
                )?,
                amount,
            ));
        }
        basic_outputs.sort_by(|l, r| r.1.cmp(&l.1));

        let mut total_already_spent = 0;
        let mut selected_inputs = Vec::new();
        for (_offset, output_wrapper) in basic_outputs
            .into_iter()
            // Max inputs is 128
            .take(INPUT_COUNT_MAX.into())
            .enumerate()
        {
            // Break if we have enough funds and don't create dust for the remainder
            if total_already_spent == amount || total_already_spent >= amount {
                break;
            }
            selected_inputs.push(output_wrapper.0.clone());
            total_already_spent += output_wrapper.1;
        }

        if total_already_spent < amount {
            return Err(crate::Error::NotEnoughBalance {
                found: total_already_spent,
                required: amount,
            });
        }

        Ok(selected_inputs)
    }

    /// Find all outputs based on the requests criteria. This method will try to query multiple nodes if
    /// the request amount exceeds individual node limit.
    pub async fn find_outputs(
        &self,
        output_ids: &[OutputId],
        addresses: &[String],
    ) -> Result<Vec<OutputWithMetadataResponse>> {
        let mut output_responses = self.get_outputs(output_ids.to_vec()).await?;

        // Use `get_address()` API to get the address outputs first,
        // then collect the `UtxoInput` in the HashSet.
        for address in addresses {
            // Get output ids of outputs that can be controlled by this address without further unlock constraints
            let basic_output_ids = self
                .basic_output_ids(vec![
                    QueryParameter::Address(address.to_string()),
                    QueryParameter::HasExpiration(false),
                    QueryParameter::HasTimelock(false),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?;

            output_responses.extend(self.get_outputs(basic_output_ids).await?);
        }

        Ok(output_responses.clone())
    }

    /// Reattaches blocks for provided block id. Blocks can be reattached only if they are valid and haven't been
    /// confirmed for a while.
    pub async fn reattach(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        let metadata = self.get_block_metadata(block_id).await?;
        if metadata.should_reattach.unwrap_or(false) {
            self.reattach_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

    /// Reattach a block without checking if it should be reattached
    pub async fn reattach_unchecked(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Get the Block object by the BlockID.
        let block = self.get_block(block_id).await?;
        let reattach_block = self.finish_block_builder(None, block.payload().cloned()).await?;

        // Post the modified
        let block_id = self.post_block_raw(&reattach_block).await?;
        // Get block if we use remote Pow, because the node will change parents and nonce
        let block = if self.get_local_pow() {
            reattach_block
        } else {
            self.get_block(&block_id).await?
        };
        Ok((block_id, block))
    }

    /// Promotes a block. The method should validate if a promotion is necessary through get_block. If not, the
    /// method should error out and should not allow unnecessary promotions.
    pub async fn promote(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        let metadata = self.get_block_metadata(block_id).await?;
        if metadata.should_promote.unwrap_or(false) {
            self.promote_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

Finds an output, as JSON, by its OutputId (TransactionId + output_index). GET /api/core/v2/outputs/{outputId}

Examples found in repository?
src/node_api/core/mod.rs (line 35)
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
    pub async fn get_outputs(&self, output_ids: Vec<OutputId>) -> Result<Vec<OutputWithMetadataResponse>> {
        let mut outputs = Vec::new();

        #[cfg(target_family = "wasm")]
        for output_id in output_ids {
            outputs.push(self.get_output(&output_id).await?);
        }

        #[cfg(not(target_family = "wasm"))]
        for output_ids_chunk in output_ids.chunks(MAX_PARALLEL_API_REQUESTS).map(<[OutputId]>::to_vec) {
            let mut tasks = Vec::new();
            for output_id in output_ids_chunk {
                let client_ = self.clone();

                tasks.push(async move {
                    tokio::spawn(async move {
                        let output_response = client_.get_output(&output_id).await?;
                        crate::Result::Ok(output_response)
                    })
                    .await
                });
            }
            for res in futures::future::try_join_all(tasks).await? {
                let output_response = res?;
                outputs.push(output_response);
            }
        }
        Ok(outputs)
    }

    /// Request outputs by their output ID in parallel, ignoring failed requests
    /// Useful to get data about spent outputs, that might not be pruned yet
    pub async fn try_get_outputs(&self, output_ids: Vec<OutputId>) -> Result<Vec<OutputWithMetadataResponse>> {
        let mut outputs = Vec::new();

        #[cfg(target_family = "wasm")]
        for output_id in output_ids {
            if let Ok(output_response) = self.get_output(&output_id).await {
                outputs.push(output_response);
            }
        }

        #[cfg(not(target_family = "wasm"))]
        for output_ids_chunk in output_ids.chunks(MAX_PARALLEL_API_REQUESTS).map(<[OutputId]>::to_vec) {
            let mut tasks = Vec::new();
            for output_id in output_ids_chunk {
                let client_ = self.clone();

                tasks.push(async move { tokio::spawn(async move { client_.get_output(&output_id).await.ok() }).await });
            }
            for output_response in (futures::future::try_join_all(tasks).await?).into_iter().flatten() {
                outputs.push(output_response);
            }
        }
        Ok(outputs)
    }
More examples
Hide additional examples
src/api/block_builder/transaction.rs (line 50)
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
    pub async fn prepare_transaction(&self) -> Result<PreparedTransactionData> {
        log::debug!("[prepare_transaction]");
        let rent_structure = self.client.get_rent_structure().await?;
        let token_supply = self.client.get_token_supply().await?;

        let mut governance_transition: Option<HashSet<AliasId>> = None;
        for output in &self.outputs {
            // Check if the outputs have enough amount to cover the storage deposit
            output.verify_storage_deposit(rent_structure.clone(), token_supply)?;
            if let Output::Alias(x) = output {
                if x.state_index() > 0 {
                    // Check if the transaction is a governance_transition, by checking if the new index is the same as
                    // the previous index
                    let output_id = self.client.alias_output_id(*x.alias_id()).await?;
                    let output_response = self.client.get_output(&output_id).await?;
                    if let OutputDto::Alias(output) = output_response.output {
                        // A governance transition is identified by an unchanged State Index in next state.
                        if x.state_index() == output.state_index {
                            let mut transitions = HashSet::new();
                            transitions.insert(AliasId::try_from(&output.alias_id)?);
                            governance_transition.replace(transitions);
                        }
                    }
                }
            }
        }

        // Input selection
        let selected_transaction_data = if self.inputs.is_some() {
            self.get_custom_inputs(governance_transition, &rent_structure, self.allow_burning)
                .await?
        } else {
            self.get_inputs(&rent_structure).await?
        };

        // Build transaction payload
        let inputs_commitment = InputsCommitment::new(selected_transaction_data.inputs.iter().map(|i| &i.output));

        let mut essence = RegularTransactionEssence::builder(self.client.get_network_id().await?, inputs_commitment);
        let inputs = selected_transaction_data
            .inputs
            .iter()
            .map(|i| {
                Ok(Input::Utxo(UtxoInput::new(
                    *i.output_metadata.transaction_id(),
                    i.output_metadata.output_index(),
                )?))
            })
            .collect::<Result<Vec<Input>>>()?;
        essence = essence.with_inputs(inputs);

        essence = essence.with_outputs(selected_transaction_data.outputs);

        // Add tagged data payload if tag set
        if let Some(index) = self.tag.clone() {
            let tagged_data_payload = TaggedDataPayload::new(index.to_vec(), self.data.clone().unwrap_or_default())?;
            essence = essence.with_payload(Payload::from(tagged_data_payload));
        }

        let regular_essence = essence.finish(&self.client.get_protocol_parameters().await?)?;

        validate_regular_transaction_essence_length(&regular_essence)?;

        let essence = TransactionEssence::Regular(regular_essence);

        Ok(PreparedTransactionData {
            essence,
            inputs_data: selected_transaction_data.inputs,
            remainder: selected_transaction_data.remainder,
        })
    }
src/api/block_builder/input_selection/manual.rs (line 44)
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
    pub(crate) async fn get_custom_inputs(
        &self,
        governance_transition: Option<HashSet<AliasId>>,
        rent_structure: &RentStructure,
        allow_burning: bool,
    ) -> Result<SelectedTransactionData> {
        log::debug!("[get_custom_inputs]");

        let mut inputs_data = Vec::new();
        let current_time = self.client.get_time_checked().await?;
        let token_supply = self.client.get_token_supply().await?;

        if let Some(inputs) = &self.inputs {
            for input in inputs {
                let output_response = self.client.get_output(input.output_id()).await?;
                let output = Output::try_from_dto(&output_response.output, token_supply)?;

                if !output_response.metadata.is_spent {
                    let (_output_amount, output_address) = ClientBlockBuilder::get_output_amount_and_address(
                        &output,
                        governance_transition.clone(),
                        current_time,
                    )?;

                    let bech32_hrp = self.client.get_bech32_hrp().await?;
                    let address_index_internal = match self.secret_manager {
                        Some(secret_manager) => {
                            match output_address {
                                Address::Ed25519(_) => Some(
                                    search_address(
                                        secret_manager,
                                        &bech32_hrp,
                                        self.coin_type,
                                        self.account_index,
                                        self.input_range.clone(),
                                        &output_address,
                                    )
                                    .await?,
                                ),
                                // Alias and NFT addresses can't be generated from a private key.
                                _ => None,
                            }
                        }
                        // Assuming default for offline signing.
                        None => Some((0, false)),
                    };

                    inputs_data.push(InputSigningData {
                        output,
                        output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                        chain: address_index_internal.map(|(address_index, internal)| {
                            Chain::from_u32_hardened(vec![
                                HD_WALLET_TYPE,
                                self.coin_type,
                                self.account_index,
                                internal as u32,
                                address_index,
                            ])
                        }),
                        bech32_address: output_address.to_bech32(&bech32_hrp),
                    });
                }
            }
        }

        let selected_transaction_data = try_select_inputs(
            inputs_data,
            Vec::new(),
            self.outputs.clone(),
            self.custom_remainder_address,
            rent_structure,
            allow_burning,
            current_time,
            token_supply,
        )?;

        Ok(selected_transaction_data)
    }
src/api/block_builder/input_selection/utxo_chains/mod.rs (line 337)
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
pub(crate) async fn get_alias_and_nft_outputs_recursively(
    client: &Client,
    utxo_chains: &mut Vec<(Address, OutputWithMetadataResponse)>,
) -> Result<()> {
    log::debug!("[get_alias_and_nft_outputs_recursively]");
    let current_time = client.get_time_checked().await?;
    let token_supply = client.get_token_supply().await?;

    let mut processed_alias_nft_addresses = std::collections::HashSet::new();

    // Add addresses for alias and nft outputs we already have
    for (_unlock_address, output_response) in utxo_chains.iter() {
        let output_id = OutputId::new(
            TransactionId::from_str(&output_response.metadata.transaction_id)?,
            output_response.metadata.output_index,
        )?;

        match Output::try_from_dto(&output_response.output, token_supply)? {
            Output::Alias(alias_output) => {
                processed_alias_nft_addresses.insert(Address::Alias(alias_output.alias_address(&output_id)));
            }
            Output::Nft(nft_output) => {
                processed_alias_nft_addresses.insert(Address::Nft(nft_output.nft_address(&output_id)));
            }
            _ => {}
        }
    }

    let mut processed_utxo_chains = Vec::new();

    // Make the outputs response optional, because we don't know it yet for new required outputs
    let mut utxo_chain_optional_response: Vec<(Address, Option<OutputWithMetadataResponse>)> =
        utxo_chains.iter_mut().map(|(a, o)| (*a, Some(o.clone()))).collect();

    // Get alias or nft addresses when needed or just add the input again
    while let Some((unlock_address, output_response)) = utxo_chain_optional_response.pop() {
        // Don't request outputs for addresses where we already have the output
        if processed_alias_nft_addresses.insert(unlock_address) {
            match unlock_address {
                Address::Alias(address) => {
                    let output_id = client.alias_output_id(*address.alias_id()).await?;
                    let output_response = client.get_output(&output_id).await?;
                    if let OutputDto::Alias(alias_output_dto) = &output_response.output {
                        let alias_output = AliasOutput::try_from_dto(alias_output_dto, token_supply)?;
                        // State transition if we add them to inputs
                        let alias_unlock_address = alias_output.state_controller_address();
                        // Add address to unprocessed_alias_nft_addresses so we get the required output there
                        // also
                        if alias_unlock_address.is_alias() || alias_unlock_address.is_nft() {
                            utxo_chain_optional_response.push((*alias_unlock_address, None));
                        }
                        processed_utxo_chains.push((*alias_unlock_address, output_response));
                    }
                }
                Address::Nft(address) => {
                    let output_id = client.nft_output_id(*address.nft_id()).await?;
                    let output_response = client.get_output(&output_id).await?;
                    if let OutputDto::Nft(nft_output) = &output_response.output {
                        let nft_output = NftOutput::try_from_dto(nft_output, token_supply)?;
                        let unlock_address = nft_output
                            .unlock_conditions()
                            .locked_address(nft_output.address(), current_time);
                        // Add address to unprocessed_alias_nft_addresses so we get the required output there also
                        if unlock_address.is_alias() || unlock_address.is_nft() {
                            utxo_chain_optional_response.push((*unlock_address, None));
                        }
                        processed_utxo_chains.push((*unlock_address, output_response));
                    }
                }
                _ => {}
            }
        }

        // Add if the output_response is available
        if let Some(output_response) = output_response {
            processed_utxo_chains.push((unlock_address, output_response));
        }
    }

    *utxo_chains = processed_utxo_chains;

    Ok(())
}
src/api/block_builder/input_selection/utxo_chains/automatic.rs (line 45)
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
    pub(crate) async fn get_utxo_chains_inputs(
        &self,
        outputs: impl Iterator<Item = &'a Output> + Clone,
    ) -> Result<Vec<InputSigningData>> {
        log::debug!("[get_utxo_chains_inputs]");
        let client = self.client;
        let bech32_hrp = client.get_bech32_hrp().await?;
        let current_time = self.client.get_time_checked().await?;
        let token_supply = client.get_token_supply().await?;

        let mut utxo_chains: Vec<(Address, OutputWithMetadataResponse)> = Vec::new();
        for output in outputs {
            match output {
                Output::Alias(alias_output) => {
                    // if the alias id is null then there can't be a previous output and it can also not be a
                    // governance transition
                    if !alias_output.alias_id().is_null() {
                        // Check if the transaction is a governance_transition, by checking if the new index is the same
                        // as the previous index
                        let output_id = client.alias_output_id(*alias_output.alias_id()).await?;
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Alias(alias_output_dto) = &output_response.output {
                            let alias_output = AliasOutput::try_from_dto(alias_output_dto, token_supply)?;

                            // A governance transition is identified by an unchanged State Index in next
                            // state.
                            if alias_output.state_index() == alias_output.state_index() {
                                utxo_chains.push((*alias_output.governor_address(), output_response));
                            } else {
                                utxo_chains.push((*alias_output.state_controller_address(), output_response));
                            }
                        }
                    }
                }
                Output::Nft(nft_output) => {
                    // If the id is null then this output creates it and we can't have a previous output
                    if !nft_output.nft_id().is_null() {
                        let output_id = client.nft_output_id(*nft_output.nft_id()).await?;
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Nft(nft_output) = &output_response.output {
                            let nft_output = NftOutput::try_from_dto(nft_output, token_supply)?;

                            let unlock_address = nft_output
                                .unlock_conditions()
                                .locked_address(nft_output.address(), current_time);

                            utxo_chains.push((*unlock_address, output_response));
                        }
                    }
                }
                Output::Foundry(foundry_output) => {
                    // if it's the first foundry output, then we can't have it as input
                    if let Ok(output_id) = client.foundry_output_id(foundry_output.id()).await {
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Foundry(foundry_output_dto) = &output_response.output {
                            let foundry_output = FoundryOutput::try_from_dto(foundry_output_dto, token_supply)?;
                            utxo_chains.push((Address::Alias(*foundry_output.alias_address()), output_response));
                        }
                    }
                }
                _ => {}
            }
        }

        // Get recursively owned alias or nft outputs
        get_alias_and_nft_outputs_recursively(self.client, &mut utxo_chains).await?;

        let mut utxo_chain_inputs = Vec::new();
        for (unlock_address, output_response) in utxo_chains {
            let address_index_internal = match self.secret_manager {
                Some(secret_manager) => {
                    match unlock_address {
                        Address::Ed25519(_) => Some(
                            search_address(
                                secret_manager,
                                &bech32_hrp,
                                self.coin_type,
                                self.account_index,
                                self.input_range.clone(),
                                &unlock_address,
                            )
                            .await?,
                        ),
                        // Alias and NFT addresses can't be generated from a private key
                        _ => None,
                    }
                }
                // Assuming default for offline signing
                None => Some((0, false)),
            };

            utxo_chain_inputs.push(InputSigningData {
                output: Output::try_from_dto(&output_response.output, token_supply)?,
                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                chain: address_index_internal.map(|(address_index, internal)| {
                    Chain::from_u32_hardened(vec![
                        HD_WALLET_TYPE,
                        self.coin_type,
                        self.account_index,
                        internal as u32,
                        address_index,
                    ])
                }),
                bech32_address: unlock_address.to_bech32(&bech32_hrp),
            });
        }

        Ok(utxo_chain_inputs)
    }
src/api/block_builder/input_selection/sender_issuer.rs (line 100)
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
    pub(crate) async fn get_inputs_for_sender_and_issuer(
        &self,
        utxo_chain_inputs: &[InputSigningData],
    ) -> Result<Vec<InputSigningData>> {
        log::debug!("[get_inputs_for_sender_and_issuer]");

        let mut required_inputs = Vec::new();
        let bech32_hrp = self.client.get_bech32_hrp().await?;
        let current_time = self.client.get_time_checked().await?;
        let token_supply = self.client.get_token_supply().await?;

        let required_sender_or_issuer_addresses =
            get_required_addresses_for_sender_and_issuer(&[], &self.outputs, current_time)?;

        for sender_or_issuer_address in required_sender_or_issuer_addresses {
            match sender_or_issuer_address {
                Address::Ed25519(_) => {
                    // Check if the address is derived from the seed
                    let (address_index, internal) = search_address(
                        self.secret_manager.ok_or(Error::MissingParameter("secret manager"))?,
                        &bech32_hrp,
                        self.coin_type,
                        self.account_index,
                        self.input_range.clone(),
                        &sender_or_issuer_address,
                    )
                    .await?;
                    let address_outputs = self
                        .basic_address_outputs(sender_or_issuer_address.to_bech32(&bech32_hrp))
                        .await?;

                    let mut found_output = false;
                    for output_response in address_outputs {
                        let output = Output::try_from_dto(&output_response.output, token_supply)?;

                        // We can ignore the unlocked_alias_or_nft_address, since we only requested basic outputs
                        let (required_unlock_address, _unlocked_alias_or_nft_address) = output
                            .required_and_unlocked_address(
                                current_time,
                                &output_response.metadata.output_id()?,
                                false,
                            )?;

                        if required_unlock_address == sender_or_issuer_address {
                            required_inputs.push(InputSigningData {
                                output,
                                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                                chain: Some(Chain::from_u32_hardened(vec![
                                    HD_WALLET_TYPE,
                                    self.coin_type,
                                    self.account_index,
                                    internal as u32,
                                    address_index,
                                ])),
                                bech32_address: sender_or_issuer_address.to_bech32(&bech32_hrp),
                            });
                            found_output = true;
                            break;
                        }
                    }

                    if !found_output {
                        return Err(Error::MissingInputWithEd25519Address);
                    }
                }
                Address::Alias(alias_address) => {
                    // Check if output is alias address.
                    let alias_id = alias_address.alias_id();

                    // check if already found or request new.
                    if !utxo_chain_inputs.iter().chain(required_inputs.iter()).any(|input| {
                        if let Output::Alias(alias_output) = &input.output {
                            alias_id == alias_output.alias_id()
                        } else {
                            false
                        }
                    }) {
                        let output_id = self.client.alias_output_id(*alias_id).await?;
                        let output_response = self.client.get_output(&output_id).await?;
                        if let OutputDto::Alias(alias_output_dto) = &output_response.output {
                            let alias_output = AliasOutput::try_from_dto(alias_output_dto, token_supply)?;
                            // State transition if we add them to inputs
                            let unlock_address = alias_output.state_controller_address();
                            let address_index_internal = match self.secret_manager {
                                Some(secret_manager) => {
                                    match unlock_address {
                                        Address::Ed25519(_) => Some(
                                            search_address(
                                                secret_manager,
                                                &bech32_hrp,
                                                self.coin_type,
                                                self.account_index,
                                                self.input_range.clone(),
                                                unlock_address,
                                            )
                                            .await?,
                                        ),
                                        // Alias and NFT addresses can't be generated from a private key
                                        _ => None,
                                    }
                                }
                                // Assuming default for offline signing
                                None => Some((0, false)),
                            };

                            required_inputs.push(InputSigningData {
                                output: Output::try_from_dto(&output_response.output, token_supply)?,
                                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                                chain: address_index_internal.map(|(address_index, internal)| {
                                    Chain::from_u32_hardened(vec![
                                        HD_WALLET_TYPE,
                                        self.coin_type,
                                        self.account_index,
                                        internal as u32,
                                        address_index,
                                    ])
                                }),
                                bech32_address: unlock_address.to_bech32(&bech32_hrp),
                            });
                        }
                    }
                }
                Address::Nft(nft_address) => {
                    // Check if output is nft address.
                    let nft_id = nft_address.nft_id();

                    // Check if already found or request new.
                    if !utxo_chain_inputs.iter().chain(required_inputs.iter()).any(|input| {
                        if let Output::Nft(nft_output) = &input.output {
                            nft_id == nft_output.nft_id()
                        } else {
                            false
                        }
                    }) {
                        let output_id = self.client.nft_output_id(*nft_id).await?;
                        let output_response = self.client.get_output(&output_id).await?;
                        if let OutputDto::Nft(nft_output) = &output_response.output {
                            let nft_output = NftOutput::try_from_dto(nft_output, token_supply)?;

                            let unlock_address = nft_output
                                .unlock_conditions()
                                .locked_address(nft_output.address(), current_time);

                            let address_index_internal = match self.secret_manager {
                                Some(secret_manager) => {
                                    match unlock_address {
                                        Address::Ed25519(_) => Some(
                                            search_address(
                                                secret_manager,
                                                &bech32_hrp,
                                                self.coin_type,
                                                self.account_index,
                                                self.input_range.clone(),
                                                unlock_address,
                                            )
                                            .await?,
                                        ),
                                        // Alias and NFT addresses can't be generated from a private key.
                                        _ => None,
                                    }
                                }
                                // Assuming default for offline signing.
                                None => Some((0, false)),
                            };

                            required_inputs.push(InputSigningData {
                                output: Output::try_from_dto(&output_response.output, token_supply)?,
                                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                                chain: address_index_internal.map(|(address_index, internal)| {
                                    Chain::from_u32_hardened(vec![
                                        HD_WALLET_TYPE,
                                        self.coin_type,
                                        self.account_index,
                                        internal as u32,
                                        address_index,
                                    ])
                                }),
                                bech32_address: unlock_address.to_bech32(&bech32_hrp),
                            });
                        }
                    }
                }
            }
        }

        // Check required Alias and NFT outputs with new added outputs.
        // No need to check for sender and issuer again, since these outputs already exist and we don't set new features
        // for them.
        let utxo_chain_inputs = self
            .get_utxo_chains_inputs(required_inputs.iter().map(|i| &i.output))
            .await?;
        required_inputs.extend(utxo_chain_inputs.into_iter());

        Ok(required_inputs)
    }

Finds an output, as raw bytes, by its OutputId (TransactionId + output_index). GET /api/core/v2/outputs/{outputId}

Get the metadata for a given OutputId (TransactionId + output_index). GET /api/core/v2/outputs/{outputId}/metadata

Examples found in repository?
src/node_api/core/mod.rs (line 94)
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
    pub async fn try_get_outputs_metadata(&self, output_ids: Vec<OutputId>) -> Result<Vec<OutputMetadataResponse>> {
        let mut output_metadata_responses = Vec::new();

        #[cfg(target_family = "wasm")]
        for output_id in output_ids {
            if let Ok(output_metadata_response) = self.get_output_metadata(&output_id).await {
                output_metadata_responses.push(output_metadata_response);
            }
        }

        #[cfg(not(target_family = "wasm"))]
        for output_ids_chunk in output_ids.chunks(MAX_PARALLEL_API_REQUESTS).map(<[OutputId]>::to_vec) {
            let mut tasks = Vec::new();
            for output_id in output_ids_chunk {
                let client_ = self.clone();

                tasks.push(async move {
                    tokio::spawn(async move { client_.get_output_metadata(&output_id).await.ok() }).await
                });
            }
            for output_metadata_response in (futures::future::try_join_all(tasks).await?).into_iter().flatten() {
                output_metadata_responses.push(output_metadata_response);
            }
        }

        Ok(output_metadata_responses)
    }

Gets all stored receipts. GET /api/core/v2/receipts

Gets the receipts by the given milestone index. GET /api/core/v2/receipts/{migratedAt}

Gets the current treasury output. The treasury output contains all tokens from the legacy network that have not yet been migrated. GET /api/core/v2/treasury

Returns the block, as object, that was included in the ledger for a given TransactionId. GET /api/core/v2/transactions/{transactionId}/included-block

Examples found in repository?
src/client/high_level.rs (line 37)
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
    pub async fn inputs_from_transaction_id(
        &self,
        transaction_id: &TransactionId,
    ) -> Result<Vec<OutputWithMetadataResponse>> {
        let block = self.get_included_block(transaction_id).await?;

        let inputs = match block.payload() {
            Some(Payload::Transaction(t)) => match t.essence() {
                TransactionEssence::Regular(e) => e.inputs(),
            },
            _ => {
                unreachable!()
            }
        };

        let input_ids = inputs
            .iter()
            .map(|i| match i {
                Input::Utxo(input) => *input.output_id(),
                Input::Treasury(_) => {
                    unreachable!()
                }
            })
            .collect();

        self.get_outputs(input_ids).await
    }

    /// A generic send function for easily sending transaction or tagged data blocks.
    pub fn block(&self) -> ClientBlockBuilder<'_> {
        ClientBlockBuilder::new(self)
    }

    /// Return a list of addresses from a secret manager regardless of their validity.
    pub fn get_addresses<'a>(&'a self, secret_manager: &'a SecretManager) -> GetAddressesBuilder<'a> {
        GetAddressesBuilder::new(secret_manager).with_client(self)
    }

    /// Find all blocks by provided block IDs.
    pub async fn find_blocks(&self, block_ids: &[BlockId]) -> Result<Vec<Block>> {
        let mut blocks = Vec::new();

        // Use a `HashSet` to prevent duplicate block_ids.
        let mut block_ids_to_query = HashSet::<BlockId>::new();

        // Collect the `BlockId` in the HashSet.
        for block_id in block_ids {
            block_ids_to_query.insert(*block_id);
        }

        // Use `get_block()` API to get the `Block`.
        for block_id in block_ids_to_query {
            let block = self.get_block(&block_id).await?;
            blocks.push(block);
        }
        Ok(blocks)
    }

    /// Retries (promotes or reattaches) a block for provided block id. Block should only be
    /// retried only if they are valid and haven't been confirmed for a while.
    pub async fn retry(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Get the metadata to check if it needs to promote or reattach
        let block_metadata = self.get_block_metadata(block_id).await?;
        if block_metadata.should_promote.unwrap_or(false) {
            self.promote_unchecked(block_id).await
        } else if block_metadata.should_reattach.unwrap_or(false) {
            self.reattach_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

    /// Retries (promotes or reattaches) a block for provided block id until it's included (referenced by a
    /// milestone). Default interval is 5 seconds and max attempts is 40. Returns the included block at first position
    /// and additional reattached blocks
    pub async fn retry_until_included(
        &self,
        block_id: &BlockId,
        interval: Option<u64>,
        max_attempts: Option<u64>,
    ) -> Result<Vec<(BlockId, Block)>> {
        log::debug!("[retry_until_included]");
        // Attachments of the Block to check inclusion state
        let mut block_ids = vec![*block_id];
        // Reattached Blocks that get returned
        let mut blocks_with_id = Vec::new();
        for _ in 0..max_attempts.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_MAX_AMOUNT) {
            #[cfg(target_family = "wasm")]
            gloo_timers::future::TimeoutFuture::new(
                (interval.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_INTERVAL) * 1000)
                    .try_into()
                    .unwrap(),
            )
            .await;

            #[cfg(not(target_family = "wasm"))]
            tokio::time::sleep(std::time::Duration::from_secs(
                interval.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_INTERVAL),
            ))
            .await;

            // Check inclusion state for each attachment
            let block_ids_len = block_ids.len();
            let mut conflicting = false;
            for (index, block_id_) in block_ids.clone().iter().enumerate() {
                let block_metadata = self.get_block_metadata(block_id_).await?;
                if let Some(inclusion_state) = block_metadata.ledger_inclusion_state {
                    match inclusion_state {
                        LedgerInclusionStateDto::Included | LedgerInclusionStateDto::NoTransaction => {
                            // if original block, request it so we can return it on first position
                            if block_id == block_id_ {
                                let mut included_and_reattached_blocks =
                                    vec![(*block_id, self.get_block(block_id).await?)];
                                included_and_reattached_blocks.extend(blocks_with_id);
                                return Ok(included_and_reattached_blocks);
                            } else {
                                // Move included block to first position
                                blocks_with_id.rotate_left(index);
                                return Ok(blocks_with_id);
                            }
                        }
                        // only set it as conflicting here and don't return, because another reattached block could
                        // have the included transaction
                        LedgerInclusionStateDto::Conflicting => conflicting = true,
                    };
                }
                // Only reattach or promote latest attachment of the block
                if index == block_ids_len - 1 {
                    if block_metadata.should_promote.unwrap_or(false) {
                        // Safe to unwrap since we iterate over it
                        self.promote_unchecked(block_ids.last().unwrap()).await?;
                    } else if block_metadata.should_reattach.unwrap_or(false) {
                        // Safe to unwrap since we iterate over it
                        let reattached = self.reattach_unchecked(block_ids.last().unwrap()).await?;
                        block_ids.push(reattached.0);
                        blocks_with_id.push(reattached);
                    }
                }
            }
            // After we checked all our reattached blocks, check if the transaction got reattached in another block
            // and confirmed
            if conflicting {
                let block = self.get_block(block_id).await?;
                if let Some(Payload::Transaction(transaction_payload)) = block.payload() {
                    let included_block = self.get_included_block(&transaction_payload.id()).await?;
                    let mut included_and_reattached_blocks = vec![(included_block.id(), included_block)];
                    included_and_reattached_blocks.extend(blocks_with_id);
                    return Ok(included_and_reattached_blocks);
                }
            }
        }
        Err(Error::TangleInclusionError(block_id.to_string()))
    }

Returns the block, as raw bytes, that was included in the ledger for a given TransactionId. GET /api/core/v2/transactions/{transactionId}/included-block

Gets the milestone by the given milestone id. GET /api/core/v2/milestones/{milestoneId}

Gets the milestone by the given milestone id. GET /api/core/v2/milestones/{milestoneId}

Gets all UTXO changes of a milestone by its milestone id. GET /api/core/v2/milestones/{milestoneId}/utxo-changes

Gets the milestone by the given milestone index. GET /api/core/v2/milestones/{index}

Gets the milestone by the given milestone index. GET /api/core/v2/milestones/{index}

Gets all UTXO changes of a milestone by its milestone index. GET /api/core/v2/milestones/by-index/{index}/utxo-changes

GET /api/core/v2/peers

Request outputs by their output ID in parallel

Examples found in repository?
src/client/high_level.rs (line 58)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
    pub async fn inputs_from_transaction_id(
        &self,
        transaction_id: &TransactionId,
    ) -> Result<Vec<OutputWithMetadataResponse>> {
        let block = self.get_included_block(transaction_id).await?;

        let inputs = match block.payload() {
            Some(Payload::Transaction(t)) => match t.essence() {
                TransactionEssence::Regular(e) => e.inputs(),
            },
            _ => {
                unreachable!()
            }
        };

        let input_ids = inputs
            .iter()
            .map(|i| match i {
                Input::Utxo(input) => *input.output_id(),
                Input::Treasury(_) => {
                    unreachable!()
                }
            })
            .collect();

        self.get_outputs(input_ids).await
    }

    /// A generic send function for easily sending transaction or tagged data blocks.
    pub fn block(&self) -> ClientBlockBuilder<'_> {
        ClientBlockBuilder::new(self)
    }

    /// Return a list of addresses from a secret manager regardless of their validity.
    pub fn get_addresses<'a>(&'a self, secret_manager: &'a SecretManager) -> GetAddressesBuilder<'a> {
        GetAddressesBuilder::new(secret_manager).with_client(self)
    }

    /// Find all blocks by provided block IDs.
    pub async fn find_blocks(&self, block_ids: &[BlockId]) -> Result<Vec<Block>> {
        let mut blocks = Vec::new();

        // Use a `HashSet` to prevent duplicate block_ids.
        let mut block_ids_to_query = HashSet::<BlockId>::new();

        // Collect the `BlockId` in the HashSet.
        for block_id in block_ids {
            block_ids_to_query.insert(*block_id);
        }

        // Use `get_block()` API to get the `Block`.
        for block_id in block_ids_to_query {
            let block = self.get_block(&block_id).await?;
            blocks.push(block);
        }
        Ok(blocks)
    }

    /// Retries (promotes or reattaches) a block for provided block id. Block should only be
    /// retried only if they are valid and haven't been confirmed for a while.
    pub async fn retry(&self, block_id: &BlockId) -> Result<(BlockId, Block)> {
        // Get the metadata to check if it needs to promote or reattach
        let block_metadata = self.get_block_metadata(block_id).await?;
        if block_metadata.should_promote.unwrap_or(false) {
            self.promote_unchecked(block_id).await
        } else if block_metadata.should_reattach.unwrap_or(false) {
            self.reattach_unchecked(block_id).await
        } else {
            Err(Error::NoNeedPromoteOrReattach(block_id.to_string()))
        }
    }

    /// Retries (promotes or reattaches) a block for provided block id until it's included (referenced by a
    /// milestone). Default interval is 5 seconds and max attempts is 40. Returns the included block at first position
    /// and additional reattached blocks
    pub async fn retry_until_included(
        &self,
        block_id: &BlockId,
        interval: Option<u64>,
        max_attempts: Option<u64>,
    ) -> Result<Vec<(BlockId, Block)>> {
        log::debug!("[retry_until_included]");
        // Attachments of the Block to check inclusion state
        let mut block_ids = vec![*block_id];
        // Reattached Blocks that get returned
        let mut blocks_with_id = Vec::new();
        for _ in 0..max_attempts.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_MAX_AMOUNT) {
            #[cfg(target_family = "wasm")]
            gloo_timers::future::TimeoutFuture::new(
                (interval.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_INTERVAL) * 1000)
                    .try_into()
                    .unwrap(),
            )
            .await;

            #[cfg(not(target_family = "wasm"))]
            tokio::time::sleep(std::time::Duration::from_secs(
                interval.unwrap_or(DEFAULT_RETRY_UNTIL_INCLUDED_INTERVAL),
            ))
            .await;

            // Check inclusion state for each attachment
            let block_ids_len = block_ids.len();
            let mut conflicting = false;
            for (index, block_id_) in block_ids.clone().iter().enumerate() {
                let block_metadata = self.get_block_metadata(block_id_).await?;
                if let Some(inclusion_state) = block_metadata.ledger_inclusion_state {
                    match inclusion_state {
                        LedgerInclusionStateDto::Included | LedgerInclusionStateDto::NoTransaction => {
                            // if original block, request it so we can return it on first position
                            if block_id == block_id_ {
                                let mut included_and_reattached_blocks =
                                    vec![(*block_id, self.get_block(block_id).await?)];
                                included_and_reattached_blocks.extend(blocks_with_id);
                                return Ok(included_and_reattached_blocks);
                            } else {
                                // Move included block to first position
                                blocks_with_id.rotate_left(index);
                                return Ok(blocks_with_id);
                            }
                        }
                        // only set it as conflicting here and don't return, because another reattached block could
                        // have the included transaction
                        LedgerInclusionStateDto::Conflicting => conflicting = true,
                    };
                }
                // Only reattach or promote latest attachment of the block
                if index == block_ids_len - 1 {
                    if block_metadata.should_promote.unwrap_or(false) {
                        // Safe to unwrap since we iterate over it
                        self.promote_unchecked(block_ids.last().unwrap()).await?;
                    } else if block_metadata.should_reattach.unwrap_or(false) {
                        // Safe to unwrap since we iterate over it
                        let reattached = self.reattach_unchecked(block_ids.last().unwrap()).await?;
                        block_ids.push(reattached.0);
                        blocks_with_id.push(reattached);
                    }
                }
            }
            // After we checked all our reattached blocks, check if the transaction got reattached in another block
            // and confirmed
            if conflicting {
                let block = self.get_block(block_id).await?;
                if let Some(Payload::Transaction(transaction_payload)) = block.payload() {
                    let included_block = self.get_included_block(&transaction_payload.id()).await?;
                    let mut included_and_reattached_blocks = vec![(included_block.id(), included_block)];
                    included_and_reattached_blocks.extend(blocks_with_id);
                    return Ok(included_and_reattached_blocks);
                }
            }
        }
        Err(Error::TangleInclusionError(block_id.to_string()))
    }

    /// Function to find inputs from addresses for a provided amount (useful for offline signing), ignoring outputs with
    /// additional unlock conditions
    pub async fn find_inputs(&self, addresses: Vec<String>, amount: u64) -> Result<Vec<UtxoInput>> {
        // Get outputs from node and select inputs
        let mut available_outputs = Vec::new();

        for address in addresses {
            let basic_output_ids = self
                .basic_output_ids(vec![
                    QueryParameter::Address(address.to_string()),
                    QueryParameter::HasExpiration(false),
                    QueryParameter::HasTimelock(false),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?;

            available_outputs.extend(self.get_outputs(basic_output_ids).await?);
        }

        let mut basic_outputs = Vec::new();
        let current_time = self.get_time_checked().await?;
        let token_supply = self.get_token_supply().await?;

        for output_resp in available_outputs {
            let (amount, _) = ClientBlockBuilder::get_output_amount_and_address(
                &Output::try_from_dto(&output_resp.output, token_supply)?,
                None,
                current_time,
            )?;
            basic_outputs.push((
                UtxoInput::new(
                    TransactionId::from_str(&output_resp.metadata.transaction_id)?,
                    output_resp.metadata.output_index,
                )?,
                amount,
            ));
        }
        basic_outputs.sort_by(|l, r| r.1.cmp(&l.1));

        let mut total_already_spent = 0;
        let mut selected_inputs = Vec::new();
        for (_offset, output_wrapper) in basic_outputs
            .into_iter()
            // Max inputs is 128
            .take(INPUT_COUNT_MAX.into())
            .enumerate()
        {
            // Break if we have enough funds and don't create dust for the remainder
            if total_already_spent == amount || total_already_spent >= amount {
                break;
            }
            selected_inputs.push(output_wrapper.0.clone());
            total_already_spent += output_wrapper.1;
        }

        if total_already_spent < amount {
            return Err(crate::Error::NotEnoughBalance {
                found: total_already_spent,
                required: amount,
            });
        }

        Ok(selected_inputs)
    }

    /// Find all outputs based on the requests criteria. This method will try to query multiple nodes if
    /// the request amount exceeds individual node limit.
    pub async fn find_outputs(
        &self,
        output_ids: &[OutputId],
        addresses: &[String],
    ) -> Result<Vec<OutputWithMetadataResponse>> {
        let mut output_responses = self.get_outputs(output_ids.to_vec()).await?;

        // Use `get_address()` API to get the address outputs first,
        // then collect the `UtxoInput` in the HashSet.
        for address in addresses {
            // Get output ids of outputs that can be controlled by this address without further unlock constraints
            let basic_output_ids = self
                .basic_output_ids(vec![
                    QueryParameter::Address(address.to_string()),
                    QueryParameter::HasExpiration(false),
                    QueryParameter::HasTimelock(false),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?;

            output_responses.extend(self.get_outputs(basic_output_ids).await?);
        }

        Ok(output_responses.clone())
    }
More examples
Hide additional examples
src/api/block_builder/input_selection/automatic.rs (line 59)
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
    pub(crate) async fn basic_address_outputs(&self, address: String) -> Result<Vec<OutputWithMetadataResponse>> {
        let mut output_ids = Vec::new();

        // First request to get all basic outputs that can directly be unlocked by the address.
        output_ids.extend(
            self.client
                .basic_output_ids(vec![
                    QueryParameter::Address(address.clone()),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?,
        );

        // Second request to get all basic outputs that can be unlocked by the address through the expiration condition.
        output_ids.extend(
            self.client
                .basic_output_ids(vec![
                    QueryParameter::ExpirationReturnAddress(address),
                    QueryParameter::HasExpiration(true),
                    QueryParameter::HasStorageDepositReturn(false),
                    // Ignore outputs that aren't expired yet
                    QueryParameter::ExpiresBefore(
                        instant::SystemTime::now()
                            .duration_since(instant::SystemTime::UNIX_EPOCH)
                            .expect("time went backwards")
                            .as_secs() as u32,
                    ),
                ])
                .await?,
        );

        self.client.get_outputs(output_ids).await
    }
src/api/consolidation.rs (line 60)
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
    pub async fn consolidate_funds(
        &self,
        secret_manager: &SecretManager,
        address_builder_options: GetAddressesBuilderOptions,
    ) -> Result<String> {
        let token_supply = self.get_token_supply().await?;
        let mut last_transfer_index = address_builder_options.range.as_ref().unwrap_or(&(0..1)).start;
        // use the start index as offset
        let offset = last_transfer_index;

        let addresses = self
            .get_addresses(secret_manager)
            .set_options(address_builder_options)?
            .finish()
            .await?;

        let consolidation_address = addresses[0].clone();

        'consolidation: loop {
            let mut block_ids = Vec::new();
            // Iterate over addresses reversed so the funds end up on the first address in the range
            for (index, address) in addresses.iter().enumerate().rev() {
                let index = index as u32;
                // add the offset so the index matches the address index also for higher start indexes
                let index = index + offset;

                // Get output ids of outputs that can be controlled by this address without further unlock constraints
                let basic_output_ids = self
                    .basic_output_ids(vec![
                        QueryParameter::Address(address.to_string()),
                        QueryParameter::HasExpiration(false),
                        QueryParameter::HasTimelock(false),
                        QueryParameter::HasStorageDepositReturn(false),
                    ])
                    .await?;

                let basic_outputs_responses = self.get_outputs(basic_output_ids).await?;

                if !basic_outputs_responses.is_empty() {
                    // If we reach the same index again
                    if last_transfer_index == index {
                        if basic_outputs_responses.len() < 2 {
                            break 'consolidation;
                        }
                    } else {
                        last_transfer_index = index;
                    }
                }

                let outputs_chunks = basic_outputs_responses.chunks(INPUT_COUNT_MAX.into());

                for chunk in outputs_chunks {
                    let mut block_builder = self.block().with_secret_manager(secret_manager);
                    let mut total_amount = 0;
                    let mut total_native_tokens = NativeTokensBuilder::new();

                    for output_response in chunk {
                        block_builder = block_builder.with_input(UtxoInput::from(OutputId::new(
                            TransactionId::from_str(&output_response.metadata.transaction_id)?,
                            output_response.metadata.output_index,
                        )?))?;

                        let output = Output::try_from_dto(&output_response.output, token_supply)?;

                        if let Some(native_tokens) = output.native_tokens() {
                            total_native_tokens.add_native_tokens(native_tokens.clone())?;
                        }
                        total_amount += output.amount();
                    }

                    let consolidation_output = BasicOutputBuilder::new_with_amount(total_amount)?
                        .add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(
                            Address::try_from_bech32(&consolidation_address)?.1,
                        )))
                        .with_native_tokens(total_native_tokens.finish()?)
                        .finish_output(token_supply)?;

                    let block = block_builder
                        .with_input_range(index..index + 1)
                        .with_outputs(vec![consolidation_output])?
                        .with_initial_address_index(0)
                        .finish()
                        .await?;
                    block_ids.push(block.id());
                }
            }

            if block_ids.is_empty() {
                break 'consolidation;
            }
            // Wait for txs to get confirmed so we don't create conflicting txs
            for block_id in block_ids {
                let _ = self.retry_until_included(&block_id, None, None).await?;
            }
        }
        Ok(consolidation_address)
    }

Request outputs by their output ID in parallel, ignoring failed requests Useful to get data about spent outputs, that might not be pruned yet

Requests metadata for outputs by their output ID in parallel, ignoring failed requests

Get basic outputs filtered by the given parameters. GET with query parameter returns all outputIDs that fit these filter criteria. Query parameters: “address”, “hasStorageDepositReturn”, “storageDepositReturnAddress”, “hasExpiration”, “expiresBefore”, “expiresAfter”, “hasTimelock”, “timelockedBefore”, “timelockedAfter”, “sender”, “tag”, “createdBefore” and “createdAfter”. Returns an empty Vec if no results are found. api/indexer/v1/outputs/basic

Examples found in repository?
src/api/block_builder/input_selection/automatic.rs (lines 34-37)
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
    pub(crate) async fn basic_address_outputs(&self, address: String) -> Result<Vec<OutputWithMetadataResponse>> {
        let mut output_ids = Vec::new();

        // First request to get all basic outputs that can directly be unlocked by the address.
        output_ids.extend(
            self.client
                .basic_output_ids(vec![
                    QueryParameter::Address(address.clone()),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?,
        );

        // Second request to get all basic outputs that can be unlocked by the address through the expiration condition.
        output_ids.extend(
            self.client
                .basic_output_ids(vec![
                    QueryParameter::ExpirationReturnAddress(address),
                    QueryParameter::HasExpiration(true),
                    QueryParameter::HasStorageDepositReturn(false),
                    // Ignore outputs that aren't expired yet
                    QueryParameter::ExpiresBefore(
                        instant::SystemTime::now()
                            .duration_since(instant::SystemTime::UNIX_EPOCH)
                            .expect("time went backwards")
                            .as_secs() as u32,
                    ),
                ])
                .await?,
        );

        self.client.get_outputs(output_ids).await
    }
More examples
Hide additional examples
src/client/high_level.rs (lines 195-200)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
    pub async fn find_inputs(&self, addresses: Vec<String>, amount: u64) -> Result<Vec<UtxoInput>> {
        // Get outputs from node and select inputs
        let mut available_outputs = Vec::new();

        for address in addresses {
            let basic_output_ids = self
                .basic_output_ids(vec![
                    QueryParameter::Address(address.to_string()),
                    QueryParameter::HasExpiration(false),
                    QueryParameter::HasTimelock(false),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?;

            available_outputs.extend(self.get_outputs(basic_output_ids).await?);
        }

        let mut basic_outputs = Vec::new();
        let current_time = self.get_time_checked().await?;
        let token_supply = self.get_token_supply().await?;

        for output_resp in available_outputs {
            let (amount, _) = ClientBlockBuilder::get_output_amount_and_address(
                &Output::try_from_dto(&output_resp.output, token_supply)?,
                None,
                current_time,
            )?;
            basic_outputs.push((
                UtxoInput::new(
                    TransactionId::from_str(&output_resp.metadata.transaction_id)?,
                    output_resp.metadata.output_index,
                )?,
                amount,
            ));
        }
        basic_outputs.sort_by(|l, r| r.1.cmp(&l.1));

        let mut total_already_spent = 0;
        let mut selected_inputs = Vec::new();
        for (_offset, output_wrapper) in basic_outputs
            .into_iter()
            // Max inputs is 128
            .take(INPUT_COUNT_MAX.into())
            .enumerate()
        {
            // Break if we have enough funds and don't create dust for the remainder
            if total_already_spent == amount || total_already_spent >= amount {
                break;
            }
            selected_inputs.push(output_wrapper.0.clone());
            total_already_spent += output_wrapper.1;
        }

        if total_already_spent < amount {
            return Err(crate::Error::NotEnoughBalance {
                found: total_already_spent,
                required: amount,
            });
        }

        Ok(selected_inputs)
    }

    /// Find all outputs based on the requests criteria. This method will try to query multiple nodes if
    /// the request amount exceeds individual node limit.
    pub async fn find_outputs(
        &self,
        output_ids: &[OutputId],
        addresses: &[String],
    ) -> Result<Vec<OutputWithMetadataResponse>> {
        let mut output_responses = self.get_outputs(output_ids.to_vec()).await?;

        // Use `get_address()` API to get the address outputs first,
        // then collect the `UtxoInput` in the HashSet.
        for address in addresses {
            // Get output ids of outputs that can be controlled by this address without further unlock constraints
            let basic_output_ids = self
                .basic_output_ids(vec![
                    QueryParameter::Address(address.to_string()),
                    QueryParameter::HasExpiration(false),
                    QueryParameter::HasTimelock(false),
                    QueryParameter::HasStorageDepositReturn(false),
                ])
                .await?;

            output_responses.extend(self.get_outputs(basic_output_ids).await?);
        }

        Ok(output_responses.clone())
    }
src/api/consolidation.rs (lines 52-57)
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
    pub async fn consolidate_funds(
        &self,
        secret_manager: &SecretManager,
        address_builder_options: GetAddressesBuilderOptions,
    ) -> Result<String> {
        let token_supply = self.get_token_supply().await?;
        let mut last_transfer_index = address_builder_options.range.as_ref().unwrap_or(&(0..1)).start;
        // use the start index as offset
        let offset = last_transfer_index;

        let addresses = self
            .get_addresses(secret_manager)
            .set_options(address_builder_options)?
            .finish()
            .await?;

        let consolidation_address = addresses[0].clone();

        'consolidation: loop {
            let mut block_ids = Vec::new();
            // Iterate over addresses reversed so the funds end up on the first address in the range
            for (index, address) in addresses.iter().enumerate().rev() {
                let index = index as u32;
                // add the offset so the index matches the address index also for higher start indexes
                let index = index + offset;

                // Get output ids of outputs that can be controlled by this address without further unlock constraints
                let basic_output_ids = self
                    .basic_output_ids(vec![
                        QueryParameter::Address(address.to_string()),
                        QueryParameter::HasExpiration(false),
                        QueryParameter::HasTimelock(false),
                        QueryParameter::HasStorageDepositReturn(false),
                    ])
                    .await?;

                let basic_outputs_responses = self.get_outputs(basic_output_ids).await?;

                if !basic_outputs_responses.is_empty() {
                    // If we reach the same index again
                    if last_transfer_index == index {
                        if basic_outputs_responses.len() < 2 {
                            break 'consolidation;
                        }
                    } else {
                        last_transfer_index = index;
                    }
                }

                let outputs_chunks = basic_outputs_responses.chunks(INPUT_COUNT_MAX.into());

                for chunk in outputs_chunks {
                    let mut block_builder = self.block().with_secret_manager(secret_manager);
                    let mut total_amount = 0;
                    let mut total_native_tokens = NativeTokensBuilder::new();

                    for output_response in chunk {
                        block_builder = block_builder.with_input(UtxoInput::from(OutputId::new(
                            TransactionId::from_str(&output_response.metadata.transaction_id)?,
                            output_response.metadata.output_index,
                        )?))?;

                        let output = Output::try_from_dto(&output_response.output, token_supply)?;

                        if let Some(native_tokens) = output.native_tokens() {
                            total_native_tokens.add_native_tokens(native_tokens.clone())?;
                        }
                        total_amount += output.amount();
                    }

                    let consolidation_output = BasicOutputBuilder::new_with_amount(total_amount)?
                        .add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(
                            Address::try_from_bech32(&consolidation_address)?.1,
                        )))
                        .with_native_tokens(total_native_tokens.finish()?)
                        .finish_output(token_supply)?;

                    let block = block_builder
                        .with_input_range(index..index + 1)
                        .with_outputs(vec![consolidation_output])?
                        .with_initial_address_index(0)
                        .finish()
                        .await?;
                    block_ids.push(block.id());
                }
            }

            if block_ids.is_empty() {
                break 'consolidation;
            }
            // Wait for txs to get confirmed so we don't create conflicting txs
            for block_id in block_ids {
                let _ = self.retry_until_included(&block_id, None, None).await?;
            }
        }
        Ok(consolidation_address)
    }

Get alias outputs filtered by the given parameters. GET with query parameter returns all outputIDs that fit these filter criteria. Query parameters: “stateController”, “governor”, “issuer”, “sender”, “createdBefore”, “createdAfter” Returns an empty list if no results are found. api/indexer/v1/outputs/alias

Get alias output by its aliasID. api/indexer/v1/outputs/alias/:{AliasId}

Examples found in repository?
src/api/block_builder/transaction.rs (line 49)
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
    pub async fn prepare_transaction(&self) -> Result<PreparedTransactionData> {
        log::debug!("[prepare_transaction]");
        let rent_structure = self.client.get_rent_structure().await?;
        let token_supply = self.client.get_token_supply().await?;

        let mut governance_transition: Option<HashSet<AliasId>> = None;
        for output in &self.outputs {
            // Check if the outputs have enough amount to cover the storage deposit
            output.verify_storage_deposit(rent_structure.clone(), token_supply)?;
            if let Output::Alias(x) = output {
                if x.state_index() > 0 {
                    // Check if the transaction is a governance_transition, by checking if the new index is the same as
                    // the previous index
                    let output_id = self.client.alias_output_id(*x.alias_id()).await?;
                    let output_response = self.client.get_output(&output_id).await?;
                    if let OutputDto::Alias(output) = output_response.output {
                        // A governance transition is identified by an unchanged State Index in next state.
                        if x.state_index() == output.state_index {
                            let mut transitions = HashSet::new();
                            transitions.insert(AliasId::try_from(&output.alias_id)?);
                            governance_transition.replace(transitions);
                        }
                    }
                }
            }
        }

        // Input selection
        let selected_transaction_data = if self.inputs.is_some() {
            self.get_custom_inputs(governance_transition, &rent_structure, self.allow_burning)
                .await?
        } else {
            self.get_inputs(&rent_structure).await?
        };

        // Build transaction payload
        let inputs_commitment = InputsCommitment::new(selected_transaction_data.inputs.iter().map(|i| &i.output));

        let mut essence = RegularTransactionEssence::builder(self.client.get_network_id().await?, inputs_commitment);
        let inputs = selected_transaction_data
            .inputs
            .iter()
            .map(|i| {
                Ok(Input::Utxo(UtxoInput::new(
                    *i.output_metadata.transaction_id(),
                    i.output_metadata.output_index(),
                )?))
            })
            .collect::<Result<Vec<Input>>>()?;
        essence = essence.with_inputs(inputs);

        essence = essence.with_outputs(selected_transaction_data.outputs);

        // Add tagged data payload if tag set
        if let Some(index) = self.tag.clone() {
            let tagged_data_payload = TaggedDataPayload::new(index.to_vec(), self.data.clone().unwrap_or_default())?;
            essence = essence.with_payload(Payload::from(tagged_data_payload));
        }

        let regular_essence = essence.finish(&self.client.get_protocol_parameters().await?)?;

        validate_regular_transaction_essence_length(&regular_essence)?;

        let essence = TransactionEssence::Regular(regular_essence);

        Ok(PreparedTransactionData {
            essence,
            inputs_data: selected_transaction_data.inputs,
            remainder: selected_transaction_data.remainder,
        })
    }
More examples
Hide additional examples
src/api/block_builder/input_selection/utxo_chains/mod.rs (line 336)
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
pub(crate) async fn get_alias_and_nft_outputs_recursively(
    client: &Client,
    utxo_chains: &mut Vec<(Address, OutputWithMetadataResponse)>,
) -> Result<()> {
    log::debug!("[get_alias_and_nft_outputs_recursively]");
    let current_time = client.get_time_checked().await?;
    let token_supply = client.get_token_supply().await?;

    let mut processed_alias_nft_addresses = std::collections::HashSet::new();

    // Add addresses for alias and nft outputs we already have
    for (_unlock_address, output_response) in utxo_chains.iter() {
        let output_id = OutputId::new(
            TransactionId::from_str(&output_response.metadata.transaction_id)?,
            output_response.metadata.output_index,
        )?;

        match Output::try_from_dto(&output_response.output, token_supply)? {
            Output::Alias(alias_output) => {
                processed_alias_nft_addresses.insert(Address::Alias(alias_output.alias_address(&output_id)));
            }
            Output::Nft(nft_output) => {
                processed_alias_nft_addresses.insert(Address::Nft(nft_output.nft_address(&output_id)));
            }
            _ => {}
        }
    }

    let mut processed_utxo_chains = Vec::new();

    // Make the outputs response optional, because we don't know it yet for new required outputs
    let mut utxo_chain_optional_response: Vec<(Address, Option<OutputWithMetadataResponse>)> =
        utxo_chains.iter_mut().map(|(a, o)| (*a, Some(o.clone()))).collect();

    // Get alias or nft addresses when needed or just add the input again
    while let Some((unlock_address, output_response)) = utxo_chain_optional_response.pop() {
        // Don't request outputs for addresses where we already have the output
        if processed_alias_nft_addresses.insert(unlock_address) {
            match unlock_address {
                Address::Alias(address) => {
                    let output_id = client.alias_output_id(*address.alias_id()).await?;
                    let output_response = client.get_output(&output_id).await?;
                    if let OutputDto::Alias(alias_output_dto) = &output_response.output {
                        let alias_output = AliasOutput::try_from_dto(alias_output_dto, token_supply)?;
                        // State transition if we add them to inputs
                        let alias_unlock_address = alias_output.state_controller_address();
                        // Add address to unprocessed_alias_nft_addresses so we get the required output there
                        // also
                        if alias_unlock_address.is_alias() || alias_unlock_address.is_nft() {
                            utxo_chain_optional_response.push((*alias_unlock_address, None));
                        }
                        processed_utxo_chains.push((*alias_unlock_address, output_response));
                    }
                }
                Address::Nft(address) => {
                    let output_id = client.nft_output_id(*address.nft_id()).await?;
                    let output_response = client.get_output(&output_id).await?;
                    if let OutputDto::Nft(nft_output) = &output_response.output {
                        let nft_output = NftOutput::try_from_dto(nft_output, token_supply)?;
                        let unlock_address = nft_output
                            .unlock_conditions()
                            .locked_address(nft_output.address(), current_time);
                        // Add address to unprocessed_alias_nft_addresses so we get the required output there also
                        if unlock_address.is_alias() || unlock_address.is_nft() {
                            utxo_chain_optional_response.push((*unlock_address, None));
                        }
                        processed_utxo_chains.push((*unlock_address, output_response));
                    }
                }
                _ => {}
            }
        }

        // Add if the output_response is available
        if let Some(output_response) = output_response {
            processed_utxo_chains.push((unlock_address, output_response));
        }
    }

    *utxo_chains = processed_utxo_chains;

    Ok(())
}
src/api/block_builder/input_selection/utxo_chains/automatic.rs (line 44)
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
    pub(crate) async fn get_utxo_chains_inputs(
        &self,
        outputs: impl Iterator<Item = &'a Output> + Clone,
    ) -> Result<Vec<InputSigningData>> {
        log::debug!("[get_utxo_chains_inputs]");
        let client = self.client;
        let bech32_hrp = client.get_bech32_hrp().await?;
        let current_time = self.client.get_time_checked().await?;
        let token_supply = client.get_token_supply().await?;

        let mut utxo_chains: Vec<(Address, OutputWithMetadataResponse)> = Vec::new();
        for output in outputs {
            match output {
                Output::Alias(alias_output) => {
                    // if the alias id is null then there can't be a previous output and it can also not be a
                    // governance transition
                    if !alias_output.alias_id().is_null() {
                        // Check if the transaction is a governance_transition, by checking if the new index is the same
                        // as the previous index
                        let output_id = client.alias_output_id(*alias_output.alias_id()).await?;
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Alias(alias_output_dto) = &output_response.output {
                            let alias_output = AliasOutput::try_from_dto(alias_output_dto, token_supply)?;

                            // A governance transition is identified by an unchanged State Index in next
                            // state.
                            if alias_output.state_index() == alias_output.state_index() {
                                utxo_chains.push((*alias_output.governor_address(), output_response));
                            } else {
                                utxo_chains.push((*alias_output.state_controller_address(), output_response));
                            }
                        }
                    }
                }
                Output::Nft(nft_output) => {
                    // If the id is null then this output creates it and we can't have a previous output
                    if !nft_output.nft_id().is_null() {
                        let output_id = client.nft_output_id(*nft_output.nft_id()).await?;
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Nft(nft_output) = &output_response.output {
                            let nft_output = NftOutput::try_from_dto(nft_output, token_supply)?;

                            let unlock_address = nft_output
                                .unlock_conditions()
                                .locked_address(nft_output.address(), current_time);

                            utxo_chains.push((*unlock_address, output_response));
                        }
                    }
                }
                Output::Foundry(foundry_output) => {
                    // if it's the first foundry output, then we can't have it as input
                    if let Ok(output_id) = client.foundry_output_id(foundry_output.id()).await {
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Foundry(foundry_output_dto) = &output_response.output {
                            let foundry_output = FoundryOutput::try_from_dto(foundry_output_dto, token_supply)?;
                            utxo_chains.push((Address::Alias(*foundry_output.alias_address()), output_response));
                        }
                    }
                }
                _ => {}
            }
        }

        // Get recursively owned alias or nft outputs
        get_alias_and_nft_outputs_recursively(self.client, &mut utxo_chains).await?;

        let mut utxo_chain_inputs = Vec::new();
        for (unlock_address, output_response) in utxo_chains {
            let address_index_internal = match self.secret_manager {
                Some(secret_manager) => {
                    match unlock_address {
                        Address::Ed25519(_) => Some(
                            search_address(
                                secret_manager,
                                &bech32_hrp,
                                self.coin_type,
                                self.account_index,
                                self.input_range.clone(),
                                &unlock_address,
                            )
                            .await?,
                        ),
                        // Alias and NFT addresses can't be generated from a private key
                        _ => None,
                    }
                }
                // Assuming default for offline signing
                None => Some((0, false)),
            };

            utxo_chain_inputs.push(InputSigningData {
                output: Output::try_from_dto(&output_response.output, token_supply)?,
                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                chain: address_index_internal.map(|(address_index, internal)| {
                    Chain::from_u32_hardened(vec![
                        HD_WALLET_TYPE,
                        self.coin_type,
                        self.account_index,
                        internal as u32,
                        address_index,
                    ])
                }),
                bech32_address: unlock_address.to_bech32(&bech32_hrp),
            });
        }

        Ok(utxo_chain_inputs)
    }
src/api/block_builder/input_selection/sender_issuer.rs (line 99)
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
    pub(crate) async fn get_inputs_for_sender_and_issuer(
        &self,
        utxo_chain_inputs: &[InputSigningData],
    ) -> Result<Vec<InputSigningData>> {
        log::debug!("[get_inputs_for_sender_and_issuer]");

        let mut required_inputs = Vec::new();
        let bech32_hrp = self.client.get_bech32_hrp().await?;
        let current_time = self.client.get_time_checked().await?;
        let token_supply = self.client.get_token_supply().await?;

        let required_sender_or_issuer_addresses =
            get_required_addresses_for_sender_and_issuer(&[], &self.outputs, current_time)?;

        for sender_or_issuer_address in required_sender_or_issuer_addresses {
            match sender_or_issuer_address {
                Address::Ed25519(_) => {
                    // Check if the address is derived from the seed
                    let (address_index, internal) = search_address(
                        self.secret_manager.ok_or(Error::MissingParameter("secret manager"))?,
                        &bech32_hrp,
                        self.coin_type,
                        self.account_index,
                        self.input_range.clone(),
                        &sender_or_issuer_address,
                    )
                    .await?;
                    let address_outputs = self
                        .basic_address_outputs(sender_or_issuer_address.to_bech32(&bech32_hrp))
                        .await?;

                    let mut found_output = false;
                    for output_response in address_outputs {
                        let output = Output::try_from_dto(&output_response.output, token_supply)?;

                        // We can ignore the unlocked_alias_or_nft_address, since we only requested basic outputs
                        let (required_unlock_address, _unlocked_alias_or_nft_address) = output
                            .required_and_unlocked_address(
                                current_time,
                                &output_response.metadata.output_id()?,
                                false,
                            )?;

                        if required_unlock_address == sender_or_issuer_address {
                            required_inputs.push(InputSigningData {
                                output,
                                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                                chain: Some(Chain::from_u32_hardened(vec![
                                    HD_WALLET_TYPE,
                                    self.coin_type,
                                    self.account_index,
                                    internal as u32,
                                    address_index,
                                ])),
                                bech32_address: sender_or_issuer_address.to_bech32(&bech32_hrp),
                            });
                            found_output = true;
                            break;
                        }
                    }

                    if !found_output {
                        return Err(Error::MissingInputWithEd25519Address);
                    }
                }
                Address::Alias(alias_address) => {
                    // Check if output is alias address.
                    let alias_id = alias_address.alias_id();

                    // check if already found or request new.
                    if !utxo_chain_inputs.iter().chain(required_inputs.iter()).any(|input| {
                        if let Output::Alias(alias_output) = &input.output {
                            alias_id == alias_output.alias_id()
                        } else {
                            false
                        }
                    }) {
                        let output_id = self.client.alias_output_id(*alias_id).await?;
                        let output_response = self.client.get_output(&output_id).await?;
                        if let OutputDto::Alias(alias_output_dto) = &output_response.output {
                            let alias_output = AliasOutput::try_from_dto(alias_output_dto, token_supply)?;
                            // State transition if we add them to inputs
                            let unlock_address = alias_output.state_controller_address();
                            let address_index_internal = match self.secret_manager {
                                Some(secret_manager) => {
                                    match unlock_address {
                                        Address::Ed25519(_) => Some(
                                            search_address(
                                                secret_manager,
                                                &bech32_hrp,
                                                self.coin_type,
                                                self.account_index,
                                                self.input_range.clone(),
                                                unlock_address,
                                            )
                                            .await?,
                                        ),
                                        // Alias and NFT addresses can't be generated from a private key
                                        _ => None,
                                    }
                                }
                                // Assuming default for offline signing
                                None => Some((0, false)),
                            };

                            required_inputs.push(InputSigningData {
                                output: Output::try_from_dto(&output_response.output, token_supply)?,
                                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                                chain: address_index_internal.map(|(address_index, internal)| {
                                    Chain::from_u32_hardened(vec![
                                        HD_WALLET_TYPE,
                                        self.coin_type,
                                        self.account_index,
                                        internal as u32,
                                        address_index,
                                    ])
                                }),
                                bech32_address: unlock_address.to_bech32(&bech32_hrp),
                            });
                        }
                    }
                }
                Address::Nft(nft_address) => {
                    // Check if output is nft address.
                    let nft_id = nft_address.nft_id();

                    // Check if already found or request new.
                    if !utxo_chain_inputs.iter().chain(required_inputs.iter()).any(|input| {
                        if let Output::Nft(nft_output) = &input.output {
                            nft_id == nft_output.nft_id()
                        } else {
                            false
                        }
                    }) {
                        let output_id = self.client.nft_output_id(*nft_id).await?;
                        let output_response = self.client.get_output(&output_id).await?;
                        if let OutputDto::Nft(nft_output) = &output_response.output {
                            let nft_output = NftOutput::try_from_dto(nft_output, token_supply)?;

                            let unlock_address = nft_output
                                .unlock_conditions()
                                .locked_address(nft_output.address(), current_time);

                            let address_index_internal = match self.secret_manager {
                                Some(secret_manager) => {
                                    match unlock_address {
                                        Address::Ed25519(_) => Some(
                                            search_address(
                                                secret_manager,
                                                &bech32_hrp,
                                                self.coin_type,
                                                self.account_index,
                                                self.input_range.clone(),
                                                unlock_address,
                                            )
                                            .await?,
                                        ),
                                        // Alias and NFT addresses can't be generated from a private key.
                                        _ => None,
                                    }
                                }
                                // Assuming default for offline signing.
                                None => Some((0, false)),
                            };

                            required_inputs.push(InputSigningData {
                                output: Output::try_from_dto(&output_response.output, token_supply)?,
                                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                                chain: address_index_internal.map(|(address_index, internal)| {
                                    Chain::from_u32_hardened(vec![
                                        HD_WALLET_TYPE,
                                        self.coin_type,
                                        self.account_index,
                                        internal as u32,
                                        address_index,
                                    ])
                                }),
                                bech32_address: unlock_address.to_bech32(&bech32_hrp),
                            });
                        }
                    }
                }
            }
        }

        // Check required Alias and NFT outputs with new added outputs.
        // No need to check for sender and issuer again, since these outputs already exist and we don't set new features
        // for them.
        let utxo_chain_inputs = self
            .get_utxo_chains_inputs(required_inputs.iter().map(|i| &i.output))
            .await?;
        required_inputs.extend(utxo_chain_inputs.into_iter());

        Ok(required_inputs)
    }

Get foundry outputs filtered by the given parameters. GET with query parameter returns all outputIDs that fit these filter criteria. Query parameters: “address”, “createdBefore”, “createdAfter” Returns an empty list if no results are found. api/indexer/v1/outputs/foundry

Get foundry output by its foundryID. api/indexer/v1/outputs/foundry/:{FoundryID}

Examples found in repository?
src/api/block_builder/input_selection/utxo_chains/automatic.rs (line 77)
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
    pub(crate) async fn get_utxo_chains_inputs(
        &self,
        outputs: impl Iterator<Item = &'a Output> + Clone,
    ) -> Result<Vec<InputSigningData>> {
        log::debug!("[get_utxo_chains_inputs]");
        let client = self.client;
        let bech32_hrp = client.get_bech32_hrp().await?;
        let current_time = self.client.get_time_checked().await?;
        let token_supply = client.get_token_supply().await?;

        let mut utxo_chains: Vec<(Address, OutputWithMetadataResponse)> = Vec::new();
        for output in outputs {
            match output {
                Output::Alias(alias_output) => {
                    // if the alias id is null then there can't be a previous output and it can also not be a
                    // governance transition
                    if !alias_output.alias_id().is_null() {
                        // Check if the transaction is a governance_transition, by checking if the new index is the same
                        // as the previous index
                        let output_id = client.alias_output_id(*alias_output.alias_id()).await?;
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Alias(alias_output_dto) = &output_response.output {
                            let alias_output = AliasOutput::try_from_dto(alias_output_dto, token_supply)?;

                            // A governance transition is identified by an unchanged State Index in next
                            // state.
                            if alias_output.state_index() == alias_output.state_index() {
                                utxo_chains.push((*alias_output.governor_address(), output_response));
                            } else {
                                utxo_chains.push((*alias_output.state_controller_address(), output_response));
                            }
                        }
                    }
                }
                Output::Nft(nft_output) => {
                    // If the id is null then this output creates it and we can't have a previous output
                    if !nft_output.nft_id().is_null() {
                        let output_id = client.nft_output_id(*nft_output.nft_id()).await?;
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Nft(nft_output) = &output_response.output {
                            let nft_output = NftOutput::try_from_dto(nft_output, token_supply)?;

                            let unlock_address = nft_output
                                .unlock_conditions()
                                .locked_address(nft_output.address(), current_time);

                            utxo_chains.push((*unlock_address, output_response));
                        }
                    }
                }
                Output::Foundry(foundry_output) => {
                    // if it's the first foundry output, then we can't have it as input
                    if let Ok(output_id) = client.foundry_output_id(foundry_output.id()).await {
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Foundry(foundry_output_dto) = &output_response.output {
                            let foundry_output = FoundryOutput::try_from_dto(foundry_output_dto, token_supply)?;
                            utxo_chains.push((Address::Alias(*foundry_output.alias_address()), output_response));
                        }
                    }
                }
                _ => {}
            }
        }

        // Get recursively owned alias or nft outputs
        get_alias_and_nft_outputs_recursively(self.client, &mut utxo_chains).await?;

        let mut utxo_chain_inputs = Vec::new();
        for (unlock_address, output_response) in utxo_chains {
            let address_index_internal = match self.secret_manager {
                Some(secret_manager) => {
                    match unlock_address {
                        Address::Ed25519(_) => Some(
                            search_address(
                                secret_manager,
                                &bech32_hrp,
                                self.coin_type,
                                self.account_index,
                                self.input_range.clone(),
                                &unlock_address,
                            )
                            .await?,
                        ),
                        // Alias and NFT addresses can't be generated from a private key
                        _ => None,
                    }
                }
                // Assuming default for offline signing
                None => Some((0, false)),
            };

            utxo_chain_inputs.push(InputSigningData {
                output: Output::try_from_dto(&output_response.output, token_supply)?,
                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                chain: address_index_internal.map(|(address_index, internal)| {
                    Chain::from_u32_hardened(vec![
                        HD_WALLET_TYPE,
                        self.coin_type,
                        self.account_index,
                        internal as u32,
                        address_index,
                    ])
                }),
                bech32_address: unlock_address.to_bech32(&bech32_hrp),
            });
        }

        Ok(utxo_chain_inputs)
    }

Get NFT outputs filtered by the given parameters. Query parameters: “address”, “hasStorageDepositReturn”, “storageDepositReturnAddress”, “hasExpiration”, “expiresBefore”, “expiresAfter”, “hasTimelock”, “timelockedBefore”, “timelockedAfter”, “issuer”, “sender”, “tag”, “createdBefore”, “createdAfter” Returns an empty list if no results are found. api/indexer/v1/outputs/nft

Get NFT output by its nftID. api/indexer/v1/outputs/nft/:{NftId}

Examples found in repository?
src/api/block_builder/input_selection/utxo_chains/mod.rs (line 351)
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
pub(crate) async fn get_alias_and_nft_outputs_recursively(
    client: &Client,
    utxo_chains: &mut Vec<(Address, OutputWithMetadataResponse)>,
) -> Result<()> {
    log::debug!("[get_alias_and_nft_outputs_recursively]");
    let current_time = client.get_time_checked().await?;
    let token_supply = client.get_token_supply().await?;

    let mut processed_alias_nft_addresses = std::collections::HashSet::new();

    // Add addresses for alias and nft outputs we already have
    for (_unlock_address, output_response) in utxo_chains.iter() {
        let output_id = OutputId::new(
            TransactionId::from_str(&output_response.metadata.transaction_id)?,
            output_response.metadata.output_index,
        )?;

        match Output::try_from_dto(&output_response.output, token_supply)? {
            Output::Alias(alias_output) => {
                processed_alias_nft_addresses.insert(Address::Alias(alias_output.alias_address(&output_id)));
            }
            Output::Nft(nft_output) => {
                processed_alias_nft_addresses.insert(Address::Nft(nft_output.nft_address(&output_id)));
            }
            _ => {}
        }
    }

    let mut processed_utxo_chains = Vec::new();

    // Make the outputs response optional, because we don't know it yet for new required outputs
    let mut utxo_chain_optional_response: Vec<(Address, Option<OutputWithMetadataResponse>)> =
        utxo_chains.iter_mut().map(|(a, o)| (*a, Some(o.clone()))).collect();

    // Get alias or nft addresses when needed or just add the input again
    while let Some((unlock_address, output_response)) = utxo_chain_optional_response.pop() {
        // Don't request outputs for addresses where we already have the output
        if processed_alias_nft_addresses.insert(unlock_address) {
            match unlock_address {
                Address::Alias(address) => {
                    let output_id = client.alias_output_id(*address.alias_id()).await?;
                    let output_response = client.get_output(&output_id).await?;
                    if let OutputDto::Alias(alias_output_dto) = &output_response.output {
                        let alias_output = AliasOutput::try_from_dto(alias_output_dto, token_supply)?;
                        // State transition if we add them to inputs
                        let alias_unlock_address = alias_output.state_controller_address();
                        // Add address to unprocessed_alias_nft_addresses so we get the required output there
                        // also
                        if alias_unlock_address.is_alias() || alias_unlock_address.is_nft() {
                            utxo_chain_optional_response.push((*alias_unlock_address, None));
                        }
                        processed_utxo_chains.push((*alias_unlock_address, output_response));
                    }
                }
                Address::Nft(address) => {
                    let output_id = client.nft_output_id(*address.nft_id()).await?;
                    let output_response = client.get_output(&output_id).await?;
                    if let OutputDto::Nft(nft_output) = &output_response.output {
                        let nft_output = NftOutput::try_from_dto(nft_output, token_supply)?;
                        let unlock_address = nft_output
                            .unlock_conditions()
                            .locked_address(nft_output.address(), current_time);
                        // Add address to unprocessed_alias_nft_addresses so we get the required output there also
                        if unlock_address.is_alias() || unlock_address.is_nft() {
                            utxo_chain_optional_response.push((*unlock_address, None));
                        }
                        processed_utxo_chains.push((*unlock_address, output_response));
                    }
                }
                _ => {}
            }
        }

        // Add if the output_response is available
        if let Some(output_response) = output_response {
            processed_utxo_chains.push((unlock_address, output_response));
        }
    }

    *utxo_chains = processed_utxo_chains;

    Ok(())
}
More examples
Hide additional examples
src/api/block_builder/input_selection/utxo_chains/automatic.rs (line 62)
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
    pub(crate) async fn get_utxo_chains_inputs(
        &self,
        outputs: impl Iterator<Item = &'a Output> + Clone,
    ) -> Result<Vec<InputSigningData>> {
        log::debug!("[get_utxo_chains_inputs]");
        let client = self.client;
        let bech32_hrp = client.get_bech32_hrp().await?;
        let current_time = self.client.get_time_checked().await?;
        let token_supply = client.get_token_supply().await?;

        let mut utxo_chains: Vec<(Address, OutputWithMetadataResponse)> = Vec::new();
        for output in outputs {
            match output {
                Output::Alias(alias_output) => {
                    // if the alias id is null then there can't be a previous output and it can also not be a
                    // governance transition
                    if !alias_output.alias_id().is_null() {
                        // Check if the transaction is a governance_transition, by checking if the new index is the same
                        // as the previous index
                        let output_id = client.alias_output_id(*alias_output.alias_id()).await?;
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Alias(alias_output_dto) = &output_response.output {
                            let alias_output = AliasOutput::try_from_dto(alias_output_dto, token_supply)?;

                            // A governance transition is identified by an unchanged State Index in next
                            // state.
                            if alias_output.state_index() == alias_output.state_index() {
                                utxo_chains.push((*alias_output.governor_address(), output_response));
                            } else {
                                utxo_chains.push((*alias_output.state_controller_address(), output_response));
                            }
                        }
                    }
                }
                Output::Nft(nft_output) => {
                    // If the id is null then this output creates it and we can't have a previous output
                    if !nft_output.nft_id().is_null() {
                        let output_id = client.nft_output_id(*nft_output.nft_id()).await?;
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Nft(nft_output) = &output_response.output {
                            let nft_output = NftOutput::try_from_dto(nft_output, token_supply)?;

                            let unlock_address = nft_output
                                .unlock_conditions()
                                .locked_address(nft_output.address(), current_time);

                            utxo_chains.push((*unlock_address, output_response));
                        }
                    }
                }
                Output::Foundry(foundry_output) => {
                    // if it's the first foundry output, then we can't have it as input
                    if let Ok(output_id) = client.foundry_output_id(foundry_output.id()).await {
                        let output_response = client.get_output(&output_id).await?;
                        if let OutputDto::Foundry(foundry_output_dto) = &output_response.output {
                            let foundry_output = FoundryOutput::try_from_dto(foundry_output_dto, token_supply)?;
                            utxo_chains.push((Address::Alias(*foundry_output.alias_address()), output_response));
                        }
                    }
                }
                _ => {}
            }
        }

        // Get recursively owned alias or nft outputs
        get_alias_and_nft_outputs_recursively(self.client, &mut utxo_chains).await?;

        let mut utxo_chain_inputs = Vec::new();
        for (unlock_address, output_response) in utxo_chains {
            let address_index_internal = match self.secret_manager {
                Some(secret_manager) => {
                    match unlock_address {
                        Address::Ed25519(_) => Some(
                            search_address(
                                secret_manager,
                                &bech32_hrp,
                                self.coin_type,
                                self.account_index,
                                self.input_range.clone(),
                                &unlock_address,
                            )
                            .await?,
                        ),
                        // Alias and NFT addresses can't be generated from a private key
                        _ => None,
                    }
                }
                // Assuming default for offline signing
                None => Some((0, false)),
            };

            utxo_chain_inputs.push(InputSigningData {
                output: Output::try_from_dto(&output_response.output, token_supply)?,
                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                chain: address_index_internal.map(|(address_index, internal)| {
                    Chain::from_u32_hardened(vec![
                        HD_WALLET_TYPE,
                        self.coin_type,
                        self.account_index,
                        internal as u32,
                        address_index,
                    ])
                }),
                bech32_address: unlock_address.to_bech32(&bech32_hrp),
            });
        }

        Ok(utxo_chain_inputs)
    }
src/api/block_builder/input_selection/sender_issuer.rs (line 156)
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
    pub(crate) async fn get_inputs_for_sender_and_issuer(
        &self,
        utxo_chain_inputs: &[InputSigningData],
    ) -> Result<Vec<InputSigningData>> {
        log::debug!("[get_inputs_for_sender_and_issuer]");

        let mut required_inputs = Vec::new();
        let bech32_hrp = self.client.get_bech32_hrp().await?;
        let current_time = self.client.get_time_checked().await?;
        let token_supply = self.client.get_token_supply().await?;

        let required_sender_or_issuer_addresses =
            get_required_addresses_for_sender_and_issuer(&[], &self.outputs, current_time)?;

        for sender_or_issuer_address in required_sender_or_issuer_addresses {
            match sender_or_issuer_address {
                Address::Ed25519(_) => {
                    // Check if the address is derived from the seed
                    let (address_index, internal) = search_address(
                        self.secret_manager.ok_or(Error::MissingParameter("secret manager"))?,
                        &bech32_hrp,
                        self.coin_type,
                        self.account_index,
                        self.input_range.clone(),
                        &sender_or_issuer_address,
                    )
                    .await?;
                    let address_outputs = self
                        .basic_address_outputs(sender_or_issuer_address.to_bech32(&bech32_hrp))
                        .await?;

                    let mut found_output = false;
                    for output_response in address_outputs {
                        let output = Output::try_from_dto(&output_response.output, token_supply)?;

                        // We can ignore the unlocked_alias_or_nft_address, since we only requested basic outputs
                        let (required_unlock_address, _unlocked_alias_or_nft_address) = output
                            .required_and_unlocked_address(
                                current_time,
                                &output_response.metadata.output_id()?,
                                false,
                            )?;

                        if required_unlock_address == sender_or_issuer_address {
                            required_inputs.push(InputSigningData {
                                output,
                                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                                chain: Some(Chain::from_u32_hardened(vec![
                                    HD_WALLET_TYPE,
                                    self.coin_type,
                                    self.account_index,
                                    internal as u32,
                                    address_index,
                                ])),
                                bech32_address: sender_or_issuer_address.to_bech32(&bech32_hrp),
                            });
                            found_output = true;
                            break;
                        }
                    }

                    if !found_output {
                        return Err(Error::MissingInputWithEd25519Address);
                    }
                }
                Address::Alias(alias_address) => {
                    // Check if output is alias address.
                    let alias_id = alias_address.alias_id();

                    // check if already found or request new.
                    if !utxo_chain_inputs.iter().chain(required_inputs.iter()).any(|input| {
                        if let Output::Alias(alias_output) = &input.output {
                            alias_id == alias_output.alias_id()
                        } else {
                            false
                        }
                    }) {
                        let output_id = self.client.alias_output_id(*alias_id).await?;
                        let output_response = self.client.get_output(&output_id).await?;
                        if let OutputDto::Alias(alias_output_dto) = &output_response.output {
                            let alias_output = AliasOutput::try_from_dto(alias_output_dto, token_supply)?;
                            // State transition if we add them to inputs
                            let unlock_address = alias_output.state_controller_address();
                            let address_index_internal = match self.secret_manager {
                                Some(secret_manager) => {
                                    match unlock_address {
                                        Address::Ed25519(_) => Some(
                                            search_address(
                                                secret_manager,
                                                &bech32_hrp,
                                                self.coin_type,
                                                self.account_index,
                                                self.input_range.clone(),
                                                unlock_address,
                                            )
                                            .await?,
                                        ),
                                        // Alias and NFT addresses can't be generated from a private key
                                        _ => None,
                                    }
                                }
                                // Assuming default for offline signing
                                None => Some((0, false)),
                            };

                            required_inputs.push(InputSigningData {
                                output: Output::try_from_dto(&output_response.output, token_supply)?,
                                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                                chain: address_index_internal.map(|(address_index, internal)| {
                                    Chain::from_u32_hardened(vec![
                                        HD_WALLET_TYPE,
                                        self.coin_type,
                                        self.account_index,
                                        internal as u32,
                                        address_index,
                                    ])
                                }),
                                bech32_address: unlock_address.to_bech32(&bech32_hrp),
                            });
                        }
                    }
                }
                Address::Nft(nft_address) => {
                    // Check if output is nft address.
                    let nft_id = nft_address.nft_id();

                    // Check if already found or request new.
                    if !utxo_chain_inputs.iter().chain(required_inputs.iter()).any(|input| {
                        if let Output::Nft(nft_output) = &input.output {
                            nft_id == nft_output.nft_id()
                        } else {
                            false
                        }
                    }) {
                        let output_id = self.client.nft_output_id(*nft_id).await?;
                        let output_response = self.client.get_output(&output_id).await?;
                        if let OutputDto::Nft(nft_output) = &output_response.output {
                            let nft_output = NftOutput::try_from_dto(nft_output, token_supply)?;

                            let unlock_address = nft_output
                                .unlock_conditions()
                                .locked_address(nft_output.address(), current_time);

                            let address_index_internal = match self.secret_manager {
                                Some(secret_manager) => {
                                    match unlock_address {
                                        Address::Ed25519(_) => Some(
                                            search_address(
                                                secret_manager,
                                                &bech32_hrp,
                                                self.coin_type,
                                                self.account_index,
                                                self.input_range.clone(),
                                                unlock_address,
                                            )
                                            .await?,
                                        ),
                                        // Alias and NFT addresses can't be generated from a private key.
                                        _ => None,
                                    }
                                }
                                // Assuming default for offline signing.
                                None => Some((0, false)),
                            };

                            required_inputs.push(InputSigningData {
                                output: Output::try_from_dto(&output_response.output, token_supply)?,
                                output_metadata: OutputMetadata::try_from(&output_response.metadata)?,
                                chain: address_index_internal.map(|(address_index, internal)| {
                                    Chain::from_u32_hardened(vec![
                                        HD_WALLET_TYPE,
                                        self.coin_type,
                                        self.account_index,
                                        internal as u32,
                                        address_index,
                                    ])
                                }),
                                bech32_address: unlock_address.to_bech32(&bech32_hrp),
                            });
                        }
                    }
                }
            }
        }

        // Check required Alias and NFT outputs with new added outputs.
        // No need to check for sender and issuer again, since these outputs already exist and we don't set new features
        // for them.
        let utxo_chain_inputs = self
            .get_utxo_chains_inputs(required_inputs.iter().map(|i| &i.output))
            .await?;
        required_inputs.extend(utxo_chain_inputs.into_iter());

        Ok(required_inputs)
    }

Get all output ids for a provided URL route and query parameters.

Examples found in repository?
src/node_api/indexer/routes.rs (line 56)
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
    pub async fn basic_output_ids(&self, query_parameters: Vec<QueryParameter>) -> Result<Vec<OutputId>> {
        let route = "api/indexer/v1/outputs/basic";

        verify_query_parameters!(
            query_parameters,
            QueryParameter::Address,
            QueryParameter::HasNativeTokens,
            QueryParameter::MinNativeTokenCount,
            QueryParameter::MaxNativeTokenCount,
            QueryParameter::HasStorageDepositReturn,
            QueryParameter::StorageDepositReturnAddress,
            QueryParameter::HasTimelock,
            QueryParameter::TimelockedBefore,
            QueryParameter::TimelockedAfter,
            QueryParameter::HasExpiration,
            QueryParameter::ExpiresBefore,
            QueryParameter::ExpiresAfter,
            QueryParameter::ExpirationReturnAddress,
            QueryParameter::Sender,
            QueryParameter::Tag,
            QueryParameter::CreatedBefore,
            QueryParameter::CreatedAfter,
            QueryParameter::PageSize,
            QueryParameter::Cursor
        )?;

        self.get_output_ids_with_pagination(route, query_parameters, true, false)
            .await
    }

    /// Get alias outputs filtered by the given parameters.
    /// GET with query parameter returns all outputIDs that fit these filter criteria.
    /// Query parameters: "stateController", "governor", "issuer", "sender", "createdBefore", "createdAfter"
    /// Returns an empty list if no results are found.
    /// api/indexer/v1/outputs/alias
    pub async fn alias_output_ids(&self, query_parameters: Vec<QueryParameter>) -> Result<Vec<OutputId>> {
        let route = "api/indexer/v1/outputs/alias";

        verify_query_parameters!(
            query_parameters,
            QueryParameter::StateController,
            QueryParameter::Governor,
            QueryParameter::Issuer,
            QueryParameter::Sender,
            QueryParameter::HasNativeTokens,
            QueryParameter::MinNativeTokenCount,
            QueryParameter::MaxNativeTokenCount,
            QueryParameter::CreatedBefore,
            QueryParameter::CreatedAfter,
            QueryParameter::PageSize,
            QueryParameter::Cursor
        )?;

        self.get_output_ids_with_pagination(route, query_parameters, true, false)
            .await
    }

    /// Get alias output by its aliasID.
    /// api/indexer/v1/outputs/alias/:{AliasId}
    pub async fn alias_output_id(&self, alias_id: AliasId) -> Result<OutputId> {
        let route = format!("api/indexer/v1/outputs/alias/{alias_id}");

        Ok(*(self
            .get_output_ids_with_pagination(&route, Vec::new(), true, false)
            .await?
            .first()
            .ok_or_else(|| crate::Error::NodeError("no output id for alias".to_string()))?))
    }

    /// Get foundry outputs filtered by the given parameters.
    /// GET with query parameter returns all outputIDs that fit these filter criteria.
    /// Query parameters: "address", "createdBefore", "createdAfter"
    /// Returns an empty list if no results are found.
    /// api/indexer/v1/outputs/foundry
    pub async fn foundry_output_ids(&self, query_parameters: Vec<QueryParameter>) -> Result<Vec<OutputId>> {
        let route = "api/indexer/v1/outputs/foundry";

        verify_query_parameters!(
            query_parameters,
            QueryParameter::AliasAddress,
            QueryParameter::HasNativeTokens,
            QueryParameter::MinNativeTokenCount,
            QueryParameter::MaxNativeTokenCount,
            QueryParameter::CreatedBefore,
            QueryParameter::CreatedAfter,
            QueryParameter::PageSize,
            QueryParameter::Cursor
        )?;

        self.get_output_ids_with_pagination(route, query_parameters, true, false)
            .await
    }

    /// Get foundry output by its foundryID.
    /// api/indexer/v1/outputs/foundry/:{FoundryID}
    pub async fn foundry_output_id(&self, foundry_id: FoundryId) -> Result<OutputId> {
        let route = format!("api/indexer/v1/outputs/foundry/{foundry_id}");

        Ok(*(self
            .get_output_ids_with_pagination(&route, Vec::new(), true, false)
            .await?
            .first()
            .ok_or_else(|| crate::Error::NodeError("no output id for foundry".to_string()))?))
    }

    /// Get NFT outputs filtered by the given parameters.
    /// Query parameters: "address", "hasStorageDepositReturn", "storageDepositReturnAddress",
    /// "hasExpiration", "expiresBefore", "expiresAfter", "hasTimelock", "timelockedBefore",
    /// "timelockedAfter", "issuer", "sender", "tag", "createdBefore", "createdAfter"
    /// Returns an empty list if no results are found.
    /// api/indexer/v1/outputs/nft
    pub async fn nft_output_ids(&self, query_parameters: Vec<QueryParameter>) -> Result<Vec<OutputId>> {
        let route = "api/indexer/v1/outputs/nft";

        verify_query_parameters!(
            query_parameters,
            QueryParameter::Address,
            QueryParameter::HasNativeTokens,
            QueryParameter::MinNativeTokenCount,
            QueryParameter::MaxNativeTokenCount,
            QueryParameter::HasStorageDepositReturn,
            QueryParameter::StorageDepositReturnAddress,
            QueryParameter::HasTimelock,
            QueryParameter::TimelockedBefore,
            QueryParameter::TimelockedAfter,
            QueryParameter::HasExpiration,
            QueryParameter::ExpiresBefore,
            QueryParameter::ExpiresAfter,
            QueryParameter::ExpirationReturnAddress,
            QueryParameter::Sender,
            QueryParameter::Tag,
            QueryParameter::CreatedBefore,
            QueryParameter::CreatedAfter,
            QueryParameter::PageSize,
            QueryParameter::Cursor
        )?;

        self.get_output_ids_with_pagination(route, query_parameters, true, false)
            .await
    }

    /// Get NFT output by its nftID.
    /// api/indexer/v1/outputs/nft/:{NftId}
    pub async fn nft_output_id(&self, nft_id: NftId) -> Result<OutputId> {
        let route = format!("api/indexer/v1/outputs/nft/{nft_id}");

        Ok(*(self
            .get_output_ids_with_pagination(&route, Vec::new(), true, false)
            .await?
            .first()
            .ok_or_else(|| crate::Error::NodeError("no output id for nft".to_string()))?))
    }

Get a node candidate from the healthy node pool.

returns the unhealthy nodes.

Transforms bech32 to hex

Transforms a hex encoded address to a bech32 encoded address

Transforms an alias id to a bech32 encoded address

Transforms an nft id to a bech32 encoded address

Transforms a hex encoded public key to a bech32 encoded address

Returns a valid Address parsed from a String.

Checks if a String is a valid bech32 encoded address.

Generates a new mnemonic.

Returns a seed for a mnemonic.

Examples found in repository?
src/secret/mnemonic.rs (line 94)
93
94
95
    pub fn try_from_mnemonic(mnemonic: &str) -> Result<Self> {
        Ok(Self(Client::mnemonic_to_seed(mnemonic)?))
    }

Returns a hex encoded seed for a mnemonic.

UTF-8 encodes the tag of a given TaggedDataPayload.

Examples found in repository?
src/utils.rs (line 192)
191
192
193
    pub fn tagged_data_to_utf8(payload: &TaggedDataPayload) -> Result<(String, String)> {
        Ok((Client::tag_to_utf8(payload)?, Client::data_to_utf8(payload)?))
    }

UTF-8 encodes the data of a given TaggedDataPayload.

Examples found in repository?
src/utils.rs (line 192)
191
192
193
    pub fn tagged_data_to_utf8(payload: &TaggedDataPayload) -> Result<(String, String)> {
        Ok((Client::tag_to_utf8(payload)?, Client::data_to_utf8(payload)?))
    }

UTF-8 encodes both the tag and data of a given TaggedDataPayload.

Trait Implementations§

Returns a copy of the value. Read more
Performs copy-assignment from source. Read more
Formats the value using the given formatter. Read more

Gracefully shutdown the Client

Auto Trait Implementations§

Blanket Implementations§

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Instruments this type with the current Span, returning an Instrumented wrapper. Read more

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Should always be Self
The resulting type after obtaining ownership.
Creates owned data from borrowed data, usually by cloning. Read more
Uses borrowed data to replace owned data, usually by cloning. Read more
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.
Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more