Skip to content

fix: reset correct fields to close token remove popup successfully#236

Merged
pauldelucia merged 1 commit into
dashpay:v0.9-devfrom
thephez:fix-token-popup-close
May 7, 2025
Merged

fix: reset correct fields to close token remove popup successfully#236
pauldelucia merged 1 commit into
dashpay:v0.9-devfrom
thephez:fix-token-popup-close

Conversation

@thephez
Copy link
Copy Markdown
Contributor

@thephez thephez commented Apr 30, 2025

This PR addresses an issue where closing the "Remove Token" confirmation popup via the "X" button did not reset the appropriate state variables.

Related to #233

Summary by CodeRabbit

  • Bug Fixes
    • Fixed an issue where closing the "Remove Token" popup did not properly reset its state, ensuring the correct popup state is cleared when the window is closed.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2025

Walkthrough

The update refines the state management logic for closing the "Remove Token" confirmation popup in the tokens screen UI. Specifically, it corrects which internal state variables are reset when the popup is closed, ensuring that only the relevant flags and variables associated with the token removal popup are cleared, rather than those related to identity token balance removal. No public interfaces or exported entities are affected; the changes are limited to internal state handling within a private method.

Changes

File(s) Change Summary
src/ui/tokens/tokens_screen.rs Corrected the cleanup logic for the "Remove Token" popup to reset the appropriate state variables.

Poem

In the garden of code where tokens bloom,
A popup closed—no more confusion or gloom.
Flags reset with precision and care,
Only the right state cleared from the air.
With every click, the logic’s now neat,
The rabbit hops on, with tidiness complete! 🐇✨


📜 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 791adf5 and 54eda51.

📒 Files selected for processing (1)
  • src/ui/tokens/tokens_screen.rs (1 hunks)
🔇 Additional comments (1)
src/ui/tokens/tokens_screen.rs (1)

4462-4466: Fix correctly resets state when token remove popup is closed via 'X' button.

The code now properly clears both the popup visibility flag (confirm_remove_token_popup) and the token data (token_to_remove) when a user closes the popup using the window close button, matching the behavior already implemented in the similar show_remove_identity_token_balance_popup function.

✨ 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 30, 2025 15:06
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 (55)
src/model/qualified_identity/qualified_identity_public_key.rs (1)

44-48: ⚠️ Potential issue

Simplified address derivation but introduced potential panic condition.

The new implementation directly constructs a PublicKey from the raw key data and derives a P2PKH address, but uses expect() which could cause a panic if the input is invalid.

Consider using a pattern that handles invalid public key data gracefully rather than panicking:

-        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(_) => 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

Avoid panicking – propagate RwLock poisoning instead of expect()

Using expect("Core client lock was poisoned") will abort the whole async task, which is undesirable in long-running background workers. Propagate the error upward (or convert it into a retryable warning) so the caller can decide what to do.

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

58-66: 🛠️ Refactor suggestion

⚠️ Potential issue

Lock-ordering risk: acquire the core_client read-guard before the wallet write-guard

reload_utxos is called while holding wallet.write() and simultaneously borrowing core_client.read(). Other tasks may acquire these locks in the reverse order (core_clientwallet) and deadlock.

Extract the core_client guard first, then pass a reference to it after obtaining the wallet lock:

-        let utxo_map = {
-            let mut wallet_guard = wallet.write().map_err(|e| e.to_string())?;
-            match wallet_guard.reload_utxos(
-                &self
-                    .core_client
-                    .read()
-                    .expect("Core client lock was poisoned"),
+        let core_client = self
+            .core_client
+            .read()
+            .map_err(|e| format!("Core client lock poisoned: {e}"))?;
+
+        let utxo_map = {
+            let mut wallet_guard = wallet.write().map_err(|e| e.to_string())?;
+            match wallet_guard.reload_utxos(
+                &core_client,

This keeps a consistent lock order (core_clientwallet) and eliminates potential deadlocks.

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

            // first grab the core_client read-lock
            let core_client = self
                .core_client
                .read()
                .map_err(|e| format!("Core client lock poisoned: {e}"))?;
    
            // then grab the wallet write-lock and call reload_utxos using the
            // already-held core_client guard
            let utxo_map = {
                let mut wallet_guard = wallet.write().map_err(|e| e.to_string())?;
                match wallet_guard.reload_utxos(
                    &core_client,
                    self.network,
                    Some(self),
                ) {
                    // …
                }
            };
src/backend_task/identity/transfer.rs (1)

19-22: ⚠️ Potential issue

Propagate poisoning errors instead of unwrap()

self.sdk.read().unwrap() will panic on a poisoned lock. Use map_err so the calling UI can surface the error gracefully.

-        let sdk_guard = {
-            let guard = self.sdk.read().unwrap();
-            guard.clone()
-        };
+        let sdk_guard = self
+            .sdk
+            .read()
+            .map_err(|e| format!("SDK lock poisoned: {e}"))?
+            .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 sdk_guard = self
    .sdk
    .read()
    .map_err(|e| format!("SDK lock poisoned: {e}"))?
    .clone();
src/backend_task/identity/register_identity.rs (1)

121-124: 💡 Verification agent

🧩 Analysis chain

Avoid unwrap() on poisoned lock – propagate or handle error gracefully

self.sdk.read().unwrap() will panic if the lock is poisoned, taking the whole task down.
Because this path is executed in the happy‐day flow of identity registration, consider either:

  • returning an error (map_err(|_| "SDK lock poisoned".into())?) so the caller can surface it, or
  • logging and falling back to a sane default instead of crashing.

Same applies to multiple RwLock reads further below; a helper like fn read_sdk(&self) -> Result<Sdk, Error> would deduplicate the pattern.


🏁 Script executed:

#!/bin/bash
# Search for all instances of `.read().unwrap()` in the identity registration file
rg -n "read\(\)\.unwrap" -C2 src/backend_task/identity/register_identity.rs

Length of output: 917


Avoid unwrap() on poisoned RwLock reads – handle or propagate errors

Unwinding on a poisoned lock can crash the entire registration task. Please replace all .read().unwrap() calls with graceful error handling or propagation.

Occurrences in src/backend_task/identity/register_identity.rs:

  • Line 122: self.sdk.read().unwrap()
  • Line 138: wallet.read().unwrap()
  • Line 336: wallet.read().unwrap().seed_hash()
  • Line 347: wallet.read().unwrap().seed_hash()

Recommended refactor:

  • Introduce helper methods, e.g.:
    impl YourStruct {
        fn read_sdk(&self) -> Result<Sdk, Error> {
            self.sdk.read()
                .map(Clone::clone)
                .map_err(|_| Error::LockPoisoned("sdk".into()))
        }
        fn read_wallet(&self) -> Result<Wallet, Error> {
            self.wallet.read()
                .map(Clone::clone)
                .map_err(|_| Error::LockPoisoned("wallet".into()))
        }
    }
  • Update call sites:
    let sdk = self.read_sdk()?;
    let wallet = self.read_wallet()?;
    let wallet_seed_hash = self.read_wallet()?.seed_hash();
  • This ensures any poisoned lock error is returned to the caller instead of panicking.
src/database/scheduled_votes.rs (1)

32-94: 🛠️ Refactor suggestion

Migration logic: ensure PRAGMA foreign_keys is always re-enabled

If any SQL statement inside the transaction fails, the foreign_keys = ON at line 91 is skipped, leaving the connection in an unsafe state.
Wrap the whole block in a match tx_result and re-enable foreign keys in the Err path too, or use a scopeguard:

let fk_guard = scopeguard::guard((), |_| {
    let _ = conn.execute("PRAGMA foreign_keys = ON", []);
});

This guarantees restoration even on early returns.

src/backend_task/document.rs (1)

13-20: 🛠️ Refactor suggestion

Consider whether all payload fields are really required for BroadcastDocument

The variant carries both a DocumentType and an explicit [u8; 32] entropy slice, although Document::put_to_platform_and_wait_for_response can usually derive the DocumentType from the Document itself and will generate its own entropy when None is supplied. Passing redundant data makes the API harder to reason about and increases the surface for misuse (e.g. mismatching doc_type and the one embedded in document).

If you don’t have a strong reason to expose them, consider simplifying the tuple:

-    BroadcastDocument(
-        Document,
-        [u8; 32],
-        DocumentType,
-        QualifiedIdentity,
-        IdentityPublicKey,
-    ),
+    BroadcastDocument(
+        Document,
+        QualifiedIdentity,
+        IdentityPublicKey,
+    ),

and call put_to_platform_and_wait_for_response with None for entropy and document.document_type() for the type.

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

    BroadcastDocument(
        Document,
        QualifiedIdentity,
        IdentityPublicKey,
    ),
    FetchDocuments(DocumentQuery),
src/backend_task/identity/top_up_identity.rs (1)

34-37: 🛠️ Refactor suggestion

Avoid panicking on poisoned RwLock – propagate the error instead

self.sdk.read().unwrap() will panic if another task poisoned the lock. In a long-running async application that’s hard to recover from. Prefer returning a descriptive error (or using expect consistently with the other accesses):

-let sdk = {
-    let guard = self.sdk.read().unwrap();
-    guard.clone()
-};
+let sdk = self
+    .sdk
+    .read()
+    .map_err(|_| "SDK lock was poisoned")?
+    .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 sdk = self
            .sdk
            .read()
            .map_err(|_| "SDK lock was poisoned")?
            .clone();
src/backend_task/register_contract.rs (1)

60-69: 🛠️ Refactor suggestion

Improve error message extraction for better resilience.

The current approach to extract the contract ID from the error message is fragile as it depends on the exact error message format. If the error message format changes, this extraction could fail.

Consider using a regex pattern to extract the ID more reliably:

-                    let id_str = error_string
-                        .split(" ")
-                        .last()
-                        .ok_or("Failed to get contract ID from proof error")?;
+                    let id_str = {
+                        // Try to extract ID using regex for more resilience
+                        let re = regex::Regex::new(r"([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{34})").unwrap();
+                        if let Some(captures) = re.captures(&error_string) {
+                            captures.get(1).map(|m| m.as_str()).ok_or("Failed to extract contract ID from error message")?
+                        } else {
+                            // Fallback to original method
+                            error_string
+                                .split(" ")
+                                .last()
+                                .ok_or("Failed to get contract ID from proof error")?
+                        }
+                    };

This would require adding the regex crate to your dependencies.

📝 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 to extract ID using regex for more resilience
                     let id_str = {
                         let re = regex::Regex::new(r"([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{34})").unwrap();
                         if let Some(captures) = re.captures(&error_string) {
                             captures
                                 .get(1)
                                 .map(|m| m.as_str())
                                 .ok_or("Failed to extract contract ID from error message")?
                         } else {
                             // Fallback to original method
                             error_string
                                 .split(" ")
                                 .last()
                                 .ok_or("Failed to get 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()
                         )
                     })?;
Cargo.toml (1)

30-31: 💡 Verification agent

❓ Verification inconclusive

Avoid pinning to an RC version of bincode unless absolutely necessary

bincode = "=2.0.0-rc.3" locks the crate to a release-candidate that may receive breaking changes or yanks.
If the 2.0 API is needed, prefer the latest stable (currently 1.3) or track 2.0.0-rc.* without the leading = so you can benefit from patch updates:

-bincode = { version = "=2.0.0-rc.3", features = ["serde"] }
+bincode = { version = "2.0.0-rc", features = ["serde"] }

Otherwise, revert to 1.3 until 2.0 is officially released.


Avoid pinning to an RC version of bincode

Locking to a single prerelease (=2.0.0-rc.3) risks yanks or breaking changes. If you need the 2.0 API, use a more flexible requirement that will pick up future rc patches (and eventually the stable 2.0.0):

• File: Cargo.toml (lines 30–31)

-bincode = { version = "=2.0.0-rc.3", features = ["serde"] }
+bincode = { version = "2.0.0-rc",  features = ["serde"] }

This will allow any 2.0.0-rc.* release (and later 2.0.0) without rebinding your Cargo.lock.

Otherwise, stick with the latest stable 1.x until 2.0 goes GA:

bincode = { version = "1.3", features = ["serde"] }
src/backend_task/tokens/query_token_non_claimed_perpetual_distribution_rewards.rs (2)

34-38: 🛠️ Refactor suggestion

Blocking DB calls inside an async method can stall the async runtime

self.db.get_token_config_for_id(..) (and other DB helpers later in the function) appear to be synchronous rusqlite queries.
Executing them directly inside async fn blocks the entire Tokio worker thread.

Consider off-loading to spawn_blocking or migrating the DB layer to an async driver:

let token_config = tokio::task::spawn_blocking({
    let db = self.db.clone();
    move || db.get_token_config_for_id(&token_id, self)
})
.await
.map_err(|e| format!("Join error: {e}"))?
.map_err(|e| format!("Failed to get token config: {e}"))?
.ok_or("Corrupted DET state: Token config not found in database")?;

114-123: ⚠️ Potential issue

Avoid panicking with expect(…) in backend paths

The expect("we should never get back an epoch so far in the future") will crash the whole task on an out-of-range epoch.
Since other errors are already propagated with Err(String), do the same here:

-            .expect("we should never get back an epoch so far in the future"),
+            .map_err(|_| "Epoch index overflow while converting to u16".to_owned())?,
📝 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 block_info = dash_sdk::dpp::block::block_info::BlockInfo {
            time_ms: current_epoch_with_metadata.1.time_ms,
            height: current_epoch_with_metadata.1.height,
            core_height: current_epoch_with_metadata.1.core_chain_locked_height, // This will not matter
            epoch: current_epoch_with_metadata
                .0
                .index()
                .try_into()
                .map_err(|_| "Epoch index overflow while converting to u16".to_owned())?,
        };
src/ui/tools/document_visualizer_screen.rs (1)

238-244: ⚠️ Potential issue

display_message() keeps showing stale JSON after an error

When an external task calls display_message(.., MessageType::Error), you set parse_status = Error but leave parsed_json untouched, so the previous (possibly unrelated) successful result stays visible.

Add self.parsed_json = None; to prevent misleading output.

-        if matches!(t, crate::ui::MessageType::Error) {
-            self.parse_status = DocumentParseStatus::Error(msg.to_owned());
+        if matches!(t, crate::ui::MessageType::Error) {
+            self.parsed_json = None;
+            self.parse_status = DocumentParseStatus::Error(msg.to_owned());
         }
📝 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 crate::ui::ScreenLike for DocumentVisualizerScreen {
    fn display_message(&mut self, msg: &str, t: crate::ui::MessageType) {
        if matches!(t, crate::ui::MessageType::Error) {
            self.parsed_json = None;
            self.parse_status = DocumentParseStatus::Error(msg.to_owned());
        }
    }

    fn display_task_result(&mut self, _r: BackendTaskSuccessResult) {}
}
src/app_dir.rs (1)

47-61: 🛠️ Refactor suggestion

Handle unimplemented network cases more gracefully

Using unimplemented!() will cause a runtime panic if an unsupported network is encountered. Consider returning an error instead to handle this case more gracefully.

- _ => unimplemented!(),
+ _ => return Err(std::io::Error::new(
+     std::io::ErrorKind::Unsupported,
+     format!("Unsupported network type: {:?}", 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.

pub fn core_cookie_path(
    network: Network,
    devnet_name: &Option<String>,
) -> Result<PathBuf, std::io::Error> {
    core_user_data_dir_path().map(|path| {
        let network_dir = match network {
            Network::Dash => "",
            Network::Testnet => "testnet3",
            Network::Devnet => devnet_name.as_deref().unwrap_or(""),
            Network::Regtest => "regtest",
            _ => return Err(std::io::Error::new(
                std::io::ErrorKind::Unsupported,
                format!("Unsupported network type: {:?}", network)
            )),
        };
        path.join(network_dir).join(".cookie")
    })
}
src/database/contracts.rs (2)

46-57: 🛠️ Refactor suggestion

Wrap token/contract insertion in a transaction for atomicity

A failure after some tokens are inserted (but before the loop finishes) will leave contract and token tables out of sync.
Starting a BEGIN IMMEDIATE transaction before inserting the contract and committing after the tokens loop guarantees all-or-nothing persistence.

If you’d like a sample diff just let me know.


62-68: ⚠️ Potential issue

Propagate serialization errors instead of silently skipping tokens

bincode::encode_to_vec returns a Result; converting it to Option with .ok() discards the error context and can hide real problems (e.g., schema incompatibilities). Bubble the error up instead:

-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)))?;

This way the caller is immediately informed and the DB isn’t left in a partially-updated state.

src/context_provider.rs (1)

30-41: ⚠️ Potential issue

Harden cookie parsing to avoid panics and stray whitespace

cookie.split(':')[1] panics if the cookie string is malformed, and trailing newlines are passed verbatim to RPC auth. A safer approach:

-let cookie_parts: Vec<&str> = cookie.split(':').collect();
-let user = cookie_parts[0];
-let password = cookie_parts[1];
+let (user, password) = cookie
+    .trim()                // remove \n
+    .split_once(':')       // returns Option<(&str,&str)>
+    .ok_or("Invalid cookie format")?;

This prevents out-of-bounds indexing and ensures clean credentials.

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

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

274-295: 🛠️ Refactor suggestion

to_json() every frame is expensive – cache on first expand

DataContract::to_json performs validation & conversion; calling it each frame of an expanded panel can become costly with large contracts.

Consider:

  1. Compute the pretty JSON string once on the first expansion and store it in a HashMap<ContractId, String> inside AppContext or in a small LRU cache.
  2. Render the cached string afterwards.

This keeps scrolling smooth and battery-friendly.

src/backend_task/contract.rs (3)

75-78: 🛠️ Refactor suggestion

Persist fetched contracts to keep backend & UI in sync

Unlike the FetchContracts branch, this variant does not store the freshly-fetched contract in the local database.
Down-stream code (token screens, balance refresh, etc.) expects the contract to be present, so a user who lands on this path may hit “contract not found” errors later.

After building token_infos you can safely reuse the existing helper:

self.db.insert_contract_if_not_exists(
    contract,
    None,
    NoTokensShouldBeAdded,
    self,
)?;

(Place it before populating results to avoid double borrowing.)


94-118: ⚠️ Potential issue

Avoid silently fabricating token IDs – handle the None case explicitly

contract.token_id(*token.0).unwrap_or_default() will fabricate a zero-identifier whenever the contract happens to miss a token-ID mapping.
If that situation ever arises (e.g. a partially-synced platform or an unexpected schema change) the UI will render a bogus “000…00” token ID, and any follow-up action (freeze, mint, etc.) will fail at runtime in much less obvious places.

-let token_info = TokenInfo {
-    token_id: contract.token_id(*token.0).unwrap_or_default(),
+let token_id = match contract.token_id(*token.0) {
+    Some(id) => id,
+    None => {
+        log::warn!(
+            "Contract {} is missing a token_id for position {} – skipping this token",
+            contract.id(),
+            token.0
+        );
+        continue;                 // skip this broken entry instead of forging an ID
+    }
+};
+
+let token_info = TokenInfo {
+    token_id,

This guards against corrupted / incomplete contracts and prevents downstream crashes.
(Tagging as critical because the current behaviour can lead to persisting invalid data.)

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

        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()
            };

            // Explicitly handle missing token_id instead of defaulting to zero
            let token_id = match contract.token_id(*token.0) {
                Some(id) => id,
                None => {
                    log::warn!(
                        "Contract {} is missing a token_id for position {} – skipping this token",
                        contract.id(),
                        token.0
                    );
                    continue; // skip this broken entry
                }
            };

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

            token_infos.push(token_info);
        }

121-138: 🛠️ Refactor suggestion

Contracts without a description disappear from the result map

results.insert(..) is only executed when document_option is Some(_).
If the Search-Contract doesn’t contain a matching fullDescription document, the caller receives no entry at all – even though the contract and its token list were fetched successfully.

Consider always inserting an entry and making the description optional:

-if let Some(document) = document_option {
-    let contract_description_info = ContractDescriptionInfo { … };
-    results.insert(contract.id(), (Some(contract_description_info), token_infos));
-}
+let description_info = document_option.map(|document| 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(), (description_info, token_infos));

This way the caller can still display the token list and simply fall back to “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.

                                // Always produce an optional description instead of only inserting when Some
                                let description_info = document_option.map(|document| 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(),
                                    (description_info, token_infos),
                                );

                            }
                        }
                        Ok(BackendTaskSuccessResult::ContractsWithDescriptions(results))
src/ui/tokens/add_token_by_id_screen.rs (1)

148-157: ⚠️ Potential issue

Success detection never triggers – token addition is stuck in “Searching”

display_message only transitions to Complete when the success message contains "AddToken", but the backend currently replies with "DataContract successfully saved".

-MessageType::Success => {
-    if msg.contains("AddToken") {
-        self.status = AddTokenStatus::Complete;
-    }
+MessageType::Success => {
+    // Any success originating from the SaveDataContract task finishes the flow
+    self.status = AddTokenStatus::Complete;
 }

Without this fix the UI never shows the green “Token added successfully!” message and the screen isn’t popped.

📝 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 display_message(&mut self, msg: &str, msg_type: MessageType) {
        match msg_type {
            MessageType::Success => {
                // Any success originating from the SaveDataContract task finishes the flow
                self.status = AddTokenStatus::Complete;
            }
            MessageType::Error => {
                self.status = AddTokenStatus::Error(msg.to_owned());
                self.error_message = Some(msg.to_owned());
            }
            // … other message types …
        }
    }
src/ui/tokens/freeze_tokens_screen.rs (1)

386-396: ⚠️ Potential issue

Prevent runtime panics – disable “Freeze” button until prerequisites are met

Currently the button is always clickable, and the back-end call later does
self.selected_key.clone().expect("No key selected"), causing a hard crash
when the user forgets to pick a key or enters no identity.

-let button = egui::Button::new(RichText::new("Freeze").color(Color32::WHITE))
-    .fill(Color32::from_rgb(64, 64, 255))
-    .corner_radius(3.0);
-
-if ui.add(button).clicked() {
+let can_freeze = self.selected_key.is_some() && !self.freeze_identity_id.trim().is_empty();
+let button = egui::Button::new(RichText::new("Freeze").color(Color32::WHITE))
+    .fill(Color32::from_rgb(64, 64, 255))
+    .corner_radius(3.0);
+
+if ui.add_enabled(can_freeze, button).clicked() {
     self.show_confirmation_popup = true;
 }

This delivers immediate UI feedback and removes the expect panic downstream.

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

41-46: ⚠️ Potential issue

Compile-time type mismatch between selected key and key list

selected_key is declared as Option<dash_sdk::platform::IdentityPublicKey>, yet all keys yielded by
identity.identity.public_keys() and available_authentication_keys() are DPP keys (dash_sdk::dpp::identity::IdentityPublicKey).
Because the two types are distinct, ui.selectable_value(&mut self.selected_key, Some(key.clone()), …) will not compile, nor will the later .expect("No key selected").

-    selected_key: Option<dash_sdk::platform::IdentityPublicKey>,
+    selected_key: Option<dash_sdk::dpp::identity::IdentityPublicKey>,

and update every occurrence, or perform an explicit conversion (platform::IdentityPublicKey::from(key.clone())) when pushing the value into the Option.

Failing to reconcile the types will break the entire screen at build time.

Also applies to: 102-128


334-345: 🛠️ Refactor suggestion

Prevent “Resume” without a signing key

Users can click “Resume” even when selected_key is None, which will panic inside show_confirmation_popup (expect("No key selected")). Disable the button until a key has been chosen:

-                if ui.add(button).clicked() {
-                    self.show_confirmation_popup = true;
-                }
+                let resume_clicked = ui.add_enabled(self.selected_key.is_some(), button).clicked();
+                if resume_clicked {
+                    self.show_confirmation_popup = true;
+                }
src/ui/contracts_documents/register_contract_screen.rs (2)

42-45: ⚠️ Potential issue

Key type mismatch identical to ResumeTokensScreen

selected_key is a platform key while qualified_identities[i].identity.public_keys() returns DPP keys, so the combobox comparison and later .unwrap() will not compile.

Align the types (or convert) exactly as suggested for ResumeTokensScreen.

Also applies to: 60-73


335-344: ⚠️ Potential issue

Unsafe unwraps on identity / key selection

When the “Register Contract” button is clicked the code unwraps both selected_qualified_identity and selected_key. The earlier guard only checks selected_key; selected_qualified_identity can still be None if the user cleared the dropdown.

Add a defensive check:

-                    app_action = AppAction::BackendTask(BackendTask::ContractTask(
-                        ContractTask::RegisterDataContract(
-                            contract.clone(),
-                            self.contract_alias_input.clone(),
-                            self.selected_qualified_identity.clone().unwrap().0,
-                            self.selected_key.clone().unwrap(),
-                        ),
-                    ));
+                    if let (Some(qi), Some(key)) = (&self.selected_qualified_identity, &self.selected_key) {
+                        app_action = AppAction::BackendTask(BackendTask::ContractTask(
+                            ContractTask::RegisterDataContract(
+                                contract.clone(),
+                                self.contract_alias_input.clone(),
+                                qi.0.clone(),
+                                key.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.

                    // Fire off a backend task
                    if let (Some(qi), Some(key)) = (&self.selected_qualified_identity, &self.selected_key) {
                        app_action = AppAction::BackendTask(BackendTask::ContractTask(
                            ContractTask::RegisterDataContract(
                                contract.clone(),
                                self.contract_alias_input.clone(),
                                qi.0.clone(),
                                key.clone(),
                            ),
                        ));
                    }
src/ui/tokens/burn_tokens_screen.rs (2)

43-45: ⚠️ Potential issue

Same key-type mismatch as above

selected_key: Option<dash_sdk::platform::IdentityPublicKey> but keys returned from identity are DPP keys, making all combobox operations uncompilable.

Adopt the same fix as earlier.

Also applies to: 121-136


141-170: 🛠️ Refactor suggestion

Insufficient validation of burn amount

Only syntactic validity is checked. Burning more tokens than owned results in a backend failure that surfaces as an error toast.

Add a balance check before opening the confirmation popup:

-            ui.text_edit_singleline(&mut self.amount_to_burn);
+            ui.text_edit_singleline(&mut self.amount_to_burn)
+                .hint_text(format!(
+                    "Max: {}",
+                    self.identity_token_balance.balance
+                ));

and in the confirmation logic:

-                let amount_ok = self.amount_to_burn.parse::<u64>().ok();
+                let amount_ok = self.amount_to_burn
+                    .parse::<u64>()
+                    .ok()
+                    .filter(|a| *a > 0 && *a <= self.identity_token_balance.balance);

Rejecting impossible burns client-side improves UX and avoids unnecessary backend calls.

📝 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_amount_input(&mut self, ui: &mut Ui) {
        ui.horizontal(|ui| {
            ui.label("Amount to Burn:");
            ui.text_edit_singleline(&mut self.amount_to_burn)
                .hint_text(format!(
                    "Max: {}",
                    self.identity_token_balance.balance
                ));
        });
    }

    /// Renders a confirm popup with the final "Are you sure?" step
    fn show_confirmation_popup(&mut self, ui: &mut Ui) -> AppAction {
        let mut action = AppAction::None;
        let mut is_open = true;
        egui::Window::new("Confirm Burn")
            .collapsible(false)
            .open(&mut is_open)
            .show(ui.ctx(), |ui| {
                // Validate user input
                let amount_ok = self.amount_to_burn
                    .parse::<u64>()
                    .ok()
                    .filter(|a| *a > 0 && *a <= self.identity_token_balance.balance);
                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;
                }

                ui.label(format!(
                    "Are you sure you want to burn {} tokens?",
                    self.amount_to_burn
                ));

                ui.add_space(10.0);
src/ui/tokens/claim_tokens_screen.rs (2)

67-74: 🛠️ Refactor suggestion

⚠️ Potential issue

Avoid expect() → graceful fallback when identity is missing
expect("No local qualified identity …") will panic and take down the whole GUI if the identity is not found (e.g., DB corruption, concurrent deletion, wrong network). Return a dedicated ClaimTokensScreen error/placeholder screen instead of crashing.

-        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(|| {
+                log::error!(
+                    "Identity {} not found when opening ClaimTokensScreen",
+                    identity_token_balance.identity_id
+                );
+                return ClaimTokensScreen::dummy_error(
+                    "Identity not found – try refreshing identities.".into(),
+                    app_context,
+                );
+            })?;

(illustrative – adapt to constructor signature).

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


232-243: ⚠️ Potential issue

Runtime panic if selected_key is None
expect("No key selected") will panic when the user opens the screen but never chooses a key (possible in developer mode after manually clearing the default). Validate before opening the confirmation window instead of panicking in the background task construction.

-                    if self.distribution_type.is_none() {
+                    if self.selected_key.is_none() {
+                        self.status = ClaimTokensStatus::ErrorMessage(
+                            "Select a signing key first.".into(),
+                        );
+                        return;
+                    }
+                    if self.distribution_type.is_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.

                    if self.selected_key.is_none() {
                        self.status = ClaimTokensStatus::ErrorMessage(
                            "Select a signing key first.".into(),
                        );
                        return;
                    }
                    if self.distribution_type.is_none() {
                        // existing distribution‐type error handling
                        return;
                    }

                    action = AppAction::BackendTasks(
                        vec![
                            BackendTask::TokenTask(TokenTask::ClaimTokens {
                                data_contract: self.token_contract.contract.clone(),
                                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,
src/ui/contracts_documents/create_document_screen.rs (1)

334-340: 🛠️ Refactor suggestion

⚠️ Potential issue

Optional fields with blank input trigger false-positive “MissingField” errors
field_inputs is pre-populated with "" for every property. When an optional numeric/boolean/etc. field is left blank the later parser still attempts parse::<…>(), causing an error. Skip empty strings for non-required fields:

-            let schema = doc_type
+            let schema = doc_type
                 .properties()
                 .get(name)
                 .ok_or_else(|| format!("Unknown property {name}"))?;
-
-            // convert UI string into proper Value
+            // For optional properties the user may leave the input empty.
+            if input_str.trim().is_empty() && !schema.required {
+                continue;
+            }
+            // convert UI string into proper Value

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

src/app.rs (1)

320-344: 🛠️ Refactor suggestion

⚠️ Potential issue

Spawn ZMQ listeners only when an AppContext exists for that network
devnet_core_zmq_listener and local_core_zmq_listener are spawned unconditionally even when devnet_app_context/local_app_context are None.
This wastes threads and logs misleading errors because there is no configuration nor message consumer.

-        let devnet_core_zmq_listener = CoreZMQListener::spawn_listener(
-            Network::Devnet,
-            "tcp://127.0.0.1:23710",
-            core_message_sender.clone(),
-            devnet_tx_zmq_status_option,
-        )
-        .expect("Failed to create devnet InstantSend listener");
+        let devnet_core_zmq_listener = if devnet_app_context.is_some() {
+            CoreZMQListener::spawn_listener(
+                Network::Devnet,
+                "tcp://127.0.0.1:23710",
+                core_message_sender.clone(),
+                devnet_tx_zmq_status_option,
+            )
+            .expect("Failed to create devnet InstantSend listener")
+        } else {
+            CoreZMQListener::disabled(Network::Devnet)
+        };

Apply the same guard for local_core_zmq_listener.

📝 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 devnet_tx_zmq_status_option = match devnet_app_context {
            Some(ref context) => Some(context.sx_zmq_status.clone()),
            None => None,
        };

        let devnet_core_zmq_listener = if devnet_app_context.is_some() {
            CoreZMQListener::spawn_listener(
                Network::Devnet,
                "tcp://127.0.0.1:23710",
                core_message_sender.clone(),
                devnet_tx_zmq_status_option,
            )
            .expect("Failed to create devnet InstantSend listener")
        } else {
            CoreZMQListener::disabled(Network::Devnet)
        };

        let local_tx_zmq_status_option = match local_app_context {
            Some(ref context) => Some(context.sx_zmq_status.clone()),
            None => None,
        };

        let local_core_zmq_listener = if local_app_context.is_some() {
            CoreZMQListener::spawn_listener(
                Network::Regtest,
                "tcp://127.0.0.1:20302",
                core_message_sender,
                local_tx_zmq_status_option,
            )
            .expect("Failed to create local InstantSend listener")
        } else {
            CoreZMQListener::disabled(Network::Regtest)
        };
src/ui/tokens/destroy_frozen_funds_screen.rs (2)

60-66: ⚠️ Potential issue

Field / method name collision will not compile

show_confirmation_popup is declared as a struct field (bool) and later re-used as a method name.
Fields and associated items share the same namespace in Rust; this causes a hard
compile-time error (duplicate definitions with name 'show_confirmation_popup').

-    show_confirmation_popup: bool,
+    show_confirmation_popup_flag: bool,   // or rename the method instead

and update every occurrence (self.show_confirmation_popupself.show_confirmation_popup_flag).

Also applies to: 155-156


82-83: 🛠️ Refactor suggestion

Unconditional expect/unwrap may crash the GUI

Several .expect() / .unwrap() calls assume data/contracts/keys are always
present. Any out-of-sync state will panic the entire GUI:

  • lines 82-83 – missing local identity
  • lines 98-105 – wallet acquisition
  • lines 197-216 – contract lookup & selected_key.expect

Prefer graceful user feedback:

-let data_contract = self
-    .app_context
-    .get_contracts(None, None)
-    .expect("Contracts not loaded")
+let data_contract = match self.app_context.get_contracts(None, None) {
+    Ok(c) => c,
+    Err(e) => {
+        self.error_message = Some(format!("Failed to load contracts: {e}"));
+        self.status = DestroyFrozenFundsStatus::ErrorMessage(e.to_string());
+        return AppAction::None;
+    }
+};

Repeat for other spots; replace .expect() with user-visible error paths.

Also applies to: 98-105, 197-216

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

52-54: ⚠️ Potential issue

Duplicate identifier show_confirmation_popup

Same compile-time issue as in the previous screen – rename either the field or the method.

Also applies to: 135-136


146-171: 🛠️ Refactor suggestion

selected_key.expect("No key selected") can panic

Disable “Pause” until a key is chosen (see suggestion in destroy screen) or
surface a validation error instead of panicking.

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

171-178: 🛠️ Refactor suggestion

Input validation & overflow

amount_to_mint.parse::<u64>():

  1. u64 may overflow for large supplies; consider u128 or decimal types.
  2. Negative / zero amounts are currently accepted.
-let amount = self.amount_to_mint.parse::<u64>().ok();
+let amount = self.amount_to_mint
+    .parse::<u128>()
+    .ok()
+    .filter(|v| *v > 0);

Fail early with a helpful message if validation fails.

Also applies to: 242-244


55-57: ⚠️ Potential issue

Name collision: show_confirmation_popup

The struct field and method share the same identifier – compilation will fail.
Rename one of them consistently across all token screens.

Also applies to: 163-164


242-243: 🛠️ Refactor suggestion

Potential panic on missing selected_key

selected_key.expect("Expected a key") will crash the GUI.
Disable the “Mint” button until a key is chosen or return an error.

src/ui/mod.rs (1)

304-337: ⚠️ Potential issue

Incorrect screen construction for several Token‐related variants

FreezeTokensScreen, UnfreezeTokensScreen, PauseTokensScreen, and ResumeTokensScreen are all routed to DestroyFrozenFundsScreen, and ClaimTokensScreen is marked unreachable!().
This breaks navigation: the user will never reach the correct UI for those operations and will panic if ClaimTokensScreen is requested.

-            ScreenType::FreezeTokensScreen(identity_token_balance) => {
-                Screen::DestroyFrozenFundsScreen(DestroyFrozenFundsScreen::new(
+            ScreenType::FreezeTokensScreen(identity_token_balance) => {
+                Screen::FreezeTokensScreen(FreezeTokensScreen::new(
                 ...
             }
 /* repeat the same pattern for the three cases below */
-            ScreenType::UnfreezeTokensScreen(identity_token_balance) => {
-                Screen::DestroyFrozenFundsScreen(DestroyFrozenFundsScreen::new(
+            ScreenType::UnfreezeTokensScreen(identity_token_balance) => {
+                Screen::UnfreezeTokensScreen(UnfreezeTokensScreen::new(
                 ...
             }
-            ScreenType::PauseTokensScreen(identity_token_balance) => {
-                Screen::DestroyFrozenFundsScreen(DestroyFrozenFundsScreen::new(
+            ScreenType::PauseTokensScreen(identity_token_balance) => {
+                Screen::PauseTokensScreen(PauseTokensScreen::new(
                 ...
             }
-            ScreenType::ResumeTokensScreen(identity_token_balance) => {
-                Screen::DestroyFrozenFundsScreen(DestroyFrozenFundsScreen::new(
+            ScreenType::ResumeTokensScreen(identity_token_balance) => {
+                Screen::ResumeTokensScreen(ResumeTokensScreen::new(
                 ...
             }
-            ScreenType::ClaimTokensScreen(_) => {
-                unreachable!();
-            }
+            ScreenType::ClaimTokensScreen(identity_token_balance) => {
+                Screen::ClaimTokensScreen(ClaimTokensScreen::new(
+                    identity_token_balance.clone(),
+                    app_context,
+                ))
+            }

Without this patch the UI will mislead users and can crash at runtime.

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

224-232: 🛠️ Refactor suggestion

⚠️ Potential issue

Unchecked amount.parse::<u64>().unwrap() may panic

User input can be non-numeric or exceed u64, leading to a crash during confirmation.

-                                amount: self.amount.parse().unwrap(),
+                                amount: match self.amount.parse::<u64>() {
+                                    Ok(v) => v,
+                                    Err(_) => {
+                                        self.error_message = Some("Invalid amount".into());
+                                        self.transfer_tokens_status =
+                                            TransferTokensStatus::ErrorMessage(
+                                                "Invalid amount".into(),
+                                            );
+                                        return;
+                                    }
+                                },

Consider also validating v > 0 && v <= self.max_amount before dispatching the backend task.

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

                        vec![
                            BackendTask::TokenTask(TokenTask::TransferTokens {
                                sending_identity: self.identity.clone(),
                                recipient_id: identifier,
                                amount: match self.amount.parse::<u64>() {
                                    Ok(v) => v,
                                    Err(_) => {
                                        self.error_message = Some("Invalid amount".into());
                                        self.transfer_tokens_status =
                                            TransferTokensStatus::ErrorMessage(
                                                "Invalid amount".into(),
                                            );
                                        return;
                                    }
                                },
                                data_contract,
                                token_position: self.identity_token_balance.token_position,
                                signing_key: self.selected_key.clone().expect("Expected a key"),
                            }),

68-76: ⚠️ Potential issue

max_amount is calculated across all tokens, not the selected one

find(|balance| balance.identity_id == identity.identity.id()) returns the first match for the identity, which may belong to a different token.
Filter by both identity_id and the token’s token_position (and/or contract id) to avoid overdrawing.

-        let max_amount = token_balances
-            .values()
-            .find(|balance| balance.identity_id == identity.identity.id())
+        let max_amount = token_balances
+            .values()
+            .find(|balance| {
+                balance.identity_id == identity.identity.id()
+                    && balance.token_position == identity_token_balance.token_position
+            })
             .map(|balance| balance.balance)
             .unwrap_or(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.

        let token_balances = app_context
            .db
            .get_identity_token_balances(app_context)
            .expect("Token balances not loaded");
        let max_amount = token_balances
            .values()
            .find(|balance| {
                balance.identity_id == identity.identity.id()
                    && balance.token_position == identity_token_balance.token_position
            })
            .map(|balance| balance.balance)
            .unwrap_or(0);

392-398: 🛠️ Refactor suggestion

selected_wallet is never refreshed when the user changes selected_key

If the user picks a different key after the screen is created, render_wallet_unlock_if_needed() may still point to the old (or None) wallet.

Add a lightweight sync at the start of ui():

fn ui(&mut self, ctx: &Context) -> AppAction {
+        // Keep wallet in sync with the currently selected key
+        if let Some(key) = &self.selected_key {
+            let mut err = None;
+            self.selected_wallet =
+                get_selected_wallet(&self.identity, None, Some(key), &mut err);
+            if let Some(e) = err {
+                self.error_message = Some(e);
+            }
+        }

This ensures the unlock dialog always targets the correct wallet.

📝 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 ui(&mut self, ctx: &Context) -> AppAction {
        // Keep wallet in sync with the currently selected key
        if let Some(key) = &self.selected_key {
            let mut err = None;
            self.selected_wallet =
                get_selected_wallet(&self.identity, None, Some(key), &mut err);
            if let Some(e) = err {
                self.error_message = Some(e);
            }
        }

        if self.selected_wallet.is_some() {
            let (needed_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui);

            if needed_unlock && !just_unlocked {
                return;
            }
        }

        // ... rest of your UI rendering ...
}
src/ui/network_chooser_screen.rs (1)

320-327: ⚠️ Potential issue

Destructive “Clear local Platform data” should confirm first

Wiping platform data is irreversible. Triggering it with a single click risks accidental data loss. Wrap the action in a confirmation dialog or at least a second “Are you sure?” button before dispatching SystemTask::WipePlatformData.

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

53-56: 🛠️ Refactor suggestion

Make TokenTask publicly accessible

TokenTask is declared pub(crate), yet callers outside backend_task::tokens may need to construct it (e.g. UI screens). If any module outside this sub-crate tries to use the enum, compilation will fail.

-pub(crate) enum TokenTask {
+pub enum TokenTask {

Limiting visibility can be fine, but ensure the enclosing path matches all current and future call-sites.

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

--- a/src/backend_task/tokens/mod.rs
+++ b/src/backend_task/tokens/mod.rs
@@
 #[derive(Debug, Clone, PartialEq)]
-pub(crate) enum TokenTask {
+pub enum TokenTask {
     RegisterTokenContract {
         identity: QualifiedIdentity,

191-204: ⚠️ Potential issue

Avoid panic when token_names is empty

register_data_contract indexes token_names[0]; if the caller passes an empty vec the process panics. Validate early:

-                let data_contract = self
+                if token_names.is_empty() {
+                    return Err("token_names must contain at least one (name, lang) pair".into());
+                }
+                let data_contract = self

Failing fast with a clear error prevents unexpected crashes.

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

                if token_names.is_empty() {
                    return Err("token_names must contain at least one (name, lang) pair".into());
                }
                let data_contract = self
                    .build_data_contract_v1_with_one_token(
                        identity.identity.id().clone(),
                        token_names.clone(),
                        contract_keywords.to_vec(),
                        token_description.clone(),
                        *should_capitalize,
                        *decimals,
                        *base_supply,
                        *max_supply,
                        *start_paused,
                        *keeps_history,
                        *main_control_group,
                        manual_minting_rules.clone(),
                    )
src/ui/tokens/unfreeze_tokens_screen.rs (2)

89-100: 🛠️ Refactor suggestion

Preserve error_message returned by get_selected_wallet

error_message populated inside get_selected_wallet is immediately shadowed and lost.
Persisting it ensures the UI can surface wallet-related problems to the user.

-        let mut error_message = None;
-        let selected_wallet =
-            get_selected_wallet(&identity, None, possible_key.clone(), &mut error_message);
+        let mut error_message = None;
+        let selected_wallet =
+            get_selected_wallet(&identity, None, possible_key.clone(), &mut error_message);
+
+        // Preserve any wallet-selection error we just collected

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


374-392: ⚠️ Potential issue

Avoid panic when no signing key is selected

Unfreeze is always enabled; if the user hasn’t picked a key the backend dispatch will panic on
expect("No key selected"). Disable the button (or show a tooltip) until self.selected_key is
Some.

-                if ui.add(button).clicked() {
-                    self.show_confirmation_popup = true;
-                }
+                let enabled = self.selected_key.is_some();
+                if ui.add_enabled(enabled, button).clicked() {
+                    self.show_confirmation_popup = true;
+                }
📝 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.

                // 2) Identity to unfreeze
                ui.heading("2. Enter the identity ID to unfreeze");
                ui.add_space(5.0);
                self.render_unfreeze_identity_input(ui);

                ui.add_space(10.0);

                // Unfreeze button
                let button = egui::Button::new(RichText::new("Unfreeze").color(Color32::WHITE))
                    .fill(Color32::from_rgb(0, 128, 128))
                    .corner_radius(3.0);

                // Disable until a key is selected
                let enabled = self.selected_key.is_some();
                if ui.add_enabled(enabled, button).clicked() {
                    self.show_confirmation_popup = true;
                }

                // If user pressed "Unfreeze," show popup
                if self.show_confirmation_popup {
                    action |= self.show_confirmation_popup(ui);
                }
src/database/tokens.rs (3)

228-247: ⚠️ Potential issue

JOIN misses network filter – cross-network leakage possible

get_identity_token_balances constrains identity_token_balances.network but not token.network.
If the same token ID exists on multiple networks the query may return the wrong row.

... JOIN token AS t ON b.token_id = t.id AND t.network = b.network

318-336: 🛠️ Refactor suggestion

Dropping and recreating token_order unconditionally causes silent data loss

initialize_token_order_table deletes existing user ordering every time it runs.
Consider running this only during explicit migrations, or check PRAGMA user_version before
destructive changes.


340-362: 🛠️ Refactor suggestion

token_order is not scoped per-network

save_token_order/load_token_order ignore network, so switching networks will mix orders and may
violate foreign-key constraints.
Add a network column (part of PK) and include it in all INSERT/SELECT statements.

Also applies to: 367-394

src/context.rs (3)

411-425: 🛠️ Refactor suggestion

get_contract_by_id ignores in-memory system contracts

The method only queries the database; IDs matching DPNS, withdrawals, token-history or
keyword-search contracts will return None unless those contracts were manually persisted.
Either:

  1. Check the four in-memory contracts before falling back to DB, or
  2. Ensure they are inserted into the DB at startup.

Failing to do so breaks callers that rely on this helper.


604-610: ⚠️ Potential issue

Error in serialising TokenConfiguration is swallowed

Returning Ok(()) when serialisation fails hides real problems and leaves DB rows incomplete.
Propagate the error (or at least log it) instead of silently succeeding.

-        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)))?;
📝 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)))?;

203-209: 💡 Verification agent

🧩 Analysis chain

Changed regtest identifier from "regtest" to "local" – verify downstream usages

If any DB rows, config files, or hard-coded paths still expect "regtest", this rename will break
look-ups and path resolution. Please audit callers of network_string().
Run a quick search to confirm:


🏁 Script executed:

rg '"regtest"' -n

Length of output: 80


Harmonize Regtest identifier in app_dir.rs

The literal "regtest" in src/app_dir.rs still diverges from the new "local" returned by network_string(). This will break path lookups for Regtest.

• File: src/app_dir.rs, line 56
• Change the Regtest arm to match the new identifier

- Network::Regtest => "regtest",
+ Network::Regtest => "local",

Optionally, you could call self.context.network_string() here instead of a hard-coded literal for future consistency.

@thephez
Copy link
Copy Markdown
Contributor Author

thephez commented Apr 30, 2025

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2025

✅ Actions performed

Full review triggered.

@thephez
Copy link
Copy Markdown
Contributor Author

thephez commented May 5, 2025

@pauldelucia can you review?

Copy link
Copy Markdown
Member

@pauldelucia pauldelucia left a comment

Choose a reason for hiding this comment

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

Thank you @thephez

@pauldelucia pauldelucia merged commit 2787fae into dashpay:v0.9-dev May 7, 2025
1 check passed
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