Skip to main content
Version: 0.14

Recipes

Short patterns covering the common cases. For longer walkthroughs — building a full wallet app from scratch, including UI — see the React wallet tutorial in the tutorials repo, which uses these hooks end-to-end.

Show transaction progress​

Every mutation hook exposes isLoading and stage; use them for optimistic UI:

import { useSend } from "@miden-sdk/react";

function SendButton({ from, to, assetId }: Props) {
const { send, stage, isLoading, error } = useSend();

const handleSend = async () => {
try {
await send({ from, to, assetId, amount: 100n });
} catch (err) {
console.error("Send failed:", err);
}
};

return (
<>
<button onClick={handleSend} disabled={isLoading}>
{isLoading ? `${stage}…` : "Send"}
</button>
{error && <p role="alert">{error.message}</p>}
</>
);
}

Format token amounts​

import { formatAssetAmount, parseAssetAmount } from "@miden-sdk/react";

// Display: 1_000_000n with 8 decimals → "0.01"
const display = formatAssetAmount(balance, 8);

// User input: "0.01" with 8 decimals → 1_000_000n
const amount = parseAssetAmount("0.01", 8);

Display a note summary​

import { getNoteSummary, formatNoteSummary } from "@miden-sdk/react";

const summary = getNoteSummary(note);
const text = formatNoteSummary(summary); // "1.5 USDC"

noteSummaries from useNotes() already runs getNoteSummary for you — these helpers are for ad-hoc formatting elsewhere.

Wait for confirmation after a send​

import { useSend, useWaitForCommit } from "@miden-sdk/react";

const { send } = useSend();
const { waitForCommit } = useWaitForCommit();

const result = await send({ from, to, assetId, amount: 100n });
await waitForCommit(result.txId);

Drop to the raw client​

import { useMidenClient } from "@miden-sdk/react";

function BlockHeaderPeek() {
const client = useMidenClient();
const header = await client.getBlockHeaderByNumber(100);
// ... whatever the hooks don't expose
}

useMidenClient() throws if the provider isn't ready — guard with useMiden().isReady when you render before init.

Prevent race conditions​

Two user actions can fire in quick succession — a double-click on "Send", or a hook plus a manual button both wanting to sign. The React SDK exposes a lock:

import { useMiden, useMidenClient } from "@miden-sdk/react";

function CompoundFlow() {
const { runExclusive } = useMiden();
const client = useMidenClient();

const run = () =>
runExclusive(async () => {
// Multiple client calls that must not interleave with other hooks'
// WASM work run here — the lock serialises them across the whole app.
await client.sync();
// ...
});

return <button onClick={run}>Run</button>;
}

runExclusive<T>(fn) takes a zero-argument async function; reach for the client via useMidenClient() inside it. Built-in mutations already use this lock internally; runExclusive is the escape hatch for your own compound flows.

Isolated clients for multi-wallet apps​

MidenProvider's config does not accept a storeName directly. Per-user isolation flows through the active signer: each SignerContext.Provider supplies its own storeName field, and MidenProvider reads that when initialising the underlying client. See the Signers guide for a custom signer that picks a unique store name per connected user (typically the wallet address or a hash of it).

If you just need two wallets side-by-side in a dev environment and don't want to wire a signer, mount two separate MidenProviders in isolated subtrees backed by different signer contexts.

Account IDs — hex and bech32 interchangeably​

Every hook accepts either:

// Both are valid
useAccount("0x1234567890abcdef");
useAccount("mtst1qy35...");

// Convert for display
account.bech32id(); // "mtst1qy35..."

import { toBech32AccountId } from "@miden-sdk/react";
toBech32AccountId(someHexId); // "mtst1qy35..."

Troubleshooting​

SymptomLikely cause
"Client not ready" thrown by a hookComponent rendered before MidenProvider finished initializing. Guard with useMiden().isReady or render via MidenProvider's loadingComponent.
Transactions stuck in "proving"Remote prover unreachable. Check prover config and network; consider prover: { primary: "testnet", fallback: "local" }.
Notes not appearing after mintCall sync() from useSyncState() or verify autoSyncInterval isn't 0.
Bech32 address has wrong prefixrpcUrl doesn't match the network you intended. "testnet" → mtst1..., "devnet" → mdev1....
WASM init fails in devEnsure your bundler serves .wasm with the application/wasm MIME type. Vite does this automatically; some custom setups don't.
"A send is already in progress"Two useSend mutations fired simultaneously. Either await the previous call before starting the next, or use runExclusive to coordinate.

Next​