diff --git a/v1/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/DropdownMenuOverlay.tsx b/v1/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/DropdownMenuOverlay.tsx index 43292f8246..d13a77ab10 100644 --- a/v1/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/DropdownMenuOverlay.tsx +++ b/v1/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/DropdownMenuOverlay.tsx @@ -34,9 +34,8 @@ import { getBackgroundStorage, setBackgroundStorage } from '@lib/scripts/backgro import { useBackgroundServiceAPIContext } from '@providers'; import { WarningModal } from '@src/views/browser-view/components'; import { useTranslation } from 'react-i18next'; -import { useCurrentWallet, useWalletManager, useLMP } from '@hooks'; +import { useCurrentWallet, useWalletManager } from '@hooks'; import { useCurrentBlockchain } from '@src/multichain'; -import { AddNewMidnightWalletLink } from './components/AddNewMidnightWalletLink'; interface Props extends MenuProps { isPopup?: boolean; @@ -61,10 +60,8 @@ export const DropdownMenuOverlay: VFC = ({ const { walletRepository } = useWalletManager(); const currentWallet = useCurrentWallet(); const wallets = useObservable(walletRepository.wallets$); - const { midnightWallets } = useLMP(); const sharedWalletsEnabled = posthog?.isFeatureFlagEnabled('shared-wallets'); - const midnightWalletsEnabled = posthog?.isFeatureFlagEnabled('midnight-wallets'); const [currentSection, setCurrentSection] = useState(Sections.Main); const { environmentName, setManageAccountsWallet, walletType, isSharedWallet } = useWalletStore(); const { blockchain } = useCurrentBlockchain(); @@ -140,7 +137,6 @@ export const DropdownMenuOverlay: VFC = ({ !isBitcoinWallet && wallets?.some((w) => w.type === WalletType.Script && w.ownSigners[0].walletId === currentWallet?.walletId); const showAddSharedWalletLink = sharedWalletsEnabled && !isSharedWallet && !hasLinkedSharedWallet; - const showAddMidnightWalletLink = midnightWalletsEnabled && midnightWallets && midnightWallets.length === 0; const handleNamiModeChange = async (activated: boolean) => { const mode = activated ? 'nami' : 'lace'; @@ -195,7 +191,6 @@ export const DropdownMenuOverlay: VFC = ({ )} {!isBitcoinWallet && showAddSharedWalletLink && } - {showAddMidnightWalletLink && } {!isBitcoinWallet && } diff --git a/v1/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/AddNewMidnightWalletLink.tsx b/v1/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/AddNewMidnightWalletLink.tsx deleted file mode 100644 index e427e69d97..0000000000 --- a/v1/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/AddNewMidnightWalletLink.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { useCallback } from 'react'; -import { Menu } from 'antd'; -import styles from '../DropdownMenuOverlay.module.scss'; -import { PostHogAction } from '@lace/common'; -import { useTranslation } from 'react-i18next'; -import { useLMP } from '@hooks'; - -interface Props { - sendAnalyticsEvent?: (event: PostHogAction) => void; -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const AddNewMidnightWalletLink = (_: Props): React.ReactElement => { - const { t } = useTranslation(); - const { switchToLMP } = useLMP(); - const onClick = useCallback(() => { - switchToLMP(); - // TODO: send analytics event - }, [switchToLMP]); - - return ( - - {t('browserView.sideMenu.links.addMidnightWallet')} - - ); -}; diff --git a/v1/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/AddNewWalletLink.tsx b/v1/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/AddNewWalletLink.tsx index 89415dcbe3..bb4a41cc98 100644 --- a/v1/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/AddNewWalletLink.tsx +++ b/v1/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/AddNewWalletLink.tsx @@ -8,6 +8,7 @@ import { useBackgroundServiceAPIContext } from '@providers'; import { BrowserViewSections } from '@lib/scripts/types'; import { useBackgroundPage } from '@providers/BackgroundPageProvider'; import { PostHogAction } from '@lace/common'; +import { cameFromLmpStorage } from '@src/utils/lmp'; interface Props { isPopup?: boolean; @@ -22,6 +23,7 @@ export const AddNewWalletLink = ({ isPopup, sendAnalyticsEvent }: Props): React. const openNewWallet = () => { sendAnalyticsEvent(PostHogAction.UserWalletProfileAddNewWalletClick); + cameFromLmpStorage.clear(); if (isPopup) { backgroundServices.handleOpenBrowser({ section: BrowserViewSections.NEW_WALLET }); } else { diff --git a/v1/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/UserInfo.tsx b/v1/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/UserInfo.tsx index cc6c72514d..bd12db9ec4 100644 --- a/v1/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/UserInfo.tsx +++ b/v1/apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/UserInfo.tsx @@ -19,7 +19,6 @@ import { Separator } from './Separator'; import { getUiWalletType } from '@src/utils/get-ui-wallet-type'; import { isScriptWallet } from '@lace/core'; import { useCurrentBlockchain } from '@src/multichain'; -import { LmpBundleWallet } from '@src/utils/lmp'; const ADRESS_FIRST_PART_LENGTH = 10; const ADRESS_LAST_PART_LENGTH = 5; @@ -196,14 +195,14 @@ export const UserInfo = ({ ); const renderLmpWallet = useCallback( - ({ walletId, walletName }: LmpBundleWallet, isLast: boolean) => ( + ({ walletId, walletName }: Wallet.LmpBundleWallet, isLast: boolean) => (
switchToLMP()} type={'hot'} profile={{ customProfileComponent: ( diff --git a/v1/apps/browser-extension-wallet/src/hooks/useLMP.ts b/v1/apps/browser-extension-wallet/src/hooks/useLMP.ts index e1c6b36f77..bc57f533e6 100644 --- a/v1/apps/browser-extension-wallet/src/hooks/useLMP.ts +++ b/v1/apps/browser-extension-wallet/src/hooks/useLMP.ts @@ -1,6 +1,13 @@ import { consumeRemoteApi } from '@cardano-sdk/web-extension'; import { logger, useObservable } from '@lace/common'; -import { APP_MODE, bundleAppApiProps, lmpApiBaseChannel, LmpBundleWallet, lmpModeStorage } from '@src/utils/lmp'; +import { Wallet } from '@lace/cardano'; +import { + APP_MODE, + bundleAppApiProps, + lmpApiBaseChannel, + lmpModeStorage, + onboardingParamsStorage +} from '@src/utils/lmp'; import { runtime } from 'webextension-polyfill'; const lmpApi = consumeRemoteApi( @@ -11,18 +18,33 @@ const lmpApi = consumeRemoteApi( { logger, runtime } ); -const switchToLMP = (): void => +const navigateToLMP = (): void => { + if (window.location.pathname.startsWith('/popup.html')) { + chrome.tabs.create({ url: '/tab.html' }); + } else { + window.location.href = '/tab.html'; + } +}; + +const switchToLMP = async (): Promise => { + await lmpModeStorage.set(APP_MODE.LMP); + navigateToLMP(); +}; + +const startMidnightCreate = (): void => + void (async () => { + await onboardingParamsStorage.set({ mode: 'create' }); + await switchToLMP(); + })(); + +const startMidnightRestore = (): void => void (async () => { - await lmpModeStorage.set(APP_MODE.LMP); - if (window.location.pathname.startsWith('/popup.html')) { - chrome.tabs.create({ url: '/tab.html' }); - } else { - window.location.href = '/tab.html'; - } + await onboardingParamsStorage.set({ mode: 'restore' }); + await switchToLMP(); })(); // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export const useLMP = () => { - const midnightWallets = useObservable(lmpApi.wallets$); - return { midnightWallets, switchToLMP }; + const midnightWallets = useObservable(lmpApi.wallets$); + return { midnightWallets, switchToLMP, startMidnightCreate, startMidnightRestore }; }; diff --git a/v1/apps/browser-extension-wallet/src/hooks/useWalletManager.ts b/v1/apps/browser-extension-wallet/src/hooks/useWalletManager.ts index cd1bc02349..6a4f324d53 100644 --- a/v1/apps/browser-extension-wallet/src/hooks/useWalletManager.ts +++ b/v1/apps/browser-extension-wallet/src/hooks/useWalletManager.ts @@ -251,11 +251,7 @@ const first64AsciiBytesToHex = (input: string): string => { return resultBuffer.toString('hex'); }; -const clearBytes = (bytes: Uint8Array) => { - for (let i = 0; i < bytes.length; i++) { - bytes[i] = 0; - } -}; +const { clearBytes } = Wallet.util; const getExtendedAccountPublicKey = async ({ wallet, @@ -1245,14 +1241,8 @@ export const useWalletManager = (): UseWalletManager => { async (wallet: AnyWallet, passphrase: Uint8Array) => { switch (wallet.type) { case WalletType.InMemory: { - const keyMaterialBytes = await Wallet.KeyManagement.emip3decrypt( - Buffer.from(wallet.encryptedSecrets.keyMaterial, 'hex'), - passphrase - ); - const keyMaterialBuffer = Buffer.from(keyMaterialBytes); - const mnemonic = keyMaterialBuffer.toString('utf8').split(' '); + const mnemonic = await Wallet.util.decryptMnemonic(wallet.encryptedSecrets.keyMaterial, passphrase); clearBytes(passphrase); - clearBytes(keyMaterialBytes); return mnemonic; } case WalletType.Ledger: diff --git a/v1/apps/browser-extension-wallet/src/lib/scripts/background/services/lmpService.ts b/v1/apps/browser-extension-wallet/src/lib/scripts/background/services/lmpService.ts index c19f148589..cd38461c74 100644 --- a/v1/apps/browser-extension-wallet/src/lib/scripts/background/services/lmpService.ts +++ b/v1/apps/browser-extension-wallet/src/lib/scripts/background/services/lmpService.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-var-requires */ -import { BundleAppApi, LmpBundleWallet, v1ApiGlobalProperty } from '@src/utils/lmp'; +import { BundleAppApi, v1ApiGlobalProperty } from '@src/utils/lmp'; import { BehaviorSubject, firstValueFrom, map } from 'rxjs'; import { bitcoinWalletManager, walletManager, walletRepository } from '../wallet'; import { AnyBip32Wallet, AnyWallet, InMemoryWallet, WalletType } from '@cardano-sdk/web-extension'; @@ -51,7 +51,7 @@ const api: BundleAppApi = { wallets$: walletRepository.wallets$.pipe( map((wallets) => wallets.map( - (wallet): LmpBundleWallet => ({ + (wallet): Wallet.LmpBundleWallet => ({ walletIcon: isBitcoinWallet(wallet) ? bitcoinLogo : cardanoLogo, walletId: wallet.walletId, walletName: wallet.metadata.name, diff --git a/v1/apps/browser-extension-wallet/src/utils/lmp.ts b/v1/apps/browser-extension-wallet/src/utils/lmp.ts index bc69437056..0a4bd6f3fb 100644 --- a/v1/apps/browser-extension-wallet/src/utils/lmp.ts +++ b/v1/apps/browser-extension-wallet/src/utils/lmp.ts @@ -1,22 +1,11 @@ -// mostly duplicated in v1 and lmp module, could be a shared library -import { RemoteApiProperties, RemoteApiPropertyType, WalletType } from '@cardano-sdk/web-extension'; +import { RemoteApiProperties, RemoteApiPropertyType } from '@cardano-sdk/web-extension'; +import { Wallet } from '@lace/cardano'; import { Observable } from 'rxjs'; import { storage } from 'webextension-polyfill'; import { Language } from '@lace/translation'; -export type BlockchainName = 'Bitcoin' | 'Cardano' | 'Midnight'; - -export type LmpBundleWallet = { - walletId: string; - walletName: string; - walletIcon: string; - encryptedRecoveryPhrase?: string; - blockchain: BlockchainName; - walletType: WalletType; -}; - export type BundleAppApi = { - wallets$: Observable; + wallets$: Observable; activate(walletId: string): Promise; language$: Observable; setLanguage(language: Language): Promise; @@ -27,10 +16,24 @@ export const bundleAppApiProps: RemoteApiProperties = { language$: RemoteApiPropertyType.HotObservable, setLanguage: RemoteApiPropertyType.MethodReturningPromise }; + export const lmpApiBaseChannel = 'bundle-lmp'; export const v1ApiGlobalProperty = 'bundleV1'; export const STORAGE_KEY = { - APP_MODE: 'lace-app-mode' + APP_MODE: 'lace-app-mode', + ONBOARDING_PARAMS: 'lace-lmp-onboarding-params', + CAME_FROM_LMP: 'lace-came-from-lmp' +}; + +export type OnboardingParams = { mode: 'create' } | { mode: 'restore' }; + +export const onboardingParamsStorage = { + set: (params: OnboardingParams): Promise => storage.local.set({ [STORAGE_KEY.ONBOARDING_PARAMS]: params }), + get: async (): Promise => { + const result = await storage.local.get(STORAGE_KEY.ONBOARDING_PARAMS); + return result[STORAGE_KEY.ONBOARDING_PARAMS] as OnboardingParams | undefined; + }, + clear: (): Promise => storage.local.remove(STORAGE_KEY.ONBOARDING_PARAMS) }; export enum APP_MODE { LMP = 'LMP', @@ -40,3 +43,19 @@ export enum APP_MODE { export const lmpModeStorage = { set: (mode: APP_MODE): Promise => storage.local.set({ [STORAGE_KEY.APP_MODE]: mode }) }; + +export const cameFromLmpStorage = { + set: (): Promise => storage.local.set({ [STORAGE_KEY.CAME_FROM_LMP]: true }), + get: async (): Promise => { + const result = await storage.local.get(STORAGE_KEY.CAME_FROM_LMP); + return !!result[STORAGE_KEY.CAME_FROM_LMP]; + }, + clear: (): Promise => storage.local.remove(STORAGE_KEY.CAME_FROM_LMP) +}; + +export const decryptMnemonic = async (encryptedRecoveryPhrase: string, password: string): Promise => { + const passphrase = new Uint8Array(Buffer.from(password)); + const mnemonic = await Wallet.util.decryptMnemonic(encryptedRecoveryPhrase, passphrase); + Wallet.util.clearBytes(passphrase); + return mnemonic; +}; diff --git a/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/MultiWallet.tsx b/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/MultiWallet.tsx index 5bff58ab85..0351ffa760 100644 --- a/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/MultiWallet.tsx +++ b/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/MultiWallet.tsx @@ -3,7 +3,7 @@ import { NavigationButton } from '@lace/common'; import { WalletSetupConfirmationDialogProvider, WalletSetupFlow, WalletSetupFlowProvider } from '@lace/core'; import { useBackgroundPage } from '@providers/BackgroundPageProvider'; import { walletRoutePaths } from '@routes'; -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import styles from './MultiWallet.module.scss'; import { WalletOnboardingFlows } from './WalletOnboardingFlows'; @@ -12,21 +12,39 @@ import { Home } from './Home'; import { usePostHogClientContext } from '@providers/PostHogClientProvider'; import { WalletSetupLayout } from '@views/browser/components'; import { Portal } from '@views/browser/features/wallet-setup/components/Portal'; +import { APP_MODE, cameFromLmpStorage, lmpModeStorage } from '@src/utils/lmp'; export const MultiWallet = (): JSX.Element => { const history = useHistory(); const posthogClient = usePostHogClientContext(); const { page, setBackgroundPage } = useBackgroundPage(); + const [cameFromLMP, setCameFromLMP] = useState(false); + + useEffect(() => { + const checkCameFromLMP = async () => { + const result = await cameFromLmpStorage.get(); + if (result) { + setCameFromLMP(true); + } + }; + void checkCameFromLMP(); + }, []); const handleOnCancel = useCallback( (withConfirmationDialog: (callback: () => void) => () => void) => { - withConfirmationDialog(() => { + withConfirmationDialog(async () => { + if (cameFromLMP) { + await cameFromLmpStorage.clear(); + await lmpModeStorage.set(APP_MODE.LMP); + window.location.href = '/tab.html'; + return; + } setBackgroundPage(); history.push(page); window.location.reload(); })(); }, - [history, page, setBackgroundPage] + [history, page, setBackgroundPage, cameFromLMP] ); return ( diff --git a/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/context.tsx b/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/context.tsx index ac49e8320b..8a846a1197 100644 --- a/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/context.tsx +++ b/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/context.tsx @@ -1,5 +1,5 @@ /* eslint-disable unicorn/no-null, complexity */ -import { CreateWalletParams, useLocalStorage } from '@hooks'; +import { CreateWalletParams, useLocalStorage, useLMP } from '@hooks'; import { Wallet } from '@lace/cardano'; import { walletRoutePaths } from '@routes'; import React, { createContext, useCallback, useContext, useMemo, useState } from 'react'; @@ -10,10 +10,11 @@ import { WalletCreateStep } from './types'; import { RecoveryMethod } from '../types'; import { usePostHogClientContext } from '@providers/PostHogClientProvider'; import { PublicPgpKeyData } from '@src/types'; -import { Blockchain, AnyWallet, WalletConflictError, WalletType } from '@cardano-sdk/web-extension'; +import { Blockchain, WalletConflictError, WalletType } from '@cardano-sdk/web-extension'; import { useObservable } from '@lace/common'; import { walletRepository } from '@lib/wallet-api-ui'; import { getWalletBlockchain } from './get-wallet-blockchain'; +import { WalletWithMnemonic } from '@lace/core'; type OnNameChange = (state: { name: string }) => void; interface PgpValidation { @@ -35,13 +36,12 @@ interface State { setPgpValidation: React.Dispatch>; selectedBlockchain: Blockchain; setSelectedBlockchain: React.Dispatch>; - walletToReuse: AnyWallet | null; - setWalletToReuse: React.Dispatch< - React.SetStateAction | null> - >; + walletToReuse: WalletWithMnemonic | null; + setWalletToReuse: React.Dispatch>; showRecoveryPhraseError: () => void; setMnemonic: (mnemonic: string[]) => void; - nonSelectedBlockchainWallets: AnyWallet[] | undefined; + nonSelectedBlockchainWallets: WalletWithMnemonic[] | undefined; + setStep: React.Dispatch>; } interface Props { @@ -75,10 +75,9 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => const [, { updateLocalStorage: setShowWalletConflictError }] = useLocalStorage('showWalletConflictError', false); const [selectedBlockchain, setSelectedBlockchain] = useState('Cardano'); const [step, setStep] = useState(WalletCreateStep.SelectBlockchain); - const [walletToReuse, setWalletToReuse] = useState | null>( - null - ); + const [walletToReuse, setWalletToReuse] = useState(null); const [recoveryMethod, setRecoveryMethod] = useState('mnemonic'); + const { midnightWallets } = useLMP(); const [pgpInfo, setPgpInfo] = useState(INITIAL_PGP_STATE); const [pgpValidation, setPgpValidation] = useState({ error: null, success: null }); const wallets = useObservable(walletRepository.wallets$); @@ -98,15 +97,23 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => console.error('finalizeBitcoinWalletCreation'); }, []); - const nonSelectedBlockchainWallets = useMemo( - () => + const nonSelectedBlockchainWallets = useMemo((): WalletWithMnemonic[] | undefined => { + // Filter v1 wallets that are not the selected blockchain and are in-memory (have recovery phrase) + const v1Wallets = wallets?.filter( (wallet) => getWalletBlockchain(wallet).toLowerCase() !== selectedBlockchain.toLowerCase() && wallet.type === WalletType.InMemory - ), - [selectedBlockchain, wallets] - ); + ) ?? []; + + // Include Midnight wallets that have encrypted recovery phrase + const filteredMidnightWallets: WalletWithMnemonic[] = midnightWallets + ? midnightWallets.filter((w) => w.encryptedRecoveryPhrase) + : []; + + const combined = [...v1Wallets, ...filteredMidnightWallets]; + return combined.length > 0 ? combined : undefined; + }, [selectedBlockchain, wallets, midnightWallets]); const showRecoveryPhraseError = useCallback(() => setStep(WalletCreateStep.RecoveryPhraseError), [setStep]); const setMnemonic = useCallback( @@ -156,6 +163,7 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => [recoveryMethod, finalizeWalletCreation, finalizeBitcoinWalletCreation, history, setShowWalletConflictError] ); + // eslint-disable-next-line max-statements, sonarjs/cognitive-complexity const next: State['next'] = useCallback( async (state) => { if (state) { @@ -163,6 +171,7 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => } switch (step) { case WalletCreateStep.SelectBlockchain: { + // Note: Midnight redirects to v2 directly from SelectBlockchain component setStep( paperWalletEnabled ? WalletCreateStep.ChooseRecoveryMethod : WalletCreateStep.RecoveryPhraseWriteDown ); @@ -170,11 +179,11 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => } case WalletCreateStep.ChooseRecoveryMethod: { if (recoveryMethod === 'mnemonic' || recoveryMethod === 'mnemonic-bitcoin') { - const nextStep = - nonSelectedBlockchainWallets.length > 0 - ? WalletCreateStep.ReuseRecoveryPhrase - : WalletCreateStep.RecoveryPhraseWriteDown; - setStep(nextStep); + if (nonSelectedBlockchainWallets && nonSelectedBlockchainWallets.length > 0) { + setStep(WalletCreateStep.ReuseRecoveryPhrase); + } else { + setStep(WalletCreateStep.RecoveryPhraseWriteDown); + } break; } setStep(WalletCreateStep.SecurePaperWallet); @@ -220,68 +229,58 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => setFormDirty, paperWalletEnabled, recoveryMethod, - nonSelectedBlockchainWallets?.length, + nonSelectedBlockchainWallets, handleSetupStep, finalizeWalletCreation, history ] ); + // eslint-disable-next-line max-statements const back = useCallback(() => { switch (step) { - case WalletCreateStep.SelectBlockchain: { + case WalletCreateStep.SelectBlockchain: setFormDirty(false); history.push(walletRoutePaths.newWallet.root); break; - } - case WalletCreateStep.ChooseRecoveryMethod: { + case WalletCreateStep.ChooseRecoveryMethod: setFormDirty(false); setStep(WalletCreateStep.SelectBlockchain); break; - } - case WalletCreateStep.RecoveryPhraseWriteDown: { + case WalletCreateStep.RecoveryPhraseWriteDown: setFormDirty(false); - paperWalletEnabled - ? setStep(WalletCreateStep.ChooseRecoveryMethod) - : history.push(walletRoutePaths.newWallet.root); + setStep(paperWalletEnabled ? WalletCreateStep.ChooseRecoveryMethod : undefined); + if (!paperWalletEnabled) history.push(walletRoutePaths.newWallet.root); break; - } - case WalletCreateStep.ReuseRecoveryPhrase: { + case WalletCreateStep.ReuseRecoveryPhrase: setStep(WalletCreateStep.RecoveryPhraseWriteDown); break; - } - case WalletCreateStep.SecurePaperWallet: { + case WalletCreateStep.SecurePaperWallet: setStep(WalletCreateStep.ChooseRecoveryMethod); break; - } - case WalletCreateStep.EnterWalletPassword: { - setStep(WalletCreateStep.ReuseRecoveryPhrase); - break; - } - case WalletCreateStep.RecoveryPhraseError: { + case WalletCreateStep.EnterWalletPassword: + case WalletCreateStep.RecoveryPhraseError: setStep(WalletCreateStep.ReuseRecoveryPhrase); break; - } - case WalletCreateStep.RecoveryPhraseInput: { + case WalletCreateStep.RecoveryPhraseInput: setFormDirty(false); generateMnemonic(); setStep(WalletCreateStep.RecoveryPhraseWriteDown); break; - } - case WalletCreateStep.Setup: { - if (recoveryMethod === 'mnemonic') { - setStep(WalletCreateStep.RecoveryPhraseInput); - break; + case WalletCreateStep.Setup: + if (walletToReuse) { + setStep(WalletCreateStep.EnterWalletPassword); + } else { + setStep( + recoveryMethod === 'mnemonic' ? WalletCreateStep.RecoveryPhraseInput : WalletCreateStep.SecurePaperWallet + ); } - setStep(WalletCreateStep.SecurePaperWallet); break; - } - case WalletCreateStep.SavePaperWallet: { + case WalletCreateStep.SavePaperWallet: setStep(WalletCreateStep.Setup); break; - } } - }, [generateMnemonic, history, setFormDirty, step, recoveryMethod, paperWalletEnabled]); + }, [generateMnemonic, history, setFormDirty, step, recoveryMethod, paperWalletEnabled, walletToReuse]); const state = useMemo( (): State => ({ @@ -302,7 +301,8 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => walletToReuse, showRecoveryPhraseError, setMnemonic, - nonSelectedBlockchainWallets + nonSelectedBlockchainWallets, + setStep }), [ back, diff --git a/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/ChooseRecoveryMethod.tsx b/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/ChooseRecoveryMethod.tsx index 933a7b7def..ba57282c06 100644 --- a/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/ChooseRecoveryMethod.tsx +++ b/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/ChooseRecoveryMethod.tsx @@ -179,6 +179,8 @@ export const ChooseRecoveryMethod: VFC = () => { next(); }; + // Paper wallet option only available for Cardano + // (Midnight redirects to v2 immediately from SelectBlockchain, never reaches this step) return ( ; + +const isLmpBundleWallet = (wallet: unknown): wallet is Wallet.LmpBundleWallet => + wallet !== null && typeof wallet === 'object' && 'blockchain' in wallet; + +const getWalletDisplayName = (wallet: WalletWithMnemonic | null): string | undefined => + wallet ? (isLmpBundleWallet(wallet) ? wallet.walletName : (wallet as CardanoWallet).metadata?.name) : undefined; + export const EnterWalletPassword = (): ReactElement => { const { back, walletToReuse, setMnemonic, next, showRecoveryPhraseError } = useCreateWallet(); const { getMnemonicForWallet } = useWalletManager(); @@ -15,7 +25,11 @@ export const EnterWalletPassword = (): ReactElement => { const handleSubmit = async (password: string) => { try { - const mnemonic = await getMnemonicForWallet(walletToReuse, Buffer.from(password)); + const mnemonic = + isLmpBundleWallet(walletToReuse) && walletToReuse.encryptedRecoveryPhrase + ? await decryptMnemonic(walletToReuse.encryptedRecoveryPhrase, password) + : await getMnemonicForWallet(walletToReuse as CardanoWallet, new Uint8Array(Buffer.from(password))); + if (mnemonic.length < SUPPORTED_PASSPHRASE_LENGTH) { showRecoveryPhraseError(); return; @@ -33,13 +47,13 @@ export const EnterWalletPassword = (): ReactElement => { setErrorMessage(t('walletSetup.reuseRecoveryPhrase.walletAlreadyExists')); return; } - setErrorMessage(error.message); + setErrorMessage(error instanceof Error ? error.message : String(error)); } }; return ( { - const { back, next, setWalletToReuse, nonSelectedBlockchainWallets } = useCreateWallet(); + const { back, next, setWalletToReuse, nonSelectedBlockchainWallets, setStep } = useCreateWallet(); + + const handleBack = useCallback(() => { + setStep(WalletCreateStep.ChooseRecoveryMethod); + }, [setStep]); return ( ); diff --git a/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/SelectBlockchain.tsx b/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/SelectBlockchain.tsx index 45d9a0ee95..a0b7095e4a 100644 --- a/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/SelectBlockchain.tsx +++ b/v1/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/SelectBlockchain.tsx @@ -6,14 +6,21 @@ import { usePostHogClientContext } from '@providers/PostHogClientProvider'; import { logger } from '@lace/common'; import { useWalletOnboarding } from '../../walletOnboardingContext'; import { useAnalyticsContext } from '@providers/AnalyticsProvider'; +import { useLMP } from '@hooks'; +import { useTranslation } from 'react-i18next'; export const SelectBlockchain = (): ReactElement => { + const { t } = useTranslation(); const posthog = usePostHogClientContext(); const { back, next, selectedBlockchain, setSelectedBlockchain } = useCreateWallet(); const [isBitcoinDialogOpen, setIsBitcoinDialogOpen] = useState(false); const bitcoinWalletsEnabled = posthog?.isFeatureFlagEnabled('bitcoin-wallets'); + const midnightWalletsEnabled = posthog?.isFeatureFlagEnabled('midnight-wallets'); const analytics = useAnalyticsContext(); const { postHogActions } = useWalletOnboarding(); + const { midnightWallets, startMidnightCreate } = useLMP(); + + const hasMidnightWallet = midnightWallets && midnightWallets.length > 0; // eslint-disable-next-line consistent-return const handleNext = () => { @@ -32,6 +39,14 @@ export const SelectBlockchain = (): ReactElement => { doNext().catch((error) => logger.error('Error in next selecting blockchain', error)); }; + const handleMidnightSelect = () => { + // Redirect to v2 immediately - v2 handles the reuse step + startMidnightCreate(); + analytics + .sendEventToPostHog(postHogActions.create.CHOSE_BLOCKCHAIN_CLICK, { blockchain: 'Midnight' }) + .catch((error) => logger.error('Error sending analytics', error)); + }; + return ( <> { selectedBlockchain={selectedBlockchain} setSelectedBlockchain={setSelectedBlockchain} showBitcoinOption={bitcoinWalletsEnabled} + showMidnightOption={midnightWalletsEnabled} + midnightDisabled={hasMidnightWallet} + midnightDisabledReason={t('core.WalletSetupSelectBlockchain.midnight.disabledReason')} + onMidnightSelect={handleMidnightSelect} /> { + const { t } = useTranslation(); const posthog = usePostHogClientContext(); const { back, next, selectedBlockchain, setSelectedBlockchain } = useRestoreWallet(); const [isBitcoinDialogOpen, setIsBitcoinDialogOpen] = useState(false); const bitcoinWalletsEnabled = posthog?.isFeatureFlagEnabled('bitcoin-wallets'); + const midnightWalletsEnabled = posthog?.isFeatureFlagEnabled('midnight-wallets'); const analytics = useAnalyticsContext(); const { postHogActions } = useWalletOnboarding(); + const { midnightWallets, startMidnightRestore } = useLMP(); + + const hasMidnightWallet = midnightWallets && midnightWallets.length > 0; // eslint-disable-next-line consistent-return const handleNext = () => { @@ -32,6 +39,10 @@ export const SelectBlockchain = (): ReactElement => { doNext().catch((error) => logger.error('Error in next selecting blockchain', error)); }; + const handleMidnightSelect = () => { + startMidnightRestore(); + }; + return ( <> { selectedBlockchain={selectedBlockchain} setSelectedBlockchain={setSelectedBlockchain} showBitcoinOption={bitcoinWalletsEnabled} + showMidnightOption={midnightWalletsEnabled} + midnightDisabled={hasMidnightWallet} + midnightDisabledReason={t('core.WalletSetupSelectBlockchain.midnight.disabledReason')} + onMidnightSelect={handleMidnightSelect} /> { + for (let i = 0; i < bytes.length; i++) { + bytes[i] = 0; + } +}; + +export const decryptMnemonic = async (encryptedKeyMaterial: string, passphrase: Uint8Array): Promise => { + const keyMaterialBytes = await KeyManagement.emip3decrypt( + new Uint8Array(Buffer.from(encryptedKeyMaterial, 'hex')), + passphrase + ); + const mnemonic = Buffer.from(keyMaterialBytes).toString('utf8').split(' '); + clearBytes(keyMaterialBytes); + return mnemonic; +}; diff --git a/v1/packages/core/src/ui/assets/icons/midnight.svg b/v1/packages/core/src/ui/assets/icons/midnight.svg new file mode 100644 index 0000000000..2fe346ed9c --- /dev/null +++ b/v1/packages/core/src/ui/assets/icons/midnight.svg @@ -0,0 +1,4 @@ + + + + diff --git a/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupReuseMnemonicStep.tsx b/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupReuseMnemonicStep.tsx index ff8771d6cf..d0171b62dc 100644 --- a/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupReuseMnemonicStep.tsx +++ b/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupReuseMnemonicStep.tsx @@ -1,24 +1,30 @@ import React, { ReactElement, useEffect, useState } from 'react'; import { WalletSetupStepLayoutRevamp } from './WalletSetupStepLayoutRevamp'; import { WalletTimelineSteps } from '@ui/components/WalletSetup'; -import { Box, Flex, Select, Text } from '@input-output-hk/lace-ui-toolkit'; +import { Box, Flex, Select, Text, TextLink } from '@input-output-hk/lace-ui-toolkit'; import { AnyWallet } from '@cardano-sdk/web-extension'; import { Wallet } from '@lace/cardano'; import { useTranslation } from 'react-i18next'; +type CardanoWallet = AnyWallet; +export type WalletWithMnemonic = CardanoWallet | Wallet.LmpBundleWallet; + +const getWalletName = (wallet: WalletWithMnemonic): string => + 'metadata' in wallet ? wallet.metadata.name : wallet.walletName; + type WalletSetupReuseMnemonicStepProps = { - wallets: AnyWallet[]; - setWalletToReuse: React.Dispatch< - React.SetStateAction | undefined> - >; - onBack: () => void; - onNext: () => void; + wallets: WalletWithMnemonic[]; + setWalletToReuse: React.Dispatch>; + onSkip: () => void; + onReuse: () => void; + onBack?: () => void; }; export const WalletSetupReuseMnemonicStep = ({ wallets = [], setWalletToReuse, - onNext, + onReuse, + onSkip, onBack }: WalletSetupReuseMnemonicStepProps): ReactElement => { const [selectedWallet, setSelectedWallet] = useState(wallets[0]?.walletId); @@ -40,11 +46,17 @@ export const WalletSetupReuseMnemonicStep = ({ title={t('core.walletSetupReuseRecoveryPhrase.title')} description={t('core.walletSetupReuseRecoveryPhrase.description')} onBack={onBack} - onNext={onNext} - nextLabel={t('core.walletSetupReuseRecoveryPhrase.useSameRecoveryPhrase')} - backLabel={t('core.walletSetupReuseRecoveryPhrase.createNewOne')} + onNext={onReuse} + nextLabel={t('core.walletSetupReuseRecoveryPhrase.reuse')} isNextEnabled={!!selectedWallet} currentTimelineStep={WalletTimelineSteps.RECOVERY_DETAILS} + customAction={ + + } > {t('core.walletSetupReuseRecoveryPhrase.selectWallet')} @@ -59,8 +71,8 @@ export const WalletSetupReuseMnemonicStep = ({ zIndex={1000} fullWidth > - {wallets.map(({ walletId, metadata }) => ( - + {wallets.map((wallet) => ( + ))} diff --git a/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupSelectBlockchain.module.scss b/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupSelectBlockchain.module.scss index 66cc59a839..cb3b45756a 100644 --- a/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupSelectBlockchain.module.scss +++ b/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupSelectBlockchain.module.scss @@ -11,6 +11,16 @@ &.selected { border: 2px solid var(--primary-accent) !important; } + + &.disabled { + cursor: not-allowed; + opacity: 0.7; + background: var(--light-mode-light-grey, var(--dark-mode-dark-grey, #1a1a1a)) !important; + + &:hover { + border: 2px solid var(--light-mode-light-grey-plus, var(--dark-mode-mid-grey, #333333)) !important; + } + } } .icon { @@ -37,6 +47,11 @@ background: var(--primary-gradient); } +.secondaryBadge { + @extend .badge; + background: var(--light-mode-dark-grey, var(--dark-mode-light-grey, #666666)); +} + .badgeText { color: white !important; } diff --git a/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupSelectBlockchain.tsx b/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupSelectBlockchain.tsx index 2ba7d4dd3c..9f0cee639c 100644 --- a/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupSelectBlockchain.tsx +++ b/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupSelectBlockchain.tsx @@ -1,9 +1,10 @@ -import React, { ReactElement } from 'react'; +import React, { ReactElement, useState } from 'react'; import { WalletSetupStepLayoutRevamp } from './WalletSetupStepLayoutRevamp'; import { WalletTimelineSteps } from '@ui/components/WalletSetup'; import { Card, Flex, Text } from '@input-output-hk/lace-ui-toolkit'; import { ReactComponent as CardanoIcon } from '../../assets/icons/cardano.svg'; import { ReactComponent as BitcoinIcon } from '../../assets/icons/bitcoin.svg'; +import { ReactComponent as MidnightIcon } from '../../assets/icons/midnight.svg'; import { Radio } from '@lace/common'; import styles from './WalletSetupSelectBlockchain.module.scss'; import cn from 'classnames'; @@ -11,15 +12,17 @@ import { Blockchain } from '@cardano-sdk/web-extension'; import { useTranslation } from 'react-i18next'; import { TFunction } from 'i18next'; +type BlockchainSelection = Blockchain | 'Midnight'; + interface BlockchainOption { - value: Blockchain; + value: BlockchainSelection; title: string; description: string; icon: React.FC>; testId: string; badge?: { text: string; - type: 'primary' | 'gradient'; + type: 'primary' | 'gradient' | 'secondary'; }; } @@ -35,6 +38,17 @@ const getBlockchainOptions = (t: TFunction): BlockchainOption[] => [ type: 'primary' } }, + { + value: 'Midnight', + title: t('core.WalletSetupSelectBlockchain.midnight'), + description: t('core.WalletSetupSelectBlockchain.midnight.description'), + icon: MidnightIcon, + testId: 'midnight-blockchain-card', + badge: { + text: t('core.WalletSetupSelectBlockchain.newBadge'), + type: 'gradient' + } + }, { value: 'Bitcoin', title: t('core.WalletSetupSelectBlockchain.bitcoin'), @@ -54,6 +68,10 @@ interface WalletSetupSelectBlockchainProps { selectedBlockchain: Blockchain; setSelectedBlockchain: (blockchain: Blockchain) => void; showBitcoinOption?: boolean; + showMidnightOption?: boolean; + midnightDisabled?: boolean; + midnightDisabledReason?: string; + onMidnightSelect?: () => void; } export const WalletSetupSelectBlockchain = ({ @@ -61,37 +79,94 @@ export const WalletSetupSelectBlockchain = ({ next, setSelectedBlockchain, selectedBlockchain, - showBitcoinOption = true + showBitcoinOption = true, + showMidnightOption = false, + midnightDisabled = false, + midnightDisabledReason, + onMidnightSelect }: WalletSetupSelectBlockchainProps): ReactElement => { const { t } = useTranslation(); - const blockchainOptionsToShow = showBitcoinOption - ? getBlockchainOptions(t) - : getBlockchainOptions(t).filter((option) => option.value !== 'Bitcoin'); + const [isMidnightSelected, setIsMidnightSelected] = useState(false); + + const blockchainOptionsToShow = getBlockchainOptions(t).filter((option) => { + if (option.value === 'Bitcoin' && !showBitcoinOption) return false; + if (option.value === 'Midnight' && !showMidnightOption) return false; + return true; + }); + + const isOptionDisabled = (value: string): boolean => value === 'Midnight' && midnightDisabled; + + const getDisabledDescription = (option: BlockchainOption): string => { + if (option.value === 'Midnight' && midnightDisabled && midnightDisabledReason) { + return midnightDisabledReason; + } + return option.description; + }; + + const getDisabledBadge = (option: BlockchainOption): BlockchainOption['badge'] => { + if (option.value === 'Midnight' && midnightDisabled) { + return { + text: t('core.WalletSetupSelectBlockchain.midnight.alreadyHaveWallet'), + type: 'secondary' + }; + } + return option.badge; + }; + + const handleSelect = (value: BlockchainSelection) => { + if (isOptionDisabled(value)) return; + + if (value === 'Midnight') { + setIsMidnightSelected(true); + } else { + setIsMidnightSelected(false); + setSelectedBlockchain(value); + } + }; + + const handleNext = () => { + if (isMidnightSelected && onMidnightSelect) { + onMidnightSelect(); + } else { + next(); + } + }; + + const isSelected = (value: BlockchainSelection): boolean => { + if (value === 'Midnight') return isMidnightSelected; + return !isMidnightSelected && selectedBlockchain === value; + }; return ( - + {blockchainOptionsToShow.map((option) => { const Icon = option.icon; + const disabled = isOptionDisabled(option.value); + const badge = getDisabledBadge(option); + const description = getDisabledDescription(option); + return ( setSelectedBlockchain(option.value)} + onClick={() => handleSelect(option.value)} className={cn(styles.blockchainCard, { - [styles.selected]: selectedBlockchain === option.value + [styles.selected]: isSelected(option.value), + [styles.disabled]: disabled })} > - + setSelectedBlockchain(option.value)} + checked={isSelected(option.value)} + onChange={() => handleSelect(option.value)} + disabled={disabled} data-testid={`${option.value.toLowerCase()}-option-radio-button`} /> @@ -99,22 +174,25 @@ export const WalletSetupSelectBlockchain = ({ {option.title} -
- - {option.badge?.text} - -
+ + {badge.text} + +
+ )} - {option.description} + {description}
diff --git a/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupStepLayoutRevamp.module.scss b/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupStepLayoutRevamp.module.scss index 1f6f1d7071..a60d0fe1f0 100644 --- a/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupStepLayoutRevamp.module.scss +++ b/v1/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupStepLayoutRevamp.module.scss @@ -22,10 +22,10 @@ .container { flex: 1; height: 100%; - padding: size_unit(5); + padding: size_unit(2.5) size_unit(5); display: flex; flex-direction: column; - gap: size_unit(2); + gap: size_unit(1.5); .header { display: flex; @@ -48,6 +48,7 @@ .content { flex: 1; + overflow: auto; @include scroll-bar-style; } diff --git a/v1/packages/core/src/ui/components/WalletSetupRevamp/index.ts b/v1/packages/core/src/ui/components/WalletSetupRevamp/index.ts index c7bde9b1e8..b294832d31 100644 --- a/v1/packages/core/src/ui/components/WalletSetupRevamp/index.ts +++ b/v1/packages/core/src/ui/components/WalletSetupRevamp/index.ts @@ -12,5 +12,6 @@ export { WalletSetupConnectHardwareWalletStepRevamp } from './WalletSetupConnect export { WalletSetupHWCreationStep } from './WalletSetupHWCreationStep'; export { WalletSetupSelectBlockchain } from './WalletSetupSelectBlockchain'; export { WalletSetupReuseMnemonicStep } from './WalletSetupReuseMnemonicStep'; +export type { WalletWithMnemonic } from './WalletSetupReuseMnemonicStep'; export { WalletSetupEnterPasswordStep } from './WalletSetupEnterPasswordStep'; export { WalletSetupMnemonicErrorStep } from './WalletSetupMnemonicErrorStep'; diff --git a/v1/packages/e2e-tests/src/assert/onboarding/ReuseRecoveryPhrasePageAssert.ts b/v1/packages/e2e-tests/src/assert/onboarding/ReuseRecoveryPhrasePageAssert.ts index acbb06e632..e83e4cc7ae 100644 --- a/v1/packages/e2e-tests/src/assert/onboarding/ReuseRecoveryPhrasePageAssert.ts +++ b/v1/packages/e2e-tests/src/assert/onboarding/ReuseRecoveryPhrasePageAssert.ts @@ -16,13 +16,11 @@ class ReuseRecoveryPhrasePageAssert extends OnboardingCommonAssert { await ReuseRecoveryPhrasePage.walletSelectInput.waitForDisplayed(); await ReuseRecoveryPhrasePage.backButton.waitForDisplayed(); - expect(await ReuseRecoveryPhrasePage.backButton.getText()).to.equal( - await t('core.walletSetupReuseRecoveryPhrase.createNewOne') - ); + expect(await ReuseRecoveryPhrasePage.backButton.getText()).to.equal(await t('core.walletSetupStep.back')); await ReuseRecoveryPhrasePage.nextButton.waitForDisplayed(); expect(await ReuseRecoveryPhrasePage.nextButton.getText()).to.equal( - await t('core.walletSetupReuseRecoveryPhrase.useSameRecoveryPhrase') + await t('core.walletSetupReuseRecoveryPhrase.reuse') ); } diff --git a/v1/packages/translation/src/lib/translations/core/en.json b/v1/packages/translation/src/lib/translations/core/en.json index 931c1a60e5..44cc1ec9d3 100644 --- a/v1/packages/translation/src/lib/translations/core/en.json +++ b/v1/packages/translation/src/lib/translations/core/en.json @@ -679,15 +679,19 @@ "core.WalletSetupSelectBlockchain.title": "Select a Blockchain", "core.WalletSetupSelectBlockchain.cardano": "Cardano", "core.WalletSetupSelectBlockchain.bitcoin": "Bitcoin", - "core.WalletSetupSelectBlockchain.description": "Choose between Cardano or Bitcoin wallet. Once onboarded, you can create additional wallets from both later.", + "core.WalletSetupSelectBlockchain.midnight": "Midnight", + "core.WalletSetupSelectBlockchain.description": "Choose between Cardano, Midnight or Bitcoin wallets. Once onboarded, you can create additional wallets from both later.", "core.WalletSetupSelectBlockchain.cardano.description": "Allows you to manage your Cardano (ADA) and other native assets on the Cardano blockchain.", "core.WalletSetupSelectBlockchain.bitcoin.description": "Allows you to manage your Bitcoin (BTC) and other assets on the Bitcoin blockchain", + "core.WalletSetupSelectBlockchain.midnight.description": "Allows you interact with the Midnight Network, a privacy-focused blockchain designed to protect sensitive data.", + "core.WalletSetupSelectBlockchain.midnight.alreadyHaveWallet": "You already have a Midnight wallet", + "core.WalletSetupSelectBlockchain.midnight.disabledReason": "At this moment only one Midnight wallet is supported. Please remove the existing wallet if you want to create a new one.", "core.WalletSetupSelectBlockchain.defaultBadge": "Default", "core.WalletSetupSelectBlockchain.newBadge": "New", "core.walletSetupReuseRecoveryPhrase.title": "Reuse your Recovery Phrase?", "core.walletSetupReuseRecoveryPhrase.description": "Do you wish to use the same passphrase as the wallet below, or wish to create a new one?", - "core.walletSetupReuseRecoveryPhrase.useSameRecoveryPhrase": "Use same recovery phrase", - "core.walletSetupReuseRecoveryPhrase.createNewOne": "Create a new one", + "core.walletSetupReuseRecoveryPhrase.reuse": "Reuse", + "core.walletSetupReuseRecoveryPhrase.skip": "Skip", "core.walletSetupReuseRecoveryPhrase.selectWallet": "Select a wallet", "core.walletSetupReuseRecoveryPhrase.confirmPassword": "Confirm your password", "core.walletSetupReuseRecoveryPhrase.insertPassword": "Insert your password", diff --git a/v1/packages/translation/src/lib/translations/core/es.json b/v1/packages/translation/src/lib/translations/core/es.json index bdd77ef62c..e4c8e6f7b4 100644 --- a/v1/packages/translation/src/lib/translations/core/es.json +++ b/v1/packages/translation/src/lib/translations/core/es.json @@ -679,15 +679,19 @@ "core.WalletSetupSelectBlockchain.title": "Selecciona una cadena de bloques", "core.WalletSetupSelectBlockchain.cardano": "Cardano", "core.WalletSetupSelectBlockchain.bitcoin": "Bitcoin", - "core.WalletSetupSelectBlockchain.description": "Elige entre una cartera de Cardano o de Bitcoin. Una vez completado el registro, podrás crear carteras adicionales para ambos más adelante.", + "core.WalletSetupSelectBlockchain.midnight": "Midnight", + "core.WalletSetupSelectBlockchain.description": "Elige entre carteras de Cardano, Midnight o Bitcoin. Una vez completado el registro, podrás crear carteras adicionales más adelante.", "core.WalletSetupSelectBlockchain.cardano.description": "Te permite gestionar Cardano (ADA) y otros activos nativos en la cadena de bloques de Cardano.", "core.WalletSetupSelectBlockchain.bitcoin.description": "Te permite gestionar Bitcoin (BTC) y otros activos en la cadena de bloques de Bitcoin.", + "core.WalletSetupSelectBlockchain.midnight.description": "Te permite interactuar con la red Midnight, una blockchain centrada en la privacidad diseñada para proteger datos sensibles.", + "core.WalletSetupSelectBlockchain.midnight.alreadyHaveWallet": "Ya tienes una billetera Midnight", + "core.WalletSetupSelectBlockchain.midnight.disabledReason": "En este momento solo se admite una billetera Midnight. Elimina la billetera existente si deseas crear una nueva.", "core.WalletSetupSelectBlockchain.defaultBadge": "Predeterminado", "core.WalletSetupSelectBlockchain.newBadge": "Nuevo", "core.walletSetupReuseRecoveryPhrase.title": "¿Reutilizar su frase de recuperación?", "core.walletSetupReuseRecoveryPhrase.description": "¿Desea usar la misma frase de recuperación que la billetera a continuación, o desea crear una nueva?", - "core.walletSetupReuseRecoveryPhrase.useSameRecoveryPhrase": "Usar la misma frase de recuperación", - "core.walletSetupReuseRecoveryPhrase.createNewOne": "Crear una nueva", + "core.walletSetupReuseRecoveryPhrase.reuse": "Reutilizar", + "core.walletSetupReuseRecoveryPhrase.skip": "Omitir", "core.walletSetupReuseRecoveryPhrase.selectWallet": "Seleccionar una billetera", "core.walletSetupReuseRecoveryPhrase.confirmPassword": "Confirme su contraseña", "core.walletSetupReuseRecoveryPhrase.insertPassword": "Introduzca su contraseña", diff --git a/v2 b/v2 index 8f9ee66983..9b6791fc37 160000 --- a/v2 +++ b/v2 @@ -1 +1 @@ -Subproject commit 8f9ee6698329465f2e81a7930188be78cc7e549a +Subproject commit 9b6791fc37fa4e62d3c2aefe1706ca571312ccaf