import { useEffect, useMemo, useRef, useState } from "react"; import reactLogo from "./assets/react.svg"; import viteLogo from "/vite.svg"; import "./App.css"; import { useDropzone } from "react-dropzone"; import { Box, CircularProgress, Input, InputLabel, Tooltip, Typography } from "@mui/material"; import { decryptStoredWallet } from "./utils/decryptWallet"; import { CountdownCircleTimer } from "react-countdown-circle-timer"; import Logo1 from "./assets/svgs/Logo1.svg"; import Logo1Dark from "./assets/svgs/Logo1Dark.svg"; import RefreshIcon from '@mui/icons-material/Refresh'; import Logo2 from "./assets/svgs/Logo2.svg"; import Copy from "./assets/svgs/Copy.svg"; import ltcLogo from './assets/ltc.png'; import qortLogo from './assets/qort.png' import { CopyToClipboard } from "react-copy-to-clipboard"; import Download from "./assets/svgs/Download.svg"; import Logout from "./assets/svgs/Logout.svg"; import Return from "./assets/svgs/Return.svg"; import Success from "./assets/svgs/Success.svg"; import Info from "./assets/svgs/Info.svg"; import { createAccount, generateRandomSentence, saveFileToDisk, } from "./utils/generateWallet/generateWallet"; import { kdf } from "./deps/kdf"; import { generateSaveWalletData } from "./utils/generateWallet/storeWallet"; import { crypto, walletVersion } from "./constants/decryptWallet"; import PhraseWallet from "./utils/generateWallet/phrase-wallet"; import { AddressBox, AppContainer, AuthenticatedContainer, AuthenticatedContainerInnerLeft, AuthenticatedContainerInnerRight, CustomButton, CustomInput, CustomLabel, TextItalic, TextP, TextSpan, } from "./App-styles"; import { Spacer } from "./common/Spacer"; import { Loader } from "./components/Loader"; import { PasswordField, ErrorText } from "./components"; type extStates = | "not-authenticated" | "authenticated" | "send-qort" | "web-app-request-connection" | "web-app-request-payment" | "web-app-request-authentication" | "download-wallet" | "create-wallet" | "transfer-success-regular" | "transfer-success-request" | "wallet-dropped" | "web-app-request-buy-order" | "buy-order-submitted" ; function App() { const [extState, setExtstate] = useState("not-authenticated"); const [backupjson, setBackupjson] = useState(null); const [rawWallet, setRawWallet] = useState(null); const [ltcBalanceLoading, setLtcBalanceLoading] = useState(false); const [qortBalanceLoading, setQortBalanceLoading] = useState(false) const [decryptedWallet, setdecryptedWallet] = useState(null); const [requestConnection, setRequestConnection] = useState(null); const [requestBuyOrder, setRequestBuyOrder] = useState(null); const [authenticatedMode, setAuthenticatedMode] = useState('qort') const [requestAuthentication, setRequestAuthentication] = useState(null); const [userInfo, setUserInfo] = useState(null); const [balance, setBalance] = useState(null); const [ltcBalance, setLtcBalance] = useState(null) const [paymentTo, setPaymentTo] = useState(""); const [paymentAmount, setPaymentAmount] = useState(0); const [paymentPassword, setPaymentPassword] = useState(""); const [sendPaymentError, setSendPaymentError] = useState(""); const [sendPaymentSuccess, setSendPaymentSuccess] = useState(""); const [countdown, setCountdown] = useState(null); const [walletToBeDownloaded, setWalletToBeDownloaded] = useState(null); const [walletToBeDownloadedPassword, setWalletToBeDownloadedPassword] = useState(""); const [authenticatePassword, setAuthenticatePassword] = useState(""); const [sendqortState, setSendqortState] = useState(null); const [isLoading, setIsLoading] = useState(false) const [ walletToBeDownloadedPasswordConfirm, setWalletToBeDownloadedPasswordConfirm, ] = useState(""); const [walletToBeDownloadedError, setWalletToBeDownloadedError] = useState(""); const [walletToBeDecryptedError, setWalletToBeDecryptedError] = useState(""); const holdRefExtState = useRef("not-authenticated") useEffect(() => { if (extState) { holdRefExtState.current = extState } }, [extState]) const address = useMemo(() => { if (!rawWallet?.address0) return ""; return rawWallet.address0; }, [rawWallet]); const { getRootProps, getInputProps } = useDropzone({ accept: { "application/json": [".json"], // Only accept JSON files }, maxFiles: 1, onDrop: async (acceptedFiles) => { const file: any = acceptedFiles[0]; 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); }); let error: any = null; let pf: any; try { if (typeof fileContents !== "string") return; pf = JSON.parse(fileContents); } catch (e) { } try { const requiredFields = [ "address0", "salt", "iv", "version", "encryptedSeed", "mac", "kdfThreads", ]; for (const field of requiredFields) { if (!(field in pf)) throw new Error(field + " not found in JSON"); } // storeWalletInfo(pf); setRawWallet(pf); // setExtstate("authenticated"); setExtstate("wallet-dropped"); setdecryptedWallet(null); } catch (e) { console.log(e); error = e; } }, }); const saveWalletFunc = async (password: string) => { let wallet = structuredClone(rawWallet); const res = await decryptStoredWallet(password, wallet); const wallet2 = new PhraseWallet(res, walletVersion); wallet = await wallet2.generateSaveWalletData( password, crypto.kdfThreads, () => { } ); setWalletToBeDownloaded({ wallet, qortAddress: rawWallet.address0, }); return { wallet, qortAddress: rawWallet.address0, }; }; const storeWalletInfo = (wallet: any) => { chrome.runtime.sendMessage( { action: "storeWalletInfo", wallet }, (response) => { if (response) { } } ); chrome.tabs.query( { active: true, currentWindow: true }, function (tabs: any[]) { chrome.tabs.sendMessage( tabs[0].id, { from: "popup", subject: "anySubject" }, function (response) { } ); } ); }; const getBalanceFunc = () => { setQortBalanceLoading(true) chrome.runtime.sendMessage({ action: "balance" }, (response) => { if (response && !response?.error) { setBalance(response); } setQortBalanceLoading(false) }); }; const getLtcBalanceFunc = () => { setLtcBalanceLoading(true) chrome.runtime.sendMessage({ action: "ltcBalance" }, (response) => { if (response && !response?.error) { setLtcBalance(response); } setLtcBalanceLoading(false) }); }; const sendCoinFunc = () => { setSendPaymentError(""); setSendPaymentSuccess(""); if (!paymentTo) { setSendPaymentError("Please enter a recipient"); return; } if (!paymentAmount) { setSendPaymentError("Please enter an amount greater than 0"); return; } if (!paymentPassword) { setSendPaymentError("Please enter your wallet password"); return; } setIsLoading(true) chrome.runtime.sendMessage( { action: "sendCoin", payload: { amount: Number(paymentAmount), receiver: paymentTo.trim(), password: paymentPassword, }, }, (response) => { if (response?.error) { setSendPaymentError(response.error); } else { setExtstate("transfer-success-regular"); // setSendPaymentSuccess("Payment successfully sent"); } setIsLoading(false) } ); }; const clearAllStates = () => { setRequestConnection(null); setRequestAuthentication(null); }; useEffect(() => { // Listen for messages from the background script chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { // Check if the message is to update the state if (message.action === "UPDATE_STATE_CONFIRM_SEND_QORT") { // Update the component state with the received 'sendqort' state setSendqortState(message.payload); setExtstate("web-app-request-payment"); } else if (message.action === "closePopup") { // Update the component state with the received 'sendqort' state window.close(); } else if (message.action === "UPDATE_STATE_REQUEST_CONNECTION") { // Update the component state with the received 'sendqort' state setRequestConnection(message.payload); setExtstate("web-app-request-connection"); } else if (message.action === "UPDATE_STATE_REQUEST_BUY_ORDER") { // Update the component state with the received 'sendqort' state setRequestBuyOrder(message.payload); setExtstate("web-app-request-buy-order"); } else if (message.action === "UPDATE_STATE_REQUEST_AUTHENTICATION") { // Update the component state with the received 'sendqort' state setRequestAuthentication(message.payload); setExtstate("web-app-request-authentication"); } else if (message.action === "SET_COUNTDOWN") { setCountdown(message.payload); } }); }, []); //param = isDecline const confirmPayment = (isDecline: boolean) => { if (isDecline) { chrome.runtime.sendMessage( { action: "sendQortConfirmation", payload: { amount: sendqortState?.amount, receiver: sendqortState?.address, password: paymentPassword, interactionId: sendqortState?.interactionId, isDecline: true, }, }, (response) => { if (response) { setSendPaymentSuccess("Payment successfully sent"); } else { window.close(); } } ); return; } setIsLoading(true) chrome.runtime.sendMessage( { action: "sendQortConfirmation", payload: { amount: sendqortState.amount, receiver: sendqortState.address, password: paymentPassword, interactionId: sendqortState.interactionId, isDecline: false, }, }, (response) => { if (response === true) { setExtstate("transfer-success-request"); setCountdown(null); } else { setSendPaymentError( response?.error || "Unable to perform payment. Please try again." ); } setIsLoading(false) } ); }; const confirmBuyOrder = (isDecline: boolean) => { if (isDecline) { chrome.runtime.sendMessage( { action: "buyOrderConfirmation", payload: { crosschainAtInfo: requestBuyOrder?.crosschainAtInfo, interactionId: requestBuyOrder?.interactionId, isDecline: true, }, }, (response) => { window.close(); } ); return; } setIsLoading(true) chrome.runtime.sendMessage( { action: "buyOrderConfirmation", payload: { crosschainAtInfo: requestBuyOrder?.crosschainAtInfo, interactionId: requestBuyOrder?.interactionId, isDecline: false, }, }, (response) => { if (response === true) { setExtstate("buy-order-submitted"); setCountdown(null); } else { setSendPaymentError( response?.error || "Unable to perform payment. Please try again." ); } setIsLoading(false) } ); }; const responseToConnectionRequest = ( isOkay: boolean, hostname: string, interactionId: string ) => { chrome.runtime.sendMessage( { action: "responseToConnectionRequest", payload: { isOkay, hostname, interactionId }, }, (response) => { if (response === false || response === true) { window.close(); } } ); }; // const rawWalletRef = useRef(null) // useEffect(()=> { // rawWalletRef.current = rawWallet // }, [rawWallet]) useEffect(() => { try { setIsLoading(true) chrome.runtime.sendMessage({ action: "getWalletInfo" }, (response) => { if (response && response?.walletInfo) { setRawWallet(response?.walletInfo); if (holdRefExtState.current === 'web-app-request-payment' || holdRefExtState.current === 'web-app-request-connection' || holdRefExtState.current === 'web-app-request-buy-order') return setExtstate("authenticated"); } }); } catch (error) { } finally { setIsLoading(false) } }, []); useEffect(() => { if (!address) return; try { chrome.runtime.sendMessage({ action: "userInfo" }, (response) => { if (response && !response.error) { setUserInfo(response); } }); getBalanceFunc(); } catch (error) { } }, [address]); useEffect(() => { return () => { console.log("exit"); }; }, []); useEffect(()=> { if(authenticatedMode === 'ltc' && !ltcBalanceLoading && ltcBalance === null ){ getLtcBalanceFunc() } }, [authenticatedMode]) const confirmPasswordToDownload = async () => { try { setWalletToBeDownloadedError(""); if (!walletToBeDownloadedPassword) { setSendPaymentError("Please enter your password"); return; } setIsLoading(true) await new Promise((res) => { setTimeout(() => { res() }, 250) }) const res = await saveWalletFunc(walletToBeDownloadedPassword); } catch (error: any) { setWalletToBeDownloadedError(error?.message); } finally { setIsLoading(false) } }; const saveFileToDiskFunc = async () => { try { await saveFileToDisk( walletToBeDownloaded.wallet, walletToBeDownloaded.qortAddress ); } catch (error: any) { setWalletToBeDownloadedError(error?.message); } finally { } }; const createAccountFunc = async () => { try { if (!walletToBeDownloadedPassword) { setWalletToBeDownloadedError("Please enter a password"); return; } if (!walletToBeDownloadedPasswordConfirm) { setWalletToBeDownloadedError("Please confirm your password"); return; } if ( walletToBeDownloadedPasswordConfirm !== walletToBeDownloadedPassword ) { setWalletToBeDownloadedError("Password fields do not match!"); return; } setIsLoading(true) await new Promise((res) => { setTimeout(() => { res() }, 250) }) const res = await createAccount(); const wallet = await res.generateSaveWalletData( walletToBeDownloadedPassword, crypto.kdfThreads, () => { } ); chrome.runtime.sendMessage({ action: "decryptWallet", payload: { password: walletToBeDownloadedPassword, wallet } }, (response) => { if (response && !response?.error) { setRawWallet(wallet); setWalletToBeDownloaded({ wallet, qortAddress: wallet.address0, }); chrome.runtime.sendMessage({ action: "userInfo" }, (response2) => { setIsLoading(false) if (response2 && !response2.error) { setUserInfo(response); } }); getBalanceFunc(); } else if (response?.error) { setIsLoading(false) setWalletToBeDecryptedError(response.error) } }); } catch (error: any) { setWalletToBeDownloadedError(error?.message); setIsLoading(false) } }; const logoutFunc = () => { try { chrome.runtime.sendMessage({ action: "logout" }, (response) => { if (response) { resetAllStates(); } }); } catch (error) { } }; const returnToMain = () => { setPaymentTo(""); setPaymentAmount(0); setPaymentPassword(""); setSendPaymentError(""); setSendPaymentSuccess(""); setCountdown(null); setWalletToBeDownloaded(null); setWalletToBeDownloadedPassword(""); setExtstate("authenticated"); }; const resetAllStates = () => { setExtstate("not-authenticated"); setBackupjson(null); setRawWallet(null); setdecryptedWallet(null); setRequestConnection(null); setRequestBuyOrder(null) setRequestAuthentication(null); setUserInfo(null); setBalance(null); setLtcBalance(null) setPaymentTo(""); setPaymentAmount(0); setPaymentPassword(""); setSendPaymentError(""); setSendPaymentSuccess(""); setCountdown(null); setWalletToBeDownloaded(null); setWalletToBeDownloadedPassword(""); setWalletToBeDownloadedPasswordConfirm(""); setWalletToBeDownloadedError(""); setSendqortState(null); }; const authenticateWallet = async () => { try { setIsLoading(true) setWalletToBeDecryptedError('') await new Promise((res) => { setTimeout(() => { res() }, 250) }) chrome.runtime.sendMessage({ action: "decryptWallet", payload: { password: authenticatePassword, wallet: rawWallet } }, (response) => { if (response && !response?.error) { setAuthenticatePassword(""); setExtstate("authenticated"); setWalletToBeDecryptedError('') chrome.runtime.sendMessage({ action: "userInfo" }, (response) => { setIsLoading(false) if (response && !response.error) { setUserInfo(response); } }); getBalanceFunc(); } else if (response?.error) { setIsLoading(false) setWalletToBeDecryptedError(response.error) } }); } catch (error) { setWalletToBeDecryptedError('Unable to authenticate. Wrong password') } } return ( {extState === "not-authenticated" && ( <>
WELCOME TO YOUR

QORTAL WALLET
Authenticate { setExtstate("create-wallet"); }} > Create account )} {/* {extState !== "not-authenticated" && ( )} */} {extState === "authenticated" && ( {authenticatedMode === 'ltc' ? ( <> {rawWallet?.ltcAddress?.slice(0, 6)}... {rawWallet?.ltcAddress?.slice(-4)} {ltcBalanceLoading && } {ltcBalance && !isNaN(+ltcBalance) && !ltcBalanceLoading && ( {ltcBalance} LTC )} ) : ( <> {userInfo?.name} {rawWallet?.address0?.slice(0, 6)}... {rawWallet?.address0?.slice(-4)} {qortBalanceLoading && } {balance && ( {balance} QORT )} { setExtstate("send-qort"); }} > Transfer QORT )} { setExtstate("download-wallet"); }} src={Download} style={{ cursor: "pointer", }} /> {authenticatedMode === 'qort' && ( { setAuthenticatedMode('ltc') }} src={ltcLogo} style={{ cursor: "pointer", width: '20px', height: 'auto' }} /> )} {authenticatedMode === 'ltc' && ( { setAuthenticatedMode('qort') }} src={qortLogo} style={{ cursor: "pointer", width: '20px', height: 'auto' }} /> )} )} {extState === "send-qort" && ( <> Transfer QORT Balance: {balance} QORT To setPaymentTo(e.target.value)} autoComplete="off" /> Amount setPaymentAmount(+e.target.value)} autoComplete="off" /> Confirm Wallet Password setPaymentPassword(e.target.value)} autoComplete="off" /> {sendPaymentError} {/* {sendPaymentSuccess} */} { sendCoinFunc(); }} > Send )} {extState === "web-app-request-buy-order" && ( <> The Application

{" "} {requestBuyOrder?.hostname}

is requesting a buy order
{+requestBuyOrder?.crosschainAtInfo?.qortAmount} QORT FOR {requestBuyOrder?.crosschainAtInfo?.expectedForeignAmount} {requestBuyOrder?.crosschainAtInfo?.foreignBlockchain} {/* Confirm Wallet Password setPaymentPassword(e.target.value)} /> */} confirmBuyOrder(false)} > accept confirmBuyOrder(true)} > decline {sendPaymentError} )} {extState === "web-app-request-payment" && ( <> The Application

{" "} {sendqortState?.hostname}

is requesting a payment
{sendqortState?.description} {sendqortState?.amount} QORT {/* Confirm Wallet Password setPaymentPassword(e.target.value)} /> */} confirmPayment(false)} > accept confirmPayment(true)} > decline {sendPaymentError} )} {extState === "web-app-request-connection" && ( <>
The Application

{" "} {requestConnection?.hostname}

is requestion a connection
responseToConnectionRequest( true, requestConnection?.hostname, requestConnection.interactionId ) } > accept responseToConnectionRequest( false, requestConnection?.hostname, requestConnection.interactionId ) } > decline )} {extState === "web-app-request-authentication" && ( <>
The Application

{" "} {requestConnection?.hostname}

requests authentication
Authenticate { setExtstate("create-wallet"); }} > Create account )} {rawWallet && extState === 'wallet-dropped' && ( <> { setRawWallet(null); setExtstate("not-authenticated"); }} src={Return} />
Authenticate <> Wallet Password setAuthenticatePassword(e.target.value) } onKeyDown={(e) => { if (e.key === "Enter") { authenticateWallet(); } }} /> Authenticate {walletToBeDecryptedError} )} {extState === "download-wallet" && ( <>
Download Wallet {!walletToBeDownloaded && ( <> Confirm Wallet Password setWalletToBeDownloadedPassword(e.target.value) } /> Confirm password {walletToBeDownloadedError} )} {walletToBeDownloaded && ( <> Download wallet )} )} {extState === "create-wallet" && ( <> {!walletToBeDownloaded && ( <> { setExtstate("not-authenticated") }} src={Return} />
Set up your Qortal account Wallet Password setWalletToBeDownloadedPassword(e.target.value) } /> Confirm Wallet Password setWalletToBeDownloadedPasswordConfirm(e.target.value) } /> Create Account {walletToBeDownloadedError} )} {walletToBeDownloaded && ( <> Congrats, you’re all set up! { saveFileToDiskFunc(); returnToMain(); }} > Backup Account )} )} {extState === "transfer-success-regular" && ( <> The transfer was succesful! { returnToMain(); }} > Continue )} {extState === "transfer-success-request" && ( <> The transfer was succesful! { window.close(); }} > Continue )} {extState === "buy-order-submitted" && ( <> Your buy order was submitted { window.close(); }} > Close )} {countdown && ( {/* */} { window.close(); }} size={75} strokeWidth={8} > {({ remainingTime }) => {remainingTime}} )} {isLoading && }
); } export default App;