<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinosv3.git/src/swap.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>Signet network — Second's public ASP is the default Ark provider</title>
<updated>2026-07-05T02:29:08Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-07-05T02:29:08Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=0f96ea2500a311ab77a9fed3b10ab928370a6b0d'/>
<id>urn:sha1:0f96ea2500a311ab77a9fed3b10ab928370a6b0d</id>
<content type='text'>
Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Features phase 2: gift/swap/ark primitives leave the core wallet</title>
<updated>2026-07-04T16:33:52Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-07-04T16:33:52Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=b7c5abd93868b97c25f52610510544764a3e44eb'/>
<id>urn:sha1:b7c5abd93868b97c25f52610510544764a3e44eb</id>
<content type='text'>
wallet.js drops from 2,762 to 2,374 lines and now exposes two generic
seams instead of feature code:
- registerCoinLock()/lockedCoinIds(): features contribute coin ids that
  spending and the balance treat as unavailable (gift reservations).
- loadFeatureState()/saveFeatureState(): namespaced per-wallet persisted
  state.

src/features/gifts-wallet.js takes the whole gift engine — reservation
sets, gift records, coin selection, the SIGHASH_SINGLE/NONE PSBT
builder, the compact codec, claim building, nostr-locked gifts — as an
installGiftWallet(wallet) instance install plus standalone exports.
Swap storage/key helpers (loadSwaps/saveSwaps/swapNode/nextSwapIndex)
install from swap.js; ark state storage installs from the ark feature.
The silent-payment scan worker only embeds when the sp feature ships
(wallet.js already tolerated its absence).

Minimal profile: 412KB, down from 476KB (full: 578KB). All suites pass:
ark UI e2e, ArkManager crash-resume, gift lifecycle, minimal smoke.

SP's engine (scanning, BIP-352 signing) stays in wallet.js — extracting
it safely wants an SP e2e harness on this stack first.

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy
</content>
</entry>
<entry>
<title>Swaps: reuse the generic 'Payment received' screen for LN receives</title>
<updated>2026-06-30T16:29:46Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-30T16:29:46Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=6d649f9bdd0582520e23dff4b5a967f0a125857e'/>
<id>urn:sha1:6d649f9bdd0582520e23dff4b5a967f0a125857e</id>
<content type='text'>
A reverse-swap claim is an on-chain receive, so instead of a separate LN
'Received!' card (which stacked with the generic celebration and needed
suppression), credit the claimed coin to the wallet the instant we broadcast the
claim — new wallet.creditReceive() mirrors the instant-payment watcher path
(adds the utxo + pending row, advances the receive index, emits). The generic
'Payment received!' then fires immediately with the right amount, one screen, one
tap. Dropped the LN success card + the suppression logic.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Swaps: don't reuse a burned swap index (deterministic-preimage collision)</title>
<updated>2026-06-29T22:31:59Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-29T22:31:59Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=052e9c714092d8f4a5a9bb29bb85277630c1926d'/>
<id>urn:sha1:052e9c714092d8f4a5a9bb29bb85277630c1926d</id>
<content type='text'>
A reverse swap's preimage is sha256(swapNode(idx).privateKey) — deterministic per
index. nextSwapIndex was derived only from *saved* swaps, so an attempt that
created the swap at Boltz but threw before saving (e.g. the lockup-address-mismatch
bug) never advanced the index; the next try reused the same preimage and Boltz
rejected it ('a swap with this preimage hash exists already').

Make nextSwapIndex a persisted, monotonic counter (reserves the index on every
call, seeded from saved swaps), and have startReverse retry with the next index
when Boltz reports the preimage already exists — which also recovers wallets
already stuck on a burned index.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Swaps: fix lockup address mismatch on mutinynet (missing NETS entry)</title>
<updated>2026-06-29T22:23:45Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-29T22:23:45Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=87fcb80de91f539891cc2d635b0013d07826dd60'/>
<id>urn:sha1:87fcb80de91f539891cc2d635b0013d07826dd60</id>
<content type='text'>
swapP2TR resolves the network via netFor(name) → NETS[name] || btc.NETWORK, but
NETS had no mutinynet entry, so it fell back to MAINNET — Hal computed a bc1p…
swap address while Boltz (bitcoinSignet) returns tb1p…, tripping 'lockup address
mismatch — not paying'. Add mutinynet: btc.TEST_NETWORK (signet/testnet params,
matching the wallet's own netCfg).

Verified: swapP2TR now derives tb1p… for mutinynet (was bc1p…).

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Send progressive disclosure, swap context in tx detail, LN amount placeholder</title>
<updated>2026-06-29T15:49:16Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-29T15:49:16Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=81df06c53eef1ef2931ed48955aded1e6ce0fa4f'/>
<id>urn:sha1:81df06c53eef1ef2931ed48955aded1e6ce0fa4f</id>
<content type='text'>
- Send: show only the destination input ('Address or Lightning invoice') until a
  valid on-chain/SP address is entered, then reveal amount + fee rate + coin
  selection + Review. A pasted bolt11 still auto-advances to its own confirmation,
  so those controls never appear for it. New wallet.isOnchainAddress() validator +
  a destReady() gate; recipient onInput/paste re-render only when validity flips.
- History: stop listing swaps at the top. Instead, a send/receive that was part of
  a swap shows its Lightning context (invoice, swap fee, status, refund) on the tx
  detail screen via swaps.findByTxid(). Removed the dead swapListView.
- Lightning receive: drop the '50000' amount placeholder — a bare number looked
  like a value was already entered; the min/max hint below covers it.

Verified in-browser: controls hidden until a valid address, then revealed; LN
receive placeholder empty; no errors.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Send: pay a pasted Lightning invoice via an auto submarine swap (itemized cost)</title>
<updated>2026-06-29T14:09:15Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-29T14:09:15Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=0baf4cf55daa1ed33c2b276f40a6fd8aa922ee34'/>
<id>urn:sha1:0baf4cf55daa1ed33c2b276f40a6fd8aa922ee34</id>
<content type='text'>
Paste a bolt11 into the normal Send recipient field and Review now detects it and
routes through a Boltz submarine swap — no 'swap' concept surfaced. Split
startSubmarine into quoteSubmarine (POST + build-but-don't-broadcast the funding
tx) and fundQuotedSubmarine, so the review shows an itemized cost before
committing: Amount + Boltz fee + on-chain network fee = Total. Pay funds the
lockup; Boltz pays the invoice once it confirms.

Verified on regtest: a 60,000-sat invoice quotes to Amount 60,000 / Boltz 362 /
network 442 / Total 60,804, with a Pay button.

Lightning-address (LNURL) input + removing the standalone Swap tab are the
remaining pieces.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Swaps: mark a submarine swap done at invoice.paid, not transaction.claimed</title>
<updated>2026-06-29T05:23:43Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-29T05:23:43Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=677eea94b433685c4aa5f18c2ad9f18218cab30d'/>
<id>urn:sha1:677eea94b433685c4aa5f18c2ad9f18218cab30d</id>
<content type='text'>
A submarine swap's success is invoice.paid — Boltz has paid the Lightning invoice,
which is the whole point and irreversible. Boltz then claims its on-chain lockup
(transaction.claim.pending → claimed), often deferred to a later batch, so the old
done-set ({transaction.claimed, invoice.settled}) left Hal stuck showing
'paying / waiting for Boltz to pay' indefinitely after the payment had already
gone through. Add invoice.paid + transaction.claim.pending to the done set.

Verified: a live swap stuck at transaction.claim.pending flips to 'success ·
Invoice paid ✓' after the fix.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Swaps: bound reverse amount by the provider's published limits, not a hardcoded 50k</title>
<updated>2026-06-29T02:14:30Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-29T02:14:30Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=93631253030839509551d7e1e6a995ed60e31b96'/>
<id>urn:sha1:93631253030839509551d7e1e6a995ed60e31b96</id>
<content type='text'>
doReverse rejected anything under a hardcoded 50,000 sats — which happens to be
the regtest provider's minimum, but mainnet boltz's real minimum is 25,000 (and
providers/networks differ), so valid 25k-50k mainnet swaps were refused and the
max was unchecked client-side.

Add SwapManager.reverseLimits() (GET /v2/swap/reverse, cached per API) and use it
in the swap UI: show the real min/max range under the amount field and validate
against it. Reset the cached limits when the network or provider changes.

Verified: regtest min/max 50,000/40,294,967; mainnet 25,000/25,000,000.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
