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 (
{wallets?.length === 0 || !wallets ? ( <> No wallets saved ) : ( <> Your saved wallets )} {rawWallet && ( Selected Wallet: {rawWallet?.name && {rawWallet.name}} {rawWallet?.address0 && ( {rawWallet?.address0} )} )} {wallets?.length > 0 && ( {wallets?.map((wallet, idx) => { return ( <> ); })} )} Add seed-phrase Add wallets { if (e.key === "Enter" && seedValue && seedName && password) { onOk({ seedValue, seedName, password }); } }} > Type or paste in your seed-phrase setSeedName(e.target.value)} /> setSeedValue(e.target.value)} /> setPassword(e.target.value)} autoComplete="off" /> { if (!seedValue || !seedName || !password) return; onOk({ seedValue, seedName, password }); }} autoFocus > Add {seedError}
); }; 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 ( <> { setSelectedWallet(wallet); }} sx={{ width: "100%", }} > { e.stopPropagation(); setIsEdit(true); }} edge="end" aria-label="edit" > } alignItems="flex-start" > {wallet?.address0} {wallet?.note} } /> {isEdit && ( setName(e.target.value)} sx={{ width: "100%", }} /> setNote(e.target.value)} inputProps={{ maxLength: 100, }} sx={{ width: "100%", }} /> )} ); };