A non-custodial wallet for Dusk. Chrome and Firefox extension builds from one codebase.
Your keys. Your DUSK. No middleman.
🔐 Self-custody — Your mnemonic never leaves your device. Encrypted at rest.
⚡ Public & Shielded — Send from your public account or shield funds for privacy.
🌐 dApp Ready — Connect to any Dusk dApp through event-based provider discovery.
🔄 Multi-network — Switch between mainnet, testnet, devnet, or custom nodes.
🧩 Shared runtime — Extension targets share the same wallet engine and UI shell.
From a fresh checkout:
npm ci
npm run build:chromeThen load dist/ as an unpacked extension in chrome://extensions (Developer mode).
From a fresh checkout:
npm ci
npm run build:firefoxThen load dist-firefox/ as a temporary add-on in about:debugging.
The extension announces an EIP-1193-style provider through Dusk discovery events. Dusk isn't EVM, but the provider patterns are familiar.
Use Connect's conflict-aware discovery support; metadata is self-attested, not wallet authentication. Run this as a JavaScript module:
import { createDuskWallet } from "@dusk/connect";
const wallet = createDuskWallet();
await wallet.ready();
// When selection is required, show wallet.providers in a picker (or Connect UI).
// Make conflicts visible and disable those entries; pass a chosen UUID to wallet.selectProvider().
if (!wallet.provider) throw new Error("Select an unconflicted Dusk wallet before connecting");
const [profile] = await wallet.connect(); // dusk_requestProfiles; prompts for a profile grant.
console.log(profile.account);
// Keep requests/events on the wrapper so later selection changes are respected.
wallet.on("profilesChanged", console.log);
wallet.on("chainChanged", console.log);
// Call wallet.destroy() when the integration is torn down.dusk_signTypedData signs structured, wallet-rendered data rather than an opaque
digest — the Dusk analogue of eth_signTypedData_v4, not of eth_sign. The approval
screen shows the domain, primary type, message previews and digest. Previews may be
truncated; Full signing request (escaped JSON) exposes the complete request
without normalizing its values. Sign is disabled if the full disclosure cannot
match the pending digest within display limits. See the disclosure behavior and
resource limits and
#113.
const result = await wallet.request("dusk_signTypedData", {
domain: { name: "Example", version: "1", chainId: "dusk:1" },
types: {
DuskTypedDataDomain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "string" },
{ name: "verifyingContract", type: "bytes32" },
],
SignIn: [{ name: "address", type: "string" }],
},
primaryType: "SignIn",
message: { address: profile.account },
});
// → { account, publicKeyHex, origin, chainId, primaryType, digestHex, signature }Two things to know before integrating:
- Do not send
origin. The wallet injects its own view of the requesting origin into the digest, and returns the exact string it used. A caller able to set it could obtain a signature attributable to a site it does not control. domain.chainIdmust match the wallet's active chain, or the request is rejected before the user sees anything.
Verify signatures with @dusk/typed-data/bls,
passing trusted chain/origin expectations and checking result.ok. Reconstruct the
hash input with the wallet-returned origin. Also check the expected signer,
authorization and replay protection.
The signature covers a tagged wrapper around digestHex, not the bare digest — verifying
the bare digest would accept signatures produced by any raw 32-byte signing path.
Canonical v0.1 docs:
- Provider API: docs/provider-api.md
- Typed-data v1 specification and integration: docs/typed-data-v1.md
- Discovery protocol: dusk-network/connect docs/wallet-discovery.md
- Connect SDK usage: dusk-network/connect README.md
- Wallet implementer guidance: dusk-network/connect docs/wallet-implementer.md
- Security/threat model: docs/SECURITY.md
- v0.1 release checklist: dusk-network/connect docs/RELEASE_CHECKLIST_v0.1.md
src/
├── background/ # Extension service worker
├── ui/ # Popup, full view, notifications
├── shared/ # Wallet logic (works everywhere)
├── platform/ # Platform abstraction (extension vs tauri)
└── wallet/ # Engine interface
The wallet engine runs in an offscreen document for extension builds. The shared runtime is structured so other hosts can reuse the same cryptographic core.
Additional documentation:
npm run build:extension # Build extension → dist/
npm run build:firefox # Build Firefox extension → dist-firefox/
# Local Rusk node (Docker)
npm run rusk:up
npm run rusk:wait
# UI component workbench
npm run storybook
# E2E (Playwright + Docker Rusk)
npm run e2e:rusk- Mnemonic encrypted with user password (PBKDF2 + AES-GCM)
- No analytics, no tracking, no remote calls except to your chosen node
MIT
