Skip to main content
Version: 0.16

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:

FactoryUse 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​

OptionTypeDescription
rpcUrl"testnet" | "devnet" | "localhost" | "local" | stringNode RPC endpoint. Shorthands expand to the hosted Miden endpoints; any other string is treated as a raw URL.
noteTransportUrl"testnet" | "devnet" | stringNote transport service endpoint. Required for private-note sendPrivate / fetchPrivate.
proverUrl"local" | "devnet" | "testnet" | stringDefault prover for transactions. "local" runs in the current environment; remote shorthands and URLs route to a remote / delegated prover.
autoSyncbooleanWhen true, the client runs one sync pass before the promise resolves.
seedstring | Uint8ArraySeed for deterministic RNG. Strings are hashed to 32 bytes via SHA-256.
storeNamestringStore 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.
useWorkerbooleanBrowser-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.

FactoryrpcUrlproverUrlnoteTransportUrlautoSync
createTestnet(opts?)"testnet""testnet""testnet"true
createDevnet(opts?)"devnet""devnet""devnet"true
create(opts?) with rpcUrlyour value"local"nonefalse
create(opts?) without rpcUrldelegates 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.