Files
bitvid/docs/bitcoin-connect-plan.md
thePR0M3TH3ANandClaude Opus 4.8 6dd40ce330 docs: add Bitcoin Connect (NWC connect UX) integration plan + TODO #61
Requested: integrate getAlby/bitcoin-connect as a smooth NWC "connect wallet"
front-end, keeping manual URI entry as backup. Plan (docs/bitcoin-connect-plan.md)
grounds it in bitvid's existing NWC stack: BC's NWC connector exposes the raw
pairing URI (provider.client.nostrWalletConnectUrl) which feeds the existing
validateWalletUri → per-npub store → zap-split/pay_invoice pipeline unchanged.
BC is a connect adapter, not a payment layer. Covers delivery (vendor+lazy import
vs esm.sh; CSP already allows esm.sh), persistence ownership, NWC-only connector
scope, least-privilege requestMethods, UI placement, FEATURE_BITCOIN_CONNECT flag,
and the security constraints (bearer spending secret, Alpha lib pin/vendor).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-09 14:19:17 -04:00

9.2 KiB
Raw Permalink Blame History

Bitcoin Connect — NWC wallet connect UX — Dev Plan

Status: DECISIONS NEEDED (D1–D6) — no code yet. Adds Alby's Bitcoin Connect as a smooth "connect your wallet" front-end for NWC, feeding bitvid's existing NWC pipeline. Manual URI entry stays as the backup/advanced path. Feature-flagged (off = no trace).

Executive summary

bitvid already has a full NWC stack: manual URI entry (#walletUri in the Wallet Connect pane → ProfileWalletController), validation (nwcSettingsService.validateWalletUri — requires nostr+walletconnect://), per-npub storage (nwcSettings.js, IndexedDB + localStorage), opt-in encrypted cross-login sync, and the zap-split/pay_invoice payment pipeline (js/payments/*). The friction is purely the connect step — pasting a URI.

Bitcoin Connect solves exactly that: a polished modal that walks the user through connecting a wallet (CoinOS, Alby Hub, mutiny, etc.) and, for the NWC connector, exposes the raw NWC pairing URI. So we bolt it on as an alternate connect button that extracts the URI and hands it to bitvid's existing validate→store pipeline. No change to how bitvid pays or stores — one new way to obtain the same string. Manual entry stays for power users / unsupported wallets.

How Bitcoin Connect works (verified against the v3 README)

  • Package @getalby/bitcoin-connect (ESM; Lit web components + functions; bundles @getalby/js-sdk internally — no separate peer dep). Also loadable from https://esm.sh/@getalby/bitcoin-connect@<pin> (our CSP already allows esm.sh).
  • init({ appName, filters: ["nwc"], persistConnection, providerConfig: { nwc: { authorizationUrlOptions: { requestMethods } } } }) — filters:["nwc"] restricts the modal to NWC; requestMethods requests least-privilege scopes.
  • launchModal() / await requestProvider() opens the connect flow.
  • onConnected((provider) => …) → a WebLN provider. NWC extraction:
    import { onConnected } from "@getalby/bitcoin-connect";
    import { WebLNProviders } from "@getalby/sdk"; // or the re-export
    onConnected((provider) => {
      if (provider instanceof WebLNProviders.NostrWebLNProvider) {
        const uri = provider.client.nostrWalletConnectUrl; // ← the NWC secret
      }
    });
    
  • disconnect() clears its connection; getConnectorConfig() returns the saved connector config. Persistence to localStorage is automatic when persistConnection: true.
  • ⚠️ README says the lib is Alpha — pin an exact version.

Decisions needed

DECISION 1 — Delivery. 🔵 NEEDED.

  • A — Vendor + lazy import(): esbuild-bundle a pinned @getalby/bitcoin-connect into vendor/bitcoin-connect.bundle.min.js (same pattern as floating-ui in scripts/build-beacon.mjs), dynamically imported only when the user opens the Wallet pane / clicks Connect. Offline-safe, supply-chain-pinned, no main-bundle bloat.
  • B — Lazy-load from esm.sh (CSP already allows it) at a pinned version. Zero vendoring work, but a runtime CDN dependency + supply-chain trust. Recommendation: A — bitvid vendors its other heavy deps; Bitcoin Connect is a Lit bundle (~hundreds of KB) so lazy-import is important either way.

DECISION 2 — Persistence ownership. 🔵 NEEDED.

  • A — persistConnection: false: Bitcoin Connect is purely the connect UI; bitvid extracts the URI and is the ONLY store (its existing encrypted-sync / per-npub model). No duplicate plaintext copy in bc:* localStorage.
  • B — let it persist and rely on disconnect() on bitvid logout. Recommendation: A — one owner of the spending secret; call disconnect() right after extracting to be safe.

DECISION 3 — Connector scope. 🔵 NEEDED.

  • A — NWC only (filters:["nwc"]). Yields a URI that flows straight into the existing pipeline; zap-split/pay_invoice unchanged.
  • B — also allow WebLN / Alby-extension / Alby Hub connectors. Smoother for Alby users, but those give a WebLN provider, not an NWC URI — bitvid's raw-NWC payment pipeline can't consume them without a second WebLN payment path. Recommendation: A for v1 (matches the pipeline); a WebLN path is a separate later effort if there's demand.

DECISION 4 — Requested scopes. 🔵 NEEDED. requestMethods to request from the wallet. bitvid needs pay_invoice (zaps); get_balance (balance display), make_invoice / lookup_invoice (receipts/tally). Recommendation: ["pay_invoice","get_balance","make_invoice","lookup_invoice"] — least privilege for what bitvid actually calls. (The wallet ultimately decides.)

DECISION 5 — UI placement vs manual mode. 🔵 NEEDED. Recommendation: In the Wallet Connect pane, make "Connect wallet" (Bitcoin Connect) the primary CTA, and move the raw-URI field under a collapsible "Advanced / paste a connection manually" — manual stays a first-class backup, just de-emphasized. On success, populate the same status/disconnect UI.

DECISION 6 — Feature flag. 🔵 NEEDED. Recommendation: FEATURE_BITCOIN_CONNECT in config/instance-config.js (default off = no trace), wired exactly like FEATURE_AUDIO_INGEST. With it off, only the manual URI field shows — zero new code path or bundle load.

Config flag (off = no trace)

  • FEATURE_BITCOIN_CONNECT (default false) — gates the "Connect wallet" button
    • the lazy Bitcoin Connect import. Off ⇒ manual URI entry only (today's behavior).

Integration points (small, contained)

  • components/profile-modal.html — Wallet Connect pane: add the "Connect wallet" button; wrap the existing #walletUri field as "Advanced/manual."
  • js/ui/profileModal/ProfileWalletController.js — a handleBitcoinConnect() that lazy-imports the lib, init()s once (NWC-only, least-privilege, no-persist), launchModal(), and on onConnected extracts nostrWalletConnectUrl → runs the SAME validateWalletUri + save path as manual "Save wallet" → then disconnect(). Reuses all existing status/disconnect/sync UI.
  • config/instance-config.js / js/config.js / js/constants.js — the flag.
  • scripts/ + vendor/ — the esbuild vendor bundle (D1-A).

Architecture

Bitcoin Connect is a connect adapter, not a payment layer:

[Connect wallet] → BC modal (NWC) → onConnected(provider)
   → provider.client.nostrWalletConnectUrl
   → validateWalletUri()  (existing, scheme check)
   → nwcSettingsService save  (existing: per-npub store + optional encrypted sync)
   → BC disconnect()          (bitvid now owns the secret)
[zaps] → js/payments/nwcClient pay_invoice + zap-split   (UNCHANGED)

The manual field writes to the exact same validate→store path, so the two entry points converge immediately and everything downstream is untouched.

Security (NWC URI = bearer SPENDING secret)

  • Never log nostrWalletConnectUrl (or the provider) — extract → validate → store via the existing (never-logged) pipeline; treat like the manual field.
  • persistConnection: false + disconnect() after extract so the secret isn't duplicated in Bitcoin Connect's own plaintext bc:* localStorage (D2).
  • Pin the version + vendor it (Alpha lib) — no unpinned CDN @latest.
  • Least-privilege requestMethods (D4) — request only what bitvid calls.
  • Re-run bitvid's validateWalletUri on the extracted string; don't trust the lib blindly. Keep the explicit "a wallet-connect URI can SPEND" warning on the encrypted-sync toggle.
  • Payment invariants unchanged: PLATFORM_FEE_PERCENT=30 split, preimage verification, and the "a pay_invoice timeout must NEVER auto-resend" rule all live in js/payments/* and are not touched.

Phases (flag-gated from day one)

  • Phase 0 — Flag + vendor bundle. Add FEATURE_BITCOIN_CONNECT (off) and the esbuild vendor step. No UX.
  • Phase 1 — Connect button (headline). Wallet pane "Connect wallet" → BC NWC modal → extract → existing save path → disconnect. Manual field → "Advanced."
  • Phase 2 — Polish. Balance display (BC showBalance / get_balance), reconnect affordance, and a "test connection" ping. Optional.
  • Phase 3 (only if wanted) — WebLN path (D3-B): accept Alby-extension/Hub connectors via a WebLN payment adapter alongside raw NWC. Separate effort.

Risks / watch-items

  • Alpha library / breaking changes — pin + vendor; wrap behind our own thin adapter so an API change is a one-file fix.
  • Bundle weight — lazy-import only on Wallet-pane open / button click.
  • Wallet grants fewer scopes than requested — if pay_invoice isn't granted, zaps fail; surface a clear error and fall back to manual.
  • Not all wallets support NWC — manual mode + non-NWC connectors (D3-B) cover the gap.
  • esm.sh path (D1-B) would add a runtime third-party origin to the trust surface.

Sources

  • getAlby/bitcoin-connect README (v3) — install, init filters/providerConfig, onConnected, NostrWebLNProvider.client.nostrWalletConnectUrl, disconnect.
  • bitcoin-connect.com. Related bitvid: js/services/nwcSettingsService.js, js/nwcSettings.js, js/ui/profileModal/ProfileWalletController.js, js/payments/*, docs/audio-integration-plan.md (flag-wiring pattern).