use getset::Getters;
use primitive_types::U256;
use serde::{Deserialize, Serialize};
use crate::{
client::{api::PreparedTransactionData, secret::SecretManage, ClientError},
types::block::{
address::{Bech32Address, ToBech32Ext},
output::{
unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition},
BasicOutputBuilder, NativeToken, TokenId,
},
slot::SlotIndex,
},
utils::ConvertTo,
wallet::{
constants::DEFAULT_EXPIRATION_SLOTS,
operations::transaction::{TransactionOptions, TransactionWithMetadata},
Wallet, WalletError,
},
};
#[derive(Debug, Clone, Serialize, Deserialize, Getters)]
#[serde(rename_all = "camelCase")]
pub struct SendNativeTokenParams {
#[getset(get = "pub")]
address: Bech32Address,
#[getset(get = "pub")]
native_token: (TokenId, U256),
#[getset(get = "pub")]
return_address: Option<Bech32Address>,
#[getset(get = "pub")]
expiration: Option<SlotIndex>,
}
impl SendNativeTokenParams {
pub fn new(address: impl ConvertTo<Bech32Address>, native_token: (TokenId, U256)) -> Result<Self, WalletError> {
Ok(Self {
address: address.convert()?,
native_token,
return_address: None,
expiration: None,
})
}
pub fn try_with_return_address(
mut self,
return_address: impl ConvertTo<Bech32Address>,
) -> Result<Self, WalletError> {
self.return_address = Some(return_address.convert()?);
Ok(self)
}
pub fn with_return_address(mut self, return_address: impl Into<Option<Bech32Address>>) -> Self {
self.return_address = return_address.into();
self
}
pub fn with_expiration(mut self, expiration: Option<SlotIndex>) -> Self {
self.expiration = expiration;
self
}
}
impl<S: 'static + SecretManage> Wallet<S>
where
WalletError: From<S::Error>,
ClientError: From<S::Error>,
{
pub async fn send_native_tokens<I: IntoIterator<Item = SendNativeTokenParams> + Send>(
&self,
params: I,
options: impl Into<Option<TransactionOptions>> + Send,
) -> Result<TransactionWithMetadata, WalletError>
where
I::IntoIter: Send,
{
let options = options.into();
let prepared_transaction = self.prepare_send_native_tokens(params, options.clone()).await?;
self.sign_and_submit_transaction(prepared_transaction, options).await
}
pub async fn prepare_send_native_tokens<I: IntoIterator<Item = SendNativeTokenParams> + Send>(
&self,
params: I,
options: impl Into<Option<TransactionOptions>> + Send,
) -> Result<PreparedTransactionData, WalletError>
where
I::IntoIter: Send,
{
log::debug!("[TRANSACTION] prepare_send_native_tokens");
let storage_score_params = self.client().get_storage_score_parameters().await?;
let wallet_address = self.address().await;
let default_return_address = wallet_address.to_bech32(self.client().get_bech32_hrp().await?);
let slot_index = self.client().get_slot_index().await?;
let mut outputs = Vec::new();
for SendNativeTokenParams {
address,
native_token,
return_address,
expiration,
} in params
{
self.client().bech32_hrp_matches(address.hrp()).await?;
let return_address = return_address
.map(|addr| {
if address.hrp() != addr.hrp() {
Err(ClientError::Bech32HrpMismatch {
provided: addr.hrp().to_string(),
expected: address.hrp().to_string(),
})?;
}
Ok::<_, WalletError>(addr)
})
.transpose()?
.unwrap_or_else(|| default_return_address.clone());
let native_token = NativeToken::new(native_token.0, native_token.1)?;
let expiration_slot_index = expiration
.map_or(slot_index + DEFAULT_EXPIRATION_SLOTS, |expiration_slot_index| {
slot_index + expiration_slot_index
});
outputs.push(
BasicOutputBuilder::new_with_amount(0)
.with_native_token(native_token)
.add_unlock_condition(AddressUnlockCondition::new(address))
.add_unlock_condition(ExpirationUnlockCondition::new(
return_address.clone(),
expiration_slot_index,
)?)
.with_sufficient_storage_deposit(return_address, storage_score_params)?
.finish_output()?,
)
}
self.prepare_send_outputs(outputs, options).await
}
}