Skip to content

fix: commit carves before the settlement wait (#6) - #8

Merged
tompro merged 2 commits into
mainfrom
fix/carve-checkpoint
Aug 31, 2026
Merged

tompro merged 2 commits into
mainfrom
fix/carve-checkpoint

Conversation

@tompro

@tompro tompro commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Closes #6.

Problem

Every flow that carves (split/merge) notes and then waits on a settlement poll — send, pay, and inter-mint move — committed the carve's changeset only after the whole flow resolved. A user abort, tab close, or crash during the (up to 2-minute) wait left the carved inputs burned at the mint but spendable locally, and stranded the fresh outputs entirely: their secrets lived only in the pending promise's memory. Observed in the wild with a target mint's Lightning node down.

Fix

  • Engine: FundOperationOptions.onCarve — a checkpoint fired by ensureExactAmount the moment a mutation-bearing carve's outcome is known (inputs burned, outputs known), before the flow moves on to the melt/wait. Never fires for a no-op exact-match carve. A rejecting hook aborts the flow before the melt — nothing further is spent from an uncommitted carve.
  • Result-shape consequence: because the carve may be committed before the flows' classification rotates run, those rotations no longer rewrite carve.note in the result — PayResult/TransferResult carry a separate verified rotatedNote. (This closes a regression the first cut of this PR introduced: the early-committed carve plus the old in-place rotation would have stranded the returned funds on failed-funds-returned — caught in review, fixed here with tests.)
  • Composables (send / pay / move): commit inside onCarve via the existing commitCarve helper; no-mutation carves fall back to a URL lookup; failed-funds-returned adds the rotated note before marking the burned carve note spent.
  • NWC: payChangeset adapts to rotatedNote; its commit timing is unchanged (its own early-commit threading is a deliberate follow-up — see below).

Verification

  • npx vitest run: 340/340, incl. new coverage: hook fires with the result / never fires for no-mutation carves / hook failure aborts pay and transfer before the melt / hook provably runs before the melt starts / rotation arrives as rotatedNote with the carve untouched.
  • npx vue-tsc --noEmit clean, eslint clean, e2e 25/25 (chromium).

Scope notes

  • The post-melt abort window (melt issued, wait abandoned) is intentionally not closed here: it is small, reconcilable by refresh, and covered by the ticket's direction 2 (resumable transfers).
  • NWC pay_invoice gets its own onCarve threading as a follow-up — flagged by review, deliberately kept out to keep this diff tight.

tompro added 2 commits August 31, 2026 14:48
A flow that carves and then waits minutes on a settlement poll used to
commit the carve only after the whole flow resolved - an abort mid-wait
left burned inputs spendable locally and stranded the fresh outputs.
ensureExactAmount now reports a mutation-bearing carve through the new
FundOperationOptions.onCarve checkpoint the moment its outcome is known;
payWithBearers and transferBetweenMints thread it through.

Because the carve may now be committed before the flows' classification
rotates run, those rotations no longer rewrite carve.note in the result -
returned funds come back as a separate verified rotatedNote, so the
committed carve and the re-secured note can both be persisted. NWC's
payChangeset adapts to the rotatedNote field; its own commit timing is
unchanged (follow-up).
The three composables commit the carve inside onCarve via the existing
commitCarve helper instead of after the flow, falling back to a URL
lookup for no-mutation carves (which fire no hook). On
failed-funds-returned with a rotation, the re-secured note is added
before the burned carved note is marked spent.
@tompro
tompro merged commit 09f7fe0 into main Aug 31, 2026
2 checks passed
@tompro
tompro deleted the fix/carve-checkpoint branch August 31, 2026 12:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Transfers aborted mid-claim strand the carve outputs (funds desync)

1 participant