use alloc::string::String;
use alloc::vec::Vec;
use miden_protocol::account::{AccountId, AccountType};
use miden_protocol::note::{NoteAttachmentContent, PartialNote};
use miden_protocol::transaction::TransactionScript;
use thiserror::Error;
use crate::AuthMethod;
use crate::code_builder::CodeBuilder;
use crate::errors::CodeBuilderError;
#[cfg(test)]
mod test;
mod component;
pub use component::AccountComponentInterface;
mod extension;
pub use extension::{AccountComponentInterfaceExt, AccountInterfaceExt};
pub struct AccountInterface {
account_id: AccountId,
auth: Vec<AuthMethod>,
components: Vec<AccountComponentInterface>,
}
impl AccountInterface {
pub fn new(
account_id: AccountId,
auth: Vec<AuthMethod>,
components: Vec<AccountComponentInterface>,
) -> Self {
Self { account_id, auth, components }
}
pub fn id(&self) -> &AccountId {
&self.account_id
}
pub fn account_type(&self) -> AccountType {
self.account_id.account_type()
}
pub fn is_faucet(&self) -> bool {
self.account_id.is_faucet()
}
pub fn is_regular_account(&self) -> bool {
self.account_id.is_regular_account()
}
pub fn has_public_state(&self) -> bool {
self.account_id.has_public_state()
}
pub fn is_private(&self) -> bool {
self.account_id.is_private()
}
pub fn is_public(&self) -> bool {
self.account_id.is_public()
}
pub fn is_network(&self) -> bool {
self.account_id.is_network()
}
pub fn auth(&self) -> &Vec<AuthMethod> {
&self.auth
}
pub fn components(&self) -> &Vec<AccountComponentInterface> {
&self.components
}
}
impl AccountInterface {
pub fn build_send_notes_script(
&self,
output_notes: &[PartialNote],
expiration_delta: Option<u16>,
) -> Result<TransactionScript, AccountInterfaceError> {
let note_creation_source = self.build_create_notes_section(output_notes)?;
let script = format!(
"begin\n{}\n{}\nend",
self.build_set_tx_expiration_section(expiration_delta),
note_creation_source,
);
let mut code_builder = CodeBuilder::new();
for note in output_notes {
if let NoteAttachmentContent::Array(array) = note.metadata().attachment().content() {
code_builder.add_advice_map_entry(array.commitment(), array.as_slice().to_vec());
}
}
let tx_script = code_builder
.compile_tx_script(script)
.map_err(AccountInterfaceError::InvalidTransactionScript)?;
Ok(tx_script)
}
fn build_create_notes_section(
&self,
output_notes: &[PartialNote],
) -> Result<String, AccountInterfaceError> {
if let Some(basic_fungible_faucet) = self.components().iter().find(|component_interface| {
matches!(component_interface, AccountComponentInterface::BasicFungibleFaucet)
}) {
basic_fungible_faucet.send_note_body(*self.id(), output_notes)
} else if let Some(_network_fungible_faucet) =
self.components().iter().find(|component_interface| {
matches!(component_interface, AccountComponentInterface::NetworkFungibleFaucet)
})
{
Err(AccountInterfaceError::UnsupportedAccountInterface)
} else if self.components().contains(&AccountComponentInterface::BasicWallet) {
AccountComponentInterface::BasicWallet.send_note_body(*self.id(), output_notes)
} else {
Err(AccountInterfaceError::UnsupportedAccountInterface)
}
}
fn build_set_tx_expiration_section(&self, expiration_delta: Option<u16>) -> String {
if let Some(expiration_delta) = expiration_delta {
format!(
"push.{expiration_delta} exec.::miden::protocol::tx::update_expiration_block_delta\n"
)
} else {
String::new()
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NoteAccountCompatibility {
No,
Maybe,
Yes,
}
#[derive(Debug, Error)]
pub enum AccountInterfaceError {
#[error("note asset is not issued by faucet {0}")]
IssuanceFaucetMismatch(AccountId),
#[error("note created by the basic fungible faucet doesn't contain exactly one asset")]
FaucetNoteWithoutAsset,
#[error("invalid transaction script")]
InvalidTransactionScript(#[source] CodeBuilderError),
#[error("invalid sender account: {0}")]
InvalidSenderAccount(AccountId),
#[error("{} interface does not support the generation of the standard send_note script", interface.name())]
UnsupportedInterface { interface: AccountComponentInterface },
#[error(
"account does not contain the basic fungible faucet or basic wallet interfaces which are needed to support the send_note script generation"
)]
UnsupportedAccountInterface,
}