New:Socket for Asana Is Now Available.Learn more
Get Started

@winbit32/wallet-kit

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@winbit32/wallet-kit

Embeddable Zcash + Monero wallet primitives extracted from Winbit32: scanner API clients, the WB32COSIGN FROST/Orchard cosign client with a headless initiator pipeline, a seed-phrase wallet with injected local signing, and an embeddable wallet bar UI. Fra

latest
Source
npmnpm
Version
0.3.0
Version published
Weekly downloads
8
-74.19%
Maintainers
1
Weekly downloads
 
Created
Source

@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):

SliceContentsStatus
Scanner clientsZcash Orchard scan-job/UTXO/broadcast client, Monero LWS scan-job client, base-URL resolutionDone
Cosign clientWB32COSIGN relay transports, .wult share handling, FROST/Orchard ceremony, headless initiator pipelineDone
MCP payment toolsmake_payment tools ship in winbit32MCP (the public payments-gateway home), which consumes this kitDone (lives in winbit32MCP)
MCP tool descriptorsFramework-free view-key tool descriptors (createWalletKitToolDescriptors): zec/xmr scan jobs, UTXOs, broadcast — mount on any MCP serverDone (v0.2.0)
x402 railSigner-agnostic vault x402 payer + probe + createVaultAccount (EIP-3009 digest seam); headless apart from the injected co-signDone (v0.2.0)
NFPT commerceSigner-agnostic secresea/NFPT buy orchestration (buyNfpt: prepare → cap → injected sign → record); the one backend-specific module, apiBase injectableDone (v0.2.0)
Phrase walletPhraseWalletService: connect with a BIP-39 phrase, scan, PCZT build → local sign (host-injected WebZjs pczt_sign) → broadcast; unblocks direct-phrase MCP sendsDone
Note cache / full clientLocal note cache + the rest of shielded-transaction-client (transparent inputs, auto-shielding)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 public api.zcash.winbit32.com host is used (its nginx injects the upstream API key, so browsers need none).
  • resolveWalletScannerBaseUrl() honours REACT_APP_WALLET_SCANNER_URL and falls back to a same-origin /api proxy in development and the public host in production. Non-CRA embedders should pass baseUrl explicitly.
  • 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 (BroadcastChannel same-device, HTTPS relay cross-device); messages are AES-GCM encrypted with the QR session key.
  • The orchard-frost WASM is loaded by the host: browsers fetch it from public/orchard-frost/, Node hosts read the same artefacts from disk (see loadOrchardFrostWasmNode in the payments-gateway for the pattern).

Phrase wallet (seed-phrase connect + local sign)

PhraseWalletService (src/zcash/) is the "connect with a seed phrase" sibling of the cosign service, sharing the same scan/build/broadcast plumbing. Key operations are injected so the kit ships no WASM — the host wires WebZjs (or equivalent) and the phrase never leaves the service's tab memory:

import { PhraseWalletService, type PhraseWalletAdapter } from '@winbit32/wallet-kit';

const adapter: PhraseWalletAdapter = {
	deriveFromPhrase: async (phrase, account) => ({ ufvk, unifiedAddress, seedFingerprintHex, changeAddress }),
	signPczt: async (pcztB64, phrase, account) => signedPcztB64,   // WebZjs pczt_sign
};
const wallet = new PhraseWalletService({ adapter, config: { pcztApiBaseUrl: '/api/pczt' } });
await wallet.connect('twelve or twenty four words …');
const { txid } = await wallet.send({ toAddress: 'u1…', amountZec: 0.1, memo: 'thanks' });

Wallet bar (embeddable UI)

mountWinbit32WalletBar (src/ui/) is the vanilla-DOM bottom bar + modal used by ziving.org — Secresea's wallet-bar experience without React. It accepts a .wult share / locket out of the box and, when the host passes a phraseAdapter, a pasted seed phrase too (phrase → signs locally in the tab; .wult → WB32COSIGN co-sign QR). See mountDonorWalletBar in ziving's wallet/index.mjs for a complete host wiring.

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.

x402 rail (USDC on Base/EVM)

The kit's second payment rail, sibling to the Orchard cosign send — so a host gets BOTH headless rails from one package (the only interactive step in either is the co-sign):

import { createVaultAccount, createVaultX402Payer, probeX402 } from '@winbit32/wallet-kit';

// signDigest routes the EIP-712 digest to a local vault MPC sign or WB32COSIGN.
const account = createVaultAccount({ address, hashTypedData, signDigest });
// makeFetchWithPayment wires @x402/fetch + @x402/evm (ExactEvmScheme(account)).
const payer = createVaultX402Payer({ account, deps: { makeFetchWithPayment } });

const preview = await probeX402({ url });               // price, no payment
const res = await payer.pay({ url, maxAmountUsd: 1 });  // probe → cap → settle
  • Framework-free + dependency-free at import: fetchImpl, makeFetchWithPayment and signDigest are all INJECTED, so it unit-tests without a vault, a network, viem or the @x402 packages.
  • An x402 "exact" EVM payment is an EIP-3009 TransferWithAuthorization — one 32-byte EIP-712 digest. Signing that digest is the ONE human touch-point: route it to a local vault MPC sign or a WB32COSIGN ceremony (the same co-sign seam the Orchard pipeline uses). The spend key never leaves the vault shares.
  • pay() / probeX402() never throw for expected failures — they return { ok:false, reason } (unsafe_target, not_x402, price_exceeds_max, asset_not_accepted, settlement_failed, …) so UI and MCP callers branch cleanly. A successful pay() returns { ok:true, paidAtomic, txHash, … }.

NFPT marketplace commerce (secresea/NFPT-scoped)

The one backend-specific module in the otherwise brand-neutral kit: it speaks the secresea NFPT marketplace API so the desktop Gopher client and a Node MCP tool share ONE "buy a listed NFPT" implementation. It's the Orchard-rail counterpart of the x402 payer — discover, then pay for an NFPT as a shielded-ZEC send.

import { buyNfpt, NFPT_DEFAULTS } from '@winbit32/wallet-kit';

const receipt = await buyNfpt({
	nfptCid, buyerZAddress,
	maxZec: 0.5,                      // cap enforced BEFORE any signing
	apiBase: NFPT_DEFAULTS.apiBase,   // injectable; defaults to https://secresea.com
	fetchImpl: fetch,
	// the ONE env-specific seam: build + sign + broadcast ONE shielded tx
	// carrying every output → { txid }. Desktop signs with the loaded vault;
	// MCP signs with the gateway FROST share + human cosign.
	signAndSend: async ({ outputs }) => ({ txid: await sendShielded(outputs) }),
	onStatus: (m) => console.log(m),
});
// receipt: { txid, recorded, totalPayableZec, seller, outputs, recordError, … }
  • Both fetchImpl and signAndSend are INJECTED, so the whole module unit-tests with no wallet, chain or network.
  • Cap-before-sign: assertWithinCap throws price_exceeds_cap before the signer is ever called.
  • Record is non-fatal: once a valid txid is in hand, a failed record-purchase is reported via recorded:false + recordError — never discarding a real on-chain transfer (the scanner reconciles ownership anyway).

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.

FAQs

Package last updated on 15 Jul 2026

Related posts