| name | signer-integration |
| description | Guide to integrating external signers (Para, Turnkey, MidenFi wallet adapter) and building custom signers for Miden React frontends. Covers provider setup, passkey authentication, unified signer interface, custom SignerContext implementation, and custom account components. Use when adding wallet connection, authentication, or external key management to a Miden frontend. |
Miden Signer Integration
Overview
By default, MidenProvider uses a local keystore (keys in IndexedDB, no wallet connection needed). For production apps, wrap MidenProvider with a signer provider to use external key management.
Signer providers must wrap MidenProvider (outer → inner):
<SignerProvider> ← manages keys + auth
<MidenProvider> ← manages Miden client
<App />
</MidenProvider>
</SignerProvider>
Pre-Built Signer Providers
Para (EVM Wallets)
import { ParaSignerProvider, useParaSigner } from "@miden-sdk/use-miden-para-react";
<ParaSignerProvider apiKey="your-api-key" environment="PRODUCTION">
<MidenProvider config={{ rpcUrl: "testnet" }}>
<App />
</MidenProvider>
</ParaSignerProvider>
const { para, wallet, isConnected } = useParaSigner();
Turnkey (Passkey Authentication)
import { TurnkeySignerProvider } from "@miden-sdk/miden-turnkey-react";
<TurnkeySignerProvider config={{ defaultOrganizationId: "your-org-id" }}>
<MidenProvider config={{ rpcUrl: "testnet" }}>
<App />
</MidenProvider>
</TurnkeySignerProvider>
<TurnkeySignerProvider config={{
apiBaseUrl: "https://api.turnkey.com",
defaultOrganizationId: "your-org-id",
}}>
...
</TurnkeySignerProvider>
TurnkeySignerProvider also accepts optional customComponents and importAccountId props, which it forwards into accountConfig (see "Custom Account Components").
Connect via passkey:
import { useSigner } from "@miden-sdk/react";
import { useTurnkeySigner } from "@miden-sdk/miden-turnkey-react";
const signer = useSigner();
if (!signer) return null;
const { isConnected, connect, disconnect } = signer;
await connect();
const { client, account, setAccount } = useTurnkeySigner();
MidenFi Wallet Adapter (Browser Extension)
import { MidenFiSignerProvider } from "@miden-sdk/miden-wallet-adapter-react";
import { WalletAdapterNetwork } from "@miden-sdk/miden-wallet-adapter-base";
<MidenFiSignerProvider
appName="My App" // optional: passed to MidenWalletAdapter
network={WalletAdapterNetwork.Testnet} // WalletAdapterNetwork enum: Devnet | Testnet | Localnet
autoConnect // reconnect on mount. Default: false
storageMode="public" // "private" | "public". Default: "public"
customComponents={[myComponent]} // optional: custom AccountComponents
privateDataPermission={permission} // optional: private data access level
allowedPrivateData={allowedData} //
>
With MidenFiSignerProvider in place, use useSigner() from the React SDK to manage connection state. The regular React SDK hooks (useSend, useConsume, etc.) automatically sign via the connected wallet — no additional wiring needed.
The provider accepts an accountType prop, but it is a no-op: account visibility is determined solely by storageMode (private/public), and the provider always imports the account by ID (importAccountId), bypassing the builder path entirely. Omit it.
Frontend-template-specific MidenFi pattern
The frontend template (on web-sdk 0.15 — @miden-sdk/miden-sdk@0.15.3, @miden-sdk/react@0.15.3, wallet adapters 0.15.1) deviates from the generic patterns above in three places worth knowing when the wallet extension is the primary signer:
- Provider order is INVERTED:
MidenProvider runs OUTSIDE MidenFiSignerProvider — see src/providers.tsx. This is the opposite of the canonical signer-outer / Miden-inner nesting at the top of this skill, and it is deliberate. In v0.15, when a signer provider is an ancestor of MidenProvider, MidenProvider treats it as its external keystore and does NOT create the WebClient until the signer connects (the init effect sees signerIsConnected === false and returns early before building the client). With a wallet that hasn't connected — or any environment without the extension — the app would hang on "Initializing…" and even public reads couldn't run. The template never signs through MidenProvider (it signs its only write, the counter increment, through the local WebClient rather than the wallet), so it runs MidenProvider in local-keystore mode (no signer ancestor → it initializes immediately, reads work pre-connect) and keeps MidenFiSignerProvider inside, purely for the connect button and the wallet's requestTransaction. MidenFiSignerProvider works standalone (it provides its own WalletContext + SignerContext; no MultiSignerProvider needed). Use this inversion only when you do not sign through MidenProvider; if external-keystore signing IS the goal, keep the canonical signer-outer order so MidenProvider picks up the signer's signCb/accountConfig.
- Wallet button uses
useMidenFiWallet() + WalletReadyState — see src/components/AppContent.tsx. The button gates on wallet?.readyState (rendering a disabled "Install MidenFi Wallet" state unless readyState is Installed or Loadable) so it can show install state before the extension is detected. useSigner().connect() would silently fall through to the adapter's window.open(adapter.url, ...) install fallback; gating on readyState avoids that path.
- The counter increment is a local two-transaction flow, not a wallet-signed tx — see
src/hooks/useIncrementCounter.ts. It does not use the wallet at all. It creates a throwaway local sender (), publishes a plain increment note as that sender's own output note (), then consumes the note (). Both transactions are submitted by the local via (remote prover), never by the wallet, so doesn't apply and the template polls the counter's storage map instead. This mirrors the project-template reference.
Unified Signer Interface
Works with any signer provider above. useSigner() returns null in local-keystore mode (no signer provider mounted), so guard before destructuring:
import { useSigner } from "@miden-sdk/react";
const signer = useSigner();
if (!signer) return null;
const { isConnected, connect, disconnect, name } = signer;
if (!isConnected) {
return <button onClick={connect}>Connect {name}</button>;
}
Building a Custom Signer
Implement SignerContextValue via SignerContext.Provider:
import { SignerContext } from "@miden-sdk/react";
import { AccountStorageMode } from "@miden-sdk/miden-sdk";
<SignerContext.Provider value={{
name: "MyWallet",
storeName: `mywallet_${userAddress}`, // unique per user for DB isolation
isConnected: true,
accountConfig: {
publicKeyCommitment: userPublicKeyCommitment, // Uint8Array
storageMode: AccountStorageMode.private(), // AccountStorageMode instance, not a string
},
signCb: async (pubKey, signingInputs) => {
// Route to your signing service
return signature; // Uint8Array
},
connect: async () => { /* trigger wallet connection */ },
disconnect: async () => { /* clear session */ },
}}>
<MidenProvider config={{ rpcUrl: "testnet" }}>
<App />
</MidenProvider>
</SignerContext.Provider>
Required fields:
name — Display name for the signer
storeName — Unique string per user (isolates IndexedDB data between users)
accountConfig — { publicKeyCommitment: Uint8Array; storageMode: AccountStorageMode; ... } (storage mode is an AccountStorageMode instance, e.g. AccountStorageMode.private(), not a string)
signCb — Callback that signs transaction data with your key management service
connect / disconnect — Session lifecycle handlers
Custom Account Components
Attach application-specific AccountComponent instances (e.g., DEX logic from .masp packages) to accounts created by the signer:
import { type SignerAccountConfig } from "@miden-sdk/react";
import { AccountComponent } from "@miden-sdk/miden-sdk";
const myDexComponent: AccountComponent = await loadCompiledComponent();
const accountConfig: SignerAccountConfig = {
publicKeyCommitment: userPublicKeyCommitment,
storageMode: myStorageMode,
customComponents: [myDexComponent],
};
SignerAccountConfig has an accountType field, but it is ignored — account kind and code mutability are not encoded in the account, so visibility comes solely from storageMode. Omit it.
Components are appended to the AccountBuilder after the default basic wallet component. The field is optional — omitting it preserves default behavior.
Which Signer to Choose
| Signer | Auth Method | Keys Stored | Best For |
|---|
| Local keystore (default) | None | Browser IndexedDB | Development, demos |
| Para | EVM wallet | Para servers | Apps with existing EVM users |
| Turnkey | Passkey (biometric) | Turnkey servers | Consumer apps, no seed phrases |
| MidenFi Wallet | Browser extension | Extension | Power users with MidenFi wallet |
| Custom | Your choice | Your infrastructure | Enterprise, custom auth flows |
Key trade-off: Local keystore requires no setup but keys are lost if the user clears browser data. External signers persist keys server-side but add a dependency.