Skip to main content
Version: Next (unstable)

Setting up the Web SDK

Install​

Add @miden-sdk/miden-sdk to your project.

npm install @miden-sdk/miden-sdk
# or
yarn add @miden-sdk/miden-sdk
# or
pnpm add @miden-sdk/miden-sdk

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.
feeFaucetIdstringOptional fee faucet ID (hex or bech32) before the first sync. After syncing, the client reads it from the chain's protocol configuration.
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.