Skip to main content

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