mirror of
https://github.com/Qortal/qortal-mobile.git
synced 2025-04-28 13:57:52 +00:00
added seedphrase
This commit is contained in:
parent
c3eb1fd50a
commit
af631df00d
49
package-lock.json
generated
49
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "qortal-go",
|
"name": "qortal-go",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "qortal-go",
|
"name": "qortal-go",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^6.1.2",
|
"@capacitor/android": "^6.1.2",
|
||||||
"@capacitor/app": "^6.0.1",
|
"@capacitor/app": "^6.0.1",
|
||||||
@ -53,6 +53,7 @@
|
|||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
"jssha": "3.3.1",
|
"jssha": "3.3.1",
|
||||||
|
"lit": "^3.2.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mime": "^4.0.4",
|
"mime": "^4.0.4",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
@ -3029,6 +3030,19 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
|
||||||
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
|
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@lit-labs/ssr-dom-shim": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@lit/reactive-element": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit-labs/ssr-dom-shim": "^1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mui/base": {
|
"node_modules/@mui/base": {
|
||||||
"version": "5.0.0-beta.40",
|
"version": "5.0.0-beta.40",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
|
||||||
@ -4757,8 +4771,7 @@
|
|||||||
"node_modules/@types/trusted-types": {
|
"node_modules/@types/trusted-types": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/use-sync-external-store": {
|
"node_modules/@types/use-sync-external-store": {
|
||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
@ -8200,6 +8213,34 @@
|
|||||||
"uc.micro": "^2.0.0"
|
"uc.micro": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lit": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit/-/lit-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-1BBa1E/z0O9ye5fZprPtdqnc0BFzxIxTTOO/tQFmyC/hj1O3jL4TfmLBw0WEwjAokdLwpclkvGgDJwTIh0/22w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit/reactive-element": "^2.0.4",
|
||||||
|
"lit-element": "^4.1.0",
|
||||||
|
"lit-html": "^3.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lit-element": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit-labs/ssr-dom-shim": "^1.2.0",
|
||||||
|
"@lit/reactive-element": "^2.0.4",
|
||||||
|
"lit-html": "^3.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lit-html": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/trusted-types": "^2.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/local-pkg": {
|
"node_modules/local-pkg": {
|
||||||
"version": "0.5.0",
|
"version": "0.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz",
|
||||||
|
@ -37,10 +37,10 @@
|
|||||||
"@tiptap/extension-color": "^2.5.9",
|
"@tiptap/extension-color": "^2.5.9",
|
||||||
"@tiptap/extension-highlight": "^2.6.6",
|
"@tiptap/extension-highlight": "^2.6.6",
|
||||||
"@tiptap/extension-image": "^2.6.6",
|
"@tiptap/extension-image": "^2.6.6",
|
||||||
|
"@tiptap/extension-mention": "^2.9.1",
|
||||||
"@tiptap/extension-placeholder": "^2.6.2",
|
"@tiptap/extension-placeholder": "^2.6.2",
|
||||||
"@tiptap/extension-text-style": "^2.5.9",
|
"@tiptap/extension-text-style": "^2.5.9",
|
||||||
"@tiptap/extension-underline": "^2.6.6",
|
"@tiptap/extension-underline": "^2.6.6",
|
||||||
"@tiptap/extension-mention": "^2.9.1",
|
|
||||||
"@tiptap/pm": "^2.5.9",
|
"@tiptap/pm": "^2.5.9",
|
||||||
"@tiptap/react": "^2.5.9",
|
"@tiptap/react": "^2.5.9",
|
||||||
"@tiptap/starter-kit": "^2.5.9",
|
"@tiptap/starter-kit": "^2.5.9",
|
||||||
@ -55,7 +55,9 @@
|
|||||||
"dompurify": "^3.1.6",
|
"dompurify": "^3.1.6",
|
||||||
"emoji-picker-react": "^4.12.0",
|
"emoji-picker-react": "^4.12.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
|
"html-to-text": "^9.0.5",
|
||||||
"jssha": "3.3.1",
|
"jssha": "3.3.1",
|
||||||
|
"lit": "^3.2.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mime": "^4.0.4",
|
"mime": "^4.0.4",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
@ -78,11 +80,10 @@
|
|||||||
"short-unique-id": "^5.2.0",
|
"short-unique-id": "^5.2.0",
|
||||||
"slate": "^0.103.0",
|
"slate": "^0.103.0",
|
||||||
"slate-react": "^0.109.0",
|
"slate-react": "^0.109.0",
|
||||||
|
"tippy.js": "^6.3.7",
|
||||||
"tiptap-extension-resize-image": "^1.1.8",
|
"tiptap-extension-resize-image": "^1.1.8",
|
||||||
"vite-plugin-top-level-await": "^1.4.4",
|
"vite-plugin-top-level-await": "^1.4.4",
|
||||||
"vite-plugin-wasm": "^3.3.0",
|
"vite-plugin-wasm": "^3.3.0"
|
||||||
"html-to-text": "^9.0.5",
|
|
||||||
"tippy.js": "^6.3.7"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/dom": "^10.3.0",
|
"@testing-library/dom": "^10.3.0",
|
||||||
|
@ -77,7 +77,7 @@ display: flex;
|
|||||||
border: 1px solid var(--50-white, rgba(255, 255, 255, 0.5));
|
border: 1px solid var(--50-white, rgba(255, 255, 255, 0.5));
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 140px;
|
width: auto;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
padding: 5px 15px 5px 15px;
|
padding: 5px 15px 5px 15px;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
|
93
src/App.tsx
93
src/App.tsx
@ -43,10 +43,13 @@ import Success from "./assets/svgs/Success.svg";
|
|||||||
import Info from "./assets/svgs/Info.svg";
|
import Info from "./assets/svgs/Info.svg";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { FilePicker } from '@capawesome/capacitor-file-picker';
|
import { FilePicker } from '@capawesome/capacitor-file-picker';
|
||||||
|
import './utils/seedPhrase/RandomSentenceGenerator';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createAccount,
|
createAccount,
|
||||||
generateRandomSentence,
|
generateRandomSentence,
|
||||||
saveFileToDisk,
|
saveFileToDisk,
|
||||||
|
saveSeedPhraseToDisk,
|
||||||
} from "./utils/generateWallet/generateWallet";
|
} from "./utils/generateWallet/generateWallet";
|
||||||
import { kdf } from "./deps/kdf";
|
import { kdf } from "./deps/kdf";
|
||||||
import { generateSaveWalletData } from "./utils/generateWallet/storeWallet";
|
import { generateSaveWalletData } from "./utils/generateWallet/storeWallet";
|
||||||
@ -116,6 +119,7 @@ import { NotAuthenticated, manifestData } from "./ExtStates/NotAuthenticated";
|
|||||||
import { openIndexedDB, showSaveFilePicker } from "./components/Apps/useQortalMessageListener";
|
import { openIndexedDB, showSaveFilePicker } from "./components/Apps/useQortalMessageListener";
|
||||||
import { fileToBase64 } from "./utils/fileReading";
|
import { fileToBase64 } from "./utils/fileReading";
|
||||||
import { handleGetFileFromIndexedDB } from "./utils/indexedDB";
|
import { handleGetFileFromIndexedDB } from "./utils/indexedDB";
|
||||||
|
import { Wallets } from "./Wallets";
|
||||||
|
|
||||||
|
|
||||||
type extStates =
|
type extStates =
|
||||||
@ -132,7 +136,8 @@ type extStates =
|
|||||||
| "wallet-dropped"
|
| "wallet-dropped"
|
||||||
| "web-app-request-buy-order"
|
| "web-app-request-buy-order"
|
||||||
| "buy-order-submitted"
|
| "buy-order-submitted"
|
||||||
| "group";
|
| "group"
|
||||||
|
| "wallets";
|
||||||
|
|
||||||
interface MyContextInterface {
|
interface MyContextInterface {
|
||||||
txList: any[];
|
txList: any[];
|
||||||
@ -436,7 +441,19 @@ function App() {
|
|||||||
const [fullScreen, setFullScreen] = useRecoilState(fullScreenAtom);
|
const [fullScreen, setFullScreen] = useRecoilState(fullScreenAtom);
|
||||||
|
|
||||||
const { toggleFullScreen } = useAppFullScreen(setFullScreen);
|
const { toggleFullScreen } = useAppFullScreen(setFullScreen);
|
||||||
|
const generatorRef = useRef(null)
|
||||||
|
const exportSeedphrase = async ()=> {
|
||||||
|
try {
|
||||||
|
const seedPhrase = generatorRef.current.parsedString
|
||||||
|
saveSeedPhraseToDisk(seedPhrase)
|
||||||
|
await showInfo({
|
||||||
|
message: `Your seed phrase was saved to INTERNAL storage, in the document folder. Keep that file secure.`,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Attach a global event listener for double-click
|
// Attach a global event listener for double-click
|
||||||
const handleDoubleClick = () => {
|
const handleDoubleClick = () => {
|
||||||
@ -992,7 +1009,7 @@ function App() {
|
|||||||
res();
|
res();
|
||||||
}, 250);
|
}, 250);
|
||||||
});
|
});
|
||||||
const res = await createAccount();
|
const res = await createAccount(generatorRef.current.parsedString);
|
||||||
const wallet = await res.generateSaveWalletData(
|
const wallet = await res.generateSaveWalletData(
|
||||||
walletToBeDownloadedPassword,
|
walletToBeDownloadedPassword,
|
||||||
crypto.kdfThreads,
|
crypto.kdfThreads,
|
||||||
@ -2282,7 +2299,35 @@ function App() {
|
|||||||
</CustomButton>
|
</CustomButton>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{rawWallet && extState === "wallet-dropped" && (
|
{extState === "wallets" && (
|
||||||
|
<>
|
||||||
|
<Spacer height="22px" />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
width: "100%",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
paddingLeft: "22px",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setRawWallet(null);
|
||||||
|
setExtstate("not-authenticated");
|
||||||
|
logoutFunc();
|
||||||
|
}}
|
||||||
|
src={Return}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Wallets setRawWallet={setRawWallet} setExtState={setExtstate} rawWallet={rawWallet} />
|
||||||
|
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{rawWallet && extState === "wallet-dropped" && (
|
||||||
<>
|
<>
|
||||||
<Spacer height="22px" />
|
<Spacer height="22px" />
|
||||||
<Box
|
<Box
|
||||||
@ -2300,8 +2345,8 @@ function App() {
|
|||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setRawWallet(null);
|
setRawWallet(null);
|
||||||
setExtstate("not-authenticated");
|
setExtstate("wallets");
|
||||||
logoutFunc()
|
logoutFunc();
|
||||||
}}
|
}}
|
||||||
src={Return}
|
src={Return}
|
||||||
/>
|
/>
|
||||||
@ -2322,9 +2367,11 @@ function App() {
|
|||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "flex-start",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Typography>{rawWallet?.name ? rawWallet?.name : rawWallet?.address0}</Typography>
|
||||||
|
<Spacer height="10px" />
|
||||||
<TextP
|
<TextP
|
||||||
sx={{
|
sx={{
|
||||||
textAlign: "start",
|
textAlign: "start",
|
||||||
@ -2492,6 +2539,40 @@ await showInfo({
|
|||||||
Set up your Qortal account
|
Set up your Qortal account
|
||||||
</TextP>
|
</TextP>
|
||||||
<Spacer height="14px" />
|
<Spacer height="14px" />
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
maxWidth: '100%',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: '10px'
|
||||||
|
}}>
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
maxWidth: '400px',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '10px'
|
||||||
|
}}>
|
||||||
|
<Typography sx={{
|
||||||
|
fontSize: '14px'
|
||||||
|
}}>Your seedphrase</Typography>
|
||||||
|
<Typography sx={{
|
||||||
|
fontSize: '12px'
|
||||||
|
}}>Only shown once! Please copy and keep safe!</Typography>
|
||||||
|
|
||||||
|
<random-sentence-generator
|
||||||
|
ref={generatorRef}
|
||||||
|
template="adverb verb noun adjective noun adverb verb noun adjective noun adjective verbed adjective noun"
|
||||||
|
|
||||||
|
></random-sentence-generator>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<CustomButton sx={{
|
||||||
|
padding: '7px',
|
||||||
|
fontSize: '12px'
|
||||||
|
}} onClick={exportSeedphrase}>
|
||||||
|
Export Seedphrase
|
||||||
|
</CustomButton>
|
||||||
|
<Spacer height="14px" />
|
||||||
<CustomLabel htmlFor="standard-adornment-password">
|
<CustomLabel htmlFor="standard-adornment-password">
|
||||||
Wallet Password
|
Wallet Password
|
||||||
</CustomLabel>
|
</CustomLabel>
|
||||||
|
@ -261,15 +261,12 @@ export const NotAuthenticated = ({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
gap: "10px",
|
gap: "10px",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
marginLeft: "28px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CustomButton onClick={handleFilePick}>
|
<CustomButton onClick={()=> setExtstate('wallets')}>
|
||||||
Authenticate
|
Wallets
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
<Tooltip title="Authenticate by importing your Qortal JSON file" arrow>
|
|
||||||
<img src={Info} />
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Spacer height="6px" />
|
<Spacer height="6px" />
|
||||||
@ -277,8 +274,7 @@ export const NotAuthenticated = ({
|
|||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
gap: "10px",
|
gap: "10px",
|
||||||
alignItems: "center",
|
alignItems: "center"
|
||||||
marginLeft: "28px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CustomButton
|
<CustomButton
|
||||||
@ -289,12 +285,7 @@ export const NotAuthenticated = ({
|
|||||||
Create account
|
Create account
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
|
|
||||||
<img
|
|
||||||
src={Info}
|
|
||||||
style={{
|
|
||||||
visibility: "hidden",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Spacer height="15px" />
|
<Spacer height="15px" />
|
||||||
|
|
||||||
|
586
src/Wallets.tsx
Normal file
586
src/Wallets.tsx
Normal file
@ -0,0 +1,586 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import List from "@mui/material/List";
|
||||||
|
import ListItem from "@mui/material/ListItem";
|
||||||
|
import Divider from "@mui/material/Divider";
|
||||||
|
import ListItemText from "@mui/material/ListItemText";
|
||||||
|
import ListItemAvatar from "@mui/material/ListItemAvatar";
|
||||||
|
import Avatar from "@mui/material/Avatar";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
ButtonBase,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { CustomButton } from "./App-styles";
|
||||||
|
import { useDropzone } from "react-dropzone";
|
||||||
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
|
import { Label } from "./components/Group/AddGroup";
|
||||||
|
import { Spacer } from "./common/Spacer";
|
||||||
|
import { getWallets, storeWallets, walletVersion } from "./background";
|
||||||
|
import { useModal } from "./common/useModal";
|
||||||
|
import PhraseWallet from "./utils/generateWallet/phrase-wallet";
|
||||||
|
import { decryptStoredWalletFromSeedPhrase } from "./utils/decryptWallet";
|
||||||
|
import { crypto } from "./constants/decryptWallet";
|
||||||
|
import { LoadingButton } from "@mui/lab";
|
||||||
|
import { PasswordField } from "./components";
|
||||||
|
import { FilePicker } from '@capawesome/capacitor-file-picker';
|
||||||
|
|
||||||
|
const parsefilenameQortal = (filename) => {
|
||||||
|
return filename.startsWith("qortal_backup_") ? filename.slice(14) : filename;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
|
||||||
|
const [wallets, setWallets] = useState([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [seedValue, setSeedValue] = useState("");
|
||||||
|
const [seedName, setSeedName] = useState("");
|
||||||
|
const [seedError, setSeedError] = useState("");
|
||||||
|
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [isOpenSeedModal, setIsOpenSeedModal] = useState(false);
|
||||||
|
const [isLoadingEncryptSeed, setIsLoadingEncryptSeed] = useState(false);
|
||||||
|
|
||||||
|
const { isShow, onCancel, onOk, show } = useModal();
|
||||||
|
|
||||||
|
const handleFilePick = async () => {
|
||||||
|
try {
|
||||||
|
const resultPermission = await FilePicker.checkPermissions();
|
||||||
|
// Open the file picker to select a JSON file
|
||||||
|
const result = await FilePicker.pickFiles({
|
||||||
|
types: ['application/json'], // Restrict to JSON files
|
||||||
|
multiple: true, // Allow only one file
|
||||||
|
readData: true,
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.files.length > 0) {
|
||||||
|
let importedWallets: any = [];
|
||||||
|
|
||||||
|
for (const file of result.files) {
|
||||||
|
try {
|
||||||
|
const decodedData = atob(file.data); // `atob` decodes Base64 to a string
|
||||||
|
const parsedFile = JSON.parse(decodedData);
|
||||||
|
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
const requiredFields = [
|
||||||
|
"address0",
|
||||||
|
"salt",
|
||||||
|
"iv",
|
||||||
|
"version",
|
||||||
|
"encryptedSeed",
|
||||||
|
"mac",
|
||||||
|
"kdfThreads",
|
||||||
|
];
|
||||||
|
for (const field of requiredFields) {
|
||||||
|
if (!(field in parsedFile)) throw new Error(`${field} not found in JSON`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the state with parsed wallet data
|
||||||
|
importedWallets.push({ ...parsedFile, filename: file?.name });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let uniqueInitialMap = new Map();
|
||||||
|
|
||||||
|
// Only add a message if it doesn't already exist in the Map
|
||||||
|
importedWallets.forEach((wallet) => {
|
||||||
|
if (!wallet?.address0) return;
|
||||||
|
if (!uniqueInitialMap.has(wallet?.address0)) {
|
||||||
|
uniqueInitialMap.set(wallet?.address0, wallet);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = Array.from(uniqueInitialMap.values());
|
||||||
|
if (data && data?.length > 0) {
|
||||||
|
const uniqueNewWallets = data.filter(
|
||||||
|
(newWallet) =>
|
||||||
|
!wallets.some(
|
||||||
|
(existingWallet) =>
|
||||||
|
existingWallet?.address0 === newWallet?.address0
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setWallets([...wallets, ...uniqueNewWallets]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.log("No file selected.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error picking JSON file:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
|
accept: {
|
||||||
|
"application/json": [".json"], // Only accept JSON files
|
||||||
|
},
|
||||||
|
onDrop: async (acceptedFiles) => {
|
||||||
|
const files: any = acceptedFiles;
|
||||||
|
let importedWallets: any = [];
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
const fileContents = await new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onabort = () => reject("File reading was aborted");
|
||||||
|
reader.onerror = () => reject("File reading has failed");
|
||||||
|
reader.onload = () => {
|
||||||
|
// Resolve the promise with the reader result when reading completes
|
||||||
|
resolve(reader.result);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read the file as text
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
|
if (typeof fileContents !== "string") continue;
|
||||||
|
const parsedData = JSON.parse(fileContents);
|
||||||
|
importedWallets.push({ ...parsedData, filename: file?.name });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let uniqueInitialMap = new Map();
|
||||||
|
|
||||||
|
// Only add a message if it doesn't already exist in the Map
|
||||||
|
importedWallets.forEach((wallet) => {
|
||||||
|
if (!wallet?.address0) return;
|
||||||
|
if (!uniqueInitialMap.has(wallet?.address0)) {
|
||||||
|
uniqueInitialMap.set(wallet?.address0, wallet);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = Array.from(uniqueInitialMap.values());
|
||||||
|
if (data && data?.length > 0) {
|
||||||
|
const uniqueNewWallets = data.filter(
|
||||||
|
(newWallet) =>
|
||||||
|
!wallets.some(
|
||||||
|
(existingWallet) =>
|
||||||
|
existingWallet?.address0 === newWallet?.address0
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setWallets([...wallets, ...uniqueNewWallets]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateWalletItem = (idx, wallet) => {
|
||||||
|
setWallets((prev) => {
|
||||||
|
let copyPrev = [...prev];
|
||||||
|
if (wallet === null) {
|
||||||
|
copyPrev.splice(idx, 1); // Use splice to remove the item
|
||||||
|
return copyPrev;
|
||||||
|
} else {
|
||||||
|
copyPrev[idx] = wallet; // Update the wallet at the specified index
|
||||||
|
return copyPrev;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSetSeedValue = async () => {
|
||||||
|
try {
|
||||||
|
setIsOpenSeedModal(true);
|
||||||
|
const { seedValue, seedName, password } = await show({
|
||||||
|
message: "",
|
||||||
|
publishFee: "",
|
||||||
|
});
|
||||||
|
setIsLoadingEncryptSeed(true);
|
||||||
|
const res = await decryptStoredWalletFromSeedPhrase(seedValue);
|
||||||
|
const wallet2 = new PhraseWallet(res, walletVersion);
|
||||||
|
const wallet = await wallet2.generateSaveWalletData(
|
||||||
|
password,
|
||||||
|
crypto.kdfThreads,
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
if (wallet?.address0) {
|
||||||
|
setWallets([
|
||||||
|
...wallets,
|
||||||
|
{
|
||||||
|
...wallet,
|
||||||
|
name: seedName,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
setIsOpenSeedModal(false);
|
||||||
|
setSeedValue("");
|
||||||
|
setSeedName("");
|
||||||
|
setPassword("");
|
||||||
|
setSeedError("");
|
||||||
|
} else {
|
||||||
|
setSeedError("Could not create wallet.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setSeedError(error?.message || "Could not create wallet.");
|
||||||
|
} finally {
|
||||||
|
setIsLoadingEncryptSeed(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedWalletFunc = (wallet) => {
|
||||||
|
setRawWallet(wallet);
|
||||||
|
setExtState("wallet-dropped");
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsLoading(true);
|
||||||
|
getWallets()
|
||||||
|
.then((res) => {
|
||||||
|
if (res && Array.isArray(res)) {
|
||||||
|
setWallets(res);
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoading && wallets && Array.isArray(wallets)) {
|
||||||
|
storeWallets(wallets);
|
||||||
|
}
|
||||||
|
}, [wallets, isLoading]);
|
||||||
|
|
||||||
|
if (isLoading) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{wallets?.length === 0 || !wallets ? (
|
||||||
|
<>
|
||||||
|
<Typography>No wallets saved</Typography>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Typography>Your saved wallets</Typography>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Spacer height="10px" />
|
||||||
|
{rawWallet && (
|
||||||
|
<Box>
|
||||||
|
<Typography>Selected Wallet:</Typography>
|
||||||
|
{rawWallet?.name && <Typography>{rawWallet.name}</Typography>}
|
||||||
|
{rawWallet?.address0 && (
|
||||||
|
<Typography>{rawWallet?.address0}</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{wallets?.length > 0 && (
|
||||||
|
<List
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: "500px",
|
||||||
|
bgcolor: "background.paper",
|
||||||
|
maxHeight: "60vh",
|
||||||
|
overflow: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{wallets?.map((wallet, idx) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WalletItem
|
||||||
|
setSelectedWallet={selectedWalletFunc}
|
||||||
|
key={wallet?.address0}
|
||||||
|
wallet={wallet}
|
||||||
|
idx={idx}
|
||||||
|
updateWalletItem={updateWalletItem}
|
||||||
|
/>
|
||||||
|
<Divider variant="inset" component="li" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
alignItems: "center",
|
||||||
|
position: wallets?.length === 0 ? "relative" : "fixed",
|
||||||
|
bottom: wallets?.length === 0 ? 'unset' : "20px",
|
||||||
|
right: wallets?.length === 0 ? 'unset' : "20px",
|
||||||
|
flexDirection: wallets?.length === 0 ? "column" : "row",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CustomButton
|
||||||
|
onClick={handleSetSeedValue}
|
||||||
|
sx={{
|
||||||
|
padding: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add seed-phrase
|
||||||
|
</CustomButton>
|
||||||
|
<CustomButton
|
||||||
|
sx={{
|
||||||
|
padding: "10px",
|
||||||
|
}}
|
||||||
|
onClick={handleFilePick}
|
||||||
|
>
|
||||||
|
|
||||||
|
Add wallets
|
||||||
|
</CustomButton>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
open={isOpenSeedModal}
|
||||||
|
aria-labelledby="alert-dialog-title"
|
||||||
|
aria-describedby="alert-dialog-description"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter" && seedValue && seedName && password) {
|
||||||
|
onOk({ seedValue, seedName, password });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-title">
|
||||||
|
Type or paste in your seed-phrase
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label>Name</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Name"
|
||||||
|
value={seedName}
|
||||||
|
onChange={(e) => setSeedName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Spacer height="7px" />
|
||||||
|
<Label>Seed-phrase</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Seed-phrase"
|
||||||
|
value={seedValue}
|
||||||
|
onChange={(e) => setSeedValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Spacer height="7px" />
|
||||||
|
|
||||||
|
<Label>Choose new password</Label>
|
||||||
|
<PasswordField
|
||||||
|
id="standard-adornment-password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
disabled={isLoadingEncryptSeed}
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpenSeedModal(false);
|
||||||
|
setSeedValue("");
|
||||||
|
setSeedName("");
|
||||||
|
setPassword("");
|
||||||
|
setSeedError("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<LoadingButton
|
||||||
|
loading={isLoadingEncryptSeed}
|
||||||
|
disabled={!seedValue || !seedName || !password}
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
if (!seedValue || !seedName || !password) return;
|
||||||
|
onOk({ seedValue, seedName, password });
|
||||||
|
}}
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</LoadingButton>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "14px",
|
||||||
|
visibility: seedError ? "visible" : "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{seedError}
|
||||||
|
</Typography>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [note, setNote] = useState("");
|
||||||
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (wallet?.name) {
|
||||||
|
setName(wallet.name);
|
||||||
|
}
|
||||||
|
if (wallet?.note) {
|
||||||
|
setNote(wallet.note);
|
||||||
|
}
|
||||||
|
}, [wallet]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ButtonBase
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedWallet(wallet);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItem
|
||||||
|
secondaryAction={
|
||||||
|
<IconButton
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsEdit(true);
|
||||||
|
}}
|
||||||
|
edge="end"
|
||||||
|
aria-label="edit"
|
||||||
|
>
|
||||||
|
<EditIcon
|
||||||
|
sx={{
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
alignItems="flex-start"
|
||||||
|
>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar alt="" src="/static/images/avatar/1.jpg" />
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
wallet?.name
|
||||||
|
? wallet.name
|
||||||
|
: wallet?.filename
|
||||||
|
? parsefilenameQortal(wallet?.filename)
|
||||||
|
: "No name"
|
||||||
|
}
|
||||||
|
secondary={
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
component="span"
|
||||||
|
variant="body2"
|
||||||
|
sx={{ color: "text.primary", display: "inline" }}
|
||||||
|
>
|
||||||
|
{wallet?.address0}
|
||||||
|
</Typography>
|
||||||
|
{wallet?.note}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</ButtonBase>
|
||||||
|
{isEdit && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
padding: "8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label>Name</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Name"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Spacer height="10px" />
|
||||||
|
<Label>Note</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Note"
|
||||||
|
value={note}
|
||||||
|
onChange={(e) => setNote(e.target.value)}
|
||||||
|
inputProps={{
|
||||||
|
maxLength: 100,
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Spacer height="10px" />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "20px",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => setIsEdit(false)}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "var(--unread)",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "var(--unread)",
|
||||||
|
},
|
||||||
|
"&:focus": {
|
||||||
|
backgroundColor: "var(--unread)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => updateWalletItem(idx, null)}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#5EB049",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "#5EB049",
|
||||||
|
},
|
||||||
|
"&:focus": {
|
||||||
|
backgroundColor: "#5EB049",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
updateWalletItem(idx, {
|
||||||
|
...wallet,
|
||||||
|
name,
|
||||||
|
note,
|
||||||
|
});
|
||||||
|
setIsEdit(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -782,6 +782,23 @@ export async function getSaveWallet() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getWallets() {
|
||||||
|
const res = await getData<any>("wallets").catch(() => null);
|
||||||
|
if (res) {
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function storeWallets(wallets) {
|
||||||
|
storeData("wallets", wallets)
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function clearAllNotifications() {
|
export async function clearAllNotifications() {
|
||||||
try {
|
try {
|
||||||
|
@ -45,6 +45,8 @@ body {
|
|||||||
|
|
||||||
.image-container {
|
.image-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
height: 100px !important;
|
||||||
|
width: 100px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-container img {
|
.image-container img {
|
||||||
@ -56,6 +58,7 @@ body {
|
|||||||
|
|
||||||
.image-container .hover-image {
|
.image-container .hover-image {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-container:hover .hover-image {
|
.image-container:hover .hover-image {
|
||||||
@ -64,6 +67,12 @@ body {
|
|||||||
|
|
||||||
.image-container:hover .base-image {
|
.image-container:hover .base-image {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container .base-image {
|
||||||
|
height: 100px !important;
|
||||||
|
width: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
|
@ -22,3 +22,13 @@ export const decryptStoredWallet = async (password, wallet) => {
|
|||||||
const decryptedBytes = AES_CBC.decrypt(encryptedSeedBytes, encryptionKey, false, iv)
|
const decryptedBytes = AES_CBC.decrypt(encryptedSeedBytes, encryptionKey, false, iv)
|
||||||
return decryptedBytes
|
return decryptedBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const decryptStoredWalletFromSeedPhrase = async (password) => {
|
||||||
|
console.log('p')
|
||||||
|
const threads = doInitWorkers(crypto.kdfThreads)
|
||||||
|
const salt = new Uint8Array(void 0)
|
||||||
|
|
||||||
|
|
||||||
|
const seed = await kdf(password, salt, threads)
|
||||||
|
return seed
|
||||||
|
}
|
@ -73,8 +73,8 @@ export function generateRandomSentence(template = 'adverb verb noun adjective no
|
|||||||
return parse(template);
|
return parse(template);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createAccount = async()=> {
|
export const createAccount = async(generatedSeedPhrase)=> {
|
||||||
const generatedSeedPhrase = generateRandomSentence()
|
if(!generatedSeedPhrase) throw new Error('No generated seed-phrase')
|
||||||
const threads = doInitWorkers(crypto.kdfThreads)
|
const threads = doInitWorkers(crypto.kdfThreads)
|
||||||
|
|
||||||
const seed = await kdf(generatedSeedPhrase, void 0, threads)
|
const seed = await kdf(generatedSeedPhrase, void 0, threads)
|
||||||
@ -97,3 +97,16 @@ export const createAccount = async()=> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const saveSeedPhraseToDisk = async (data) => {
|
||||||
|
|
||||||
|
const fileName = "qortal_seedphrase.txt"
|
||||||
|
|
||||||
|
await Filesystem.writeFile({
|
||||||
|
path: fileName,
|
||||||
|
data,
|
||||||
|
directory: Directory.Documents, // Save in the Documents folder
|
||||||
|
encoding: Encoding.UTF8,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
173
src/utils/seedPhrase/RandomSentenceGenerator.ts
Normal file
173
src/utils/seedPhrase/RandomSentenceGenerator.ts
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
// Author: irontiga <irontiga@gmail.com>
|
||||||
|
|
||||||
|
import { html, LitElement, css } from 'lit'
|
||||||
|
import * as WORDLISTS from './wordList'
|
||||||
|
|
||||||
|
class RandomSentenceGenerator extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
template: { type: String, attribute: 'template' },
|
||||||
|
parsedString: { type: String },
|
||||||
|
fetchedWordlistCount: { type: Number, value: 0 },
|
||||||
|
capitalize: { type: Boolean },
|
||||||
|
partsOfSpeechMap: { type: Object },
|
||||||
|
templateEntropy: { type: Number, reflect: true, attribute: 'template-entropy' },
|
||||||
|
maxWordLength: { type: Number, attribute: 'max-word-length' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.template = 'adjective noun verb adverb.'
|
||||||
|
this.maxWordLength = 0
|
||||||
|
this.parsedString = ''
|
||||||
|
this.fetchedWordlistCount = 0
|
||||||
|
this.capitalize = true
|
||||||
|
this.partsOfSpeechMap = {
|
||||||
|
'noun': 'nouns',
|
||||||
|
'adverb': 'adverbs',
|
||||||
|
'adv': 'adverbs',
|
||||||
|
'verb': 'verbs',
|
||||||
|
'interjection': 'interjections',
|
||||||
|
'adjective': 'adjectives',
|
||||||
|
'adj': 'adjectives',
|
||||||
|
'verbed': 'verbed'
|
||||||
|
}
|
||||||
|
this.partsOfSpeech = Object.keys(this.partsOfSpeechMap)
|
||||||
|
this._wordlists = WORDLISTS
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
div {
|
||||||
|
text-align: center;
|
||||||
|
width: 90%;
|
||||||
|
background-color: #1f2023;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<div>${this.parsedString}</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
firstUpdated() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
updated(changedProperties) {
|
||||||
|
let regen = false
|
||||||
|
|
||||||
|
if (changedProperties.has('template')) {
|
||||||
|
regen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changedProperties.has('maxWordLength')) {
|
||||||
|
console.dir(this.maxWordLength)
|
||||||
|
|
||||||
|
if (this.maxWordLength) {
|
||||||
|
const wl = { ...this._wordlists }
|
||||||
|
|
||||||
|
for (const partOfSpeech in this._wordlists) {
|
||||||
|
if (Array.isArray(this._wordlists[partOfSpeech])) {
|
||||||
|
wl[partOfSpeech] = this._wordlists[partOfSpeech].filter(word => word.length <= this.maxWordLength)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._wordlists = wl
|
||||||
|
}
|
||||||
|
|
||||||
|
regen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regen) this.generate()
|
||||||
|
}
|
||||||
|
|
||||||
|
_RNG(entropy) {
|
||||||
|
if (entropy > 1074) {
|
||||||
|
throw new Error('Javascript can not handle that much entropy!')
|
||||||
|
}
|
||||||
|
|
||||||
|
let randNum = 0
|
||||||
|
|
||||||
|
const crypto = window.crypto || window.msCrypto
|
||||||
|
|
||||||
|
if (crypto) {
|
||||||
|
const entropy256 = Math.ceil(entropy / 8)
|
||||||
|
|
||||||
|
let buffer = new Uint8Array(entropy256)
|
||||||
|
|
||||||
|
crypto.getRandomValues(buffer)
|
||||||
|
|
||||||
|
randNum = buffer.reduce((num, value) => {
|
||||||
|
return num * value
|
||||||
|
}, 1) / Math.pow(256, entropy256)
|
||||||
|
} else {
|
||||||
|
console.warn('Secure RNG not found. Using Math.random')
|
||||||
|
|
||||||
|
randNum = Math.random()
|
||||||
|
}
|
||||||
|
|
||||||
|
return randNum
|
||||||
|
}
|
||||||
|
|
||||||
|
setRNG(fn) {
|
||||||
|
this._RNG = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
_captitalize(str) {
|
||||||
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
getWord(partOfSpeech) {
|
||||||
|
const words = this._wordlists[this.partsOfSpeechMap[partOfSpeech]]
|
||||||
|
const requiredEntropy = Math.log(words.length) / Math.log(2)
|
||||||
|
const index = this._RNG(requiredEntropy) * words.length
|
||||||
|
|
||||||
|
return {
|
||||||
|
word: words[Math.round(index)],
|
||||||
|
entropy: words.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generate() {
|
||||||
|
this.parsedString = this.parse(this.template)
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(template) {
|
||||||
|
const split = template.split(/[\s]/g)
|
||||||
|
|
||||||
|
let entropy = 1
|
||||||
|
|
||||||
|
const final = split.map(word => {
|
||||||
|
const lower = word.toLowerCase()
|
||||||
|
|
||||||
|
this.partsOfSpeech.some(partOfSpeech => {
|
||||||
|
const partOfSpeechIndex = lower.indexOf(partOfSpeech) // Check it exists
|
||||||
|
const nextChar = word.charAt(partOfSpeech.length)
|
||||||
|
|
||||||
|
if (partOfSpeechIndex === 0 && !(nextChar && (nextChar.match(/[a-zA-Z]/g) != null))) {
|
||||||
|
const replacement = this.getWord(partOfSpeech)
|
||||||
|
word = replacement.word + word.slice(partOfSpeech.length) // Append the rest of the "word" (punctuation)
|
||||||
|
entropy = entropy * replacement.entropy
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return word
|
||||||
|
})
|
||||||
|
|
||||||
|
this.templateEntropy = Math.floor(Math.log(entropy) / Math.log(8))
|
||||||
|
|
||||||
|
return final.join(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.customElements.define('random-sentence-generator', RandomSentenceGenerator)
|
||||||
|
|
||||||
|
export default RandomSentenceGenerator
|
40
src/utils/seedPhrase/verb-past-tense.ts
Normal file
40
src/utils/seedPhrase/verb-past-tense.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
export const EXCEPTIONS = {
|
||||||
|
'are': 'were',
|
||||||
|
'eat': 'ate',
|
||||||
|
'go': 'went',
|
||||||
|
'have': 'had',
|
||||||
|
'inherit': 'inherited',
|
||||||
|
'is': 'was',
|
||||||
|
'run': 'ran',
|
||||||
|
'sit': 'sat',
|
||||||
|
'visit': 'visited'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPastTense = (verb, exceptions = EXCEPTIONS) => {
|
||||||
|
if (exceptions[verb]) {
|
||||||
|
return exceptions[verb]
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((/e$/i).test(verb)) {
|
||||||
|
return verb + 'd'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((/[aeiou]c$/i).test(verb)) {
|
||||||
|
return verb + 'ked'
|
||||||
|
}
|
||||||
|
|
||||||
|
// for american english only
|
||||||
|
if ((/el$/i).test(verb)) {
|
||||||
|
return verb + 'ed'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((/[aeio][aeiou][dlmnprst]$/).test(verb)) {
|
||||||
|
return verb + 'ed'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((/[aeiou][bdglmnprst]$/i).test(verb)) {
|
||||||
|
return verb.replace(/(.+[aeiou])([bdglmnprst])/, '$1$2$2ed')
|
||||||
|
}
|
||||||
|
|
||||||
|
return verb + 'ed'
|
||||||
|
}
|
32
src/utils/seedPhrase/wordList.ts
Normal file
32
src/utils/seedPhrase/wordList.ts
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user