Skip to content

fix(ark): let users see and fund the exit-fee reserve during an exit - #183

Closed
CypherBoxLLC wants to merge 2 commits into
fix/coinos-withdraw-idempotencyfrom
fix/exit-funding-reachable-during-exit
Closed

CypherBoxLLC wants to merge 2 commits into
fix/coinos-withdraw-idempotencyfrom
fix/exit-funding-reachable-during-exit

Conversation

@CypherBoxLLC

Copy link
Copy Markdown
Owner

Stacked on #182. Base is fix/coinos-withdraw-idempotency, so the diff here is the single commit
54bfd1e. GitHub will retarget this to main automatically when #182 merges.

The bug

While an exit was running, the red "Emergency exit in progress" panel REPLACED the exit-funding UI.
The fee reserve could be neither seen nor topped up at the exact moment it matters, which is the
moment the exit is spending it. If the reserve ran dry mid-exit there was no in-app way to refill it.

The panel and the funding section now coexist.

Also here

Promise.all becomes Promise.allSettled in the receive sheet. A failing Ark address was
discarding a perfectly good on-chain address alongside it and leaving a permanent spinner, so a
half-down backend took out the whole receive screen.

Verification

  • 40 suites, 323 tests green
  • tsc error set byte-identical to origin/main (405 errors, 19 TS2304)

Two bugs found while running a real unilateral exit, both of which made the exit
harder to complete at the exact moment it mattered.

1. THE FUNDING UI DISAPPEARED DURING AN EXIT.

Settings.tsx replaced the whole exit section with an "Emergency exit in
progress" panel, so the reserve could be neither seen nor topped up while an
exit ran. That panel also promises funds sweep automatically once the timelock
expires, while removing the means to make that true: every exit branch needs a
CPFP broadcast and every claim needs a fee.

Observed live on 2026-08-18. A five-capsule exit ran the reserve down from 3654
to 699 sats with four claims still owed, and there was no way to add more from
that screen. The capsules would have sat exited-but-unclaimed indefinitely.

The panel now shows the on-chain reserve balance and offers a top-up that opens
the existing funding sheet. The Convert tab is hidden mid-exit because it is a
cooperative offboard, ASP-gated by assertNoActiveArkExit, so it cannot work
there; Receive and From-a-wallet both can, since they are ASP-independent, which
matters because an exit is often running precisely when the server is
unreachable. A user sitting on Convert when an exit begins is moved to Receive,
otherwise the sheet would render nothing at all.

2. A LOCKED WALLET LEFT THE RECEIVE SHEET SPINNING FOREVER.

ReceivedListNew fetched the Ark address and the on-chain address with
Promise.all, so a failure in either discarded BOTH. The Ark address needs the
Ark wallet; the on-chain address is a local BDK call and is the ASP-independent
way to fund an exit. With the wallet locked the Ark fetch threw "Ark wallet not
initialized" and took the perfectly obtainable on-chain address down with it,
leaving null state and a spinner that never resolved, with only a three-second
toast saying "Failed to fetch Ark addresses".

Now allSettled, so each address stands on its own, and the message names the
actual cause: a locked vault says so and tells the user to open it.
…is stuck

Reported from device: the Fund exit fees confirm screen sat on a spinner
captioned "Please wait" with no amount, no fee and no explanation.

Two separate defects, both of which produce that.

SwipeButton exposes only `isLoading`, and when true it renders a spinner
captioned "Please wait". This screen was passing `!plan.ok`, `indeterminate` and
`sourceId !== 'coinos'` into it. None of those are busy states, they are
permanent ones, so the screen told the user it was working on something that was
never going to happen. `planExitFunding` had already computed exactly why (the
reserve is already funded, or the balance cannot cover the fee plus a useful
top-up) and that reason was rendered as small print inside the card while the
call to action contradicted it. A blocked state is now rendered as a blocked
state, carrying the reason, and the swipe only appears when swiping can achieve
something.

Second, the preparation step could hang forever. It calls getArkOnchainAddress,
which goes through ensureArkOnchainHandle to spawn bark's on-chain BDK wallet
against an esplora endpoint, and nothing on that path has a timeout. A provider
that accepts the connection and then never answers leaves `loading` true for the
lifetime of the screen, because it is only cleared in the `finally`. That is not
hypothetical on this device: the same launch logged `onchain spawn failed via
blockstream.info` with a 338 byte body where a 64 character blockhash belongs,
while both explorers answered a desktop on the same LAN correctly.

Both awaits are now bounded at 20s, and a failure renders on screen with the
likely cause rather than a toast that vanishes before it can be read. The
deposit address depends on a reachable chain source, so changing network usually
clears it.

Follow-up once #184 merges: that error should go through arkNetworkFaultMessage,
which names whether the chain source or the Ark server is at fault. It is on a
separate branch, so wiring it here would make this change unmergeable on its own.
@CypherBoxLLC

Copy link
Copy Markdown
Owner Author

Landed in main via #208, which merged the device-tested integration branch as a unit.

This PR did not auto-close because its base was another feature branch rather than main, so GitHub never evaluated it against main. Retargeting is refused with "There are no new commits between base branch 'main' and head branch", which confirms the commits are already reachable from main.

Closing as merged.

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.

1 participant