Repository navigation
feat(accounts): import an additional recovery phrase without wiping the wallet - #1067
Open
aristidesstaffieri wants to merge 1 commit into
Open
aristidesstaffieri wants to merge 1 commit into
aristidesstaffieri wants to merge 1 commit into
Conversation
…he wallet Port of stellar/freighter#3084. Add wallet now offers Import Recovery Phrase, which adds a typed 12- or 24-word phrase as a new source with index 0 and any funded indices up to 49 on mainnet. Index 0 becomes active and nothing already in the wallet changes. A held phrase, an address already in the wallet or a wrong password is refused before anything is written. The funded-account lookups run before the account-mutation lock is taken, and the checks and writes run under it. If no account gets stored, the source and its indices are removed again.
Contributor
|
iOS Simulator preview build is ready: https://github.com/stellar/freighter-mobile/releases/tag/untagged-986d85e93df7fd19167b |
Contributor
|
Android Emulator preview APK is ready: https://github.com/stellar/freighter-mobile/releases/tag/untagged-7761902fef6fa3f99072 |
aristidesstaffieri
marked this pull request as ready for review
November 13, 2026 16:52
12 of 23 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes stellar/freighter-private#55
What
Ports stellar/freighter#3084 to mobile. Add wallet now offers Import Recovery Phrase next to Create new wallet and Import Secret Key. The user types or pastes a 12 or 24-word phrase, confirms with the password (or Face ID), and the phrase joins the wallet as a new source with index 0 plus every funded index from 1 to 49 on mainnet. Index 0 becomes active. Existing sources, accounts, names, indices and the pin stay as they are. A phrase the wallet already holds, a phrase with an address already in the wallet, a wrong password or an invalid phrase is refused before anything is written.
The typed phrase is held in the same auth module slot that #1058 uses for a generated phrase, so it never shows up in store state or navigation params, and lock, log out and wipe already clear it. The screen passes only the hold's id to the password step. The import is split in two: the password check, held-phrase check and funded-account lookups run first, outside the account-mutation lock from #1066, and the checks and writes run under it. Up to 49 lookups at 5s each would otherwise hold the lock and keep a wipe waiting. If a lock or log out drops the held phrase while the lookups run, the import stores nothing.
Suggested reading order:
prepareRecoveryPhraseImportandstoreImportedRecoveryPhraseinducks/auth.ts, thenImportRecoveryPhraseScreen.Auth
importRecoveryPhrase(ducks/auth.ts, new): runs the prepare half, then the store half under the lock, then reloads the accounts, tracks the add-account event withsource: "imported_phrase"and links the new source.prepareRecoveryPhraseImport(new): validates the held phrase, checks the password against the active keystore, refuses a held phrase before any lookup, and finds the funded indices.storeImportedRecoveryPhrase(new): checks the hold, the source and every address again, registers the source, then stores each account in order. If no account was listed, it removes the source and releases its indices. On any failure it puts the previous account back as active.discoverFundedIndices(new): mainnet lookups with at most 10 in flight, each capped at 5s by the timeout from fix(accounts): serialize account-mutating auth actions #1066. A failed or timed-out lookup counts as unfunded.holdPendingRecoveryPhrase(new): holds a typed phrase by id.generatePendingRecoveryPhrasenow goes through it.removeSecretSource(services/auth/secretSources.ts, new): drops one source record and moves the pin to the earliest remaining source if it was pinned. Only the import's rollback calls it.isValidImportMnemonic(helpers/recoveryPhrase.ts, new): 12 or 24 words that pass the BIP-39 wordlist and checksum, without deriving the seed.Screens
AddAnotherWallet: adds the Import Recovery Phrase option.ImportRecoveryPhraseScreen(new): reuses onboarding'sImportRecoveryPhraseTemplate, which already has a Paste button on the secure clipboard. The extension left Paste out because extension pages can't read the clipboard. An invalid phrase shows the error under the field. The hold is dropped when the screen goes away.VerifyPasswordScreen: takes animportPhrasetarget and shows Import on its button.Not ported from #3084: the shared password helper (mobile already shares
getKeyFromKeyManager) and moving onboarding recovery onto the lookup helper (its lookups already run in parallel).Why
The only way to import a phrase today is onboarding's Import Wallet, which deletes every account and source before storing the new phrase, so using it to add a second phrase wipes the first wallet. Onboarding keeps that behavior because it is also the forgot-password path.
Known limitations
Screenshots / video
[TODO: Add wallet with the new option, the import screen, the password step]
Test plan
auth.test.tscovers an import storing index 0 and a funded index under a new source, with index 0 active and the wallet's pin and phrase kept. It also covers refusals for a held phrase (before any lookup), an address already in the wallet, a wrong password and an invalid phrase.auth.test.tscovers the rollback when no account is stored, a partial failure keeping the source and restoring the active account, two runs of the same import storing one set, a newer hold replacing an older one, and a lock during the lookups storing nothing.recoveryPhrase.test.tscovers 12 and 24 words, stray case and whitespace, a failed checksum, and a valid 15-word phrase being refused.secretSources.test.tscovers removing a record, moving the pin, and an unknown id.yarn test(5555 passed, 14 skipped) andtsc --noEmitpass.Checklist
PR structure
Testing
Release