1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
91
92
93
94
95
96
97
// Copyright 2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
// use primitive_types::U256;
use serde::{Deserialize, Serialize};
use crate::{
client::api::PreparedTransactionData,
types::block::{
address::Address,
output::{unlock_condition::AddressUnlockCondition, NftId, NftOutputBuilder, Output},
},
wallet::account::{handle::AccountHandle, operations::transaction::Transaction, TransactionOptions},
};
/// Address and nft for `send_nft()`
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddressAndNftId {
/// Bech32 encoded address
pub address: String,
/// Nft id
#[serde(rename = "nftId")]
pub nft_id: NftId,
}
impl AccountHandle {
/// Function to send native tokens in basic outputs with a
/// [`StorageDepositReturnUnlockCondition`](crate::types::block::output::unlock_condition::StorageDepositReturnUnlockCondition) and
/// [`ExpirationUnlockCondition`](crate::types::block::output::unlock_condition::ExpirationUnlockCondition), so the
/// storage deposit gets back to the sender and also that the sender gets access to the output again after a
/// defined time (default 1 day), Calls [AccountHandle.send()](crate::wallet::account::handle::AccountHandle.send)
/// internally, the options can define the RemainderValueStrategy. Custom inputs will be replaced with the
/// required nft inputs. Address needs to be Bech32 encoded
/// ```ignore
/// let outputs = vec![AddressAndNftId {
/// address: "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu".to_string(),
/// nft_id: NftId::from_str("04f9b54d488d2e83a6c90db08ae4b39651bbba8a")?,
/// }];
///
/// let transaction = account.send_nft(outputs, None).await?;
///
/// println!(
/// "Transaction: {} Block sent: http://localhost:14265/api/core/v2/blocks/{}",
/// transaction.transaction_id,
/// transaction.block_id.expect("no block created yet")
/// );
/// ```
pub async fn send_nft(
&self,
addresses_nft_ids: Vec<AddressAndNftId>,
options: Option<TransactionOptions>,
) -> crate::wallet::Result<Transaction> {
let prepared_transaction = self.prepare_send_nft(addresses_nft_ids, options).await?;
self.sign_and_submit_transaction(prepared_transaction).await
}
/// Function to prepare the transaction for
/// [AccountHandle.send_nft()](crate::account::handle::AccountHandle.send_nft)
async fn prepare_send_nft(
&self,
addresses_nft_ids: Vec<AddressAndNftId>,
options: Option<TransactionOptions>,
) -> crate::wallet::Result<PreparedTransactionData> {
log::debug!("[TRANSACTION] prepare_send_nft");
let unspent_outputs = self.unspent_outputs(None).await?;
let token_supply = self.client.get_token_supply().await?;
let mut outputs = Vec::new();
for address_and_nft_id in addresses_nft_ids {
let (address, bech32_hrp) = Address::try_from_bech32_with_hrp(address_and_nft_id.address)?;
self.client.bech32_hrp_matches(&bech32_hrp).await?;
// Find nft output from the inputs
if let Some(nft_output_data) = unspent_outputs.iter().find(|o| {
if let Output::Nft(nft_output) = &o.output {
address_and_nft_id.nft_id == nft_output.nft_id_non_null(&o.output_id)
} else {
false
}
}) {
if let Output::Nft(nft_output) = &nft_output_data.output {
// Set the nft id and new address unlock condition
let nft_builder = NftOutputBuilder::from(nft_output)
.with_nft_id(address_and_nft_id.nft_id)
.with_unlock_conditions(vec![AddressUnlockCondition::new(address)]);
outputs.push(nft_builder.finish_output(token_supply)?);
}
} else {
return Err(crate::wallet::Error::NftNotFoundInUnspentOutputs);
};
}
self.prepare_transaction(outputs, options).await
}
}