Setting up the Web SDK
Install
Add @miden-sdk/miden-sdk to your project.
npm install @miden-sdk/[email protected]
# or
yarn add @miden-sdk/[email protected]
# or
pnpm add @miden-sdk/[email protected]
The SDK targets modern browsers (Chrome, Firefox, Safari, Edge). The browser build uses WebAssembly and a Web Worker when available. Under Node 20+, the package automatically selects its native N-API binding with SQLite-backed storage.
Create a client
Every operation goes through a MidenClient instance. Four factories cover the common cases:
| Factory | Use for |
|---|---|
MidenClient.createTestnet() | Miden testnet — RPC, prover, and note transport preconfigured |
MidenClient.createDevnet() | Miden devnet — same shape, devnet endpoints |
MidenClient.createMock() | Deterministic local mock chain for tests — no network |
MidenClient.create({ ... }) | Custom endpoints (localhost, self-hosted node, or any shorthand) |
import { MidenClient } from "@miden-sdk/miden-sdk";
// Miden testnet (most common for dApps under development)
const client = await MidenClient.createTestnet();
// Local node — "localhost" / "local" shorthand resolves to http://localhost:57291
const local = await MidenClient.create({ rpcUrl: "localhost" });
// Any custom URL
const custom = await MidenClient.create({
rpcUrl: "https://my-node.example.com",
});
// Mock chain (see the Testing guide)
const mock = await MidenClient.createMock();
All factories are async because they initialize the platform runtime and client storage before the client is usable.
ClientOptions reference
createTestnet(), createDevnet(), and create() accept the same ClientOptions shape. The differences are in what each factory pre-fills before the options are applied. createMock() accepts a separate MockOptions shape for configuring its local mock chain.
Field reference
| Option | Type | Description |
|---|---|---|
rpcUrl | "testnet" | "devnet" | "localhost" | "local" | string | Node RPC endpoint. Shorthands expand to the hosted Miden endpoints; any other string is treated as a raw URL. |
noteTransportUrl | "testnet" | "devnet" | string | Note transport service endpoint. Required for private-note sendPrivate / fetchPrivate. |
proverUrl | "local" | "devnet" | "testnet" | string | Default prover for transactions. "local" runs in the current environment; remote shorthands and URLs route to a remote / delegated prover. |
autoSync | boolean | When true, the client runs one sync pass before the promise resolves. |
seed | string | Uint8Array | Seed for deterministic RNG. Strings are hashed to 32 bytes via SHA-256. |
storeName | string | Store isolation key (IndexedDB database name in browsers). Set this to keep multiple clients' data separate in the same origin. |
keystore | { getKey, insertKey, sign } | Browser-only external keystore callbacks. Leave unset to use the built-in keystore; Node uses its filesystem keystore. |
useWorker | boolean | Browser-only. Defaults to true; set it to false for callback provers or single-WebView native shells. |
Factory defaults
Any option not passed falls back to the factory default, then to an SDK default.
| Factory | rpcUrl | proverUrl | noteTransportUrl | autoSync |
|---|---|---|---|---|
createTestnet(opts?) | "testnet" | "testnet" | "testnet" | true |
createDevnet(opts?) | "devnet" | "devnet" | "devnet" | true |
create(opts?) with rpcUrl | your value | "local" | none | false |
create(opts?) without rpcUrl | delegates to createTestnet(opts) | ← | ← | ← |
create() without an rpcUrl is not a separate "custom" client — it forwards its options to createTestnet(). If you want a localhost client with local proving and no autosync, pass rpcUrl: "localhost" explicitly.
Testnet with an in-browser prover
// Testnet — but prove locally in the browser instead of offloading
const client = await MidenClient.createTestnet({ proverUrl: "local" });
Keeping two isolated clients in the same origin
const a = await MidenClient.createTestnet({ storeName: "wallet-a" });
const b = await MidenClient.createTestnet({ storeName: "wallet-b" });
Each call creates its own IndexedDB database. The two wallets' state never crosses over.
Keystores and authentication
The built-in keystore handles signing for the common flows: client.accounts.create() generates a Falcon key, persists it, and the client uses it automatically during client.transactions.* calls.
When you need explicit control — for example when creating a contract account with a pre-derived seed, or wiring an external signer — build an AuthSecretKey directly:
import { AuthSecretKey } from "@miden-sdk/miden-sdk";
// Falcon key (the default auth scheme for regular wallets)
const seed = crypto.getRandomValues(new Uint8Array(32));
const auth = AuthSecretKey.rpoFalconWithRNG(seed);
Passing auth to client.accounts.create() stores it in the configured keystore for later signing.
See Accounts for full examples covering wallets, contracts, and faucets. For advanced browser setups — external signers, hardware wallets — the keystore option on ClientOptions wires the SDK to your own sign/getKey/insertKey callbacks.
Remote provers and per-transaction overrides
Local proving in the browser is CPU-intensive for larger transactions. Override globally via ClientOptions.proverUrl, or per transaction via the prover field:
// Globally: every transaction uses the remote prover by default
const client = await MidenClient.create({
rpcUrl: "testnet",
proverUrl: "https://prover.example.com",
});
// Per-transaction: pass a TransactionProver instance
await client.transactions.send({
account: wallet,
to: recipient,
token: faucet,
amount: 100n,
prover: customProver,
});
See Transactions for the full lifecycle.
Minimal example
import { MidenClient } from "@miden-sdk/miden-sdk";
async function demo() {
const client = await MidenClient.createTestnet();
const wallet = await client.accounts.create();
console.log("Wallet:", wallet.id().toString());
client.terminate();
}
demo().catch(console.error);
For a testable, offline-friendly version of this pattern, see Testing.