aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-18 20:11:36 -0700
committerAdam Soltys <[email protected]>2026-08-18 20:11:36 -0700
commit1d0e26f5815f817f489c709bdf25f4708d18cd7d (patch)
tree8f9159a848aa6e1aa886aa0e2bf95356f7afdbbc
parentecf7da305d56cee8a8d2306ca7fa5dad4b2f0c39 (diff)
downloadcoinosv3-1d0e26f5815f817f489c709bdf25f4708d18cd7d.tar.gz
coinosv3-1d0e26f5815f817f489c709bdf25f4708d18cd7d.zip
A many-coined receive keeps the server's own order: the claim step stops 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
-rw-r--r--src/ark/manager.js9
1 files changed, 6 insertions, 3 deletions
diff --git a/src/ark/manager.js b/src/ark/manager.js
index e5e04d4..c1121db 100644
--- a/src/ark/manager.js
+++ b/src/ark/manager.js
@@ -1152,13 +1152,16 @@ export class ArkManager {
}
if (action.step === 'htlcsReady') {
const keys = this._key(action.keyIndex);
- const decoded = action.htlcBytes.map((b) => decodeVtxo(hex.decode(b)))
- .sort((a, b) => (a.id < b.id ? -1 : 1));
+ // Server order, verbatim: ClaimLightningReceive matches cosign parts to
+ // its HTLC list positionally, so a re-sorted list is rejected as
+ // "cosign inputs do not match htlcs" (bites only multi-vtxo receives).
+ const decoded = action.htlcBytes.map((b) => decodeVtxo(hex.decode(b)));
// preimage not revealed yet + HTLCs near expiry: abandon rather than
// commit (the inbound HTLC times out and the sender is refunded)
if (!action.preimageRevealed) {
const tip = await this.chain.tipHeight();
- if (tip > decoded[0].policy.htlcExpiry - this._expiryMargin()) {
+ const minExpiry = Math.min(...decoded.map((v) => v.policy.htlcExpiry));
+ if (tip > minExpiry - this._expiryMargin()) {
action.step = 'failed';
action.error = 'HTLCs near expiry — abandoned before preimage reveal';
this._movement({ type: 'ln-receive', amountSat: action.amountSat, status: 'failed', detail: action.error });