Skip to content

fix: import UserDirs#212

Merged
QuantumExplorer merged 1 commit into
dashpay:v0.9-devfrom
thephez:fix-undeclared-userdirs
Apr 24, 2025
Merged

fix: import UserDirs#212
QuantumExplorer merged 1 commit into
dashpay:v0.9-devfrom
thephez:fix-undeclared-userdirs

Conversation

@thephez
Copy link
Copy Markdown
Contributor

@thephez thephez commented Apr 24, 2025

Builds of v0.9-dev (629a55a) failed for me on Ubuntu 22.04 with the following error without this change:

warning: version requirement `0.9.34+deprecated` for dependency `serde_yaml` includes semver metadata which will be ignored, removing the metadata is recommended to avoid confusion
warning: unused manifest key: build
   Compiling dash-evo-tool v0.9.0 (/code/dash-evo-tool)
error[E0433]: failed to resolve: use of undeclared type `UserDirs`
  --> src/app_dir.rs:29:9
   |
29 |         UserDirs::new()
   |         ^^^^^^^^ use of undeclared type `UserDirs`
   |
help: consider importing this struct
   |
1  + use directories::UserDirs;
   |

warning: unused variable: `rows`
   --> src/ui/wallets/import_wallet_screen.rs:162:17
    |
162 |             let rows = (self.selected_seed_phrase_length + columns - 1) / columns;
    |                 ^^^^ help: if this is intentional, prefix it with an underscore: `_rows`
    |
    = note: `#[warn(unused_variables)]` on by default

For more information about this error, try `rustc --explain E0433`.
warning: `dash-evo-tool` (bin "dash-evo-tool") generated 1 warning
error: could not compile `dash-evo-tool` (bin "dash-evo-tool") due to 1 previous error; 1 warning emitted

Summary by CodeRabbit

  • New Features

    • Added comprehensive token management screens, enabling users to add, mint, burn, transfer, freeze, unfreeze, pause, resume, and claim tokens, as well as view token claims.
    • Introduced a contract registration screen for registering new data contracts.
    • Enhanced network support with Devnet and Local (Regtest) environments, including configuration and UI integration.
    • Added advanced system actions, such as clearing local platform data for Devnet.
    • Improved contract chooser panel with collapsible sections for document types, tokens, and contract JSON.
  • Improvements

    • Expanded database structure and migrations to support token metadata, balances, and ordering.
    • Refined UI components for better navigation, filtering, and styling consistency (e.g., updated button and frame styling).
    • Enhanced wallet unlocking flows and key management across identity and token actions.
    • Improved error handling, logging, and backend task feedback throughout the application.
  • Bug Fixes

    • Addressed robustness in identity and token order management, and improved error specificity in wallet operations.
  • Chores

    • Upgraded dependencies and minimum Rust version.
    • Updated configuration files and environment variable templates for new network types.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2025

Walkthrough

This update introduces comprehensive support for token management and multi-network operations within the application. Major changes include the addition of new backend and UI modules for token operations (such as minting, burning, freezing, unfreezing, pausing, resuming, transferring, and claiming tokens), as well as the ability to register data contracts directly from the UI. The application now supports Devnet and Regtest (Local) networks alongside Mainnet and Testnet, with corresponding configuration, context, and UI enhancements. Database schema migrations and new tables support token metadata, balances, and ordering. The codebase also improves thread safety via locking, refines error handling, and updates UI components for better user interaction and navigation.

Changes

Files / Areas Change Summary
Cargo.toml, .env.example, rust-toolchain.toml, .gitignore Updated dependencies, Rust version, environment config for Devnet/Local, and ignore rules.
src/app.rs, src/context.rs, src/config.rs, src/app_dir.rs, src/context_provider.rs Added Devnet/Regtest network support, expanded context/config management, improved locking and network switching.
src/backend_task/mod.rs, src/backend_task/tokens/*, src/backend_task/register_contract.rs, src/backend_task/system_task/* Introduced token management backend tasks (mint, burn, freeze, transfer, etc.), contract registration, and system tasks (e.g., wiping devnet data).
src/database/tokens.rs, src/database/contracts.rs, src/database/identities.rs, src/database/asset_lock_transaction.rs, src/database/initialization.rs, src/database/scheduled_votes.rs Added/updated tables and methods for tokens, balances, ordering, and improved migration handling.
src/ui/tokens/*, src/ui/components/tokens_subscreen_chooser_panel.rs, src/ui/mod.rs, src/ui/components/mod.rs Added UI screens for all token actions, token subscreen chooser, and integrated them into main UI navigation.
src/ui/contracts_documents/register_contract_screen.rs, src/ui/contracts_documents/mod.rs, src/ui/contracts_documents/document_query_screen.rs Added contract registration UI and improved contract query screen.
src/ui/network_chooser_screen.rs, src/ui/components/left_panel.rs, src/ui/components/top_panel.rs UI support for Devnet/Regtest, new navigation options, and improved panel styling.
src/ui/dpns/dpns_contested_names_screen.rs Enhanced DPNS screen with filtering and bulk voting options.
src/backend_task/core/mod.rs, src/backend_task/core/start_dash_qt.rs, src/backend_task/core/refresh_wallet_info.rs Generalized core RPC handling for all networks, improved authentication (cookie fallback), and locking.
src/model/qualified_identity/mod.rs Added method to list available authentication keys.
src/model/wallet/*, src/ui/identities/*, src/ui/wallets/* Improved wallet unlocking, error messages, and UI consistency (corner radius, margins).
src/logging.rs Enabled global debug logging.
Other UI files (various) Updated panel/frame styling, improved button rounding, and minor UI/UX tweaks.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant AppContext
    participant BackendTask
    participant Database
    participant DashSDK

    User->>UI: Initiate token action (e.g., Mint/Burn/Transfer)
    UI->>AppContext: Dispatch BackendTask::TokenTask
    AppContext->>BackendTask: Run token task (e.g., mint_tokens)
    BackendTask->>DashSDK: Build and sign token state transition
    DashSDK-->>BackendTask: Signed transition or error
    BackendTask->>DashSDK: Broadcast transition
    DashSDK-->>BackendTask: Proof/result or error
    BackendTask->>Database: Update token balances/metadata
    BackendTask-->>AppContext: Return BackendTaskSuccessResult
    AppContext-->>UI: Display result/status
    UI-->>User: Show confirmation, error, or success
Loading
sequenceDiagram
    participant User
    participant UI
    participant AppContext
    participant Database

    User->>UI: Register new data contract
    UI->>AppContext: Dispatch BackendTask::RegisterDataContract
    AppContext->>DashSDK: Submit contract to platform
    DashSDK-->>AppContext: Confirmation or error
    AppContext->>Database: Insert contract and tokens
    Database-->>AppContext: Success/Failure
    AppContext-->>UI: Display registration result
    UI-->>User: Show contract registration status
Loading

Possibly related PRs

  • dashpay/dash-evo-tool#183: Refactors identity order table and dangling reference cleanup, directly related to the new identity order handling and validation.
  • dashpay/dash-evo-tool#185: Adds Devnet and Local network configuration in .env.example, directly matching the new network support in this PR.
  • dashpay/dash-evo-tool#89: Refactors core task handling for multiple networks, which is extended further in this PR to support Devnet and Regtest.

Poem

A rabbit with tokens, so clever and spry,
Now hops through networks—Mainnet, Devnet, and Local nearby!
Mint, burn, or freeze, with a click of a paw,
Contracts to register, with nary a flaw.
The database burrows are tidy and neat,
New screens and new buttons—a user’s treat!
🥕 Hop, hop, hooray! The toolkit’s complete!

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@thephez thephez changed the base branch from master to v0.9-dev April 24, 2025 18:49
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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

🛑 Comments failed to post (57)
.github/workflows/release.yml (1)

33-33: ⚠️ Potential issue

Warning: Unknown GitHub Actions runner label.

The label "ubuntu-22.04-arm" is not a standard GitHub-hosted runner. GitHub's standard runners include combinations like "ubuntu-latest" and "self-hosted" or architecture-specific labels like "linux" and "arm64".

If this is a custom self-hosted runner, you should define it in an actionlint.yaml configuration file to avoid warnings. Otherwise, consider using one of GitHub's officially supported runner labels such as "ubuntu-latest" with architecture labels or a properly specified self-hosted runner.

🧰 Tools
🪛 actionlint (1.7.4)

33-33: label "ubuntu-22.04-arm" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2022", "windows-2019", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-22.04", "ubuntu-20.04", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "macos-12-xl", "macos-12-xlarge", "macos-12-large", "macos-12", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file

(runner-label)

src/model/qualified_identity/qualified_identity_public_key.rs (1)

44-47: ⚠️ Potential issue

Refactored address derivation method with potential error handling issue.

The code now explicitly constructs a PublicKey from raw data and then derives an address, which is more explicit but uses expect() which could cause a panic with invalid keys.

Consider using proper error handling instead of expect() to gracefully handle invalid public keys:

-        let pubkey =
-            PublicKey::from_slice(value.data().as_slice()).expect("Expected valid public key");
-
-        let address = Address::p2pkh(&pubkey, network);
+        let pubkey = match PublicKey::from_slice(value.data().as_slice()) {
+            Ok(key) => key,
+            Err(e) => {
+                eprintln!("Invalid public key: {}", e);
+                return Self {
+                    identity_public_key: value,
+                    in_wallet_at_derivation_path: None,
+                };
+            }
+        };
+
+        let address = Address::p2pkh(&pubkey, network);

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

src/backend_task/core/refresh_wallet_info.rs (2)

33-37: ⚠️ Potential issue

Added explicit locking for thread-safe core client access.

The code now correctly acquires a read lock on the core client before making RPC calls, enhancing thread safety.

However, using expect() for lock errors is problematic as it will crash the application if the lock is poisoned. Consider proper error handling:

-        match self
-            .core_client
-            .read()
-            .expect("Core client lock was poisoned")
-            .get_received_by_address(address, None)
+        match self
+            .core_client
+            .read()
+            .map_err(|e| format!("Failed to acquire core client lock: {}", e.to_string()))?
+            .get_received_by_address(address, None)
📝 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.

            match self
                .core_client
                .read()
                .map_err(|e| format!("Failed to acquire core client lock: {}", e.to_string()))?
                .get_received_by_address(address, None)

59-65: ⚠️ Potential issue

Improved thread-safe access to core client in reload_utxos.

The code now acquires a read lock on the core client before passing it to reload_utxos, but similar to above, it uses expect() which could crash the application.

Replace with proper error handling:

-            match wallet_guard.reload_utxos(
-                &self
-                    .core_client
-                    .read()
-                    .expect("Core client lock was poisoned"),
-                self.network,
-                Some(self),
-            ) {
+            match wallet_guard.reload_utxos(
+                &self
+                    .core_client
+                    .read()
+                    .map_err(|e| format!("Failed to acquire core client lock: {}", e.to_string()))?,
+                self.network,
+                Some(self),
+            ) {
📝 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.

            match wallet_guard.reload_utxos(
                &self
                    .core_client
                    .read()
                    .map_err(|e| format!("Failed to acquire core client lock: {}", e.to_string()))?,
                self.network,
                Some(self),
            ) {
                // …
src/backend_task/identity/load_identity_from_wallet.rs (1)

32-34: 🛠️ Refactor suggestion

Use read lock instead of write lock for immutable access

identity_authentication_ecdsa_public_key only needs an immutable borrow, yet a write lock is taken:

let wallet = wallet_arc_ref.wallet.write().unwrap();

Switch to read() to maximise concurrency and avoid unnecessary blocking of other threads:

- let wallet = wallet_arc_ref.wallet.write().unwrap();
+ let wallet = wallet_arc_ref.wallet.read().expect("wallet read lock poisoned");
src/backend_task/identity/top_up_identity.rs (1)

63-66: 🛠️ Refactor suggestion

Hold core-client read lock only as long as necessary

get_raw_transaction_info can be time-consuming and will block all writers while the read lock is held.
Consider:

let core_client = self.core_client.read()
    .expect("Core client lock poisoned")
    .clone();       // clone the RPC handle (cheap)
drop(core_client_lock); // release lock early
let raw_info = core_client.get_raw_transaction_info(&tx_id, None)?;

This minimises contention if other tasks need to mutate core_client.

src/ui/wallets/wallets_screen/mod.rs (1)

557-571: 🛠️ Refactor suggestion

Enhanced asset lock functionality with proper action handling.

The method now returns an AppAction instead of (), allowing it to trigger a backend task when the "Find asset locks" button is clicked. This improves the UI feedback loop and enables refreshing wallet info directly from this UI component.

The early return pattern here is good, but consider adding a test to verify that the action is properly propagated to the backend task system.

 fn render_wallet_asset_locks(&mut self, ui: &mut Ui) -> AppAction {
     let mut app_action = AppAction::None;
     if let Some(arc_wallet) = &self.selected_wallet {
         let wallet = arc_wallet.read().unwrap();

         if wallet.unused_asset_locks.is_empty() {
             ui.label("No asset locks available.");

             if ui.button("Find asset locks").clicked() {
                 app_action = AppAction::BackendTask(BackendTask::CoreTask(
                     CoreTask::RefreshWalletInfo(arc_wallet.clone()),
                 ))
-            };
+            }
             return app_action;
         }
📝 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.

    fn render_wallet_asset_locks(&mut self, ui: &mut Ui) -> AppAction {
        let mut app_action = AppAction::None;
        if let Some(arc_wallet) = &self.selected_wallet {
            let wallet = arc_wallet.read().unwrap();

            if wallet.unused_asset_locks.is_empty() {
                ui.label("No asset locks available.");

                if ui.button("Find asset locks").clicked() {
                    app_action = AppAction::BackendTask(BackendTask::CoreTask(
                        CoreTask::RefreshWalletInfo(arc_wallet.clone()),
                    ))
                }
                return app_action;
            }

            // ... rest of rendering when there are locks
        }
        app_action
    }
src/backend_task/identity/register_identity.rs (2)

149-151: 🛠️ Refactor suggestion

Factor out repeated core-client access & eliminate expect panics

The same three lines (self.core_client.read().expect("…")) appear at least three times.
Besides the panic risk discussed above, the repetition is a code-smell and makes future changes harder.

fn core_client(&self) -> Result<Arc<CoreClient>, String> {
    self.core_client
        .read()
        .map_err(|_| "Core client lock poisoned".to_owned())
        .map(|g| g.clone())
}

Then:

-self.core_client
-    .read()
-    .expect("Core client lock was poisoned")
+self.core_client()?

This removes the panic, shortens call-sites, and centralises the error message.

Also applies to: 221-223, 288-290


121-124: 🛠️ Refactor suggestion

⚠️ Potential issue

Avoid panicking on poisoned lock – return a typed error instead

RwLock::read().unwrap() will panic if the lock is poisoned. In a long-running background task this will bring the entire process down, the opposite of what we want from a backend worker. Consider mapping the poisoned-lock case into your existing Result<String> flow instead of panicking:

-let guard = self.sdk.read().unwrap();
-guard.clone()
+let sdk = self
+    .sdk
+    .read()
+    .map_err(|_| "SDK lock poisoned".to_owned())?
+    .clone();
+sdk
📝 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.

        let sdk = {
            let sdk = self
                .sdk
                .read()
                .map_err(|_| "SDK lock poisoned".to_owned())?
                .clone();
            sdk
        };
src/backend_task/tokens/resume_tokens.rs (1)

14-48: 🛠️ Refactor suggestion

Unused sender parameter – either use it or drop it

_sender is accepted but never utilised. This is easy to forget and may hide logic that was supposed to report progress back to the UI. Options:

  1. Send a success/failure TaskResult exactly as the other token tasks do.
  2. Remove the parameter to keep the signature minimal.
-        sdk: &Sdk,
-        _sender: mpsc::Sender<TaskResult>,
+        sdk: &Sdk,

If you choose (1), remember to .await sender.send(…) before returning.

📝 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.

impl AppContext {
    pub async fn resume_tokens(
        &self,
        actor_identity: &QualifiedIdentity,
        data_contract: &DataContract,
        token_position: u16,
        signing_key: IdentityPublicKey,
        sdk: &Sdk,
    ) -> Result<BackendTaskSuccessResult, String> {
        // Use .resume(...) constructor
        let builder = TokenEmergencyActionTransitionBuilder::resume(
            data_contract,
            token_position,
            actor_identity.identity.id(),
        );

        // Optionally chain .with_public_note(...).with_settings(...), etc.

        let state_transition = builder
            .sign(sdk, &signing_key, actor_identity, self.platform_version)
            .await
            .map_err(|e| format!("Error signing Resume Tokens transition: {}", e.to_string()))?;

        // Broadcast
        let _proof_result = state_transition
            .broadcast_and_wait::<StateTransitionProofResult>(sdk, None)
            .await
            .map_err(|e| format!("Error broadcasting Resume Tokens transition: {}", e))?;

        // Return success
        Ok(BackendTaskSuccessResult::Message(
            "ResumeTokens".to_string(),
        ))
    }
}
src/database/scheduled_votes.rs (1)

24-26: ⚠️ Potential issue

network should be part of the primary key to avoid collisions across networks

As defined, (identity_id, contested_name) must be unique globally, even though the table is scoped per network elsewhere in the code (WHERE network = ?).
Running the app on Mainnet and Testnet simultaneously will cause PRIMARY KEY conflicts.

-PRIMARY KEY (identity_id, contested_name),
+PRIMARY KEY (identity_id, contested_name, network),
📝 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.

                PRIMARY KEY (identity_id, contested_name, network),
                FOREIGN KEY (identity_id) REFERENCES identity(id) ON DELETE CASCADE
            )",
src/backend_task/tokens/burn_tokens.rs (1)

20-24: ⚠️ Potential issue

Validate amount before constructing the state transition

TokenBurnTransitionBuilder::new will happily accept an amount of 0, which produces a no-op state transition that still costs fees.
Consider short-circuiting or returning an error if amount == 0 (and optionally if it exceeds the owner’s balance) to save users from unnecessary fees.

-        amount: u64,
+        amount: u64,
 ) -> Result<BackendTaskSuccessResult, String> {
+        if amount == 0 {
+            return Err("Burn amount must be greater than zero".into());
+        }
📝 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.

        signing_key: IdentityPublicKey,
        amount: u64,
        sdk: &Sdk,
        _sender: mpsc::Sender<TaskResult>,
    ) -> Result<BackendTaskSuccessResult, String> {
        if amount == 0 {
            return Err("Burn amount must be greater than zero".into());
        }
src/backend_task/tokens/query_tokens.rs (1)

45-56: 🛠️ Refactor suggestion

Avoid unnecessary second-phase queries for duplicate contract IDs

If several contractKeywords documents point to the same contract, the current logic will query its shortDescription repeatedly.
De-duplicate before the loop to save bandwidth and reduce latency.

-        let mut contract_ids: Vec<Identifier> = Vec::with_capacity(kw_docs.len());
+        let mut contract_ids: Vec<Identifier> = Vec::with_capacity(kw_docs.len());
+        let mut seen = HashSet::with_capacity(kw_docs.len());
 ...
-                    contract_ids.push(
+                    let cid = cid_val
+                        .to_identifier()
+                        .map_err(|e| format!("Bad contractId: {e}"))?;
+                    if seen.insert(cid) {
+                        contract_ids.push(cid);
+                    }
📝 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.

        let mut contract_ids: Vec<Identifier> = Vec::with_capacity(kw_docs.len());
        let mut seen = HashSet::with_capacity(kw_docs.len());
        for (_doc_id, doc_opt) in kw_docs.iter() {
            if let Some(doc) = doc_opt {
                if let Some(cid_val) = doc.get("contractId") {
                    let cid = cid_val
                        .to_identifier()
                        .map_err(|e| format!("Bad contractId: {e}"))?;
                    if seen.insert(cid) {
                        contract_ids.push(cid);
                    }
                }
            }
        }
src/backend_task/register_contract.rs (1)

59-69: ⚠️ Potential issue

Brittle string-splitting may extract the wrong contract ID

Relying on error_string.split(" ").last() assumes the platform error message always ends with the Base58 contract ID, with no trailing punctuation or newline.
If the format changes (or a log prefix/suffix is added) we’ll parse garbage and break the recovery path.

Consider using a regex with an explicit Base58 capture group or exposing the contract ID in the SDK error type.

-let id_str = error_string
-    .split(" ")
-    .last()
-    .ok_or("Failed to get contract ID from proof error")?;
+let id_re = regex::Regex::new(r"[1-9A-HJ-NP-Za-km-z]{42,44}$")
+    .expect("hard-coded regex is valid");
+let id_str = id_re
+    .find(&error_string)
+    .map(|m| m.as_str())
+    .ok_or("Failed to extract contract ID from proof error")?;

(You’d need to add regex = "1" to Cargo.toml.)
This keeps the heuristic local but far more robust.

📝 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.

                    let error_string = e.to_string();
                    let id_re = regex::Regex::new(r"[1-9A-HJ-NP-Za-km-z]{42,44}$")
                        .expect("hard-coded regex is valid");
                    let id_str = id_re
                        .find(&error_string)
                        .map(|m| m.as_str())
                        .ok_or("Failed to extract contract ID from proof error")?;
                    let id = Identifier::from_string(id_str, Encoding::Base58).map_err(|e| {
                        format!(
                            "Failed to convert contract ID from string to Identifier: {}",
                            e.to_string()
                        )
                    })?;
src/database/asset_lock_transaction.rs (1)

331-378: ⚠️ Potential issue

Migration drops secondary indexes & triggers

The CREATE TABLE … block recreates only the columns and FKs; any indexes, triggers, or CHECK constraints added later on asset_lock_transaction are silently lost.

Before dropping the old table, enumerate PRAGMA index_list/PRAGMA trigger_list and recreate them, or use sqlite_schema introspection:

-- pseudo-code
for idx in pragma_index_list("asset_lock_transaction_old") {}

Losing indexes can degrade performance and break invariants.

src/database/contracts.rs (1)

61-67: ⚠️ Potential issue

Swallowing serialization failure hides real problems

encode_to_vec should never fail for a well-formed TokenConfiguration.
Silently returning Ok(()) masks bugs and leaves the DB in a partial state.

-let Some(serialized_token_configuration) =
-    bincode::encode_to_vec(&token_configuration, config).ok()
-else {
-    // We should always be able to serialize
-    return Ok(());
-};
+let serialized_token_configuration = bincode::encode_to_vec(&token_configuration, config)
+    .map_err(|e| {
+        rusqlite::Error::ToSqlConversionFailure(Box::new(e))
+    })?;

Propagating the error lets the caller decide whether to retry, alert, or rollback.

📝 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.

                        let config = config::standard();
                        let serialized_token_configuration = bincode::encode_to_vec(&token_configuration, config)
                            .map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?;
src/database/identities.rs (2)

318-330: 🛠️ Refactor suggestion

Dropping and recreating identity_order every init loses user customisation
Running initialize_identity_order_table on every start obliterates the saved ordering. Preserve data unless you’re inside a migration:

-// Drop the table if it already exists
-conn.execute("DROP TABLE IF EXISTS identity_order", [])?;
-
-// Recreate with foreign key enforcement
-conn.execute(
-    "CREATE TABLE identity_order ( ... )",
-    [],
-)?;
+conn.execute(
+    "CREATE TABLE IF NOT EXISTS identity_order(
+        pos INTEGER NOT NULL PRIMARY KEY,
+        identity_id BLOB NOT NULL,
+        FOREIGN KEY(identity_id) REFERENCES identity(id) ON DELETE CASCADE
+    )",
+    [],
+)?;
📝 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.

        conn.execute(
            "CREATE TABLE IF NOT EXISTS identity_order(
                pos INTEGER NOT NULL PRIMARY KEY,
                identity_id BLOB NOT NULL,
                FOREIGN KEY(identity_id) REFERENCES identity(id) ON DELETE CASCADE
            )",
            [],
        )?;

34-42: ⚠️ Potential issue

get_alias silently eats every database error
.ok() converts all SQL errors (IO, malformed DB, lock poisoning…) into None, so callers can’t distinguish “alias is NULL” from “disk I/O failure”. Either bubble the error or map QueryReturnedNoRows explicitly.

-        let mut stmt = conn.prepare("SELECT alias FROM identity WHERE id = ?")?;
-        let alias: Option<String> = stmt.query_row(params![id], |row| row.get(0)).ok();
-        Ok(alias)
+        let mut stmt = conn.prepare("SELECT alias FROM identity WHERE id = ?")?;
+        match stmt.query_row(params![id], |row| row.get::<_, Option<String>>(0)) {
+            Ok(alias)                   => Ok(alias),
+            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
+            Err(e)                      => Err(e),
+        }
📝 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.

    pub fn get_alias(&self, identifier: &Identifier) -> rusqlite::Result<Option<String>> {
        let id = identifier.to_vec();
        let conn = self.conn.lock().unwrap();

        let mut stmt = conn.prepare("SELECT alias FROM identity WHERE id = ?")?;
        match stmt.query_row(params![id], |row| row.get::<_, Option<String>>(0)) {
            Ok(alias) => Ok(alias),
            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
            Err(e) => Err(e),
        }
    }
src/backend_task/tokens/query_my_token_balances.rs (2)

84-88: 🛠️ Refactor suggestion

Emit a single refresh event per successful query

TaskResult::Refresh is sent once per token; with large wallets this may flood the UI/event-loop, causing unnecessary redraws and contention on the channel.

-                    for balance in token_balances.iter() {
-                        ...
-                        sender.send(TaskResult::Refresh).await?;
-                    }
+                    for balance in token_balances.iter() {
+                        ...
+                    }
+                    // Notify once after DB batch finished
+                    sender.send(TaskResult::Refresh).await?;

Apply the same batching strategy in query_token_balance.

Also applies to: 135-139


32-44: 🛠️ Refactor suggestion

Unnecessary allocation of unused tuple elements

token_infos captures data_contract_id and token_position, yet those fields are never used after construction.
Keeping them inflates memory-usage and obscures the real intention, because only token_id is needed to build the query.

-            let token_infos = self
-                .identity_token_balances()?
-                ...
-                .map(|t| (t.token_id.clone(),
-                          t.data_contract_id.clone(),
-                          t.token_position))
+            let token_ids: Vec<Identifier> = self
+                .identity_token_balances()?
+                ...
+                .map(|t| t.token_id.clone())
+                .collect();

This removes two needless clones per record and simplifies the subsequent logic (you can drop the extra collect::<Vec<_>>() / re-collect step entirely).

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

src/app_dir.rs (2)

52-60: 🛠️ Refactor suggestion

Handle unknown networks gracefully

Using unimplemented!() will panic in production if the enum gains a new variant.
Return an Err with ErrorKind::InvalidInput (or use _ => return Err(...)) to fail gracefully and keep your function’s Result contract.


30-33: ⚠️ Potential issue

Compile-time bug: and_then closure returns wrong type

Option::and_then expects the closure to return another Option, but to_owned().into() yields a plain PathBuf.
This fails to compile on linux targets.

-        UserDirs::new()
-            .and_then(|dirs| dirs.home_dir().to_owned().into())
-            .map(|home_dir| home_dir.join(".dashcore"))
+        UserDirs::new()
+            .map(|dirs| dirs.home_dir().to_path_buf())
+            .map(|home_dir| home_dir.join(".dashcore"))

Alternatively, keep and_then and wrap the value with Some(...).

📝 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.

        UserDirs::new()
            .map(|dirs| dirs.home_dir().to_path_buf())
            .map(|home_dir| home_dir.join(".dashcore"))
            .ok_or_else(|| {
src/config.rs (1)

274-274: ⚠️ Potential issue

Avoid panicking when parsing DAPI addresses

expect("Could not parse DAPI addresses") will crash the application on a configuration typo.
Please propagate a proper Result instead, bubbling the error up to Config::load().

-        AddressList::from_str(&self.dapi_addresses).expect("Could not parse DAPI addresses")
+        AddressList::from_str(&self.dapi_addresses)
+            .map_err(|e| format!("Invalid DAPI address list: {}", e))?

You can then change the caller to handle the returned Result<AddressList, String>.

src/backend_task/contract.rs (2)

95-109: 🛠️ Refactor suggestion

expect call may panic during token-name extraction

expected_token_configuration(*token.0).expect("Expected to get token configuration")
will abort the whole backend if the contract is malformed. Propagate the error instead:

-        let token_configuration = contract
-            .expected_token_configuration(*token.0)
-            .expect("Expected to get token configuration")
-            .as_cow_v0();
+        let token_configuration = contract
+            .expected_token_configuration(*token.0)
+            .ok_or_else(|| format!("Missing token configuration at position {}", token.0))?
+            .as_cow_v0();

Return the error upstream or mark the TokenInfo as invalid rather than panicking.

📝 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.

let token_name = {
    let token_configuration = contract
        .expected_token_configuration(*token.0)
        .ok_or_else(|| format!("Missing token configuration at position {}", token.0))?
        .as_cow_v0();
    let conventions = match &token_configuration.conventions {
        TokenConfigurationConvention::V0(conventions) => conventions,
    };
    conventions
        .plural_form_by_language_code_or_default("en")
        .to_string()
};

67-141: ⚠️ Potential issue

Contracts lacking a description are omitted from the result map

results.insert(..) is executed only when document_option.is_some().
Callers of ContractsWithDescriptions will receive no entry at all for contracts whose description document is missing, making it impossible to distinguish “contract exists but no description” from “fetch failed”.

A safer pattern:

-                                if let Some(document) = document_option {
+                                let contract_description = document_option.map(|d| {
+                                    ContractDescriptionInfo {
+                                        data_contract_id: contract.id(),
+                                        description: d
+                                            .get("description")
+                                            .and_then(|v| v.as_text())
+                                            .unwrap_or_default()
+                                            .to_string(),
+                                    }
+                                });
+
+                                // Always insert – description may be None
                                     results.insert(
                                         contract.id(),
-                                        (Some(contract_description_info), token_infos),
+                                        (contract_description, token_infos),
                                     );
-                                }

This preserves the one-to-one mapping and lets the UI show “No description available”.

📝 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.

                                let document_option = Document::fetch(sdk, document_query)
                                    .await
                                    .map_err(|e| format!("Error fetching description: {}", e))?;

                                let mut token_infos = vec![];
                                for token in contract.tokens() {
                                    let token_name = {
                                        let token_configuration = contract
                                            .expected_token_configuration(*token.0)
                                            .expect("Expected to get token configuration")
                                            .as_cow_v0();
                                        let conventions = match &token_configuration.conventions {
                                            TokenConfigurationConvention::V0(conventions) => conventions,
                                        };
                                        conventions
                                            .plural_form_by_language_code_or_default("en")
                                            .to_string()
                                    };

                                    let token_info = TokenInfo {
                                        token_id: contract.token_id(*token.0).unwrap_or_default(),
                                        token_name,
                                        data_contract_id: contract.id(),
                                        token_position: *token.0,
                                        description: token.1.description().clone(),
                                    };

                                    token_infos.push(token_info);
                                }

-                               if let Some(document) = document_option {
-                                   let contract_description_info = ContractDescriptionInfo {
-                                       data_contract_id: contract.id(),
-                                       description: document
-                                           .get("description")
-                                           .and_then(|v| v.as_text())
-                                           .unwrap_or_default()
-                                           .to_string(),
-                                   };
-
-                                   results.insert(
-                                       contract.id(),
-                                       (Some(contract_description_info), token_infos),
-                                   );
-                               }
+                               let contract_description = document_option.map(|d| {
+                                   ContractDescriptionInfo {
+                                       data_contract_id: contract.id(),
+                                       description: d
+                                           .get("description")
+                                           .and_then(|v| v.as_text())
+                                           .unwrap_or_default()
+                                           .to_string(),
+                                   }
+                               });
+                               // Always insert – description may be None
+                               results.insert(
+                                   contract.id(),
+                                   (contract_description, token_infos),
+                               );
src/backend_task/core/mod.rs (1)

111-141: 🛠️ Refactor suggestion

Underlying RPC-client errors are swallowed

When both cookie and user/pass authentication fail, the second Client::new error is mapped to a generic message, discarding valuable diagnostics.

-            let client = match Client::new(&addr, Auth::CookieFile(cookie_path.clone())) {
+            let client = match Client::new(&addr, Auth::CookieFile(cookie_path.clone())) {
                 Ok(client) => Ok(client),
                 Err(first_err) => {
                     tracing::info!(
                         "Failed to authenticate using .cookie file at {:?}, falling back to user/pass",
                         cookie_path
                     );
-                    Client::new(
+                    Client::new(
                         &addr,
                         Auth::UserPass(
                             network_config.core_rpc_user.to_string(),
                             network_config.core_rpc_password.to_string(),
                         ),
-                    )
+                    ).map_err(|second_err| {
+                        format!(
+                            "Failed to create {} client. Cookie auth error: {}. User/pass auth error: {}",
+                            network, first_err, second_err
+                        )
+                    })
                 }
-            }
-                .map_err(|_| format!("Failed to create {} client", network.to_string()))?;
+            }?;

This surfaces both failure reasons and eases debugging misconfigurations.

📝 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.

    // Try cookie authentication first
    let client = match Client::new(&addr, Auth::CookieFile(cookie_path.clone())) {
        Ok(client) => Ok(client),
        Err(first_err) => {
            tracing::info!(
                "Failed to authenticate using .cookie file at {:?}, falling back to user/pass",
                cookie_path
            );
            Client::new(
                &addr,
                Auth::UserPass(
                    network_config.core_rpc_user.to_string(),
                    network_config.core_rpc_password.to_string(),
                ),
            ).map_err(|second_err| {
                format!(
                    "Failed to create {} client. Cookie auth error: {}. User/pass auth error: {}",
                    network, first_err, second_err
                )
            })
        }
    }?;
src/ui/tokens/view_token_claims_screen.rs (1)

88-105: 🛠️ Refactor suggestion

Refresh button triggers an un-scoped query

FetchDocuments(self.new_claims_query.clone()) runs without any where clause that restricts results to:

• the current identity (identity_token_balance.identity_id)
• the specific token (identity_token_balance.token_id)

This will fetch all claim documents in the contract and could be expensive on large datasets. Consider adding a precise filter before dispatching:

-                DesiredAppAction::BackendTask(BackendTask::DocumentTask(
-                    DocumentTask::FetchDocuments(self.new_claims_query.clone()),
-                )),
+                DesiredAppAction::BackendTask(BackendTask::DocumentTask(
+                    DocumentTask::FetchDocuments({
+                        let mut q = self.new_claims_query.clone();
+                        q.where_clauses.push((
+                            "identityId".into(),
+                            Operator::Equal(identity_token_balance.identity_id),
+                        ));
+                        q
+                    }),
+                )),

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

src/context_provider.rs (2)

66-67: 🛠️ Refactor suggestion

Hold write-lock for the shortest time possible

let sdk = app_context.sdk.write() acquires a write lock and then calls sdk.set_context_provider(self.clone()).
If set_context_provider internally tries to lock app_context (directly or indirectly) you may hit a dead-lock because app_context’s Mutex is still held by bind_app_context.

Release the app_context lock before taking the SDK lock or clone the necessary data beforehand:

-        drop(ac);
-
-        let sdk = app_context.sdk.write().expect("lock poisoned");
-        sdk.set_context_provider(self.clone());
+        drop(ac); // release app_context mutex
+
+        {
+            let mut sdk = app_context.sdk.write().expect("lock poisoned");
+            sdk.set_context_provider(self.clone());
+        } // write-lock released here

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


30-41: ⚠️ Potential issue

Defensive parsing of Core .cookie file

cookie_parts[0]/[1] will panic if the cookie file is malformed or contains trailing new-lines; additionally, read_to_string keeps the line-ending which contaminates the password.

Recommend using splitn + trim with graceful fallback:

-        let cookie = std::fs::read_to_string(cookie_path);
-        let (user, pass) = if let Ok(cookie) = cookie {
-            // split the cookie at ":", first part is user (__cookie__), second part is password
-            let cookie_parts: Vec<&str> = cookie.split(':').collect();
-            let user = cookie_parts[0];
-            let password = cookie_parts[1];
-            (user.to_string(), password.to_string())
+        let cookie = std::fs::read_to_string(&cookie_path);
+        let (user, pass) = if let Ok(cookie) = cookie {
+            match cookie.trim_end().splitn(2, ':').collect::<Vec<_>>()[..] {
+                [u, p] => (u.to_owned(), p.to_owned()),
+                _ => {
+                    tracing::warn!("Malformed cookie in {:?}", cookie_path);
+                    (config.core_rpc_user.clone(), config.core_rpc_password.clone())
+                }
+            }
📝 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.

        let cookie_path =
            core_cookie_path(network, &config.devnet_name).expect("Failed to get core cookie path");

        // Read the cookie from disk
-       let cookie = std::fs::read_to_string(cookie_path);
-       let (user, pass) = if let Ok(cookie) = cookie {
-           // split the cookie at ":", first part is user (__cookie__), second part is password
-           let cookie_parts: Vec<&str> = cookie.split(':').collect();
-           let user = cookie_parts[0];
-           let password = cookie_parts[1];
-           (user.to_string(), password.to_string())
+       let cookie = std::fs::read_to_string(&cookie_path);
+       let (user, pass) = if let Ok(cookie) = cookie {
+           match cookie.trim_end().splitn(2, ':').collect::<Vec<_>>()[..] {
+               [u, p] => (u.to_owned(), p.to_owned()),
+               _ => {
+                   tracing::warn!("Malformed cookie in {:?}", cookie_path);
+                   (config.core_rpc_user.clone(), config.core_rpc_password.clone())
+               }
+           }
        } else {
            // …
        }
src/ui/dpns/dpns_contested_names_screen.rs (1)

346-373: 🛠️ Refactor suggestion

Consolidate & harden the filter-term normalisation

The three filter blocks (Active, Past & Owned) are copy-pasted with only tiny variations:

  1. They all lower-case and replace o/O → 0, l → 1.
  2. Upper-case L (and possibly I/i) is not translated so e.g. searching for “Leet” fails.
  3. Any future tweak will have to be applied in three places → maintenance risk.

Recommend extracting a small helper and extending the char-mapping:

+fn normalise_filter(term: &str) -> String {
+    term.to_lowercase()
+        .chars()
+        .map(|c| match c {
+            'o' | '0' => '0',
+            'l' | 'i' | '1' => '1',     // treat visually-similar glyphs
+            _ => c,
+        })
+        .collect()
+}

Then each table becomes as simple as:

let filter = normalise_filter(&self.active_filter_term);
if !filter.is_empty() {
    cn.retain(|c| c.normalized_contested_name.to_lowercase().contains(&filter));
}

This removes the duplication and fixes the missed upper-case L.

Also applies to: 698-726, 860-873

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

129-176: ⚠️ Potential issue

Confirm can panic when no key is selected

self.selected_key is optional, yet the “Confirm” handler blindly unwraps:

signing_key: self.selected_key.clone().expect("No key selected"),

If the user never picked a key (or the default search failed) the application will crash.

Suggested guard:

-if ui.button("Confirm").clicked() {
+if ui.button("Confirm").clicked() {
+    if self.selected_key.is_none() {
+        self.error_message = Some("Please select a signing key first.".into());
+        return action;            // keep the popup open
+    }

…or disable the button entirely until a key is chosen:

-let confirm = ui.button("Confirm");
-if confirm.clicked() { … }
+let confirm = ui.add_enabled(self.selected_key.is_some(), egui::Button::new("Confirm"));
+if confirm.clicked() { … }
src/ui/tokens/burn_tokens_screen.rs (1)

146-216: ⚠️ Potential issue

Missing input validation can crash or burn an impossible amount

Issues inside the confirmation popup:

  1. self.selected_key.expect(..) – same panic risk as in Resume Tokens.
  2. amount_to_burn.parse::<u64>() succeeds but the parsed value may exceed the holder’s balance (or be 0), yet the burn is still dispatched.
  3. When amount_to_burn is invalid you immediately close the popup (self.show_confirmation_popup = false), leaving the user without context.

Recommended quick fixes:

-let amount_ok = self.amount_to_burn.parse::<u64>().ok();
-if amount_ok.is_none() {
-    self.error_message = Some("Please enter a valid integer amount.".into());
-    self.status = BurnTokensStatus::ErrorMessage("Invalid amount".into());
-    self.show_confirmation_popup = false;
-    return;
-}
+let amount_ok = match self.amount_to_burn.trim().parse::<u64>() {
+    Ok(amt) if amt > 0 && amt <= self.identity_token_balance.balance => amt,
+    _ => {
+        self.error_message = Some(format!(
+            "Please enter a number between 1 and {}.",
+            self.identity_token_balance.balance
+        ));
+        return action;     // keep popup open
+    }
+};

And gate the dispatch behind self.selected_key.is_some() using the same pattern suggested for the resume screen.

src/ui/tokens/unfreeze_tokens_screen.rs (3)

193-199: ⚠️ Potential issue

Runtime panic on missing key

self.selected_key.clone().expect("No key selected") will bring down the async worker thread. Replace with if let Some(key) = &self.selected_key { … } else { … } and propagate an error back to the UI.


76-90: ⚠️ Potential issue

possible_key can be None – protect later .expect()

possible_key is optional. When the user has zero auth keys the “Unfreeze” button remains enabled, but self.selected_key.clone().expect("No key selected") (l. 197) will panic the first time they press it.

Disable the button or show validation feedback until a key is picked:

-let button = egui::Button::new(RichText::new("Unfreeze").color(Color32::WHITE))
-    .fill(Color32::from_rgb(0, 128, 128))
-    .corner_radius(3.0);
-
-if ui.add(button).clicked() {
-    self.show_confirmation_popup = true;
-}
+let key_selected = self.selected_key.is_some();
+let button = egui::Button::new(RichText::new("Unfreeze").color(Color32::WHITE))
+    .fill(Color32::from_rgb(0, 128, 128))
+    .corner_radius(3.0)
+    .enabled(key_selected);
+
+if key_selected && ui.add(button).clicked() {
+    self.show_confirmation_popup = true;
+}

Remember to repeat the check inside show_confirmation_popup as a final guard.

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


68-74: 🛠️ Refactor suggestion

⚠️ Potential issue

Avoid expect → surface a user-friendly error instead of panicking

expect("No local qualified identity…") will crash the whole UI when the identity is missing (e.g. user deleted it from disk).
Return to a previous screen or render an error banner instead, so the app stays responsive.

-let identity = app_context
-    .load_local_qualified_identities()
-    .unwrap_or_default()
-    .into_iter()
-    .find(|id| id.identity.id() == identity_token_balance.identity_id)
-    .expect("No local qualified identity found for this token’s identity");
+let identity = app_context
+    .load_local_qualified_identities()
+    .unwrap_or_default()
+    .into_iter()
+    .find(|id| id.identity.id() == identity_token_balance.identity_id)
+    .ok_or_else(|| {
+        format!(
+            "No local qualified identity found for identity {}",
+            identity_token_balance.identity_id
+        )
+    })?;

(If you prefer not to propagate errors, at least set self.status = ErrorMessage(...) and stay on the screen.)

📝 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.

        let identity = app_context
            .load_local_qualified_identities()
            .unwrap_or_default()
            .into_iter()
            .find(|id| id.identity.id() == identity_token_balance.identity_id)
            .ok_or_else(|| {
                format!(
                    "No local qualified identity found for identity {}",
                    identity_token_balance.identity_id
                )
            })?;
src/ui/tokens/pause_tokens_screen.rs (2)

162-169: ⚠️ Potential issue

Potential panic on missing signing key

self.selected_key.clone().expect("No key selected") will crash if the user hasn’t picked a key. Either:

  1. Disable the “Pause” button until a key is chosen (preferred – the UI already shows a ComboBox).
  2. Or convert the absence to an ErrorMessage state instead of panicking.

65-71: ⚠️ Potential issue

Gracefully handle missing identity instead of panicking

Same concern as in the Unfreeze screen: crashing the UI is harsh. Convert this into a recoverable error.

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

523-528: 🛠️ Refactor suggestion

Hard-coding token position 0 may collide with existing tokens

Inserting every token at TokenContractPosition::from(0u16) will fail if the contract is extended with more tokens later.
Allow the caller to specify the desired position or compute tokens.len() to append safely.

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

196-204: 🛠️ Refactor suggestion

⚠️ Potential issue

Gracefully handle missing/ unloaded contracts

If get_contracts returns Err or the sought-for contract is not yet cached, the chain of expect(..) will panic. Prefer surfacing a user-visible error and allowing them to retry once the contract list is downloaded.

Suggestion (sketch):

let data_contract = match self
    .app_context
    .get_contracts(None, None)
    .and_then(|v| {
        v.into_iter()
            .find(|c| c.contract.id() == self.identity_token_balance.data_contract_id)
            .map(|c| c.contract.clone())
            .ok_or_else(|| anyhow!("Data contract not found"))
    }) {
    Ok(c) => c,
    Err(e) => {
        self.error_message = Some(format!("Cannot load contract: {e}"));
        self.status = DestroyFrozenFundsStatus::ErrorMessage("Contract not available".into());
        return AppAction::None;
    }
};

213-214: 🛠️ Refactor suggestion

⚠️ Potential issue

expect on selected_key risks panic – validate earlier

If the user never chooses a key, clicking “Destroy” will crash the app. Disable the button until a key is chosen or convert the expect into a graceful error path.


74-82: 🛠️ Refactor suggestion

⚠️ Potential issue

Avoid crashing the whole UI when the local identity is missing

expect("No local qualified identity…") will panic and terminate the entire GUI if the token’s owner identity is not present locally (e.g. user restored the wallet on a different machine). Consider returning a screen-level error instead and letting the user resolve the situation.

-        let identity = app_context
-            .load_local_qualified_identities()
-            .unwrap_or_default()
-            .into_iter()
-            .find(|id| id.identity.id() == identity_token_balance.identity_id)
-            .expect("No local qualified identity found matching this token's identity.");
+        let identity = app_context
+            .load_local_qualified_identities()
+            .unwrap_or_default()
+            .into_iter()
+            .find(|id| id.identity.id() == identity_token_balance.identity_id);
+
+        let identity = match identity {
+            Some(id) => id,
+            None => {
+                return Self {
+                    error_message: Some("Local identity for this token not found".into()),
+                    status: DestroyFrozenFundsStatus::ErrorMessage(
+                        "Local identity missing".into(),
+                    ),
+                    // ...keep the remaining struct initialisation unchanged
+                    ..Default::default() // or mirror the manual fields
+                };
+            }
+        };
📝 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.

        // Locate the local identity that owns the token contract
        let identity = app_context
            .load_local_qualified_identities()
            .unwrap_or_default()
            .into_iter()
            .find(|id| id.identity.id() == identity_token_balance.identity_id);

        let identity = match identity {
            Some(id) => id,
            None => {
                return Self {
                    error_message: Some("Local identity for this token not found".into()),
                    status: DestroyFrozenFundsStatus::ErrorMessage(
                        "Local identity missing".into(),
                    ),
                    ..Default::default()
                };
            }
        };

        // Grab a suitable key
src/ui/tokens/mint_tokens_screen.rs (1)

68-74: 🛠️ Refactor suggestion

⚠️ Potential issue

Avoid panicking when no local identity matches

Same risk pattern as in the destroy-funds screen: expect("No local qualified identity…") will abort the UI. Return a screen with an error banner instead.

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

341-342: 🛠️ Refactor suggestion

Unchecked unwrap may panic when no key or identity selected

selected_qualified_identity and selected_key are force-unwrapped. If the user deselects them (or a future UI change allows “none”), the app will crash. Either disable the “Register Contract” button until both are Some, or handle None gracefully.

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

184-202: ⚠️ Potential issue

Unchecked .expect() can still trigger a panic

Inside the confirmation path the code dereferences several Options with expect() (data_contract and selected_key).
The UI does try to set an error message earlier, but a race condition (or a programmer-mode key deletion) can still make these Options None, crashing the whole app.

Replace these expect()s with a user-visible error and keep the application alive.


68-75: 🛠️ Refactor suggestion

Avoid hard-panicking when the identity cannot be found

expect("No local qualified identity found for this token") will crash the entire UI if the local DB ever becomes inconsistent (e.g. the balance row survives while the identity was deleted).
Return an error message and gracefully navigate the user back instead of panicking.

-let identity = app_context
-    .load_local_qualified_identities()
-    .unwrap_or_default()
-    .into_iter()
-    .find(|id| id.identity.id() == identity_token_balance.identity_id)
-    .expect("No local qualified identity found for this token");
+let identity = match app_context
+    .load_local_qualified_identities()
+    .unwrap_or_default()
+    .into_iter()
+    .find(|id| id.identity.id() == identity_token_balance.identity_id)
+{
+    Some(id) => id,
+    None => {
+        log::error!("Identity {:#x?} is missing for token balance", identity_token_balance.identity_id);
+        return Self::show_missing_identity_error(app_context.clone());
+    }
+};
📝 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.

        let identity = match app_context
            .load_local_qualified_identities()
            .unwrap_or_default()
            .into_iter()
            .find(|id| id.identity.id() == identity_token_balance.identity_id)
        {
            Some(id) => id,
            None => {
                log::error!(
                    "Identity {:#x?} is missing for token balance",
                    identity_token_balance.identity_id
                );
                return Self::show_missing_identity_error(app_context.clone());
            }
        };

        let identity_clone = identity.identity.clone();
src/ui/tokens/transfer_tokens_screen.rs (2)

67-75: ⚠️ Potential issue

max_amount may reference a different token

The lookup only filters by identity_id; if the same identity owns multiple tokens, the first match may not correspond to the currently selected identity_token_balance, leading to an incorrect “Max” value and potential rejection from the backend.

Prefer using the balance that matches both identity_id and the (data_contract_id, token_position) pair (or just reuse identity_token_balance.balance).


222-233: ⚠️ Potential issue

Unhandled parse errors on amount can crash the UI

self.amount.parse().unwrap() will panic on non-numeric input, empty string, or values > u64::MAX.
Validate the amount and surface a user-friendly error instead.

-let parsed_amount = self.amount.parse().unwrap();
+let parsed_amount = match self.amount.parse::<u64>() {
+    Ok(a) if a > 0 && a <= self.max_amount => a,
+    _ => {
+        self.error_message = Some("Enter a valid amount ≤ available balance".into());
+        self.transfer_tokens_status =
+            TransferTokensStatus::ErrorMessage("Invalid amount".into());
+        return AppAction::None;
+    }
+};
 ...
-    amount: self.amount.parse().unwrap(),
+    amount: parsed_amount,

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

src/ui/tokens/claim_tokens_screen.rs (2)

244-267: 🛠️ Refactor suggestion

Replace expect panics with graceful error propagation

show_confirmation_popup uses several expect calls that will abort the whole application on a missing contract or key.
Consider surfacing these problems via ClaimTokensStatus::ErrorMessage instead, so the user receives feedback instead of a crash.

Example (only one shown – apply the same pattern to the others):

-    let data_contract = self
-        .app_context
-        .get_contracts(None, None)
-        .expect("Contracts not loaded")
-        .iter()
-        .find(|c| c.contract.id() == self.identity_token_balance.data_contract_id)
-        .expect("Data contract not found")
-        .contract
-        .clone();
+    let data_contract = match self
+        .app_context
+        .get_contracts(None, None)
+        .ok()
+        .and_then(|v| {
+            v.into_iter()
+                .find(|c| c.contract.id() == self.identity_token_balance.data_contract_id)
+        }) {
+        Some(c) => c.contract.clone(),
+        None => {
+            self.status = ClaimTokensStatus::ErrorMessage("Data-contract not found".into());
+            return action; // early-out with no backend tasks enqueued
+        }
+    };
📝 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.

                    let data_contract = match self
                        .app_context
                        .get_contracts(None, None)
                        .ok()
                        .and_then(|v| {
                            v.into_iter()
                                .find(|c| c.contract.id() == self.identity_token_balance.data_contract_id)
                        }) {
                        Some(c) => c.contract.clone(),
                        None => {
                            self.status = ClaimTokensStatus::ErrorMessage("Data-contract not found".into());
                            return action; // early-out with no backend tasks enqueued
                        }
                    };

                    action = AppAction::BackendTasks(
                        vec![
                            BackendTask::TokenTask(TokenTask::ClaimTokens {
                                data_contract,
                                token_position: self.identity_token_balance.token_position,
                                actor_identity: self.identity.clone(),
                                distribution_type,
                                signing_key: self.selected_key.clone().expect("No key selected"),
                            }),
                            BackendTask::TokenTask(TokenTask::QueryMyTokenBalances),
                        ],
                        BackendTasksExecutionMode::Sequential,
                    );

430-437: ⚠️ Potential issue

Validate selected_key before opening the confirmation dialog

The click handler only checks distribution_type.
If self.selected_key is still None, the subsequent expect("No key selected") in show_confirmation_popup will panic and crash the UI.

 if ui.add(button).clicked() {
-    if self.distribution_type.is_none() {
+    if self.selected_key.is_none() {
+        self.status = ClaimTokensStatus::ErrorMessage(
+            "Please select a signing key.".to_string(),
+        );
+        return;
+    }
+    if self.distribution_type.is_none() {
         self.status = ClaimTokensStatus::ErrorMessage(
             "Please select a distribution type.".to_string(),
         );
         return;
-    } else {
-        self.show_confirmation_popup = true;
-    }
+    }
+    self.show_confirmation_popup = true;
 }
src/context.rs (2)

596-622: ⚠️ Potential issue

Fail fast if token configuration cannot be serialized

insert_token silently swallows a bincode failure by returning Ok(()).
This hides real errors and leaves the database in an inconsistent state (token row missing).

Replace with an explicit error:

-let Some(serialized_token_configuration) =
-    bincode::encode_to_vec(&token_configuration, config).ok()
-else {
-    // We should always be able to serialize
-    return Ok(());
-};
+let serialized_token_configuration = bincode::encode_to_vec(&token_configuration, config)
+    .map_err(|e| format!("Failed to serialize token configuration: {e}"))?;
📝 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.

    pub fn insert_token(
        &self,
        token_id: &Identifier,
        token_name: &str,
        token_configuration: TokenConfiguration,
        contract_id: &Identifier,
        token_position: u16,
    ) -> Result<()> {
        let config = config::standard();
        let serialized_token_configuration = bincode::encode_to_vec(&token_configuration, config)
            .map_err(|e| format!("Failed to serialize token configuration: {e}"))?;

        self.db.insert_token(
            token_id,
            token_name,
            serialized_token_configuration.as_slice(),
            contract_id,
            token_position,
            self,
        )?;

        Ok(())
    }

411-425: 🛠️ Refactor suggestion

get_contract_by_id doesn’t look into in-memory system contracts

get_contract_by_id returns None when the record is missing from SQLite, but it never checks the four pre-loaded system contracts (DPNS, TokenHistory, Withdrawals, KeywordSearch).
Any UI that relies on this helper will fail to resolve those contracts even though they are available in memory.

Consider:

-// If the contract is not found in the database, return None
-if contract.is_none() {
-    return Ok(None);
-}
-
-// If the contract is found, return it
-Ok(Some(contract.unwrap()))
+if let Some(c) = contract {
+    return Ok(Some(c));
+}
+
+// Check in-memory system contracts
+for c in [
+    &self.dpns_contract,
+    &self.token_history_contract,
+    &self.withdraws_contract,
+    &self.keyword_search_contract,
+] {
+    if c.id() == contract_id {
+        return Ok(Some(QualifiedContract {
+            contract: c.as_ref().clone(),
+            alias: None,
+        }));
+    }
+}
+Ok(None)
📝 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.

    pub fn get_contract_by_id(
        &self,
        contract_id: &Identifier,
    ) -> Result<Option<QualifiedContract>> {
        // Get the contract from the database
        let contract = self.db.get_contract_by_id(*contract_id, self)?;

        // First, return anything the DB knows about
        if let Some(c) = contract {
            return Ok(Some(c));
        }

        // Otherwise, check the in-memory system contracts
        for c in [
            &self.dpns_contract,
            &self.token_history_contract,
            &self.withdraws_contract,
            &self.keyword_search_contract,
        ] {
            if c.id() == contract_id {
                return Ok(Some(QualifiedContract {
                    contract: c.as_ref().clone(),
                    alias: None,
                }));
            }
        }

        // Not found anywhere
        Ok(None)
    }
src/ui/mod.rs (1)

294-317: ⚠️ Potential issue

Incorrect screen routing – several token actions open the wrong UI

ScreenType::create_screen maps Freeze, Unfreeze, Pause, and Resume actions to DestroyFrozenFundsScreen, which makes the corresponding menu items open the wrong screen.

-ScreenType::FreezeTokensScreen(identity_token_balance) => {
-    Screen::DestroyFrozenFundsScreen(DestroyFrozenFundsScreen::new(
+ScreenType::FreezeTokensScreen(identity_token_balance) => {
+    Screen::FreezeTokensScreen(FreezeTokensScreen::new(
         identity_token_balance.clone(),
         app_context,
     ))
 }
 ScreenType::UnfreezeTokensScreen(identity_token_balance) => {
-    Screen::DestroyFrozenFundsScreen(DestroyFrozenFundsScreen::new(
+    Screen::UnfreezeTokensScreen(UnfreezeTokensScreen::new(
         identity_token_balance.clone(),
         app_context,
     ))
 }
 ScreenType::PauseTokensScreen(identity_token_balance) => {
-    Screen::DestroyFrozenFundsScreen(DestroyFrozenFundsScreen::new(
+    Screen::PauseTokensScreen(PauseTokensScreen::new(
         identity_token_balance.clone(),
         app_context,
     ))
 }
 ScreenType::ResumeTokensScreen(identity_token_balance) => {
-    Screen::DestroyFrozenFundsScreen(DestroyFrozenFundsScreen::new(
+    Screen::ResumeTokensScreen(ResumeTokensScreen::new(
         identity_token_balance.clone(),
         app_context,
     ))
 }

Fixing this restores the expected navigation flow.

📝 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.

            ScreenType::DestroyFrozenFundsScreen(identity_token_balance) => {
                Screen::DestroyFrozenFundsScreen(DestroyFrozenFundsScreen::new(
                    identity_token_balance.clone(),
                    app_context,
                ))
            }
            ScreenType::FreezeTokensScreen(identity_token_balance) => {
                Screen::FreezeTokensScreen(FreezeTokensScreen::new(
                    identity_token_balance.clone(),
                    app_context,
                ))
            }
            ScreenType::UnfreezeTokensScreen(identity_token_balance) => {
                Screen::UnfreezeTokensScreen(UnfreezeTokensScreen::new(
                    identity_token_balance.clone(),
                    app_context,
                ))
            }
            ScreenType::PauseTokensScreen(identity_token_balance) => {
                Screen::PauseTokensScreen(PauseTokensScreen::new(
                    identity_token_balance.clone(),
                    app_context,
                ))
            }
            ScreenType::ResumeTokensScreen(identity_token_balance) => {
                Screen::ResumeTokensScreen(ResumeTokensScreen::new(
                    identity_token_balance.clone(),
                    app_context,
                ))
            }
src/database/tokens.rs (4)

326-336: 🛠️ Refactor suggestion

token_order table misses network; order persists across networks

Ordering should be per-identity and per-network.
Without a network column, switching networks will mix Devnet/Mainnet rows and the UNIQUE constraint PRIMARY KEY(pos, token_id) may break.

-CREATE TABLE token_order (
-    pos INTEGER NOT NULL,
-    token_id BLOB NOT NULL,
-    identity_id BLOB NOT NULL,
+CREATE TABLE token_order (
+    pos INTEGER NOT NULL,
+    token_id BLOB NOT NULL,
+    identity_id BLOB NOT NULL,
+    network TEXT NOT NULL,
     PRIMARY KEY(pos, token_id, identity_id, network),
...
-INSERT INTO token_order (pos, token_id, identity_id)
-     VALUES (?1, ?2, ?3)
+INSERT INTO token_order (pos, token_id, identity_id, network)
+     VALUES (?1, ?2, ?3, ?4)

save_token_order/load_token_order must be updated accordingly.

Also applies to: 350-360, 366-394


17-29: 🛠️ Refactor suggestion

⚠️ Potential issue

Add network to PRIMARY KEY & ON CONFLICT to avoid cross-network data corruption

id alone is the primary key, yet we store the same token ID on multiple networks.
Currently an insert on Devnet would silently overwrite a Mainnet row with the same id because ON CONFLICT(id) has no network discriminator.

-CREATE TABLE IF NOT EXISTS token (
-    id BLOB PRIMARY KEY,
+CREATE TABLE IF NOT EXISTS token (
+    id BLOB NOT NULL,
+    network TEXT NOT NULL,
     token_alias TEXT NOT NULL,
     token_config BLOB NOT NULL,
     data_contract_id BLOB NOT NULL,
     token_position INTEGER NOT NULL,
-    network TEXT NOT NULL,
+    PRIMARY KEY(id, network),
...
-ON CONFLICT(id) DO UPDATE SET
+ON CONFLICT(id, network) DO UPDATE SET

Same composite key should be reflected in every SELECT/DELETE and foreign key definition.

Failing to fix this will cause panics or, worse, users seeing the wrong token metadata when they switch networks.

Also applies to: 105-124


139-147: 🛠️ Refactor suggestion

Foreign-key inconsistency between identity_token_balances and token

identity_token_balances includes network in its PK but references token(id) without network.
If the same id exists on two networks foreign-key checks can link to the wrong row.

Recommendation: reference the composite (id, network) once the previous comment is addressed.


191-207: 🛠️ Refactor suggestion

Using expect() may panic on malformed DB rows

Identifier::from_vec(..).expect("Failed to parse …") will abort the whole UI if a single record is corrupt.
Prefer graceful handling:

-let token_id = token_id_res.expect("Failed to parse token ID");
+let token_id = match token_id_res {
+    Ok(id) => id,
+    Err(e) => {
+        log::warn!("Skipping token row with invalid id: {e}");
+        continue;
+    }
+};

Repeat for other expect calls.

Also applies to: 265-268

src/app.rs (1)

314-339: 🛠️ Refactor suggestion

Listeners are spawned even when AppContext is None

devnet_core_zmq_listener / local_core_zmq_listener are created unconditionally.
If the config is missing, the app context is None but the listener still binds the port, wasting resources and potentially exposing an unused socket.

Consider skipping listener creation when the corresponding AppContext is None.

@QuantumExplorer QuantumExplorer merged commit 701aece into dashpay:v0.9-dev Apr 24, 2025
1 check passed
@thephez thephez deleted the fix-undeclared-userdirs branch April 24, 2025 20:55
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.

2 participants