Advanced hooks
Hooks beyond the core send / mint / consume trio: custom scripts, anchored transaction previews, MASM compilation, session wallets, store backup, note serialization, and sync control.
useTransaction
General-purpose transaction runner that accepts either a prebuilt TransactionRequest or a builder callback. This is the escape hatch when the higher-level hooks don't cover your flow.
import { useTransaction } from "@miden-sdk/react";
import { TransactionRequestBuilder } from "@miden-sdk/miden-sdk";
const { execute, isLoading, stage } = useTransaction();
// Direct request
await execute({
accountId: contractAccount,
request: prebuiltRequest,
});
// Builder callback — receives the raw WebClient
await execute({
accountId: contractAccount,
request: (_client) =>
new TransactionRequestBuilder()
.withCustomScript(txScript)
.build(),
});
UseTransactionResult exposes execute (not executeTransaction), plus result, isLoading, stage, error, and reset.
ExecuteTransactionOptions:
| Field | Description |
|---|---|
accountId | Account the transaction applies to |
request | TransactionRequest or (client: WebClient) => TransactionRequest | Promise<TransactionRequest> |
skipSync | Skip pre-send auto-sync (default false) |
privateNoteTarget | Deliver private output notes to this account after commit (any AccountRef form) |
anchor | Execute against a reference block captured with useChainAnchor |
The privateNoteTarget field is the 4-step pipeline shortcut: execute the tx, commit onchain, then auto-deliver the private note through the note transport to the target. Useful for "send private note" UIs where the recipient already has the React SDK running.
useChainAnchor and usePreview
Use these hooks when a transaction summary is proposed on one client and authorized or executed on another, such as multisig and offline co-signing flows. useChainAnchor pins the request to one reference block; usePreview derives the summary awaiting authorization at that block.
Capture and preview in separate UI steps. anchoredRequest is React state, so it becomes available on the render after captureAnchor() completes:
import { useState } from "react";
import { useChainAnchor, usePreview, useTransaction } from "@miden-sdk/react";
import type { TransactionRequest, TransactionSummary } from "@miden-sdk/miden-sdk";
type MultisigProposalProps = {
accountId: string;
buildRequest: () => TransactionRequest | Promise<TransactionRequest>;
sendProposal: (anchor: Uint8Array, summary: Uint8Array) => Promise<void>;
collectAuthorization: (
summary: TransactionSummary,
request: TransactionRequest,
) => Promise<TransactionRequest>;
};
function MultisigProposal({
accountId,
buildRequest,
sendProposal,
collectAuthorization,
}: MultisigProposalProps) {
const { captureAnchor, anchor, anchoredRequest, isCapturing } = useChainAnchor();
const { preview, isPreviewing } = usePreview();
const { execute, isLoading } = useTransaction();
const [authorizedRequest, setAuthorizedRequest] =
useState<TransactionRequest | null>(null);
const [isAuthorizing, setIsAuthorizing] = useState(false);
const busy = isCapturing || isPreviewing || isAuthorizing || isLoading;
const capture = async () => {
setAuthorizedRequest(null);
await captureAnchor({ request: buildRequest });
};
const previewAndShare = async () => {
if (!anchor || !anchoredRequest) return;
setAuthorizedRequest(null);
setIsAuthorizing(true);
try {
const summary = await preview({
accountId,
request: anchoredRequest,
anchor,
});
await sendProposal(anchor.serialize(), summary.serialize());
setAuthorizedRequest(await collectAuthorization(summary, anchoredRequest));
} finally {
setIsAuthorizing(false);
}
};
const executeAnchored = async () => {
if (!anchor || !authorizedRequest) return;
await execute({ accountId, request: authorizedRequest, anchor });
setAuthorizedRequest(null);
};
return (
<>
<button onClick={capture} disabled={busy}>
Capture reference block
</button>
<button
onClick={previewAndShare}
disabled={!anchor || !anchoredRequest || busy}
>
Preview and share
</button>
<button
onClick={executeAnchored}
disabled={!anchor || !authorizedRequest || busy}
>
Execute authorized request
</button>
</>
);
}
collectAuthorization is supplied by your application. It collects the required
signatures and returns the request with the authorization advice attached. Keep
the proposed actions, fee conversion salt, and reference block unchanged. For a
multisig account, buildRequest must declare a fresh salt with
withFeeConversionSalt. The execute button becomes available after authorization
has been collected.
preview() rejects with TRANSACTION_ALREADY_AUTHORIZED when the request needs no additional authorization; execute it directly in that case. A ChainAnchor owns a WASM allocation, so call anchor.free() when the proposal workflow no longer needs it.
useExecuteProgram
View call — executes a transaction script locally and returns the stack output. No prove, no submit, no state change. Think of it as Miden's eth_call.
import { useExecuteProgram } from "@miden-sdk/react";
const { execute, isLoading, error } = useExecuteProgram();
const result = await execute({
accountId: contractAccount,
script: compiledTxScript,
foreignAccounts: [counterAccount], // optional
});
// result.stack is a bigint[] — read indices directly
const count: bigint = result.stack[0];
console.log("Count:", count);
UseExecuteProgramResult exposes execute (not executeProgram), plus result, isLoading, error, and reset. No stage — view calls don't prove or submit.
The React hook flattens the 16-element stack into a plain bigint[]. useMidenClient() exposes the underlying WASM WebClient directly — its method is client.executeProgram(...) (not namespaced under client.transactions). See the Web SDK transactions guide for the imperative MidenClient.transactions.executeProgram equivalent and the FeltArray shape.
useCompile
Compiles Miden Assembly into AccountComponent, TransactionScript, or NoteScript. Each result method is independently callable — call only what you need for the current operation.
import { useCompile } from "@miden-sdk/react";
import { StorageSlot } from "@miden-sdk/miden-sdk";
const { component, txScript, noteScript, isReady } = useCompile();
// Account component
const counterComponent = await component({
code: counterContractCode,
namespace: "external_contract::counter_contract",
slots: [StorageSlot.emptyValue("miden::tutorials::counter")],
});
// Transaction script (with optional libraries)
const script = await txScript({
code: `
use external_contract::counter_contract
@transaction_script
pub proc main
call.counter_contract::increment_count
end
`,
libraries: [{ component: counterComponent }],
});
// Note script — use the @note_script attribute on a library proc
const attachScript = await noteScript({
code: `
use miden::protocol::active_note
use miden::core::sys
@note_script
pub proc on_consume
# body runs when the consuming account redeems this note
exec.sys::truncate_stack
end
`,
});
UseCompileResult exposes the three compile methods plus isReady. Loading and error state are tracked internally per call — catch errors at the individual await site. See the Web SDK compile guide for the full CompileComponentOptions / CompileTxScriptOptions / CompileNoteScriptOptions shapes.
useSessionAccount
Drives the "session wallet" pattern — create a throw-away wallet, wait for a funding note, consume it, then hand control back to your app. Useful for one-off interactions that shouldn't touch a long-lived account.
import { useMiden, useSessionAccount } from "@miden-sdk/react";
import { getWasmOrThrow } from "@miden-sdk/miden-sdk/lazy";
// Resolve the numeric authentication enum once, before rendering this module.
const { AuthScheme } = await getWasmOrThrow();
export function SessionWallet({
fund,
}: {
fund: (sessionAccountId: string) => Promise<void>;
}) {
const { isReady: clientReady } = useMiden();
const { initialize, sessionAccountId, isReady, step, error, reset } =
useSessionAccount({
// The callback receives the new wallet's hex ID. Send a funding note
// containing native fee tokens, plus any assets the session needs.
fund,
walletOptions: {
storageMode: "private",
authScheme: AuthScheme.AuthRpoFalcon512,
},
pollIntervalMs: 3_000,
maxWaitMs: 60_000,
});
return (
<>
<button
onClick={() => void initialize().catch(console.error)}
disabled={!clientReady || step !== "idle"}
>
{step === "idle" ? "Start session" : step}
</button>
{isReady && <p>Session ready: {sessionAccountId}</p>}
{error && <p role="alert">{error.message}</p>}
<button onClick={reset}>Reset session</button>
</>
);
}
In 0.16.0, pass the raw numeric authentication enum explicitly, as above; the
hook's default resolves to an undefined enum member. The fund callback belongs
to your application. Its note must include the native fee asset so the new wallet
can pay for its first consume transaction.
The flow progresses through idle → creating → funding → consuming → ready.
UseSessionAccountReturn:
| Field | Description |
|---|---|
initialize() | Kicks off the create → fund → consume flow |
sessionAccountId | Hex ID of the session wallet once created |
isReady | true after the funding note has been consumed |
step | SessionAccountStep — one of the five states above |
error | Non-null if any step failed |
reset() | Clears session data (and any persisted state under storagePrefix) |
Session state persists under the configurable storagePrefix (default "miden-session") so page reloads can resume mid-flow.
useExportStore / useImportStore
Back up and restore the entire local store as a JSON dump. Handy for wallet backup/restore UIs.
import { useExportStore, useImportStore, useMidenClient } from "@miden-sdk/react";
// Export — returns a JSON string
const { exportStore } = useExportStore();
const dump: string = await exportStore();
download(new Blob([dump]), "wallet-backup.json");
// Import (destructive — overwrites the target store)
// Positional: (storeDump, storeName, options?)
const { importStore } = useImportStore();
const client = useMidenClient();
const storeName = await client.storeIdentifier();
await importStore(uploadedDump, storeName, { skipSync: false });
ImportStoreOptions exposes skipSync (default false) so you can defer the post-import sync. There's no second "raw bytes" form — importStore takes the JSON dump string as its first argument and the target store name as its second.
useImportNote / useExportNote
Serialize notes to bytes for QR delivery or import notes handed over out-of-band. These complement the private-note transport layer — use the transport when the recipient is online, and QR/bytes when they aren't.
In 0.16.0, exportNote requires an output-note ID tracked by this client. An
imported input note alone is insufficient and returns No output note found.
import { useExportNote, useImportNote } from "@miden-sdk/react";
const { exportNote } = useExportNote();
const noteBytes = await exportNote(noteId);
// encode noteBytes into a QR, link, email, etc.
const { importNote } = useImportNote();
await importNote(uploadedBytes);
useSyncControl
Pause and resume the auto-sync loop without dismounting MidenProvider. Useful when a long operation needs consistent local state, or during battery-sensitive background work.
import { useSyncControl } from "@miden-sdk/react";
const { pauseSync, resumeSync, isPaused } = useSyncControl();
// Before a long sequence
pauseSync();
// ... operations that need a stable snapshot ...
resumeSync();
pauseSync() stops the timer but doesn't cancel an in-flight sync — wait for isSyncing from useSyncState() to settle if you need a truly quiescent state.