From 6dd40ce3305c1a082de6b3fa52bb091844bb89c2 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:19:17 -0400 Subject: [PATCH 01/18] docs: add Bitcoin Connect (NWC connect UX) integration plan + TODO #61 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/bitcoin-connect-plan.md | 167 +++++++++++++++++++++++++++++ todo/TODO_2026-06-20_pre-launch.md | 18 ++++ 2 files changed, 185 insertions(+) create mode 100644 docs/bitcoin-connect-plan.md diff --git a/docs/bitcoin-connect-plan.md b/docs/bitcoin-connect-plan.md new file mode 100644 index 00000000..9ff3ab64 --- /dev/null +++ b/docs/bitcoin-connect-plan.md @@ -0,0 +1,167 @@ +# Bitcoin Connect — NWC wallet connect UX — Dev Plan + +Status: **DECISIONS NEEDED (D1–D6)** — no code yet. Adds Alby's +[Bitcoin Connect](https://bitcoin-connect.com/) 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@` (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:** + ```js + 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). diff --git a/todo/TODO_2026-06-20_pre-launch.md b/todo/TODO_2026-06-20_pre-launch.md index c7ee104d..576b3fe6 100644 --- a/todo/TODO_2026-06-20_pre-launch.md +++ b/todo/TODO_2026-06-20_pre-launch.md @@ -2085,3 +2085,21 @@ example: event `95053d75…` "Nostr Compass Podcast #20" (pubkey `b7ed68b0…`). media grid — plan the category resolver + per-tab loaders together so video / audio / live / shorts don't each grow a bespoke fetch, and so the channel-profile tabs and sidebar tabs share one source of truth. + +### 61. Bitcoin Connect — smooth NWC wallet connect UX — full plan in docs/bitcoin-connect-plan.md +Requested 2026-07-09. Add Alby's Bitcoin Connect (getalby/bitcoin-connect, +bitcoin-connect.com) as a polished "connect your wallet" modal for NWC, feeding +bitvid's EXISTING NWC pipeline; keep manual URI entry as the backup/advanced path. +- [ ] Verified feasible: BC's NWC connector exposes the raw pairing URI via + `provider.client.nostrWalletConnectUrl` (on `onConnected`), which flows straight + into `nwcSettingsService.validateWalletUri` → the existing per-npub store. No + change to how bitvid pays or stores — one new way to obtain the same string. +- [ ] Decisions D1–D6 in the plan (recommendations: vendor + lazy import; + `persistConnection:false` + `disconnect()` after extract; NWC-only connector; + least-privilege `requestMethods` incl. `pay_invoice`; Connect = primary CTA with + manual under "Advanced"; `FEATURE_BITCOIN_CONNECT` flag, off = no trace). +- [ ] Security: NWC URI is a bearer SPENDING secret — never log it; pin+vendor the + (Alpha) lib; re-validate the extracted string; payment invariants + (PLATFORM_FEE_PERCENT=30, preimage verify, no pay_invoice auto-resend) untouched. +- [ ] Phased + flag-gated: Phase 0 flag+vendor, Phase 1 connect button, Phase 2 + polish (balance/reconnect), Phase 3 optional WebLN path for Alby-extension/Hub. From 69e87c6e156db503a5693d339fa6fd4000cb06e1 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:49:37 -0400 Subject: [PATCH 02/18] =?UTF-8?q?feat(wallet):=20Bitcoin=20Connect=20NWC?= =?UTF-8?q?=20"Connect=20wallet"=20flow=20[LOCAL=20=E2=80=94=20flag=20ON,?= =?UTF-8?q?=20do=20not=20push=20as-is]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 0+1 of docs/bitcoin-connect-plan.md (#61). Adds Alby's Bitcoin Connect as a one-tap NWC connect front-end that feeds bitvid's EXISTING wallet pipeline; manual URI entry stays as the backup. - Vendored: pinned @getalby/bitcoin-connect@3.12.3 bundled by scripts/build-bitcoin-connect.mjs → vendor/bitcoin-connect.bundle.min.js (esbuild, browser ESM). crypto-shim aliases the L402-only `import('crypto')` bitvid never calls. `npm run build:bitcoin-connect` rebuilds it. - Flag: FEATURE_BITCOIN_CONNECT wired like FEATURE_AUDIO_INGEST (instance-config → config → constants). - UI: a "Connect wallet" button in the Wallet Connect pane (hidden unless the flag is on); the manual URI field stays below as the backup. - Controller: ProfileWalletController.handleBitcoinConnect() lazy-imports the bundle (only on click — the ~700KB never touches the main bundle), inits NWC-only with least-privilege requestMethods and persistConnection:false, then on onConnected extracts provider.client.nostrWalletConnectUrl → the SAME validate→save path as the manual field → disconnect() so bitvid solely holds the spending secret. The URI is never logged. NOTE: config/instance-config.js FEATURE_BITCOIN_CONNECT is TRUE for local testing only. Reset to false (shipping default) before pushing/deploying. Local commit; not pushed, not deployed. Co-Authored-By: Claude Opus 4.8 --- components/profile-modal.html | 24 + config/instance-config.js | 12 + js/config.js | 2 + js/constants.js | 10 + js/ui/profileModal/ProfileWalletController.js | 137 ++ package-lock.json | 115 +- package.json | 2 + scripts/build-bitcoin-connect.mjs | 51 + scripts/vendor/bitcoin-connect-entry.mjs | 17 + scripts/vendor/crypto-shim.mjs | 26 + vendor/bitcoin-connect.LICENSE | 21 + vendor/bitcoin-connect.bundle.min.js | 1249 +++++++++++++++++ 12 files changed, 1663 insertions(+), 3 deletions(-) create mode 100644 scripts/build-bitcoin-connect.mjs create mode 100644 scripts/vendor/bitcoin-connect-entry.mjs create mode 100644 scripts/vendor/crypto-shim.mjs create mode 100644 vendor/bitcoin-connect.LICENSE create mode 100644 vendor/bitcoin-connect.bundle.min.js diff --git a/components/profile-modal.html b/components/profile-modal.html index 00375790..e0932ba2 100644 --- a/components/profile-modal.html +++ b/components/profile-modal.html @@ -860,6 +860,30 @@

Connect a Nostr Wallet Connect (NWC) compatible wallet to zap creators directly from bitvid.

+
`),de,wn(`w-7 h-7 ${de}`),de,this.renderQR(),this._copyAuthString,Pe,qe,this._hasCopiedAuthString?ha:fa,this._hasCopiedAuthString?"Copied!":"Copy")}renderQR(){if(!this._authString)return null;let e=this._authString;return setTimeout(()=>{var t;let r=(t=this.shadowRoot)==null?void 0:t.getElementById("qr");if(!r)return void console.error("qr canvas not found");let n=r.getContext("2d");if(!n)return void console.error("could not get context for qr canvas");let i=(0,To.default)(0,"L");i.addData(e),i.make();let a=i.getModuleCount();r.width=4*a,r.height=4*a,i.renderTo2dContext(n,4)},100),X(r0||(r0=o0` + + + `))}_copyAuthString(){this._authString&&(navigator.clipboard.writeText(this._authString),this._hasCopiedAuthString=!0,setTimeout(()=>{this._hasCopiedAuthString=!1},2e3))}async initAlbyGo(){try{var e;let t=(e=ne.getState().bitcoinConnectConfig.providerConfig)==null||(e=e.nwc)==null?void 0:e.authorizationUrlOptions,r=t?.expiresAt,n=t?.requestMethods;n||(n=["get_info","get_balance","get_budget","pay_invoice","list_transactions","lookup_invoice","make_invoice"]);let i=new Yn({name:this._appName,icon:this._appIcon,relayUrls:["wss://relay.getalby.com/v1"],requestMethods:n,notificationTypes:t?.notificationTypes,maxAmount:t?.maxAmount,budgetRenewal:t?.budgetRenewal,expiresAt:r?Math.floor(r.getTime()/1e3):void 0,isolated:t?.isolated,metadata:t?.metadata,returnTo:t?.returnTo});this._authString=i.getConnectionUri("alby"),window.location.href=this._authString;let{unsub:a}=await i.subscribe({onSuccess:async function(f){f.close(),ne.getState().connect({nwcUrl:f.nostrWalletConnectUrl,connectorName:"Alby Hub",connectorType:"nwc.albyhub"})}});this._unsub=a}catch(t){console.error(t),alert(""+t)}}};ee([ge()],co.prototype,"_authString",void 0),ee([ge()],co.prototype,"_hasCopiedAuthString",void 0),co=ee([pe("bc-alby-go")],co);var i0,s0,k5=e=>e,lo=class extends Ce()(ye){constructor(){super(...arguments),this._lnbitsAdminKey="",this._lnbitsUrl=""}render(){return X(i0||(i0=k5`
+ +
+
+
+ In LNbits, choose the wallet you want to connect, open it, click on + API docs and copy the Admin Key. Paste it below: +
+ +
+ LNbits Admin Key +
+ +
+ LNbits URL +
+ + + + Connect + +
+
+
`),qs,mr,de,this._lnbitsAdminKey,this._lnbitsAdminKeyChanged,bn,It,de,this._lnbitsUrl,this._lnbitsUrlChanged,bn,It,this.onConnect)}_lnbitsAdminKeyChanged(e){this._lnbitsAdminKey=e.target.value}_lnbitsUrlChanged(e){this._lnbitsUrl=e.target.value}async onConnect(){if(!this._lnbitsAdminKey)return void ne.getState().setError("Please enter your admin key");if(!this._lnbitsUrl)return void ne.getState().setError("Please enter your LNbits instance URL");let e=this._lnbitsUrl;e.endsWith("/")&&(e=e.substring(0,e.length-1)),await ne.getState().connect({lnbitsAdminKey:this._lnbitsAdminKey,lnbitsInstanceUrl:e,connectorName:qs,connectorType:"lnbits"})}};ee([ge()],lo.prototype,"_lnbitsAdminKey",void 0),ee([ge()],lo.prototype,"_lnbitsUrl",void 0),lo=ee([pe("bc-lnbits")],lo);var zh=Ee(s0||(s0=(e=>e)` + + + + + + + + + + + + + +`)),a0,L5=Ee(a0||(a0=(e=>e)` + + +`)),c0,U5=Ee(c0||(c0=(e=>e)` + + + + + +`)),l0,u0,h0,f0,d0,p0,y0,g0,b0,w0,m0,v0,E0,_0,C0,A0,S0,T0,O0,je=e=>e,Wt=class extends Ce()(ye){constructor(){super(...arguments),this._hasCopiedInvoice=!1,this._isPaying=!1,this._showQR=!1,this.paymentMethods="all"}updated(e){super.updated(e),e.has("paid")&&this.paid&&setTimeout(()=>{No()},3e3)}renderHeading(e){return X(l0||(l0=je` +

+ ${0} sats +

+ `),de,Pe,e.satoshi.toLocaleString(void 0,{useGrouping:!0}))}renderPaidState(){return X(u0||(u0=je` +
+

Paid!

+ ${0} +
+ `),Pe,zh)}renderPayingState(){return X(h0||(h0=je` +
+

Paying...

+ ${0} +
+ `),de,wn(`w-48 h-48 ${Pe}`))}renderPaymentConfirmation(){return X(f0||(f0=je` + + ${0} + Confirm Payment + + ${0} + `),this._payInvoice,lr,ua(this._connectorName))}renderWaitingForPayment(){return X(d0||(d0=je` +
+ ${0} +

Waiting for payment

+
+ `),wn(`w-7 h-7 ${Pe}`),de)}renderConnectWalletMobile(){let e=null,t=null,r=null;return this.paymentMethods!=="all"&&this.paymentMethods!=="internal"||(e=X(p0||(p0=je` + + ${0}Connect Wallet + + `),this._onClickConnectWallet,lr)),this.paymentMethods!=="all"&&this.paymentMethods!=="external"||(this._showQR||(t=X(y0||(y0=je` + + ${0} Copy & Display Invoice + + `),this._copyAndDisplayInvoice,U5)),this._showQR&&(r=this.renderQR())),X(g0||(g0=je` +
+ ${0} + ${0} ${0} +
+ ${0} + `),this.paymentMethods==="all"||this.paymentMethods==="external"?X(b0||(b0=je` + + ${0} Open in a Bitcoin Wallet + + `),this.invoice,L5):null,e,t,r?X(w0||(w0=je`
${0}
`),r):null)}renderConnectWalletDesktop(){let e=null;this.paymentMethods!=="all"&&this.paymentMethods!=="internal"||(e=X(m0||(m0=je` +
+ + ${0} + Connect Wallet to Pay + +
+ `),this.paymentMethods!=="internal"?"mt-8":"",this._onClickConnectWallet,lr));let t=null;this.paymentMethods==="all"&&(t=X(v0||(v0=je`
${0}
`),qh("or")));let r=null;return this.paymentMethods!=="all"&&this.paymentMethods!=="external"||(r=X(E0||(E0=je` +
+

+ Scan to Pay +

+ ${0} +
+ `),this.paymentMethods==="external"?"mt-8":"",de,this.renderQR())),X(_0||(_0=je` ${0} ${0} ${0} `),e,t,r)}renderQR(){if(!this._showQR||!this.invoice)return null;let e=this.invoice;return setTimeout(()=>{var t;let r=(t=this.shadowRoot)==null?void 0:t.getElementById("qr");if(!r)return void console.error("qr canvas not found");let n=r.getContext("2d");if(!n)return void console.error("could not get context for qr canvas");let i=(0,To.default)(0,"L");i.addData(e),i.make();let a=i.getModuleCount();r.width=2*a,r.height=2*a,i.renderTo2dContext(n,2)},100),X(C0||(C0=je` + + + + + + `),this.invoice,this._copyInvoice,Pe,qe,this._hasCopiedInvoice?"Invoice copied to clipboard":"Copy invoice to clipboard",this._hasCopiedInvoice?ha:fa,this._hasCopiedInvoice?"Copied!":"Copy Invoice")}renderMemo(e){return e.description?X(A0||(A0=je` +

+ ${0} +

+ `),At,e.description):null}render(){if(!this.invoice)return null;let e;try{e=new ro({pr:this.invoice})}catch(n){return console.error(n),ne.getState().setError(n.message),null}let t=window.innerWidth<600,r;return t||(this._showQR=!0),r=this.paid?this.renderPaidState():this._isPaying?this.renderPayingState():this._connected?this.renderPaymentConfirmation():X(S0||(S0=je` + ${0} + ${0} + `),this.paymentMethods!=="internal"?this.renderWaitingForPayment():null,t?this.renderConnectWalletMobile():this.renderConnectWalletDesktop()),X(T0||(T0=je` +
+ ${0} ${0} + ${0} +
+ `),this.renderHeading(e),this.renderMemo(e),r)}_onClickConnectWallet(){this.dispatchEvent(new Event("onclickconnectwallet",{bubbles:!0,composed:!0}))}_copyAndDisplayInvoice(){this._copyInvoice(),this._showQR=!0}_copyInvoice(){this.invoice&&(navigator.clipboard.writeText(this.invoice),this._hasCopiedInvoice=!0,setTimeout(()=>{this._hasCopiedInvoice=!1},2e3))}async _payInvoice(){this._isPaying=!0;try{let e=ne.getState().provider;if(!e)throw new Error("No WebLN provider available");if(!this.invoice)throw new Error("No invoice to pay");let t=await e.sendPayment(this.invoice);if(!t.preimage)throw new Error("No preimage in result");this.dispatchEvent(new CustomEvent("bc:onpaid",{bubbles:!0,composed:!0,detail:t})),this.paid=!0}catch(e){console.error(e),ne.getState().setError(e.message)}this._isPaying=!1}};ee([ge()],Wt.prototype,"_hasCopiedInvoice",void 0),ee([ge()],Wt.prototype,"_isPaying",void 0),ee([ge()],Wt.prototype,"_showQR",void 0),ee([Ue({type:String})],Wt.prototype,"invoice",void 0),ee([Ue({type:Boolean})],Wt.prototype,"paid",void 0),ee([Ue({type:String,attribute:"payment-methods"})],Wt.prototype,"paymentMethods",void 0),Wt=ee([pe("bc-send-payment")],Wt);var M5=Ee(O0||(O0=(e=>e)` + + + + + + + + + + + + + + + + + + + + + +`)),N0,D5=e=>e,x0=class extends Ce()(ye){render(){return X(N0||(N0=D5`
+ + +
+
+
+
+ ${0} +
+
+

+ To get the best self-custodial bitcoin lightning wallet that can + connect to apps, try + Alby Hub. +

+
+
+
${0}
+

+ For a quick setup of a custodial wallet, you can choose between + Rizful + and + Coinos. +

+
+
+
`),Vh,de,qe,Pe,M5,de,qe,Pe,qe,Pe)}};x0=ee([pe("bc-new-wallet")],x0);var R0,B5=e=>e,ps=class extends Ce()(ye){constructor(){super(...arguments),this._nwcUrl=""}render(){return X(R0||(R0=B5`
+ +
+
+
+ 1. Add a new + Wallet Connection + + from + LN Node => Generate NWC + and copy the Connection Secret. +
+
+ 2. Paste the Connection Secret below: +
+ + + + Connect + +
+
+
`),de,At,de,this._nwcUrl,this.nwcUrlChanged,bn,It,this.onConnect)}nwcUrlChanged(e){this._nwcUrl=e.target.value}async onConnect(){this._nwcUrl?await ne.getState().connect({nwcUrl:this._nwcUrl,connectorName:la,connectorType:"nwc.generic"}):ne.getState().setError("Please enter a URL")}};ee([ge()],ps.prototype,"_nwcUrl",void 0),ps=ee([pe("bc-lnfi")],ps);var I0,F5=e=>e,P0=class extends Ce()(ye){connectedCallback(){super.connectedCallback(),this._timeout=setTimeout(()=>{No(),ne.setState({route:"/start"})},3e3)}disconnectedCallback(){super.disconnectedCallback(),this._timeout&&clearTimeout(this._timeout)}render(){return X(I0||(I0=F5`
+

Connected!

+ ${0} +
`),Pe,zh)}};P0=ee([pe("bc-connected")],P0);var k0,L0,U0=e=>e,uo=class extends Ce()(ye){constructor(){super(),this._hasCopiedAuthString=!1,this.initAlbyGo()}disconnectedCallback(){var e;super.disconnectedCallback(),(e=this._unsub)==null||e.call(this)}render(){return X(k0||(k0=U0`
+ +
+
+
+ Scan with your camera, or by clicking "Send" in the Flash Wallet +
+ +
+ ${0} +

Waiting for connection

+
+ + + ${0} + + + + ${0} + ${0} + +
+ +
+

+ Don't have the Flash Wallet app? + Download one here +

+
+
+
+ `),de,wn(`w-7 h-7 ${de}`),de,this.renderQR(),this._copyAuthString,Pe,qe,this._hasCopiedAuthString?ha:fa,this._hasCopiedAuthString?"Copied!":"Copy",mr,qe,Pe)}renderQR(){if(!this._authString)return null;let e=this._authString;return setTimeout(()=>{var t;let r=(t=this.shadowRoot)==null?void 0:t.getElementById("qr");if(!r)return void console.error("qr canvas not found");let n=r.getContext("2d");if(!n)return void console.error("could not get context for qr canvas");let i=(0,To.default)(0,"L");i.addData(e),i.make();let a=i.getModuleCount();r.width=4*a,r.height=4*a,i.renderTo2dContext(n,4)},100),X(L0||(L0=U0` + + + `))}_copyAuthString(){this._authString&&(navigator.clipboard.writeText(this._authString),this._hasCopiedAuthString=!0,setTimeout(()=>{this._hasCopiedAuthString=!1},2e3))}async initAlbyGo(){try{var e;let t=(e=ne.getState().bitcoinConnectConfig.providerConfig)==null||(e=e.nwc)==null?void 0:e.authorizationUrlOptions,r=t?.expiresAt,n=t?.requestMethods;n||(n=["get_info","get_balance","get_budget","pay_invoice","list_transactions","lookup_invoice","make_invoice"]);let i=new Yn({name:this._appName,icon:this._appIcon,relayUrls:["wss://nwclay.paywithflash.com"],requestMethods:n,notificationTypes:t?.notificationTypes,maxAmount:t?.maxAmount,budgetRenewal:t?.budgetRenewal,expiresAt:r?Math.floor(r.getTime()/1e3):void 0,isolated:t?.isolated,metadata:t?.metadata,returnTo:t?.returnTo});this._authString=i.connectionUri,window.location.href=this._authString;let{unsub:a}=await i.subscribe({onSuccess:async function(f){f.close(),ne.getState().connect({nwcUrl:f.nostrWalletConnectUrl,connectorName:"Flash Wallet",connectorType:"nwc.flash"})}});this._unsub=a}catch(t){console.error(t),alert(""+t)}}};ee([ge()],uo.prototype,"_authString",void 0),ee([ge()],uo.prototype,"_hasCopiedAuthString",void 0),uo=ee([pe("bc-flash-wallet")],uo);var M0,D0,j5=e=>e,ys=class extends Ce()(ye){constructor(){super(...arguments),this._nwcUrl=""}render(){return X(M0||(M0=j5`
+ +
+
+
+ 1. In LNBits, go to Plugins and enable the NWC plugin. +
+
+ 2. Create a new connection and update the relay from "nostrclient" + to wss://relay.getalby.com/v1, + then copy the connection secret. +
+
+ 3. Paste the Connection Secret below. +
+ +
+
+ Enter your + Connection Secret + + below +
+ + + + Connect + +
+
+
+
`),zs,de,de,de,de,this._nwcUrl,this.nwcUrlChanged,bn,It,this.onConnect)}nwcUrlChanged(e){this._nwcUrl=e.target.value}async onConnect(){this._nwcUrl?await ne.getState().connect({nwcUrl:this._nwcUrl,connectorName:zs,connectorType:"nwc.lnbits"}):ne.getState().setError("Please enter a URL")}};ee([ge()],ys.prototype,"_nwcUrl",void 0),ys=ee([pe("bc-lnbits-nwc")],ys);var H5=Ee(D0||(D0=(e=>e)` + + + + + + + + +`)),Js="Cashu.me",B0=class extends st{constructor(){super("nwc.cashume",Js,"#7f38ca",H5)}async _onClick(){ne.getState().pushRoute("/cashu-me")}};B0=ee([pe("bc-cashu-me-connector")],B0);var F0,$5=e=>e,gs=class extends Ce()(ye){constructor(){super(...arguments),this._nwcUrl=""}render(){return X(F0||(F0=$5`
+ +
+
+
+ 1. Open + Cashu.me + + , click on the hamburger menu on + the top left, then click + Settings. Scroll down to + Nostr Wallet Connect + and enable NWC, and then press the copy button. +
+
+ 2. Keep the Cashu.me browser tab open while using Bitcoin Connect. +
+
+ 3. Paste the Connection Secret below: +
+ +
+
+ Enter your + Connection Secret + + below +
+ + + + Connect + +
+
+
+
`),Js,de,de,de,de,this._nwcUrl,this.nwcUrlChanged,bn,It,this.onConnect)}nwcUrlChanged(e){this._nwcUrl=e.target.value}async onConnect(){this._nwcUrl?await ne.getState().connect({nwcUrl:this._nwcUrl,connectorName:Js,connectorType:"nwc.cashume"}):ne.getState().setError("Please enter a URL")}};ee([ge()],gs.prototype,"_nwcUrl",void 0),gs=ee([pe("bc-cashu-me")],gs);var j0,G5=e=>e,bs=class extends Ce()(ye){constructor(){super(...arguments),this._nwcUrl=""}render(){return X(j0||(j0=G5`
+ +
+
+
+ 1. Open + Rizful Wallet + +
+
+ 2. Click on + Hamburger Menu -> NWC -> Get NWC codes -> New Connection. Then click Get Connection Code and + click + Copy Connection Code +
+ +
+ 3. Paste the Connection Secret below: +
+ +
+
+ Enter your + Connection Secret + + below +
+ + + + Connect + +
+
+
+
`),Qs,de,de,de,de,this._nwcUrl,this.nwcUrlChanged,bn,It,this.onConnect)}nwcUrlChanged(e){this._nwcUrl=e.target.value}async onConnect(){this._nwcUrl?await ne.getState().connect({nwcUrl:this._nwcUrl,connectorName:Qs,connectorType:"nwc.rizful"}):ne.getState().setError("Please enter a URL")}};ee([ge()],bs.prototype,"_nwcUrl",void 0),bs=ee([pe("bc-rizful")],bs);var H0,$0,G0,W0,K0,V0,Z0,Y0,q0,z0,Q0,J0,X0,it=e=>e,W5={"/start":X(H0||(H0=it``)),"/help":X($0||($0=it``)),"/nwc":X(G0||(G0=it``)),"/lnfi":X(W0||(W0=it``)),"/alby-hub":X(K0||(K0=it``)),"/alby-go":X(V0||(V0=it``)),"/lnbits":X(Z0||(Z0=it``)),"/lnbits-nwc":X(Y0||(Y0=it``)),"/flash-wallet":X(q0||(q0=it``)),"/cashu-me":X(z0||(z0=it``)),"/rizful":X(Q0||(Q0=it``)),"/new-wallet":X(J0||(J0=it``)),"/connected":X(X0||(X0=it``))},eh,th,K5=e=>e,nh=class extends Ce()(ye){render(){return X(eh||(eh=K5`
${0}
`),W5[this._route])}};nh=ee([pe("bc-router-outlet")],nh);var V5=Ee(th||(th=(e=>e)` + +`)),rh,Z5=Ee(rh||(rh=(e=>e)` + + + + +`)),oh,ih,sh,ws=e=>e,ho=class extends Ce()(ye){render(){return X(oh||(oh=ws`
+
+ ${0} + ${0} +
+
+ +
+
`),this.showHelp?X(ih||(ih=ws``),qe,At,()=>ne.getState().pushRoute("/help"),Z5):null,this.closable?X(sh||(sh=ws``),qe,At,this._handleClose,V5):null)}_handleClose(){this.dispatchEvent(new Event("onclose",{bubbles:!0,composed:!0}))}};ee([Ue({type:Boolean})],ho.prototype,"closable",void 0),ee([Ue({type:Boolean,attribute:"show-help"})],ho.prototype,"showHelp",void 0),ho=ee([pe("bc-modal-header")],ho);var ah,Y5=e=>e,ch=class extends Ce()(ye){constructor(){super(...arguments),this._handleClose=()=>{No()}}render(){return X(ah||(ah=Y5`
+
+
+ +
+
`),this._handleClose,this._handleClose)}};ch=ee([pe("bc-modal")],ch);var lh,uh,q5=e=>e,hh=class extends Ce()(Xt){render(){return X(lh||(lh=q5` +
+ ${0} +

+ Connecting to wallet... +

+ ${0} +
+ `),wn(`w-20 h-20 ${At} mb-4`),de,ua(void 0))}};hh=ee([pe("bci-connecting")],hh);var z5=Ee(uh||(uh=(e=>e)` + + + + + + + + + + + + + + + + +`)),fh,Q5=Ee(fh||(fh=(e=>e)` + + +`)),dh,ms,ph,yh,gh,bh,wh,rr=e=>e,vs=(ms=class extends(dh=Ce()(ye)){render(){return X(ph||(ph=rr`
+ +
+ ${0} +
+
${0}
+
+
+ ${0} +
+ ${0} +
`),this.closable,Pe,Q5,It,z5,X(this._connecting?yh||(yh=rr``):gh||(gh=rr` `)),this._error?X(bh||(bh=rr`

+ ${0} +

`),this._error):null)}},ms.styles=[...dh.styles,Oo(wh||(wh=rr` + :host { + display: flex; + justify-content: center; + width: 100%; + } + `))],ms);ee([Ue({type:Boolean})],vs.prototype,"closable",void 0),vs=ee([pe("bc-connect")],vs);var mh,Es,vh,Eh,_h,Ch,fo=e=>e,un=(Es=class extends(mh=Ce()(ye)){constructor(){super(),this.paymentMethods="all",this._showConnect=!1,ne.subscribe((e,t)=>{e.connected!==t.connected&&e.connected&&(this._showConnect=!1)})}render(){return this._showConnect&&!this.paid?X(vh||(vh=fo` `),!0):X(Eh||(Eh=fo`
+ +

+ Payment Request +

+
+
+ +
+ ${0} +
`),this.closable,de,this.invoice,this.paymentMethods,this.paid,this._onClickConnectWallet,this._error?X(_h||(_h=fo`

+ ${0} +

`),this._error):null)}_onClickConnectWallet(){this._showConnect=!0}},Es.styles=[...mh.styles,Oo(Ch||(Ch=fo` + :host { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + } + `))],Es);ee([Ue({type:Boolean})],un.prototype,"closable",void 0),ee([Ue({type:String})],un.prototype,"invoice",void 0),ee([Ue({type:String,attribute:"payment-methods"})],un.prototype,"paymentMethods",void 0),ee([Ue({type:Boolean})],un.prototype,"paid",void 0),ee([ge()],un.prototype,"_showConnect",void 0),un=ee([pe("bc-payment")],un);export{No as closeModal,i5 as disconnect,s5 as getConnectorConfig,r5 as init,n5 as isConnected,Kh as launchModal,X3 as onConnected,e5 as onDisconnected}; +/*! Bundled license information: + +@lightninglabs/lnc-web/dist/index.js: + (*! For license information please see index.js.LICENSE.txt *) + +@getalby/sdk/dist/esm/index.js: + (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *) + (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *) + (*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) *) + (*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) *) + +@getalby/sdk/dist/esm/index.js: + (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *) + +@getalby/sdk/dist/esm/index.js: + (*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) *) + +@getalby/lightning-tools/dist/esm/index.js: + (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *) + (*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) *) +*/ From 1add0bebec91480713f60897d7bb9425f5037369 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:56:00 -0400 Subject: [PATCH 03/18] feat(wallet): tuck manual URI under an "Advanced" disclosure (Bitcoin Connect D5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When FEATURE_BITCOIN_CONNECT is on, "Connect wallet" is the primary path and the raw Wallet Connect URI field collapses under an "Advanced — paste a connection manually"
disclosure (backup, de-emphasized). When the flag is off, the controller forces the disclosure open and hides its summary, so the URI field shows plainly exactly as before (no behavior change). Default-zap field stays always visible. Adds a small token-based rotating-caret style for the summary. Still LOCAL (flag ON for testing); not pushed, not deployed. Co-Authored-By: Claude Opus 4.8 --- components/profile-modal.html | 29 +++++++++++------ css/tailwind.source.css | 19 ++++++++++++ js/ui/profileModal/ProfileWalletController.js | 31 ++++++++++++++----- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/components/profile-modal.html b/components/profile-modal.html index e0932ba2..4a430a12 100644 --- a/components/profile-modal.html +++ b/components/profile-modal.html @@ -885,15 +885,26 @@

- +
+ + Advanced — paste a connection manually + + +