@winbit32/wallet-kit
v0.2.0
Published
Embeddable Zcash + Monero wallet primitives extracted from Winbit32: scanner API clients and the WB32COSIGN FROST/Orchard cosign client with a headless initiator pipeline. Framework-agnostic, browser-first, Node >= 18.
Downloads
306
Readme
@winbit32/wallet-kit
Embeddable Zcash + Monero wallet primitives extracted from Winbit32. The goal: anyone who wants to add a Zcash/Monero wallet (scanning, shielded sends, FROST co-signing) to their site installs this package and calls functions — no Winbit32 UI required.
Status / roadmap
Extraction happens in slices (each slice moves source here and leaves a re-export shim at the old Winbit32 path, so the app keeps working):
| Slice | Contents | Status |
| --- | --- | --- |
| Scanner clients | Zcash Orchard scan-job/UTXO/broadcast client, Monero LWS scan-job client, base-URL resolution | Done |
| Cosign client | WB32COSIGN relay transports, .wult share handling, FROST/Orchard ceremony, headless initiator pipeline | Done |
| MCP payment tools | make_payment tools ship in winbit32MCP (the public payments-gateway home), which consumes this kit | Done (lives in winbit32MCP) |
| MCP tool descriptors | Framework-free view-key tool descriptors (createWalletKitToolDescriptors): zec/xmr scan jobs, UTXOs, broadcast — mount on any MCP server | Done (v0.2.0) |
| Shielded send/scan core | shielded-transaction-client (PCZT build/sign/broadcast via WebZjs), note cache; unblocks direct-phrase MCP sends | Planned |
What's in the box today
import {
resolveWalletScannerBaseUrl,
createWalletScannerClient, // Zcash: Orchard scan jobs, UTXOs, tx broadcast
createMoneroWalletScannerClient, // Monero: LWS scan jobs
mapScannerBalanceToSnapshot, // wire → bigint balance snapshot
} from '@winbit32/wallet-kit';
const zec = createWalletScannerClient({ baseUrl: 'https://api.zcash.winbit32.com/api' });
const { data } = await zec.startOrchardScanJob({ ufvk, autoDetect: true });
const xmr = createMoneroWalletScannerClient();
const job = await xmr.startMoneroScanJob({ address, viewKey });- All clients take an optional
baseUrl/apiKey; without them the publicapi.zcash.winbit32.comhost is used (its nginx injects the upstream API key, so browsers need none). resolveWalletScannerBaseUrl()honoursREACT_APP_WALLET_SCANNER_URLand falls back to a same-origin/apiproxy in development and the public host in production. Non-CRA embedders should passbaseUrlexplicitly.- No runtime dependencies; browser-first (uses
fetch); works in Node ≥ 18.
Co-signing (FROST/Orchard, 2-of-2)
The canonical WB32COSIGN client lives here (src/cosign/). It powers both
browser initiators (Secresea-style "send from a .wult share" flows) and
fully headless Node hosts such as the seneschal.space payments-gateway:
import {
unwrapVaultShare, toOrchardBundle, // .wult → key bundle
deriveOrchardAddressFromBundle, // bundle → UFVK + u1… address
runHeadlessCosignSend, // scan → build → ceremony → broadcast
resolveCosignConfig,
} from '@winbit32/wallet-kit';
const share = await unwrapVaultShare(wultBytes, password);
const bundle = toOrchardBundle(share);
const { txid } = await runHeadlessCosignSend({
config: resolveCosignConfig({ relayBaseUrl, pcztApiBaseUrl, fetchImpl: fetch }),
wasm, bundle, ufvk, unifiedAddress, scanner,
toAddress: 'u1…', amountZat: 25_000, // zatoshis
onQrReady: (qr) => showToHuman(qr), // WB32COSIGN:1:… pairing payload
});- The host holds one share; the human's cosigner (winbit32.com
#cosign) holds the other. Neither side ever has the full spending key. - Transports are pluggable (
BroadcastChannelsame-device, HTTPS relay cross-device); messages are AES-GCM encrypted with the QR session key. - The
orchard-frostWASM is loaded by the host: browsers fetch it frompublic/orchard-frost/, Node hosts read the same artefacts from disk (seeloadOrchardFrostWasmNodein the payments-gateway for the pattern).
MCP tool descriptors
Mount the kit's view-key capabilities on any MCP server without taking a dependency on any particular SDK:
import { createWalletKitToolDescriptors } from '@winbit32/wallet-kit';
const tools = createWalletKitToolDescriptors(); // public scanner defaults
for (const tool of tools) {
// adapt `tool.params` (flat primitive spec) to your schema flavour
server.registerTool(`myprefix_${tool.name}`, { /* … */ },
async (input) => ({ content: [{ type: 'text', text: JSON.stringify(await tool.handler(input)) }] }));
}Tools: zec_scan_start/status/cancel, zec_utxos, zec_broadcast,
xmr_scan_start/status/cancel. All view-key-only — they can observe funds
but never move them. Signing tools (e.g. make_payment) deliberately live
with the host's payment state machine (see winbit32MCP), which uses the
kit's headless cosign pipeline underneath.
Building
cd packages/wallet-kit
npm run build # tsc → dist/ (CommonJS + .d.ts; ESM hosts use src/ via bundler)The compiled dist/ is CommonJS so plain Node hosts (e.g. the
payments-gateway via a file: dependency) can require/import it without
a bundler. Browser consumers (Winbit32 itself) compile src/ directly
through their own bundler via the alias seam described below.
Tests
Kit tests run under the host repo's jest (CRA 27) in a Node environment:
CI=true npx react-app-rewired test --watchAll=false --testPathPattern='packages/wallet-kit'src/cosign/__tests__/ceremony.integration.test.ts runs a real
FROST/Orchard ceremony against the staged WASM — initiator and an
independently-implemented cosigner stand-in — plus the whole
runHeadlessCosignSend pipeline against fake PCZT/scanner endpoints. It
skips (not fails) if public/orchard-frost/ artefacts are missing.
How Winbit32 consumes it
The app imports @winbit32/wallet-kit directly from packages/wallet-kit/src
via a webpack alias + jest moduleNameMapper + tsconfig paths (see
config-overrides.js). npm workspaces are deliberately NOT used yet: the
CRA + WASM webpack setup is sensitive to node_modules hoisting, so the
alias seam gives us the package boundary without the install churn. When
the kit is published, the alias is simply dropped and the app depends on
the npm package like everyone else.
Old Winbit32 paths (src/components/toolbox/walletScannerBaseUrl.ts,
.../zcash-extensions/api/walletScannerClient.ts,
.../monero-extensions/api/moneroWalletScannerClient.ts) remain as
re-export shims; new code should import from @winbit32/wallet-kit.
