<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinosv3.git/src/features/sp-wallet.js, branch master</title>
<subtitle>coinos v3 — bitcoin, Ark, Lightning and nostr wallet (web + Android)</subtitle>
<id>https://git.coinos.io/coinosv3.git/atom?h=master</id>
<link rel='self' href='https://git.coinos.io/coinosv3.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/'/>
<updated>2026-08-02T02:38:30Z</updated>
<entry>
<title>Simplify: one Lightning path, one balance, fewer features</title>
<updated>2026-08-02T02:38:30Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-02T02:38:30Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=348a57004f94dd0f58c4e70769e65ca7ea3f7619'/>
<id>urn:sha1:348a57004f94dd0f58c4e70769e65ca7ea3f7619</id>
<content type='text'>
Dropped: silent payments (feature, wallet chains, scan worker, indexer
config), Boltz submarine/reverse swaps (feature + swap.js + tools), the
third-party ark swap bridge (config, client flows, settings card — the
ASP's own Lightning send is the one path now), and the Consolidate
coins settings action.

The NWC pouch is gone with them: background answering now spends the
WHOLE ark balance. The IndexedDB record mirrors the real manager state
plus the chain-3 keys for its live coins and a change-index window; the
worker pays through the ASP exactly like the open app, and the app
folds the worker's spends, change, and key allocations back in on
connect (additive merge + reconcile against the server). A leftover
chain-6 pouch from the short-lived pouch era is swept home
automatically using the keys stored in its own record. The stated
tradeoff: same-origin script access now reaches the ark balance, not a
carve-out — the ark balance is walking-around money, the on-chain
wallet's keys are never mirrored, and per-connection NWC budgets remain
the enforced ceiling.

Bundle: 726KB -&gt; 602KB.

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Sync silent-payment scan across devices so the catch-up runs once</title>
<updated>2026-07-31T07:15:59Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-07-31T07:15:59Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=6dc7f9f89a071e43de77bf6dd82cf8d4012ce8c3'/>
<id>urn:sha1:6dc7f9f89a071e43de77bf6dd82cf8d4012ce8c3</id>
<content type='text'>
SP state (lastSpScan watermark + found spUtxos) was in the nostr snapshot
but, unlike Ark/gifts, its cache extension wasn't mergeAlways — so it only
applied via the newest full snapshot at load time, last-writer-wins. The
watermark could regress (a device behind on SP, or mid-rescan, publishing a
newer snapshot would reset another device's watermark), so every device
tended to re-grind the ~1M-tweak backlog independently.

Make it a commutative merge like Ark:
- mergeAlways: true, so it merges live and from older snapshots too.
- lastSpScan takes the MAX (monotonic high-water mark, never regresses) —
  this is what makes the block catch-up run once across all devices.
- spUtxos is an additive union by outpoint; a coin we already track is never
  mutated, and spend detection stays each device's own job (_refreshSpUtxos
  drops backend-spent coins), so stale merged-in coins self-heal on refresh.
- Anti-entropy: republish our superset when we know more, guarded so it stops
  once devices converge.

A manual rescan still works — it forces from=1 synchronously (no await before
capturing it), so a concurrent max-merge can't sabotage the re-scan.

Verified with a merge unit test driving the real extension (max/union/converge).

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Fix silent-payment catch-up pegging the CPU (both threads)</title>
<updated>2026-07-31T06:47:03Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-07-31T06:47:03Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=c264f12ded66f60ab137d05a6e2449f17c98ad68'/>
<id>urn:sha1:c264f12ded66f60ab137d05a6e2449f17c98ad68</id>
<content type='text'>
A wallet a few thousand blocks behind gets the whole scan window from the
indexer at once (~1M tweaks over ~3900 mainnet blocks). The catch-up loop
chunked by 100 BLOCKS, but the cost is one EC point-multiply per TWEAK and
mainnet blocks are wildly uneven (up to 2565 tweaks each) — so a 100-block
chunk could be ~25k tweaks, seconds of work that blew the worker's 30s
timeout. On timeout the caller ran the SAME chunk inline on the main thread
while the orphaned worker kept grinding it, pegging both threads and
freezing the visible tab for the whole ~20min catch-up.

Two fixes:
- Chunk by tweak count (budget 2000, always ≥1 block) instead of block
  count, so every worker call stays sub-second regardless of block density.
  Verified against live mainnet data: max chunk drops from ~25k tweaks to
  ~2.8k (~850ms, &lt;3% of the timeout).
- On a worker-call timeout, TERMINATE the worker instead of leaving it
  grinding the orphaned request behind the caller's inline fallback — that
  double-grind was the both-threads-pegged symptom. _ensureSpWorker respawns
  a fresh worker on the next call.

Diagnosed via CDP CPU profiling of the live tab (95% self-time in
_spHitBlocks -&gt; _spCandidate -&gt; secp256k1 wNAF/precompute).

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Stop pointless SP catch-up scans</title>
<updated>2026-07-06T06:16:30Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-07-06T06:16:30Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=81a55be4ea07d4dee4a83a3728c8ba0914744cd5'/>
<id>urn:sha1:81a55be4ea07d4dee4a83a3728c8ba0914744cd5</id>
<content type='text'>
A gift-claimed wallet is a brand-new seed but the claim flow never
passed generated:true, so every gift wallet ground through the
indexer's full history (~336k tweaks) hunting silent payments it could
not have received — the CPU burn seen with a fresh gift wallet open.
Fresh claim wallets now fast-forward their scan watermark to the tip
like any other generated wallet. And two tabs on the same wallet no
longer duplicate a catch-up: a localStorage lease, refreshed per chunk,
lets the second tab yield.

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Chunk the silent-payment catch-up scan</title>
<updated>2026-07-06T04:27:06Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-07-06T04:27:06Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=76e1a88b9c2eae1de0145ff077f038b57e68c3a8'/>
<id>urn:sha1:76e1a88b9c2eae1de0145ff077f038b57e68c3a8</id>
<content type='text'>
An imported mainnet wallet's first scan pulled the indexer's whole
window (~336k tweaks, 25MB) and posted it to the worker as ONE message:
it blew the 30s worker timeout, fell back to the inline main-thread
loop (minutes of jank) while the worker kept grinding the orphaned
request — the runaway CPU/memory users saw. The catch-up now processes
100-block chunks: each worker call is seconds, the scan watermark
checkpoints after every chunk (an interrupted catch-up resumes instead
of restarting from zero), buffers free as it goes, and the UI yields
between chunks. Verified against the live indexer: full catch-up in
~5min of background work, main heap bounded (~80MB peak), no timeouts.

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Features phase 2b: silent payments + nostr sync leave the core wallet</title>
<updated>2026-07-04T19:35:15Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-07-04T19:35:15Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=cc2c05d2a1b7a9601eb3cb0df998483099e590cc'/>
<id>urn:sha1:cc2c05d2a1b7a9601eb3cb0df998483099e590cc</id>
<content type='text'>
wallet.js drops from 2,374 to ~1,970 lines, holding only BIP84 on-chain
logic plus generic feature seams. New seams added this pass:
  registerBalanceExtra   extra confirmed/pending buckets
  registerInputProvider  extra spendable inputs for buildTx
  registerSendPreparer   destinations needing post-selection derivation
  registerInputSigner    non-BIP84 input signing
  registerHistoryProvider / registerScanHook / registerRealtimeHook /
  registerLoadHook / registerCacheExtension / registerCacheSavedHook

src/features/sp-wallet.js: the whole BIP-352 engine — key derivation,
worker-accelerated indexer scanning, the live indexer WebSocket, SP
outputs as spendable inputs, sp1 send preparation, and per-output-key
signing — installs via those seams (installSpWallet).

src/features/sync.js: nostr identity + encrypted cross-device state sync
plus its Settings card, installed via load/cache-saved/realtime hooks.
profileChip/nostrProfile move here-adjacent (into gifts, the only other
nostr consumer), so core app.js no longer imports nostr at all.

buildTx/sign are now feature-agnostic (input providers + preparers +
signers); _recomputeBalance* use the balance-extra registry, so a core
build with no SP no longer calls a missing spBalance() (that regression
dropped the minimal build into offline mode — caught by the smoke test).

New coverage: tools/sp-test.js drives two browser "devices" through an
SP send, indexer discovery, and an SP-input spend on the ark regtest
chain (needs tools/sp-indexer.js pointed at bitcoind:18543). Minimal
smoke now also asserts no SP/nostr code ships.

Minimal profile: 314KB, down from 412KB (full: 580KB). All suites green:
minimal smoke, sp-test, ark UI, gift lifecycle.

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy
</content>
</entry>
</feed>
