Skip to content

Add descriptor coordination to desktop#233

Open
wksantiago wants to merge 8 commits intomainfrom
descriptor-coordination
Open

Add descriptor coordination to desktop#233
wksantiago wants to merge 8 commits intomainfrom
descriptor-coordination

Conversation

@wksantiago
Copy link
Contributor

@wksantiago wksantiago commented Feb 16, 2026

Summary

  • Implement FROST descriptor coordination flow for multi-party wallet setup
  • Handle DescriptorContributionNeeded/DescriptorReady events for non-initiator peers
  • Add security hardening: signing share zeroization, threshold validation, network allowlist, debug redaction
  • Cancel during coordination aborts session on relay
  • Deduplicate xpub derivation logic

Test plan

  • Verify initiator can start descriptor coordination session
  • Verify non-initiator peers auto-contribute xpub
  • Verify cancel aborts session cleanly
  • Verify invalid threshold values are rejected in UI
  • Verify unknown network strings are rejected

Summary by CodeRabbit

  • New Features
    • Wallet setup wizard with multi-stage configuration, progress indicators, and ability to cancel an in-progress setup.
    • Network selection and automatic extended-public-key derivation during setup.
    • Tier configuration with customizable threshold and timelock per tier.
    • Coordinated descriptor sessions with real-time contribution/ready/complete/failure updates.
    • Persisted wallet descriptors appear in the wallet list after coordination completes.

@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

Warning

Rate limit exceeded

@wksantiago has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 32 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

Wires the desktop wallet UI into FROST descriptor coordination: adds wallet setup UI state and flows, new wallet and descriptor-related messages, FROST node lifecycle plumbing and descriptor event handling, xpub derivation/persistence, and a KfpNode descriptor session cancellation method.

Changes

Cohort / File(s) Summary
Dependencies
keep-desktop/Cargo.toml
Added keep-bitcoin = { path = "../keep-bitcoin" } and bitcoin = { workspace = true }.
App core & messaging
keep-desktop/src/app.rs, keep-desktop/src/message.rs
Added frost_node field and active_coordinations; many Wallet* Message variants and new FrostNodeMsg descriptor lifecycle variants; masked Debug for FrostNodeMsg; message routing to wallet handlers; task batching for frost/reconnect.
FROST integration & descriptor flow
keep-desktop/src/frost.rs
Added xpub derivation task, spawned node plumbing now stores node reference, extended frost_event_listener to emit descriptor events, App helpers: drain_frost_events, get_frost_node, update_wallet_setup, handle_frost_event; handlers for DescriptorContributionNeeded/Contributed/Ready/Complete/Failed; descriptor persistence on complete; clear node on disconnect.
Wallet UI & setup state
keep-desktop/src/screen/wallet.rs
Introduced TierConfig, DescriptorProgress, SetupPhase, SetupState; added setup field to WalletScreen; new view helpers for multi-step setup (configure → coordinating → complete).
Descriptor session management (node)
keep-frost-net/src/node/descriptor.rs
Added pub fn cancel_descriptor_session(&self, session_id: &[u8; 32]) to KfpNode to remove a descriptor session.
Other small changes
keep-desktop/src/* (tests, imports)
Numerous imports/initialization updates and minor wiring adjustments across app/frost/message modules to integrate the new flows.

Sequence Diagram(s)

sequenceDiagram
    participant User as User/UI
    participant App as Desktop App
    participant FrostNode as FROST Node
    participant Storage as Wallet Storage

    User->>App: WalletStartSetup / configure params
    App->>App: initialize SetupState (shares, tiers, network)
    User->>App: WalletBeginCoordination
    App->>FrostNode: ensure node & request descriptor session
    FrostNode->>App: DescriptorContributionNeeded(session_id, network, initiator_pubkey)
    App->>App: derive_xpub (background task)
    App->>FrostNode: contribute descriptor (xpub + fingerprint)
    FrostNode->>App: DescriptorContributed / DescriptorReady
    FrostNode->>App: DescriptorComplete(session_id, external, internal)
    App->>Storage: persist descriptor (WalletEntry)
    App->>App: update SetupState → Complete / notify UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Poem

🐇
I hop with tiny keystrokes bright,
Binding shares beneath moonlight.
Tiers and xpubs, threads entwine—
FROST and wallet stitch the line.
Hop—descriptor!—now all is right.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding descriptor coordination functionality to the desktop application, which is the core focus of this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch descriptor-coordination

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@wksantiago wksantiago self-assigned this Feb 16, 2026
@wksantiago wksantiago linked an issue Feb 16, 2026 that may be closed by this pull request
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
keep-desktop/src/app.rs (1)

1073-1085: Redundant error field on session failure.

On error, both s.phase is set to Coordinating(Failed(e)) and s.error is set. Since the phase is now Coordinating, view_setup will render view_coordinating which shows the failure in the status badge. The s.error field is only displayed in view_configure, which won't be shown. Setting s.error here is harmless but has no visible effect.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keep-desktop/src/app.rs` around lines 1073 - 1085, The error assignment is
redundant: in Message::WalletSessionStarted when matching Err(e) you already set
s.phase = SetupPhase::Coordinating(DescriptorProgress::Failed(e.clone())), so
remove the useless s.error = Some(e) line from the Err branch in the
Screen::Wallet -> WalletScreen handling; keep the phase update (and the
e.clone() used for DescriptorProgress::Failed) and do not set s.error so
view_setup/view_coordinating behavior remains correct (view_configure-exclusive
s.error should not be populated here).
keep-desktop/src/screen/wallet.rs (1)

217-226: Encoding tier index + value as a colon-delimited string is fragile.

The format!("{i}:{v}") encoding pairs the tier index with the user-entered value. This relies on split_once(':') in update_tier_field (app.rs, line 1110). Since the input is a numeric text field, colons shouldn't appear in practice, but this implicit coupling is easy to break if inputs change.

Consider defining a small struct or tuple message variant (e.g., WalletThresholdChanged(usize, String)) to avoid the encode/parse round-trip entirely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keep-desktop/src/screen/wallet.rs` around lines 217 - 226, The code currently
encodes the tier index into a colon-delimited string via format!("{i}:{v}") in
the text_input closures; instead change the Message variants
WalletThresholdChanged and WalletTimelockChanged to take (usize, String) (or a
small struct/tuple) and update the on_input closures in the loop over
setup.tiers.enumerate() to send Message::WalletThresholdChanged(i, v) and
Message::WalletTimelockChanged(i, v) (use move |v| with the captured i), then
remove the split_once(':') parsing in update_tier_field and handle the typed
(usize, String) payload directly. This touches the text_input call sites, the
Message enum variants, and the update_tier_field handler; ensure all call sites
and pattern matches are updated accordingly.
keep-desktop/src/frost.rs (2)

647-687: Auto-contribution flow for non-initiator peers looks correct.

The function properly retrieves the last connected share info, derives the xpub, and contributes it back to the initiator. The error path correctly surfaces a Failed progress message.

One observation: if frost_last_share is None (line 653-656), the contribution is silently skipped. This could happen if the relay was reconnected without a share context. Consider logging a warning in this case.

💡 Add a log on silent skip
         let share = match &self.frost_last_share {
             Some(s) => s.clone(),
-            None => return iced::Task::none(),
+            None => {
+                tracing::warn!("DescriptorContributionNeeded but no active share");
+                return iced::Task::none();
+            }
         };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keep-desktop/src/frost.rs` around lines 647 - 687, In
handle_descriptor_contribution_needed, when frost_last_share is None (the
early-return path that currently silently skips contributing), add a warning log
so reconnections without share context are visible; locate the match on
self.frost_last_share and replace the immediate return iced::Task::none() with a
log::warn! or tracing::warn! call that includes context (e.g., mention
session_id and network) before returning, and consider doing the same for the
get_frost_node() None branch to surface missing node state.

283-342: Consider extracting parameters into a struct to reduce argument count.

With 8 parameters, spawn_frost_node already has #[allow(clippy::too_many_arguments)]. As this function continues to grow, grouping related parameters (e.g., keep_arc + keep_path + share_entry, or the output refs) into structs would improve readability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keep-desktop/src/frost.rs` around lines 283 - 342, spawn_frost_node currently
accepts many parameters; refactor by creating one or two small parameter structs
(e.g., FrostNodeConfig grouping keep_arc: Arc<Mutex<Option<Keep>>>, keep_path:
PathBuf, share_entry: ShareEntry and NetworkConfig + relay_urls into
NetworkConfigWrapper, and NodeHandles grouping node_out:
Arc<Mutex<Option<Arc<KfpNode>>>> and kill_switch) and update the function
signature of spawn_frost_node to take these structs instead, then update the
internal call to setup_frost_node (and the setup_frost_node signature if it also
takes the same long list) and all callers to pass the new structs; keep existing
types and semantics (Arc/Mutex, FrostChannels, etc.) intact so only parameter
packing/unpacking is needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@keep-desktop/src/frost.rs`:
- Around line 853-920: handle_descriptor_complete currently returns early if the
UI is not on Screen::Wallet or setup is missing, causing successful descriptors
to be dropped; instead, always persist the descriptor via lock_keep and
keep.store_wallet_descriptor (and map_err(friendly_err)) regardless of UI state,
by first obtaining group_pubkey and network from a UI-independent source (e.g.,
cache the coordination info on App or retrieve the setup stored by session id
rather than relying on Screen::Wallet/ws.setup). After storing, conditionally
update the UI: if let Screen::Wallet(ws) = &mut self.screen and
ws.setup.as_ref().map(|s| s.session_id.as_ref()) == Some(&session_id) then set
setup.phase and push to ws.descriptors on Ok, or set DescriptorProgress::Failed
on Err; ensure you still preserve created_at and use the same
keep_core::WalletDescriptor construction and friendly_err mapping when
persisting.

---

Nitpick comments:
In `@keep-desktop/src/app.rs`:
- Around line 1073-1085: The error assignment is redundant: in
Message::WalletSessionStarted when matching Err(e) you already set s.phase =
SetupPhase::Coordinating(DescriptorProgress::Failed(e.clone())), so remove the
useless s.error = Some(e) line from the Err branch in the Screen::Wallet ->
WalletScreen handling; keep the phase update (and the e.clone() used for
DescriptorProgress::Failed) and do not set s.error so
view_setup/view_coordinating behavior remains correct (view_configure-exclusive
s.error should not be populated here).

In `@keep-desktop/src/frost.rs`:
- Around line 647-687: In handle_descriptor_contribution_needed, when
frost_last_share is None (the early-return path that currently silently skips
contributing), add a warning log so reconnections without share context are
visible; locate the match on self.frost_last_share and replace the immediate
return iced::Task::none() with a log::warn! or tracing::warn! call that includes
context (e.g., mention session_id and network) before returning, and consider
doing the same for the get_frost_node() None branch to surface missing node
state.
- Around line 283-342: spawn_frost_node currently accepts many parameters;
refactor by creating one or two small parameter structs (e.g., FrostNodeConfig
grouping keep_arc: Arc<Mutex<Option<Keep>>>, keep_path: PathBuf, share_entry:
ShareEntry and NetworkConfig + relay_urls into NetworkConfigWrapper, and
NodeHandles grouping node_out: Arc<Mutex<Option<Arc<KfpNode>>>> and kill_switch)
and update the function signature of spawn_frost_node to take these structs
instead, then update the internal call to setup_frost_node (and the
setup_frost_node signature if it also takes the same long list) and all callers
to pass the new structs; keep existing types and semantics (Arc/Mutex,
FrostChannels, etc.) intact so only parameter packing/unpacking is needed.

In `@keep-desktop/src/screen/wallet.rs`:
- Around line 217-226: The code currently encodes the tier index into a
colon-delimited string via format!("{i}:{v}") in the text_input closures;
instead change the Message variants WalletThresholdChanged and
WalletTimelockChanged to take (usize, String) (or a small struct/tuple) and
update the on_input closures in the loop over setup.tiers.enumerate() to send
Message::WalletThresholdChanged(i, v) and Message::WalletTimelockChanged(i, v)
(use move |v| with the captured i), then remove the split_once(':') parsing in
update_tier_field and handle the typed (usize, String) payload directly. This
touches the text_input call sites, the Message enum variants, and the
update_tier_field handler; ensure all call sites and pattern matches are updated
accordingly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
keep-desktop/src/frost.rs (2)

875-883: lock_keep guard held during store_wallet_descriptor — verify this is a fast operation.

The mutex guard from lock_keep is held while calling store_wallet_descriptor. If this involves disk I/O (file write, fsync), it blocks the UI thread. Given that store_wallet_descriptor likely writes to the vault, consider whether this should be dispatched to a blocking task like other write operations in this codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keep-desktop/src/frost.rs` around lines 875 - 883, The code currently holds
the mutex guard from lock_keep(&self.keep) while calling
store_wallet_descriptor(&descriptor), which may perform blocking disk I/O and
stall the UI; instead, drop the guard before performing the write or run the
write in a blocking thread: capture/clone the Option<Keep> (or the minimal data
needed) out of the guard, release the guard, then call
keep.store_wallet_descriptor(&descriptor) off the UI thread (e.g.,
spawn_blocking or the existing blocking task mechanism) and map errors with
friendly_err into store_result so the mutex is not held during the potentially
slow I/O.

46-67: Signing share zeroization covers the extracted bytes but not intermediaries.

signing_bytes is correctly wrapped in Zeroizing, but signing_share.serialize() produces an intermediate allocation (the serialized form) before conversion to [u8; 32]. That intermediate is not zeroized. Similarly, key_package and signing_share themselves hold secret material that won't be explicitly cleared.

This is likely a limitation of the frost-core types not implementing Zeroize. The current approach is reasonable given that constraint — just noting the gap for completeness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keep-desktop/src/frost.rs` around lines 46 - 67, The serialized signing share
creates a transient allocation that isn't zeroized; to fix, serialize into a
zeroized buffer (e.g., use Zeroizing<Vec<u8>> or serialize directly into a
Zeroizing<[u8;32]>/zeroized stack buffer) and then copy/try_into that zeroized
buffer to produce signing_bytes, ensuring the intermediate Vec/serialization
buffer is dropped while zeroized; also limit scope and explicitly drop or let go
out of scope the key_package and signing_share as soon as signing_bytes is
produced so their secret data is not retained. Target symbols:
signing_share.serialize(), signing_bytes, Zeroizing, key_package, signing_share.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@keep-desktop/src/app.rs`:
- Around line 1080-1107: The handler Message::WalletSessionStarted currently
re-reads WalletScreen.selected_share and shares to compute group_pubkey, which
can race with UI changes; instead, capture the coordination metadata
(group_pubkey and network) in begin_descriptor_coordination before spawning the
async task and change the WalletSessionStarted message payload to carry
(session_id, group_pubkey, network). Update begin_descriptor_coordination to
compute group_pubkey from WalletScreen.selected_share and shares and include
network.clone() when creating the task, have the spawned task return
(session_id, group_pubkey, network) on success, and modify the
WalletSessionStarted match arm to use those passed-in values to insert into
active_coordinations (ActiveCoordination) rather than re-reading WalletScreen
state.

In `@keep-desktop/src/frost.rs`:
- Around line 854-910: handle_descriptor_complete is skipping non-initiators
because active_coordinations is never populated in the non-initiator path;
update handle_descriptor_contribution_needed to insert an entry into
self.active_coordinations keyed by the incoming session_id using the
group_pubkey from self.frost_last_share and the provided network so later
DescriptorComplete handling can find it. Concretely, in
handle_descriptor_contribution_needed create and insert a Coordination (or the
same struct stored in active_coordinations) with group_pubkey:
self.frost_last_share.group_pubkey (or equivalent) and network: network (the
parameter), keyed by session_id ([u8;32]) before returning so
handle_descriptor_complete can remove and use it.

---

Nitpick comments:
In `@keep-desktop/src/frost.rs`:
- Around line 875-883: The code currently holds the mutex guard from
lock_keep(&self.keep) while calling store_wallet_descriptor(&descriptor), which
may perform blocking disk I/O and stall the UI; instead, drop the guard before
performing the write or run the write in a blocking thread: capture/clone the
Option<Keep> (or the minimal data needed) out of the guard, release the guard,
then call keep.store_wallet_descriptor(&descriptor) off the UI thread (e.g.,
spawn_blocking or the existing blocking task mechanism) and map errors with
friendly_err into store_result so the mutex is not held during the potentially
slow I/O.
- Around line 46-67: The serialized signing share creates a transient allocation
that isn't zeroized; to fix, serialize into a zeroized buffer (e.g., use
Zeroizing<Vec<u8>> or serialize directly into a Zeroizing<[u8;32]>/zeroized
stack buffer) and then copy/try_into that zeroized buffer to produce
signing_bytes, ensuring the intermediate Vec/serialization buffer is dropped
while zeroized; also limit scope and explicitly drop or let go out of scope the
key_package and signing_share as soon as signing_bytes is produced so their
secret data is not retained. Target symbols: signing_share.serialize(),
signing_bytes, Zeroizing, key_package, signing_share.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
keep-desktop/src/app.rs (2)

1394-1417: do_lock does not clear active_coordinations.

handle_disconnect_relay (called at line 1395) clears the frost node, which prevents new descriptor events from arriving, but active_coordinations retains stale entries. These will persist until the next unlock session. For consistency with other state cleanup performed in do_lock, consider clearing it:

 self.toast = None;
 self.toast_dismiss_at = None;
+self.active_coordinations.clear();
 self.screen = Screen::Unlock(UnlockScreen::new(true));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keep-desktop/src/app.rs` around lines 1394 - 1417, In do_lock, after calling
handle_disconnect_relay (and/or alongside other state resets), clear the stale
coordination state by invoking the active_coordinations clear/reset on self
(e.g., self.active_coordinations.clear() or equivalent) so active_coordinations
does not retain entries across lock; update do_lock (referencing do_lock,
handle_disconnect_relay, and active_coordinations) to perform this clear before
returning the Task.

1211-1216: Initial received: 1 is set before the initiator's contribution actually succeeds.

The UI progress is updated to show 1 contribution received before the async derive_xpub + request_descriptor task (lines 1222–1240) completes. If that task fails, the error path in WalletSessionStarted overwrites the phase to Failed, so the user won't be stuck. This is a minor optimistic-UI glitch — the count briefly shows 1 even though nothing was contributed yet.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keep-desktop/src/app.rs` around lines 1211 - 1216, The UI sets received: 1
prematurely in the Wallet screen setup block (Screen::Wallet / WalletScreen)
before the async task that runs derive_xpub and request_descriptor completes;
move the state update so it only increments to received: 1 after the async
contribution succeeds (i.e., after derive_xpub + request_descriptor returns
successfully) or update it in the success branch that currently emits
WalletSessionStarted; specifically, remove or defer setting
SetupPhase::Coordinating(DescriptorProgress::WaitingContributions { received: 1,
... }) at the top and instead update the WalletScreen phase to
WaitingContributions with received: 1 inside the successful completion handler
for the derive_xpub/request_descriptor flow (or in the code path that emits
WalletSessionStarted) so the progress reflects only confirmed contributions.
keep-desktop/src/frost.rs (1)

31-39: parse_bitcoin_network mirrors the allowlist in app.rs — keep them in sync.

The accepted values here ("bitcoin", "testnet", "signet", "regtest") match VALID_NETWORKS in app.rs line 1047. If one changes, the other must follow. Consider extracting a shared constant or reusing parse_bitcoin_network as the single source of truth for network validation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keep-desktop/src/frost.rs` around lines 31 - 39, The network allowlist is
duplicated between parse_bitcoin_network and VALID_NETWORKS; extract a single
source of truth (e.g., move the Vec<&'static str> or array of valid names into a
shared module like crate::networks::VALID_NETWORKS or expose
parse_bitcoin_network from frost.rs) and have both parse_bitcoin_network and the
code in app.rs reference that shared symbol instead of hardcoding values; update
parse_bitcoin_network to consult the shared list (or export it and derive the
match from it) and replace VALID_NETWORKS in app.rs with a reference to the
shared constant or call to parse_bitcoin_network so they remain in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@keep-desktop/src/app.rs`:
- Around line 1080-1107: The handler for Message::WalletSessionStarted inserts
into self.active_coordinations unconditionally even when the UI setup was
cancelled (s.setup became None), creating a dangling coordination; modify the Ok
branch so the ActiveCoordination insert only occurs when the wallet setup is
still present (the same if let Screen::Wallet(WalletScreen { setup: Some(s), ..
}) match used to set s.session_id), i.e., move or guard the
self.active_coordinations.insert(...) behind that check (or check
s.setup.is_some() before inserting) so we never register a coordination without
a corresponding setup/session_id to clean up; reference
Message::WalletSessionStarted, WalletScreen, s.setup, session_id, and
ActiveCoordination when making the change.

In `@keep-desktop/src/frost.rs`:
- Around line 41-73: The serialized signing share bytes produced by
signing_share.serialize() are not wrapped in Zeroizing and may remain in memory;
in derive_xpub wrap the Vec<u8> from signing_share.serialize() in Zeroizing
(e.g., Zeroizing::new(the_vec)) before converting to a [u8;32] so the
intermediate buffer is zeroized on drop, keeping the existing try_into()
conversion and the signing_bytes Zeroizing<[u8;32]> binding and preserving the
current map_err() error handling for invalid length.

---

Nitpick comments:
In `@keep-desktop/src/app.rs`:
- Around line 1394-1417: In do_lock, after calling handle_disconnect_relay
(and/or alongside other state resets), clear the stale coordination state by
invoking the active_coordinations clear/reset on self (e.g.,
self.active_coordinations.clear() or equivalent) so active_coordinations does
not retain entries across lock; update do_lock (referencing do_lock,
handle_disconnect_relay, and active_coordinations) to perform this clear before
returning the Task.
- Around line 1211-1216: The UI sets received: 1 prematurely in the Wallet
screen setup block (Screen::Wallet / WalletScreen) before the async task that
runs derive_xpub and request_descriptor completes; move the state update so it
only increments to received: 1 after the async contribution succeeds (i.e.,
after derive_xpub + request_descriptor returns successfully) or update it in the
success branch that currently emits WalletSessionStarted; specifically, remove
or defer setting
SetupPhase::Coordinating(DescriptorProgress::WaitingContributions { received: 1,
... }) at the top and instead update the WalletScreen phase to
WaitingContributions with received: 1 inside the successful completion handler
for the derive_xpub/request_descriptor flow (or in the code path that emits
WalletSessionStarted) so the progress reflects only confirmed contributions.

In `@keep-desktop/src/frost.rs`:
- Around line 31-39: The network allowlist is duplicated between
parse_bitcoin_network and VALID_NETWORKS; extract a single source of truth
(e.g., move the Vec<&'static str> or array of valid names into a shared module
like crate::networks::VALID_NETWORKS or expose parse_bitcoin_network from
frost.rs) and have both parse_bitcoin_network and the code in app.rs reference
that shared symbol instead of hardcoding values; update parse_bitcoin_network to
consult the shared list (or export it and derive the match from it) and replace
VALID_NETWORKS in app.rs with a reference to the shared constant or call to
parse_bitcoin_network so they remain in sync.

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.

Wire desktop wallet screen to descriptor coordination flow

1 participant