miden_client/account/account_reader.rs
1//! Provides lazy access to account data.
2
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5
6use miden_protocol::account::{
7 AccountHeader,
8 AccountId,
9 PartialAccount,
10 StorageMapKey,
11 StorageMapWitness,
12 StorageSlotName,
13};
14use miden_protocol::address::Address;
15use miden_protocol::asset::{Asset, AssetAmount, AssetId, FungibleAsset};
16use miden_protocol::{Felt, Word};
17
18use crate::errors::ClientError;
19use crate::store::{AccountStatus, Store};
20
21/// Provides lazy access to account data.
22///
23/// `AccountReader` executes queries lazily - each method call fetches fresh data from storage,
24/// ensuring you always see the current state.
25///
26/// # Example
27/// ```ignore
28/// let reader = client.account_reader(account_id);
29///
30/// // Each call fetches fresh data
31/// let nonce = reader.nonce().await?;
32/// let status = reader.status().await?;
33/// let commitment = reader.commitment().await?;
34///
35/// // Vault access
36/// let balance = reader.get_balance(faucet_id).await?;
37///
38/// // Storage access
39/// let value = reader.get_storage_item("my_slot").await?;
40/// ```
41pub struct AccountReader {
42 store: Arc<dyn Store>,
43 account_id: AccountId,
44}
45
46impl AccountReader {
47 /// Creates a new `AccountReader` for the given account.
48 pub fn new(store: Arc<dyn Store>, account_id: AccountId) -> Self {
49 Self { store, account_id }
50 }
51
52 /// Returns the account ID (fixed at construction).
53 pub fn account_id(&self) -> AccountId {
54 self.account_id
55 }
56
57 // HEADER ACCESS
58 // --------------------------------------------------------------------------------------------
59
60 /// Retrieves the current account nonce.
61 pub async fn nonce(&self) -> Result<Felt, ClientError> {
62 let (header, _) = self.header().await?;
63 Ok(header.nonce())
64 }
65
66 /// Retrieves the account commitment (hash of the full state).
67 pub async fn commitment(&self) -> Result<Word, ClientError> {
68 let (header, _) = self.header().await?;
69 Ok(header.to_commitment())
70 }
71
72 /// Retrieves the storage commitment (root of the storage tree).
73 pub async fn storage_commitment(&self) -> Result<Word, ClientError> {
74 let (header, _) = self.header().await?;
75 Ok(header.storage_commitment())
76 }
77
78 /// Retrieves the vault root (root of the asset vault tree).
79 pub async fn vault_root(&self) -> Result<Word, ClientError> {
80 let (header, _) = self.header().await?;
81 Ok(header.vault_root())
82 }
83
84 /// Retrieves the code commitment (hash of the account code).
85 pub async fn code_commitment(&self) -> Result<Word, ClientError> {
86 let (header, _) = self.header().await?;
87 Ok(header.code_commitment())
88 }
89
90 /// Retrieves the current account status (New, Tracked, or Locked).
91 pub async fn status(&self) -> Result<AccountStatus, ClientError> {
92 let (_, status) = self.header().await?;
93 Ok(status)
94 }
95
96 /// Retrieves the account header and status.
97 pub async fn header(&self) -> Result<(AccountHeader, AccountStatus), ClientError> {
98 self.store
99 .get_account_header(self.account_id)
100 .await?
101 .ok_or(ClientError::AccountDataNotFound(self.account_id))
102 }
103
104 /// Retrieves the minimal partial account representation for this account.
105 pub(crate) async fn partial_account(&self) -> Result<PartialAccount, ClientError> {
106 self.store
107 .get_minimal_partial_account(self.account_id)
108 .await?
109 .ok_or(ClientError::AccountDataNotFound(self.account_id))?
110 .try_into()
111 }
112
113 /// Retrieves the addresses associated with this account.
114 pub async fn addresses(&self) -> Result<Vec<Address>, ClientError> {
115 self.store
116 .get_addresses_by_account_id(self.account_id)
117 .await
118 .map_err(ClientError::StoreError)
119 }
120
121 // VAULT ACCESS
122 // --------------------------------------------------------------------------------------------
123
124 /// Retrieves all assets in the account's vault as a plain list, without building the vault's
125 /// Merkle tree.
126 ///
127 /// To load the entire vault, use
128 /// [`Client::get_account_vault`](crate::Client::get_account_vault).
129 pub async fn assets(&self) -> Result<Vec<Asset>, ClientError> {
130 self.store
131 .get_account_assets(self.account_id)
132 .await
133 .map_err(ClientError::StoreError)
134 }
135
136 /// Retrieves the balance of a fungible asset in the account's vault.
137 ///
138 /// Returns [`AssetAmount::ZERO`] if the asset is not present in the vault.
139 ///
140 /// To load the entire vault, use
141 /// [`Client::get_account_vault`](crate::Client::get_account_vault).
142 ///
143 /// # Errors
144 /// Returns an error if the stored asset cannot be read as a fungible asset. The lookup key is
145 /// built as a fungible asset id, so that only happens for a stored value the protocol rejects.
146 pub async fn get_balance(&self, faucet_id: AccountId) -> Result<AssetAmount, ClientError> {
147 let asset_id = AssetId::new_fungible(faucet_id);
148 let Some((asset, _)) = self.store.get_account_asset(self.account_id, asset_id).await?
149 else {
150 return Ok(AssetAmount::ZERO);
151 };
152
153 let fungible_asset = FungibleAsset::from_id_and_value(asset.id(), asset.to_value_word())?;
154
155 Ok(fungible_asset.amount())
156 }
157
158 // STORAGE ACCESS
159 // --------------------------------------------------------------------------------------------
160
161 /// Retrieves a storage slot value by name.
162 ///
163 /// This method fetches the requested slot from storage.
164 ///
165 /// For `Value` slots, returns the stored word. For `Map` slots, returns the map root.
166 pub async fn get_storage_item(
167 &self,
168 slot_name: impl Into<StorageSlotName>,
169 ) -> Result<Word, ClientError> {
170 self.store
171 .get_account_storage_item(self.account_id, slot_name.into())
172 .await
173 .map_err(ClientError::StoreError)
174 }
175
176 /// Retrieves a value from a storage map slot by name and key.
177 ///
178 /// This method fetches only the requested slot from storage.
179 ///
180 /// # Errors
181 /// Returns an error if the slot is not found or is not a map.
182 pub async fn get_storage_map_item(
183 &self,
184 slot_name: impl Into<StorageSlotName>,
185 key: StorageMapKey,
186 ) -> Result<Word, ClientError> {
187 let (value, _witness) =
188 self.store.get_account_map_item(self.account_id, slot_name.into(), key).await?;
189 Ok(value)
190 }
191
192 /// Retrieves a value and its Merkle witness from a storage map slot.
193 ///
194 /// This method fetches the requested slot from storage and it's inclusion proof.
195 ///
196 /// # Errors
197 /// Returns an error if the slot is not found or is not a map.
198 pub async fn get_storage_map_witness(
199 &self,
200 slot_name: impl Into<StorageSlotName>,
201 key: StorageMapKey,
202 ) -> Result<(Word, StorageMapWitness), ClientError> {
203 self.store
204 .get_account_map_item(self.account_id, slot_name.into(), key)
205 .await
206 .map_err(ClientError::StoreError)
207 }
208}