<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinosv3.git/src/ark/manager.js, branch fdroid-v1.8</title>
<subtitle>coinos v3 — bitcoin, Ark, Lightning and nostr wallet (web + Android)</subtitle>
<id>https://git.coinos.io/coinosv3.git/atom?h=fdroid-v1.8</id>
<link rel='self' href='https://git.coinos.io/coinosv3.git/atom?h=fdroid-v1.8'/>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/'/>
<updated>2026-09-14T16:16:23Z</updated>
<entry>
<title>A zap answers as fast as the route allows.</title>
<updated>2026-09-14T16:16:23Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-14T16:16:23Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=dd1489303f59ccef6b322142f94e08d750c06e79'/>
<id>urn:sha1:dd1489303f59ccef6b322142f94e08d750c06e79</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
</content>
</entry>
<entry>
<title>A zap answered by two tabs no longer reports as failed.</title>
<updated>2026-09-14T15:53:35Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-14T15:53:35Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=6394de43d4e596aaeddd2b262bb7a595679d8fbc'/>
<id>urn:sha1:6394de43d4e596aaeddd2b262bb7a595679d8fbc</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
</content>
</entry>
<entry>
<title>The renewal happens whether or not anyone opens the app.</title>
<updated>2026-09-14T06:52:39Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-14T06:52:39Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=4131e5ee24c4891caffdd8f4ca903ee973d104f7'/>
<id>urn:sha1:4131e5ee24c4891caffdd8f4ca903ee973d104f7</id>
<content type='text'>
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) &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_019fMdLypdg5JaU7bdeUFF8v
</content>
</entry>
<entry>
<title>Report rejected Ark sends and exclude server-rejected expired inputs</title>
<updated>2026-09-11T15:51:54Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-11T15:51:54Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=207cb4451a07417baf63b2586e6759cf8b471482'/>
<id>urn:sha1:207cb4451a07417baf63b2586e6759cf8b471482</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Ark: persist vtxo bytes as base64 (a third smaller than hex); hex still read, hex still on the wire.</title>
<updated>2026-09-10T14:09:45Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-10T14:09:45Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=6113cf567fa0830233d5c3d524361f24a3f78caa'/>
<id>urn:sha1:6113cf567fa0830233d5c3d524361f24a3f78caa</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Nw1Dijik1tLVKRM1WkU7xE
</content>
</entry>
<entry>
<title>Ark: local state stops growing forever — spent coins shed bytes, done actions shed hex.</title>
<updated>2026-09-10T13:59:13Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-10T13:59:13Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=435e9886620deff3d66286e3b85e97ed8f5fae53'/>
<id>urn:sha1:435e9886620deff3d66286e3b85e97ed8f5fae53</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Nw1Dijik1tLVKRM1WkU7xE
</content>
</entry>
<entry>
<title>The carousel stutter is the ark history memo re-cooling on a timer: 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</title>
<updated>2026-09-04T14:19:12Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-04T14:19:12Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=0a34c1d0981c65ebba0551904ad598704036d725'/>
<id>urn:sha1:0a34c1d0981c65ebba0551904ad598704036d725</id>
<content type='text'>
Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
</content>
</entry>
<entry>
<title>Ark reconcile signs in time-sliced batches: it built a status query (schnorr 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</title>
<updated>2026-09-04T05:38:21Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-04T05:38:21Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=94e837c04afd8c5c1257275cceccce85199151ec'/>
<id>urn:sha1:94e837c04afd8c5c1257275cceccce85199151ec</id>
<content type='text'>
Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
</content>
</entry>
<entry>
<title>Ark key derivation gets memoized — the boot CPU hog. A profile of the 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</title>
<updated>2026-09-04T05:01:21Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-04T05:01:21Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=45fe7005b578d36ad462a9adc77f261808c48d20'/>
<id>urn:sha1:45fe7005b578d36ad462a9adc77f261808c48d20</id>
<content type='text'>
Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
</content>
</entry>
<entry>
<title>An offboard's price stops hiding in one number: the miners' cut and the 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</title>
<updated>2026-09-03T05:25:11Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-03T05:25:11Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=0064b59c3f4543fa709f2b6166174a08d6689506'/>
<id>urn:sha1:0064b59c3f4543fa709f2b6166174a08d6689506</id>
<content type='text'>
Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
</content>
</entry>
</feed>
