Struct iota_client::client::Client
source · pub struct Client { /* private fields */ }
Expand description
An instance of the client using HORNET or Bee URI
Implementations§
source§impl Client
impl Client
sourcepub async fn finish_block_builder(
&self,
parents: Option<Parents>,
payload: Option<Payload>
) -> Result<Block>
pub async fn finish_block_builder(
&self,
parents: Option<Parents>,
payload: Option<Payload>
) -> Result<Block>
Finishes the block with local PoW if needed. Without local PoW, it will finish the block with a 0 nonce.
Examples found in repository?
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
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
}
}
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)?)
}
sourcepub async fn finish_pow(
&self,
parents: Option<Parents>,
payload: Option<Payload>
) -> Result<Block>
pub async fn finish_pow(
&self,
parents: Option<Parents>,
payload: Option<Payload>
) -> Result<Block>
Calls the appropriate PoW function depending whether the compilation is for wasm or not.
Examples found in repository?
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()?)
}
}
source§impl Client
impl Client
sourcepub async fn consolidate_funds(
&self,
secret_manager: &SecretManager,
address_builder_options: GetAddressesBuilderOptions
) -> Result<String>
pub async fn consolidate_funds(
&self,
secret_manager: &SecretManager,
address_builder_options: GetAddressesBuilderOptions
) -> Result<String>
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
source§impl Client
impl Client
sourcepub async fn inputs_from_transaction_id(
&self,
transaction_id: &TransactionId
) -> Result<Vec<OutputWithMetadataResponse>>
pub async fn inputs_from_transaction_id(
&self,
transaction_id: &TransactionId
) -> Result<Vec<OutputWithMetadataResponse>>
Get the inputs of a transaction for the given transaction id.
sourcepub fn block(&self) -> ClientBlockBuilder<'_>
pub fn block(&self) -> ClientBlockBuilder<'_>
A generic send function for easily sending transaction or tagged data blocks.
Examples found in repository?
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)
}
sourcepub fn get_addresses<'a>(
&'a self,
secret_manager: &'a SecretManager
) -> GetAddressesBuilder<'a>
pub fn get_addresses<'a>(
&'a self,
secret_manager: &'a SecretManager
) -> GetAddressesBuilder<'a>
Return a list of addresses from a secret manager regardless of their validity.
Examples found in repository?
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
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)
}
sourcepub async fn find_blocks(&self, block_ids: &[BlockId]) -> Result<Vec<Block>>
pub async fn find_blocks(&self, block_ids: &[BlockId]) -> Result<Vec<Block>>
Find all blocks by provided block IDs.
sourcepub async fn retry(&self, block_id: &BlockId) -> Result<(BlockId, Block)>
pub async fn retry(&self, block_id: &BlockId) -> Result<(BlockId, Block)>
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.
sourcepub async fn retry_until_included(
&self,
block_id: &BlockId,
interval: Option<u64>,
max_attempts: Option<u64>
) -> Result<Vec<(BlockId, Block)>>
pub async fn retry_until_included(
&self,
block_id: &BlockId,
interval: Option<u64>,
max_attempts: Option<u64>
) -> Result<Vec<(BlockId, Block)>>
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?
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)
}
sourcepub async fn find_inputs(
&self,
addresses: Vec<String>,
amount: u64
) -> Result<Vec<UtxoInput>>
pub async fn find_inputs(
&self,
addresses: Vec<String>,
amount: u64
) -> Result<Vec<UtxoInput>>
Function to find inputs from addresses for a provided amount (useful for offline signing), ignoring outputs with additional unlock conditions
sourcepub async fn find_outputs(
&self,
output_ids: &[OutputId],
addresses: &[String]
) -> Result<Vec<OutputWithMetadataResponse>>
pub async fn find_outputs(
&self,
output_ids: &[OutputId],
addresses: &[String]
) -> Result<Vec<OutputWithMetadataResponse>>
Find all outputs based on the requests criteria. This method will try to query multiple nodes if the request amount exceeds individual node limit.
sourcepub async fn reattach(&self, block_id: &BlockId) -> Result<(BlockId, Block)>
pub async fn reattach(&self, block_id: &BlockId) -> Result<(BlockId, Block)>
Reattaches blocks for provided block id. Blocks can be reattached only if they are valid and haven’t been confirmed for a while.
sourcepub async fn reattach_unchecked(
&self,
block_id: &BlockId
) -> Result<(BlockId, Block)>
pub async fn reattach_unchecked(
&self,
block_id: &BlockId
) -> Result<(BlockId, Block)>
Reattach a block without checking if it should be reattached
Examples found in repository?
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()))
}
}
sourcepub async fn promote(&self, block_id: &BlockId) -> Result<(BlockId, Block)>
pub async fn promote(&self, block_id: &BlockId) -> Result<(BlockId, 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.
sourcepub async fn promote_unchecked(
&self,
block_id: &BlockId
) -> Result<(BlockId, Block)>
pub async fn promote_unchecked(
&self,
block_id: &BlockId
) -> Result<(BlockId, Block)>
Promote a block without checking if it should be promoted
Examples found in repository?
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()))
}
}
sourcepub async fn get_time_checked(&self) -> Result<u32>
pub async fn get_time_checked(&self) -> Result<u32>
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?
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
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)
}
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)
}
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(())
}
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)
}
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)
}
source§impl Client
impl Client
sourcepub fn builder() -> ClientBuilder
pub fn builder() -> ClientBuilder
Create the builder to instantiate the IOTA Client.
sourcepub async fn get_network_info(&self) -> Result<NetworkInfo>
pub async fn get_network_info(&self) -> Result<NetworkInfo>
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?
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
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(¤t_time)
{
return Err(Error::TimeNotSynced {
current_time,
milestone_timestamp: latest_ms_timestamp,
});
}
}
Ok(current_time)
}
sourcepub async fn get_protocol_parameters(&self) -> Result<ProtocolParameters>
pub async fn get_protocol_parameters(&self) -> Result<ProtocolParameters>
Gets the protocol parameters of the node we’re connecting to.
Examples found in repository?
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
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(®ular_essence)?;
let essence = TransactionEssence::Regular(regular_essence);
Ok(PreparedTransactionData {
essence,
inputs_data: selected_transaction_data.inputs,
remainder: selected_transaction_data.remainder,
})
}
sourcepub async fn get_protocol_version(&self) -> Result<u8>
pub async fn get_protocol_version(&self) -> Result<u8>
Gets the protocol version of the node we’re connecting to.
sourcepub async fn get_network_name(&self) -> Result<String>
pub async fn get_network_name(&self) -> Result<String>
Gets the network name of the node we’re connecting to.
sourcepub async fn get_network_id(&self) -> Result<u64>
pub async fn get_network_id(&self) -> Result<u64>
Gets the network id of the node we’re connecting to.
Examples found in repository?
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(®ular_essence)?;
let essence = TransactionEssence::Regular(regular_essence);
Ok(PreparedTransactionData {
essence,
inputs_data: selected_transaction_data.inputs,
remainder: selected_transaction_data.remainder,
})
}
sourcepub async fn get_bech32_hrp(&self) -> Result<String>
pub async fn get_bech32_hrp(&self) -> Result<String>
Gets the bech32 HRP of the node we’re connecting to.
Examples found in repository?
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
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(),
})
}
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)
}
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)
}
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)
}
sourcepub async fn get_min_pow_score(&self) -> Result<u32>
pub async fn get_min_pow_score(&self) -> Result<u32>
Gets the minimum pow score of the node we’re connecting to.
Examples found in repository?
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);
}
}
}
}
}
sourcepub async fn get_below_max_depth(&self) -> Result<u8>
pub async fn get_below_max_depth(&self) -> Result<u8>
Gets the below maximum depth of the node we’re connecting to.
sourcepub async fn get_rent_structure(&self) -> Result<RentStructure>
pub async fn get_rent_structure(&self) -> Result<RentStructure>
Gets the rent structure of the node we’re connecting to.
Examples found in repository?
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(®ular_essence)?;
let essence = TransactionEssence::Regular(regular_essence);
Ok(PreparedTransactionData {
essence,
inputs_data: selected_transaction_data.inputs,
remainder: selected_transaction_data.remainder,
})
}
sourcepub async fn get_token_supply(&self) -> Result<u64>
pub async fn get_token_supply(&self) -> Result<u64>
Gets the token supply of the node we’re connecting to.
Examples found in repository?
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
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)
}
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(®ular_essence)?;
let essence = TransactionEssence::Regular(regular_essence);
Ok(PreparedTransactionData {
essence,
inputs_data: selected_transaction_data.inputs,
remainder: selected_transaction_data.remainder,
})
}
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)
}
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(())
}
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)
}
sourcepub fn get_tips_interval(&self) -> u64
pub fn get_tips_interval(&self) -> u64
returns the tips interval
Examples found in repository?
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);
}
}
}
}
}
sourcepub fn get_local_pow(&self) -> bool
pub fn get_local_pow(&self) -> bool
returns if local pow should be used or not
Examples found in repository?
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
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()?)
}
}
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
}
}
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)?)
}
sourcepub fn get_fallback_to_local_pow(&self) -> bool
pub fn get_fallback_to_local_pow(&self) -> bool
returns the fallback_to_local_pow
Examples found in repository?
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)?)
}
source§impl Client
impl Client
sourcepub async fn get_health(&self, url: &str) -> Result<bool>
pub async fn get_health(&self, url: &str) -> Result<bool>
Returns the health of the node. GET /health
sourcepub async fn get_routes(&self) -> Result<RoutesResponse>
pub async fn get_routes(&self) -> Result<RoutesResponse>
Returns the available API route groups of the node. GET /api/routes
sourcepub async fn get_info(&self) -> Result<NodeInfoWrapper>
pub async fn get_info(&self) -> Result<NodeInfoWrapper>
Returns general information about the node. GET /api/core/v2/info
sourcepub async fn get_node_info(
url: &str,
auth: Option<NodeAuth>
) -> Result<InfoResponse>
pub async fn get_node_info(
url: &str,
auth: Option<NodeAuth>
) -> Result<InfoResponse>
GET /api/core/v2/info endpoint
Examples found in repository?
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(())
}
sourcepub async fn get_tips(&self) -> Result<Vec<BlockId>>
pub async fn get_tips(&self) -> Result<Vec<BlockId>>
Returns tips that are ideal for attaching a block. GET /api/core/v2/tips
Examples found in repository?
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
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))
}
sourcepub async fn post_block(&self, block: &Block) -> Result<BlockId>
pub async fn post_block(&self, block: &Block) -> Result<BlockId>
Returns the BlockId of the submitted block. POST JSON to /api/core/v2/blocks
sourcepub async fn post_block_raw(&self, block: &Block) -> Result<BlockId>
pub async fn post_block_raw(&self, block: &Block) -> Result<BlockId>
Returns the BlockId of the submitted block. POST /api/core/v2/blocks
Examples found in repository?
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
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
}
}
sourcepub async fn get_block(&self, block_id: &BlockId) -> Result<Block>
pub async fn get_block(&self, block_id: &BlockId) -> Result<Block>
Finds a block by its BlockId. This method returns the given block object. GET /api/core/v2/blocks/{BlockId}
Examples found in repository?
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
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
}
}
sourcepub async fn get_block_raw(&self, block_id: &BlockId) -> Result<Vec<u8>>
pub async fn get_block_raw(&self, block_id: &BlockId) -> Result<Vec<u8>>
Finds a block by its BlockId. This method returns the given block raw data. GET /api/core/v2/blocks/{BlockId}
sourcepub async fn get_block_metadata(
&self,
block_id: &BlockId
) -> Result<BlockMetadataResponse>
pub async fn get_block_metadata(
&self,
block_id: &BlockId
) -> Result<BlockMetadataResponse>
Returns the metadata of a block. GET /api/core/v2/blocks/{BlockId}/metadata
Examples found in repository?
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()))
}
}
sourcepub async fn get_output(
&self,
output_id: &OutputId
) -> Result<OutputWithMetadataResponse>
pub async fn get_output(
&self,
output_id: &OutputId
) -> Result<OutputWithMetadataResponse>
Finds an output, as JSON, by its OutputId (TransactionId + output_index). GET /api/core/v2/outputs/{outputId}
Examples found in repository?
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
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(®ular_essence)?;
let essence = TransactionEssence::Regular(regular_essence);
Ok(PreparedTransactionData {
essence,
inputs_data: selected_transaction_data.inputs,
remainder: selected_transaction_data.remainder,
})
}
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)
}
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(())
}
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)
}
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)
}
sourcepub async fn get_output_raw(&self, output_id: &OutputId) -> Result<Vec<u8>>
pub async fn get_output_raw(&self, output_id: &OutputId) -> Result<Vec<u8>>
Finds an output, as raw bytes, by its OutputId (TransactionId + output_index). GET /api/core/v2/outputs/{outputId}
sourcepub async fn get_output_metadata(
&self,
output_id: &OutputId
) -> Result<OutputMetadataResponse>
pub async fn get_output_metadata(
&self,
output_id: &OutputId
) -> Result<OutputMetadataResponse>
Get the metadata for a given OutputId
(TransactionId + output_index).
GET /api/core/v2/outputs/{outputId}/metadata
Examples found in repository?
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)
}
sourcepub async fn get_receipts(&self) -> Result<Vec<ReceiptDto>>
pub async fn get_receipts(&self) -> Result<Vec<ReceiptDto>>
Gets all stored receipts. GET /api/core/v2/receipts
sourcepub async fn get_receipts_migrated_at(
&self,
milestone_index: u32
) -> Result<Vec<ReceiptDto>>
pub async fn get_receipts_migrated_at(
&self,
milestone_index: u32
) -> Result<Vec<ReceiptDto>>
Gets the receipts by the given milestone index. GET /api/core/v2/receipts/{migratedAt}
sourcepub async fn get_treasury(&self) -> Result<TreasuryResponse>
pub async fn get_treasury(&self) -> Result<TreasuryResponse>
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
sourcepub async fn get_included_block(
&self,
transaction_id: &TransactionId
) -> Result<Block>
pub async fn get_included_block(
&self,
transaction_id: &TransactionId
) -> Result<Block>
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?
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()))
}
sourcepub async fn get_included_block_raw(
&self,
transaction_id: &TransactionId
) -> Result<Vec<u8>>
pub async fn get_included_block_raw(
&self,
transaction_id: &TransactionId
) -> Result<Vec<u8>>
Returns the block, as raw bytes, that was included in the ledger for a given TransactionId. GET /api/core/v2/transactions/{transactionId}/included-block
sourcepub async fn get_milestone_by_id(
&self,
milestone_id: &MilestoneId
) -> Result<MilestonePayload>
pub async fn get_milestone_by_id(
&self,
milestone_id: &MilestoneId
) -> Result<MilestonePayload>
Gets the milestone by the given milestone id. GET /api/core/v2/milestones/{milestoneId}
sourcepub async fn get_milestone_by_id_raw(
&self,
milestone_id: &MilestoneId
) -> Result<Vec<u8>>
pub async fn get_milestone_by_id_raw(
&self,
milestone_id: &MilestoneId
) -> Result<Vec<u8>>
Gets the milestone by the given milestone id. GET /api/core/v2/milestones/{milestoneId}
sourcepub async fn get_utxo_changes_by_id(
&self,
milestone_id: &MilestoneId
) -> Result<UtxoChangesResponse>
pub async fn get_utxo_changes_by_id(
&self,
milestone_id: &MilestoneId
) -> Result<UtxoChangesResponse>
Gets all UTXO changes of a milestone by its milestone id. GET /api/core/v2/milestones/{milestoneId}/utxo-changes
sourcepub async fn get_milestone_by_index(
&self,
index: u32
) -> Result<MilestonePayload>
pub async fn get_milestone_by_index(
&self,
index: u32
) -> Result<MilestonePayload>
Gets the milestone by the given milestone index. GET /api/core/v2/milestones/{index}
sourcepub async fn get_milestone_by_index_raw(&self, index: u32) -> Result<Vec<u8>>
pub async fn get_milestone_by_index_raw(&self, index: u32) -> Result<Vec<u8>>
Gets the milestone by the given milestone index. GET /api/core/v2/milestones/{index}
sourcepub async fn get_utxo_changes_by_index(
&self,
index: u32
) -> Result<UtxoChangesResponse>
pub async fn get_utxo_changes_by_index(
&self,
index: u32
) -> Result<UtxoChangesResponse>
Gets all UTXO changes of a milestone by its milestone index. GET /api/core/v2/milestones/by-index/{index}/utxo-changes
source§impl Client
impl Client
sourcepub async fn get_outputs(
&self,
output_ids: Vec<OutputId>
) -> Result<Vec<OutputWithMetadataResponse>>
pub async fn get_outputs(
&self,
output_ids: Vec<OutputId>
) -> Result<Vec<OutputWithMetadataResponse>>
Request outputs by their output ID in parallel
Examples found in repository?
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
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
}
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)
}
sourcepub async fn try_get_outputs(
&self,
output_ids: Vec<OutputId>
) -> Result<Vec<OutputWithMetadataResponse>>
pub async fn try_get_outputs(
&self,
output_ids: Vec<OutputId>
) -> Result<Vec<OutputWithMetadataResponse>>
Request outputs by their output ID in parallel, ignoring failed requests Useful to get data about spent outputs, that might not be pruned yet
sourcepub async fn try_get_outputs_metadata(
&self,
output_ids: Vec<OutputId>
) -> Result<Vec<OutputMetadataResponse>>
pub async fn try_get_outputs_metadata(
&self,
output_ids: Vec<OutputId>
) -> Result<Vec<OutputMetadataResponse>>
Requests metadata for outputs by their output ID in parallel, ignoring failed requests
source§impl Client
impl Client
sourcepub async fn basic_output_ids(
&self,
query_parameters: Vec<QueryParameter>
) -> Result<Vec<OutputId>>
pub async fn basic_output_ids(
&self,
query_parameters: Vec<QueryParameter>
) -> Result<Vec<OutputId>>
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?
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
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())
}
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)
}
sourcepub async fn alias_output_ids(
&self,
query_parameters: Vec<QueryParameter>
) -> Result<Vec<OutputId>>
pub async fn alias_output_ids(
&self,
query_parameters: Vec<QueryParameter>
) -> Result<Vec<OutputId>>
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
sourcepub async fn alias_output_id(&self, alias_id: AliasId) -> Result<OutputId>
pub async fn alias_output_id(&self, alias_id: AliasId) -> Result<OutputId>
Get alias output by its aliasID. api/indexer/v1/outputs/alias/:{AliasId}
Examples found in repository?
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(®ular_essence)?;
let essence = TransactionEssence::Regular(regular_essence);
Ok(PreparedTransactionData {
essence,
inputs_data: selected_transaction_data.inputs,
remainder: selected_transaction_data.remainder,
})
}
More examples
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(())
}
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)
}
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)
}
sourcepub async fn foundry_output_ids(
&self,
query_parameters: Vec<QueryParameter>
) -> Result<Vec<OutputId>>
pub async fn foundry_output_ids(
&self,
query_parameters: Vec<QueryParameter>
) -> Result<Vec<OutputId>>
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
sourcepub async fn foundry_output_id(&self, foundry_id: FoundryId) -> Result<OutputId>
pub async fn foundry_output_id(&self, foundry_id: FoundryId) -> Result<OutputId>
Get foundry output by its foundryID. api/indexer/v1/outputs/foundry/:{FoundryID}
Examples found in repository?
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)
}
sourcepub async fn nft_output_ids(
&self,
query_parameters: Vec<QueryParameter>
) -> Result<Vec<OutputId>>
pub async fn nft_output_ids(
&self,
query_parameters: Vec<QueryParameter>
) -> Result<Vec<OutputId>>
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
sourcepub async fn nft_output_id(&self, nft_id: NftId) -> Result<OutputId>
pub async fn nft_output_id(&self, nft_id: NftId) -> Result<OutputId>
Get NFT output by its nftID. api/indexer/v1/outputs/nft/:{NftId}
Examples found in repository?
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
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)
}
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)
}
source§impl Client
impl Client
sourcepub async fn get_output_ids_with_pagination(
&self,
route: &str,
query_parameters: Vec<QueryParameter>,
need_quorum: bool,
prefer_permanode: bool
) -> Result<Vec<OutputId>>
pub async fn get_output_ids_with_pagination(
&self,
route: &str,
query_parameters: Vec<QueryParameter>,
need_quorum: bool,
prefer_permanode: bool
) -> Result<Vec<OutputId>>
Get all output ids for a provided URL route and query parameters.
Examples found in repository?
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()))?))
}
source§impl Client
impl Client
sourcepub fn bech32_to_hex(bech32: &str) -> Result<String>
pub fn bech32_to_hex(bech32: &str) -> Result<String>
Transforms bech32 to hex
sourcepub async fn hex_to_bech32(
&self,
hex: &str,
bech32_hrp: Option<&str>
) -> Result<String>
pub async fn hex_to_bech32(
&self,
hex: &str,
bech32_hrp: Option<&str>
) -> Result<String>
Transforms a hex encoded address to a bech32 encoded address
sourcepub async fn alias_id_to_bech32(
&self,
alias_id: AliasId,
bech32_hrp: Option<&str>
) -> Result<String>
pub async fn alias_id_to_bech32(
&self,
alias_id: AliasId,
bech32_hrp: Option<&str>
) -> Result<String>
Transforms an alias id to a bech32 encoded address
sourcepub async fn nft_id_to_bech32(
&self,
nft_id: NftId,
bech32_hrp: Option<&str>
) -> Result<String>
pub async fn nft_id_to_bech32(
&self,
nft_id: NftId,
bech32_hrp: Option<&str>
) -> Result<String>
Transforms an nft id to a bech32 encoded address
sourcepub async fn hex_public_key_to_bech32_address(
&self,
hex: &str,
bech32_hrp: Option<&str>
) -> Result<String>
pub async fn hex_public_key_to_bech32_address(
&self,
hex: &str,
bech32_hrp: Option<&str>
) -> Result<String>
Transforms a hex encoded public key to a bech32 encoded address
sourcepub fn parse_bech32_address(address: &str) -> Result<Address>
pub fn parse_bech32_address(address: &str) -> Result<Address>
Returns a valid Address parsed from a String.
sourcepub fn is_address_valid(address: &str) -> bool
pub fn is_address_valid(address: &str) -> bool
Checks if a String is a valid bech32 encoded address.
sourcepub fn generate_mnemonic() -> Result<String>
pub fn generate_mnemonic() -> Result<String>
Generates a new mnemonic.
sourcepub fn mnemonic_to_seed(mnemonic: &str) -> Result<Seed>
pub fn mnemonic_to_seed(mnemonic: &str) -> Result<Seed>
Returns a seed for a mnemonic.
sourcepub fn mnemonic_to_hex_seed(mnemonic: &str) -> Result<String>
pub fn mnemonic_to_hex_seed(mnemonic: &str) -> Result<String>
Returns a hex encoded seed for a mnemonic.
sourcepub fn tag_to_utf8(payload: &TaggedDataPayload) -> Result<String>
pub fn tag_to_utf8(payload: &TaggedDataPayload) -> Result<String>
UTF-8 encodes the tag
of a given TaggedDataPayload.
sourcepub fn data_to_utf8(payload: &TaggedDataPayload) -> Result<String>
pub fn data_to_utf8(payload: &TaggedDataPayload) -> Result<String>
UTF-8 encodes the data
of a given TaggedDataPayload.
sourcepub fn tagged_data_to_utf8(
payload: &TaggedDataPayload
) -> Result<(String, String)>
pub fn tagged_data_to_utf8(
payload: &TaggedDataPayload
) -> Result<(String, String)>
UTF-8 encodes both the tag
and data
of a given TaggedDataPayload.