From 6d0e2cf49134cd8ca8928ea0b31205facbd5d51f Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 6 Apr 2022 11:25:08 +0100 Subject: [PATCH 01/85] start component work for multi accounts --- .../src/components/Accounts.stories.tsx | 22 +++++++++++++ nym-wallet/src/components/Accounts.tsx | 32 +++++++++++++++++++ .../src/components/ClientAddress.stories.tsx | 2 +- 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 nym-wallet/src/components/Accounts.stories.tsx create mode 100644 nym-wallet/src/components/Accounts.tsx diff --git a/nym-wallet/src/components/Accounts.stories.tsx b/nym-wallet/src/components/Accounts.stories.tsx new file mode 100644 index 0000000000..c753f18d89 --- /dev/null +++ b/nym-wallet/src/components/Accounts.stories.tsx @@ -0,0 +1,22 @@ +import { Box } from '@mui/material'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Accounts } from 'src/components/Accounts'; + +export default { + title: 'Wallet / Multi Account', + component: Accounts, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + + + +); + +export const Default = Template.bind({}); +Default.args = { + accounts: [ + { name: 'Account 1', address: 'abcdefg' }, + { name: 'Account 2', address: 'tuvwxyz' }, + ], +}; diff --git a/nym-wallet/src/components/Accounts.tsx b/nym-wallet/src/components/Accounts.tsx new file mode 100644 index 0000000000..4e30a6c6b0 --- /dev/null +++ b/nym-wallet/src/components/Accounts.tsx @@ -0,0 +1,32 @@ +import React, { useState } from 'react'; +import { Button, Dialog, DialogContent, DialogTitle } from '@mui/material'; +import { Circle } from '@mui/icons-material'; + +export type TAccount = { + name: string; + address: string; +}; + +export const Accounts = ({ accounts }: { accounts: TAccount[] }) => { + const [addresses, setAddresses] = useState(accounts); + const [selectedAddress, setSelectedAddress] = useState(accounts[0]); + const [showDialog, setShowDialog] = useState(false); + + return ( + <> + + setShowDialog(false)} /> + + ); +}; + +const AccountModal = ({ show, onClose }: { show: boolean; onClose: () => void }) => { + return ( + + Yo + Content + + ); +}; diff --git a/nym-wallet/src/components/ClientAddress.stories.tsx b/nym-wallet/src/components/ClientAddress.stories.tsx index eae99150a7..08a3d873b4 100644 --- a/nym-wallet/src/components/ClientAddress.stories.tsx +++ b/nym-wallet/src/components/ClientAddress.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { Box, Typography } from '@mui/material'; +import { Box } from '@mui/material'; import { ClientAddressDisplay } from './ClientAddress'; export default { From 3b245e16db3f2f1dcf7be529e92bd45dde99f792 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 7 Apr 2022 11:34:48 +0100 Subject: [PATCH 02/85] more multi account UI work --- nym-wallet/package.json | 1 + .../src/components/Accounts.stories.tsx | 5 +- nym-wallet/src/components/Accounts.tsx | 64 +++++++++++++++---- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/nym-wallet/package.json b/nym-wallet/package.json index 7d055fb77e..d550b3d3e0 100644 --- a/nym-wallet/package.json +++ b/nym-wallet/package.json @@ -42,6 +42,7 @@ "react-hook-form": "^7.14.2", "react-router-dom": "^5.2.0", "semver": "^6.3.0", + "string-to-color": "^2.2.2", "use-clipboard-copy": "^0.2.0", "yup": "^0.32.9" }, diff --git a/nym-wallet/src/components/Accounts.stories.tsx b/nym-wallet/src/components/Accounts.stories.tsx index c753f18d89..fd34e2a97b 100644 --- a/nym-wallet/src/components/Accounts.stories.tsx +++ b/nym-wallet/src/components/Accounts.stories.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { Box } from '@mui/material'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import { Accounts } from 'src/components/Accounts'; @@ -16,7 +17,7 @@ const Template: ComponentStory = (args) => ( export const Default = Template.bind({}); Default.args = { accounts: [ - { name: 'Account 1', address: 'abcdefg' }, - { name: 'Account 2', address: 'tuvwxyz' }, + { name: 'Account 1', address: 'abcd1234uvw987xyz' }, + { name: 'Account 2', address: 'cd102034u087xyz' }, ], }; diff --git a/nym-wallet/src/components/Accounts.tsx b/nym-wallet/src/components/Accounts.tsx index 4e30a6c6b0..c1c4c6c30b 100644 --- a/nym-wallet/src/components/Accounts.tsx +++ b/nym-wallet/src/components/Accounts.tsx @@ -1,12 +1,55 @@ import React, { useState } from 'react'; -import { Button, Dialog, DialogContent, DialogTitle } from '@mui/material'; -import { Circle } from '@mui/icons-material'; +import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Typography } from '@mui/material'; +import stc from 'string-to-color'; +import { Add, Circle, Close, Edit } from '@mui/icons-material'; +import { NymCard } from './NymCard'; export type TAccount = { name: string; address: string; }; +const AccountColor = ({ address }: { address: string }) => ; + +const AccountItem = ({ name, address }: { name: string; address: string }) => ( + + + + } + /> +); + +const AccountModal = ({ show, addresses, onClose }: { show: boolean; addresses: TAccount[]; onClose: () => void }) => ( + + + + Account + + + + + + Switch between accounts + + + + {addresses.map(({ name, address }) => ( + + ))} + + + + + +); + export const Accounts = ({ accounts }: { accounts: TAccount[] }) => { const [addresses, setAddresses] = useState(accounts); const [selectedAddress, setSelectedAddress] = useState(accounts[0]); @@ -14,19 +57,14 @@ export const Accounts = ({ accounts }: { accounts: TAccount[] }) => { return ( <> - - setShowDialog(false)} /> + setShowDialog(false)} addresses={addresses} /> ); }; - -const AccountModal = ({ show, onClose }: { show: boolean; onClose: () => void }) => { - return ( - - Yo - Content - - ); -}; From c6578384a84584797d35ced0bec70b8ad5a7c15c Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 7 Apr 2022 14:01:58 +0100 Subject: [PATCH 03/85] storybook ui for multi addresses --- nym-wallet/Cargo.lock | 6 ++---- nym-wallet/src/components/Accounts.tsx | 14 ++++++++++++-- nym-wallet/src/components/NymCard.tsx | 7 ++++--- nym-wallet/src/components/Title.tsx | 4 ++-- nym-wallet/src/pages/balance/vesting.tsx | 2 +- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 9ca3f04426..bb9e4d1ba5 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -778,8 +778,7 @@ dependencies = [ [[package]] name = "cosmos-sdk-proto" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0254ffee603f5301d6a66963d9e1cc5091479c22e2e925e1f7689c8027a0828" +source = "git+https://github.com/nymtech/cosmos-rust?branch=bugfix/account-id-length-validation#911fbe1236cfed591783ccef01018f7ccc97c496" dependencies = [ "prost", "prost-types", @@ -789,8 +788,7 @@ dependencies = [ [[package]] name = "cosmrs" version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505ea048e9ff2f906d6b954f9f8157d903ca468bfb301d906b40ecc25ba6838d" +source = "git+https://github.com/nymtech/cosmos-rust?branch=bugfix/account-id-length-validation#911fbe1236cfed591783ccef01018f7ccc97c496" dependencies = [ "bip32", "cosmos-sdk-proto", diff --git a/nym-wallet/src/components/Accounts.tsx b/nym-wallet/src/components/Accounts.tsx index c1c4c6c30b..d6a2a364b4 100644 --- a/nym-wallet/src/components/Accounts.tsx +++ b/nym-wallet/src/components/Accounts.tsx @@ -13,9 +13,19 @@ const AccountColor = ({ address }: { address: string }) => ( + {name} + {address} + + } noPadding + borderless + Icon={ + + + + } Action={ diff --git a/nym-wallet/src/components/NymCard.tsx b/nym-wallet/src/components/NymCard.tsx index 0009573a3a..e98bb4323f 100644 --- a/nym-wallet/src/components/NymCard.tsx +++ b/nym-wallet/src/components/NymCard.tsx @@ -14,10 +14,11 @@ export const NymCard: React.FC<{ title: string | React.ReactElement; subheader?: string; Action?: React.ReactNode; - Icon?: any; + Icon?: React.ReactNode; noPadding?: boolean; -}> = ({ title, subheader, Action, Icon, noPadding, children }) => ( - + borderless?: boolean; +}> = ({ title, subheader, Action, Icon, noPadding, borderless, children }) => ( + } diff --git a/nym-wallet/src/components/Title.tsx b/nym-wallet/src/components/Title.tsx index ed041f824e..f8dbfcc476 100644 --- a/nym-wallet/src/components/Title.tsx +++ b/nym-wallet/src/components/Title.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Box, Typography } from '@mui/material'; -export const Title: React.FC<{ title: string | React.ReactNode; Icon: any }> = ({ title, Icon }) => ( +export const Title: React.FC<{ title: string | React.ReactNode; Icon?: React.ReactNode }> = ({ title, Icon }) => ( - {Icon && }{' '} + {Icon} {title} diff --git a/nym-wallet/src/pages/balance/vesting.tsx b/nym-wallet/src/pages/balance/vesting.tsx index 222989c905..3d81f98735 100644 --- a/nym-wallet/src/pages/balance/vesting.tsx +++ b/nym-wallet/src/pages/balance/vesting.tsx @@ -137,7 +137,7 @@ export const VestingCard = () => { } Action={ { From 585724fc79f00c1c337489624df1ce2fec01c92c Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 7 Apr 2022 16:27:30 +0100 Subject: [PATCH 04/85] more storybook work for multi accounts --- nym-wallet/package.json | 2 + .../src/components/Accounts.stories.tsx | 12 +- nym-wallet/src/components/Accounts.tsx | 185 ++++++++++++++---- 3 files changed, 158 insertions(+), 41 deletions(-) diff --git a/nym-wallet/package.json b/nym-wallet/package.json index d550b3d3e0..7cb5729725 100644 --- a/nym-wallet/package.json +++ b/nym-wallet/package.json @@ -44,6 +44,7 @@ "semver": "^6.3.0", "string-to-color": "^2.2.2", "use-clipboard-copy": "^0.2.0", + "uuid": "^8.3.2", "yup": "^0.32.9" }, "devDependencies": { @@ -72,6 +73,7 @@ "@types/react-router": "^5.1.18", "@types/react-router-dom": "^5.1.8", "@types/semver": "^7.3.8", + "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.13.0", "@typescript-eslint/parser": "^5.13.0", "babel-loader": "^8.2.2", diff --git a/nym-wallet/src/components/Accounts.stories.tsx b/nym-wallet/src/components/Accounts.stories.tsx index fd34e2a97b..fd4b22e70f 100644 --- a/nym-wallet/src/components/Accounts.stories.tsx +++ b/nym-wallet/src/components/Accounts.stories.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Box } from '@mui/material'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import { Accounts } from 'src/components/Accounts'; +import { v4 as uuid4 } from 'uuid'; export default { title: 'Wallet / Multi Account', @@ -16,8 +17,13 @@ const Template: ComponentStory = (args) => ( export const Default = Template.bind({}); Default.args = { - accounts: [ - { name: 'Account 1', address: 'abcd1234uvw987xyz' }, - { name: 'Account 2', address: 'cd102034u087xyz' }, + storedAccounts: [{ name: 'Account 1', address: uuid4() }], +}; + +export const MultipleAccounts = Template.bind({}); +MultipleAccounts.args = { + storedAccounts: [ + { name: 'Account 1', address: uuid4() }, + { name: 'Account 2', address: uuid4() }, ], }; diff --git a/nym-wallet/src/components/Accounts.tsx b/nym-wallet/src/components/Accounts.tsx index d6a2a364b4..b0013d80a8 100644 --- a/nym-wallet/src/components/Accounts.tsx +++ b/nym-wallet/src/components/Accounts.tsx @@ -1,8 +1,23 @@ -import React, { useState } from 'react'; -import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Typography } from '@mui/material'; +import React, { useEffect, useState } from 'react'; +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + ListItem, + ListItemAvatar, + ListItemButton, + ListItemIcon, + ListItemText, + TextField, + Typography, +} from '@mui/material'; import stc from 'string-to-color'; import { Add, Circle, Close, Edit } from '@mui/icons-material'; -import { NymCard } from './NymCard'; +import { v4 as uuidv4 } from 'uuid'; export type TAccount = { name: string; @@ -11,30 +26,35 @@ export type TAccount = { const AccountColor = ({ address }: { address: string }) => ; -const AccountItem = ({ name, address }: { name: string; address: string }) => ( - - {name} - {address} - - } - noPadding - borderless - Icon={ - +const AccountItem = ({ name, address, onSelect }: { name: string; address: string; onSelect: () => void }) => ( + + + - - } - Action={ - - - - } - /> + + + + e.stopPropagation()}> + + + + + ); -const AccountModal = ({ show, addresses, onClose }: { show: boolean; addresses: TAccount[]; onClose: () => void }) => ( +const AccountModal = ({ + show, + accounts, + onClose, + onAccountSelect, + onAddAccount, +}: { + show: boolean; + accounts: TAccount[]; + onClose: () => void; + onAccountSelect: (account: TAccount) => void; + onAddAccount: () => void; +}) => ( @@ -43,38 +63,127 @@ const AccountModal = ({ show, addresses, onClose }: { show: boolean; addresses: - + Switch between accounts - - {addresses.map(({ name, address }) => ( - + + {accounts.map(({ name, address }) => ( + { + onAccountSelect({ name, address }); + onClose(); + }} + key={address} + /> ))} - - ); -export const Accounts = ({ accounts }: { accounts: TAccount[] }) => { - const [addresses, setAddresses] = useState(accounts); - const [selectedAddress, setSelectedAddress] = useState(accounts[0]); - const [showDialog, setShowDialog] = useState(false); +const AddAccountModal = ({ + show, + onClose, + onAdd, +}: { + show: boolean; + onClose: () => void; + onAdd: (accountName: string) => void; +}) => { + const [accountName, setAccountName] = useState(''); + + useEffect(() => { + if (!show) setAccountName(''); + }, [show]); + + return ( + + + + Account + + + + + + New wallet address + + + + + setAccountName(e.target.value)} + /> + + + + + + + ); +}; + +export const Accounts = ({ storedAccounts }: { storedAccounts: TAccount[] }) => { + const [accounts, setAccounts] = useState(storedAccounts); + const [selectedAccount, setSelectedAccount] = useState(accounts[0]); + const [showAccountsDialog, setShowAccountsDialog] = useState(false); + const [showNewAccountsDialog, setShowNewAccountsDialog] = useState(false); return ( <> - setShowDialog(false)} addresses={addresses} /> + setShowAccountsDialog(false)} + accounts={accounts} + onAccountSelect={(acc) => setSelectedAccount(acc)} + onAddAccount={() => { + setShowAccountsDialog(false); + setShowNewAccountsDialog(true); + }} + /> + setShowNewAccountsDialog(false)} + onAdd={(name) => { + setAccounts((accs) => [...accs, { address: uuidv4(), name }]); + setShowNewAccountsDialog(false); + setShowAccountsDialog(true); + }} + /> ); }; From 902721bda3170d2205b4474e47addd744dcc72af Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 7 Apr 2022 16:28:57 +0100 Subject: [PATCH 05/85] yarn lock updated --- yarn.lock | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/yarn.lock b/yarn.lock index 422fcfa5a8..8a71f26ae8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5080,6 +5080,11 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== +"@types/uuid@^8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + "@types/webpack-env@^1.16.0": version "1.16.3" resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.16.3.tgz#b776327a73e561b71e7881d0cd6d34a1424db86a" @@ -7234,6 +7239,11 @@ colorette@^2.0.10, colorette@^2.0.14: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== +colornames@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96" + integrity sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y= + colors@1.4.0, colors@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" @@ -10258,6 +10268,11 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +hex-rgb@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/hex-rgb/-/hex-rgb-4.3.0.tgz#af5e974e83bb2fefe44d55182b004ec818c07776" + integrity sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw== + highlight.js@^10.1.1, highlight.js@~10.7.0: version "10.7.3" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" @@ -12316,6 +12331,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.padend@^4.6.1: + version "4.6.1" + resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e" + integrity sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4= + lodash.template@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" @@ -12331,11 +12351,21 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "^3.0.0" +lodash.trimstart@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/lodash.trimstart/-/lodash.trimstart-4.5.1.tgz#8ff4dec532d82486af59573c39445914e944a7f1" + integrity sha1-j/TexTLYJIavWVc8OURZFOlEp/E= + lodash.uniq@4.5.0, lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= +lodash.words@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.words/-/lodash.words-4.2.0.tgz#5ecfeaf8ecf8acaa8e0c8386295f1993c9cf4036" + integrity sha1-Xs/q+Oz4rKqODIOGKV8Zk8nPQDY= + lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -15666,6 +15696,11 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rgb-hex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/rgb-hex/-/rgb-hex-3.0.0.tgz#eab0168cc1279563b18a14605315389142e2e487" + integrity sha512-8h7ZcwxCBDKvchSWbWngJuSCqJGQ6nDuLLg+QcRyQDbX9jMWt+PpPeXAhSla0GOooEomk3lCprUpGkMdsLjKyg== + rifm@^0.12.1: version "0.12.1" resolved "https://registry.yarnpkg.com/rifm/-/rifm-0.12.1.tgz#8fa77f45b7f1cda2a0068787ac821f0593967ac4" @@ -16468,6 +16503,18 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" +string-to-color@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/string-to-color/-/string-to-color-2.2.2.tgz#46210bf7777dc9198dcdf997bd18ae6749cc9a73" + integrity sha512-XeA2goP7PNsSlz8RRn6KhYswnMf5Tl+38ajfy8n4oZJyMGC4qqKgHNHsZ/3qwvr42NRIjf9eSr721SyetDeMkA== + dependencies: + colornames "^1.1.1" + hex-rgb "^4.1.0" + lodash.padend "^4.6.1" + lodash.trimstart "^4.5.1" + lodash.words "^4.2.0" + rgb-hex "^3.0.0" + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" From 3265df019a7a839191229ec65df174491f175f37 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 7 Apr 2022 22:00:30 +0100 Subject: [PATCH 06/85] more multi account ui --- nym-wallet/src/components/Accounts.tsx | 150 +++++++++++++++++++++---- 1 file changed, 127 insertions(+), 23 deletions(-) diff --git a/nym-wallet/src/components/Accounts.tsx b/nym-wallet/src/components/Accounts.tsx index b0013d80a8..fd91fe34dc 100644 --- a/nym-wallet/src/components/Accounts.tsx +++ b/nym-wallet/src/components/Accounts.tsx @@ -16,7 +16,7 @@ import { Typography, } from '@mui/material'; import stc from 'string-to-color'; -import { Add, Circle, Close, Edit } from '@mui/icons-material'; +import { Add, CircleTwoTone, Close, Edit } from '@mui/icons-material'; import { v4 as uuidv4 } from 'uuid'; export type TAccount = { @@ -24,17 +24,36 @@ export type TAccount = { address: string; }; -const AccountColor = ({ address }: { address: string }) => ; +type TDialog = 'Accounts' | 'Add' | 'Edit'; -const AccountItem = ({ name, address, onSelect }: { name: string; address: string; onSelect: () => void }) => ( - +const AccountColor = ({ address }: { address: string }) => ; + +const AccountItem = ({ + name, + address, + selected, + onSelect, + onEdit, +}: { + name: string; + address: string; + selected: boolean; + onSelect: () => void; + onEdit: () => void; +}) => ( + - + - e.stopPropagation()}> + { + e.stopPropagation(); + onEdit(); + }} + > @@ -42,23 +61,27 @@ const AccountItem = ({ name, address, onSelect }: { name: string; address: strin ); -const AccountModal = ({ +const AccountsModal = ({ show, accounts, + selectedAccount, onClose, onAccountSelect, onAddAccount, + onEditAccount, }: { show: boolean; accounts: TAccount[]; + selectedAccount: TAccount['address']; onClose: () => void; onAccountSelect: (account: TAccount) => void; onAddAccount: () => void; + onEditAccount: (acc: TAccount) => void; }) => ( - + - Account + Accounts @@ -76,6 +99,8 @@ const AccountModal = ({ onAccountSelect({ name, address }); onClose(); }} + onEdit={() => onEditAccount({ name, address })} + selected={selectedAccount === address} key={address} /> ))} @@ -111,10 +136,10 @@ const AddAccountModal = ({ }, [show]); return ( - + - Account + Add new account @@ -130,6 +155,7 @@ const AddAccountModal = ({ fullWidth value={accountName} onChange={(e) => setAccountName(e.target.value)} + autoFocus /> @@ -150,38 +176,116 @@ const AddAccountModal = ({ ); }; +const EditAccountModal = ({ + account, + show, + onClose, + onEdit, +}: { + account?: TAccount; + show: boolean; + onClose: () => void; + onEdit: (account: TAccount) => void; +}) => { + const [accountName, setAccountName] = useState(''); + + useEffect(() => { + setAccountName(account ? account?.name : ''); + }, [account]); + + return ( + + + + Edit account name + + + + + + New wallet address + + + + + setAccountName(e.target.value)} + autoFocus + /> + + + + + + + ); +}; + export const Accounts = ({ storedAccounts }: { storedAccounts: TAccount[] }) => { const [accounts, setAccounts] = useState(storedAccounts); const [selectedAccount, setSelectedAccount] = useState(accounts[0]); - const [showAccountsDialog, setShowAccountsDialog] = useState(false); - const [showNewAccountsDialog, setShowNewAccountsDialog] = useState(false); + const [accountToEdit, setAccountToEdit] = useState(); + const [dialogToDisplasy, setDialogToDisplay] = useState(); + + useEffect(() => { + const selected = accounts.find((acc) => acc.address === selectedAccount.address); + if (selected) setSelectedAccount(selected); + }, [accounts]); return ( <> - setShowAccountsDialog(false)} + setDialogToDisplay(undefined)} accounts={accounts} onAccountSelect={(acc) => setSelectedAccount(acc)} + selectedAccount={selectedAccount.address} onAddAccount={() => { - setShowAccountsDialog(false); - setShowNewAccountsDialog(true); + setDialogToDisplay('Add'); + }} + onEditAccount={(acc) => { + setAccountToEdit(acc); + setDialogToDisplay('Edit'); }} /> setShowNewAccountsDialog(false)} + show={dialogToDisplasy === 'Add'} + onClose={() => { + setDialogToDisplay(undefined); + }} onAdd={(name) => { setAccounts((accs) => [...accs, { address: uuidv4(), name }]); - setShowNewAccountsDialog(false); - setShowAccountsDialog(true); + setDialogToDisplay('Accounts'); + }} + /> + { + setDialogToDisplay('Accounts'); + }} + onEdit={(account) => { + setAccounts((accs) => accs.map((acc) => (acc.address === account.address ? account : acc))); + setDialogToDisplay('Accounts'); }} /> From 50f4699c9527d2f82c7bd610ac02365f24b3241b Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 7 Apr 2022 22:04:19 +0100 Subject: [PATCH 07/85] change button size --- nym-wallet/src/components/Accounts.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nym-wallet/src/components/Accounts.tsx b/nym-wallet/src/components/Accounts.tsx index fd91fe34dc..e8ef621b10 100644 --- a/nym-wallet/src/components/Accounts.tsx +++ b/nym-wallet/src/components/Accounts.tsx @@ -250,6 +250,8 @@ export const Accounts = ({ storedAccounts }: { storedAccounts: TAccount[] }) => startIcon={} color="inherit" onClick={() => setDialogToDisplay('Accounts')} + size="large" + disableRipple > {selectedAccount.name} From 019a04c0fc152a304ef687ae2381e74df595f768 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Tue, 12 Apr 2022 10:33:16 +0100 Subject: [PATCH 08/85] code tidy and restructure --- nym-wallet/src/components/Accounts.tsx | 295 ------------------ .../src/components/Accounts/AccountColor.tsx | 5 + .../src/components/Accounts/AccountItem.tsx | 43 +++ .../{ => Accounts}/Accounts.stories.tsx | 0 .../src/components/Accounts/Accounts.tsx | 72 +++++ .../src/components/Accounts/AddAccount.tsx | 69 ++++ .../src/components/Accounts/EditAccount.tsx | 70 +++++ nym-wallet/src/components/Accounts/index.tsx | 75 +++++ nym-wallet/src/context/index.tsx | 2 + .../context/index.tsx => context/sign-in.tsx} | 2 +- nym-wallet/src/index.tsx | 2 +- .../pages/sign-in/pages/confirm-mnemonic.tsx | 2 +- .../pages/sign-in/pages/connect-password.tsx | 4 +- .../pages/sign-in/pages/create-mnemonic.tsx | 2 +- .../pages/sign-in/pages/create-password.tsx | 7 +- .../pages/sign-in/pages/verify-mnemonic.tsx | 4 +- 16 files changed, 347 insertions(+), 307 deletions(-) delete mode 100644 nym-wallet/src/components/Accounts.tsx create mode 100644 nym-wallet/src/components/Accounts/AccountColor.tsx create mode 100644 nym-wallet/src/components/Accounts/AccountItem.tsx rename nym-wallet/src/components/{ => Accounts}/Accounts.stories.tsx (100%) create mode 100644 nym-wallet/src/components/Accounts/Accounts.tsx create mode 100644 nym-wallet/src/components/Accounts/AddAccount.tsx create mode 100644 nym-wallet/src/components/Accounts/EditAccount.tsx create mode 100644 nym-wallet/src/components/Accounts/index.tsx create mode 100644 nym-wallet/src/context/index.tsx rename nym-wallet/src/{pages/sign-in/context/index.tsx => context/sign-in.tsx} (97%) diff --git a/nym-wallet/src/components/Accounts.tsx b/nym-wallet/src/components/Accounts.tsx deleted file mode 100644 index e8ef621b10..0000000000 --- a/nym-wallet/src/components/Accounts.tsx +++ /dev/null @@ -1,295 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { - Box, - Button, - Dialog, - DialogActions, - DialogContent, - DialogTitle, - IconButton, - ListItem, - ListItemAvatar, - ListItemButton, - ListItemIcon, - ListItemText, - TextField, - Typography, -} from '@mui/material'; -import stc from 'string-to-color'; -import { Add, CircleTwoTone, Close, Edit } from '@mui/icons-material'; -import { v4 as uuidv4 } from 'uuid'; - -export type TAccount = { - name: string; - address: string; -}; - -type TDialog = 'Accounts' | 'Add' | 'Edit'; - -const AccountColor = ({ address }: { address: string }) => ; - -const AccountItem = ({ - name, - address, - selected, - onSelect, - onEdit, -}: { - name: string; - address: string; - selected: boolean; - onSelect: () => void; - onEdit: () => void; -}) => ( - - - - - - - - { - e.stopPropagation(); - onEdit(); - }} - > - - - - - -); - -const AccountsModal = ({ - show, - accounts, - selectedAccount, - onClose, - onAccountSelect, - onAddAccount, - onEditAccount, -}: { - show: boolean; - accounts: TAccount[]; - selectedAccount: TAccount['address']; - onClose: () => void; - onAccountSelect: (account: TAccount) => void; - onAddAccount: () => void; - onEditAccount: (acc: TAccount) => void; -}) => ( - - - - Accounts - - - - - - Switch between accounts - - - - {accounts.map(({ name, address }) => ( - { - onAccountSelect({ name, address }); - onClose(); - }} - onEdit={() => onEditAccount({ name, address })} - selected={selectedAccount === address} - key={address} - /> - ))} - - - - - -); - -const AddAccountModal = ({ - show, - onClose, - onAdd, -}: { - show: boolean; - onClose: () => void; - onAdd: (accountName: string) => void; -}) => { - const [accountName, setAccountName] = useState(''); - - useEffect(() => { - if (!show) setAccountName(''); - }, [show]); - - return ( - - - - Add new account - - - - - - New wallet address - - - - - setAccountName(e.target.value)} - autoFocus - /> - - - - - - - ); -}; - -const EditAccountModal = ({ - account, - show, - onClose, - onEdit, -}: { - account?: TAccount; - show: boolean; - onClose: () => void; - onEdit: (account: TAccount) => void; -}) => { - const [accountName, setAccountName] = useState(''); - - useEffect(() => { - setAccountName(account ? account?.name : ''); - }, [account]); - - return ( - - - - Edit account name - - - - - - New wallet address - - - - - setAccountName(e.target.value)} - autoFocus - /> - - - - - - - ); -}; - -export const Accounts = ({ storedAccounts }: { storedAccounts: TAccount[] }) => { - const [accounts, setAccounts] = useState(storedAccounts); - const [selectedAccount, setSelectedAccount] = useState(accounts[0]); - const [accountToEdit, setAccountToEdit] = useState(); - const [dialogToDisplasy, setDialogToDisplay] = useState(); - - useEffect(() => { - const selected = accounts.find((acc) => acc.address === selectedAccount.address); - if (selected) setSelectedAccount(selected); - }, [accounts]); - - return ( - <> - - setDialogToDisplay(undefined)} - accounts={accounts} - onAccountSelect={(acc) => setSelectedAccount(acc)} - selectedAccount={selectedAccount.address} - onAddAccount={() => { - setDialogToDisplay('Add'); - }} - onEditAccount={(acc) => { - setAccountToEdit(acc); - setDialogToDisplay('Edit'); - }} - /> - { - setDialogToDisplay(undefined); - }} - onAdd={(name) => { - setAccounts((accs) => [...accs, { address: uuidv4(), name }]); - setDialogToDisplay('Accounts'); - }} - /> - { - setDialogToDisplay('Accounts'); - }} - onEdit={(account) => { - setAccounts((accs) => accs.map((acc) => (acc.address === account.address ? account : acc))); - setDialogToDisplay('Accounts'); - }} - /> - - ); -}; diff --git a/nym-wallet/src/components/Accounts/AccountColor.tsx b/nym-wallet/src/components/Accounts/AccountColor.tsx new file mode 100644 index 0000000000..98da4d0b72 --- /dev/null +++ b/nym-wallet/src/components/Accounts/AccountColor.tsx @@ -0,0 +1,5 @@ +import React from 'react'; +import { CircleTwoTone } from '@mui/icons-material'; +import stc from 'string-to-color'; + +export const AccountColor = ({ address }: { address: string }) => ; diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx new file mode 100644 index 0000000000..0db35b7707 --- /dev/null +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { IconButton, ListItem, ListItemAvatar, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; +import stc from 'string-to-color'; +import { Edit } from '@mui/icons-material'; +import { AccountColor } from './AccountColor'; + +export type TAccount = { + name: string; + address: string; +}; + +export const AccountItem = ({ + name, + address, + selected, + onSelect, + onEdit, +}: { + name: string; + address: string; + selected: boolean; + onSelect: () => void; + onEdit: () => void; +}) => ( + + + + + + + + { + e.stopPropagation(); + onEdit(); + }} + > + + + + + +); diff --git a/nym-wallet/src/components/Accounts.stories.tsx b/nym-wallet/src/components/Accounts/Accounts.stories.tsx similarity index 100% rename from nym-wallet/src/components/Accounts.stories.tsx rename to nym-wallet/src/components/Accounts/Accounts.stories.tsx diff --git a/nym-wallet/src/components/Accounts/Accounts.tsx b/nym-wallet/src/components/Accounts/Accounts.tsx new file mode 100644 index 0000000000..b4969f651a --- /dev/null +++ b/nym-wallet/src/components/Accounts/Accounts.tsx @@ -0,0 +1,72 @@ +import Reactfrom 'react'; +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + Typography, +} from '@mui/material'; +import { CircleTwoTone, Close } from '@mui/icons-material'; +import { AccountColor } from './AccountColor'; + +export const AccountsModal = ({ + show, + accounts, + selectedAccount, + onClose, + onAccountSelect, + onAddAccount, + onEditAccount, +}: { + show: boolean; + accounts: TAccount[]; + selectedAccount: TAccount['address']; + onClose: () => void; + onAccountSelect: (account: TAccount) => void; + onAddAccount: () => void; + onEditAccount: (acc: TAccount) => void; +}) => ( + + + + Accounts + + + + + + Switch between accounts + + + + {accounts.map(({ name, address }) => ( + { + onAccountSelect({ name, address }); + onClose(); + }} + onEdit={() => onEditAccount({ name, address })} + selected={selectedAccount === address} + key={address} + /> + ))} + + + + + +); diff --git a/nym-wallet/src/components/Accounts/AddAccount.tsx b/nym-wallet/src/components/Accounts/AddAccount.tsx new file mode 100644 index 0000000000..fcfeedb618 --- /dev/null +++ b/nym-wallet/src/components/Accounts/AddAccount.tsx @@ -0,0 +1,69 @@ +import React, { useEffect, useState } from 'react'; +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + TextField, + Typography, +} from '@mui/material'; +import { Add, Close } from '@mui/icons-material'; + +export const AddAccountModal = ({ + show, + onClose, + onAdd, +}: { + show: boolean; + onClose: () => void; + onAdd: (accountName: string) => void; +}) => { + const [accountName, setAccountName] = useState(''); + + useEffect(() => { + if (!show) setAccountName(''); + }, [show]); + + return ( + + + + Add new account + + + + + + New wallet address + + + + + setAccountName(e.target.value)} + autoFocus + /> + + + + + + + ); +}; diff --git a/nym-wallet/src/components/Accounts/EditAccount.tsx b/nym-wallet/src/components/Accounts/EditAccount.tsx new file mode 100644 index 0000000000..663a39a9ca --- /dev/null +++ b/nym-wallet/src/components/Accounts/EditAccount.tsx @@ -0,0 +1,70 @@ +import React, { useEffect, useState } from 'react'; +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + TextField, + Typography, +} from '@mui/material'; +import { Close } from '@mui/icons-material'; + +export const EditAccountModal = ({ + account, + show, + onClose, + onEdit, +}: { + account?: TAccount; + show: boolean; + onClose: () => void; + onEdit: (account: TAccount) => void; +}) => { + const [accountName, setAccountName] = useState(''); + + useEffect(() => { + setAccountName(account ? account?.name : ''); + }, [account]); + + return ( + + + + Edit account name + + + + + + New wallet address + + + + + setAccountName(e.target.value)} + autoFocus + /> + + + + + + + ); +}; diff --git a/nym-wallet/src/components/Accounts/index.tsx b/nym-wallet/src/components/Accounts/index.tsx new file mode 100644 index 0000000000..57372c9f12 --- /dev/null +++ b/nym-wallet/src/components/Accounts/index.tsx @@ -0,0 +1,75 @@ +import React, { useEffect, useState } from 'react'; +import { Button } from '@mui/material'; +import { v4 as uuidv4 } from 'uuid'; +import { EditAccountModal } from './EditAccount'; +import { AddAccountModal } from './AddAccount'; +import { AccountColor } from './AccountColor'; +import { AccountsModal } from './Accounts'; + +export type TAccount = { + name: string; + address: string; +}; + +type TDialog = 'Accounts' | 'Add' | 'Edit'; + +export const Accounts = ({ storedAccounts }: { storedAccounts: TAccount[] }) => { + const [accounts, setAccounts] = useState(storedAccounts); + const [selectedAccount, setSelectedAccount] = useState(accounts[0]); + const [accountToEdit, setAccountToEdit] = useState(); + const [dialogToDisplasy, setDialogToDisplay] = useState(); + + useEffect(() => { + const selected = accounts.find((acc) => acc.address === selectedAccount.address); + if (selected) setSelectedAccount(selected); + }, [accounts]); + + return ( + <> + + setDialogToDisplay(undefined)} + accounts={accounts} + onAccountSelect={(acc) => setSelectedAccount(acc)} + selectedAccount={selectedAccount.address} + onAddAccount={() => { + setDialogToDisplay('Add'); + }} + onEditAccount={(acc) => { + setAccountToEdit(acc); + setDialogToDisplay('Edit'); + }} + /> + { + setDialogToDisplay(undefined); + }} + onAdd={(name) => { + setAccounts((accs) => [...accs, { address: uuidv4(), name }]); + setDialogToDisplay('Accounts'); + }} + /> + { + setDialogToDisplay('Accounts'); + }} + onEdit={(account) => { + setAccounts((accs) => accs.map((acc) => (acc.address === account.address ? account : acc))); + setDialogToDisplay('Accounts'); + }} + /> + + ); +}; diff --git a/nym-wallet/src/context/index.tsx b/nym-wallet/src/context/index.tsx new file mode 100644 index 0000000000..fe68268d78 --- /dev/null +++ b/nym-wallet/src/context/index.tsx @@ -0,0 +1,2 @@ +export * from './main'; +export * from './sign-in'; diff --git a/nym-wallet/src/pages/sign-in/context/index.tsx b/nym-wallet/src/context/sign-in.tsx similarity index 97% rename from nym-wallet/src/pages/sign-in/context/index.tsx rename to nym-wallet/src/context/sign-in.tsx index 962c6a7340..4af1973e13 100644 --- a/nym-wallet/src/pages/sign-in/context/index.tsx +++ b/nym-wallet/src/context/sign-in.tsx @@ -1,7 +1,7 @@ import React, { createContext, useEffect, useMemo, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { createMnemonic } from 'src/requests'; -import { TMnemonicWords } from '../types'; +import { TMnemonicWords } from 'src/pages/sign-in/types'; export const SignInContext = createContext({} as TSignInContent); diff --git a/nym-wallet/src/index.tsx b/nym-wallet/src/index.tsx index 2400a8b00a..e16fbcdab0 100644 --- a/nym-wallet/src/index.tsx +++ b/nym-wallet/src/index.tsx @@ -10,7 +10,7 @@ import { Admin, Settings } from './pages'; import { ErrorFallback } from './components'; import { NymWalletTheme, WelcomeTheme } from './theme'; import { maximizeWindow } from './utils'; -import { SignInProvider } from './pages/sign-in/context'; +import { SignInProvider } from './context'; const App = () => { const { clientDetails } = useContext(ClientContext); diff --git a/nym-wallet/src/pages/sign-in/pages/confirm-mnemonic.tsx b/nym-wallet/src/pages/sign-in/pages/confirm-mnemonic.tsx index e1125ab283..4b62a9a8ed 100644 --- a/nym-wallet/src/pages/sign-in/pages/confirm-mnemonic.tsx +++ b/nym-wallet/src/pages/sign-in/pages/confirm-mnemonic.tsx @@ -2,8 +2,8 @@ import React, { useContext, useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { Button, Stack } from '@mui/material'; import { validateMnemonic } from 'src/requests'; +import { SignInContext } from 'src/context'; import { MnemonicInput, Subtitle } from '../components'; -import { SignInContext } from '../context'; export const ConfirmMnemonic = () => { const { error, setError, setMnemonic, mnemonic } = useContext(SignInContext); diff --git a/nym-wallet/src/pages/sign-in/pages/connect-password.tsx b/nym-wallet/src/pages/sign-in/pages/connect-password.tsx index f53689686e..22fa9e5a18 100644 --- a/nym-wallet/src/pages/sign-in/pages/connect-password.tsx +++ b/nym-wallet/src/pages/sign-in/pages/connect-password.tsx @@ -2,10 +2,10 @@ import React, { useContext, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { Button, CircularProgress, FormControl, Stack } from '@mui/material'; import { useSnackbar } from 'notistack'; +import { SignInContext } from 'src/context'; +import { createPassword } from 'src/requests'; import { Subtitle, Title, PasswordStrength } from '../components'; import { PasswordInput } from '../components/textfields'; -import { SignInContext } from '../context'; -import { createPassword } from '../../../requests'; export const ConnectPassword = () => { const [confirmedPassword, setConfirmedPassword] = useState(''); diff --git a/nym-wallet/src/pages/sign-in/pages/create-mnemonic.tsx b/nym-wallet/src/pages/sign-in/pages/create-mnemonic.tsx index 9b5b1e6060..f9c962bce5 100644 --- a/nym-wallet/src/pages/sign-in/pages/create-mnemonic.tsx +++ b/nym-wallet/src/pages/sign-in/pages/create-mnemonic.tsx @@ -1,10 +1,10 @@ import React, { useContext, useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { Alert, Button, Stack, Typography } from '@mui/material'; +import { SignInContext } from 'src/context'; import { Check, ContentCopySharp } from '@mui/icons-material'; import { useClipboard } from 'use-clipboard-copy'; import { WordTiles } from '../components'; -import { SignInContext } from '../context'; export const CreateMnemonic = () => { const { mnemonic, mnemonicWords, generateMnemonic, resetState } = useContext(SignInContext); diff --git a/nym-wallet/src/pages/sign-in/pages/create-password.tsx b/nym-wallet/src/pages/sign-in/pages/create-password.tsx index 11b4abdff5..4ff04baad7 100644 --- a/nym-wallet/src/pages/sign-in/pages/create-password.tsx +++ b/nym-wallet/src/pages/sign-in/pages/create-password.tsx @@ -2,10 +2,9 @@ import React, { useContext, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { Button, FormControl, Stack } from '@mui/material'; import { useSnackbar } from 'notistack'; -import { Subtitle, Title, PasswordStrength } from '../components'; -import { PasswordInput } from '../components/textfields'; -import { SignInContext } from '../context'; -import { createPassword } from '../../../requests'; +import { SignInContext } from 'src/context/sign-in'; +import { createPassword } from 'src/requests'; +import { PasswordInput, Subtitle, Title, PasswordStrength } from '../components'; export const CreatePassword = () => { const { password, setPassword, resetState } = useContext(SignInContext); diff --git a/nym-wallet/src/pages/sign-in/pages/verify-mnemonic.tsx b/nym-wallet/src/pages/sign-in/pages/verify-mnemonic.tsx index 9e4a4f17b7..ddb84f1265 100644 --- a/nym-wallet/src/pages/sign-in/pages/verify-mnemonic.tsx +++ b/nym-wallet/src/pages/sign-in/pages/verify-mnemonic.tsx @@ -1,10 +1,10 @@ import React, { useContext, useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { Button, Stack } from '@mui/material'; +import { SignInContext } from 'src/context/sign-in'; +import { randomNumberBetween } from 'src/utils'; import { HiddenWords, Subtitle, Title, WordTiles } from '../components'; import { THiddenMnemonicWord, THiddenMnemonicWords, TMnemonicWord, TMnemonicWords } from '../types'; -import { randomNumberBetween } from '../../../utils'; -import { SignInContext } from '../context'; const numberOfRandomWords = 6; From a5e6032393fcf6d98f1bd0f87a93df437a937ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 12 Apr 2022 11:53:38 +0200 Subject: [PATCH 09/85] wallet: support multiple accounts per encrypted login (#1205) * wallet: support multiple accounts per encrypted login Rework wallet storage to allow grouping accounts under a single encrypted entry, in a way that is backwards compatible. * wallet: remove commented out lines --- nym-wallet/src-tauri/src/error.rs | 8 + nym-wallet/src-tauri/src/main.rs | 7 +- .../src/operations/mixnet/account.rs | 73 +++- .../src/wallet_storage/account_data.rs | 257 ++++++++++--- .../src-tauri/src/wallet_storage/mod.rs | 341 +++++++++++++++--- .../src-tauri/src/wallet_storage/password.rs | 14 +- 6 files changed, 579 insertions(+), 121 deletions(-) diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index 077e949939..ef0da6a5f8 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -85,10 +85,18 @@ pub enum BackendError { WalletFileNotFound, #[error("Account ID not found in wallet")] NoSuchIdInWallet, + #[error("Account ID not found in wallet login entry")] + NoSuchIdInWalletLoginEntry, #[error("Account ID already found in wallet")] IdAlreadyExistsInWallet, + #[error("Account ID already found in stored wallet login")] + IdAlreadyExistsInStoredWalletLogin, #[error("Adding a different password to the wallet not currently supported")] WalletDifferentPasswordDetected, + #[error("Unexpted multiple account entries found")] + WalletUnexpectedMultipleAccounts, + #[error("Unexpted mnemonic account found")] + WalletUnexpectedMnemonicAccount, } impl Serialize for BackendError { diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index 61346a19cf..417cc416e9 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -34,21 +34,24 @@ fn main() { tauri::Builder::default() .manage(Arc::new(RwLock::new(State::default()))) .invoke_handler(tauri::generate_handler![ + mixnet::account::add_account_for_password, mixnet::account::connect_with_mnemonic, - mixnet::account::validate_mnemonic, mixnet::account::create_new_account, mixnet::account::create_new_mnemonic, mixnet::account::create_password, mixnet::account::does_password_file_exist, mixnet::account::get_balance, - mixnet::account::get_validator_nymd_urls, mixnet::account::get_validator_api_urls, + mixnet::account::get_validator_nymd_urls, + mixnet::account::list_accounts_for_password, mixnet::account::logout, + mixnet::account::remove_account_for_password, mixnet::account::remove_password, mixnet::account::sign_in_with_password, mixnet::account::switch_network, mixnet::account::update_validator_urls, mixnet::account::validate_mnemonic, + mixnet::account::validate_mnemonic, mixnet::admin::get_contract_settings, mixnet::admin::update_contract_settings, mixnet::bond::bond_gateway, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 12901cc046..2c29d81d38 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -4,6 +4,7 @@ use crate::error::BackendError; use crate::network::Network as WalletNetwork; use crate::nymd_client; use crate::state::State; +use crate::wallet_storage::account_data::StoredLogin; use crate::wallet_storage::{self, DEFAULT_WALLET_ACCOUNT_ID}; use bip39::{Language, Mnemonic}; @@ -113,9 +114,8 @@ pub async fn create_new_account( } #[tauri::command] -pub async fn create_new_mnemonic() -> Result { - let rand_mnemonic = random_mnemonic(); - Ok(rand_mnemonic.to_string()) +pub fn create_new_mnemonic() -> String { + random_mnemonic().to_string() } #[tauri::command] @@ -394,16 +394,16 @@ pub fn does_password_file_exist() -> Result { } #[tauri::command] -pub fn create_password(mnemonic: String, password: String) -> Result<(), BackendError> { +pub fn create_password(mnemonic: &str, password: String) -> Result<(), BackendError> { if does_password_file_exist()? { return Err(BackendError::WalletFileAlreadyExists); } log::info!("Creating password"); - let mnemonic = Mnemonic::from_str(&mnemonic)?; + let mnemonic = Mnemonic::from_str(mnemonic)?; let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); // Currently we only support a single, default, id in the wallet - let id = wallet_storage::WalletAccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); + let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); let password = wallet_storage::UserPassword::new(password); wallet_storage::store_wallet_login_information(mnemonic, hd_path, id, &password) } @@ -416,15 +416,70 @@ pub async fn sign_in_with_password( log::info!("Signing in with password"); // Currently we only support a single, default, id in the wallet - let id = wallet_storage::WalletAccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); + let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); let password = wallet_storage::UserPassword::new(password); let stored_account = wallet_storage::load_existing_wallet_login_information(&id, &password)?; - _connect_with_mnemonic(stored_account.mnemonic().clone(), state).await + let mnemonic = match stored_account { + StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), + StoredLogin::Multiple(ref accounts) => { + // Login using the first account in the list + accounts + .get_accounts() + .next() + .ok_or(BackendError::NoSuchIdInWalletLoginEntry)? + .account + .mnemonic() + .clone() + } + }; + _connect_with_mnemonic(mnemonic, state).await } #[tauri::command] pub fn remove_password() -> Result<(), BackendError> { log::info!("Removing password"); - let id = wallet_storage::WalletAccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); + let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); wallet_storage::remove_wallet_login_information(&id) } + +#[tauri::command] +pub fn add_account_for_password( + mnemonic: &str, + password: &str, + inner_id: &str, +) -> Result<(), BackendError> { + let mnemonic = Mnemonic::from_str(mnemonic)?; + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + // Currently we only support a single, default, id in the wallet + let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); + let inner_id = wallet_storage::AccountId::new(inner_id.to_string()); + let password = wallet_storage::UserPassword::new(password.to_string()); + wallet_storage::append_account_to_wallet_login_information( + mnemonic, hd_path, id, inner_id, &password, + ) +} + +#[tauri::command] +pub fn remove_account_for_password(password: &str, inner_id: &str) -> Result<(), BackendError> { + // Currently we only support a single, default, id in the wallet + let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); + let inner_id = wallet_storage::AccountId::new(inner_id.to_string()); + let password = wallet_storage::UserPassword::new(password.to_string()); + wallet_storage::remove_account_from_wallet_login(&id, &inner_id, &password) +} + +#[tauri::command] +pub fn list_accounts_for_password(password: &str) -> Result, BackendError> { + // Currently we only support a single, default, id in the wallet + let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); + let password = wallet_storage::UserPassword::new(password.to_string()); + let login = wallet_storage::load_existing_wallet_login_information(&id, &password)?; + let ids = match login { + StoredLogin::Mnemonic(_) => vec![id.to_string()], + StoredLogin::Multiple(ref accounts) => accounts + .get_accounts() + .map(|account| account.id.to_string()) + .collect::>(), + }; + Ok(ids) +} diff --git a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs index 760208b6b8..7336a33c0e 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -8,15 +8,16 @@ use zeroize::Zeroize; use crate::error::BackendError; use super::encryption::EncryptedData; -use super::password::WalletAccountId; +use super::password::AccountId; use super::UserPassword; const CURRENT_WALLET_FILE_VERSION: u32 = 1; +/// The wallet, stored as a serialized json file. #[derive(Serialize, Deserialize, Debug)] pub(crate) struct StoredWallet { version: u32, - accounts: Vec, + accounts: Vec, } impl StoredWallet { @@ -25,16 +26,59 @@ impl StoredWallet { self.version } - pub fn is_empty(&self) -> bool { - self.accounts.is_empty() - } - #[allow(unused)] pub fn len(&self) -> usize { self.accounts.len() } - pub fn remove_account(&mut self, id: &WalletAccountId) -> Option { + pub fn is_empty(&self) -> bool { + self.accounts.is_empty() + } + + pub fn add_encrypted_login(&mut self, new_login: EncryptedLogin) -> Result<(), BackendError> { + if self.get_encrypted_login(&new_login.id).is_ok() { + return Err(BackendError::IdAlreadyExistsInWallet); + } + self.accounts.push(new_login); + Ok(()) + } + + fn get_encrypted_login( + &self, + id: &AccountId, + ) -> Result<&EncryptedData, BackendError> { + self + .accounts + .iter() + .find(|account| &account.id == id) + .map(|account| &account.account) + .ok_or(BackendError::NoSuchIdInWallet) + } + + fn get_encrypted_login_mut( + &mut self, + id: &AccountId, + ) -> Result<&mut EncryptedLogin, BackendError> { + self + .accounts + .iter_mut() + .find(|account| &account.id == id) + //.map(|account| &mut account.account) + .ok_or(BackendError::NoSuchIdInWallet) + } + + #[cfg(test)] + pub fn get_encrypted_login_by_index(&self, index: usize) -> Option<&EncryptedLogin> { + self.accounts.get(index) + } + + pub fn replace_encrypted_login(&mut self, new_login: EncryptedLogin) -> Result<(), BackendError> { + let login = self.get_encrypted_login_mut(&new_login.id)?; + *login = new_login; + Ok(()) + } + + pub fn remove_encrypted_login(&mut self, id: &AccountId) -> Option { if let Some(index) = self.accounts.iter().position(|account| &account.id == id) { log::info!("Removing from wallet file: {id}"); Some(self.accounts.remove(index)) @@ -44,43 +88,15 @@ impl StoredWallet { } } - #[allow(unused)] - pub fn encrypted_account_by_index(&self, index: usize) -> Option<&EncryptedAccount> { - self.accounts.get(index) - } - - fn encrypted_account( + pub fn decrypt_login( &self, - id: &WalletAccountId, - ) -> Result<&EncryptedData, BackendError> { - self - .accounts - .iter() - .find(|account| &account.id == id) - .map(|account| &account.account) - .ok_or(BackendError::NoSuchIdInWallet) - } - - pub fn add_encrypted_account( - &mut self, - new_account: EncryptedAccount, - ) -> Result<(), BackendError> { - if self.encrypted_account(&new_account.id).is_ok() { - return Err(BackendError::IdAlreadyExistsInWallet); - } - self.accounts.push(new_account); - Ok(()) - } - - pub fn decrypt_account( - &self, - id: &WalletAccountId, + id: &AccountId, password: &UserPassword, - ) -> Result { - self.encrypted_account(id)?.decrypt_struct(password) + ) -> Result { + self.get_encrypted_login(id)?.decrypt_struct(password) } - pub fn decrypt_all(&self, password: &UserPassword) -> Result, BackendError> { + pub fn decrypt_all(&self, password: &UserPassword) -> Result, BackendError> { self .accounts .iter() @@ -102,39 +118,51 @@ impl Default for StoredWallet { } } +/// Each entry in the stored wallet file. An id field in plaintext and an encrypted stored login. #[derive(Serialize, Deserialize, Debug)] -pub(crate) struct EncryptedAccount { - pub id: WalletAccountId, - pub account: EncryptedData, +pub(crate) struct EncryptedLogin { + pub id: AccountId, + pub account: EncryptedData, } -// future-proofing +/// A stored login is either a account, such as a mnemonic, or a list of multiple accounts where +/// each has an inner id. Future proofed for having private key backed accounts. #[derive(Serialize, Deserialize, Debug, Zeroize)] #[serde(untagged)] #[zeroize(drop)] -pub(crate) enum StoredAccount { +pub(crate) enum StoredLogin { Mnemonic(MnemonicAccount), // PrivateKey(PrivateKeyAccount) + Multiple(MultipleAccounts), } -impl StoredAccount { +impl StoredLogin { pub(crate) fn new_mnemonic_backed_account( mnemonic: bip39::Mnemonic, hd_path: DerivationPath, - ) -> StoredAccount { - StoredAccount::Mnemonic(MnemonicAccount { mnemonic, hd_path }) + ) -> Self { + Self::Mnemonic(MnemonicAccount { mnemonic, hd_path }) } - // If we add accounts backed by something that is not a mnemonic, this should probably be changed - // to return `Option<..>`. - pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { + #[cfg(test)] + pub(crate) fn as_mnemonic_account(&self) -> Option<&MnemonicAccount> { match self { - StoredAccount::Mnemonic(account) => account.mnemonic(), + StoredLogin::Mnemonic(mn) => Some(mn), + StoredLogin::Multiple(_) => None, + } + } + + #[cfg(test)] + pub(crate) fn as_multiple_accounts(&self) -> Option<&MultipleAccounts> { + match self { + StoredLogin::Mnemonic(_) => None, + StoredLogin::Multiple(accounts) => Some(accounts), } } } -#[derive(Serialize, Deserialize, Debug)] +/// An account backed by a unique mnemonic. +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] pub(crate) struct MnemonicAccount { mnemonic: bip39::Mnemonic, #[serde(with = "display_hd_path")] @@ -142,14 +170,27 @@ pub(crate) struct MnemonicAccount { } impl MnemonicAccount { + pub(crate) fn generate_new(&self) -> MnemonicAccount { + MnemonicAccount { + mnemonic: bip39::Mnemonic::generate(self.mnemonic().word_count()).unwrap(), + hd_path: self.hd_path().clone(), + } + } + pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { &self.mnemonic } - #[allow(unused)] pub(crate) fn hd_path(&self) -> &DerivationPath { &self.hd_path } + + pub(crate) fn into_multiple(self, id: AccountId) -> MultipleAccounts { + MultipleAccounts::new(WalletAccount { + id, + account: self.into(), + }) + } } impl Zeroize for MnemonicAccount { @@ -173,6 +214,114 @@ impl Drop for MnemonicAccount { } } +/// Multiple stored accounts, each entry having an id and a data field. +#[derive(Serialize, Deserialize, Debug, Zeroize, PartialEq, Eq)] +pub(crate) struct MultipleAccounts { + accounts: Vec, +} + +impl MultipleAccounts { + pub(crate) fn new(account: WalletAccount) -> Self { + MultipleAccounts { + accounts: vec![account], + } + } + + pub(crate) fn get_accounts(&self) -> impl Iterator { + self.accounts.iter() + } + + pub(crate) fn get_account(&self, id: &AccountId) -> Option<&WalletAccount> { + self.accounts.iter().find(|account| &account.id == id) + } + + #[allow(unused)] + pub(crate) fn len(&self) -> usize { + self.accounts.len() + } + + pub(crate) fn is_empty(&self) -> bool { + self.accounts.is_empty() + } + + pub(crate) fn add( + &mut self, + id: AccountId, + mnemonic: bip39::Mnemonic, + hd_path: DerivationPath, + ) -> Result<(), BackendError> { + if self.get_account(&id).is_some() { + Err(BackendError::IdAlreadyExistsInStoredWalletLogin) + } else { + self + .accounts + .push(WalletAccount::new_mnemonic_backed_account( + id, mnemonic, hd_path, + )); + Ok(()) + } + } + + pub(crate) fn remove(&mut self, id: &AccountId) { + self.accounts.retain(|accounts| &accounts.id != id); + } +} + +impl From> for MultipleAccounts { + fn from(accounts: Vec) -> MultipleAccounts { + Self { accounts } + } +} + +/// An entry in the list of stored accounts +#[derive(Serialize, Deserialize, Debug, Zeroize, PartialEq, Eq)] +pub(crate) struct WalletAccount { + pub id: AccountId, + pub account: AccountData, +} + +impl WalletAccount { + pub(crate) fn new_mnemonic_backed_account( + id: AccountId, + mnemonic: bip39::Mnemonic, + hd_path: DerivationPath, + ) -> Self { + Self { + id, + account: AccountData::new_mnemonic_backed_account(mnemonic, hd_path), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Zeroize, PartialEq, Eq)] +#[serde(untagged)] +#[zeroize(drop)] +pub(crate) enum AccountData { + Mnemonic(MnemonicAccount), + // PrivateKey(PrivateKeyAccount) +} + +impl AccountData { + pub(crate) fn new_mnemonic_backed_account( + mnemonic: bip39::Mnemonic, + hd_path: DerivationPath, + ) -> AccountData { + AccountData::Mnemonic(MnemonicAccount { mnemonic, hd_path }) + } + + pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { + match self { + AccountData::Mnemonic(account) => account.mnemonic(), + } + } +} + +impl From for AccountData { + fn from(mnemonic_account: MnemonicAccount) -> Self { + AccountData::Mnemonic(mnemonic_account) + } +} + mod display_hd_path { use cosmrs::bip32::DerivationPath; use serde::{Deserialize, Deserializer, Serializer}; diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 862e23e682..8fb7d10775 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -1,17 +1,17 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub(crate) use crate::wallet_storage::password::{UserPassword, WalletAccountId}; +pub(crate) use crate::wallet_storage::password::{AccountId, UserPassword}; use crate::error::BackendError; use crate::platform_constants::{STORAGE_DIR_NAME, WALLET_INFO_FILENAME}; -use crate::wallet_storage::account_data::StoredAccount; +use crate::wallet_storage::account_data::StoredLogin; use crate::wallet_storage::encryption::encrypt_struct; use cosmrs::bip32::DerivationPath; use std::fs::{self, create_dir_all, OpenOptions}; use std::path::PathBuf; -use self::account_data::{EncryptedAccount, StoredWallet}; +use self::account_data::{EncryptedLogin, StoredWallet}; pub(crate) mod account_data; pub(crate) mod encryption; @@ -30,8 +30,9 @@ pub(crate) fn wallet_login_filepath() -> Result { get_storage_directory().map(|dir| dir.join(WALLET_INFO_FILENAME)) } +/// Load stored wallet file #[allow(unused)] -pub(crate) fn load_existing_wallet(password: &UserPassword) -> Result { +pub(crate) fn load_existing_wallet() -> Result { let store_dir = get_storage_directory()?; let filepath = store_dir.join(WALLET_INFO_FILENAME); load_existing_wallet_at_file(filepath) @@ -46,10 +47,12 @@ fn load_existing_wallet_at_file(filepath: PathBuf) -> Result Result { +) -> Result { let store_dir = get_storage_directory()?; let filepath = store_dir.join(WALLET_INFO_FILENAME); load_existing_wallet_login_information_at_file(filepath, id, password) @@ -57,16 +60,19 @@ pub(crate) fn load_existing_wallet_login_information( fn load_existing_wallet_login_information_at_file( filepath: PathBuf, - id: &WalletAccountId, + id: &AccountId, password: &UserPassword, -) -> Result { - load_existing_wallet_at_file(filepath)?.decrypt_account(id, password) +) -> Result { + load_existing_wallet_at_file(filepath)?.decrypt_login(id, password) } +/// Encrypt `mnemonic` and store it together with `id`. It is stored at the top-level. +/// Currently we enforce that we can only add entries with the same password as the other already +/// existing entries. This is not unlikely to change in the future. pub(crate) fn store_wallet_login_information( mnemonic: bip39::Mnemonic, hd_path: DerivationPath, - id: WalletAccountId, + id: AccountId, password: &UserPassword, ) -> Result<(), BackendError> { // make sure the entire directory structure exists @@ -81,7 +87,7 @@ fn store_wallet_login_information_at_file( filepath: PathBuf, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, - id: WalletAccountId, + id: AccountId, password: &UserPassword, ) -> Result<(), BackendError> { let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) { @@ -94,13 +100,13 @@ fn store_wallet_login_information_at_file( return Err(BackendError::WalletDifferentPasswordDetected); } - let new_account = StoredAccount::new_mnemonic_backed_account(mnemonic, hd_path); - let new_encrypted_account = EncryptedAccount { + let new_account = StoredLogin::new_mnemonic_backed_account(mnemonic, hd_path); + let new_encrypted_account = EncryptedLogin { id, account: encrypt_struct(&new_account, password)?, }; - stored_wallet.add_encrypted_account(new_encrypted_account)?; + stored_wallet.add_encrypted_login(new_encrypted_account)?; let file = OpenOptions::new() .create(true) @@ -111,16 +117,86 @@ fn store_wallet_login_information_at_file( Ok(serde_json::to_writer_pretty(file, &stored_wallet)?) } -pub(crate) fn remove_wallet_login_information(id: &WalletAccountId) -> Result<(), BackendError> { +/// Append an account to an already existing top-level encrypted account entry. +/// If the existing top-level entry is just a single account, it will be converted to the first +/// account in the list of accounts associated with the encrypted entry. The inner id for this +/// entry will be set to the same as the outer, unencrypted, id. +pub(crate) fn append_account_to_wallet_login_information( + mnemonic: bip39::Mnemonic, + hd_path: DerivationPath, + id: AccountId, + inner_id: AccountId, + password: &UserPassword, +) -> Result<(), BackendError> { + // make sure the entire directory structure exists + let store_dir = get_storage_directory()?; + create_dir_all(&store_dir)?; + let filepath = store_dir.join(WALLET_INFO_FILENAME); + + append_account_to_wallet_login_information_at_file( + filepath, mnemonic, hd_path, id, inner_id, password, + ) +} + +fn append_account_to_wallet_login_information_at_file( + filepath: PathBuf, + mnemonic: bip39::Mnemonic, + hd_path: DerivationPath, + id: AccountId, + inner_id: AccountId, + password: &UserPassword, +) -> Result<(), BackendError> { + let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) { + Err(BackendError::WalletFileNotFound) => StoredWallet::default(), + result => result?, + }; + + let mut decrypted_login = stored_wallet.decrypt_login(&id, password)?; + + // Since we can't clone the mnemonic, we have to perform a little dance were we add the mnemonic + // to the inner enum payload, while also converting by swapping if necessary. + if let StoredLogin::Multiple(ref mut accounts) = decrypted_login { + accounts.add(inner_id, mnemonic, hd_path)?; + } else if let StoredLogin::Mnemonic(ref mut account) = decrypted_login { + // Move out the account by swapping, since we can't clone. + let account = std::mem::replace(account, account.generate_new()); + // Convert the enum variant + let mut accounts = account.into_multiple(id.clone()); + accounts.add(inner_id, mnemonic, hd_path)?; + // Overwrite the stored login with the new enum variant + decrypted_login = StoredLogin::Multiple(accounts); + } + + let encrypted_accounts = EncryptedLogin { + id, + account: encrypt_struct(&decrypted_login, password)?, + }; + + stored_wallet.replace_encrypted_login(encrypted_accounts)?; + + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(filepath)?; + + Ok(serde_json::to_writer_pretty(file, &stored_wallet)?) +} + +/// Remove the entire encrypted login entry for the given `id`. This means potentially removing all +/// associated accounts! +/// If this was the last entry in the file, the file is removed. +pub(crate) fn remove_wallet_login_information(id: &AccountId) -> Result<(), BackendError> { let store_dir = get_storage_directory()?; let filepath = store_dir.join(WALLET_INFO_FILENAME); remove_wallet_login_information_at_file(filepath, id) } -pub(crate) fn remove_wallet_login_information_at_file( +fn remove_wallet_login_information_at_file( filepath: PathBuf, - id: &WalletAccountId, + id: &AccountId, ) -> Result<(), BackendError> { + log::warn!("Removing wallet account with id: {id}. This includes all associated accounts!"); let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) { Err(BackendError::WalletFileNotFound) => StoredWallet::default(), result => result?, @@ -132,7 +208,7 @@ pub(crate) fn remove_wallet_login_information_at_file( } stored_wallet - .remove_account(id) + .remove_encrypted_login(id) .ok_or(BackendError::NoSuchIdInWallet)?; if stored_wallet.is_empty() { @@ -149,8 +225,76 @@ pub(crate) fn remove_wallet_login_information_at_file( } } +/// Remove an account from inside the encrypted login. +/// - If the encrypted login is just a single account, abort to be on the safe side. +/// - If it is the last associated account with that login, the encrypted login will be removed. +/// - If this was the last encrypted login in the file, it will be removed. +pub(crate) fn remove_account_from_wallet_login( + id: &AccountId, + inner_id: &AccountId, + password: &UserPassword, +) -> Result<(), BackendError> { + let store_dir = get_storage_directory()?; + let filepath = store_dir.join(WALLET_INFO_FILENAME); + remove_account_from_wallet_login_at_file(filepath, id, inner_id, password) +} + +fn remove_account_from_wallet_login_at_file( + filepath: PathBuf, + id: &AccountId, + inner_id: &AccountId, + password: &UserPassword, +) -> Result<(), BackendError> { + log::info!("Removing associated account from login account: {id}"); + let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) { + Err(BackendError::WalletFileNotFound) => StoredWallet::default(), + result => result?, + }; + + let mut decrypted_login = stored_wallet.decrypt_login(id, password)?; + + let is_empty = match decrypted_login { + StoredLogin::Mnemonic(_) => { + log::warn!("Encountered mnemonic login instead of list of accounts, aborting"); + return Err(BackendError::WalletUnexpectedMnemonicAccount); + } + StoredLogin::Multiple(ref mut accounts) => { + accounts.remove(inner_id); + accounts.is_empty() + } + }; + + if is_empty { + stored_wallet + .remove_encrypted_login(id) + .ok_or(BackendError::NoSuchIdInWallet)?; + } else { + // Replace the encrypted login with the prune one. + let encrypted_accounts = EncryptedLogin { + id: id.clone(), + account: encrypt_struct(&decrypted_login, password)?, + }; + stored_wallet.replace_encrypted_login(encrypted_accounts)?; + } + + if stored_wallet.is_empty() { + log::info!("Removing file: {:#?}", filepath); + Ok(fs::remove_file(filepath)?) + } else { + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(filepath)?; + + Ok(serde_json::to_writer_pretty(file, &stored_wallet)?) + } +} + #[cfg(test)] mod tests { + use crate::wallet_storage::account_data::WalletAccount; + use super::*; use config::defaults::COSMOS_DERIVATION_PATH; use std::str::FromStr; @@ -171,8 +315,8 @@ mod tests { let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); - let id1 = WalletAccountId::new("first".to_string()); - let id2 = WalletAccountId::new("second".to_string()); + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); // Nothing was stored on the disk assert!(matches!( @@ -197,10 +341,13 @@ mod tests { let stored_wallet = load_existing_wallet_at_file(wallet_file.clone()).unwrap(); assert_eq!(stored_wallet.len(), 1); assert_eq!( - stored_wallet.encrypted_account_by_index(0).unwrap().id, - WalletAccountId::new("first".to_string()) + stored_wallet.get_encrypted_login_by_index(0).unwrap().id, + AccountId::new("first".to_string()) ); - let encrypted_blob = &stored_wallet.encrypted_account_by_index(0).unwrap().account; + let encrypted_blob = &stored_wallet + .get_encrypted_login_by_index(0) + .unwrap() + .account; // some actual ciphertext was saved assert!(!encrypted_blob.ciphertext().is_empty()); @@ -235,9 +382,12 @@ mod tests { let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let StoredAccount::Mnemonic(ref acc) = loaded_account; - assert_eq!(&dummy_account1, acc.mnemonic()); - assert_eq!(&cosmos_hd_path, acc.hd_path()); + if let StoredLogin::Mnemonic(ref acc) = loaded_account { + assert_eq!(&dummy_account1, acc.mnemonic()); + assert_eq!(&cosmos_hd_path, acc.hd_path()); + } else { + todo!(); + } // Can't store extra account if you use different password assert!(matches!( @@ -264,7 +414,7 @@ mod tests { let loaded_accounts = load_existing_wallet_at_file(wallet_file.clone()).unwrap(); assert_eq!(2, loaded_accounts.len()); let encrypted_blob = &loaded_accounts - .encrypted_account_by_index(1) + .get_encrypted_login_by_index(1) .unwrap() .account; @@ -275,18 +425,24 @@ mod tests { // first account should be unchanged let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let StoredAccount::Mnemonic(ref acc1) = loaded_account; - assert_eq!(&dummy_account1, acc1.mnemonic()); - assert_eq!(&cosmos_hd_path, acc1.hd_path()); + if let StoredLogin::Mnemonic(ref acc1) = loaded_account { + assert_eq!(&dummy_account1, acc1.mnemonic()); + assert_eq!(&cosmos_hd_path, acc1.hd_path()); + } else { + todo!(); + } let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); - let StoredAccount::Mnemonic(ref acc2) = loaded_account; - assert_eq!(&dummy_account2, acc2.mnemonic()); - assert_eq!(&different_hd_path, acc2.hd_path()); + if let StoredLogin::Mnemonic(ref acc2) = loaded_account { + assert_eq!(&dummy_account2, acc2.mnemonic()); + assert_eq!(&different_hd_path, acc2.hd_path()); + } else { + todo!(); + } // Fails to delete non-existent id in the wallet - let id3 = WalletAccountId::new("phony".to_string()); + let id3 = AccountId::new("phony".to_string()); assert!(matches!( remove_wallet_login_information_at_file(wallet_file.clone(), &id3), Err(BackendError::NoSuchIdInWallet), @@ -298,9 +454,12 @@ mod tests { // The first account should be unchanged let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let StoredAccount::Mnemonic(ref acc1) = loaded_account; - assert_eq!(&dummy_account1, acc1.mnemonic()); - assert_eq!(&cosmos_hd_path, acc1.hd_path()); + if let StoredLogin::Mnemonic(ref acc1) = loaded_account { + assert_eq!(&dummy_account1, acc1.mnemonic()); + assert_eq!(&cosmos_hd_path, acc1.hd_path()); + } else { + todo!(); + } // Delete the first account assert!(wallet_file.exists()); @@ -312,6 +471,8 @@ mod tests { #[test] fn decrypt_stored_wallet() { + pretty_env_logger::init(); + const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet.json"; let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET); @@ -320,27 +481,109 @@ mod tests { let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); - let id1 = WalletAccountId::new("first".to_string()); - let id2 = WalletAccountId::new("second".to_string()); + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); assert!(!wallet.password_can_decrypt_all(&bad_password)); assert!(wallet.password_can_decrypt_all(&password)); - let account1 = wallet.decrypt_account(&id1, &password).unwrap(); - let account2 = wallet.decrypt_account(&id2, &password).unwrap(); + let account1 = wallet.decrypt_login(&id1, &password).unwrap(); + let account2 = wallet.decrypt_login(&id2, &password).unwrap(); + + assert!(matches!(account1, StoredLogin::Mnemonic(_))); + assert!(matches!(account2, StoredLogin::Mnemonic(_))); let expected_account1 = bip39::Mnemonic::from_str("country mean universe text phone begin deputy reject result good cram illness common cluster proud swamp digital patrol spread bar face december base kick").unwrap(); let expected_account2 = bip39::Mnemonic::from_str("home mansion start quiz dress decide hint second dragon sunny juice always steak real minimum art rival skin draw total pulp foot goddess agent").unwrap(); - assert_eq!(account1.mnemonic(), &expected_account1); - assert_eq!(account2.mnemonic(), &expected_account2); + assert_eq!( + account1.as_mnemonic_account().unwrap().mnemonic(), + &expected_account1 + ); + assert_eq!( + account1.as_mnemonic_account().unwrap().hd_path(), + &cosmos_hd_path, + ); - let StoredAccount::Mnemonic(ref mnemonic1) = account1; - assert_eq!(mnemonic1.mnemonic(), &expected_account1); - assert_eq!(mnemonic1.hd_path(), &cosmos_hd_path); + assert_eq!( + account2.as_mnemonic_account().unwrap().mnemonic(), + &expected_account2 + ); + assert_eq!( + account2.as_mnemonic_account().unwrap().hd_path(), + &cosmos_hd_path, + ); + } - let StoredAccount::Mnemonic(ref mnemonic2) = account2; - assert_eq!(mnemonic2.mnemonic(), &expected_account2); - assert_eq!(mnemonic2.hd_path(), &cosmos_hd_path); + #[test] + fn append_a_third_account() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + let id3 = AccountId::new("third".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2.clone(), + cosmos_hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // Check that it's there as the correct non-multiple type + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); + let acc2 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(acc2.mnemonic(), &dummy_account2); + assert_eq!(acc2.hd_path(), &cosmos_hd_path); + + // Add a third mnenonic grouped together with the second one + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account3.clone(), + cosmos_hd_path.clone(), + id2.clone(), + id3.clone(), + &password, + ) + .unwrap(); + + // Check that we can still load all three + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc1 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(acc1.mnemonic(), &dummy_account1); + assert_eq!(acc1.hd_path(), &cosmos_hd_path); + + let loaded_accounts = + load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); + let accounts = loaded_accounts.as_multiple_accounts().unwrap(); + + let expected = vec![ + WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, cosmos_hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, cosmos_hd_path), + ] + .into(); + + assert_eq!(accounts, &expected); } } diff --git a/nym-wallet/src-tauri/src/wallet_storage/password.rs b/nym-wallet/src-tauri/src/wallet_storage/password.rs index 04b85277a1..016c5d3e5c 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/password.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/password.rs @@ -6,22 +6,22 @@ use std::fmt; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub(crate) struct WalletAccountId(String); +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Zeroize)] +pub(crate) struct AccountId(String); -impl WalletAccountId { - pub(crate) fn new(id: String) -> WalletAccountId { - WalletAccountId(id) +impl AccountId { + pub(crate) fn new(id: String) -> AccountId { + AccountId(id) } } -impl AsRef for WalletAccountId { +impl AsRef for AccountId { fn as_ref(&self) -> &str { self.0.as_ref() } } -impl fmt::Display for WalletAccountId { +impl fmt::Display for AccountId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } From 0cad7f635d3ff2730fabf0c163028458149a0aa6 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Tue, 12 Apr 2022 12:25:15 +0100 Subject: [PATCH 10/85] new stories --- .../src/components/Accounts/AccountItem.tsx | 6 -- .../{Accounts.tsx => AccountsModal.tsx} | 18 +--- .../Accounts/AddAccount.stories.tsx | 30 ++++++ .../src/components/Accounts/AddAccount.tsx | 69 ------------ .../components/Accounts/AddAccountModal.tsx | 100 ++++++++++++++++++ .../{EditAccount.tsx => EditAccountModal.tsx} | 1 + .../Accounts/ShowMnemonic.stories.tsx | 23 ++++ .../components/Accounts/ShowMnemonicModal.tsx | 47 ++++++++ nym-wallet/src/components/Accounts/index.tsx | 14 +-- nym-wallet/src/components/Accounts/types.ts | 6 ++ 10 files changed, 216 insertions(+), 98 deletions(-) rename nym-wallet/src/components/Accounts/{Accounts.tsx => AccountsModal.tsx} (84%) create mode 100644 nym-wallet/src/components/Accounts/AddAccount.stories.tsx delete mode 100644 nym-wallet/src/components/Accounts/AddAccount.tsx create mode 100644 nym-wallet/src/components/Accounts/AddAccountModal.tsx rename nym-wallet/src/components/Accounts/{EditAccount.tsx => EditAccountModal.tsx} (97%) create mode 100644 nym-wallet/src/components/Accounts/ShowMnemonic.stories.tsx create mode 100644 nym-wallet/src/components/Accounts/ShowMnemonicModal.tsx create mode 100644 nym-wallet/src/components/Accounts/types.ts diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index 0db35b7707..98f1851a16 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -1,14 +1,8 @@ import React from 'react'; import { IconButton, ListItem, ListItemAvatar, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; -import stc from 'string-to-color'; import { Edit } from '@mui/icons-material'; import { AccountColor } from './AccountColor'; -export type TAccount = { - name: string; - address: string; -}; - export const AccountItem = ({ name, address, diff --git a/nym-wallet/src/components/Accounts/Accounts.tsx b/nym-wallet/src/components/Accounts/AccountsModal.tsx similarity index 84% rename from nym-wallet/src/components/Accounts/Accounts.tsx rename to nym-wallet/src/components/Accounts/AccountsModal.tsx index b4969f651a..7c44effbd0 100644 --- a/nym-wallet/src/components/Accounts/Accounts.tsx +++ b/nym-wallet/src/components/Accounts/AccountsModal.tsx @@ -1,16 +1,8 @@ -import Reactfrom 'react'; -import { - Box, - Button, - Dialog, - DialogActions, - DialogContent, - DialogTitle, - IconButton, - Typography, -} from '@mui/material'; -import { CircleTwoTone, Close } from '@mui/icons-material'; -import { AccountColor } from './AccountColor'; +import React from 'react'; +import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Typography } from '@mui/material'; +import { Add, Close } from '@mui/icons-material'; +import { AccountItem } from './AccountItem'; +import { TAccount } from './types'; export const AccountsModal = ({ show, diff --git a/nym-wallet/src/components/Accounts/AddAccount.stories.tsx b/nym-wallet/src/components/Accounts/AddAccount.stories.tsx new file mode 100644 index 0000000000..ac1289d443 --- /dev/null +++ b/nym-wallet/src/components/Accounts/AddAccount.stories.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Box } from '@mui/material'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { AddAccountModal } from 'src/components/Accounts/AddAccountModal'; + +export default { + title: 'Wallet / Multi Account / Add Account', + component: AddAccountModal, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + + + +); + +export const Default = Template.bind({}); +Default.args = { + show: true, + onClose: () => {}, + onAdd: () => {}, +}; + +export const WithoutPassword = Template.bind({}); +WithoutPassword.args = { + show: true, + withoutPassword: true, + onClose: () => {}, + onAdd: () => {}, +}; diff --git a/nym-wallet/src/components/Accounts/AddAccount.tsx b/nym-wallet/src/components/Accounts/AddAccount.tsx deleted file mode 100644 index fcfeedb618..0000000000 --- a/nym-wallet/src/components/Accounts/AddAccount.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { - Box, - Button, - Dialog, - DialogActions, - DialogContent, - DialogTitle, - IconButton, - TextField, - Typography, -} from '@mui/material'; -import { Add, Close } from '@mui/icons-material'; - -export const AddAccountModal = ({ - show, - onClose, - onAdd, -}: { - show: boolean; - onClose: () => void; - onAdd: (accountName: string) => void; -}) => { - const [accountName, setAccountName] = useState(''); - - useEffect(() => { - if (!show) setAccountName(''); - }, [show]); - - return ( - - - - Add new account - - - - - - New wallet address - - - - - setAccountName(e.target.value)} - autoFocus - /> - - - - - - - ); -}; diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx new file mode 100644 index 0000000000..9c4bea6ca4 --- /dev/null +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -0,0 +1,100 @@ +import React, { useEffect, useState } from 'react'; +import { + Alert, + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + Stack, + TextField, + Typography, +} from '@mui/material'; +import { Add, Close } from '@mui/icons-material'; + +const passwordCreationSteps = [ + 'Log out', + 'During sign in screen click “Sign in with mnemonic” button', + 'On next screen click “Create a password for your account”', + 'Sign in to wallet with your new password', + 'Now you can create multiple accounts', +]; + +const NoPassword = () => ( + + + + You can’t add new accounts if your wallet doesn’t have a password. + + Follow steps below to create password. + + How to create password to your account + {passwordCreationSteps.map((step, i) => ( + {`${i + 1}. ${step}`} + ))} + +); + +export const AddAccountModal = ({ + show, + withoutPassword, + onClose, + onAdd, +}: { + show: boolean; + withoutPassword?: boolean; + onClose: () => void; + onAdd: (accountName: string) => void; +}) => { + const [accountName, setAccountName] = useState(''); + + useEffect(() => { + if (!show) setAccountName(''); + }, [show]); + + return ( + + + + Add new account + + + + + + New wallet address + + + + + {withoutPassword ? ( + + ) : ( + setAccountName(e.target.value)} + autoFocus + /> + )} + + + + + + + ); +}; diff --git a/nym-wallet/src/components/Accounts/EditAccount.tsx b/nym-wallet/src/components/Accounts/EditAccountModal.tsx similarity index 97% rename from nym-wallet/src/components/Accounts/EditAccount.tsx rename to nym-wallet/src/components/Accounts/EditAccountModal.tsx index 663a39a9ca..cb3f2c3b3f 100644 --- a/nym-wallet/src/components/Accounts/EditAccount.tsx +++ b/nym-wallet/src/components/Accounts/EditAccountModal.tsx @@ -11,6 +11,7 @@ import { Typography, } from '@mui/material'; import { Close } from '@mui/icons-material'; +import { TAccount } from './types'; export const EditAccountModal = ({ account, diff --git a/nym-wallet/src/components/Accounts/ShowMnemonic.stories.tsx b/nym-wallet/src/components/Accounts/ShowMnemonic.stories.tsx new file mode 100644 index 0000000000..f1478fea9d --- /dev/null +++ b/nym-wallet/src/components/Accounts/ShowMnemonic.stories.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Box } from '@mui/material'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { ShowMnemonicModal } from './ShowMnemonicModal'; + +export default { + title: 'Wallet / Multi Account / Show Mnemonic', + component: ShowMnemonicModal, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + + + +); + +export const Default = Template.bind({}); +Default.args = { + mnemonic: + 'lonely employ curtain skull gas swim pizza injury tail birth inmate apart giraffe behave caution hammer echo action best symptom skull toast beyond casino', + show: true, + onClose: () => {}, +}; diff --git a/nym-wallet/src/components/Accounts/ShowMnemonicModal.tsx b/nym-wallet/src/components/Accounts/ShowMnemonicModal.tsx new file mode 100644 index 0000000000..392bd4e841 --- /dev/null +++ b/nym-wallet/src/components/Accounts/ShowMnemonicModal.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { + Alert, + AlertTitle, + Box, + Dialog, + DialogContent, + DialogTitle, + IconButton, + Stack, + Typography, +} from '@mui/material'; +import { Close } from '@mui/icons-material'; +import { CopyToClipboard } from '@nymproject/react'; + +export const ShowMnemonicModal = ({ + mnemonic, + show, + onClose, +}: { + mnemonic: string; + show: boolean; + onClose: () => void; +}) => ( + + + + Show mnemonic + + + + + + + + + DO NOT share this phrase with anyone! + These words can be used to steal all your accounts. + + } icon={false}> + Mnemonic + {mnemonic} + + + + +); diff --git a/nym-wallet/src/components/Accounts/index.tsx b/nym-wallet/src/components/Accounts/index.tsx index 57372c9f12..ac16c21f66 100644 --- a/nym-wallet/src/components/Accounts/index.tsx +++ b/nym-wallet/src/components/Accounts/index.tsx @@ -1,17 +1,11 @@ import React, { useEffect, useState } from 'react'; import { Button } from '@mui/material'; import { v4 as uuidv4 } from 'uuid'; -import { EditAccountModal } from './EditAccount'; -import { AddAccountModal } from './AddAccount'; +import { EditAccountModal } from './EditAccountModal'; +import { AddAccountModal } from './AddAccountModal'; import { AccountColor } from './AccountColor'; -import { AccountsModal } from './Accounts'; - -export type TAccount = { - name: string; - address: string; -}; - -type TDialog = 'Accounts' | 'Add' | 'Edit'; +import { AccountsModal } from './AccountsModal'; +import { TAccount, TDialog } from './types'; export const Accounts = ({ storedAccounts }: { storedAccounts: TAccount[] }) => { const [accounts, setAccounts] = useState(storedAccounts); diff --git a/nym-wallet/src/components/Accounts/types.ts b/nym-wallet/src/components/Accounts/types.ts new file mode 100644 index 0000000000..92225d133e --- /dev/null +++ b/nym-wallet/src/components/Accounts/types.ts @@ -0,0 +1,6 @@ +export type TAccount = { + name: string; + address: string; +}; + +export type TDialog = 'Accounts' | 'Add' | 'Edit'; From bd13aa6f352ba08a208f21ca84b0587133faa27f Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 13 Apr 2022 11:53:13 +0100 Subject: [PATCH 11/85] add new modals --- .../src/components/Accounts/AccountsModal.tsx | 28 ++++---- .../Accounts/EditAccount.stories.tsx | 23 ++++++ .../components/Accounts/EditAccountModal.tsx | 2 +- .../Accounts/ImportAccount.stories.tsx | 22 ++++++ .../Accounts/ImportAccountModal.tsx | 70 +++++++++++++++++++ nym-wallet/src/components/Accounts/index.tsx | 18 ++++- nym-wallet/src/components/Accounts/types.ts | 6 -- nym-wallet/src/hooks/useAccounts.ts | 11 +++ nym-wallet/src/requests/account.ts | 5 ++ nym-wallet/src/types/global.ts | 5 ++ 10 files changed, 165 insertions(+), 25 deletions(-) create mode 100644 nym-wallet/src/components/Accounts/EditAccount.stories.tsx create mode 100644 nym-wallet/src/components/Accounts/ImportAccount.stories.tsx create mode 100644 nym-wallet/src/components/Accounts/ImportAccountModal.tsx delete mode 100644 nym-wallet/src/components/Accounts/types.ts create mode 100644 nym-wallet/src/hooks/useAccounts.ts diff --git a/nym-wallet/src/components/Accounts/AccountsModal.tsx b/nym-wallet/src/components/Accounts/AccountsModal.tsx index 7c44effbd0..4b4c9205aa 100644 --- a/nym-wallet/src/components/Accounts/AccountsModal.tsx +++ b/nym-wallet/src/components/Accounts/AccountsModal.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Typography } from '@mui/material'; -import { Add, Close } from '@mui/icons-material'; +import { Add, ArrowDownwardSharp, Close } from '@mui/icons-material'; +import { TAccount } from 'src/types'; import { AccountItem } from './AccountItem'; -import { TAccount } from './types'; export const AccountsModal = ({ show, @@ -10,16 +10,18 @@ export const AccountsModal = ({ selectedAccount, onClose, onAccountSelect, - onAddAccount, - onEditAccount, + onAdd, + onEdit, + onImport, }: { show: boolean; accounts: TAccount[]; selectedAccount: TAccount['address']; onClose: () => void; onAccountSelect: (account: TAccount) => void; - onAddAccount: () => void; - onEditAccount: (acc: TAccount) => void; + onAdd: () => void; + onEdit: (acc: TAccount) => void; + onImport: () => void; }) => ( @@ -42,21 +44,17 @@ export const AccountsModal = ({ onAccountSelect({ name, address }); onClose(); }} - onEdit={() => onEditAccount({ name, address })} + onEdit={() => onEdit({ name, address })} selected={selectedAccount === address} key={address} /> ))} - + diff --git a/nym-wallet/src/components/Accounts/EditAccount.stories.tsx b/nym-wallet/src/components/Accounts/EditAccount.stories.tsx new file mode 100644 index 0000000000..4163ef93cc --- /dev/null +++ b/nym-wallet/src/components/Accounts/EditAccount.stories.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Box } from '@mui/material'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { v4 as uuid } from 'uuid'; +import { EditAccountModal } from './EditAccountModal'; + +export default { + title: 'Wallet / Multi Account / Edit Account', + component: EditAccountModal, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + + + +); + +export const Default = Template.bind({}); +Default.args = { + account: { name: 'Account 1', address: uuid() }, + show: true, + onClose: () => {}, +}; diff --git a/nym-wallet/src/components/Accounts/EditAccountModal.tsx b/nym-wallet/src/components/Accounts/EditAccountModal.tsx index cb3f2c3b3f..1f290c6f0f 100644 --- a/nym-wallet/src/components/Accounts/EditAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/EditAccountModal.tsx @@ -11,7 +11,7 @@ import { Typography, } from '@mui/material'; import { Close } from '@mui/icons-material'; -import { TAccount } from './types'; +import { TAccount } from 'src/types'; export const EditAccountModal = ({ account, diff --git a/nym-wallet/src/components/Accounts/ImportAccount.stories.tsx b/nym-wallet/src/components/Accounts/ImportAccount.stories.tsx new file mode 100644 index 0000000000..98365cb809 --- /dev/null +++ b/nym-wallet/src/components/Accounts/ImportAccount.stories.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Box } from '@mui/material'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { ImportAccountModal } from './ImportAccountModal'; + +export default { + title: 'Wallet / Multi Account / Import Account', + component: ImportAccountModal, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + + + +); + +export const Default = Template.bind({}); +Default.args = { + show: true, + onClose: () => {}, + onImport: () => {}, +}; diff --git a/nym-wallet/src/components/Accounts/ImportAccountModal.tsx b/nym-wallet/src/components/Accounts/ImportAccountModal.tsx new file mode 100644 index 0000000000..8222f330bf --- /dev/null +++ b/nym-wallet/src/components/Accounts/ImportAccountModal.tsx @@ -0,0 +1,70 @@ +import React, { useEffect, useState } from 'react'; +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + TextField, + Typography, +} from '@mui/material'; +import { Close } from '@mui/icons-material'; + +export const ImportAccountModal = ({ + show, + onClose, + onImport, +}: { + show: boolean; + onClose: () => void; + onImport: (mnemonic: string) => void; +}) => { + const [mnemonic, setMnemonic] = useState(''); + + useEffect(() => { + if (!show) setMnemonic(''); + }, [show]); + + return ( + + + + Import account + + + + + + Provide mnemonic of account you want to import + + + + + setMnemonic(e.target.value)} + autoFocus + multiline + rows={3} + /> + + + + + + + ); +}; diff --git a/nym-wallet/src/components/Accounts/index.tsx b/nym-wallet/src/components/Accounts/index.tsx index ac16c21f66..44c4bf8d85 100644 --- a/nym-wallet/src/components/Accounts/index.tsx +++ b/nym-wallet/src/components/Accounts/index.tsx @@ -1,11 +1,14 @@ import React, { useEffect, useState } from 'react'; import { Button } from '@mui/material'; import { v4 as uuidv4 } from 'uuid'; +import { TAccount } from 'src/types'; import { EditAccountModal } from './EditAccountModal'; import { AddAccountModal } from './AddAccountModal'; import { AccountColor } from './AccountColor'; import { AccountsModal } from './AccountsModal'; -import { TAccount, TDialog } from './types'; +import { ImportAccountModal } from './ImportAccountModal'; + +export type TDialog = 'Accounts' | 'Add' | 'Edit' | 'Import'; export const Accounts = ({ storedAccounts }: { storedAccounts: TAccount[] }) => { const [accounts, setAccounts] = useState(storedAccounts); @@ -35,13 +38,14 @@ export const Accounts = ({ storedAccounts }: { storedAccounts: TAccount[] }) => accounts={accounts} onAccountSelect={(acc) => setSelectedAccount(acc)} selectedAccount={selectedAccount.address} - onAddAccount={() => { + onAdd={() => { setDialogToDisplay('Add'); }} - onEditAccount={(acc) => { + onEdit={(acc) => { setAccountToEdit(acc); setDialogToDisplay('Edit'); }} + onImport={() => setDialogToDisplay('Import')} /> setDialogToDisplay('Accounts'); }} /> + setDialogToDisplay('Accounts')} + onImport={(mnemonic) => { + setAccounts((accs) => [...accs, { name: 'New Account', address: uuidv4() }]); + setDialogToDisplay('Accounts'); + }} + /> ); }; diff --git a/nym-wallet/src/components/Accounts/types.ts b/nym-wallet/src/components/Accounts/types.ts deleted file mode 100644 index 92225d133e..0000000000 --- a/nym-wallet/src/components/Accounts/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type TAccount = { - name: string; - address: string; -}; - -export type TDialog = 'Accounts' | 'Add' | 'Edit'; diff --git a/nym-wallet/src/hooks/useAccounts.ts b/nym-wallet/src/hooks/useAccounts.ts new file mode 100644 index 0000000000..21e22226a2 --- /dev/null +++ b/nym-wallet/src/hooks/useAccounts.ts @@ -0,0 +1,11 @@ +import React, { useState } from 'react'; +import { TDialog } from 'src/components/Accounts'; + +export const useAccounts = () => { + const [accounts, setAccounts] = useState(storedAccounts); + const [selectedAccount, setSelectedAccount] = useState(accounts[0]); + const [accountToEdit, setAccountToEdit] = useState(); + const [dialogToDisplay, setDialogToDisplay] = useState(); + + return { dialogToDisplay, accounts, accountToEdit, selectedAccount }; +}; diff --git a/nym-wallet/src/requests/account.ts b/nym-wallet/src/requests/account.ts index 12316d7b49..5b143c3a93 100644 --- a/nym-wallet/src/requests/account.ts +++ b/nym-wallet/src/requests/account.ts @@ -30,3 +30,8 @@ export const isPasswordCreated = async (): Promise => { const res: boolean = await invoke('does_password_file_exist'); return res; }; + +export const createNewAccount = async (mnemonic: string): Promise => { + const res: Account = await invoke('create_new_account', { mnemonic }); + return res; +}; diff --git a/nym-wallet/src/types/global.ts b/nym-wallet/src/types/global.ts index 58df3bff09..758cd2095f 100644 --- a/nym-wallet/src/types/global.ts +++ b/nym-wallet/src/types/global.ts @@ -74,3 +74,8 @@ export type TCurrency = { }; export type Period = 'Before' | { In: number } | 'After'; + +export type TAccount = { + name: string; + address: string; +}; From f118a0c854b8d1d26e8785def116b433332482b4 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 13 Apr 2022 18:06:53 +0100 Subject: [PATCH 12/85] move story files --- .../src/components/Accounts/{ => stories}/Accounts.stories.tsx | 0 .../components/Accounts/{ => stories}/AddAccount.stories.tsx | 0 .../components/Accounts/{ => stories}/EditAccount.stories.tsx | 2 +- .../components/Accounts/{ => stories}/ImportAccount.stories.tsx | 2 +- .../components/Accounts/{ => stories}/ShowMnemonic.stories.tsx | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename nym-wallet/src/components/Accounts/{ => stories}/Accounts.stories.tsx (100%) rename nym-wallet/src/components/Accounts/{ => stories}/AddAccount.stories.tsx (100%) rename nym-wallet/src/components/Accounts/{ => stories}/EditAccount.stories.tsx (88%) rename nym-wallet/src/components/Accounts/{ => stories}/ImportAccount.stories.tsx (87%) rename nym-wallet/src/components/Accounts/{ => stories}/ShowMnemonic.stories.tsx (90%) diff --git a/nym-wallet/src/components/Accounts/Accounts.stories.tsx b/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx similarity index 100% rename from nym-wallet/src/components/Accounts/Accounts.stories.tsx rename to nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx diff --git a/nym-wallet/src/components/Accounts/AddAccount.stories.tsx b/nym-wallet/src/components/Accounts/stories/AddAccount.stories.tsx similarity index 100% rename from nym-wallet/src/components/Accounts/AddAccount.stories.tsx rename to nym-wallet/src/components/Accounts/stories/AddAccount.stories.tsx diff --git a/nym-wallet/src/components/Accounts/EditAccount.stories.tsx b/nym-wallet/src/components/Accounts/stories/EditAccount.stories.tsx similarity index 88% rename from nym-wallet/src/components/Accounts/EditAccount.stories.tsx rename to nym-wallet/src/components/Accounts/stories/EditAccount.stories.tsx index 4163ef93cc..a75139e3c0 100644 --- a/nym-wallet/src/components/Accounts/EditAccount.stories.tsx +++ b/nym-wallet/src/components/Accounts/stories/EditAccount.stories.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Box } from '@mui/material'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import { v4 as uuid } from 'uuid'; -import { EditAccountModal } from './EditAccountModal'; +import { EditAccountModal } from 'src/components/Accounts/EditAccountModal'; export default { title: 'Wallet / Multi Account / Edit Account', diff --git a/nym-wallet/src/components/Accounts/ImportAccount.stories.tsx b/nym-wallet/src/components/Accounts/stories/ImportAccount.stories.tsx similarity index 87% rename from nym-wallet/src/components/Accounts/ImportAccount.stories.tsx rename to nym-wallet/src/components/Accounts/stories/ImportAccount.stories.tsx index 98365cb809..b203cc676c 100644 --- a/nym-wallet/src/components/Accounts/ImportAccount.stories.tsx +++ b/nym-wallet/src/components/Accounts/stories/ImportAccount.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Box } from '@mui/material'; import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { ImportAccountModal } from './ImportAccountModal'; +import { ImportAccountModal } from 'src/components/Accounts/ImportAccountModal'; export default { title: 'Wallet / Multi Account / Import Account', diff --git a/nym-wallet/src/components/Accounts/ShowMnemonic.stories.tsx b/nym-wallet/src/components/Accounts/stories/ShowMnemonic.stories.tsx similarity index 90% rename from nym-wallet/src/components/Accounts/ShowMnemonic.stories.tsx rename to nym-wallet/src/components/Accounts/stories/ShowMnemonic.stories.tsx index f1478fea9d..0db1a2046a 100644 --- a/nym-wallet/src/components/Accounts/ShowMnemonic.stories.tsx +++ b/nym-wallet/src/components/Accounts/stories/ShowMnemonic.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Box } from '@mui/material'; import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { ShowMnemonicModal } from './ShowMnemonicModal'; +import { ShowMnemonicModal } from 'src/components/Accounts/ShowMnemonicModal'; export default { title: 'Wallet / Multi Account / Show Mnemonic', From 81b7d49624711bc628120d02f049a1b3672448bf Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 13 Apr 2022 18:07:06 +0100 Subject: [PATCH 13/85] create avatar component --- nym-wallet/src/components/Accounts/AccountAvatar.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 nym-wallet/src/components/Accounts/AccountAvatar.tsx diff --git a/nym-wallet/src/components/Accounts/AccountAvatar.tsx b/nym-wallet/src/components/Accounts/AccountAvatar.tsx new file mode 100644 index 0000000000..fb2ba5da4b --- /dev/null +++ b/nym-wallet/src/components/Accounts/AccountAvatar.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { Avatar } from '@mui/material'; +import stc from 'string-to-color'; +import { TAccount } from 'src/types'; + +export const AccountAvatar = ({ name, address }: TAccount) => ( + {name.split('')[0]} +); From 2bebb4b0c2b84a2783071034c09fcf12769d9c51 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 13 Apr 2022 18:07:31 +0100 Subject: [PATCH 14/85] remove unused component --- nym-wallet/src/components/Accounts/AccountColor.tsx | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 nym-wallet/src/components/Accounts/AccountColor.tsx diff --git a/nym-wallet/src/components/Accounts/AccountColor.tsx b/nym-wallet/src/components/Accounts/AccountColor.tsx deleted file mode 100644 index 98da4d0b72..0000000000 --- a/nym-wallet/src/components/Accounts/AccountColor.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; -import { CircleTwoTone } from '@mui/icons-material'; -import stc from 'string-to-color'; - -export const AccountColor = ({ address }: { address: string }) => ; From 6ba79ee924ecc32430dc5297e14c99df850bbece Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 13 Apr 2022 18:07:54 +0100 Subject: [PATCH 15/85] use avatar component --- nym-wallet/src/components/Accounts/AccountItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index 98f1851a16..b8dd61279c 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { IconButton, ListItem, ListItemAvatar, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; import { Edit } from '@mui/icons-material'; -import { AccountColor } from './AccountColor'; +import { AccountAvatar } from './AccountAvatar'; export const AccountItem = ({ name, @@ -19,7 +19,7 @@ export const AccountItem = ({ - + From ecdbe1a6fb14999cbda55a7512bbe76565f4533c Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 13 Apr 2022 18:08:57 +0100 Subject: [PATCH 16/85] refactor --- .../components/Accounts/AddAccountModal.tsx | 162 ++++++++++++------ nym-wallet/src/components/Accounts/index.tsx | 9 +- 2 files changed, 116 insertions(+), 55 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index 9c4bea6ca4..60d1963618 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -12,7 +12,10 @@ import { TextField, Typography, } from '@mui/material'; -import { Add, Close } from '@mui/icons-material'; +import { Check, Close, ContentCopySharp } from '@mui/icons-material'; +import { useClipboard } from 'use-clipboard-copy'; + +const createAccountSteps = ['Save and copy mnemonic for your new account', 'Name your new account']; const passwordCreationSteps = [ 'Log out', @@ -22,21 +25,92 @@ const passwordCreationSteps = [ 'Now you can create multiple accounts', ]; -const NoPassword = () => ( - - - - You can’t add new accounts if your wallet doesn’t have a password. - - Follow steps below to create password. - - How to create password to your account - {passwordCreationSteps.map((step, i) => ( - {`${i + 1}. ${step}`} - ))} - +const NoPassword = ({ onClose }: { onClose: () => void }) => ( + + + + + + You can’t add new accounts if your wallet doesn’t have a password. + + Follow steps below to create password. + + How to create password to your account + {passwordCreationSteps.map((step, i) => ( + {`${i + 1}. ${step}`} + ))} + + + + + + ); +const MnemonicStep = ({ mnemonic, onSave }: { mnemonic: string; onSave: () => void }) => { + const { copy, copied } = useClipboard({ copiedTimeout: 5000 }); + return ( + + + + + + Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the + future + + + + + + + + + + + + ); +}; + +const NameAccount = ({ onAdd }: { onAdd: (value: string) => void }) => { + const [value, setValue] = useState(''); + return ( + + + setValue(e.target.value)} fullWidth /> + + + + + + ); +}; + export const AddAccountModal = ({ show, withoutPassword, @@ -48,11 +122,7 @@ export const AddAccountModal = ({ onClose: () => void; onAdd: (accountName: string) => void; }) => { - const [accountName, setAccountName] = useState(''); - - useEffect(() => { - if (!show) setAccountName(''); - }, [show]); + const [step, setStep] = useState(0); return ( @@ -63,38 +133,30 @@ export const AddAccountModal = ({ - - New wallet address - + {!withoutPassword && ( + + {`Step ${step + 1}/${createAccountSteps.length}`} + + )} + {createAccountSteps[step]} - - - {withoutPassword ? ( - - ) : ( - setAccountName(e.target.value)} - autoFocus - /> - )} - - - - - + {withoutPassword && } + {!withoutPassword && + (() => { + switch (step) { + case 0: + return ( + setStep((s) => s + 1)} + /> + ); + case 1: + return ; + default: + return null; + } + })()} ); }; diff --git a/nym-wallet/src/components/Accounts/index.tsx b/nym-wallet/src/components/Accounts/index.tsx index 44c4bf8d85..ad3f635455 100644 --- a/nym-wallet/src/components/Accounts/index.tsx +++ b/nym-wallet/src/components/Accounts/index.tsx @@ -4,9 +4,9 @@ import { v4 as uuidv4 } from 'uuid'; import { TAccount } from 'src/types'; import { EditAccountModal } from './EditAccountModal'; import { AddAccountModal } from './AddAccountModal'; -import { AccountColor } from './AccountColor'; import { AccountsModal } from './AccountsModal'; import { ImportAccountModal } from './ImportAccountModal'; +import { AccountAvatar } from './AccountAvatar'; export type TDialog = 'Accounts' | 'Add' | 'Edit' | 'Import'; @@ -24,10 +24,9 @@ export const Accounts = ({ storedAccounts }: { storedAccounts: TAccount[] }) => return ( <> + setDialogToDisplay(undefined)} + accounts={accounts} + onAccountSelect={(acc) => setSelectedAccount(acc)} + selectedAccount={selectedAccount.address} + onAdd={() => { + setDialogToDisplay('Add'); + }} + onEdit={(acc) => { + setAccountToEdit(acc); + setDialogToDisplay('Edit'); + }} + onImport={() => setDialogToDisplay('Import')} + /> + { + setDialogToDisplay('Accounts'); + }} + onAdd={(name) => { + addAccount({ name, address: uuidv4() }); + setDialogToDisplay('Accounts'); + }} + /> + { + setDialogToDisplay('Accounts'); + }} + onEdit={(account) => { + editAccount(account); + setDialogToDisplay('Accounts'); + }} + /> + setDialogToDisplay('Accounts')} + onImport={() => { + importAccount({ name: 'New Account', address: uuidv4() }); + setDialogToDisplay('Accounts'); + }} + /> + +); diff --git a/nym-wallet/src/components/Accounts/index.tsx b/nym-wallet/src/components/Accounts/index.tsx deleted file mode 100644 index ad3f635455..0000000000 --- a/nym-wallet/src/components/Accounts/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Button } from '@mui/material'; -import { v4 as uuidv4 } from 'uuid'; -import { TAccount } from 'src/types'; -import { EditAccountModal } from './EditAccountModal'; -import { AddAccountModal } from './AddAccountModal'; -import { AccountsModal } from './AccountsModal'; -import { ImportAccountModal } from './ImportAccountModal'; -import { AccountAvatar } from './AccountAvatar'; - -export type TDialog = 'Accounts' | 'Add' | 'Edit' | 'Import'; - -export const Accounts = ({ storedAccounts }: { storedAccounts: TAccount[] }) => { - const [accounts, setAccounts] = useState(storedAccounts); - const [selectedAccount, setSelectedAccount] = useState(accounts[0]); - const [accountToEdit, setAccountToEdit] = useState(); - const [dialogToDisplasy, setDialogToDisplay] = useState(); - - useEffect(() => { - const selected = accounts.find((acc) => acc.address === selectedAccount.address); - if (selected) setSelectedAccount(selected); - }, [accounts]); - - return ( - <> - - setDialogToDisplay(undefined)} - accounts={accounts} - onAccountSelect={(acc) => setSelectedAccount(acc)} - selectedAccount={selectedAccount.address} - onAdd={() => { - setDialogToDisplay('Add'); - }} - onEdit={(acc) => { - setAccountToEdit(acc); - setDialogToDisplay('Edit'); - }} - onImport={() => setDialogToDisplay('Import')} - /> - { - setDialogToDisplay('Accounts'); - }} - onAdd={(name) => { - setAccounts((accs) => [...accs, { address: uuidv4(), name }]); - setDialogToDisplay('Accounts'); - }} - /> - { - setDialogToDisplay('Accounts'); - }} - onEdit={(account) => { - setAccounts((accs) => accs.map((acc) => (acc.address === account.address ? account : acc))); - setDialogToDisplay('Accounts'); - }} - /> - setDialogToDisplay('Accounts')} - onImport={() => { - setAccounts((accs) => [...accs, { name: 'New Account', address: uuidv4() }]); - setDialogToDisplay('Accounts'); - }} - /> - - ); -}; diff --git a/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx b/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx index fd4b22e70f..4b8b87ee4d 100644 --- a/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx +++ b/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx @@ -1,27 +1,23 @@ import React from 'react'; import { Box } from '@mui/material'; import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { Accounts } from 'src/components/Accounts'; + import { v4 as uuid4 } from 'uuid'; +import { AccountsContainer } from '../AccountContainer'; export default { title: 'Wallet / Multi Account', - component: Accounts, -} as ComponentMeta; + component: AccountsContainer, +} as ComponentMeta; -const Template: ComponentStory = (args) => ( +const Template: ComponentStory = (args) => ( - + ); export const Default = Template.bind({}); Default.args = { - storedAccounts: [{ name: 'Account 1', address: uuid4() }], -}; - -export const MultipleAccounts = Template.bind({}); -MultipleAccounts.args = { storedAccounts: [ { name: 'Account 1', address: uuid4() }, { name: 'Account 2', address: uuid4() }, diff --git a/nym-wallet/src/components/Accounts/types.ts b/nym-wallet/src/components/Accounts/types.ts new file mode 100644 index 0000000000..b73b463940 --- /dev/null +++ b/nym-wallet/src/components/Accounts/types.ts @@ -0,0 +1 @@ +export type TDialog = 'Accounts' | 'Add' | 'Edit' | 'Import'; diff --git a/nym-wallet/src/hooks/useAccounts.ts b/nym-wallet/src/hooks/useAccounts.ts deleted file mode 100644 index 21e22226a2..0000000000 --- a/nym-wallet/src/hooks/useAccounts.ts +++ /dev/null @@ -1,11 +0,0 @@ -import React, { useState } from 'react'; -import { TDialog } from 'src/components/Accounts'; - -export const useAccounts = () => { - const [accounts, setAccounts] = useState(storedAccounts); - const [selectedAccount, setSelectedAccount] = useState(accounts[0]); - const [accountToEdit, setAccountToEdit] = useState(); - const [dialogToDisplay, setDialogToDisplay] = useState(); - - return { dialogToDisplay, accounts, accountToEdit, selectedAccount }; -}; From afac630a77699a5fb74dad6fb5f9f0b04a50286a Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Mon, 25 Apr 2022 11:27:07 +0100 Subject: [PATCH 18/85] add list accounts function --- nym-wallet/src/requests/account.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nym-wallet/src/requests/account.ts b/nym-wallet/src/requests/account.ts index 5b143c3a93..b9223581e8 100644 --- a/nym-wallet/src/requests/account.ts +++ b/nym-wallet/src/requests/account.ts @@ -35,3 +35,8 @@ export const createNewAccount = async (mnemonic: string): Promise => { const res: Account = await invoke('create_new_account', { mnemonic }); return res; }; + +export const listAccounts = async (password: string) => { + const res: Account[] = await invoke('list_accounts_for_password', { password }); + return res; +}; From 5446874ebe4fc12da575cea4c943a4a7a1dc2618 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Tue, 26 Apr 2022 14:29:17 +0100 Subject: [PATCH 19/85] align top bar with nav --- nym-wallet/src/layouts/AppLayout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-wallet/src/layouts/AppLayout.tsx b/nym-wallet/src/layouts/AppLayout.tsx index 521c3de940..d813f8c42b 100644 --- a/nym-wallet/src/layouts/AppLayout.tsx +++ b/nym-wallet/src/layouts/AppLayout.tsx @@ -21,7 +21,7 @@ export const ApplicationLayout: React.FC = ({ children }) => { sx={{ background: '#121726', overflow: 'auto', - py: 4, + py: 3, px: 5, }} display="flex" From be9b83a87d277d4c904f59f4ddf9d57a25efa4b4 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 27 Apr 2022 11:19:19 +0100 Subject: [PATCH 20/85] set up account switching mechanism --- nym-wallet/src/components/LoadingPage.tsx | 32 +++++++++++++++++++ nym-wallet/src/context/main.tsx | 25 +++++++++++---- nym-wallet/src/index.tsx | 17 +++++----- .../pages/sign-in/pages/signin-mnemonic.tsx | 11 ++----- .../pages/sign-in/pages/signin-password.tsx | 11 ++----- 5 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 nym-wallet/src/components/LoadingPage.tsx diff --git a/nym-wallet/src/components/LoadingPage.tsx b/nym-wallet/src/components/LoadingPage.tsx new file mode 100644 index 0000000000..f34e74f17b --- /dev/null +++ b/nym-wallet/src/components/LoadingPage.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Box, LinearProgress, Stack } from '@mui/material'; +import { NymWordmark } from '@nymproject/react'; +export const LoadingPage = () => ( + + + + + + + + + + +); diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index dc6091c8de..f8cbc5ab6e 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -42,6 +42,7 @@ type TClientContext = { logIn: (opts: { type: 'mnemonic' | 'password'; value: string }) => void; signInWithPassword: (password: string) => void; logOut: () => void; + onAccountChange: (mnemonic: string) => void; }; export const ClientContext = createContext({} as TClientContext); @@ -61,6 +62,15 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode const history = useHistory(); const { enqueueSnackbar } = useSnackbar(); + const clearState = () => { + userBalance.clearAll(); + setClientDetails(undefined); + setNetwork(undefined); + setError(undefined); + setIsLoading(false); + setMixnodeDetails(undefined); + }; + const loadAccount = async (n: Network) => { try { const client = await selectNetwork(n); @@ -116,16 +126,18 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode }; const logOut = async () => { - userBalance.clearAll(); - setClientDetails(undefined); - setNetwork(undefined); - setError(undefined); - setIsLoading(false); - setMixnodeDetails(undefined); + clearState(); await signOut(); enqueueSnackbar('Successfully logged out', { variant: 'success' }); }; + const onAccountChange = async (value: string) => { + clearState(); + await signOut(); + await logIn({ type: 'mnemonic', value }); + enqueueSnackbar('Account switch success', { variant: 'success' }); + }; + const handleShowAdmin = () => setShowAdmin((show) => !show); const handleShowSettings = () => setShowSettings((show) => !show); const switchNetwork = (_network: Network) => setNetwork(_network); @@ -151,6 +163,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode handleShowAdmin, logIn, logOut, + onAccountChange, }), [mode, isLoading, error, clientDetails, mixnodeDetails, userBalance, showAdmin, showSettings, network, currency], ); diff --git a/nym-wallet/src/index.tsx b/nym-wallet/src/index.tsx index e16fbcdab0..2ff0e2c071 100644 --- a/nym-wallet/src/index.tsx +++ b/nym-wallet/src/index.tsx @@ -11,21 +11,22 @@ import { ErrorFallback } from './components'; import { NymWalletTheme, WelcomeTheme } from './theme'; import { maximizeWindow } from './utils'; import { SignInProvider } from './context'; +import { LoadingPage } from './components/LoadingPage'; const App = () => { - const { clientDetails } = useContext(ClientContext); + const { clientDetails, isLoading } = useContext(ClientContext); useEffect(() => { maximizeWindow(); }, []); - return !clientDetails ? ( - - - - - - ) : ( + if (!clientDetails) + return ( + + {!isLoading ? : } + + ); + return ( diff --git a/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx b/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx index 491895ad96..4118b7c9d8 100644 --- a/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx +++ b/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx @@ -1,13 +1,13 @@ import React, { useContext, useState, useEffect } from 'react'; -import { Box, Button, FormControl, LinearProgress, Stack } from '@mui/material'; import { useHistory } from 'react-router-dom'; +import { Box, Button, FormControl, Stack } from '@mui/material'; import { isPasswordCreated } from 'src/requests'; import { MnemonicInput, Subtitle } from '../components'; import { ClientContext } from '../../../context/main'; export const SignInMnemonic = () => { const [mnemonic, setMnemonic] = useState(''); - const { setError, logIn, error, isLoading } = useContext(ClientContext); + const { setError, logIn, error } = useContext(ClientContext); const [passwordExists, setPasswordExists] = useState(true); const history = useHistory(); @@ -25,13 +25,6 @@ export const SignInMnemonic = () => { checkForPassword(); }, []); - if (isLoading) - return ( - - - - ); - return ( diff --git a/nym-wallet/src/pages/sign-in/pages/signin-password.tsx b/nym-wallet/src/pages/sign-in/pages/signin-password.tsx index 64f3a83677..aed85dcff0 100644 --- a/nym-wallet/src/pages/sign-in/pages/signin-password.tsx +++ b/nym-wallet/src/pages/sign-in/pages/signin-password.tsx @@ -1,21 +1,14 @@ import React, { useContext, useState } from 'react'; import { useHistory } from 'react-router-dom'; -import { Box, Button, LinearProgress, FormControl, Stack } from '@mui/material'; +import { Box, Button, FormControl, Stack } from '@mui/material'; import { PasswordInput, Subtitle } from '../components'; import { ClientContext } from '../../../context/main'; export const SignInPassword = () => { const [password, setPassword] = useState(''); - const { setError, logIn, error, isLoading } = useContext(ClientContext); + const { setError, logIn, error } = useContext(ClientContext); const history = useHistory(); - if (isLoading) - return ( - - - - ); - return ( From 1b945ae918bc33756bf629cb688566b407a76d32 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 27 Apr 2022 13:43:39 +0100 Subject: [PATCH 21/85] use account switch function --- .../Accounts/stories/Accounts.stories.tsx | 13 +++++++------ nym-wallet/src/components/AppBar.tsx | 10 ++++++++-- nym-wallet/src/context/main.tsx | 2 +- nym-wallet/src/requests/account.ts | 13 ++++++++++--- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx b/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx index 4b8b87ee4d..680b14f96d 100644 --- a/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx +++ b/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx @@ -3,23 +3,24 @@ import { Box } from '@mui/material'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import { v4 as uuid4 } from 'uuid'; -import { AccountsContainer } from '../AccountContainer'; +import { Accounts } from '../Accounts'; export default { title: 'Wallet / Multi Account', - component: AccountsContainer, -} as ComponentMeta; + component: Accounts, +} as ComponentMeta; -const Template: ComponentStory = (args) => ( +const Template: ComponentStory = (args) => ( - + ); export const Default = Template.bind({}); Default.args = { - storedAccounts: [ + accounts: [ { name: 'Account 1', address: uuid4() }, { name: 'Account 2', address: uuid4() }, ], + selectedAccount: { name: 'Account 1', address: uuid4() }, }; diff --git a/nym-wallet/src/components/AppBar.tsx b/nym-wallet/src/components/AppBar.tsx index b5ee0ab55d..6d7c2d3820 100644 --- a/nym-wallet/src/components/AppBar.tsx +++ b/nym-wallet/src/components/AppBar.tsx @@ -4,6 +4,7 @@ import { Logout } from '@mui/icons-material'; import { ClientContext } from '../context/main'; import { NetworkSelector } from './NetworkSelector'; import { Node as NodeIcon } from '../svg-icons/node'; +import { AccountsContainer } from './Accounts/AccountContainer'; export const AppBar = () => { const { showSettings, logOut, handleShowSettings } = useContext(ClientContext); @@ -12,8 +13,13 @@ export const AppBar = () => { - - + + + + + + + diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index f8cbc5ab6e..0a53ff3e95 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -135,7 +135,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode clearState(); await signOut(); await logIn({ type: 'mnemonic', value }); - enqueueSnackbar('Account switch success', { variant: 'success' }); + enqueueSnackbar('Account switch success', { variant: 'success', preventDuplicate: true }); }; const handleShowAdmin = () => setShowAdmin((show) => !show); diff --git a/nym-wallet/src/requests/account.ts b/nym-wallet/src/requests/account.ts index b9223581e8..6fcf7c6628 100644 --- a/nym-wallet/src/requests/account.ts +++ b/nym-wallet/src/requests/account.ts @@ -31,9 +31,16 @@ export const isPasswordCreated = async (): Promise => { return res; }; -export const createNewAccount = async (mnemonic: string): Promise => { - const res: Account = await invoke('create_new_account', { mnemonic }); - return res; +export const addAccount = async ({ + mnemonic, + password, + accountName, +}: { + mnemonic: string; + password: string; + accountName: string; +}): Promise => { + await invoke('add_account_for_password', { mnemonic, password, innerId: accountName }); }; export const listAccounts = async (password: string) => { From 66b9b13edcfa214e784321ee119e61b64ecf1354 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 29 Apr 2022 10:05:18 +0100 Subject: [PATCH 22/85] refactor and add more mocks --- .../src/components/Accounts/AccountAvatar.tsx | 2 +- .../components/Accounts/AccountContainer.tsx | 18 ++- .../src/components/Accounts/AccountItem.tsx | 22 +++- .../src/components/Accounts/Accounts.tsx | 121 +++++++++--------- .../src/components/Accounts/AccountsModal.tsx | 8 +- .../components/Accounts/AddAccountModal.tsx | 2 +- .../src/components/Accounts/ShowMnemonic.tsx | 41 ++++++ nym-wallet/src/components/Accounts/mocks.ts | 14 ++ .../Accounts/stories/Accounts.stories.tsx | 10 +- .../Accounts/stories/EditAccount.stories.tsx | 2 +- nym-wallet/src/components/AppBar.tsx | 10 +- nym-wallet/src/context/main.tsx | 19 ++- nym-wallet/src/types/global.ts | 1 + 13 files changed, 184 insertions(+), 86 deletions(-) create mode 100644 nym-wallet/src/components/Accounts/ShowMnemonic.tsx create mode 100644 nym-wallet/src/components/Accounts/mocks.ts diff --git a/nym-wallet/src/components/Accounts/AccountAvatar.tsx b/nym-wallet/src/components/Accounts/AccountAvatar.tsx index fb2ba5da4b..0854917e0a 100644 --- a/nym-wallet/src/components/Accounts/AccountAvatar.tsx +++ b/nym-wallet/src/components/Accounts/AccountAvatar.tsx @@ -3,6 +3,6 @@ import { Avatar } from '@mui/material'; import stc from 'string-to-color'; import { TAccount } from 'src/types'; -export const AccountAvatar = ({ name, address }: TAccount) => ( +export const AccountAvatar = ({ name, address }: Pick) => ( {name.split('')[0]} ); diff --git a/nym-wallet/src/components/Accounts/AccountContainer.tsx b/nym-wallet/src/components/Accounts/AccountContainer.tsx index eb61715f20..c00230099c 100644 --- a/nym-wallet/src/components/Accounts/AccountContainer.tsx +++ b/nym-wallet/src/components/Accounts/AccountContainer.tsx @@ -4,20 +4,26 @@ import { Accounts } from './Accounts'; import { TDialog } from './types'; export const AccountsContainer = ({ storedAccounts }: { storedAccounts: TAccount[] }) => { - const [accounts, setAccounts] = useState(storedAccounts); - const [selectedAccount, setSelectedAccount] = useState(accounts[0]); + const [accounts, setAccounts] = useState(storedAccounts); + const [selectedAccount, setSelectedAccount] = useState(storedAccounts[0]); const [accountToEdit, setAccountToEdit] = useState(); const [dialogToDisplay, setDialogToDisplay] = useState(); useEffect(() => { - const selected = accounts.find((acc) => acc.address === selectedAccount.address); + const selected = accounts?.find((acc) => acc.address === selectedAccount?.address); if (selected) setSelectedAccount(selected); }, [accounts]); const addAccount = (account: TAccount) => setAccounts((accs) => [...accs, account]); const editAccount = (account: TAccount) => - setAccounts((accs) => accs.map((acc) => (acc.address === account.address ? account : acc))); + setAccounts((accs) => accs?.map((acc) => (acc.address === account.address ? account : acc))); const importAccount = (account: TAccount) => setAccounts((accs) => [...accs, account]); + const handleAccountToEdit = (accountName: string) => + setAccountToEdit(accounts.find((acc) => acc.name === accountName)); + const handleSelectedAccount = (accountName: string) => { + const match = accounts.find((acc) => acc.name === accountName); + if (match) setSelectedAccount(match); + }; return ( ); diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index b8dd61279c..d76b6c5916 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -1,7 +1,17 @@ import React from 'react'; -import { IconButton, ListItem, ListItemAvatar, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; +import { + Box, + IconButton, + ListItem, + ListItemAvatar, + ListItemButton, + ListItemIcon, + ListItemText, + Typography, +} from '@mui/material'; import { Edit } from '@mui/icons-material'; import { AccountAvatar } from './AccountAvatar'; +import { ShowMnemonic } from './ShowMnemonic'; export const AccountItem = ({ name, @@ -21,7 +31,15 @@ export const AccountItem = ({ - + + {address} + + + } + /> { diff --git a/nym-wallet/src/components/Accounts/Accounts.tsx b/nym-wallet/src/components/Accounts/Accounts.tsx index 194ec48b51..665022c6c2 100644 --- a/nym-wallet/src/components/Accounts/Accounts.tsx +++ b/nym-wallet/src/components/Accounts/Accounts.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Button } from '@mui/material'; import { v4 as uuidv4 } from 'uuid'; import { TAccount } from 'src/types'; +import { createMnemonic } from 'src/requests'; import { EditAccountModal } from './EditAccountModal'; import { AddAccountModal } from './AddAccountModal'; import { AccountsModal } from './AccountsModal'; @@ -21,69 +22,71 @@ export const Accounts = ({ setSelectedAccount, setDialogToDisplay, }: { - accounts: TAccount[]; + accounts?: TAccount[]; selectedAccount: TAccount; accountToEdit?: TAccount; dialogToDisplay?: TDialog; addAccount: (acc: TAccount) => void; editAccount: (acc: TAccount) => void; importAccount: (acc: TAccount) => void; - setAccountToEdit: (acc: TAccount) => void; - setSelectedAccount: (accs: TAccount) => void; + setAccountToEdit: (accountName: string) => void; + setSelectedAccount: (accountName: string) => void; setDialogToDisplay: (dialog: TDialog | undefined) => void; -}) => ( - <> - - setDialogToDisplay(undefined)} - accounts={accounts} - onAccountSelect={(acc) => setSelectedAccount(acc)} - selectedAccount={selectedAccount.address} - onAdd={() => { - setDialogToDisplay('Add'); - }} - onEdit={(acc) => { - setAccountToEdit(acc); - setDialogToDisplay('Edit'); - }} - onImport={() => setDialogToDisplay('Import')} - /> - { - setDialogToDisplay('Accounts'); - }} - onAdd={(name) => { - addAccount({ name, address: uuidv4() }); - setDialogToDisplay('Accounts'); - }} - /> - { - setDialogToDisplay('Accounts'); - }} - onEdit={(account) => { - editAccount(account); - setDialogToDisplay('Accounts'); - }} - /> - setDialogToDisplay('Accounts')} - onImport={() => { - importAccount({ name: 'New Account', address: uuidv4() }); - setDialogToDisplay('Accounts'); - }} - /> - -); +}) => + accounts ? ( + <> + + setDialogToDisplay(undefined)} + accounts={accounts} + onAccountSelect={(accountName) => setSelectedAccount(accountName)} + selectedAccount={selectedAccount.address} + onAdd={() => { + setDialogToDisplay('Add'); + }} + onEdit={(accountName) => { + setAccountToEdit(accountName); + setDialogToDisplay('Edit'); + }} + onImport={() => setDialogToDisplay('Import')} + /> + { + setDialogToDisplay('Accounts'); + }} + onAdd={async (name) => { + const mnemonic = await createMnemonic(); + addAccount({ name, address: uuidv4(), mnemonic }); + setDialogToDisplay('Accounts'); + }} + /> + { + setDialogToDisplay('Accounts'); + }} + onEdit={(account) => { + editAccount(account); + setDialogToDisplay('Accounts'); + }} + /> + setDialogToDisplay('Accounts')} + onImport={(mnemonic) => { + importAccount({ name: 'New Account', address: uuidv4(), mnemonic }); + setDialogToDisplay('Accounts'); + }} + /> + + ) : null; diff --git a/nym-wallet/src/components/Accounts/AccountsModal.tsx b/nym-wallet/src/components/Accounts/AccountsModal.tsx index 4b4c9205aa..9127ebfba6 100644 --- a/nym-wallet/src/components/Accounts/AccountsModal.tsx +++ b/nym-wallet/src/components/Accounts/AccountsModal.tsx @@ -18,9 +18,9 @@ export const AccountsModal = ({ accounts: TAccount[]; selectedAccount: TAccount['address']; onClose: () => void; - onAccountSelect: (account: TAccount) => void; + onAccountSelect: (accountName: string) => void; onAdd: () => void; - onEdit: (acc: TAccount) => void; + onEdit: (accoutnName: string) => void; onImport: () => void; }) => ( @@ -41,10 +41,10 @@ export const AccountsModal = ({ name={name} address={address} onSelect={() => { - onAccountSelect({ name, address }); + onAccountSelect(name); onClose(); }} - onEdit={() => onEdit({ name, address })} + onEdit={() => onEdit(name)} selected={selectedAccount === address} key={address} /> diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index 60d1963618..70f9b8d454 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { Alert, Box, diff --git a/nym-wallet/src/components/Accounts/ShowMnemonic.tsx b/nym-wallet/src/components/Accounts/ShowMnemonic.tsx new file mode 100644 index 0000000000..f193333430 --- /dev/null +++ b/nym-wallet/src/components/Accounts/ShowMnemonic.tsx @@ -0,0 +1,41 @@ +import React, { useEffect, useState } from 'react'; +import { Box, Typography } from '@mui/material'; +import { accounts } from './mocks'; + +const fetchMnemonic = (accountName: string): Promise => + new Promise((res) => { + const account = accounts.find((acc) => acc.name === accountName); + if (account) setTimeout(() => res(account.mnemonic), 0); + else res('n/a'); + }); + +export const ShowMnemonic = ({ accountName }: { accountName: string }) => { + const [showMnemonic, setShowMnemonic] = useState(); + const [mnemonic, setMnemonic] = useState(); + + useEffect(() => { + const getMnemonic = async () => { + const mnic = await fetchMnemonic(accountName); + setMnemonic(mnic); + }; + + if (showMnemonic) getMnemonic(); + else setMnemonic(undefined); + }, [showMnemonic]); + + return ( + + { + e.stopPropagation(); + setShowMnemonic((show) => (!show ? accountName : undefined)); + }} + > + {`${showMnemonic ? 'Hide' : 'Show'} mnemonic`} + + {mnemonic && {mnemonic}} + + ); +}; diff --git a/nym-wallet/src/components/Accounts/mocks.ts b/nym-wallet/src/components/Accounts/mocks.ts new file mode 100644 index 0000000000..154af2e816 --- /dev/null +++ b/nym-wallet/src/components/Accounts/mocks.ts @@ -0,0 +1,14 @@ +export const accounts = [ + { + name: 'Account 1', + address: 'n107wsxkj08hycflnkp5ayfg6rt3pt0psm7w2t9r', + mnemonic: + 'ski shield hurry home trust sample napkin silver buzz peasant ability unlock chair lazy degree metal verify glimpse whisper primary youth cart pave peace', + }, + { + name: 'Account 2', + address: 'n1dgp04lqaasnzaww66zwdp6u24smqe7ltuny8vk', + mnemonic: + 'fire olympic connect title case across border route food this brief used cause ghost panic profit rather ritual nothing crystal fury into kangaroo rich', + }, +]; diff --git a/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx b/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx index 680b14f96d..c6a7ae19a8 100644 --- a/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx +++ b/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx @@ -1,9 +1,8 @@ import React from 'react'; import { Box } from '@mui/material'; import { ComponentMeta, ComponentStory } from '@storybook/react'; - -import { v4 as uuid4 } from 'uuid'; import { Accounts } from '../Accounts'; +import { accounts } from '../mocks'; export default { title: 'Wallet / Multi Account', @@ -18,9 +17,6 @@ const Template: ComponentStory = (args) => ( export const Default = Template.bind({}); Default.args = { - accounts: [ - { name: 'Account 1', address: uuid4() }, - { name: 'Account 2', address: uuid4() }, - ], - selectedAccount: { name: 'Account 1', address: uuid4() }, + accounts, + selectedAccount: accounts[0], }; diff --git a/nym-wallet/src/components/Accounts/stories/EditAccount.stories.tsx b/nym-wallet/src/components/Accounts/stories/EditAccount.stories.tsx index a75139e3c0..0430ac2950 100644 --- a/nym-wallet/src/components/Accounts/stories/EditAccount.stories.tsx +++ b/nym-wallet/src/components/Accounts/stories/EditAccount.stories.tsx @@ -17,7 +17,7 @@ const Template: ComponentStory = (args) => ( export const Default = Template.bind({}); Default.args = { - account: { name: 'Account 1', address: uuid() }, + account: { name: 'Account 1', address: uuid(), mnemonic: 'abc123' }, show: true, onClose: () => {}, }; diff --git a/nym-wallet/src/components/AppBar.tsx b/nym-wallet/src/components/AppBar.tsx index 6d7c2d3820..ffd583ec0a 100644 --- a/nym-wallet/src/components/AppBar.tsx +++ b/nym-wallet/src/components/AppBar.tsx @@ -7,16 +7,18 @@ import { Node as NodeIcon } from '../svg-icons/node'; import { AccountsContainer } from './Accounts/AccountContainer'; export const AppBar = () => { - const { showSettings, logOut, handleShowSettings } = useContext(ClientContext); + const { showSettings, storedAccounts, logOut, handleShowSettings } = useContext(ClientContext); return ( - - - + {storedAccounts && ( + + + + )} diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index 0a53ff3e95..213717faf4 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -2,7 +2,8 @@ import React, { useMemo, createContext, useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { useSnackbar } from 'notistack'; import { TLoginType } from 'src/pages/sign-in/types'; -import { Account, Network, TCurrency, TMixnodeBondDetails } from '../types'; +import { accounts } from 'src/components/Accounts/mocks'; +import { Account, Network, TAccount, TCurrency, TMixnodeBondDetails } from '../types'; import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance'; import { config } from '../../config'; import { getMixnodeBondDetails, selectNetwork, signInWithMnemonic, signInWithPassword, signOut } from '../requests'; @@ -22,9 +23,15 @@ export const urls = (networkName?: Network) => networkExplorer: `https://${networkName}-explorer.nymtech.net`, }; +const getAccounts = (): Promise => + new Promise((res) => { + setTimeout(() => res(accounts), 3000); + }); + type TClientContext = { mode: 'light' | 'dark'; clientDetails?: Account; + storedAccounts?: TAccount[]; mixnodeDetails?: TMixnodeBondDetails | null; userBalance: TUseuserBalance; showAdmin: boolean; @@ -49,6 +56,7 @@ export const ClientContext = createContext({} as TClientContext); export const ClientContextProvider = ({ children }: { children: React.ReactNode }) => { const [clientDetails, setClientDetails] = useState(); + const [storedAccounts, setStoredAccounts] = useState(); const [mixnodeDetails, setMixnodeDetails] = useState(); const [network, setNetwork] = useState(); const [currency, setCurrency] = useState(); @@ -104,6 +112,14 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode refreshAccount(); }, [network]); + useEffect(() => { + const fetchAccounts = async () => { + const accs = await getAccounts(); + setStoredAccounts(accs); + }; + fetchAccounts(); + }, []); + const logIn = async ({ type, value }: { type: TLoginType; value: string }) => { if (value.length === 0) { setError(`A ${type} must be provided`); @@ -148,6 +164,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode isLoading, error, clientDetails, + storedAccounts, mixnodeDetails, userBalance, showAdmin, diff --git a/nym-wallet/src/types/global.ts b/nym-wallet/src/types/global.ts index 758cd2095f..3f2abc8569 100644 --- a/nym-wallet/src/types/global.ts +++ b/nym-wallet/src/types/global.ts @@ -78,4 +78,5 @@ export type Period = 'Before' | { In: number } | 'After'; export type TAccount = { name: string; address: string; + mnemonic: string; }; From 31c4fc68076d8bf865d282d6c575987f6be8af1c Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 29 Apr 2022 10:45:23 +0100 Subject: [PATCH 23/85] update display mnemonic component --- nym-wallet/src/components/Accounts/AccountItem.tsx | 4 +++- nym-wallet/src/components/Accounts/ShowMnemonic.tsx | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index d76b6c5916..a019c76d57 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -36,7 +36,9 @@ export const AccountItem = ({ secondary={ {address} - + + + } /> diff --git a/nym-wallet/src/components/Accounts/ShowMnemonic.tsx b/nym-wallet/src/components/Accounts/ShowMnemonic.tsx index f193333430..d107e7c83b 100644 --- a/nym-wallet/src/components/Accounts/ShowMnemonic.tsx +++ b/nym-wallet/src/components/Accounts/ShowMnemonic.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Box, Typography } from '@mui/material'; import { accounts } from './mocks'; +import { CopyToClipboard } from '@nymproject/react'; const fetchMnemonic = (accountName: string): Promise => new Promise((res) => { @@ -27,7 +28,7 @@ export const ShowMnemonic = ({ accountName }: { accountName: string }) => { { e.stopPropagation(); setShowMnemonic((show) => (!show ? accountName : undefined)); @@ -35,7 +36,12 @@ export const ShowMnemonic = ({ accountName }: { accountName: string }) => { > {`${showMnemonic ? 'Hide' : 'Show'} mnemonic`} - {mnemonic && {mnemonic}} + {mnemonic && ( + + {mnemonic} + + + )} ); }; From 8bbffb6a88d2941275e4b6b430d813f4ad66bf03 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 29 Apr 2022 10:45:43 +0100 Subject: [PATCH 24/85] prevent bubbling --- .../src/components/clipboard/CopyToClipboard.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ts-packages/react-components/src/components/clipboard/CopyToClipboard.tsx b/ts-packages/react-components/src/components/clipboard/CopyToClipboard.tsx index 344241e566..2bcc6852fc 100644 --- a/ts-packages/react-components/src/components/clipboard/CopyToClipboard.tsx +++ b/ts-packages/react-components/src/components/clipboard/CopyToClipboard.tsx @@ -13,7 +13,8 @@ export const CopyToClipboard: React.FC<{ }> = ({ value, tooltip, onCopy, sx }) => { const copy = useClipboard(); const [showConfirmation, setShowConfirmation] = React.useState(false); - const handleCopy = () => { + const handleCopy = (e: React.MouseEvent) => { + e.stopPropagation(); setShowConfirmation(true); copy.copy(value); if (onCopy) { From 71b5bc9e713f86368676f37da97f2dc9dbd65004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 28 Apr 2022 16:46:02 +0200 Subject: [PATCH 25/85] wallet: backend account switching --- nym-wallet/src-tauri/src/main.rs | 1 + .../src/operations/mixnet/account.rs | 77 ++++++++++++++++++- nym-wallet/src-tauri/src/state.rs | 22 ++++++ .../src-tauri/src/wallet_storage/mod.rs | 1 + nym-wallet/src/types/rust/accountentry.ts | 4 + 5 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 nym-wallet/src/types/rust/accountentry.ts diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index 417cc416e9..d5c7d9ccf8 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -44,6 +44,7 @@ fn main() { mixnet::account::get_validator_api_urls, mixnet::account::get_validator_nymd_urls, mixnet::account::list_accounts_for_password, + mixnet::account::sign_in_decrypted_account, mixnet::account::logout, mixnet::account::remove_account_for_password, mixnet::account::remove_password, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 2c29d81d38..0a278cf7c7 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -3,7 +3,7 @@ use crate::config::Config; use crate::error::BackendError; use crate::network::Network as WalletNetwork; use crate::nymd_client; -use crate::state::State; +use crate::state::{DecryptedAccount, State}; use crate::wallet_storage::account_data::StoredLogin; use crate::wallet_storage::{self, DEFAULT_WALLET_ACCOUNT_ID}; @@ -14,7 +14,7 @@ use cosmrs::bip32::DerivationPath; use itertools::Itertools; use rand::seq::SliceRandom; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::convert::TryInto; use std::str::FromStr; use std::sync::Arc; @@ -51,6 +51,14 @@ pub struct CreatedAccount { mnemonic: String, } +#[cfg_attr(test, derive(ts_rs::TS))] +#[cfg_attr(test, ts(export, export_to = "../src/types/rust/createdaccount.ts"))] +#[derive(Clone, Serialize, Deserialize)] +pub struct AccountEntry { + id: String, + address: String, +} + #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export_to = "../src/types/rust/balance.ts"))] #[derive(Serialize, Deserialize)] @@ -432,6 +440,37 @@ pub async fn sign_in_with_password( .clone() } }; + + // Keep track of all accounts + // WIP(JON): simplify + let all_accounts: HashMap<_, _> = match stored_account { + StoredLogin::Mnemonic(ref account) => [( + id.to_string(), + DecryptedAccount { + id: id.to_string(), + mnemonic: account.mnemonic().clone(), + }, + )] + .into_iter() + .collect(), + StoredLogin::Multiple(ref accounts) => accounts + .get_accounts() + .map(|account| { + ( + account.id.to_string(), + DecryptedAccount { + id: account.id.to_string(), + mnemonic: account.account.mnemonic().clone(), + }, + ) + }) + .collect(), + }; + { + let mut w_state = state.write().await; + w_state.set_all_accounts(all_accounts); + } + _connect_with_mnemonic(mnemonic, state).await } @@ -468,6 +507,23 @@ pub fn remove_account_for_password(password: &str, inner_id: &str) -> Result<(), wallet_storage::remove_account_from_wallet_login(&id, &inner_id, &password) } +#[tauri::command] +pub async fn list_accounts( + state: tauri::State<'_, Arc>>, +) -> Result, BackendError> { + let state = state.read().await; + let all_accounts = state.get_all_accounts(); + let a = all_accounts + .values() + .map(|account| AccountEntry { + id: account.id.clone(), + address: "placeholder".to_string(), + }) + .collect(); + Ok(a) +} + +// WIP(JON): consider changing return type #[tauri::command] pub fn list_accounts_for_password(password: &str) -> Result, BackendError> { // Currently we only support a single, default, id in the wallet @@ -483,3 +539,20 @@ pub fn list_accounts_for_password(password: &str) -> Result, Backend }; Ok(ids) } + +#[tauri::command] +pub async fn sign_in_decrypted_account( + account_id: &str, + state: tauri::State<'_, Arc>>, +) -> Result { + let account = { + let state = state.read().await; + state + .get_all_accounts() + .get(account_id) + .ok_or(BackendError::NoSuchIdInWalletLoginEntry)? + .clone() + }; + let mnemonic = account.mnemonic; + _connect_with_mnemonic(mnemonic, state).await +} diff --git a/nym-wallet/src-tauri/src/state.rs b/nym-wallet/src-tauri/src/state.rs index 63e5a961c5..c1672d9391 100644 --- a/nym-wallet/src-tauri/src/state.rs +++ b/nym-wallet/src-tauri/src/state.rs @@ -2,6 +2,7 @@ use crate::config::{Config, OptionalValidators, ValidatorUrl}; use crate::error::BackendError; use crate::network::Network; +use bip39::Mnemonic; use strum::IntoEnumIterator; use validator_client::nymd::SigningNymdClient; use validator_client::Client; @@ -18,6 +19,10 @@ pub struct State { signing_clients: HashMap>, current_network: Network, + // All the accounts the we get from decrypting the wallet. We hold on to these for being able to + // switch accounts on-the-fly + all_accounts: HashMap, + /// Validators that have been fetched dynamically, probably during startup. fetched_validators: OptionalValidators, } @@ -63,6 +68,15 @@ impl State { self.current_network } + pub(crate) fn set_all_accounts(&mut self, all_accounts: HashMap) { + self.all_accounts.clear(); + self.all_accounts.extend(all_accounts) + } + + pub(crate) fn get_all_accounts(&self) -> &HashMap { + &self.all_accounts + } + pub fn logout(&mut self) { self.signing_clients = HashMap::new(); } @@ -162,6 +176,14 @@ macro_rules! client { }; } +// Keep track of mnemonics on the backend, so we can switch accounts +#[derive(Clone)] +pub(crate) struct DecryptedAccount { + pub id: String, + pub mnemonic: Mnemonic, + // address: String +} + #[macro_export] macro_rules! nymd_client { ($state:ident) => { diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 8fb7d10775..82c7585d35 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -153,6 +153,7 @@ fn append_account_to_wallet_login_information_at_file( let mut decrypted_login = stored_wallet.decrypt_login(&id, password)?; + // WIP(JON): redo this since we now can clone the mnemonic // Since we can't clone the mnemonic, we have to perform a little dance were we add the mnemonic // to the inner enum payload, while also converting by swapping if necessary. if let StoredLogin::Multiple(ref mut accounts) = decrypted_login { diff --git a/nym-wallet/src/types/rust/accountentry.ts b/nym-wallet/src/types/rust/accountentry.ts new file mode 100644 index 0000000000..f6ad1b53a9 --- /dev/null +++ b/nym-wallet/src/types/rust/accountentry.ts @@ -0,0 +1,4 @@ +export interface AccountEntry { + id: string; + address: string; +} From a7caf97b73055d15930e7a9767e0541e8a558616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 29 Apr 2022 15:12:07 +0200 Subject: [PATCH 26/85] wallet: derive_address --- nym-wallet/src-tauri/src/main.rs | 4 +-- .../src/operations/mixnet/account.rs | 33 +++++++++++++++---- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index d5c7d9ccf8..dba176ab7e 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -43,16 +43,16 @@ fn main() { mixnet::account::get_balance, mixnet::account::get_validator_api_urls, mixnet::account::get_validator_nymd_urls, + mixnet::account::list_accounts, mixnet::account::list_accounts_for_password, - mixnet::account::sign_in_decrypted_account, mixnet::account::logout, mixnet::account::remove_account_for_password, mixnet::account::remove_password, + mixnet::account::sign_in_decrypted_account, mixnet::account::sign_in_with_password, mixnet::account::switch_network, mixnet::account::update_validator_urls, mixnet::account::validate_mnemonic, - mixnet::account::validate_mnemonic, mixnet::admin::get_contract_settings, mixnet::admin::update_contract_settings, mixnet::bond::bond_gateway, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 0a278cf7c7..c448d74939 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -14,13 +14,14 @@ use cosmrs::bip32::DerivationPath; use itertools::Itertools; use rand::seq::SliceRandom; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::convert::TryInto; use std::str::FromStr; use std::sync::Arc; use strum::IntoEnumIterator; use tokio::sync::RwLock; use url::Url; +use validator_client::nymd::wallet::DirectSecp256k1HdWallet; use validator_client::{nymd::SigningNymdClient, Client}; @@ -53,7 +54,7 @@ pub struct CreatedAccount { #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export_to = "../src/types/rust/createdaccount.ts"))] -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct AccountEntry { id: String, address: String, @@ -471,6 +472,11 @@ pub async fn sign_in_with_password( w_state.set_all_accounts(all_accounts); } + let all_accounts = list_accounts(state.clone()).await?; + for account in all_accounts { + dbg!(&account); + } + _connect_with_mnemonic(mnemonic, state).await } @@ -507,20 +513,35 @@ pub fn remove_account_for_password(password: &str, inner_id: &str) -> Result<(), wallet_storage::remove_account_from_wallet_login(&id, &inner_id, &password) } +fn derive_address( + mnemonic: bip39::Mnemonic, + prefix: &str, +) -> Result { + let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)?; + let accounts = wallet.try_derive_accounts()?; + Ok(accounts[0].address().clone()) +} + #[tauri::command] pub async fn list_accounts( state: tauri::State<'_, Arc>>, ) -> Result, BackendError> { let state = state.read().await; - let all_accounts = state.get_all_accounts(); - let a = all_accounts + let network: Network = state.current_network().into(); + let prefix = network.bech32_prefix(); + + let all_accounts = state + .get_all_accounts() .values() .map(|account| AccountEntry { id: account.id.clone(), - address: "placeholder".to_string(), + address: derive_address(account.mnemonic.clone(), prefix) + .unwrap() + .to_string(), }) .collect(); - Ok(a) + + Ok(all_accounts) } // WIP(JON): consider changing return type From 580656c00284c9715df898238cf6b989cec5702d Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 29 Apr 2022 14:12:47 +0100 Subject: [PATCH 27/85] refactor + wip --- nym-wallet/src-tauri/src/main.rs | 2 +- .../components/Accounts/AccountContainer.tsx | 38 ++++++--- .../src/components/Accounts/AccountItem.tsx | 6 +- .../src/components/Accounts/Accounts.tsx | 28 +++---- .../src/components/Accounts/AccountsModal.tsx | 16 ++-- .../components/Accounts/AddAccountModal.tsx | 80 ++++++++++++++++--- .../components/Accounts/EditAccountModal.tsx | 10 +-- .../src/components/Accounts/ShowMnemonic.tsx | 18 ----- nym-wallet/src/components/Accounts/mocks.ts | 8 +- .../Accounts/stories/EditAccount.stories.tsx | 2 +- nym-wallet/src/context/main.tsx | 23 +++--- nym-wallet/src/requests/account.ts | 5 +- nym-wallet/src/types/rust/index.ts | 1 + 13 files changed, 142 insertions(+), 95 deletions(-) diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index d5c7d9ccf8..eab7759a91 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -43,7 +43,7 @@ fn main() { mixnet::account::get_balance, mixnet::account::get_validator_api_urls, mixnet::account::get_validator_nymd_urls, - mixnet::account::list_accounts_for_password, + mixnet::account::list_accounts, mixnet::account::sign_in_decrypted_account, mixnet::account::logout, mixnet::account::remove_account_for_password, diff --git a/nym-wallet/src/components/Accounts/AccountContainer.tsx b/nym-wallet/src/components/Accounts/AccountContainer.tsx index c00230099c..0a4f8b95e4 100644 --- a/nym-wallet/src/components/Accounts/AccountContainer.tsx +++ b/nym-wallet/src/components/Accounts/AccountContainer.tsx @@ -1,27 +1,41 @@ import React, { useEffect, useState } from 'react'; -import { TAccount } from 'src/types'; +import { AccountEntry } from 'src/types'; +import { addAccount as addAccountRequest } from 'src/requests'; import { Accounts } from './Accounts'; import { TDialog } from './types'; -export const AccountsContainer = ({ storedAccounts }: { storedAccounts: TAccount[] }) => { - const [accounts, setAccounts] = useState(storedAccounts); - const [selectedAccount, setSelectedAccount] = useState(storedAccounts[0]); - const [accountToEdit, setAccountToEdit] = useState(); +export const AccountsContainer = ({ storedAccounts }: { storedAccounts: AccountEntry[] }) => { + const [accounts, setAccounts] = useState(storedAccounts); + const [selectedAccount, setSelectedAccount] = useState(storedAccounts[0]); + const [accountToEdit, setAccountToEdit] = useState(); const [dialogToDisplay, setDialogToDisplay] = useState(); useEffect(() => { const selected = accounts?.find((acc) => acc.address === selectedAccount?.address); if (selected) setSelectedAccount(selected); - }, [accounts]); + }, [accounts, storedAccounts]); - const addAccount = (account: TAccount) => setAccounts((accs) => [...accs, account]); - const editAccount = (account: TAccount) => + const addAccount = async ({ + accountName, + mnemonic, + password, + }: { + accountName: string; + mnemonic: string; + password: string; + }) => { + await addAccountRequest({ + accountName, + mnemonic, + password, + }); + }; + const editAccount = (account: AccountEntry) => setAccounts((accs) => accs?.map((acc) => (acc.address === account.address ? account : acc))); - const importAccount = (account: TAccount) => setAccounts((accs) => [...accs, account]); - const handleAccountToEdit = (accountName: string) => - setAccountToEdit(accounts.find((acc) => acc.name === accountName)); + const importAccount = (account: AccountEntry) => setAccounts((accs) => [...accs, account]); + const handleAccountToEdit = (accountName: string) => setAccountToEdit(accounts.find((acc) => acc.id === accountName)); const handleSelectedAccount = (accountName: string) => { - const match = accounts.find((acc) => acc.name === accountName); + const match = accounts.find((acc) => acc.id === accountName); if (match) setSelectedAccount(match); }; diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index a019c76d57..a1fdb2f1cd 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -16,17 +16,17 @@ import { ShowMnemonic } from './ShowMnemonic'; export const AccountItem = ({ name, address, - selected, + isSelected, onSelect, onEdit, }: { name: string; address: string; - selected: boolean; + isSelected: boolean; onSelect: () => void; onEdit: () => void; }) => ( - + diff --git a/nym-wallet/src/components/Accounts/Accounts.tsx b/nym-wallet/src/components/Accounts/Accounts.tsx index 665022c6c2..e0fd917b64 100644 --- a/nym-wallet/src/components/Accounts/Accounts.tsx +++ b/nym-wallet/src/components/Accounts/Accounts.tsx @@ -1,8 +1,7 @@ import React from 'react'; import { Button } from '@mui/material'; import { v4 as uuidv4 } from 'uuid'; -import { TAccount } from 'src/types'; -import { createMnemonic } from 'src/requests'; +import { AccountEntry } from 'src/types'; import { EditAccountModal } from './EditAccountModal'; import { AddAccountModal } from './AddAccountModal'; import { AccountsModal } from './AccountsModal'; @@ -22,13 +21,13 @@ export const Accounts = ({ setSelectedAccount, setDialogToDisplay, }: { - accounts?: TAccount[]; - selectedAccount: TAccount; - accountToEdit?: TAccount; + accounts?: AccountEntry[]; + selectedAccount: AccountEntry; + accountToEdit?: AccountEntry; dialogToDisplay?: TDialog; - addAccount: (acc: TAccount) => void; - editAccount: (acc: TAccount) => void; - importAccount: (acc: TAccount) => void; + addAccount: (acc: { accountName: string; mnemonic: string; password: string }) => Promise; + editAccount: (acc: AccountEntry) => void; + importAccount: (acc: AccountEntry & { mnemonic: string }) => void; setAccountToEdit: (accountName: string) => void; setSelectedAccount: (accountName: string) => void; setDialogToDisplay: (dialog: TDialog | undefined) => void; @@ -36,19 +35,19 @@ export const Accounts = ({ accounts ? ( <> setDialogToDisplay(undefined)} accounts={accounts} onAccountSelect={(accountName) => setSelectedAccount(accountName)} - selectedAccount={selectedAccount.address} + selectedAccount={selectedAccount.id} onAdd={() => { setDialogToDisplay('Add'); }} @@ -63,9 +62,8 @@ export const Accounts = ({ onClose={() => { setDialogToDisplay('Accounts'); }} - onAdd={async (name) => { - const mnemonic = await createMnemonic(); - addAccount({ name, address: uuidv4(), mnemonic }); + onAdd={async (data) => { + addAccount(data); setDialogToDisplay('Accounts'); }} /> @@ -84,7 +82,7 @@ export const Accounts = ({ show={dialogToDisplay === 'Import'} onClose={() => setDialogToDisplay('Accounts')} onImport={(mnemonic) => { - importAccount({ name: 'New Account', address: uuidv4(), mnemonic }); + importAccount({ id: 'New Account', address: uuidv4(), mnemonic }); setDialogToDisplay('Accounts'); }} /> diff --git a/nym-wallet/src/components/Accounts/AccountsModal.tsx b/nym-wallet/src/components/Accounts/AccountsModal.tsx index 9127ebfba6..bc2de38243 100644 --- a/nym-wallet/src/components/Accounts/AccountsModal.tsx +++ b/nym-wallet/src/components/Accounts/AccountsModal.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Typography } from '@mui/material'; import { Add, ArrowDownwardSharp, Close } from '@mui/icons-material'; -import { TAccount } from 'src/types'; +import { AccountEntry } from 'src/types'; import { AccountItem } from './AccountItem'; export const AccountsModal = ({ @@ -15,8 +15,8 @@ export const AccountsModal = ({ onImport, }: { show: boolean; - accounts: TAccount[]; - selectedAccount: TAccount['address']; + accounts: AccountEntry[]; + selectedAccount: AccountEntry['id']; onClose: () => void; onAccountSelect: (accountName: string) => void; onAdd: () => void; @@ -36,16 +36,16 @@ export const AccountsModal = ({ - {accounts.map(({ name, address }) => ( + {accounts.map(({ id, address }) => ( { - onAccountSelect(name); + onAccountSelect(id); onClose(); }} - onEdit={() => onEdit(name)} - selected={selectedAccount === address} + onEdit={() => onEdit(id)} + isSelected={selectedAccount === id} key={address} /> ))} diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index 70f9b8d454..6a45c6213f 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Alert, Box, @@ -14,8 +14,9 @@ import { } from '@mui/material'; import { Check, Close, ContentCopySharp } from '@mui/icons-material'; import { useClipboard } from 'use-clipboard-copy'; +import { createMnemonic } from 'src/requests'; -const createAccountSteps = ['Save and copy mnemonic for your new account', 'Name your new account']; +const createAccountSteps = ['Save and copy mnemonic for your new account', 'Name your new account', 'Confirm password']; const passwordCreationSteps = [ 'Log out', @@ -49,7 +50,7 @@ const NoPassword = ({ onClose }: { onClose: () => void }) => ( ); -const MnemonicStep = ({ mnemonic, onSave }: { mnemonic: string; onSave: () => void }) => { +const MnemonicStep = ({ mnemonic, onNext }: { mnemonic: string; onNext: () => void }) => { const { copy, copied } = useClipboard({ copiedTimeout: 5000 }); return ( @@ -80,7 +81,7 @@ const MnemonicStep = ({ mnemonic, onSave }: { mnemonic: string; onSave: () => vo - @@ -88,7 +89,7 @@ const MnemonicStep = ({ mnemonic, onSave }: { mnemonic: string; onSave: () => vo ); }; -const NameAccount = ({ onAdd }: { onAdd: (value: string) => void }) => { +const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => { const [value, setValue] = useState(''); return ( @@ -102,9 +103,32 @@ const NameAccount = ({ onAdd }: { onAdd: (value: string) => void }) => { disableElevation variant="contained" size="large" - onClick={() => onAdd(value)} + onClick={() => onNext(value)} > - Add + Next + + + + ); +}; + +const ConfirmPassword = ({ onConfirm }: { onConfirm: (password: string) => void }) => { + const [value, setValue] = useState(''); + return ( + + + setValue(e.target.value)} fullWidth /> + + + @@ -120,9 +144,26 @@ export const AddAccountModal = ({ show: boolean; withoutPassword?: boolean; onClose: () => void; - onAdd: (accountName: string) => void; + onAdd: (data: { accountName: string; mnemonic: string; password: string }) => void; }) => { const [step, setStep] = useState(0); + const [data, setData] = useState<{ mnemonic?: string; accountName?: string }>({ + mnemonic: undefined, + accountName: undefined, + }); + + const generateMnemonic = async () => { + const mnemon = await createMnemonic(); + setData((d) => ({ ...d, mnemonic: mnemon })); + }; + + useEffect(() => { + if (show) generateMnemonic(); + else { + setData({ mnemonic: undefined, accountName: undefined }); + setStep(0); + } + }, [show]); return ( @@ -142,17 +183,30 @@ export const AddAccountModal = ({ {withoutPassword && } {!withoutPassword && + data.mnemonic && (() => { switch (step) { case 0: + return setStep((s) => s + 1)} />; + case 1: return ( - setStep((s) => s + 1)} + { + setData((d) => ({ ...d, accountName })); + setStep((s) => s + 1); + }} + /> + ); + case 2: + return ( + { + if (data.accountName && data.mnemonic) { + onAdd({ accountName: data.accountName, mnemonic: data.mnemonic, password }); + } + }} /> ); - case 1: - return ; default: return null; } diff --git a/nym-wallet/src/components/Accounts/EditAccountModal.tsx b/nym-wallet/src/components/Accounts/EditAccountModal.tsx index 1f290c6f0f..4ab69ea75f 100644 --- a/nym-wallet/src/components/Accounts/EditAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/EditAccountModal.tsx @@ -11,7 +11,7 @@ import { Typography, } from '@mui/material'; import { Close } from '@mui/icons-material'; -import { TAccount } from 'src/types'; +import { AccountEntry } from 'src/types'; export const EditAccountModal = ({ account, @@ -19,15 +19,15 @@ export const EditAccountModal = ({ onClose, onEdit, }: { - account?: TAccount; + account?: AccountEntry; show: boolean; onClose: () => void; - onEdit: (account: TAccount) => void; + onEdit: (account: AccountEntry) => void; }) => { const [accountName, setAccountName] = useState(''); useEffect(() => { - setAccountName(account ? account?.name : ''); + setAccountName(account ? account?.id : ''); }, [account]); return ( @@ -60,7 +60,7 @@ export const EditAccountModal = ({ disableElevation variant="contained" size="large" - onClick={() => account && onEdit({ ...account, name: accountName })} + onClick={() => account && onEdit({ ...account, id: accountName })} disabled={!accountName?.length} > Edit diff --git a/nym-wallet/src/components/Accounts/ShowMnemonic.tsx b/nym-wallet/src/components/Accounts/ShowMnemonic.tsx index d107e7c83b..00cce65f44 100644 --- a/nym-wallet/src/components/Accounts/ShowMnemonic.tsx +++ b/nym-wallet/src/components/Accounts/ShowMnemonic.tsx @@ -1,29 +1,11 @@ import React, { useEffect, useState } from 'react'; import { Box, Typography } from '@mui/material'; -import { accounts } from './mocks'; import { CopyToClipboard } from '@nymproject/react'; -const fetchMnemonic = (accountName: string): Promise => - new Promise((res) => { - const account = accounts.find((acc) => acc.name === accountName); - if (account) setTimeout(() => res(account.mnemonic), 0); - else res('n/a'); - }); - export const ShowMnemonic = ({ accountName }: { accountName: string }) => { const [showMnemonic, setShowMnemonic] = useState(); const [mnemonic, setMnemonic] = useState(); - useEffect(() => { - const getMnemonic = async () => { - const mnic = await fetchMnemonic(accountName); - setMnemonic(mnic); - }; - - if (showMnemonic) getMnemonic(); - else setMnemonic(undefined); - }, [showMnemonic]); - return ( = (args) => ( export const Default = Template.bind({}); Default.args = { - account: { name: 'Account 1', address: uuid(), mnemonic: 'abc123' }, + account: { id: 'Account 1', address: uuid() }, show: true, onClose: () => {}, }; diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index 213717faf4..08fc81be6b 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -2,11 +2,17 @@ import React, { useMemo, createContext, useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { useSnackbar } from 'notistack'; import { TLoginType } from 'src/pages/sign-in/types'; -import { accounts } from 'src/components/Accounts/mocks'; -import { Account, Network, TAccount, TCurrency, TMixnodeBondDetails } from '../types'; +import { Account, Network, TCurrency, TMixnodeBondDetails, AccountEntry } from '../types'; import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance'; import { config } from '../../config'; -import { getMixnodeBondDetails, selectNetwork, signInWithMnemonic, signInWithPassword, signOut } from '../requests'; +import { + getMixnodeBondDetails, + listAccounts, + selectNetwork, + signInWithMnemonic, + signInWithPassword, + signOut, +} from '../requests'; import { currencyMap } from '../utils'; import { Console } from '../utils/console'; @@ -23,15 +29,10 @@ export const urls = (networkName?: Network) => networkExplorer: `https://${networkName}-explorer.nymtech.net`, }; -const getAccounts = (): Promise => - new Promise((res) => { - setTimeout(() => res(accounts), 3000); - }); - type TClientContext = { mode: 'light' | 'dark'; clientDetails?: Account; - storedAccounts?: TAccount[]; + storedAccounts?: AccountEntry[]; mixnodeDetails?: TMixnodeBondDetails | null; userBalance: TUseuserBalance; showAdmin: boolean; @@ -56,7 +57,7 @@ export const ClientContext = createContext({} as TClientContext); export const ClientContextProvider = ({ children }: { children: React.ReactNode }) => { const [clientDetails, setClientDetails] = useState(); - const [storedAccounts, setStoredAccounts] = useState(); + const [storedAccounts, setStoredAccounts] = useState(); const [mixnodeDetails, setMixnodeDetails] = useState(); const [network, setNetwork] = useState(); const [currency, setCurrency] = useState(); @@ -114,7 +115,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode useEffect(() => { const fetchAccounts = async () => { - const accs = await getAccounts(); + const accs = await listAccounts(); setStoredAccounts(accs); }; fetchAccounts(); diff --git a/nym-wallet/src/requests/account.ts b/nym-wallet/src/requests/account.ts index 6fcf7c6628..914fa74061 100644 --- a/nym-wallet/src/requests/account.ts +++ b/nym-wallet/src/requests/account.ts @@ -1,4 +1,5 @@ import { invoke } from '@tauri-apps/api'; +import { AccountEntry } from 'src/types/rust/accountentry'; import { Account } from '../types'; export const createMnemonic = async (): Promise => invoke('create_new_mnemonic'); @@ -43,7 +44,7 @@ export const addAccount = async ({ await invoke('add_account_for_password', { mnemonic, password, innerId: accountName }); }; -export const listAccounts = async (password: string) => { - const res: Account[] = await invoke('list_accounts_for_password', { password }); +export const listAccounts = async () => { + const res: AccountEntry[] = await invoke('list_accounts'); return res; }; diff --git a/nym-wallet/src/types/rust/index.ts b/nym-wallet/src/types/rust/index.ts index d82e3f75c5..c9e0b3e2a0 100644 --- a/nym-wallet/src/types/rust/index.ts +++ b/nym-wallet/src/types/rust/index.ts @@ -23,3 +23,4 @@ export * from './vestingperiod'; export * from './pendingundelegate'; export * from './delegationevent'; export * from './epoch'; +export * from './accountentry'; From 05374393ef9efe48137bee5fa1ea76f6626431be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 29 Apr 2022 15:36:06 +0200 Subject: [PATCH 28/85] wallet: contructors for DecryptedAccount --- .../src/operations/mixnet/account.rs | 18 +++-------------- nym-wallet/src-tauri/src/state.rs | 20 ++++++++++++++++--- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index c448d74939..d6d5a81c7d 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -442,29 +442,17 @@ pub async fn sign_in_with_password( } }; - // Keep track of all accounts - // WIP(JON): simplify + // Keep track of all accounts for that id let all_accounts: HashMap<_, _> = match stored_account { StoredLogin::Mnemonic(ref account) => [( id.to_string(), - DecryptedAccount { - id: id.to_string(), - mnemonic: account.mnemonic().clone(), - }, + DecryptedAccount::new(id.to_string(), account.mnemonic().clone()), )] .into_iter() .collect(), StoredLogin::Multiple(ref accounts) => accounts .get_accounts() - .map(|account| { - ( - account.id.to_string(), - DecryptedAccount { - id: account.id.to_string(), - mnemonic: account.account.mnemonic().clone(), - }, - ) - }) + .map(|account| (account.id.to_string(), account.into())) .collect(), }; { diff --git a/nym-wallet/src-tauri/src/state.rs b/nym-wallet/src-tauri/src/state.rs index c1672d9391..bc98dbcb4e 100644 --- a/nym-wallet/src-tauri/src/state.rs +++ b/nym-wallet/src-tauri/src/state.rs @@ -1,8 +1,8 @@ use crate::config::{Config, OptionalValidators, ValidatorUrl}; use crate::error::BackendError; use crate::network::Network; +use crate::wallet_storage::account_data::WalletAccount; -use bip39::Mnemonic; use strum::IntoEnumIterator; use validator_client::nymd::SigningNymdClient; use validator_client::Client; @@ -180,8 +180,22 @@ macro_rules! client { #[derive(Clone)] pub(crate) struct DecryptedAccount { pub id: String, - pub mnemonic: Mnemonic, - // address: String + pub mnemonic: bip39::Mnemonic, +} + +impl DecryptedAccount { + pub fn new(id: String, mnemonic: bip39::Mnemonic) -> Self { + Self { id, mnemonic } + } +} + +impl From<&WalletAccount> for DecryptedAccount { + fn from(wallet_account: &WalletAccount) -> Self { + Self { + id: wallet_account.id.to_string(), + mnemonic: wallet_account.account.mnemonic().clone(), + } + } } #[macro_export] From f7be9e7e6f694cfe9e9c3459f19e5436a9692b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 29 Apr 2022 16:27:24 +0200 Subject: [PATCH 29/85] wallet: remove DecryptedAccount and make Accounts clone --- .../src/operations/mixnet/account.rs | 38 +++++++------------ nym-wallet/src-tauri/src/state.rs | 28 ++------------ .../src/wallet_storage/account_data.rs | 19 ++++++++-- 3 files changed, 32 insertions(+), 53 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index d6d5a81c7d..55b0ac30d0 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -3,8 +3,8 @@ use crate::config::Config; use crate::error::BackendError; use crate::network::Network as WalletNetwork; use crate::nymd_client; -use crate::state::{DecryptedAccount, State}; -use crate::wallet_storage::account_data::StoredLogin; +use crate::state::State; +use crate::wallet_storage::account_data::{StoredLogin, WalletAccount}; use crate::wallet_storage::{self, DEFAULT_WALLET_ACCOUNT_ID}; use bip39::{Language, Mnemonic}; @@ -442,29 +442,18 @@ pub async fn sign_in_with_password( } }; - // Keep track of all accounts for that id - let all_accounts: HashMap<_, _> = match stored_account { - StoredLogin::Mnemonic(ref account) => [( - id.to_string(), - DecryptedAccount::new(id.to_string(), account.mnemonic().clone()), - )] - .into_iter() - .collect(), - StoredLogin::Multiple(ref accounts) => accounts - .get_accounts() - .map(|account| (account.id.to_string(), account.into())) - .collect(), - }; { + // Keep track of all accounts for that id + let all_accounts: HashMap = stored_account + .into_multiple_accounts(id) + .into_accounts() + .map(|a| (a.id.to_string(), a)) + .collect(); + let mut w_state = state.write().await; w_state.set_all_accounts(all_accounts); } - let all_accounts = list_accounts(state.clone()).await?; - for account in all_accounts { - dbg!(&account); - } - _connect_with_mnemonic(mnemonic, state).await } @@ -522,8 +511,8 @@ pub async fn list_accounts( .get_all_accounts() .values() .map(|account| AccountEntry { - id: account.id.clone(), - address: derive_address(account.mnemonic.clone(), prefix) + id: account.id.to_string(), + address: derive_address(account.account.mnemonic().clone(), prefix) .unwrap() .to_string(), }) @@ -554,14 +543,15 @@ pub async fn sign_in_decrypted_account( account_id: &str, state: tauri::State<'_, Arc>>, ) -> Result { - let account = { + let mnemonic = { let state = state.read().await; state .get_all_accounts() .get(account_id) .ok_or(BackendError::NoSuchIdInWalletLoginEntry)? + .account + .mnemonic() .clone() }; - let mnemonic = account.mnemonic; _connect_with_mnemonic(mnemonic, state).await } diff --git a/nym-wallet/src-tauri/src/state.rs b/nym-wallet/src-tauri/src/state.rs index bc98dbcb4e..ae9f2c07e0 100644 --- a/nym-wallet/src-tauri/src/state.rs +++ b/nym-wallet/src-tauri/src/state.rs @@ -21,7 +21,7 @@ pub struct State { // All the accounts the we get from decrypting the wallet. We hold on to these for being able to // switch accounts on-the-fly - all_accounts: HashMap, + all_accounts: HashMap, /// Validators that have been fetched dynamically, probably during startup. fetched_validators: OptionalValidators, @@ -68,12 +68,12 @@ impl State { self.current_network } - pub(crate) fn set_all_accounts(&mut self, all_accounts: HashMap) { + pub(crate) fn set_all_accounts(&mut self, all_accounts: HashMap) { self.all_accounts.clear(); self.all_accounts.extend(all_accounts) } - pub(crate) fn get_all_accounts(&self) -> &HashMap { + pub(crate) fn get_all_accounts(&self) -> &HashMap { &self.all_accounts } @@ -176,28 +176,6 @@ macro_rules! client { }; } -// Keep track of mnemonics on the backend, so we can switch accounts -#[derive(Clone)] -pub(crate) struct DecryptedAccount { - pub id: String, - pub mnemonic: bip39::Mnemonic, -} - -impl DecryptedAccount { - pub fn new(id: String, mnemonic: bip39::Mnemonic) -> Self { - Self { id, mnemonic } - } -} - -impl From<&WalletAccount> for DecryptedAccount { - fn from(wallet_account: &WalletAccount) -> Self { - Self { - id: wallet_account.id.to_string(), - mnemonic: wallet_account.account.mnemonic().clone(), - } - } -} - #[macro_export] macro_rules! nymd_client { ($state:ident) => { diff --git a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs index 7336a33c0e..dca15df57e 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -159,10 +159,17 @@ impl StoredLogin { StoredLogin::Multiple(accounts) => Some(accounts), } } + + pub(crate) fn into_multiple_accounts(self, id: AccountId) -> MultipleAccounts { + match self { + StoredLogin::Mnemonic(ref account) => account.clone().into_multiple(id), + StoredLogin::Multiple(ref accounts) => accounts.clone(), + } + } } /// An account backed by a unique mnemonic. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct MnemonicAccount { mnemonic: bip39::Mnemonic, #[serde(with = "display_hd_path")] @@ -215,7 +222,7 @@ impl Drop for MnemonicAccount { } /// Multiple stored accounts, each entry having an id and a data field. -#[derive(Serialize, Deserialize, Debug, Zeroize, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] pub(crate) struct MultipleAccounts { accounts: Vec, } @@ -235,6 +242,10 @@ impl MultipleAccounts { self.accounts.iter().find(|account| &account.id == id) } + pub(crate) fn into_accounts(self) -> impl Iterator { + self.accounts.into_iter() + } + #[allow(unused)] pub(crate) fn len(&self) -> usize { self.accounts.len() @@ -274,7 +285,7 @@ impl From> for MultipleAccounts { } /// An entry in the list of stored accounts -#[derive(Serialize, Deserialize, Debug, Zeroize, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] pub(crate) struct WalletAccount { pub id: AccountId, pub account: AccountData, @@ -293,7 +304,7 @@ impl WalletAccount { } } -#[derive(Serialize, Deserialize, Debug, Zeroize, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] #[serde(untagged)] #[zeroize(drop)] pub(crate) enum AccountData { From 7e356ea3b3627b666c7c140a7bfa03e255abfbe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 29 Apr 2022 16:49:57 +0200 Subject: [PATCH 30/85] wallet: simplify account handling --- .../src/operations/mixnet/account.rs | 2 +- .../src/wallet_storage/account_data.rs | 10 ++------- .../src-tauri/src/wallet_storage/mod.rs | 21 +++++-------------- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 55b0ac30d0..5709dcdf58 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -445,7 +445,7 @@ pub async fn sign_in_with_password( { // Keep track of all accounts for that id let all_accounts: HashMap = stored_account - .into_multiple_accounts(id) + .unwrap_into_multiple_accounts(id) .into_accounts() .map(|a| (a.id.to_string(), a)) .collect(); diff --git a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs index dca15df57e..b6b334e814 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -160,7 +160,7 @@ impl StoredLogin { } } - pub(crate) fn into_multiple_accounts(self, id: AccountId) -> MultipleAccounts { + pub(crate) fn unwrap_into_multiple_accounts(self, id: AccountId) -> MultipleAccounts { match self { StoredLogin::Mnemonic(ref account) => account.clone().into_multiple(id), StoredLogin::Multiple(ref accounts) => accounts.clone(), @@ -177,17 +177,11 @@ pub(crate) struct MnemonicAccount { } impl MnemonicAccount { - pub(crate) fn generate_new(&self) -> MnemonicAccount { - MnemonicAccount { - mnemonic: bip39::Mnemonic::generate(self.mnemonic().word_count()).unwrap(), - hd_path: self.hd_path().clone(), - } - } - pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { &self.mnemonic } + #[cfg(test)] pub(crate) fn hd_path(&self) -> &DerivationPath { &self.hd_path } diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 82c7585d35..9dfaa74fa4 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -151,26 +151,15 @@ fn append_account_to_wallet_login_information_at_file( result => result?, }; - let mut decrypted_login = stored_wallet.decrypt_login(&id, password)?; + let decrypted_login = stored_wallet.decrypt_login(&id, password)?; - // WIP(JON): redo this since we now can clone the mnemonic - // Since we can't clone the mnemonic, we have to perform a little dance were we add the mnemonic - // to the inner enum payload, while also converting by swapping if necessary. - if let StoredLogin::Multiple(ref mut accounts) = decrypted_login { - accounts.add(inner_id, mnemonic, hd_path)?; - } else if let StoredLogin::Mnemonic(ref mut account) = decrypted_login { - // Move out the account by swapping, since we can't clone. - let account = std::mem::replace(account, account.generate_new()); - // Convert the enum variant - let mut accounts = account.into_multiple(id.clone()); - accounts.add(inner_id, mnemonic, hd_path)?; - // Overwrite the stored login with the new enum variant - decrypted_login = StoredLogin::Multiple(accounts); - } + // Add accounts to the inner structure + let mut accounts = decrypted_login.unwrap_into_multiple_accounts(id.clone()); + accounts.add(inner_id, mnemonic, hd_path)?; let encrypted_accounts = EncryptedLogin { id, - account: encrypt_struct(&decrypted_login, password)?, + account: encrypt_struct(&StoredLogin::Multiple(accounts), password)?, }; stored_wallet.replace_encrypted_login(encrypted_accounts)?; From 6d874cc34aec02aa131db2eeb8bfd498550dd6d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 29 Apr 2022 16:53:51 +0200 Subject: [PATCH 31/85] wallet: remove list_accounts_for_password --- nym-wallet/src-tauri/src/main.rs | 1 - .../src-tauri/src/operations/mixnet/account.rs | 17 ----------------- 2 files changed, 18 deletions(-) diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index dba176ab7e..0130ca496c 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -44,7 +44,6 @@ fn main() { mixnet::account::get_validator_api_urls, mixnet::account::get_validator_nymd_urls, mixnet::account::list_accounts, - mixnet::account::list_accounts_for_password, mixnet::account::logout, mixnet::account::remove_account_for_password, mixnet::account::remove_password, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 5709dcdf58..b75f7ef34d 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -521,23 +521,6 @@ pub async fn list_accounts( Ok(all_accounts) } -// WIP(JON): consider changing return type -#[tauri::command] -pub fn list_accounts_for_password(password: &str) -> Result, BackendError> { - // Currently we only support a single, default, id in the wallet - let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); - let password = wallet_storage::UserPassword::new(password.to_string()); - let login = wallet_storage::load_existing_wallet_login_information(&id, &password)?; - let ids = match login { - StoredLogin::Mnemonic(_) => vec![id.to_string()], - StoredLogin::Multiple(ref accounts) => accounts - .get_accounts() - .map(|account| account.id.to_string()) - .collect::>(), - }; - Ok(ids) -} - #[tauri::command] pub async fn sign_in_decrypted_account( account_id: &str, From 30a41261ea7aecaf985c3323c45e1d82baf1ce21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 3 May 2022 20:03:46 +0200 Subject: [PATCH 32/85] wallet: add more log statements --- nym-wallet/src-tauri/src/error.rs | 2 ++ .../src-tauri/src/operations/mixnet/account.rs | 14 ++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index ef0da6a5f8..65f1bac4b2 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -97,6 +97,8 @@ pub enum BackendError { WalletUnexpectedMultipleAccounts, #[error("Unexpted mnemonic account found")] WalletUnexpectedMnemonicAccount, + #[error("Failed to derive address from mnemonic")] + FailedToDeriveAddress, } impl Serialize for BackendError { diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index b75f7ef34d..b630cfac91 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -21,7 +21,7 @@ use std::sync::Arc; use strum::IntoEnumIterator; use tokio::sync::RwLock; use url::Url; -use validator_client::nymd::wallet::DirectSecp256k1HdWallet; +use validator_client::nymd::wallet::{AccountData, DirectSecp256k1HdWallet}; use validator_client::{nymd::SigningNymdClient, Client}; @@ -470,6 +470,7 @@ pub fn add_account_for_password( password: &str, inner_id: &str, ) -> Result<(), BackendError> { + log::info!("Adding account for the current password: {inner_id}"); let mnemonic = Mnemonic::from_str(mnemonic)?; let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); // Currently we only support a single, default, id in the wallet @@ -483,6 +484,7 @@ pub fn add_account_for_password( #[tauri::command] pub fn remove_account_for_password(password: &str, inner_id: &str) -> Result<(), BackendError> { + log::info!("Removing account: {inner_id}"); // Currently we only support a single, default, id in the wallet let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); let inner_id = wallet_storage::AccountId::new(inner_id.to_string()); @@ -494,9 +496,12 @@ fn derive_address( mnemonic: bip39::Mnemonic, prefix: &str, ) -> Result { - let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)?; - let accounts = wallet.try_derive_accounts()?; - Ok(accounts[0].address().clone()) + DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)? + .try_derive_accounts()? + .first() + .map(AccountData::address) + .cloned() + .ok_or(BackendError::FailedToDeriveAddress) } #[tauri::command] @@ -526,6 +531,7 @@ pub async fn sign_in_decrypted_account( account_id: &str, state: tauri::State<'_, Arc>>, ) -> Result { + log::info!("Signing in to already decrypted account: {account_id}"); let mnemonic = { let state = state.read().await; state From df4c6493d40796a590da6cde350994434bf2be50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 3 May 2022 23:20:16 +0200 Subject: [PATCH 33/85] wallet: split and add more wallet_store tests --- .../src-tauri/src/wallet_storage/mod.rs | 364 +++++++++++++----- 1 file changed, 263 insertions(+), 101 deletions(-) diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 9dfaa74fa4..f97a2f0ed6 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -292,8 +292,7 @@ mod tests { // I'm not 100% sure how to feel about having to touch the file system at all #[test] - #[allow(clippy::too_many_lines)] - fn storing_wallet_information() { + fn store_two_mnemonic_accounts_using_two_logins() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); @@ -371,13 +370,9 @@ mod tests { let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); - - if let StoredLogin::Mnemonic(ref acc) = loaded_account { - assert_eq!(&dummy_account1, acc.mnemonic()); - assert_eq!(&cosmos_hd_path, acc.hd_path()); - } else { - todo!(); - } + let acc = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(&dummy_account1, acc.mnemonic()); + assert_eq!(&cosmos_hd_path, acc.hd_path()); // Can't store extra account if you use different password assert!(matches!( @@ -415,21 +410,62 @@ mod tests { // first account should be unchanged let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); - if let StoredLogin::Mnemonic(ref acc1) = loaded_account { - assert_eq!(&dummy_account1, acc1.mnemonic()); - assert_eq!(&cosmos_hd_path, acc1.hd_path()); - } else { - todo!(); - } + let acc1 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(&dummy_account1, acc1.mnemonic()); + assert_eq!(&cosmos_hd_path, acc1.hd_path()); + + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); + let acc2 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(&dummy_account2, acc2.mnemonic()); + assert_eq!(&different_hd_path, acc2.hd_path()); + } + + #[test] + fn store_and_remove_wallet_login_information() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let different_hd_path: DerivationPath = "m".parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + // Store two accounts with two different passwords + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2.clone(), + different_hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // Load and compare + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc1 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(&dummy_account1, acc1.mnemonic()); + assert_eq!(&cosmos_hd_path, acc1.hd_path()); let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); - if let StoredLogin::Mnemonic(ref acc2) = loaded_account { - assert_eq!(&dummy_account2, acc2.mnemonic()); - assert_eq!(&different_hd_path, acc2.hd_path()); - } else { - todo!(); - } + let acc2 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(&dummy_account2, acc2.mnemonic()); + assert_eq!(&different_hd_path, acc2.hd_path()); // Fails to delete non-existent id in the wallet let id3 = AccountId::new("phony".to_string()); @@ -444,12 +480,210 @@ mod tests { // The first account should be unchanged let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); - if let StoredLogin::Mnemonic(ref acc1) = loaded_account { - assert_eq!(&dummy_account1, acc1.mnemonic()); - assert_eq!(&cosmos_hd_path, acc1.hd_path()); - } else { - todo!(); - } + let acc1 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(&dummy_account1, acc1.mnemonic()); + assert_eq!(&cosmos_hd_path, acc1.hd_path()); + + // And we can't load the second one anymore + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password), + Err(BackendError::NoSuchIdInWallet), + )); + + // Delete the first account + assert!(wallet_file.exists()); + remove_wallet_login_information_at_file(wallet_file.clone(), &id1).unwrap(); + + // The file should now be removed + assert!(!wallet_file.exists()); + + // And trying to load when the file is gone fails + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file, &id1, &password), + Err(BackendError::WalletFileNotFound), + )); + } + + #[test] + fn append_accounts_to_existing_login() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account4 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + let id3 = AccountId::new("third".to_string()); + let id4 = AccountId::new("fourth".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2.clone(), + cosmos_hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // Check that it's there as the correct non-multiple type + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); + let acc2 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(acc2.mnemonic(), &dummy_account2); + assert_eq!(acc2.hd_path(), &cosmos_hd_path); + + // Add a third mnenonic grouped together with the second one + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account3.clone(), + cosmos_hd_path.clone(), + id2.clone(), + id3.clone(), + &password, + ) + .unwrap(); + + // Add a fourth mnenonic grouped together with the second one + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account4.clone(), + cosmos_hd_path.clone(), + id2.clone(), + id4.clone(), + &password, + ) + .unwrap(); + + // Check that we can still load all four + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc1 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(acc1.mnemonic(), &dummy_account1); + assert_eq!(acc1.hd_path(), &cosmos_hd_path); + + let loaded_accounts = + load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); + let accounts = loaded_accounts.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, cosmos_hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, cosmos_hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id4, dummy_account4, cosmos_hd_path), + ] + .into(); + assert_eq!(accounts, &expected); + } + + #[test] + fn append_accounts_and_remove() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account4 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + let id3 = AccountId::new("third".to_string()); + let id4 = AccountId::new("fourth".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2.clone(), + cosmos_hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // Add a third mnenonic grouped together with the second one + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account3, + cosmos_hd_path.clone(), + id2.clone(), + id3.clone(), + &password, + ) + .unwrap(); + + // Add a fourth mnenonic grouped together with the second one + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account4.clone(), + cosmos_hd_path.clone(), + id2.clone(), + id4.clone(), + &password, + ) + .unwrap(); + + // Delete the third mnemonic, from the second login entry + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id2, &id3, &password).unwrap(); + + // Check that we can still load all three others + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc1 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(acc1.mnemonic(), &dummy_account1); + assert_eq!(acc1.hd_path(), &cosmos_hd_path); + + let loaded_accounts = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); + let accounts = loaded_accounts.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new_mnemonic_backed_account( + id2.clone(), + dummy_account2, + cosmos_hd_path.clone(), + ), + WalletAccount::new_mnemonic_backed_account(id4, dummy_account4, cosmos_hd_path.clone()), + ] + .into(); + assert_eq!(accounts, &expected); + + // Delete the second account, this will also delete the fourth mnemonic that is grouped with the + // second one. + remove_wallet_login_information_at_file(wallet_file.clone(), &id2).unwrap(); + + // Check that we can still load the first + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc1 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(acc1.mnemonic(), &dummy_account1); + assert_eq!(acc1.hd_path(), &cosmos_hd_path); + + // and the loading the deleted one fails + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password) + .unwrap_err(); // Delete the first account assert!(wallet_file.exists()); @@ -459,10 +693,10 @@ mod tests { assert!(!wallet_file.exists()); } + // Test to that decrypts a stored file from the repo, to make sure we are able to decrypt stored + // wallets created with older versions. #[test] fn decrypt_stored_wallet() { - pretty_env_logger::init(); - const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet.json"; let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET); @@ -504,76 +738,4 @@ mod tests { &cosmos_hd_path, ); } - - #[test] - fn append_a_third_account() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - - let password = UserPassword::new("password".to_string()); - - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); - let id3 = AccountId::new("third".to_string()); - - store_wallet_login_information_at_file( - wallet_file.clone(), - dummy_account1.clone(), - cosmos_hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - store_wallet_login_information_at_file( - wallet_file.clone(), - dummy_account2.clone(), - cosmos_hd_path.clone(), - id2.clone(), - &password, - ) - .unwrap(); - - // Check that it's there as the correct non-multiple type - let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); - let acc2 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(acc2.mnemonic(), &dummy_account2); - assert_eq!(acc2.hd_path(), &cosmos_hd_path); - - // Add a third mnenonic grouped together with the second one - append_account_to_wallet_login_information_at_file( - wallet_file.clone(), - dummy_account3.clone(), - cosmos_hd_path.clone(), - id2.clone(), - id3.clone(), - &password, - ) - .unwrap(); - - // Check that we can still load all three - let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let acc1 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(acc1.mnemonic(), &dummy_account1); - assert_eq!(acc1.hd_path(), &cosmos_hd_path); - - let loaded_accounts = - load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); - let accounts = loaded_accounts.as_multiple_accounts().unwrap(); - - let expected = vec![ - WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, cosmos_hd_path.clone()), - WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, cosmos_hd_path), - ] - .into(); - - assert_eq!(accounts, &expected); - } } From 1a5580229bb0ed99f86c6b69dc99bc98493a9c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 4 May 2022 01:35:57 +0200 Subject: [PATCH 34/85] wallet: more unit tests --- .../src-tauri/src/wallet_storage/mod.rs | 684 +++++++++++++++--- 1 file changed, 568 insertions(+), 116 deletions(-) diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index f97a2f0ed6..f4e2243bc9 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -290,7 +290,260 @@ mod tests { use std::str::FromStr; use tempfile::tempdir; - // I'm not 100% sure how to feel about having to touch the file system at all + #[test] + fn trying_to_load_nonexistant_wallet_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let id1 = AccountId::new("first".to_string()); + let password = UserPassword::new("password".to_string()); + + assert!(matches!( + load_existing_wallet_at_file(wallet_file.clone()), + Err(BackendError::WalletFileNotFound), + )); + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password), + Err(BackendError::WalletFileNotFound), + )); + remove_wallet_login_information_at_file(wallet_file, &id1).unwrap_err(); + } + + #[test] + fn store_single_login() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = AccountId::new("first".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path, + id1, + &password, + ) + .unwrap(); + + let stored_wallet = load_existing_wallet_at_file(wallet_file).unwrap(); + assert_eq!(stored_wallet.len(), 1); + + let login = stored_wallet.get_encrypted_login_by_index(0).unwrap(); + assert_eq!(login.id, AccountId::new("first".to_string())); + + // some actual ciphertext was saved + assert!(!login.account.ciphertext().is_empty()); + } + + #[test] + fn store_twice_for_the_same_id_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = AccountId::new("first".to_string()); + + // Store the first login + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + // and storing the same id again fails + assert!(matches!( + store_wallet_login_information_at_file( + wallet_file, + dummy_account1, + cosmos_hd_path, + id1, + &password, + ), + Err(BackendError::IdAlreadyExistsInWallet), + )); + } + + #[test] + fn load_with_wrong_password_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let bad_password = UserPassword::new("bad-password".to_string()); + let id1 = AccountId::new("first".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path, + id1.clone(), + &password, + ) + .unwrap(); + + // Trying to load it with wrong password now fails + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file, &id1, &bad_password), + Err(BackendError::DecryptionError), + )); + } + + #[test] + fn load_with_wrong_id_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path, + id1, + &password, + ) + .unwrap(); + + // Trying to load with the wrong id + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file, &id2, &password), + Err(BackendError::NoSuchIdInWallet), + )); + } + + #[test] + fn store_and_load_a_single_login() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = AccountId::new("first".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file, &id1, &password).unwrap(); + let acc = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(&dummy_account1, acc.mnemonic()); + assert_eq!(&cosmos_hd_path, acc.hd_path()); + } + + #[test] + fn store_a_second_login_with_a_different_password_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + let bad_password = UserPassword::new("bad-password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path.clone(), + id1, + &password, + ) + .unwrap(); + + // Can't store a second login if you use different password + assert!(matches!( + store_wallet_login_information_at_file( + wallet_file, + dummy_account2, + cosmos_hd_path, + id2, + &bad_password + ), + Err(BackendError::WalletDifferentPasswordDetected), + )); + } + + #[test] + fn store_two_mnemonic_accounts_gives_different_salts_and_iv() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let different_hd_path: DerivationPath = "m".parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + // Store the first account + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path, + id1, + &password, + ) + .unwrap(); + + let stored_wallet = load_existing_wallet_at_file(wallet_file.clone()).unwrap(); + let encrypted_blob = &stored_wallet + .get_encrypted_login_by_index(0) + .unwrap() + .account; + + // keep track of salt and iv for future assertion + let original_iv = encrypted_blob.iv().to_vec(); + let original_salt = encrypted_blob.salt().to_vec(); + + // Add an extra account + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2, + different_hd_path, + id2, + &password, + ) + .unwrap(); + + let loaded_accounts = load_existing_wallet_at_file(wallet_file).unwrap(); + assert_eq!(loaded_accounts.len(), 2); + let encrypted_blob = &loaded_accounts + .get_encrypted_login_by_index(1) + .unwrap() + .account; + + // fresh IV and salt are used + assert_ne!(original_iv, encrypted_blob.iv()); + assert_ne!(original_salt, encrypted_blob.salt()); + } + #[test] fn store_two_mnemonic_accounts_using_two_logins() { let store_dir = tempdir().unwrap(); @@ -302,21 +555,10 @@ mod tests { let different_hd_path: DerivationPath = "m".parse().unwrap(); let password = UserPassword::new("password".to_string()); - let bad_password = UserPassword::new("bad-password".to_string()); let id1 = AccountId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - // Nothing was stored on the disk - assert!(matches!( - load_existing_wallet_at_file(wallet_file.clone()), - Err(BackendError::WalletFileNotFound), - )); - assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password), - Err(BackendError::WalletFileNotFound), - )); - // Store the first account store_wallet_login_information_at_file( wallet_file.clone(), @@ -327,66 +569,13 @@ mod tests { ) .unwrap(); - let stored_wallet = load_existing_wallet_at_file(wallet_file.clone()).unwrap(); - assert_eq!(stored_wallet.len(), 1); - assert_eq!( - stored_wallet.get_encrypted_login_by_index(0).unwrap().id, - AccountId::new("first".to_string()) - ); - let encrypted_blob = &stored_wallet - .get_encrypted_login_by_index(0) - .unwrap() - .account; - - // some actual ciphertext was saved - assert!(!encrypted_blob.ciphertext().is_empty()); - - // keep track of salt and iv for future assertion - let original_iv = encrypted_blob.iv().to_vec(); - let original_salt = encrypted_blob.salt().to_vec(); - - // trying to load it with wrong password now fails - assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &bad_password), - Err(BackendError::DecryptionError), - )); - // and with the wrong id also fails - assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password), - Err(BackendError::NoSuchIdInWallet), - )); - - // and storing the same id again fails - assert!(matches!( - store_wallet_login_information_at_file( - wallet_file.clone(), - dummy_account1.clone(), - cosmos_hd_path.clone(), - id1.clone(), - &password, - ), - Err(BackendError::IdAlreadyExistsInWallet), - )); - let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); let acc = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account1, acc.mnemonic()); assert_eq!(&cosmos_hd_path, acc.hd_path()); - // Can't store extra account if you use different password - assert!(matches!( - store_wallet_login_information_at_file( - wallet_file.clone(), - dummy_account2.clone(), - cosmos_hd_path.clone(), - id2.clone(), - &bad_password - ), - Err(BackendError::WalletDifferentPasswordDetected), - )); - - // add extra account properly now + // Add an extra account store_wallet_login_information_at_file( wallet_file.clone(), dummy_account2.clone(), @@ -396,17 +585,6 @@ mod tests { ) .unwrap(); - let loaded_accounts = load_existing_wallet_at_file(wallet_file.clone()).unwrap(); - assert_eq!(2, loaded_accounts.len()); - let encrypted_blob = &loaded_accounts - .get_encrypted_login_by_index(1) - .unwrap() - .account; - - // fresh IV and salt are used - assert_ne!(original_iv, encrypted_blob.iv()); - assert_ne!(original_salt, encrypted_blob.salt()); - // first account should be unchanged let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); @@ -421,6 +599,35 @@ mod tests { assert_eq!(&different_hd_path, acc2.hd_path()); } + #[test] + fn remove_non_existent_id_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path, + id1, + &password, + ) + .unwrap(); + + // Fails to delete non-existent id in the wallet + assert!(matches!( + remove_wallet_login_information_at_file(wallet_file, &id2), + Err(BackendError::NoSuchIdInWallet), + )); + } + #[test] fn store_and_remove_wallet_login_information() { let store_dir = tempdir().unwrap(); @@ -467,13 +674,6 @@ mod tests { assert_eq!(&dummy_account2, acc2.mnemonic()); assert_eq!(&different_hd_path, acc2.hd_path()); - // Fails to delete non-existent id in the wallet - let id3 = AccountId::new("phony".to_string()); - assert!(matches!( - remove_wallet_login_information_at_file(wallet_file.clone(), &id3), - Err(BackendError::NoSuchIdInWallet), - )); - // Delete the second account remove_wallet_login_information_at_file(wallet_file.clone(), &id2).unwrap(); @@ -504,6 +704,58 @@ mod tests { )); } + #[test] + fn append_account_converts_the_type() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + // Check that it's there as the correct non-multiple type + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc1 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(acc1.mnemonic(), &dummy_account1); + assert_eq!(acc1.hd_path(), &cosmos_hd_path); + + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2.clone(), + cosmos_hd_path.clone(), + id1.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // Check that it is now multiple mnemonic type + let loaded_accounts = + load_existing_wallet_login_information_at_file(wallet_file, &id1, &password).unwrap(); + let accounts = loaded_accounts.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new_mnemonic_backed_account(id1, dummy_account1, cosmos_hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, cosmos_hd_path), + ] + .into(); + assert_eq!(accounts, &expected); + } + #[test] fn append_accounts_to_existing_login() { let store_dir = tempdir().unwrap(); @@ -547,7 +799,7 @@ mod tests { assert_eq!(acc2.mnemonic(), &dummy_account2); assert_eq!(acc2.hd_path(), &cosmos_hd_path); - // Add a third mnenonic grouped together with the second one + // Add a third and fourth mnenonic grouped together with the second one append_account_to_wallet_login_information_at_file( wallet_file.clone(), dummy_account3.clone(), @@ -557,8 +809,6 @@ mod tests { &password, ) .unwrap(); - - // Add a fourth mnenonic grouped together with the second one append_account_to_wallet_login_information_at_file( wallet_file.clone(), dummy_account4.clone(), @@ -569,7 +819,7 @@ mod tests { ) .unwrap(); - // Check that we can still load all four + // Check that we can load all four let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); @@ -589,7 +839,215 @@ mod tests { } #[test] - fn append_accounts_and_remove() { + fn delete_the_same_account_twice_for_a_login_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2, + cosmos_hd_path, + id1.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id2, &password).unwrap(); + + // Delete the same again fails + // WIP(JON) + //assert!(matches!( + remove_account_from_wallet_login_at_file(wallet_file, &id1, &id2, &password).unwrap(); + //Err(BackendError::NoSuchIdInWallet), + //)); + } + + #[test] + fn delete_appended_account_doesnt_affect_others() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + let id3 = AccountId::new("third".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2.clone(), + cosmos_hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account3.clone(), + cosmos_hd_path.clone(), + id2.clone(), + id3.clone(), + &password, + ) + .unwrap(); + + remove_wallet_login_information_at_file(wallet_file.clone(), &id1).unwrap(); + + // The second login one is still there + let loaded_accounts = + load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); + let accounts = loaded_accounts.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, cosmos_hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, cosmos_hd_path), + ] + .into(); + assert_eq!(accounts, &expected); + } + + #[test] + fn remove_all_accounts_for_a_login_removes_the_file_when_empty() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2, + cosmos_hd_path, + id1.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id1, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id2, &password).unwrap(); + + // The file should now be removed + assert!(!wallet_file.exists()); + + // And trying to load when the file is gone fails + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file, &id1, &password), + Err(BackendError::WalletFileNotFound), + )); + } + + #[test] + fn remove_all_accounts_for_a_login_removes_that_login() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + let id3 = AccountId::new("third".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2, + cosmos_hd_path.clone(), + id1.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account3.clone(), + cosmos_hd_path.clone(), + id3.clone(), + &password, + ) + .unwrap(); + + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id1, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id2, &password).unwrap(); + + // And trying to load when the file is gone fails + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password), + Err(BackendError::NoSuchIdInWallet), + )); + + // The other login is still there + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file, &id3, &password).unwrap(); + let acc3 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(acc3.mnemonic(), &dummy_account3); + assert_eq!(acc3.hd_path(), &cosmos_hd_path); + } + + #[test] + fn append_accounts_and_remove_appended_accounts() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); @@ -624,7 +1082,7 @@ mod tests { ) .unwrap(); - // Add a third mnenonic grouped together with the second one + // Add a third and fourth mnenonic grouped together with the second one append_account_to_wallet_login_information_at_file( wallet_file.clone(), dummy_account3, @@ -634,8 +1092,6 @@ mod tests { &password, ) .unwrap(); - - // Add a fourth mnenonic grouped together with the second one append_account_to_wallet_login_information_at_file( wallet_file.clone(), dummy_account4.clone(), @@ -649,13 +1105,7 @@ mod tests { // Delete the third mnemonic, from the second login entry remove_account_from_wallet_login_at_file(wallet_file.clone(), &id2, &id3, &password).unwrap(); - // Check that we can still load all three others - let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let acc1 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(acc1.mnemonic(), &dummy_account1); - assert_eq!(acc1.hd_path(), &cosmos_hd_path); - + // Check that we can still load the other accounts let loaded_accounts = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); @@ -665,32 +1115,29 @@ mod tests { dummy_account2, cosmos_hd_path.clone(), ), - WalletAccount::new_mnemonic_backed_account(id4, dummy_account4, cosmos_hd_path.clone()), + WalletAccount::new_mnemonic_backed_account( + id4.clone(), + dummy_account4, + cosmos_hd_path.clone(), + ), ] .into(); assert_eq!(accounts, &expected); - // Delete the second account, this will also delete the fourth mnemonic that is grouped with the - // second one. - remove_wallet_login_information_at_file(wallet_file.clone(), &id2).unwrap(); + // Delete the second and fourth mnemonic from the second login entry removes the login entry + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id2, &id2, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id2, &id4, &password).unwrap(); + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password), + Err(BackendError::NoSuchIdInWallet), + )); - // Check that we can still load the first + // The first login is still available let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + load_existing_wallet_login_information_at_file(wallet_file, &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(acc1.mnemonic(), &dummy_account1); assert_eq!(acc1.hd_path(), &cosmos_hd_path); - - // and the loading the deleted one fails - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password) - .unwrap_err(); - - // Delete the first account - assert!(wallet_file.exists()); - remove_wallet_login_information_at_file(wallet_file.clone(), &id1).unwrap(); - - // The file should now be removed - assert!(!wallet_file.exists()); } // Test to that decrypts a stored file from the repo, to make sure we are able to decrypt stored @@ -738,4 +1185,9 @@ mod tests { &cosmos_hd_path, ); } + + #[test] + fn decrypt_stored_wallet_with_multiple_accounts() { + // WIP(JON) + } } From 7adee63ebe79d84f3152a33ea58c8400257475f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 4 May 2022 10:01:34 +0200 Subject: [PATCH 35/85] wallet: add show_mnemonic_for_account_in_password --- nym-wallet/src-tauri/src/main.rs | 1 + .../src/operations/mixnet/account.rs | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index 0130ca496c..151a13c9a1 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -47,6 +47,7 @@ fn main() { mixnet::account::logout, mixnet::account::remove_account_for_password, mixnet::account::remove_password, + mixnet::account::show_mnemonic_for_account_in_password, mixnet::account::sign_in_decrypted_account, mixnet::account::sign_in_with_password, mixnet::account::switch_network, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index b630cfac91..e146983df7 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -526,6 +526,30 @@ pub async fn list_accounts( Ok(all_accounts) } +#[tauri::command] +pub fn show_mnemonic_for_account_in_password( + account_id: String, + password: String, +) -> Result { + log::info!("Getting mnemonic for: {account_id}"); + let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); + let account_id = wallet_storage::AccountId::new(account_id); + let password = wallet_storage::UserPassword::new(password); + let stored_account = wallet_storage::load_existing_wallet_login_information(&id, &password)?; + + let mnemonic = match stored_account { + StoredLogin::Mnemonic(_) => return Err(BackendError::WalletUnexpectedMnemonicAccount), + StoredLogin::Multiple(ref accounts) => accounts + .get_account(&account_id) + .ok_or(BackendError::NoSuchIdInWalletLoginEntry)? + .account + .mnemonic() + .clone(), + }; + + Ok(mnemonic.to_string()) +} + #[tauri::command] pub async fn sign_in_decrypted_account( account_id: &str, From b15cc094ea1e01af62d76b6ff66456b790647b90 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 4 May 2022 10:00:17 +0100 Subject: [PATCH 36/85] small refactors --- .../components/Accounts/AccountContainer.tsx | 4 ++++ .../src/components/Accounts/Accounts.tsx | 4 ++-- nym-wallet/src/context/main.tsx | 20 +++++++++---------- nym-wallet/src/index.tsx | 12 +++++------ .../pages/sign-in/pages/signin-mnemonic.tsx | 5 +++-- 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AccountContainer.tsx b/nym-wallet/src/components/Accounts/AccountContainer.tsx index 0a4f8b95e4..75c10316df 100644 --- a/nym-wallet/src/components/Accounts/AccountContainer.tsx +++ b/nym-wallet/src/components/Accounts/AccountContainer.tsx @@ -15,6 +15,10 @@ export const AccountsContainer = ({ storedAccounts }: { storedAccounts: AccountE if (selected) setSelectedAccount(selected); }, [accounts, storedAccounts]); + useEffect(() => { + setAccounts(storedAccounts); + }, [storedAccounts]); + const addAccount = async ({ accountName, mnemonic, diff --git a/nym-wallet/src/components/Accounts/Accounts.tsx b/nym-wallet/src/components/Accounts/Accounts.tsx index e0fd917b64..04d27f0dfb 100644 --- a/nym-wallet/src/components/Accounts/Accounts.tsx +++ b/nym-wallet/src/components/Accounts/Accounts.tsx @@ -22,7 +22,7 @@ export const Accounts = ({ setDialogToDisplay, }: { accounts?: AccountEntry[]; - selectedAccount: AccountEntry; + selectedAccount?: AccountEntry; accountToEdit?: AccountEntry; dialogToDisplay?: TDialog; addAccount: (acc: { accountName: string; mnemonic: string; password: string }) => Promise; @@ -32,7 +32,7 @@ export const Accounts = ({ setSelectedAccount: (accountName: string) => void; setDialogToDisplay: (dialog: TDialog | undefined) => void; }) => - accounts ? ( + accounts && selectedAccount ? ( <> - setDialogToDisplay(undefined)} - accounts={accounts} - onAccountSelect={(accountName) => setSelectedAccount(accountName)} - selectedAccount={selectedAccount.id} - onAdd={() => { - setDialogToDisplay('Add'); - }} - onEdit={(accountName) => { - setAccountToEdit(accountName); - setDialogToDisplay('Edit'); - }} - onImport={() => setDialogToDisplay('Import')} - /> - { - setDialogToDisplay('Accounts'); - }} - onAdd={async (data) => { - addAccount(data); - setDialogToDisplay('Accounts'); - }} - /> - { - setDialogToDisplay('Accounts'); - }} - onEdit={(account) => { - editAccount(account); - setDialogToDisplay('Accounts'); - }} - /> - setDialogToDisplay('Accounts')} - onImport={(mnemonic) => { - importAccount({ id: 'New Account', address: uuidv4(), mnemonic }); - setDialogToDisplay('Accounts'); - }} - /> + + + + ) : null; +}; diff --git a/nym-wallet/src/components/Accounts/AccountsModal.tsx b/nym-wallet/src/components/Accounts/AccountsModal.tsx index bc2de38243..8210b3c562 100644 --- a/nym-wallet/src/components/Accounts/AccountsModal.tsx +++ b/nym-wallet/src/components/Accounts/AccountsModal.tsx @@ -1,62 +1,48 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Typography } from '@mui/material'; import { Add, ArrowDownwardSharp, Close } from '@mui/icons-material'; -import { AccountEntry } from 'src/types'; +import { AccountsContext } from 'src/context'; import { AccountItem } from './AccountItem'; -export const AccountsModal = ({ - show, - accounts, - selectedAccount, - onClose, - onAccountSelect, - onAdd, - onEdit, - onImport, -}: { - show: boolean; - accounts: AccountEntry[]; - selectedAccount: AccountEntry['id']; - onClose: () => void; - onAccountSelect: (accountName: string) => void; - onAdd: () => void; - onEdit: (accoutnName: string) => void; - onImport: () => void; -}) => ( - - - - Accounts - - - - - - Switch between accounts - - - - {accounts.map(({ id, address }) => ( - { - onAccountSelect(id); - onClose(); - }} - onEdit={() => onEdit(id)} - isSelected={selectedAccount === id} - key={address} - /> - ))} - - - - - - -); +export const AccountsModal = ({ onClose }: { onClose?: () => void }) => { + const { accounts, dialogToDisplay, setDialogToDisplay } = useContext(AccountsContext); + + const handleClose = () => { + setDialogToDisplay(undefined); + onClose?.(); + }; + + return ( + + + + Accounts + + + + + + Switch between accounts + + + + {accounts?.map(({ id, address }) => ( + + ))} + + + + + + + ); +}; diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index 6a45c6213f..4745dd4590 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Alert, Box, @@ -15,6 +15,7 @@ import { import { Check, Close, ContentCopySharp } from '@mui/icons-material'; import { useClipboard } from 'use-clipboard-copy'; import { createMnemonic } from 'src/requests'; +import { AccountsContext } from 'src/context'; const createAccountSteps = ['Save and copy mnemonic for your new account', 'Name your new account', 'Confirm password']; @@ -135,42 +136,36 @@ const ConfirmPassword = ({ onConfirm }: { onConfirm: (password: string) => void ); }; -export const AddAccountModal = ({ - show, - withoutPassword, - onClose, - onAdd, -}: { - show: boolean; - withoutPassword?: boolean; - onClose: () => void; - onAdd: (data: { accountName: string; mnemonic: string; password: string }) => void; -}) => { +export const AddAccountModal = ({ withoutPassword }: { withoutPassword?: boolean }) => { const [step, setStep] = useState(0); const [data, setData] = useState<{ mnemonic?: string; accountName?: string }>({ mnemonic: undefined, accountName: undefined, }); + const { dialogToDisplay, setDialogToDisplay, handleAddAccount } = useContext(AccountsContext); + const generateMnemonic = async () => { const mnemon = await createMnemonic(); setData((d) => ({ ...d, mnemonic: mnemon })); }; + const handleClose = () => { + setDialogToDisplay('Accounts'); + setData({ mnemonic: undefined, accountName: undefined }); + setStep(0); + }; + useEffect(() => { - if (show) generateMnemonic(); - else { - setData({ mnemonic: undefined, accountName: undefined }); - setStep(0); - } - }, [show]); + if (dialogToDisplay === 'Accounts') generateMnemonic(); + }, [dialogToDisplay]); return ( - + Add new account - + @@ -181,7 +176,7 @@ export const AddAccountModal = ({ )} {createAccountSteps[step]} - {withoutPassword && } + {withoutPassword && } {!withoutPassword && data.mnemonic && (() => { @@ -202,7 +197,7 @@ export const AddAccountModal = ({ { if (data.accountName && data.mnemonic) { - onAdd({ accountName: data.accountName, mnemonic: data.mnemonic, password }); + handleAddAccount({ accountName: data.accountName, mnemonic: data.mnemonic, password }); } }} /> diff --git a/nym-wallet/src/components/Accounts/EditAccountModal.tsx b/nym-wallet/src/components/Accounts/EditAccountModal.tsx index 4ab69ea75f..c5fec1d28b 100644 --- a/nym-wallet/src/components/Accounts/EditAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/EditAccountModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Box, Button, @@ -11,31 +11,23 @@ import { Typography, } from '@mui/material'; import { Close } from '@mui/icons-material'; -import { AccountEntry } from 'src/types'; +import { AccountsContext } from 'src/context'; -export const EditAccountModal = ({ - account, - show, - onClose, - onEdit, -}: { - account?: AccountEntry; - show: boolean; - onClose: () => void; - onEdit: (account: AccountEntry) => void; -}) => { +export const EditAccountModal = () => { const [accountName, setAccountName] = useState(''); + const { accountToEdit, dialogToDisplay, setDialogToDisplay, handleEditAccount } = useContext(AccountsContext); + useEffect(() => { - setAccountName(account ? account?.id : ''); - }, [account]); + setAccountName(accountToEdit ? accountToEdit?.id : ''); + }, [accountToEdit]); return ( - + setDialogToDisplay('Accounts')} fullWidth hideBackdrop> Edit account name - + setDialogToDisplay('Accounts')}> @@ -60,7 +52,12 @@ export const EditAccountModal = ({ disableElevation variant="contained" size="large" - onClick={() => account && onEdit({ ...account, id: accountName })} + onClick={() => { + if (accountToEdit) { + handleEditAccount({ ...accountToEdit, id: accountName }); + setDialogToDisplay('Accounts'); + } + }} disabled={!accountName?.length} > Edit diff --git a/nym-wallet/src/components/Accounts/ImportAccountModal.tsx b/nym-wallet/src/components/Accounts/ImportAccountModal.tsx index 8222f330bf..2e61066f71 100644 --- a/nym-wallet/src/components/Accounts/ImportAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/ImportAccountModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useState } from 'react'; import { Box, Button, @@ -11,28 +11,24 @@ import { Typography, } from '@mui/material'; import { Close } from '@mui/icons-material'; +import { AccountsContext } from 'src/context'; -export const ImportAccountModal = ({ - show, - onClose, - onImport, -}: { - show: boolean; - onClose: () => void; - onImport: (mnemonic: string) => void; -}) => { +export const ImportAccountModal = () => { const [mnemonic, setMnemonic] = useState(''); - useEffect(() => { - if (!show) setMnemonic(''); - }, [show]); + const { dialogToDisplay, setDialogToDisplay, handleImportAccount } = useContext(AccountsContext); + + const handleClose = () => { + setMnemonic(''); + setDialogToDisplay('Accounts'); + }; return ( - + Import account - + @@ -59,7 +55,7 @@ export const ImportAccountModal = ({ disableElevation variant="contained" size="large" - onClick={() => onImport(mnemonic)} + onClick={() => handleImportAccount({ id: '', address: '' })} disabled={!mnemonic.length} > Import account diff --git a/nym-wallet/src/context/accounts.tsx b/nym-wallet/src/context/accounts.tsx index a97b3ae0a4..ef3bec09d5 100644 --- a/nym-wallet/src/context/accounts.tsx +++ b/nym-wallet/src/context/accounts.tsx @@ -1,5 +1,5 @@ import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; -import { Account, AccountEntry, TAccountsDialog } from 'src/types'; +import { AccountEntry } from 'src/types'; import { addAccount as addAccountRequest } from 'src/requests'; import { ClientContext } from './main'; @@ -16,6 +16,8 @@ type TAccounts = { handleImportAccount: (account: AccountEntry) => void; }; +export type TAccountsDialog = 'Accounts' | 'Add' | 'Edit' | 'Import'; + export const AccountsContext = createContext({} as TAccounts); export const AccountsProvider: React.FC = ({ children }) => { diff --git a/nym-wallet/src/context/index.tsx b/nym-wallet/src/context/index.tsx index fe68268d78..e6b2ab0a8e 100644 --- a/nym-wallet/src/context/index.tsx +++ b/nym-wallet/src/context/index.tsx @@ -1,2 +1,3 @@ export * from './main'; export * from './sign-in'; +export * from './accounts'; diff --git a/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx b/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx index 1b81179917..51e4377bb4 100644 --- a/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx +++ b/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx @@ -1,9 +1,9 @@ import React, { useContext, useState, useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { Box, Button, FormControl, Stack } from '@mui/material'; +import { ClientContext } from 'src/context'; import { isPasswordCreated } from 'src/requests'; import { MnemonicInput, Subtitle } from '../components'; -import { ClientContext } from 'src/context'; export const SignInMnemonic = () => { const [mnemonic, setMnemonic] = useState(''); From 7a0dff5f00f4ac76c925f752349874a7a934df3c Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 4 May 2022 16:22:00 +0100 Subject: [PATCH 43/85] update main state to respond to account changes --- nym-wallet/src/context/main.tsx | 42 +++++++++++++++++++++--------- nym-wallet/src/requests/account.ts | 21 ++++++++++++--- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index 2c1b8120a3..00fcd0df83 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -12,6 +12,7 @@ import { signInWithMnemonic, signInWithPassword, signOut, + switchAccount, } from '../requests'; import { currencyMap } from '../utils'; import { Console } from '../utils/console'; @@ -50,7 +51,7 @@ type TClientContext = { logIn: (opts: { type: 'mnemonic' | 'password'; value: string }) => void; signInWithPassword: (password: string) => void; logOut: () => void; - onAccountChange: (mnemonic: string) => void; + onAccountChange: (accountId: string) => void; }; export const ClientContext = createContext({} as TClientContext); @@ -92,6 +93,11 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode } }; + const loadStoredAccounts = async () => { + const accounts = await listAccounts(); + setStoredAccounts(accounts); + }; + const getBondDetails = async () => { setMixnodeDetails(undefined); try { @@ -111,11 +117,6 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode refreshAccount(); }, [network]); - const loadAccounts = async () => { - const accs = await listAccounts(); - setStoredAccounts(accs); - }; - useEffect(() => { if (!clientDetails) clearState(); }, [clientDetails]); @@ -131,7 +132,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode await signInWithMnemonic(value); } else { await signInWithPassword(value); - await loadAccounts(); + await loadStoredAccounts(); } setNetwork('MAINNET'); history.push('/balance'); @@ -148,11 +149,14 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode enqueueSnackbar('Successfully logged out', { variant: 'success' }); }; - const onAccountChange = async (value: string) => { - clearState(); - await signOut(); - await logIn({ type: 'mnemonic', value }); - enqueueSnackbar('Account switch success', { variant: 'success', preventDuplicate: true }); + const onAccountChange = async (accountId: string) => { + if (network) { + setIsLoading(true); + await switchAccount(accountId); + await loadAccount(network); + setIsLoading(false); + enqueueSnackbar('Account switch success', { variant: 'success', preventDuplicate: true }); + } }; const handleShowAdmin = () => setShowAdmin((show) => !show); @@ -183,7 +187,19 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode logOut, onAccountChange, }), - [mode, isLoading, error, clientDetails, mixnodeDetails, userBalance, showAdmin, showSettings, network, currency], + [ + mode, + isLoading, + error, + clientDetails, + mixnodeDetails, + userBalance, + showAdmin, + showSettings, + network, + currency, + storedAccounts, + ], ); return {children}; diff --git a/nym-wallet/src/requests/account.ts b/nym-wallet/src/requests/account.ts index 914fa74061..603bf9e1b2 100644 --- a/nym-wallet/src/requests/account.ts +++ b/nym-wallet/src/requests/account.ts @@ -13,13 +13,13 @@ export const signInWithMnemonic = async (mnemonic: string): Promise => return res; }; -export const validateMnemonic = async (mnemonic: string): Promise => { - const res: boolean = await invoke('validate_mnemonic', { mnemonic }); +export const signInWithPassword = async (password: string): Promise => { + const res: Account = await invoke('sign_in_with_password', { password }); return res; }; -export const signInWithPassword = async (password: string): Promise => { - const res: Account = await invoke('sign_in_with_password', { password }); +export const validateMnemonic = async (mnemonic: string): Promise => { + const res: boolean = await invoke('validate_mnemonic', { mnemonic }); return res; }; @@ -44,7 +44,20 @@ export const addAccount = async ({ await invoke('add_account_for_password', { mnemonic, password, innerId: accountName }); }; +export const removeAccount = async ({ password, accountName }: { password: string; accountName: string }) => { + await invoke('remove_account_for_password', { password, innerId: accountName }); +}; + export const listAccounts = async () => { const res: AccountEntry[] = await invoke('list_accounts'); return res; }; + +export const showMnemonicForAccount = async ({ password, accountName }: { password: string; accountName: string }) => { + const res: string = await invoke('show_mnemonic_for_account_in_password', { password, innerId: accountName }); + return res; +}; + +export const switchAccount = async (accountId: string) => { + await invoke('sign_in_decrypted_account', { accountId }); +}; From 92cbe651deb004fd45c5ff38a848d6806765f830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 4 May 2022 20:14:01 +0200 Subject: [PATCH 44/85] wallet: add account returns wallet entry --- .../src/operations/mixnet/account.rs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 70cda15f9d..f07adeb3d8 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -464,11 +464,12 @@ pub fn remove_password() -> Result<(), BackendError> { } #[tauri::command] -pub fn add_account_for_password( +pub async fn add_account_for_password( mnemonic: &str, password: &str, inner_id: &str, -) -> Result<(), BackendError> { + state: tauri::State<'_, Arc>>, +) -> Result { log::info!("Adding account for the current password: {inner_id}"); let mnemonic = Mnemonic::from_str(mnemonic)?; let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); @@ -476,9 +477,24 @@ pub fn add_account_for_password( let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); let inner_id = wallet_storage::AccountId::new(inner_id.to_string()); let password = wallet_storage::UserPassword::new(password.to_string()); + + // Creating the returned account entry could fail, so do it before attempting to store to wallet + let state = state.read().await; + let network: Network = state.current_network().into(); + let address = derive_address(mnemonic.clone(), network.bech32_prefix())?.to_string(); + wallet_storage::append_account_to_wallet_login_information( - mnemonic, hd_path, id, inner_id, &password, - ) + mnemonic, + hd_path, + id, + inner_id.clone(), + &password, + )?; + + Ok(AccountEntry { + id: inner_id.to_string(), + address, + }) } #[tauri::command] From 72c7049fca0c2faf76c7b9c9b52bd1ef745e117b Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 4 May 2022 20:45:33 +0100 Subject: [PATCH 45/85] set up loading page for account switch --- nym-wallet/src/components/AppBar.tsx | 5 +--- nym-wallet/src/components/LoadingPage.tsx | 2 +- nym-wallet/src/context/accounts.tsx | 31 +++++++++++++---------- nym-wallet/src/context/main.tsx | 1 + nym-wallet/src/index.tsx | 18 ++++++++----- 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/nym-wallet/src/components/AppBar.tsx b/nym-wallet/src/components/AppBar.tsx index 2cec6a9fbc..43a22955d5 100644 --- a/nym-wallet/src/components/AppBar.tsx +++ b/nym-wallet/src/components/AppBar.tsx @@ -1,7 +1,6 @@ import React, { useContext } from 'react'; import { AppBar as MuiAppBar, Grid, IconButton, Toolbar } from '@mui/material'; import { Logout } from '@mui/icons-material'; -import { AccountsProvider } from 'src/context/accounts'; import { ClientContext } from '../context/main'; import { NetworkSelector } from './NetworkSelector'; import { Node as NodeIcon } from '../svg-icons/node'; @@ -16,9 +15,7 @@ export const AppBar = () => { - - - + diff --git a/nym-wallet/src/components/LoadingPage.tsx b/nym-wallet/src/components/LoadingPage.tsx index 90782bd833..62e4f4be50 100644 --- a/nym-wallet/src/components/LoadingPage.tsx +++ b/nym-wallet/src/components/LoadingPage.tsx @@ -23,7 +23,7 @@ export const LoadingPage = () => ( }} > - + diff --git a/nym-wallet/src/context/accounts.tsx b/nym-wallet/src/context/accounts.tsx index ef3bec09d5..908428516b 100644 --- a/nym-wallet/src/context/accounts.tsx +++ b/nym-wallet/src/context/accounts.tsx @@ -37,11 +37,15 @@ export const AccountsProvider: React.FC = ({ children }) => { mnemonic: string; password: string; }) => { - await addAccountRequest({ - accountName, - mnemonic, - password, - }); + try { + await addAccountRequest({ + accountName, + mnemonic, + password, + }); + } catch (e) { + console.log('Error adding account'); + } }; const handleEditAccount = (account: AccountEntry) => setAccounts((accs) => accs?.map((acc) => (acc.address === account.address ? account : acc))); @@ -52,20 +56,21 @@ export const AccountsProvider: React.FC = ({ children }) => { setAccountToEdit(accounts?.find((acc) => acc.id === accountName)); const handleSelectAccount = async (accountName: string) => { - const match = accounts?.find((acc) => acc.id === accountName); - if (match) { - try { - await onAccountChange(match.id); - setSelectedAccount(match); - } catch (e) { - console.log('Error swtiching account'); - } + try { + await onAccountChange(accountName); + const match = accounts?.find((acc) => acc.id === accountName); + setSelectedAccount(match); + } catch (e) { + console.log('Error swtiching account'); } }; useEffect(() => { if (storedAccounts) { setAccounts(storedAccounts); + } + + if (storedAccounts && !selectedAccount) { setSelectedAccount(storedAccounts[0]); } }, [storedAccounts]); diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index 00fcd0df83..18faa9f4c8 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -112,6 +112,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode const refreshAccount = async () => { if (network) { await loadAccount(network); + await loadStoredAccounts(); } }; refreshAccount(); diff --git a/nym-wallet/src/index.tsx b/nym-wallet/src/index.tsx index 23cfd9b928..3be7eb6e86 100644 --- a/nym-wallet/src/index.tsx +++ b/nym-wallet/src/index.tsx @@ -10,7 +10,7 @@ import { Admin, Settings } from './pages'; import { ErrorFallback } from './components'; import { NymWalletTheme, WelcomeTheme } from './theme'; import { maximizeWindow } from './utils'; -import { SignInProvider } from './context'; +import { AccountsProvider, SignInProvider } from './context'; import { LoadingPage } from './components/LoadingPage'; const App = () => { @@ -26,11 +26,17 @@ const App = () => { ) : ( - - - - - + + {isLoading ? ( + + ) : ( + + + + + + )} + ); }; From ef4af0a1db8643d61f808e2933de1e30adaaf266 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 4 May 2022 20:49:19 +0100 Subject: [PATCH 46/85] display account errors --- nym-wallet/src/context/accounts.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nym-wallet/src/context/accounts.tsx b/nym-wallet/src/context/accounts.tsx index 908428516b..ffd5a10757 100644 --- a/nym-wallet/src/context/accounts.tsx +++ b/nym-wallet/src/context/accounts.tsx @@ -1,6 +1,7 @@ import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; import { AccountEntry } from 'src/types'; import { addAccount as addAccountRequest } from 'src/requests'; +import { useSnackbar } from 'notistack'; import { ClientContext } from './main'; type TAccounts = { @@ -27,6 +28,7 @@ export const AccountsProvider: React.FC = ({ children }) => { const [dialogToDisplay, setDialogToDisplay] = useState(); const { onAccountChange, storedAccounts } = useContext(ClientContext); + const { enqueueSnackbar } = useSnackbar(); const handleAddAccount = async ({ accountName, @@ -44,7 +46,7 @@ export const AccountsProvider: React.FC = ({ children }) => { password, }); } catch (e) { - console.log('Error adding account'); + enqueueSnackbar('Error adding account', { variant: 'error' }); } }; const handleEditAccount = (account: AccountEntry) => @@ -61,7 +63,7 @@ export const AccountsProvider: React.FC = ({ children }) => { const match = accounts?.find((acc) => acc.id === accountName); setSelectedAccount(match); } catch (e) { - console.log('Error swtiching account'); + enqueueSnackbar('Error swtiching account', { variant: 'error' }); } }; From a1961dbc2f86ff60561b021d6712cb90ec75cd12 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 4 May 2022 21:34:42 +0100 Subject: [PATCH 47/85] add accounts wip --- .../components/Accounts/AddAccountModal.tsx | 6 ++++- nym-wallet/src/context/accounts.tsx | 20 ++++++++++++----- nym-wallet/src/context/main.tsx | 13 +++++++---- nym-wallet/src/requests/account.ts | 22 +++++++++++-------- 4 files changed, 41 insertions(+), 20 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index 4745dd4590..4bac2e5660 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -3,6 +3,7 @@ import { Alert, Box, Button, + CircularProgress, Dialog, DialogActions, DialogContent, @@ -115,6 +116,8 @@ const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => { const ConfirmPassword = ({ onConfirm }: { onConfirm: (password: string) => void }) => { const [value, setValue] = useState(''); + const { isLoading } = useContext(AccountsContext); + return ( @@ -122,12 +125,13 @@ const ConfirmPassword = ({ onConfirm }: { onConfirm: (password: string) => void diff --git a/nym-wallet/src/context/accounts.tsx b/nym-wallet/src/context/accounts.tsx index ffd5a10757..a2699446e1 100644 --- a/nym-wallet/src/context/accounts.tsx +++ b/nym-wallet/src/context/accounts.tsx @@ -9,6 +9,7 @@ type TAccounts = { selectedAccount?: AccountEntry; accountToEdit?: AccountEntry; dialogToDisplay?: TAccountsDialog; + isLoading: boolean; handleAddAccount: (data: { accountName: string; mnemonic: string; password: string }) => void; setDialogToDisplay: (dialog?: TAccountsDialog) => void; handleSelectAccount: (accountId: string) => void; @@ -22,11 +23,11 @@ export type TAccountsDialog = 'Accounts' | 'Add' | 'Edit' | 'Import'; export const AccountsContext = createContext({} as TAccounts); export const AccountsProvider: React.FC = ({ children }) => { - const [accounts, setAccounts] = useState(); + const [accounts, setAccounts] = useState([]); const [selectedAccount, setSelectedAccount] = useState(); const [accountToEdit, setAccountToEdit] = useState(); const [dialogToDisplay, setDialogToDisplay] = useState(); - + const [isLoading, setIsLoading] = useState(false); const { onAccountChange, storedAccounts } = useContext(ClientContext); const { enqueueSnackbar } = useSnackbar(); @@ -39,14 +40,20 @@ export const AccountsProvider: React.FC = ({ children }) => { mnemonic: string; password: string; }) => { + setIsLoading(true); try { - await addAccountRequest({ + const newAccount = await addAccountRequest({ accountName, mnemonic, password, }); + setAccounts((accs) => [...accs, newAccount]); + enqueueSnackbar('New account created', { variant: 'success' }); + setDialogToDisplay('Accounts'); } catch (e) { - enqueueSnackbar('Error adding account', { variant: 'error' }); + enqueueSnackbar(`Error adding account: ${e}`, { variant: 'error' }); + } finally { + setIsLoading(false); } }; const handleEditAccount = (account: AccountEntry) => @@ -63,7 +70,7 @@ export const AccountsProvider: React.FC = ({ children }) => { const match = accounts?.find((acc) => acc.id === accountName); setSelectedAccount(match); } catch (e) { - enqueueSnackbar('Error swtiching account', { variant: 'error' }); + console.log('boom!'); } }; @@ -86,13 +93,14 @@ export const AccountsProvider: React.FC = ({ children }) => { accountToEdit, dialogToDisplay, setDialogToDisplay, + isLoading, handleAddAccount, handleEditAccount, handleAccountToEdit, handleSelectAccount, handleImportAccount, }), - [accounts, selectedAccount, accountToEdit, dialogToDisplay], + [accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading], )} > {children} diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index 18faa9f4c8..be20a1a0f1 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -153,10 +153,15 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode const onAccountChange = async (accountId: string) => { if (network) { setIsLoading(true); - await switchAccount(accountId); - await loadAccount(network); - setIsLoading(false); - enqueueSnackbar('Account switch success', { variant: 'success', preventDuplicate: true }); + try { + await switchAccount(accountId); + await loadAccount(network); + enqueueSnackbar('Account switch success', { variant: 'success', preventDuplicate: true }); + } catch (e) { + enqueueSnackbar(`Error swtiching account: ${e}`, { variant: 'error' }); + } finally { + setIsLoading(false); + } } }; diff --git a/nym-wallet/src/requests/account.ts b/nym-wallet/src/requests/account.ts index 603bf9e1b2..f40eb6465c 100644 --- a/nym-wallet/src/requests/account.ts +++ b/nym-wallet/src/requests/account.ts @@ -2,32 +2,35 @@ import { invoke } from '@tauri-apps/api'; import { AccountEntry } from 'src/types/rust/accountentry'; import { Account } from '../types'; -export const createMnemonic = async (): Promise => invoke('create_new_mnemonic'); +export const createMnemonic = async () => { + const res: string = await invoke('create_new_mnemonic'); + return res; +}; -export const createPassword = async ({ mnemonic, password }: { mnemonic: string; password: string }): Promise => { +export const createPassword = async ({ mnemonic, password }: { mnemonic: string; password: string }) => { await invoke('create_password', { mnemonic, password }); }; -export const signInWithMnemonic = async (mnemonic: string): Promise => { +export const signInWithMnemonic = async (mnemonic: string) => { const res: Account = await invoke('connect_with_mnemonic', { mnemonic }); return res; }; -export const signInWithPassword = async (password: string): Promise => { +export const signInWithPassword = async (password: string) => { const res: Account = await invoke('sign_in_with_password', { password }); return res; }; -export const validateMnemonic = async (mnemonic: string): Promise => { +export const validateMnemonic = async (mnemonic: string) => { const res: boolean = await invoke('validate_mnemonic', { mnemonic }); return res; }; -export const signOut = async (): Promise => { +export const signOut = async () => { await invoke('logout'); }; -export const isPasswordCreated = async (): Promise => { +export const isPasswordCreated = async () => { const res: boolean = await invoke('does_password_file_exist'); return res; }; @@ -40,8 +43,9 @@ export const addAccount = async ({ mnemonic: string; password: string; accountName: string; -}): Promise => { - await invoke('add_account_for_password', { mnemonic, password, innerId: accountName }); +}) => { + const res: AccountEntry = await invoke('add_account_for_password', { mnemonic, password, innerId: accountName }); + return res; }; export const removeAccount = async ({ password, accountName }: { password: string; accountName: string }) => { From ecb27e2cc2fd7a8b9a93242471cb171ae910340a Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 4 May 2022 22:36:09 +0100 Subject: [PATCH 48/85] import accounts wip --- .../src/components/Accounts/Accounts.tsx | 2 - .../components/Accounts/AddAccountModal.tsx | 70 ++++++++++++++++--- nym-wallet/src/context/accounts.tsx | 10 +-- nym-wallet/src/context/main.tsx | 27 ++++--- nym-wallet/src/pages/sign-in/types.ts | 2 - 5 files changed, 80 insertions(+), 31 deletions(-) diff --git a/nym-wallet/src/components/Accounts/Accounts.tsx b/nym-wallet/src/components/Accounts/Accounts.tsx index 2dd50102d4..9845ec1269 100644 --- a/nym-wallet/src/components/Accounts/Accounts.tsx +++ b/nym-wallet/src/components/Accounts/Accounts.tsx @@ -4,7 +4,6 @@ import { AccountsContext } from 'src/context'; import { EditAccountModal } from './EditAccountModal'; import { AddAccountModal } from './AddAccountModal'; import { AccountsModal } from './AccountsModal'; -import { ImportAccountModal } from './ImportAccountModal'; import { AccountAvatar } from './AccountAvatar'; export const Accounts = () => { @@ -23,7 +22,6 @@ export const Accounts = () => { - ) : null; }; diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index 4bac2e5660..b21327a7df 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -19,6 +19,11 @@ import { createMnemonic } from 'src/requests'; import { AccountsContext } from 'src/context'; const createAccountSteps = ['Save and copy mnemonic for your new account', 'Name your new account', 'Confirm password']; +const importAccountSteps = [ + 'Provide mnemonic of account you want to import', + 'Name your new account', + 'Confirm password', +]; const passwordCreationSteps = [ 'Log out', @@ -91,6 +96,36 @@ const MnemonicStep = ({ mnemonic, onNext }: { mnemonic: string; onNext: () => vo ); }; +const ImportMnemonic = ({ + value, + onChange, + onNext, +}: { + value: string; + onChange: (value: string) => void; + onNext: () => void; +}) => ( + + + + onChange(e.target.value)} fullWidth /> + + + + + + +); + const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => { const [value, setValue] = useState(''); return ( @@ -142,9 +177,9 @@ const ConfirmPassword = ({ onConfirm }: { onConfirm: (password: string) => void export const AddAccountModal = ({ withoutPassword }: { withoutPassword?: boolean }) => { const [step, setStep] = useState(0); - const [data, setData] = useState<{ mnemonic?: string; accountName?: string }>({ - mnemonic: undefined, - accountName: undefined, + const [data, setData] = useState({ + mnemonic: '', + accountName: '', }); const { dialogToDisplay, setDialogToDisplay, handleAddAccount } = useContext(AccountsContext); @@ -156,19 +191,25 @@ export const AddAccountModal = ({ withoutPassword }: { withoutPassword?: boolean const handleClose = () => { setDialogToDisplay('Accounts'); - setData({ mnemonic: undefined, accountName: undefined }); + setData({ mnemonic: '', accountName: '' }); setStep(0); }; useEffect(() => { - if (dialogToDisplay === 'Accounts') generateMnemonic(); + if (dialogToDisplay === 'Add') generateMnemonic(); + else setData({ mnemonic: '', accountName: '' }); }, [dialogToDisplay]); return ( - + - Add new account + {`${dialogToDisplay} new account`} @@ -178,15 +219,24 @@ export const AddAccountModal = ({ withoutPassword }: { withoutPassword?: boolean {`Step ${step + 1}/${createAccountSteps.length}`} )} - {createAccountSteps[step]} + + {dialogToDisplay === 'Add' ? createAccountSteps[step] : importAccountSteps[step]} + {withoutPassword && } {!withoutPassword && - data.mnemonic && (() => { switch (step) { case 0: - return setStep((s) => s + 1)} />; + return dialogToDisplay === 'Add' ? ( + setStep((s) => s + 1)} /> + ) : ( + setData((d) => ({ ...d, mnemonic: value }))} + onNext={() => setStep((s) => s + 1)} + /> + ); case 1: return ( { setAccountToEdit(accounts?.find((acc) => acc.id === accountName)); const handleSelectAccount = async (accountName: string) => { - try { - await onAccountChange(accountName); - const match = accounts?.find((acc) => acc.id === accountName); - setSelectedAccount(match); - } catch (e) { - console.log('boom!'); - } + await onAccountChange(accountName); + const match = accounts?.find((acc) => acc.id === accountName); + setSelectedAccount(match); }; useEffect(() => { diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index be20a1a0f1..2e3332ed96 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -1,7 +1,6 @@ import React, { useMemo, createContext, useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { useSnackbar } from 'notistack'; -import { TLoginType } from 'src/pages/sign-in/types'; import { Account, Network, TCurrency, TMixnodeBondDetails, AccountEntry } from '../types'; import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance'; import { config } from '../../config'; @@ -30,6 +29,8 @@ export const urls = (networkName?: Network) => networkExplorer: `https://${networkName}-explorer.nymtech.net`, }; +type TLoginType = 'mnemonic' | 'password'; + type TClientContext = { mode: 'light' | 'dark'; clientDetails?: Account; @@ -42,13 +43,14 @@ type TClientContext = { currency?: TCurrency; isLoading: boolean; error?: string; + loginType?: TLoginType; setIsLoading: (isLoading: boolean) => void; setError: (value?: string) => void; switchNetwork: (network: Network) => void; getBondDetails: () => Promise; handleShowSettings: () => void; handleShowAdmin: () => void; - logIn: (opts: { type: 'mnemonic' | 'password'; value: string }) => void; + logIn: (opts: { type: TLoginType; value: string }) => void; signInWithPassword: (password: string) => void; logOut: () => void; onAccountChange: (accountId: string) => void; @@ -65,6 +67,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode const [showAdmin, setShowAdmin] = useState(false); const [showSettings, setShowSettings] = useState(false); const [mode] = useState<'light' | 'dark'>('light'); + const [loginType, setLoginType] = useState<'mnemonic' | 'password'>(); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(); @@ -108,14 +111,15 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode } }; + const refreshAccount = async (_network: Network) => { + await loadAccount(_network); + if (loginType === 'password') { + await loadStoredAccounts(); + } + }; + useEffect(() => { - const refreshAccount = async () => { - if (network) { - await loadAccount(network); - await loadStoredAccounts(); - } - }; - refreshAccount(); + if (network) refreshAccount(network); }, [network]); useEffect(() => { @@ -131,9 +135,10 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode setIsLoading(true); if (type === 'mnemonic') { await signInWithMnemonic(value); + setLoginType('mnemonic'); } else { await signInWithPassword(value); - await loadStoredAccounts(); + setLoginType('password'); } setNetwork('MAINNET'); history.push('/balance'); @@ -182,6 +187,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode showSettings, network, currency, + loginType, setIsLoading, setError, signInWithPassword, @@ -194,6 +200,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode onAccountChange, }), [ + loginType, mode, isLoading, error, diff --git a/nym-wallet/src/pages/sign-in/types.ts b/nym-wallet/src/pages/sign-in/types.ts index d021361a10..2f5bc6ad8a 100644 --- a/nym-wallet/src/pages/sign-in/types.ts +++ b/nym-wallet/src/pages/sign-in/types.ts @@ -19,5 +19,3 @@ export type TMnemonicWords = TMnemonicWord[]; export type THiddenMnemonicWord = { hidden: boolean } & TMnemonicWord; export type THiddenMnemonicWords = THiddenMnemonicWord[]; - -export type TLoginType = 'mnemonic' | 'password'; From 0812378fdd99b3cba5c7092262a58100b6123a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 5 May 2022 12:13:31 +0200 Subject: [PATCH 49/85] wallet: reset account state when adding and removing accounts --- .../src/operations/mixnet/account.rs | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index f07adeb3d8..a9afeb388c 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -479,32 +479,60 @@ pub async fn add_account_for_password( let password = wallet_storage::UserPassword::new(password.to_string()); // Creating the returned account entry could fail, so do it before attempting to store to wallet - let state = state.read().await; - let network: Network = state.current_network().into(); - let address = derive_address(mnemonic.clone(), network.bech32_prefix())?.to_string(); + let address = { + let state = state.read().await; + let network: Network = state.current_network().into(); + derive_address(mnemonic.clone(), network.bech32_prefix())?.to_string() + }; wallet_storage::append_account_to_wallet_login_information( mnemonic, hd_path, - id, + id.clone(), inner_id.clone(), &password, )?; + // Re-read all the acccounts from the wallet to reset the state, rather than updating it + // incrementally + reset_state_with_all_accounts_from_file(&id, &password, state).await?; + Ok(AccountEntry { id: inner_id.to_string(), address, }) } +async fn reset_state_with_all_accounts_from_file( + id: &wallet_storage::AccountId, + password: &wallet_storage::UserPassword, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + let stored_account = wallet_storage::load_existing_wallet_login_information(id, password)?; + let all_accounts: Vec<_> = stored_account + .unwrap_into_multiple_accounts(id.clone()) + .into_accounts() + .collect(); + + let mut w_state = state.write().await; + w_state.set_all_accounts(all_accounts); + Ok(()) +} + #[tauri::command] -pub fn remove_account_for_password(password: &str, inner_id: &str) -> Result<(), BackendError> { +pub async fn remove_account_for_password( + password: &str, + inner_id: &str, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { log::info!("Removing account: {inner_id}"); // Currently we only support a single, default, id in the wallet let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); let inner_id = wallet_storage::AccountId::new(inner_id.to_string()); let password = wallet_storage::UserPassword::new(password.to_string()); - wallet_storage::remove_account_from_wallet_login(&id, &inner_id, &password) + wallet_storage::remove_account_from_wallet_login(&id, &inner_id, &password)?; + + reset_state_with_all_accounts_from_file(&id, &password, state).await } fn derive_address( From 6e9eab4edbd78a7eeb8e75630691d9aebacfd05a Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 5 May 2022 11:15:56 +0100 Subject: [PATCH 50/85] allow access to prev steps when creating account --- .../src/components/Accounts/AddAccountModal.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index b21327a7df..c6010357c3 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -126,7 +126,7 @@ const ImportMnemonic = ({ ); -const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => { +const NameAccount = ({ onPrev, onNext }: { onPrev: () => void; onNext: (value: string) => void }) => { const [value, setValue] = useState(''); return ( @@ -134,6 +134,9 @@ const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => { setValue(e.target.value)} fullWidth /> + ); }; diff --git a/nym-wallet/src/components/Accounts/stories/AddAccount.stories.tsx b/nym-wallet/src/components/Accounts/stories/AddAccount.stories.tsx index a1cb49cf40..af7e10ad8c 100644 --- a/nym-wallet/src/components/Accounts/stories/AddAccount.stories.tsx +++ b/nym-wallet/src/components/Accounts/stories/AddAccount.stories.tsx @@ -8,9 +8,9 @@ export default { component: AddAccountModal, } as ComponentMeta; -const Template: ComponentStory = (args) => ( +const Template: ComponentStory = () => ( - + ); diff --git a/nym-wallet/src/context/accounts.tsx b/nym-wallet/src/context/accounts.tsx index 88acd38f9d..e71b893048 100644 --- a/nym-wallet/src/context/accounts.tsx +++ b/nym-wallet/src/context/accounts.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; +import React, { createContext, Dispatch, SetStateAction, useContext, useEffect, useMemo, useState } from 'react'; import { AccountEntry } from 'src/types'; import { addAccount as addAccountRequest } from 'src/requests'; import { useSnackbar } from 'notistack'; @@ -10,6 +10,8 @@ type TAccounts = { accountToEdit?: AccountEntry; dialogToDisplay?: TAccountsDialog; isLoading: boolean; + error?: string; + setError: Dispatch>; handleAddAccount: (data: { accountName: string; mnemonic: string; password: string }) => void; setDialogToDisplay: (dialog?: TAccountsDialog) => void; handleSelectAccount: (accountId: string) => void; @@ -27,6 +29,7 @@ export const AccountsProvider: React.FC = ({ children }) => { const [selectedAccount, setSelectedAccount] = useState(); const [accountToEdit, setAccountToEdit] = useState(); const [dialogToDisplay, setDialogToDisplay] = useState(); + const [error, setError] = useState(); const [isLoading, setIsLoading] = useState(false); const { onAccountChange, storedAccounts } = useContext(ClientContext); const { enqueueSnackbar } = useSnackbar(); @@ -51,7 +54,7 @@ export const AccountsProvider: React.FC = ({ children }) => { enqueueSnackbar('New account created', { variant: 'success' }); setDialogToDisplay('Accounts'); } catch (e) { - enqueueSnackbar(`Error adding account: ${e}`, { variant: 'error' }); + setError(`Error adding account: ${e}`); } finally { setIsLoading(false); } @@ -84,6 +87,8 @@ export const AccountsProvider: React.FC = ({ children }) => { ({ + error, + setError, accounts, selectedAccount, accountToEdit, @@ -96,7 +101,7 @@ export const AccountsProvider: React.FC = ({ children }) => { handleSelectAccount, handleImportAccount, }), - [accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading], + [accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading, error], )} > {children} From 0e4787f0783555f256d3a34f0bf8940aac4d9f80 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 6 May 2022 15:54:38 +0100 Subject: [PATCH 54/85] minor refactors --- .../src/components/Accounts/AccountItem.tsx | 17 ++- .../src/components/Accounts/Accounts.tsx | 2 + .../components/Accounts/AddAccountModal.tsx | 58 +--------- .../src/components/Accounts/MnemonicModal.tsx | 102 ++++++++++++++++++ .../src/components/Accounts/ShowMnemonic.tsx | 29 ----- .../components/Accounts/ShowMnemonicModal.tsx | 47 -------- .../Accounts/stories/ShowMnemonic.stories.tsx | 23 ---- nym-wallet/src/components/Mnemonic.tsx | 37 +++++++ nym-wallet/src/context/accounts.tsx | 29 ++++- nym-wallet/src/requests/account.ts | 2 +- 10 files changed, 184 insertions(+), 162 deletions(-) create mode 100644 nym-wallet/src/components/Accounts/MnemonicModal.tsx delete mode 100644 nym-wallet/src/components/Accounts/ShowMnemonic.tsx delete mode 100644 nym-wallet/src/components/Accounts/ShowMnemonicModal.tsx delete mode 100644 nym-wallet/src/components/Accounts/stories/ShowMnemonic.stories.tsx create mode 100644 nym-wallet/src/components/Mnemonic.tsx diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index 940da46285..584fc37231 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -12,10 +12,10 @@ import { import { Edit } from '@mui/icons-material'; import { AccountsContext } from 'src/context'; import { AccountAvatar } from './AccountAvatar'; -import { ShowMnemonic } from './ShowMnemonic'; export const AccountItem = ({ name, address }: { name: string; address: string }) => { - const { selectedAccount, handleSelectAccount, handleAccountToEdit } = useContext(AccountsContext); + const { selectedAccount, handleSelectAccount, handleAccountToEdit, setDialogToDisplay, setAccountMnemonic } = + useContext(AccountsContext); return ( {address} - + ) => { + e.stopPropagation(); + setDialogToDisplay('Mnemonic'); + setAccountMnemonic((accountMnemonic) => ({ ...accountMnemonic, accountName: name })); + }} + > + Show mnemonic + } diff --git a/nym-wallet/src/components/Accounts/Accounts.tsx b/nym-wallet/src/components/Accounts/Accounts.tsx index 9845ec1269..77fd2eba9f 100644 --- a/nym-wallet/src/components/Accounts/Accounts.tsx +++ b/nym-wallet/src/components/Accounts/Accounts.tsx @@ -5,6 +5,7 @@ import { EditAccountModal } from './EditAccountModal'; import { AddAccountModal } from './AddAccountModal'; import { AccountsModal } from './AccountsModal'; import { AccountAvatar } from './AccountAvatar'; +import { MnemonicModal } from './MnemonicModal'; export const Accounts = () => { const { accounts, selectedAccount, setDialogToDisplay } = useContext(AccountsContext); @@ -22,6 +23,7 @@ export const Accounts = () => { + ) : null; }; diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index 3fae200f38..78a44b96f3 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -17,6 +17,7 @@ import { Check, Close, ContentCopySharp } from '@mui/icons-material'; import { useClipboard } from 'use-clipboard-copy'; import { createMnemonic } from 'src/requests'; import { AccountsContext } from 'src/context'; +import { Mnemonic } from '../Mnemonic'; const createAccountSteps = [ 'Copy and save mnemonic for your new account', @@ -29,67 +30,12 @@ const importAccountSteps = [ 'Confirm the password used to login to your wallet', ]; -const passwordCreationSteps = [ - 'Log out', - 'During sign in screen click “Sign in with mnemonic” button', - 'On next screen click “Create a password for your account”', - 'Sign in to wallet with your new password', - 'Now you can create multiple accounts', -]; - -const NoPassword = ({ onClose }: { onClose: () => void }) => ( - - - - - - You can’t add new accounts if your wallet doesn’t have a password. - - Follow steps below to create password. - - How to create password to your account - {passwordCreationSteps.map((step, i) => ( - {`${i + 1}. ${step}`} - ))} - - - - - - -); - const MnemonicStep = ({ mnemonic, onNext }: { mnemonic: string; onNext: () => void }) => { const { copy, copied } = useClipboard({ copiedTimeout: 5000 }); return ( - - - - Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the - future - - - - - - + + )} + + + ); +}; diff --git a/nym-wallet/src/components/Accounts/ShowMnemonic.tsx b/nym-wallet/src/components/Accounts/ShowMnemonic.tsx deleted file mode 100644 index 00cce65f44..0000000000 --- a/nym-wallet/src/components/Accounts/ShowMnemonic.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Box, Typography } from '@mui/material'; -import { CopyToClipboard } from '@nymproject/react'; - -export const ShowMnemonic = ({ accountName }: { accountName: string }) => { - const [showMnemonic, setShowMnemonic] = useState(); - const [mnemonic, setMnemonic] = useState(); - - return ( - - { - e.stopPropagation(); - setShowMnemonic((show) => (!show ? accountName : undefined)); - }} - > - {`${showMnemonic ? 'Hide' : 'Show'} mnemonic`} - - {mnemonic && ( - - {mnemonic} - - - )} - - ); -}; diff --git a/nym-wallet/src/components/Accounts/ShowMnemonicModal.tsx b/nym-wallet/src/components/Accounts/ShowMnemonicModal.tsx deleted file mode 100644 index 392bd4e841..0000000000 --- a/nym-wallet/src/components/Accounts/ShowMnemonicModal.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { - Alert, - AlertTitle, - Box, - Dialog, - DialogContent, - DialogTitle, - IconButton, - Stack, - Typography, -} from '@mui/material'; -import { Close } from '@mui/icons-material'; -import { CopyToClipboard } from '@nymproject/react'; - -export const ShowMnemonicModal = ({ - mnemonic, - show, - onClose, -}: { - mnemonic: string; - show: boolean; - onClose: () => void; -}) => ( - - - - Show mnemonic - - - - - - - - - DO NOT share this phrase with anyone! - These words can be used to steal all your accounts. - - } icon={false}> - Mnemonic - {mnemonic} - - - - -); diff --git a/nym-wallet/src/components/Accounts/stories/ShowMnemonic.stories.tsx b/nym-wallet/src/components/Accounts/stories/ShowMnemonic.stories.tsx deleted file mode 100644 index 0db1a2046a..0000000000 --- a/nym-wallet/src/components/Accounts/stories/ShowMnemonic.stories.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { Box } from '@mui/material'; -import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { ShowMnemonicModal } from 'src/components/Accounts/ShowMnemonicModal'; - -export default { - title: 'Wallet / Multi Account / Show Mnemonic', - component: ShowMnemonicModal, -} as ComponentMeta; - -const Template: ComponentStory = (args) => ( - - - -); - -export const Default = Template.bind({}); -Default.args = { - mnemonic: - 'lonely employ curtain skull gas swim pizza injury tail birth inmate apart giraffe behave caution hammer echo action best symptom skull toast beyond casino', - show: true, - onClose: () => {}, -}; diff --git a/nym-wallet/src/components/Mnemonic.tsx b/nym-wallet/src/components/Mnemonic.tsx new file mode 100644 index 0000000000..e49c2b8042 --- /dev/null +++ b/nym-wallet/src/components/Mnemonic.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Alert, Button, Stack, TextField, Typography } from '@mui/material'; +import { Check, ContentCopySharp } from '@mui/icons-material'; + +export const Mnemonic = ({ + mnemonic, + copied, + handleCopy, +}: { + mnemonic: string; + copied: boolean; + handleCopy: (text?: string) => void; +}) => ( + + + + Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future + + + + + + +); diff --git a/nym-wallet/src/context/accounts.tsx b/nym-wallet/src/context/accounts.tsx index e71b893048..d98a7d697f 100644 --- a/nym-wallet/src/context/accounts.tsx +++ b/nym-wallet/src/context/accounts.tsx @@ -1,6 +1,6 @@ import React, { createContext, Dispatch, SetStateAction, useContext, useEffect, useMemo, useState } from 'react'; import { AccountEntry } from 'src/types'; -import { addAccount as addAccountRequest } from 'src/requests'; +import { addAccount as addAccountRequest, showMnemonicForAccount } from 'src/requests'; import { useSnackbar } from 'notistack'; import { ClientContext } from './main'; @@ -11,16 +11,20 @@ type TAccounts = { dialogToDisplay?: TAccountsDialog; isLoading: boolean; error?: string; + accountMnemonic: TAccountMnemonic; setError: Dispatch>; + setAccountMnemonic: Dispatch>; handleAddAccount: (data: { accountName: string; mnemonic: string; password: string }) => void; setDialogToDisplay: (dialog?: TAccountsDialog) => void; handleSelectAccount: (accountId: string) => void; handleAccountToEdit: (accountId: string) => void; handleEditAccount: (account: AccountEntry) => void; handleImportAccount: (account: AccountEntry) => void; + handleGetAcccountMnemonic: (data: { password: string; accountName: string }) => void; }; -export type TAccountsDialog = 'Accounts' | 'Add' | 'Edit' | 'Import'; +export type TAccountsDialog = 'Accounts' | 'Add' | 'Edit' | 'Import' | 'Mnemonic'; +export type TAccountMnemonic = { value?: string; accountName?: string }; export const AccountsContext = createContext({} as TAccounts); @@ -29,6 +33,10 @@ export const AccountsProvider: React.FC = ({ children }) => { const [selectedAccount, setSelectedAccount] = useState(); const [accountToEdit, setAccountToEdit] = useState(); const [dialogToDisplay, setDialogToDisplay] = useState(); + const [accountMnemonic, setAccountMnemonic] = useState({ + value: undefined, + accountName: undefined, + }); const [error, setError] = useState(); const [isLoading, setIsLoading] = useState(false); const { onAccountChange, storedAccounts } = useContext(ClientContext); @@ -73,6 +81,18 @@ export const AccountsProvider: React.FC = ({ children }) => { setSelectedAccount(match); }; + const handleGetAcccountMnemonic = async ({ password, accountName }: { password: string; accountName: string }) => { + try { + setIsLoading(true); + const mnemonic = await showMnemonicForAccount({ password, accountName }); + setAccountMnemonic({ value: mnemonic, accountName }); + } catch (e) { + setError(e as string); + } finally { + setIsLoading(false); + } + }; + useEffect(() => { if (storedAccounts) { setAccounts(storedAccounts); @@ -93,15 +113,18 @@ export const AccountsProvider: React.FC = ({ children }) => { selectedAccount, accountToEdit, dialogToDisplay, + accountMnemonic, setDialogToDisplay, + setAccountMnemonic, isLoading, handleAddAccount, handleEditAccount, handleAccountToEdit, handleSelectAccount, handleImportAccount, + handleGetAcccountMnemonic, }), - [accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading, error], + [accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading, error, accountMnemonic], )} > {children} diff --git a/nym-wallet/src/requests/account.ts b/nym-wallet/src/requests/account.ts index f40eb6465c..bc85fe30ad 100644 --- a/nym-wallet/src/requests/account.ts +++ b/nym-wallet/src/requests/account.ts @@ -58,7 +58,7 @@ export const listAccounts = async () => { }; export const showMnemonicForAccount = async ({ password, accountName }: { password: string; accountName: string }) => { - const res: string = await invoke('show_mnemonic_for_account_in_password', { password, innerId: accountName }); + const res: string = await invoke('show_mnemonic_for_account_in_password', { password, accountId: accountName }); return res; }; From f2fa2214894fb1031d67a8fe887e9e0393e2db31 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Sat, 7 May 2022 17:53:46 +0100 Subject: [PATCH 55/85] big tidy and some refactoring of high level component hierachy --- .../src/components/Accounts/AccountItem.tsx | 17 ++++- .../components/Accounts/AddAccountModal.tsx | 33 +++------ .../src/components/Accounts/MnemonicModal.tsx | 4 +- nym-wallet/src/components/Accounts/index.tsx | 9 +++ nym-wallet/src/components/AppBar.tsx | 20 ++++-- nym-wallet/src/components/ClientAddress.tsx | 4 +- nym-wallet/src/components/Fee.tsx | 4 +- nym-wallet/src/components/LoadingPage.tsx | 45 ++++++------ nym-wallet/src/components/Nav.tsx | 4 +- nym-wallet/src/components/NetworkSelector.tsx | 4 +- .../src/components/TokenPoolSelector.tsx | 4 +- nym-wallet/src/components/index.ts | 1 + nym-wallet/src/context/accounts.tsx | 12 ++-- .../src/context/{sign-in.tsx => auth.tsx} | 19 ++---- nym-wallet/src/context/index.tsx | 2 +- nym-wallet/src/context/main.tsx | 19 +++--- nym-wallet/src/hooks/useCheckOwnership.ts | 4 +- nym-wallet/src/hooks/useGetBalance.ts | 6 +- nym-wallet/src/index.tsx | 68 ++++++------------- nym-wallet/src/layouts/AppLayout.tsx | 64 +++++++++-------- nym-wallet/src/layouts/AuthLayout.tsx | 41 +++++++++++ nym-wallet/src/pages/Admin/index.tsx | 4 +- .../{sign-in => auth}/components/error.tsx | 0 .../{sign-in => auth}/components/heading.tsx | 0 .../{sign-in => auth}/components/index.ts | 2 - .../components/password-strength.tsx | 0 .../{sign-in => auth}/components/step.tsx | 0 .../components/textfields.tsx | 0 .../components/word-tiles.tsx | 0 nym-wallet/src/pages/auth/index.tsx | 9 +++ .../pages/confirm-mnemonic.tsx | 4 +- .../pages/connect-password.tsx | 4 +- .../pages/create-mnemonic.tsx | 4 +- .../pages/create-password.tsx | 5 +- .../pages/existing-account.tsx | 2 +- .../pages/{sign-in => auth}/pages/index.ts | 0 .../pages/signin-mnemonic.tsx | 4 +- .../pages/signin-password.tsx | 4 +- .../pages/verify-mnemonic.tsx | 4 +- .../pages/{sign-in => auth}/pages/welcome.tsx | 0 .../src/pages/{sign-in => auth}/types.ts | 0 nym-wallet/src/pages/balance/balance.tsx | 4 +- .../balance/components/vesting-timeline.tsx | 4 +- nym-wallet/src/pages/balance/index.tsx | 4 +- nym-wallet/src/pages/balance/vesting.tsx | 8 +-- nym-wallet/src/pages/bond/BondForm.tsx | 4 +- nym-wallet/src/pages/bond/SuccessView.tsx | 4 +- .../src/pages/delegate/DelegateForm.tsx | 4 +- nym-wallet/src/pages/delegate/SuccessView.tsx | 4 +- nym-wallet/src/pages/delegate/index.tsx | 4 +- nym-wallet/src/pages/index.ts | 2 +- nym-wallet/src/pages/internal-docs/index.tsx | 4 +- nym-wallet/src/pages/receive/index.tsx | 4 +- .../src/pages/send/SendConfirmation.tsx | 4 +- nym-wallet/src/pages/send/SendForm.tsx | 4 +- nym-wallet/src/pages/send/SendReview.tsx | 4 +- nym-wallet/src/pages/send/SendWizard.tsx | 4 +- nym-wallet/src/pages/send/index.tsx | 4 +- nym-wallet/src/pages/settings/index.tsx | 4 +- nym-wallet/src/pages/settings/node-stats.tsx | 4 +- nym-wallet/src/pages/settings/profile.tsx | 4 +- .../src/pages/settings/system-variables.tsx | 4 +- .../src/pages/settings/useSettingsState.ts | 4 +- .../pages/sign-in/components/page-layout.tsx | 33 --------- .../pages/sign-in/components/render-page.tsx | 11 --- nym-wallet/src/pages/sign-in/index.tsx | 1 - nym-wallet/src/pages/unbond/index.tsx | 4 +- nym-wallet/src/pages/undelegate/index.tsx | 4 +- nym-wallet/src/routes/app.tsx | 57 ++++++++-------- nym-wallet/src/routes/auth.tsx | 54 +++++++++++++++ nym-wallet/src/routes/index.tsx | 12 +++- nym-wallet/src/routes/sign-in.tsx | 48 ------------- nym-wallet/src/theme/index.tsx | 6 +- 73 files changed, 387 insertions(+), 363 deletions(-) create mode 100644 nym-wallet/src/components/Accounts/index.tsx rename nym-wallet/src/context/{sign-in.tsx => auth.tsx} (79%) create mode 100644 nym-wallet/src/layouts/AuthLayout.tsx rename nym-wallet/src/pages/{sign-in => auth}/components/error.tsx (100%) rename nym-wallet/src/pages/{sign-in => auth}/components/heading.tsx (100%) rename nym-wallet/src/pages/{sign-in => auth}/components/index.ts (73%) rename nym-wallet/src/pages/{sign-in => auth}/components/password-strength.tsx (100%) rename nym-wallet/src/pages/{sign-in => auth}/components/step.tsx (100%) rename nym-wallet/src/pages/{sign-in => auth}/components/textfields.tsx (100%) rename nym-wallet/src/pages/{sign-in => auth}/components/word-tiles.tsx (100%) create mode 100644 nym-wallet/src/pages/auth/index.tsx rename nym-wallet/src/pages/{sign-in => auth}/pages/confirm-mnemonic.tsx (96%) rename nym-wallet/src/pages/{sign-in => auth}/pages/connect-password.tsx (97%) rename nym-wallet/src/pages/{sign-in => auth}/pages/create-mnemonic.tsx (96%) rename nym-wallet/src/pages/{sign-in => auth}/pages/create-password.tsx (93%) rename nym-wallet/src/pages/{sign-in => auth}/pages/existing-account.tsx (99%) rename nym-wallet/src/pages/{sign-in => auth}/pages/index.ts (100%) rename nym-wallet/src/pages/{sign-in => auth}/pages/signin-mnemonic.tsx (94%) rename nym-wallet/src/pages/{sign-in => auth}/pages/signin-password.tsx (93%) rename nym-wallet/src/pages/{sign-in => auth}/pages/verify-mnemonic.tsx (96%) rename nym-wallet/src/pages/{sign-in => auth}/pages/welcome.tsx (100%) rename nym-wallet/src/pages/{sign-in => auth}/types.ts (100%) delete mode 100644 nym-wallet/src/pages/sign-in/components/page-layout.tsx delete mode 100644 nym-wallet/src/pages/sign-in/components/render-page.tsx delete mode 100644 nym-wallet/src/pages/sign-in/index.tsx create mode 100644 nym-wallet/src/routes/auth.tsx delete mode 100644 nym-wallet/src/routes/sign-in.tsx diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index 584fc37231..99cf45f121 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -7,15 +7,18 @@ import { ListItemButton, ListItemIcon, ListItemText, + Tooltip, Typography, } from '@mui/material'; import { Edit } from '@mui/icons-material'; +import { useClipboard } from 'use-clipboard-copy'; import { AccountsContext } from 'src/context'; import { AccountAvatar } from './AccountAvatar'; export const AccountItem = ({ name, address }: { name: string; address: string }) => { const { selectedAccount, handleSelectAccount, handleAccountToEdit, setDialogToDisplay, setAccountMnemonic } = useContext(AccountsContext); + const { copy, copied } = useClipboard({ copiedTimeout: 1000 }); return ( - {address} + + ) => { + e.stopPropagation(); + copy(address); + }} + sx={{ '&:hover': { color: 'grey.900' } }} + > + {address} + + ); -const NameAccount = ({ onPrev, onNext }: { onPrev: () => void; onNext: (value: string) => void }) => { +const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => { const [value, setValue] = useState(''); return ( @@ -91,9 +90,6 @@ const NameAccount = ({ onPrev, onNext }: { onPrev: () => void; onNext: (value: s setValue(e.target.value)} fullWidth /> - - +); diff --git a/nym-wallet/src/components/Accounts/Accounts.tsx b/nym-wallet/src/components/Accounts/Accounts.tsx index 77fd2eba9f..a59b59c95f 100644 --- a/nym-wallet/src/components/Accounts/Accounts.tsx +++ b/nym-wallet/src/components/Accounts/Accounts.tsx @@ -1,25 +1,18 @@ -import React, { useContext } from 'react'; -import { Button } from '@mui/material'; -import { AccountsContext } from 'src/context'; +import React, { useContext, useState } from 'react'; +import { AccountsContext, AppContext } from 'src/context'; import { EditAccountModal } from './EditAccountModal'; import { AddAccountModal } from './AddAccountModal'; import { AccountsModal } from './AccountsModal'; -import { AccountAvatar } from './AccountAvatar'; import { MnemonicModal } from './MnemonicModal'; +import { AccountOverview } from './AccountOverview'; +import { MultiAccountHowTo } from './MultiAccountHowTo'; export const Accounts = () => { const { accounts, selectedAccount, setDialogToDisplay } = useContext(AccountsContext); return accounts && selectedAccount ? ( <> - + setDialogToDisplay('Accounts')} /> @@ -27,3 +20,17 @@ export const Accounts = () => { ) : null; }; + +export const SingleAccount = () => { + const [showHowToDialog, setShowHowToDialog] = useState(false); + const { clientDetails } = useContext(AppContext); + return ( + <> + setShowHowToDialog(true)} + /> + setShowHowToDialog(false)} /> + + ); +}; diff --git a/nym-wallet/src/components/Accounts/MultiAccountHowTo.tsx b/nym-wallet/src/components/Accounts/MultiAccountHowTo.tsx new file mode 100644 index 0000000000..4f9f514c47 --- /dev/null +++ b/nym-wallet/src/components/Accounts/MultiAccountHowTo.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Alert, Box, Dialog, DialogContent, DialogTitle, IconButton, Stack, Typography } from '@mui/material'; +import { Close } from '@mui/icons-material'; + +const passwordCreationSteps = [ + 'Log out', + 'When signing in, select “Sign in with mnemonic”', + 'On the next screen click “Create a password for your account”', + 'Sign in to wallet with your new password', + 'Now you can create multiple accounts', +]; + +export const MultiAccountHowTo = ({ show, handleClose }: { show: boolean; handleClose: () => void }) => ( + + + + Multi accounts + + + + + + How to set up multiple accounts + + + + + + In order to create multiple accounts your wallet need password. + Follow steps below to create password. + + How to create a password for your account + {passwordCreationSteps.map((step, index) => ( + {`${index + 1}. ${step}`} + ))} + + + +); diff --git a/nym-wallet/src/components/Accounts/index.tsx b/nym-wallet/src/components/Accounts/index.tsx index 94e4348091..9e02c40d2b 100644 --- a/nym-wallet/src/components/Accounts/index.tsx +++ b/nym-wallet/src/components/Accounts/index.tsx @@ -1,9 +1,16 @@ -import React from 'react'; -import { AccountsProvider } from 'src/context'; -import { Accounts } from './Accounts'; +import React, { useContext } from 'react'; +import { AccountsProvider, AppContext } from 'src/context'; +import { Accounts, SingleAccount } from './Accounts'; -export const MultiAccounts = () => ( - - - -); +export const MultiAccounts = () => { + const { loginType } = useContext(AppContext); + + if (loginType === 'password') { + return ( + + + + ); + } + return ; +}; From b6b757436e160a13ce9741284b62664e43495cbb Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Mon, 9 May 2022 13:36:38 +0100 Subject: [PATCH 58/85] remove edit button until feature delivered --- nym-wallet/src/components/Accounts/AccountItem.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index 99cf45f121..d23d1d37cb 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -63,7 +63,8 @@ export const AccountItem = ({ name, address }: { name: string; address: string } } /> - + {/* edit and remove accounts todo */} + {/* { e.stopPropagation(); @@ -72,7 +73,7 @@ export const AccountItem = ({ name, address }: { name: string; address: string } > - + */} ); From 575845af38a1589bf5a69f68ea036d64fe3b9b84 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Mon, 9 May 2022 13:38:38 +0100 Subject: [PATCH 59/85] fix lint errors --- .../src/components/Accounts/AccountItem.tsx | 16 ++-------------- nym-wallet/src/context/mocks/accounts.tsx | 12 ++---------- nym-wallet/src/hooks/useGetBalance.ts | 2 +- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index d23d1d37cb..8344d355a4 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -1,23 +1,11 @@ import React, { useContext } from 'react'; -import { - Box, - IconButton, - ListItem, - ListItemAvatar, - ListItemButton, - ListItemIcon, - ListItemText, - Tooltip, - Typography, -} from '@mui/material'; -import { Edit } from '@mui/icons-material'; +import { Box, ListItem, ListItemAvatar, ListItemButton, ListItemText, Tooltip, Typography } from '@mui/material'; import { useClipboard } from 'use-clipboard-copy'; import { AccountsContext } from 'src/context'; import { AccountAvatar } from './AccountAvatar'; export const AccountItem = ({ name, address }: { name: string; address: string }) => { - const { selectedAccount, handleSelectAccount, handleAccountToEdit, setDialogToDisplay, setAccountMnemonic } = - useContext(AccountsContext); + const { selectedAccount, handleSelectAccount, setDialogToDisplay, setAccountMnemonic } = useContext(AccountsContext); const { copy, copied } = useClipboard({ copiedTimeout: 1000 }); return ( { const [error, setError] = useState(); const [isLoading, setIsLoading] = useState(false); - const handleAddAccount = async ({ - accountName, - mnemonic, - password, - }: { - accountName: string; - mnemonic: string; - password: string; - }) => { + const handleAddAccount = async ({ accountName }: { accountName: string; mnemonic: string; password: string }) => { setIsLoading(true); try { setAccounts((accs) => [...accs, { address: 'abc123', id: accountName }]); @@ -51,7 +43,7 @@ export const MockAccountsProvider: React.FC = ({ children }) => { } }; - const handleGetAcccountMnemonic = async ({ password, accountName }: { password: string; accountName: string }) => { + const handleGetAcccountMnemonic = async ({ accountName }: { password: string; accountName: string }) => { try { setIsLoading(true); const mnemonic = 'test mnemonic'; diff --git a/nym-wallet/src/hooks/useGetBalance.ts b/nym-wallet/src/hooks/useGetBalance.ts index a6d1c8321d..9056f1d4d2 100644 --- a/nym-wallet/src/hooks/useGetBalance.ts +++ b/nym-wallet/src/hooks/useGetBalance.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from 'react'; import { invoke } from '@tauri-apps/api'; import { VestingAccountInfo } from 'src/types/rust/vestingaccountinfo'; -import { Balance, Coin, Network, OriginalVestingResponse, Period } from '../types'; +import { Balance, Coin, OriginalVestingResponse, Period } from '../types'; import { getVestingCoins, getVestedCoins, From beeb67e9c2174f27b1068fba5563424d66acc511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 10 May 2022 10:11:33 +0200 Subject: [PATCH 60/85] wallet: change default name of first account --- .../src/wallet_storage/account_data.rs | 2 +- .../src-tauri/src/wallet_storage/mod.rs | 131 +++++++++--------- 2 files changed, 70 insertions(+), 63 deletions(-) diff --git a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs index 2eb875913d..bc21ceae84 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -269,7 +269,7 @@ impl MultipleAccounts { pub(crate) fn remove(&mut self, id: &AccountId) -> Result<(), BackendError> { if self.get_account(id).is_none() { - return Err(BackendError::NoSuchIdInWalletLoginEntry); + return Err(BackendError::NoSuchIdInWalletLoginEntry); } self.accounts.retain(|accounts| &accounts.id != id); Ok(()) diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 0de4b0bc06..c6e8d4173b 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -20,6 +20,10 @@ mod password; pub(crate) const DEFAULT_WALLET_ACCOUNT_ID: &str = "default"; +/// When converting a single account entry to one that contains many, the first account will use +/// this name. +const DEFAULT_NAME_FIRST_ACCOUNT: &str = "Account 1"; + fn get_storage_directory() -> Result { tauri::api::path::local_data_dir() .map(|dir| dir.join(STORAGE_DIR_NAME)) @@ -154,7 +158,8 @@ fn append_account_to_wallet_login_information_at_file( let decrypted_login = stored_wallet.decrypt_login(&id, password)?; // Add accounts to the inner structure - let mut accounts = decrypted_login.unwrap_into_multiple_accounts(id.clone()); + let mut accounts = decrypted_login + .unwrap_into_multiple_accounts(AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string())); accounts.add(inner_id, mnemonic, hd_path)?; let encrypted_accounts = EncryptedLogin { @@ -711,17 +716,18 @@ mod tests { let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = AccountId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); + let default_id = AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string()); store_wallet_login_information_at_file( wallet_file.clone(), dummy_account1.clone(), - cosmos_hd_path.clone(), + hd_path.clone(), id1.clone(), &password, ) @@ -732,12 +738,12 @@ mod tests { load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(acc1.mnemonic(), &dummy_account1); - assert_eq!(acc1.hd_path(), &cosmos_hd_path); + assert_eq!(acc1.hd_path(), &hd_path); append_account_to_wallet_login_information_at_file( wallet_file.clone(), dummy_account2.clone(), - cosmos_hd_path.clone(), + hd_path.clone(), id1.clone(), id2.clone(), &password, @@ -749,8 +755,8 @@ mod tests { load_existing_wallet_login_information_at_file(wallet_file, &id1, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account(id1, dummy_account1, cosmos_hd_path.clone()), - WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, cosmos_hd_path), + WalletAccount::new_mnemonic_backed_account(default_id, dummy_account1, hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, hd_path), ] .into(); assert_eq!(accounts, &expected); @@ -765,7 +771,7 @@ mod tests { let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); let dummy_account4 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -773,11 +779,12 @@ mod tests { let id2 = AccountId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); let id4 = AccountId::new("fourth".to_string()); + let default_id = AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string()); store_wallet_login_information_at_file( wallet_file.clone(), dummy_account1.clone(), - cosmos_hd_path.clone(), + hd_path.clone(), id1.clone(), &password, ) @@ -786,7 +793,7 @@ mod tests { store_wallet_login_information_at_file( wallet_file.clone(), dummy_account2.clone(), - cosmos_hd_path.clone(), + hd_path.clone(), id2.clone(), &password, ) @@ -797,13 +804,13 @@ mod tests { load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); let acc2 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(acc2.mnemonic(), &dummy_account2); - assert_eq!(acc2.hd_path(), &cosmos_hd_path); + assert_eq!(acc2.hd_path(), &hd_path); // Add a third and fourth mnenonic grouped together with the second one append_account_to_wallet_login_information_at_file( wallet_file.clone(), dummy_account3.clone(), - cosmos_hd_path.clone(), + hd_path.clone(), id2.clone(), id3.clone(), &password, @@ -812,7 +819,7 @@ mod tests { append_account_to_wallet_login_information_at_file( wallet_file.clone(), dummy_account4.clone(), - cosmos_hd_path.clone(), + hd_path.clone(), id2.clone(), id4.clone(), &password, @@ -824,15 +831,15 @@ mod tests { load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(acc1.mnemonic(), &dummy_account1); - assert_eq!(acc1.hd_path(), &cosmos_hd_path); + assert_eq!(acc1.hd_path(), &hd_path); let loaded_accounts = load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, cosmos_hd_path.clone()), - WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, cosmos_hd_path.clone()), - WalletAccount::new_mnemonic_backed_account(id4, dummy_account4, cosmos_hd_path), + WalletAccount::new_mnemonic_backed_account(default_id, dummy_account2, hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id4, dummy_account4, hd_path), ] .into(); assert_eq!(accounts, &expected); @@ -887,18 +894,19 @@ mod tests { let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = AccountId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); + let default_id = AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string()); store_wallet_login_information_at_file( wallet_file.clone(), dummy_account1, - cosmos_hd_path.clone(), + hd_path.clone(), id1.clone(), &password, ) @@ -907,7 +915,7 @@ mod tests { store_wallet_login_information_at_file( wallet_file.clone(), dummy_account2.clone(), - cosmos_hd_path.clone(), + hd_path.clone(), id2.clone(), &password, ) @@ -916,7 +924,7 @@ mod tests { append_account_to_wallet_login_information_at_file( wallet_file.clone(), dummy_account3.clone(), - cosmos_hd_path.clone(), + hd_path.clone(), id2.clone(), id3.clone(), &password, @@ -930,8 +938,8 @@ mod tests { load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, cosmos_hd_path.clone()), - WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, cosmos_hd_path), + WalletAccount::new_mnemonic_backed_account(default_id, dummy_account2, hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, hd_path), ] .into(); assert_eq!(accounts, &expected); @@ -940,7 +948,7 @@ mod tests { #[test] fn remove_all_accounts_for_a_login_removes_the_file_when_empty() { let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let wallet = store_dir.path().join(WALLET_INFO_FILENAME); let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); @@ -950,9 +958,10 @@ mod tests { let id1 = AccountId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); + let default_id = AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string()); store_wallet_login_information_at_file( - wallet_file.clone(), + wallet.clone(), dummy_account1, cosmos_hd_path.clone(), id1.clone(), @@ -961,7 +970,7 @@ mod tests { .unwrap(); append_account_to_wallet_login_information_at_file( - wallet_file.clone(), + wallet.clone(), dummy_account2, cosmos_hd_path, id1.clone(), @@ -970,15 +979,15 @@ mod tests { ) .unwrap(); - remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id1, &password).unwrap(); - remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id2, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &default_id, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &id2, &password).unwrap(); // The file should now be removed - assert!(!wallet_file.exists()); + assert!(!wallet.exists()); // And trying to load when the file is gone fails assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file, &id1, &password), + load_existing_wallet_login_information_at_file(wallet, &id1, &password), Err(BackendError::WalletFileNotFound), )); } @@ -986,7 +995,7 @@ mod tests { #[test] fn remove_all_accounts_for_a_login_removes_that_login() { let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let wallet = store_dir.path().join(WALLET_INFO_FILENAME); let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); @@ -998,9 +1007,10 @@ mod tests { let id1 = AccountId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); + let default_id = AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string()); store_wallet_login_information_at_file( - wallet_file.clone(), + wallet.clone(), dummy_account1, cosmos_hd_path.clone(), id1.clone(), @@ -1009,7 +1019,7 @@ mod tests { .unwrap(); append_account_to_wallet_login_information_at_file( - wallet_file.clone(), + wallet.clone(), dummy_account2, cosmos_hd_path.clone(), id1.clone(), @@ -1019,7 +1029,7 @@ mod tests { .unwrap(); store_wallet_login_information_at_file( - wallet_file.clone(), + wallet.clone(), dummy_account3.clone(), cosmos_hd_path.clone(), id3.clone(), @@ -1027,18 +1037,18 @@ mod tests { ) .unwrap(); - remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id1, &password).unwrap(); - remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id2, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &default_id, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &id2, &password).unwrap(); // And trying to load when the file is gone fails assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password), + load_existing_wallet_login_information_at_file(wallet.clone(), &id1, &password), Err(BackendError::NoSuchIdInWallet), )); // The other login is still there let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file, &id3, &password).unwrap(); + load_existing_wallet_login_information_at_file(wallet, &id3, &password).unwrap(); let acc3 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(acc3.mnemonic(), &dummy_account3); assert_eq!(acc3.hd_path(), &cosmos_hd_path); @@ -1047,13 +1057,13 @@ mod tests { #[test] fn append_accounts_and_remove_appended_accounts() { let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let wallet = store_dir.path().join(WALLET_INFO_FILENAME); let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); let dummy_account4 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -1061,20 +1071,21 @@ mod tests { let id2 = AccountId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); let id4 = AccountId::new("fourth".to_string()); + let default_id = AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string()); store_wallet_login_information_at_file( - wallet_file.clone(), + wallet.clone(), dummy_account1.clone(), - cosmos_hd_path.clone(), + hd_path.clone(), id1.clone(), &password, ) .unwrap(); store_wallet_login_information_at_file( - wallet_file.clone(), + wallet.clone(), dummy_account2.clone(), - cosmos_hd_path.clone(), + hd_path.clone(), id2.clone(), &password, ) @@ -1082,18 +1093,18 @@ mod tests { // Add a third and fourth mnenonic grouped together with the second one append_account_to_wallet_login_information_at_file( - wallet_file.clone(), + wallet.clone(), dummy_account3, - cosmos_hd_path.clone(), + hd_path.clone(), id2.clone(), id3.clone(), &password, ) .unwrap(); append_account_to_wallet_login_information_at_file( - wallet_file.clone(), + wallet.clone(), dummy_account4.clone(), - cosmos_hd_path.clone(), + hd_path.clone(), id2.clone(), id4.clone(), &password, @@ -1101,41 +1112,37 @@ mod tests { .unwrap(); // Delete the third mnemonic, from the second login entry - remove_account_from_wallet_login_at_file(wallet_file.clone(), &id2, &id3, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet.clone(), &id2, &id3, &password).unwrap(); // Check that we can still load the other accounts let loaded_accounts = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); + load_existing_wallet_login_information_at_file(wallet.clone(), &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ WalletAccount::new_mnemonic_backed_account( - id2.clone(), + default_id.clone(), dummy_account2, - cosmos_hd_path.clone(), - ), - WalletAccount::new_mnemonic_backed_account( - id4.clone(), - dummy_account4, - cosmos_hd_path.clone(), + hd_path.clone(), ), + WalletAccount::new_mnemonic_backed_account(id4.clone(), dummy_account4, hd_path.clone()), ] .into(); assert_eq!(accounts, &expected); // Delete the second and fourth mnemonic from the second login entry removes the login entry - remove_account_from_wallet_login_at_file(wallet_file.clone(), &id2, &id2, &password).unwrap(); - remove_account_from_wallet_login_at_file(wallet_file.clone(), &id2, &id4, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet.clone(), &id2, &default_id, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet.clone(), &id2, &id4, &password).unwrap(); assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password), + load_existing_wallet_login_information_at_file(wallet.clone(), &id2, &password), Err(BackendError::NoSuchIdInWallet), )); // The first login is still available let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file, &id1, &password).unwrap(); + load_existing_wallet_login_information_at_file(wallet, &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(acc1.mnemonic(), &dummy_account1); - assert_eq!(acc1.hd_path(), &cosmos_hd_path); + assert_eq!(acc1.hd_path(), &hd_path); } // Test to that decrypts a stored file from the repo, to make sure we are able to decrypt stored From 25cc7dbebf811be9136007a10e4b86f496ed8279 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Tue, 10 May 2022 10:23:08 +0100 Subject: [PATCH 61/85] content update --- nym-wallet/src/components/Accounts/MultiAccountHowTo.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-wallet/src/components/Accounts/MultiAccountHowTo.tsx b/nym-wallet/src/components/Accounts/MultiAccountHowTo.tsx index 4f9f514c47..739f2ff8ea 100644 --- a/nym-wallet/src/components/Accounts/MultiAccountHowTo.tsx +++ b/nym-wallet/src/components/Accounts/MultiAccountHowTo.tsx @@ -26,7 +26,7 @@ export const MultiAccountHowTo = ({ show, handleClose }: { show: boolean; handle - In order to create multiple accounts your wallet need password. + In order to create multiple accounts your wallet needs a password. Follow steps below to create password. How to create a password for your account From 54194c03e1e5d0aad55f59029ec31e2a6508a323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 11 May 2022 12:17:08 +0200 Subject: [PATCH 62/85] wallet: fix default inner account name --- nym-wallet/src-tauri/src/operations/mixnet/account.rs | 7 +++---- nym-wallet/src-tauri/src/wallet_storage/account_data.rs | 7 +++++-- nym-wallet/src-tauri/src/wallet_storage/mod.rs | 5 ++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index e3d5464e70..8ed635dbe2 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -387,7 +387,7 @@ pub async fn sign_in_with_password( let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); let password = wallet_storage::UserPassword::new(password); let stored_account = wallet_storage::load_existing_wallet_login_information(&id, &password)?; - let (mnemonic, all_accounts) = extract_mnemonic_and_all_accounts(stored_account, id)?; + let (mnemonic, all_accounts) = extract_mnemonic_and_all_accounts(stored_account)?; { let mut w_state = state.write().await; @@ -399,7 +399,6 @@ pub async fn sign_in_with_password( fn extract_mnemonic_and_all_accounts( stored_account: StoredLogin, - id: wallet_storage::AccountId, ) -> Result<(Mnemonic, Vec), BackendError> { let mnemonic = match stored_account { StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), @@ -417,7 +416,7 @@ fn extract_mnemonic_and_all_accounts( // Keep track of all accounts for that id let all_accounts: Vec<_> = stored_account - .unwrap_into_multiple_accounts(id) + .unwrap_into_multiple_accounts() .into_accounts() .collect(); @@ -478,7 +477,7 @@ async fn reset_state_with_all_accounts_from_file( ) -> Result<(), BackendError> { let stored_account = wallet_storage::load_existing_wallet_login_information(id, password)?; let all_accounts: Vec<_> = stored_account - .unwrap_into_multiple_accounts(id.clone()) + .unwrap_into_multiple_accounts() .into_accounts() .collect(); diff --git a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs index bc21ceae84..d54ef89571 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -10,6 +10,7 @@ use crate::error::BackendError; use super::encryption::EncryptedData; use super::password::AccountId; use super::UserPassword; +use super::DEFAULT_NAME_FIRST_ACCOUNT; const CURRENT_WALLET_FILE_VERSION: u32 = 1; @@ -160,9 +161,11 @@ impl StoredLogin { } } - pub(crate) fn unwrap_into_multiple_accounts(self, id: AccountId) -> MultipleAccounts { + pub(crate) fn unwrap_into_multiple_accounts(self) -> MultipleAccounts { match self { - StoredLogin::Mnemonic(ref account) => account.clone().into_multiple(id), + StoredLogin::Mnemonic(ref account) => account + .clone() + .into_multiple(AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string())), StoredLogin::Multiple(ref accounts) => accounts.clone(), } } diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index c6e8d4173b..441c3329ba 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -22,7 +22,7 @@ pub(crate) const DEFAULT_WALLET_ACCOUNT_ID: &str = "default"; /// When converting a single account entry to one that contains many, the first account will use /// this name. -const DEFAULT_NAME_FIRST_ACCOUNT: &str = "Account 1"; +pub(crate) const DEFAULT_NAME_FIRST_ACCOUNT: &str = "Account 1"; fn get_storage_directory() -> Result { tauri::api::path::local_data_dir() @@ -158,8 +158,7 @@ fn append_account_to_wallet_login_information_at_file( let decrypted_login = stored_wallet.decrypt_login(&id, password)?; // Add accounts to the inner structure - let mut accounts = decrypted_login - .unwrap_into_multiple_accounts(AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string())); + let mut accounts = decrypted_login.unwrap_into_multiple_accounts(); accounts.add(inner_id, mnemonic, hd_path)?; let encrypted_accounts = EncryptedLogin { From 7b00282b2771fff38c08551708c9122a8d63f42a Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 11 May 2022 13:21:22 +0100 Subject: [PATCH 63/85] reset add account step on completion --- nym-wallet/src/components/Accounts/AddAccountModal.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index 3ad40f9209..c7ce4d35c6 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -207,9 +207,10 @@ export const AddAccountModal = () => { case 2: return ( { + onConfirm={async (password) => { if (data.accountName && data.mnemonic) { - handleAddAccount({ accountName: data.accountName, mnemonic: data.mnemonic, password }); + await handleAddAccount({ accountName: data.accountName, mnemonic: data.mnemonic, password }); + setStep(0); } }} /> From bfb868bfc76e75fec249c65db9e669726a7fdb4a Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 11 May 2022 13:39:01 +0100 Subject: [PATCH 64/85] move textfield components to global components --- .../components/Accounts/AddAccountModal.tsx | 9 +++++++-- .../src/components/Accounts/MnemonicModal.tsx | 10 ++++------ nym-wallet/src/components/Error.tsx | 19 +++++-------------- nym-wallet/src/components/ErrorFallback.tsx | 17 +++++++++++++++++ nym-wallet/src/components/index.ts | 3 ++- .../auth => }/components/textfields.tsx | 2 +- .../src/pages/auth/components/error.tsx | 8 -------- nym-wallet/src/pages/auth/components/index.ts | 2 -- .../src/pages/auth/pages/confirm-mnemonic.tsx | 3 ++- .../src/pages/auth/pages/connect-password.tsx | 2 +- .../src/pages/auth/pages/create-password.tsx | 3 ++- .../src/pages/auth/pages/signin-mnemonic.tsx | 3 ++- .../src/pages/auth/pages/signin-password.tsx | 3 ++- 13 files changed, 45 insertions(+), 39 deletions(-) create mode 100644 nym-wallet/src/components/ErrorFallback.tsx rename nym-wallet/src/{pages/auth => }/components/textfields.tsx (98%) delete mode 100644 nym-wallet/src/pages/auth/components/error.tsx diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index c7ce4d35c6..e2b86c8289 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -16,6 +16,7 @@ import { ArrowBackSharp } from '@mui/icons-material'; import { useClipboard } from 'use-clipboard-copy'; import { createMnemonic } from 'src/requests'; import { AccountsContext } from 'src/context'; +import { PasswordInput } from 'src/components'; import { Mnemonic } from '../Mnemonic'; const createAccountSteps = [ @@ -117,7 +118,12 @@ const ConfirmPassword = ({ onConfirm }: { onConfirm: (password: string) => void {error} )} - setValue(e.target.value)} fullWidth type="password" /> + setValue(pswrd)} + label="Confirm password" + autoFocus + /> - +export const Error = ({ message }: { message: string }) => ( + + {message} + ); diff --git a/nym-wallet/src/components/ErrorFallback.tsx b/nym-wallet/src/components/ErrorFallback.tsx new file mode 100644 index 0000000000..f582536b6b --- /dev/null +++ b/nym-wallet/src/components/ErrorFallback.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { FallbackProps } from 'react-error-boundary'; +import { Alert, AlertTitle, Button } from '@mui/material'; + +export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => ( +
+ + {error.name} + {error.message} + + + Stack trace + {error.stack} + + +
+); diff --git a/nym-wallet/src/components/index.ts b/nym-wallet/src/components/index.ts index 776aab98a6..b688f8a81f 100644 --- a/nym-wallet/src/components/index.ts +++ b/nym-wallet/src/components/index.ts @@ -1,4 +1,4 @@ -export * from './Error'; +export * from './ErrorFallback'; export * from './CopyToClipboard'; export * from './NymCard'; export * from './Nav'; @@ -16,3 +16,4 @@ export * from './InfoToolTip'; export * from './Title'; export * from './TokenPoolSelector'; export * from './LoadingPage'; +export * from './textfields'; diff --git a/nym-wallet/src/pages/auth/components/textfields.tsx b/nym-wallet/src/components/textfields.tsx similarity index 98% rename from nym-wallet/src/pages/auth/components/textfields.tsx rename to nym-wallet/src/components/textfields.tsx index bf4e5e5597..835e6beb23 100644 --- a/nym-wallet/src/pages/auth/components/textfields.tsx +++ b/nym-wallet/src/components/textfields.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { Box, IconButton, Stack, TextField } from '@mui/material'; import { Visibility, VisibilityOff } from '@mui/icons-material'; -import { Error } from './error'; +import { Error } from './Error'; export const MnemonicInput: React.FC<{ mnemonic: string; diff --git a/nym-wallet/src/pages/auth/components/error.tsx b/nym-wallet/src/pages/auth/components/error.tsx deleted file mode 100644 index a6d042ddc2..0000000000 --- a/nym-wallet/src/pages/auth/components/error.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import { Alert } from '@mui/material'; - -export const Error = ({ message }: { message: string }) => ( - - {message} - -); diff --git a/nym-wallet/src/pages/auth/components/index.ts b/nym-wallet/src/pages/auth/components/index.ts index c249976b6a..1798949439 100644 --- a/nym-wallet/src/pages/auth/components/index.ts +++ b/nym-wallet/src/pages/auth/components/index.ts @@ -1,6 +1,4 @@ export * from './heading'; export * from './word-tiles'; export * from './password-strength'; -export * from './error'; -export * from './textfields'; export * from './step'; diff --git a/nym-wallet/src/pages/auth/pages/confirm-mnemonic.tsx b/nym-wallet/src/pages/auth/pages/confirm-mnemonic.tsx index 521b2a07a8..761c2b479a 100644 --- a/nym-wallet/src/pages/auth/pages/confirm-mnemonic.tsx +++ b/nym-wallet/src/pages/auth/pages/confirm-mnemonic.tsx @@ -2,8 +2,9 @@ import React, { useContext, useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { Button, Stack } from '@mui/material'; import { validateMnemonic } from 'src/requests'; +import { MnemonicInput } from 'src/components'; import { AuthContext } from 'src/context/auth'; -import { MnemonicInput, Subtitle } from '../components'; +import { Subtitle } from '../components'; export const ConfirmMnemonic = () => { const { error, setError, setMnemonic, mnemonic } = useContext(AuthContext); diff --git a/nym-wallet/src/pages/auth/pages/connect-password.tsx b/nym-wallet/src/pages/auth/pages/connect-password.tsx index 9698444f4c..de8f086f64 100644 --- a/nym-wallet/src/pages/auth/pages/connect-password.tsx +++ b/nym-wallet/src/pages/auth/pages/connect-password.tsx @@ -4,8 +4,8 @@ import { Button, CircularProgress, FormControl, Stack } from '@mui/material'; import { useSnackbar } from 'notistack'; import { AuthContext } from 'src/context/auth'; import { createPassword } from 'src/requests'; +import { PasswordInput } from 'src/components'; import { Subtitle, Title, PasswordStrength } from '../components'; -import { PasswordInput } from '../components/textfields'; export const ConnectPassword = () => { const [confirmedPassword, setConfirmedPassword] = useState(''); diff --git a/nym-wallet/src/pages/auth/pages/create-password.tsx b/nym-wallet/src/pages/auth/pages/create-password.tsx index a9a2129b21..8e99cffb8a 100644 --- a/nym-wallet/src/pages/auth/pages/create-password.tsx +++ b/nym-wallet/src/pages/auth/pages/create-password.tsx @@ -4,7 +4,8 @@ import { Button, FormControl, Stack } from '@mui/material'; import { useSnackbar } from 'notistack'; import { AuthContext } from 'src/context/auth'; import { createPassword } from 'src/requests'; -import { PasswordInput, Subtitle, Title, PasswordStrength } from '../components'; +import { PasswordInput } from 'src/components'; +import { Subtitle, Title, PasswordStrength } from '../components'; export const CreatePassword = () => { const { password, setPassword, resetState, mnemonic } = useContext(AuthContext); diff --git a/nym-wallet/src/pages/auth/pages/signin-mnemonic.tsx b/nym-wallet/src/pages/auth/pages/signin-mnemonic.tsx index c77086668a..c699bbf050 100644 --- a/nym-wallet/src/pages/auth/pages/signin-mnemonic.tsx +++ b/nym-wallet/src/pages/auth/pages/signin-mnemonic.tsx @@ -3,7 +3,8 @@ import { useHistory } from 'react-router-dom'; import { Box, Button, FormControl, Stack } from '@mui/material'; import { AppContext } from 'src/context'; import { isPasswordCreated } from 'src/requests'; -import { MnemonicInput, Subtitle } from '../components'; +import { MnemonicInput } from 'src/components'; +import { Subtitle } from '../components'; export const SignInMnemonic = () => { const [mnemonic, setMnemonic] = useState(''); diff --git a/nym-wallet/src/pages/auth/pages/signin-password.tsx b/nym-wallet/src/pages/auth/pages/signin-password.tsx index d32cc2aef9..3528cb0dbe 100644 --- a/nym-wallet/src/pages/auth/pages/signin-password.tsx +++ b/nym-wallet/src/pages/auth/pages/signin-password.tsx @@ -1,7 +1,8 @@ import React, { useContext, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { Box, Button, FormControl, Stack } from '@mui/material'; -import { PasswordInput, Subtitle } from '../components'; +import { PasswordInput } from 'src/components'; +import { Subtitle } from '../components'; import { AppContext } from '../../../context/main'; export const SignInPassword = () => { From b83d4ca1a3aac53cf4e108aed11f11ee8d3b1213 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 11 May 2022 13:47:41 +0100 Subject: [PATCH 65/85] reset to initial step after add account --- nym-wallet/src/components/Accounts/AddAccountModal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index e2b86c8289..df9b4c25ed 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -165,6 +165,7 @@ export const AddAccountModal = () => { useEffect(() => { if (dialogToDisplay === 'Add') generateMnemonic(); + if (dialogToDisplay === 'Accounts') setStep(0); }, [dialogToDisplay]); useEffect(() => { From 54e95d795e523ca0f428312098efc6095d87c94b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 11 May 2022 14:42:26 +0200 Subject: [PATCH 66/85] wallet: revert back to old default account name for the time being --- .../src/operations/mixnet/account.rs | 39 ++++++++++++------- .../src/wallet_storage/account_data.rs | 30 ++++++++++---- .../src-tauri/src/wallet_storage/mod.rs | 4 +- 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 8ed635dbe2..497f6fc26c 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -387,9 +387,12 @@ pub async fn sign_in_with_password( let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); let password = wallet_storage::UserPassword::new(password); let stored_account = wallet_storage::load_existing_wallet_login_information(&id, &password)?; - let (mnemonic, all_accounts) = extract_mnemonic_and_all_accounts(stored_account)?; + let (mnemonic, all_accounts) = extract_first_mnemonic_and_all_accounts(stored_account, id)?; { + for account in &all_accounts { + log::trace!("account: {:?}", account); + } let mut w_state = state.write().await; w_state.set_all_accounts(all_accounts); } @@ -397,8 +400,9 @@ pub async fn sign_in_with_password( _connect_with_mnemonic(mnemonic, state).await } -fn extract_mnemonic_and_all_accounts( +fn extract_first_mnemonic_and_all_accounts( stored_account: StoredLogin, + login_id: wallet_storage::AccountId, ) -> Result<(Mnemonic, Vec), BackendError> { let mnemonic = match stored_account { StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), @@ -416,10 +420,9 @@ fn extract_mnemonic_and_all_accounts( // Keep track of all accounts for that id let all_accounts: Vec<_> = stored_account - .unwrap_into_multiple_accounts() + .unwrap_into_multiple_accounts(login_id) .into_accounts() .collect(); - Ok((mnemonic, all_accounts)) } @@ -477,7 +480,7 @@ async fn reset_state_with_all_accounts_from_file( ) -> Result<(), BackendError> { let stored_account = wallet_storage::load_existing_wallet_login_information(id, password)?; let all_accounts: Vec<_> = stored_account - .unwrap_into_multiple_accounts() + .unwrap_into_multiple_accounts(id.clone()) .into_accounts() .collect(); @@ -518,6 +521,7 @@ fn derive_address( pub async fn list_accounts( state: tauri::State<'_, Arc>>, ) -> Result, BackendError> { + log::trace!("Listing accounts"); let state = state.read().await; let network: Network = state.current_network().into(); let prefix = network.bech32_prefix(); @@ -530,6 +534,10 @@ pub async fn list_accounts( .unwrap() .to_string(), }) + .map(|account| { + log::trace!("{:?}", account); + account + }) .collect(); Ok(all_accounts) @@ -547,13 +555,18 @@ pub fn show_mnemonic_for_account_in_password( let stored_account = wallet_storage::load_existing_wallet_login_information(&id, &password)?; let mnemonic = match stored_account { - StoredLogin::Mnemonic(_) => return Err(BackendError::WalletUnexpectedMnemonicAccount), - StoredLogin::Multiple(ref accounts) => accounts - .get_account(&account_id) - .ok_or(BackendError::NoSuchIdInWalletLoginEntry)? - .account - .mnemonic() - .clone(), + StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), + StoredLogin::Multiple(ref accounts) => { + for account in accounts.get_accounts() { + log::debug!("{:?}", account); + } + accounts + .get_account(&account_id) + .ok_or(BackendError::NoSuchIdInWalletLoginEntry)? + .account + .mnemonic() + .clone() + } }; Ok(mnemonic.to_string()) @@ -599,7 +612,7 @@ mod tests { wallet_storage::load_existing_wallet_login_information_at_file(wallet_file, &id, &password) .unwrap(); let (mnemonic, all_accounts) = - extract_mnemonic_and_all_accounts(stored_account, id.clone()).unwrap(); + extract_first_mnemonic_and_all_accounts(&stored_account, id.clone()).unwrap(); let expected_mnemonic = bip39::Mnemonic::from_str("country mean universe text phone begin deputy reject result good cram illness common cluster proud swamp digital patrol spread bar face december base kick").unwrap(); assert_eq!(mnemonic, expected_mnemonic); diff --git a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs index d54ef89571..205cf684df 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -1,6 +1,19 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +// The wallet storage is a single json file, containing multiple entries. These are referred to as +// Logins, and has a plaintext id tag attached. +// +// Each encrypted login contains either a single account, or a list of multiple accounts. +// +// NOTE: A not insignificant amount of complexity comes from being able to handle both these cases, +// instead of, for example, converting a single account to a list of multiple accounts with a single +// entry. This also avoids resaving the wallet file when opening a file created with an earlier +// version of the wallet. +// +// In the future we might want to simplify by dropping the support for a single account entry, +// instead treating as muliple accounts with one entry. + use cosmrs::bip32::DerivationPath; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; @@ -10,7 +23,6 @@ use crate::error::BackendError; use super::encryption::EncryptedData; use super::password::AccountId; use super::UserPassword; -use super::DEFAULT_NAME_FIRST_ACCOUNT; const CURRENT_WALLET_FILE_VERSION: u32 = 1; @@ -161,11 +173,9 @@ impl StoredLogin { } } - pub(crate) fn unwrap_into_multiple_accounts(self) -> MultipleAccounts { + pub(crate) fn unwrap_into_multiple_accounts(self, id: AccountId) -> MultipleAccounts { match self { - StoredLogin::Mnemonic(ref account) => account - .clone() - .into_multiple(AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string())), + StoredLogin::Mnemonic(ref account) => account.clone().into_multiple(id), StoredLogin::Multiple(ref accounts) => accounts.clone(), } } @@ -189,11 +199,15 @@ impl MnemonicAccount { &self.hd_path } - pub(crate) fn into_multiple(self, id: AccountId) -> MultipleAccounts { - MultipleAccounts::new(WalletAccount { + pub(crate) fn into_wallet_account(self, id: AccountId) -> WalletAccount { + WalletAccount { id, account: self.into(), - }) + } + } + + pub(crate) fn into_multiple(self, id: AccountId) -> MultipleAccounts { + MultipleAccounts::new(self.into_wallet_account(id)) } } diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 441c3329ba..ac328fde2d 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -22,7 +22,7 @@ pub(crate) const DEFAULT_WALLET_ACCOUNT_ID: &str = "default"; /// When converting a single account entry to one that contains many, the first account will use /// this name. -pub(crate) const DEFAULT_NAME_FIRST_ACCOUNT: &str = "Account 1"; +//pub(crate) const DEFAULT_NAME_FIRST_ACCOUNT: &str = "Account 1"; fn get_storage_directory() -> Result { tauri::api::path::local_data_dir() @@ -158,7 +158,7 @@ fn append_account_to_wallet_login_information_at_file( let decrypted_login = stored_wallet.decrypt_login(&id, password)?; // Add accounts to the inner structure - let mut accounts = decrypted_login.unwrap_into_multiple_accounts(); + let mut accounts = decrypted_login.unwrap_into_multiple_accounts(id.clone()); accounts.add(inner_id, mnemonic, hd_path)?; let encrypted_accounts = EncryptedLogin { From 97c6567139f68b42aad68cd4e1d09a51f83423de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 11 May 2022 17:19:41 +0200 Subject: [PATCH 67/85] wallet: restore unit tests --- .../src/operations/mixnet/account.rs | 72 +++++++------------ .../src-tauri/src/wallet_storage/mod.rs | 32 ++++----- 2 files changed, 39 insertions(+), 65 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 497f6fc26c..19eeae5c18 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -386,24 +386,15 @@ pub async fn sign_in_with_password( // Currently we only support a single, default, id in the wallet let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); let password = wallet_storage::UserPassword::new(password); - let stored_account = wallet_storage::load_existing_wallet_login_information(&id, &password)?; - let (mnemonic, all_accounts) = extract_first_mnemonic_and_all_accounts(stored_account, id)?; + let stored_login = wallet_storage::load_existing_wallet_login_information(&id, &password)?; - { - for account in &all_accounts { - log::trace!("account: {:?}", account); - } - let mut w_state = state.write().await; - w_state.set_all_accounts(all_accounts); - } + let mnemonic = extract_first_mnemonic(&stored_login)?; + set_state_with_all_accounts(stored_login, id, state.clone()).await?; _connect_with_mnemonic(mnemonic, state).await } -fn extract_first_mnemonic_and_all_accounts( - stored_account: StoredLogin, - login_id: wallet_storage::AccountId, -) -> Result<(Mnemonic, Vec), BackendError> { +fn extract_first_mnemonic(stored_account: &StoredLogin) -> Result { let mnemonic = match stored_account { StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), StoredLogin::Multiple(ref accounts) => { @@ -418,12 +409,7 @@ fn extract_first_mnemonic_and_all_accounts( } }; - // Keep track of all accounts for that id - let all_accounts: Vec<_> = stored_account - .unwrap_into_multiple_accounts(login_id) - .into_accounts() - .collect(); - Ok((mnemonic, all_accounts)) + Ok(mnemonic) } #[tauri::command] @@ -449,23 +435,24 @@ pub async fn add_account_for_password( let password = wallet_storage::UserPassword::new(password.to_string()); // Creating the returned account entry could fail, so do it before attempting to store to wallet - let address = { - let state = state.read().await; - let network: Network = state.current_network().into(); - derive_address(mnemonic.clone(), network.bech32_prefix())?.to_string() - }; - wallet_storage::append_account_to_wallet_login_information( - mnemonic, + mnemonic.clone(), hd_path, id.clone(), inner_id.clone(), &password, )?; + let address = { + let state = state.read().await; + let network: Network = state.current_network().into(); + derive_address(mnemonic, network.bech32_prefix())?.to_string() + }; + // Re-read all the acccounts from the wallet to reset the state, rather than updating it // incrementally - reset_state_with_all_accounts_from_file(&id, &password, state).await?; + let stored_login = wallet_storage::load_existing_wallet_login_information(&id, &password)?; + set_state_with_all_accounts(stored_login, id, state).await?; Ok(AccountEntry { id: inner_id.to_string(), @@ -473,17 +460,21 @@ pub async fn add_account_for_password( }) } -async fn reset_state_with_all_accounts_from_file( - id: &wallet_storage::AccountId, - password: &wallet_storage::UserPassword, +async fn set_state_with_all_accounts( + stored_login: StoredLogin, + id: wallet_storage::AccountId, state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - let stored_account = wallet_storage::load_existing_wallet_login_information(id, password)?; - let all_accounts: Vec<_> = stored_account + log::trace!("Set state with accounts:"); + let all_accounts: Vec<_> = stored_login .unwrap_into_multiple_accounts(id.clone()) .into_accounts() .collect(); + for account in &all_accounts { + log::trace!("account: {:?}", account); + } + let mut w_state = state.write().await; w_state.set_all_accounts(all_accounts); Ok(()) @@ -502,7 +493,9 @@ pub async fn remove_account_for_password( let password = wallet_storage::UserPassword::new(password.to_string()); wallet_storage::remove_account_from_wallet_login(&id, &inner_id, &password)?; - reset_state_with_all_accounts_from_file(&id, &password, state).await + // Load to reset the internal state + let stored_login = wallet_storage::load_existing_wallet_login_information(&id, &password)?; + set_state_with_all_accounts(stored_login, id, state).await } fn derive_address( @@ -606,24 +599,13 @@ mod tests { let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET); let id = wallet_storage::AccountId::new("first".to_string()); let password = wallet_storage::UserPassword::new("password".to_string()); - let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let stored_account = wallet_storage::load_existing_wallet_login_information_at_file(wallet_file, &id, &password) .unwrap(); - let (mnemonic, all_accounts) = - extract_first_mnemonic_and_all_accounts(&stored_account, id.clone()).unwrap(); + let mnemonic = extract_first_mnemonic(&stored_account).unwrap(); let expected_mnemonic = bip39::Mnemonic::from_str("country mean universe text phone begin deputy reject result good cram illness common cluster proud swamp digital patrol spread bar face december base kick").unwrap(); assert_eq!(mnemonic, expected_mnemonic); - - assert_eq!( - all_accounts, - vec![wallet_storage::WalletAccount::new_mnemonic_backed_account( - id, - expected_mnemonic, - hd_path, - )] - ); } } diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index ac328fde2d..9792c877b2 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub(crate) use crate::wallet_storage::account_data::{StoredLogin, WalletAccount}; +pub(crate) use crate::wallet_storage::account_data::StoredLogin; pub(crate) use crate::wallet_storage::password::{AccountId, UserPassword}; use crate::error::BackendError; @@ -157,7 +157,9 @@ fn append_account_to_wallet_login_information_at_file( let decrypted_login = stored_wallet.decrypt_login(&id, password)?; - // Add accounts to the inner structure + // Add accounts to the inner structure. + // Note that in case we only have single account entry, without an inner_id, we convert to + // multiple accounts and we set the first inner_id to id. let mut accounts = decrypted_login.unwrap_into_multiple_accounts(id.clone()); accounts.add(inner_id, mnemonic, hd_path)?; @@ -287,7 +289,7 @@ fn remove_account_from_wallet_login_at_file( #[cfg(test)] mod tests { - use crate::wallet_storage::WalletAccount; + use crate::wallet_storage::account_data::WalletAccount; use super::*; use config::defaults::COSMOS_DERIVATION_PATH; @@ -721,7 +723,6 @@ mod tests { let id1 = AccountId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - let default_id = AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string()); store_wallet_login_information_at_file( wallet_file.clone(), @@ -754,7 +755,7 @@ mod tests { load_existing_wallet_login_information_at_file(wallet_file, &id1, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account(default_id, dummy_account1, hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id1, dummy_account1, hd_path.clone()), WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, hd_path), ] .into(); @@ -778,7 +779,6 @@ mod tests { let id2 = AccountId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); let id4 = AccountId::new("fourth".to_string()); - let default_id = AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string()); store_wallet_login_information_at_file( wallet_file.clone(), @@ -836,7 +836,7 @@ mod tests { load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account(default_id, dummy_account2, hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, hd_path.clone()), WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, hd_path.clone()), WalletAccount::new_mnemonic_backed_account(id4, dummy_account4, hd_path), ] @@ -900,7 +900,6 @@ mod tests { let id1 = AccountId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); - let default_id = AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string()); store_wallet_login_information_at_file( wallet_file.clone(), @@ -937,7 +936,7 @@ mod tests { load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account(default_id, dummy_account2, hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, hd_path.clone()), WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, hd_path), ] .into(); @@ -957,7 +956,6 @@ mod tests { let id1 = AccountId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - let default_id = AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string()); store_wallet_login_information_at_file( wallet.clone(), @@ -978,7 +976,7 @@ mod tests { ) .unwrap(); - remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &default_id, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &id1, &password).unwrap(); remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &id2, &password).unwrap(); // The file should now be removed @@ -1006,7 +1004,6 @@ mod tests { let id1 = AccountId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); - let default_id = AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string()); store_wallet_login_information_at_file( wallet.clone(), @@ -1036,7 +1033,7 @@ mod tests { ) .unwrap(); - remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &default_id, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &id1, &password).unwrap(); remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &id2, &password).unwrap(); // And trying to load when the file is gone fails @@ -1070,7 +1067,6 @@ mod tests { let id2 = AccountId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); let id4 = AccountId::new("fourth".to_string()); - let default_id = AccountId::new(DEFAULT_NAME_FIRST_ACCOUNT.to_string()); store_wallet_login_information_at_file( wallet.clone(), @@ -1118,18 +1114,14 @@ mod tests { load_existing_wallet_login_information_at_file(wallet.clone(), &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account( - default_id.clone(), - dummy_account2, - hd_path.clone(), - ), + WalletAccount::new_mnemonic_backed_account(id2.clone(), dummy_account2, hd_path.clone()), WalletAccount::new_mnemonic_backed_account(id4.clone(), dummy_account4, hd_path.clone()), ] .into(); assert_eq!(accounts, &expected); // Delete the second and fourth mnemonic from the second login entry removes the login entry - remove_account_from_wallet_login_at_file(wallet.clone(), &id2, &default_id, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet.clone(), &id2, &id2, &password).unwrap(); remove_account_from_wallet_login_at_file(wallet.clone(), &id2, &id4, &password).unwrap(); assert!(matches!( load_existing_wallet_login_information_at_file(wallet.clone(), &id2, &password), From 83a7f6577bcc2d6b9ec7d3c2044da0fbd8d0d173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 11 May 2022 22:23:37 +0200 Subject: [PATCH 68/85] wallet: logout before re-connecting --- nym-wallet/src-tauri/src/operations/mixnet/account.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 19eeae5c18..82ee1553f7 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -116,9 +116,8 @@ pub async fn create_new_account( } #[tauri::command] -pub fn create_new_mnemonic() -> Result { - let rand_mnemonic = random_mnemonic(); - Ok(rand_mnemonic.to_string()) +pub fn create_new_mnemonic() -> String { + random_mnemonic().to_string() } #[tauri::command] @@ -580,6 +579,11 @@ pub async fn sign_in_decrypted_account( .account; account.mnemonic().clone() }; + + { + state.write().await.logout(); + } + _connect_with_mnemonic(mnemonic, state).await } From d6206a04bdbd945a070ffe020f38d1cc0174bdd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 12 May 2022 08:37:54 +0200 Subject: [PATCH 69/85] wallet: restore test in account --- .../src/operations/mixnet/account.rs | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 82ee1553f7..0bf20ae202 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -581,7 +581,7 @@ pub async fn sign_in_decrypted_account( }; { - state.write().await.logout(); + state.write().await.logout(); } _connect_with_mnemonic(mnemonic, state).await @@ -603,13 +603,30 @@ mod tests { let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET); let id = wallet_storage::AccountId::new("first".to_string()); let password = wallet_storage::UserPassword::new("password".to_string()); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let stored_account = + let stored_login = wallet_storage::load_existing_wallet_login_information_at_file(wallet_file, &id, &password) .unwrap(); - let mnemonic = extract_first_mnemonic(&stored_account).unwrap(); + let mnemonic = extract_first_mnemonic(&stored_login).unwrap(); let expected_mnemonic = bip39::Mnemonic::from_str("country mean universe text phone begin deputy reject result good cram illness common cluster proud swamp digital patrol spread bar face december base kick").unwrap(); assert_eq!(mnemonic, expected_mnemonic); + + let all_accounts: Vec<_> = stored_login + .unwrap_into_multiple_accounts(id.clone()) + .into_accounts() + .collect(); + + assert_eq!( + all_accounts, + vec![ + wallet_storage::account_data::WalletAccount::new_mnemonic_backed_account( + id, + expected_mnemonic, + hd_path + ) + ] + ); } } From 7771c5d3d9bce749844298615d4fbf7293c6aa8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 12 May 2022 10:28:45 +0200 Subject: [PATCH 70/85] wallet: create password with multi-account by default --- .../src/operations/mixnet/account.rs | 7 +- .../src/wallet_storage/account_data.rs | 18 +- .../src-tauri/src/wallet_storage/mod.rs | 468 +++++++++++++++++- .../src-tauri/src/wallet_storage/password.rs | 12 + 4 files changed, 492 insertions(+), 13 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 0bf20ae202..0f17c2880c 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -372,7 +372,7 @@ pub fn create_password(mnemonic: &str, password: String) -> Result<(), BackendEr // Currently we only support a single, default, id in the wallet let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); let password = wallet_storage::UserPassword::new(password); - wallet_storage::store_wallet_login_information(mnemonic, hd_path, id, &password) + wallet_storage::store_wallet_login_multiple_information(mnemonic, hd_path, id, &password) } #[tauri::command] @@ -629,4 +629,9 @@ mod tests { ] ); } + + #[test] + fn decrypt_stored_wallet_multiple_for_sign_in() { + // WIP(JON): same as above but with file containing multiple accounts + } } diff --git a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs index 205cf684df..418be34433 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -157,6 +157,10 @@ impl StoredLogin { Self::Mnemonic(MnemonicAccount { mnemonic, hd_path }) } + pub(crate) fn new_multiple_login() -> Self { + Self::Multiple(MultipleAccounts::empty()) + } + #[cfg(test)] pub(crate) fn as_mnemonic_account(&self) -> Option<&MnemonicAccount> { match self { @@ -194,7 +198,6 @@ impl MnemonicAccount { &self.mnemonic } - #[cfg(test)] pub(crate) fn hd_path(&self) -> &DerivationPath { &self.hd_path } @@ -239,6 +242,12 @@ pub(crate) struct MultipleAccounts { } impl MultipleAccounts { + pub(crate) fn empty() -> Self { + MultipleAccounts { + accounts: Vec::new(), + } + } + pub(crate) fn new(account: WalletAccount) -> Self { MultipleAccounts { accounts: vec![account], @@ -340,6 +349,13 @@ impl AccountData { AccountData::Mnemonic(account) => account.mnemonic(), } } + + #[cfg(test)] + pub(crate) fn hd_path(&self) -> &DerivationPath { + match self { + AccountData::Mnemonic(account) => account.hd_path(), + } + } } impl From for AccountData { diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 9792c877b2..731547ee39 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -18,11 +18,12 @@ pub(crate) mod encryption; mod password; +/// The default wallet (top-level) login id. pub(crate) const DEFAULT_WALLET_ACCOUNT_ID: &str = "default"; /// When converting a single account entry to one that contains many, the first account will use /// this name. -//pub(crate) const DEFAULT_NAME_FIRST_ACCOUNT: &str = "Account 1"; +pub(crate) const DEFAULT_NAME_FIRST_ACCOUNT: &str = "Account 1"; fn get_storage_directory() -> Result { tauri::api::path::local_data_dir() @@ -73,6 +74,8 @@ pub(crate) fn load_existing_wallet_login_information_at_file( /// Encrypt `mnemonic` and store it together with `id`. It is stored at the top-level. /// Currently we enforce that we can only add entries with the same password as the other already /// existing entries. This is not unlikely to change in the future. +// DEPRECATED: consider making this cfg(test) only as it's still useful to verify being able to +// handle older formats pub(crate) fn store_wallet_login_information( mnemonic: bip39::Mnemonic, hd_path: DerivationPath, @@ -99,7 +102,8 @@ fn store_wallet_login_information_at_file( result => result?, }; - // Confirm that the given password also can unlock the other entries + // Confirm that the given password also can unlock the other entries. + // This is restriction we can relax in the future, but for now it's a sanity check. if !stored_wallet.password_can_decrypt_all(password) { return Err(BackendError::WalletDifferentPasswordDetected); } @@ -121,6 +125,62 @@ fn store_wallet_login_information_at_file( Ok(serde_json::to_writer_pretty(file, &stored_wallet)?) } +pub(crate) fn store_wallet_login_multiple_information( + mnemonic: bip39::Mnemonic, + hd_path: DerivationPath, + id: AccountId, + password: &UserPassword, +) -> Result<(), BackendError> { + // make sure the entire directory structure exists + let store_dir = get_storage_directory()?; + create_dir_all(&store_dir)?; + let filepath = store_dir.join(WALLET_INFO_FILENAME); + + store_wallet_login_multiple_information_at_file(filepath, mnemonic, hd_path, id, password) +} + +fn store_wallet_login_multiple_information_at_file( + filepath: PathBuf, + mnemonic: bip39::Mnemonic, + hd_path: DerivationPath, + id: AccountId, + password: &UserPassword, +) -> Result<(), BackendError> { + let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) { + Err(BackendError::WalletFileNotFound) => StoredWallet::default(), + result => result?, + }; + + // Confirm that the given password also can unlock the other entries. + // This is restriction we can relax in the future, but for now it's a sanity check. + if !stored_wallet.password_can_decrypt_all(password) { + return Err(BackendError::WalletDifferentPasswordDetected); + } + + let mut new_accounts = account_data::MultipleAccounts::empty(); + new_accounts.add( + DEFAULT_NAME_FIRST_ACCOUNT.to_string().into(), + mnemonic, + hd_path, + )?; + + let new_login = StoredLogin::Multiple(new_accounts); + let new_encrypted_login = EncryptedLogin { + id, + account: encrypt_struct(&new_login, password)?, + }; + + stored_wallet.add_encrypted_login(new_encrypted_login)?; + + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(filepath)?; + + Ok(serde_json::to_writer_pretty(file, &stored_wallet)?) +} + /// Append an account to an already existing top-level encrypted account entry. /// If the existing top-level entry is just a single account, it will be converted to the first /// account in the list of accounts associated with the encrypted entry. The inner id for this @@ -343,6 +403,35 @@ mod tests { assert!(!login.account.ciphertext().is_empty()); } + #[test] + fn store_single_login_with_multi() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = AccountId::new("first".to_string()); + + store_wallet_login_multiple_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path, + id1, + &password, + ) + .unwrap(); + + let stored_wallet = load_existing_wallet_at_file(wallet_file).unwrap(); + assert_eq!(stored_wallet.len(), 1); + + let login = stored_wallet.get_encrypted_login_by_index(0).unwrap(); + assert_eq!(login.id, AccountId::new("first".to_string())); + + // some actual ciphertext was saved + assert!(!login.account.ciphertext().is_empty()); + } + #[test] fn store_twice_for_the_same_id_fails() { let store_dir = tempdir().unwrap(); @@ -376,6 +465,39 @@ mod tests { )); } + #[test] + fn store_twice_for_the_same_id_fails_with_multiple() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = AccountId::new("first".to_string()); + + // Store the first login + store_wallet_login_multiple_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + // and storing the same id again fails + assert!(matches!( + store_wallet_login_multiple_information_at_file( + wallet_file, + dummy_account1, + cosmos_hd_path, + id1, + &password, + ), + Err(BackendError::IdAlreadyExistsInWallet), + )); + } + #[test] fn load_with_wrong_password_fails() { let store_dir = tempdir().unwrap(); @@ -403,6 +525,33 @@ mod tests { )); } + #[test] + fn load_with_wrong_password_fails_with_multi() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let bad_password = UserPassword::new("bad-password".to_string()); + let id1 = AccountId::new("first".to_string()); + + store_wallet_login_multiple_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path, + id1.clone(), + &password, + ) + .unwrap(); + + // Trying to load it with wrong password now fails + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file, &id1, &bad_password), + Err(BackendError::DecryptionError), + )); + } + #[test] fn load_with_wrong_id_fails() { let store_dir = tempdir().unwrap(); @@ -430,6 +579,33 @@ mod tests { )); } + #[test] + fn load_with_wrong_id_fails_with_multi() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_wallet_login_multiple_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path, + id1, + &password, + ) + .unwrap(); + + // Trying to load with the wrong id + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file, &id2, &password), + Err(BackendError::NoSuchIdInWallet), + )); + } + #[test] fn store_and_load_a_single_login() { let store_dir = tempdir().unwrap(); @@ -456,6 +632,37 @@ mod tests { assert_eq!(&cosmos_hd_path, acc.hd_path()); } + #[test] + fn store_and_load_a_single_login_with_multi() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = AccountId::new("first".to_string()); + + store_wallet_login_multiple_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + let loaded_login = + load_existing_wallet_login_information_at_file(wallet_file, &id1, &password).unwrap(); + let accounts = loaded_login.as_multiple_accounts().unwrap(); + assert_eq!(accounts.len(), 1); + let account = accounts + .get_account(&DEFAULT_NAME_FIRST_ACCOUNT.into()) + .unwrap(); + assert_eq!(account.id, DEFAULT_NAME_FIRST_ACCOUNT.into()); + assert_eq!(account.account.mnemonic(), &dummy_account1); + assert_eq!(account.account.hd_path(), &cosmos_hd_path); + } + #[test] fn store_a_second_login_with_a_different_password_fails() { let store_dir = tempdir().unwrap(); @@ -493,6 +700,43 @@ mod tests { )); } + #[test] + fn store_a_second_login_with_a_different_password_fails_with_multi() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + let bad_password = UserPassword::new("bad-password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_wallet_login_multiple_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path.clone(), + id1, + &password, + ) + .unwrap(); + + // Can't store a second login if you use different password + assert!(matches!( + store_wallet_login_multiple_information_at_file( + wallet_file, + dummy_account2, + cosmos_hd_path, + id2, + &bad_password + ), + Err(BackendError::WalletDifferentPasswordDetected), + )); + } + #[test] fn store_two_mnemonic_accounts_gives_different_salts_and_iv() { let store_dir = tempdir().unwrap(); @@ -605,6 +849,66 @@ mod tests { assert_eq!(&different_hd_path, acc2.hd_path()); } + #[test] + fn store_one_mnemonic_account_and_one_multi_account() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let different_hd_path: DerivationPath = "m".parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + // Store the first account + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(&dummy_account1, acc.mnemonic()); + assert_eq!(&cosmos_hd_path, acc.hd_path()); + + // Add an extra account + store_wallet_login_multiple_information_at_file( + wallet_file.clone(), + dummy_account2.clone(), + different_hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // first account should be unchanged + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc1 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(&dummy_account1, acc1.mnemonic()); + assert_eq!(&cosmos_hd_path, acc1.hd_path()); + + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); + let acc2 = loaded_account.as_multiple_accounts().unwrap(); + assert_eq!(acc2.len(), 1); + let account = acc2 + .get_account(&DEFAULT_NAME_FIRST_ACCOUNT.into()) + .unwrap(); + assert_eq!(account.id, DEFAULT_NAME_FIRST_ACCOUNT.into()); + assert_eq!(account.account.mnemonic(), &dummy_account2); + assert_eq!(account.account.hd_path(), &different_hd_path); + } + #[test] fn remove_non_existent_id_fails() { let store_dir = tempdir().unwrap(); @@ -618,7 +922,7 @@ mod tests { let id1 = AccountId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - store_wallet_login_information_at_file( + store_wallet_login_multiple_information_at_file( wallet_file.clone(), dummy_account1, cosmos_hd_path, @@ -844,6 +1148,90 @@ mod tests { assert_eq!(accounts, &expected); } + #[test] + fn append_accounts_to_existing_login_with_multi() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account4 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + let id3 = AccountId::new("third".to_string()); + let id4 = AccountId::new("fourth".to_string()); + + store_wallet_login_multiple_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + store_wallet_login_multiple_information_at_file( + wallet_file.clone(), + dummy_account2.clone(), + hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // Add a third and fourth mnenonic grouped together with the second one + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account3.clone(), + hd_path.clone(), + id2.clone(), + id3.clone(), + &password, + ) + .unwrap(); + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account4.clone(), + hd_path.clone(), + id2.clone(), + id4.clone(), + &password, + ) + .unwrap(); + + // Check that we can load all four + let loaded_login = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let accounts = loaded_login.as_multiple_accounts().unwrap(); + let expected = vec![WalletAccount::new_mnemonic_backed_account( + DEFAULT_NAME_FIRST_ACCOUNT.into(), + dummy_account1, + hd_path.clone(), + )] + .into(); + assert_eq!(accounts, &expected); + + let loaded_login = + load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); + let accounts = loaded_login.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new_mnemonic_backed_account( + DEFAULT_NAME_FIRST_ACCOUNT.into(), + dummy_account2, + hd_path.clone(), + ), + WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id4, dummy_account4, hd_path), + ] + .into(); + assert_eq!(accounts, &expected); + } + #[test] fn delete_the_same_account_twice_for_a_login_fails() { let store_dir = tempdir().unwrap(); @@ -885,6 +1273,47 @@ mod tests { )); } + #[test] + fn delete_the_same_account_twice_for_a_login_fails_with_multi() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_wallet_login_multiple_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2, + cosmos_hd_path, + id1.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id2, &password).unwrap(); + + assert!(matches!( + remove_account_from_wallet_login_at_file(wallet_file, &id1, &id2, &password), + Err(BackendError::NoSuchIdInWalletLoginEntry), + )); + } + #[test] fn delete_appended_account_doesnt_affect_others() { let store_dir = tempdir().unwrap(); @@ -957,7 +1386,7 @@ mod tests { let id1 = AccountId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - store_wallet_login_information_at_file( + store_wallet_login_multiple_information_at_file( wallet.clone(), dummy_account1, cosmos_hd_path.clone(), @@ -976,7 +1405,13 @@ mod tests { ) .unwrap(); - remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &id1, &password).unwrap(); + remove_account_from_wallet_login_at_file( + wallet.clone(), + &id1, + &DEFAULT_NAME_FIRST_ACCOUNT.into(), + &password, + ) + .unwrap(); remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &id2, &password).unwrap(); // The file should now be removed @@ -1005,7 +1440,7 @@ mod tests { let id2 = AccountId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); - store_wallet_login_information_at_file( + store_wallet_login_multiple_information_at_file( wallet.clone(), dummy_account1, cosmos_hd_path.clone(), @@ -1024,7 +1459,7 @@ mod tests { ) .unwrap(); - store_wallet_login_information_at_file( + store_wallet_login_multiple_information_at_file( wallet.clone(), dummy_account3.clone(), cosmos_hd_path.clone(), @@ -1033,7 +1468,13 @@ mod tests { ) .unwrap(); - remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &id1, &password).unwrap(); + remove_account_from_wallet_login_at_file( + wallet.clone(), + &id1, + &DEFAULT_NAME_FIRST_ACCOUNT.into(), + &password, + ) + .unwrap(); remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &id2, &password).unwrap(); // And trying to load when the file is gone fails @@ -1045,9 +1486,14 @@ mod tests { // The other login is still there let loaded_account = load_existing_wallet_login_information_at_file(wallet, &id3, &password).unwrap(); - let acc3 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(acc3.mnemonic(), &dummy_account3); - assert_eq!(acc3.hd_path(), &cosmos_hd_path); + let acc3 = loaded_account.as_multiple_accounts().unwrap(); + let expected = vec![WalletAccount::new_mnemonic_backed_account( + DEFAULT_NAME_FIRST_ACCOUNT.into(), + dummy_account3, + cosmos_hd_path, + )] + .into(); + assert_eq!(acc3, &expected); } #[test] diff --git a/nym-wallet/src-tauri/src/wallet_storage/password.rs b/nym-wallet/src-tauri/src/wallet_storage/password.rs index 016c5d3e5c..920032a5da 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/password.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/password.rs @@ -21,6 +21,18 @@ impl AsRef for AccountId { } } +impl From for AccountId { + fn from(id: String) -> Self { + Self::new(id) + } +} + +impl From<&str> for AccountId { + fn from(id: &str) -> Self { + Self::new(id.to_string()) + } +} + impl fmt::Display for AccountId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) From bce86235c7042a95423dec0c0fdc5bbfe9cb19f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 12 May 2022 11:56:00 +0200 Subject: [PATCH 71/85] wallet: create LoginId type to distinguish --- .../src/operations/mixnet/account.rs | 49 ++++-- .../src/wallet_storage/account_data.rs | 18 +- .../src-tauri/src/wallet_storage/mod.rs | 162 +++++++++--------- .../src-tauri/src/wallet_storage/password.rs | 51 +++++- 4 files changed, 166 insertions(+), 114 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 0f17c2880c..3b7336ad0c 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -5,7 +5,7 @@ use crate::network::Network as WalletNetwork; use crate::network_config; use crate::nymd_client; use crate::state::State; -use crate::wallet_storage::{self, StoredLogin, DEFAULT_WALLET_ACCOUNT_ID}; +use crate::wallet_storage::{self, StoredLogin, DEFAULT_LOGIN_ID}; use bip39::{Language, Mnemonic}; use config::defaults::all::Network; @@ -370,7 +370,7 @@ pub fn create_password(mnemonic: &str, password: String) -> Result<(), BackendEr let mnemonic = Mnemonic::from_str(mnemonic)?; let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); // Currently we only support a single, default, id in the wallet - let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); + let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let password = wallet_storage::UserPassword::new(password); wallet_storage::store_wallet_login_multiple_information(mnemonic, hd_path, id, &password) } @@ -383,12 +383,13 @@ pub async fn sign_in_with_password( log::info!("Signing in with password"); // Currently we only support a single, default, id in the wallet - let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); + let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let password = wallet_storage::UserPassword::new(password); let stored_login = wallet_storage::load_existing_wallet_login_information(&id, &password)?; let mnemonic = extract_first_mnemonic(&stored_login)?; - set_state_with_all_accounts(stored_login, id, state.clone()).await?; + let first_id_when_converting = id.into(); + set_state_with_all_accounts(stored_login, first_id_when_converting, state.clone()).await?; _connect_with_mnemonic(mnemonic, state).await } @@ -414,7 +415,7 @@ fn extract_first_mnemonic(stored_account: &StoredLogin) -> Result Result<(), BackendError> { log::info!("Removing password"); - let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); + let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); wallet_storage::remove_wallet_login_information(&id) } @@ -429,11 +430,10 @@ pub async fn add_account_for_password( let mnemonic = Mnemonic::from_str(mnemonic)?; let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); // Currently we only support a single, default, id in the wallet - let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); + let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let inner_id = wallet_storage::AccountId::new(inner_id.to_string()); let password = wallet_storage::UserPassword::new(password.to_string()); - // Creating the returned account entry could fail, so do it before attempting to store to wallet wallet_storage::append_account_to_wallet_login_information( mnemonic.clone(), hd_path, @@ -451,7 +451,10 @@ pub async fn add_account_for_password( // Re-read all the acccounts from the wallet to reset the state, rather than updating it // incrementally let stored_login = wallet_storage::load_existing_wallet_login_information(&id, &password)?; - set_state_with_all_accounts(stored_login, id, state).await?; + // NOTE: since we are appending, this id shouldn't be needed, but setting the state is supposed + // to be a general function + let first_id_when_converting = id.into(); + set_state_with_all_accounts(stored_login, first_id_when_converting, state).await?; Ok(AccountEntry { id: inner_id.to_string(), @@ -459,14 +462,15 @@ pub async fn add_account_for_password( }) } +// The first `AccoundId` when converting is the `LoginId` for the entry that was loaded. async fn set_state_with_all_accounts( stored_login: StoredLogin, - id: wallet_storage::AccountId, + first_id_when_converting: wallet_storage::AccountId, state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { log::trace!("Set state with accounts:"); let all_accounts: Vec<_> = stored_login - .unwrap_into_multiple_accounts(id.clone()) + .unwrap_into_multiple_accounts(first_id_when_converting) .into_accounts() .collect(); @@ -487,14 +491,17 @@ pub async fn remove_account_for_password( ) -> Result<(), BackendError> { log::info!("Removing account: {inner_id}"); // Currently we only support a single, default, id in the wallet - let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); + let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let inner_id = wallet_storage::AccountId::new(inner_id.to_string()); let password = wallet_storage::UserPassword::new(password.to_string()); wallet_storage::remove_account_from_wallet_login(&id, &inner_id, &password)?; // Load to reset the internal state let stored_login = wallet_storage::load_existing_wallet_login_information(&id, &password)?; - set_state_with_all_accounts(stored_login, id, state).await + // NOTE: Since we removed from a multi-account login, this id shouldn't be needed, but setting + // the state is supposed to be a general function + let first_id_when_converting = id.into(); + set_state_with_all_accounts(stored_login, first_id_when_converting, state).await } fn derive_address( @@ -541,7 +548,7 @@ pub fn show_mnemonic_for_account_in_password( password: String, ) -> Result { log::info!("Getting mnemonic for: {account_id}"); - let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); + let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let account_id = wallet_storage::AccountId::new(account_id); let password = wallet_storage::UserPassword::new(password); let stored_account = wallet_storage::load_existing_wallet_login_information(&id, &password)?; @@ -601,20 +608,24 @@ mod tests { fn decrypt_stored_wallet_for_sign_in() { const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet.json"; let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET); - let id = wallet_storage::AccountId::new("first".to_string()); + let login_id = wallet_storage::LoginId::new("first".to_string()); + let account_id = wallet_storage::AccountId::new("first".to_string()); let password = wallet_storage::UserPassword::new("password".to_string()); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let stored_login = - wallet_storage::load_existing_wallet_login_information_at_file(wallet_file, &id, &password) - .unwrap(); + let stored_login = wallet_storage::load_existing_wallet_login_information_at_file( + wallet_file, + &login_id, + &password, + ) + .unwrap(); let mnemonic = extract_first_mnemonic(&stored_login).unwrap(); let expected_mnemonic = bip39::Mnemonic::from_str("country mean universe text phone begin deputy reject result good cram illness common cluster proud swamp digital patrol spread bar face december base kick").unwrap(); assert_eq!(mnemonic, expected_mnemonic); let all_accounts: Vec<_> = stored_login - .unwrap_into_multiple_accounts(id.clone()) + .unwrap_into_multiple_accounts(account_id.clone()) .into_accounts() .collect(); @@ -622,7 +633,7 @@ mod tests { all_accounts, vec![ wallet_storage::account_data::WalletAccount::new_mnemonic_backed_account( - id, + account_id, expected_mnemonic, hd_path ) diff --git a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs index 418be34433..148e088e5b 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -21,7 +21,7 @@ use zeroize::Zeroize; use crate::error::BackendError; use super::encryption::EncryptedData; -use super::password::AccountId; +use super::password::{AccountId, LoginId}; use super::UserPassword; const CURRENT_WALLET_FILE_VERSION: u32 = 1; @@ -56,10 +56,7 @@ impl StoredWallet { Ok(()) } - fn get_encrypted_login( - &self, - id: &AccountId, - ) -> Result<&EncryptedData, BackendError> { + fn get_encrypted_login(&self, id: &LoginId) -> Result<&EncryptedData, BackendError> { self .accounts .iter() @@ -68,10 +65,7 @@ impl StoredWallet { .ok_or(BackendError::NoSuchIdInWallet) } - fn get_encrypted_login_mut( - &mut self, - id: &AccountId, - ) -> Result<&mut EncryptedLogin, BackendError> { + fn get_encrypted_login_mut(&mut self, id: &LoginId) -> Result<&mut EncryptedLogin, BackendError> { self .accounts .iter_mut() @@ -91,7 +85,7 @@ impl StoredWallet { Ok(()) } - pub fn remove_encrypted_login(&mut self, id: &AccountId) -> Option { + pub fn remove_encrypted_login(&mut self, id: &LoginId) -> Option { if let Some(index) = self.accounts.iter().position(|account| &account.id == id) { log::info!("Removing from wallet file: {id}"); Some(self.accounts.remove(index)) @@ -103,7 +97,7 @@ impl StoredWallet { pub fn decrypt_login( &self, - id: &AccountId, + id: &LoginId, password: &UserPassword, ) -> Result { self.get_encrypted_login(id)?.decrypt_struct(password) @@ -134,7 +128,7 @@ impl Default for StoredWallet { /// Each entry in the stored wallet file. An id field in plaintext and an encrypted stored login. #[derive(Serialize, Deserialize, Debug)] pub(crate) struct EncryptedLogin { - pub id: AccountId, + pub id: LoginId, pub account: EncryptedData, } diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 731547ee39..9b80994422 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 pub(crate) use crate::wallet_storage::account_data::StoredLogin; -pub(crate) use crate::wallet_storage::password::{AccountId, UserPassword}; +pub(crate) use crate::wallet_storage::password::{AccountId, LoginId, UserPassword}; use crate::error::BackendError; use crate::platform_constants::{STORAGE_DIR_NAME, WALLET_INFO_FILENAME}; @@ -19,11 +19,11 @@ pub(crate) mod encryption; mod password; /// The default wallet (top-level) login id. -pub(crate) const DEFAULT_WALLET_ACCOUNT_ID: &str = "default"; +pub(crate) const DEFAULT_LOGIN_ID: &str = "default"; /// When converting a single account entry to one that contains many, the first account will use /// this name. -pub(crate) const DEFAULT_NAME_FIRST_ACCOUNT: &str = "Account 1"; +pub(crate) const DEFAULT_FIRST_ACCOUNT_NAME: &str = "Account 1"; fn get_storage_directory() -> Result { tauri::api::path::local_data_dir() @@ -55,7 +55,7 @@ fn load_existing_wallet_at_file(filepath: PathBuf) -> Result Result { let store_dir = get_storage_directory()?; @@ -65,7 +65,7 @@ pub(crate) fn load_existing_wallet_login_information( pub(crate) fn load_existing_wallet_login_information_at_file( filepath: PathBuf, - id: &AccountId, + id: &LoginId, password: &UserPassword, ) -> Result { load_existing_wallet_at_file(filepath)?.decrypt_login(id, password) @@ -79,7 +79,7 @@ pub(crate) fn load_existing_wallet_login_information_at_file( pub(crate) fn store_wallet_login_information( mnemonic: bip39::Mnemonic, hd_path: DerivationPath, - id: AccountId, + id: LoginId, password: &UserPassword, ) -> Result<(), BackendError> { // make sure the entire directory structure exists @@ -94,7 +94,7 @@ fn store_wallet_login_information_at_file( filepath: PathBuf, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, - id: AccountId, + id: LoginId, password: &UserPassword, ) -> Result<(), BackendError> { let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) { @@ -128,7 +128,7 @@ fn store_wallet_login_information_at_file( pub(crate) fn store_wallet_login_multiple_information( mnemonic: bip39::Mnemonic, hd_path: DerivationPath, - id: AccountId, + id: LoginId, password: &UserPassword, ) -> Result<(), BackendError> { // make sure the entire directory structure exists @@ -143,7 +143,7 @@ fn store_wallet_login_multiple_information_at_file( filepath: PathBuf, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, - id: AccountId, + id: LoginId, password: &UserPassword, ) -> Result<(), BackendError> { let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) { @@ -159,7 +159,7 @@ fn store_wallet_login_multiple_information_at_file( let mut new_accounts = account_data::MultipleAccounts::empty(); new_accounts.add( - DEFAULT_NAME_FIRST_ACCOUNT.to_string().into(), + DEFAULT_FIRST_ACCOUNT_NAME.to_string().into(), mnemonic, hd_path, )?; @@ -188,7 +188,7 @@ fn store_wallet_login_multiple_information_at_file( pub(crate) fn append_account_to_wallet_login_information( mnemonic: bip39::Mnemonic, hd_path: DerivationPath, - id: AccountId, + id: LoginId, inner_id: AccountId, password: &UserPassword, ) -> Result<(), BackendError> { @@ -206,7 +206,7 @@ fn append_account_to_wallet_login_information_at_file( filepath: PathBuf, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, - id: AccountId, + id: LoginId, inner_id: AccountId, password: &UserPassword, ) -> Result<(), BackendError> { @@ -220,7 +220,8 @@ fn append_account_to_wallet_login_information_at_file( // Add accounts to the inner structure. // Note that in case we only have single account entry, without an inner_id, we convert to // multiple accounts and we set the first inner_id to id. - let mut accounts = decrypted_login.unwrap_into_multiple_accounts(id.clone()); + let first_id_when_converting = id.clone().into(); + let mut accounts = decrypted_login.unwrap_into_multiple_accounts(first_id_when_converting); accounts.add(inner_id, mnemonic, hd_path)?; let encrypted_accounts = EncryptedLogin { @@ -242,7 +243,7 @@ fn append_account_to_wallet_login_information_at_file( /// Remove the entire encrypted login entry for the given `id`. This means potentially removing all /// associated accounts! /// If this was the last entry in the file, the file is removed. -pub(crate) fn remove_wallet_login_information(id: &AccountId) -> Result<(), BackendError> { +pub(crate) fn remove_wallet_login_information(id: &LoginId) -> Result<(), BackendError> { let store_dir = get_storage_directory()?; let filepath = store_dir.join(WALLET_INFO_FILENAME); remove_wallet_login_information_at_file(filepath, id) @@ -250,7 +251,7 @@ pub(crate) fn remove_wallet_login_information(id: &AccountId) -> Result<(), Back fn remove_wallet_login_information_at_file( filepath: PathBuf, - id: &AccountId, + id: &LoginId, ) -> Result<(), BackendError> { log::warn!("Removing wallet account with id: {id}. This includes all associated accounts!"); let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) { @@ -286,7 +287,7 @@ fn remove_wallet_login_information_at_file( /// - If it is the last associated account with that login, the encrypted login will be removed. /// - If this was the last encrypted login in the file, it will be removed. pub(crate) fn remove_account_from_wallet_login( - id: &AccountId, + id: &LoginId, inner_id: &AccountId, password: &UserPassword, ) -> Result<(), BackendError> { @@ -297,7 +298,7 @@ pub(crate) fn remove_account_from_wallet_login( fn remove_account_from_wallet_login_at_file( filepath: PathBuf, - id: &AccountId, + id: &LoginId, inner_id: &AccountId, password: &UserPassword, ) -> Result<(), BackendError> { @@ -360,7 +361,7 @@ mod tests { fn trying_to_load_nonexistant_wallet_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let id1 = AccountId::new("first".to_string()); + let id1 = LoginId::new("first".to_string()); let password = UserPassword::new("password".to_string()); assert!(matches!( @@ -382,13 +383,13 @@ mod tests { let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); + let id1 = LoginId::new("first".to_string()); store_wallet_login_information_at_file( wallet_file.clone(), dummy_account1, cosmos_hd_path, - id1, + id1.clone(), &password, ) .unwrap(); @@ -397,7 +398,7 @@ mod tests { assert_eq!(stored_wallet.len(), 1); let login = stored_wallet.get_encrypted_login_by_index(0).unwrap(); - assert_eq!(login.id, AccountId::new("first".to_string())); + assert_eq!(login.id, id1); // some actual ciphertext was saved assert!(!login.account.ciphertext().is_empty()); @@ -411,13 +412,13 @@ mod tests { let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); + let id1 = LoginId::new("first".to_string()); store_wallet_login_multiple_information_at_file( wallet_file.clone(), dummy_account1, cosmos_hd_path, - id1, + id1.clone(), &password, ) .unwrap(); @@ -426,7 +427,7 @@ mod tests { assert_eq!(stored_wallet.len(), 1); let login = stored_wallet.get_encrypted_login_by_index(0).unwrap(); - assert_eq!(login.id, AccountId::new("first".to_string())); + assert_eq!(login.id, id1); // some actual ciphertext was saved assert!(!login.account.ciphertext().is_empty()); @@ -440,7 +441,7 @@ mod tests { let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); + let id1 = LoginId::new("first".to_string()); // Store the first login store_wallet_login_information_at_file( @@ -473,7 +474,7 @@ mod tests { let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); + let id1 = LoginId::new("first".to_string()); // Store the first login store_wallet_login_multiple_information_at_file( @@ -507,7 +508,7 @@ mod tests { let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); - let id1 = AccountId::new("first".to_string()); + let id1 = LoginId::new("first".to_string()); store_wallet_login_information_at_file( wallet_file.clone(), @@ -534,7 +535,7 @@ mod tests { let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); - let id1 = AccountId::new("first".to_string()); + let id1 = LoginId::new("first".to_string()); store_wallet_login_multiple_information_at_file( wallet_file.clone(), @@ -560,8 +561,8 @@ mod tests { let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); store_wallet_login_information_at_file( wallet_file.clone(), @@ -587,8 +588,8 @@ mod tests { let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); store_wallet_login_multiple_information_at_file( wallet_file.clone(), @@ -614,7 +615,7 @@ mod tests { let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); + let id1 = LoginId::new("first".to_string()); store_wallet_login_information_at_file( wallet_file.clone(), @@ -640,7 +641,7 @@ mod tests { let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); + let id1 = LoginId::new("first".to_string()); store_wallet_login_multiple_information_at_file( wallet_file.clone(), @@ -656,9 +657,9 @@ mod tests { let accounts = loaded_login.as_multiple_accounts().unwrap(); assert_eq!(accounts.len(), 1); let account = accounts - .get_account(&DEFAULT_NAME_FIRST_ACCOUNT.into()) + .get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into()) .unwrap(); - assert_eq!(account.id, DEFAULT_NAME_FIRST_ACCOUNT.into()); + assert_eq!(account.id, DEFAULT_FIRST_ACCOUNT_NAME.into()); assert_eq!(account.account.mnemonic(), &dummy_account1); assert_eq!(account.account.hd_path(), &cosmos_hd_path); } @@ -675,8 +676,8 @@ mod tests { let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); store_wallet_login_information_at_file( wallet_file.clone(), @@ -712,8 +713,8 @@ mod tests { let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); store_wallet_login_multiple_information_at_file( wallet_file.clone(), @@ -749,8 +750,8 @@ mod tests { let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); // Store the first account store_wallet_login_information_at_file( @@ -806,8 +807,8 @@ mod tests { let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); // Store the first account store_wallet_login_information_at_file( @@ -861,8 +862,8 @@ mod tests { let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); // Store the first account store_wallet_login_information_at_file( @@ -902,9 +903,9 @@ mod tests { let acc2 = loaded_account.as_multiple_accounts().unwrap(); assert_eq!(acc2.len(), 1); let account = acc2 - .get_account(&DEFAULT_NAME_FIRST_ACCOUNT.into()) + .get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into()) .unwrap(); - assert_eq!(account.id, DEFAULT_NAME_FIRST_ACCOUNT.into()); + assert_eq!(account.id, DEFAULT_FIRST_ACCOUNT_NAME.into()); assert_eq!(account.account.mnemonic(), &dummy_account2); assert_eq!(account.account.hd_path(), &different_hd_path); } @@ -919,8 +920,8 @@ mod tests { let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); store_wallet_login_multiple_information_at_file( wallet_file.clone(), @@ -950,8 +951,8 @@ mod tests { let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); // Store two accounts with two different passwords store_wallet_login_information_at_file( @@ -1025,7 +1026,7 @@ mod tests { let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); + let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); store_wallet_login_information_at_file( @@ -1059,7 +1060,7 @@ mod tests { load_existing_wallet_login_information_at_file(wallet_file, &id1, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account(id1, dummy_account1, hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id1.into(), dummy_account1, hd_path.clone()), WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, hd_path), ] .into(); @@ -1079,8 +1080,8 @@ mod tests { let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); let id4 = AccountId::new("fourth".to_string()); @@ -1140,7 +1141,7 @@ mod tests { load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id2.into(), dummy_account2, hd_path.clone()), WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, hd_path.clone()), WalletAccount::new_mnemonic_backed_account(id4, dummy_account4, hd_path), ] @@ -1161,8 +1162,8 @@ mod tests { let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); let id4 = AccountId::new("fourth".to_string()); @@ -1209,7 +1210,7 @@ mod tests { load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); let accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![WalletAccount::new_mnemonic_backed_account( - DEFAULT_NAME_FIRST_ACCOUNT.into(), + DEFAULT_FIRST_ACCOUNT_NAME.into(), dummy_account1, hd_path.clone(), )] @@ -1221,7 +1222,7 @@ mod tests { let accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ WalletAccount::new_mnemonic_backed_account( - DEFAULT_NAME_FIRST_ACCOUNT.into(), + DEFAULT_FIRST_ACCOUNT_NAME.into(), dummy_account2, hd_path.clone(), ), @@ -1243,7 +1244,7 @@ mod tests { let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); + let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); store_wallet_login_information_at_file( @@ -1284,7 +1285,7 @@ mod tests { let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); + let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); store_wallet_login_multiple_information_at_file( @@ -1326,8 +1327,8 @@ mod tests { let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); store_wallet_login_information_at_file( @@ -1365,7 +1366,7 @@ mod tests { load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id2.into(), dummy_account2, hd_path.clone()), WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, hd_path), ] .into(); @@ -1383,7 +1384,7 @@ mod tests { let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); + let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); store_wallet_login_multiple_information_at_file( @@ -1408,7 +1409,7 @@ mod tests { remove_account_from_wallet_login_at_file( wallet.clone(), &id1, - &DEFAULT_NAME_FIRST_ACCOUNT.into(), + &DEFAULT_FIRST_ACCOUNT_NAME.into(), &password, ) .unwrap(); @@ -1436,9 +1437,9 @@ mod tests { let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); + let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - let id3 = AccountId::new("third".to_string()); + let id3 = LoginId::new("third".to_string()); store_wallet_login_multiple_information_at_file( wallet.clone(), @@ -1471,7 +1472,7 @@ mod tests { remove_account_from_wallet_login_at_file( wallet.clone(), &id1, - &DEFAULT_NAME_FIRST_ACCOUNT.into(), + &DEFAULT_FIRST_ACCOUNT_NAME.into(), &password, ) .unwrap(); @@ -1488,7 +1489,7 @@ mod tests { load_existing_wallet_login_information_at_file(wallet, &id3, &password).unwrap(); let acc3 = loaded_account.as_multiple_accounts().unwrap(); let expected = vec![WalletAccount::new_mnemonic_backed_account( - DEFAULT_NAME_FIRST_ACCOUNT.into(), + DEFAULT_FIRST_ACCOUNT_NAME.into(), dummy_account3, cosmos_hd_path, )] @@ -1509,8 +1510,8 @@ mod tests { let password = UserPassword::new("password".to_string()); - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); let id4 = AccountId::new("fourth".to_string()); @@ -1560,14 +1561,19 @@ mod tests { load_existing_wallet_login_information_at_file(wallet.clone(), &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account(id2.clone(), dummy_account2, hd_path.clone()), + WalletAccount::new_mnemonic_backed_account( + id2.clone().into(), + dummy_account2, + hd_path.clone(), + ), WalletAccount::new_mnemonic_backed_account(id4.clone(), dummy_account4, hd_path.clone()), ] .into(); assert_eq!(accounts, &expected); // Delete the second and fourth mnemonic from the second login entry removes the login entry - remove_account_from_wallet_login_at_file(wallet.clone(), &id2, &id2, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet.clone(), &id2, &id2.clone().into(), &password) + .unwrap(); remove_account_from_wallet_login_at_file(wallet.clone(), &id2, &id4, &password).unwrap(); assert!(matches!( load_existing_wallet_login_information_at_file(wallet.clone(), &id2, &password), @@ -1594,8 +1600,8 @@ mod tests { let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); + let id1 = LoginId::new("first".to_string()); + let id2 = LoginId::new("second".to_string()); assert!(!wallet.password_can_decrypt_all(&bad_password)); assert!(wallet.password_can_decrypt_all(&password)); diff --git a/nym-wallet/src-tauri/src/wallet_storage/password.rs b/nym-wallet/src-tauri/src/wallet_storage/password.rs index 920032a5da..4e47215e50 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/password.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/password.rs @@ -6,6 +6,41 @@ use std::fmt; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; +// The `LoginId` is the top level id in the wallet file, and is not stored encrypted +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Zeroize)] +pub(crate) struct LoginId(String); + +impl LoginId { + pub(crate) fn new(id: String) -> LoginId { + LoginId(id) + } +} + +impl AsRef for LoginId { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +impl From for LoginId { + fn from(id: String) -> Self { + Self::new(id) + } +} + +impl From<&str> for LoginId { + fn from(id: &str) -> Self { + Self::new(id.to_string()) + } +} + +impl fmt::Display for LoginId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +// For each encrypted login, we can have multiple encrypted accounts. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Zeroize)] pub(crate) struct AccountId(String); @@ -22,14 +57,20 @@ impl AsRef for AccountId { } impl From for AccountId { - fn from(id: String) -> Self { - Self::new(id) - } + fn from(id: String) -> Self { + Self::new(id) + } } impl From<&str> for AccountId { - fn from(id: &str) -> Self { - Self::new(id.to_string()) + fn from(id: &str) -> Self { + Self::new(id.to_string()) + } +} + +impl From for AccountId { + fn from(login_id: LoginId) -> Self { + Self::new(login_id.0) } } From bf9db4128d75299f5f66c81f776caa557f6caa49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 12 May 2022 12:05:17 +0200 Subject: [PATCH 72/85] wallet: qualify with wallet_storage --- .../src-tauri/src/operations/mixnet/account.rs | 16 +++++++++------- .../src-tauri/src/wallet_storage/password.rs | 6 +++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 3b7336ad0c..16c90fe06d 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -5,7 +5,7 @@ use crate::network::Network as WalletNetwork; use crate::network_config; use crate::nymd_client; use crate::state::State; -use crate::wallet_storage::{self, StoredLogin, DEFAULT_LOGIN_ID}; +use crate::wallet_storage::{self, DEFAULT_LOGIN_ID}; use bip39::{Language, Mnemonic}; use config::defaults::all::Network; @@ -394,10 +394,12 @@ pub async fn sign_in_with_password( _connect_with_mnemonic(mnemonic, state).await } -fn extract_first_mnemonic(stored_account: &StoredLogin) -> Result { +fn extract_first_mnemonic( + stored_account: &wallet_storage::StoredLogin, +) -> Result { let mnemonic = match stored_account { - StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), - StoredLogin::Multiple(ref accounts) => { + wallet_storage::StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), + wallet_storage::StoredLogin::Multiple(ref accounts) => { // Login using the first account in the list accounts .get_accounts() @@ -464,7 +466,7 @@ pub async fn add_account_for_password( // The first `AccoundId` when converting is the `LoginId` for the entry that was loaded. async fn set_state_with_all_accounts( - stored_login: StoredLogin, + stored_login: wallet_storage::StoredLogin, first_id_when_converting: wallet_storage::AccountId, state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { @@ -554,8 +556,8 @@ pub fn show_mnemonic_for_account_in_password( let stored_account = wallet_storage::load_existing_wallet_login_information(&id, &password)?; let mnemonic = match stored_account { - StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), - StoredLogin::Multiple(ref accounts) => { + wallet_storage::StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), + wallet_storage::StoredLogin::Multiple(ref accounts) => { for account in accounts.get_accounts() { log::debug!("{:?}", account); } diff --git a/nym-wallet/src-tauri/src/wallet_storage/password.rs b/nym-wallet/src-tauri/src/wallet_storage/password.rs index 4e47215e50..8701f8994d 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/password.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/password.rs @@ -69,9 +69,9 @@ impl From<&str> for AccountId { } impl From for AccountId { - fn from(login_id: LoginId) -> Self { - Self::new(login_id.0) - } + fn from(login_id: LoginId) -> Self { + Self::new(login_id.0) + } } impl fmt::Display for AccountId { From 9e4904ff37b4cf17e31865031765b55bff3e3ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 12 May 2022 12:46:20 +0200 Subject: [PATCH 73/85] wallet: tweak names of wallet_storage functions --- .../src/operations/mixnet/account.rs | 23 +- .../src-tauri/src/wallet_storage/mod.rs | 237 ++++++++---------- 2 files changed, 118 insertions(+), 142 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 16c90fe06d..fea2ddbd7f 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -372,7 +372,7 @@ pub fn create_password(mnemonic: &str, password: String) -> Result<(), BackendEr // Currently we only support a single, default, id in the wallet let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let password = wallet_storage::UserPassword::new(password); - wallet_storage::store_wallet_login_multiple_information(mnemonic, hd_path, id, &password) + wallet_storage::store_wallet_login_with_multiple_accounts(mnemonic, hd_path, id, &password) } #[tauri::command] @@ -385,7 +385,7 @@ pub async fn sign_in_with_password( // Currently we only support a single, default, id in the wallet let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let password = wallet_storage::UserPassword::new(password); - let stored_login = wallet_storage::load_existing_wallet_login_information(&id, &password)?; + let stored_login = wallet_storage::load_existing_wallet_login(&id, &password)?; let mnemonic = extract_first_mnemonic(&stored_login)?; let first_id_when_converting = id.into(); @@ -418,7 +418,7 @@ fn extract_first_mnemonic( pub fn remove_password() -> Result<(), BackendError> { log::info!("Removing password"); let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - wallet_storage::remove_wallet_login_information(&id) + wallet_storage::remove_wallet_login(&id) } #[tauri::command] @@ -436,7 +436,7 @@ pub async fn add_account_for_password( let inner_id = wallet_storage::AccountId::new(inner_id.to_string()); let password = wallet_storage::UserPassword::new(password.to_string()); - wallet_storage::append_account_to_wallet_login_information( + wallet_storage::append_account_to_wallet_login( mnemonic.clone(), hd_path, id.clone(), @@ -452,7 +452,7 @@ pub async fn add_account_for_password( // Re-read all the acccounts from the wallet to reset the state, rather than updating it // incrementally - let stored_login = wallet_storage::load_existing_wallet_login_information(&id, &password)?; + let stored_login = wallet_storage::load_existing_wallet_login(&id, &password)?; // NOTE: since we are appending, this id shouldn't be needed, but setting the state is supposed // to be a general function let first_id_when_converting = id.into(); @@ -499,7 +499,7 @@ pub async fn remove_account_for_password( wallet_storage::remove_account_from_wallet_login(&id, &inner_id, &password)?; // Load to reset the internal state - let stored_login = wallet_storage::load_existing_wallet_login_information(&id, &password)?; + let stored_login = wallet_storage::load_existing_wallet_login(&id, &password)?; // NOTE: Since we removed from a multi-account login, this id shouldn't be needed, but setting // the state is supposed to be a general function let first_id_when_converting = id.into(); @@ -553,7 +553,7 @@ pub fn show_mnemonic_for_account_in_password( let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let account_id = wallet_storage::AccountId::new(account_id); let password = wallet_storage::UserPassword::new(password); - let stored_account = wallet_storage::load_existing_wallet_login_information(&id, &password)?; + let stored_account = wallet_storage::load_existing_wallet_login(&id, &password)?; let mnemonic = match stored_account { wallet_storage::StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), @@ -615,12 +615,9 @@ mod tests { let password = wallet_storage::UserPassword::new("password".to_string()); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let stored_login = wallet_storage::load_existing_wallet_login_information_at_file( - wallet_file, - &login_id, - &password, - ) - .unwrap(); + let stored_login = + wallet_storage::load_existing_wallet_login_at_file(wallet_file, &login_id, &password) + .unwrap(); let mnemonic = extract_first_mnemonic(&stored_login).unwrap(); let expected_mnemonic = bip39::Mnemonic::from_str("country mean universe text phone begin deputy reject result good cram illness common cluster proud swamp digital patrol spread bar face december base kick").unwrap(); diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 9b80994422..636081e6eb 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -54,16 +54,16 @@ fn load_existing_wallet_at_file(filepath: PathBuf) -> Result Result { let store_dir = get_storage_directory()?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - load_existing_wallet_login_information_at_file(filepath, id, password) + load_existing_wallet_login_at_file(filepath, id, password) } -pub(crate) fn load_existing_wallet_login_information_at_file( +pub(crate) fn load_existing_wallet_login_at_file( filepath: PathBuf, id: &LoginId, password: &UserPassword, @@ -71,12 +71,10 @@ pub(crate) fn load_existing_wallet_login_information_at_file( load_existing_wallet_at_file(filepath)?.decrypt_login(id, password) } -/// Encrypt `mnemonic` and store it together with `id`. It is stored at the top-level. -/// Currently we enforce that we can only add entries with the same password as the other already -/// existing entries. This is not unlikely to change in the future. -// DEPRECATED: consider making this cfg(test) only as it's still useful to verify being able to -// handle older formats -pub(crate) fn store_wallet_login_information( +// DEPRECATED: only used in tests, where it's used to test supporting older wallet formats +#[allow(unused)] +#[cfg(test)] +pub(crate) fn store_wallet_login( mnemonic: bip39::Mnemonic, hd_path: DerivationPath, id: LoginId, @@ -87,10 +85,12 @@ pub(crate) fn store_wallet_login_information( create_dir_all(&store_dir)?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - store_wallet_login_information_at_file(filepath, mnemonic, hd_path, id, password) + store_wallet_login_at_file(filepath, mnemonic, hd_path, id, password) } -fn store_wallet_login_information_at_file( +// DEPRECATED: only used in tests, where it's used to test supporting older wallet formats +#[cfg(test)] +fn store_wallet_login_at_file( filepath: PathBuf, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, @@ -125,7 +125,7 @@ fn store_wallet_login_information_at_file( Ok(serde_json::to_writer_pretty(file, &stored_wallet)?) } -pub(crate) fn store_wallet_login_multiple_information( +pub(crate) fn store_wallet_login_with_multiple_accounts( mnemonic: bip39::Mnemonic, hd_path: DerivationPath, id: LoginId, @@ -136,10 +136,10 @@ pub(crate) fn store_wallet_login_multiple_information( create_dir_all(&store_dir)?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - store_wallet_login_multiple_information_at_file(filepath, mnemonic, hd_path, id, password) + store_wallet_login_with_multiple_accounts_at_file(filepath, mnemonic, hd_path, id, password) } -fn store_wallet_login_multiple_information_at_file( +fn store_wallet_login_with_multiple_accounts_at_file( filepath: PathBuf, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, @@ -185,7 +185,7 @@ fn store_wallet_login_multiple_information_at_file( /// If the existing top-level entry is just a single account, it will be converted to the first /// account in the list of accounts associated with the encrypted entry. The inner id for this /// entry will be set to the same as the outer, unencrypted, id. -pub(crate) fn append_account_to_wallet_login_information( +pub(crate) fn append_account_to_wallet_login( mnemonic: bip39::Mnemonic, hd_path: DerivationPath, id: LoginId, @@ -197,12 +197,10 @@ pub(crate) fn append_account_to_wallet_login_information( create_dir_all(&store_dir)?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - append_account_to_wallet_login_information_at_file( - filepath, mnemonic, hd_path, id, inner_id, password, - ) + append_account_to_wallet_login_at_file(filepath, mnemonic, hd_path, id, inner_id, password) } -fn append_account_to_wallet_login_information_at_file( +fn append_account_to_wallet_login_at_file( filepath: PathBuf, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, @@ -243,16 +241,13 @@ fn append_account_to_wallet_login_information_at_file( /// Remove the entire encrypted login entry for the given `id`. This means potentially removing all /// associated accounts! /// If this was the last entry in the file, the file is removed. -pub(crate) fn remove_wallet_login_information(id: &LoginId) -> Result<(), BackendError> { +pub(crate) fn remove_wallet_login(id: &LoginId) -> Result<(), BackendError> { let store_dir = get_storage_directory()?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - remove_wallet_login_information_at_file(filepath, id) + remove_wallet_login_at_file(filepath, id) } -fn remove_wallet_login_information_at_file( - filepath: PathBuf, - id: &LoginId, -) -> Result<(), BackendError> { +fn remove_wallet_login_at_file(filepath: PathBuf, id: &LoginId) -> Result<(), BackendError> { log::warn!("Removing wallet account with id: {id}. This includes all associated accounts!"); let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) { Err(BackendError::WalletFileNotFound) => StoredWallet::default(), @@ -369,10 +364,10 @@ mod tests { Err(BackendError::WalletFileNotFound), )); assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password), + load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password), Err(BackendError::WalletFileNotFound), )); - remove_wallet_login_information_at_file(wallet_file, &id1).unwrap_err(); + remove_wallet_login_at_file(wallet_file, &id1).unwrap_err(); } #[test] @@ -385,7 +380,7 @@ mod tests { let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account1, cosmos_hd_path, @@ -414,7 +409,7 @@ mod tests { let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet_file.clone(), dummy_account1, cosmos_hd_path, @@ -444,7 +439,7 @@ mod tests { let id1 = LoginId::new("first".to_string()); // Store the first login - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account1.clone(), cosmos_hd_path.clone(), @@ -455,13 +450,7 @@ mod tests { // and storing the same id again fails assert!(matches!( - store_wallet_login_information_at_file( - wallet_file, - dummy_account1, - cosmos_hd_path, - id1, - &password, - ), + store_wallet_login_at_file(wallet_file, dummy_account1, cosmos_hd_path, id1, &password,), Err(BackendError::IdAlreadyExistsInWallet), )); } @@ -477,7 +466,7 @@ mod tests { let id1 = LoginId::new("first".to_string()); // Store the first login - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet_file.clone(), dummy_account1.clone(), cosmos_hd_path.clone(), @@ -488,7 +477,7 @@ mod tests { // and storing the same id again fails assert!(matches!( - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet_file, dummy_account1, cosmos_hd_path, @@ -510,7 +499,7 @@ mod tests { let bad_password = UserPassword::new("bad-password".to_string()); let id1 = LoginId::new("first".to_string()); - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account1, cosmos_hd_path, @@ -521,7 +510,7 @@ mod tests { // Trying to load it with wrong password now fails assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file, &id1, &bad_password), + load_existing_wallet_login_at_file(wallet_file, &id1, &bad_password), Err(BackendError::DecryptionError), )); } @@ -537,7 +526,7 @@ mod tests { let bad_password = UserPassword::new("bad-password".to_string()); let id1 = LoginId::new("first".to_string()); - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet_file.clone(), dummy_account1, cosmos_hd_path, @@ -548,7 +537,7 @@ mod tests { // Trying to load it with wrong password now fails assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file, &id1, &bad_password), + load_existing_wallet_login_at_file(wallet_file, &id1, &bad_password), Err(BackendError::DecryptionError), )); } @@ -564,7 +553,7 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account1, cosmos_hd_path, @@ -575,7 +564,7 @@ mod tests { // Trying to load with the wrong id assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file, &id2, &password), + load_existing_wallet_login_at_file(wallet_file, &id2, &password), Err(BackendError::NoSuchIdInWallet), )); } @@ -591,7 +580,7 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet_file.clone(), dummy_account1, cosmos_hd_path, @@ -602,7 +591,7 @@ mod tests { // Trying to load with the wrong id assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file, &id2, &password), + load_existing_wallet_login_at_file(wallet_file, &id2, &password), Err(BackendError::NoSuchIdInWallet), )); } @@ -617,7 +606,7 @@ mod tests { let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account1.clone(), cosmos_hd_path.clone(), @@ -626,8 +615,7 @@ mod tests { ) .unwrap(); - let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file, &id1, &password).unwrap(); + let loaded_account = load_existing_wallet_login_at_file(wallet_file, &id1, &password).unwrap(); let acc = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account1, acc.mnemonic()); assert_eq!(&cosmos_hd_path, acc.hd_path()); @@ -643,7 +631,7 @@ mod tests { let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet_file.clone(), dummy_account1.clone(), cosmos_hd_path.clone(), @@ -652,8 +640,7 @@ mod tests { ) .unwrap(); - let loaded_login = - load_existing_wallet_login_information_at_file(wallet_file, &id1, &password).unwrap(); + let loaded_login = load_existing_wallet_login_at_file(wallet_file, &id1, &password).unwrap(); let accounts = loaded_login.as_multiple_accounts().unwrap(); assert_eq!(accounts.len(), 1); let account = accounts @@ -679,7 +666,7 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account1, cosmos_hd_path.clone(), @@ -690,7 +677,7 @@ mod tests { // Can't store a second login if you use different password assert!(matches!( - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file, dummy_account2, cosmos_hd_path, @@ -716,7 +703,7 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet_file.clone(), dummy_account1, cosmos_hd_path.clone(), @@ -727,7 +714,7 @@ mod tests { // Can't store a second login if you use different password assert!(matches!( - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet_file, dummy_account2, cosmos_hd_path, @@ -754,7 +741,7 @@ mod tests { let id2 = LoginId::new("second".to_string()); // Store the first account - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account1, cosmos_hd_path, @@ -774,7 +761,7 @@ mod tests { let original_salt = encrypted_blob.salt().to_vec(); // Add an extra account - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account2, different_hd_path, @@ -811,7 +798,7 @@ mod tests { let id2 = LoginId::new("second".to_string()); // Store the first account - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account1.clone(), cosmos_hd_path.clone(), @@ -821,13 +808,13 @@ mod tests { .unwrap(); let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); let acc = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account1, acc.mnemonic()); assert_eq!(&cosmos_hd_path, acc.hd_path()); // Add an extra account - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account2.clone(), different_hd_path.clone(), @@ -838,13 +825,12 @@ mod tests { // first account should be unchanged let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account1, acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); - let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); + let loaded_account = load_existing_wallet_login_at_file(wallet_file, &id2, &password).unwrap(); let acc2 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account2, acc2.mnemonic()); assert_eq!(&different_hd_path, acc2.hd_path()); @@ -866,7 +852,7 @@ mod tests { let id2 = LoginId::new("second".to_string()); // Store the first account - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account1.clone(), cosmos_hd_path.clone(), @@ -876,13 +862,13 @@ mod tests { .unwrap(); let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); let acc = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account1, acc.mnemonic()); assert_eq!(&cosmos_hd_path, acc.hd_path()); // Add an extra account - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet_file.clone(), dummy_account2.clone(), different_hd_path.clone(), @@ -893,13 +879,12 @@ mod tests { // first account should be unchanged let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account1, acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); - let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); + let loaded_account = load_existing_wallet_login_at_file(wallet_file, &id2, &password).unwrap(); let acc2 = loaded_account.as_multiple_accounts().unwrap(); assert_eq!(acc2.len(), 1); let account = acc2 @@ -923,7 +908,7 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet_file.clone(), dummy_account1, cosmos_hd_path, @@ -934,7 +919,7 @@ mod tests { // Fails to delete non-existent id in the wallet assert!(matches!( - remove_wallet_login_information_at_file(wallet_file, &id2), + remove_wallet_login_at_file(wallet_file, &id2), Err(BackendError::NoSuchIdInWallet), )); } @@ -955,7 +940,7 @@ mod tests { let id2 = LoginId::new("second".to_string()); // Store two accounts with two different passwords - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account1.clone(), cosmos_hd_path.clone(), @@ -963,7 +948,7 @@ mod tests { &password, ) .unwrap(); - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account2.clone(), different_hd_path.clone(), @@ -974,43 +959,43 @@ mod tests { // Load and compare let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account1, acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); + load_existing_wallet_login_at_file(wallet_file.clone(), &id2, &password).unwrap(); let acc2 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account2, acc2.mnemonic()); assert_eq!(&different_hd_path, acc2.hd_path()); // Delete the second account - remove_wallet_login_information_at_file(wallet_file.clone(), &id2).unwrap(); + remove_wallet_login_at_file(wallet_file.clone(), &id2).unwrap(); // The first account should be unchanged let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account1, acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); // And we can't load the second one anymore assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password), + load_existing_wallet_login_at_file(wallet_file.clone(), &id2, &password), Err(BackendError::NoSuchIdInWallet), )); // Delete the first account assert!(wallet_file.exists()); - remove_wallet_login_information_at_file(wallet_file.clone(), &id1).unwrap(); + remove_wallet_login_at_file(wallet_file.clone(), &id1).unwrap(); // The file should now be removed assert!(!wallet_file.exists()); // And trying to load when the file is gone fails assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file, &id1, &password), + load_existing_wallet_login_at_file(wallet_file, &id1, &password), Err(BackendError::WalletFileNotFound), )); } @@ -1029,7 +1014,7 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account1.clone(), hd_path.clone(), @@ -1040,12 +1025,12 @@ mod tests { // Check that it's there as the correct non-multiple type let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(acc1.mnemonic(), &dummy_account1); assert_eq!(acc1.hd_path(), &hd_path); - append_account_to_wallet_login_information_at_file( + append_account_to_wallet_login_at_file( wallet_file.clone(), dummy_account2.clone(), hd_path.clone(), @@ -1056,8 +1041,7 @@ mod tests { .unwrap(); // Check that it is now multiple mnemonic type - let loaded_accounts = - load_existing_wallet_login_information_at_file(wallet_file, &id1, &password).unwrap(); + let loaded_accounts = load_existing_wallet_login_at_file(wallet_file, &id1, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ WalletAccount::new_mnemonic_backed_account(id1.into(), dummy_account1, hd_path.clone()), @@ -1085,7 +1069,7 @@ mod tests { let id3 = AccountId::new("third".to_string()); let id4 = AccountId::new("fourth".to_string()); - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account1.clone(), hd_path.clone(), @@ -1094,7 +1078,7 @@ mod tests { ) .unwrap(); - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account2.clone(), hd_path.clone(), @@ -1105,13 +1089,13 @@ mod tests { // Check that it's there as the correct non-multiple type let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); + load_existing_wallet_login_at_file(wallet_file.clone(), &id2, &password).unwrap(); let acc2 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(acc2.mnemonic(), &dummy_account2); assert_eq!(acc2.hd_path(), &hd_path); // Add a third and fourth mnenonic grouped together with the second one - append_account_to_wallet_login_information_at_file( + append_account_to_wallet_login_at_file( wallet_file.clone(), dummy_account3.clone(), hd_path.clone(), @@ -1120,7 +1104,7 @@ mod tests { &password, ) .unwrap(); - append_account_to_wallet_login_information_at_file( + append_account_to_wallet_login_at_file( wallet_file.clone(), dummy_account4.clone(), hd_path.clone(), @@ -1132,13 +1116,12 @@ mod tests { // Check that we can load all four let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(acc1.mnemonic(), &dummy_account1); assert_eq!(acc1.hd_path(), &hd_path); - let loaded_accounts = - load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); + let loaded_accounts = load_existing_wallet_login_at_file(wallet_file, &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ WalletAccount::new_mnemonic_backed_account(id2.into(), dummy_account2, hd_path.clone()), @@ -1167,7 +1150,7 @@ mod tests { let id3 = AccountId::new("third".to_string()); let id4 = AccountId::new("fourth".to_string()); - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet_file.clone(), dummy_account1.clone(), hd_path.clone(), @@ -1176,7 +1159,7 @@ mod tests { ) .unwrap(); - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet_file.clone(), dummy_account2.clone(), hd_path.clone(), @@ -1186,7 +1169,7 @@ mod tests { .unwrap(); // Add a third and fourth mnenonic grouped together with the second one - append_account_to_wallet_login_information_at_file( + append_account_to_wallet_login_at_file( wallet_file.clone(), dummy_account3.clone(), hd_path.clone(), @@ -1195,7 +1178,7 @@ mod tests { &password, ) .unwrap(); - append_account_to_wallet_login_information_at_file( + append_account_to_wallet_login_at_file( wallet_file.clone(), dummy_account4.clone(), hd_path.clone(), @@ -1207,7 +1190,7 @@ mod tests { // Check that we can load all four let loaded_login = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); let accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![WalletAccount::new_mnemonic_backed_account( DEFAULT_FIRST_ACCOUNT_NAME.into(), @@ -1217,8 +1200,7 @@ mod tests { .into(); assert_eq!(accounts, &expected); - let loaded_login = - load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); + let loaded_login = load_existing_wallet_login_at_file(wallet_file, &id2, &password).unwrap(); let accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ WalletAccount::new_mnemonic_backed_account( @@ -1247,7 +1229,7 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account1, cosmos_hd_path.clone(), @@ -1256,7 +1238,7 @@ mod tests { ) .unwrap(); - append_account_to_wallet_login_information_at_file( + append_account_to_wallet_login_at_file( wallet_file.clone(), dummy_account2, cosmos_hd_path, @@ -1288,7 +1270,7 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet_file.clone(), dummy_account1, cosmos_hd_path.clone(), @@ -1297,7 +1279,7 @@ mod tests { ) .unwrap(); - append_account_to_wallet_login_information_at_file( + append_account_to_wallet_login_at_file( wallet_file.clone(), dummy_account2, cosmos_hd_path, @@ -1331,7 +1313,7 @@ mod tests { let id2 = LoginId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account1, hd_path.clone(), @@ -1340,7 +1322,7 @@ mod tests { ) .unwrap(); - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet_file.clone(), dummy_account2.clone(), hd_path.clone(), @@ -1349,7 +1331,7 @@ mod tests { ) .unwrap(); - append_account_to_wallet_login_information_at_file( + append_account_to_wallet_login_at_file( wallet_file.clone(), dummy_account3.clone(), hd_path.clone(), @@ -1359,11 +1341,10 @@ mod tests { ) .unwrap(); - remove_wallet_login_information_at_file(wallet_file.clone(), &id1).unwrap(); + remove_wallet_login_at_file(wallet_file.clone(), &id1).unwrap(); // The second login one is still there - let loaded_accounts = - load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); + let loaded_accounts = load_existing_wallet_login_at_file(wallet_file, &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ WalletAccount::new_mnemonic_backed_account(id2.into(), dummy_account2, hd_path.clone()), @@ -1387,7 +1368,7 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet.clone(), dummy_account1, cosmos_hd_path.clone(), @@ -1396,7 +1377,7 @@ mod tests { ) .unwrap(); - append_account_to_wallet_login_information_at_file( + append_account_to_wallet_login_at_file( wallet.clone(), dummy_account2, cosmos_hd_path, @@ -1420,7 +1401,7 @@ mod tests { // And trying to load when the file is gone fails assert!(matches!( - load_existing_wallet_login_information_at_file(wallet, &id1, &password), + load_existing_wallet_login_at_file(wallet, &id1, &password), Err(BackendError::WalletFileNotFound), )); } @@ -1441,7 +1422,7 @@ mod tests { let id2 = AccountId::new("second".to_string()); let id3 = LoginId::new("third".to_string()); - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet.clone(), dummy_account1, cosmos_hd_path.clone(), @@ -1450,7 +1431,7 @@ mod tests { ) .unwrap(); - append_account_to_wallet_login_information_at_file( + append_account_to_wallet_login_at_file( wallet.clone(), dummy_account2, cosmos_hd_path.clone(), @@ -1460,7 +1441,7 @@ mod tests { ) .unwrap(); - store_wallet_login_multiple_information_at_file( + store_wallet_login_with_multiple_accounts_at_file( wallet.clone(), dummy_account3.clone(), cosmos_hd_path.clone(), @@ -1480,13 +1461,12 @@ mod tests { // And trying to load when the file is gone fails assert!(matches!( - load_existing_wallet_login_information_at_file(wallet.clone(), &id1, &password), + load_existing_wallet_login_at_file(wallet.clone(), &id1, &password), Err(BackendError::NoSuchIdInWallet), )); // The other login is still there - let loaded_account = - load_existing_wallet_login_information_at_file(wallet, &id3, &password).unwrap(); + let loaded_account = load_existing_wallet_login_at_file(wallet, &id3, &password).unwrap(); let acc3 = loaded_account.as_multiple_accounts().unwrap(); let expected = vec![WalletAccount::new_mnemonic_backed_account( DEFAULT_FIRST_ACCOUNT_NAME.into(), @@ -1515,7 +1495,7 @@ mod tests { let id3 = AccountId::new("third".to_string()); let id4 = AccountId::new("fourth".to_string()); - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet.clone(), dummy_account1.clone(), hd_path.clone(), @@ -1524,7 +1504,7 @@ mod tests { ) .unwrap(); - store_wallet_login_information_at_file( + store_wallet_login_at_file( wallet.clone(), dummy_account2.clone(), hd_path.clone(), @@ -1534,7 +1514,7 @@ mod tests { .unwrap(); // Add a third and fourth mnenonic grouped together with the second one - append_account_to_wallet_login_information_at_file( + append_account_to_wallet_login_at_file( wallet.clone(), dummy_account3, hd_path.clone(), @@ -1543,7 +1523,7 @@ mod tests { &password, ) .unwrap(); - append_account_to_wallet_login_information_at_file( + append_account_to_wallet_login_at_file( wallet.clone(), dummy_account4.clone(), hd_path.clone(), @@ -1558,7 +1538,7 @@ mod tests { // Check that we can still load the other accounts let loaded_accounts = - load_existing_wallet_login_information_at_file(wallet.clone(), &id2, &password).unwrap(); + load_existing_wallet_login_at_file(wallet.clone(), &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ WalletAccount::new_mnemonic_backed_account( @@ -1576,13 +1556,12 @@ mod tests { .unwrap(); remove_account_from_wallet_login_at_file(wallet.clone(), &id2, &id4, &password).unwrap(); assert!(matches!( - load_existing_wallet_login_information_at_file(wallet.clone(), &id2, &password), + load_existing_wallet_login_at_file(wallet.clone(), &id2, &password), Err(BackendError::NoSuchIdInWallet), )); // The first login is still available - let loaded_account = - load_existing_wallet_login_information_at_file(wallet, &id1, &password).unwrap(); + let loaded_account = load_existing_wallet_login_at_file(wallet, &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(acc1.mnemonic(), &dummy_account1); assert_eq!(acc1.hd_path(), &hd_path); From d2d99ca5c9454fc3da739b9bf0dca0ae02585203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 12 May 2022 14:42:40 +0200 Subject: [PATCH 74/85] wallet: additional tidy --- .../src/wallet_storage/account_data.rs | 152 ++++++++---------- .../src-tauri/src/wallet_storage/mod.rs | 53 +++--- 2 files changed, 89 insertions(+), 116 deletions(-) diff --git a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs index 148e088e5b..d49e067450 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -70,7 +70,6 @@ impl StoredWallet { .accounts .iter_mut() .find(|account| &account.id == id) - //.map(|account| &mut account.account) .ok_or(BackendError::NoSuchIdInWallet) } @@ -132,6 +131,19 @@ pub(crate) struct EncryptedLogin { pub account: EncryptedData, } +impl EncryptedLogin { + pub(crate) fn encrypt( + id: LoginId, + login: &StoredLogin, + password: &UserPassword, + ) -> Result { + Ok(EncryptedLogin { + id, + account: super::encryption::encrypt_struct(login, password)?, + }) + } +} + /// A stored login is either a account, such as a mnemonic, or a list of multiple accounts where /// each has an inner id. Future proofed for having private key backed accounts. #[derive(Serialize, Deserialize, Debug, Zeroize)] @@ -144,17 +156,6 @@ pub(crate) enum StoredLogin { } impl StoredLogin { - pub(crate) fn new_mnemonic_backed_account( - mnemonic: bip39::Mnemonic, - hd_path: DerivationPath, - ) -> Self { - Self::Mnemonic(MnemonicAccount { mnemonic, hd_path }) - } - - pub(crate) fn new_multiple_login() -> Self { - Self::Multiple(MultipleAccounts::empty()) - } - #[cfg(test)] pub(crate) fn as_mnemonic_account(&self) -> Option<&MnemonicAccount> { match self { @@ -171,64 +172,19 @@ impl StoredLogin { } } + // Return the login as multiple accounts, and if there is only a single mnemonic backed account, + // return a set containing only the single account paired with the account id passed as function + // argument. pub(crate) fn unwrap_into_multiple_accounts(self, id: AccountId) -> MultipleAccounts { match self { - StoredLogin::Mnemonic(ref account) => account.clone().into_multiple(id), + StoredLogin::Mnemonic(ref account) => { + vec![WalletAccount::from_mnemonic_account(id, account.clone())].into() + } StoredLogin::Multiple(ref accounts) => accounts.clone(), } } } -/// An account backed by a unique mnemonic. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -pub(crate) struct MnemonicAccount { - mnemonic: bip39::Mnemonic, - #[serde(with = "display_hd_path")] - hd_path: DerivationPath, -} - -impl MnemonicAccount { - pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { - &self.mnemonic - } - - pub(crate) fn hd_path(&self) -> &DerivationPath { - &self.hd_path - } - - pub(crate) fn into_wallet_account(self, id: AccountId) -> WalletAccount { - WalletAccount { - id, - account: self.into(), - } - } - - pub(crate) fn into_multiple(self, id: AccountId) -> MultipleAccounts { - MultipleAccounts::new(self.into_wallet_account(id)) - } -} - -impl Zeroize for MnemonicAccount { - fn zeroize(&mut self) { - // in ideal world, Mnemonic would have had zeroize defined on it (there's an almost year old PR that introduces it) - // and the memory would have been filled with zeroes. - // - // we really don't want to keep our real mnemonic in memory, so let's do the semi-nasty thing - // of overwriting it with a fresh mnemonic that was never used before - // - // note: this function can only fail on an invalid word count, which clearly is not the case here - self.mnemonic = bip39::Mnemonic::generate(self.mnemonic.word_count()).unwrap(); - - // further note: we don't really care about the hd_path, there's nothing secret about it. - } -} - -impl Drop for MnemonicAccount { - fn drop(&mut self) { - self.zeroize() - } -} - /// Multiple stored accounts, each entry having an id and a data field. #[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] pub(crate) struct MultipleAccounts { @@ -236,18 +192,12 @@ pub(crate) struct MultipleAccounts { } impl MultipleAccounts { - pub(crate) fn empty() -> Self { + pub(crate) fn new() -> Self { MultipleAccounts { accounts: Vec::new(), } } - pub(crate) fn new(account: WalletAccount) -> Self { - MultipleAccounts { - accounts: vec![account], - } - } - pub(crate) fn get_accounts(&self) -> impl Iterator { self.accounts.iter() } @@ -317,11 +267,20 @@ impl WalletAccount { ) -> Self { Self { id, - account: AccountData::new_mnemonic_backed_account(mnemonic, hd_path), + account: AccountData::Mnemonic(MnemonicAccount::new(mnemonic, hd_path)), + } + } + + pub(crate) fn from_mnemonic_account(id: AccountId, mnemonic_account: MnemonicAccount) -> Self { + Self { + id, + account: AccountData::Mnemonic(mnemonic_account), } } } +/// An account usually is a mnemonic account, but in the future it might be backed by a private +/// key. #[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] #[serde(untagged)] #[zeroize(drop)] @@ -331,13 +290,6 @@ pub(crate) enum AccountData { } impl AccountData { - pub(crate) fn new_mnemonic_backed_account( - mnemonic: bip39::Mnemonic, - hd_path: DerivationPath, - ) -> AccountData { - AccountData::Mnemonic(MnemonicAccount { mnemonic, hd_path }) - } - pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { match self { AccountData::Mnemonic(account) => account.mnemonic(), @@ -352,9 +304,47 @@ impl AccountData { } } -impl From for AccountData { - fn from(mnemonic_account: MnemonicAccount) -> Self { - AccountData::Mnemonic(mnemonic_account) +/// An account backed by a unique mnemonic. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub(crate) struct MnemonicAccount { + mnemonic: bip39::Mnemonic, + #[serde(with = "display_hd_path")] + hd_path: DerivationPath, +} + +impl MnemonicAccount { + pub(crate) fn new(mnemonic: bip39::Mnemonic, hd_path: DerivationPath) -> Self { + Self { mnemonic, hd_path } + } + + pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { + &self.mnemonic + } + + #[cfg(test)] + pub(crate) fn hd_path(&self) -> &DerivationPath { + &self.hd_path + } +} + +impl Zeroize for MnemonicAccount { + fn zeroize(&mut self) { + // in ideal world, Mnemonic would have had zeroize defined on it (there's an almost year old PR that introduces it) + // and the memory would have been filled with zeroes. + // + // we really don't want to keep our real mnemonic in memory, so let's do the semi-nasty thing + // of overwriting it with a fresh mnemonic that was never used before + // + // note: this function can only fail on an invalid word count, which clearly is not the case here + self.mnemonic = bip39::Mnemonic::generate(self.mnemonic.word_count()).unwrap(); + + // further note: we don't really care about the hd_path, there's nothing secret about it. + } +} + +impl Drop for MnemonicAccount { + fn drop(&mut self) { + self.zeroize() } } diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 636081e6eb..8aefd859ba 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -6,7 +6,6 @@ pub(crate) use crate::wallet_storage::password::{AccountId, LoginId, UserPasswor use crate::error::BackendError; use crate::platform_constants::{STORAGE_DIR_NAME, WALLET_INFO_FILENAME}; -use crate::wallet_storage::encryption::encrypt_struct; use cosmrs::bip32::DerivationPath; use std::fs::{self, create_dir_all, OpenOptions}; use std::path::PathBuf; @@ -108,12 +107,9 @@ fn store_wallet_login_at_file( return Err(BackendError::WalletDifferentPasswordDetected); } - let new_account = StoredLogin::new_mnemonic_backed_account(mnemonic, hd_path); - let new_encrypted_account = EncryptedLogin { - id, - account: encrypt_struct(&new_account, password)?, - }; - + let new_account = account_data::MnemonicAccount::new(mnemonic, hd_path); + let new_login = StoredLogin::Mnemonic(new_account); + let new_encrypted_account = EncryptedLogin::encrypt(id, &new_login, password)?; stored_wallet.add_encrypted_login(new_encrypted_account)?; let file = OpenOptions::new() @@ -157,18 +153,10 @@ fn store_wallet_login_with_multiple_accounts_at_file( return Err(BackendError::WalletDifferentPasswordDetected); } - let mut new_accounts = account_data::MultipleAccounts::empty(); - new_accounts.add( - DEFAULT_FIRST_ACCOUNT_NAME.to_string().into(), - mnemonic, - hd_path, - )?; - + let mut new_accounts = account_data::MultipleAccounts::new(); + new_accounts.add(DEFAULT_FIRST_ACCOUNT_NAME.into(), mnemonic, hd_path)?; let new_login = StoredLogin::Multiple(new_accounts); - let new_encrypted_login = EncryptedLogin { - id, - account: encrypt_struct(&new_login, password)?, - }; + let new_encrypted_login = EncryptedLogin::encrypt(id, &new_login, password)?; stored_wallet.add_encrypted_login(new_encrypted_login)?; @@ -222,10 +210,7 @@ fn append_account_to_wallet_login_at_file( let mut accounts = decrypted_login.unwrap_into_multiple_accounts(first_id_when_converting); accounts.add(inner_id, mnemonic, hd_path)?; - let encrypted_accounts = EncryptedLogin { - id, - account: encrypt_struct(&StoredLogin::Multiple(accounts), password)?, - }; + let encrypted_accounts = EncryptedLogin::encrypt(id, &StoredLogin::Multiple(accounts), password)?; stored_wallet.replace_encrypted_login(encrypted_accounts)?; @@ -305,6 +290,7 @@ fn remove_account_from_wallet_login_at_file( let mut decrypted_login = stored_wallet.decrypt_login(id, password)?; + // Remove the account let is_empty = match decrypted_login { StoredLogin::Mnemonic(_) => { log::warn!("Encountered mnemonic login instead of list of accounts, aborting"); @@ -316,19 +302,17 @@ fn remove_account_from_wallet_login_at_file( } }; + // Remove the login, or encrypt the new updated login if is_empty { stored_wallet .remove_encrypted_login(id) .ok_or(BackendError::NoSuchIdInWallet)?; } else { - // Replace the encrypted login with the pruned one. - let encrypted_accounts = EncryptedLogin { - id: id.clone(), - account: encrypt_struct(&decrypted_login, password)?, - }; + let encrypted_accounts = EncryptedLogin::encrypt(id.clone(), &decrypted_login, password)?; stored_wallet.replace_encrypted_login(encrypted_accounts)?; } + // Remove the file, or write the new file if stored_wallet.is_empty() { log::info!("Removing file: {:#?}", filepath); Ok(fs::remove_file(filepath)?) @@ -785,7 +769,7 @@ mod tests { #[test] fn store_two_mnemonic_accounts_using_two_logins() { let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let wallet = store_dir.path().join(WALLET_INFO_FILENAME); let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); @@ -799,7 +783,7 @@ mod tests { // Store the first account store_wallet_login_at_file( - wallet_file.clone(), + wallet.clone(), dummy_account1.clone(), cosmos_hd_path.clone(), id1.clone(), @@ -807,15 +791,14 @@ mod tests { ) .unwrap(); - let loaded_account = - load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let acc = loaded_account.as_mnemonic_account().unwrap(); + let login = load_existing_wallet_login_at_file(wallet.clone(), &id1, &password).unwrap(); + let acc = login.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account1, acc.mnemonic()); assert_eq!(&cosmos_hd_path, acc.hd_path()); // Add an extra account store_wallet_login_at_file( - wallet_file.clone(), + wallet.clone(), dummy_account2.clone(), different_hd_path.clone(), id2.clone(), @@ -825,12 +808,12 @@ mod tests { // first account should be unchanged let loaded_account = - load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + load_existing_wallet_login_at_file(wallet.clone(), &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account1, acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); - let loaded_account = load_existing_wallet_login_at_file(wallet_file, &id2, &password).unwrap(); + let loaded_account = load_existing_wallet_login_at_file(wallet, &id2, &password).unwrap(); let acc2 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account2, acc2.mnemonic()); assert_eq!(&different_hd_path, acc2.hd_path()); From 98cbd2509c85f957eac4ad5482592a95ec8c071d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 12 May 2022 15:24:38 +0200 Subject: [PATCH 75/85] wallet: more moving things around --- .../src/operations/mixnet/account.rs | 27 +++---- .../src/wallet_storage/account_data.rs | 64 +++++++--------- .../src-tauri/src/wallet_storage/mod.rs | 74 +++++++++++-------- 3 files changed, 80 insertions(+), 85 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index fea2ddbd7f..4663769e74 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -405,7 +405,6 @@ fn extract_first_mnemonic( .get_accounts() .next() .ok_or(BackendError::NoSuchIdInWalletLoginEntry)? - .account .mnemonic() .clone() } @@ -530,8 +529,8 @@ pub async fn list_accounts( let all_accounts = state .get_all_accounts() .map(|account| AccountEntry { - id: account.id.to_string(), - address: derive_address(account.account.mnemonic().clone(), prefix) + id: account.id().to_string(), + address: derive_address(account.mnemonic().clone(), prefix) .unwrap() .to_string(), }) @@ -564,7 +563,6 @@ pub fn show_mnemonic_for_account_in_password( accounts .get_account(&account_id) .ok_or(BackendError::NoSuchIdInWalletLoginEntry)? - .account .mnemonic() .clone() } @@ -583,9 +581,8 @@ pub async fn sign_in_decrypted_account( let state = state.read().await; let account = &state .get_all_accounts() - .find(|a| a.id.as_ref() == account_id) - .ok_or(BackendError::NoSuchIdInWalletLoginEntry)? - .account; + .find(|a| a.id().as_ref() == account_id) + .ok_or(BackendError::NoSuchIdInWalletLoginEntry)?; account.mnemonic().clone() }; @@ -602,7 +599,10 @@ mod tests { use std::path::PathBuf; - use crate::wallet_storage; + use crate::wallet_storage::{ + self, + account_data::{MnemonicAccount, WalletAccount}, + }; // This decryptes a stored wallet file using the same procedure as when signing in. Most tests // related to the encryped wallet storage is in `wallet_storage`. @@ -630,13 +630,10 @@ mod tests { assert_eq!( all_accounts, - vec![ - wallet_storage::account_data::WalletAccount::new_mnemonic_backed_account( - account_id, - expected_mnemonic, - hd_path - ) - ] + vec![WalletAccount::new( + account_id, + MnemonicAccount::new(expected_mnemonic, hd_path), + )] ); } diff --git a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs index d49e067450..caf1f7e4a7 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -177,9 +177,7 @@ impl StoredLogin { // argument. pub(crate) fn unwrap_into_multiple_accounts(self, id: AccountId) -> MultipleAccounts { match self { - StoredLogin::Mnemonic(ref account) => { - vec![WalletAccount::from_mnemonic_account(id, account.clone())].into() - } + StoredLogin::Mnemonic(ref account) => vec![WalletAccount::new(id, account.clone())].into(), StoredLogin::Multiple(ref accounts) => accounts.clone(), } } @@ -228,11 +226,10 @@ impl MultipleAccounts { if self.get_account(&id).is_some() { Err(BackendError::IdAlreadyExistsInStoredWalletLogin) } else { - self - .accounts - .push(WalletAccount::new_mnemonic_backed_account( - id, mnemonic, hd_path, - )); + self.accounts.push(WalletAccount::new( + id, + MnemonicAccount::new(mnemonic, hd_path), + )); Ok(()) } } @@ -255,28 +252,34 @@ impl From> for MultipleAccounts { /// An entry in the list of stored accounts #[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] pub(crate) struct WalletAccount { - pub id: AccountId, - pub account: AccountData, + id: AccountId, + account: AccountData, } impl WalletAccount { - pub(crate) fn new_mnemonic_backed_account( - id: AccountId, - mnemonic: bip39::Mnemonic, - hd_path: DerivationPath, - ) -> Self { - Self { - id, - account: AccountData::Mnemonic(MnemonicAccount::new(mnemonic, hd_path)), - } - } - - pub(crate) fn from_mnemonic_account(id: AccountId, mnemonic_account: MnemonicAccount) -> Self { + pub(crate) fn new(id: AccountId, mnemonic_account: MnemonicAccount) -> Self { Self { id, account: AccountData::Mnemonic(mnemonic_account), } } + + pub(crate) fn id(&self) -> &AccountId { + &self.id + } + + pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { + match self.account { + AccountData::Mnemonic(ref account) => account.mnemonic(), + } + } + + #[cfg(test)] + pub(crate) fn hd_path(&self) -> &DerivationPath { + match self.account { + AccountData::Mnemonic(ref account) => account.hd_path(), + } + } } /// An account usually is a mnemonic account, but in the future it might be backed by a private @@ -284,26 +287,11 @@ impl WalletAccount { #[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] #[serde(untagged)] #[zeroize(drop)] -pub(crate) enum AccountData { +enum AccountData { Mnemonic(MnemonicAccount), // PrivateKey(PrivateKeyAccount) } -impl AccountData { - pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { - match self { - AccountData::Mnemonic(account) => account.mnemonic(), - } - } - - #[cfg(test)] - pub(crate) fn hd_path(&self) -> &DerivationPath { - match self { - AccountData::Mnemonic(account) => account.hd_path(), - } - } -} - /// An account backed by a unique mnemonic. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct MnemonicAccount { diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 8aefd859ba..d9533b0d70 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -10,7 +10,9 @@ use cosmrs::bip32::DerivationPath; use std::fs::{self, create_dir_all, OpenOptions}; use std::path::PathBuf; -use self::account_data::{EncryptedLogin, StoredWallet}; +#[cfg(test)] +use self::account_data::MnemonicAccount; +use self::account_data::{EncryptedLogin, MultipleAccounts, StoredWallet}; pub(crate) mod account_data; pub(crate) mod encryption; @@ -107,7 +109,7 @@ fn store_wallet_login_at_file( return Err(BackendError::WalletDifferentPasswordDetected); } - let new_account = account_data::MnemonicAccount::new(mnemonic, hd_path); + let new_account = MnemonicAccount::new(mnemonic, hd_path); let new_login = StoredLogin::Mnemonic(new_account); let new_encrypted_account = EncryptedLogin::encrypt(id, &new_login, password)?; stored_wallet.add_encrypted_login(new_encrypted_account)?; @@ -153,7 +155,7 @@ fn store_wallet_login_with_multiple_accounts_at_file( return Err(BackendError::WalletDifferentPasswordDetected); } - let mut new_accounts = account_data::MultipleAccounts::new(); + let mut new_accounts = MultipleAccounts::new(); new_accounts.add(DEFAULT_FIRST_ACCOUNT_NAME.into(), mnemonic, hd_path)?; let new_login = StoredLogin::Multiple(new_accounts); let new_encrypted_login = EncryptedLogin::encrypt(id, &new_login, password)?; @@ -329,7 +331,7 @@ fn remove_account_from_wallet_login_at_file( #[cfg(test)] mod tests { - use crate::wallet_storage::account_data::WalletAccount; + use crate::wallet_storage::account_data::{MnemonicAccount, WalletAccount}; use super::*; use config::defaults::COSMOS_DERIVATION_PATH; @@ -630,9 +632,9 @@ mod tests { let account = accounts .get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into()) .unwrap(); - assert_eq!(account.id, DEFAULT_FIRST_ACCOUNT_NAME.into()); - assert_eq!(account.account.mnemonic(), &dummy_account1); - assert_eq!(account.account.hd_path(), &cosmos_hd_path); + assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME); + assert_eq!(account.mnemonic(), &dummy_account1); + assert_eq!(account.hd_path(), &cosmos_hd_path); } #[test] @@ -873,9 +875,9 @@ mod tests { let account = acc2 .get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into()) .unwrap(); - assert_eq!(account.id, DEFAULT_FIRST_ACCOUNT_NAME.into()); - assert_eq!(account.account.mnemonic(), &dummy_account2); - assert_eq!(account.account.hd_path(), &different_hd_path); + assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME); + assert_eq!(account.mnemonic(), &dummy_account2); + assert_eq!(account.hd_path(), &different_hd_path); } #[test] @@ -1027,8 +1029,11 @@ mod tests { let loaded_accounts = load_existing_wallet_login_at_file(wallet_file, &id1, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account(id1.into(), dummy_account1, hd_path.clone()), - WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, hd_path), + WalletAccount::new( + id1.into(), + MnemonicAccount::new(dummy_account1, hd_path.clone()), + ), + WalletAccount::new(id2, MnemonicAccount::new(dummy_account2, hd_path)), ] .into(); assert_eq!(accounts, &expected); @@ -1107,9 +1112,12 @@ mod tests { let loaded_accounts = load_existing_wallet_login_at_file(wallet_file, &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account(id2.into(), dummy_account2, hd_path.clone()), - WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, hd_path.clone()), - WalletAccount::new_mnemonic_backed_account(id4, dummy_account4, hd_path), + WalletAccount::new( + id2.into(), + MnemonicAccount::new(dummy_account2, hd_path.clone()), + ), + WalletAccount::new(id3, MnemonicAccount::new(dummy_account3, hd_path.clone())), + WalletAccount::new(id4, MnemonicAccount::new(dummy_account4, hd_path)), ] .into(); assert_eq!(accounts, &expected); @@ -1175,10 +1183,9 @@ mod tests { let loaded_login = load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); let accounts = loaded_login.as_multiple_accounts().unwrap(); - let expected = vec![WalletAccount::new_mnemonic_backed_account( + let expected = vec![WalletAccount::new( DEFAULT_FIRST_ACCOUNT_NAME.into(), - dummy_account1, - hd_path.clone(), + MnemonicAccount::new(dummy_account1, hd_path.clone()), )] .into(); assert_eq!(accounts, &expected); @@ -1186,13 +1193,12 @@ mod tests { let loaded_login = load_existing_wallet_login_at_file(wallet_file, &id2, &password).unwrap(); let accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account( + WalletAccount::new( DEFAULT_FIRST_ACCOUNT_NAME.into(), - dummy_account2, - hd_path.clone(), + MnemonicAccount::new(dummy_account2, hd_path.clone()), ), - WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, hd_path.clone()), - WalletAccount::new_mnemonic_backed_account(id4, dummy_account4, hd_path), + WalletAccount::new(id3, MnemonicAccount::new(dummy_account3, hd_path.clone())), + WalletAccount::new(id4, MnemonicAccount::new(dummy_account4, hd_path)), ] .into(); assert_eq!(accounts, &expected); @@ -1330,8 +1336,11 @@ mod tests { let loaded_accounts = load_existing_wallet_login_at_file(wallet_file, &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account(id2.into(), dummy_account2, hd_path.clone()), - WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, hd_path), + WalletAccount::new( + id2.into(), + MnemonicAccount::new(dummy_account2, hd_path.clone()), + ), + WalletAccount::new(id3, MnemonicAccount::new(dummy_account3, hd_path)), ] .into(); assert_eq!(accounts, &expected); @@ -1451,10 +1460,9 @@ mod tests { // The other login is still there let loaded_account = load_existing_wallet_login_at_file(wallet, &id3, &password).unwrap(); let acc3 = loaded_account.as_multiple_accounts().unwrap(); - let expected = vec![WalletAccount::new_mnemonic_backed_account( + let expected = vec![WalletAccount::new( DEFAULT_FIRST_ACCOUNT_NAME.into(), - dummy_account3, - cosmos_hd_path, + MnemonicAccount::new(dummy_account3, cosmos_hd_path), )] .into(); assert_eq!(acc3, &expected); @@ -1524,12 +1532,14 @@ mod tests { load_existing_wallet_login_at_file(wallet.clone(), &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new_mnemonic_backed_account( + WalletAccount::new( id2.clone().into(), - dummy_account2, - hd_path.clone(), + MnemonicAccount::new(dummy_account2, hd_path.clone()), + ), + WalletAccount::new( + id4.clone(), + MnemonicAccount::new(dummy_account4, hd_path.clone()), ), - WalletAccount::new_mnemonic_backed_account(id4.clone(), dummy_account4, hd_path.clone()), ] .into(); assert_eq!(accounts, &expected); From fa41fe62c4d0ef7053664aa4730013aa5626a6ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 12 May 2022 21:31:46 +0200 Subject: [PATCH 76/85] wallet: simplify naming --- .../src/operations/mixnet/account.rs | 18 +- .../src-tauri/src/wallet_storage/mod.rs | 717 ++++++++---------- 2 files changed, 344 insertions(+), 391 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 4663769e74..d9fdebdcff 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -372,7 +372,7 @@ pub fn create_password(mnemonic: &str, password: String) -> Result<(), BackendEr // Currently we only support a single, default, id in the wallet let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let password = wallet_storage::UserPassword::new(password); - wallet_storage::store_wallet_login_with_multiple_accounts(mnemonic, hd_path, id, &password) + wallet_storage::store_login_with_multiple_accounts(mnemonic, hd_path, id, &password) } #[tauri::command] @@ -385,7 +385,7 @@ pub async fn sign_in_with_password( // Currently we only support a single, default, id in the wallet let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let password = wallet_storage::UserPassword::new(password); - let stored_login = wallet_storage::load_existing_wallet_login(&id, &password)?; + let stored_login = wallet_storage::load_existing_login(&id, &password)?; let mnemonic = extract_first_mnemonic(&stored_login)?; let first_id_when_converting = id.into(); @@ -417,7 +417,7 @@ fn extract_first_mnemonic( pub fn remove_password() -> Result<(), BackendError> { log::info!("Removing password"); let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - wallet_storage::remove_wallet_login(&id) + wallet_storage::remove_login(&id) } #[tauri::command] @@ -435,7 +435,7 @@ pub async fn add_account_for_password( let inner_id = wallet_storage::AccountId::new(inner_id.to_string()); let password = wallet_storage::UserPassword::new(password.to_string()); - wallet_storage::append_account_to_wallet_login( + wallet_storage::append_account_to_login( mnemonic.clone(), hd_path, id.clone(), @@ -451,7 +451,7 @@ pub async fn add_account_for_password( // Re-read all the acccounts from the wallet to reset the state, rather than updating it // incrementally - let stored_login = wallet_storage::load_existing_wallet_login(&id, &password)?; + let stored_login = wallet_storage::load_existing_login(&id, &password)?; // NOTE: since we are appending, this id shouldn't be needed, but setting the state is supposed // to be a general function let first_id_when_converting = id.into(); @@ -495,10 +495,10 @@ pub async fn remove_account_for_password( let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let inner_id = wallet_storage::AccountId::new(inner_id.to_string()); let password = wallet_storage::UserPassword::new(password.to_string()); - wallet_storage::remove_account_from_wallet_login(&id, &inner_id, &password)?; + wallet_storage::remove_account_from_login(&id, &inner_id, &password)?; // Load to reset the internal state - let stored_login = wallet_storage::load_existing_wallet_login(&id, &password)?; + let stored_login = wallet_storage::load_existing_login(&id, &password)?; // NOTE: Since we removed from a multi-account login, this id shouldn't be needed, but setting // the state is supposed to be a general function let first_id_when_converting = id.into(); @@ -552,7 +552,7 @@ pub fn show_mnemonic_for_account_in_password( let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let account_id = wallet_storage::AccountId::new(account_id); let password = wallet_storage::UserPassword::new(password); - let stored_account = wallet_storage::load_existing_wallet_login(&id, &password)?; + let stored_account = wallet_storage::load_existing_login(&id, &password)?; let mnemonic = match stored_account { wallet_storage::StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), @@ -616,7 +616,7 @@ mod tests { let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let stored_login = - wallet_storage::load_existing_wallet_login_at_file(wallet_file, &login_id, &password) + wallet_storage::load_existing_login_at_file(wallet_file, &login_id, &password) .unwrap(); let mnemonic = extract_first_mnemonic(&stored_login).unwrap(); diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index d9533b0d70..3f1fa234c9 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -1,6 +1,14 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +/// The wallet storage file contains a set of logins, each with an associated login ID and an +/// encrypted field. Once decrypted, each login contains either an account, or a set of accounts. +/// One difference is that the latter have an associated account ID. +/// +/// Wallet +/// - Login +/// -- Account +/// --- Mnemonic pub(crate) use crate::wallet_storage::account_data::StoredLogin; pub(crate) use crate::wallet_storage::password::{AccountId, LoginId, UserPassword}; @@ -55,16 +63,16 @@ fn load_existing_wallet_at_file(filepath: PathBuf) -> Result Result { let store_dir = get_storage_directory()?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - load_existing_wallet_login_at_file(filepath, id, password) + load_existing_login_at_file(filepath, id, password) } -pub(crate) fn load_existing_wallet_login_at_file( +pub(crate) fn load_existing_login_at_file( filepath: PathBuf, id: &LoginId, password: &UserPassword, @@ -75,7 +83,7 @@ pub(crate) fn load_existing_wallet_login_at_file( // DEPRECATED: only used in tests, where it's used to test supporting older wallet formats #[allow(unused)] #[cfg(test)] -pub(crate) fn store_wallet_login( +pub(crate) fn store_login( mnemonic: bip39::Mnemonic, hd_path: DerivationPath, id: LoginId, @@ -86,12 +94,12 @@ pub(crate) fn store_wallet_login( create_dir_all(&store_dir)?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - store_wallet_login_at_file(filepath, mnemonic, hd_path, id, password) + store_login_at_file(filepath, mnemonic, hd_path, id, password) } // DEPRECATED: only used in tests, where it's used to test supporting older wallet formats #[cfg(test)] -fn store_wallet_login_at_file( +fn store_login_at_file( filepath: PathBuf, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, @@ -123,7 +131,7 @@ fn store_wallet_login_at_file( Ok(serde_json::to_writer_pretty(file, &stored_wallet)?) } -pub(crate) fn store_wallet_login_with_multiple_accounts( +pub(crate) fn store_login_with_multiple_accounts( mnemonic: bip39::Mnemonic, hd_path: DerivationPath, id: LoginId, @@ -134,10 +142,10 @@ pub(crate) fn store_wallet_login_with_multiple_accounts( create_dir_all(&store_dir)?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - store_wallet_login_with_multiple_accounts_at_file(filepath, mnemonic, hd_path, id, password) + store_login_with_multiple_accounts_at_file(filepath, mnemonic, hd_path, id, password) } -fn store_wallet_login_with_multiple_accounts_at_file( +fn store_login_with_multiple_accounts_at_file( filepath: PathBuf, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, @@ -175,7 +183,7 @@ fn store_wallet_login_with_multiple_accounts_at_file( /// If the existing top-level entry is just a single account, it will be converted to the first /// account in the list of accounts associated with the encrypted entry. The inner id for this /// entry will be set to the same as the outer, unencrypted, id. -pub(crate) fn append_account_to_wallet_login( +pub(crate) fn append_account_to_login( mnemonic: bip39::Mnemonic, hd_path: DerivationPath, id: LoginId, @@ -187,10 +195,10 @@ pub(crate) fn append_account_to_wallet_login( create_dir_all(&store_dir)?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - append_account_to_wallet_login_at_file(filepath, mnemonic, hd_path, id, inner_id, password) + append_account_to_login_at_file(filepath, mnemonic, hd_path, id, inner_id, password) } -fn append_account_to_wallet_login_at_file( +fn append_account_to_login_at_file( filepath: PathBuf, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, @@ -228,13 +236,13 @@ fn append_account_to_wallet_login_at_file( /// Remove the entire encrypted login entry for the given `id`. This means potentially removing all /// associated accounts! /// If this was the last entry in the file, the file is removed. -pub(crate) fn remove_wallet_login(id: &LoginId) -> Result<(), BackendError> { +pub(crate) fn remove_login(id: &LoginId) -> Result<(), BackendError> { let store_dir = get_storage_directory()?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - remove_wallet_login_at_file(filepath, id) + remove_login_at_file(filepath, id) } -fn remove_wallet_login_at_file(filepath: PathBuf, id: &LoginId) -> Result<(), BackendError> { +fn remove_login_at_file(filepath: PathBuf, id: &LoginId) -> Result<(), BackendError> { log::warn!("Removing wallet account with id: {id}. This includes all associated accounts!"); let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) { Err(BackendError::WalletFileNotFound) => StoredWallet::default(), @@ -268,17 +276,17 @@ fn remove_wallet_login_at_file(filepath: PathBuf, id: &LoginId) -> Result<(), Ba /// - If the encrypted login is just a single account, abort to be on the safe side. /// - If it is the last associated account with that login, the encrypted login will be removed. /// - If this was the last encrypted login in the file, it will be removed. -pub(crate) fn remove_account_from_wallet_login( +pub(crate) fn remove_account_from_login( id: &LoginId, inner_id: &AccountId, password: &UserPassword, ) -> Result<(), BackendError> { let store_dir = get_storage_directory()?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - remove_account_from_wallet_login_at_file(filepath, id, inner_id, password) + remove_account_from_login_at_file(filepath, id, inner_id, password) } -fn remove_account_from_wallet_login_at_file( +fn remove_account_from_login_at_file( filepath: PathBuf, id: &LoginId, inner_id: &AccountId, @@ -350,10 +358,10 @@ mod tests { Err(BackendError::WalletFileNotFound), )); assert!(matches!( - load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password), + load_existing_login_at_file(wallet_file.clone(), &id1, &password), Err(BackendError::WalletFileNotFound), )); - remove_wallet_login_at_file(wallet_file, &id1).unwrap_err(); + remove_login_at_file(wallet_file, &id1).unwrap_err(); } #[test] @@ -361,15 +369,15 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); - store_wallet_login_at_file( + store_login_at_file( wallet_file.clone(), - dummy_account1, - cosmos_hd_path, + account1, + hd_path, id1.clone(), &password, ) @@ -390,14 +398,14 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); - store_wallet_login_with_multiple_accounts_at_file( + store_login_with_multiple_accounts_at_file( wallet_file.clone(), - dummy_account1, + account1, cosmos_hd_path, id1.clone(), &password, @@ -419,16 +427,16 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); // Store the first login - store_wallet_login_at_file( + store_login_at_file( wallet_file.clone(), - dummy_account1.clone(), - cosmos_hd_path.clone(), + account1.clone(), + hd_path.clone(), id1.clone(), &password, ) @@ -436,7 +444,7 @@ mod tests { // and storing the same id again fails assert!(matches!( - store_wallet_login_at_file(wallet_file, dummy_account1, cosmos_hd_path, id1, &password,), + store_login_at_file(wallet_file, account1, hd_path, id1, &password,), Err(BackendError::IdAlreadyExistsInWallet), )); } @@ -446,16 +454,16 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); // Store the first login - store_wallet_login_with_multiple_accounts_at_file( + store_login_with_multiple_accounts_at_file( wallet_file.clone(), - dummy_account1.clone(), - cosmos_hd_path.clone(), + account1.clone(), + hd_path.clone(), id1.clone(), &password, ) @@ -463,13 +471,7 @@ mod tests { // and storing the same id again fails assert!(matches!( - store_wallet_login_with_multiple_accounts_at_file( - wallet_file, - dummy_account1, - cosmos_hd_path, - id1, - &password, - ), + store_login_with_multiple_accounts_at_file(wallet_file, account1, hd_path, id1, &password,), Err(BackendError::IdAlreadyExistsInWallet), )); } @@ -479,16 +481,16 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); let id1 = LoginId::new("first".to_string()); - store_wallet_login_at_file( + store_login_at_file( wallet_file.clone(), - dummy_account1, - cosmos_hd_path, + account1, + hd_path, id1.clone(), &password, ) @@ -496,7 +498,7 @@ mod tests { // Trying to load it with wrong password now fails assert!(matches!( - load_existing_wallet_login_at_file(wallet_file, &id1, &bad_password), + load_existing_login_at_file(wallet_file, &id1, &bad_password), Err(BackendError::DecryptionError), )); } @@ -506,16 +508,16 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); let id1 = LoginId::new("first".to_string()); - store_wallet_login_with_multiple_accounts_at_file( + store_login_with_multiple_accounts_at_file( wallet_file.clone(), - dummy_account1, - cosmos_hd_path, + account1, + hd_path, id1.clone(), &password, ) @@ -523,7 +525,7 @@ mod tests { // Trying to load it with wrong password now fails assert!(matches!( - load_existing_wallet_login_at_file(wallet_file, &id1, &bad_password), + load_existing_login_at_file(wallet_file, &id1, &bad_password), Err(BackendError::DecryptionError), )); } @@ -533,24 +535,17 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); - store_wallet_login_at_file( - wallet_file.clone(), - dummy_account1, - cosmos_hd_path, - id1, - &password, - ) - .unwrap(); + store_login_at_file(wallet_file.clone(), account1, hd_path, id1, &password).unwrap(); // Trying to load with the wrong id assert!(matches!( - load_existing_wallet_login_at_file(wallet_file, &id2, &password), + load_existing_login_at_file(wallet_file, &id2, &password), Err(BackendError::NoSuchIdInWallet), )); } @@ -560,16 +555,16 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); - store_wallet_login_with_multiple_accounts_at_file( + store_login_with_multiple_accounts_at_file( wallet_file.clone(), - dummy_account1, - cosmos_hd_path, + account1, + hd_path, id1, &password, ) @@ -577,7 +572,7 @@ mod tests { // Trying to load with the wrong id assert!(matches!( - load_existing_wallet_login_at_file(wallet_file, &id2, &password), + load_existing_login_at_file(wallet_file, &id2, &password), Err(BackendError::NoSuchIdInWallet), )); } @@ -587,24 +582,24 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); - store_wallet_login_at_file( + store_login_at_file( wallet_file.clone(), - dummy_account1.clone(), - cosmos_hd_path.clone(), + account1.clone(), + hd_path.clone(), id1.clone(), &password, ) .unwrap(); - let loaded_account = load_existing_wallet_login_at_file(wallet_file, &id1, &password).unwrap(); - let acc = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(&dummy_account1, acc.mnemonic()); - assert_eq!(&cosmos_hd_path, acc.hd_path()); + let loaded_login = load_existing_login_at_file(wallet_file, &id1, &password).unwrap(); + let acc = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account1, acc.mnemonic()); + assert_eq!(&hd_path, acc.hd_path()); } #[test] @@ -612,29 +607,29 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let acc1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); - store_wallet_login_with_multiple_accounts_at_file( + store_login_with_multiple_accounts_at_file( wallet_file.clone(), - dummy_account1.clone(), - cosmos_hd_path.clone(), + acc1.clone(), + hd_path.clone(), id1.clone(), &password, ) .unwrap(); - let loaded_login = load_existing_wallet_login_at_file(wallet_file, &id1, &password).unwrap(); + let loaded_login = load_existing_login_at_file(wallet_file, &id1, &password).unwrap(); let accounts = loaded_login.as_multiple_accounts().unwrap(); assert_eq!(accounts.len(), 1); let account = accounts .get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into()) .unwrap(); assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME); - assert_eq!(account.mnemonic(), &dummy_account1); - assert_eq!(account.hd_path(), &cosmos_hd_path); + assert_eq!(account.mnemonic(), &acc1); + assert_eq!(account.hd_path(), &hd_path); } #[test] @@ -642,8 +637,8 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -652,9 +647,9 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); - store_wallet_login_at_file( + store_login_at_file( wallet_file.clone(), - dummy_account1, + account1, cosmos_hd_path.clone(), id1, &password, @@ -663,13 +658,7 @@ mod tests { // Can't store a second login if you use different password assert!(matches!( - store_wallet_login_at_file( - wallet_file, - dummy_account2, - cosmos_hd_path, - id2, - &bad_password - ), + store_login_at_file(wallet_file, account2, cosmos_hd_path, id2, &bad_password), Err(BackendError::WalletDifferentPasswordDetected), )); } @@ -679,9 +668,9 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); @@ -689,10 +678,10 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); - store_wallet_login_with_multiple_accounts_at_file( + store_login_with_multiple_accounts_at_file( wallet_file.clone(), - dummy_account1, - cosmos_hd_path.clone(), + account1, + hd_path.clone(), id1, &password, ) @@ -700,10 +689,10 @@ mod tests { // Can't store a second login if you use different password assert!(matches!( - store_wallet_login_with_multiple_accounts_at_file( + store_login_with_multiple_accounts_at_file( wallet_file, - dummy_account2, - cosmos_hd_path, + account2, + hd_path, id2, &bad_password ), @@ -716,9 +705,9 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -727,14 +716,7 @@ mod tests { let id2 = LoginId::new("second".to_string()); // Store the first account - store_wallet_login_at_file( - wallet_file.clone(), - dummy_account1, - cosmos_hd_path, - id1, - &password, - ) - .unwrap(); + store_login_at_file(wallet_file.clone(), account1, hd_path, id1, &password).unwrap(); let stored_wallet = load_existing_wallet_at_file(wallet_file.clone()).unwrap(); let encrypted_blob = &stored_wallet @@ -747,9 +729,9 @@ mod tests { let original_salt = encrypted_blob.salt().to_vec(); // Add an extra account - store_wallet_login_at_file( + store_login_at_file( wallet_file.clone(), - dummy_account2, + account2, different_hd_path, id2, &password, @@ -773,8 +755,8 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); @@ -784,24 +766,24 @@ mod tests { let id2 = LoginId::new("second".to_string()); // Store the first account - store_wallet_login_at_file( + store_login_at_file( wallet.clone(), - dummy_account1.clone(), + account1.clone(), cosmos_hd_path.clone(), id1.clone(), &password, ) .unwrap(); - let login = load_existing_wallet_login_at_file(wallet.clone(), &id1, &password).unwrap(); + let login = load_existing_login_at_file(wallet.clone(), &id1, &password).unwrap(); let acc = login.as_mnemonic_account().unwrap(); - assert_eq!(&dummy_account1, acc.mnemonic()); + assert_eq!(&account1, acc.mnemonic()); assert_eq!(&cosmos_hd_path, acc.hd_path()); // Add an extra account - store_wallet_login_at_file( + store_login_at_file( wallet.clone(), - dummy_account2.clone(), + account2.clone(), different_hd_path.clone(), id2.clone(), &password, @@ -809,15 +791,14 @@ mod tests { .unwrap(); // first account should be unchanged - let loaded_account = - load_existing_wallet_login_at_file(wallet.clone(), &id1, &password).unwrap(); - let acc1 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(&dummy_account1, acc1.mnemonic()); + let loaded_login = load_existing_login_at_file(wallet.clone(), &id1, &password).unwrap(); + let acc1 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account1, acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); - let loaded_account = load_existing_wallet_login_at_file(wallet, &id2, &password).unwrap(); - let acc2 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(&dummy_account2, acc2.mnemonic()); + let loaded_login = load_existing_login_at_file(wallet, &id2, &password).unwrap(); + let acc2 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account2, acc2.mnemonic()); assert_eq!(&different_hd_path, acc2.hd_path()); } @@ -826,9 +807,9 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -837,25 +818,24 @@ mod tests { let id2 = LoginId::new("second".to_string()); // Store the first account - store_wallet_login_at_file( + store_login_at_file( wallet_file.clone(), - dummy_account1.clone(), - cosmos_hd_path.clone(), + account1.clone(), + hd_path.clone(), id1.clone(), &password, ) .unwrap(); - let loaded_account = - load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let acc = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(&dummy_account1, acc.mnemonic()); - assert_eq!(&cosmos_hd_path, acc.hd_path()); + let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account1, acc.mnemonic()); + assert_eq!(&hd_path, acc.hd_path()); // Add an extra account - store_wallet_login_with_multiple_accounts_at_file( + store_login_with_multiple_accounts_at_file( wallet_file.clone(), - dummy_account2.clone(), + account2.clone(), different_hd_path.clone(), id2.clone(), &password, @@ -863,20 +843,19 @@ mod tests { .unwrap(); // first account should be unchanged - let loaded_account = - load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let acc1 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(&dummy_account1, acc1.mnemonic()); - assert_eq!(&cosmos_hd_path, acc1.hd_path()); + let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc1 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account1, acc1.mnemonic()); + assert_eq!(&hd_path, acc1.hd_path()); - let loaded_account = load_existing_wallet_login_at_file(wallet_file, &id2, &password).unwrap(); - let acc2 = loaded_account.as_multiple_accounts().unwrap(); + let loaded_login = load_existing_login_at_file(wallet_file, &id2, &password).unwrap(); + let acc2 = loaded_login.as_multiple_accounts().unwrap(); assert_eq!(acc2.len(), 1); let account = acc2 .get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into()) .unwrap(); assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME); - assert_eq!(account.mnemonic(), &dummy_account2); + assert_eq!(account.mnemonic(), &account2); assert_eq!(account.hd_path(), &different_hd_path); } @@ -885,18 +864,18 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); - store_wallet_login_with_multiple_accounts_at_file( + store_login_with_multiple_accounts_at_file( wallet_file.clone(), - dummy_account1, - cosmos_hd_path, + account1, + hd_path, id1, &password, ) @@ -904,7 +883,7 @@ mod tests { // Fails to delete non-existent id in the wallet assert!(matches!( - remove_wallet_login_at_file(wallet_file, &id2), + remove_login_at_file(wallet_file, &id2), Err(BackendError::NoSuchIdInWallet), )); } @@ -914,8 +893,8 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); @@ -925,17 +904,17 @@ mod tests { let id2 = LoginId::new("second".to_string()); // Store two accounts with two different passwords - store_wallet_login_at_file( + store_login_at_file( wallet_file.clone(), - dummy_account1.clone(), + account1.clone(), cosmos_hd_path.clone(), id1.clone(), &password, ) .unwrap(); - store_wallet_login_at_file( + store_login_at_file( wallet_file.clone(), - dummy_account2.clone(), + account2.clone(), different_hd_path.clone(), id2.clone(), &password, @@ -943,44 +922,41 @@ mod tests { .unwrap(); // Load and compare - let loaded_account = - load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let acc1 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(&dummy_account1, acc1.mnemonic()); + let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc1 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account1, acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); - let loaded_account = - load_existing_wallet_login_at_file(wallet_file.clone(), &id2, &password).unwrap(); - let acc2 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(&dummy_account2, acc2.mnemonic()); + let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id2, &password).unwrap(); + let acc2 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account2, acc2.mnemonic()); assert_eq!(&different_hd_path, acc2.hd_path()); // Delete the second account - remove_wallet_login_at_file(wallet_file.clone(), &id2).unwrap(); + remove_login_at_file(wallet_file.clone(), &id2).unwrap(); // The first account should be unchanged - let loaded_account = - load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let acc1 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(&dummy_account1, acc1.mnemonic()); + let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc1 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(&account1, acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); // And we can't load the second one anymore assert!(matches!( - load_existing_wallet_login_at_file(wallet_file.clone(), &id2, &password), + load_existing_login_at_file(wallet_file.clone(), &id2, &password), Err(BackendError::NoSuchIdInWallet), )); // Delete the first account assert!(wallet_file.exists()); - remove_wallet_login_at_file(wallet_file.clone(), &id1).unwrap(); + remove_login_at_file(wallet_file.clone(), &id1).unwrap(); // The file should now be removed assert!(!wallet_file.exists()); // And trying to load when the file is gone fails assert!(matches!( - load_existing_wallet_login_at_file(wallet_file, &id1, &password), + load_existing_login_at_file(wallet_file, &id1, &password), Err(BackendError::WalletFileNotFound), )); } @@ -990,8 +966,8 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -999,9 +975,9 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - store_wallet_login_at_file( + store_login_at_file( wallet_file.clone(), - dummy_account1.clone(), + account1.clone(), hd_path.clone(), id1.clone(), &password, @@ -1009,15 +985,14 @@ mod tests { .unwrap(); // Check that it's there as the correct non-multiple type - let loaded_account = - load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let acc1 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(acc1.mnemonic(), &dummy_account1); - assert_eq!(acc1.hd_path(), &hd_path); + let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(acc.mnemonic(), &account1); + assert_eq!(acc.hd_path(), &hd_path); - append_account_to_wallet_login_at_file( + append_account_to_login_at_file( wallet_file.clone(), - dummy_account2.clone(), + account2.clone(), hd_path.clone(), id1.clone(), id2.clone(), @@ -1026,17 +1001,14 @@ mod tests { .unwrap(); // Check that it is now multiple mnemonic type - let loaded_accounts = load_existing_wallet_login_at_file(wallet_file, &id1, &password).unwrap(); - let accounts = loaded_accounts.as_multiple_accounts().unwrap(); + let loaded_login = load_existing_login_at_file(wallet_file, &id1, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new( - id1.into(), - MnemonicAccount::new(dummy_account1, hd_path.clone()), - ), - WalletAccount::new(id2, MnemonicAccount::new(dummy_account2, hd_path)), + WalletAccount::new(id1.into(), MnemonicAccount::new(account1, hd_path.clone())), + WalletAccount::new(id2, MnemonicAccount::new(account2, hd_path)), ] .into(); - assert_eq!(accounts, &expected); + assert_eq!(loaded_accounts, &expected); } #[test] @@ -1044,10 +1016,10 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account4 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account3 = bip39::Mnemonic::generate(24).unwrap(); + let account4 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -1057,18 +1029,18 @@ mod tests { let id3 = AccountId::new("third".to_string()); let id4 = AccountId::new("fourth".to_string()); - store_wallet_login_at_file( + store_login_at_file( wallet_file.clone(), - dummy_account1.clone(), + account1.clone(), hd_path.clone(), id1.clone(), &password, ) .unwrap(); - store_wallet_login_at_file( + store_login_at_file( wallet_file.clone(), - dummy_account2.clone(), + account2.clone(), hd_path.clone(), id2.clone(), &password, @@ -1076,25 +1048,24 @@ mod tests { .unwrap(); // Check that it's there as the correct non-multiple type - let loaded_account = - load_existing_wallet_login_at_file(wallet_file.clone(), &id2, &password).unwrap(); - let acc2 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(acc2.mnemonic(), &dummy_account2); + let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id2, &password).unwrap(); + let acc2 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(acc2.mnemonic(), &account2); assert_eq!(acc2.hd_path(), &hd_path); // Add a third and fourth mnenonic grouped together with the second one - append_account_to_wallet_login_at_file( + append_account_to_login_at_file( wallet_file.clone(), - dummy_account3.clone(), + account3.clone(), hd_path.clone(), id2.clone(), id3.clone(), &password, ) .unwrap(); - append_account_to_wallet_login_at_file( + append_account_to_login_at_file( wallet_file.clone(), - dummy_account4.clone(), + account4.clone(), hd_path.clone(), id2.clone(), id4.clone(), @@ -1103,24 +1074,20 @@ mod tests { .unwrap(); // Check that we can load all four - let loaded_account = - load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let acc1 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(acc1.mnemonic(), &dummy_account1); + let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc1 = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(acc1.mnemonic(), &account1); assert_eq!(acc1.hd_path(), &hd_path); - let loaded_accounts = load_existing_wallet_login_at_file(wallet_file, &id2, &password).unwrap(); - let accounts = loaded_accounts.as_multiple_accounts().unwrap(); + let loaded_login = load_existing_login_at_file(wallet_file, &id2, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new( - id2.into(), - MnemonicAccount::new(dummy_account2, hd_path.clone()), - ), - WalletAccount::new(id3, MnemonicAccount::new(dummy_account3, hd_path.clone())), - WalletAccount::new(id4, MnemonicAccount::new(dummy_account4, hd_path)), + WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())), + WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path.clone())), + WalletAccount::new(id4, MnemonicAccount::new(account4, hd_path)), ] .into(); - assert_eq!(accounts, &expected); + assert_eq!(loaded_accounts, &expected); } #[test] @@ -1128,10 +1095,10 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account4 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account3 = bip39::Mnemonic::generate(24).unwrap(); + let account4 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -1141,18 +1108,18 @@ mod tests { let id3 = AccountId::new("third".to_string()); let id4 = AccountId::new("fourth".to_string()); - store_wallet_login_with_multiple_accounts_at_file( + store_login_with_multiple_accounts_at_file( wallet_file.clone(), - dummy_account1.clone(), + account1.clone(), hd_path.clone(), id1.clone(), &password, ) .unwrap(); - store_wallet_login_with_multiple_accounts_at_file( + store_login_with_multiple_accounts_at_file( wallet_file.clone(), - dummy_account2.clone(), + account2.clone(), hd_path.clone(), id2.clone(), &password, @@ -1160,18 +1127,18 @@ mod tests { .unwrap(); // Add a third and fourth mnenonic grouped together with the second one - append_account_to_wallet_login_at_file( + append_account_to_login_at_file( wallet_file.clone(), - dummy_account3.clone(), + account3.clone(), hd_path.clone(), id2.clone(), id3.clone(), &password, ) .unwrap(); - append_account_to_wallet_login_at_file( + append_account_to_login_at_file( wallet_file.clone(), - dummy_account4.clone(), + account4.clone(), hd_path.clone(), id2.clone(), id4.clone(), @@ -1180,67 +1147,66 @@ mod tests { .unwrap(); // Check that we can load all four - let loaded_login = - load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let accounts = loaded_login.as_multiple_accounts().unwrap(); + let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![WalletAccount::new( DEFAULT_FIRST_ACCOUNT_NAME.into(), - MnemonicAccount::new(dummy_account1, hd_path.clone()), + MnemonicAccount::new(account1, hd_path.clone()), )] .into(); - assert_eq!(accounts, &expected); + assert_eq!(loaded_accounts, &expected); - let loaded_login = load_existing_wallet_login_at_file(wallet_file, &id2, &password).unwrap(); - let accounts = loaded_login.as_multiple_accounts().unwrap(); + let loaded_login = load_existing_login_at_file(wallet_file, &id2, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ WalletAccount::new( DEFAULT_FIRST_ACCOUNT_NAME.into(), - MnemonicAccount::new(dummy_account2, hd_path.clone()), + MnemonicAccount::new(account2, hd_path.clone()), ), - WalletAccount::new(id3, MnemonicAccount::new(dummy_account3, hd_path.clone())), - WalletAccount::new(id4, MnemonicAccount::new(dummy_account4, hd_path)), + WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path.clone())), + WalletAccount::new(id4, MnemonicAccount::new(account4, hd_path)), ] .into(); - assert_eq!(accounts, &expected); + assert_eq!(loaded_accounts, &expected); } #[test] fn delete_the_same_account_twice_for_a_login_fails() { let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - store_wallet_login_at_file( - wallet_file.clone(), - dummy_account1, - cosmos_hd_path.clone(), + store_login_at_file( + wallet.clone(), + account1, + hd_path.clone(), id1.clone(), &password, ) .unwrap(); - append_account_to_wallet_login_at_file( - wallet_file.clone(), - dummy_account2, - cosmos_hd_path, + append_account_to_login_at_file( + wallet.clone(), + account2, + hd_path, id1.clone(), id2.clone(), &password, ) .unwrap(); - remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id2, &password).unwrap(); + remove_account_from_login_at_file(wallet.clone(), &id1, &id2, &password).unwrap(); assert!(matches!( - remove_account_from_wallet_login_at_file(wallet_file, &id1, &id2, &password), + remove_account_from_login_at_file(wallet, &id1, &id2, &password), Err(BackendError::NoSuchIdInWalletLoginEntry), )); } @@ -1250,38 +1216,38 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - store_wallet_login_with_multiple_accounts_at_file( + store_login_with_multiple_accounts_at_file( wallet_file.clone(), - dummy_account1, - cosmos_hd_path.clone(), + account1, + hd_path.clone(), id1.clone(), &password, ) .unwrap(); - append_account_to_wallet_login_at_file( + append_account_to_login_at_file( wallet_file.clone(), - dummy_account2, - cosmos_hd_path, + account2, + hd_path, id1.clone(), id2.clone(), &password, ) .unwrap(); - remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id2, &password).unwrap(); + remove_account_from_login_at_file(wallet_file.clone(), &id1, &id2, &password).unwrap(); assert!(matches!( - remove_account_from_wallet_login_at_file(wallet_file, &id1, &id2, &password), + remove_account_from_login_at_file(wallet_file, &id1, &id2, &password), Err(BackendError::NoSuchIdInWalletLoginEntry), )); } @@ -1291,9 +1257,9 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account3 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -1302,27 +1268,27 @@ mod tests { let id2 = LoginId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); - store_wallet_login_at_file( + store_login_at_file( wallet_file.clone(), - dummy_account1, + account1, hd_path.clone(), id1.clone(), &password, ) .unwrap(); - store_wallet_login_at_file( + store_login_at_file( wallet_file.clone(), - dummy_account2.clone(), + account2.clone(), hd_path.clone(), id2.clone(), &password, ) .unwrap(); - append_account_to_wallet_login_at_file( + append_account_to_login_at_file( wallet_file.clone(), - dummy_account3.clone(), + account3.clone(), hd_path.clone(), id2.clone(), id3.clone(), @@ -1330,20 +1296,17 @@ mod tests { ) .unwrap(); - remove_wallet_login_at_file(wallet_file.clone(), &id1).unwrap(); + remove_login_at_file(wallet_file.clone(), &id1).unwrap(); // The second login one is still there - let loaded_accounts = load_existing_wallet_login_at_file(wallet_file, &id2, &password).unwrap(); - let accounts = loaded_accounts.as_multiple_accounts().unwrap(); + let loaded_login = load_existing_login_at_file(wallet_file, &id2, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ - WalletAccount::new( - id2.into(), - MnemonicAccount::new(dummy_account2, hd_path.clone()), - ), - WalletAccount::new(id3, MnemonicAccount::new(dummy_account3, hd_path)), + WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())), + WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path)), ] .into(); - assert_eq!(accounts, &expected); + assert_eq!(loaded_accounts, &expected); } #[test] @@ -1351,49 +1314,49 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - store_wallet_login_with_multiple_accounts_at_file( + store_login_with_multiple_accounts_at_file( wallet.clone(), - dummy_account1, - cosmos_hd_path.clone(), + account1, + hd_path.clone(), id1.clone(), &password, ) .unwrap(); - append_account_to_wallet_login_at_file( + append_account_to_login_at_file( wallet.clone(), - dummy_account2, - cosmos_hd_path, + account2, + hd_path, id1.clone(), id2.clone(), &password, ) .unwrap(); - remove_account_from_wallet_login_at_file( + remove_account_from_login_at_file( wallet.clone(), &id1, &DEFAULT_FIRST_ACCOUNT_NAME.into(), &password, ) .unwrap(); - remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &id2, &password).unwrap(); + remove_account_from_login_at_file(wallet.clone(), &id1, &id2, &password).unwrap(); // The file should now be removed assert!(!wallet.exists()); // And trying to load when the file is gone fails assert!(matches!( - load_existing_wallet_login_at_file(wallet, &id1, &password), + load_existing_login_at_file(wallet, &id1, &password), Err(BackendError::WalletFileNotFound), )); } @@ -1403,10 +1366,10 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let account1 = bip39::Mnemonic::generate(24).unwrap(); + let account2 = bip39::Mnemonic::generate(24).unwrap(); + let account3 = bip39::Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -1414,55 +1377,55 @@ mod tests { let id2 = AccountId::new("second".to_string()); let id3 = LoginId::new("third".to_string()); - store_wallet_login_with_multiple_accounts_at_file( + store_login_with_multiple_accounts_at_file( wallet.clone(), - dummy_account1, - cosmos_hd_path.clone(), + account1, + hd_path.clone(), id1.clone(), &password, ) .unwrap(); - append_account_to_wallet_login_at_file( + append_account_to_login_at_file( wallet.clone(), - dummy_account2, - cosmos_hd_path.clone(), + account2, + hd_path.clone(), id1.clone(), id2.clone(), &password, ) .unwrap(); - store_wallet_login_with_multiple_accounts_at_file( + store_login_with_multiple_accounts_at_file( wallet.clone(), - dummy_account3.clone(), - cosmos_hd_path.clone(), + account3.clone(), + hd_path.clone(), id3.clone(), &password, ) .unwrap(); - remove_account_from_wallet_login_at_file( + remove_account_from_login_at_file( wallet.clone(), &id1, &DEFAULT_FIRST_ACCOUNT_NAME.into(), &password, ) .unwrap(); - remove_account_from_wallet_login_at_file(wallet.clone(), &id1, &id2, &password).unwrap(); + remove_account_from_login_at_file(wallet.clone(), &id1, &id2, &password).unwrap(); // And trying to load when the file is gone fails assert!(matches!( - load_existing_wallet_login_at_file(wallet.clone(), &id1, &password), + load_existing_login_at_file(wallet.clone(), &id1, &password), Err(BackendError::NoSuchIdInWallet), )); // The other login is still there - let loaded_account = load_existing_wallet_login_at_file(wallet, &id3, &password).unwrap(); - let acc3 = loaded_account.as_multiple_accounts().unwrap(); + let loaded_login = load_existing_login_at_file(wallet, &id3, &password).unwrap(); + let acc3 = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![WalletAccount::new( DEFAULT_FIRST_ACCOUNT_NAME.into(), - MnemonicAccount::new(dummy_account3, cosmos_hd_path), + MnemonicAccount::new(account3, hd_path), )] .into(); assert_eq!(acc3, &expected); @@ -1473,10 +1436,10 @@ mod tests { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account4 = bip39::Mnemonic::generate(24).unwrap(); + let acc1 = bip39::Mnemonic::generate(24).unwrap(); + let acc2 = bip39::Mnemonic::generate(24).unwrap(); + let acc3 = bip39::Mnemonic::generate(24).unwrap(); + let acc4 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -1486,18 +1449,18 @@ mod tests { let id3 = AccountId::new("third".to_string()); let id4 = AccountId::new("fourth".to_string()); - store_wallet_login_at_file( + store_login_at_file( wallet.clone(), - dummy_account1.clone(), + acc1.clone(), hd_path.clone(), id1.clone(), &password, ) .unwrap(); - store_wallet_login_at_file( + store_login_at_file( wallet.clone(), - dummy_account2.clone(), + acc2.clone(), hd_path.clone(), id2.clone(), &password, @@ -1505,18 +1468,18 @@ mod tests { .unwrap(); // Add a third and fourth mnenonic grouped together with the second one - append_account_to_wallet_login_at_file( + append_account_to_login_at_file( wallet.clone(), - dummy_account3, + acc3, hd_path.clone(), id2.clone(), id3.clone(), &password, ) .unwrap(); - append_account_to_wallet_login_at_file( + append_account_to_login_at_file( wallet.clone(), - dummy_account4.clone(), + acc4.clone(), hd_path.clone(), id2.clone(), id4.clone(), @@ -1525,39 +1488,35 @@ mod tests { .unwrap(); // Delete the third mnemonic, from the second login entry - remove_account_from_wallet_login_at_file(wallet.clone(), &id2, &id3, &password).unwrap(); + remove_account_from_login_at_file(wallet.clone(), &id2, &id3, &password).unwrap(); // Check that we can still load the other accounts - let loaded_accounts = - load_existing_wallet_login_at_file(wallet.clone(), &id2, &password).unwrap(); - let accounts = loaded_accounts.as_multiple_accounts().unwrap(); + let loaded_login = load_existing_login_at_file(wallet.clone(), &id2, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ WalletAccount::new( id2.clone().into(), - MnemonicAccount::new(dummy_account2, hd_path.clone()), - ), - WalletAccount::new( - id4.clone(), - MnemonicAccount::new(dummy_account4, hd_path.clone()), + MnemonicAccount::new(acc2, hd_path.clone()), ), + WalletAccount::new(id4.clone(), MnemonicAccount::new(acc4, hd_path.clone())), ] .into(); - assert_eq!(accounts, &expected); + assert_eq!(loaded_accounts, &expected); // Delete the second and fourth mnemonic from the second login entry removes the login entry - remove_account_from_wallet_login_at_file(wallet.clone(), &id2, &id2.clone().into(), &password) + remove_account_from_login_at_file(wallet.clone(), &id2, &id2.clone().into(), &password) .unwrap(); - remove_account_from_wallet_login_at_file(wallet.clone(), &id2, &id4, &password).unwrap(); + remove_account_from_login_at_file(wallet.clone(), &id2, &id4, &password).unwrap(); assert!(matches!( - load_existing_wallet_login_at_file(wallet.clone(), &id2, &password), + load_existing_login_at_file(wallet.clone(), &id2, &password), Err(BackendError::NoSuchIdInWallet), )); // The first login is still available - let loaded_account = load_existing_wallet_login_at_file(wallet, &id1, &password).unwrap(); - let acc1 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(acc1.mnemonic(), &dummy_account1); - assert_eq!(acc1.hd_path(), &hd_path); + let loaded_login = load_existing_login_at_file(wallet, &id1, &password).unwrap(); + let account = loaded_login.as_mnemonic_account().unwrap(); + assert_eq!(account.mnemonic(), &acc1); + assert_eq!(account.hd_path(), &hd_path); } // Test to that decrypts a stored file from the repo, to make sure we are able to decrypt stored @@ -1569,7 +1528,7 @@ mod tests { let wallet = load_existing_wallet_at_file(wallet_file).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); let id1 = LoginId::new("first".to_string()); @@ -1578,32 +1537,26 @@ mod tests { assert!(!wallet.password_can_decrypt_all(&bad_password)); assert!(wallet.password_can_decrypt_all(&password)); - let account1 = wallet.decrypt_login(&id1, &password).unwrap(); - let account2 = wallet.decrypt_login(&id2, &password).unwrap(); + let acc1 = wallet.decrypt_login(&id1, &password).unwrap(); + let acc2 = wallet.decrypt_login(&id2, &password).unwrap(); - assert!(matches!(account1, StoredLogin::Mnemonic(_))); - assert!(matches!(account2, StoredLogin::Mnemonic(_))); + assert!(matches!(acc1, StoredLogin::Mnemonic(_))); + assert!(matches!(acc2, StoredLogin::Mnemonic(_))); - let expected_account1 = bip39::Mnemonic::from_str("country mean universe text phone begin deputy reject result good cram illness common cluster proud swamp digital patrol spread bar face december base kick").unwrap(); - let expected_account2 = bip39::Mnemonic::from_str("home mansion start quiz dress decide hint second dragon sunny juice always steak real minimum art rival skin draw total pulp foot goddess agent").unwrap(); + let expected_acc1 = bip39::Mnemonic::from_str("country mean universe text phone begin deputy reject result good cram illness common cluster proud swamp digital patrol spread bar face december base kick").unwrap(); + let expected_acc2 = bip39::Mnemonic::from_str("home mansion start quiz dress decide hint second dragon sunny juice always steak real minimum art rival skin draw total pulp foot goddess agent").unwrap(); assert_eq!( - account1.as_mnemonic_account().unwrap().mnemonic(), - &expected_account1 - ); - assert_eq!( - account1.as_mnemonic_account().unwrap().hd_path(), - &cosmos_hd_path, + acc1.as_mnemonic_account().unwrap().mnemonic(), + &expected_acc1 ); + assert_eq!(acc1.as_mnemonic_account().unwrap().hd_path(), &hd_path,); assert_eq!( - account2.as_mnemonic_account().unwrap().mnemonic(), - &expected_account2 - ); - assert_eq!( - account2.as_mnemonic_account().unwrap().hd_path(), - &cosmos_hd_path, + acc2.as_mnemonic_account().unwrap().mnemonic(), + &expected_acc2 ); + assert_eq!(acc2.as_mnemonic_account().unwrap().hd_path(), &hd_path,); } #[test] From 1d22f35c82a2503e82bd44b2f62ed37fea21e618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 12 May 2022 22:05:40 +0200 Subject: [PATCH 77/85] wallet: pass by ref --- .../src/operations/mixnet/account.rs | 3 +- .../src-tauri/src/wallet_storage/mod.rs | 307 ++++++++---------- 2 files changed, 129 insertions(+), 181 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index d9fdebdcff..379ebe13f4 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -616,8 +616,7 @@ mod tests { let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let stored_login = - wallet_storage::load_existing_login_at_file(wallet_file, &login_id, &password) - .unwrap(); + wallet_storage::load_existing_login_at_file(&wallet_file, &login_id, &password).unwrap(); let mnemonic = extract_first_mnemonic(&stored_login).unwrap(); let expected_mnemonic = bip39::Mnemonic::from_str("country mean universe text phone begin deputy reject result good cram illness common cluster proud swamp digital patrol spread bar face december base kick").unwrap(); diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 3f1fa234c9..6ace4c9247 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -49,10 +49,10 @@ pub(crate) fn wallet_login_filepath() -> Result { pub(crate) fn load_existing_wallet() -> Result { let store_dir = get_storage_directory()?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - load_existing_wallet_at_file(filepath) + load_existing_wallet_at_file(&filepath) } -fn load_existing_wallet_at_file(filepath: PathBuf) -> Result { +fn load_existing_wallet_at_file(filepath: &PathBuf) -> Result { if !filepath.exists() { return Err(BackendError::WalletFileNotFound); } @@ -69,11 +69,11 @@ pub(crate) fn load_existing_login( ) -> Result { let store_dir = get_storage_directory()?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - load_existing_login_at_file(filepath, id, password) + load_existing_login_at_file(&filepath, id, password) } pub(crate) fn load_existing_login_at_file( - filepath: PathBuf, + filepath: &PathBuf, id: &LoginId, password: &UserPassword, ) -> Result { @@ -94,19 +94,19 @@ pub(crate) fn store_login( create_dir_all(&store_dir)?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - store_login_at_file(filepath, mnemonic, hd_path, id, password) + store_login_at_file(&filepath, mnemonic, hd_path, id, password) } // DEPRECATED: only used in tests, where it's used to test supporting older wallet formats #[cfg(test)] fn store_login_at_file( - filepath: PathBuf, + filepath: &PathBuf, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, id: LoginId, password: &UserPassword, ) -> Result<(), BackendError> { - let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) { + let mut stored_wallet = match load_existing_wallet_at_file(filepath) { Err(BackendError::WalletFileNotFound) => StoredWallet::default(), result => result?, }; @@ -142,17 +142,17 @@ pub(crate) fn store_login_with_multiple_accounts( create_dir_all(&store_dir)?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - store_login_with_multiple_accounts_at_file(filepath, mnemonic, hd_path, id, password) + store_login_with_multiple_accounts_at_file(&filepath, mnemonic, hd_path, id, password) } fn store_login_with_multiple_accounts_at_file( - filepath: PathBuf, + filepath: &PathBuf, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, id: LoginId, password: &UserPassword, ) -> Result<(), BackendError> { - let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) { + let mut stored_wallet = match load_existing_wallet_at_file(filepath) { Err(BackendError::WalletFileNotFound) => StoredWallet::default(), result => result?, }; @@ -195,18 +195,18 @@ pub(crate) fn append_account_to_login( create_dir_all(&store_dir)?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - append_account_to_login_at_file(filepath, mnemonic, hd_path, id, inner_id, password) + append_account_to_login_at_file(&filepath, mnemonic, hd_path, id, inner_id, password) } fn append_account_to_login_at_file( - filepath: PathBuf, + filepath: &PathBuf, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, id: LoginId, inner_id: AccountId, password: &UserPassword, ) -> Result<(), BackendError> { - let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) { + let mut stored_wallet = match load_existing_wallet_at_file(filepath) { Err(BackendError::WalletFileNotFound) => StoredWallet::default(), result => result?, }; @@ -239,12 +239,12 @@ fn append_account_to_login_at_file( pub(crate) fn remove_login(id: &LoginId) -> Result<(), BackendError> { let store_dir = get_storage_directory()?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - remove_login_at_file(filepath, id) + remove_login_at_file(&filepath, id) } -fn remove_login_at_file(filepath: PathBuf, id: &LoginId) -> Result<(), BackendError> { +fn remove_login_at_file(filepath: &PathBuf, id: &LoginId) -> Result<(), BackendError> { log::warn!("Removing wallet account with id: {id}. This includes all associated accounts!"); - let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) { + let mut stored_wallet = match load_existing_wallet_at_file(filepath) { Err(BackendError::WalletFileNotFound) => StoredWallet::default(), result => result?, }; @@ -283,17 +283,17 @@ pub(crate) fn remove_account_from_login( ) -> Result<(), BackendError> { let store_dir = get_storage_directory()?; let filepath = store_dir.join(WALLET_INFO_FILENAME); - remove_account_from_login_at_file(filepath, id, inner_id, password) + remove_account_from_login_at_file(&filepath, id, inner_id, password) } fn remove_account_from_login_at_file( - filepath: PathBuf, + filepath: &PathBuf, id: &LoginId, inner_id: &AccountId, password: &UserPassword, ) -> Result<(), BackendError> { log::info!("Removing associated account from login account: {id}"); - let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) { + let mut stored_wallet = match load_existing_wallet_at_file(filepath) { Err(BackendError::WalletFileNotFound) => StoredWallet::default(), result => result?, }; @@ -354,14 +354,14 @@ mod tests { let password = UserPassword::new("password".to_string()); assert!(matches!( - load_existing_wallet_at_file(wallet_file.clone()), + load_existing_wallet_at_file(&wallet_file), Err(BackendError::WalletFileNotFound), )); assert!(matches!( - load_existing_login_at_file(wallet_file.clone(), &id1, &password), + load_existing_login_at_file(&wallet_file, &id1, &password), Err(BackendError::WalletFileNotFound), )); - remove_login_at_file(wallet_file, &id1).unwrap_err(); + remove_login_at_file(&wallet_file, &id1).unwrap_err(); } #[test] @@ -374,16 +374,9 @@ mod tests { let password = UserPassword::new("password".to_string()); let id1 = LoginId::new("first".to_string()); - store_login_at_file( - wallet_file.clone(), - account1, - hd_path, - id1.clone(), - &password, - ) - .unwrap(); + store_login_at_file(&wallet_file, account1, hd_path, id1.clone(), &password).unwrap(); - let stored_wallet = load_existing_wallet_at_file(wallet_file).unwrap(); + let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); assert_eq!(stored_wallet.len(), 1); let login = stored_wallet.get_encrypted_login_by_index(0).unwrap(); @@ -404,7 +397,7 @@ mod tests { let id1 = LoginId::new("first".to_string()); store_login_with_multiple_accounts_at_file( - wallet_file.clone(), + &wallet_file, account1, cosmos_hd_path, id1.clone(), @@ -412,7 +405,7 @@ mod tests { ) .unwrap(); - let stored_wallet = load_existing_wallet_at_file(wallet_file).unwrap(); + let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); assert_eq!(stored_wallet.len(), 1); let login = stored_wallet.get_encrypted_login_by_index(0).unwrap(); @@ -434,7 +427,7 @@ mod tests { // Store the first login store_login_at_file( - wallet_file.clone(), + &wallet_file, account1.clone(), hd_path.clone(), id1.clone(), @@ -444,7 +437,7 @@ mod tests { // and storing the same id again fails assert!(matches!( - store_login_at_file(wallet_file, account1, hd_path, id1, &password,), + store_login_at_file(&wallet_file, account1, hd_path, id1, &password,), Err(BackendError::IdAlreadyExistsInWallet), )); } @@ -461,7 +454,7 @@ mod tests { // Store the first login store_login_with_multiple_accounts_at_file( - wallet_file.clone(), + &wallet_file, account1.clone(), hd_path.clone(), id1.clone(), @@ -471,7 +464,7 @@ mod tests { // and storing the same id again fails assert!(matches!( - store_login_with_multiple_accounts_at_file(wallet_file, account1, hd_path, id1, &password,), + store_login_with_multiple_accounts_at_file(&wallet_file, account1, hd_path, id1, &password,), Err(BackendError::IdAlreadyExistsInWallet), )); } @@ -487,18 +480,11 @@ mod tests { let bad_password = UserPassword::new("bad-password".to_string()); let id1 = LoginId::new("first".to_string()); - store_login_at_file( - wallet_file.clone(), - account1, - hd_path, - id1.clone(), - &password, - ) - .unwrap(); + store_login_at_file(&wallet_file, account1, hd_path, id1.clone(), &password).unwrap(); // Trying to load it with wrong password now fails assert!(matches!( - load_existing_login_at_file(wallet_file, &id1, &bad_password), + load_existing_login_at_file(&wallet_file, &id1, &bad_password), Err(BackendError::DecryptionError), )); } @@ -515,7 +501,7 @@ mod tests { let id1 = LoginId::new("first".to_string()); store_login_with_multiple_accounts_at_file( - wallet_file.clone(), + &wallet_file, account1, hd_path, id1.clone(), @@ -525,7 +511,7 @@ mod tests { // Trying to load it with wrong password now fails assert!(matches!( - load_existing_login_at_file(wallet_file, &id1, &bad_password), + load_existing_login_at_file(&wallet_file, &id1, &bad_password), Err(BackendError::DecryptionError), )); } @@ -541,11 +527,11 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); - store_login_at_file(wallet_file.clone(), account1, hd_path, id1, &password).unwrap(); + store_login_at_file(&wallet_file, account1, hd_path, id1, &password).unwrap(); // Trying to load with the wrong id assert!(matches!( - load_existing_login_at_file(wallet_file, &id2, &password), + load_existing_login_at_file(&wallet_file, &id2, &password), Err(BackendError::NoSuchIdInWallet), )); } @@ -561,18 +547,12 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); - store_login_with_multiple_accounts_at_file( - wallet_file.clone(), - account1, - hd_path, - id1, - &password, - ) - .unwrap(); + store_login_with_multiple_accounts_at_file(&wallet_file, account1, hd_path, id1, &password) + .unwrap(); // Trying to load with the wrong id assert!(matches!( - load_existing_login_at_file(wallet_file, &id2, &password), + load_existing_login_at_file(&wallet_file, &id2, &password), Err(BackendError::NoSuchIdInWallet), )); } @@ -588,7 +568,7 @@ mod tests { let id1 = LoginId::new("first".to_string()); store_login_at_file( - wallet_file.clone(), + &wallet_file, account1.clone(), hd_path.clone(), id1.clone(), @@ -596,7 +576,7 @@ mod tests { ) .unwrap(); - let loaded_login = load_existing_login_at_file(wallet_file, &id1, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc = loaded_login.as_mnemonic_account().unwrap(); assert_eq!(&account1, acc.mnemonic()); assert_eq!(&hd_path, acc.hd_path()); @@ -613,7 +593,7 @@ mod tests { let id1 = LoginId::new("first".to_string()); store_login_with_multiple_accounts_at_file( - wallet_file.clone(), + &wallet_file, acc1.clone(), hd_path.clone(), id1.clone(), @@ -621,7 +601,7 @@ mod tests { ) .unwrap(); - let loaded_login = load_existing_login_at_file(wallet_file, &id1, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let accounts = loaded_login.as_multiple_accounts().unwrap(); assert_eq!(accounts.len(), 1); let account = accounts @@ -648,7 +628,7 @@ mod tests { let id2 = LoginId::new("second".to_string()); store_login_at_file( - wallet_file.clone(), + &wallet_file, account1, cosmos_hd_path.clone(), id1, @@ -658,7 +638,7 @@ mod tests { // Can't store a second login if you use different password assert!(matches!( - store_login_at_file(wallet_file, account2, cosmos_hd_path, id2, &bad_password), + store_login_at_file(&wallet_file, account2, cosmos_hd_path, id2, &bad_password), Err(BackendError::WalletDifferentPasswordDetected), )); } @@ -679,7 +659,7 @@ mod tests { let id2 = LoginId::new("second".to_string()); store_login_with_multiple_accounts_at_file( - wallet_file.clone(), + &wallet_file, account1, hd_path.clone(), id1, @@ -690,7 +670,7 @@ mod tests { // Can't store a second login if you use different password assert!(matches!( store_login_with_multiple_accounts_at_file( - wallet_file, + &wallet_file, account2, hd_path, id2, @@ -716,9 +696,9 @@ mod tests { let id2 = LoginId::new("second".to_string()); // Store the first account - store_login_at_file(wallet_file.clone(), account1, hd_path, id1, &password).unwrap(); + store_login_at_file(&wallet_file, account1, hd_path, id1, &password).unwrap(); - let stored_wallet = load_existing_wallet_at_file(wallet_file.clone()).unwrap(); + let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); let encrypted_blob = &stored_wallet .get_encrypted_login_by_index(0) .unwrap() @@ -729,16 +709,9 @@ mod tests { let original_salt = encrypted_blob.salt().to_vec(); // Add an extra account - store_login_at_file( - wallet_file.clone(), - account2, - different_hd_path, - id2, - &password, - ) - .unwrap(); + store_login_at_file(&wallet_file, account2, different_hd_path, id2, &password).unwrap(); - let loaded_accounts = load_existing_wallet_at_file(wallet_file).unwrap(); + let loaded_accounts = load_existing_wallet_at_file(&wallet_file).unwrap(); assert_eq!(loaded_accounts.len(), 2); let encrypted_blob = &loaded_accounts .get_encrypted_login_by_index(1) @@ -767,7 +740,7 @@ mod tests { // Store the first account store_login_at_file( - wallet.clone(), + &wallet, account1.clone(), cosmos_hd_path.clone(), id1.clone(), @@ -775,14 +748,14 @@ mod tests { ) .unwrap(); - let login = load_existing_login_at_file(wallet.clone(), &id1, &password).unwrap(); + let login = load_existing_login_at_file(&wallet, &id1, &password).unwrap(); let acc = login.as_mnemonic_account().unwrap(); assert_eq!(&account1, acc.mnemonic()); assert_eq!(&cosmos_hd_path, acc.hd_path()); // Add an extra account store_login_at_file( - wallet.clone(), + &wallet, account2.clone(), different_hd_path.clone(), id2.clone(), @@ -791,12 +764,12 @@ mod tests { .unwrap(); // first account should be unchanged - let loaded_login = load_existing_login_at_file(wallet.clone(), &id1, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet, &id1, &password).unwrap(); let acc1 = loaded_login.as_mnemonic_account().unwrap(); assert_eq!(&account1, acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); - let loaded_login = load_existing_login_at_file(wallet, &id2, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet, &id2, &password).unwrap(); let acc2 = loaded_login.as_mnemonic_account().unwrap(); assert_eq!(&account2, acc2.mnemonic()); assert_eq!(&different_hd_path, acc2.hd_path()); @@ -819,7 +792,7 @@ mod tests { // Store the first account store_login_at_file( - wallet_file.clone(), + &wallet_file, account1.clone(), hd_path.clone(), id1.clone(), @@ -827,14 +800,14 @@ mod tests { ) .unwrap(); - let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc = loaded_login.as_mnemonic_account().unwrap(); assert_eq!(&account1, acc.mnemonic()); assert_eq!(&hd_path, acc.hd_path()); // Add an extra account store_login_with_multiple_accounts_at_file( - wallet_file.clone(), + &wallet_file, account2.clone(), different_hd_path.clone(), id2.clone(), @@ -843,12 +816,12 @@ mod tests { .unwrap(); // first account should be unchanged - let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc1 = loaded_login.as_mnemonic_account().unwrap(); assert_eq!(&account1, acc1.mnemonic()); assert_eq!(&hd_path, acc1.hd_path()); - let loaded_login = load_existing_login_at_file(wallet_file, &id2, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); let acc2 = loaded_login.as_multiple_accounts().unwrap(); assert_eq!(acc2.len(), 1); let account = acc2 @@ -872,18 +845,12 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); - store_login_with_multiple_accounts_at_file( - wallet_file.clone(), - account1, - hd_path, - id1, - &password, - ) - .unwrap(); + store_login_with_multiple_accounts_at_file(&wallet_file, account1, hd_path, id1, &password) + .unwrap(); // Fails to delete non-existent id in the wallet assert!(matches!( - remove_login_at_file(wallet_file, &id2), + remove_login_at_file(&wallet_file, &id2), Err(BackendError::NoSuchIdInWallet), )); } @@ -905,7 +872,7 @@ mod tests { // Store two accounts with two different passwords store_login_at_file( - wallet_file.clone(), + &wallet_file, account1.clone(), cosmos_hd_path.clone(), id1.clone(), @@ -913,7 +880,7 @@ mod tests { ) .unwrap(); store_login_at_file( - wallet_file.clone(), + &wallet_file, account2.clone(), different_hd_path.clone(), id2.clone(), @@ -922,41 +889,41 @@ mod tests { .unwrap(); // Load and compare - let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc1 = loaded_login.as_mnemonic_account().unwrap(); assert_eq!(&account1, acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); - let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id2, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); let acc2 = loaded_login.as_mnemonic_account().unwrap(); assert_eq!(&account2, acc2.mnemonic()); assert_eq!(&different_hd_path, acc2.hd_path()); // Delete the second account - remove_login_at_file(wallet_file.clone(), &id2).unwrap(); + remove_login_at_file(&wallet_file, &id2).unwrap(); // The first account should be unchanged - let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc1 = loaded_login.as_mnemonic_account().unwrap(); assert_eq!(&account1, acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); // And we can't load the second one anymore assert!(matches!( - load_existing_login_at_file(wallet_file.clone(), &id2, &password), + load_existing_login_at_file(&wallet_file, &id2, &password), Err(BackendError::NoSuchIdInWallet), )); // Delete the first account assert!(wallet_file.exists()); - remove_login_at_file(wallet_file.clone(), &id1).unwrap(); + remove_login_at_file(&wallet_file, &id1).unwrap(); // The file should now be removed assert!(!wallet_file.exists()); // And trying to load when the file is gone fails assert!(matches!( - load_existing_login_at_file(wallet_file, &id1, &password), + load_existing_login_at_file(&wallet_file, &id1, &password), Err(BackendError::WalletFileNotFound), )); } @@ -976,7 +943,7 @@ mod tests { let id2 = AccountId::new("second".to_string()); store_login_at_file( - wallet_file.clone(), + &wallet_file, account1.clone(), hd_path.clone(), id1.clone(), @@ -985,13 +952,13 @@ mod tests { .unwrap(); // Check that it's there as the correct non-multiple type - let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc = loaded_login.as_mnemonic_account().unwrap(); assert_eq!(acc.mnemonic(), &account1); assert_eq!(acc.hd_path(), &hd_path); append_account_to_login_at_file( - wallet_file.clone(), + &wallet_file, account2.clone(), hd_path.clone(), id1.clone(), @@ -1001,7 +968,7 @@ mod tests { .unwrap(); // Check that it is now multiple mnemonic type - let loaded_login = load_existing_login_at_file(wallet_file, &id1, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ WalletAccount::new(id1.into(), MnemonicAccount::new(account1, hd_path.clone())), @@ -1030,7 +997,7 @@ mod tests { let id4 = AccountId::new("fourth".to_string()); store_login_at_file( - wallet_file.clone(), + &wallet_file, account1.clone(), hd_path.clone(), id1.clone(), @@ -1039,7 +1006,7 @@ mod tests { .unwrap(); store_login_at_file( - wallet_file.clone(), + &wallet_file, account2.clone(), hd_path.clone(), id2.clone(), @@ -1048,14 +1015,14 @@ mod tests { .unwrap(); // Check that it's there as the correct non-multiple type - let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id2, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); let acc2 = loaded_login.as_mnemonic_account().unwrap(); assert_eq!(acc2.mnemonic(), &account2); assert_eq!(acc2.hd_path(), &hd_path); // Add a third and fourth mnenonic grouped together with the second one append_account_to_login_at_file( - wallet_file.clone(), + &wallet_file, account3.clone(), hd_path.clone(), id2.clone(), @@ -1064,7 +1031,7 @@ mod tests { ) .unwrap(); append_account_to_login_at_file( - wallet_file.clone(), + &wallet_file, account4.clone(), hd_path.clone(), id2.clone(), @@ -1074,12 +1041,12 @@ mod tests { .unwrap(); // Check that we can load all four - let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let acc1 = loaded_login.as_mnemonic_account().unwrap(); assert_eq!(acc1.mnemonic(), &account1); assert_eq!(acc1.hd_path(), &hd_path); - let loaded_login = load_existing_login_at_file(wallet_file, &id2, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())), @@ -1109,7 +1076,7 @@ mod tests { let id4 = AccountId::new("fourth".to_string()); store_login_with_multiple_accounts_at_file( - wallet_file.clone(), + &wallet_file, account1.clone(), hd_path.clone(), id1.clone(), @@ -1118,7 +1085,7 @@ mod tests { .unwrap(); store_login_with_multiple_accounts_at_file( - wallet_file.clone(), + &wallet_file, account2.clone(), hd_path.clone(), id2.clone(), @@ -1128,7 +1095,7 @@ mod tests { // Add a third and fourth mnenonic grouped together with the second one append_account_to_login_at_file( - wallet_file.clone(), + &wallet_file, account3.clone(), hd_path.clone(), id2.clone(), @@ -1137,7 +1104,7 @@ mod tests { ) .unwrap(); append_account_to_login_at_file( - wallet_file.clone(), + &wallet_file, account4.clone(), hd_path.clone(), id2.clone(), @@ -1147,7 +1114,7 @@ mod tests { .unwrap(); // Check that we can load all four - let loaded_login = load_existing_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap(); let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![WalletAccount::new( DEFAULT_FIRST_ACCOUNT_NAME.into(), @@ -1156,7 +1123,7 @@ mod tests { .into(); assert_eq!(loaded_accounts, &expected); - let loaded_login = load_existing_login_at_file(wallet_file, &id2, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ WalletAccount::new( @@ -1184,17 +1151,10 @@ mod tests { let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - store_login_at_file( - wallet.clone(), - account1, - hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); + store_login_at_file(&wallet, account1, hd_path.clone(), id1.clone(), &password).unwrap(); append_account_to_login_at_file( - wallet.clone(), + &wallet, account2, hd_path, id1.clone(), @@ -1203,10 +1163,10 @@ mod tests { ) .unwrap(); - remove_account_from_login_at_file(wallet.clone(), &id1, &id2, &password).unwrap(); + remove_account_from_login_at_file(&wallet, &id1, &id2, &password).unwrap(); assert!(matches!( - remove_account_from_login_at_file(wallet, &id1, &id2, &password), + remove_account_from_login_at_file(&wallet, &id1, &id2, &password), Err(BackendError::NoSuchIdInWalletLoginEntry), )); } @@ -1226,7 +1186,7 @@ mod tests { let id2 = AccountId::new("second".to_string()); store_login_with_multiple_accounts_at_file( - wallet_file.clone(), + &wallet_file, account1, hd_path.clone(), id1.clone(), @@ -1235,7 +1195,7 @@ mod tests { .unwrap(); append_account_to_login_at_file( - wallet_file.clone(), + &wallet_file, account2, hd_path, id1.clone(), @@ -1244,10 +1204,10 @@ mod tests { ) .unwrap(); - remove_account_from_login_at_file(wallet_file.clone(), &id1, &id2, &password).unwrap(); + remove_account_from_login_at_file(&wallet_file, &id1, &id2, &password).unwrap(); assert!(matches!( - remove_account_from_login_at_file(wallet_file, &id1, &id2, &password), + remove_account_from_login_at_file(&wallet_file, &id1, &id2, &password), Err(BackendError::NoSuchIdInWalletLoginEntry), )); } @@ -1269,7 +1229,7 @@ mod tests { let id3 = AccountId::new("third".to_string()); store_login_at_file( - wallet_file.clone(), + &wallet_file, account1, hd_path.clone(), id1.clone(), @@ -1278,7 +1238,7 @@ mod tests { .unwrap(); store_login_at_file( - wallet_file.clone(), + &wallet_file, account2.clone(), hd_path.clone(), id2.clone(), @@ -1287,7 +1247,7 @@ mod tests { .unwrap(); append_account_to_login_at_file( - wallet_file.clone(), + &wallet_file, account3.clone(), hd_path.clone(), id2.clone(), @@ -1296,10 +1256,10 @@ mod tests { ) .unwrap(); - remove_login_at_file(wallet_file.clone(), &id1).unwrap(); + remove_login_at_file(&wallet_file, &id1).unwrap(); // The second login one is still there - let loaded_login = load_existing_login_at_file(wallet_file, &id2, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap(); let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())), @@ -1324,7 +1284,7 @@ mod tests { let id2 = AccountId::new("second".to_string()); store_login_with_multiple_accounts_at_file( - wallet.clone(), + &wallet, account1, hd_path.clone(), id1.clone(), @@ -1333,7 +1293,7 @@ mod tests { .unwrap(); append_account_to_login_at_file( - wallet.clone(), + &wallet, account2, hd_path, id1.clone(), @@ -1342,21 +1302,16 @@ mod tests { ) .unwrap(); - remove_account_from_login_at_file( - wallet.clone(), - &id1, - &DEFAULT_FIRST_ACCOUNT_NAME.into(), - &password, - ) - .unwrap(); - remove_account_from_login_at_file(wallet.clone(), &id1, &id2, &password).unwrap(); + remove_account_from_login_at_file(&wallet, &id1, &DEFAULT_FIRST_ACCOUNT_NAME.into(), &password) + .unwrap(); + remove_account_from_login_at_file(&wallet, &id1, &id2, &password).unwrap(); // The file should now be removed assert!(!wallet.exists()); // And trying to load when the file is gone fails assert!(matches!( - load_existing_login_at_file(wallet, &id1, &password), + load_existing_login_at_file(&wallet, &id1, &password), Err(BackendError::WalletFileNotFound), )); } @@ -1378,7 +1333,7 @@ mod tests { let id3 = LoginId::new("third".to_string()); store_login_with_multiple_accounts_at_file( - wallet.clone(), + &wallet, account1, hd_path.clone(), id1.clone(), @@ -1387,7 +1342,7 @@ mod tests { .unwrap(); append_account_to_login_at_file( - wallet.clone(), + &wallet, account2, hd_path.clone(), id1.clone(), @@ -1397,7 +1352,7 @@ mod tests { .unwrap(); store_login_with_multiple_accounts_at_file( - wallet.clone(), + &wallet, account3.clone(), hd_path.clone(), id3.clone(), @@ -1405,23 +1360,18 @@ mod tests { ) .unwrap(); - remove_account_from_login_at_file( - wallet.clone(), - &id1, - &DEFAULT_FIRST_ACCOUNT_NAME.into(), - &password, - ) - .unwrap(); - remove_account_from_login_at_file(wallet.clone(), &id1, &id2, &password).unwrap(); + remove_account_from_login_at_file(&wallet, &id1, &DEFAULT_FIRST_ACCOUNT_NAME.into(), &password) + .unwrap(); + remove_account_from_login_at_file(&wallet, &id1, &id2, &password).unwrap(); // And trying to load when the file is gone fails assert!(matches!( - load_existing_login_at_file(wallet.clone(), &id1, &password), + load_existing_login_at_file(&wallet, &id1, &password), Err(BackendError::NoSuchIdInWallet), )); // The other login is still there - let loaded_login = load_existing_login_at_file(wallet, &id3, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet, &id3, &password).unwrap(); let acc3 = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![WalletAccount::new( DEFAULT_FIRST_ACCOUNT_NAME.into(), @@ -1450,7 +1400,7 @@ mod tests { let id4 = AccountId::new("fourth".to_string()); store_login_at_file( - wallet.clone(), + &wallet, acc1.clone(), hd_path.clone(), id1.clone(), @@ -1459,7 +1409,7 @@ mod tests { .unwrap(); store_login_at_file( - wallet.clone(), + &wallet, acc2.clone(), hd_path.clone(), id2.clone(), @@ -1469,7 +1419,7 @@ mod tests { // Add a third and fourth mnenonic grouped together with the second one append_account_to_login_at_file( - wallet.clone(), + &wallet, acc3, hd_path.clone(), id2.clone(), @@ -1478,7 +1428,7 @@ mod tests { ) .unwrap(); append_account_to_login_at_file( - wallet.clone(), + &wallet, acc4.clone(), hd_path.clone(), id2.clone(), @@ -1488,10 +1438,10 @@ mod tests { .unwrap(); // Delete the third mnemonic, from the second login entry - remove_account_from_login_at_file(wallet.clone(), &id2, &id3, &password).unwrap(); + remove_account_from_login_at_file(&wallet, &id2, &id3, &password).unwrap(); // Check that we can still load the other accounts - let loaded_login = load_existing_login_at_file(wallet.clone(), &id2, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet, &id2, &password).unwrap(); let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); let expected = vec![ WalletAccount::new( @@ -1504,16 +1454,15 @@ mod tests { assert_eq!(loaded_accounts, &expected); // Delete the second and fourth mnemonic from the second login entry removes the login entry - remove_account_from_login_at_file(wallet.clone(), &id2, &id2.clone().into(), &password) - .unwrap(); - remove_account_from_login_at_file(wallet.clone(), &id2, &id4, &password).unwrap(); + remove_account_from_login_at_file(&wallet, &id2, &id2.clone().into(), &password).unwrap(); + remove_account_from_login_at_file(&wallet, &id2, &id4, &password).unwrap(); assert!(matches!( - load_existing_login_at_file(wallet.clone(), &id2, &password), + load_existing_login_at_file(&wallet, &id2, &password), Err(BackendError::NoSuchIdInWallet), )); // The first login is still available - let loaded_login = load_existing_login_at_file(wallet, &id1, &password).unwrap(); + let loaded_login = load_existing_login_at_file(&wallet, &id1, &password).unwrap(); let account = loaded_login.as_mnemonic_account().unwrap(); assert_eq!(account.mnemonic(), &acc1); assert_eq!(account.hd_path(), &hd_path); @@ -1526,7 +1475,7 @@ mod tests { const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet.json"; let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET); - let wallet = load_existing_wallet_at_file(wallet_file).unwrap(); + let wallet = load_existing_wallet_at_file(&wallet_file).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); From f661bf04468d433d12c0311351a7fdf9b3ae16b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 12 May 2022 22:15:38 +0200 Subject: [PATCH 78/85] wallet: write_to_file function --- .../src-tauri/src/wallet_storage/mod.rs | 50 ++++++------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 6ace4c9247..71b986a52c 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -44,6 +44,16 @@ pub(crate) fn wallet_login_filepath() -> Result { get_storage_directory().map(|dir| dir.join(WALLET_INFO_FILENAME)) } +fn write_to_file(filepath: &PathBuf, wallet: &StoredWallet) -> Result<(), BackendError> { + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(filepath)?; + + Ok(serde_json::to_writer_pretty(file, &wallet)?) +} + /// Load stored wallet file #[allow(unused)] pub(crate) fn load_existing_wallet() -> Result { @@ -122,13 +132,7 @@ fn store_login_at_file( let new_encrypted_account = EncryptedLogin::encrypt(id, &new_login, password)?; stored_wallet.add_encrypted_login(new_encrypted_account)?; - let file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(filepath)?; - - Ok(serde_json::to_writer_pretty(file, &stored_wallet)?) + write_to_file(filepath, &stored_wallet) } pub(crate) fn store_login_with_multiple_accounts( @@ -170,13 +174,7 @@ fn store_login_with_multiple_accounts_at_file( stored_wallet.add_encrypted_login(new_encrypted_login)?; - let file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(filepath)?; - - Ok(serde_json::to_writer_pretty(file, &stored_wallet)?) + write_to_file(filepath, &stored_wallet) } /// Append an account to an already existing top-level encrypted account entry. @@ -224,13 +222,7 @@ fn append_account_to_login_at_file( stored_wallet.replace_encrypted_login(encrypted_accounts)?; - let file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(filepath)?; - - Ok(serde_json::to_writer_pretty(file, &stored_wallet)?) + write_to_file(filepath, &stored_wallet) } /// Remove the entire encrypted login entry for the given `id`. This means potentially removing all @@ -262,13 +254,7 @@ fn remove_login_at_file(filepath: &PathBuf, id: &LoginId) -> Result<(), BackendE log::info!("Removing file: {:#?}", filepath); Ok(fs::remove_file(filepath)?) } else { - let file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(filepath)?; - - Ok(serde_json::to_writer_pretty(file, &stored_wallet)?) + write_to_file(filepath, &stored_wallet) } } @@ -327,13 +313,7 @@ fn remove_account_from_login_at_file( log::info!("Removing file: {:#?}", filepath); Ok(fs::remove_file(filepath)?) } else { - let file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(filepath)?; - - Ok(serde_json::to_writer_pretty(file, &stored_wallet)?) + write_to_file(filepath, &stored_wallet) } } From e27fc825246cfe31603e9cacd050243c681a6678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 13 May 2022 09:00:09 +0200 Subject: [PATCH 79/85] wallet: use login_id and account_id instead of id and inner_id --- .../src/operations/mixnet/account.rs | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 379ebe13f4..cf9cab79f8 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -369,10 +369,10 @@ pub fn create_password(mnemonic: &str, password: String) -> Result<(), BackendEr let mnemonic = Mnemonic::from_str(mnemonic)?; let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - // Currently we only support a single, default, id in the wallet - let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); + // Currently we only support a single, default, login id in the wallet + let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let password = wallet_storage::UserPassword::new(password); - wallet_storage::store_login_with_multiple_accounts(mnemonic, hd_path, id, &password) + wallet_storage::store_login_with_multiple_accounts(mnemonic, hd_path, login_id, &password) } #[tauri::command] @@ -383,13 +383,13 @@ pub async fn sign_in_with_password( log::info!("Signing in with password"); // Currently we only support a single, default, id in the wallet - let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); + let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let password = wallet_storage::UserPassword::new(password); - let stored_login = wallet_storage::load_existing_login(&id, &password)?; + let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; let mnemonic = extract_first_mnemonic(&stored_login)?; - let first_id_when_converting = id.into(); - set_state_with_all_accounts(stored_login, first_id_when_converting, state.clone()).await?; + let first_login_id_when_converting = login_id.into(); + set_state_with_all_accounts(stored_login, first_login_id_when_converting, state.clone()).await?; _connect_with_mnemonic(mnemonic, state).await } @@ -416,30 +416,30 @@ fn extract_first_mnemonic( #[tauri::command] pub fn remove_password() -> Result<(), BackendError> { log::info!("Removing password"); - let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - wallet_storage::remove_login(&id) + let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); + wallet_storage::remove_login(&login_id) } #[tauri::command] pub async fn add_account_for_password( mnemonic: &str, password: &str, - inner_id: &str, + account_id: &str, state: tauri::State<'_, Arc>>, ) -> Result { - log::info!("Adding account for the current password: {inner_id}"); + log::info!("Adding account for the current password: {account_id}"); let mnemonic = Mnemonic::from_str(mnemonic)?; let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - // Currently we only support a single, default, id in the wallet - let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - let inner_id = wallet_storage::AccountId::new(inner_id.to_string()); + // Currently we only support a single, default, login id in the wallet + let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); + let account_id = wallet_storage::AccountId::new(account_id.to_string()); let password = wallet_storage::UserPassword::new(password.to_string()); wallet_storage::append_account_to_login( mnemonic.clone(), hd_path, - id.clone(), - inner_id.clone(), + login_id.clone(), + account_id.clone(), &password, )?; @@ -451,14 +451,14 @@ pub async fn add_account_for_password( // Re-read all the acccounts from the wallet to reset the state, rather than updating it // incrementally - let stored_login = wallet_storage::load_existing_login(&id, &password)?; + let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; // NOTE: since we are appending, this id shouldn't be needed, but setting the state is supposed // to be a general function - let first_id_when_converting = id.into(); + let first_id_when_converting = login_id.into(); set_state_with_all_accounts(stored_login, first_id_when_converting, state).await?; Ok(AccountEntry { - id: inner_id.to_string(), + id: account_id.to_string(), address, }) } @@ -487,22 +487,22 @@ async fn set_state_with_all_accounts( #[tauri::command] pub async fn remove_account_for_password( password: &str, - inner_id: &str, + account_id: &str, state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - log::info!("Removing account: {inner_id}"); + log::info!("Removing account: {account_id}"); // Currently we only support a single, default, id in the wallet - let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); - let inner_id = wallet_storage::AccountId::new(inner_id.to_string()); + let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); + let account_id = wallet_storage::AccountId::new(account_id.to_string()); let password = wallet_storage::UserPassword::new(password.to_string()); - wallet_storage::remove_account_from_login(&id, &inner_id, &password)?; + wallet_storage::remove_account_from_login(&login_id, &account_id, &password)?; // Load to reset the internal state - let stored_login = wallet_storage::load_existing_login(&id, &password)?; + let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; // NOTE: Since we removed from a multi-account login, this id shouldn't be needed, but setting // the state is supposed to be a general function - let first_id_when_converting = id.into(); - set_state_with_all_accounts(stored_login, first_id_when_converting, state).await + let first_account_id_when_converting = login_id.into(); + set_state_with_all_accounts(stored_login, first_account_id_when_converting, state).await } fn derive_address( @@ -549,10 +549,10 @@ pub fn show_mnemonic_for_account_in_password( password: String, ) -> Result { log::info!("Getting mnemonic for: {account_id}"); - let id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); + let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let account_id = wallet_storage::AccountId::new(account_id); let password = wallet_storage::UserPassword::new(password); - let stored_account = wallet_storage::load_existing_login(&id, &password)?; + let stored_account = wallet_storage::load_existing_login(&login_id, &password)?; let mnemonic = match stored_account { wallet_storage::StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(), From cbcef9fbcdda9b8c1e6e53202b3decde53d6383f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 13 May 2022 09:18:21 +0200 Subject: [PATCH 80/85] wallet: switch from PathBuf to Path --- nym-wallet/src-tauri/src/wallet_storage/mod.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 71b986a52c..28709922aa 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -16,7 +16,7 @@ use crate::error::BackendError; use crate::platform_constants::{STORAGE_DIR_NAME, WALLET_INFO_FILENAME}; use cosmrs::bip32::DerivationPath; use std::fs::{self, create_dir_all, OpenOptions}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[cfg(test)] use self::account_data::MnemonicAccount; @@ -44,7 +44,7 @@ pub(crate) fn wallet_login_filepath() -> Result { get_storage_directory().map(|dir| dir.join(WALLET_INFO_FILENAME)) } -fn write_to_file(filepath: &PathBuf, wallet: &StoredWallet) -> Result<(), BackendError> { +fn write_to_file(filepath: &Path, wallet: &StoredWallet) -> Result<(), BackendError> { let file = OpenOptions::new() .create(true) .write(true) @@ -62,7 +62,7 @@ pub(crate) fn load_existing_wallet() -> Result { load_existing_wallet_at_file(&filepath) } -fn load_existing_wallet_at_file(filepath: &PathBuf) -> Result { +fn load_existing_wallet_at_file(filepath: &Path) -> Result { if !filepath.exists() { return Err(BackendError::WalletFileNotFound); } @@ -83,7 +83,7 @@ pub(crate) fn load_existing_login( } pub(crate) fn load_existing_login_at_file( - filepath: &PathBuf, + filepath: &Path, id: &LoginId, password: &UserPassword, ) -> Result { @@ -110,7 +110,7 @@ pub(crate) fn store_login( // DEPRECATED: only used in tests, where it's used to test supporting older wallet formats #[cfg(test)] fn store_login_at_file( - filepath: &PathBuf, + filepath: &Path, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, id: LoginId, @@ -150,7 +150,7 @@ pub(crate) fn store_login_with_multiple_accounts( } fn store_login_with_multiple_accounts_at_file( - filepath: &PathBuf, + filepath: &Path, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, id: LoginId, @@ -197,7 +197,7 @@ pub(crate) fn append_account_to_login( } fn append_account_to_login_at_file( - filepath: &PathBuf, + filepath: &Path, mnemonic: bip39::Mnemonic, hd_path: DerivationPath, id: LoginId, @@ -234,7 +234,7 @@ pub(crate) fn remove_login(id: &LoginId) -> Result<(), BackendError> { remove_login_at_file(&filepath, id) } -fn remove_login_at_file(filepath: &PathBuf, id: &LoginId) -> Result<(), BackendError> { +fn remove_login_at_file(filepath: &Path, id: &LoginId) -> Result<(), BackendError> { log::warn!("Removing wallet account with id: {id}. This includes all associated accounts!"); let mut stored_wallet = match load_existing_wallet_at_file(filepath) { Err(BackendError::WalletFileNotFound) => StoredWallet::default(), @@ -273,7 +273,7 @@ pub(crate) fn remove_account_from_login( } fn remove_account_from_login_at_file( - filepath: &PathBuf, + filepath: &Path, id: &LoginId, inner_id: &AccountId, password: &UserPassword, From 252385688d72b617f5c5e14b7b7c2447494d93c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 13 May 2022 09:35:27 +0200 Subject: [PATCH 81/85] wallet: remove unnecessary blank lines --- .../src-tauri/src/wallet_storage/mod.rs | 58 ------------------- 1 file changed, 58 deletions(-) diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 28709922aa..ed2d236cfd 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -348,7 +348,6 @@ mod tests { fn store_single_login() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -370,7 +369,6 @@ mod tests { fn store_single_login_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -399,7 +397,6 @@ mod tests { fn store_twice_for_the_same_id_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -426,7 +423,6 @@ mod tests { fn store_twice_for_the_same_id_fails_with_multiple() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -453,7 +449,6 @@ mod tests { fn load_with_wrong_password_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -473,7 +468,6 @@ mod tests { fn load_with_wrong_password_fails_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -500,7 +494,6 @@ mod tests { fn load_with_wrong_id_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -520,7 +513,6 @@ mod tests { fn load_with_wrong_id_fails_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -541,7 +533,6 @@ mod tests { fn store_and_load_a_single_login() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -566,7 +557,6 @@ mod tests { fn store_and_load_a_single_login_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let acc1 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let password = UserPassword::new("password".to_string()); @@ -596,14 +586,11 @@ mod tests { fn store_a_second_login_with_a_different_password_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let account2 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); @@ -627,14 +614,11 @@ mod tests { fn store_a_second_login_with_a_different_password_fails_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let account2 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); let bad_password = UserPassword::new("bad-password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); @@ -664,14 +648,11 @@ mod tests { fn store_two_mnemonic_accounts_gives_different_salts_and_iv() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let account2 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); @@ -707,14 +688,11 @@ mod tests { fn store_two_mnemonic_accounts_using_two_logins() { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let account2 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); @@ -759,14 +737,11 @@ mod tests { fn store_one_mnemonic_account_and_one_multi_account() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let account2 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); @@ -816,12 +791,9 @@ mod tests { fn remove_non_existent_id_fails() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); @@ -839,14 +811,11 @@ mod tests { fn store_and_remove_wallet_login_information() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let account2 = bip39::Mnemonic::generate(24).unwrap(); let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); let different_hd_path: DerivationPath = "m".parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); @@ -912,13 +881,10 @@ mod tests { fn append_account_converts_the_type() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let account2 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); @@ -962,15 +928,12 @@ mod tests { fn append_accounts_to_existing_login() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let account2 = bip39::Mnemonic::generate(24).unwrap(); let account3 = bip39::Mnemonic::generate(24).unwrap(); let account4 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); @@ -1041,15 +1004,12 @@ mod tests { fn append_accounts_to_existing_login_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let account2 = bip39::Mnemonic::generate(24).unwrap(); let account3 = bip39::Mnemonic::generate(24).unwrap(); let account4 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); @@ -1121,13 +1081,10 @@ mod tests { fn delete_the_same_account_twice_for_a_login_fails() { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let account2 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); @@ -1155,13 +1112,10 @@ mod tests { fn delete_the_same_account_twice_for_a_login_fails_with_multi() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let account2 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); @@ -1196,14 +1150,11 @@ mod tests { fn delete_appended_account_doesnt_affect_others() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let account2 = bip39::Mnemonic::generate(24).unwrap(); let account3 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); @@ -1253,13 +1204,10 @@ mod tests { fn remove_all_accounts_for_a_login_removes_the_file_when_empty() { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let account2 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); @@ -1300,14 +1248,11 @@ mod tests { fn remove_all_accounts_for_a_login_removes_that_login() { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let account1 = bip39::Mnemonic::generate(24).unwrap(); let account2 = bip39::Mnemonic::generate(24).unwrap(); let account3 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); let id3 = LoginId::new("third".to_string()); @@ -1365,15 +1310,12 @@ mod tests { fn append_accounts_and_remove_appended_accounts() { let store_dir = tempdir().unwrap(); let wallet = store_dir.path().join(WALLET_INFO_FILENAME); - let acc1 = bip39::Mnemonic::generate(24).unwrap(); let acc2 = bip39::Mnemonic::generate(24).unwrap(); let acc3 = bip39::Mnemonic::generate(24).unwrap(); let acc4 = bip39::Mnemonic::generate(24).unwrap(); let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - let password = UserPassword::new("password".to_string()); - let id1 = LoginId::new("first".to_string()); let id2 = LoginId::new("second".to_string()); let id3 = AccountId::new("third".to_string()); From 53347bec67b419341b15aec96ded1ef0522b8720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 13 May 2022 11:23:50 +0200 Subject: [PATCH 82/85] changelog: added note about multiple accounts --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b495f7e723..ac0d253e04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +- wallet: added support for multiple accounts ([#1265]) - wallet: the wallet backend learned how to keep track of validator name, either hardcoded or by querying the status endpoint. - mixnet-contract: Replace all naked `-` with `saturating_sub`. - validator-api: add Swagger to document the REST API ([#1249]). @@ -20,6 +21,7 @@ [#1256]: https://github.com/nymtech/nym/pull/1256 [#1257]: https://github.com/nymtech/nym/pull/1257 [#1260]: https://github.com/nymtech/nym/pull/1260 +[#1265]: https://github.com/nymtech/nym/pull/1265 ## [nym-wallet-v1.0.4](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.4) (2022-05-04) From db01c245d973c26f23928b19079ea6ed544de370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 13 May 2022 11:37:54 +0200 Subject: [PATCH 83/85] wallet: update wallet error enum cases --- nym-wallet/src-tauri/src/error.rs | 22 ++++++++--------- .../src/operations/mixnet/account.rs | 6 ++--- .../src/wallet_storage/account_data.rs | 10 ++++---- .../src-tauri/src/wallet_storage/mod.rs | 24 +++++++++---------- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index 9e962199a0..30c3ab5dcb 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -83,20 +83,20 @@ pub enum BackendError { WalletFileAlreadyExists, #[error("The wallet file is not found")] WalletFileNotFound, - #[error("Account ID not found in wallet")] - NoSuchIdInWallet, - #[error("Account ID not found in wallet login entry")] - NoSuchIdInWalletLoginEntry, - #[error("Account ID already found in wallet")] - IdAlreadyExistsInWallet, - #[error("Account ID already found in stored wallet login")] - IdAlreadyExistsInStoredWalletLogin, + #[error("Login ID not found in wallet")] + WalletNoSuchLoginId, + #[error("Account ID not found in wallet login")] + WalletNoSuchAccountIdInWalletLogin, + #[error("Login ID already found in wallet")] + WalletLoginIdAlreadyExists, + #[error("Account ID already found in wallet login")] + WalletAccountIdAlreadyExistsInWalletLogin, #[error("Adding a different password to the wallet not currently supported")] WalletDifferentPasswordDetected, - #[error("Unexpted multiple account entries found")] - WalletUnexpectedMultipleAccounts, - #[error("Unexpted mnemonic account found")] + #[error("Unexpted mnemonic account for login")] WalletUnexpectedMnemonicAccount, + #[error("Unexpted multiple account entry for login")] + WalletUnexpectedMultipleAccounts, #[error("Failed to derive address from mnemonic")] FailedToDeriveAddress, } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index cf9cab79f8..c2b4e1eff8 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -404,7 +404,7 @@ fn extract_first_mnemonic( accounts .get_accounts() .next() - .ok_or(BackendError::NoSuchIdInWalletLoginEntry)? + .ok_or(BackendError::WalletNoSuchAccountIdInWalletLogin)? .mnemonic() .clone() } @@ -562,7 +562,7 @@ pub fn show_mnemonic_for_account_in_password( } accounts .get_account(&account_id) - .ok_or(BackendError::NoSuchIdInWalletLoginEntry)? + .ok_or(BackendError::WalletNoSuchAccountIdInWalletLogin)? .mnemonic() .clone() } @@ -582,7 +582,7 @@ pub async fn sign_in_decrypted_account( let account = &state .get_all_accounts() .find(|a| a.id().as_ref() == account_id) - .ok_or(BackendError::NoSuchIdInWalletLoginEntry)?; + .ok_or(BackendError::WalletNoSuchAccountIdInWalletLogin)?; account.mnemonic().clone() }; diff --git a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs index caf1f7e4a7..26c7bce100 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -50,7 +50,7 @@ impl StoredWallet { pub fn add_encrypted_login(&mut self, new_login: EncryptedLogin) -> Result<(), BackendError> { if self.get_encrypted_login(&new_login.id).is_ok() { - return Err(BackendError::IdAlreadyExistsInWallet); + return Err(BackendError::WalletLoginIdAlreadyExists); } self.accounts.push(new_login); Ok(()) @@ -62,7 +62,7 @@ impl StoredWallet { .iter() .find(|account| &account.id == id) .map(|account| &account.account) - .ok_or(BackendError::NoSuchIdInWallet) + .ok_or(BackendError::WalletNoSuchLoginId) } fn get_encrypted_login_mut(&mut self, id: &LoginId) -> Result<&mut EncryptedLogin, BackendError> { @@ -70,7 +70,7 @@ impl StoredWallet { .accounts .iter_mut() .find(|account| &account.id == id) - .ok_or(BackendError::NoSuchIdInWallet) + .ok_or(BackendError::WalletNoSuchLoginId) } #[cfg(test)] @@ -224,7 +224,7 @@ impl MultipleAccounts { hd_path: DerivationPath, ) -> Result<(), BackendError> { if self.get_account(&id).is_some() { - Err(BackendError::IdAlreadyExistsInStoredWalletLogin) + Err(BackendError::WalletAccountIdAlreadyExistsInWalletLogin) } else { self.accounts.push(WalletAccount::new( id, @@ -236,7 +236,7 @@ impl MultipleAccounts { pub(crate) fn remove(&mut self, id: &AccountId) -> Result<(), BackendError> { if self.get_account(id).is_none() { - return Err(BackendError::NoSuchIdInWalletLoginEntry); + return Err(BackendError::WalletNoSuchAccountIdInWalletLogin); } self.accounts.retain(|accounts| &accounts.id != id); Ok(()) diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index ed2d236cfd..2851f6ada5 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -248,7 +248,7 @@ fn remove_login_at_file(filepath: &Path, id: &LoginId) -> Result<(), BackendErro stored_wallet .remove_encrypted_login(id) - .ok_or(BackendError::NoSuchIdInWallet)?; + .ok_or(BackendError::WalletNoSuchLoginId)?; if stored_wallet.is_empty() { log::info!("Removing file: {:#?}", filepath); @@ -302,7 +302,7 @@ fn remove_account_from_login_at_file( if is_empty { stored_wallet .remove_encrypted_login(id) - .ok_or(BackendError::NoSuchIdInWallet)?; + .ok_or(BackendError::WalletNoSuchLoginId)?; } else { let encrypted_accounts = EncryptedLogin::encrypt(id.clone(), &decrypted_login, password)?; stored_wallet.replace_encrypted_login(encrypted_accounts)?; @@ -415,7 +415,7 @@ mod tests { // and storing the same id again fails assert!(matches!( store_login_at_file(&wallet_file, account1, hd_path, id1, &password,), - Err(BackendError::IdAlreadyExistsInWallet), + Err(BackendError::WalletLoginIdAlreadyExists), )); } @@ -441,7 +441,7 @@ mod tests { // and storing the same id again fails assert!(matches!( store_login_with_multiple_accounts_at_file(&wallet_file, account1, hd_path, id1, &password,), - Err(BackendError::IdAlreadyExistsInWallet), + Err(BackendError::WalletLoginIdAlreadyExists), )); } @@ -505,7 +505,7 @@ mod tests { // Trying to load with the wrong id assert!(matches!( load_existing_login_at_file(&wallet_file, &id2, &password), - Err(BackendError::NoSuchIdInWallet), + Err(BackendError::WalletNoSuchLoginId), )); } @@ -525,7 +525,7 @@ mod tests { // Trying to load with the wrong id assert!(matches!( load_existing_login_at_file(&wallet_file, &id2, &password), - Err(BackendError::NoSuchIdInWallet), + Err(BackendError::WalletNoSuchLoginId), )); } @@ -803,7 +803,7 @@ mod tests { // Fails to delete non-existent id in the wallet assert!(matches!( remove_login_at_file(&wallet_file, &id2), - Err(BackendError::NoSuchIdInWallet), + Err(BackendError::WalletNoSuchLoginId), )); } @@ -860,7 +860,7 @@ mod tests { // And we can't load the second one anymore assert!(matches!( load_existing_login_at_file(&wallet_file, &id2, &password), - Err(BackendError::NoSuchIdInWallet), + Err(BackendError::WalletNoSuchLoginId), )); // Delete the first account @@ -1104,7 +1104,7 @@ mod tests { assert!(matches!( remove_account_from_login_at_file(&wallet, &id1, &id2, &password), - Err(BackendError::NoSuchIdInWalletLoginEntry), + Err(BackendError::WalletNoSuchAccountIdInWalletLogin), )); } @@ -1142,7 +1142,7 @@ mod tests { assert!(matches!( remove_account_from_login_at_file(&wallet_file, &id1, &id2, &password), - Err(BackendError::NoSuchIdInWalletLoginEntry), + Err(BackendError::WalletNoSuchAccountIdInWalletLogin), )); } @@ -1292,7 +1292,7 @@ mod tests { // And trying to load when the file is gone fails assert!(matches!( load_existing_login_at_file(&wallet, &id1, &password), - Err(BackendError::NoSuchIdInWallet), + Err(BackendError::WalletNoSuchLoginId), )); // The other login is still there @@ -1380,7 +1380,7 @@ mod tests { remove_account_from_login_at_file(&wallet, &id2, &id4, &password).unwrap(); assert!(matches!( load_existing_login_at_file(&wallet, &id2, &password), - Err(BackendError::NoSuchIdInWallet), + Err(BackendError::WalletNoSuchLoginId), )); // The first login is still available From 6cb25cf1fbdf13bb7a0f7662a1e2f05b35bd4e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 13 May 2022 11:50:24 +0200 Subject: [PATCH 84/85] wallet: dont use default wallet when removing --- nym-wallet/src-tauri/src/wallet_storage/mod.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 2851f6ada5..43edb72987 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -204,10 +204,7 @@ fn append_account_to_login_at_file( inner_id: AccountId, password: &UserPassword, ) -> Result<(), BackendError> { - let mut stored_wallet = match load_existing_wallet_at_file(filepath) { - Err(BackendError::WalletFileNotFound) => StoredWallet::default(), - result => result?, - }; + let mut stored_wallet = load_existing_wallet_at_file(filepath)?; let decrypted_login = stored_wallet.decrypt_login(&id, password)?; @@ -236,10 +233,7 @@ pub(crate) fn remove_login(id: &LoginId) -> Result<(), BackendError> { fn remove_login_at_file(filepath: &Path, id: &LoginId) -> Result<(), BackendError> { log::warn!("Removing wallet account with id: {id}. This includes all associated accounts!"); - let mut stored_wallet = match load_existing_wallet_at_file(filepath) { - Err(BackendError::WalletFileNotFound) => StoredWallet::default(), - result => result?, - }; + let mut stored_wallet = load_existing_wallet_at_file(filepath)?; if stored_wallet.is_empty() { log::info!("Removing file: {:#?}", filepath); @@ -279,10 +273,7 @@ fn remove_account_from_login_at_file( password: &UserPassword, ) -> Result<(), BackendError> { log::info!("Removing associated account from login account: {id}"); - let mut stored_wallet = match load_existing_wallet_at_file(filepath) { - Err(BackendError::WalletFileNotFound) => StoredWallet::default(), - result => result?, - }; + let mut stored_wallet = load_existing_wallet_at_file(filepath)?; let mut decrypted_login = stored_wallet.decrypt_login(id, password)?; From f7486f0490c529592567100e6bbad6b785ff6239 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 13 May 2022 12:22:19 +0100 Subject: [PATCH 85/85] add field validation for mnemonic and account name --- .../components/Accounts/AddAccountModal.tsx | 122 +++++++++++++----- nym-wallet/src/context/accounts.tsx | 2 +- nym-wallet/src/requests/account.ts | 2 +- 3 files changed, 89 insertions(+), 37 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index df9b4c25ed..3f3aec7b62 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -14,10 +14,12 @@ import { } from '@mui/material'; import { ArrowBackSharp } from '@mui/icons-material'; import { useClipboard } from 'use-clipboard-copy'; -import { createMnemonic } from 'src/requests'; +import { createMnemonic, validateMnemonic } from 'src/requests'; import { AccountsContext } from 'src/context'; import { PasswordInput } from 'src/components'; import { Mnemonic } from '../Mnemonic'; +import { Console } from 'src/utils/console'; +import { match } from 'assert'; const createAccountSteps = [ 'Copy and save mnemonic for your new account', @@ -54,41 +56,81 @@ const ImportMnemonic = ({ value: string; onChange: (value: string) => void; onNext: () => void; -}) => ( - - - - onChange(e.target.value)} - fullWidth - type="password" - /> - - - - - - -); +}) => { + const [error, setError] = useState(); + + const handleOnNext = async () => { + const isValid = await validateMnemonic(value); + if (!isValid) setError('Please enter a valid mnemonic. Mnemonic must have a word count that is a multiple of 6.'); + else onNext(); + }; -const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => { - const [value, setValue] = useState(''); return ( - setValue(e.target.value)} fullWidth /> + + {error && ( + + {error} + + )} + { + onChange(e.target.value); + setError(undefined); + }} + fullWidth + type="password" + /> + + + + + + + ); +}; + +const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => { + const [value, setValue] = useState(''); + const [error, setError] = useState(); + + const nameValidation = /^([a-zA-Z0-9\s]){1,20}$/; + + const handleNext = (value: string) => { + if (!nameValidation.test(value)) + [setError('Account name must contain only letters and numbers and be between 1 and 20 characters')]; + else onNext(value); + }; + + return ( + + + + {error} + + { + setValue(e.target.value); + setError(undefined); + }} + fullWidth + /> @@ -158,6 +200,10 @@ export const AddAccountModal = () => { const handleClose = () => { setDialogToDisplay('Accounts'); + resetState(); + }; + + const resetState = () => { setData({ mnemonic: '', accountName: '' }); setStep(0); setError(undefined); @@ -165,7 +211,7 @@ export const AddAccountModal = () => { useEffect(() => { if (dialogToDisplay === 'Add') generateMnemonic(); - if (dialogToDisplay === 'Accounts') setStep(0); + if (dialogToDisplay === 'Accounts') resetState(); }, [dialogToDisplay]); useEffect(() => { @@ -216,7 +262,13 @@ export const AddAccountModal = () => { { if (data.accountName && data.mnemonic) { - await handleAddAccount({ accountName: data.accountName, mnemonic: data.mnemonic, password }); + try { + await handleAddAccount({ accountName: data.accountName, mnemonic: data.mnemonic, password }); + setStep(0); + setDialogToDisplay('Accounts'); + } catch (e) { + Console.error(e as string); + } } }} /> diff --git a/nym-wallet/src/context/accounts.tsx b/nym-wallet/src/context/accounts.tsx index 53e7127124..51794db671 100644 --- a/nym-wallet/src/context/accounts.tsx +++ b/nym-wallet/src/context/accounts.tsx @@ -60,9 +60,9 @@ export const AccountsProvider: React.FC = ({ children }) => { }); setAccounts((accs) => [...accs, newAccount]); enqueueSnackbar('New account created', { variant: 'success' }); - setDialogToDisplay('Accounts'); } catch (e) { setError(`Error adding account: ${e}`); + throw new Error(); } finally { setIsLoading(false); } diff --git a/nym-wallet/src/requests/account.ts b/nym-wallet/src/requests/account.ts index bc85fe30ad..171776aef5 100644 --- a/nym-wallet/src/requests/account.ts +++ b/nym-wallet/src/requests/account.ts @@ -44,7 +44,7 @@ export const addAccount = async ({ password: string; accountName: string; }) => { - const res: AccountEntry = await invoke('add_account_for_password', { mnemonic, password, innerId: accountName }); + const res: AccountEntry = await invoke('add_account_for_password', { mnemonic, password, accountId: accountName }); return res; };