Skip to content

v0.9.3 to master#458

Merged
pauldelucia merged 48 commits into
masterfrom
v0.9.3
Oct 27, 2025
Merged

v0.9.3 to master#458
pauldelucia merged 48 commits into
masterfrom
v0.9.3

Conversation

@pauldelucia
Copy link
Copy Markdown
Member

@pauldelucia pauldelucia commented Oct 27, 2025

Summary by CodeRabbit

  • New Features

    • ZK proof tools, masternode-list inspector, Dashpay “Coming Soon” screen, and new reusable UI components (AmountInput, ConfirmationDialog, IdentitySelector).
  • Improvements

    • Typed Amount model used across token/transfer/burn/purchase UIs, clearer token pricing display, settings caching and improved theme handling, per-network ZMQ endpoint config and env entries, richer confirmation flows and identity selection.
  • Chores

    • Rust toolchain bumped to 1.89, new CI test workflow, dependency updates, package/version bumps.

pauldelucia and others added 30 commits July 17, 2025 10:55
* fix: top up with qr code wasnt working

* fmt and clippy

* coderabbit
* fix: dash-qt path not detected correctly

* fix: correct handling of dash-qt path clear button

* chore: copilot review
…and others) (#406)

* build(deps): update all rust dependencies

* chore: replace depreacated ui.close_menu()

* chore: upgrade Popup in identities screen

* fix: actions menu items width too low

* chore: all warnings fixed

* chore: close identity actions on click outside

---------

Co-authored-by: pauldelucia <pauldelucia2@gmail.com>
* refactor: unified identity selection

* feat: identity selector implementation on multiple screens

* feat: add label to IdentitySelector

* chore: I think final

* Update src/ui/components/identity_selector.rs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* feat: amount input, first building version

* test(amount): fixed tests

* chore: I think final

* chore: my_tokens display correct amount

* chore: transfer tokens update

* chore: hide unit on rewards estimate column

* chore: two new helper methods

* chore: I think finals

* cargo fmt

* feat: component trait

* impl Component for AmountInput

* chore: updated component trait

* chore: update for egui enabled state mgmt

* doc: component design pattern doc

* chore: component design pattern continued

* chore: amount improvements

* chore: copilot review

* chore: amount improvements

* backport: amount component from

* chore: fix imports

* chore: refactor

* chore: futher refactor

* chore: further refactor based on feedback

* doc: simplified component design pattern description

* chore: peer review

* doc: update docs

* chore: amount input
* fix: failing tests

* gha: run tests

* fix: tests fail due to lack of .env file
…ns (#419)

* feat: amount input, first building version

* test(amount): fixed tests

* chore: I think final

* chore: my_tokens display correct amount

* chore: transfer tokens update

* chore: hide unit on rewards estimate column

* chore: two new helper methods

* chore: I think finals

* cargo fmt

* feat: component trait

* impl Component for AmountInput

* chore: updated component trait

* chore: update for egui enabled state mgmt

* doc: component design pattern doc

* chore: component design pattern continued

* chore: amount improvements

* chore: copilot review

* chore: amount improvements

* refactor: mint and burn token screens use AmountInput

* chore: use AmountInput on token creator screen

* fix: burn error handling

* feat: errors displayed in the AmountInput component

* fix: vertical align of amount input

* backport: amount component from

* chore: fix imports

* chore: refactor

* chore: futher refactor

* chore: further refactor based on feedback

* doc: simplified component design pattern description

* chore: peer review

* doc: update docs

* chore: amount input

* chore: fixes after merge

* chore: self-review

* feat: amout set decimal places + rename label => with_label

* refactor: amount input init on token screen

* chore: fix token creator layout

* chore: format base amount with leading zeros in confirmation

* chore: base supply 0 by default
* feat: add network field to identity structures

* fix
* feat: add display for private key in both WIF and Hex formats

* fix: display private keys as normal text and depend on color theme

---------

Co-authored-by: pauldelucia <pauldelucia2@gmail.com>
* feat: allow setting ZMQ URI

* chore: rename zmq_endpoint to core_zmq_endpoint
* refactor: unified alert window

* chore: update CLAUDE to create reusable components whenever appropriate

* feat: correct confirm dialog on set token price screen

* chore: remove callbacks which are overkill

* chore: cargo fmt

* chore: doctest fix

* chore: impl Component for ConfirmationDialog

* chore: use WidgetText

* feat: Add Escape key handling for confirmation dialog

* chore: button inactive when in progress

* chore: fixes after merge

* chore: some theme improvements

* fmt and visual appeal

---------

Co-authored-by: pauldelucia <pauldelucia2@gmail.com>
* feat: nicer contract chooser panel

* fmt

* clippy
* feat: nicer expanding tabs in token creator

* fix
* feat: implement new ConfirmationDialog component throughout app

* fix: remove backup files
* a lot of work on DML viewer

* more work

* more work

* more work

* more work

* more work

* more work

* more work

* more work

* ui improvements

* ui improvements

* optimizations

* fast start

* more work

* more work

* more work

* fmt

* much more work

* fixes

* updates

* fix

* fmt

* revert

* update for dashcore 40

* params for testnet

* added testnet diff

* fixes

* clippy

* more clippy

* fixes

* clean UI

* use backend tasks

* backend messages

* coderabbit suggestions to add timeouts and prevent infinite loops

* transient and fatal errors

* update dash core configs for testnet

* fmt

* fix timeout

* fix h-3 error

* clear state when switching networks

---------

Co-authored-by: pauldelucia <pauldelucia2@gmail.com>
The 1.0-dev branch builds were still reporting as 0.9.0
…ser panel (#441)

* feat: add left panel button labels and create contract subscreen chooser panel

* fmt
* feat: left panel scroll

* clippy
* feat: add existing identity by wallet + identity index

* fmt

* remove logging

* update derivation index label

* add robustness

* fix: screen button labels display hard to see in dark mode

* feat: left panel scroll (#443)

* feat: left panel scroll

* clippy

* feat: load all identities up to an index

* clippy

* fix
pauldelucia and others added 11 commits October 17, 2025 15:54
* feat: remove wallet

* clippy

* Clarify wallet removal warning message

Updated warning message for wallet removal to clarify that keys will no longer work unless the wallet is re-imported.
* fix: identity creation handling

* cleanup

* cleanup

* fix identity topup too

* remove note
… and UI updates (#449)

* fix: keyword search not displaying errors

* update platform version and keyword search cleanup

* fix
…412)

* fix: inactive button on set price group action

* chore: apply review feedback

* refactor: use token amount input

* chore: update AmountInput

* chore: tiered pricing

* chore: direct purchase

* chore: remove total agreed price

* chore: max amount input defaults to max_credits

* fmt

* fix

* clippy

---------

Co-authored-by: pauldelucia <pauldelucia2@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
* works

* works

* fix

* ok

* ok

* ok

* ok

* ok

* fix verification message showing in generation screen

* remove advanced settings. set default 16 grinding bits and 128 bit security

* ok

* lock

* ok

* clippy

* fmt

* clippy and fmt

* pin grovestark dep

* fix

* ok

* clippy

* rename file

* remove ProofData

* cleanup

* fix

* rename GroveStark to GroveSTARK

* rename zk_proofs_screen to grovestark_screen

* rename ZKProofs tool subscreen to GroveSTARK

* move grovestark prover to model

* rename ContractsDashpayComingSoon to ContractsDashpay

* rename ContractsDashpay to Dashpay

* rename dashpay stuff

* fixes

* ok

* update grovestark rev

* update
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 27, 2025

Walkthrough

Adds per-network core ZMQ config, Rust/tooling bumps, new GroveSTARK prover and MNList runners, a Core P2P handler, typed Amount and UI component primitives (AmountInput, ConfirmationDialog, IdentitySelector, Component trait), settings caching, network-aware identity/wallet flows, and broad UI/backend refactors.

Changes

Cohort / File(s) Summary
Tooling & CI
rust-toolchain.toml, .github/workflows/clippy.yml, .github/workflows/tests.yml, .github/workflows/release.yml, snap/snapcraft.yaml, Cargo.toml, CLAUDE.md
Bump Rust to 1.89, update CI workflows (clippy/tests/release), add tests workflow, adjust release steps, bump package/snap versions, and upgrade many dependencies.
Env & Core Config
.env.example, src/config.rs, dash_core_configs/...
Add MAINNET/TESTNET/DEVNET/LOCAL *_core_zmq_endpoint entries in .env.example; add core_zmq_endpoint: Option<String> to NetworkConfig and persist it; rename zmq keys to zmqpubrawchainlocksig in core confs.
Backend Tasks & Core Items
src/backend_task/mod.rs, src/backend_task/grovestark.rs, src/backend_task/mnlist.rs, src/backend_task/core/mod.rs
Add GroveSTARK and MnList task types and runners, extend BackendTaskSuccessResult with ZK/MNList variants, and add CoreItem variants for InstantLockedTransaction and ChainLockedBlock.
P2P & ZMQ Components
src/components/core_p2p_handler.rs, src/components/core_zmq_listener.rs, src/components/mod.rs
New CoreP2PHandler implementing handshake/framing and MnList/QRInfo queries; ZMQ listener updated to handle ChainLock payloads/topic; export core_p2p_handler.
Settings & Context
src/model/settings.rs, src/context.rs, src/database/settings.rs, src/app.rs
Add Settings model and caching (cached_settings) with get_settings/invalidate_settings_cache; update AppContext methods to invalidate cache on updates; drive app initialization and screen wiring from Settings.
Model Additions
src/model/amount.rs, src/model/grovestark_prover.rs, src/model/mod.rs, src/model/qualified_identity/*
New Amount type and helpers; GroveSTARK prover and proof data types; register model modules; add network field to QualifiedIdentity and propagate network across identity/wallet flows.
Identity & Wallet flows
src/backend_task/identity/*, src/model/qualified_identity/*, src/database/identities.rs, src/database/wallet.rs
Async wallet-based identity loaders, SearchIdentitiesUpToIndex task, removed some sender params, updated derivation import paths, added Database::remove_wallet, and set QualifiedIdentity.network where constructed.
Token backend & builders
src/backend_task/tokens/*.rs, src/backend_task/mod.rs
Consolidated pattern matching and flattened conditionals in mint/claim/burn/transfer flows, made token pricing schedule optional via builder chaining, and added MNList/ZK outputs; set DocumentV0.creator_id = None where constructed.
Settings persistence / .env
.env.example, src/config.rs
Add and persist per-network core ZMQ endpoint entries in environment files and NetworkConfig.
UI Component Framework
src/ui/components/component_trait.rs, src/ui/components/amount_input.rs, src/ui/components/confirmation_dialog.rs, src/ui/components/identity_selector.rs, src/ui/components/mod.rs, src/ui/components/styled.rs
Add Component/ComponentResponse traits; add AmountInput, ConfirmationDialog, IdentitySelector components; re-export traits and dialog in components modules.
UI Panels, helpers & theme
src/ui/components/*_panel.rs, src/ui/helpers.rs, src/ui/theme.rs
New subscreen chooser panels and left/top panel layout changes, richer key label formats, new layout constant, detect_system_theme now returns Result, and primary_button_stroke helper added.
UI screens wide refactor
src/ui/tokens/**, src/ui/identities/**, src/ui/contracts_documents/**, src/ui/mod.rs, src/app.rs
Migrate many screens to use Amount/AmountInput, ConfirmationDialog, IdentitySelector, and Component trait; replace boolean popup flags with optional ConfirmationDialog; add known_identities and expanded-state flags; add Dashpay/GroveSTARK/MasternodeListDiff screens and RootScreenType variants.
Core P2P integration
src/backend_task/mnlist.rs, other callers
Integrate CoreP2PHandler into MNList fetching flows and add MNList-related BackendTask result variants.

Sequence Diagram(s)

sequenceDiagram
    participant UI as UI Screen
    participant Comp as AmountInput
    participant Dialog as ConfirmationDialog
    participant Backend as BackendTask runner

    UI->>Comp: render / user types amount
    Comp-->>UI: validated Amount or error
    UI->>UI: validate identity/key selection
    UI->>Dialog: create/show dialog (lazy)
    Dialog-->>UI: user confirms or cancels
    alt user confirms
        UI->>Backend: dispatch BackendTask with typed Amount & params
        Backend-->>UI: task queued / result
    else user cancels
        UI->>UI: clear dialog
    end
Loading
sequenceDiagram
    participant App as Application
    participant Ctx as AppContext
    participant DB as Database

    App->>Ctx: get_settings()
    alt cache hit
        Ctx-->>App: cached Settings
    else cache miss
        Ctx->>DB: load settings
        DB-->>Ctx: Settings
        Ctx->>Ctx: store cache
        Ctx-->>App: Settings
    end
    App->>Ctx: update_dash_core_execution_settings(...)
    Ctx->>Ctx: invalidate_settings_cache()
    Ctx->>DB: persist changes
    DB-->>Ctx: Result
Loading
sequenceDiagram
    participant App as App
    participant P2P as CoreP2PHandler
    participant Core as Dash P2P Node

    App->>P2P: new(network, port)
    P2P->>Core: TCP connect
    Core-->>P2P: socket OK
    P2P->>Core: send Version
    Core-->>P2P: recv Version
    P2P->>Core: send Verack
    Core-->>P2P: recv Verack
    Note right of P2P: handshake complete
    App->>P2P: get_dml_diff(...)
    P2P->>Core: send GetMnListDiff
    Core-->>P2P: recv MnListDiff
    P2P-->>App: return MnListDiff
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~150 minutes

Areas requiring extra attention:

  • src/components/core_p2p_handler.rs — protocol framing, checksums, message parsing, timeouts, and error classification.
  • src/model/grovestark_prover.rs and src/backend_task/grovestark.rs — cryptography, signing, proof generation/verification, and serialization/external guarantees.
  • src/context.rs and settings cache — concurrency (RwLock), cache invalidation correctness, and persistence integration.
  • src/app.rs and src/ui/mod.rs — settings-driven initialization, new RootScreenType variants, per-network ZMQ resolution, and removal of legacy dash-qt detection.
  • UI-wide component integration — Component trait correctness, Amount/AmountInput behavior, dialog lifecycle, and consistent cleanup across screens.
  • Identity/wallet derivation and DB changes — network propagation, derivation import path updates, and Database::remove_wallet behavior.

Possibly related PRs

Suggested reviewers

  • QuantumExplorer

Poem

🐰
I nibble bytes and hop through trees,
I threaded proofs and ZMQs with ease.
Amounts now hum in tidy rows,
Dialogs bloom where confirmation grows.
Hooray — a patch, then off for carrots! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title Check ❓ Inconclusive The pull request title "v0.9.3 to master" is vague and provides no meaningful information about the actual changes in the changeset. While the version number does reference a tag/branch associated with this PR, the title consists solely of version and branch identifiers (metadata) rather than describing what functional or structural changes are being introduced. The changeset contains substantial additions including new UI components (Amount, AmountInput, ConfirmationDialog, IdentitySelector), new screens (DashpayScreen, GroveSTARKScreen, MasternodeListDiffScreen), backend improvements (GroveSTARK and MnList task handling), settings caching, dependency updates, and numerous refinements, but none of these are reflected in the title. This is more of a merge notation than a descriptive PR title. The title should be replaced with a clear, descriptive summary of the main change(s). For example, "Add UI component framework, GroveSTARK/MnList support, and settings caching" or "Add reusable UI components and backend task handlers for crypto operations" would better communicate what the changeset accomplishes. Alternatively, if multiple major features are being introduced simultaneously, consider whether breaking them into separate PRs would improve clarity.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 85.11% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch v0.9.3

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e26aa31 and a5a5bbd.

📒 Files selected for processing (1)
  • src/components/core_zmq_listener.rs (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Error handling uses thiserror for structured error types.

Files:

  • src/components/core_zmq_listener.rs
🧬 Code graph analysis (1)
src/components/core_zmq_listener.rs (2)
src/context.rs (1)
  • new (86-197)
src/app.rs (1)
  • new (152-505)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Build and Release Dash Evo Tool (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-gnu, x86_64-linux)
  • GitHub Check: Build and Release Dash Evo Tool (Windows, windows-latest, x86_64-pc-windows-msvc, windows, .exe)
  • GitHub Check: Build and Release Dash Evo Tool (macos-arm64, macos-latest, aarch64-apple-darwin, arm64-mac)
  • GitHub Check: Build and Release Dash Evo Tool (linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, arm64-...
  • GitHub Check: Build and Release Dash Evo Tool (macos-x86_64, macos-13, x86_64-apple-darwin, x86_64-mac)
  • GitHub Check: Clippy
  • GitHub Check: Test Suite
🔇 Additional comments (3)
src/components/core_zmq_listener.rs (3)

3-3: LGTM: ChainLock import added.

The import correctly includes ChainLock alongside Block and InstantLock for the updated ZMQ message handling.


35-39: LGTM: Enum variant correctly expanded.

The ChainLockedBlock variant now properly carries both Block and ChainLock, matching the updated deserialization logic in both platform implementations.


342-381: LGTM: Windows ChainLock deserialization correctly implemented.

The Windows path now properly deserializes both Block and ChainLock and sends them together in the ZMQMessage, addressing the previous critical review comment. The error messages are correctly labeled. The topic string will be corrected once the constant at line 55 is updated to "rawchainlocksig".


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.

Copy link
Copy Markdown
Contributor

@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: 45

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (22)
src/ui/tokens/tokens_screen/data_contract_json_pop_up.rs (1)

62-62: Fix incorrect method call: should use available_width() not available_height().

Line 62 uses available_height() to constrain the maximum width, which is a logic error. This should be available_width() to properly constrain the horizontal dimension.

Apply this diff:

-                        .max_width(ui.available_height() - 20.0)
+                        .max_width(ui.available_width() - 20.0)
src/ui/components/left_panel.rs (1)

20-21: Per‑frame texture thrash: use persisted cache, not temp.

get_temp/insert_temp data is cleared every frame; icons will reload and re-upload to GPU continuously. Persist across frames to avoid jank and leaks.

Apply this change:

-    ctx.data_mut(|d| d.get_temp::<TextureHandle>(egui::Id::new(path)))
+    ctx.data_mut(|d| d.get_persisted::<TextureHandle>(egui::Id::new(path)))
@@
-                    // Cache the texture
-                    ctx.data_mut(|d| d.insert_temp(egui::Id::new(path), texture.clone()));
+                    // Persist for the app lifetime (survives frames)
+                    ctx.data_mut(|d| d.insert_persisted(egui::Id::new(path), texture.clone()));

Alternatively, store TextureHandle(s) in AppContext or use egui_extras::RetainedImage.

Also applies to: 30-38

src/backend_task/tokens/query_tokens.rs (1)

29-31: Avoid panics in backend tasks: propagate query construction errors instead of expect().

Replace expect("create query")/expect("create desc query") with map_err and early-return. Panics here would crash the UI thread path.

Apply this diff:

-        let mut kw_query =
-            DocumentQuery::new(self.keyword_search_contract.clone(), "contractKeywords")
-                .expect("create query");
+        let mut kw_query =
+            DocumentQuery::new(self.keyword_search_contract.clone(), "contractKeywords")
+                .map_err(|e| format!("create keywords query: {e}"))?;-            let mut desc_query =
-                DocumentQuery::new(self.keyword_search_contract.clone(), "shortDescription")
-                    .expect("create desc query");
+            let mut desc_query =
+                DocumentQuery::new(self.keyword_search_contract.clone(), "shortDescription")
+                    .map_err(|e| format!("create description query: {e}"))?;

Also applies to: 71-73

src/components/core_zmq_listener.rs (1)

47-56: Critical: topic mismatch (subscribing “rawchainlock”, handling “rawchainlocksig”).

Currently you subscribe to CHAIN_LOCKED_BLOCK_MSG="rawchainlock" but match on "rawchainlocksig", so ChainLock messages are never handled. Update the constant and subscription.

Apply this diff:

-#[cfg(not(target_os = "windows"))]
-pub const CHAIN_LOCKED_BLOCK_MSG: &[u8; 12] = b"rawchainlock";
+#[cfg(not(target_os = "windows"))]
+pub const CHAIN_LOCKED_BLOCK_MSG: &[u8; 14] = b"rawchainlocksig";-            // Subscribe to the "rawtxlocksig" events.
+            // Subscribe to the chainlock signature events.
             socket
                 .set_subscribe(CHAIN_LOCKED_BLOCK_MSG)
-                .expect("Failed to subscribe to rawchainlock");
+                .expect("Failed to subscribe to rawchainlocksig");

Also applies to: 99-107, 140-181

src/ui/tokens/tokens_screen/distributions.rs (3)

550-551: Also saturate products in Stepwise preview text.

Same overflow risk for block/time/epoch = step_position * interval.

Apply this diff:

-                                                let block = step_position * perpetual_dist_interval_input;
+                                                let block = step_position.saturating_mul(perpetual_dist_interval_input);-                                                let time = step_position * perpetual_dist_interval_input;
+                                                let time = step_position.saturating_mul(perpetual_dist_interval_input);-                                                let epoch = step_position * perpetual_dist_interval_input;
+                                                let epoch = step_position.saturating_mul(perpetual_dist_interval_input);

Also applies to: 564-565, 578-579


787-790: Fix sanitizer type: base amount (b) is u64 but sanitize_i64 is used.

Use sanitize_u64 to match the specified type.

Apply this diff:

-                            ui.text_edit_singleline(&mut self.exp_b_input);
-                            sanitize_i64(&mut self.exp_b_input);
+                            ui.text_edit_singleline(&mut self.exp_b_input);
+                            sanitize_u64(&mut self.exp_b_input);

196-335: Info text for StepDecreasing describes a Random function.

The section under “# StepDecreasingAmount” explains random emissions, which contradicts the selected function and the UI (Random not supported). Replace with a correct description of step‑decreasing emissions.

Apply this diff (illustrative replacement):

-# StepDecreasingAmount
-
-Emits a random number of tokens within a specified range.
-
-`f(x) ∈ [min, max]`
+# StepDecreasingAmount
+
+Emits a fixed amount that decreases by a ratio each interval up to optional bounds.
+
+Formula (per interval i ≥ s):
+    f(i) = max(min_value, b · (1 − n/d)^{i − s})
+
+Parameters:
+    - n/d: decrease per interval (0 < n < d ≤ 65_535)
+    - s: optional start period offset
+    - b: initial emission amount
+    - min_value: optional floor
+
+Use cases:
+    - Tapering rewards over time
+    - Emission halving-like schedules
src/ui/tokens/unfreeze_tokens_screen.rs (1)

522-529: Copy/paste typo: says “Mint” on Unfreeze screen.

Change text to “Unfreeze.”

- "You are signing an existing group Mint so you are not allowed to put a note.",
+ "You are signing an existing group Unfreeze so you are not allowed to put a note.",
src/model/qualified_identity/encrypted_key_storage.rs (3)

28-33: Make serialization portable and bounded (use u32, add cap).

Using usize in encoded length is arch‑dependent and unbounded. Switch to u32 with a sane max to avoid compatibility/OOM issues.

--- a/src/model/qualified_identity/encrypted_key_storage.rs
+++ b/src/model/qualified_identity/encrypted_key_storage.rs
@@ -26,9 +26,14 @@ impl Encode for WalletDerivationPath {
-        // Encode the length of the `DerivationPath`
-        self.derivation_path.len().encode(encoder)?;
+        // Encode the length of the `DerivationPath` (portable)
+        let path_len_u32 = u32::try_from(self.derivation_path.len())
+            .map_err(|_| EncodeError::OtherString("DerivationPath too long".into()))?;
+        path_len_u32.encode(encoder)?;
@@ -60,7 +65,12 @@ impl Decode for WalletDerivationPath {
-        // Decode the length of the `DerivationPath`
-        let path_len = usize::decode(decoder)?;
+        // Decode the length of the `DerivationPath` (portable) + bound check
+        let path_len_u32 = u32::decode(decoder)?;
+        let path_len = path_len_u32 as usize;
+        // hard cap prevents pathological allocations
+        const MAX_PATH_LEN: usize = 128;
+        if path_len > MAX_PATH_LEN {
+            return Err(DecodeError::OtherString("DerivationPath too long".into()));
+        }
@@ -100,7 +110,12 @@ impl<'de> BorrowDecode<'de> for WalletDerivationPath {
-        // Decode the length of the `DerivationPath`
-        let path_len = usize::decode(decoder)?;
+        // Decode the length of the `DerivationPath` (portable) + bound check
+        let path_len_u32 = u32::decode(decoder)?;
+        let path_len = path_len_u32 as usize;
+        const MAX_PATH_LEN: usize = 128;
+        if path_len > MAX_PATH_LEN {
+            return Err(DecodeError::OtherString("DerivationPath too long".into()));
+        }

Also applies to: 62-69, 100-107


141-166: Do not print private keys. Redact clear key material.

fmt::Display currently hex‑prints clear keys, which can leak secrets via logs/UI. Redact instead.

 impl fmt::Display for PrivateKeyData {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            PrivateKeyData::Clear(data) => {
-                write!(f, "Clear({})", hex::encode(data))
-            }
+            PrivateKeyData::Clear(_data) => write!(f, "Clear([redacted])"),
             PrivateKeyData::Encrypted(data) => {
                 write!(f, "Encrypted({} bytes)", data.len())
             }
-            PrivateKeyData::AlwaysClear(data) => {
-                write!(f, "Clear({})", hex::encode(data))
-            }
+            PrivateKeyData::AlwaysClear(_data) => write!(f, "Clear([redacted])"),
             PrivateKeyData::AtWalletDerivationPath(WalletDerivationPath {
                 wallet_seed_hash: wallet_seed,
                 derivation_path,
             }) => {
                 write!(
                     f,
                     "AtWalletDerivationPath({}/{})",
                     hex::encode(wallet_seed),
                     derivation_path
                 )
             }
         }
     }
 }

251-275: Adopt structured errors (thiserror) instead of String.

Public APIs return Result<_, String>. Per repo guideline, prefer thiserror enums for clarity and downstream handling. I can draft a minimal KeyStorageError and update signatures if desired.

As per coding guidelines.

Also applies to: 277-323

src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs (1)

101-107: Fix label/content mismatch.

Button copies the full payment URI; label says “Copy Address”.

-        if ui.button("Copy Address").clicked() {
+        if ui.button("Copy Payment URI").clicked() {
             if let Err(e) = copy_to_clipboard(pay_uri.as_str()) {
src/ui/tokens/tokens_screen/groups.rs (1)

180-240: Fix removal loop: removing then incrementing skips the next member.

When you remove at index j, the next element shifts into j. Incrementing unconditionally skips it.

-                        let mut j = 0;
-                        while j < group_ui.members.len() {
-                            let mut member = group_ui.members[j].clone();
+                        let mut j = 0;
+                        while j < group_ui.members.len() {
+                            let mut member = group_ui.members[j].clone();
+                            let mut removed = false;
@@
-                                if ui.button("Remove Member").clicked() {
-                                    group_ui.members.remove(j);// return so we skip the assignment at the end
-                                } else {
-                                    // Only assign back if we didn’t remove
-                                    group_ui.members[j] = member;
-                                }
+                                if ui.button("Remove Member").clicked() {
+                                    group_ui.members.remove(j);
+                                    removed = true;
+                                } else {
+                                    group_ui.members[j] = member;
+                                }
                             });
 
-                            j += 1;
+                            if !removed {
+                                j += 1;
+                            }
                         }
src/ui/tokens/direct_token_purchase_screen.rs (1)

360-365: Fix completion trigger typo.

“purchaseed” prevents status from reaching Complete.

-                if message.contains("Successfully purchaseed tokens") || message == "PurchaseTokens"
+                if message.contains("Successfully purchased tokens") || message == "PurchaseTokens"
src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs (3)

16-23: Release wallet lock before Core RPC to avoid lock-order deadlocks.

You hold a write lock on Wallet while also taking a read lock on core_client and performing RPC. This mixed lock ordering across subsystems risks deadlock and blocks the UI thread. Drop the wallet lock immediately after deriving the receive address.

Apply this minimal change:

                 let receive_address = wallet.receive_address(
                     self.app_context.network,
                     false,
                     Some(&self.app_context),
                 )?;
+                // Release wallet lock before contacting Core
+                drop(wallet);
 
                 // Import address to Core if needed for monitoring
                 let core_client = self

Also applies to: 25-31


73-79: Button label/content mismatch (address vs URI).

You copy pay_uri but the button says "Copy Address". Either copy address.to_string() or rename the button.

Apply one of:

-        if ui.button("Copy Address").clicked() {
-            if let Err(e) = copy_to_clipboard(pay_uri.as_str()) {
+        if ui.button("Copy Payment URI").clicked() {
+            if let Err(e) = copy_to_clipboard(pay_uri.as_str()) {

or

-        if ui.button("Copy Address").clicked() {
-            if let Err(e) = copy_to_clipboard(pay_uri.as_str()) {
+        if ui.button("Copy Address").clicked() {
+            if let Err(e) = copy_to_clipboard(address.to_string().as_str()) {

57-57: Increase amount precision from 4 to 8 decimal places to match Dash payment URI specification.

Dash URIs should support up to 8 decimal places (smallest unit is the duff = 10^-8 Dash). The current format {:.4} truncates precision and can cause payment errors.

Update both occurrences:

  • src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs:57
  • src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs:84

Change: format!("{}?amount={:.4}", ...)format!("{}?amount={:.8}", ...)

src/ui/identities/keys/key_info_screen.rs (1)

593-616: Bug: use-after-move on Result (compile error).

validation_result is moved by if let Err(err) pattern, then unwrapped. Capture once and branch on the boolean.

Apply:

-        let validation_result = self
-            .key
-            .validate_private_key_bytes(&private_key_bytes, self.app_context.network);
-        if let Err(err) = validation_result {
-            self.error_message = Some(format!("Issue verifying private key {}", err));
-        } else if validation_result.unwrap() {
+        let is_valid = match self
+            .key
+            .validate_private_key_bytes(&private_key_bytes, self.app_context.network)
+        {
+            Ok(v) => v,
+            Err(err) => {
+                self.error_message = Some(format!("Issue verifying private key {}", err));
+                return;
+            }
+        };
+        if is_valid {
             // If valid, store the private key in the context and reset the input field
src/model/qualified_identity/mod.rs (1)

606-611: Don’t panic on inconsistent wallet index.
This will crash the UI. Return an error instead so callers can surface it gracefully.

Apply:

- if self.wallet_index.is_some_and(|v| v != wallet_index) {
-     panic!(
-         "Inconsistent wallet index found: {:?} vs {:?}",
-         self.wallet_index, wallet_index
-     );
- };
+ if self.wallet_index.is_some_and(|v| v != wallet_index) {
+     return Err(format!(
+         "Inconsistent wallet index found: {:?} vs {:?}",
+         self.wallet_index, wallet_index
+     ));
+ };
src/ui/tokens/pause_tokens_screen.rs (1)

92-111: User-facing text refers to “Burning” on a Pause screen.
Replace “Burning” with “Pausing” and adjust the permission text accordingly.

Suggested replacements:

  • "Pausing is not allowed on this token"
  • "You are not allowed to pause this token. Only the contract owner is."
  • "You are not allowed to pause this token."
- error_message = Some("Burning is not allowed on this token".to_string());
+ error_message = Some("Pausing is not allowed on this token".to_string());
...
- "You are not allowed to burn this token. Only the contract owner is."
+ "You are not allowed to pause this token. Only the contract owner is."
...
- error_message = Some("You are not allowed to burn this token".to_string());
+ error_message = Some("You are not allowed to pause this token".to_string());

Also applies to: 96-105, 107-112

src/ui/identities/add_existing_identity_screen.rs (1)

668-693: Time unit mismatch: TimestampMillis but storing seconds.

AddIdentityStatus::WaitingForResult(TimestampMillis) is populated with as_secs(), yet later treated as seconds. Store milliseconds and compute elapsed accordingly to match the type.

-            let now = SystemTime::now()
-                .duration_since(UNIX_EPOCH)
-                .expect("Time went backwards")
-                .as_secs();
+            let now = SystemTime::now()
+                .duration_since(UNIX_EPOCH)
+                .expect("Time went backwards")
+                .as_millis() as u64;
             self.add_identity_status = AddIdentityStatus::WaitingForResult(now);

And when displaying elapsed:

-                            let now = SystemTime::now()
+                            let now = SystemTime::now()
                                 .duration_since(UNIX_EPOCH)
                                 .expect("Time went backwards")
-                                .as_secs();
-                            let elapsed_seconds = now - start_time;
+                                .as_millis() as u64;
+                            let elapsed_ms = now - start_time;
+                            let elapsed_seconds = elapsed_ms / 1000;
src/ui/components/contract_chooser_panel.rs (1)

542-604: Context menu never opens (state is never set)

chooser_state.show_context_menu/right_click_contract_id/context_menu_position are read but never set after replacing collapsing headers. This regresses right-click functionality.

Wire secondary clicks on the contract header to set these fields.

- if render_collapsing_header(ui, &display_name, is_expanded, is_selected_contract, 0) {
+ let header_clicked = render_collapsing_header(ui, &display_name, is_expanded, is_selected_contract, 0);
+ // Detect right-click anywhere on the last header row
+ if ui.ctx().input(|i| i.pointer.secondary_clicked()) {
+     if let Some(pos) = ui.ctx().pointer_interact_pos() {
+         chooser_state.right_click_contract_id = Some(contract_id.clone());
+         chooser_state.context_menu_position = pos;
+         chooser_state.show_context_menu = true;
+     }
+ }
+ if header_clicked {
     if is_expanded {
         chooser_state.expanded_contracts.remove(&contract_id);
     } else {
         chooser_state.expanded_contracts.insert(contract_id.clone());
     }
 }

Also, close the menu using the actual window rect:

-            egui::Window::new("Contract Menu")
-                ...
-                .show(ctx, |ui| { ... });
+            if let Some(win) = egui::Window::new("Contract Menu")
+                .id(egui::Id::new("contract_context_menu"))
+                .title_bar(false)
+                .resizable(false)
+                .collapsible(false)
+                .fixed_pos(chooser_state.context_menu_position)
+                .show(ctx, |ui| { /* ... */ }) {
+                if ctx.input(|i| i.pointer.any_click()) {
+                    if let Some(pos) = ctx.pointer_interact_pos() {
+                        if !win.response.rect.contains(pos) {
+                            chooser_state.show_context_menu = false;
+                        }
+                    }
+                }
+            }

Comment on lines +34 to +38
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace deprecated actions-rs/toolchain action.

The actions-rs/toolchain@v1 action is no longer maintained and its runner is too old for GitHub Actions. Consider using dtolnay/rust-toolchain instead, which is actively maintained.

Apply this diff:

-      - name: Install Rust toolchain
-        uses: actions-rs/toolchain@v1
-        with:
-          toolchain: stable
-          override: true
+      - name: Install Rust toolchain
+        uses: dtolnay/rust-toolchain@stable
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
🧰 Tools
🪛 actionlint (1.7.8)

35-35: the runner of "actions-rs/toolchain@v1" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🤖 Prompt for AI Agents
In .github/workflows/tests.yml around lines 34 to 38, the workflow uses the
deprecated actions-rs/toolchain@v1; replace that step with the maintained
dtolnay/rust-toolchain action. Change the uses to dtolnay/rust-toolchain@v1 and
pass the equivalent inputs to select the stable toolchain (e.g., toolchain:
stable) and preserve the override behavior using the new action's inputs
(consult dtolnay/rust-toolchain docs for exact input names), ensuring the
workflow uses the up-to-date runner support.

Comment on lines +54 to +64
- name: Run tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all-features --workspace

- name: Run doc tests
uses: actions-rs/cargo@v1
with:
command: test
args: --doc --all-features --workspace
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace deprecated actions-rs/cargo actions.

The actions-rs/cargo@v1 action is no longer maintained and its runner is too old for GitHub Actions. Replace with direct cargo commands.

Apply this diff:

       - name: Run tests
-        uses: actions-rs/cargo@v1
-        with:
-          command: test
-          args: --all-features --workspace
+        run: cargo test --all-features --workspace

       - name: Run doc tests
-        uses: actions-rs/cargo@v1
-        with:
-          command: test
-          args: --doc --all-features --workspace
+        run: cargo test --doc --all-features --workspace
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Run tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all-features --workspace
- name: Run doc tests
uses: actions-rs/cargo@v1
with:
command: test
args: --doc --all-features --workspace
- name: Run tests
run: cargo test --all-features --workspace
- name: Run doc tests
run: cargo test --doc --all-features --workspace
🧰 Tools
🪛 actionlint (1.7.8)

55-55: the runner of "actions-rs/cargo@v1" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)


61-61: the runner of "actions-rs/cargo@v1" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🤖 Prompt for AI Agents
.github/workflows/tests.yml around lines 54 to 64: the workflow uses the
deprecated actions-rs/cargo@v1 action for running tests; replace those steps
with direct run steps that invoke the cargo CLI (e.g., use a step with name "Run
tests" and run: cargo test --all-features --workspace, and a step with name "Run
doc tests" and run: cargo test --doc --all-features --workspace), ensuring the
steps run on the default runner environment and keeping any needed environment
or working-directory settings from the original workflow.


### 2. Lazily initialize the component

Inside your screen's `show()` method or simiar:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typo: “simiar” → “similar”.

Minor doc polish for clarity.

Apply this diff:

-Inside your screen's `show()` method or simiar:
+Inside your screen's `show()` method or similar:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Inside your screen's `show()` method or simiar:
Inside your screen's `show()` method or similar:
🧰 Tools
🪛 LanguageTool

[grammar] ~27-~27: Ensure spelling is correct
Context: ...Inside your screen's show() method or simiar: rust let amount_widget = self.amount_widget.get_or_insert_with(|| { AmountInput::new(amount_type) .with_label("Amount:") }); ### 3. Show component and handle updates Af...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
In doc/COMPONENT_DESIGN_PATTERN.md around line 27, there's a typo: change
"simiar" to "similar" in the sentence "Inside your screen's `show()` method or
simiar:" so it reads "Inside your screen's `show()` method or similar:" and
ensure spacing/punctuation remains consistent.

Comment thread src/app.rs
Comment on lines +251 to 330
if let (Network::Testnet, Some(testnet_app_context)) =
(chosen_network, testnet_app_context.as_ref())
{
identities_screen = IdentitiesScreen::new(testnet_app_context);
dpns_active_contests_screen =
DPNSScreen::new(testnet_app_context, DPNSSubscreen::Active);
dpns_past_contests_screen = DPNSScreen::new(testnet_app_context, DPNSSubscreen::Past);
dpns_my_usernames_screen = DPNSScreen::new(testnet_app_context, DPNSSubscreen::Owned);
dpns_scheduled_votes_screen =
DPNSScreen::new(testnet_app_context, DPNSSubscreen::ScheduledVotes);
transition_visualizer_screen = TransitionVisualizerScreen::new(testnet_app_context);
proof_visualizer_screen = ProofVisualizerScreen::new(testnet_app_context);
document_visualizer_screen = DocumentVisualizerScreen::new(testnet_app_context);
contract_visualizer_screen = ContractVisualizerScreen::new(testnet_app_context);
document_query_screen = DocumentQueryScreen::new(testnet_app_context);
grovestark_screen = GroveSTARKScreen::new(testnet_app_context);
wallets_balances_screen = WalletsBalancesScreen::new(testnet_app_context);
proof_log_screen = ProofLogScreen::new(testnet_app_context);
platform_info_screen = PlatformInfoScreen::new(testnet_app_context);
masternode_list_diff_screen = MasternodeListDiffScreen::new(testnet_app_context);
contracts_dashpay_screen = DashpayScreen::new(testnet_app_context);
tokens_balances_screen =
TokensScreen::new(testnet_app_context, TokensSubscreen::MyTokens);
token_search_screen =
TokensScreen::new(testnet_app_context, TokensSubscreen::SearchTokens);
token_creator_screen =
TokensScreen::new(testnet_app_context, TokensSubscreen::TokenCreator);
} else if let (Network::Devnet, Some(devnet_app_context)) =
(chosen_network, devnet_app_context.as_ref())
{
identities_screen = IdentitiesScreen::new(devnet_app_context);
dpns_active_contests_screen =
DPNSScreen::new(devnet_app_context, DPNSSubscreen::Active);
dpns_past_contests_screen = DPNSScreen::new(devnet_app_context, DPNSSubscreen::Past);
dpns_my_usernames_screen = DPNSScreen::new(devnet_app_context, DPNSSubscreen::Owned);
dpns_scheduled_votes_screen =
DPNSScreen::new(devnet_app_context, DPNSSubscreen::ScheduledVotes);
transition_visualizer_screen = TransitionVisualizerScreen::new(devnet_app_context);
proof_visualizer_screen = ProofVisualizerScreen::new(devnet_app_context);
document_visualizer_screen = DocumentVisualizerScreen::new(devnet_app_context);
document_query_screen = DocumentQueryScreen::new(devnet_app_context);
masternode_list_diff_screen = MasternodeListDiffScreen::new(devnet_app_context);
contract_visualizer_screen = ContractVisualizerScreen::new(devnet_app_context);
grovestark_screen = GroveSTARKScreen::new(devnet_app_context);
wallets_balances_screen = WalletsBalancesScreen::new(devnet_app_context);
proof_log_screen = ProofLogScreen::new(devnet_app_context);
platform_info_screen = PlatformInfoScreen::new(devnet_app_context);
tokens_balances_screen =
TokensScreen::new(devnet_app_context, TokensSubscreen::MyTokens);
token_search_screen =
TokensScreen::new(devnet_app_context, TokensSubscreen::SearchTokens);
token_creator_screen =
TokensScreen::new(devnet_app_context, TokensSubscreen::TokenCreator);
} else if let (Network::Regtest, Some(local_app_context)) =
(chosen_network, local_app_context.as_ref())
{
identities_screen = IdentitiesScreen::new(local_app_context);
dpns_active_contests_screen = DPNSScreen::new(local_app_context, DPNSSubscreen::Active);
dpns_past_contests_screen = DPNSScreen::new(local_app_context, DPNSSubscreen::Past);
dpns_my_usernames_screen = DPNSScreen::new(local_app_context, DPNSSubscreen::Owned);
dpns_scheduled_votes_screen =
DPNSScreen::new(local_app_context, DPNSSubscreen::ScheduledVotes);
transition_visualizer_screen = TransitionVisualizerScreen::new(local_app_context);
proof_visualizer_screen = ProofVisualizerScreen::new(local_app_context);
document_visualizer_screen = DocumentVisualizerScreen::new(local_app_context);
contract_visualizer_screen = ContractVisualizerScreen::new(local_app_context);
document_query_screen = DocumentQueryScreen::new(local_app_context);
grovestark_screen = GroveSTARKScreen::new(local_app_context);
wallets_balances_screen = WalletsBalancesScreen::new(local_app_context);
masternode_list_diff_screen = MasternodeListDiffScreen::new(local_app_context);
proof_log_screen = ProofLogScreen::new(local_app_context);
platform_info_screen = PlatformInfoScreen::new(local_app_context);
contracts_dashpay_screen = DashpayScreen::new(local_app_context);
tokens_balances_screen =
TokensScreen::new(local_app_context, TokensSubscreen::MyTokens);
token_search_screen =
TokensScreen::new(local_app_context, TokensSubscreen::SearchTokens);
token_creator_screen =
TokensScreen::new(local_app_context, TokensSubscreen::TokenCreator);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Fallback if chosen_network context is unavailable.

If settings.network is Testnet/Devnet/Regtest but corresponding AppContext::new returned None, current_app_context() will later panic on expect(...). Add a fallback to Dash (and persist the change) to avoid boot-time crashes.

-        } else if let (Network::Regtest, Some(local_app_context)) = (chosen_network, local_app_context.as_ref()) {
+        } else if let (Network::Regtest, Some(local_app_context)) = (chosen_network, local_app_context.as_ref()) {
             // ... existing branches ...
+        } else {
+            // Fallback to mainnet if selected network is unavailable
+            network_chooser_screen.current_network = Network::Dash;
+            // Optionally: persist change
+            let _ = mainnet_app_context.update_settings(settings.root_screen_type);
+            // Screens already constructed for mainnet above
+        }

🏁 Script executed:

#!/bin/bash
# Find the current_app_context() function implementation and its usages
rg -nP "fn\s+current_app_context\s*\(" -A 10

Length of output: 1542


🏁 Script executed:

#!/bin/bash
# Check broader context around the reviewed code section in app.rs
# Get lines 200-350 to see initialization and what happens after the match
sed -n '200,350p' src/app.rs

Length of output: 9202


🏁 Script executed:

#!/bin/bash
# Search for all invocations of current_app_context
rg -nC 3 "current_app_context\(\)"

Length of output: 7044


🏁 Script executed:

#!/bin/bash
# Look for mainnet initialization to see if there's a fallback default
rg -nP "mainnet_app_context.*=" -B 2 -A 2 | head -50

Length of output: 615


🏁 Script executed:

#!/bin/bash
# Check what happens if all contexts are None - look for panic or expect calls
rg -nP "expect\(|panic\(" src/app.rs | head -20

Length of output: 921


🏁 Script executed:

#!/bin/bash
# Find AppContext::new() implementation to see what causes None
rg -nP "impl\s+AppContext" -A 5 | head -40

Length of output: 2633


🏁 Script executed:

#!/bin/bash
# Search for AppContext::new() definition
rg -nP "fn new\(" -B 5 -A 15 src/ --type=rs | grep -A 15 "AppContext::new"

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Check where AppContext::new returns Option or Result
fd -e rs -type f | xargs grep -l "AppContext::new" | head -5

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Look at the full initialization in app.rs, especially lines around 171-200
sed -n '160,200p' src/app.rs

Length of output: 1332


🏁 Script executed:

#!/bin/bash
# Check what happens after the if/else branches - lines 330-380
sed -n '330,380p' src/app.rs

Length of output: 2211


🏁 Script executed:

#!/bin/bash
# Find where self.chosen_network is assigned
rg -nP "chosen_network\s*=" src/app.rs | head -20

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Search for App struct definition to see chosen_network field
rg -nP "struct\s+App\s*\{" -A 30 src/app.rs

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for lines after the if/else chain to see if chosen_network is explicitly set
sed -n '240,250p' src/app.rs

Length of output: 412


🏁 Script executed:

#!/bin/bash
# Check if NetworkChooserScreen.new initializes chosen_network and how
rg -nP "chosen_network" src/app.rs -B 2 -A 2

Length of output: 1982


Add else clause to reset network fallback, but ensure local chosen_network variable is also updated.

The issue is confirmed: if settings.network persists Testnet/Devnet/Regtest but that context is None, current_app_context() will panic on expect(). However, the suggested fix is incomplete—it updates network_chooser_screen.current_network and settings, but the local chosen_network variable (line 248) is never reset. This variable is assigned to self.chosen_network at line 489, so without resetting it, self.chosen_network retains the unavailable network value and the panic still occurs.

The else clause must also include: chosen_network = Network::Dash;

🤖 Prompt for AI Agents
In src/app.rs around lines 251 to 330, the added else branch resets
network_chooser_screen.current_network and settings but fails to update the
local chosen_network variable, so self.chosen_network (set later) can still hold
an unavailable network and cause a panic; modify that else branch to also set
chosen_network = Network::Dash (the local variable declared earlier) so the
function and eventual assignment to self.chosen_network reflect the fallback
network.

Comment on lines +5 to +9
pub async fn run_grovestark_task(
task: GroveSTARKTask,
sdk: &Sdk,
) -> Result<BackendTaskSuccessResult, String> {
match task {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Harden error handling and secrets: stop deriving Debug/PartialEq on private keys; use typed errors.

  • Avoid logging/comparing secrets: remove Debug/PartialEq; provide redacted Debug impl.
  • Switch from String errors to a thiserror enum per guidelines.
+use thiserror::Error;
+use std::fmt;
@@
-pub async fn run_grovestark_task(
+pub async fn run_grovestark_task(
     task: GroveSTARKTask,
     sdk: &Sdk,
-) -> Result<BackendTaskSuccessResult, String> {
+) -> Result<BackendTaskSuccessResult, GroveStarkTaskError> {
@@
-                Ok(proof_data) => Ok(BackendTaskSuccessResult::GeneratedZKProof(proof_data)),
-                Err(e) => Err(format!("Failed to generate proof: {}", e)),
+                Ok(proof_data) => Ok(BackendTaskSuccessResult::GeneratedZKProof(proof_data)),
+                Err(e) => Err(GroveStarkTaskError::Generate(e.to_string())),
             }
@@
-                Ok(is_valid) => Ok(BackendTaskSuccessResult::VerifiedZKProof(
+                Ok(is_valid) => Ok(BackendTaskSuccessResult::VerifiedZKProof(
                     is_valid, proof_data,
                 )),
-                Err(e) => Err(format!("Failed to verify proof: {}", e)),
+                Err(e) => Err(GroveStarkTaskError::Verify(e.to_string())),
             }
         }
     }
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Clone)]
 pub enum GroveSTARKTask {
@@
     VerifyProof {
         proof_data: ProofDataOutput,
     },
 }
+
+#[derive(Debug, Error)]
+pub enum GroveStarkTaskError {
+    #[error("Proof generation failed: {0}")]
+    Generate(String),
+    #[error("Proof verification failed: {0}")]
+    Verify(String),
+}
+
+// Redacted Debug to avoid leaking keys
+impl fmt::Debug for GroveSTARKTask {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            GroveSTARKTask::GenerateProof {
+                identity_id,
+                contract_id,
+                document_type,
+                document_id,
+                key_id,
+                ..
+            } => f
+                .debug_struct("GroveSTARKTask::GenerateProof")
+                .field("identity_id", identity_id)
+                .field("contract_id", contract_id)
+                .field("document_type", document_type)
+                .field("document_id", document_id)
+                .field("key_id", key_id)
+                .field("private_key", &"<redacted>")
+                .field("public_key", &"<redacted>")
+                .finish(),
+            GroveSTARKTask::VerifyProof { .. } => {
+                f.debug_struct("GroveSTARKTask::VerifyProof").finish()
+            }
+        }
+    }
+}

Also applies to: 34-36, 41-46, 51-65

🤖 Prompt for AI Agents
In src/backend_task/grovestark.rs around lines 5-9 (and also apply to lines
34-36, 41-46, 51-65), the code exposes secrets by deriving Debug/PartialEq on
private key types and returns plain String errors; remove Debug and PartialEq
derives from any private key structs, implement a custom Debug that redacts
secret material (e.g., prints "<redacted>") and keep Eq/PartialEq only where
safe, and replace functions returning Result<..., String> with a typed error
enum using thiserror::Error to categorize failures (parsing, crypto, sdk, etc.);
update all error returns and matches to use the new enum and map underlying
errors into it with contextual messages, ensuring no secret values are included
in error Debug/Display output.

Comment on lines +230 to +241
AppAction::BackendTask(BackendTask::TokenTask(Box::new(TokenTask::PauseTokens {
actor_identity: self.identity.clone(),
data_contract,
token_position: self.identity_token_info.token_position,
signing_key: self.selected_key.clone().expect("No key selected"),
public_note: if self.group_action_id.is_some() {
None
} else {
self.public_note.clone()
},
group_info,
})))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Gracefully handle missing signing key instead of expect.
Avoid crashing when no key is selected.

Apply:

- signing_key: self.selected_key.clone().expect("No key selected"),
+ signing_key: match self.selected_key.clone() {
+     Some(k) => k,
+     None => {
+         self.error_message = Some("Please select a signing key.".into());
+         self.status = PauseTokensStatus::ErrorMessage("No key selected".into());
+         return AppAction::None;
+     }
+ },

Also consider disabling the “Pause” button until a key is selected.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
AppAction::BackendTask(BackendTask::TokenTask(Box::new(TokenTask::PauseTokens {
actor_identity: self.identity.clone(),
data_contract,
token_position: self.identity_token_info.token_position,
signing_key: self.selected_key.clone().expect("No key selected"),
public_note: if self.group_action_id.is_some() {
None
} else {
self.public_note.clone()
},
group_info,
})))
AppAction::BackendTask(BackendTask::TokenTask(Box::new(TokenTask::PauseTokens {
actor_identity: self.identity.clone(),
data_contract,
token_position: self.identity_token_info.token_position,
signing_key: match self.selected_key.clone() {
Some(k) => k,
None => {
self.error_message = Some("Please select a signing key.".into());
self.status = PauseTokensStatus::ErrorMessage("No key selected".into());
return AppAction::None;
}
},
public_note: if self.group_action_id.is_some() {
None
} else {
self.public_note.clone()
},
group_info,
})))
🤖 Prompt for AI Agents
In src/ui/tokens/pause_tokens_screen.rs around lines 230 to 241, the code uses
expect("No key selected") when building the PauseTokens task which will panic if
no signing key is chosen; change this to handle the missing key gracefully by
returning early (e.g., show an error/notification or enqueue a UI-level failure
action) instead of panicking, and ensure the Pause action is only constructed
when selected_key.is_some(); additionally, disable or make the “Pause” button
inert in the UI when selected_key.is_none() so the user cannot trigger the
action without a signing key.

Comment on lines +293 to +299
Some(TokenPricingSchedule::SinglePrice(price_per_smallest_unit)) => {
// Convert price per smallest unit back to price per token for display
let price_per_token = price_per_smallest_unit * decimal_multiplier;
let amount =
Amount::new(price_per_token, DASH_DECIMAL_PLACES).with_unit_name("DASH");
(Some(amount), vec![(None, None)])
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix potential u64 overflow when converting per-unit price to per-token

Multiplying price_per_smallest_unit by decimal_multiplier in u64 can overflow. Use checked/saturating math as used elsewhere (e.g., purchase screen).

-                let price_per_token = price_per_smallest_unit * decimal_multiplier;
+                let price_per_token = (price_per_smallest_unit as u128)
+                    .saturating_mul(decimal_multiplier as u128)
+                    .min(u64::MAX as u128) as u64;

Apply similarly for tiered prices (lines 311-319).

Also applies to: 311-319

Comment on lines +390 to 406
perpetual_dist_interval_input * step_count_input;
if amount == 1 {
"Every Block".to_string()
} else {
format!("Every {} Blocks", amount)
}
}
PerpetualDistributionIntervalTypeUI::TimeBased => {
let amount = perpetual_dist_interval_input * step_count_input;
format!("Every {} {}", amount, self.perpetual_dist_interval_unit.capitalized_label_for_num_amount(amount))
let amount =
perpetual_dist_interval_input * step_count_input;
format!(
"Every {} {}",
amount,
self.perpetual_dist_interval_unit
.capitalized_label_for_num_amount(amount)
)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use saturating_mul for interval math to avoid overflow panics.

Multiplying two user-entered u64s can overflow (panic in debug, wrap in release). Use saturating_mul or checked_mul with a safe fallback.

Apply this diff:

-                                            let amount =
-                                                perpetual_dist_interval_input * step_count_input;
+                                            let amount = perpetual_dist_interval_input
+                                                .saturating_mul(step_count_input);-                                            let amount =
-                                                perpetual_dist_interval_input * step_count_input;
+                                            let amount = perpetual_dist_interval_input
+                                                .saturating_mul(step_count_input);-                                            let amount =
-                                                perpetual_dist_interval_input * step_count_input;
+                                            let amount = perpetual_dist_interval_input
+                                                .saturating_mul(step_count_input);

Also applies to: 398-405, 409-415

🤖 Prompt for AI Agents
In src/ui/tokens/tokens_screen/distributions.rs around lines 390-406 (and also
at 398-405 and 409-415), the code multiplies two user-provided u64 values and
can panic on overflow; replace plain multiplication with saturating_mul (or use
checked_mul with a safe fallback) when computing amount (e.g., amount =
perpetual_dist_interval_input.saturating_mul(step_count_input)) so the UI uses a
saturating result instead of panicking or wrapping; apply the same change to the
other specified line ranges.

Comment on lines +67 to +76
let known_identities = app_context
.load_local_qualified_identities()
.expect("Identities not loaded");

let friend_identities: Vec<(String, Identifier)> = all_identities
.iter()
.filter(|id| id.identity.id() != identity_token_balance.identity_id)
.map(|id| {
let alias = id
.alias
.clone()
.unwrap_or_else(|| id.identity.id().to_string(Encoding::Base58));
(alias, id.identity.id())
})
.collect();

let identity = all_identities
let identity = known_identities
.iter()
.find(|identity| identity.identity.id() == identity_token_balance.identity_id)
.expect("Identity not found")
.clone();
let max_amount = identity_token_balance.balance;
let max_amount = Amount::from(&identity_token_balance);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid panics during screen init; surface a UI error instead

.expect("Identities not loaded") and .expect("Identity not found") will crash the app. Propagate into transfer_tokens_status and show an error.

I can provide a small patch to convert these into user-visible errors and a disabled screen state.

Comment on lines +85 to +88
let frozen_identities = app_context
.load_local_qualified_identities()
.expect("Identities not loaded");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid panic on identities load; surface error to the UI.

Replace expect("Identities not loaded") with handled error (set self.error_message/status) so the screen doesn’t crash in release.

- let frozen_identities = app_context
-     .load_local_qualified_identities()
-     .expect("Identities not loaded");
+ let frozen_identities = match app_context.load_local_qualified_identities() {
+     Ok(ids) => ids,
+     Err(e) => {
+         tracing::error!("Failed to load identities: {e}");
+         Vec::new()
+     }
+ };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let frozen_identities = app_context
.load_local_qualified_identities()
.expect("Identities not loaded");
let frozen_identities = match app_context.load_local_qualified_identities() {
Ok(ids) => ids,
Err(e) => {
tracing::error!("Failed to load identities: {e}");
Vec::new()
}
};
🤖 Prompt for AI Agents
In src/ui/tokens/unfreeze_tokens_screen.rs around lines 85 to 88, the call to
app_context.load_local_qualified_identities() uses expect(...) which will panic
on error; replace this with proper Result handling: match or if let Err(e) => {
self.error_message = Some(e.to_string()); self.status = Some(<appropriate error
status>); return early from the function so the UI shows the error instead of
crashing }, and on Ok(identities) continue normal flow. Ensure you convert the
error to a user-facing string and set the existing UI fields
(error_message/status) used by this screen.

Copy link
Copy Markdown
Contributor

@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: 28

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (20)
src/ui/tokens/tokens_screen/token_creator.rs (1)

1113-1183: Remove duplicate assignment.

Line 1174 duplicates the assignment on line 1171. Both set show_token_creator_confirmation_popup = false.

Apply this diff:

                 action = AppAction::BackendTasks(tasks, BackendTasksExecutionMode::Sequential);
                 self.show_token_creator_confirmation_popup = false;
                 let now = Utc::now().timestamp() as u64;
                 self.token_creator_status = TokenCreatorStatus::WaitingForResult(now);
-                self.show_token_creator_confirmation_popup = false;
                 self.token_creator_confirmation_dialog = None;
             }
src/components/core_zmq_listener.rs (1)

140-180: Non-Windows path updated correctly (except error message).

The non-Windows implementation properly decodes both Block and ChainLock and sends them together. However, the error message at Line 167 mentions "InstantLock" when it should say "ChainLock".

snap/snapcraft.yaml (2)

161-166: Build may fail if ImageMagick isn’t present.

convert is used to create a placeholder icon, but imagemagick isn’t listed in build-packages. Either add it or avoid the dynamic generation.

Apply:

   build-packages:
     - build-essential
     - pkg-config
     ...
     - xkb-data
+    - imagemagick

Or remove the placeholder branch and make the icon a required file.


216-226: Add integrity verification for Dash tarball download.

Dash publishes official SHA256SUMS.asc and GPG signatures on GitHub releases, which must be verified before extraction. The current code downloads and extracts without any verification, creating a supply chain vulnerability.

Add SHA256 verification at lines 216-226 in snap/snapcraft.yaml:

  # Download from official Dash releases
  curl -L "https://github.com/dashpay/dash/releases/download/v${DASH_VERSION}/dashcore-${DASH_VERSION}-${DASH_ARCH}-linux-gnu.tar.gz" -o dash.tar.gz
+ curl -L "https://github.com/dashpay/dash/releases/download/v${DASH_VERSION}/SHA256SUMS.asc" -o SHA256SUMS.asc
+ sha256sum -c <(grep "dashcore-${DASH_VERSION}-${DASH_ARCH}-linux-gnu.tar.gz" SHA256SUMS.asc) || exit 1
  tar -xzf dash.tar.gz

For stronger security, verify the GPG signature on SHA256SUMS.asc using Dash Core developer keys published in the release notes.

src/config.rs (1)

53-62: Returning reference to a temporary (&None) — change return type.

_ => &None returns a reference to a temporary and will not compile safely. Return Option<&NetworkConfig> instead.

Apply this diff (callers must switch to matching on Option<&NetworkConfig>):

-    pub fn config_for_network(&self, network: Network) -> &Option<NetworkConfig> {
-        match network {
-            Network::Dash => &self.mainnet_config,
-            Network::Testnet => &self.testnet_config,
-            Network::Devnet => &self.devnet_config,
-            Network::Regtest => &self.local_config,
-            _ => &None,
-        }
-    }
+    pub fn config_for_network(&self, network: Network) -> Option<&NetworkConfig> {
+        match network {
+            Network::Dash => self.mainnet_config.as_ref(),
+            Network::Testnet => self.testnet_config.as_ref(),
+            Network::Devnet => self.devnet_config.as_ref(),
+            Network::Regtest => self.local_config.as_ref(),
+            _ => None,
+        }
+    }

As per coding guidelines, config responsibilities belong here; this API tweak prevents invalid borrows. Based on learnings.

src/ui/contracts_documents/update_contract_screen.rs (1)

196-203: Avoid unwrap() on identity/key — guard explicitly to prevent UI panic.

Even with upstream checks, selection can be None; unwraps may crash.

Apply this diff to bail out safely:

-                if ui.add(button).clicked() {
-                    // Fire off a backend task
-                    app_action = AppAction::BackendTask(BackendTask::ContractTask(Box::new(
-                        ContractTask::UpdateDataContract(
-                            (**contract).clone(),
-                            self.selected_qualified_identity.clone().unwrap(), // unwrap should be safe here
-                            self.selected_key.clone().unwrap(), // unwrap should be safe here
-                        ),
-                    )));
-                }
+                if ui.add(button).clicked() {
+                    if let (Some(qi), Some(key)) =
+                        (self.selected_qualified_identity.clone(), self.selected_key.clone())
+                    {
+                        app_action = AppAction::BackendTask(BackendTask::ContractTask(Box::new(
+                            ContractTask::UpdateDataContract((**contract).clone(), qi, key),
+                        )));
+                    } else {
+                        ui.colored_label(Color32::DARK_RED, "Select identity and critical key first.");
+                    }
+                }
src/ui/identities/add_new_identity_screen/by_using_unused_asset_lock.rs (1)

56-63: Prevent panic when proof is None; disable or handle selection safely.

Selecting an asset lock with no proof will panic at runtime due to expect("Asset lock proof is required"). Guard the button and handle the None case.

-                    // Button to select this asset lock
-                    if ui.button("Select").clicked() {
-                        // Update the selected asset lock
-                        self.funding_asset_lock = Some((
-                            tx.clone(),
-                            proof.clone().expect("Asset lock proof is required"),
-                            address.clone(),
-                        ));
-
-                        // Update the step to ready to create identity
-                        let mut step = self.step.write().unwrap();
-                        *step = WalletFundedScreenStep::ReadyToCreate;
-                    }
+                    // Only allow selecting entries that have a proof
+                    let can_select = proof.is_some();
+                    let clicked = ui.add_enabled(can_select, egui::Button::new("Select")).clicked();
+                    if clicked {
+                        if let Some(proof) = proof.clone() {
+                            self.funding_asset_lock = Some((tx.clone(), proof, address.clone()));
+                            let mut step = self.step.write().unwrap();
+                            *step = WalletFundedScreenStep::ReadyToCreate;
+                        }
+                    } else if !can_select {
+                        ui.label(egui::RichText::new("Missing asset lock proof").color(egui::Color32::RED));
+                    }
src/database/identities.rs (1)

168-181: Set in-memory network: LGTM; avoid panicking decode on corrupt rows.

Assigning identity.network = app_context.network ensures consistency. However, QualifiedIdentity::from_bytes(&data) currently panics on decode failures, which can crash the app on corrupted DB rows. Prefer a fallible decode and bubble up an error or skip the row.

Example approach:

  • Introduce QualifiedIdentity::try_from_bytes(&[u8]) -> Result<Self, DecodeError>.
  • Replace calls here with match ... { Ok(qi) => qi, Err(e) => return Err(rusqlite::Error::UserFunctionError(Box::new(e))) }.

Based on relevant snippet in src/model/qualified_identity/mod.rs.

src/ui/tokens/tokens_screen/data_contract_json_pop_up.rs (1)

61-63: Fix width constraint: use available_width, not available_height.

Likely a copy/paste typo; constraining width by height warps the layout.

-                        .max_width(ui.available_height() - 20.0)
+                        .max_width(ui.available_width() - 20.0)
src/ui/tokens/tokens_screen/groups.rs (2)

231-239: Removing a member skips the next entry (while-loop index bug).

After removing members[j], the code still does j += 1, so the next member shifts into j and is skipped. Fix by tracking removal and not incrementing j when removed.

-                        if ui.button("Remove Member").clicked() {
-                            group_ui.members.remove(j);// return so we skip the assignment at the end
-                        } else {
-                            // Only assign back if we didn’t remove
-                            group_ui.members[j] = member;
-                        }
+                        let mut removed = false;
+                        if ui.button("Remove Member").clicked() {
+                            group_ui.members.remove(j);
+                            removed = true;
+                        } else {
+                            // Only assign back if we didn’t remove
+                            group_ui.members[j] = member;
+                        }
                     });
 
-                    j += 1;
+                    if removed {
+                        continue; // re-check current index since items shifted left
+                    }
+                    j += 1;

43-64: Duplicate member handling contradicts the UI (“later entry ignored”).

BTreeMap::insert overwrites the earlier member with the later duplicate. The UI says later duplicates are ignored. Preserve the first occurrence instead.

-            // Insert into the map
-            members_map.insert(id, power);
+            // Insert into the map; ignore later duplicates to match the UI message
+            use std::collections::btree_map::Entry;
+            match members_map.entry(id) {
+                Entry::Vacant(v) => {
+                    v.insert(power);
+                }
+                Entry::Occupied(_) => {
+                    // later duplicate: ignore
+                }
+            }
src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs (1)

101-116: Button label vs copied content mismatch (URI vs address).

You copy the full payment URI (with amount), but the UI says “Copy Address”. Align the label/feedback.

-        if ui.button("Copy Address").clicked() {
+        if ui.button("Copy Payment URI").clicked() {
             if let Err(e) = copy_to_clipboard(pay_uri.as_str()) {
                 self.copied_to_clipboard = Some(Some(e));
             } else {
                 self.copied_to_clipboard = Some(None);
             }
         }
@@
-            if let Some(error) = error {
+            if let Some(error) = error {
                 ui.label(format!("Failed to copy to clipboard: {}", error));
             } else {
-                ui.label("Address copied to clipboard.");
+                ui.label("Payment URI copied to clipboard.");
             }
src/ui/tokens/tokens_screen/distributions.rs (2)

786-790: Type mismatch: label says u64 but sanitization allows signed.

“Base amount (b, u64)” uses sanitize_i64, allowing negatives. Switch to sanitize_u64.

- ui.text_edit_singleline(&mut self.exp_b_input);
- sanitize_i64(&mut self.exp_b_input);
+ ui.text_edit_singleline(&mut self.exp_b_input);
+ sanitize_u64(&mut self.exp_b_input);

215-256: Doc string mismatch for StepDecreasingAmount.

The popup text under StepDecreasingAmount describes a Random function. Update to the correct step-decreasing description to avoid confusion.

src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs (1)

73-79: Minor UX: button copies URI, label says “Copy Address”.

Either change the label to “Copy Payment URI” or copy only the address.

src/ui/identities/identities_screen.rs (1)

681-793: Update Keys popup to egui 0.32's new Popup API with menu semantics.

The suggested changes are valid for egui 0.32. Apply the modernization to use Popup::menu() and the full namespace path egui::containers::popup::PopupCloseBehavior for stability. Replace ui.close_kind(egui::UiKind::Menu) with ui.close() since menus close by default in 0.32.

Apply:

- let popup_id = ui.make_persistent_id(format!("keys_popup_{}", qualified_identity.identity.id().to_string(Encoding::Base58)));
- egui::Popup::from_toggle_button_response(&button_response).id(popup_id)
-     .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside)
+ let popup_id = ui.make_persistent_id(format!(
+     "keys_popup_{}",
+     qualified_identity.identity.id().to_string(Encoding::Base58)
+ ));
+ egui::containers::popup::Popup::menu(&button_response)
+     .id(popup_id)
+     .close_behavior(egui::containers::popup::PopupCloseBehavior::CloseOnClickOutside)
      .show(|ui| { ... })

Replace ui.close_kind(egui::UiKind::Menu) with ui.close() for consistency with the new menu API.

Note: The Actions popup at lines 624-625 uses the same deprecated pattern and should also be updated in a separate change.

src/ui/tokens/direct_token_purchase_screen.rs (1)

362-365: Fix success message match typo (“purchaseed”).

The extra “e” can cause success detection to fail silently.

-                if message.contains("Successfully purchaseed tokens") || message == "PurchaseTokens"
+                if message.contains("Successfully purchased tokens") || message == "PurchaseTokens"
src/backend_task/mod.rs (1)

170-213: Adopt structured errors with thiserror per coding guidelines.

The review comment correctly identifies that backend_task violates the codebase guideline for Rust files, which mandates structured error handling using thiserror. This pattern is already established in the codebase (ConfigError, WalletError).

Current violations in backend_task/mod.rs:

  • run_backend_task() returns Result<_, String> (line 174)
  • run_backend_tasks_sequential/concurrent() return Vec<Result<_, String>> (lines 140, 156)
  • All submodule task runners use String errors

Implementation requires:

  • Define BackendTaskError enum in src/backend_task/mod.rs
  • Update run_backend_task and all task runner signatures across submodules
  • Update app.rs From impl (lines 52-56) to handle BackendTaskError
  • Ensure error propagation from submodules
src/ui/tokens/tokens_screen/mod.rs (2)

1873-1903: Wrong gating for StepDecreasing min_value and max_interval_count.

Both options are gated by step_decreasing_start_period_offset_input.is_empty() instead of their own inputs, causing valid values to be ignored.

Apply this fix:

-                    min_value: if self.step_decreasing_start_period_offset_input.is_empty() {
+                    min_value: if self.step_decreasing_min_value_input.is_empty() {
                         None
                     } else {
                         match self.step_decreasing_min_value_input.parse::<u64>() {
                             Ok(0) => None,
                             Ok(v) => Some(v),
                             Err(_) => {
                                 return Err(
                                     "Invalid min value for StepDecreasingAmount distribution. Put 0 for None."
                                         .to_string(),
                                 );
                             }
                         }
                     },
-                    max_interval_count: if self.step_decreasing_start_period_offset_input.is_empty()
+                    max_interval_count: if self.step_decreasing_max_interval_count_input.is_empty()
                     {
                         None
                     } else {
                         match self.step_decreasing_max_interval_count_input.parse::<u16>() {
                             Ok(0) => None,
                             Ok(v) => Some(v),
                             Err(_) => {
                                 return Err(
                                     "Invalid max interval count for StepDecreasingAmount distribution. Put 0 for None."
                                         .to_string(),
                                 );
                             }
                         }
                     },

1627-1629: Token reordering is a no‑op (assignment commented out).

reorder_vec_to builds reordered but never assigns it, so UI reordering and persisted order never take effect.

         // Replace the original with the reordered map
-        //self.my_tokens = reordered;
+        self.my_tokens = reordered;
♻️ Duplicate comments (41)
src/backend_task/tokens/transfer_tokens.rs (1)

102-136: Duplication persists: extract common balance-update logic into a helper function.

The identical pattern-matching and balance-update logic in both the HistoricalDocument (lines 102-136) and GroupActionWithDocument (lines 151-185) variants still exists. The only difference is the error message context. This violates DRY principles and increases maintenance burden.

The refactoring suggestion from the previous review remains valid and should be implemented to reduce duplication.

Also applies to: 151-185

src/backend_task/identity/register_dpns_name.rs (2)

60-78: Set creator_id to match owner_id for proper document creator tracking.

This critical issue was previously flagged but remains unresolved. In DPP DocumentV0, creator_id represents the Identity that submitted/created the document and should match owner_id. Setting creator_id: None on line 63 while owner_id is set to qualified_identity.identity.id() is incorrect.

Apply this diff to fix the preorder document initialization:

         let preorder_document = Document::V0(DocumentV0 {
             id: preorder_id,
             owner_id: qualified_identity.identity.id(),
-            creator_id: None,
+            creator_id: Some(qualified_identity.identity.id()),
             properties: BTreeMap::from([(
                 "saltedDomainHash".to_string(),
                 salted_domain_hash.into(),

79-121: Set creator_id to match owner_id for proper document creator tracking.

Same critical issue in the domain document initialization. Line 82 sets creator_id: None while owner_id is set to qualified_identity.identity.id(). This must be corrected to ensure proper document creator identity recording per DPP DocumentV0 semantics.

Apply this diff to fix the domain document initialization:

         let domain_document = Document::V0(DocumentV0 {
             id: domain_id,
             owner_id: qualified_identity.identity.id(),
-            creator_id: None,
+            creator_id: Some(qualified_identity.identity.id()),
             properties: BTreeMap::from([
                 ("parentDomainName".to_string(), "dash".into()),
src/model/wallet/encryption.rs (1)

2-2: aes-gcm Nonce handling is incorrect; restore Nonce::from_slice (0.10.x API).

encrypt/decrypt expect &Nonce, not a converted slice. This will not compile with aes-gcm 0.10.x; import and use Nonce::from_slice.

Apply this diff:

-use aes_gcm::{Aes256Gcm, KeyInit};
+use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
@@
-    let encrypted_seed = cipher
-        .encrypt(nonce.as_slice().into(), message)
+    let encrypted_seed = cipher
+        .encrypt(Nonce::from_slice(&nonce), message)
         .map_err(|e| e.to_string())?;
@@
-        let seed = cipher
-            .decrypt(self.nonce.as_slice().into(), self.encrypted_seed.as_slice())
+        let seed = cipher
+            .decrypt(Nonce::from_slice(&self.nonce), self.encrypted_seed.as_slice())
             .map_err(|e| e.to_string())?;

Also consider migrating to a typed error (thiserror) and zeroizing sensitive material after use.

Run to confirm crate version:

#!/bin/bash
rg -nP '^\s*aes-gcm\s*=\s*' Cargo.toml
rg -nP '^\s*aead\s*=\s*' Cargo.toml

Also applies to: 53-54, 88-89

src/components/core_zmq_listener.rs (2)

342-369: Critical: enum payload updated but Windows path still sends old arity.

ZMQMessage::ChainLockedBlock now expects (Block, ChainLock). Windows code sends only Block, which won't compile and is semantically incomplete. Decode ChainLock and send both; also align the topic to "rawchainlocksig".


165-170: Fix misleading log label: ChainLock vs InstantLock.

This path decodes ChainLock; the error message mentions InstantLock. Update the label to avoid confusion during debugging.

src/ui/identities/funding_common.rs (1)

56-94: Don't panic on poisoned locks; handle gracefully.

Using expect() on RwLock poisoning can crash the app in a UI flow. The existing review comment already covers this issue. Return None or log and continue instead of panicking.

.github/workflows/tests.yml (2)

34-38: Replace deprecated actions-rs/toolchain with dtolnay/rust-toolchain.

The actions-rs toolchain action is unmaintained and too old for current runners. Switch to dtolnay/rust-toolchain@stable.

-      - name: Install Rust toolchain
-        uses: actions-rs/toolchain@v1
-        with:
-          toolchain: stable
-          override: true
+      - name: Install Rust toolchain
+        uses: dtolnay/rust-toolchain@stable
+        with:
+          toolchain: stable

(Based on actionlint hints.)


54-59: Replace deprecated actions-rs/cargo steps with direct cargo invocations.

Use run: cargo ... instead of actions-rs/cargo@v1 to avoid runner incompatibilities.

       - name: Run tests
-        uses: actions-rs/cargo@v1
-        with:
-          command: test
-          args: --all-features --workspace
+        run: cargo test --all-features --workspace

       - name: Run doc tests
-        uses: actions-rs/cargo@v1
-        with:
-          command: test
-          args: --doc --all-features --workspace
+        run: cargo test --doc --all-features --workspace

Optional: add --locked if you want to enforce Cargo.lock.

Also applies to: 60-64

src/ui/components/left_panel.rs (2)

235-250: Make network display match exhaustive to catch future variants.

The wildcard arm in the network match won't force updates when new Network variants (e.g., Signet) are added. Use an exhaustive match to ensure compile-time coverage.

Consider extracting to a helper method with exhaustive matching:

impl Network {
    fn display_name_and_color(&self) -> (&str, Color32) {
        match self {
            Network::Dash => ("Mainnet", DashColors::DASH_BLUE),
            Network::Testnet => ("Testnet", Color32::from_rgb(255, 165, 0)),
            Network::Devnet => ("Devnet", Color32::DARK_RED),
            Network::Regtest => ("Local Network", Color32::from_rgb(139, 69, 19)),
            // Compiler will error here when new variants are added
        }
    }
}

153-153: Unify inconsistent navigation actions.

The code uses SetMainScreenThenGoToMainScreen when texture is loaded (line 153) but falls back to SetMainScreen when texture is unavailable (lines 179, 194). Since texture availability is a UI rendering concern, both paths should trigger the same action for consistent navigation.

Apply this diff to unify all action assignments:

 if added.clicked() {
     action = AppAction::SetMainScreenThenGoToMainScreen(
         *screen_type,
     );
 
 // ...later in fallback branches:
 
-                                            action = AppAction::SetMainScreen(*screen_type);
+                                            action = AppAction::SetMainScreenThenGoToMainScreen(*screen_type);
 
 // ...and:
 
-                                            action = AppAction::SetMainScreen(*screen_type);
+                                            action = AppAction::SetMainScreenThenGoToMainScreen(*screen_type);

Also applies to: 179-179, 194-194

doc/COMPONENT_DESIGN_PATTERN.md (1)

27-27: Fix typo: "simiar" → "similar".

Minor documentation polish for clarity.

Apply this diff:

-Inside your screen's `show()` method or simiar:
+Inside your screen's `show()` method or similar:
src/ui/contracts_documents/group_actions_screen.rs (1)

546-557: Replace .expect() with error handling to avoid UI panics.

The .expect("Failed to create identity selector") call on line 553 will crash the UI if IdentitySelector::new(...).selected_identity(...) returns an error. Instead, handle the Result and display an inline error message.

Apply this diff:

-            ui.add(
-                IdentitySelector::new(
-                    "group_actions_identity_selector",
-                    &mut self.selected_identity_str,
-                    &self.qualified_identities,
-                )
-                .selected_identity(&mut self.selected_identity)
-                .expect("Failed to create identity selector")
-                .other_option(false)
-                .width(250.0)
-                .label("Identity:"),
-            );
+            match IdentitySelector::new(
+                "group_actions_identity_selector",
+                &mut self.selected_identity_str,
+                &self.qualified_identities,
+            )
+            .selected_identity(&mut self.selected_identity) {
+                Ok(selector) => {
+                    ui.add(
+                        selector
+                            .other_option(false)
+                            .width(250.0)
+                            .label("Identity:")
+                    );
+                }
+                Err(e) => {
+                    ui.colored_label(Color32::RED, format!("Failed to init identity selector: {e}"));
+                }
+            }
src/ui/components/top_panel.rs (1)

332-352: Close docs popup after selection for consistent UX.

The Documents popup doesn't close after selection (line 349 has commented-out ui.close()), while the Contracts popup does close (line 386). Both should behave identically.

Apply this diff:

 for (text, da) in doc_actions {
     if ui.button(text).clicked() {
         action = da.create_action(app_context);
-        // ui.close();
+        ui.close();
     }
 }
src/ui/identities/withdraw_screen.rs (2)

201-208: Remove .expect on withdrawal_amount in dialog (panic risk).

This can panic if state drifts. Build the message with a safe fallback or abort opening the dialog. Same issue was flagged earlier.

Apply:

-        let dialog = self.confirmation_dialog.get_or_insert_with(|| {
-            ConfirmationDialog::new(
-                "Confirm Withdrawal".to_string(),
-                format!(
-                    "Are you sure you want to withdraw {} to {}",
-                    self.withdrawal_amount
-                        .as_ref()
-                        .expect("Withdrawal amount should be present"),
-                    message_address
-                ),
-            )
-            .danger_mode(true) // Withdrawal is a destructive operation
-        });
+        let dialog = self.confirmation_dialog.get_or_insert_with(|| {
+            let amount_display = self
+                .withdrawal_amount
+                .as_ref()
+                .map(|a| a.to_string())
+                .unwrap_or_else(|| "unknown amount".to_string());
+            ConfirmationDialog::new(
+                "Confirm Withdrawal".to_string(),
+                format!(
+                    "Are you sure you want to withdraw {} to {}",
+                    amount_display,
+                    message_address
+                ),
+            )
+            .danger_mode(true)
+        });

221-225: Remove .expect before sending backend task (panic risk).

Gracefully handle missing amount; set error and stop instead of panicking. Same as prior review.

-                let credits = self
-                    .withdrawal_amount
-                    .as_ref()
-                    .expect("Withdrawal amount should be present")
-                    .value() as u128;
+                let credits = match self.withdrawal_amount.as_ref() {
+                    Some(amount) => amount.value(),
+                    None => {
+                        self.withdraw_from_identity_status = WithdrawFromIdentityStatus::ErrorMessage(
+                            "No withdrawal amount specified".to_string(),
+                        );
+                        self.confirmation_dialog = None;
+                        return AppAction::None;
+                    }
+                };
src/ui/components/contracts_subscreen_chooser_panel.rs (1)

63-66: Fix inner_margin type: Margin::same expects f32, not i8.

The i8 cast will not compile. Pass f32.

-                .inner_margin(Margin::same(Spacing::XL as i8))
+                .inner_margin(Margin::same(Spacing::XL))
src/ui/tokens/claim_tokens_screen.rs (1)

186-189: Eliminate defaulting and panics in confirmation path. Validate Options before enqueuing tasks.

Still using unwrap_or(...) for distribution_type and expect("No key selected") for selected_key. Handle both gracefully and keep UI responsive.

-        let distribution_type = self
-            .distribution_type
-            .unwrap_or(TokenDistributionType::Perpetual);
+        let Some(distribution_type) = self.distribution_type else {
+            self.status = ClaimTokensStatus::ErrorMessage("Please select a distribution type.".into());
+            return AppAction::None;
+        };
@@
-            Some(ConfirmationStatus::Confirmed) => {
+            Some(ConfirmationStatus::Confirmed) => {
                 self.confirmation_dialog = None;
                 let now = SystemTime::now()
                     .duration_since(UNIX_EPOCH)
                     .expect("Time went backwards")
                     .as_secs();
                 self.status = ClaimTokensStatus::WaitingForResult(now);
 
+                let Some(signing_key) = self.selected_key.clone() else {
+                    self.status = ClaimTokensStatus::ErrorMessage("Select a signing key first.".into());
+                    return AppAction::None;
+                };
+
                 AppAction::BackendTasks(
                     vec![
                         BackendTask::TokenTask(Box::new(TokenTask::ClaimTokens {
                             data_contract: Arc::new(self.token_contract.contract.clone()),
                             token_position: self.identity_token_basic_info.token_position,
                             actor_identity: self.identity.clone(),
                             distribution_type,
-                            signing_key: self.selected_key.clone().expect("No key selected"),
+                            signing_key,
                             public_note: self.public_note.clone(),
                         })),
                         BackendTask::TokenTask(Box::new(TokenTask::QueryMyTokenBalances)),
                     ],
                     BackendTasksExecutionMode::Sequential,
                 )

Also applies to: 197-220

src/ui/components/tools_subscreen_chooser_panel.rs (1)

85-85: Fix type cast: use f32 for margins.

Margin::same(Spacing::XL as i8) should be Margin::same(Spacing::XL) since Spacing::XL is f32, not i8. Casting to i8 will truncate the value and produce incorrect margins.

Apply this diff:

-                .inner_margin(Margin::same(Spacing::XL as i8))
+                .inner_margin(Margin::same(Spacing::XL))
src/model/qualified_identity/mod.rs (1)

229-233: Remove conflicting AsRef impl.

Rust's standard library already provides impl<T: ?Sized> AsRef<T> for T. This manual implementation conflicts and will cause a compilation error.

Apply this diff:

-impl AsRef<QualifiedIdentity> for QualifiedIdentity {
-    fn as_ref(&self) -> &QualifiedIdentity {
-        self
-    }
-}
src/backend_task/grovestark.rs (2)

5-9: Use typed errors instead of String per coding guidelines.

The function returns Result<BackendTaskSuccessResult, String>, but the coding guidelines specify that error handling should use thiserror for structured error types. String errors lack context and cannot be easily pattern-matched by callers.

As per coding guidelines, apply this pattern:

use thiserror::Error;

#[derive(Debug, Error)]
pub enum GroveStarkTaskError {
    #[error("Failed to generate proof: {0}")]
    ProofGeneration(String),
    #[error("Failed to verify proof: {0}")]
    ProofVerification(String),
}

pub async fn run_grovestark_task(
    task: GroveSTARKTask,
    sdk: &Sdk,
) -> Result<BackendTaskSuccessResult, GroveStarkTaskError> {
    match task {
        GroveSTARKTask::GenerateProof { .. } => {
            // ...
            match prover.generate_proof(...).await {
                Ok(proof_data) => Ok(BackendTaskSuccessResult::GeneratedZKProof(proof_data)),
                Err(e) => Err(GroveStarkTaskError::ProofGeneration(e.to_string())),
            }
        }
        GroveSTARKTask::VerifyProof { proof_data } => {
            // ...
            match prover.verify_proof(&proof_data) {
                Ok(is_valid) => Ok(BackendTaskSuccessResult::VerifiedZKProof(is_valid, proof_data)),
                Err(e) => Err(GroveStarkTaskError::ProofVerification(e.to_string())),
            }
        }
    }
}

Also applies to: 34-36, 41-46


51-65: CRITICAL: Remove Debug/PartialEq derives to prevent secret exposure.

GroveSTARKTask::GenerateProof contains private_key: [u8; 32] and public_key: [u8; 32]. Deriving Debug will print these secrets in logs, error messages, and panic output. Deriving PartialEq on secrets can leak timing information.

Apply this fix:

-#[derive(Debug, Clone, PartialEq)]
+#[derive(Clone)]
 pub enum GroveSTARKTask {
     GenerateProof {
         identity_id: String,
         contract_id: String,
         document_type: String,
         document_id: String,
         key_id: u32,
         private_key: [u8; 32],
         public_key: [u8; 32],
     },
     VerifyProof {
         proof_data: ProofDataOutput,
     },
 }
+
+// Redacted Debug to prevent secret leakage
+impl std::fmt::Debug for GroveSTARKTask {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            GroveSTARKTask::GenerateProof {
+                identity_id,
+                contract_id,
+                document_type,
+                document_id,
+                key_id,
+                ..
+            } => f
+                .debug_struct("GenerateProof")
+                .field("identity_id", identity_id)
+                .field("contract_id", contract_id)
+                .field("document_type", document_type)
+                .field("document_id", document_id)
+                .field("key_id", key_id)
+                .field("private_key", &"<redacted>")
+                .field("public_key", &"<redacted>")
+                .finish(),
+            GroveSTARKTask::VerifyProof { proof_data } => f
+                .debug_struct("VerifyProof")
+                .field("proof_data", proof_data)
+                .finish(),
+        }
+    }
+}
src/ui/components/identity_selector.rs (1)

262-271: Compile error: unwrap_or on non-Option InnerResponse.

ComboBox::show_ui returns InnerResponse; inner is bool, not Option.

-            let combo_changed = combo_response.inner.unwrap_or(false);
+            let combo_changed = combo_response.inner;
src/ui/identities/transfer_screen.rs (1)

177-200: Type consistency: avoid u64 → u128 → u64 roundtrips for Credits.

Keep Credits as Credits throughout.

-        // Use the amount directly since it's already an Amount struct
-        let credits = self.amount.as_ref().map(|v| v.value()).unwrap_or_default() as u128;
-        if credits == 0 {
+        // Use Credits directly (Amount.value() returns Credits)
+        let credits: Credits = self.amount.as_ref().map(|v| v.value()).unwrap_or(0);
+        if credits == 0 {
             self.error_message = Some("Amount must be greater than 0".to_string());
             self.transfer_credits_status =
                 TransferCreditsStatus::ErrorMessage("Amount must be greater than 0".to_string());
             self.confirmation_popup = false;
             return AppAction::None;
         }
@@
-            credits as Credits,
+            credits,
src/app.rs (1)

247-250: Add mainnet fallback when selected network context is unavailable (and update local chosen_network).

Avoid expect() panics in current_app_context() at startup when a persisted network has no context. Also update the local chosen_network variable; otherwise self.chosen_network stays invalid. — As per coding guidelines (central state/screen mgmt in app.rs).

-        let selected_main_screen = settings.root_screen_type;
-        let chosen_network = settings.network;
+        let selected_main_screen = settings.root_screen_type;
+        let mut chosen_network = settings.network;
         network_chooser_screen.current_network = chosen_network;
@@
         } else if let (Network::Regtest, Some(local_app_context)) =
             (chosen_network, local_app_context.as_ref())
         {
             // ... existing branches ...
-        }
+        } else {
+            // Fallback to mainnet if selected network is unavailable
+            network_chooser_screen.current_network = Network::Dash;
+            chosen_network = Network::Dash;
+            // Persist settings using mainnet context (persists network/root screen)
+            let _ = mainnet_app_context.update_settings(selected_main_screen);
+        }

Also applies to: 251-330

src/backend_task/mnlist.rs (3)

37-41: Blocking I/O inside async; switch to spawn_blocking or make sync; use typed errors.

CoreP2PHandler and RpcApi calls are synchronous, blocking the async executor. Move blocking regions to tokio::task::spawn_blocking or make run_mnlist_task synchronous. Also replace Result<_, String> with a thiserror-typed error per guidelines.

Also applies to: 49-76, 117-127


81-82: Avoid unwrap() on core_client lock.

Map poisoned RwLock to an error instead of panicking.

- let client = app.core_client.read().unwrap();
+ let client = app.core_client.read().map_err(|_| "core_client lock poisoned".to_string())?;

Prefer a typed error over String.


83-114: Range/window off-by-one; propagate errors from RPC calls.

Use an inclusive range sized ≤ max_blocks and fail fast on get_block_hash errors instead of silently skipping.

- let end_height = start_height.saturating_add(max_blocks).min(block_height);
+ let end_height = start_height.saturating_add(max_blocks.saturating_sub(1)).min(block_height);

- for h in start_height..end_height {
-    if let Ok(bh2) = client.get_block_hash(h) {
+ for h in start_height..=end_height {
+    let bh2 = client.get_block_hash(h)
+        .map_err(|e| format!("get_block_hash({h}) failed: {e}"))?;
     let bh = BlockHash::from_byte_array(bh2.to_byte_array());
     if let Ok(block) = client.get_block(&bh2) {
        …
     } else {
        …
     }
-   }
 }

Also consider logging when get_block fails.

src/backend_task/identity/load_identity_from_wallet.rs (1)

292-331: Don’t use string matching for control flow; use typed errors.

Matching on error.starts_with("No identity found…") is brittle. Return a dedicated error enum (e.g., IdentityLoadError::NotFound { index }) from load_user_identity_from_wallet and match on it here.

src/ui/tokens/tokens_screen/distributions.rs (1)

381-406: Use saturating_mul for interval math to avoid overflow.

Multiplying two u64s can overflow. Use saturating_mul (or checked_mul with fallback).

- let amount = perpetual_dist_interval_input * step_count_input;
+ let amount = perpetual_dist_interval_input.saturating_mul(step_count_input);

Apply to BlockBased, TimeBased, and EpochBased branches.

Also applies to: 408-415

src/ui/tokens/destroy_frozen_funds_screen.rs (1)

296-311: Don’t panic on missing signing key.

expect("No key selected") will crash if the user confirms without a key. Validate and surface an error; optionally disable the Destroy button until both frozen_identity_id parses and a key is selected.

- signing_key: self.selected_key.clone().expect("No key selected"),
+ signing_key: match self.selected_key.clone() {
+     Some(k) => k,
+     None => {
+         self.error_message = Some("Please select a signing key.".into());
+         self.status = DestroyFrozenFundsStatus::ErrorMessage("No key selected".into());
+         return AppAction::None;
+     }
+ },
src/ui/tokens/pause_tokens_screen.rs (1)

230-241: Handle missing signing key without panicking.

Replace expect("No key selected") with validation that sets an error and returns early; also disable the Pause button until a key is chosen.

- signing_key: self.selected_key.clone().expect("No key selected"),
+ signing_key: match self.selected_key.clone() {
+     Some(k) => k,
+     None => {
+         self.error_message = Some("Please select a signing key.".into());
+         self.status = PauseTokensStatus::ErrorMessage("No key selected".into());
+         return AppAction::None;
+     }
+ },
src/ui/tokens/direct_token_purchase_screen.rs (2)

289-300: Avoid panic on missing signing key in confirm path.

Replace expect("Expected a key") with a graceful check and error state before dispatch.

-                            signing_key: self.selected_key.clone().expect("Expected a key"),
+                            // Guard: require a selected signing key
+                            signing_key: {
+                                let Some(signing_key) = self.selected_key.clone() else {
+                                    self.error_message = Some("Select a signing key first.".into());
+                                    self.status = PurchaseTokensStatus::ErrorMessage("No signing key selected".into());
+                                    return AppAction::None;
+                                };
+                                signing_key
+                            },

519-521: Remove duplicate “DASH” in price label.

Amount already carries the unit. Show it once.

-                        ui.label(format!("{} DASH ({} credits)",dash_amount, calculated_price_credits));
+                        ui.label(format!("{} ({} credits)", dash_amount, calculated_price_credits));
src/ui/components/confirmation_dialog.rs (2)

37-43: Blocker: returning reference to temporary None.

This borrows a temporary and won’t compile. Always return a ref to the stored Option.

Apply:

-    fn changed_value(&self) -> &Option<Self::DomainType> {
-        if self.has_changed() {
-            &self.dialog_response
-        } else {
-            &None
-        }
-    }
+    fn changed_value(&self) -> &Option<Self::DomainType> {
+        &self.dialog_response
+    }

1-1: Blocker: invalid WidgetText handling (match on &WidgetText; Arc not Into).

Match on a value (clone) and construct WidgetText directly; drop Arc.

Apply:

- use std::sync::Arc;
+ // removed: Arc no longer needed
-                            let confirm_label = if let WidgetText::RichText(rich_text) =
-                                confirm_text
-                            {
-                                // preserve rich text formatting
-                                rich_text.clone()
-                            } else {
-                                Arc::new(egui::RichText::new(confirm_text.text()).color(text_color))
-                            };
+                            let confirm_label: WidgetText = match confirm_text.clone() {
+                                WidgetText::RichText(rt) => WidgetText::RichText(rt),
+                                other => egui::RichText::new(other.text())
+                                    .color(text_color)
+                                    .into(),
+                            };
-                            let cancel_label = if let WidgetText::RichText(rich_text) = cancel_text
-                            {
-                                // preserve rich text formatting
-                                rich_text.clone()
-                            } else {
-                                egui::RichText::new(cancel_text.text())
-                                    .color(ComponentStyles::secondary_button_text())
-                                    .into()
-                            };
+                            let cancel_label: WidgetText = match cancel_text.clone() {
+                                WidgetText::RichText(rt) => WidgetText::RichText(rt),
+                                other => egui::RichText::new(other.text())
+                                    .color(ComponentStyles::secondary_button_text())
+                                    .into(),
+                            };

Also applies to: 212-220, 241-250

src/ui/tokens/mint_tokens_screen.rs (1)

248-252: Treat empty recipient as “issuer (self)” and pass None to backend.

Empty recipient should mint to issuer; avoid parsing empty string and improve confirmation text.

Apply:

@@
-        let msg = format!(
-            "Are you sure you want to mint {} tokens to {}?",
-            self.amount.clone().unwrap_or(Amount::new(0, 0)),
-            self.recipient_identity_id
-        );
+        let recipient_display = if self.recipient_identity_id.trim().is_empty() {
+            "issuer (self)".to_string()
+        } else {
+            self.recipient_identity_id.clone()
+        };
+        let msg = format!(
+            "Are you sure you want to mint {} tokens to {}?",
+            self.amount.clone().unwrap_or(Amount::new(0, 0)),
+            recipient_display
+        );
@@
-        let parsed_receiver_id = Identifier::from_string_try_encodings(
-            &self.recipient_identity_id,
-            &[
-                dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58,
-                dash_sdk::dpp::platform_value::string_encoding::Encoding::Hex,
-            ],
-        );
-
-        if parsed_receiver_id.is_err() {
-            self.status = MintTokensStatus::ErrorMessage("Invalid receiver".into());
-            self.error_message = Some("Invalid receiver".into());
-            return AppAction::None;
-        }
-
-        let receiver_id = parsed_receiver_id.unwrap();
+        let receiver_id_opt = if self.recipient_identity_id.trim().is_empty() {
+            None
+        } else {
+            match Identifier::from_string_try_encodings(
+                &self.recipient_identity_id,
+                &[
+                    dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58,
+                    dash_sdk::dpp::platform_value::string_encoding::Encoding::Hex,
+                ],
+            ) {
+                Ok(id) => Some(id),
+                Err(_) => {
+                    self.status = MintTokensStatus::ErrorMessage("Invalid receiver".into());
+                    self.error_message = Some("Invalid receiver".into());
+                    return AppAction::None;
+                }
+            }
+        };
@@
-            recipient_id: Some(receiver_id),
+            recipient_id: receiver_id_opt,
             amount: self.amount.clone().unwrap_or(Amount::new(0, 0)).value(),
             group_info,
         })))
@@
-                        let msg = format!(
-                            "Are you sure you want to mint {} tokens to {}?",
-                            self.amount.clone().unwrap_or(Amount::new(0, 0)),
-                            self.recipient_identity_id
-                        );
+                        let recipient_display = if self.recipient_identity_id.trim().is_empty() {
+                            "issuer (self)".to_string()
+                        } else {
+                            self.recipient_identity_id.clone()
+                        };
+                        let msg = format!(
+                            "Are you sure you want to mint {} tokens to {}?",
+                            self.amount.clone().unwrap_or(Amount::new(0, 0)),
+                            recipient_display
+                        );

Also applies to: 281-296, 320-333, 638-648

src/ui/tokens/set_token_price_screen.rs (1)

293-299: Overflow risk converting price-per-unit to price-per-token.

Use wider type and saturate/clamp to u64.

Apply:

-                let price_per_token = price_per_smallest_unit * decimal_multiplier;
+                let price_per_token = (price_per_smallest_unit as u128)
+                    .saturating_mul(decimal_multiplier as u128)
+                    .min(u64::MAX as u128) as u64;
@@
-                        let price_per_token = price_per_smallest_unit * decimal_multiplier;
+                        let price_per_token = (*price_per_smallest_unit as u128)
+                            .saturating_mul(decimal_multiplier as u128)
+                            .min(u64::MAX as u128) as u64;

Also applies to: 311-319

src/ui/tokens/freeze_tokens_screen.rs (1)

559-562: Bug: confirmation dialog never created on click.

You reset to None and never instantiate; popup never appears.

Apply:

-                    if ui.add(button).clicked() {
-                        // Initialize confirmation dialog when button is clicked
-                        self.confirmation_dialog = None; // Reset for fresh dialog
-                    }
+                    if ui.add(button).clicked() {
+                        self.confirmation_dialog = Some(
+                            ConfirmationDialog::new(
+                                "Confirm Freeze",
+                                format!(
+                                    "Are you sure you want to freeze identity {}?",
+                                    self.freeze_identity_id
+                                ),
+                            )
+                            .confirm_text(Some("Confirm"))
+                            .cancel_text(Some("Cancel")),
+                        );
+                    }
src/ui/contracts_documents/document_action_screen.rs (1)

360-367: Compile error: passing &mut of a temporary to TextEdit

TextEdit::multiline needs &mut String; &mut json.clone() is a temporary and won’t compile. Use a real mutable String or a non-editable widget.

Apply one of these:

Option A (non-editable code label):

-                            if let Ok(json) = serde_json::to_string_pretty(doc) {
-                                ui.add(
-                                    egui::TextEdit::multiline(&mut json.clone())
-                                        .font(egui::TextStyle::Monospace)
-                                        .desired_rows(10)
-                                        .desired_width(380.0)
-                                        .interactive(false),
-                                );
+                            if let Ok(json) = serde_json::to_string_pretty(doc) {
+                                ui.code(json);

Option B (editable-looking, but disabled):

-                            if let Ok(json) = serde_json::to_string_pretty(doc) {
-                                ui.add(
-                                    egui::TextEdit::multiline(&mut json.clone())
+                            if let Ok(mut json_str) = serde_json::to_string_pretty(doc) {
+                                ui.add(
+                                    egui::TextEdit::multiline(&mut json_str)
                                         .font(egui::TextStyle::Monospace)
                                         .desired_rows(10)
                                         .desired_width(380.0)
                                         .interactive(false),
                                 );
src/ui/tokens/transfer_tokens_screen.rs (1)

67-75: Avoid panics during screen init; surface a UI error instead

load_local_qualified_identities().expect(...) and find(...).expect(...) will crash the app if data is unavailable. Return a screen with a user-visible error state (e.g., TransferTokensStatus::ErrorMessage) and disable actions instead.

I can provide a small patch to replace these expects with graceful fallbacks and an error message.

Comment on lines +40 to +53
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential pkg-config clang cmake libsqlite3-dev

- name: Install protoc
run: |
curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v25.2/protoc-25.2-linux-x86_64.zip
sudo unzip -o protoc-25.2-linux-x86_64.zip -d /usr/local bin/protoc
sudo unzip -o protoc-25.2-linux-x86_64.zip -d /usr/local 'include/*'
rm -f protoc-25.2-linux-x86_64.zip
env:
PROTOC: /usr/local/bin/protoc

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Install unzip before using it; optionally export PROTOC at job scope.

unzip is used but not installed; add it to apt-get to avoid failures. Exporting PROTOC at job-level is optional since /usr/local/bin is in PATH.

       - name: Install system dependencies
         run: |
           sudo apt-get update
-          sudo apt-get install -y build-essential pkg-config clang cmake libsqlite3-dev
+          sudo apt-get install -y build-essential pkg-config clang cmake libsqlite3-dev unzip

Optionally set at job env:

env:
  CARGO_TERM_COLOR: always
  PROTOC: /usr/local/bin/protoc
🤖 Prompt for AI Agents
.github/workflows/tests.yml around lines 40 to 53: the workflow calls unzip but
never installs it and sets PROTOC only in the step env; update the "Install
system dependencies" step to include unzip in the apt-get install list (e.g.,
add unzip to build-essential pkg-config clang cmake libsqlite3-dev) so the
subsequent step can run, and optionally move or add PROTOC=/usr/local/bin/protoc
under the job-level env block so the variable is available to all steps.

Comment on lines 9 to +10
zmqpubrawtxlocksig=tcp://0.0.0.0:23709
zmqpubrawchainlock=tcp://0.0.0.0:23709 No newline at end of file
zmqpubrawchainlocksig=tcp://0.0.0.0:23709 No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Open ZMQ endpoints on 0.0.0.0 — restrict scope or document firewalling.

Exposing ZMQ to all interfaces can leak mempool/chain data and invite scraping. Prefer localhost unless remote subscribers are required.

Apply this diff to bind locally (adjust if you truly need remote subscribers):

- zmqpubrawtxlocksig=tcp://0.0.0.0:23709
- zmqpubrawchainlocksig=tcp://0.0.0.0:23709
+ zmqpubrawtxlocksig=tcp://127.0.0.1:23709
+ zmqpubrawchainlocksig=tcp://127.0.0.1:23709
🤖 Prompt for AI Agents
In dash_core_configs/testnet.conf around lines 9 to 10, the ZMQ endpoints are
bound to 0.0.0.0 which exposes mempool/chain data to all interfaces; change the
bindings to localhost (127.0.0.1) so ZMQ only listens locally, or if remote
subscribers are required, leave as-is but document and enforce firewall rules
and IP-restricted access for those ports to limit exposure.

Comment on lines +92 to +101
&& let (Value::Identifier(owner_bytes), Value::U64(amount)) =
(owner_value, amount_value)
{
if let Ok(owner_id) = Identifier::from_bytes(owner_bytes) {
if let Err(e) = self
.insert_token_identity_balance(&token_id, &owner_id, *amount)
{
eprintln!(
"Failed to update token balance from historical document: {}",
e
);
}
}
}
&& let Ok(owner_id) = Identifier::from_bytes(owner_bytes)
&& let Err(e) =
self.insert_token_identity_balance(&token_id, &owner_id, *amount)
{
eprintln!(
"Failed to update token balance from historical document: {}",
e
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Log missing/invalid data and parse failures (HistoricalDocument).

Only insert failures are logged; other failures are silent.

-                    if let (Some(owner_value), Some(amount_value)) =
-                        (document.get("ownerId"), document.get("amount"))
-                        && let (Value::Identifier(owner_bytes), Value::U64(amount)) =
-                            (owner_value, amount_value)
-                        && let Ok(owner_id) = Identifier::from_bytes(owner_bytes)
-                        && let Err(e) =
-                            self.insert_token_identity_balance(&token_id, &owner_id, *amount)
-                    {
-                        eprintln!(
-                            "Failed to update token balance from historical document: {}",
-                            e
-                        );
-                    }
+                    if let (Some(owner_value), Some(amount_value)) =
+                        (document.get("ownerId"), document.get("amount"))
+                        && let (Value::Identifier(owner_bytes), Value::U64(amount)) =
+                            (owner_value, amount_value)
+                        && let Ok(owner_id) = Identifier::from_bytes(owner_bytes)
+                    {
+                        if let Err(e) = self.insert_token_identity_balance(
+                            &token_id,
+                            &owner_id,
+                            *amount,
+                        ) {
+                            eprintln!(
+                                "Failed to update token balance from historical document: {}",
+                                e
+                            );
+                        }
+                    } else {
+                        eprintln!(
+                            "Historical document missing/invalid ownerId or amount; skipping balance update"
+                        );
+                    }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 116 to +118
(owner_value, amount_value)
{
if let Ok(owner_id) = Identifier::from_bytes(owner_bytes) {
if let Err(e) = self
.insert_token_identity_balance(&token_id, &owner_id, *amount)
{
eprintln!(
"Failed to update token balance from group action document: {}",
e
);
}
}
}
&& let Ok(owner_id) = Identifier::from_bytes(owner_bytes)
&& let Err(e) =
self.insert_token_identity_balance(&token_id, &owner_id, *amount)
{
eprintln!(
"Failed to update token balance from group action document: {}",
e
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Mirror the fix for GroupActionWithDocument.

Add else logging; move insert error check into the then-branch.

-                    if let (Some(owner_value), Some(amount_value)) =
-                        (document.get("ownerId"), document.get("amount"))
-                        && let (Value::Identifier(owner_bytes), Value::U64(amount)) =
-                            (owner_value, amount_value)
-                        && let Ok(owner_id) = Identifier::from_bytes(owner_bytes)
-                        && let Err(e) =
-                            self.insert_token_identity_balance(&token_id, &owner_id, *amount)
-                    {
-                        eprintln!(
-                            "Failed to update token balance from group action document: {}",
-                            e
-                        );
-                    }
+                    if let (Some(owner_value), Some(amount_value)) =
+                        (document.get("ownerId"), document.get("amount"))
+                        && let (Value::Identifier(owner_bytes), Value::U64(amount)) =
+                            (owner_value, amount_value)
+                        && let Ok(owner_id) = Identifier::from_bytes(owner_bytes)
+                    {
+                        if let Err(e) =
+                            self.insert_token_identity_balance(&token_id, &owner_id, *amount)
+                        {
+                            eprintln!(
+                                "Failed to update token balance from group action document: {}",
+                                e
+                            );
+                        }
+                    } else {
+                        eprintln!(
+                            "Group action document missing/invalid ownerId or amount; skipping balance update"
+                        );
+                    }

Comment on lines 74 to 83
if let (Some(claimer_value), Some(amount_value)) =
(document.get("claimerId"), document.get("amount"))
{
if let (Value::Identifier(claimer_bytes), Value::U64(amount)) =
&& let (Value::Identifier(claimer_bytes), Value::U64(amount)) =
(claimer_value, amount_value)
{
if let Ok(claimer_id) = Identifier::from_bytes(claimer_bytes) {
if let Err(e) = self.insert_token_identity_balance(
&token_id,
&claimer_id,
*amount,
) {
eprintln!(
"Failed to update token balance from claim document: {}",
e
);
}
}
}
&& let Ok(claimer_id) = Identifier::from_bytes(claimer_bytes)
&& let Err(e) =
self.insert_token_identity_balance(&token_id, &claimer_id, *amount)
{
eprintln!("Failed to update token balance from claim document: {}", e);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t swallow validation/parse failures (ClaimResult::Document).

Only insertion failures are logged; missing/invalid claimerId or amount, or parse failure, produce no log.

-                    if let (Some(claimer_value), Some(amount_value)) =
-                        (document.get("claimerId"), document.get("amount"))
-                        && let (Value::Identifier(claimer_bytes), Value::U64(amount)) =
-                            (claimer_value, amount_value)
-                        && let Ok(claimer_id) = Identifier::from_bytes(claimer_bytes)
-                        && let Err(e) =
-                            self.insert_token_identity_balance(&token_id, &claimer_id, *amount)
-                    {
-                        eprintln!("Failed to update token balance from claim document: {}", e);
-                    }
+                    if let (Some(claimer_value), Some(amount_value)) =
+                        (document.get("claimerId"), document.get("amount"))
+                        && let (Value::Identifier(claimer_bytes), Value::U64(amount)) =
+                            (claimer_value, amount_value)
+                        && let Ok(claimer_id) = Identifier::from_bytes(claimer_bytes)
+                    {
+                        if let Err(e) =
+                            self.insert_token_identity_balance(&token_id, &claimer_id, *amount)
+                        {
+                            eprintln!(
+                                "Failed to update token balance from claim document: {}",
+                                e
+                            );
+                        }
+                    } else {
+                        eprintln!(
+                            "Claim document missing/invalid claimerId or amount; skipping balance update"
+                        );
+                    }
🤖 Prompt for AI Agents
In src/backend_task/tokens/claim_tokens.rs around lines 74 to 83, the current
chained let-guards only log insertion errors and silently ignore missing/invalid
fields or parse failures; update the code to explicitly handle and log each
failure path: check for presence of "claimerId" and "amount" and log if either
is missing, validate that they are the expected Value variants and log
descriptive errors when they are not, handle Identifier::from_bytes errors and
log the parse error, then proceed to call insert_token_identity_balance and log
any insertion error as before; ensure each branch uses clear, contextual error
messages so missing/invalid/parse failures are not swallowed.

Comment on lines +287 to +299
BackendTask::TokenTask(Box::new(TokenTask::BurnTokens {
owner_identity: self.identity_token_info.identity.clone(),
data_contract,
token_position: self.identity_token_info.token_position,
signing_key: self.selected_key.clone().expect("Expected a key"),
public_note: if self.group_action_id.is_some() {
None
} else {
self.public_note.clone()
},
amount: amount.value(),
group_info,
})),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Avoid panic on missing signing key in confirm path.

Replace expect("Expected a key") with a guard and user-facing error.

-                            signing_key: self.selected_key.clone().expect("Expected a key"),
+                            signing_key: {
+                                let Some(signing_key) = self.selected_key.clone() else {
+                                    self.error_message = Some("Select a signing key first.".into());
+                                    self.status = BurnTokensStatus::ErrorMessage("No signing key selected".into());
+                                    return AppAction::None;
+                                };
+                                signing_key
+                            },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
BackendTask::TokenTask(Box::new(TokenTask::BurnTokens {
owner_identity: self.identity_token_info.identity.clone(),
data_contract,
token_position: self.identity_token_info.token_position,
signing_key: self.selected_key.clone().expect("Expected a key"),
public_note: if self.group_action_id.is_some() {
None
} else {
self.public_note.clone()
},
amount: amount.value(),
group_info,
})),
BackendTask::TokenTask(Box::new(TokenTask::BurnTokens {
owner_identity: self.identity_token_info.identity.clone(),
data_contract,
token_position: self.identity_token_info.token_position,
signing_key: {
let Some(signing_key) = self.selected_key.clone() else {
self.error_message = Some("Select a signing key first.".into());
self.status = BurnTokensStatus::ErrorMessage("No signing key selected".into());
return AppAction::None;
};
signing_key
},
public_note: if self.group_action_id.is_some() {
None
} else {
self.public_note.clone()
},
amount: amount.value(),
group_info,
})),
🤖 Prompt for AI Agents
In src/ui/tokens/burn_tokens_screen.rs around lines 287 to 299, the code panics
by using expect("Expected a key") when building the BurnTokens task; instead,
guard against a missing signing_key and show a user-facing error (or set an
error state) rather than panicking. Before creating BackendTask::TokenTask,
check if self.selected_key.is_some(); if not, abort building the task and update
the UI state to display an appropriate error message (or return an Err) so the
confirm path fails gracefully; only construct and enqueue the BurnTokens task
when a signing key is present.

Comment on lines +230 to +241
AppAction::BackendTask(BackendTask::TokenTask(Box::new(TokenTask::ResumeTokens {
actor_identity: self.identity.clone(),
data_contract,
token_position: self.identity_token_info.token_position,
signing_key: self.selected_key.clone().expect("No key selected"),
public_note: if self.group_action_id.is_some() {
None
} else {
self.public_note.clone()
},
group_info,
})))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Gracefully handle missing signing key in Resume flow.

Same issue: replace expect("No key selected") with a checked branch that sets an error and returns without crashing.

- signing_key: self.selected_key.clone().expect("No key selected"),
+ signing_key: match self.selected_key.clone() {
+     Some(k) => k,
+     None => {
+         self.error_message = Some("Please select a signing key.".into());
+         self.status = ResumeTokensStatus::ErrorMessage("No key selected".into());
+         return AppAction::None;
+     }
+ },

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/ui/tokens/resume_tokens_screen.rs around lines 230 to 241, the code
currently calls expect("No key selected") when building the ResumeTokens task
which will panic if no signing key is selected; instead, check self.selected_key
first and if None set an appropriate error state on the UI (e.g., self.error =
Some("No signing key selected".into())) and return early so the AppAction is not
constructed, and only build and dispatch the BackendTask when a signing_key is
present (use the cloned key from the Some variant).

Comment on lines +63 to +67
Frame::group(ui.style())
.stroke(visuals.widgets.noninteractive.bg_stroke)
.fill(visuals.extreme_bg_color)
.inner_margin(Margin::same(12))
.show(ui, |ui| {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Use f32 for Margin::same.

Literal 12 is an integer; Margin::same requires f32.

-                .inner_margin(Margin::same(12))
+                .inner_margin(Margin::same(12.0))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Frame::group(ui.style())
.stroke(visuals.widgets.noninteractive.bg_stroke)
.fill(visuals.extreme_bg_color)
.inner_margin(Margin::same(12))
.show(ui, |ui| {
Frame::group(ui.style())
.stroke(visuals.widgets.noninteractive.bg_stroke)
.fill(visuals.extreme_bg_color)
.inner_margin(Margin::same(12.0))
.show(ui, |ui| {
🤖 Prompt for AI Agents
In src/ui/tokens/tokens_screen/contract_details.rs around lines 63 to 67, the
call Margin::same(12) passes an integer literal but the function expects an f32;
replace the integer literal with a floating-point literal such as
Margin::same(12.0) (or Margin::same(12.0_f32)) so the type matches and the code
compiles.

Comment on lines +2509 to +2521
let decimals = self.decimals_input.parse::<u8>().unwrap_or(0);
let input = self
.base_supply_input
.get_or_insert_with(|| AmountInput::new(Amount::new(0, decimals)));

if decimals != input.decimal_places() {
// Update decimals; it will change actual value but I guess this is what user expects
input.set_decimal_places(decimals);
}

let response = input.show(ui);
response.inner.update(&mut self.base_supply_amount);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Clamp decimals and allow zero base supply.

Parsing decimals to 0 by default is surprising; also, base supply of 0 should be permitted in creator flows.

-        let decimals = self.decimals_input.parse::<u8>().unwrap_or(0);
+        // Clamp to a reasonable range; use DEFAULT_DECIMALS if parse fails
+        let decimals = self
+            .decimals_input
+            .parse::<u8>()
+            .unwrap_or(DEFAULT_DECIMALS)
+            .min(18);
@@
-        if decimals != input.decimal_places() {
+        if decimals != input.decimal_places() {
             // Update decimals; it will change actual value but I guess this is what user expects
             input.set_decimal_places(decimals);
         }
+        // Allow zero base supply
+        input.set_min_amount(Some(0));

Please confirm the platform’s maximum supported decimals for tokens. If it differs from 18, adjust the clamp accordingly.


🌐 Web query:

What is the allowed range (min/max) for token decimals on Dash Platform tokens?

💡 Result:

Allowed decimals: minimum 0, maximum 16. [1][2]

Sources:
[1] Dash Platform — Contract Tokens (Decimal places constraint).
[2] Dash Platform — Tokens (Decimal precision default and configuration).


🏁 Script executed:

#!/bin/bash
# Find DEFAULT_DECIMALS constant definition
rg -n "DEFAULT_DECIMALS" --type rust

Length of output: 382


🏁 Script executed:

#!/bin/bash
# Find set_min_amount method definition
rg -n "set_min_amount" --type rust -C 2

Length of output: 391


🏁 Script executed:

#!/bin/bash
# Check context around the code in question
cat src/ui/tokens/tokens_screen/mod.rs | sed -n '2500,2530p'

Length of output: 1238


Update decimals clamping to match Dash Platform constraint (max 16, not 18).

Per Dash Platform specifications, token decimals have a maximum of 16. Update the suggested clamping accordingly:

-        let decimals = self.decimals_input.parse::<u8>().unwrap_or(0);
+        // Clamp to Dash Platform range [0, 16]; use DEFAULT_DECIMALS if parse fails
+        let decimals = self
+            .decimals_input
+            .parse::<u8>()
+            .unwrap_or(DEFAULT_DECIMALS)
+            .min(16);

The rest of the review's suggestions are valid: DEFAULT_DECIMALS (8) is defined at line 78, and set_min_amount() exists in src/ui/components/amount_input.rs:227 and accepts Option<Credits>. Allowing zero base supply via input.set_min_amount(Some(0)) is correctly proposed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let decimals = self.decimals_input.parse::<u8>().unwrap_or(0);
let input = self
.base_supply_input
.get_or_insert_with(|| AmountInput::new(Amount::new(0, decimals)));
if decimals != input.decimal_places() {
// Update decimals; it will change actual value but I guess this is what user expects
input.set_decimal_places(decimals);
}
let response = input.show(ui);
response.inner.update(&mut self.base_supply_amount);
}
// Clamp to Dash Platform range [0, 16]; use DEFAULT_DECIMALS if parse fails
let decimals = self
.decimals_input
.parse::<u8>()
.unwrap_or(DEFAULT_DECIMALS)
.min(16);
let input = self
.base_supply_input
.get_or_insert_with(|| AmountInput::new(Amount::new(0, decimals)));
if decimals != input.decimal_places() {
// Update decimals; it will change actual value but I guess this is what user expects
input.set_decimal_places(decimals);
}
let response = input.show(ui);
response.inner.update(&mut self.base_supply_amount);
}
🤖 Prompt for AI Agents
In src/ui/tokens/tokens_screen/mod.rs around lines 2509 to 2521, update the
decimals handling to clamp to Dash Platform's max of 16 (not 18) and use
DEFAULT_DECIMALS as the fallback; specifically, parse decimals, clamp to 0..=16,
create the AmountInput with Amount::new(0, decimals) using DEFAULT_DECIMALS when
parsing fails, call input.set_decimal_places(clamped_decimals) when changed, and
ensure zero base supply is allowed by calling input.set_min_amount(Some(0)).

Comment on lines +216 to +223
let data_contract = Arc::new(
self.app_context
.get_unqualified_contract_by_id(&self.identity_token_balance.data_contract_id)
.expect("Failed to get data contract")
.expect("Data contract not found"),
);

AppAction::BackendTask(BackendTask::TokenTask(Box::new(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t unwrap contract lookup; convert to user-visible error

get_unqualified_contract_by_id(...).expect(...).expect(...) can panic. Handle both Result and Option, set an error status, and bail.

-        let data_contract = Arc::new(
-            self.app_context
-                .get_unqualified_contract_by_id(&self.identity_token_balance.data_contract_id)
-                .expect("Failed to get data contract")
-                .expect("Data contract not found"),
-        );
+        let data_contract = match self
+            .app_context
+            .get_unqualified_contract_by_id(&self.identity_token_balance.data_contract_id)
+        {
+            Ok(Some(dc)) => Arc::new(dc),
+            Ok(None) => {
+                self.transfer_tokens_status =
+                    TransferTokensStatus::ErrorMessage("Data contract not found".into());
+                return AppAction::None;
+            }
+            Err(e) => {
+                self.transfer_tokens_status = TransferTokensStatus::ErrorMessage(format!(
+                    "Failed to get data contract: {e}"
+                ));
+                return AppAction::None;
+            }
+        };
🤖 Prompt for AI Agents
In src/ui/tokens/transfer_tokens_screen.rs around lines 216 to 223, the chained
expect calls on get_unqualified_contract_by_id can panic; replace them with
proper error handling by matching the Result first and the Option second: if the
Result is Err, set the screen's error status/message accordingly and return/bail
from the function; if the Result is Ok(None), set a "Data contract not found"
error status/message and return/bail; only when you have Ok(Some(contract)) wrap
it in Arc and continue to create the BackendTask. Ensure any early returns clean
up or reset UI state as needed.

Copy link
Copy Markdown
Contributor

@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 (1)
.github/workflows/release.yml (1)

119-129: Improved build logging and diagnostics with strict shell settings.

The build step now enforces strict shell behavior (set -euo pipefail), captures output to per-platform logs, and sets environment variables for better error diagnostics. This is solid for CI/CD observability.

Minor note on environment variable syntax: Line 129 uses a ternary operator (matrix.target == 'x86_64-pc-windows-gnu' && '1' || '') to conditionally set BLST_PORTABLE. While functional, consider using an explicit GitHub Actions if: condition at the step level for improved clarity. However, the current approach is acceptable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80e9890 and 0846a61.

📒 Files selected for processing (1)
  • .github/workflows/release.yml (3 hunks)
🔇 Additional comments (1)
.github/workflows/release.yml (1)

142-147: Good practice: Uploading per-platform build logs as artifacts.

The new step to upload build logs ensures diagnostics are preserved for all builds (using if: always()), enabling efficient post-mortem analysis. This complements the error summarization and improves observability across platforms.

- name: Install essentials
if: ${{ runner.os == 'Linux' }}
run: sudo apt-get update && sudo apt-get install -y build-essential pkg-config clang cmake unzip libsqlite3-dev gcc-mingw-w64 mingw-w64 libsqlite3-dev mingw-w64-x86-64-dev gcc-aarch64-linux-gnu zip && uname -a && cargo clean
run: sudo apt-get update && sudo apt-get install -y build-essential pkg-config clang cmake unzip libsqlite3-dev gcc-mingw-w64 mingw-w64 libsqlite3-dev mingw-w64-x86-64-dev gcc-aarch64-linux-gnu zip nasm && uname -a && cargo clean
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove duplicate libsqlite3-dev package from install list.

Line 81 specifies libsqlite3-dev twice in the apt-get install command, which is redundant.

Apply this diff to deduplicate the package:

-        run: sudo apt-get update && sudo apt-get install -y build-essential pkg-config clang cmake unzip libsqlite3-dev gcc-mingw-w64 mingw-w64 libsqlite3-dev mingw-w64-x86-64-dev gcc-aarch64-linux-gnu zip nasm && uname -a && cargo clean
+        run: sudo apt-get update && sudo apt-get install -y build-essential pkg-config clang cmake unzip libsqlite3-dev gcc-mingw-w64 mingw-w64 mingw-w64-x86-64-dev gcc-aarch64-linux-gnu zip nasm && uname -a && cargo clean
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
run: sudo apt-get update && sudo apt-get install -y build-essential pkg-config clang cmake unzip libsqlite3-dev gcc-mingw-w64 mingw-w64 libsqlite3-dev mingw-w64-x86-64-dev gcc-aarch64-linux-gnu zip nasm && uname -a && cargo clean
run: sudo apt-get update && sudo apt-get install -y build-essential pkg-config clang cmake unzip libsqlite3-dev gcc-mingw-w64 mingw-w64 mingw-w64-x86-64-dev gcc-aarch64-linux-gnu zip nasm && uname -a && cargo clean
🤖 Prompt for AI Agents
.github/workflows/release.yml around line 81: the apt-get install command lists
libsqlite3-dev twice; remove the duplicated libsqlite3-dev entry so each package
appears only once in the install list, leaving the rest of the packages and
command intact.

Comment on lines +131 to +140
- name: Summarize build errors
if: failure()
run: |
echo "::group::Detected error lines"
# Try to extract the most relevant error lines from the log
grep -nE "^error(\[[A-Z0-9]+\])?:|^thread '.*' panicked at|^note:|^= note:" -m 200 build-${{ matrix.platform }}.log || true
echo "::endgroup::"
echo "::group::Last 200 lines of build output"
tail -n 200 build-${{ matrix.platform }}.log || true
echo "::endgroup::"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify grep flag behavior in error summarization step.

Line 136 uses grep -m 200, which stops after 200 matches, not 200 lines. Depending on the log structure, this could truncate error output unexpectedly if there are many error/note lines. Confirm this aligns with intent; otherwise, consider removing the -m flag or increasing the limit if needed.


🏁 Script executed:

#!/bin/bash
# Description: Verify grep -m 200 behavior on a sample Rust build log
# Test: Build a Rust project and capture error patterns to understand match density

# Create a test log with multiple error patterns
cat > test_build.log << 'EOF'
error[E0425]: cannot find value `undefined` in this scope
 --> src/main.rs:10:5
  |
10 | undefined_var;
  | ^^^^^^^^^^^^^ not found in this scope
note: that this error is documented as follows:
= note: this is expected behavior

error[E0382]: use of moved value: `x`
 --> src/main.rs:20:10
  |
20 | let y = x;
  | ^ value used after move
= note: move occurs because `x` has type `Vec<i32>`, which does not implement the `Copy` trait
EOF

# Simulate the grep used in the workflow
echo "=== Testing grep -m 200 ==="
grep -nE "^error(\[[A-Z0-9]+\])?:|^thread '.*' panicked at|^note:|^= note:" -m 200 test_build.log | wc -l
echo "Lines matched: $(grep -nE "^error(\[[A-Z0-9]+\])?:|^thread '.*' panicked at|^note:|^= note:" -m 200 test_build.log | wc -l)"

Length of output: 46


Let me verify this with a more realistic test case that simulates a build log with multiple errors:

Let me test with a more realistic scenario with many Rust compilation errors:

Based on the verification, I can now generate the final rewritten comment. The review comment's concern is factually correct—grep -m NUM stops after NUM matching lines. The script confirmed this behavior. However, I should note that the workflow also has a fallback tail -n 200 command that captures the last 200 lines of the log, providing additional error context.

Update the grep command to remove the -m 200 limit or document the truncation risk.

The -m 200 flag stops after 200 matching lines, not 200 file lines. Given that dozens or hundreds of build errors can occur during refactoring, and each Rust error typically spans multiple matching lines (error declaration, location info, and notes), a single build could generate more than 200 matches, causing the error summary to truncate unexpectedly. Consider removing -m 200 to capture all error patterns, or if truncation is intentional, add a comment explaining the limit.

🤖 Prompt for AI Agents
.github/workflows/release.yml around lines 131 to 140: the grep command uses -m
200 which limits matches to 200 matching lines (not file lines) and can truncate
multi-line Rust errors; remove the -m 200 option so grep captures all matching
error/note/panic lines, or if you want a deliberate cap, replace it with a
comment explaining truncation and choose a more appropriate strategy (e.g., pipe
through head/tail after matching or output match count) so the summarized errors
are not accidentally incomplete.

Copy link
Copy Markdown
Contributor

@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

♻️ Duplicate comments (2)
.github/workflows/release.yml (2)

94-94: Remove duplicate libsqlite3-dev package from install list.

Line 94 specifies libsqlite3-dev twice in the apt-get install command. This was flagged in a previous review. Apply this diff to deduplicate:

-        run: sudo apt-get update && sudo apt-get install -y build-essential pkg-config clang cmake unzip libsqlite3-dev gcc-mingw-w64 mingw-w64 libsqlite3-dev mingw-w64-x86-64-dev gcc-aarch64-linux-gnu zip nasm && uname -a && cargo clean
+        run: sudo apt-get update && sudo apt-get install -y build-essential pkg-config clang cmake unzip libsqlite3-dev gcc-mingw-w64 mingw-w64 mingw-w64-x86-64-dev gcc-aarch64-linux-gnu zip nasm && uname -a && cargo clean

157-168: Address unresolved grep -m 200 truncation issue from prior review.

Line 163 uses grep -m 200, which was flagged as a critical issue in a previous review. The -m flag limits matches to 200 matching lines (not file lines). During heavy build failures with dozens of Rust errors, each spanning multiple lines (error declaration, location, notes), this truncates error output unexpectedly.

The fallback tail -n 200 provides partial mitigation but doesn't solve the grep truncation. Either:

  • Remove the -m 200 limit to capture all matching error patterns, or
  • If truncation is intentional, add a comment explaining the limit and document the trade-off.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 028e213 and e26aa31.

📒 Files selected for processing (1)
  • .github/workflows/release.yml (7 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Build and Release Dash Evo Tool (linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, arm64-...
  • GitHub Check: Build and Release Dash Evo Tool (Windows, windows-latest, x86_64-pc-windows-msvc, windows, .exe)
  • GitHub Check: Build and Release Dash Evo Tool (macos-arm64, macos-latest, aarch64-apple-darwin, arm64-mac)
  • GitHub Check: Build and Release Dash Evo Tool (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-gnu, x86_64-linux)
  • GitHub Check: Build and Release Dash Evo Tool (macos-x86_64, macos-13, x86_64-apple-darwin, x86_64-mac)
  • GitHub Check: Test Suite
  • GitHub Check: Clippy
🔇 Additional comments (4)
.github/workflows/release.yml (4)

44-48: Verify obsolete Windows GNU protoc and libsql steps are intentionally disabled or should be removed.

The matrix target changed from x86_64-pc-windows-gnu to x86_64-pc-windows-msvc. However, the workflow still contains steps that conditionally target the old GNU variant:

  • Line 109-112: "Install protoc (Windows)" targets x86_64-pc-windows-gnu
  • Line 139-140: "Windows libsql" targets x86_64-pc-windows-gnu

These steps will never execute with the new matrix. Confirm whether they should be removed or maintained for future GNU support.


56-67: LGTM!

Windows-specific setup is well-structured: properly configures long paths, sets Cargo/Rustup homes, and persists environment variables for downstream steps. PowerShell usage is appropriate here.


114-125: LGTM!

Windows MSVC protoc installation is properly structured: correct error handling, version consistency (v25.2), and proper PATH configuration using $env:GITHUB_PATH. Implementation follows PowerShell best practices.


176-188: LGTM!

Platform-specific packaging is well-structured: Windows uses PowerShell with proper error handling and file cleanup, Linux/macOS use standard zip command. Packaging logic correctly targets appropriate shells and maintains consistent artifact naming.

Comment on lines +143 to +155
shell: bash
run: |
cargo build --release --target ${{ matrix.target }}
set -euo pipefail
echo "::group::Cargo build output (${{ matrix.target }})"
cargo build --release --target ${{ matrix.target }} 2>&1 | tee build-${{ matrix.platform }}.log
echo "::endgroup::"
mv target/${{ matrix.target }}/release/dash-evo-tool${{ matrix.ext }} dash-evo-tool/dash-evo-tool${{ matrix.ext }}
env:
CC_x86_64_pc_windows_gnu: x86_64-w64-mingw32-gcc
AR_x86_64_pc_windows_gnu: x86_64-w64-mingw32-ar
CFLAGS_x86_64_pc_windows_gnu: "-O2"
RUST_BACKTRACE: "1"
BLST_PORTABLE: ${{ matrix.target == 'x86_64-pc-windows-gnu' && '1' || '' }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify bash shell compatibility with Windows paths and remove dead BLST_PORTABLE condition.

Two concerns:

  1. Shell compatibility on Windows: shell: bash on line 143 relies on Git Bash being available. While this typically works, verify that Cargo/Rust paths and build artifacts are correctly resolved on Windows. Consider using PowerShell for Windows and bash for Unix platforms for better path handling consistency.

  2. Dead code: Line 155 sets BLST_PORTABLE conditionally on x86_64-pc-windows-gnu, but this target is no longer in the matrix (switched to MSVC on line 46). This condition will never be true. Remove the condition or update it if BLST_PORTABLE is needed for the MSVC target.


🏁 Script executed:

#!/bin/bash
# Examine the GitHub Actions workflow file structure
cat .github/workflows/release.yml | head -60
echo "---"
# Show lines around 143-155 and context
sed -n '130,165p' .github/workflows/release.yml

Length of output: 3505


🏁 Script executed:

#!/bin/bash
# Check for x86_64-pc-windows-gnu in the matrix
echo "=== Checking for x86_64-pc-windows-gnu in workflow ==="
grep -n "x86_64-pc-windows-gnu" .github/workflows/release.yml
echo ""
echo "=== Checking matrix strategy section ==="
sed -n '/strategy:/,/^[^ ]/p' .github/workflows/release.yml | head -50

Length of output: 2093


🏁 Script executed:

#!/bin/bash
# Check all uses of matrix.target in the workflow
echo "=== All matrix.target references ==="
grep -n "matrix\.target" .github/workflows/release.yml

Length of output: 864


🏁 Script executed:

#!/bin/bash
# Check if BLST_PORTABLE is used elsewhere
echo "=== All BLST_PORTABLE references ==="
grep -n "BLST_PORTABLE" .github/workflows/release.yml

Length of output: 126


🏁 Script executed:

#!/bin/bash
# Get full matrix definition
echo "=== Extracting matrix definition ==="
python3 << 'EOF'
import re
with open('.github/workflows/release.yml', 'r') as f:
    content = f.read()
    # Find the matrix section
    matrix_match = re.search(r'strategy:\s*matrix:(.*?)(?=\n[^ ]|\Z)', content, re.DOTALL)
    if matrix_match:
        matrix_section = matrix_match.group(1)
        lines = matrix_section.split('\n')
        for i, line in enumerate(lines[:40], 1):  # First 40 lines of matrix
            print(f"{i:3}: {line}")
EOF

Length of output: 932


🏁 Script executed:

#!/bin/bash
# Check build step location and context
echo "=== Build step and surrounding context ==="
sed -n '135,160p' .github/workflows/release.yml | cat -n

Length of output: 1468


🏁 Script executed:

#!/bin/bash
# Check shell: bash usage in workflow
echo "=== All shell specifications ==="
grep -n "shell:" .github/workflows/release.yml

Length of output: 192


🏁 Script executed:

#!/bin/bash
# Check matrix platform values
echo "=== Checking matrix.platform usage ==="
grep -n "matrix\.platform" .github/workflows/release.yml

Length of output: 1044


Remove dead code targeting x86_64-pc-windows-gnu—this target is no longer in the matrix.

The BLST_PORTABLE condition at line 155 references x86_64-pc-windows-gnu, which has been replaced with x86_64-pc-windows-msvc in the matrix. This condition will never be true and should be removed.

Additionally, clean up other dead code targeting the old gnu target:

  • Environment variables at lines 151–153: CC_x86_64_pc_windows_gnu, AR_x86_64_pc_windows_gnu, CFLAGS_x86_64_pc_windows_gnu
  • The "Windows libsql" step at line 139, which is gated on the removed target
  • The "Install protoc (Windows libsql)" step at line 109

The shell: bash at line 143 is appropriate and works correctly on GitHub Actions Windows runners.

@pauldelucia pauldelucia merged commit 3268b73 into master Oct 27, 2025
10 checks passed
@pauldelucia pauldelucia deleted the v0.9.3 branch October 27, 2025 11:08
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.

4 participants