<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinosv3.git/tools/ark/custody-test.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-10-23T14:59:40Z</updated>
<entry>
<title>Deep coins are renewed under every tier, free of the base fee, before the server's depth cap strands them</title>
<updated>2026-10-23T14:59:40Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-23T14:59:40Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=6c3b49a559c90954bf5ae6dbfada73ec5b3409cc'/>
<id>urn:sha1:6c3b49a559c90954bf5ae6dbfada73ec5b3409cc</id>
<content type='text'>
A coin's exit depth grows by one per off-chain hop; the server refuses to
cosign a spend of a coin at max_vtxo_exit_depth (100), so a wallet paying
hourly from one change chain has an unspendable coin within days. Survey of
the ASP's 2,279 unspent coins: median depth 7, 154 coins past 40 holding
7.7k sats, five already stuck at the cap.

The server now waives the refresh base fee when every input is at its
deep_waiver_depth (60; ArkInfo.fees.refresh carries it). The client mirrors
that in refreshFee, never selects a capped coin as an input (the shortfall
message says why), and flattens deep coins whatever the custody tier: the
open app in a round of their own, the worker with a standing order two
blocks out per coin. The default tier's copy names the one exception.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>The third custody tier keeps exits cheap by depth, not by date: "Keep my exit cheap" renews a coin once its history is four hops deep</title>
<updated>2026-10-23T05:06:01Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-23T05:06:01Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=f4997d29a0c97eac61338448854c7ab9724cdc92'/>
<id>urn:sha1:f4997d29a0c97eac61338448854c7ab9724cdc92</id>
<content type='text'>
Why: a unilateral exit publishes one on-chain transaction per off-chain hop, so depth — not age — is what makes an exit expensive, and a renewal a week before expiry flattens exactly the same history a renewal in the last hours does. The week-ahead "early" tier only guarded against the server being unreachable at the very end, which is not the case worth selling. (Adam, 2026-10-23.)

exit = self (last-hours renewal, free bracket + base, the same grid-snapped backstop) plus: any coin whose genesis chain is 4+ hops deep is renewed in the next round the app runs, paying the top bracket + base — but only when that fee is under a tenth of the coin (flattening a 21-sat coin for 300 sats helps nobody; its exit is uneconomic anyway). Deep coins ride along with an expiry renewal when one is due, or get a round of their own. The server-side backstop stays near-expiry only: depth changes only while the wallet transacts, i.e. while the app is open. A stored 'early' reads as 'exit'. Card copy says what the hop count means and what the two prices are for.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Custody is a choice, and coinos holding the coins is the default: nothing renews on its own and no renewal fee is paid unless the user opts in</title>
<updated>2026-10-23T04:06:32Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-23T04:06:32Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=648a5503089d33232855925a24ff60f78cab6df4'/>
<id>urn:sha1:648a5503089d33232855925a24ff60f78cab6df4</id>
<content type='text'>
Why: renewal fees were "money passively draining" to people who thought they had a custodial wallet (Stacker News, 2026-10-22). Measured over Sep–Oct: 84% of what users paid came from the client's own consolidation round at 20 coins (0.2% + 300 on every fresh coin), the rest from 300-sat rounds that each cohort of Lightning-received coins (30-day pool lifetime) triggered on its own schedule. The ASP itself honors a coin past its timelock — rounds and offboards never checked expiry, and captaind's allow_expired_arkoor (now on at ark.coinos.io) lets arkoor spends through too — so expiry only ever meant "the exit is gone", which most people coming from v2 never had.

Settings → Spending gets a Custody card with three tiers:
- coinos holds my coins (default): no renewals, no fees; a coin past its timelock shows as "held by coinos", stays spendable and receivable, and is left out of a unilateral exit (there is nothing on-chain for it to take).
- Keep my own exit: renew in the last hours (free bracket + base), the server backstop registered 36 blocks out and snapped to a 48-block grid so every wallet's orders share one round.
- Renew a week early: 1008 blocks out on a 144-block grid (0.1% + base).
renewPolicy() in ark/manager.js is the one table; the app, the service worker (the mirror carries the tier) and the Manage page all read it. Only shown on servers that honor expired coins (presets carry honorsExpired); elsewhere renewal stays automatic.

Manager: honorsExpired makes a past-timelock coin ordinary money (balance, selection, receive validation via acceptExpired) while a server refusal (expiryRejected) still excludes it; such coins spend LAST so an older build on the receiving side almost never meets one. A once-per-wallet rescan re-reads the mailbox and adopts coins older builds refused as expired, un-forgets expired dust, and clears stale refusals. The refresh gate that retired a renewal of expired inputs is gone (the server accepts them; the Manage page's Renew of exactly those coins was failing silently). Automatic consolidation is gone; the send cap goes 24 → 48 coins; small change (&lt;1,000 sats) folds into a renewal that happens anyway.

Server (cs:~/ark/captaind-main.toml, restarted): allow_expired_arkoor = true; pool vtxo_lifetime 4320 → 8640 so Lightning-received coins live as long as the rest.

Tests: tools/ark/custody-test.js (policies, honoring, ordering, grid snap, validator), tools/ark/custody-ui-test.js (the card, held coins, exit count, switching), nwc-sw-test updated for the tier the worker reads.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
