Skip to content

Conversation

@praveenperera
Copy link
Contributor

@praveenperera praveenperera commented Jan 22, 2026

Closes #387

Summary by CodeRabbit

  • New Features
    • Users can now paste a complete mnemonic (12 or 24 words) directly into the wallet import screen on both Android and iOS. The system automatically detects valid mnemonics, validates the word count, and populates the input fields accordingly, streamlining the wallet import process.

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

@coderabbitai
Copy link

coderabbitai bot commented Jan 22, 2026

📝 Walkthrough

Walkthrough

This PR adds mnemonic paste detection to the hot wallet import flow on both Android and iOS platforms. When users paste 12 or more space-separated words, the system detects this as a mnemonic input, parses it, validates the word count and validity, and populates the word grid accordingly.

Changes

Cohort / File(s) Summary
Android Paste Detection
android/app/src/main/java/org/bitcoinppl/cove/flows/NewWalletFlow/hot_wallet/HotWalletImportCard.kt
Added optional onPasteMnemonic callback parameter to WordInputGrid and WordInputField composables; implemented paste detection that triggers when input splits into 12+ non-empty words.
Android Screen Integration
android/app/src/main/java/org/bitcoinppl/cove/flows/NewWalletFlow/hot_wallet/HotWalletImportScreen.kt
Introduced currentNumberOfWords state tracking, implemented handlePasteMnemonic() to parse and validate pasted mnemonic strings using groupedPlainWordsOf, updated word validation and UI rendering to use dynamic word count, adjusted NFC and QR scanner integrations.
iOS Paste Detection
ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletImportCard.swift
Added optional onPasteMnemonic callback to TextFieldReturnHandler; enhanced text field delegate to detect pasting of 12+ words and invoke callback; propagated callback through CardTab and AutocompleteField hierarchy.
iOS Screen Integration
ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletImportScreen.swift
Added handlePasteMnemonic() function to parse, validate, and group pasted mnemonics with error alerting; adjusted onChangeFocusField signature to use underscore parameter; modified tab index computation and lastIndex boundary values (12-word: 1→0; 24-word: 3→1).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A paste of words, so quick and fast,
No more typing each one last!
Mnemonics whole, from clipboard springs,
The rabbit grins at easier things ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main objective: enabling users to paste a complete mnemonic during hot wallet import instead of entering words individually.
Linked Issues check ✅ Passed The implementation successfully adds paste detection across both Android and iOS platforms, parsing pasted mnemonics, validating word counts (12 or 24), and populating the word grid, fulfilling issue #387's requirement.
Out of Scope Changes check ✅ Passed All changes are focused on implementing paste functionality for mnemonic input. Minor adjustments to focus logic and lastIndex mapping are directly related to supporting the new paste feature.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

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

@praveenperera praveenperera self-assigned this Jan 22, 2026
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: 2

🤖 Fix all issues with AI agents
In
`@android/app/src/main/java/org/bitcoinppl/cove/flows/NewWalletFlow/hot_wallet/HotWalletImportScreen.kt`:
- Around line 104-122: The keyed remember for enteredWords using
remember(currentNumberOfWords) causes the state to be recreated (losing pasted
words) when currentNumberOfWords changes; remove the key so enteredWords is
remembered across the transient mode change and explicitly reinitialize its
contents when you truly want to switch sizes (e.g., provide an
initEnteredWords(numberOfGroups) helper and call it only when confirming a size
change), and modify handlePasteMnemonic to set and clear an isHandlingPaste flag
(or otherwise defer size-based reinitialization until after paste handling
finishes) so pasted words are applied to the stable enteredWords state; update
references to enteredWords, currentNumberOfWords, handlePasteMnemonic and
isHandlingPaste accordingly.

In `@ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletImportScreen.swift`:
- Around line 600-629: The handler handlePasteMnemonic currently calls
setWords(grouped) before validating the pasted mnemonic; validate first using
groupedPlainWordsOf(mnemonic:groups:) (with the same groupsOf value) and only
call setWords(grouped) after the validation succeeds, otherwise set alertState =
.init(.invalidWords) on error and do not update the UI; keep the existing
try/catch around groupedPlainWordsOf and move it above the setWords call so the
UI is populated only on successful validation.
🧹 Nitpick comments (1)
android/app/src/main/java/org/bitcoinppl/cove/flows/NewWalletFlow/hot_wallet/HotWalletImportScreen.kt (1)

195-220: Validation occurs after state update.

The groupedPlainWordsOf validation happens after setWords, so invalid mnemonics are displayed in the UI with an error alert. If this is intentional (letting users see and fix invalid words), consider adding a brief comment. Otherwise, move validation before setWords to prevent invalid data from being displayed.

Comment on lines +104 to +122
// use local state so we can update when paste changes word count
var currentNumberOfWords by remember { mutableStateOf(numberOfWords) }

val wordCount =
when (numberOfWords) {
when (currentNumberOfWords) {
NumberOfBip39Words.TWELVE -> 12
NumberOfBip39Words.TWENTY_FOUR -> 24
}

val numberOfGroups = wordCount / GROUPS_OF
var enteredWords by remember(numberOfWords) {
var enteredWords by remember(currentNumberOfWords) {
mutableStateOf(List(numberOfGroups) { List(GROUPS_OF) { "" } })
}

var alertState by remember { mutableStateOf(AlertState.None) }
var duplicateWalletId by remember { mutableStateOf<WalletId?>(null) }
var genericErrorMessage by remember { mutableStateOf("") }
var focusedField by remember(numberOfWords) { mutableIntStateOf(0) }
var tabIndex by remember(numberOfWords) { mutableIntStateOf(0) }
var focusedField by remember(currentNumberOfWords) { mutableIntStateOf(0) }
var tabIndex by remember(currentNumberOfWords) { mutableIntStateOf(0) }
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

Potential bug: keyed remember may discard pasted words when word count changes.

When currentNumberOfWords changes (e.g., pasting 24 words while in 12-word mode), the remember(currentNumberOfWords) recreates enteredWords with empty lists on recomposition. The update in setWords happens on the old state instance before recomposition, so the pasted words may be lost.

Consider removing the key from remember and handling initialization explicitly, or ensuring state updates occur after recomposition settles:

Suggested approach
-    var enteredWords by remember(currentNumberOfWords) {
+    var enteredWords by remember {
         mutableStateOf(List(numberOfGroups) { List(GROUPS_OF) { "" } })
     }
+
+    // Reset enteredWords when word count changes (except during paste)
+    var isHandlingPaste by remember { mutableStateOf(false) }
+    LaunchedEffect(currentNumberOfWords) {
+        if (!isHandlingPaste) {
+            val groups = if (currentNumberOfWords == NumberOfBip39Words.TWELVE) 1 else 2
+            enteredWords = List(groups) { List(GROUPS_OF) { "" } }
+        }
+        isHandlingPaste = false
+    }

Then set isHandlingPaste = true at the start of handlePasteMnemonic.

🤖 Prompt for AI Agents
In
`@android/app/src/main/java/org/bitcoinppl/cove/flows/NewWalletFlow/hot_wallet/HotWalletImportScreen.kt`
around lines 104 - 122, The keyed remember for enteredWords using
remember(currentNumberOfWords) causes the state to be recreated (losing pasted
words) when currentNumberOfWords changes; remove the key so enteredWords is
remembered across the transient mode change and explicitly reinitialize its
contents when you truly want to switch sizes (e.g., provide an
initEnteredWords(numberOfGroups) helper and call it only when confirming a size
change), and modify handlePasteMnemonic to set and clear an isHandlingPaste flag
(or otherwise defer size-based reinitialization until after paste handling
finishes) so pasted words are applied to the stable enteredWords state; update
references to enteredWords, currentNumberOfWords, handlePasteMnemonic and
isHandlingPaste accordingly.

Comment on lines +600 to 629
func handlePasteMnemonic(_ mnemonicString: String) {
// extract word-like tokens, stripping numbers and punctuation
let words = mnemonicString
.split(whereSeparator: { $0.isWhitespace })
.map { String($0).lowercased() }
.filter { word in
// keep only alphabetic strings (filters out "1.", "2)", etc.)
word.allSatisfy(\.isLetter)
}

// need 12 or 24 words
guard words.count == 12 || words.count == 24 else {
alertState = .init(.invalidWords)
return
}

// group words into chunks of groupsOf (12)
let grouped = stride(from: 0, to: words.count, by: groupsOf).map {
Array(words[$0 ..< min($0 + groupsOf, words.count)])
}
setWords(grouped)

// validate - show alert if invalid
do {
_ = try groupedPlainWordsOf(mnemonic: words.joined(separator: " "), groups: UInt8(groupsOf))
} catch {
Log.debug("Invalid pasted mnemonic: \(error)")
alertState = .init(.invalidWords)
}
}
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

Validation should occur before populating the UI.

The current flow calls setWords(grouped) before validating with groupedPlainWordsOf. If the pasted words aren't valid BIP39 words, they'll be populated into the input fields before the error alert appears, leaving the UI in an invalid state.

Consider validating first and only setting words on success:

Proposed fix
 func handlePasteMnemonic(_ mnemonicString: String) {
     // extract word-like tokens, stripping numbers and punctuation
     let words = mnemonicString
         .split(whereSeparator: { $0.isWhitespace })
         .map { String($0).lowercased() }
         .filter { word in
             // keep only alphabetic strings (filters out "1.", "2)", etc.)
             word.allSatisfy(\.isLetter)
         }

     // need 12 or 24 words
     guard words.count == 12 || words.count == 24 else {
         alertState = .init(.invalidWords)
         return
     }

+    // validate words are valid BIP39 before populating UI
+    do {
+        _ = try groupedPlainWordsOf(mnemonic: words.joined(separator: " "), groups: UInt8(groupsOf))
+    } catch {
+        Log.debug("Invalid pasted mnemonic: \(error)")
+        alertState = .init(.invalidWords)
+        return
+    }
+
     // group words into chunks of groupsOf (12)
     let grouped = stride(from: 0, to: words.count, by: groupsOf).map {
         Array(words[$0 ..< min($0 + groupsOf, words.count)])
     }
     setWords(grouped)
-
-    // validate - show alert if invalid
-    do {
-        _ = try groupedPlainWordsOf(mnemonic: words.joined(separator: " "), groups: UInt8(groupsOf))
-    } catch {
-        Log.debug("Invalid pasted mnemonic: \(error)")
-        alertState = .init(.invalidWords)
-    }
 }
🤖 Prompt for AI Agents
In `@ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletImportScreen.swift` around
lines 600 - 629, The handler handlePasteMnemonic currently calls
setWords(grouped) before validating the pasted mnemonic; validate first using
groupedPlainWordsOf(mnemonic:groups:) (with the same groupsOf value) and only
call setWords(grouped) after the validation succeeds, otherwise set alertState =
.init(.invalidWords) on error and do not update the UI; keep the existing
try/catch around groupedPlainWordsOf and move it above the setWords call so the
UI is populated only on successful validation.

@greptile-apps
Copy link

greptile-apps bot commented Jan 22, 2026

Greptile Summary

Added mnemonic paste functionality allowing users to paste complete 12 or 24-word mnemonics directly into any input field during hot wallet import. The implementation detects multi-word pastes, filters out numeric prefixes (e.g., "1.", "2."), validates the word count and mnemonic validity, then auto-populates all fields and navigates to the last page.

Key Changes:

  • Added paste detection in both Android (BasicTextField) and iOS (UITextFieldDelegate) that triggers when 12+ words are detected
  • Implemented handlePasteMnemonic function that extracts alphabetic tokens, validates word count (12 or 24), and calls validation via groupedPlainWordsOf
  • Dynamic word count adjustment: if user pastes 12 words when expecting 24, the UI adapts to show only 12-word layout
  • Fixed iOS lastIndex bug (changed from 1-indexed to 0-indexed for 12-word imports)
  • Consistent error handling on both platforms with user-facing alerts for invalid word counts or checksum failures

Implementation Quality:

  • Clean separation of concerns with paste detection at field level and validation at screen level
  • Proper filtering of numbered prefixes (common in mnemonic backups)
  • Validation happens after populating fields, providing immediate feedback
  • Consistent behavior across Android and iOS platforms

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation is well-structured with proper validation, error handling, and consistent behavior across platforms. The paste detection logic is sound, filtering handles edge cases (numbered prefixes), and validation uses the existing groupedPlainWordsOf function ensuring mnemonic integrity.
  • No files require special attention

Important Files Changed

Filename Overview
android/app/src/main/java/org/bitcoinppl/cove/flows/NewWalletFlow/hot_wallet/HotWalletImportCard.kt Added paste detection in text field to intercept 12+ word pastes and trigger mnemonic handler
android/app/src/main/java/org/bitcoinppl/cove/flows/NewWalletFlow/hot_wallet/HotWalletImportScreen.kt Implemented mnemonic paste handler with word count detection, validation, and dynamic word count adjustment
ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletImportCard.swift Added paste detection via UITextFieldDelegate to intercept 12+ word pastes and trigger mnemonic handler
ios/Cove/Flows/NewWalletFlow/HotWallet/HotWalletImportScreen.swift Implemented mnemonic paste handler with word filtering, validation, and fixed lastIndex bug

Sequence Diagram

sequenceDiagram
    participant User
    participant TextField as WordInputField
    participant Card as HotWalletImportCard
    participant Screen as HotWalletImportScreen
    participant Validator as groupedPlainWordsOf

    User->>TextField: Paste mnemonic (12-24 words)
    TextField->>TextField: Detect paste (split by whitespace)
    
    alt Word count >= 12
        TextField->>Card: onPasteMnemonic(mnemonicString)
        Card->>Screen: handlePasteMnemonic(mnemonicString)
        Screen->>Screen: Extract alphabetic tokens
        Screen->>Screen: Filter numbered prefixes (1., 2., etc)
        
        alt Word count == 12 or 24
            Screen->>Screen: Group words into chunks of 12
            Screen->>Validator: groupedPlainWordsOf(words, groups)
            
            alt Validation succeeds
                Validator-->>Screen: Valid mnemonic
                Screen->>Screen: setWords(grouped)
                Screen->>Screen: Update currentNumberOfWords
                Screen->>Screen: Focus last field on last page
                Screen-->>User: Display filled fields
            else Validation fails
                Validator-->>Screen: Invalid mnemonic
                Screen->>Screen: Set alertState = InvalidWords
                Screen-->>User: Show error alert
            end
        else Invalid word count
            Screen->>Screen: Set alertState = InvalidWords/GenericError
            Screen-->>User: Show error alert
        end
    else Word count < 12
        TextField->>TextField: Process as normal single-word input
    end
Loading

@praveenperera praveenperera merged commit f9e7097 into master Jan 22, 2026
10 checks passed
@praveenperera praveenperera deleted the pr/paste-all-words branch January 22, 2026 21:43
@coderabbitai coderabbitai bot mentioned this pull request Jan 23, 2026
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.

Allow pasting in all words at once (hot wallet import)

2 participants