diff options
| author | Adam Soltys <[email protected]> | 2026-06-15 09:52:18 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-15 09:52:18 -0700 |
| commit | 9ab7f898a02ecbafd7da511107cddf2fd5fe104d (patch) | |
| tree | 922502921bfebe3f4d24df97cf47d64952c80aca /src/app.js | |
| parent | 92b27e7d6de51ae178f068b079d3eec017689d85 (diff) | |
| download | coinosv3-9ab7f898a02ecbafd7da511107cddf2fd5fe104d.tar.gz coinosv3-9ab7f898a02ecbafd7da511107cddf2fd5fe104d.zip | |
Fix Max button, slash scan requests (429s), cache history locally
Max button: fill the amount field with the computed maximum (n inputs, 1
output, current fee rate) so it gives visible feedback instead of just
blanking/disabling the field.
Rate limits: eliminate look-ahead (gap limit 1 — stop at the first unused
address, safe because we only ever expose one unused address at a time),
query one address at a time (API concurrency 1, 400ms spacing across two
backends), scan chains sequentially, and pause 500ms after each used address.
An empty scan is now ~2-3 requests instead of ~10+.
History cache: persist scanned state (addresses, utxos, txs, indices) in
localStorage keyed by sha256(seed) — the seed itself is never stored. Re-
importing the same seed in this browser shows the last-known balance/history
instantly while a fresh scan runs. Cleared by nothing automatically (public
chain data); different seeds get separate keys.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'src/app.js')
| -rw-r--r-- | src/app.js | 21 |
1 files changed, 20 insertions, 1 deletions
@@ -347,6 +347,7 @@ async function openWallet(mnemonic) { async function enterWallet(mnemonic, passphrase) { wallet.load({ mnemonic, passphrase, netName: 'mainnet', offline: false }); persistSession(mnemonic, passphrase); + wallet.restoreCache(); // show last-known balance/history instantly, if cached ui.screen = 'wallet'; ui.tab = 'receive'; ui.send = blankSend(); @@ -626,7 +627,10 @@ function sendForm() { { class: 'input-group' }, h('input', { type: 'number', step: 'any', min: '0', placeholder: '0.00000000', - disabled: s.max, value: s.max ? '' : s.amount, + disabled: s.max, + value: s.max + ? (s.unit === 'sats' ? String(estimatedMaxSats()) : fmtBtc(estimatedMaxSats())) + : s.amount, onInput: (e) => (s.amount = e.target.value), }), h( @@ -714,6 +718,21 @@ function coinControl() { ); } +// Coins that a send would draw from (all, or the manually-selected subset). +function spendableCoins() { + const s = ui.send; + return s.manual ? wallet.utxos.filter((u) => s.coins.has(utxoId(u))) : wallet.utxos; +} + +// Estimated max sendable = selected total − fee for (n inputs, 1 output). +function estimatedMaxSats() { + const coins = spendableCoins(); + const total = coins.reduce((a, u) => a + u.value, 0); + const vbytes = 11 + 68 * coins.length + 31; + const fee = Math.ceil(vbytes * currentFeeRate()); + return Math.max(0, total - fee); +} + function currentFeeRate() { const s = ui.send; if (s.feeChoice === 'custom') return Math.max(1, Math.round(Number(s.customFee) || 1)); |
