diff options
| author | Adam Soltys <[email protected]> | 2026-06-28 19:19:17 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-28 19:19:17 -0700 |
| commit | 656726e17684071e929a2f0453cdd306c4d8dc12 (patch) | |
| tree | 3530fc737a77d43974beec807aac60a6e8303c9f | |
| parent | 93631253030839509551d7e1e6a995ed60e31b96 (diff) | |
| download | coinosv3-656726e17684071e929a2f0453cdd306c4d8dc12.tar.gz coinosv3-656726e17684071e929a2f0453cdd306c4d8dc12.zip | |
Swaps: gate 'Receive over Lightning' on watch-only (was throwing 'uint8array expected')
A watch-only wallet has no private key, so swapNode().privateKey is null and
startReverse's sha256(node.privateKey) threw 'uint8array expected'. The receive
card wasn't gated by watchOnly (the pay card already was). Hide both for watch-only
and show a prompt to re-enter the recovery phrase, since swaps need the key to
derive the preimage and claim on-chain.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
| -rw-r--r-- | src/app.js | 24 |
1 files changed, 15 insertions, 9 deletions
@@ -2597,20 +2597,26 @@ function swapListView() { function swapTab() { // Pull the selected provider's real reverse-swap limits (min/max) once, to bound // the amount instead of guessing — providers + networks differ. - if (!ui.swapLimits && !ui._swapLimitsLoading) { + if (!ui.swapLimits && !ui._swapLimitsLoading && !wallet.watchOnly) { ui._swapLimitsLoading = true; swaps.reverseLimits().then((l) => { ui.swapLimits = l; ui._swapLimitsLoading = false; render(); }).catch(() => { ui._swapLimitsLoading = false; }); } const lim = ui.swapLimits; return h('div', { class: 'col', style: 'gap:12px' }, - h('div', { class: 'card col' }, - h('h3', {}, '⚡ Receive over Lightning'), - h('p', { class: 'small muted', style: 'margin:0' }, 'Get a Lightning invoice; when paid, the sats are swapped on-chain into this wallet — non-custodial (you hold the preimage and claim on-chain).'), - h('label', { class: 'field' }, - h('span', { class: 'lab' }, 'Amount (sats)'), - h('input', { type: 'number', inputmode: 'numeric', placeholder: lim ? String(lim.min) : '100000', value: ui.swapReverseAmt || '', onInput: (e) => { ui.swapReverseAmt = e.target.value; } }), - lim ? h('span', { class: 'small muted' }, `Min ${lim.min.toLocaleString()} · max ${lim.max.toLocaleString()} sats`) : null), - h('button', { disabled: !!ui.swapBusy, onClick: doReverse }, ui.swapBusy ? '…' : 'Create invoice')), + // Swaps need the private key (to derive the preimage and claim on-chain), so a + // watch-only wallet can't do them — prompt to re-enter the seed instead. + wallet.watchOnly + ? h('div', { class: 'card col' }, + h('h3', {}, '⚡ Lightning swaps'), + h('p', { class: 'small muted', style: 'margin:0' }, 'Re-enter your recovery phrase to send or receive over Lightning — swaps need your keys to claim on-chain.')) + : h('div', { class: 'card col' }, + h('h3', {}, '⚡ Receive over Lightning'), + h('p', { class: 'small muted', style: 'margin:0' }, 'Get a Lightning invoice; when paid, the sats are swapped on-chain into this wallet — non-custodial (you hold the preimage and claim on-chain).'), + h('label', { class: 'field' }, + h('span', { class: 'lab' }, 'Amount (sats)'), + h('input', { type: 'number', inputmode: 'numeric', placeholder: lim ? String(lim.min) : '100000', value: ui.swapReverseAmt || '', onInput: (e) => { ui.swapReverseAmt = e.target.value; } }), + lim ? h('span', { class: 'small muted' }, `Min ${lim.min.toLocaleString()} · max ${lim.max.toLocaleString()} sats`) : null), + h('button', { disabled: !!ui.swapBusy, onClick: doReverse }, ui.swapBusy ? '…' : 'Create invoice')), wallet.watchOnly ? null : h('div', { class: 'card col' }, h('h3', {}, '⚡ Pay a Lightning invoice'), h('p', { class: 'small muted', style: 'margin:0' }, 'Pay a bolt11 from your on-chain balance: funds a swap that Boltz pays over Lightning.'), |
