miden_client/account/mod.rs
1//! The `account` module provides types and client APIs for managing accounts within the Miden
2//! network.
3//!
4//! Accounts are foundational entities of the Miden protocol. They store assets and define
5//! rules for manipulating them. Once an account is registered with the client, its state will
6//! be updated accordingly, and validated against the network state on every sync.
7//!
8//! # Example
9//!
10//! To add a new account to the client's store, you might use the [`Client::add_account`] method as
11//! follows:
12//!
13//! ```rust
14//! # use miden_client::{
15//! # account::{Account, AccountBuilder, AccountBuilderSchemaCommitmentExt, AccountType, component::BasicWallet},
16//! # crypto::FeltRng
17//! # };
18//! # async fn add_new_account_example<AUTH>(
19//! # client: &mut miden_client::Client<AUTH>
20//! # ) -> Result<(), miden_client::ClientError> {
21//! # let random_seed = Default::default();
22//! let account = AccountBuilder::new(random_seed)
23//! .account_type(AccountType::Private)
24//! .with_component(BasicWallet)
25//! .build_with_schema_commitment()?;
26//!
27//! // Add the account to the client. The account already embeds its seed information.
28//! client.add_account(&account, false).await?;
29//! # Ok(())
30//! # }
31//! ```
32//!
33//! For more details on accounts, refer to the [Account] documentation.
34
35use alloc::string::{String, ToString};
36use alloc::vec::Vec;
37
38use miden_protocol::Felt;
39use miden_protocol::account::auth::PublicKey;
40pub use miden_protocol::account::{
41 Account,
42 AccountBuilder,
43 AccountCode,
44 AccountComponent,
45 AccountComponentCode,
46 AccountDelta,
47 AccountFile,
48 AccountHeader,
49 AccountId,
50 AccountIdPrefix,
51 AccountIdPrefixV1,
52 AccountIdV1,
53 AccountIdVersion,
54 AccountPatch,
55 AccountProcedureRoot,
56 AccountStorage,
57 AccountStoragePatch,
58 AccountType,
59 AccountUpdateDetails,
60 AccountVaultPatch,
61 PartialAccount,
62 PartialStorage,
63 PartialStorageMap,
64 RoleSymbol,
65 StorageMap,
66 StorageMapKey,
67 StorageMapKeyHash,
68 StorageMapPatch,
69 StorageMapPatchEntries,
70 StorageMapWitness,
71 StorageSlot,
72 StorageSlotContent,
73 StorageSlotId,
74 StorageSlotName,
75 StorageSlotPatch,
76 StorageSlotType,
77 StorageValuePatch,
78};
79pub use miden_protocol::address::{Address, AddressInterface, AddressType, NetworkId};
80use miden_protocol::asset::AssetVault;
81pub use miden_protocol::errors::{AccountIdError, AddressError, NetworkIdError};
82use miden_protocol::note::NoteTag;
83use miden_tx::utils::serde::{
84 ByteReader,
85 ByteWriter,
86 Deserializable,
87 DeserializationError,
88 Serializable,
89};
90
91/// Display-only metadata for a faucet account, persisted in the client's settings store.
92///
93/// Populated lazily by the CLI resolver from the on-chain token config of a public faucet
94/// and persisted under a `faucet_metadata:<faucet-id>` key.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct FaucetMetadata {
97 pub symbol: String,
98 pub decimals: u8,
99}
100
101impl Serializable for FaucetMetadata {
102 fn write_into<W: ByteWriter>(&self, target: &mut W) {
103 self.symbol.write_into(target);
104 target.write_u8(self.decimals);
105 }
106}
107
108impl Deserializable for FaucetMetadata {
109 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
110 let symbol = String::read_from(source)?;
111 let decimals = source.read_u8()?;
112 Ok(Self { symbol, decimals })
113 }
114}
115
116mod account_reader;
117pub use account_reader::AccountReader;
118/// Raw access to `miden-standards` account modules for items not curated by `miden-client`.
119pub use miden_standards::account as standards;
120use miden_standards::account::auth::{Approver, AuthSingleSig};
121use miden_standards::account::faucets::FungibleFaucet;
122pub use miden_standards::account::inspection::{
123 AccountBuilderSchemaCommitmentExt,
124 AccountSchemaCommitment,
125};
126// RE-EXPORTS
127// ================================================================================================
128pub use miden_standards::account::interface::{
129 AccountComponentInterface,
130 AccountComponentInterfaceExt,
131 AccountInterface,
132 AccountInterfaceExt,
133};
134use miden_standards::account::wallets::BasicWallet;
135
136use super::Client;
137use crate::asset::TokenSymbol;
138use crate::errors::ClientError;
139use crate::rpc::domain::account::GetAccountRequest;
140use crate::rpc::node::{EndpointError, GetAccountError};
141use crate::store::{AccountStatus, AccountStorageFilter, ClientAccountType};
142use crate::sync::NoteTagRecord;
143
144pub mod component {
145 pub const MIDEN_PACKAGE_EXTENSION: &str = "masp";
146
147 pub use miden_protocol::account::auth::*;
148 pub use miden_protocol::account::component::{
149 FeltSchema,
150 InitStorageData,
151 InitStorageDataError,
152 MapSlotSchema,
153 SchemaRequirement,
154 SchemaType,
155 SchemaTypeError,
156 StorageSchema,
157 StorageSlotSchema,
158 StorageValueName,
159 StorageValueNameError,
160 ValueSlotSchema,
161 WordSchema,
162 WordValue,
163 };
164 pub use miden_protocol::account::{
165 AccountComponent,
166 AccountComponentMetadata,
167 AccountComponentName,
168 AccountProcedureRoot,
169 RoleSymbol,
170 };
171 pub use miden_standards::account::access::{
172 AccessControl,
173 Authority,
174 AuthorityError,
175 Ownable2Step,
176 Ownable2StepError,
177 Pausable,
178 PausableManager,
179 PausableStorage,
180 RoleBasedAccessControl,
181 };
182 pub use miden_standards::account::auth::*;
183 pub use miden_standards::account::components::StandardAccountComponent;
184 pub use miden_standards::account::faucets::{
185 Description,
186 ExternalLink,
187 FungibleFaucet,
188 FungibleFaucetBuilder,
189 FungibleFaucetError,
190 LogoURI,
191 NonFungibleFaucet,
192 TokenMetadata,
193 TokenMetadataError,
194 TokenName,
195 create_network_fungible_faucet,
196 create_singlesig_user_fungible_faucet,
197 };
198 pub use miden_standards::account::fees::{
199 BasicConstantFeePolicy,
200 FeePolicy,
201 FeePolicyError,
202 FeePolicyManager,
203 FeePolicyManagerBuilder,
204 };
205 pub use miden_standards::account::policies::{
206 AllowlistManager,
207 AllowlistStorage,
208 BasicAllowlist,
209 BasicBlocklist,
210 BlocklistManager,
211 BlocklistStorage,
212 BurnAllowAll,
213 BurnOwnerOnly,
214 BurnPolicy,
215 BurnPolicyError,
216 MinBurnAmount,
217 MintAllowAll,
218 MintOwnerOnly,
219 MintPolicy,
220 MintPolicyError,
221 TokenPolicyManager,
222 TokenPolicyManagerBuilder,
223 TransferAllowAll,
224 TransferPolicy,
225 TransferPolicyError,
226 };
227 pub use miden_standards::account::wallets::BasicWallet;
228}
229
230// CLIENT METHODS
231// ================================================================================================
232
233/// This section of the [Client] contains methods for:
234///
235/// - **Account creation:** Use the [`AccountBuilder`] to construct new accounts, specifying account
236/// visibility (`AccountType::Public` / `AccountType::Private`) and attaching necessary components
237/// (e.g., basic wallet or fungible faucet). Prefer
238/// [`AccountBuilderSchemaCommitmentExt::build_with_schema_commitment`] so the account includes
239/// merged storage schema commitment metadata; use plain [`AccountBuilder::build`] only when you
240/// need to opt out. After creation, accounts can be added to the client.
241///
242/// - **Account tracking:** Accounts added via the client are persisted to the local store, where
243/// their state (including nonce, balance, and metadata) is updated upon every synchronization
244/// with the network.
245///
246/// - **Data retrieval:** The module also provides methods to fetch account-related data.
247impl<AUTH> Client<AUTH> {
248 // ACCOUNT CREATION
249 // --------------------------------------------------------------------------------------------
250
251 /// Adds the provided [Account] in the store so it can start being tracked by the client.
252 ///
253 /// If the account is already being tracked and `overwrite` is set to `true`, the account will
254 /// be overwritten. Newly created accounts must embed their seed (`account.seed()` must return
255 /// `Some(_)`).
256 ///
257 /// # Errors
258 ///
259 /// - If the account is new but it does not contain the seed.
260 /// - If the account is already tracked and `overwrite` is set to `false`.
261 /// - If `overwrite` is set to `true` and the `account_data` nonce is lower than the one already
262 /// being tracked.
263 /// - If `overwrite` is set to `true` and the `account_data` commitment doesn't match the
264 /// network's account commitment.
265 pub async fn add_account(
266 &mut self,
267 account: &Account,
268 overwrite: bool,
269 ) -> Result<(), ClientError> {
270 self.add_account_inner(account, ClientAccountType::Native, overwrite).await
271 }
272
273 /// Inserts `account` into the store (or overwrites it if `overwrite` is true) and registers
274 /// the per-account note tag if `client_account_type` is [`ClientAccountType::Native`].
275 ///
276 /// Switching the [`ClientAccountType`] of an already-tracked account is not supported and
277 /// returns [`ClientError::AccountWatchedMismatch`].
278 async fn add_account_inner(
279 &mut self,
280 account: &Account,
281 client_account_type: ClientAccountType,
282 overwrite: bool,
283 ) -> Result<(), ClientError> {
284 if account.is_new() {
285 if account.seed().is_none() {
286 return Err(ClientError::AddNewAccountWithoutSeed);
287 }
288 } else {
289 // Ignore the seed since it's not a new account
290 if account.seed().is_some() {
291 tracing::warn!(
292 "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."
293 );
294 }
295 }
296
297 let tracked_account = self.store.get_minimal_partial_account(account.id()).await?;
298
299 match tracked_account {
300 None => {
301 let default_address = Address::new(account.id());
302
303 self.store
304 .insert_account(account, default_address.clone(), client_account_type)
305 .await
306 .map_err(ClientError::StoreError)?;
307
308 if matches!(client_account_type, ClientAccountType::Native) {
309 // Set the default address note tag so sync pulls notes.
310 let default_address_note_tag = default_address.to_note_tag();
311 let note_tag_record =
312 NoteTagRecord::with_account_source(default_address_note_tag, account.id());
313 self.store.add_note_tag(note_tag_record).await?;
314 }
315
316 Ok(())
317 },
318 Some(tracked_account) => {
319 if !overwrite {
320 // Only overwrite the account if the flag is set to `true`
321 return Err(ClientError::AccountAlreadyTracked(account.id()));
322 }
323
324 if client_account_type != tracked_account.client_account_type() {
325 // Switching between Watched and Native after the account is tracked is not
326 // supported: the per-account note tag and any client-side state derived from
327 // that mode are set up at insertion time and not migrated on the fly.
328 return Err(ClientError::AccountWatchedMismatch(account.id()));
329 }
330
331 if tracked_account.nonce().as_canonical_u64() > account.nonce().as_canonical_u64() {
332 // If the new account is older than the one being tracked, return an error
333 return Err(ClientError::AccountNonceTooLow);
334 }
335
336 if tracked_account.is_locked() {
337 // If the tracked account is locked, check that the account commitment matches
338 // the one in the network
339 let network_account_commitment = self
340 .rpc_api
341 .get_account(account.id(), GetAccountRequest::new())
342 .await?
343 .1
344 .account_commitment();
345 if network_account_commitment != account.to_commitment() {
346 return Err(ClientError::AccountCommitmentMismatch(
347 network_account_commitment,
348 ));
349 }
350 }
351
352 self.store.update_account(account).await?;
353
354 Ok(())
355 },
356 }
357 }
358
359 /// Imports an account from the network to the client's store. The account needs to be public
360 /// and be tracked by the network, it will be fetched by its ID. If the account was already
361 /// being tracked by the client, its state will be overwritten.
362 ///
363 /// To import an account as watched (state-tracking only, no note sync), use
364 /// [`Self::import_watched_account_by_id`] instead. Switching an already-tracked account
365 /// between Native and Watched is not supported.
366 ///
367 /// # Errors
368 /// - If the account is not found on the network.
369 /// - If the account is private.
370 /// - If the account is already tracked as watched.
371 /// - There was an error sending the request to the network.
372 pub async fn import_account_by_id(&mut self, account_id: AccountId) -> Result<(), ClientError> {
373 let account = self.fetch_public_account(account_id).await?;
374 self.add_account_inner(&account, ClientAccountType::Native, true).await
375 }
376
377 /// Starts watching an on-chain account ([`ClientAccountType::Watched`]).
378 ///
379 /// Like [`Self::import_account_by_id`], the account is fetched from the network by its ID.
380 /// Unlike `import_account_by_id`, the account is added without registering its derived note
381 /// tag: `sync_state` will keep the account's commitment, nonce and storage up to date but
382 /// will **not** pull notes targeted at it.
383 ///
384 /// If the account is already being tracked as watched its state is overwritten. Switching an
385 /// already-tracked native account to watched is not supported.
386 ///
387 /// # Errors
388 /// - If the account is not found on the network.
389 /// - If the account is private.
390 /// - If the account is already tracked as native.
391 /// - There was an error sending the request to the network.
392 pub async fn import_watched_account_by_id(
393 &mut self,
394 account_id: AccountId,
395 ) -> Result<(), ClientError> {
396 let account = self.fetch_public_account(account_id).await?;
397 self.add_account_inner(&account, ClientAccountType::Watched, true).await
398 }
399
400 /// Fetches a public [`Account`] from the network, returning a typed error when the account
401 /// doesn't exist on chain or is private.
402 async fn fetch_public_account(&self, account_id: AccountId) -> Result<Account, ClientError> {
403 let fetched_account =
404 self.rpc_api.get_account_details(account_id).await.map_err(|err| {
405 match err.endpoint_error() {
406 Some(EndpointError::GetAccount(GetAccountError::AccountNotFound)) => {
407 ClientError::AccountNotFoundOnChain(account_id)
408 },
409 _ => ClientError::RpcError(err),
410 }
411 })?;
412
413 fetched_account.ok_or(ClientError::AccountIsPrivate(account_id))
414 }
415
416 /// Fetches a public faucet's display metadata from the network.
417 ///
418 /// Uses [`get_account`](crate::rpc::NodeRpcClient::get_account) with a minimal request so that
419 /// the node does not return vault data. The faucet's token config lives in a single value slot,
420 /// which is always present in the returned storage header.
421 ///
422 /// Returns:
423 /// - `Ok(Some(_))` — the account is public and its token config storage slot decoded.
424 /// - `Ok(None)` — the account is private, not on chain, or the storage slot does not parse
425 /// as a token config. Caller should fall back to a raw display.
426 /// - `Err(_)` — transport-level RPC error.
427 pub async fn fetch_remote_token_metadata(
428 &self,
429 faucet_id: AccountId,
430 ) -> Result<Option<FaucetMetadata>, ClientError> {
431 let proof = match self.rpc_api.get_account(faucet_id, GetAccountRequest::new()).await {
432 Ok((_, proof)) => proof,
433 Err(err) => match err.endpoint_error() {
434 Some(EndpointError::GetAccount(
435 GetAccountError::AccountNotFound | GetAccountError::AccountNotPublic,
436 )) => return Ok(None),
437 _ => return Err(ClientError::RpcError(err)),
438 },
439 };
440
441 let Some(storage_header) = proof.storage_header() else {
442 return Ok(None);
443 };
444
445 let Some(slot_header) =
446 storage_header.find_slot_header_by_name(FungibleFaucet::token_config_slot())
447 else {
448 return Ok(None);
449 };
450
451 let [_token_supply, _max_supply, decimals, symbol] = *slot_header.value();
452 let Ok(symbol) = TokenSymbol::try_from(symbol) else {
453 return Ok(None);
454 };
455 let Ok(decimals) = u8::try_from(decimals.as_canonical_u64()) else {
456 return Ok(None);
457 };
458 Ok(Some(FaucetMetadata { symbol: symbol.to_string(), decimals }))
459 }
460
461 /// Adds an [`Address`] to the associated [`AccountId`], alongside its derived [`NoteTag`]. If
462 /// the account is tracked as watched, the note tag is not registered.
463 ///
464 /// # Errors
465 /// - If the account is not found on the network.
466 /// - If the address is already being tracked.
467 pub async fn add_address(
468 &mut self,
469 address: Address,
470 account_id: AccountId,
471 ) -> Result<(), ClientError> {
472 let network_id = self.rpc_api.get_network_id().await?;
473 let address_bench32 = address.encode(network_id);
474 if self.store.get_addresses_by_account_id(account_id).await?.contains(&address) {
475 return Err(ClientError::AddressAlreadyTracked(address_bench32));
476 }
477
478 let tracked_account = self.store.get_minimal_partial_account(account_id).await?;
479 match tracked_account {
480 None => Err(ClientError::AccountDataNotFound(account_id)),
481 Some(tracked_account) => {
482 self.store.insert_address(address.clone(), account_id).await?;
483 // Watched accounts intentionally have no derived note tag registered to avoid sync
484 // state pulling notes for them.
485 if !tracked_account.is_watched() {
486 let derived_note_tag: NoteTag = address.to_note_tag();
487 let note_tag_record =
488 NoteTagRecord::with_account_source(derived_note_tag, account_id);
489 self.store.add_note_tag(note_tag_record).await?;
490 }
491 Ok(())
492 },
493 }
494 }
495
496 /// Removes an [`Address`] from the associated [`AccountId`], alongside its derived [`NoteTag`].
497 /// If no address was tracked for the given account, this is a no-op.
498 pub async fn remove_address(
499 &mut self,
500 address: Address,
501 account_id: AccountId,
502 ) -> Result<(), ClientError> {
503 let derived_note_tag = address.to_note_tag();
504 let note_tag_record = NoteTagRecord::with_account_source(derived_note_tag, account_id);
505 self.store.remove_address(address).await?;
506 // Remove the note tag if no other address are associated with it.
507 let addresses = self.store.get_addresses_by_account_id(account_id).await?;
508 if addresses.iter().all(|address| address.to_note_tag() != derived_note_tag) {
509 self.store.remove_note_tag(note_tag_record).await?;
510 }
511 Ok(())
512 }
513
514 // ACCOUNT DATA RETRIEVAL
515 // --------------------------------------------------------------------------------------------
516
517 /// Retrieves the asset vault for a specific account.
518 ///
519 /// To check the balance for a single asset, use [`Client::account_reader`] instead.
520 pub async fn get_account_vault(
521 &self,
522 account_id: AccountId,
523 ) -> Result<AssetVault, ClientError> {
524 self.store.get_account_vault(account_id).await.map_err(ClientError::StoreError)
525 }
526
527 /// Retrieves the whole account storage for a specific account.
528 ///
529 /// To only load a specific slot, use [`Client::account_reader`] instead.
530 pub async fn get_account_storage(
531 &self,
532 account_id: AccountId,
533 ) -> Result<AccountStorage, ClientError> {
534 self.store
535 .get_account_storage(account_id, AccountStorageFilter::All)
536 .await
537 .map_err(ClientError::StoreError)
538 }
539
540 /// Retrieves the account code for a specific account.
541 ///
542 /// Returns `None` if the account is not found.
543 pub async fn get_account_code(
544 &self,
545 account_id: AccountId,
546 ) -> Result<Option<AccountCode>, ClientError> {
547 self.store.get_account_code(account_id).await.map_err(ClientError::StoreError)
548 }
549
550 /// Returns a list of [`AccountHeader`] of all accounts stored in the database along with their
551 /// statuses.
552 ///
553 /// Said accounts' state is the state after the last performed sync.
554 pub async fn get_account_headers(
555 &self,
556 ) -> Result<Vec<(AccountHeader, AccountStatus)>, ClientError> {
557 self.store.get_account_headers().await.map_err(Into::into)
558 }
559
560 /// Retrieves the full [`Account`] object from the store, returning `None` if not found.
561 ///
562 /// This method loads the complete account state including vault, storage, and code.
563 ///
564 /// For lazy access that fetches only the data you need, use
565 /// [`Client::account_reader`] instead.
566 ///
567 /// Use [`Client::try_get_account`] if you want to error when the account is not found.
568 pub async fn get_account(&self, account_id: AccountId) -> Result<Option<Account>, ClientError> {
569 match self.store.get_account(account_id).await? {
570 Some(record) => Ok(Some(record.try_into()?)),
571 None => Ok(None),
572 }
573 }
574
575 /// Retrieves the full [`Account`] object from the store, erroring if not found.
576 ///
577 /// This method loads the complete account state including vault, storage, and code.
578 ///
579 /// Use [`Client::get_account`] if you want to handle missing accounts gracefully.
580 pub async fn try_get_account(&self, account_id: AccountId) -> Result<Account, ClientError> {
581 self.get_account(account_id)
582 .await?
583 .ok_or(ClientError::AccountDataNotFound(account_id))
584 }
585
586 /// Creates an [`AccountReader`] for lazy access to account data.
587 ///
588 /// The `AccountReader` provides lazy access to account state - each method call
589 /// fetches fresh data from storage, ensuring you always see the current state.
590 ///
591 /// For loading the full [`Account`] object, use [`Client::get_account`] instead.
592 ///
593 /// # Example
594 /// ```ignore
595 /// let reader = client.account_reader(account_id);
596 ///
597 /// // Each call fetches fresh data
598 /// let nonce = reader.nonce().await?;
599 /// let balance = reader.get_balance(faucet_id).await?;
600 ///
601 /// // Storage access is integrated
602 /// let value = reader.get_storage_item("my_slot").await?;
603 /// let (map_value, witness) = reader.get_storage_map_witness("balances", key).await?;
604 /// ```
605 pub fn account_reader(&self, account_id: AccountId) -> AccountReader {
606 AccountReader::new(self.store.clone(), account_id)
607 }
608
609 /// Prunes historical account states for the specified account up to the given nonce.
610 ///
611 /// Deletes all historical entries with `replaced_at_nonce <= up_to_nonce` and any
612 /// orphaned account code.
613 ///
614 /// Returns the total number of rows deleted, including historical entries and orphaned
615 /// account code.
616 pub async fn prune_account_history(
617 &self,
618 account_id: AccountId,
619 up_to_nonce: Felt,
620 ) -> Result<usize, ClientError> {
621 Ok(self.store.prune_account_history(account_id, up_to_nonce).await?)
622 }
623}
624
625// UTILITY FUNCTIONS
626// ================================================================================================
627
628/// Builds an regular account ID from the provided parameters. The ID may be used along
629/// `Client::import_account_by_id` to import a public account from the network (provided that the
630/// used seed is known).
631///
632/// This function currently supports accounts composed of the [`BasicWallet`] component and one of
633/// the supported authentication schemes ([`AuthSingleSig`]).
634///
635/// # Arguments
636/// - `init_seed`: Initial seed used to create the account. This is the seed passed to
637/// [`AccountBuilder::new`].
638/// - `public_key`: Public key of the account used for the authentication component.
639/// - `account_visibility`: Public/private visibility of the account.
640///
641/// # Errors
642/// - If the account cannot be built.
643pub fn build_wallet_id(
644 init_seed: [u8; 32],
645 public_key: &PublicKey,
646 account_visibility: AccountType,
647) -> Result<AccountId, ClientError> {
648 let auth_scheme = public_key.auth_scheme();
649 let auth_component: AccountComponent =
650 AuthSingleSig::new(Approver::new(public_key.to_commitment(), auth_scheme)).into();
651
652 let account = AccountBuilder::new(init_seed)
653 .account_type(account_visibility)
654 .with_component(auth_component)
655 .with_component(BasicWallet)
656 .build_with_schema_commitment()?;
657
658 Ok(account.id())
659}
660
661#[cfg(test)]
662mod schema_commitment_tests {
663 use miden_protocol::EMPTY_WORD;
664 use miden_protocol::account::auth::AuthSecretKey;
665 use miden_standards::account::inspection::AccountSchemaCommitment;
666
667 use super::{
668 AccountBuilder,
669 AccountBuilderSchemaCommitmentExt,
670 AccountType,
671 Approver,
672 AuthSingleSig,
673 BasicWallet,
674 };
675 use crate::auth::AuthSchemeId;
676
677 #[test]
678 fn wallet_build_includes_schema_commitment_metadata_slot() {
679 let key = AuthSecretKey::new_falcon512_poseidon2();
680 let account = AccountBuilder::new([2u8; 32])
681 .account_type(AccountType::Private)
682 .with_component(AuthSingleSig::new(Approver::new(
683 key.public_key().to_commitment(),
684 AuthSchemeId::Falcon512Poseidon2,
685 )))
686 .with_component(BasicWallet)
687 .build_with_schema_commitment()
688 .expect("build_with_schema_commitment");
689
690 let commitment = account
691 .storage()
692 .get_item(AccountSchemaCommitment::schema_commitment_slot())
693 .expect("schema commitment slot");
694 assert_ne!(commitment, EMPTY_WORD);
695 }
696}