From bf5134d97520a1e20f8eb8522a385efef1bf1cd3 Mon Sep 17 00:00:00 2001 From: Pavlos Chrysochoidis <10210143+pchrysochoidis@users.noreply.github.com> Date: Sat, 8 Oct 2022 01:07:59 +0100 Subject: [PATCH] wallet-ext: backup mnemonic view ui update & use bg service for mnemonic --- apps/wallet/src/background/Keyring.ts | 19 ++ .../messages/payloads/keyring/index.ts | 4 + .../src/ui/app/background-client/index.ts | 30 ++- .../initialize/backup/Backup.module.scss | 189 +++++++++++------- .../ui/app/pages/initialize/backup/index.tsx | 76 +++++-- .../ui/app/pages/initialize/import/index.tsx | 3 +- .../src/ui/app/pages/initialize/index.tsx | 7 +- .../src/ui/app/redux/slices/account/index.ts | 21 +- .../middlewares/KeypairVaultMiddleware.ts | 4 +- apps/wallet/src/ui/styles/utils/index.scss | 10 + 10 files changed, 255 insertions(+), 108 deletions(-) diff --git a/apps/wallet/src/background/Keyring.ts b/apps/wallet/src/background/Keyring.ts index 0f2e3eb633710..cb0b5bff1016b 100644 --- a/apps/wallet/src/background/Keyring.ts +++ b/apps/wallet/src/background/Keyring.ts @@ -115,6 +115,25 @@ class Keyring { id ) ); + } else if ( + isKeyringPayload<'getMnemonic'>(payload, 'getMnemonic') + ) { + if (this.#locked) { + throw new Error('Keyring is locked. Unlock it first.'); + } + if (!this.#mnemonic) { + throw new Error('Error mnemonic is empty'); + } + uiConnection.send( + createMessage>( + { + type: 'keyring', + method: 'getMnemonic', + return: this.#mnemonic, + }, + id + ) + ); } } catch (e) { uiConnection.send( diff --git a/apps/wallet/src/shared/messaging/messages/payloads/keyring/index.ts b/apps/wallet/src/shared/messaging/messages/payloads/keyring/index.ts index 897f1f61623cb..5534f2f2cd799 100644 --- a/apps/wallet/src/shared/messaging/messages/payloads/keyring/index.ts +++ b/apps/wallet/src/shared/messaging/messages/payloads/keyring/index.ts @@ -10,6 +10,10 @@ type MethodToPayloads = { args: string; return: string; }; + getMnemonic: { + args: string | undefined; + return: string; + }; }; export interface KeyringPayload diff --git a/apps/wallet/src/ui/app/background-client/index.ts b/apps/wallet/src/ui/app/background-client/index.ts index 95aa2bfea52a1..56e3bf6503fe9 100644 --- a/apps/wallet/src/ui/app/background-client/index.ts +++ b/apps/wallet/src/ui/app/background-client/index.ts @@ -1,10 +1,11 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { lastValueFrom, take } from 'rxjs'; +import { lastValueFrom, map, take } from 'rxjs'; import { createMessage } from '_messages'; import { PortStream } from '_messaging/PortStream'; +import { isKeyringPayload } from '_payloads/keyring'; import { isPermissionRequests } from '_payloads/permissions'; import { isUpdateActiveOrigin } from '_payloads/tabs/updateActiveOrigin'; import { isGetTransactionRequestsResponse } from '_payloads/transactions/ui/GetTransactionRequestsResponse'; @@ -117,6 +118,33 @@ export class BackgroundClient { ); } + public async getMnemonic(password?: string) { + return await lastValueFrom( + this.sendMessage( + createMessage>({ + type: 'keyring', + method: 'getMnemonic', + args: password, + return: undefined, + }) + ).pipe( + take(1), + map(({ payload }) => { + if ( + isKeyringPayload<'getMnemonic'>( + payload, + 'getMnemonic' + ) && + payload.return + ) { + return payload.return; + } + throw new Error('Mnemonic not found'); + }) + ) + ); + } + private handleIncomingMessage(msg: Message) { if (!this._initialized || !this._dispatch) { throw new Error( diff --git a/apps/wallet/src/ui/app/pages/initialize/backup/Backup.module.scss b/apps/wallet/src/ui/app/pages/initialize/backup/Backup.module.scss index 7d8fd86ee82b7..d2c8022337907 100644 --- a/apps/wallet/src/ui/app/pages/initialize/backup/Backup.module.scss +++ b/apps/wallet/src/ui/app/pages/initialize/backup/Backup.module.scss @@ -1,77 +1,122 @@ @use '_values/colors'; +@use '_values/sizing'; @use '_utils'; +@use 'sass:color'; .wallet-created { - .mnemonic { - padding: 14px; - border-radius: 15px; - margin: auto; - margin-bottom: 16px; - background: colors.$white; - border: 1px solid #e9eaeb; - font-family: Inter, sans-serif; - width: 320px; - height: 90px; - - @include utils.typography('header/search-text'); - - line-height: 130%; - color: colors.$gray-85; - } - - .header-title { - text-align: left; - margin-bottom: 20px; - color: colors.$gray-100; - font-size: 30px; - font-weight: 700; - line-height: 36px; - } - - .sub-title { - text-align: center; - color: colors.$gray-90; - font-weight: 600; - font-size: 18px; - line-height: 100%; - } - - .success-icon { - border-radius: 50%; - width: 46px; - height: 46px; - margin: auto; - border: 3px dotted colors.$success; - display: flex; - align-items: center; - justify-content: center; - - .success-bg { - background-color: colors.$success; - border-radius: 50%; - width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; - } - - i { - color: colors.$white; - font-size: 25px; - } - } - - .btn { - width: 100%; - margin: auto; - max-width: 320px; - margin-top: 20px; - - i { - margin-right: 10px; - font-weight: 500; - font-size: 12px; - } - } + $color: color.change(colors.$sui-blue, $alpha: 0.1); + + display: flex; + flex-flow: column nowrap; + align-items: center; + justify-content: center; + padding: 40px 30px; + background: $color; + border: 1px solid $color; + border-radius: 20px; + max-width: sizing.$popup-width; + max-height: sizing.$popup-height; + flex: 1; +} + +.success-icon { + border-radius: 50%; + width: 48px; + height: 48px; + min-height: 48px; + border: 3px dotted colors.$success; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 10px; +} + +.success-bg { + background-color: colors.$success; + border-radius: 50%; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; +} + +.thumbs-up { + color: colors.$white; + font-size: 25px; +} + +.header-title { + text-align: center; + margin: 0; + margin-bottom: 30px; + color: colors.$gray-90; + text-transform: capitalize; + + @include utils.typography('Primary/Heading1-B'); +} + +.sub-title { + margin: 0; + margin-bottom: 15px; + color: colors.$gray-75; + text-transform: uppercase; + + @include utils.typography('Primary/CAPTION-B'); +} + +.mnemonic { + display: flex; + flex-flow: column nowrap; + gap: 8px; + align-self: stretch; + font-weight: 600; + font-size: 17px; + line-height: 140%; + padding: 14px; + border-radius: 15px; + background: colors.$white; + border: 1px solid #e9eaeb; + box-shadow: 0 1px 2px rgba(16 24 40 / 5%); + color: colors.$gray-85; +} + +.copy { + margin-top: 10px; + color: colors.$sui-dark-blue; + align-self: flex-end; + cursor: pointer; + + @include utils.typography('Primary/CAPTION-SB'); +} + +.info { + text-align: center; + color: colors.$gray-75; + margin-top: 15px; + + @include utils.typography('ParagraphPrimary/P2-R'); +} + +.info-caption { + text-align: center; + color: colors.$gray-75; + margin-bottom: 5px; + + @include utils.typography('Primary/CAPTION-M'); +} + +.btn { + display: flex; + flex-flow: row nowrap; + gap: 8px; + margin-top: 45px; + align-self: stretch; + padding: 14px 20px; +} + +.arrow-up { + font-size: 12px; + font-weight: 300; + transform: rotate(135deg); } diff --git a/apps/wallet/src/ui/app/pages/initialize/backup/index.tsx b/apps/wallet/src/ui/app/pages/initialize/backup/index.tsx index 915033ff22520..1b0f43bd6b9e7 100644 --- a/apps/wallet/src/ui/app/pages/initialize/backup/index.tsx +++ b/apps/wallet/src/ui/app/pages/initialize/backup/index.tsx @@ -1,49 +1,83 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { useCallback } from 'react'; +import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import Button from '_app/shared/button'; +import Alert from '_components/alert'; +import CopyToClipboard from '_components/copy-to-clipboard'; import Icon, { SuiIcons } from '_components/icon'; -import { useAppDispatch, useAppSelector } from '_src/ui/app/hooks'; -import { setMnemonic } from '_src/ui/app/redux/slices/account'; +import Loading from '_components/loading'; +import { useAppDispatch } from '_hooks'; +import { loadMnemonicFromKeyring } from '_redux/slices/account'; import st from './Backup.module.scss'; const BackupPage = () => { - const mnemonic = useAppSelector( - ({ account }) => account.createdMnemonic || account.mnemonic - ); + const [loading, setLoading] = useState(true); + const [mnemonic, setLocalMnemonic] = useState(null); const navigate = useNavigate(); const dispatch = useAppDispatch(); - const handleOnClick = useCallback(() => { - if (mnemonic) { - navigate('/'); - dispatch(setMnemonic(mnemonic)); - } - }, [navigate, dispatch, mnemonic]); + useEffect(() => { + // TODO: this assumes that the Keyring in bg service is unlocked. It should be fix + // when we add a locked status guard. (#encrypt-wallet) + (async () => { + setLoading(true); + try { + setLocalMnemonic( + await dispatch(loadMnemonicFromKeyring({})).unwrap() + ); + } catch (e) { + // Do nothing + } finally { + setLoading(false); + } + })(); + }, [dispatch]); return (
- +
- -

Wallet Successfully Created!

-

Backup Recovery Passphrase

-
{mnemonic}
- +

Wallet Created Successfully!

+

Recovery Phrase

+ + {mnemonic ? ( +
+ {mnemonic} + + COPY + +
+ ) : ( + Something is wrong, Recovery Phrase is empty. + )} +
+
+ Your recovery phrase makes it easy to back up and restore your + account. +
+
+
WARNING
+ Never disclose your secret recovery phrase. Anyone with the + passphrase can take over your account forever. +
); diff --git a/apps/wallet/src/ui/app/pages/initialize/import/index.tsx b/apps/wallet/src/ui/app/pages/initialize/import/index.tsx index aaeb2ca5d80f9..53dd38792657c 100644 --- a/apps/wallet/src/ui/app/pages/initialize/import/index.tsx +++ b/apps/wallet/src/ui/app/pages/initialize/import/index.tsx @@ -8,7 +8,7 @@ import * as Yup from 'yup'; import Button from '_app/shared/button'; import Icon, { SuiIcons } from '_components/icon'; import { useAppDispatch, useAppSelector } from '_hooks'; -import { createMnemonic, setMnemonic } from '_redux/slices/account'; +import { createMnemonic } from '_redux/slices/account'; import { normalizeMnemonics, validateMnemonics } from '_src/shared/utils/bip39'; import type { FocusEventHandler } from 'react'; @@ -38,7 +38,6 @@ const ImportPage = () => { const onHandleSubmit = useCallback( async ({ mnemonic }: ValuesType) => { await dispatch(createMnemonic({ existingMnemonic: mnemonic })); - await dispatch(setMnemonic(mnemonic)); }, [dispatch] ); diff --git a/apps/wallet/src/ui/app/pages/initialize/index.tsx b/apps/wallet/src/ui/app/pages/initialize/index.tsx index 3c3d5a76bec22..a450774483c5a 100644 --- a/apps/wallet/src/ui/app/pages/initialize/index.tsx +++ b/apps/wallet/src/ui/app/pages/initialize/index.tsx @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { Outlet } from 'react-router-dom'; +import { Outlet, useLocation } from 'react-router-dom'; import Loading from '_components/loading'; import { useInitializedGuard } from '_hooks'; @@ -10,7 +10,10 @@ import PageLayout from '_pages/layout'; import st from './InitializePage.module.scss'; const InitializePage = () => { - const checkingInitialized = useInitializedGuard(false); + const { pathname } = useLocation(); + const checkingInitialized = useInitializedGuard( + /^\/initialize\/backup(\/)?$/.test(pathname) + ); return ( diff --git a/apps/wallet/src/ui/app/redux/slices/account/index.ts b/apps/wallet/src/ui/app/redux/slices/account/index.ts index f069825c3fb51..8979bc5edc4b2 100644 --- a/apps/wallet/src/ui/app/redux/slices/account/index.ts +++ b/apps/wallet/src/ui/app/redux/slices/account/index.ts @@ -56,6 +56,16 @@ export const createMnemonic = createAsyncThunk< } ); +export const loadMnemonicFromKeyring = createAsyncThunk< + string, + { password?: string }, // can be undefined when we know Keyring is unlocked + AppThunkConfig +>( + 'account/loadMnemonicFromKeyring', + async ({ password }, { extra: { background } }) => + await background.getMnemonic(password) +); + export const logout = createAsyncThunk( 'account/logout', async (): Promise => { @@ -68,7 +78,6 @@ type AccountState = { loading: boolean; mnemonic: string | null; creating: boolean; - createdMnemonic: string | null; address: SuiAddress | null; }; @@ -76,7 +85,6 @@ const initialState: AccountState = { loading: true, mnemonic: null, creating: false, - createdMnemonic: null, address: null, }; @@ -84,9 +92,6 @@ const accountSlice = createSlice({ name: 'account', initialState, reducers: { - setMnemonic: (state, action: PayloadAction) => { - state.mnemonic = action.payload; - }, setAddress: (state, action: PayloadAction) => { state.address = action.payload; }, @@ -102,15 +107,15 @@ const accountSlice = createSlice({ }) .addCase(createMnemonic.fulfilled, (state, action) => { state.creating = false; - state.createdMnemonic = action.payload; + state.mnemonic = action.payload; }) .addCase(createMnemonic.rejected, (state) => { state.creating = false; - state.createdMnemonic = null; + state.mnemonic = null; }), }); -export const { setMnemonic, setAddress } = accountSlice.actions; +export const { setAddress } = accountSlice.actions; const reducer: Reducer = accountSlice.reducer; export default reducer; diff --git a/apps/wallet/src/ui/app/redux/store/middlewares/KeypairVaultMiddleware.ts b/apps/wallet/src/ui/app/redux/store/middlewares/KeypairVaultMiddleware.ts index 8760f89371b96..3a820999ad1f4 100644 --- a/apps/wallet/src/ui/app/redux/store/middlewares/KeypairVaultMiddleware.ts +++ b/apps/wallet/src/ui/app/redux/store/middlewares/KeypairVaultMiddleware.ts @@ -5,8 +5,8 @@ import { isAnyOf } from '@reduxjs/toolkit'; import { loadAccountFromStorage, - setMnemonic, setAddress, + createMnemonic, } from '_redux/slices/account'; import { thunkExtras } from '_store/thunk-extras'; @@ -15,7 +15,7 @@ import type { Middleware } from '@reduxjs/toolkit'; const keypairVault = thunkExtras.keypairVault; const matchUpdateMnemonic = isAnyOf( loadAccountFromStorage.fulfilled, - setMnemonic + createMnemonic.fulfilled ); export const KeypairVaultMiddleware: Middleware = diff --git a/apps/wallet/src/ui/styles/utils/index.scss b/apps/wallet/src/ui/styles/utils/index.scss index 0c81683fbe4d2..e05c61c0fa06b 100644 --- a/apps/wallet/src/ui/styles/utils/index.scss +++ b/apps/wallet/src/ui/styles/utils/index.scss @@ -172,6 +172,16 @@ $main-extra-space: sizing.$main-bottom-space; font-weight: 600; letter-spacing: 0.05em; text-align: center; + } @else if $type == 'Primary/CAPTION-B' { + font-size: 12px; + font-weight: 700; + letter-spacing: 0.05em; + text-align: center; + } @else if $type == 'Primary/CAPTION-M' { + font-size: 12px; + font-weight: 500; + letter-spacing: 0.05em; + text-align: center; } @else if $type == 'ParagraphPrimary/P2-R' { font-size: 13px; font-weight: 400;