| name | react-sdk-patterns |
| description | Complete guide to building Miden frontends with @miden-sdk/react hooks. Covers MidenProvider setup, all query hooks (useAccounts, useAccount, useNotes, useSyncState, useAssetMetadata), all mutation hooks (useCreateWallet, useSend, useMultiSend, useMint, useConsume, useSwap, useTransaction, useCreateFaucet), transaction stages, signer integration, and utility functions. Use when writing, editing, or reviewing Miden React frontend code. |
Miden React SDK Patterns
SDK Choice
ALWAYS use @miden-sdk/react hooks. Only fall back to the raw WasmWebClient (exported as WebClient) via useMidenClient() for operations not covered by hooks. The React SDK handles WASM safety (runExclusive), state management (Zustand), auto-sync, and transaction stage tracking automatically.
MidenProvider Configuration
import { MidenProvider } from "@miden-sdk/react";
<MidenProvider
config={{
rpcUrl: "testnet", // "devnet" | "testnet" | "localhost" | "local" | custom URL
prover: "testnet", // "local" | "localhost" | "devnet" | "testnet" | custom URL | { primary, fallback }
autoSyncInterval: 15000, // ms, set to 0 to disable. Default: 15000
noteTransportUrl: "...", // optional: for private note delivery
}}
loadingComponent={<Loading />} // shown during WASM init
errorComponent={(error) => <Error error={error} />} // function form receives the Error; a static element does not
>
<App />
</MidenProvider>
| Network | rpcUrl | Use When |
|---|
| Testnet | "testnet" | Recommended for new projects — primary development network |
| Devnet | "devnet" | Early-access testing (may lag feature parity with testnet) |
| Localhost | "localhost" | Local node at http://localhost:57291 |
Query Hooks
Each returns its own result shape plus isLoading, error, refetch.
useAccounts()
const { accounts, wallets, faucets, isLoading, error, refetch } = useAccounts();
An account's faucet-vs-wallet kind is not encoded in the account id, so wallets mirrors accounts and faucets is always empty. Use accounts and detect faucets per-account via account.isFaucet() (load the full Account with useAccount).
useAccount(accountId: string)
const { account, assets, getBalance, isLoading, error, refetch } = useAccount(accountId);
account.id() and account.nonce() are methods (call them, then .toString() to render). bech32id() is installed on the Account prototype by the React SDK.
useNotes(filter?)
const { notes, consumableNotes, noteSummaries, consumableNoteSummaries, isLoading, error, refetch } = useNotes();
const { notes } = useNotes({ status: "committed" });
const { consumableNotes } = useNotes({ accountId: "0x..." });
const { noteSummaries, consumableNoteSummaries } = useNotes({ sender: "0x..." });
const { noteSummaries, consumableNoteSummaries } = useNotes({ excludeIds: ["0xnote1", "0xnote2"] });
useNoteStream(options?)
const { notes, latest, markHandled, markAllHandled, snapshot, isLoading, error } = useNoteStream();
const { notes } = useNoteStream({ status: "committed", sender: "0x..." });
const { notes } = useNoteStream({ since: Date.now() - 60000 });
const { notes } = useNoteStream({ excludeIds: new Set(["0xnote1"]) });
const { notes } = useNoteStream({ amountFilter: (amount) => amount > 100n });
useSyncState()
const { syncHeight, isSyncing, lastSyncTime, sync, error } = useSyncState();
await sync();
useAssetMetadata(assetIds?: string[])
const { assetMetadata } = useAssetMetadata([faucetId]);
const meta = assetMetadata.get(faucetId);
Pass an array even for a single asset — the hook calls .filter on its argument, so a bare string throws a runtime TypeError.
useTransactionHistory(options?)
const { records, record, status, isLoading, error, refetch } = useTransactionHistory({ id: txId });
Mutation Hooks
Each returns its own action function plus error and reset. The two families differ in their loading/progress fields:
- Transaction hooks (
useSend, useMultiSend, useMint, useConsume, useSwap, useTransaction) expose isLoading and stage (a TransactionStage).
- Account create/import hooks (
useCreateWallet, useCreateFaucet, useImportAccount) expose isCreating (or isImporting for the latter) and have no stage.
Transaction stages: "idle" → "executing" → "proving" → "submitting" → "complete"
Auth scheme for the create/import hooks. The AuthScheme re-exported from the package root is the friendly string const { Falcon: "falcon", ECDSA: "ecdsa" }:
import { AuthScheme } from "@miden-sdk/react";
Known issue (web-sdk#223): useCreateWallet / useCreateFaucet / useImportAccount forward authScheme straight to the low-level WebClient.newWallet, which currently expects the numeric wasm enum (AuthRpoFalcon512 = 2, AuthEcdsaK256Keccak = 1), not the friendly string, and the default resolves to undefined (which hangs the call). Until it is fixed, pass the numeric value: authScheme: 2 (Falcon) or authScheme: 1 (ECDSA). The examples below use 2.
useCreateWallet()
const { createWallet, wallet, isCreating, error, reset } = useCreateWallet();
const account = await createWallet({
storageMode: "private",
authScheme: 2,
initSeed: seedBytes,
});
useCreateFaucet()
const { createFaucet, faucet, isCreating, error, reset } = useCreateFaucet();
const account = await createFaucet({
tokenSymbol: "TEST",
tokenName: "Test Token",
decimals: 8,
maxSupply: 1000000n,
storageMode: "private",
authScheme: 2,
});
useImportAccount()
const { importAccount, account, isImporting, error, reset } = useImportAccount();
const account = await importAccount({ type: "id", accountId: "0x..." });
const account = await importAccount({ type: "file", file: accountFileOrBytes });
const account = await importAccount({
type: "seed",
seed: seedBytes,
authScheme: 2,
});
useSend()
const { send, result, isLoading, stage, error, reset } = useSend();
await send({
from: senderAccountId,
to: recipientAccountId,
assetId: faucetId,
amount: 1000n,
noteType: "private",
recallHeight: 100,
timelockHeight: 50,
sendAll: true,
attachment: [1n, 2n],
});
useMultiSend()
const { sendMany, result, isLoading, stage, error, reset } = useMultiSend();
await sendMany({
from: senderAccountId,
assetId: faucetId,
recipients: [
{ to: recipient1, amount: 500n },
{ to: recipient2, amount: 300n, noteType: "public" },
{ to: recipient3, amount: 200n, attachment: [1n, 2n, 3n] },
],
noteType: "private",
});
useMint()
const { mint, result, isLoading, stage, error, reset } = useMint();
await mint({
targetAccountId: recipientId,
faucetId: myFaucetId,
amount: 10000n,
noteType: "public",
});
useConsume()
const { consume, result, isLoading, stage, error, reset } = useConsume();
await consume({
accountId: myAccountId,
notes: [noteId1, noteId2],
});
useSwap()
const { swap, result, isLoading, stage, error, reset } = useSwap();
await swap({
accountId: myAccountId,
offeredFaucetId: tokenA,
offeredAmount: 100n,
requestedFaucetId: tokenB,
requestedAmount: 50n,
noteType: "private",
paybackNoteType: "private",
});
useTransaction() — Escape Hatch
const { execute, result, isLoading, stage, error, reset } = useTransaction();
await execute({ accountId, request: txRequest });
await execute({
accountId,
request: (client) => client.newSwapTransactionRequest(),
});
useWaitForCommit()
const { waitForCommit } = useWaitForCommit();
await waitForCommit(result.txId, {
timeoutMs: 10000,
intervalMs: 1000,
});
useWaitForNotes()
const { waitForConsumableNotes } = useWaitForNotes();
await waitForConsumableNotes({
accountId: myAccountId,
minCount: 1,
timeoutMs: 10000,
});
useSessionAccount(options)
const { initialize, sessionAccountId, isReady, step, error, reset } = useSessionAccount({
fund: async (sessionId) => {
await send({ from: mainWallet, to: sessionId, assetId: faucetId, amount: 100n });
},
assetId: faucetId,
walletOptions: {
storageMode: "private",
authScheme: 2,
},
pollIntervalMs: 3000,
});
Transaction Progress UI
function SendButton({ from, to, assetId, amount }) {
const { send, stage, isLoading, error } = useSend();
return (
<div>
<button onClick={() => send({ from, to, assetId, amount })} disabled={isLoading}>
{isLoading ? `${stage}...` : "Send"}
</button>
{error && <p>Error: {error.message}</p>}
</div>
);
}
Signer Integration
Local Keystore (Default)
No signer provider needed. Keys are managed in the browser via IndexedDB.
External Signers
Wrap MidenProvider with a signer provider. Three pre-built options:
ParaSignerProvider from @miden-sdk/use-miden-para-react — EVM wallets
TurnkeySignerProvider from @miden-sdk/miden-turnkey-react — passkey auth
MidenFiSignerProvider from @miden-sdk/miden-wallet-adapter-react — MidenFi wallet
These three packages live in external repos (not in web-sdk), so confirm the exact published names against the current Para/Turnkey/MidenFi integration docs before installing. The v0.15 example app (packages/react-sdk/examples/wallet/src/main.tsx) imports them as above; some web-sdk docs alias the Para package as @miden-sdk/para.
import { ParaSignerProvider } from "@miden-sdk/use-miden-para-react";
<ParaSignerProvider apiKey="..." environment="BETA">
<MidenProvider config={...}><App /></MidenProvider>
</ParaSignerProvider>
useSigner() — Unified Interface
Returns SignerContextValue | null — null in local-keystore mode (no signer provider mounted). Guard before destructuring.
const signer = useSigner();
if (!signer) return null;
const { isConnected, connect, disconnect, name } = signer;
Custom Signer
Implement SignerContextValue interface via SignerContext.Provider. Requires: name, storeName (unique per user for DB isolation), accountConfig, signCb, isConnected, connect, disconnect. See frontend-source-guide skill for source references.
Utility Functions
import { formatAssetAmount, parseAssetAmount, getNoteSummary, formatNoteSummary, toBech32AccountId } from "@miden-sdk/react";
formatAssetAmount(1000000n, 8)
parseAssetAmount("0.01", 8)
const summary = getNoteSummary(note);
formatNoteSummary(summary);
toBech32AccountId("0x1234...");
The HRP is inferred from the configured rpcUrl and defaults to testnet: mainnet=mm, testnet=mtst (default), devnet=mdev — there is no miden HRP.
Direct Client Access
const client = useMidenClient();
const { runExclusive } = useMiden();
await runExclusive(async () => {
const height = await client.getSyncHeight();
});
Type Imports
import { AuthScheme } from "@miden-sdk/react";
import type {
MidenConfig, QueryResult, MutationResult, TransactionStage,
AccountsResult, AccountResult, AssetBalance, NotesResult, NoteSummary,
SendOptions, MultiSendOptions, MintOptions, ConsumeOptions, SwapOptions,
CreateWalletOptions, CreateFaucetOptions, ExecuteTransactionOptions,
TransactionResult, SyncState, WaitForCommitOptions, WaitForNotesOptions,
Account, AccountId, InputNoteRecord, ConsumableNoteRecord,
TransactionRecord, TransactionRequest, NoteType, AccountStorageMode,
SignerContextValue, SignCallback, SignerAccountConfig,
} from "@miden-sdk/react";