{"record":{"id":"3b7a400a4c5233d3","repo":"tinyhumansai/openhuman","slug":"recovery-phrase-is-required","errorCode":null,"errorMessage":"Recovery phrase is required.","messagePattern":"Recovery phrase is required\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/features/wallet/setupLocalWalletFromMnemonic.ts","lineNumber":22,"sourceCode":"  deriveWalletAccountsFromMnemonic,\n  type WalletSetupSource,\n} from '../../utils/cryptoKeys';\nimport { openhumanEncryptSecret } from '../../utils/tauriCommands/auth';\n\nexport async function persistLocalWalletFromMnemonic(args: {\n  mnemonic: string;\n  source: WalletSetupSource;\n  setEncryptionKey: (value: string | null) => Promise<void>;\n  /**\n   * Set to `true` only when the user has explicitly confirmed wallet replacement\n   * through the double-confirmation dialog. Defaults to `false`.\n   */\n  force?: boolean;\n}): Promise<void> {\n  const { mnemonic, source, setEncryptionKey, force } = args;\n  const words = mnemonic.trim().split(/\\s+/).filter(Boolean);\n  if (words.length === 0) {\n    throw new Error('Recovery phrase is required.');\n  }\n  const normalizedMnemonic = words.join(' ');\n  const aesKey = deriveAesKeyFromMnemonic(normalizedMnemonic);\n  const encryptedMnemonic = (await openhumanEncryptSecret(normalizedMnemonic)).result?.trim();\n  if (!encryptedMnemonic) {\n    throw new Error('Failed to secure recovery phrase. Please try again.');\n  }\n\n  await setEncryptionKey(aesKey);\n  await setupLocalWallet({\n    consentGranted: true,\n    source,\n    mnemonicWordCount: words.length,\n    encryptedMnemonic,\n    accounts: deriveWalletAccountsFromMnemonic(normalizedMnemonic),\n    force,\n  });\n}","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/features/wallet/setupLocalWalletFromMnemonic.ts#L4-L40","documentation":"setupLocalWalletFromMnemonic() drives local-wallet creation from a user-entered recovery phrase. After trimming and whitespace-splitting the mnemonic, if zero words remain it throws 'Recovery phrase is required.' — pure input validation before any crypto derivation (deriveAesKeyFromMnemonic, openhumanEncryptSecret) or RPC runs.","triggerScenarios":"Calling the setup flow with an empty string, a whitespace-only string, or a value whose split/filter(Boolean) yields []. Typical UI cause: the submit handler firing before the textarea state binds, or the continue button enabled with an empty field.","commonSituations":"Form state desync (controlled textarea not wired), paste handler stripping content, autocomplete/password managers clearing the field, or an import flow handing an empty mnemonic through.","solutions":["Disable the submit/continue button until words.length >= 12 (standard BIP-39 length) so the error is unreachable.","Validate in the UI: trim input, count words, show inline 'enter your recovery phrase' feedback before calling the setup function.","If importing programmatically, assert the mnemonic is a non-empty string before invoking.","Check the form component for accidental state resets between screen steps."],"exampleFix":"// before\nconst canSubmit = true; // always enabled\nawait setupLocalWalletFromMnemonic({ mnemonic, ... });\n\n// after\nconst words = mnemonic.trim().split(/\\s+/).filter(Boolean);\nconst canSubmit = words.length === 12 || words.length === 24;\nif (!canSubmit) return setFormError('Enter your full recovery phrase.');\nawait setupLocalWalletFromMnemonic({ mnemonic, ... });","handlingStrategy":"validation","validationCode":"const words = mnemonic.trim().split(/\\s+/).filter(Boolean);\nif (words.length < 12) {\n  return setFormError('Enter your full recovery phrase (12 or 24 words).');\n}","typeGuard":"function isNonEmptyMnemonic(v: string): boolean {\n  return v.trim().split(/\\s+/).filter(Boolean).length >= 12;\n}","tryCatchPattern":"try {\n  await setupLocalWalletFromMnemonic(args);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Recovery phrase is required.') {\n    focusMnemonicField('Enter your recovery phrase to continue.');\n    return;\n  }\n  throw err;\n}","preventionTips":["Disable submit until word count is 12 or 24.","Trim and collapse whitespace on paste before counting words.","Never call wallet setup with programmatic imports that have not been length-checked."],"tags":["wallet","mnemonic","validation","form","input"],"backgroundTag":"missing-required-argument","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}