From fc5b2c5700c325b3e8e30a8ef86d5796a4a762d3 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 2 Jan 2025 13:44:44 +0545 Subject: [PATCH 1/8] refactor: update sign settings to transfer settings --- assets/_locales/en/messages.json | 18 ++- assets/_locales/zh_CN/messages.json | 18 ++- .../modules/dispatch/dispatch.background.ts | 4 +- src/api/modules/sign/sign.background.ts | 2 +- src/components/dashboard/SignSettings.tsx | 136 +++++++++--------- src/routes/dashboard/dashboard.constants.ts | 6 +- src/routes/popup/send/auth.tsx | 6 +- src/routes/popup/send/confirm.tsx | 14 +- 8 files changed, 108 insertions(+), 96 deletions(-) diff --git a/assets/_locales/en/messages.json b/assets/_locales/en/messages.json index 5f20fd140..0f6027dc1 100644 --- a/assets/_locales/en/messages.json +++ b/assets/_locales/en/messages.json @@ -471,9 +471,17 @@ "message": "Sign notification", "description": "Sign notification settings title" }, - "setting_sign_settings": { - "message": "Sign settings", - "description": "Sign settings title" + "setting_transfer_settings": { + "message": "Transfer settings", + "description": "Transfer settings title" + }, + "setting_transfer_settings_description": { + "message": "Allowance for requiring password when transferring", + "description": "Transfer settings description" + }, + "enable_transfer_settings": { + "message": "Enable transfer settings", + "description": "Enable transfer settings title" }, "setting_display_theme": { "message": "Theme", @@ -543,10 +551,6 @@ "message": "Show animation on wallet usage", "description": "ArConfetti settings description" }, - "setting_sign_notification_description": { - "message": "Notify when signing a transaction", - "description": "Sign notification settings description" - }, "setting_display_theme_description": { "message": "The theme of the extension UI", "description": "Display theme settings description" diff --git a/assets/_locales/zh_CN/messages.json b/assets/_locales/zh_CN/messages.json index b54e4a3b8..a8f454e09 100644 --- a/assets/_locales/zh_CN/messages.json +++ b/assets/_locales/zh_CN/messages.json @@ -467,9 +467,17 @@ "message": "签署通知", "description": "Sign notification settings title" }, - "setting_sign_settings": { - "message": "签名设置", - "description": "Sign settings title" + "setting_transfer_settings": { + "message": "转账设置", + "description": "Transfer settings title" + }, + "setting_transfer_settings_description": { + "message": "转账时需要密码的额度", + "description": "Transfer settings description" + }, + "enable_transfer_settings": { + "message": "启用转账设置", + "description": "Enable transfer settings title" }, "setting_display_theme": { "message": "主题", @@ -539,10 +547,6 @@ "message": "在使用钱包时显示动画", "description": "ArConfetti settings description" }, - "setting_sign_notification_description": { - "message": "签署交易时通知", - "description": "Sign notification settings description" - }, "setting_display_theme_description": { "message": "扩展 UI 的主题", "description": "Display theme settings description" diff --git a/src/api/modules/dispatch/dispatch.background.ts b/src/api/modules/dispatch/dispatch.background.ts index 464c4302c..05b3d0e7e 100644 --- a/src/api/modules/dispatch/dispatch.background.ts +++ b/src/api/modules/dispatch/dispatch.background.ts @@ -107,7 +107,7 @@ const background: BackgroundModuleFunction = async ( // await updateAllowance(appData.url, price); // show notification - await signNotification(0, dataEntry.id, appData.url, "dispatch"); + // await signNotification(0, dataEntry.id, appData.url, "dispatch"); // remove wallet from memory freeDecryptedWallet(keyfile); @@ -157,7 +157,7 @@ const background: BackgroundModuleFunction = async ( // await updateAllowance(appData.url, price); // show notification - await signNotification(price, transaction.id, appData.url); + // await signNotification(price, transaction.id, appData.url); // remove wallet from memory freeDecryptedWallet(keyfile); diff --git a/src/api/modules/sign/sign.background.ts b/src/api/modules/sign/sign.background.ts index 57ef992c2..70487b3c5 100644 --- a/src/api/modules/sign/sign.background.ts +++ b/src/api/modules/sign/sign.background.ts @@ -139,7 +139,7 @@ const background: BackgroundModuleFunction = async ( } // notify the user of the signing - await signNotification(price, transaction.id, appData.url); + // await signNotification(price, transaction.id, appData.url); // update allowance spent amount (in winstons) // await updateAllowance(appData.url, price); diff --git a/src/components/dashboard/SignSettings.tsx b/src/components/dashboard/SignSettings.tsx index 71975716a..1826cb206 100644 --- a/src/components/dashboard/SignSettings.tsx +++ b/src/components/dashboard/SignSettings.tsx @@ -1,95 +1,87 @@ -import { useState, useEffect } from "react"; -import PermissionCheckbox from "~components/auth/PermissionCheckbox"; -import { InputV2, Spacer, Text } from "@arconnect/components"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { InputV2, Text } from "@arconnect/components"; import browser from "webextension-polyfill"; import styled from "styled-components"; import { ExtensionStorage } from "~utils/storage"; import { useStorage } from "@plasmohq/storage/hook"; import { EventType, trackEvent } from "~utils/analytics"; +import { ToggleSwitch } from "~routes/popup/subscriptions/subscriptionDetails"; -export function SignSettingsDashboardView() { - const [signSettingsState, setSignSettingsState] = useState(false); - - const [signatureAllowance, setSignatureAllowance] = useStorage({ - key: "signatureAllowance", - instance: ExtensionStorage - }); - +export const SignSettingsDashboardView = () => { + const valueChanged = useRef(false); const [editingValue, setEditingValue] = useState(null); - useEffect(() => { - async function initializeSettings() { - const currentSetting = await ExtensionStorage.get( - "setting_sign_notification" - ); - setSignSettingsState(currentSetting); + const [signatureAllowance, setSignatureAllowance] = useStorage( + { + key: "signatureAllowance", + instance: ExtensionStorage + }, + (v) => (v === undefined ? 10 : Number(v)) + ); - // Check if signatureAllowance is set, if not, initialize to 10 - let allowance = await ExtensionStorage.get("signatureAllowance"); - if (allowance === undefined || allowance === null) { - await ExtensionStorage.set("signatureAllowance", 10); - setEditingValue(10); - } else { - setEditingValue(allowance); - } - } + const [signatureAllowanceEnabled, setSignatureAllowanceEnabled] = useStorage( + { + key: "signatureAllowanceEnabled", + instance: ExtensionStorage + }, + (v) => (v === undefined ? true : Boolean(v)) + ); - initializeSettings(); + const handleChange = useCallback((e: React.ChangeEvent) => { + valueChanged.current = true; + setEditingValue(e.target.value); }, []); - const toggleSignSettings = async () => { - const newSetting = !signSettingsState; - setSignSettingsState(newSetting); - await ExtensionStorage.set("setting_sign_notification", newSetting); - }; + const handleBlur = useCallback( + (e: React.FocusEvent) => { + const newAllowance = Number(e.target.value); - const handleBlur = async (e) => { - const newAllowance = Number(e.target.value); - if (newAllowance !== signatureAllowance) { - trackEvent(EventType.SEND_ALLOWANCE_CHANGE, { - before: signatureAllowance, - after: newAllowance - }); - setSignatureAllowance(newAllowance); + if (newAllowance !== signatureAllowance) { + trackEvent(EventType.SEND_ALLOWANCE_CHANGE, { + before: signatureAllowance, + after: newAllowance + }); + setSignatureAllowance(newAllowance); + } + }, + [signatureAllowance] + ); - // Save the updated allowance to the extension storage - await ExtensionStorage.set("signatureAllowance", newAllowance); + useEffect(() => { + if (!valueChanged.current) { + setEditingValue(signatureAllowance); } - }; - - const handleChange = (e) => { - setEditingValue(e.target.value); - }; + }, [signatureAllowance]); return ( - <> - - - {browser.i18n.getMessage( - !!signSettingsState ? "enabled" : "disabled" - )} -
- - {browser.i18n.getMessage("setting_sign_notification_description")} - -
- - + + {browser.i18n.getMessage("enable_transfer_settings")} + -
- + + + ); -} +}; const Wrapper = styled.div` position: relative; `; + +const ToggleSwitchWrapper = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: baseline; +`; diff --git a/src/routes/dashboard/dashboard.constants.ts b/src/routes/dashboard/dashboard.constants.ts index ebf0e6020..cf421fc47 100644 --- a/src/routes/dashboard/dashboard.constants.ts +++ b/src/routes/dashboard/dashboard.constants.ts @@ -83,9 +83,9 @@ export const basicSettings: (DashboardRouteConfig | Setting)[] = [ export const advancedSettings: (DashboardRouteConfig | Setting)[] = [ { - name: "sign_settings", - displayName: "setting_sign_settings", - description: "setting_sign_notification_description", + name: "transfer_settings", + displayName: "setting_transfer_settings", + description: "setting_transfer_settings_description", icon: BellIcon, component: SignSettingsDashboardView }, diff --git a/src/routes/popup/send/auth.tsx b/src/routes/popup/send/auth.tsx index 78f503ce7..ec9bc7376 100644 --- a/src/routes/popup/send/auth.tsx +++ b/src/routes/popup/send/auth.tsx @@ -62,11 +62,15 @@ export function SendAuthView({ params: { tokenID } }: SendAuthViewProps) { const [loading, setLoading] = useState(false); const [signAllowance, setSignAllowance] = useState(10); + const [signAllowanceEnabled, setSignAllowanceEnabled] = + useState(true); useEffect(() => { const fetchSignAllowance = async () => { const allowance = await ExtensionStorage.get("signatureAllowance"); + const enabled = await ExtensionStorage.get("signatureAllowanceEnabled"); setSignAllowance(Number(allowance)); + setSignAllowanceEnabled(enabled === "true" || enabled === undefined); }; fetchSignAllowance(); }, []); @@ -220,7 +224,7 @@ export function SendAuthView({ params: { tokenID } }: SendAuthViewProps) { ); // Check if the transaction amount is less than the signature allowance - if (transactionAmount.lte(signAllowance)) { + if (signAllowanceEnabled && transactionAmount.lte(signAllowance)) { // Process transaction without user signing try { // Decrypt wallet without user input diff --git a/src/routes/popup/send/confirm.tsx b/src/routes/popup/send/confirm.tsx index 276d4ea47..4e535aef7 100644 --- a/src/routes/popup/send/confirm.tsx +++ b/src/routes/popup/send/confirm.tsx @@ -124,13 +124,21 @@ export function ConfirmView({ 10 ); + const [allowanceEnabled] = useStorage( + { + key: "signatureAllowanceEnabled", + instance: ExtensionStorage + }, + true + ); + useEffect(() => { const fetchData = async () => { try { const data: TransactionData = await TempTransactionStorage.get("send"); if (data) { const qty = BigNumber(data.qty); - if (qty.lt(Number(allowance))) { + if (allowanceEnabled && qty.lt(Number(allowance))) { setNeedsSign(false); } else { setNeedsSign(true); @@ -161,7 +169,7 @@ export function ConfirmView({ fetchData(); trackPage(PageType.CONFIRM_SEND); - }, [allowance]); + }, [allowance, allowanceEnabled]); const [wallets] = useStorage( { @@ -387,7 +395,7 @@ export function ConfirmView({ isLocalWallet(decryptedWallet); const keyfile = decryptedWallet.keyfile; - if (transactionAmount.lte(allowance)) { + if (allowanceEnabled && transactionAmount.lte(allowance)) { try { convertedTransaction.setOwner(keyfile.n); From 85cc4bfff00f2e673e2c844781379dc1853d2b9a Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Thu, 2 Jan 2025 22:11:54 +0545 Subject: [PATCH 2/8] fix: return default gateway if no gateways found with wayfinder disabled --- src/gateways/wayfinder.ts | 44 +++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/gateways/wayfinder.ts b/src/gateways/wayfinder.ts index cd82b9ec1..20a29573f 100644 --- a/src/gateways/wayfinder.ts +++ b/src/gateways/wayfinder.ts @@ -21,33 +21,33 @@ export const STAKED_GQL_FULL_HISTORY: Requirements = { export async function findGateway( requirements: Requirements ): Promise { - // Get if the Wayfinder feature is enabled: - const wayfinderEnabled = await getSetting("wayfinder").getValue(); + try { + // Get if the Wayfinder feature is enabled: + const wayfinderEnabled = await getSetting("wayfinder").getValue(); - // This should have been loaded into the cache by handleGatewayUpdateAlarm, but sometimes this function might run - // before that, so in that case we fall back to the same behavior has having the Wayfinder disabled: - const procData = await getGatewayCache(); + // This should have been loaded into the cache by handleGatewayUpdateAlarm, but sometimes this function might run + // before that, so in that case we fall back to the same behavior has having the Wayfinder disabled: + const procData = await getGatewayCache(); - if (!wayfinderEnabled || !procData) { - if (requirements.arns) { - return { - host: "arweave.dev", - port: 443, - protocol: "https" - }; - } + if (!wayfinderEnabled || !procData) { + if (requirements.arns) { + return { + host: "arweave.dev", + port: 443, + protocol: "https" + }; + } - // wayfinder disabled or all the chain is needed - if (requirements.startBlock === 0) { - return defaultGateway; - } + // wayfinder disabled or all the chain is needed + if (requirements.startBlock === 0) { + return defaultGateway; + } - throw new Error( - wayfinderEnabled ? "Missing gateway cache" : "Wayfinder disabled" - ); - } + throw new Error( + wayfinderEnabled ? "Missing gateway cache" : "Wayfinder disabled" + ); + } - try { // this could probably be filtered out during the caching process const filteredGateways = procData.filter((gateway) => { return ( From 67e5512800cc4766e2eea7d5337d0c2e719e84c8 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Fri, 3 Jan 2025 00:07:13 +0545 Subject: [PATCH 3/8] feat: implement password freshness check and prompt in authentication flows --- src/components/popup/HeadV2.tsx | 11 ++-- src/routes/auth/batchSignDataItem.tsx | 23 ++----- src/routes/auth/connect.tsx | 32 +++++---- src/routes/auth/sign.tsx | 94 +++++++++++++++++++++------ src/routes/auth/signDataItem.tsx | 30 ++------- src/routes/auth/unlock.tsx | 5 +- src/wallets/auth.ts | 20 +++++- src/wallets/hooks.ts | 18 +++++ 8 files changed, 151 insertions(+), 82 deletions(-) diff --git a/src/components/popup/HeadV2.tsx b/src/components/popup/HeadV2.tsx index d3aab13b7..da7a2021a 100644 --- a/src/components/popup/HeadV2.tsx +++ b/src/components/popup/HeadV2.tsx @@ -124,9 +124,11 @@ export default function HeadV2({ ) : null} - {title} + + {title} + - {appName ? ( + {!showOptions && appName ? ( (props.padding ? props.padding : "15px")}; - padding-left: ${(props) => - props.hasBackButton ? "32px" : props.padding || "15px"}; justify-content: ${(props) => (props.center ? "center" : "space-between")}; align-items: center; background-color: rgba(${(props) => props.theme.background}, 0.75); @@ -256,9 +256,10 @@ const BackButtonIcon = styled(ArrowNarrowLeft)` const PageTitle = styled(Text).attrs({ subtitle: true, noMargin: true -})` +})<{ showLeftMargin: boolean }>` font-size: 1.3rem; font-weight: 500; + ${(props) => props.showLeftMargin && `margin-left: 28px;`} `; const AvatarButton = styled.button` diff --git a/src/routes/auth/batchSignDataItem.tsx b/src/routes/auth/batchSignDataItem.tsx index 89709ce60..8acd7ec33 100644 --- a/src/routes/auth/batchSignDataItem.tsx +++ b/src/routes/auth/batchSignDataItem.tsx @@ -15,12 +15,11 @@ import styled from "styled-components"; import SignDataItemDetails from "~components/signDataItem"; import { Quantity, Token } from "ao-tokens"; -import { ExtensionStorage } from "~utils/storage"; -import { useStorage } from "@plasmohq/storage/hook"; import { checkPassword } from "~wallets/auth"; import { timeoutPromise } from "~utils/promises/timeout"; import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; +import { useAskPassword } from "~wallets/hooks"; export function BatchSignDataItemAuthRequestView() { const { authRequest, acceptRequest, rejectRequest } = @@ -30,11 +29,11 @@ export function BatchSignDataItemAuthRequestView() { const [loading, setLoading] = useState(false); const [transaction, setTransaction] = useState(null); const [transactionList, setTransactionList] = useState(null); - const [password, setPassword] = useState(false); const passwordInput = useInput(); + const askPassword = useAskPassword(); async function sign() { - if (password) { + if (askPassword) { const checkPw = await checkPassword(passwordInput.state); if (!checkPw) { @@ -51,14 +50,6 @@ export function BatchSignDataItemAuthRequestView() { acceptRequest(); } - const [signatureAllowance] = useStorage( - { - key: "signatureAllowance", - instance: ExtensionStorage - }, - 10 - ); - useEffect(() => { const fetchTransactionList = async () => { setLoading(true); @@ -90,10 +81,6 @@ export function BatchSignDataItemAuthRequestView() { ); amount = tokenAmount.toLocaleString(); name = tokenInfo.Name; - console.log(signatureAllowance, Number(amount)); - if (signatureAllowance > Number(amount)) { - setPassword(true); - } } catch (error) { console.error("Token fetch timed out or failed", error); amount = quantity; @@ -147,7 +134,7 @@ export function BatchSignDataItemAuthRequestView() {
{!transaction ? ( <> - {password && ( + {askPassword && ( <> ("always_ask"); + const askPassword = useAskPassword(); + // permissions to add const [permissions, setPermissions] = useState([]); @@ -104,18 +106,20 @@ export function ConnectAuthRequestView() { }, [requestedPermissions, requestedPermCopy]); // connect - async function connect() { + async function connect(checkPassword = true) { if (!url) return; - const unlockRes = await globalUnlock(passwordInput.state); + if (checkPassword) { + const unlockRes = await globalUnlock(passwordInput.state); - if (!unlockRes) { - passwordInput.setStatus("error"); - return setToast({ - type: "error", - content: browser.i18n.getMessage("invalidPassword"), - duration: 2200 - }); + if (!unlockRes) { + passwordInput.setStatus("error"); + return setToast({ + type: "error", + content: browser.i18n.getMessage("invalidPassword"), + duration: 2200 + }); + } } // get existing permissions @@ -180,7 +184,11 @@ export function ConnectAuthRequestView() { } else if (page === "review") { setPage("confirm"); } else if (page === "confirm") { - setPage("unlock"); + if (!askPassword) { + return connect(false); + } else { + setPage("unlock"); + } } else if (page === "unlock") { await connect(); } @@ -483,7 +491,7 @@ export function ConnectAuthRequestView() { authRequest={authRequest} primaryButtonProps={{ label: browser.i18n.getMessage( - page === "unlock" + page === "unlock" || (page === "confirm" && !askPassword) ? "connect" : page !== "confirm" ? "next" diff --git a/src/routes/auth/sign.tsx b/src/routes/auth/sign.tsx index 2a42f0c38..a7eafd2ea 100644 --- a/src/routes/auth/sign.tsx +++ b/src/routes/auth/sign.tsx @@ -3,9 +3,9 @@ import { isSplitTransaction } from "~api/modules/sign/transaction_builder"; import { formatFiatBalance, formatTokenBalance } from "~tokens/currency"; import type { DecodedTag } from "~api/modules/sign/tags"; import type { Tag } from "arweave/web/lib/transaction"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useScanner } from "@arconnect/keystone-sdk"; -import { useActiveWallet } from "~wallets/hooks"; +import { useActiveWallet, useAskPassword } from "~wallets/hooks"; import { formatAddress } from "~utils/format"; import { getArPrice } from "~lib/coingecko"; import type { UR } from "@ngraveio/bc-ur"; @@ -18,7 +18,14 @@ import { TagValue, TransactionProperty } from "~routes/popup/transaction/[id]"; -import { Section, Spacer, Text, useToasts } from "@arconnect/components"; +import { + InputV2, + Section, + Spacer, + Text, + useInput, + useToasts +} from "@arconnect/components"; import AnimatedQRScanner from "~components/hardware/AnimatedQRScanner"; import AnimatedQRPlayer from "~components/hardware/AnimatedQRPlayer"; import Wrapper from "~components/auth/Wrapper"; @@ -34,6 +41,8 @@ import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; import { getTagValue } from "~tokens/aoTokens/ao"; import { humanizeTimestampTags } from "~utils/timestamp"; +import styled from "styled-components"; +import { ChevronDownIcon, ChevronUpIcon } from "@iconicicons/react"; export function SignAuthRequestView() { const { authRequest, acceptRequest, rejectRequest } = @@ -56,6 +65,12 @@ export function SignAuthRequestView() { // currency setting const [currency] = useSetting("currency"); + const [showTags, setShowTags] = useState(false); + + // askPassword + const askPassword = useAskPassword(); + const passwordInput = useInput(); + // arweave price const [arPrice, setArPrice] = useState(0); @@ -223,6 +238,17 @@ export function SignAuthRequestView() { // toast const { setToast } = useToasts(); + const sign = async () => { + if (!transaction) return; + if (wallet.type === "hardware") { + // load tx ur + if (!page) await loadTransactionUR(); + + // update page + setPage((val) => (!val ? "qr" : "scanner")); + } else await acceptRequest(); + }; + return (
@@ -298,18 +324,27 @@ export function SignAuthRequestView() { - + setShowTags(!showTags)} + > {browser.i18n.getMessage("transaction_tags")} + {showTags ? : } - {tags.map((tag, i) => ( - - {tag.name} - {tag.value} - - ))} + {showTags && + tags.map((tag, i) => ( + + {tag.name} + {tag.value} + + ))}
)) || ( @@ -343,6 +378,25 @@ export function SignAuthRequestView() {
+ {askPassword && ( + <> + + { + if (e.key !== "Enter") return; + await sign(); + }} + fullWidth + /> + + + + )} { - if (!transaction) return; - if (wallet.type === "hardware") { - // load tx ur - if (!page) await loadTransactionUR(); - - // update page - setPage((val) => (!val ? "qr" : "scanner")); - } else await acceptRequest(); - } + onClick: sign } } secondaryButtonProps={{ @@ -375,3 +420,12 @@ export function SignAuthRequestView() { ); } + +const PasswordWrapper = styled.div` + display: flex; + flex-direction: column; + + p { + text-transform: capitalize; + } +`; diff --git a/src/routes/auth/signDataItem.tsx b/src/routes/auth/signDataItem.tsx index 23d859a69..0af6bbd45 100644 --- a/src/routes/auth/signDataItem.tsx +++ b/src/routes/auth/signDataItem.tsx @@ -1,5 +1,4 @@ import { - ButtonV2, InputV2, Loading, Section, @@ -43,6 +42,7 @@ import { Degraded, WarningWrapper } from "~routes/popup/send"; import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; +import { useAskPassword } from "~wallets/hooks"; export function SignDataItemAuthRequestView() { const { authRequest, acceptRequest, rejectRequest } = @@ -50,7 +50,6 @@ export function SignDataItemAuthRequestView() { const { authID, data, url } = authRequest; - const [password, setPassword] = useState(false); const [loading, setLoading] = useState(false); const [tokenName, setTokenName] = useState(""); const [logo, setLogo] = useState(""); @@ -58,6 +57,7 @@ export function SignDataItemAuthRequestView() { const [showTags, setShowTags] = useState(false); const [mismatch, setMismatch] = useState(false); const { setToast } = useToasts(); + const askPassword = useAskPassword(); const recipient = data?.tags?.find((tag) => tag.name === "Recipient")?.value || ""; @@ -79,14 +79,6 @@ export function SignDataItemAuthRequestView() { const childRef = useRef(null); useAdjustAmountTitleWidth(parentRef, childRef, formattedAmount); - const [signatureAllowance] = useStorage( - { - key: "signatureAllowance", - instance: ExtensionStorage - }, - 10 - ); - // active address const [activeAddress] = useStorage( { @@ -116,7 +108,7 @@ export function SignDataItemAuthRequestView() { // sign message async function sign() { - if (password) { + if (askPassword) { const checkPw = await checkPassword(passwordInput.state); if (!checkPw) { setToast({ @@ -131,16 +123,6 @@ export function SignDataItemAuthRequestView() { await acceptRequest(); } - useEffect(() => { - if (amount === null) return; - - if (signatureAllowance < amount?.toNumber()) { - setPassword(true); - } else { - setPassword(false); - } - }, [signatureAllowance, amount]); - useEffect(() => { if (!tokenName) return; getPrice(tokenName, currency) @@ -205,7 +187,7 @@ export function SignDataItemAuthRequestView() { // listen for enter to reset useEffect(() => { const listener = async (e: KeyboardEvent) => { - if (password) return; + if (askPassword) return; if (e.key !== "Enter") return; await sign(); }; @@ -213,7 +195,7 @@ export function SignDataItemAuthRequestView() { window.addEventListener("keydown", listener); return () => window.removeEventListener("keydown", listener); - }, [authID, password]); + }, [authID, askPassword]); useEffect(() => { if (tokenName && !logo) { @@ -371,7 +353,7 @@ export function SignDataItemAuthRequestView() {
- {password && ( + {askPassword && ( <> { + const freshness = Number(await ExtensionStorage.get(PASSWORD_FRESHNESS)); + return !!freshness && Date.now() - freshness < FRESHNESS_DURATION; +} + +export async function setPasswordFreshness(): Promise { + await ExtensionStorage.set(PASSWORD_FRESHNESS, Date.now()); +} + +export async function clearPasswordFreshness(): Promise { + await ExtensionStorage.remove(PASSWORD_FRESHNESS); +} diff --git a/src/wallets/hooks.ts b/src/wallets/hooks.ts index 039a60032..a9c519bdc 100644 --- a/src/wallets/hooks.ts +++ b/src/wallets/hooks.ts @@ -11,6 +11,7 @@ import type { StoredWallet } from "~wallets"; import Arweave from "arweave"; import BigNumber from "bignumber.js"; import { retryWithDelayAndTimeout } from "~utils/promises/retry"; +import { isPasswordFresh } from "./auth"; /** * Wallets with details hook @@ -161,3 +162,20 @@ export function useDebounce(value: T, delay: number): T { return debouncedValue; } + +/** + * Hook to determine if a password prompt should be shown to the user + * + * @description Returns true when the stored password has expired and user needs to re-enter it. + * + * @returns {boolean} True if password prompt should be shown, false otherwise + */ +export const useAskPassword = (): boolean => { + const [askPassword, setAskPassword] = useState(false); + + useEffect(() => { + isPasswordFresh().then((isFresh) => setAskPassword(!isFresh)); + }, []); + + return askPassword; +}; From 62f228acdb055d3a929852bdfa7c4f541619e2f6 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Fri, 3 Jan 2025 00:25:08 +0545 Subject: [PATCH 4/8] fix: update asking password logic --- assets/_locales/en/messages.json | 2 +- src/components/dashboard/SignSettings.tsx | 4 ++-- src/routes/popup/send/auth.tsx | 6 ++++-- src/routes/popup/send/confirm.tsx | 10 ++++++++-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/assets/_locales/en/messages.json b/assets/_locales/en/messages.json index 0f6027dc1..1652f180e 100644 --- a/assets/_locales/en/messages.json +++ b/assets/_locales/en/messages.json @@ -480,7 +480,7 @@ "description": "Transfer settings description" }, "enable_transfer_settings": { - "message": "Enable transfer settings", + "message": "Enable Transfer Settings", "description": "Enable transfer settings title" }, "setting_display_theme": { diff --git a/src/components/dashboard/SignSettings.tsx b/src/components/dashboard/SignSettings.tsx index 1826cb206..1456a3bc9 100644 --- a/src/components/dashboard/SignSettings.tsx +++ b/src/components/dashboard/SignSettings.tsx @@ -16,7 +16,7 @@ export const SignSettingsDashboardView = () => { key: "signatureAllowance", instance: ExtensionStorage }, - (v) => (v === undefined ? 10 : Number(v)) + 10 ); const [signatureAllowanceEnabled, setSignatureAllowanceEnabled] = useStorage( @@ -24,7 +24,7 @@ export const SignSettingsDashboardView = () => { key: "signatureAllowanceEnabled", instance: ExtensionStorage }, - (v) => (v === undefined ? true : Boolean(v)) + true ); const handleChange = useCallback((e: React.ChangeEvent) => { diff --git a/src/routes/popup/send/auth.tsx b/src/routes/popup/send/auth.tsx index ec9bc7376..8da45b045 100644 --- a/src/routes/popup/send/auth.tsx +++ b/src/routes/popup/send/auth.tsx @@ -67,8 +67,10 @@ export function SendAuthView({ params: { tokenID } }: SendAuthViewProps) { useEffect(() => { const fetchSignAllowance = async () => { - const allowance = await ExtensionStorage.get("signatureAllowance"); - const enabled = await ExtensionStorage.get("signatureAllowanceEnabled"); + const allowance = + (await ExtensionStorage.get("signatureAllowance")) ?? 10; + const enabled = + (await ExtensionStorage.get("signatureAllowanceEnabled")) ?? true; setSignAllowance(Number(allowance)); setSignAllowanceEnabled(enabled === "true" || enabled === undefined); }; diff --git a/src/routes/popup/send/confirm.tsx b/src/routes/popup/send/confirm.tsx index 4e535aef7..77fc34ffe 100644 --- a/src/routes/popup/send/confirm.tsx +++ b/src/routes/popup/send/confirm.tsx @@ -138,7 +138,10 @@ export function ConfirmView({ const data: TransactionData = await TempTransactionStorage.get("send"); if (data) { const qty = BigNumber(data.qty); - if (allowanceEnabled && qty.lt(Number(allowance))) { + if ( + !allowanceEnabled || + (allowanceEnabled && qty.lte(Number(allowance))) + ) { setNeedsSign(false); } else { setNeedsSign(true); @@ -395,7 +398,10 @@ export function ConfirmView({ isLocalWallet(decryptedWallet); const keyfile = decryptedWallet.keyfile; - if (allowanceEnabled && transactionAmount.lte(allowance)) { + if ( + !allowanceEnabled || + (allowanceEnabled && transactionAmount.lte(allowance)) + ) { try { convertedTransaction.setOwner(keyfile.n); From e3eb1c01f9ebc30c2e579bd5077217065ef58de0 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Fri, 3 Jan 2025 01:38:35 +0545 Subject: [PATCH 5/8] fix: add missing wallet provider to arlocal & devtools --- src/tabs/arlocal.tsx | 101 +++++++++++++++++++++++------------------- src/tabs/devtools.tsx | 45 +++++++++++-------- 2 files changed, 82 insertions(+), 64 deletions(-) diff --git a/src/tabs/arlocal.tsx b/src/tabs/arlocal.tsx index bbe18bf5f..6baa079d1 100644 --- a/src/tabs/arlocal.tsx +++ b/src/tabs/arlocal.tsx @@ -31,8 +31,9 @@ import axios from "axios"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; import { useRemoveCover } from "~wallets/setup/non/non-wallet-setup.hook"; import { useWallets } from "~utils/wallets/wallets.hooks"; +import { WalletsProvider } from "~utils/wallets/wallets.provider"; -export default function ArLocal() { +function ArLocal() { useRemoveCover(); // testnet data @@ -157,53 +158,61 @@ export default function ArLocal() { const { walletStatus } = useWallets(); + return ( + + {walletStatus === "noWallets" && } + + + ArLocal {browser.i18n.getMessage("devtools")} + <Spacer x={0.2} /> + <Text noMargin>by ArConnect</Text> + + + {browser.i18n.getMessage(online ? "testnetLive" : "testnetDown")} + + + + + + + + loadTestnet()} + refreshing={loadingTestnet} + > + + + + + {(!online && ) || ( + <> + + + + + + + )} + + + ); +} + +export default function ArLocalRoot() { return ( - - {walletStatus === "noWallets" && } - - - ArLocal {browser.i18n.getMessage("devtools")} - <Spacer x={0.2} /> - <Text noMargin>by ArConnect</Text> - - - {browser.i18n.getMessage(online ? "testnetLive" : "testnetDown")} - - - - - - - - loadTestnet()} - refreshing={loadingTestnet} - > - - - - - {(!online && ) || ( - <> - - - - - - - )} - - + + + ); } diff --git a/src/tabs/devtools.tsx b/src/tabs/devtools.tsx index 31c78ace5..780255396 100644 --- a/src/tabs/devtools.tsx +++ b/src/tabs/devtools.tsx @@ -13,8 +13,9 @@ import styled from "styled-components"; import { ArConnectThemeProvider } from "~components/hardware/HardwareWalletTheme"; import { useRemoveCover } from "~wallets/setup/non/non-wallet-setup.hook"; import { useWallets } from "~utils/wallets/wallets.hooks"; +import { WalletsProvider } from "~utils/wallets/wallets.provider"; -export default function DevTools() { +function DevTools() { useRemoveCover(); // fetch app data @@ -49,25 +50,33 @@ export default function DevTools() { const { walletStatus } = useWallets(); + return ( + + {walletStatus === "noWallets" && } + + ArConnect {browser.i18n.getMessage("devtools")} + + {browser.i18n.getMessage( + connected ? "appConnected" : "appNotConnected" + )} + + + + {(!connected && app && ) || + (connected && app && ( + + ))} + + + ); +} + +export default function DevToolsRoot() { return ( - - {walletStatus === "noWallets" && } - - ArConnect {browser.i18n.getMessage("devtools")} - - {browser.i18n.getMessage( - connected ? "appConnected" : "appNotConnected" - )} - - - - {(!connected && app && ) || - (connected && app && ( - - ))} - - + + + ); } From 49de29aef781c2ee36b2f426aaf0d36996369171 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Fri, 3 Jan 2025 13:07:19 +0545 Subject: [PATCH 6/8] fix: load default tokens after reset --- src/routes/welcome/generate/done.tsx | 4 ++++ src/routes/welcome/load/wallets.tsx | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/src/routes/welcome/generate/done.tsx b/src/routes/welcome/generate/done.tsx index 1094e0759..96ebc3bf3 100644 --- a/src/routes/welcome/generate/done.tsx +++ b/src/routes/welcome/generate/done.tsx @@ -18,6 +18,7 @@ import { ExtensionStorage } from "~utils/storage"; import { useStorage } from "@plasmohq/storage/hook"; import JSConfetti from "js-confetti"; import { useLocation } from "~wallets/router/router.utils"; +import { loadTokens } from "~tokens/token"; export function GenerateDoneWelcomeView() { const { navigate } = useLocation(); @@ -82,6 +83,9 @@ export function GenerateDoneWelcomeView() { password ); + // load tokens + await loadTokens(); + // log user onboarded await trackEvent(EventType.ONBOARDED, {}); diff --git a/src/routes/welcome/load/wallets.tsx b/src/routes/welcome/load/wallets.tsx index 8c17ff30c..b7fe98ed9 100644 --- a/src/routes/welcome/load/wallets.tsx +++ b/src/routes/welcome/load/wallets.tsx @@ -29,6 +29,7 @@ import styled from "styled-components"; import { WalletKeySizeErrorModal } from "~components/modals/WalletKeySizeErrorModal"; import { useLocation } from "~wallets/router/router.utils"; import type { CommonRouteProps } from "~wallets/router/router.types"; +import { loadTokens } from "~tokens/token"; export type WalletsWelcomeViewProps = CommonRouteProps; @@ -165,6 +166,9 @@ export function WalletsWelcomeView({ params }: WalletsWelcomeViewProps) { // add wallet await addWallet(jwk, password); + + // load tokens + await loadTokens(); } else if (existingWallets.length < 1) { // the user has not migrated, so they need to add a wallet return finishUp(); @@ -245,6 +249,9 @@ export function WalletsWelcomeView({ params }: WalletsWelcomeViewProps) { // add migrated wallets await addWallet(walletsToMigrate, password); + // load tokens + await loadTokens(); + // confirmation toast setToast({ type: "info", From 59302d7544c0f82356cadaecef0158557d316d08 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Fri, 3 Jan 2025 14:26:33 +0545 Subject: [PATCH 7/8] fix: improve tag handling and header title in SignDataItemAuthRequestView --- src/routes/auth/signDataItem.tsx | 42 +++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/routes/auth/signDataItem.tsx b/src/routes/auth/signDataItem.tsx index 23d859a69..4b6a7ad8b 100644 --- a/src/routes/auth/signDataItem.tsx +++ b/src/routes/auth/signDataItem.tsx @@ -31,7 +31,11 @@ import prettyBytes from "pretty-bytes"; import { formatFiatBalance } from "~tokens/currency"; import useSetting from "~settings/hook"; import { getPrice } from "~lib/coingecko"; -import type { TokenInfo, TokenInfoWithProcessId } from "~tokens/aoTokens/ao"; +import { + getTagValue, + type TokenInfo, + type TokenInfoWithProcessId +} from "~tokens/aoTokens/ao"; import { ChevronUpIcon, ChevronDownIcon } from "@iconicicons/react"; import { getUserAvatar } from "~lib/avatar"; import { LogoWrapper, Logo, WarningIcon } from "~components/popup/Token"; @@ -43,6 +47,7 @@ import { Degraded, WarningWrapper } from "~routes/popup/send"; import { useCurrentAuthRequest } from "~utils/auth/auth.hooks"; import { HeadAuth } from "~components/HeadAuth"; import { AuthButtons } from "~components/auth/AuthButtons"; +import { humanizeTimestampTags } from "~utils/timestamp"; export function SignDataItemAuthRequestView() { const { authRequest, acceptRequest, rejectRequest } = @@ -59,12 +64,12 @@ export function SignDataItemAuthRequestView() { const [mismatch, setMismatch] = useState(false); const { setToast } = useToasts(); - const recipient = - data?.tags?.find((tag) => tag.name === "Recipient")?.value || ""; - const quantity = - data?.tags?.find((tag) => tag.name === "Quantity")?.value || "0"; - const transfer = data?.tags?.some( - (tag) => tag.name === "Action" && tag.value === "Transfer" + const tags = useMemo(() => humanizeTimestampTags(data?.tags || []), [data]); + const recipient = useMemo(() => getTagValue("Recipient", tags) || "", [tags]); + const quantity = useMemo(() => getTagValue("Quantity", tags) || "0", [tags]); + const transfer = useMemo( + () => tags.some((tag) => tag.name === "Action" && tag.value === "Transfer"), + [tags] ); const process = data?.target; @@ -114,6 +119,21 @@ export function SignDataItemAuthRequestView() { const passwordInput = useInput(); + const headerTitle = useMemo(() => { + if (!tags?.length) return browser.i18n.getMessage("sign_item"); + + const actionValue = getTagValue("Action", tags); + const isAOTransaction = tags.some( + (tag) => tag.name === "Data-Protocol" && tag.value === "ao" + ); + + if (isAOTransaction && actionValue) { + return actionValue.replace(/-/g, " "); + } + + return browser.i18n.getMessage("sign_item"); + }, [tags]); + // sign message async function sign() { if (password) { @@ -252,7 +272,7 @@ export function SignDataItemAuthRequestView() { return (
- + {mismatch && transfer && ( @@ -294,7 +314,11 @@ export function SignDataItemAuthRequestView() {
{transfer && ( <> - {formatFiatBalance(fiatPrice, currency)} + {+fiatPrice > 0 && ( + + {formatFiatBalance(fiatPrice, currency)} + + )} Date: Fri, 3 Jan 2025 23:16:03 +0545 Subject: [PATCH 8/8] refactor: show allowance input on transfer settings enabled --- src/components/dashboard/SignSettings.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/dashboard/SignSettings.tsx b/src/components/dashboard/SignSettings.tsx index 1456a3bc9..b0787efae 100644 --- a/src/components/dashboard/SignSettings.tsx +++ b/src/components/dashboard/SignSettings.tsx @@ -62,15 +62,16 @@ export const SignSettingsDashboardView = () => { setChecked={setSignatureAllowanceEnabled} /> - + {signatureAllowanceEnabled && ( + + )}
); };