diff --git a/CHANGELOG.md b/CHANGELOG.md index f81aea52d..427baee36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ * Incremented the limits for various RPC calls to accommodate larger data sets ([#1621](https://github.com/0xMiden/miden-client/pull/1621)). * [BREAKING] Introduced named storage slots, changed `FilesystemKeystore` to not be generic over RNG ([#1626](https://github.com/0xMiden/miden-client/pull/1626)). * Added `submit_new_transaction_with_prover` to the Rust client and `submitNewTransactionWithProver` to the WebClient([#1622](https://github.com/0xMiden/miden-client/pull/1622)). +* Added `getAccountDetails` to WebClient's `RpcClient` ([#1628](https://github.com/0xMiden/miden-client/pull/1628)) ## 0.12.5 (2025-12-01) diff --git a/crates/web-client/js/types/index.d.ts b/crates/web-client/js/types/index.d.ts index f34ebebc4..deb69acbb 100644 --- a/crates/web-client/js/types/index.d.ts +++ b/crates/web-client/js/types/index.d.ts @@ -39,6 +39,7 @@ export { FeltArray, FetchedNote, FlattenedU8Vec, + FetchedAccount, ForeignAccount, ForeignAccountArray, FungibleAsset, diff --git a/crates/web-client/src/models/fetched_account.rs b/crates/web-client/src/models/fetched_account.rs new file mode 100644 index 000000000..bdc8d4941 --- /dev/null +++ b/crates/web-client/src/models/fetched_account.rs @@ -0,0 +1,62 @@ +use miden_client::rpc::domain::account::FetchedAccount as NativeFetchedAccount; +use wasm_bindgen::prelude::wasm_bindgen; + +use crate::models::account::Account; +use crate::models::account_id::AccountId; +use crate::models::word::Word; + +/// Describes the response from the `GetAccountDetails` endpoint. +/// +/// The content varies based on account visibility: +/// - **Public or Network accounts**: Contains the complete [`Account`] details, as these are stored +/// on-chain +/// - **Private accounts**: Contains only the state commitment, since full account data is stored +/// off-chain +#[wasm_bindgen] +pub struct FetchedAccount(NativeFetchedAccount); + +#[wasm_bindgen] +impl FetchedAccount { + /// Returns true if the fetched account is private + #[wasm_bindgen(js_name = "isPrivate")] + pub fn is_private(&self) -> bool { + matches!(&self.0, NativeFetchedAccount::Private(_, _)) + } + + /// Returns true if the fetched account is public + #[wasm_bindgen(js_name = "isPublic")] + pub fn is_public(&self) -> bool { + matches!(&self.0, NativeFetchedAccount::Public(_, _)) + } + + /// Returns the associated [`Account`] if the account is public, otherwise none + pub fn account(&self) -> Option { + self.0.account().map(Into::into) + } + + /// Returns the account identifier + #[wasm_bindgen(js_name = "accountId")] + pub fn account_id(&self) -> AccountId { + self.0.account_id().into() + } + + /// Returns the account update summary commitment + pub fn commitment(&self) -> Word { + self.0.commitment().into() + } +} + +// CONVERSIONS +// ================================================================================================ + +impl From for FetchedAccount { + fn from(native_account: NativeFetchedAccount) -> Self { + FetchedAccount(native_account) + } +} + +impl From for NativeFetchedAccount { + fn from(fetched_account: FetchedAccount) -> Self { + fetched_account.0 + } +} diff --git a/crates/web-client/src/models/mod.rs b/crates/web-client/src/models/mod.rs index 80e44b803..8ee7a75db 100644 --- a/crates/web-client/src/models/mod.rs +++ b/crates/web-client/src/models/mod.rs @@ -52,6 +52,7 @@ pub mod consumable_note_record; pub mod endpoint; pub mod executed_transaction; pub mod felt; +pub mod fetched_account; pub mod foreign_account; pub mod fungible_asset; pub mod input_note; diff --git a/crates/web-client/src/rpc_client/mod.rs b/crates/web-client/src/rpc_client/mod.rs index fdc50f453..00f10258a 100644 --- a/crates/web-client/src/rpc_client/mod.rs +++ b/crates/web-client/src/rpc_client/mod.rs @@ -12,7 +12,9 @@ use note::FetchedNote; use wasm_bindgen::prelude::*; use crate::js_error_with_context; +use crate::models::account_id::AccountId; use crate::models::endpoint::Endpoint; +use crate::models::fetched_account::FetchedAccount; use crate::models::note_id::NoteId; use crate::models::note_script::NoteScript; use crate::models::word::Word; @@ -92,4 +94,24 @@ impl RpcClient { Ok(note_script.into()) } + + /// Fetches the current state of an account from the node using the `/GetAccountDetails` RPC + /// endpoint. + /// + /// @param `account_id` - the ID of the wanted account. + /// @returns Promise that resolves to the [`FetchedAccount`]. + #[wasm_bindgen(js_name = "getAccountDetails")] + pub async fn get_account_details( + &self, + account_id: AccountId, + ) -> Result { + let native_account_id = account_id.into(); + + let fetched_account = + self.inner.get_account_details(native_account_id).await.map_err(|err| { + js_error_with_context(err, "failed to get account for the given account id") + })?; + + Ok(fetched_account.into()) + } } diff --git a/crates/web-client/test/account.test.ts b/crates/web-client/test/account.test.ts index ab31e854c..17627de6c 100644 --- a/crates/web-client/test/account.test.ts +++ b/crates/web-client/test/account.test.ts @@ -1,5 +1,12 @@ import { Page, expect } from "@playwright/test"; import test from "./playwright.global.setup"; +import { + createNewFaucet, + createNewWallet, + fundAccountFromFaucet, + getAccount, + StorageMode, +} from "./webClientTestUtils"; // GET_ACCOUNT TESTS // ======================================================================================================= @@ -159,3 +166,90 @@ test.describe("getAccounts tests", () => { expect(result.resultTypes.length).toEqual(0); }); }); + +test.describe("fetch account tests", () => { + test("retrieves a public account", async ({ page }) => { + const walletSeed = new Uint8Array(32); + crypto.getRandomValues(walletSeed); + + const mutable = false; + const storageMode = StorageMode.PUBLIC; + const authSchemeId = 0; + const initialWallet = await createNewWallet(page, { + storageMode, + mutable, + authSchemeId, + walletSeed, + }); + const faucet = await createNewFaucet(page); + + const { targetAccountBalance: initialBalance } = + await fundAccountFromFaucet(page, initialWallet.id, faucet.id); + const { commitment } = await getAccount(page, initialWallet.id); + + const { isPublic, accountIsDefined, fetchedCommitment, balance } = + await page.evaluate(async (accountId: string) => { + const endpoint = new window.Endpoint(window.rpcUrl); + const rpcClient = new window.RpcClient(endpoint); + + const fetchedAccount = await rpcClient.getAccountDetails( + window.AccountId.fromHex(accountId) + ); + return { + isPublic: fetchedAccount.isPublic(), + accountIsDefined: !!fetchedAccount.account(), + fetchedCommitment: fetchedAccount.commitment().toHex(), + balance: fetchedAccount.account() + ? fetchedAccount + .account()! + .vault() + .fungibleAssets()[0] + .amount() + .toString() + : undefined, + }; + }, initialWallet.id); + expect(isPublic).toBe(true); + expect(accountIsDefined).toBe(true); + expect(fetchedCommitment).toBe(commitment); + expect(balance).toBe(initialBalance); + }); + + test("retrieves a private account", async ({ page }) => { + const walletSeed = new Uint8Array(32); + crypto.getRandomValues(walletSeed); + + const mutable = false; + const storageMode = StorageMode.PRIVATE; + const authSchemeId = 0; + const initialWallet = await createNewWallet(page, { + storageMode, + mutable, + authSchemeId, + walletSeed, + }); + const faucet = await createNewFaucet(page); + + await fundAccountFromFaucet(page, initialWallet.id, faucet.id); + + const { commitment } = await getAccount(page, initialWallet.id); + + const { isPrivate, accountIsDefined, fetchedCommitment } = + await page.evaluate(async (accountId: string) => { + const endpoint = new window.Endpoint(window.rpcUrl); + const rpcClient = new window.RpcClient(endpoint); + + const fetchedAccount = await rpcClient.getAccountDetails( + window.AccountId.fromHex(accountId) + ); + return { + isPrivate: fetchedAccount.isPrivate(), + accountIsDefined: !!fetchedAccount.account(), + fetchedCommitment: fetchedAccount.commitment().toHex(), + }; + }, initialWallet.id); + expect(isPrivate).toBe(true); + expect(accountIsDefined).toBe(false); + expect(fetchedCommitment).toBe(commitment); + }); +}); diff --git a/docs/typedoc/web-client/README.md b/docs/typedoc/web-client/README.md index a421631eb..fab2b22e6 100644 --- a/docs/typedoc/web-client/README.md +++ b/docs/typedoc/web-client/README.md @@ -46,6 +46,7 @@ - [ExecutedTransaction](classes/ExecutedTransaction.md) - [Felt](classes/Felt.md) - [FeltArray](classes/FeltArray.md) +- [FetchedAccount](classes/FetchedAccount.md) - [FetchedNote](classes/FetchedNote.md) - [FlattenedU8Vec](classes/FlattenedU8Vec.md) - [ForeignAccount](classes/ForeignAccount.md) diff --git a/docs/typedoc/web-client/classes/FetchedAccount.md b/docs/typedoc/web-client/classes/FetchedAccount.md new file mode 100644 index 000000000..4ca3c74f8 --- /dev/null +++ b/docs/typedoc/web-client/classes/FetchedAccount.md @@ -0,0 +1,95 @@ +[**@demox-labs/miden-sdk**](../README.md) + +*** + +[@demox-labs/miden-sdk](../README.md) / FetchedAccount + +# Class: FetchedAccount + +Describes the response from the `GetAccountDetails` endpoint. + +The content varies based on account visibility: +- **Public or Network accounts**: Contains the complete [`Account`] details, as these are stored + on-chain +- **Private accounts**: Contains only the state commitment, since full account data is stored + off-chain + +## Methods + +### \[dispose\]() + +> **\[dispose\]**(): `void` + +#### Returns + +`void` + +*** + +### account() + +> **account**(): [`Account`](Account.md) + +Returns the associated [`Account`] if the account is public, otherwise none + +#### Returns + +[`Account`](Account.md) + +*** + +### accountId() + +> **accountId**(): [`AccountId`](AccountId.md) + +Returns the account identifier + +#### Returns + +[`AccountId`](AccountId.md) + +*** + +### commitment() + +> **commitment**(): [`Word`](Word.md) + +Returns the account update summary commitment + +#### Returns + +[`Word`](Word.md) + +*** + +### free() + +> **free**(): `void` + +#### Returns + +`void` + +*** + +### isPrivate() + +> **isPrivate**(): `boolean` + +Returns true if the fetched account is private + +#### Returns + +`boolean` + +*** + +### isPublic() + +> **isPublic**(): `boolean` + +Returns true if the fetched account is public + +#### Returns + +`boolean` diff --git a/docs/typedoc/web-client/classes/RpcClient.md b/docs/typedoc/web-client/classes/RpcClient.md index 38d5c0465..199bda3bb 100644 --- a/docs/typedoc/web-client/classes/RpcClient.md +++ b/docs/typedoc/web-client/classes/RpcClient.md @@ -50,6 +50,27 @@ Endpoint to connect to. *** +### getAccountDetails() + +> **getAccountDetails**(`account_id`): `Promise`\<[`FetchedAccount`](FetchedAccount.md)\> + +Fetches the current state of an account from the node using the `/GetAccountDetails` RPC +endpoint. + +#### Parameters + +##### account\_id + +[`AccountId`](AccountId.md) + +#### Returns + +`Promise`\<[`FetchedAccount`](FetchedAccount.md)\> + +Promise that resolves to the [`FetchedAccount`]. + +*** + ### getNotesById() > **getNotesById**(`note_ids`): `Promise`\<[`FetchedNote`](FetchedNote.md)[]\>