Skip to content

UI bug fix import wallet - #475

Merged
praveenperera merged 98 commits into
masterfrom
ui-bug-fix-import-wallet
Dec 11, 2025
Merged

praveenperera merged 98 commits into
masterfrom
ui-bug-fix-import-wallet

Conversation

@praveenperera

@praveenperera praveenperera commented Dec 5, 2025 •

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Enhanced keyboard handling and text input accessibility improvements.
  • Bug Fixes

    • Fixed fiat balance synchronization across wallet views.
    • Improved keyboard behavior during text field transitions.
  • Performance

    • Optimized wallet scanning by removing unnecessary delays.
    • Accelerated price fetching with improved retry mechanisms.
    • Enhanced operation efficiency through streamlined data handling.
  • Chores

    • Updated project dependencies and infrastructure.

✏️ Tip: You can customize this high-level summary in your review settings.

- Update SortRow to derive button state dynamically from Rust
- Add sort state read to trigger Compose recomposition
- Make sort property public with private set in CoinControlManager
- Add enabled parameter to Continue button
- Remove hardcoded arrow directions - now derived from ListSortDirection
Add wrapper functions to dispatch setWalletMetadata to background
threads, preventing potential UI jank from database I/O:

- iOS: setWalletMetadata dispatches to rustBridge queue
- Android: persistWalletMetadata dispatches to ioScope
- Android: Add ioScope for reconcile threading (matches iOS pattern)

UI state updates remain on main thread, only the DB write moves.
Match iOS reconcile pattern: run FFI callbacks on background thread
before dispatching to main thread for UI updates. This prevents
potential ANR issues from blocking the main thread during reconcile.

Managers updated:
- SendFlowManager
- CoinControlManager
- AppManager
- AuthManager
- PendingWalletManager
- ImportWalletManager
- Add guard against invalid maxWidthPx (0 or infinity)
- Initialize optimalSize to max instead of min to prevent
  returning minimum font size when constraints aren't ready
- Use finer 0.1sp steps instead of 0.5sp for less over-shrinking
- Add all style dependencies to remember keys
- Remove aggressive character-based pre-reduction in BalanceAutoSizeText,
  letting binary search find optimal size directly
- Add tight text styling to Typography with PlatformTextStyle and
  LineHeightStyle to remove extra font padding
- Improve AutoSizeText binary search precision and add guard for
  invalid width
- Revert BalanceWidget spacing to 6.dp now that typography matches iOS
Directly modify metadata instead of calling dispatch to prevent
potential deadlock if this function is ever called from within
a dispatch handler.
Add observer for selectedUnit changes that notifies SendFlowManager
when unit changes, mirroring iOS .onChange behavior. Without this,
the dropdown would update walletMetadata but the Rust backend was
never notified to recalculate displayed amounts.
@praveenperera
praveenperera force-pushed the android-final-ui-polish-and-fixes branch 4 times, most recently from 83a4d49 to 2035faa Compare December 11, 2025 15:59
Base automatically changed from android-final-ui-polish-and-fixes to master December 11, 2025 16:38
@praveenperera
praveenperera marked this pull request as ready for review December 11, 2025 16:47
@praveenperera

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Dec 11, 2025

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@praveenperera
praveenperera enabled auto-merge (squash) December 11, 2025 16:48
@greptile-apps

greptile-apps Bot commented Dec 11, 2025

Copy link
Copy Markdown

Greptile Overview

Greptile Summary

This PR addresses UI bugs related to wallet import and improves perceived performance across the application. The changes focus on three main areas:

Backend Performance Improvements:

  • Converted amountInFiat from async to sync method using cached prices, returning Option<f64> instead of throwing errors
  • Added immediate sending of cached balance and transactions to UI on wallet load, eliminating loading delays
  • Added database fallback for price cache to ensure data availability even before network fetch
  • Migrated retry logic from tryhard to backon crate with improved exponential backoff
  • Reduced route transition delays from 800ms to 250ms for snappier navigation

iOS Wallet Import Keyboard Fix:

  • Implemented custom TextFieldReturnHandler delegate using SwiftUIIntrospect to prevent keyboard dismissal on return key
  • Improved validation logic to prevent advancing to next field on invalid words
  • Enhanced keyboard accessory reattachment with new show/hide observers for focus transitions

Fiat Balance Loading UX:

  • Changed balance displays from String to ViewBuilder with ProgressView when fiat prices are loading
  • Added automatic fiat balance refresh when prices update
  • Removed manual delays and balance update calls, relying on cached data pushed from backend

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The changes are well-structured architectural improvements focused on performance and UX. The backend changes convert async operations to sync cached operations, reducing complexity. The iOS keyboard fix uses a proper delegate pattern. UniFFI-generated code is automatically produced and matches the Rust API changes. All changes are additive or simplifying, with no complex logic modifications that could introduce bugs.
  • No files require special attention

Important Files Changed

File Analysis

Filename Score Overview
rust/src/manager/wallet_manager.rs 5/5 Changed amountInFiat from async to sync method using cached prices, returns Option<f64>. Removed balanceInFiat method. Added immediate cached balance/transaction sending to UI on wallet load.
rust/src/fiat/client.rs 5/5 Added value_in_currency_cached sync method, added database fallback for cached prices, improved retry logic using backon, adjusted timing constants for price fetching.
ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletImportCard.swift 4/5 Added TextFieldReturnHandler delegate to handle return key without dismissing keyboard, improved validation logic to prevent advancing on invalid words, integrated SwiftUIIntrospect.
ios/Cove/Components/KeyboardAccessory/KeyboardAccessoryHost.swift 5/5 Added keyboard show/hide observers to handle accessory reattachment on focus transitions, improved state tracking for keyboard visibility.
ios/Cove/Flows/SelectedWalletFlow/WalletBalanceHeaderView.swift 5/5 Changed balance display from String to ViewBuilder, added ProgressView when fiat balance is loading (nil), improved loading state UX.
ios/Cove/AppManager.swift 5/5 Added updateFiatBalance() call when prices change, reduced wallet mode change delay to 250ms, extracted delay constant.

Sequence Diagram

sequenceDiagram
    participant UI as iOS/Android UI
    participant WM as WalletManager
    participant Wallet as Wallet (BDK)
    participant FiatClient as FiatClient
    participant DB as Database Cache
    
    Note over UI,DB: Wallet Load Flow (Instant Cached Data)
    UI->>WM: Load Wallet
    WM->>Wallet: try_load_persisted()
    Wallet-->>WM: wallet instance
    WM->>Wallet: balance() (cached)
    Wallet-->>WM: Balance
    WM->>Wallet: transactions() (cached)
    Wallet-->>WM: Vec<Transaction>
    WM->>UI: Send cached balance & transactions
    Note over UI: UI shows data immediately
    WM->>WM: Start wallet scan (async)
    
    Note over UI,DB: Fiat Balance Display Flow
    UI->>WM: Display fiat balance
    WM->>FiatClient: value_in_currency_cached()
    FiatClient->>FiatClient: Check in-memory cache
    alt Cache hit
        FiatClient-->>WM: Some(fiat_value)
        WM-->>UI: Display fiat amount
    else Cache miss (in-memory)
        FiatClient->>DB: get_prices()
        alt DB cache hit
            DB-->>FiatClient: Some(prices)
            FiatClient-->>WM: Some(fiat_value)
            WM-->>UI: Display fiat amount
        else No cache
            FiatClient-->>WM: None
            WM-->>UI: Show ProgressView
        end
    end
    
    Note over UI,DB: Price Update Flow
    FiatClient->>FiatClient: Background fetch (60s interval)
    FiatClient->>FiatClient: Retry with backon
    FiatClient-->>UI: FiatPricesChanged event
    UI->>WM: updateFiatBalance()
    WM->>UI: Update displayed amounts
    
    Note over UI,DB: Import Wallet Keyboard Flow
    UI->>UI: User enters word in TextField
    UI->>UI: Press Return key
    UI->>UI: TextFieldReturnHandler.onReturn()
    UI->>UI: Validate word
    alt Valid word
        UI->>UI: Set state to valid
        UI->>UI: Move focus to next field
        Note over UI: Keyboard stays visible
    else Invalid word
        UI->>UI: Set state to invalid
        Note over UI: Stay on current field
    end
Loading

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

17 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@praveenperera
praveenperera enabled auto-merge (squash) December 11, 2025 17:08
@praveenperera
praveenperera merged commit 202b5d9 into master Dec 11, 2025
7 of 9 checks passed
@praveenperera
praveenperera deleted the ui-bug-fix-import-wallet branch December 11, 2025 17:08

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (9)
ios/Cove/Components/KeyboardAccessory/KeyboardAccessoryHost.swift (1)

51-57: Naming inconsistency: observer name doesn't match notification type.

keyboardDidShowObserver subscribes to keyboardWillShowNotification. Consider renaming to keyboardWillShowObserver for consistency and to avoid confusion.

-    private var keyboardDidShowObserver: NSObjectProtocol?
+    private var keyboardWillShowObserver: NSObjectProtocol?

And update the corresponding references in init() and deinit.

ios/Cove/Flows/SelectedWalletFlow/WalletBalanceHeaderView.swift (1)

177-181: Minor typo in comment

The comment on Line 178 has a small typo: "pased" → "passed".

-            // if fiatBalance was pased in explicitly, don't update it, only for previews
+            // if fiatBalance was passed in explicitly, don't update it, only for previews
ios/Cove/Flows/SelectedWalletFlow/SelectedWalletContainer.swift (1)

78-79: Verify that silent error handling is intentional.

Scan failures are currently only logged without user-facing feedback. Please confirm this is the intended behavior—perhaps the UI's loading state or other mechanisms already surface wallet scan issues to users.

ios/Cove/Extention/KeyboardObserver.swift (2)

5-13: Observer token properties look good; consider excluding them from observation

The use of @Observable + @MainActor and nonisolated(unsafe) for the notification tokens is a reasonable pattern given deinit isolation. If you don’t need showObserver / hideObserver to participate in the observation graph, consider annotating them with @ObservationIgnored to make the intent explicit and keep the observable surface limited to UI-relevant state (keyboardIsShowing / keyboardHeight).


16-29: Keyboard show/hide handling is correct; you may want to sync with system animation timing

The show/hide observers correctly hop back to the main actor and update both keyboardIsShowing and keyboardHeight with animation. If you want tighter visual parity with the system keyboard animation (especially across devices and iPad configurations), you could pull the duration (and optionally curve) from notification.userInfo instead of hard‑coding 0.25, and reuse that value in both the show and hide branches.

Also applies to: 32-45

rust/src/wallet.rs (1)

514-533: Consider optimization and deterministic sorting.

The current implementation maps all transactions before filtering, which computes sent_and_received for transactions that may be discarded. Additionally, sort_unstable_by may produce non-deterministic ordering for transactions with equal amounts.

Consider this refactor for better efficiency and deterministic ordering:

     pub fn transactions(&self) -> Vec<crate::transaction::Transaction> {
         use crate::transaction::{Amount, Transaction};
 
         let zero = Amount::ZERO;
 
         let mut transactions = self
             .bdk
             .transactions()
             .map(|tx| {
                 let sent_and_received = self.bdk.sent_and_received(&tx.tx_node.tx).into();
                 (tx, sent_and_received)
             })
+            // Filter early to avoid unnecessary Transaction::new calls
+            .filter(|(_, sent_and_received)| sent_and_received.amount() > zero)
             .map(|(tx, sent_and_received)| Transaction::new(&self.id, sent_and_received, tx))
-            .filter(|tx| tx.sent_and_received().amount() > zero)
             .collect::<Vec<Transaction>>();
 
-        transactions.sort_unstable_by(|a, b| a.cmp(b).reverse());
+        // Use stable sort for deterministic ordering
+        transactions.sort_by(|a, b| a.cmp(b).reverse());
         transactions
     }
rust/src/app.rs (1)

522-539: Consider the 120-second retry delay for production.

The fiat price initialization now uses a ConstantBuilder with a 120-second delay between retries (up to 5 attempts, totaling ~10 minutes). This seems excessive for an app initialization path. If the first few attempts fail, users may experience a degraded experience for an extended period.

Consider using an exponential backoff similar to other retry logic in the codebase:

-            let init_result = (|| crate::fiat::client::init_prices())
-                .retry(
-                    ConstantBuilder::default()
-                        .with_delay(Duration::from_secs(120))
-                        .with_max_times(5),
-                )
-                .notify(|err, _| warn!("unable to init prices: {err}, trying again"))
-                .await;
+            let init_result = (|| crate::fiat::client::init_prices())
+                .retry(
+                    backon::ExponentialBuilder::default()
+                        .with_min_delay(Duration::from_secs(5))
+                        .with_max_delay(Duration::from_secs(60))
+                        .with_max_times(5),
+                )
+                .notify(|err, _| warn!("unable to init prices: {err}, trying again"))
+                .await;

This would retry faster initially (5s, 10s, 20s, 40s, 60s) while still backing off on persistent failures.

rust/src/fiat/client.rs (2)

126-131: Consider logging database errors instead of silently ignoring them.

The if let Ok(Some(prices)) pattern silently discards any database errors. Consider logging errors to aid debugging:

-        // fallback to database cache
-        if let Ok(Some(prices)) = Database::global().global_cache.get_prices() {
+        // fallback to database cache
+        match Database::global().global_cache.get_prices() {
+            Ok(Some(prices)) => {
                 debug!("loaded cached prices from database");
                 PRICES.swap(Arc::new(Some(prices)));
                 return Some(prices);
+            }
+            Ok(None) => {}
+            Err(e) => {
+                warn!("failed to load cached prices from database: {e:?}");
+            }
         }

244-262: Minor redundancy: Double HARD_LIMIT check.

The function checks HARD_LIMIT at line 248, then calls get_or_fetch_prices() which checks it again at line 142. This is harmless but creates slight redundancy. Consider if the outer check is still valuable or could be removed.

Otherwise, the retry configuration with 5 attempts and 1s max delay is appropriate for a background refresh operation.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ebce2cf and 80270c6.

⛔ Files ignored due to path filters (2)
  • ios/CoveCore/Sources/CoveCore/generated/cove.swift is excluded by !**/generated/**
  • rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • android/app/src/main/java/org/bitcoinppl/cove_core/cove.kt (8 hunks)
  • ios/Cove.xcodeproj/project.pbxproj (8 hunks)
  • ios/Cove.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved (2 hunks)
  • ios/Cove/AppManager.swift (3 hunks)
  • ios/Cove/Components/KeyboardAccessory/KeyboardAccessoryHost.swift (2 hunks)
  • ios/Cove/Extention/KeyboardObserver.swift (1 hunks)
  • ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletImportCard.swift (4 hunks)
  • ios/Cove/Flows/SelectedWalletFlow/SelectedWalletContainer.swift (1 hunks)
  • ios/Cove/Flows/SelectedWalletFlow/WalletBalanceHeaderView.swift (3 hunks)
  • ios/Cove/WalletManager.swift (4 hunks)
  • rust/Cargo.toml (1 hunks)
  • rust/src/app.rs (4 hunks)
  • rust/src/fee_client.rs (2 hunks)
  • rust/src/fiat/client.rs (6 hunks)
  • rust/src/manager/wallet_manager.rs (2 hunks)
  • rust/src/router.rs (3 hunks)
  • rust/src/wallet.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
rust/**/*.rs

⚙️ CodeRabbit configuration file

rust/**/*.rs: - Verify correct functionality and logic consistency.

  • Check for idiomatic Rust usage and performance best practices.
  • Suggest improvements to error handling, unwraps, and Result handling.
  • Check for any potential memory leaks or unsafe code.
  • Check for any potential mutex deadlocks.
  • Check for potential security issues, make sure Bitcoin wallets are handled securely.
  • Identify spelling mistakes in comments, string literals, and documentation

Files:

  • rust/src/wallet.rs
  • rust/src/router.rs
  • rust/src/fee_client.rs
  • rust/src/app.rs
  • rust/src/fiat/client.rs
  • rust/src/manager/wallet_manager.rs
ios/Cove/**/*.swift

⚙️ CodeRabbit configuration file

ios/Cove/**/*.swift: - Review SwiftUI view code for proper layout, best practices

  • Identify spelling mistakes in comments, string literals, and documentation
  • Ignore generated bindings code in ios/CoveCore/Sources/CoveCore/generated/**

Files:

  • ios/Cove/Flows/SelectedWalletFlow/SelectedWalletContainer.swift
  • ios/Cove/Components/KeyboardAccessory/KeyboardAccessoryHost.swift
  • ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletImportCard.swift
  • ios/Cove/AppManager.swift
  • ios/Cove/Flows/SelectedWalletFlow/WalletBalanceHeaderView.swift
  • ios/Cove/WalletManager.swift
  • ios/Cove/Extention/KeyboardObserver.swift
🧬 Code graph analysis (6)
rust/src/wallet.rs (2)
rust/src/transaction/ffi.rs (2)
  • sent_and_received (189-191)
  • sent_and_received (212-214)
rust/src/transaction.rs (2)
  • sent_and_received (111-116)
  • new (67-109)
android/app/src/main/java/org/bitcoinppl/cove_core/cove.kt (5)
android/app/src/main/java/org/bitcoinppl/cove_core/types/cove_types.kt (17)
  • callWithHandle (2377-2398)
  • callWithHandle (2727-2748)
  • callWithHandle (3011-3032)
  • callWithHandle (3327-3348)
  • callWithHandle (3630-3651)
  • callWithHandle (4000-4021)
  • callWithHandle (4282-4303)
  • callWithHandle (4780-4801)
  • callWithHandle (5055-5076)
  • callWithHandle (5379-5400)
  • callWithHandle (5724-5745)
  • callWithHandle (6027-6048)
  • callWithHandle (6401-6422)
  • callWithHandle (6646-6667)
  • callWithHandle (6994-7015)
  • callWithHandle (7334-7355)
  • uniffiRustCall (273-275)
android/app/src/main/java/org/bitcoinppl/cove_core/nfc/cove_nfc.kt (1)
  • uniffiRustCall (273-275)
android/app/src/main/java/org/bitcoinppl/cove_core/ur/cove_ur.kt (1)
  • uniffiRustCall (273-275)
android/app/src/main/java/uniffi/cove_ur/cove_ur.kt (1)
  • uniffiRustCall (273-275)
android/app/src/main/java/org/bitcoinppl/cove_core/device/cove_device.kt (1)
  • uniffiRustCall (273-275)
ios/Cove/AppManager.swift (2)
ios/Cove/WalletManager.swift (1)
  • updateFiatBalance (140-142)
android/app/src/main/java/org/bitcoinppl/cove/WalletManager.kt (1)
  • updateFiatBalance (194-206)
rust/src/app.rs (1)
rust/src/fiat/client.rs (2)
  • init_prices (198-230)
  • prices (115-135)
rust/src/fiat/client.rs (2)
rust/src/app.rs (6)
  • new (42-44)
  • new (80-116)
  • prices (124-126)
  • prices (471-473)
  • global (119-121)
  • global (253-255)
rust/src/database.rs (2)
  • new (52-54)
  • global (87-91)
ios/Cove/Flows/SelectedWalletFlow/WalletBalanceHeaderView.swift (2)
ios/CoveCore/Sources/CoveCore/generated/cove.swift (4)
  • displayFiatAmount (7293-7301)
  • displayFiatAmount (7899-7907)
  • amount (7258-7264)
  • amount (9115-9121)
ios/Cove/WalletManager.swift (1)
  • amountFmtUnit (118-123)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: swiftformat
  • GitHub Check: clippy
  • GitHub Check: test (macos-latest)
  • GitHub Check: test (ubuntu-latest)
  • GitHub Check: compile-ios
  • GitHub Check: compile-android
  • GitHub Check: ktlint
🔇 Additional comments (30)
ios/Cove/Components/KeyboardAccessory/KeyboardAccessoryHost.swift (2)

89-104: Well-structured keyboard reattachment logic.

The approach of clearing the accessory before re-setting it ensures a clean state on each focus change. Using keyboardWillHide to track detachment and keyboardWillShow to reattach handles the focus-transition edge cases correctly.


106-116: Observer cleanup is correct.

All three observers are properly removed in deinit, preventing potential crashes or memory leaks.

android/app/src/main/java/org/bitcoinppl/cove_core/cove.kt (4)

2339-2340: Verify FFI signature is consistent with Rust side changes.

The external function signature now returns RustBuffer.ByValue with an uniffi_out_err parameter, indicating a shift from async future-based FFI to synchronous error-returning calls. Ensure this matches the corresponding Rust function signature change (amount_in_fiat).


18125-18137: Implementation pattern is correct.

The synchronous implementation correctly uses the callWithHandle { uniffiRustCall { ... } } pattern:

  • callWithHandle ensures object lifetime safety via atomic call counter increment/decrement (per relevant snippets).
  • uniffiRustCall provides inline error handling via UniffiRustCallStatus.
  • FfiConverterOptionalDouble.lift() properly converts the RustBuffer.ByValue result.

The nesting and error-handling flow align with patterns in other generated FFI files (e.g., cove_nfc.kt, cove_device.kt).


42518-42545: FfiConverterOptionalDouble implementation is correct.

The converter properly encodes/decodes Rust Option<f64> as a single-byte discriminant followed by optional data:

  • read(): Discriminant byte 0 = null, non-zero = unwrap and read Double
  • allocationSize(): 1 byte discriminant + (Double size if non-null)
  • write(): 0 for null, 1 + Double value for non-null

This matches the standard FFI pattern for optional types and is consistent with existing converters in the file (e.g., FfiConverterOptionalLong).


17890-17893: Function signature is already synchronous with proper call site compatibility.

The current code shows amountInFiat is defined as a regular synchronous function (fun 'amountInFiat'('amount': Amount): kotlin.Double?), and the single call site in WalletTransactionsScreen.kt (line 822) is already using it correctly without .await() and properly handling the null return value via try-catch. No .await() patterns exist in the codebase for this function, and no breaking changes are evident in the current state.

Likely an incorrect or invalid review comment.

ios/Cove/Flows/SelectedWalletFlow/WalletBalanceHeaderView.swift (3)

23-37: Primary balance view builder change looks solid

The new primaryBalanceView cleanly handles sensitivity masking, fiat vs BTC switching, and introduces a proper loading state via ProgressView instead of empty strings. This is a clear improvement in readability and UX.


39-54: Secondary balance view logic correctly mirrors primary with fiat fallback

secondaryBalanceView correctly shows masked content when hidden, fiat-as-secondary when BTC is primary (with a loading spinner), and BTC/sats when fiat is primary. The split between primary/secondary roles is clear and matches the intended toggle behavior.


80-85: Good use of contentTransition(.numericText()) around the new views

Applying .contentTransition(.numericText()) to the containers using primaryBalanceView and secondaryBalanceView is appropriate for smooth numeric updates as balances change, and works well with the conditional Text/ProgressView produced by the view builders.

Also applies to: 90-94

ios/Cove/Flows/SelectedWalletFlow/SelectedWalletContainer.swift (1)

73-82: LGTM! Immediate scan improves UX.

The removal of artificial delays and immediate invocation of startWalletScan() is a solid improvement. Starting the scan right away with cached data first makes the UI feel more responsive. The if let manager check appropriately handles cases where manager initialization might have failed.

ios/Cove/Extention/KeyboardObserver.swift (1)

48-51: Observer cleanup in deinit is implemented correctly

Storing the NSObjectProtocol tokens and removing them in deinit avoids leaks and stray callbacks from NotificationCenter after KeyboardObserver is deallocated. This matches the block‑based observer API’s lifecycle expectations.

ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletImportCard.swift (3)

302-312: Potential stale capture of allEnteredWords in closure.

The closure { [self] in submitFocusField() } is created once when returnHandler == nil. This captures allEnteredWords (a let constant) at creation time. When submitFocusField() later calls autocomplete.isValidWord(word: text, allWords: allEnteredWords) and autocomplete.nextFieldNumber(...), it uses the stale captured value rather than the current array.

Consider restructuring to avoid capturing view constants, or verify that stale allEnteredWords doesn't affect validation correctness.


14-56: TextFieldReturnHandler delegate forwarding looks correct.

The delegate wrapper properly uses weak var for originalDelegate to avoid retain cycles, and forwards the most commonly used UITextFieldDelegate methods. One minor omission is textFieldDidEndEditing(_:reason:) (iOS 10+), but the simpler textFieldDidEndEditing(_:) is forwarded, which should suffice for most use cases.


238-270: Validation logic before advancing looks good.

The updated submitFocusField() correctly validates the word before advancing focus: empty input stays in typing state, invalid words are marked invalid without advancing, and valid words advance to the next field. This prevents navigation to subsequent fields until the current word is valid.

ios/Cove.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved (1)

22-30: SwiftUIIntrospect dependency added correctly.

The package pin for swiftui-introspect at version 26.0.0 is properly configured with the correct repository URL and revision hash.

ios/Cove.xcodeproj/project.pbxproj (2)

10-10: SwiftUIIntrospect package integration is correct.

The project file correctly adds SwiftUIIntrospect as a remote Swift package dependency with build file references, framework linkage, and package product dependencies properly configured.

Also applies to: 58-58, 108-108, 142-142, 431-438, 458-462


309-309: Project version bump to 49 is appropriate.

Version increment from 48 to 49 reflects the changes in this PR.

Also applies to: 359-359

rust/src/fee_client.rs (1)

153-160: LGTM! Retry logic successfully migrated to backon.

The migration from tryhard to backon preserves the same retry behavior (exponential backoff with 10ms-5s delays, max 20 attempts). The implementation is clean and consistent with the broader PR migration strategy.

ios/Cove/AppManager.swift (2)

4-4: Good practice: centralized delay constant.

Extracting the delay value to a named constant improves maintainability and makes the intent clearer.


244-244: LGTM! Synchronous fiat balance update on price changes.

The added call to updateFiatBalance() ensures the wallet fiat balance is refreshed when prices change, improving UI consistency. This aligns with the broader shift to synchronous fiat balance updates in the PR.

rust/src/router.rs (1)

3-4: Verify the reduced delay values don't cause UI issues.

The PR consolidates delays to a single constant (good practice), but significantly reduces them from 500-800ms to 250ms. This 50-68% reduction might cause UI flickering, loading screen artifacts, or race conditions if the original delays were intentional buffers for async operations.

Verify through testing that:

  1. Loading screens display long enough to avoid flicker
  2. Route transitions complete cleanly without race conditions
  3. User experience remains smooth with the shorter delays

If any issues arise, consider increasing LOAD_AND_RESET_DELAY_MS or making it configurable per use case.

Also applies to: 232-232, 306-306, 310-310

rust/src/app.rs (1)

410-410: LGTM! Consistent use of centralized delay constant.

Using LOAD_AND_RESET_DELAY_MS instead of a hardcoded value improves maintainability and consistency across the codebase.

ios/Cove/WalletManager.swift (1)

40-40: LGTM! Consistent async-to-sync migration for fiat balance.

The conversion of updateFiatBalance() from async to synchronous is implemented consistently across all call sites. The new implementation uses rust.amountInFiat() which relies on cached fiat prices, eliminating the need for async/await.

Verify that rust.amountInFiat() never blocks the main thread for an extended period. If the cached prices are unavailable or the computation is complex, this could cause UI freezes. Based on the Rust code review, it appears to use value_in_currency_cached(), which should be fast, but confirming this in testing is prudent.

Also applies to: 52-52, 140-142, 146-149, 178-178

rust/src/manager/wallet_manager.rs (2)

248-254: Excellent UX improvement: early cached data notification.

Sending cached balance and transactions immediately before spawning the actor provides faster initial UI feedback. This aligns well with the broader shift to cached data patterns in the PR.


509-514: LGTM! Simplified synchronous fiat conversion API.

The new amount_in_fiat() method is cleaner than the previous async approach:

  • Returns Option<f64> instead of Result<f64, Error> (simpler error handling)
  • Uses cached prices via value_in_currency_cached() (no async overhead)
  • Clear semantics: None means no cached prices available

This is a breaking API change, but the migration appears complete across the codebase (Swift side updated to call this method).

rust/src/fiat/client.rs (5)

21-25: LGTM! Clear and well-documented constants.

The refresh interval and hard limit constants are appropriately named and documented. The values (60s background refresh, 30s hard limit) provide reasonable rate limiting for the price API.


82-89: LGTM!

Clean and simplified construction. The new_with_url helper for testing is appropriately marked as #[allow(dead_code)].


91-97: LGTM!

Clean implementation with proper Option return type for cached-only lookups. The calculation btc * price as f64 is straightforward and correct.


202-209: LGTM! Good retry configuration for initialization.

The exponential backoff with 20 retries and max delay of 5s is appropriate for app initialization where we want to be persistent in getting initial prices. The fallback to database cache on failure provides good resilience.


381-384: LGTM!

Simple and correct FFI-exported comparison function. The Arc smart pointers are correctly dereferenced for value comparison via the derived PartialEq implementation.

Comment thread rust/Cargo.toml
async-trait = "0.1.81"
futures = "0.3.30"
tryhard = "0.5"
backon = "1.3"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check backon crate information from crates.io
curl -s https://crates.io/api/v1/crates/backon | jq '.crate | {max_version, newest_version, name}'

# Also get all recent versions to see the version history
curl -s https://crates.io/api/v1/crates/backon/versions | jq '.versions[] | {num, created_at}' | head -30

Repository: bitcoinppl/cove

Length of output: 632


🏁 Script executed:

#!/bin/bash
# Check if there are any known security advisories for backon
# Using rust security advisory database via cargo-audit approach
pip install requests
python3 <<'EOF'
import requests
import json

# Try to get advisory information from rustsec database
response = requests.get("https://api.rustsec.org/osv/query?package=backon")
if response.status_code == 200:
    data = response.json()
    print("Advisories found:")
    print(json.dumps(data, indent=2))
else:
    print(f"No advisories returned (status: {response.status_code})")
EOF

Repository: bitcoinppl/cove

Length of output: 145


Update backon dependency to a more recent version.

Version 1.3 is valid but outdated—version 1.3.0 was released in November 2024. The latest available version is 1.6.0 (October 2025), with several intermediate releases (1.4.0, 1.4.1, 1.5.0, 1.5.1, 1.5.2) containing bug fixes and improvements. Consider updating to 1.6.0 or at least 1.5.x to benefit from recent updates.

🤖 Prompt for AI Agents
In rust/Cargo.toml around line 143, the backon dependency is pinned to "1.3"
which is outdated; update the version to a newer release (preferably "1.6.0", or
at minimum a 1.5.x series such as "1.5.2") by changing the version string in
Cargo.toml, then run cargo update (and cargo build/test) to refresh Cargo.lock
and verify there are no breaking changes or new warnings; if your project
requires strict semver, consider using a caret version (^1.6 or ^1.5) to allow
patch/minor updates.

Comment thread rust/src/fiat/client.rs
Comment on lines 140 to 145
if let Some(prices) = PRICES.load().as_ref() {
let now_secs = Timestamp::now().as_second() as u64;
if now_secs - prices.fetched_at < self.wait_before_new_prices {
if now_secs - prices.fetched_at < HARD_LIMIT {
return Ok(*prices);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential underflow if fetched_at is in the future due to clock skew.

If prices.fetched_at > now_secs (possible with clock drift or NTP adjustments), the subtraction will underflow since both are u64. Consider using saturating_sub or checked_sub:

         if let Some(prices) = PRICES.load().as_ref() {
             let now_secs = Timestamp::now().as_second() as u64;
-            if now_secs - prices.fetched_at < HARD_LIMIT {
+            if now_secs.saturating_sub(prices.fetched_at) < HARD_LIMIT {
                 return Ok(*prices);
             }
         }

The same pattern appears at lines 118-119 and 247-248.

🤖 Prompt for AI Agents
In rust/src/fiat/client.rs around lines 140-145 (also applies to 118-119 and
247-248), the subtraction now_secs - prices.fetched_at can underflow if
fetched_at is in the future; update the check to use a non-panicking safe
subtraction (e.g., now_secs.saturating_sub(prices.fetched_at) < HARD_LIMIT or
use checked_sub and treat a None as stale) so future timestamps don’t underflow
and stale logic remains correct, and apply the same fix at the other two
locations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant