From 0677a8b3357ffb12537081df919a7251bf63e3d4 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 29 Oct 2026 09:52:21 -0700 Subject: A closed wallet whose payment fails now says so: the error (INSUFFICIENT_BALANCE or PAYMENT_FAILED, with the reason) is left with the notifier at once and goes out unless someone answers within 5 s. Waiting the grace period out in the worker ran past iOS's ten seconds after a failed send, so the other app heard nothing (PullCap, 2026-10-29). A Lightning send no longer picks a coin at the server's depth cap, so such a coin fails here with a reason instead of at the ASP; a capped coin under 330 sats (a round won't take it) says to receive more instead of "waiting to be renewed", and the worker stops booking renewals the server would refuse as dust Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMan6TUeqTyZB8eXLGymUY --- tools/nwc-sw-test.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'tools') diff --git a/tools/nwc-sw-test.js b/tools/nwc-sw-test.js index 376a12e..7e85ee1 100644 --- a/tools/nwc-sw-test.js +++ b/tools/nwc-sw-test.js @@ -317,6 +317,29 @@ console.log('\n[sends begun on an earlier wake]'); } } +console.log('\n[a send that fails]'); +// The ASP refused the cosign (a coin at the depth cap, PullCap 2026-10-29): +// the error is left with the notifier at once, not waited out in a worker +// iOS stops before it speaks. +{ + const { ArkManager } = await import('../src/ark/manager.js'); + const names = ['init', 'payLnInvoice']; + const orig = Object.fromEntries(names.map((k) => [k, ArkManager.prototype[k]])); + ArkManager.prototype.init = async function () { this.state = this.storage.load(); return this; }; + try { + for (const [why, code] of [ + ['insufficient ark balance (259 sat have a history too long to spend and are too few to renew; receive more to keep paying)', 'INSUFFICIENT_BALANCE'], + ['ln-pay failed: route not found', 'PAYMENT_FAILED'], + ]) { + ArkManager.prototype.payLnInvoice = async function () { throw new Error(why); }; + const { deps, published, declined, decryptReply } = harness(); + const handled = await respondFromBg({ type: 'nwc', event: ev(makeReq('pay_invoice', { invoice: INV21 })) }, { ...deps, errGraceMs: 60_000 }); + const r = declined.length ? decryptReply(declined[0]) : null; + check(`${code}: the error is handed to the notifier, nothing waited out here`, handled === true && declined.length === 1 && !published.length && r.error?.code === code && r.error.message === why, JSON.stringify(r)); + } + } finally { Object.assign(ArkManager.prototype, orig); } +} + console.log('\n[network fit]'); { // a mutinynet-era mirror must not answer a mainnet invoice with a nonsense -- cgit v1.2.3