A MetaMask snap for Bitcoin accounts and signing in the Ducat protocol.
It derives Bitcoin accounts from the user's MetaMask Secret Recovery Phrase, keeps the keys inside MetaMask, and exposes a small JSON-RPC API to the Ducat web app for message signing, PSBT signing, and simple transfers. The web app drives everything the user does — borrow, repay, withdraw, and so on — and the snap handles account discovery and the MetaMask confirmation prompts.
Mainnet, signet, and mutinynet are supported. Local development against regtest needs a dev build (see below).
Snaps let developers extend MetaMask with capabilities it doesn't ship by default — like support for non-EVM chains. They run in an isolated environment with a limited set of permissions. See the MetaMask Snaps docs for more.
ducat_getAccounts({ network })
ducat_getCapabilities()
ducat_getHomeState({ network })
ducat_signMessage({ network, address, message })
ducat_signPsbt({ network, psbt, signInputs, context })
ducat_signBatch({ network, entries, context })
ducat_sendTransfer({ network, address, amountSats, feeRate })
ducat_clearRecentActions()
Only the Ducat app origins below are allowed to call these:
https://app.ducatprotocol.com
https://dev.app.ducatprotocol.com
https://staging.app.ducatprotocol.com
localhost and preview deployments are intentionally not in the published manifest — a local
process or a hijacked preview subdomain should never be able to drive mainnet signing. For
local QA, build with the dev origins/regtest flags (DUCAT_SNAP_DEV_ORIGINS,
DUCAT_SNAP_DEV_REGTEST); those are stripped from the published build.
npm ci
npm run build # mm-snap build -> dist/bundle.js
npm run manifest # regenerate the manifest shasum
npm run serve # serve the snap at http://localhost:8080Before opening a PR:
npm run type-check
npm test
npm run verify:harness # MetaMask simulation harness (accounts, signing, BitVM3)Point the frontend at the local build with NEXT_PUBLIC_DUCAT_SNAP_ID="local:http://localhost:8080",
or at the published package with NEXT_PUBLIC_DUCAT_SNAP_ID="npm:@ducat-unit/wallet-snap".
Keys, child keys, WIFs, and raw entropy never leave the snap. Only derived Ducat accounts are
signed for, only the explicit signInputs indexes are signed, and every signing path requires a
MetaMask confirmation. Frontend-supplied context is treated as untrusted display metadata — the
parsed PSBT is the source of truth for what's actually signed. Taproot vault inputs must carry a
Ducat cosign tapleaf whose control block recomputes to the prevout output key.
The third-party security audit (Sayfer) is in docs/audit.pdf.
See LICENSE.