From 63ecbba75a91cfa8bea7fb722bb16956de31ddfe Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Tue, 5 Mar 2024 14:01:49 +0100 Subject: [PATCH 1/5] feat: add back keystone functionalities --- src/routes/popup/send/auth.tsx | 6 +- src/routes/popup/send/confirm.tsx | 122 ++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/src/routes/popup/send/auth.tsx b/src/routes/popup/send/auth.tsx index ac8f9a953..93d3293d0 100644 --- a/src/routes/popup/send/auth.tsx +++ b/src/routes/popup/send/auth.tsx @@ -40,7 +40,6 @@ import { type Gateway } from "~gateways/gateway"; import { isUToken, sendRequest } from "~utils/send"; -import { isLocalWallet } from "~utils/assertions"; import { EventType, trackEvent } from "~utils/analytics"; interface Props { tokenID?: string; @@ -196,7 +195,10 @@ export default function SendAuth({ tokenID }: Props) { const arweave = new Arweave(gateway); const decryptedWallet = await getActiveKeyfile(); - isLocalWallet(decryptedWallet); + + if (decryptedWallet.type === "hardware") { + return setLoading(false); + } console.log( "transaction amount:", diff --git a/src/routes/popup/send/confirm.tsx b/src/routes/popup/send/confirm.tsx index 942f0cf4c..c9248aab5 100644 --- a/src/routes/popup/send/confirm.tsx +++ b/src/routes/popup/send/confirm.tsx @@ -23,6 +23,8 @@ import { fallbackGateway, type Gateway } from "~gateways/gateway"; +import AnimatedQRScanner from "~components/hardware/AnimatedQRScanner"; +import AnimatedQRPlayer from "~components/hardware/AnimatedQRPlayer"; import { getActiveKeyfile, getActiveWallet, type StoredWallet } from "~wallets"; import { isLocalWallet } from "~utils/assertions"; import { decryptWallet, freeDecryptedWallet } from "~wallets/encryption"; @@ -40,6 +42,10 @@ import { fractionedToBalance } from "~tokens/currency"; import { type Token } from "~tokens/token"; import { useContact } from "~contacts/hooks"; import { sendAoTransfer, useAo } from "~tokens/aoTokens/ao"; +import { useActiveWallet } from "~wallets/hooks"; +import { UR } from "@ngraveio/bc-ur"; +import { decodeSignature, transactionToUR } from "~wallets/hardware/keystone"; +import { useScanner } from "@arconnect/keystone-sdk"; interface Props { tokenID: string; @@ -462,6 +468,122 @@ export default function Confirm({ tokenID, qty }: Props) { } } + /** + * Hardware wallet functionalities + */ + + // current wallet + const wallet = useActiveWallet(); + + // load tx UR + const [transactionUR, setTransactionUR] = useState(); + const [preparedTx, setPreparedTx] = useState>(); + + useEffect(() => { + (async () => { + // get the tx from storage + const prepared = await prepare(recipient.address); + + // redirect to transfer if the + // transaction was not found + if (!prepared || !prepared.transaction) { + return push("/send/transfer"); + } + + // check if the current wallet + // is a hardware wallet + if (wallet?.type !== "hardware") return; + + const arweave = new Arweave(prepared.gateway); + const convertedTransaction = arweave.transactions.fromRaw( + prepared.transaction + ); + + // get tx UR + try { + setTransactionUR( + await transactionToUR( + convertedTransaction, + wallet.xfp, + wallet.publicKey + ) + ); + setPreparedTx(prepared); + } catch { + setToast({ + type: "error", + duration: 2300, + content: browser.i18n.getMessage("transaction_auth_ur_fail") + }); + push("/send/transfer"); + } + })(); + }, [wallet]); + + // current hardware wallet operation + const [hardwareStatus, setHardwareStatus] = useState<"play" | "scan">("play"); + + // qr-tx scanner + const scanner = useScanner( + // handle scanner success, + // post transfer + async (res) => { + setLoading(true); + + try { + if (!preparedTx) return; + + // get tx + const { gateway, type } = preparedTx; + const arweave = new Arweave(gateway); + const transaction = arweave.transactions.fromRaw( + preparedTx.transaction + ); + + if (!transaction) { + throw new Error("Transaction undefined"); + } + + if (wallet?.type !== "hardware") { + throw new Error("Wallet switched while signing"); + } + + // decode signature + const { id, signature } = await decodeSignature(res); + + // set signature + transaction.setSignature({ + id, + signature, + owner: wallet.publicKey + }); + + // post tx + await submitTx(transaction, arweave, type); + + setToast({ + type: "success", + content: browser.i18n.getMessage("sent_tx"), + duration: 2000 + }); + uToken + ? push("/") + : push( + `/transaction/${transaction.id}?back=${encodeURIComponent("/")}` + ); + } catch (e) { + console.log(e); + setToast({ + type: "error", + content: browser.i18n.getMessage("failed_tx"), + duration: 2000 + }); + } + + setLoading(false); + } + ); + return ( From 67b2470cca4a2160de06d506d1a9a32d61948788 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Tue, 5 Mar 2024 15:38:45 +0100 Subject: [PATCH 2/5] feat: finish up keystone --- src/routes/popup/send/confirm.tsx | 45 +++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/routes/popup/send/confirm.tsx b/src/routes/popup/send/confirm.tsx index c9248aab5..263d79196 100644 --- a/src/routes/popup/send/confirm.tsx +++ b/src/routes/popup/send/confirm.tsx @@ -1,4 +1,10 @@ -import { Input, Text, useInput, useToasts } from "@arconnect/components"; +import { + Input, + Spacer, + Text, + useInput, + useToasts +} from "@arconnect/components"; import { ArrowRightIcon } from "@iconicicons/react"; import styled from "styled-components"; import browser from "webextension-polyfill"; @@ -46,6 +52,7 @@ import { useActiveWallet } from "~wallets/hooks"; import { UR } from "@ngraveio/bc-ur"; import { decodeSignature, transactionToUR } from "~wallets/hardware/keystone"; import { useScanner } from "@arconnect/keystone-sdk"; +import Progress from "~components/Progress"; interface Props { tokenID: string; @@ -521,15 +528,13 @@ export default function Confirm({ tokenID, qty }: Props) { }, [wallet]); // current hardware wallet operation - const [hardwareStatus, setHardwareStatus] = useState<"play" | "scan">("play"); + const [hardwareStatus, setHardwareStatus] = useState<"play" | "scan">(); // qr-tx scanner const scanner = useScanner( // handle scanner success, // post transfer async (res) => { - setLoading(true); - try { if (!preparedTx) return; @@ -579,8 +584,6 @@ export default function Confirm({ tokenID, qty }: Props) { duration: 2000 }); } - - setLoading(false); } ); @@ -613,7 +616,7 @@ export default function Confirm({ tokenID, qty }: Props) {
- {token && ( + {token && !hardwareStatus && ( <> )} + {hardwareStatus === "play" && transactionUR && ( + + )} + {hardwareStatus === "scan" && ( + <> + + setToast({ + type: "error", + duration: 2300, + content: browser.i18n.getMessage(`keystone_${error}`) + }) + } + /> + + + {browser.i18n.getMessage( + "keystone_scan_progress", + `${scanner.progress.toFixed(0)}%` + )} + + + + )}
{/* Password if Necessary */} {needsSign && ( @@ -660,7 +688,8 @@ export default function Confirm({ tokenID, qty }: Props) { fullWidth disabled={(needsSign && !passwordInput.state) || isLoading} onClick={async () => { - await sendLocal(); + if (wallet.type === "local") await sendLocal(); + else setHardwareStatus("play"); }} > Confirm {">"} From 182894a720c8d3be47b0806e424c59202507063b Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Fri, 8 Mar 2024 23:38:01 +0100 Subject: [PATCH 3/5] fix: keystone send issues --- src/routes/popup/send/confirm.tsx | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/routes/popup/send/confirm.tsx b/src/routes/popup/send/confirm.tsx index 263d79196..7a5140bb6 100644 --- a/src/routes/popup/send/confirm.tsx +++ b/src/routes/popup/send/confirm.tsx @@ -488,6 +488,8 @@ export default function Confirm({ tokenID, qty }: Props) { useEffect(() => { (async () => { + if (!recipient?.address) return; + // get the tx from storage const prepared = await prepare(recipient.address); @@ -525,7 +527,7 @@ export default function Confirm({ tokenID, qty }: Props) { push("/send/transfer"); } })(); - }, [wallet]); + }, [wallet, recipient]); // current hardware wallet operation const [hardwareStatus, setHardwareStatus] = useState<"play" | "scan">(); @@ -641,7 +643,12 @@ export default function Confirm({ tokenID, qty }: Props) { )} {hardwareStatus === "play" && transactionUR && ( - + <> + + {browser.i18n.getMessage("sign_scan_qr")} + + + )} {hardwareStatus === "scan" && ( <> @@ -686,13 +693,20 @@ export default function Confirm({ tokenID, qty }: Props) { { if (wallet.type === "local") await sendLocal(); - else setHardwareStatus("play"); + else if (!hardwareStatus || hardwareStatus === "play") { + setHardwareStatus((val) => (val === "play" ? "scan" : "play")); + } }} > - Confirm {">"} + {(hardwareStatus === "play" && "Scan response") || "Confirm"} + {" >"}
From 877e0656ceb533ba32a28a021dcb25e29069df28 Mon Sep 17 00:00:00 2001 From: Marton Lederer Date: Wed, 13 Mar 2024 11:06:02 +0100 Subject: [PATCH 4/5] fix: double spending & track --- src/routes/popup/send/confirm.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/routes/popup/send/confirm.tsx b/src/routes/popup/send/confirm.tsx index 7a5140bb6..2964d8c01 100644 --- a/src/routes/popup/send/confirm.tsx +++ b/src/routes/popup/send/confirm.tsx @@ -547,6 +547,9 @@ export default function Confirm({ tokenID, qty }: Props) { preparedTx.transaction ); + // reset the prepared tx so we don't send it again + setPreparedTx(undefined); + if (!transaction) { throw new Error("Transaction undefined"); } @@ -573,6 +576,15 @@ export default function Confirm({ tokenID, qty }: Props) { content: browser.i18n.getMessage("sent_tx"), duration: 2000 }); + + const latestTxQty = Number( + (await ExtensionStorage.get("last_send_qty")) || "0" + ); + trackEvent(EventType.TX_SENT, { + contact: contact ? true : false, + amount: tokenID === "AR" ? latestTxQty : 0, + fee: networkFee + }); uToken ? push("/") : push( From 1d22ecafbe924b7e0f68061aa3311d43a1c73f7d Mon Sep 17 00:00:00 2001 From: nicholas ma Date: Wed, 13 Mar 2024 19:10:00 -0700 Subject: [PATCH 5/5] chore: version bump for beta --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b0c3548e0..5b83b9b2b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "arconnect", "displayName": "ArConnect", - "version": "1.5.0", + "version": "1.6.0", "description": "__MSG_extensionDescription__", "author": "th8ta", "packageManager": "yarn@1.22.18",