aboutsummaryrefslogtreecommitdiffstats
path: root/src/ark
Commit message (Collapse)AuthorAgeFilesLines
* Renewal repair: a coin already held as spent is not asked about (its bytes ↵Adam Soltys2026-09-261-2/+8
| | | | | | | may be pruned). Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* Renewal: a submitted round the server has forgotten is repaired, not asked ↵Adam Soltys2026-09-261-1/+34
| | | | | | | | | | | | | | | | | | | | | | about forever. Adam's wallet carried a refresh action from August 31 in step 'submitted' whose unlock hash the server never ran: the round went through under a later submission (another hash), the inputs were spent by it and the 5,233-sat output reached the wallet through the mailbox — but the original action kept asking RoundParticipationStatus on every sync for four weeks, and since this week's coins page shows renewals in flight, it showed up as "in progress" with the raw gRPC error underneath. On "not found" the manager now checks the inputs: all spent means the round ran under another submission and the record is retired (failed, superseded — no history row, no card); none spent means the server dropped it before a round ran, so it is submitted again; a mix goes through the existing input repair. The coins page never prints a server error string; a failed check reads as "the wallet keeps trying". Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
* Coins page: a renewal in flight keeps the whole page; a renewal you asked ↵Adam Soltys2026-09-241-3/+5
| | | | | | | | | | | | | | | | for is in history. Tapping Renew marked the coins pending and the page, finding nothing spendable, collapsed to a single "Pending: N sats" line — no table, no exit explainer. Coins in a round now stay in the table (greyed, tagged renewing), with a card saying where the round is (queued, in a round waiting for a confirmation, finishing). The page no longer closes on Renew. History: a manual renewal shows as "Renewing · in progress" while the round runs, and as "Renewed" after, even when it was free (automatic free renewals stay out of history, as before). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
* Ark: the receive rescan starts at index zero — another device of the same ↵Adam Soltys2026-09-221-2/+4
| | | | | | | wallet may have minted lower. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Tmfg8k5f3TbNJS34qiqHAp
* Ark: rediscover Lightning receives the state has no record of.Adam Soltys2026-09-221-0/+40
| | | | | | | | | | | | The background answerer minted an invoice for [email protected] into the NWC pouch; the app's next mirror write replaced the pouch with its own state, and the receive record was gone before anything could claim it. Preimages come from a counter, so on each connect the manager now asks the server about the next few indices and rebuilds any receive it has no record of, then claims it like any other. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Tmfg8k5f3TbNJS34qiqHAp
* Ark: an invoice the server hands back for a hash it already holds is not ours.Adam Soltys2026-09-211-6/+20
| | | | | | | | | | | | | StartLightningReceive on a payment hash with an unpaid invoice returns that invoice — an earlier sale's amount and memo under a fresh QR (a 0.60 CAD bill showed an 8,467-sat invoice on 2026-09-21). The returned invoice must now carry the amount asked for and a comfortable expiry, or the index is treated as spoken for and skipped. The counter is also floored above every index an ln-recv action of ours already names, so a restored or merged state can't rewind it. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Tmfg8k5f3TbNJS34qiqHAp
* Ark: an issued-but-unpaid invoice on a preimage index is skipped too.Adam Soltys2026-09-211-2/+5
| | | | | | | | | The ASP answers a settled hash with "already been paid" and a merely issued one with "UNIQUE constraint failed: invoices.payment_hash"; both mean the index is taken. Headroom raised to 200 skips. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Tmfg8k5f3TbNJS34qiqHAp
* Ark: a Lightning invoice steps past a preimage index another device already ↵Adam Soltys2026-09-211-7/+22
| | | | | | | | | | | | | | spent. The ASP answered "invoice has already been paid" for a point-of-sale invoice: the receive counter on the phone was behind the one the laptop had used. createLnInvoice now retries on that answer with the next index (the counter only moves up; the key index is allocated once an invoice exists), so the Receive tab, NWC make_invoice and the point of sale all recover instead of failing. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Tmfg8k5f3TbNJS34qiqHAp
* Zaps: tap again while one flies, hold the bolt to change the amount.Adam Soltys2026-09-191-1/+5
| | | | | | | | | | | | | | | | A pending zap no longer refuses the next tap: the optimistic tally keeps one entry per zap and matches each report and receipt to the oldest it can describe, so two default-amount zaps stack, settle and hand over to their receipts one at a time. Instant post zaps run off the send screen's zap slot, so a second tap can't evict the one still resolving, and send action ids are unique within a millisecond. Holding ⚡ (500ms) opens the amount screen in an edit mode — Save alone, or Save and zap this note — from the action bar and the chat chips alike; the press's timer lives on the element so a background repaint mid-press can't turn a lifted finger into a hold. Co-Authored-By: Claude Fable 5.1 <[email protected]>
* A zap answers as fast as the route allows.Adam Soltys2026-09-141-0/+15
| | | | | | | | | | | | | | | The wallet used to add two to four seconds of its own around a Lightning send: a random pause plus a relay look-back before paying, and a two-second poll for the result after. The notifier's claim now decides who pays in one round trip, with the relay look-back kept only for replays and for when the notifier gives no verdict; a phone answering from behind another app waits a second so a desktop tab wins the claim instead of paying at a throttled crawl. After the send, one held CheckLightningPayment call brings the preimage the moment CLN has it — for wallet-connect pays, the background worker, and the app's own Send. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
* A zap answered by two tabs no longer reports as failed.Adam Soltys2026-09-141-1/+22
| | | | | | | | | | | | | | | Every open tab of the same wallet sees every wallet-connect request. Two hidden tabs on one laptop both tried to pay the same zap: the ASP refused the second with "payment already in progress", and that error reached Amethyst half a second before the first tab's preimage. A Web Lock per request now lets one tab (or the worker) answer while the rest stay quiet, and a device that does meet "already in progress" waits for the sibling payment's outcome — relaying its preimage only if the payer never speaks — instead of shouting a failure over sats that went through. The losing side also stops writing a failed row into history for it. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
* The renewal happens whether or not anyone opens the app.Adam Soltys2026-09-133-2/+180
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Every coin expires, and until now the only thing standing between a balance and its expiry was maybeAutoRefresh: it renews when the wallet is opened inside the last 96 blocks, roughly sixteen hours. Bark's own developers say the same premise fails on mobile — people don't reliably open a wallet at the right hour, which is why every mobile Ark client they know of has moved to delegated refreshes and why they are building server-driven renewal. captaind already has it, and ark.coinos.io has been carrying the column unused: SubmitRoundParticipation takes a scheduled_height, and the server holds that participation out of every round until the height arrives, then runs it with nobody online — the round tree branch is signed by the server, which is the whole point of a delegated refresh. The fee is priced at the height it will run at, so a standing order lands in the free bracket rather than today's. So each spendable coin now gets one registered 36 blocks before its expiry, six hourly rounds of slack. It is deliberately not an action: the coin stays spendable, the wallet never reads as busy, and spending it simply kills the order — the server drops a participation whose input is gone, and _driveScheduled drops ours. The claim comes later, whenever the wallet next opens: the mailbox breadcrumb we already post wakes it, and the leaf cosign and forfeit are the ordinary refresh path from there. Nothing about the input's expiry blocks that claim, which is what makes this worth having. A wallet that goes quiet for a month comes back to a coin already renewed and anchored in a fresh tree, instead of one the ASP has swept and would have to honor out of goodwill. One cycle per visit is all it can buy — the replacement's id doesn't exist yet, so its own backstop waits for the next time the app is opened. tools/ark/scheduled-refresh-test.js covers the five behaviours on regtest: the server holds the order until its height, runs it while the wallet is closed, the wallet claims on return, a spend voids the order, and a claim still works after the input has EXPIRED (the last one mines a coin's whole lifetime, so it also re-refreshes the funding wallet first — an expired vtxo can be renewed but not sent). Riding along: the expired-coin notice that was already sitting in the working tree, and manager-test's tampered-vtxo check, which had been reading base64 vtxo bytes as hex since they moved to IndexedDB. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019fMdLypdg5JaU7bdeUFF8v
* Report rejected Ark sends and exclude server-rejected expired inputsAdam Soltys2026-09-111-2/+24
|
* Ark state moves to IndexedDB: raw bytes on disk, memory-fronted, write-behind.Adam Soltys2026-09-101-0/+213
| | | | | | | | | | | | | | | | | | | | | | | | | localStorage held a deep wallet's ark state as ~700KB of JSON (UTF-16 on disk), parsed on every boot and stringified on every save, and the wallet cache blob carried a second ~600KB copy. src/ark/store.js keeps the state in IndexedDB (db coinos-ark) with each coin's signed bytes as a Uint8Array, fronted by an in-memory map so loadArkState/saveArkState stay synchronous: reads return clones, writes coalesce (200ms) and flush immediately when the page hides. Existing localStorage copies migrate on first open (newest _rev wins) and are removed only after the IndexedDB write completed; no IndexedDB means localStorage exactly as before. Records keep their btc-wallet-cache:<id>:ark… keys, and a wallet wipe clears both stores via the new per-feature wipeCache hook. The ark cache extension is now syncOnly: it still rides the sync snapshot (and the Blossom blob) but the local wallet cache no longer embeds it — measured on a live wallet the cache blob went from 655KB to 10KB. Verified in the live tab: all wallets migrated, bytes raw on disk, balance and coin set unchanged, localStorage ark keys gone. tools/ark-store-test.js covers migration by _rev, clone-on-read, write-behind, wipe and fallback. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Nw1Dijik1tLVKRM1WkU7xE
* Ark: persist vtxo bytes as base64 (a third smaller than hex); hex still ↵Adam Soltys2026-09-102-11/+23
| | | | | | | | | | | | | | | | | | read, hex still on the wire. The live coins' signed bytes are most of a deep wallet's state — 560KB of hex, which localStorage then holds as UTF-16. vtxoBytesToStr/FromStr in proto.js persist base64 and read either form, _addVtxo writes base64, and pruneArkState converts hex to base64 on every save, so an existing state migrates itself the first time it saves. Measured on a live deep wallet: 904KB → 665KB (-26%), all 41 coins decoding identically. The sync wire (slimArkForSync) keeps sending hex for now: a device still on an older build would otherwise fail to decode every coin merged from a newer one. Flip it once pre-2026-09-10 builds are gone. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Nw1Dijik1tLVKRM1WkU7xE
* Ark: local state stops growing forever — spent coins shed bytes, done ↵Adam Soltys2026-09-101-0/+52
| | | | | | | | | | | | | | | | | | | actions shed hex. pruneArkState runs on every manager save: a spent coin loses its signed bytes once its spend is 30 days old and no in-flight action names it (a spent coin can never take the exit those bytes exist for), and an action done more than 7 days ago loses the output-vtxo hex it minted (the change / HTLC coins live in `vtxos` in their own right). Ids, amounts, inputIds and preimages stay for history rows and spend dedupe; failed ln-pays keep their HTLC bytes for the refund. Selective and idempotent; unit-tested. Measured on a live deep wallet the immediate saving is small (every recent spend is inside the window) — what remains is the live coins' own chains, which only a renewal shrinks. The change is a growth cap, not a diet. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Nw1Dijik1tLVKRM1WkU7xE
* Boot: price the Ark exit advisory by size only — no more 5s of secp256k1 ↵Adam Soltys2026-09-092-11/+41
| | | | | | | | | | | | | | | | | | | | | | | | | | | on load. The exit-depth advisory rebuilt every spendable coin's exit chain to learn its fee, and each hop's input taproot key ran a musig key aggregation (several secp256k1 multiplies on uncached points). With 27 coins of depth up to 86 (1685 hops) that was ~5.3s of solid main-thread work on every boot, laptop and phone alike — requestIdleCallback only delayed it, and the Android wrapper hits a full boot whenever the OS evicts the app, so "reopening the app" froze for seconds. A fee only needs vsizes, and a taproot output script is 34 bytes whatever key it holds: vtxoTransactions/transitionInputTxout gain a sizeOnly mode with stand-in keys, exit.js exposes exitTxVsizes (same chain checks, no EC, nothing broadcastable), and the advisory + coins page use it. Verified against the real signed chain on the live coin set: identical vsizes on all 27 coins, 9s → 26ms. Also: the sync-slot replay at boot merged the ark snapshot and rewrote ~1MB of state (plus re-adopting it into the manager) twice per boot even when the union brought nothing. Compare the serialized union to what's stored and skip the write/adopt when unchanged. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Nw1Dijik1tLVKRM1WkU7xE
* The carousel stutter is the ark history memo re-cooling on a timer: ↵Adam Soltys2026-09-041-0/+4
| | | | | | | rebuilding it costs ~60ms (sort + dedupe + gift resolve + secp256k1), and its 1.5s TTL meant an unhurried flick always hit a cold rebuild, stuttering the slide's tail — 'warms up if I keep going fast, cold if I pause, refresh reproduces' is the classic TTL-expiry signature. Now keyed on a state revision (_save bumps state._rev) so it's reused until the data actually changes and never re-cools between flicks. Diagnostics still on to confirm Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* Ark reconcile signs in time-sliced batches: it built a status query (schnorr ↵Adam Soltys2026-09-031-3/+12
| | | | | | | sign + vtxo decode, real secp256k1) for every spendable coin in one synchronous map — a ~100ms+ EC burst on boot, the local work blocking a carousel drag right after a refresh (community/DM wraps arrive over the network later; this fires immediately on init). The requests now kick off in ≤6ms slices that yield; network calls still overlap Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* Ark key derivation gets memoized — the boot CPU hog. A profile of the ↵Adam Soltys2026-09-031-2/+13
| | | | | | | first 9s after refresh showed ~800ms of secp256k1 (q0/mul/TJ) dominating the main thread; the source was _key(index) recomputing a BIP32 derive + a getPublicKey point-mul on every call, and reconcile() calls it for every spendable coin (56 here), with decode/send/exit/fee-math hitting it again. A key for an index is constant per wallet, so it's cached by index now (mailbox key too) — dozens of EC muls per boot collapse to one each Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* An offboard's price stops hiding in one number: the miners' cut and the ↵Adam Soltys2026-09-022-6/+20
| | | | | | | ASP's service charge (base + lifetime ppm) record separately and the history detail shows them on their own lines — settling the mystery of a ~350-sat chain fee inside a ~650-sat total. The community row's member count anchors to last session's settled figure instead of ticking upward through the guestbook replay, and a profile's Pay button reached from search actually reaches Send — the lingering search screen was winning the router Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* The 200ms of secp256k1 hiding in every repaint: the depth advisory priced a ↵Adam Soltys2026-09-021-1/+10
| | | | | | | full uncooperative exit — every hop of every coin, an EC tweak per hop — on each wallet render; the estimate now memoizes on coin set + feerate, and decoded vtxos cache by id (bytes are immutable). Profiled on a real phone: point add/double/wNAF was 1.3s of a 4.3s six-tab-switch trace Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* The tester with 233 sats gets to spend them: invoice coverage judges the SUM ↵Adam Soltys2026-09-021-1/+5
| | | | | | | of spendable coins (the single-coin gate silently declined anything over the largest coin, though multi-input pays gather 24), an uncoverable invoice now owns its paste — board the shortfall from Savings, or say what's short — instead of dropping the bolt11 into the address field like a dead click, and chain calls gain a last-resort relay through the registrar (/esplora passthrough, tip cached) for the network that blackholes every public esplora Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* The chain adapter learns a second road: mainnet esplora calls fail over ↵Adam Soltys2026-08-311-6/+30
| | | | | | | between the configured host, blockstream.info, and mempool.space (remembering whichever answered last, rotating on network failures, 429s and 5xx) — one tester's network simply cannot reach mempool.space and every chain-touching feature was dying for it, stranded mailbox receives included. And a failed quote no longer wedges the Lightning pay form at an eternal spinner: it prices at the outage fallback and lets Pay proceed, where the payment path quotes again for real Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* The mailbox cursor stops leaping wounded messages: it advances only after a ↵Adam Soltys2026-08-311-2/+31
| | | | | | | message's work is DONE (a final rejection counts, a transient failure does not), holds at the lowest failed checkpoint so a healthy message behind a sick one can't skip it, and the poll defers the rest of a batch on failure — one esplora hiccup mid-validateVtxo used to skip an arkoor receive forever (monogram's 204 sat, twice) Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* One spend, one row: send and lightning-pay movements now record which coins ↵Adam Soltys2026-08-301-0/+4
| | | | | | | they consumed, spend actions sync as slivers (id + net amount + inputIds), and the reconcile exclusion generalizes from renewals to ANY accounted-for coin — a laptop send used to paint the phone's history twice, once as the real Sent and once as a red 'Spent on another device' at full input magnitude. The red row survives only for spends nothing anywhere explains Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
* Staging gets the mainnet fee model end to end: /lnquote?network=mutinynet ↵Adam Soltys2026-08-292-2/+5
| | | | | | | quotes from the staging ASP's own CLN (helpers take their backend), the client asks it by default on mutinynet, and the quote URL concat survives a query string Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019WboeoetMF3MTmgT7Ew8yi
* A round's output can no longer strand: every refresh asks the server to drop ↵Adam Soltys2026-08-293-15/+56
| | | | | | | its unlock hash in the mailbox, and any device that finds such a breadcrumb with no matching action adopts the claim itself (input ids now read from the status response — the rescue needs no operator). History learns to tell the whole truth and nothing more: coins the server saw spent elsewhere get a folded row (skipping coins a known renewal consumed), renewals fold across devices on their unlock hash and appear only when they cost something, and a rescue records the fee the round actually took. The account page keeps its header icons when reached from an open wallet; the messages page folds the control/guestbook replay once per burst instead of painting every intermediate state — no more blinking messages from superseded ban lists or member counts crawling to their total, which now paints from last session's settled figure Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019WboeoetMF3MTmgT7Ew8yi
* The wallet learns to read BOLT 12 for itself: offers and invoices decode ↵Adam Soltys2026-08-292-5/+61
| | | | | | | client-side, and every invoice the ASP fetches over onion messages is verified here — payment hash read from the raw TLVs, offer terms mirrored byte-for-byte, the issuer's schnorr signature checked against LDK's exact merkle construction (proven against a live CLN-signed fixture) — so no server can hand back an invoice whose preimage it already holds. Paste an lno1 or resolve a name whose record carries one and the existing HTLC pay flow carries it (captaind's Invoice enum takes lni strings natively); a name's owner may now publish their OWN node's offer in their DNS record, retiring the registrar's stand-in — senders fetch invoices straight from their node and no coinos service ever touches the money. The /lnquote fallback prices blinded paths by entry node + advertised payinfo Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019WboeoetMF3MTmgT7Ew8yi
* The mailbox checkpoint was a timestamp all along: receives are dated by when ↵Adam Soltys2026-08-291-7/+11
| | | | | | | the server actually delivered them (verified to the millisecond), so an import shows hours-old payments as hours old — no celebration for news that isn't; a wallet whose Savings holds nothing opens onto the Spending that holds everything; and same-payment duplicates fold at display time on every device, however the state arrived Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* A payment that lands now is dated now: expiry-based age estimation applies ↵Adam Soltys2026-08-281-2/+8
| | | | | | | only to the first catch-up — it mis-dated a live receive by a month after the vtxo lifetime doubled, since the math assumes every coin began with the full current lifetime Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* One payment, one line: the settle path is idempotent (the mailbox push and ↵Adam Soltys2026-08-281-2/+8
| | | | | | | the status poll raced past each other's step checks — a toque got two history entries), movements carry their payment hash, and the sync merge folds same-preimage lightning records into the earliest telling Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* Renewals earn a line in the ledger: "Renewed ↻" rows in history showing ↵Adam Soltys2026-08-281-1/+1
| | | | | | | what the round cost (or "free"), with the in-and-out detail behind them — and the toast is simply "Renewal requested" Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* Renewal stops billing for tidiness: six refund coins after a failed payment ↵Adam Soltys2026-08-271-1/+1
| | | | | | | no longer trigger a top-bracket consolidation (multi-input sends made fragmentation cheap to live with — the trigger moves to the 24-input ceiling), and the history line for a refresh now names the fee it paid Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* Dead coins stop travelling: an expired vtxo leaves the balance and the coin ↵Adam Soltys2026-08-241-8/+35
| | | | | | | picker (sub-dust IOUs excepted), a shortfall names the dead weight it can't spend, and the names float renews any coin a day ahead of the reaper — a 100k forward left home on an expired coin and the recipient rightly refused it at the door Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
* The claim sorts its coins the way the server counts them: HTLC vtxos line up ↵Adam Soltys2026-08-181-4/+13
| | | | | | | by outpoint in internal byte order — the display-hex sort read the txid backwards and nine-coin receives bounced off the positional check Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_012u4oe1KPx9faCsDBLfZYqi
* A many-coined receive keeps the server's own order: the claim step stops ↵Adam Soltys2026-08-181-3/+6
| | | | | | | alphabetizing HTLC vtxos the ASP matches positionally, and the expiry guard reads the true earliest deadline instead of whoever sorted first Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_012u4oe1KPx9faCsDBLfZYqi
* A refresh learns to let go: a participation rejected for unusable inputs ↵Adam Soltys2026-08-181-4/+96
| | | | | | | asks the server which coins died and rebuilds around the survivors instead of resubmitting the doomed set every sync forever; the balance reconciles on a ten-minute cadence so ghosts can't pose as sats; and arkRescue() reclaims a round output whose action a device lost Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_012u4oe1KPx9faCsDBLfZYqi
* Money stops arriving without a story: the mailbox checkpoint stays local ↵Adam Soltys2026-08-181-1/+10
| | | | | | | reading progress (a merged one skipped every unprocessed message), vtxos that beat their message here get a backfilled receive, and the commit stamp takes its own footer line Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* The HTLC expiry stops trusting a cached clock: tip fetches bypass the CDN, ↵Adam Soltys2026-08-181-1/+15
| | | | | | | and when the server names its own tip the payment adopts it and retries instead of dying Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* The board drive gets a second witness: the wallet's own chain view vouches ↵Adam Soltys2026-08-181-4/+21
| | | | | | | for confirmations when the ark esplora is rate-limited or walled off, and chain errors finally surface instead of masquerading as unconfirmed Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* A confirmed board stops waiting on a flaky tip: confirmation itself counts ↵Adam Soltys2026-08-181-4/+9
| | | | | | | for one, and a failed mailbox read no longer starves the drives that only need the chain Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
* Zaps too gather what they need: several coins each lock an HTLC under one ↵Adam Soltys2026-08-141-50/+88
| | | | | | | payment hash and the server sums them — the fragmented-balance error is gone from Lightning as well Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* A send covers itself from as many coins as it needs: one atomic package ↵Adam Soltys2026-08-142-39/+102
| | | | | | | cosign spends them all, the recipient receives several vtxos, and "consolidate with refresh() first" retires from the vocabulary Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* A mailbox backlog claimed at sign-in reads as the old payments they are, not ↵Adam Soltys2026-08-141-1/+27
| | | | | | | a wall of seconds-ago windfalls; a gift send knows itself in history — status shown, link recopyable, revoke at hand Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* Pay what the route costs: sends quote the network fee up front (zero between ↵Adam Soltys2026-08-142-6/+37
| | | | | | | coinos wallets), lock exactly that, and a refunded miss retries with one more sat Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
* A waiting board says what it waits for: the Moving line counts ↵Adam Soltys2026-08-091-6/+12
| | | | | | | confirmations, a snag shows itself, and one stuck action no longer starves the queue behind it Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01DbA53PJA8fgSvMcEzfuw22
* Boards show the server their funding tx, as mainnet now insistsAdam Soltys2026-08-052-1/+8
| | | | | Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01M5kFodm9azZJVTQxnkDdKe
* hal speaks pver 5: preimages prove their size, and fees round up as oneAdam Soltys2026-08-057-54/+84
| | | | | | | | | | 0.6.0 servers require the new hashlock clauses (OP_SIZE 32 guard) for Lightning and rounds. New policies/transitions carry the v1 scripts and type bytes; _v0 variants keep old vtxos reconstructing and exiting. The retired third-party HTLC policy is dropped (upstream took 0x08). Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01M5kFodm9azZJVTQxnkDdKe
* A wallet too small to renew warns before its coins expire; dust never diesAdam Soltys2026-08-041-1/+7
| | | | | | | | | | | | | | | | | | The warning: when the Spending balance minus round fee is under the 330-sat round minimum AND a vtxo enters the renewal window, auto-refresh can't save the coins — a wallet-screen notice (and a daily local notification, when permitted) says to receive any amount or spend before the deadline. New walletNotices feature hook renders under the balance card. The amnesty: sub-dust vtxos have no unilateral exit to protect, and regtest proves captaind keeps cosigning their spends past expiryHeight — even after the watchman claims the backing on-chain (tools/ark/expired-dust-test.js). Only hal's own receive validation stood in the way; it now exempts sub-330 vtxos from the expiry check. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015QwAtBSpKvECqp1rHLHjJk