use serde::{Deserialize, Serialize};
use crate::{
client::api::PreparedTransactionData,
types::block::{
address::Address,
output::{
feature::MetadataFeature,
unlock_condition::{GovernorAddressUnlockCondition, StateControllerAddressUnlockCondition},
AliasId, AliasOutputBuilder, Output,
},
Error,
},
wallet::account::{types::Transaction, Account, OutputData, TransactionOptions},
};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AliasOutputOptions {
pub address: Option<String>,
pub immutable_metadata: Option<Vec<u8>>,
pub metadata: Option<Vec<u8>>,
pub state_metadata: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AliasOutputOptionsDto {
pub address: Option<String>,
pub immutable_metadata: Option<String>,
pub metadata: Option<String>,
pub state_metadata: Option<String>,
}
impl TryFrom<&AliasOutputOptionsDto> for AliasOutputOptions {
type Error = crate::wallet::Error;
fn try_from(value: &AliasOutputOptionsDto) -> crate::wallet::Result<Self> {
Ok(Self {
address: value.address.clone(),
immutable_metadata: match &value.immutable_metadata {
Some(metadata) => {
Some(prefix_hex::decode(metadata).map_err(|_| Error::InvalidField("immutable_metadata"))?)
}
None => None,
},
metadata: match &value.metadata {
Some(metadata) => Some(prefix_hex::decode(metadata).map_err(|_| Error::InvalidField("metadata"))?),
None => None,
},
state_metadata: match &value.state_metadata {
Some(metadata) => {
Some(prefix_hex::decode(metadata).map_err(|_| Error::InvalidField("state_metadata"))?)
}
None => None,
},
})
}
}
impl Account {
pub async fn create_alias_output(
&self,
alias_output_options: Option<AliasOutputOptions>,
options: Option<TransactionOptions>,
) -> crate::wallet::Result<Transaction> {
let prepared_transaction = self.prepare_create_alias_output(alias_output_options, options).await?;
self.sign_and_submit_transaction(prepared_transaction).await
}
pub(crate) async fn prepare_create_alias_output(
&self,
alias_output_options: Option<AliasOutputOptions>,
options: Option<TransactionOptions>,
) -> crate::wallet::Result<PreparedTransactionData> {
log::debug!("[TRANSACTION] prepare_create_alias_output");
let rent_structure = self.client.get_rent_structure().await?;
let token_supply = self.client.get_token_supply().await?;
let controller_address = match alias_output_options
.as_ref()
.and_then(|options| options.address.as_ref())
{
Some(bech32_address) => {
let (bech32_hrp, address) = Address::try_from_bech32_with_hrp(bech32_address)?;
self.client.bech32_hrp_matches(&bech32_hrp).await?;
address
}
None => {
self.public_addresses()
.await
.first()
.expect("first address is generated during account creation")
.address
.inner
}
};
let mut alias_output_builder =
AliasOutputBuilder::new_with_minimum_storage_deposit(rent_structure, AliasId::null())
.with_state_index(0)
.with_foundry_counter(0)
.add_unlock_condition(StateControllerAddressUnlockCondition::new(controller_address))
.add_unlock_condition(GovernorAddressUnlockCondition::new(controller_address));
if let Some(options) = alias_output_options {
if let Some(immutable_metadata) = options.immutable_metadata {
alias_output_builder =
alias_output_builder.add_immutable_feature(MetadataFeature::new(immutable_metadata)?);
}
if let Some(metadata) = options.metadata {
alias_output_builder = alias_output_builder.add_feature(MetadataFeature::new(metadata)?);
}
if let Some(state_metadata) = options.state_metadata {
alias_output_builder = alias_output_builder.with_state_metadata(state_metadata);
}
}
let outputs = vec![alias_output_builder.finish_output(token_supply)?];
self.prepare_transaction(outputs, options).await
}
pub(crate) async fn get_alias_output(&self, alias_id: Option<AliasId>) -> Option<(AliasId, OutputData)> {
log::debug!("[get_alias_output]");
self.read()
.await
.unspent_outputs()
.values()
.find_map(|output_data| match &output_data.output {
Output::Alias(alias_output) => {
let output_alias_id = alias_output.alias_id_non_null(&output_data.output_id);
alias_id.map_or_else(
|| Some((output_alias_id, output_data.clone())),
|alias_id| {
if output_alias_id == alias_id {
Some((output_alias_id, output_data.clone()))
} else {
None
}
},
)
}
_ => None,
})
}
}