use alloc::vec::Vec;
use miden_protocol::Felt;
use miden_protocol::account::auth::PublicKey;
pub use miden_protocol::account::{
Account,
AccountBuilder,
AccountCode,
AccountComponent,
AccountComponentCode,
AccountDelta,
AccountFile,
AccountHeader,
AccountId,
AccountIdPrefix,
AccountStorage,
AccountStorageMode,
AccountType,
PartialAccount,
PartialStorage,
PartialStorageMap,
StorageMap,
StorageMapKey,
StorageMapWitness,
StorageSlot,
StorageSlotContent,
StorageSlotId,
StorageSlotName,
StorageSlotType,
};
pub use miden_protocol::address::{Address, AddressInterface, AddressType, NetworkId};
use miden_protocol::asset::AssetVault;
pub use miden_protocol::errors::{AccountIdError, AddressError, NetworkIdError};
use miden_protocol::note::NoteTag;
mod account_reader;
pub use account_reader::AccountReader;
use miden_standards::account::auth::AuthSingleSig;
pub use miden_standards::account::interface::AccountInterfaceExt;
use miden_standards::account::wallets::BasicWallet;
use super::Client;
use crate::errors::ClientError;
use crate::rpc::domain::account::FetchedAccount;
use crate::rpc::node::{EndpointError, GetAccountError};
use crate::store::{AccountStatus, AccountStorageFilter};
use crate::sync::NoteTagRecord;
pub mod component {
pub const MIDEN_PACKAGE_EXTENSION: &str = "masp";
pub use miden_protocol::account::auth::*;
pub use miden_protocol::account::component::{
FeltSchema,
InitStorageData,
SchemaType,
StorageSchema,
StorageSlotSchema,
StorageValueName,
};
pub use miden_protocol::account::{AccountComponent, AccountComponentMetadata};
pub use miden_standards::account::auth::*;
pub use miden_standards::account::components::{
basic_fungible_faucet_library,
basic_wallet_library,
multisig_library,
network_fungible_faucet_library,
no_auth_library,
singlesig_acl_library,
singlesig_library,
};
pub use miden_standards::account::faucets::{BasicFungibleFaucet, NetworkFungibleFaucet};
pub use miden_standards::account::mint_policies::{
AuthControlled,
AuthControlledInitConfig,
OwnerControlled,
OwnerControlledInitConfig,
};
pub use miden_standards::account::wallets::BasicWallet;
}
impl<AUTH> Client<AUTH> {
pub async fn add_account(
&mut self,
account: &Account,
overwrite: bool,
) -> Result<(), ClientError> {
if account.is_new() {
if account.seed().is_none() {
return Err(ClientError::AddNewAccountWithoutSeed);
}
} else {
if account.seed().is_some() {
tracing::warn!(
"Added an existing account and still provided a seed when it is not needed. It's possible that the account's file was incorrectly generated. The seed will be ignored."
);
}
}
let tracked_account = self.store.get_account(account.id()).await?;
match tracked_account {
None => {
self.check_account_limit().await?;
self.check_note_tag_limit().await?;
let default_address = Address::new(account.id());
let default_address_note_tag = default_address.to_note_tag();
let note_tag_record =
NoteTagRecord::with_account_source(default_address_note_tag, account.id());
self.store.add_note_tag(note_tag_record).await?;
self.store
.insert_account(account, default_address)
.await
.map_err(ClientError::StoreError)
},
Some(tracked_account) => {
if !overwrite {
return Err(ClientError::AccountAlreadyTracked(account.id()));
}
if tracked_account.nonce().as_canonical_u64() > account.nonce().as_canonical_u64() {
return Err(ClientError::AccountNonceTooLow);
}
if tracked_account.is_locked() {
let network_account_commitment =
self.rpc_api.get_account_details(account.id()).await?.commitment();
if network_account_commitment != account.to_commitment() {
return Err(ClientError::AccountCommitmentMismatch(
network_account_commitment,
));
}
}
self.store.update_account(account).await.map_err(ClientError::StoreError)
},
}
}
pub async fn import_account_by_id(&mut self, account_id: AccountId) -> Result<(), ClientError> {
let fetched_account =
self.rpc_api.get_account_details(account_id).await.map_err(|err| {
match err.endpoint_error() {
Some(EndpointError::GetAccount(GetAccountError::AccountNotFound)) => {
ClientError::AccountNotFoundOnChain(account_id)
},
_ => ClientError::RpcError(err),
}
})?;
let account = match fetched_account {
FetchedAccount::Private(..) => {
return Err(ClientError::AccountIsPrivate(account_id));
},
FetchedAccount::Public(account, ..) => *account,
};
self.add_account(&account, true).await
}
pub async fn add_address(
&mut self,
address: Address,
account_id: AccountId,
) -> Result<(), ClientError> {
let network_id = self.rpc_api.get_network_id().await?;
let address_bench32 = address.encode(network_id);
if self.store.get_addresses_by_account_id(account_id).await?.contains(&address) {
return Err(ClientError::AddressAlreadyTracked(address_bench32));
}
let tracked_account = self.store.get_account(account_id).await?;
match tracked_account {
None => Err(ClientError::AccountDataNotFound(account_id)),
Some(_tracked_account) => {
let derived_note_tag: NoteTag = address.to_note_tag();
let note_tag_record =
NoteTagRecord::with_account_source(derived_note_tag, account_id);
if self.store.get_note_tags().await?.contains(¬e_tag_record) {
return Err(ClientError::NoteTagDerivedAddressAlreadyTracked(
address_bench32,
derived_note_tag,
));
}
self.check_note_tag_limit().await?;
self.store.insert_address(address, account_id).await?;
Ok(())
},
}
}
pub async fn remove_address(
&mut self,
address: Address,
account_id: AccountId,
) -> Result<(), ClientError> {
self.store.remove_address(address, account_id).await?;
Ok(())
}
pub async fn get_account_vault(
&self,
account_id: AccountId,
) -> Result<AssetVault, ClientError> {
self.store.get_account_vault(account_id).await.map_err(ClientError::StoreError)
}
pub async fn get_account_storage(
&self,
account_id: AccountId,
) -> Result<AccountStorage, ClientError> {
self.store
.get_account_storage(account_id, AccountStorageFilter::All)
.await
.map_err(ClientError::StoreError)
}
pub async fn get_account_code(
&self,
account_id: AccountId,
) -> Result<Option<AccountCode>, ClientError> {
self.store.get_account_code(account_id).await.map_err(ClientError::StoreError)
}
pub async fn get_account_headers(
&self,
) -> Result<Vec<(AccountHeader, AccountStatus)>, ClientError> {
self.store.get_account_headers().await.map_err(Into::into)
}
pub async fn get_account(&self, account_id: AccountId) -> Result<Option<Account>, ClientError> {
match self.store.get_account(account_id).await? {
Some(record) => Ok(Some(record.try_into()?)),
None => Ok(None),
}
}
pub async fn try_get_account(&self, account_id: AccountId) -> Result<Account, ClientError> {
self.get_account(account_id)
.await?
.ok_or(ClientError::AccountDataNotFound(account_id))
}
pub fn account_reader(&self, account_id: AccountId) -> AccountReader {
AccountReader::new(self.store.clone(), account_id)
}
pub async fn prune_account_history(
&self,
account_id: AccountId,
up_to_nonce: Felt,
) -> Result<usize, ClientError> {
Ok(self.store.prune_account_history(account_id, up_to_nonce).await?)
}
}
pub fn build_wallet_id(
init_seed: [u8; 32],
public_key: &PublicKey,
storage_mode: AccountStorageMode,
is_mutable: bool,
) -> Result<AccountId, ClientError> {
let account_type = if is_mutable {
AccountType::RegularAccountUpdatableCode
} else {
AccountType::RegularAccountImmutableCode
};
let auth_scheme = public_key.auth_scheme();
let auth_component: AccountComponent =
AuthSingleSig::new(public_key.to_commitment(), auth_scheme).into();
let account = AccountBuilder::new(init_seed)
.account_type(account_type)
.storage_mode(storage_mode)
.with_auth_component(auth_component)
.with_component(BasicWallet)
.build()?;
Ok(account.id())
}