Skip to content

Release/v1.2.2#614

Draft
praveenperera wants to merge 23 commits intomasterfrom
release/v1.2.2
Draft

Release/v1.2.2#614
praveenperera wants to merge 23 commits intomasterfrom
release/v1.2.2

Conversation

@praveenperera
Copy link
Contributor

@praveenperera praveenperera commented Mar 18, 2026

Summary by CodeRabbit

  • New Features

    • Added iOS 26 SwiftUI compatibility workaround for toolbar display issues.
    • Implemented wallet load state tracking for better initialization flow.
  • Bug Fixes

    • Fixed RGB color normalization in custom wallet colors.
    • Improved sidebar gesture handling to prevent stale drag states.
    • Enhanced wallet state transitions during initial synchronization.
    • Corrected navigation title display across multiple screens.
    • Added StrongBox security fallback for encrypted preferences.
  • Refactor

    • Updated navigation patterns from toolbar to standardized title display.
    • Improved fee rate calculation logic with policy constraints.
    • Updated HTTP client configuration for TLS verification.
  • Chores

    • Version bumped to 1.2.2.
    • Updated dependency libraries and build configurations.

The old logic added +1.1 sat/vb offsets between tiers that cascaded,
turning API values of (0.1, 0.5, 1.0) into displayed rates of
(1.2, 2.3, 3.4). Replace with a 1.0 sat/vb floor and 0.1 minimum
tier gaps so low fees correctly show (1.0, 1.1, 1.2) while high
fees pass through unchanged.
Use the API-provided minimum_fee as a floor (maxed with MIN_FEE_RATE) when computing slow/derived fee tiers so rates never fall below the API minimum. Update assertions to use to_sat_per_kwu() conversions and add unit tests covering: minimum_fee above the hardcoded floor and minimum_fee lifting the slow tier above the raw midpoint.
* Fix descriptor export for Sparrow compatibility

Export descriptors as a single BIP-389 multipath descriptor with <0;1>
notation and h-hardened notation, matching Coldcard's default format.

BDK's miniscript uses ' (apostrophe) for hardened steps, but Sparrow
normalizes to h before validating the checksum, which causes a mismatch.
The new to_normalized_string() and to_multipath_string() methods on
DescriptorExt produce h-notation strings with correctly recomputed
checksums.

* Add test for descriptor Display omitting checksum

Add a unit test that verifies the alternate Display format ({:#}) for Descriptor omits the checksum. The test parses a wpkh descriptor, compares normal and alternate formatting, and asserts that recomputing the checksum from the alternate body reproduces the normal output. This guards against upstream changes to miniscript/display behavior.
Add IPHONEOS_DEPLOYMENT_TARGET = "18.0" to rust/.cargo/config.toml to set the minimum iOS deployment target. Change reqwest feature from "rustls" to "rustls-no-provider" in rust/Cargo.toml (both direct and workspace entries) and update Cargo.lock accordingly.
Adapt cove-util to the updated rand API by importing rand::RngExt and calling rand::rng().fill(&mut chain_code) directly (removing the intermediate rng binding). Cargo.lock was also updated/regen'd to reflect dependency changes.
Replace rand::Rng with rand::RngExt for methods like random_range,
random_bool, random, and fill. Replace rand::RngCore with rand::Rng
for next_u32 in deterministic_random_draw.
…leanup

- Use loadAndReset instead of pushRoute for import word buttons to
  prevent infinite alert loop on back press
- Swap "Use as Watch Only" and "Use with Hardware Wallet" button order
  to match iOS
- Remove close() call in clearWalletManager to avoid cancelling
  in-flight reconcile messages, matching iOS behavior
- Remove dead code in iOS alertButtons catch-all branch
- Regenerate UniFFI Swift bindings
Add rustls dependency to Cargo.toml (including workspace entry) and enable the ring feature so a crypto backend is available for rustls. Initialize the ring provider at startup by calling rustls::crypto::ring::default_provider().install_default().ok() in init_on_start to satisfy reqwest builds that use rustls-no-provider.
Release version bump to 1.2.2 across platforms: Android app versionCode incremented 15->16 and versionName 1.2.1->1.2.2 (android/app/build.gradle.kts); iOS MARKETING_VERSION set to 1.2.2 and CURRENT_PROJECT_VERSION incremented 62->63 (ios/Cove.xcodeproj/project.pbxproj); Rust crate version updated to 1.2.2 in Cargo.toml and reflected in Cargo.lock (rust/). This prepares the codebase for the 1.2.2 release.
Fixes Android crash where rustls-platform-verifier panics without JNI
init, poisoning the FIAT_CLIENT/FEE_CLIENT LazyLock singletons. Build
reqwest clients with a pre-configured rustls ClientConfig using
webpki-roots for cert verification. Also move ring provider install to
App::new() so it runs before any LazyLock client initialization.
Replace onScrollGeometryChange (which depends on contentInsets.top that
fluctuates during iOS 26 Liquid Glass transitions) with onGeometryChange
on the header view, measuring position in stable global coordinates.
Add 10pt hysteresis dead zone to prevent threshold oscillation.
The simultaneousGesture on SidebarView could set isDragging=true from
a slight finger movement during a button tap, causing the onChange
handler to skip the close animation. Also guard onDragEnded against
stale gesture data reopening a programmatically closed sidebar.
SwiftUI has a bug where ToolbarPlacementEnvironment.updateValue() enters
an infinite loop during _UINavigationParallaxTransition when a .principal
toolbar item exists at accessibilityExtraExtraLarge dynamic type size.

Bypass .principal entirely by hosting the SwiftUI title content inside a
UIHostingController assigned to UIKit's navigationItem.titleView. This
preserves the centered title appearance while avoiding the SwiftUI toolbar
placement system.
Use .navigationTitle with .toolbarColorScheme(.dark) for screens with
plain static titles on dark backgrounds. Add .navigationTitleView { }
View extension for screens needing custom title content (shield icon,
context menu). Document the iOS 26 .principal toolbar bug in CLAUDE.md.
Switch ML Kit barcode scanning from the play-services thin client to the
bundled variant (com.google.mlkit:barcode-scanning) which embeds the ML
model in the APK and has zero GMS runtime dependency.

Add StrongBox fallback in KeychainAccessor: check device capability via
PackageManager before requesting StrongBox, and retry with TEE-only key
if StrongBox-backed initialization fails. If StrongBox was not used, the
exception propagates unchanged (fail-closed).
Increment CURRENT_PROJECT_VERSION from 65 to 66 in ios/Cove.xcodeproj/project.pbxproj for the affected build configurations to update the app build number.
@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

This PR delivers version 1.2.2 across iOS, Android, and Rust with iOS 26 SwiftUI compatibility fixes, wallet load state tracking from Rust, updated dependencies, fee client logic refinement, and Android ML Kit barcode scanning dependency migration.

Changes

Cohort / File(s) Summary
iOS 26 SwiftUI Bug Workarounds
ARCHITECTURE.md, ios/Cove/Views/NavigationTitleView.swift, ios/Cove/Flows/NewWalletFlow/HotWallet/...Screen.swift, ios/Cove/Flows/NewWalletFlow/NewWalletSelectScreen.swift, ios/Cove/Flows/SelectedWalletFlow/...Screen.swift
Documents and implements workaround for iOS 26 toolbar principal placement bug affecting large accessibility font sizes; replaces ToolbarItem(placement: .principal) with navigationTitle modifiers and new UIKit-backed NavigationTitleView helper across 7 screen files to use proper navigation bar title rendering instead.
Wallet Load State Tracking
android/app/src/main/java/org/bitcoinppl/cove/WalletManager.kt, ios/Cove/WalletManager.swift, android/app/src/main/java/org/bitcoinppl/cove_core/cove.kt, rust/src/manager/wallet_manager.rs, rust/src/manager/wallet_manager/actor.rs
Introduces initial load state propagation from Rust to native layers; adds WalletLoadState field to RustWalletManager with initialLoadState() getter, new FFI method binding, and refined state machine logic for LOADING/SCANNING/LOADED transitions during wallet initialization and scan operations.
Version & Build Configuration Updates
android/app/build.gradle.kts, ios/Cove.xcodeproj/project.pbxproj, rust/Cargo.toml, rust/.cargo/config.toml
Bumps version from 1.2.1 to 1.2.2 across Android (versionCode 15→17), iOS (CURRENT_PROJECT_VERSION 62→66, MARKETING_VERSION), and Rust; adds iOS deployment target 18.0 to Cargo config; updates multiple Rust dependencies including bdk crates, reqwest, rustls, and database libraries.
Android Barcode Scanning & Security
android/app/build.gradle.kts, android/app/proguard-rules.pro, android/app/src/main/java/org/bitcoinppl/cove/Security.kt
Migrates ML Kit barcode scanning from play-services-mlkit to standalone google.mlkit dependency; adds ProGuard rule for ML Kit reflection-based component discovery; refactors EncryptedSharedPreferences initialization to support optional StrongBox backing with fallback to TEE-only configuration.
Android State & Navigation Logic
android/app/src/main/java/org/bitcoinppl/cove/AppManager.kt, android/app/src/main/java/org/bitcoinppl/cove/MainActivity.kt
Simplifies clearWalletManager() by removing try-catch; refactors HotWalletKeyMissing dialog from pushRoute to loadAndReset navigation and reintroduces "Use as Watch Only" button as separate action triggering ConfirmWatchOnly state transition.
Rust HTTP & TLS Infrastructure
rust/src/http_client.rs, rust/src/lib.rs, rust/src/app.rs, rust/src/fee_client.rs
Introduces new http_client module with new_client() function using webpki-roots for TLS verification; installs rustls default provider in App::new; refactors FeeClient to use centralized client factory; rewrites FeeRateOptions computation with explicit POLICY_MIN_FEE_RATE and TIER_GAP constants and adds comprehensive fee rate calculation tests.
Rust Descriptor Export & Key Management
rust/crates/cove-bdk/src/descriptor_ext.rs, rust/src/keys.rs, rust/src/manager/wallet_manager.rs, rust/src/fiat/client.rs
Adds NotMatchingPair error variant and three new DescriptorExt methods (to_normalized_string, to_multipath_string, to_export_string) for descriptor formatting and checksum handling; updates keys.rs error conversion and adds extensive descriptor roundtrip/multipath tests; updates FiatClient to use centralized HTTP client.
Rand Trait Refactoring
rust/crates/cove-bdk/src/coin_selection/deterministic_random_draw.rs, rust/crates/cove-bip39/src/lib.rs, rust/crates/cove-types/src/transaction/sent_and_received.rs, rust/crates/cove-util/src/lib.rs, rust/src/database/historical_price/record.rs, rust/src/transaction/ffi.rs, rust/src/wallet/metadata.rs, rust/src/manager/wallet_manager/actor.rs, rust/src/mnemonic/number_of_bip39_words.rs
Consistently replaces Rng trait import with RngExt across 9 Rust files to align with rand crate API updates; no functional behavior changes, only trait resolution updates for random extension methods.
iOS Navigation & Color Updates
ios/Cove/Extention/WalletColor+Ext.swift, ios/Cove/Flows/SelectedWalletFlow/MoreInfoPopover.swift, ios/Cove/Flows/SelectedWalletFlow/WalletBalanceHeaderView.swift, ios/Cove/SidebarContainer.swift
Fixes RGB normalization in customToColor (divide by 255); changes MoreInfoPopover tint to dynamic label color; increases WalletBalanceHeaderView top padding from 35 to 75; adds sidebar drag gesture safeguards (clear isDragging on no-motion stale drags and programmatic sidebar close).
iOS Alert & Route Handling
ios/Cove/CoveApp.swift
Defers main thread route identity reset via DispatchQueue.main.async to avoid UIKit transition animation loops; removes hotWalletKeyMissing/confirmWatchOnly cases from alertButtons switch, consolidating alert handling structure.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

android, ios, rust, dependencies


🐰 A hop, skip, and state machine away,
From toolbar bugs we now do sway,
Load states cascade, webpki shines bright,
Version two-dot-two takes flight,
Hopping onward to the next delight! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title "Release/v1.2.2" accurately reflects the primary purpose of this changeset, which is a version release bump from 1.2.1 to 1.2.2 across multiple platforms and dependencies.

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

Important

Merge conflicts detected (Beta)

  • Resolve merge conflict in branch release/v1.2.2
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/v1.2.2
📝 Coding Plan
  • Generate coding plan for human review comments

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

❤️ Share

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

Tip

CodeRabbit can approve the review once all CodeRabbit's comments are resolved.

Enable the reviews.request_changes_workflow setting to automatically approve the review once all CodeRabbit's comments are resolved.

@praveenperera praveenperera marked this pull request as draft March 18, 2026 01:57
@greptile-apps
Copy link

greptile-apps bot commented Mar 18, 2026

Greptile Summary

This is the v1.2.2 release PR, bundling a significant set of bug fixes, stability improvements, and dependency upgrades across Rust, iOS, and Android. The changes are well-scoped and clearly motivated by real user-facing issues.

Key bug fixes:

  • iOS color rendering: customToColor was passing raw 0–255 UInt8 values to SwiftUI's Color(red:green:blue:) which expects 0.0–1.0 doubles — custom wallet colors were entirely wrong and are now fixed.
  • iOS 26 CPU freeze: SwiftUI's .principal toolbar placement enters an infinite loop at large accessibility font sizes during back-navigation. All screens migrated to either .navigationTitle (static text) or the new UIKit-bridged .navigationTitleView {} modifier.
  • Descriptor export checksum mismatch: BDK uses apostrophe (') notation for hardened paths; Sparrow/Electrum validate checksums using h notation. New to_normalized_string / to_multipath_string / to_export_string helpers on DescriptorExt fix the export and are backed by thorough unit tests.
  • Fee tier inflation at low fees: The tier gap was 1.1 sat/vb, artificially inflating displayed fees when the network was cheap; reduced to 0.1 sat/vb with a policy floor of 1.0 sat/vb.
  • Android StrongBox fallback: Devices that advertise StrongBox but fail at runtime now fall back gracefully to TEE-backed EncryptedSharedPreferences.
  • Android MLKit: Switched to the standalone bundled variant so the app works on devices without Google Play Services.

Stability improvements:

  • UniFFI vtable pointers now heap-allocated with explicit process lifetime, fixing a potential dangling-pointer crash when Rust invoked Swift callbacks.
  • Initial wallet load state seeded from Rust cache on both iOS and Android, eliminating a spurious loading spinner on re-entry to a previously synced wallet.
  • Sidebar gesture state cleaned up when the sidebar is closed programmatically.
  • DispatchQueue.main.async deferral for view identity reset to avoid a ChildEnvironment propagation loop during UIKit's parallax transition.

Notable dependency updates:

  • reqwest 0.12 → 0.13 with explicit rustls + webpki-roots TLS configuration (avoids JNI requirements on Android)
  • rand 0.9 → 0.10 (RngExt API migration across all call sites)
  • bdk_wallet 2.0 → 2.3, bip39 2.0 → 2.2
  • rusqlite added with bundled-sqlcipher-vendored-openssl feature (increases compile time and binary size)

Confidence Score: 4/5

  • PR is safe to merge with one unconfirmed behavioral question around iOS alert dismissal for removed cases.
  • The release bundles well-motivated bug fixes (color rendering, descriptor checksums, fee tiers, iOS 26 CPU freeze) with good test coverage in Rust. Dependency upgrades are intentional and the UniFFI vtable fix addresses a genuine memory-safety concern. The one open question is whether .hotWalletKeyMissing and .confirmWatchOnly alert states are still reachable on iOS after their removal from the OK-button handler group — if they are, users would encounter undismissable alerts. Everything else reviewed is clean.
  • ios/Cove/CoveApp.swift (alert state case removal), ios/Cove/Views/NavigationTitleView.swift (child VC lifecycle), android/app/build.gradle.kts (versionCode gap)

Important Files Changed

Filename Overview
android/app/proguard-rules.pro Broad wildcard keep rule added for bundled MLKit to preserve ComponentDiscovery reflection; prevents ProGuard from shrinking any class under com.google.mlkit.
android/app/src/main/java/org/bitcoinppl/cove/Security.kt StrongBox availability check added with a graceful TEE fallback, fixing devices where StrongBox was advertised but failed at runtime during EncryptedSharedPreferences creation.
android/app/src/main/java/org/bitcoinppl/cove/WalletManager.kt Initial load state now seeded from Rust cache via initialLoadState(), preventing unnecessary loading spinner. StartedInitialFullScan handler improved to preserve existing transaction list during re-scan.
ios/Cove/Extention/WalletColor+Ext.swift Critical bug fixed: customToColor was passing raw 0–255 UInt8 values directly to SwiftUI's Color(red:green:blue:) which expects 0.0–1.0 doubles, causing completely wrong custom colors.
ios/Cove/Views/NavigationTitleView.swift New file implementing a UIKit-based navigation title workaround (navigationTitleView { }) to bypass the iOS 26 SwiftUI infinite-loop bug in .principal toolbar placement at large accessibility font sizes.
ios/Cove/CoveApp.swift Route change handler defers id = UUID() to next run-loop tick via DispatchQueue.main.async to avoid ChildEnvironment propagation loop during parallax transition. .hotWalletKeyMissing and .confirmWatchOnly removed from the OK-button alert case group.
ios/CoveCore/Sources/CoveCore/generated/cove.swift UniFFI vtable lifetime fixed: vtables now heap-allocated as stable UnsafePointer with process lifetime instead of stack-frame array pointers that could become dangling. initialLoadState() method added to RustWalletManagerProtocol.
rust/src/http_client.rs New module building a reqwest::Client with explicit webpki-roots TLS configuration, bypassing rustls-platform-verifier which requires JNI initialization on Android.
rust/src/fee_client.rs Fee tier gap reduced from 1.1 to 0.1 sat/vb (as TIER_GAP), fixing artificially inflated tiers during low-fee periods. POLICY_MIN_FEE_RATE floor of 1.0 sat/vb enforced. Comprehensive tests added.
rust/crates/cove-bdk/src/descriptor_ext.rs New to_normalized_string, to_multipath_string, and to_export_string methods that normalize BDK's apostrophe-notation to h-notation and recompute checksums, fixing descriptor export to Sparrow/Electrum.
rust/src/manager/wallet_manager.rs Added initial_load_state field seeded from cached transactions at construction time. Public initial_load_state() method exposed via #[uniffi::method]. Descriptor export updated to use DescriptorExt::to_export_string for proper normalization.
rust/Cargo.toml Major dependency updates: rand 0.9→0.10, reqwest 0.12→0.13, bdk_wallet 2.0→2.3, bip39 2.0→2.2. New deps: hmac, hkdf, rusqlite (bundled SQLCipher), rustls (ring), webpki-roots. Explicit TLS provider configuration added.
ios/Cove/WalletManager.swift Initial loadState now seeded from rust.initialLoadState() instead of defaulting to .loading, preventing unnecessary loading spinner for already-cached wallets.

Sequence Diagram

sequenceDiagram
    participant UI as iOS/Android UI
    participant KWM as Kotlin/Swift WalletManager
    participant RWM as RustWalletManager (Rust)
    participant DB as SQLite/redb Cache

    UI->>KWM: init(walletId)
    KWM->>RWM: RustWalletManager(id)
    RWM->>DB: load cached transactions
    DB-->>RWM: Vec<Transaction>
    RWM->>RWM: set initial_load_state<br/>(Loading if empty, Scanning(txns) if cached)
    KWM->>RWM: initialLoadState()
    RWM-->>KWM: WalletLoadState (Loading | Scanning(txns) | Loaded(txns))
    KWM->>UI: render with cached state (no spinner flash)

    Note over RWM: Background sync begins
    RWM-->>KWM: reconcile(StartedInitialFullScan)
    KWM->>KWM: if SCANNING & txns non-empty → keep SCANNING<br/>if LOADED → transition to SCANNING(txns)<br/>else → LOADING
    KWM->>UI: update UI state

    RWM-->>KWM: reconcile(AvailableTransactions)
    KWM->>UI: render final LOADED state
Loading

Comments Outside Diff (5)

  1. android/app/build.gradle.kts, line 29-30 (link)

    P2 versionCode skipped from 15 to 17

    The versionCode jumped from 15 to 17, skipping 16. Was there an intermediate internal/hotfix release that consumed code 16? If not, it's worth aligning the iOS CURRENT_PROJECT_VERSION (66, up from 62 — also +4) and Android codes so version tracking stays consistent.

  2. android/app/proguard-rules.pro, line 52 (link)

    P2 Broad MLKit ProGuard keep rule may inflate APK size

    -keep class com.google.mlkit.** { *; } preserves every class in the MLKit library tree, preventing R8 from shrinking unused code. The bundled MLKit variant only needs the registrar constructor to be retained for ComponentDiscovery. A more targeted rule limits the impact on APK size:

    -keep class com.google.mlkit.vision.barcode.** { *; }
    -keepnames class com.google.mlkit.** implements com.google.android.gms.common.internal.GmsClient
    

    This is a style suggestion — the current rule is functionally correct.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

  3. ios/Cove/CoveApp.swift, line 319-325 (link)

    P1 .hotWalletKeyMissing and .confirmWatchOnly removed from OK-button case group

    These two alert states were removed from the case block that renders a dismissal button. If they are still reachable AppAlertState variants and are presented as alerts elsewhere in the view, removing them here would leave the user staring at an alert with no buttons — a UI deadlock.

    Could you confirm that both cases are either:

    1. Handled in a separate case block later in the same alertState switch with their own buttons, or
    2. No longer set as alertState anywhere in the codebase (i.e. can be treated as dead code)?

    If they're dead code, it would be cleaner to remove the enum variants entirely to prevent future accidental use.

  4. ios/Cove/Views/NavigationTitleView.swift, line 746-758 (link)

    P2 hostingController not added as a child view controller

    The UIHostingController is created and its view is assigned to navItem.titleView, but addChild(hosting) / hosting.didMove(toParent: self) are never called. Without these calls the hosting controller won't receive viewWillAppear/viewDidAppear events, and SwiftUI environment values (color scheme, dynamic type size, etc.) are injected only through the UIKit responder chain rather than the proper UIHostingController hierarchy.

    For a simple static wallet-name label this is unlikely to cause visible issues, but if the title content ever uses .onAppear, .task, or environment-dependent rendering, the behavior may be unpredictable. Consider adding:

    addChild(hosting)
    view.addSubview(hosting.view) // not strictly needed for titleView, but keeps lifecycle correct
    hosting.didMove(toParent: self)

    …and the corresponding willMove(toParent: nil) / removeFromParent() in clearTitle().

  5. rust/Cargo.toml, line 3074 (link)

    P2 bundled-sqlcipher-vendored-openssl significantly increases binary size and compile time

    The feature bundled-sqlcipher-vendored-openssl compiles OpenSSL from source as part of the build. This is visible in the lock file, which now includes openssl-src, openssl-sys, openssl-probe, and related dependencies. Compile times will increase notably, especially on CI.

    If the goal is simply an encrypted SQLite for BDK's wallet persistence, consider bundled-sqlcipher (uses the system or a lighter bundled Sqlcipher without vendored OpenSSL) or bundled (plain SQLite without encryption) if BDK's own encryption layer is sufficient. This is worth revisiting if build times become an issue.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Last reviewed commit: "Update build.gradle...."

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (2)
rust/src/wallet/metadata.rs (1)

51-51: ⚠️ Potential issue | 🟡 Minor

Typo in comment.

Minor typo: "transactions i the transaction list" should be "transactions in the transaction list".

📝 Suggested fix
-    /// Show labels for transactions i the transaction list
+    /// Show labels for transactions in the transaction list

As per coding guidelines: rust/**/*.rs: Identify spelling mistakes in comments, string literals, and documentation.

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

In `@rust/src/wallet/metadata.rs` at line 51, Fix the typo in the doc comment in
metadata.rs: change the comment string "Show labels for transactions i the
transaction list" to "Show labels for transactions in the transaction list" so
the documentation/readme for the related item (the doc comment above the wallet
metadata declaration) reads correctly.
rust/src/keys.rs (1)

337-344: ⚠️ Potential issue | 🔴 Critical

Fix moved-value usage in From conversion match arm—proposed fix is incorrect.

At Line 343, error is matched by value, making the subsequent error.to_string() call invalid. The proposed fix E::NotMatchingPair.to_string() also won't compile since NotMatchingPair is a unit variant without associated data.

Use one of these approaches instead:

  1. Match by reference: Change match error to match &error to borrow rather than consume.
  2. Hardcode the message: Since NotMatchingPair is a unit variant with a fixed error message, use:
    E::NotMatchingPair => Self::UnsupportedDescriptor("descriptors are not a matching external/internal pair".to_string()),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/src/keys.rs` around lines 337 - 344, The From conversion currently
consumes `error` and then tries to reuse it; change `match error` to `match
&error` to borrow the enum and avoid moved-value issues, adjust the arms to
clone the borrowed strings for `E::UnsupportedDescriptor(s)` and
`E::UnsupportedDescriptorType(s)` (e.g.
`Self::UnsupportedDescriptor(s.clone())`), keep `E::NoOrigin => Self::NoOrigin`,
and for `E::NotMatchingPair` return a fixed message (e.g.
`Self::UnsupportedDescriptor("descriptors are not a matching external/internal
pair".to_string())`) instead of calling `to_string()` on a moved unit variant;
these changes should be applied inside the `fn from(error:
cove_bdk::descriptor_ext::Error) -> Self` match block referencing
`cove_bdk::descriptor_ext::Error` and its variants.
🧹 Nitpick comments (3)
ios/Cove/Flows/SelectedWalletFlow/SecretWordsScreen.swift (1)

110-112: Avoid showing the same screen title twice.

The body already renders a prominent Recovery Words heading, so Lines 110-112 repeat the same label in the navigation bar and burn vertical space on an already dense view.

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

In `@ios/Cove/Flows/SelectedWalletFlow/SecretWordsScreen.swift` around lines 110 -
112, Remove the duplicate navigation bar title by deleting the
.navigationTitle("Recovery Words") and .navigationBarTitleDisplayMode(.inline)
modifiers in SecretWordsScreen so the prominent in-body "Recovery Words" heading
remains the primary label; you may keep .toolbarColorScheme(.dark, for:
.navigationBar) if you still want a dark nav appearance, but do not render the
same string in both the view body and the navigation bar.
ios/Cove/Flows/NewWalletFlow/NewWalletSelectScreen.swift (1)

115-115: Drop the duplicated inline title-display modifier.

navigationBarTitleDisplayMode(.inline) appears twice in the same chain; keeping one is clearer.

Suggested cleanup
-        .navigationBarTitleDisplayMode(.inline)
         .fileImporter(
             isPresented: $isImporting,
             allowedContentTypes: [.plainText, .json]
@@
         .background(Color.midnightBlue)
         .navigationTitle("Add New Wallet")
         .navigationBarTitleDisplayMode(.inline)
         .toolbarColorScheme(.dark, for: .navigationBar)

As per coding guidelines, "Review SwiftUI view code for proper layout, best practices".

Also applies to: 150-152

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

In `@ios/Cove/Flows/NewWalletFlow/NewWalletSelectScreen.swift` at line 115, Remove
the duplicated navigationBarTitleDisplayMode(.inline) modifier in the SwiftUI
view chain for NewWalletSelectScreen (the body of struct NewWalletSelectScreen)
— leave a single .navigationBarTitleDisplayMode(.inline) and delete the extra
occurrence (also remove the duplicate at the other occurrence around lines noted
in the review), ensuring the view modifier chain contains only one inline
title-display modifier.
ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletSelectScreen.swift (1)

95-95: Remove duplicate navigationBarTitleDisplayMode modifier.

.navigationBarTitleDisplayMode(.inline) is applied twice; keeping one improves clarity with no behavior change.

Suggested cleanup
-        .navigationBarTitleDisplayMode(.inline)
         .frame(maxHeight: .infinity)
         .background(
             Image(.newWalletPattern)
@@
         .background(Color.midnightBlue)
         .navigationTitle("Add New Wallet")
         .navigationBarTitleDisplayMode(.inline)
         .toolbarColorScheme(.dark, for: .navigationBar)

As per coding guidelines, "Review SwiftUI view code for proper layout, best practices".

Also applies to: 106-108

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

In `@ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletSelectScreen.swift` at line
95, HotWalletSelectScreen contains a duplicated SwiftUI modifier
navigationBarTitleDisplayMode(.inline); remove the redundant occurrence so the
view only applies navigationBarTitleDisplayMode once (keep the single modifier
on the HotWalletSelectScreen view body), ensuring you update the
HotWalletSelectScreen view to eliminate the duplicate instance (also remove the
duplicate around the block referenced in the 106-108 area) to improve clarity
without changing behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@android/app/src/main/java/org/bitcoinppl/cove/Security.kt`:
- Around line 20-28: The current catch block around createEncryptedPrefs is too
broad and should only handle security-related failures from StrongBox; replace
the catch of Exception with catching GeneralSecurityException (which covers
StrongBoxUnavailableException) in the code path that uses hasStrongBox and
createEncryptedPrefs so only crypto/hardware unavailability triggers the TEE
fallback; keep rethrowing other exceptions when useStrongBox is false and remove
any message-based detection logic so failures unrelated to StrongBox aren’t
silently masked (refer to hasStrongBox, createEncryptedPrefs,
sharedPreferences/KeychainAccessor to locate the block).

In `@android/app/src/main/java/org/bitcoinppl/cove/WalletManager.kt`:
- Around line 105-111: The constructor currently subscribes to updates
(listenForUpdates(this)) before setting WalletManager.loadState from
rustManager.initialLoadState(), allowing reconcile updates to overwrite fresher
state; fix by initializing loadState from rustManager.initialLoadState() before
calling listenForUpdates(this) (or otherwise deferring subscription until after
the when-block that maps org.bitcoinppl.cove_core.WalletLoadState to
WalletLoadState), ensuring the mapping logic that sets
WalletLoadState.LOADING/SCANNING/LOADED runs prior to any reconcile callbacks.

In `@ios/Cove/Flows/SelectedWalletFlow/SelectedWalletScreen.swift`:
- Around line 510-512: The onAppear handler in SelectedWalletScreen.swift
currently sets shouldShowNavBar = false but does not reset the global header
flag app.isPastHeader, causing a transient toolbar mismatch; update the
.onAppear block (where shouldShowNavBar is changed) to also set app.isPastHeader
= false so the global header state is synchronized with the local navbar state
(i.e., set both shouldShowNavBar = false and app.isPastHeader = false in the
same onAppear).

In `@ios/Cove/Views/NavigationTitleView.swift`:
- Around line 61-65: The dismantleUIViewController implementation
unconditionally calls vc.clearTitle(), which can remove a title view installed
by a newer helper; change dismantleUIViewController in NavigationTitleView.swift
so it only clears the navigation title if this helper still owns it—e.g., have
NavigationTitleViewController/clearTitle check ownership by comparing the stored
titleView reference (or an ownership token/id kept on the controller) against
the navigationItem.titleView (or token) and only remove/clear when they match;
update any place that sets titleView to also set that ownership token so
dismantleUIViewController safely no-ops when another helper replaced the title.

In `@rust/src/app.rs`:
- Line 87: The call to
rustls::crypto::ring::default_provider().install_default() currently uses .ok(),
which drops errors silently; change this to call .expect("failed to install
default crypto provider") so the program panics with a clear message on
failure—update the expression where install_default() is invoked to use expect
with that message to surface configuration errors.

In `@rust/src/http_client.rs`:
- Around line 10-13: The HTTP client initializer currently panics via .expect()
and lacks timeouts; change new_client() to return Result<reqwest::Client,
Box<dyn std::error::Error>> (or an appropriate error type) instead of calling
.expect(), and configure explicit timeouts on the builder (e.g., connect_timeout
and overall timeout) before build; update all call sites that construct the
client (the constructors/factories for FeeClient and FiatClient) to propagate or
handle the Result (return Result from those constructors or handle errors) so
the code no longer panics on builder failure and will time out on
slow/unresponsive servers. Ensure you still use
tls_backend_preconfigured(tls_config) on reqwest::ClientBuilder and
map/propagate build errors rather than unwrapping.

---

Outside diff comments:
In `@rust/src/keys.rs`:
- Around line 337-344: The From conversion currently consumes `error` and then
tries to reuse it; change `match error` to `match &error` to borrow the enum and
avoid moved-value issues, adjust the arms to clone the borrowed strings for
`E::UnsupportedDescriptor(s)` and `E::UnsupportedDescriptorType(s)` (e.g.
`Self::UnsupportedDescriptor(s.clone())`), keep `E::NoOrigin => Self::NoOrigin`,
and for `E::NotMatchingPair` return a fixed message (e.g.
`Self::UnsupportedDescriptor("descriptors are not a matching external/internal
pair".to_string())`) instead of calling `to_string()` on a moved unit variant;
these changes should be applied inside the `fn from(error:
cove_bdk::descriptor_ext::Error) -> Self` match block referencing
`cove_bdk::descriptor_ext::Error` and its variants.

In `@rust/src/wallet/metadata.rs`:
- Line 51: Fix the typo in the doc comment in metadata.rs: change the comment
string "Show labels for transactions i the transaction list" to "Show labels for
transactions in the transaction list" so the documentation/readme for the
related item (the doc comment above the wallet metadata declaration) reads
correctly.

---

Nitpick comments:
In `@ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletSelectScreen.swift`:
- Line 95: HotWalletSelectScreen contains a duplicated SwiftUI modifier
navigationBarTitleDisplayMode(.inline); remove the redundant occurrence so the
view only applies navigationBarTitleDisplayMode once (keep the single modifier
on the HotWalletSelectScreen view body), ensuring you update the
HotWalletSelectScreen view to eliminate the duplicate instance (also remove the
duplicate around the block referenced in the 106-108 area) to improve clarity
without changing behavior.

In `@ios/Cove/Flows/NewWalletFlow/NewWalletSelectScreen.swift`:
- Line 115: Remove the duplicated navigationBarTitleDisplayMode(.inline)
modifier in the SwiftUI view chain for NewWalletSelectScreen (the body of struct
NewWalletSelectScreen) — leave a single .navigationBarTitleDisplayMode(.inline)
and delete the extra occurrence (also remove the duplicate at the other
occurrence around lines noted in the review), ensuring the view modifier chain
contains only one inline title-display modifier.

In `@ios/Cove/Flows/SelectedWalletFlow/SecretWordsScreen.swift`:
- Around line 110-112: Remove the duplicate navigation bar title by deleting the
.navigationTitle("Recovery Words") and .navigationBarTitleDisplayMode(.inline)
modifiers in SecretWordsScreen so the prominent in-body "Recovery Words" heading
remains the primary label; you may keep .toolbarColorScheme(.dark, for:
.navigationBar) if you still want a dark nav appearance, but do not render the
same string in both the view body and the navigation bar.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a53771d0-dab7-4639-8da9-2811f40a1ef0

📥 Commits

Reviewing files that changed from the base of the PR and between 79e4a30 and 7956555.

⛔ Files ignored due to path filters (3)
  • ios/CoveCore/Sources/CoveCore/generated/cove.swift is excluded by !**/generated/**
  • ios/CoveCore/Sources/CoveCore/generated/cove_device.swift is excluded by !**/generated/**
  • rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (42)
  • ARCHITECTURE.md
  • android/app/build.gradle.kts
  • android/app/proguard-rules.pro
  • android/app/src/main/java/org/bitcoinppl/cove/AppManager.kt
  • android/app/src/main/java/org/bitcoinppl/cove/MainActivity.kt
  • android/app/src/main/java/org/bitcoinppl/cove/Security.kt
  • android/app/src/main/java/org/bitcoinppl/cove/WalletManager.kt
  • android/app/src/main/java/org/bitcoinppl/cove_core/cove.kt
  • ios/Cove.xcodeproj/project.pbxproj
  • ios/Cove/CoveApp.swift
  • ios/Cove/Extention/WalletColor+Ext.swift
  • ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletCreateScreen.swift
  • ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletImportScreen.swift
  • ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletSelectScreen.swift
  • ios/Cove/Flows/NewWalletFlow/HotWallet/VerifyWords/VerifyWordsScreen.swift
  • ios/Cove/Flows/NewWalletFlow/NewWalletSelectScreen.swift
  • ios/Cove/Flows/SelectedWalletFlow/MoreInfoPopover.swift
  • ios/Cove/Flows/SelectedWalletFlow/SecretWordsScreen.swift
  • ios/Cove/Flows/SelectedWalletFlow/SelectedWalletScreen.swift
  • ios/Cove/Flows/SelectedWalletFlow/WalletBalanceHeaderView.swift
  • ios/Cove/SidebarContainer.swift
  • ios/Cove/Views/NavigationTitleView.swift
  • ios/Cove/WalletManager.swift
  • rust/.cargo/config.toml
  • rust/Cargo.toml
  • rust/crates/cove-bdk/src/coin_selection/deterministic_random_draw.rs
  • rust/crates/cove-bdk/src/descriptor_ext.rs
  • rust/crates/cove-bip39/src/lib.rs
  • rust/crates/cove-types/src/transaction/sent_and_received.rs
  • rust/crates/cove-util/src/lib.rs
  • rust/src/app.rs
  • rust/src/database/historical_price/record.rs
  • rust/src/fee_client.rs
  • rust/src/fiat/client.rs
  • rust/src/http_client.rs
  • rust/src/keys.rs
  • rust/src/lib.rs
  • rust/src/manager/wallet_manager.rs
  • rust/src/manager/wallet_manager/actor.rs
  • rust/src/mnemonic/number_of_bip39_words.rs
  • rust/src/transaction/ffi.rs
  • rust/src/wallet/metadata.rs
💤 Files with no reviewable changes (1)
  • android/app/src/main/java/org/bitcoinppl/cove/AppManager.kt

Comment on lines +20 to +28
val useStrongBox = hasStrongBox(context)
sharedPreferences =
EncryptedSharedPreferences.create(
context,
"cove_secure_storage",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
try {
createEncryptedPrefs(context, requestStrongBox = useStrongBox)
} catch (e: Exception) {
if (!useStrongBox) throw e // not a StrongBox issue, no fallback available
Log.w("KeychainAccessor", "StrongBox-backed prefs failed, falling back to TEE", e)
createEncryptedPrefs(context, requestStrongBox = false)
}
Copy link

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

🌐 Web query:

For AndroidX Security Crypto MasterKey.Builder.setRequestStrongBoxBacked(true), which exception types explicitly indicate StrongBox unavailability, and what fallback handling pattern is recommended?

💡 Result:

The primary exception type that explicitly indicates StrongBox unavailability when using MasterKey.Builder.setRequestStrongBoxBacked(true) is StrongBoxUnavailableException (android.security.keystore.StrongBoxUnavailableException). This is thrown by the underlying Android Keystore system during key generation (via KeyGenerator.generateKey) when StrongBox hardware is requested but unavailable or unsupported for the key parameters. MasterKey.Builder itself does not throw this exception directly during the builder.setRequestStrongBoxBacked(true) call or build. Instead: - It checks for PackageManager.FEATURE_STRONGBOX_KEYSTORE availability before setting KeyGenParameterSpec.Builder.setIsStrongBoxBacked(true). - The exception occurs later, during MasterKey.build when MasterKeys.getOrCreate calls KeyGenerator.generateKey with the StrongBox-backed spec, if the Keystore cannot fulfill the request. Other potential exceptions during key generation include GeneralSecurityException (superclass), ProviderException, or KeyStoreException, but StrongBoxUnavailableException is the specific indicator for StrongBox unavailability. Recommended fallback handling pattern: 1. Check device support proactively: context.getPackageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE). 2. Attempt StrongBox-backed key creation in try-catch. 3. On StrongBoxUnavailableException (or catch GeneralSecurityException and check instanceof), fallback to non-StrongBox (TEE-backed or software) by recreating the MasterKey without setRequestStrongBoxBacked(true). 4. Verify key properties post-creation using MasterKey.isStrongBoxBacked or KeyInfo.getSecurityLevel == STRONGBOX. Example pattern: boolean hasStrongBox = context.getPackageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE); MasterKey masterKey; try { MasterKey.Builder builder = new MasterKey.Builder(context).setRequestStrongBoxBacked(hasStrongBox); masterKey = builder.build; } catch (StrongBoxUnavailableException | GeneralSecurityException e) { // Fallback to TEE/software masterKey = new MasterKey.Builder(context).build; // defaults to non-StrongBox } if (masterKey.isStrongBoxBacked) { // Use StrongBox key } else { // Handle TEE fallback, log, or degrade gracefully } Note: androidx.security:crypto:MasterKey is deprecated; migrate to direct javax.crypto.KeyGenerator + AndroidKeyStore for better control and non-deprecated StrongBox handling.

Citations:


🏁 Script executed:

fd -t f "Security.kt" android/

Repository: bitcoinppl/cove

Length of output: 116


🏁 Script executed:

cat -n android/app/src/main/java/org/bitcoinppl/cove/Security.kt

Repository: bitcoinppl/cove

Length of output: 2981


🏁 Script executed:

rg -t kotlin "StrongBoxUnavailableException|ProviderException" android/

Repository: bitcoinppl/cove

Length of output: 41


🏁 Script executed:

rg -t kotlin "android.security.keystore|StrongBoxUnavailableException" --type kotlin android/

Repository: bitcoinppl/cove

Length of output: 41


🏁 Script executed:

python3 << 'EOF'
# Check the Android Keystore exception hierarchy
# ProviderException is a general cryptography exception
# StrongBoxUnavailableException is more specific

print("Android Exception Hierarchy for StrongBox:")
print("- StrongBoxUnavailableException (extends GeneralSecurityException)")
print("  └─ Thrown during MasterKey.build() -> KeyGenerator.generateKey()")
print()
print("- GeneralSecurityException (broader)")
print("  ├─ StrongBoxUnavailableException")
print("  ├─ KeyStoreException")
print("  └─ Others...")
print()
print("- ProviderException")
print("  └─ A generic exception from security providers (may or may not be related to StrongBox)")
print()
print("Recommended catch order:")
print("1. Specific: StrongBoxUnavailableException (if available in androidx.security)")
print("2. Broader: GeneralSecurityException (covers StrongBox and other key generation errors)")
print("3. NOT recommended: Catch Exception or rely on message string matching")
EOF

Repository: bitcoinppl/cove

Length of output: 721


Catch the correct exception for StrongBox unavailability, not ProviderException

Line 24 catches Exception, allowing any failure—not just StrongBox-specific ones—to trigger fallback, potentially masking security initialization errors. However, the proposed fix is incorrect: catch GeneralSecurityException instead of ProviderException and do not rely on message string matching. Per Android security guidelines, StrongBoxUnavailableException (which extends GeneralSecurityException) is thrown when StrongBox hardware is unavailable during key generation. Catching the broader GeneralSecurityException is the recommended pattern—message-based detection is unreliable and fragile.

Correct fix
         sharedPreferences =
             try {
                 createEncryptedPrefs(context, requestStrongBox = useStrongBox)
-            } catch (e: Exception) {
-                if (!useStrongBox) throw e // not a StrongBox issue, no fallback available
-                Log.w("KeychainAccessor", "StrongBox-backed prefs failed, falling back to TEE", e)
-                createEncryptedPrefs(context, requestStrongBox = false)
+            } catch (e: java.security.GeneralSecurityException) {
+                if (!useStrongBox) throw e // not a StrongBox issue, no fallback available
+                Log.w("KeychainAccessor", "StrongBox-backed prefs failed, falling back to TEE", e)
+                createEncryptedPrefs(context, requestStrongBox = false)
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/app/src/main/java/org/bitcoinppl/cove/Security.kt` around lines 20 -
28, The current catch block around createEncryptedPrefs is too broad and should
only handle security-related failures from StrongBox; replace the catch of
Exception with catching GeneralSecurityException (which covers
StrongBoxUnavailableException) in the code path that uses hasStrongBox and
createEncryptedPrefs so only crypto/hardware unavailability triggers the TEE
fallback; keep rethrowing other exceptions when useStrongBox is false and remove
any message-based detection logic so failures unrelated to StrongBox aren’t
silently masked (refer to hasStrongBox, createEncryptedPrefs,
sharedPreferences/KeychainAccessor to locate the block).

Comment on lines +105 to +111

// set initial load state from Rust cached data
loadState = when (val rustLoadState = rustManager.initialLoadState()) {
is org.bitcoinppl.cove_core.WalletLoadState.Loading -> WalletLoadState.LOADING
is org.bitcoinppl.cove_core.WalletLoadState.Scanning -> WalletLoadState.SCANNING(rustLoadState.v1)
is org.bitcoinppl.cove_core.WalletLoadState.Loaded -> WalletLoadState.LOADED(rustLoadState.v1)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Initialize loadState before subscribing to updates.

At Line 105, listenForUpdates(this) can start delivering reconcile messages before the constructor sets loadState, so the assignment at Line 107 can overwrite fresher state.

🔧 Proposed fix
-        rustManager.listenForUpdates(this)
-
         // set initial load state from Rust cached data
         loadState = when (val rustLoadState = rustManager.initialLoadState()) {
             is org.bitcoinppl.cove_core.WalletLoadState.Loading -> WalletLoadState.LOADING
             is org.bitcoinppl.cove_core.WalletLoadState.Scanning -> WalletLoadState.SCANNING(rustLoadState.v1)
             is org.bitcoinppl.cove_core.WalletLoadState.Loaded -> WalletLoadState.LOADED(rustLoadState.v1)
         }
+
+        rustManager.listenForUpdates(this)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/app/src/main/java/org/bitcoinppl/cove/WalletManager.kt` around lines
105 - 111, The constructor currently subscribes to updates
(listenForUpdates(this)) before setting WalletManager.loadState from
rustManager.initialLoadState(), allowing reconcile updates to overwrite fresher
state; fix by initializing loadState from rustManager.initialLoadState() before
calling listenForUpdates(this) (or otherwise deferring subscription until after
the when-block that maps org.bitcoinppl.cove_core.WalletLoadState to
WalletLoadState), ensuring the mapping logic that sets
WalletLoadState.LOADING/SCANNING/LOADED runs prior to any reconcile callbacks.

Comment on lines +510 to +512
.onAppear {
shouldShowNavBar = false
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Reset global header state when resetting local navbar state.

Line 510 resets shouldShowNavBar, but app.isPastHeader is not reset here. That can cause a temporary toolbar-style mismatch until geometry updates run.

Suggested fix
 .onAppear {
     shouldShowNavBar = false
+    app.isPastHeader = false
 }
📝 Committable suggestion

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

Suggested change
.onAppear {
shouldShowNavBar = false
}
.onAppear {
shouldShowNavBar = false
app.isPastHeader = false
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Cove/Flows/SelectedWalletFlow/SelectedWalletScreen.swift` around lines
510 - 512, The onAppear handler in SelectedWalletScreen.swift currently sets
shouldShowNavBar = false but does not reset the global header flag
app.isPastHeader, causing a transient toolbar mismatch; update the .onAppear
block (where shouldShowNavBar is changed) to also set app.isPastHeader = false
so the global header state is synchronized with the local navbar state (i.e.,
set both shouldShowNavBar = false and app.isPastHeader = false in the same
onAppear).

Comment on lines +61 to +65
static func dismantleUIViewController(
_ vc: NavigationTitleViewController<TitleContent>, coordinator _: Coordinator
) {
vc.clearTitle()
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Only clear the navigation title if this helper still owns it.

Line 108 clears titleView unconditionally. If SwiftUI replaces this helper, an older instance can dismantle after a newer one has already attached its own title view and blank the navbar.

Suggested fix
     func clearTitle() {
-        targetNavigationItem?.titleView = nil
+        if let hostingView = hostingController?.view,
+           targetNavigationItem?.titleView === hostingView
+        {
+            targetNavigationItem?.titleView = nil
+        }
         targetNavigationItem = nil
     }

Also applies to: 107-109

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

In `@ios/Cove/Views/NavigationTitleView.swift` around lines 61 - 65, The
dismantleUIViewController implementation unconditionally calls vc.clearTitle(),
which can remove a title view installed by a newer helper; change
dismantleUIViewController in NavigationTitleView.swift so it only clears the
navigation title if this helper still owns it—e.g., have
NavigationTitleViewController/clearTitle check ownership by comparing the stored
titleView reference (or an ownership token/id kept on the controller) against
the navigationItem.titleView (or token) and only remove/clear when they match;
update any place that sets titleView to also set that ownership token so
dismantleUIViewController safely no-ops when another helper replaced the title.

set_env();

// one time init
rustls::crypto::ring::default_provider().install_default().ok();
Copy link

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

🌐 Web query:

rustls 0.23 install_default crypto provider error handling

💡 Result:

In rustls 0.23, the default crypto provider is aws-lc-rs (enabled by the default aws_lc_rs feature). There is no automatic process-wide default CryptoProvider installed. Functions like ClientConfig::builder and ServerConfig::builder require a process-level default to be set, or they panic with "no process-level CryptoProvider available -- call CryptoProvider::install_default before this point". To fix this, call CryptoProvider::install_default early in main, before any TLS usage. This can only be called once per process. For the default aws-lc-rs provider: use rustls::crypto::aws_lc_rs; fn main { aws_lc_rs::default_provider.install_default.expect("should not fail"); // ... rest of app } For ring (enable the ring feature, avoid enabling both aws_lc_rs and ring to prevent ambiguity): use rustls::crypto::ring; fn main { ring::default_provider.install_default.expect("should not fail"); // ... rest of app } The install_default method returns Result<, Arc<dyn std::error::Error + Send + Sync>>. It fails if already called (second call returns the previously installed provider as error), or if the provider is invalid (e.g., mismatched cipher suites and KX groups). If using default-features = false, or custom-provider feature, or ambiguous features (both aws_lc_rs and ring), you must explicitly install a provider. Alternatively, use builder_with_provider(provider) to avoid process-wide default. Cargo.toml examples: - Default (aws-lc-rs): rustls = "0.23" - Ring: rustls = { version = "0.23", features = ["ring"] } - No default deps: rustls = { version = "0.23", default-features = false, features = ["aws_lc_rs", "std"] } then install explicitly. aws-lc-rs requires cmake and nasm (Windows) at build time.

Citations:


🏁 Script executed:

# Check Cargo.toml for rustls configuration
cat -n Cargo.toml | grep -A 5 -B 5 rustls

# Check the actual code context in app.rs
fd app.rs --path rust/ --exec cat -n {} \; | head -100

Repository: bitcoinppl/cove

Length of output: 317


🏁 Script executed:

# Find Cargo.toml
find . -name "Cargo.toml" -type f

# Find app.rs in rust directory
find . -path "*/rust/src/app.rs" -type f

Repository: bitcoinppl/cove

Length of output: 548


🏁 Script executed:

# Check rustls configuration in main Cargo.toml
cat ./rust/Cargo.toml | grep -A 10 -B 2 rustls

# Check app.rs around line 87
sed -n '70,100p' ./rust/src/app.rs

Repository: bitcoinppl/cove

Length of output: 2544


🏁 Script executed:

# Verify the exact error type from rustls documentation by checking if there's any documentation in the crate
rg "install_default" rust/Cargo.toml rust/src/app.rs -B 2 -A 2

# Also check if there are other usages of install_default in the codebase
rg "install_default" --type rust

Repository: bitcoinppl/cove

Length of output: 359


🏁 Script executed:

# Get full context around both occurrences - read the entire section with line numbers
sed -n '75,105p' ./rust/src/app.rs

Repository: bitcoinppl/cove

Length of output: 1032


Change .ok() to .expect() for proper error handling.

Using .ok() silently discards errors from install_default(). According to rustls 0.23 best practices, this should use .expect() instead. While the benign case (provider already installed) won't occur in single-threaded initialization, using .ok() also masks real configuration errors (e.g., invalid crypto provider setup). Change to .expect("failed to install default crypto provider") to fail loudly on actual issues.

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

In `@rust/src/app.rs` at line 87, The call to
rustls::crypto::ring::default_provider().install_default() currently uses .ok(),
which drops errors silently; change this to call .expect("failed to install
default crypto provider") so the program panics with a clear message on
failure—update the expression where install_default() is invoked to use expect
with that message to surface configuration errors.

Comment on lines +10 to +13
reqwest::ClientBuilder::new()
.tls_backend_preconfigured(tls_config)
.build()
.expect("failed to build http client")
Copy link

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
# Verify all call sites that consume crate::http_client::new_client()
# Expected: enumerate constructors that need to handle a fallible return.
rg -nP --type=rust -C2 '\bcrate::http_client::new_client\s*\('

Repository: bitcoinppl/cove

Length of output: 853


🏁 Script executed:

# Read the http_client.rs file to verify the current implementation
head -40 rust/src/http_client.rs

Repository: bitcoinppl/cove

Length of output: 628


🏁 Script executed:

# Search for all calls to new_client() including potential uses with :: prefix or different patterns
rg -n "new_client\s*\(" rust/ --type=rust

Repository: bitcoinppl/cove

Length of output: 420


🏁 Script executed:

# Check if eyre is already a dependency in the project
grep -r "eyre" rust/Cargo.toml

Repository: bitcoinppl/cove

Length of output: 99


🏁 Script executed:

# Examine the call sites to understand how they use the client
cat -n rust/src/fee_client.rs | sed -n '40,60p'

Repository: bitcoinppl/cove

Length of output: 902


🏁 Script executed:

# Check fiat client usage
cat -n rust/src/fiat/client.rs | sed -n '75,95p'

Repository: bitcoinppl/cove

Length of output: 794


🏁 Script executed:

# Search for tests that might use new_client() or instantiate the clients
rg -n "new_client\|FeeClient\|FiatClient" rust/src --type=rust | grep -i test

Repository: bitcoinppl/cove

Length of output: 41


🏁 Script executed:

# Check for any test files in the rust directory
find rust -name "*test*" -o -name "*tests*" | grep -E "\.(rs|toml)$"

Repository: bitcoinppl/cove

Length of output: 41


🏁 Script executed:

# Examine the struct definitions to see client field types
rg -A5 "pub struct FeeClient" rust/src --type=rust

Repository: bitcoinppl/cove

Length of output: 285


🏁 Script executed:

# Check FiatClient struct
rg -A5 "pub struct FiatClient" rust/src --type=rust

Repository: bitcoinppl/cove

Length of output: 285


Avoid panic in HTTP client initialization and add explicit timeouts to prevent hangs.

The code panics via .expect() on line 13 if the client builder fails, and the client lacks explicit timeout configuration. While the panic occurs at initialization rather than during requests, panicking on a fallible operation is poor error handling. Missing timeouts can cause the application to hang indefinitely on slow or unresponsive servers.

Change new_client() to return Result<reqwest::Client> instead of panicking, and add explicit connection and request timeouts. This requires updating three call sites in FeeClient and FiatClient constructors, but the scope is localized and manageable.

Proposed refactor
+use eyre::{Context as _, Result};
+use std::time::Duration;
+
 /// Build a reqwest Client that uses webpki-roots for TLS cert verification,
 /// bypassing rustls-platform-verifier (which requires Android JNI init)
-pub fn new_client() -> reqwest::Client {
+pub fn new_client() -> Result<reqwest::Client> {
     let root_store =
         rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());

     let tls_config =
         rustls::ClientConfig::builder().with_root_certificates(root_store).with_no_client_auth();

     reqwest::ClientBuilder::new()
+        .connect_timeout(Duration::from_secs(10))
+        .timeout(Duration::from_secs(20))
         .tls_backend_preconfigured(tls_config)
         .build()
-        .expect("failed to build http client")
+        .context("failed to build http client")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/src/http_client.rs` around lines 10 - 13, The HTTP client initializer
currently panics via .expect() and lacks timeouts; change new_client() to return
Result<reqwest::Client, Box<dyn std::error::Error>> (or an appropriate error
type) instead of calling .expect(), and configure explicit timeouts on the
builder (e.g., connect_timeout and overall timeout) before build; update all
call sites that construct the client (the constructors/factories for FeeClient
and FiatClient) to propagate or handle the Result (return Result from those
constructors or handle errors) so the code no longer panics on builder failure
and will time out on slow/unresponsive servers. Ensure you still use
tls_backend_preconfigured(tls_config) on reqwest::ClientBuilder and
map/propagate build errors rather than unwrapping.

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