Skip to content

fix: handle already-joined vault on QR scan instead of hanging#4287

Merged
johnnyluo merged 3 commits into
mainfrom
aminsato/4282_join_duplicate_vault
Apr 27, 2026
Merged

fix: handle already-joined vault on QR scan instead of hanging#4287
johnnyluo merged 3 commits into
mainfrom
aminsato/4282_join_duplicate_vault

Conversation

@aminsato
Copy link
Copy Markdown
Collaborator

@aminsato aminsato commented Apr 26, 2026

Summary

Fixes #4282 — joining a QR for an existing seed-phrase vault on a device that already has it threw DuplicateVaultException after keygen completed, leaving the Vault created successfully screen stuck on an infinite spinner.

  • Primary fixJoinKeygenViewModel (KEYGEN/KeyImport branch) now compares the QR's hexChainCode against existing vaults before doing anything. If a match is found, it skips mediator discovery + keygen entirely and routes to Route.Home(openVaultId = existingVault.id). The non-blank guard prevents matching legacy vaults that have an empty stored chain code.
  • Defence in depthKeygenViewModel.saveVault() now catches DuplicateVaultException on the non-override path (only KEYGEN/KeyImport can hit it; reshare/migrate use upsert). On catch it looks up the existing vault by pubKeyECDSA, stops the mediator service, and routes to Home — so the success screen never hangs even if the early hexChainCode check ever misses (e.g. the QR carries an empty chain code, or another flow saved the vault concurrently).
    Screen_recording_20260426_050958.webm

Why two layers

The early hexChainCode check is the user-friendly path: skip the whole join when we already know the vault is here. The save-side catch protects the cases the early check can miss — initiating-device flows, blank/legacy chain codes, races against another save — so we never regress to the original infinite spinner.

Test plan

  • Device A has Vault X (seed-phrase). Device B has the same Vault X stored. Start join keygen flow on B with A's QR — B should land on Home with Vault X selected, no spinner hang, no error toast.
  • Normal first-time join on a clean device still completes keygen and reaches the backup flow.
  • Reshare / Migrate flows are unaffected (still upsert; never throw DuplicateVaultException).
  • Verify on emulator with two debug builds (forward port 18080 + socat per README).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Prevented duplicate vault creation when rejoining or importing; users are automatically redirected to their existing vault instead of creating duplicates.
  • New Features

    • UI now detects when a vault is already on the device, showing a localized message with the vault name and a primary action to open the existing vault (navigates to Home and clears back stack).
  • Localization

    • Added localized strings for the new "already on device" message and "open existing vault" action across multiple languages.

Joining a QR for an existing seed-phrase vault on a device that
already has it threw DuplicateVaultException after keygen, leaving
the success screen stuck on an infinite spinner.

Detect the duplicate by hexChainCode in JoinKeygenViewModel and route
to the existing vault's Home before keygen starts. As a defence in
depth, KeygenViewModel.saveVault now catches DuplicateVaultException
on the non-override path and routes to Home so the success screen
cannot hang on save failure.

Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

📝 Walkthrough

Walkthrough

Adds duplicate-vault detection to the keygen join/save flows: JoinKeygenViewModel checks decoded join payload against existing vaults by hexChainCode and short-circuits to an "already joined" UI; KeygenViewModel treats DuplicateVaultException as an existing vault and routes to that vault's home.

Changes

Cohort / File(s) Summary
ViewModels — duplicate detection & routing
app/src/main/java/com/vultisig/wallet/ui/models/keygen/JoinKeygenViewModel.kt, app/src/main/java/com/vultisig/wallet/ui/models/keygen/KeygenViewModel.kt
Adds AlreadyJoinedVault model and JoinKeygenUiModel.alreadyJoined; JoinKeygenViewModel detects existing vaults by hexChainCode, sets alreadyJoined and returns; adds openExistingVault() to navigate to existing vault with clearBackStack. KeygenViewModel now catches DuplicateVaultException, resolves existing vault by pubKeyECDSA, stops mediator, and navigates to that vault.
UI — already-joined presentation
app/src/main/java/com/vultisig/wallet/ui/screens/keygen/JoinKeygenScreen.kt
Prioritizes state.alreadyJoined over generic error; displays localized "vault already on device" message with vault name and changes primary action to openExistingVault.
Localization — new strings across locales
app/src/main/res/values/.../strings.xml
app/src/main/res/values-de/strings.xml, .../values-es/strings.xml, .../values-hr/strings.xml, .../values-it/strings.xml, .../values-ko/strings.xml, .../values-nl/strings.xml, .../values-pt/strings.xml, .../values-ru/strings.xml, .../values-zh-rCN/strings.xml, .../values/strings.xml
Adds join_key_gen_vault_already_on_device (with %1$s vault name placeholder) and join_key_gen_open_existing_vault to multiple locale string resources.

Sequence Diagram(s)

sequenceDiagram
    participant UI as JoinKeygenScreen
    participant VM as JoinKeygenViewModel
    participant Repo as VaultRepository
    participant Nav as Navigator
    participant Keygen as KeygenSession/Mediator

    UI->>VM: submit decoded join payload (hexChainCode)
    VM->>Repo: read existingVaults()
    alt matching hexChainCode found
        VM-->>UI: set JoinKeygenUiModel(alreadyJoined)
        UI->>Nav: openExistingVault(vaultId, clearBackStack=true)
    else no match
        VM->>Keygen: start keygen/session
        Keygen->>VM: produce Vault (pubKeyECDSA)
        VM->>Repo: saveVault(vault)
        alt DuplicateVaultException thrown
            VM->>Repo: getByEcdsa(pubKeyECDSA) -> existingVault
            VM->>Keygen: stopMediator()
            VM->>Nav: openExistingVault(existingVault.id, clearBackStack=true)
        else saved successfully
            VM->>Nav: navigate to new vault home
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰
I sniffed the chaincode in the night,
Found a vault already tucked in tight,
No double burrows, one comfy nest—
I point the traveler to the best,
Hop home safe, and sleep in light!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Title check ✅ Passed The title accurately summarizes the main change: handling already-joined vaults on QR scan to prevent hanging. It is specific, concise, and directly reflects the primary objective.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ 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 aminsato/4282_join_duplicate_vault

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

Replaces the silent redirect with an info screen that names the
existing vault and offers an explicit 'Open vault' button, so the
user understands what happened. Adds the two new strings (info text
and button label) to all 10 supported locales.

Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
@aminsato
Copy link
Copy Markdown
Collaborator Author

@codetabbitai full review

Copy link
Copy Markdown
Contributor

@johnnyluo johnnyluo left a comment

Choose a reason for hiding this comment

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

LGTM

@johnnyluo johnnyluo added this pull request to the merge queue Apr 26, 2026
Merged via the queue into main with commit c4106fd Apr 27, 2026
2 checks passed
@johnnyluo johnnyluo deleted the aminsato/4282_join_duplicate_vault branch April 27, 2026 00:09
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.

Joining QR for seed phrase vault into Secure Vault fails with DuplicateVaultException / generateKey error

2 participants