aboutsummaryrefslogtreecommitdiffstats
path: root/src/nwc-bg.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-22 21:06:32 -0700
committerAdam Soltys <[email protected]>2026-10-22 21:06:32 -0700
commit648a5503089d33232855925a24ff60f78cab6df4 (patch)
tree1280073d797653d3ed86f0bb7b6c26b5d2271804 /src/nwc-bg.js
parent8257a98e8d1891d0795f6b8b0623678245cc8e33 (diff)
downloadcoinosv3-648a5503089d33232855925a24ff60f78cab6df4.tar.gz
coinosv3-648a5503089d33232855925a24ff60f78cab6df4.zip
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
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 (<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 <[email protected]>
Diffstat (limited to 'src/nwc-bg.js')
-rw-r--r--src/nwc-bg.js5
1 files changed, 4 insertions, 1 deletions
diff --git a/src/nwc-bg.js b/src/nwc-bg.js
index 3bdef08..3349126 100644
--- a/src/nwc-bg.js
+++ b/src/nwc-bg.js
@@ -84,7 +84,7 @@ export const BG_KEY_LOOKAHEAD = 25;
// module). Chain 3 covers coins + change, chain 4/0 the mailbox, chain 5 a
// window of receive preimages — everything the worker needs to pay AND to
// mint invoices while the app is closed.
-export function buildBg({ ark, mgrState, keyFor, connections, offer, autowithdraw }) {
+export function buildBg({ ark, mgrState, keyFor, connections, offer, autowithdraw, custody }) {
const keys = {};
const idx = new Set((mgrState.vtxos || []).filter((v) => v.state !== 'spent').map((v) => v.keyIndex));
for (let i = 0; i < BG_KEY_LOOKAHEAD; i++) idx.add((mgrState.nextKeyIndex || 1) + i);
@@ -112,6 +112,9 @@ export function buildBg({ ark, mgrState, keyFor, connections, offer, autowithdra
// { on, threshold, keep, target: { kind, address, url?, spkHex? } } — the
// destination is resolved by the app, so the worker never needs a relay
autowithdraw: autowithdraw || null,
+ // the custody tier ('custodial' | 'self' | 'early'): whether the worker
+ // registers renewal orders for bare coins, and how far ahead
+ custody: custody || null,
// The budget's spent counter rides along (stamped with its window) so the
// worker counts what the app spent; a revoked one rides along flagged, to
// be refused rather than ignored.