Skip to main content
Version: 0.14 (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) with WebAssembly and Web Worker support. It also runs under Node 20+ when the host provides those primitives.

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 in-memory 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 — the SDK has to load its WebAssembly module and spin up a Web Worker before the client is usable.

ClientOptions reference

All four factories accept the same ClientOptions shape. The differences are in what each factory pre-fills before the options are applied.

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 browser; 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 }External keystore callbacks. Leave unset to use the built-in keystore.

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)
createMock(opts?)(no network)(dummy proving)(in-memory)(manual)

create() without an rpcUrl is not a separate "custom" client — it forwards its options to createTestnet(). If you want a no-prover, no-autosync client against localhost, 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);

The caller is responsible for retaining auth as long as the account is in use: the client holds a reference for signing, but the secret material only exists on the caller side until it is handed to the keystore.

See Accounts for full examples covering wallets, contracts, and faucets. For advanced 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();
await client.sync();

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.