1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
use alloc::string::String;
use alloc::vec::Vec;
use js_export_macro::js_export;
use miden_client::Word as NativeWord;
use miden_client::account::StorageSlotName;
use miden_client::asset::{AccountStorageHeader, Asset as NativeAsset};
use miden_client::block::BlockNumber;
use miden_client::rpc::domain::account::{
AccountProof as NativeAccountProof,
AccountStorageMapDetails,
StorageMapEntries,
};
use super::account_code::AccountCode;
use super::account_header::AccountHeader;
use super::account_id::AccountId;
use super::fungible_asset::FungibleAsset;
use super::word::Word;
use crate::js_error_with_context;
use crate::platform::JsErr;
/// Proof of existence of an account's state at a specific block number, as returned by the node.
///
/// For public accounts, this includes the account header, storage slot values, account code,
/// and optionally storage map entries for the requested storage maps.
/// For private accounts, only the account commitment and merkle proof are available.
#[derive(Clone)]
#[js_export]
pub struct AccountProof {
inner: NativeAccountProof,
block_num: BlockNumber,
}
#[js_export]
impl AccountProof {
/// Returns the account ID.
#[js_export(js_name = "accountId")]
pub fn account_id(&self) -> AccountId {
self.inner.account_id().into()
}
/// Returns the block number at which this proof was retrieved.
#[js_export(js_name = "blockNum")]
pub fn block_num(&self) -> u32 {
self.block_num.as_u32()
}
/// Returns the account commitment (hash of the full state).
#[js_export(js_name = "accountCommitment")]
pub fn account_commitment(&self) -> Word {
self.inner.account_commitment().into()
}
/// Returns the account header, if available (public accounts only).
#[js_export(js_name = "accountHeader")]
pub fn account_header(&self) -> Option<AccountHeader> {
self.inner.account_header().map(Into::into)
}
/// Returns the account code, if available (public accounts only).
#[js_export(js_name = "accountCode")]
pub fn account_code(&self) -> Option<AccountCode> {
self.inner.account_code().map(Into::into)
}
/// Returns the value of a storage slot by name, if available.
///
/// For `Value` slots, this returns the stored word.
/// For `Map` slots, this returns the map root commitment.
///
/// Returns `undefined` if the account is private or the slot name is not found.
#[js_export(js_name = "getStorageSlotValue")]
pub fn get_storage_slot_value(&self, slot_name: String) -> Result<Option<Word>, JsErr> {
let Some(storage_header) = self.inner.storage_header() else {
return Ok(None);
};
let slot_name = StorageSlotName::new(slot_name)
.map_err(|err| js_error_with_context(err, "invalid slot name"))?;
Ok(storage_header
.find_slot_header_by_name(&slot_name)
.map(|slot| slot.value().into()))
}
/// Returns the number of storage slots, if available (public accounts only).
#[js_export(js_name = "numStorageSlots")]
pub fn num_storage_slots(&self) -> Option<u8> {
self.inner.storage_header().map(AccountStorageHeader::num_slots)
}
/// Returns storage map entries for a given slot name, if available.
///
/// Returns `undefined` if the account is private, the slot was not requested in the
/// storage requirements, or the slot is not a map.
///
/// When the node returned the map as a partial SMT covering only the requested keys,
/// the entries are those keys with their proven values. When the slot exceeded the
/// node's response limit, an empty list is returned — use
/// `hasStorageMapTooManyEntries` to distinguish that case and fetch the map via
/// `RpcClient.syncStorageMaps()`.
///
/// Each entry contains a `key` and `value` as `Word` objects.
#[js_export(js_name = "getStorageMapEntries")]
pub fn get_storage_map_entries(
&self,
slot_name: String,
) -> Result<Option<Vec<StorageMapEntryJs>>, JsErr> {
let slot_name = StorageSlotName::new(slot_name)
.map_err(|err| js_error_with_context(err, "invalid slot name"))?;
let Some(map_details) = self.inner.find_map_details(&slot_name) else {
return Ok(None);
};
let entries = match &map_details.entries {
StorageMapEntries::AllEntries(entries) => entries
.iter()
.map(|e| StorageMapEntryJs {
key: Word::from(NativeWord::from(e.key)),
value: Word::from(e.value),
})
.collect(),
// Every key in `map_keys` is guaranteed to be tracked by `partial_smt`
// (validated when the RPC response is parsed), so the value reads cannot
// fail in practice.
StorageMapEntries::PartialMap { map_keys, partial_smt } => map_keys
.iter()
.map(|key| {
partial_smt
.get_value(&key.hash().as_word())
.map(|value| StorageMapEntryJs {
key: Word::from(NativeWord::from(*key)),
value: Word::from(value),
})
.map_err(|err| {
js_error_with_context(
err,
"partial storage map does not track a requested key",
)
})
})
.collect::<Result<Vec<_>, _>>()?,
// The node returned no entries because the slot exceeds the per-response
// limit; the map has to be fetched with `RpcClient.syncStorageMaps()`.
StorageMapEntries::LimitExceeded => Vec::new(),
};
Ok(Some(entries))
}
/// Returns whether a storage map slot had too many entries to return inline.
///
/// When this returns `true`, use `RpcClient.syncStorageMaps()` to fetch the full
/// storage map data.
///
/// Returns `undefined` if the slot was not found or the account is private.
#[js_export(js_name = "hasStorageMapTooManyEntries")]
pub fn has_storage_map_too_many_entries(
&self,
slot_name: String,
) -> Result<Option<bool>, JsErr> {
let slot_name = StorageSlotName::new(slot_name)
.map_err(|err| js_error_with_context(err, "invalid slot name"))?;
Ok(self
.inner
.find_map_details(&slot_name)
.map(AccountStorageMapDetails::is_limit_exceeded))
}
/// Returns the fungible assets in the account's vault, if vault details were included
/// in the proof response.
///
/// Returns `undefined` if the account is private or vault data was not requested.
#[js_export(js_name = "vaultFungibleAssets")]
pub fn vault_fungible_assets(&self) -> Option<Vec<FungibleAsset>> {
self.inner.vault_details().map(|d| {
d.assets
.iter()
.filter_map(|asset| match asset {
NativeAsset::Fungible(f) => Some((*f).into()),
NativeAsset::NonFungible(_) => None,
})
.collect()
})
}
/// Returns the names of all storage slots that have map details available.
///
/// This can be used to discover which storage maps were included in the proof response.
/// Returns `undefined` if the account is private.
#[js_export(js_name = "getStorageMapSlotNames")]
pub fn get_storage_map_slot_names(&self) -> Option<Vec<String>> {
self.inner
.storage_details()
.map(|details| details.map_details.iter().map(|d| d.slot_name.to_string()).collect())
}
}
// STORAGE MAP ENTRY
// ================================================================================================
/// A key-value entry from a storage map.
#[derive(Clone)]
#[js_export(js_name = "StorageMapEntryJs")]
pub struct StorageMapEntryJs {
key: Word,
value: Word,
}
#[js_export]
impl StorageMapEntryJs {
/// Returns the storage map key.
pub fn key(&self) -> Word {
self.key.clone()
}
/// Returns the storage map value.
pub fn value(&self) -> Word {
self.value.clone()
}
}
// CONVERSIONS
// ================================================================================================
impl AccountProof {
pub fn new(inner: NativeAccountProof, block_num: BlockNumber) -> Self {
Self { inner, block_num }
}
}