(); + const { setToast } = useToasts(); const handlePaymentClose = () => { setShowPaymentSelector(false); @@ -41,6 +44,19 @@ export default function Purchase() { setShowCurrencySelector(false); }; + const showTransakErrorToast = () => { + setToast({ + type: "error", + content: browser.i18n.getMessage("transak_unavailable"), + duration: 2400 + }); + }; + + const finishUp = (quote: Quote | null) => { + setQuote(quote); + setLoading(false); + }; + //segment useEffect(() => { trackPage(PageType.TRANSAK_PURCHASE); @@ -84,8 +100,7 @@ export default function Purchase() { !selectedCurrency || !paymentMethod ) { - setLoading(false); - setQuote(null); + finishUp(null); return; } const baseUrl = "https://api.transak.com/api/v1/pricing/public/quotes"; @@ -106,18 +121,31 @@ export default function Purchase() { const url = `${baseUrl}?${params.toString()}`; try { - const response = await fetch(url); + const response = await retryWithDelay(() => fetch(url)); if (!response.ok) { - setQuote(null); - throw new Error("Network response was not ok"); + try { + const resJson = await response.json(); + if (resJson?.error?.message) { + setToast({ + type: "error", + content: resJson?.error?.message, + duration: 2400 + }); + } else { + throw new Error("Network response was not ok"); + } + } catch { + showTransakErrorToast(); + } + finishUp(null); + return; } const data = await response.json(); - setQuote(data.response); - setLoading(false); + finishUp(data.response); } catch (error) { console.error("Error fetching data:", error); - setQuote(null); - setLoading(false); + showTransakErrorToast(); + finishUp(null); } setLoading(false); }; diff --git a/src/routes/popup/receive.tsx b/src/routes/popup/receive.tsx index 081192fa..07f9a111 100644 --- a/src/routes/popup/receive.tsx +++ b/src/routes/popup/receive.tsx @@ -115,26 +115,26 @@ export default function Receive({ walletName, walletAddress }: ReceiveProps) { ); } -const Wrapper = styled.div` +export const Wrapper = styled.div` display: flex; flex-direction: column; height: calc(100vh - 72px); `; -const ContentWrapper = styled.div` +export const ContentWrapper = styled.div` display: flex; flex-direction: column; justify-content: center; `; -const AddressField = styled(ButtonV2)` +export const AddressField = styled(ButtonV2)` display: flex; align-items: center; gap: 5px; font-weight: 500; `; -const CopyAction = styled(CopyIcon)` +export const CopyAction = styled(CopyIcon)` font-size: 1.25rem; width: 1em; height: 1em; @@ -151,7 +151,7 @@ const CopyAction = styled(CopyIcon)` } `; -const QRCodeWrapper = styled.div` +export const QRCodeWrapper = styled.div` display: flex; justify-content: center; align-items: center; diff --git a/src/routes/popup/settings/wallets/[address]/qr.tsx b/src/routes/popup/settings/wallets/[address]/qr.tsx index 0bd00f90..1fb0fcd5 100644 --- a/src/routes/popup/settings/wallets/[address]/qr.tsx +++ b/src/routes/popup/settings/wallets/[address]/qr.tsx @@ -1,26 +1,230 @@ -import { useStorage } from "@plasmohq/storage/hook"; -import { useMemo } from "react"; -import Receive from "~routes/popup/receive"; -import { ExtensionStorage } from "~utils/storage"; -import type { StoredWallet } from "~wallets"; +import { + useToasts, + Section, + TooltipV2, + useInput, + ButtonV2, + InputV2, + Spacer, + Text +} from "@arconnect/components"; +import { CheckIcon, CopyIcon } from "@iconicicons/react"; +import copy from "copy-to-clipboard"; +import { QRCodeSVG } from "qrcode.react"; +import { + useEffect, + useRef, + useState, + type Key, + type MouseEventHandler +} from "react"; +import { useLocation } from "wouter"; +import HeadV2 from "~components/popup/HeadV2"; +import { WarningIcon } from "~components/popup/Token"; +import browser from "webextension-polyfill"; +import { Degraded, WarningWrapper } from "~routes/popup/send"; +import { formatAddress } from "~utils/format"; +import { getKeyfile, type DecryptedWallet } from "~wallets"; +import { freeDecryptedWallet } from "~wallets/encryption"; +import { + AddressField, + ContentWrapper, + CopyAction, + QRCodeWrapper, + Wrapper +} from "~routes/popup/receive"; +import { dataToFrames } from "qrloop"; +import { checkPassword } from "~wallets/auth"; export default function GenerateQR({ address }: { address: string }) { - // wallets - const [wallets] = useStorage( - { - key: "wallets", - instance: ExtensionStorage - }, - [] - ); + const [wallet, setWallet] = useState (null); + const [copied, setCopied] = useState(false); + const [loading, setLoading] = useState(false); + const [frames, setFrames] = useState ([]); - // this wallet - const wallet = useMemo( - () => wallets?.find((w) => w.address === address), - [wallets, address] - ); + const [, setLocation] = useLocation(); + const { setToast } = useToasts(); + const passwordInput = useInput(); + + const isHardware = wallet?.type === "hardware"; + + const copyAddress: MouseEventHandler = (e) => { + e.stopPropagation(); + copy(address); + setCopied(true); + setTimeout(() => setCopied(false), 1000); + setToast({ + type: "success", + duration: 2000, + content: `${formatAddress(address, 3)} ${browser.i18n.getMessage( + "copied_address_2" + )}` + }); + }; - if (!wallet) return <>>; + async function generateQr() { + try { + setLoading(true); + const isPasswordCorrect = await checkPassword(passwordInput.state); + if (isPasswordCorrect) { + const wallet = await getKeyfile(address); + setWallet(wallet); + } else { + passwordInput.setStatus("error"); + setToast({ + type: "error", + content: browser.i18n.getMessage("invalidPassword"), + duration: 2200 + }); + } + } catch { + } finally { + setLoading(false); + } + } - return ; + useEffect(() => { + if ((wallet as any)?.keyfile) { + setFrames(dataToFrames(JSON.stringify((wallet as any)?.keyfile))); + freeDecryptedWallet((wallet as any).keyfile); + } + }, [wallet]); + + useEffect(() => { + return () => setFrames([]); + }, []); + + return ( + + + ); } + +const QRCodeLoop = ({ + frames, + size, + fps +}: { + frames: string[]; + size: number; + fps: number; +}) => { + const [frame, setFrame] = useState(0); + const rafRef = useRef(null); + + useEffect(() => { + const nextFrame = (frame: number, frames: string[]) => { + frame = (frame + 1) % frames.length; + return frame; + }; + + let lastT: number; + const loop = (t: number) => { + rafRef.current = requestAnimationFrame(loop); + if (!lastT) lastT = t; + if ((t - lastT) * fps < 1000) return; + lastT = t; + setFrame((prevFrame) => nextFrame(prevFrame, frames)); + }; + rafRef.current = requestAnimationFrame(loop); + + return () => { + cancelAnimationFrame(rafRef.current); + }; + }, [frames, fps]); + + return ( +++ {wallet ? ( +{ + if (address) { + setLocation(`/quick-settings/wallets/${address}`); + } else { + setLocation("/"); + } + }} + /> + + {isHardware ? ( ++ ) : ( ++ + ) : ( ++ ++ + + {browser.i18n.getMessage("cannot_generate_qr_code")} + +++ + )} ++ ++ ++ + ++ {formatAddress(address ?? "", 6)} + ++ ++ + + )} ++ {browser.i18n.getMessage("generate_qr_code_title")} + +{ + if (e.key !== "Enter") return; + generateQr(); + }} + /> + + + {browser.i18n.getMessage("generate")} + ++ {frames.map((chunk: any, i: Key) => ( ++ ); +}; diff --git a/src/tokens/aoTokens/ao.ts b/src/tokens/aoTokens/ao.ts index 53e7b50a..3d25085e 100644 --- a/src/tokens/aoTokens/ao.ts +++ b/src/tokens/aoTokens/ao.ts @@ -163,21 +163,8 @@ export function useAoTokens( const balance = await timeoutPromise( (async () => { if (id === AO_NATIVE_TOKEN) { - const res = await dryrun({ - Id, - Owner: activeAddress, - process: AO_NATIVE_TOKEN_BALANCE_MIRROR, - tags: [{ name: "Action", value: "Balance" }] - }); - const balance = res.Messages[0].Data; - if (balance) { - return new Quantity( - BigInt(balance), - BigInt(12) - ).toString(); - } - // default return - return new Quantity(0, BigInt(12)).toString(); + const res = await getNativeTokenBalance(activeAddress); + return res; } else { let balance: string; if (refresh) { @@ -253,6 +240,17 @@ export async function getAoTokenBalance( } } +export async function getNativeTokenBalance(address: string): Promise++ ))} ++ { + const res = await dryrun({ + Id, + Owner: address, + process: AO_NATIVE_TOKEN_BALANCE_MIRROR, + tags: [{ name: "Action", value: "Balance" }] + }); + const balance = res.Messages[0].Data; + return balance ? new Quantity(BigInt(balance), BigInt(12)).toString() : "0"; +} + export function useAoTokensCache(): [TokenInfoWithBalance[], boolean] { const [balances, setBalances] = useState<{ id: string; balance: string }[]>( [] @@ -547,6 +545,6 @@ export interface TokenInfo { export type TokenInfoWithProcessId = TokenInfo & { processId: string }; export interface TokenInfoWithBalance extends TokenInfo { - id: string; + id?: string; balance: string; } diff --git a/src/utils/apps.ts b/src/utils/apps.ts index ac9f14fd..42a877a9 100644 --- a/src/utils/apps.ts +++ b/src/utils/apps.ts @@ -5,16 +5,17 @@ import arwikiLogo from "url:/assets/ecosystem/arwiki.png"; import bazarLogo from "url:/assets/ecosystem/bazar.png"; import protocollandLogo from "url:/assets/ecosystem/protocolland.svg"; import permaswapLogo from "url:/assets/ecosystem/permaswap.svg"; -import pianityLogo from "url:/assets/ecosystem/pianity.png"; import barkLogo from "url:/assets/ecosystem/bark.png"; -import ansLogo from "url:/assets/ecosystem/ans-logo.svg"; import arnsLogo from "url:/assets/ecosystem/arns.svg"; import astroLogo from "url:/assets/ecosystem/astro.png"; -import artByCityLogo from "url:/assets/ecosystem/artbycity.png"; import permapagesLogo from "url:/assets/ecosystem/permapages.svg"; -import echoLogo from "url:/assets/ecosystem/echo.svg"; -import permaFacts from "url:/assets/ecosystem/permafacts.svg"; -import arLogoLight from "url:/assets/ar/logo_light.png"; +import dexiLogo from "url:/assets/ecosystem/dexi.svg"; +import dcaAgentLogo from "url:/assets/ecosystem/autonomous-dca-agent.png"; +import aoLinkLogo from "url:/assets/ecosystem/aolink.svg"; +import llamaLogo from "url:/assets/ecosystem/llama.png"; +import arswapLogo from "url:/assets/ecosystem/arswap.png"; +import liquidopsLogo from "url:/assets/ecosystem/liquidops.svg"; +import betterideaLogo from "url:/assets/ecosystem/betteridea.png"; export interface App { name: string; @@ -38,8 +39,7 @@ export const apps: App[] = [ { name: "Bark", category: "Exchange", - description: - "Bark is the AO Computer's first decentralized Finance. It supports AMM trading pairs and extreme scalability.", + description: "Bark is the AO Computer's first decentralized exchange.", assets: { logo: barkLogo, thumbnail: "/apps/bark/thumbnail.png", @@ -54,7 +54,7 @@ export const apps: App[] = [ name: "Protocol.Land", category: "Storage", description: - "Code collaboration, reimagined. Protocol.Land is a decentralized, source controlled, code collaboration where you own your code.", + "Protocol.Land is a decentralized home for decentralized codebases.", assets: { logo: protocollandLogo, thumbnail: "/apps/protocolland/thumbnail.png", @@ -70,9 +70,9 @@ export const apps: App[] = [ }, { name: "Astro", - category: "De-fi", + category: "Defi", description: - "Astro USD (USDA) is the very first stablecoin in the Arweave (and AO Computer) ecosystem.", + "Astro introduces USDA as the first overcollateralized stablecoin on AO.", assets: { logo: astroLogo, thumbnail: "/apps/astro/thumbnail.png", @@ -86,18 +86,19 @@ export const apps: App[] = [ } }, { - name: "AFTR Market", - category: "Developer Tooling", + name: "LiquidOps", + category: "Defi", description: - "AFTR Market provides asset management and governance on-chain for Arweave assets.", + "A simple, secure lending & borrowing platform for AR & AO assets.", assets: { - logo: aftrmarketLogo, - thumbnail: "/apps/aftr/thumbnail.png" + logo: liquidopsLogo, + thumbnail: "/apps/astro/thumbnail.png", + lightBackground: "rgba(230, 235, 240, 1)", + darkBackground: "rgba(19, 28, 37, 1)" }, links: { - website: "https://www.aftr.market/", - twitter: "https://twitter.com/AftrMarket", - discord: "https://discord.gg/YEy8VpuNXR" + website: "https://www.liquidops.io", + twitter: "https://x.com/Liquid_Ops" } }, { @@ -107,7 +108,8 @@ export const apps: App[] = [ "The first fully decentralized atomic asset exchange built on the permaweb. Through the power of the Universal Content Marketplace (UCM) protocol and the Universal Data License (UDL) content creators can trade digital assets with real world rights.", assets: { logo: bazarLogo, - thumbnail: "/apps/bazar/thumbnail.gif" + thumbnail: "/apps/bazar/thumbnail.gif", + lightBackground: "rgba(230, 235, 240, 1)" }, links: { website: "https://bazar.arweave.dev", @@ -115,99 +117,112 @@ export const apps: App[] = [ } }, { - name: "Arweave Name System", - category: "Social", + name: "AFTR Market", + category: "Infrastructure", description: - "The Arweave Name System (ArNS) works similarly to traditional Domain Name Services - but with ArNS, the registry is decentralized, permanent, and stored on Arweave. It's a simple way to name and help you - and your users - find your data, apps, or websites on Arweave.", + "AFTR Market provides asset management and governance on-chain for Arweave assets.", assets: { - logo: arnsLogo, - thumbnail: "/apps/arns/thumbnail.jpeg" + logo: aftrmarketLogo, + thumbnail: "/apps/aftr/thumbnail.png" }, links: { - website: "https://arns.app", - twitter: "https://twitter.com/ar_io_network", - discord: "https://discord.com/invite/HGG52EtTc2", - github: "https://github.com/ar-io" + website: "https://www.aftr.market/", + twitter: "https://twitter.com/AftrMarket", + discord: "https://discord.gg/YEy8VpuNXR" } }, { - name: "Art By City", - category: "Publishing", + name: "Dexi", + category: "Defi", description: - "Art By City is a chain-agnostic Web3 art and creative content protocol built on Arweave. The protocol is governed by the Art By City DAO. The Art By City DAO is a Profit Sharing Community or PSC. The Art By City community governs development of the Art By City protocol, dApps, and tools artists will need to take control of their Web3 experience.", + "Dexi autonomously identifies, collects, and aggregates financial data from events within the AO network, including asset prices, token swaps, liquidity fluctuations, and token asset characteristics.", assets: { - logo: artByCityLogo, - thumbnail: "/apps/artbycity/thumbnail.png", - lightBackground: "rgba(255, 255, 255, 1)", - darkBackground: "rgba(255, 255, 255, 1)" + logo: dexiLogo, + thumbnail: "/apps/dexi/thumbnail.png", + lightBackground: "rgba(230, 235, 240, 1)" }, links: { - website: "https://artby.city", - twitter: "https://twitter.com/artbycity", - discord: "https://discord.gg/w4Yhc95b8p", - github: "https://github.com/art-by-city" + website: "https://dexi.arweave.dev/", + twitter: "https://x.com/autonomous_af" } }, { - name: "ECHO", - category: "Social", + name: "Autonomous DCA Agent", + category: "Agent", description: - "ECHO is the first decentralized social engagement protocol based on Arweave. Its goal is to provide the fundamental infrastructure of Web3 social by introducing the first comment widget that can be deployed on any Web3 website with permanent data storage, so that users can speak up for themselves in a decentralized, permissionless, and censorship-resistant environment. ", + "The Autonomous DCA Agent executes a dynamic dollar-cost-average (DCA) investment strategy across various liquidity pools within the AO ecosystem.", assets: { - logo: echoLogo, - thumbnail: "/apps/echo/thumbnail.png" + logo: dcaAgentLogo, + thumbnail: "/apps/autonomousdca/thumbnail.png", + lightBackground: "rgba(230, 235, 240, 1)" }, links: { - website: "https://0xecho.com", - twitter: "https://twitter.com/0x_ECHO", - discord: "https://discord.gg/KFxyaw9Wdj", - github: "https://github.com/0x-echo" + website: "https://dca_agent.arweave.dev/", + twitter: "https://x.com/autonomous_af" } }, { - name: "Permafacts", - category: "Publishing", + name: "ao Link", + category: "Developer Tooling", + description: + "ao.link serves as a message explorer for the ao Network, offering functionalities similar to block explorers in conventional blockchain systems.", + assets: { + logo: aoLinkLogo, + thumbnail: "/apps/aolink/logo.png", + lightBackground: "rgba(230, 235, 240, 1)" + }, + links: { + website: "https://www.ao.link/", + twitter: "https://x.com/TheDataOS" + } + }, + { + name: "Llama Land", + category: "Social", description: - "A provably neutral publishing platform, built on top of the #FactsProtocol, aimed at dis-intermediating the truth. Publish assertions, and take your position in the Fact Marketplace!", + "AI powered MMO game built on AO. Petition the Llama King for Llama Coin! 100% onchain.", assets: { - logo: permaFacts, - thumbnail: "/apps/permafacts/thumbnail.png" + logo: llamaLogo, + thumbnail: "/apps/llamaland/logo.png", + lightBackground: "rgba(230, 235, 240, 1)", + darkBackground: "rgba(19, 28, 37, 1)" }, links: { - website: "https://permafacts.arweave.dev", - twitter: "https://twitter.com/permafacts", - discord: "https://discord.gg/uGg8VAvqU7", - github: "https://github.com/facts-laboratory" + website: "https://llamaland.g8way.io/#/", + twitter: "https://x.com/LlamaLandAO" } }, { - name: "Pianity", - category: "NFTs", + name: "ArSwap", + category: "Exchange", description: - "Pianity is a music NFT platform – built on environmentally-conscious Arweave technology — where musicians and their community gather to create, sell, buy and collect songs in limited editions. Pianity's pioneering approach, which includes free listening for all, enables deeper connections between artists and their audience.", + "Unlocking DeFi on AO. Swap tokens, provide liquidity, and earn fees.", assets: { - logo: pianityLogo, - thumbnail: "/apps/pianity/thumbnail.png" + logo: arswapLogo, + thumbnail: "/apps/arswap/logo.png", + lightBackground: "rgba(230, 235, 240, 1)", + darkBackground: "rgba(19, 28, 37, 1)" }, links: { - website: "https://pianity.com", - twitter: "https://twitter.com/pianitynft", - discord: "https://discord.gg/pianity" + website: "https://arswap.org/", + twitter: "https://x.com/ar_swap" } }, { - name: "Arweave Name Service (ANS)", + name: "Arweave Name System", category: "Social", description: - "ans.gg is a popular name service built on top of the Arweave blockchain. Buy your domain once, own forever.", + "The Arweave Name System (ArNS) works similarly to traditional Domain Name Services - but with ArNS, the registry is decentralized, permanent, and stored on Arweave. It's a simple way to name and help you - and your users - find your data, apps, or websites on Arweave.", assets: { - logo: ansLogo, - thumbnail: "/apps/ans/thumbnail.png" + logo: arnsLogo, + thumbnail: "/apps/arns/thumbnail.jpeg", + lightBackground: "rgba(230, 235, 240, 1)" }, links: { - website: "https://ans.gg", - twitter: "https://twitter.com/ArweaveANS", - discord: "https://discord.gg/decentland" + website: "https://arns.app", + twitter: "https://twitter.com/ar_io_network", + discord: "https://discord.com/invite/HGG52EtTc2", + github: "https://github.com/ar-io" } }, { @@ -217,7 +232,9 @@ export const apps: App[] = [ "Create and manage your own permanent web3 profile and permaweb pages built on Arweave.", assets: { logo: permapagesLogo, - thumbnail: "/apps/permapages/thumbnail.png" + thumbnail: "/apps/permapages/thumbnail.png", + lightBackground: "rgba(230, 235, 240, 1)", + darkBackground: "rgba(19, 28, 37, 1)" }, links: { website: "https://permapages.app", @@ -243,17 +260,20 @@ export const apps: App[] = [ } }, { - name: "ArCode Studio", - category: "Development", - description: - "ArCode Studio is an online IDE for smartweave contracts. As ArCode works on the browser all the files are saved in cache memory and removed when the cache is cleared.", + name: "BetterIDEa IDE", + category: "Developer Tooling", + description: "Feature rich web IDE for building on AO", assets: { - logo: arLogoLight, - thumbnail: "/apps/arcode/thumbnail.jpeg" + logo: betterideaLogo, + thumbnail: "/apps/betteridea/thumbnail.png", + lightBackground: "rgba(240, 240, 240, 1)", + darkBackground: "rgba(20, 34, 19, 1)" }, links: { - website: "https://arcode.ar-io.dev", - github: "https://github.com/luckyr13/arcode" + website: "https://betteridea.dev", + twitter: "https://twitter.com/betteridea_dev", + discord: "https://discord.gg/nm6VKUQBrA", + github: "https://github.com/betteridea-dev" } }, { @@ -263,7 +283,8 @@ export const apps: App[] = [ "As MediaWiki is the software that powers Wikipedia, ArWiki is the software that powers the Arweave Wiki. However, ArWiki is a Web3 platform -- it is completely decentralized, and is hosted on and served from the Arweave permaweb itself.", assets: { logo: arwikiLogo, - thumbnail: "/apps/arwiki/thumbnail.jpeg" + thumbnail: "/apps/arwiki/thumbnail.jpeg", + lightBackground: "rgba(230, 235, 240, 1)" }, links: { website: "https://arwiki.wiki", @@ -277,7 +298,8 @@ export const apps: App[] = [ "A decentralized archival platform that preserves human history and culture digitally.", assets: { logo: alexLogo, - thumbnail: "/apps/alex/thumbnail.png" + thumbnail: "/apps/alex/thumbnail.png", + lightBackground: "rgba(230, 235, 240, 1)" }, links: { website: "https://alex.arweave.dev/", @@ -293,7 +315,7 @@ export const apps: App[] = [ assets: { logo: permaswapLogo, thumbnail: permaswapLogo, - lightBackground: "rgba(230, 235, 240, 1)", + // lightBackground: "rgba(230, 235, 240, 1)", darkBackground: "rgba(19, 28, 37, 1)" }, links: { diff --git a/src/utils/retry.ts b/src/utils/retry.ts new file mode 100644 index 00000000..4c7e48b0 --- /dev/null +++ b/src/utils/retry.ts @@ -0,0 +1,32 @@ +/** + * Retries a given function up to a maximum number of attempts. + * @param fn - The asynchronous function to retry, which should return a Promise. + * @param maxAttempts - The maximum number of attempts to make. + * @param delay - The delay between attempts in milliseconds. + * @return A Promise that resolves with the result of the function or rejects after all attempts fail. + */ +export async function retryWithDelay ( + fn: () => Promise , + maxAttempts: number = 3, + delay: number = 1000 +): Promise { + let attempts = 0; + + const attempt = async (): Promise => { + try { + return await fn(); + } catch (error) { + attempts += 1; + if (attempts < maxAttempts) { + // console.log(`Attempt ${attempts} failed, retrying...`) + return new Promise ((resolve) => + setTimeout(() => resolve(attempt()), delay) + ); + } else { + throw error; + } + } + }; + + return attempt(); +} diff --git a/src/wallets/index.ts b/src/wallets/index.ts index abcd442c..d572f8ee 100644 --- a/src/wallets/index.ts +++ b/src/wallets/index.ts @@ -208,6 +208,54 @@ export async function getActiveKeyfile(): Promise { return decryptedWallet; } +/** + * Get the wallet with decrypted JWK + * + * !!IMPORTANT!! + * + * When using this function, always make sure to remove the keyfile + * from the memory, after it is no longer needed, using the + * "freeDecryptedWallet(activekeyfile.keyfile)" function. + * + * @returns wallet with decrypted JWK + */ +export async function getKeyfile(address: string): Promise { + // fetch data from storage + const wallets = await getWallets(); + const wallet = wallets.find((wallet) => wallet.address === address); + + // return if hardware wallet + if (wallet.type === "hardware") { + return wallet; + } + + // get decryption key + let decryptionKey = await getDecryptionKey(); + + // unlock ArConnect if the decryption key is undefined + // this means that the user has to enter their decryption + // key so it can be used later + if (!decryptionKey && !!wallet) { + await authenticate({ + type: "unlock" + }); + + // re-read the decryption key + decryptionKey = await getDecryptionKey(); + } + + // decrypt keyfile + const decryptedKeyfile = await decryptWallet(wallet.keyfile, decryptionKey); + + // construct decrypted wallet object + const decryptedWallet: DecryptedWallet = { + ...wallet, + keyfile: decryptedKeyfile + }; + + return decryptedWallet; +} + /** * Add a wallet for the user * diff --git a/yarn.lock b/yarn.lock index 99d7b467..d888d11d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5428,6 +5428,11 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +charenc@0.0.2: + version "0.0.2" + resolved "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== + check-password-strength@^2.0.7: version "2.0.10" resolved "https://registry.yarnpkg.com/check-password-strength/-/check-password-strength-2.0.10.tgz#d716d767944f43aa83665cdf96a2c28e18b2a7b6" @@ -5882,6 +5887,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crypt@0.0.2: + version "0.0.2" + resolved "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== + crypto-browserify@^3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -7591,6 +7601,11 @@ is-buffer@^2.0.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== +is-buffer@~1.1.6: + version "1.1.6" + resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -8638,6 +8653,15 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +md5@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" + integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== + dependencies: + charenc "0.0.2" + crypt "0.0.2" + is-buffer "~1.1.6" + mdn-data@2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" @@ -10089,6 +10113,14 @@ qrcode.react@^1.0.1: prop-types "^15.6.0" qr.js "0.0.0" +qrloop@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/qrloop/-/qrloop-1.4.1.tgz#bfe90e6aeb4c4735edec13d14681edc56e79e29d" + integrity sha512-LXkwCl1Qd8imTHb+KqjMn+cHmuncyFT81AXoooWJvbG3+g9q61l9udSRPgY4cgl+5goQHuAK4teEdUF6UErYXw== + dependencies: + buffer "^6.0.3" + md5 "^2.3.0" + qs@^6.11.2: version "6.12.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.1.tgz#39422111ca7cbdb70425541cba20c7d7b216599a"