diff --git a/apps/extension/src/core/domains/signing/handler.ts b/apps/extension/src/core/domains/signing/handler.ts index 70193f618..427b888fd 100644 --- a/apps/extension/src/core/domains/signing/handler.ts +++ b/apps/extension/src/core/domains/signing/handler.ts @@ -39,7 +39,6 @@ export default class SigningHandler extends ExtensionHandler { hostName: ok ? hostName : undefined, } - // an empty registry is sufficient, we don't need metadata here let registry = new TypeRegistry() if (isJsonPayload(payload)) { diff --git a/apps/extension/src/ui/apps/popup/pages/Sign/EthSignMessageRequest.tsx b/apps/extension/src/ui/apps/popup/pages/Sign/EthSignMessageRequest.tsx index e098e9624..c4b965014 100644 --- a/apps/extension/src/ui/apps/popup/pages/Sign/EthSignMessageRequest.tsx +++ b/apps/extension/src/ui/apps/popup/pages/Sign/EthSignMessageRequest.tsx @@ -1,12 +1,11 @@ import { AccountJsonHardwareEthereum } from "@core/domains/accounts/types" import { AppPill } from "@talisman/components/AppPill" -import Grid from "@talisman/components/Grid" -import { SimpleButton } from "@talisman/components/SimpleButton" import { Content, Footer, Header } from "@ui/apps/popup/Layout" import { EthSignBodyMessage } from "@ui/domains/Sign/Ethereum/EthSignBodyMessage" import { useEthSignMessageRequest } from "@ui/domains/Sign/SignRequestContext" import { Suspense, lazy, useEffect, useMemo } from "react" import { useTranslation } from "react-i18next" +import { Button } from "talisman-ui" import { SignContainer } from "./common" import { SignAccountAvatar } from "./SignAccountAvatar" @@ -15,8 +14,18 @@ const LedgerEthereum = lazy(() => import("@ui/domains/Sign/LedgerEthereum")) export const EthSignMessageRequest = () => { const { t } = useTranslation("request") - const { url, request, approve, approveHardware, reject, status, message, account, network } = - useEthSignMessageRequest() + const { + url, + request, + approve, + approveHardware, + reject, + status, + message, + account, + network, + isValid, + } = useEthSignMessageRequest() const { processing, errorMessage } = useMemo(() => { return { @@ -52,19 +61,19 @@ export const EthSignMessageRequest = () => { onReject={reject} /> ) : ( - - +
+ +
)} )} diff --git a/apps/extension/src/ui/apps/popup/pages/Sign/EthSignTransactionRequest.tsx b/apps/extension/src/ui/apps/popup/pages/Sign/EthSignTransactionRequest.tsx index 447d170b5..5c080fb94 100644 --- a/apps/extension/src/ui/apps/popup/pages/Sign/EthSignTransactionRequest.tsx +++ b/apps/extension/src/ui/apps/popup/pages/Sign/EthSignTransactionRequest.tsx @@ -1,8 +1,6 @@ import { AccountJsonHardwareEthereum } from "@core/domains/accounts/types" import { EthPriorityOptionName } from "@core/domains/signing/types" import { AppPill } from "@talisman/components/AppPill" -import Grid from "@talisman/components/Grid" -import { SimpleButton } from "@talisman/components/SimpleButton" import { WithTooltip } from "@talisman/components/Tooltip" import { InfoIcon } from "@talisman/theme/icons" import { useQuery } from "@tanstack/react-query" @@ -62,10 +60,6 @@ const SignContainer = styled(Container)` white-space: nowrap; } - ${SimpleButton} { - width: auto; - } - .center { text-align: center; } @@ -86,10 +80,6 @@ const SignContainer = styled(Container)` } } - ${Grid} { - margin-top: 1.6rem; - } - .error { color: var(--color-status-error); max-width: 100%; @@ -246,7 +236,7 @@ export const EthSignTransactionRequest = () => { {transaction && txDetails && network?.nativeToken ? ( -
+
{t("Estimated Fee")}{" "} @@ -292,7 +282,6 @@ export const EthSignTransactionRequest = () => { transaction ? ( { ) ) : ( - - +
+ +
)} diff --git a/apps/extension/src/ui/apps/popup/pages/Sign/PolkadotSignMessageRequest.tsx b/apps/extension/src/ui/apps/popup/pages/Sign/PolkadotSignMessageRequest.tsx index 46a0480d5..869177417 100644 --- a/apps/extension/src/ui/apps/popup/pages/Sign/PolkadotSignMessageRequest.tsx +++ b/apps/extension/src/ui/apps/popup/pages/Sign/PolkadotSignMessageRequest.tsx @@ -1,8 +1,6 @@ import { AccountJsonHardwareSubstrate, AccountJsonQr } from "@core/domains/accounts/types" import { SignerPayloadRaw } from "@core/domains/signing/types" import { AppPill } from "@talisman/components/AppPill" -import { Box } from "@talisman/components/Box" -import { SimpleButton } from "@talisman/components/SimpleButton" import { Content, Footer, Header } from "@ui/apps/popup/Layout" import { AccountPill } from "@ui/domains/Account/AccountPill" import { Message } from "@ui/domains/Sign/Message" @@ -10,6 +8,7 @@ import { QrSubstrate } from "@ui/domains/Sign/Qr/QrSubstrate" import { usePolkadotSigningRequest } from "@ui/domains/Sign/SignRequestContext" import { FC, Suspense, lazy, useEffect, useMemo } from "react" import { useTranslation } from "react-i18next" +import { Button } from "talisman-ui" import { Container } from "./common" import { SignAccountAvatar } from "./SignAccountAvatar" @@ -69,19 +68,14 @@ export const PolkadotSignMessageRequest: FC = () => { {account && request && ( <> {account.origin !== "HARDWARE" && account.origin !== "QR" && ( - - +
+ + +
)} {account.origin === "HARDWARE" && ( diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyMessage.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyMessage.tsx index 86f116d51..097f61b92 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyMessage.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyMessage.tsx @@ -3,17 +3,25 @@ import { EthSignRequest } from "@core/domains/signing/types" import { log } from "@core/log" import { isHexString, stripHexPrefix } from "@ethereumjs/util" import * as Sentry from "@sentry/browser" -import { classNames } from "@talismn/util" +import { classNames, isEthereumAddress } from "@talismn/util" import { Message } from "@ui/domains/Sign/Message" import { useEvmNetwork } from "@ui/hooks/useEvmNetwork" import { dump as convertToYaml } from "js-yaml" import { FC, useMemo } from "react" import { useTranslation } from "react-i18next" +import { SignAlertMessage } from "../SignAlertMessage" import { SignParamAccountButton, SignParamNetworkAddressButton } from "./shared" const useEthSignMessage = (request: EthSignRequest) => { - const { isTypedData, typedMessage, verifyingAddress, chainId, ethChainId } = useMemo(() => { + const { + isTypedData, + typedMessage, + verifyingAddress, + chainId, + ethChainId, + isInvalidVerifyingContract, + } = useMemo(() => { try { const isTypedData = Boolean(request?.method?.startsWith("eth_signTypedData")) const typedMessage = isTypedData ? JSON.parse(request.request) : undefined @@ -22,7 +30,15 @@ const useEthSignMessage = (request: EthSignRequest) => { ? parseInt(typedMessage.domain?.chainId) : undefined const ethChainId = request.ethChainId - return { isTypedData, typedMessage, verifyingAddress, chainId, ethChainId } + const isInvalidVerifyingContract = verifyingAddress && !isEthereumAddress(verifyingAddress) + return { + isTypedData, + typedMessage, + verifyingAddress, + chainId, + ethChainId, + isInvalidVerifyingContract, + } } catch (err) { log.error(err) return { isTypedData: false } @@ -52,7 +68,7 @@ const useEthSignMessage = (request: EthSignRequest) => { return request.request }, [request.request, typedMessage]) - return { isTypedData, text, verifyingAddress, chainId, ethChainId } + return { isTypedData, text, verifyingAddress, chainId, ethChainId, isInvalidVerifyingContract } } export type EthSignBodyMessageProps = { @@ -62,7 +78,8 @@ export type EthSignBodyMessageProps = { export const EthSignBodyMessage: FC = ({ account, request }) => { const { t } = useTranslation("request") - const { isTypedData, text, verifyingAddress, ethChainId } = useEthSignMessage(request) + const { isTypedData, text, verifyingAddress, ethChainId, isInvalidVerifyingContract } = + useEthSignMessage(request) const evmNetwork = useEvmNetwork(ethChainId) return ( @@ -87,6 +104,11 @@ export const EthSignBodyMessage: FC = ({ account, reque className={classNames("w-full grow", isTypedData && "whitespace-pre text-xs")} text={text} /> + {isInvalidVerifyingContract && ( + + {t("Verifying contract's address is invalid.")} + + )}
) } diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/shared/SignParamAccountButton.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/shared/SignParamAccountButton.tsx index 70ed49ecf..1188eefcf 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/shared/SignParamAccountButton.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/shared/SignParamAccountButton.tsx @@ -1,8 +1,9 @@ -import { WithTooltip } from "@talisman/components/Tooltip" +import { isEthereumAddress } from "@talismn/util" +import { AccountIcon } from "@ui/domains/Account/AccountIcon" import { Address } from "@ui/domains/Account/Address" -import AccountAvatar from "@ui/domains/Account/Avatar" import { useAccountByAddress } from "@ui/hooks/useAccountByAddress" -import { FC } from "react" +import { FC, useMemo } from "react" +import { Tooltip, TooltipContent, TooltipTrigger } from "talisman-ui" import { SignParamButton, SignParamButtonProps } from "./SignParamButton" @@ -16,6 +17,7 @@ export const SignParamAccountButton: FC = ({ withIcon, }) => { const account = useAccountByAddress(address) + const isInvalidAddress = useMemo(() => !isEthereumAddress(address), [address]) return ( = ({ withIcon={withIcon} iconPrefix={ account ? ( - ) : ( - ) } + contentClassName={isInvalidAddress ? "!text-alert-warn" : undefined} > {account?.name ? ( - - {account.name} - + + + {account.name} + + {address} + ) : (
)} diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/shared/SignParamContractButton.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/shared/SignParamContractButton.tsx index 277fa4a3e..abacc0c2f 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/shared/SignParamContractButton.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/shared/SignParamContractButton.tsx @@ -1,9 +1,10 @@ import { CustomEvmNetwork, EvmNetwork } from "@core/domains/ethereum/types" -import { WithTooltip } from "@talisman/components/Tooltip" +import { isEthereumAddress } from "@talismn/util" import { Address } from "@ui/domains/Account/Address" import { AssetLogo } from "@ui/domains/Asset/AssetLogo" import useToken from "@ui/hooks/useToken" -import { FC } from "react" +import { FC, useMemo } from "react" +import { Tooltip, TooltipContent, TooltipTrigger } from "talisman-ui" import { SignParamButton } from "./SignParamButton" @@ -21,6 +22,7 @@ export const SignParamNetworkAddressButton: FC { const nativeToken = useToken(network.nativeToken?.id) + const isInvalidAddress = useMemo(() => !isEthereumAddress(address), [address]) return ( {name ? ( - - {name} - + + + {name} + + {address} + ) : (
)} diff --git a/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignMessageRequestContext.ts b/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignMessageRequestContext.ts index ad93c5c96..5e6f85c72 100644 --- a/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignMessageRequestContext.ts +++ b/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignMessageRequestContext.ts @@ -2,10 +2,11 @@ import { KnownSigningRequestIdOnly } from "@core/domains/signing/types" import { log } from "@core/log" import { HexString } from "@polkadot/util/types" import { provideContext } from "@talisman/util/provideContext" +import { isEthereumAddress } from "@talismn/util" import { api } from "@ui/api" import { useEvmNetwork } from "@ui/hooks/useEvmNetwork" import { useRequest } from "@ui/hooks/useRequest" -import { useCallback } from "react" +import { useCallback, useMemo } from "react" import { useAnySigningRequest } from "./AnySignRequestContext" @@ -35,11 +36,26 @@ const useEthSignMessageRequestProvider = ({ id }: KnownSigningRequestIdOnly<"eth [baseRequest] ) + const isValid = useMemo(() => { + if (!request) return false + + const isTypedData = Boolean(request?.method?.startsWith("eth_signTypedData")) + if (isTypedData) { + // for now only check signTypedData's verifying contract's address + const typedMessage = isTypedData ? JSON.parse(request.request) : undefined + const verifyingContract = typedMessage?.domain?.verifyingContract as string | undefined + if (verifyingContract && !isEthereumAddress(verifyingContract)) return false + } + + return true + }, [request]) + return { ...baseRequest, approveHardware, request, network, + isValid, } } diff --git a/apps/playground/src/components/Ethereum/sign/SignTypedData.tsx b/apps/playground/src/components/Ethereum/sign/SignTypedData.tsx index 6aa63170b..ed1e5453c 100644 --- a/apps/playground/src/components/Ethereum/sign/SignTypedData.tsx +++ b/apps/playground/src/components/Ethereum/sign/SignTypedData.tsx @@ -13,9 +13,7 @@ import { useAccount, useNetwork } from "wagmi" import { Section } from "../../shared/Section" -type GetTypedData = (chainId: number) => TypedDataV1 | TypedMessage - -const getTestDataV1: GetTypedData = () => [ +const getTestDataV1 = (): TypedDataV1 => [ { type: "string", name: "Question", @@ -28,7 +26,10 @@ const getTestDataV1: GetTypedData = () => [ }, ] -const getTestDataV3: GetTypedData = (chainId: number) => ({ +const getTestDataV3 = ( + chainId: number, + validContractAddress: boolean +): TypedMessage => ({ types: { EIP712Domain: [ { name: "name", type: "string" }, @@ -51,7 +52,9 @@ const getTestDataV3: GetTypedData = (chainId: number) => ({ name: "Ether Mail", version: "1", chainId, - verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + verifyingContract: validContractAddress + ? "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + : "javascript:alert(1)", }, message: { from: { @@ -66,11 +69,16 @@ const getTestDataV3: GetTypedData = (chainId: number) => ({ }, }) -const getTestDataV4: GetTypedData = (chainId: number) => ({ +const getTestDataV4 = ( + chainId: number, + validContractAddress: boolean +): TypedMessage => ({ domain: { chainId: chainId, name: "Ether Mail", - verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + verifyingContract: validContractAddress + ? "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + : "javascript:alert(1)", version: "1", }, message: { @@ -117,26 +125,56 @@ const getTestDataV4: GetTypedData = (chainId: number) => ({ }, }) -const TEST_PAYLOADS = { +type TypedDataTestPayload = { + version: SignTypedDataVersion + method: string + getData: (chainId: number) => TypedMessage | TypedDataV1 + getParams: (address: string, chainId: number) => [unknown, unknown] +} + +type TestCases = "V1" | "V3" | "V3_INVALID" | "V4" | "V4_INVALID" + +const TEST_PAYLOADS: Record = { V1: { + version: SignTypedDataVersion.V1, method: "eth_signTypedData", - getData: (chainId: number) => getTestDataV1(chainId), - getParams: (address: string, chainId: number) => [getTestDataV1(chainId), address], + getData: () => getTestDataV1(), + getParams: (address: string) => [getTestDataV1(), address], }, V3: { + version: SignTypedDataVersion.V3, method: "eth_signTypedData_v3", - getData: (chainId: number) => getTestDataV3(chainId), + getData: (chainId: number) => getTestDataV3(chainId, true), getParams: (address: string, chainId: number) => [ address, - JSON.stringify(getTestDataV3(chainId)), + JSON.stringify(getTestDataV3(chainId, true)), + ], + }, + V3_INVALID: { + version: SignTypedDataVersion.V3, + method: "eth_signTypedData_v3", + getData: (chainId: number) => getTestDataV3(chainId, false), + getParams: (address: string, chainId: number) => [ + address, + JSON.stringify(getTestDataV3(chainId, false)), ], }, V4: { + version: SignTypedDataVersion.V4, + method: "eth_signTypedData_v4", + getData: (chainId: number) => getTestDataV4(chainId, true), + getParams: (address: string, chainId: number) => [ + address, + JSON.stringify(getTestDataV4(chainId, true)), + ], + }, + V4_INVALID: { + version: SignTypedDataVersion.V4, method: "eth_signTypedData_v4", - getData: (chainId: number) => getTestDataV4(chainId), + getData: (chainId: number) => getTestDataV4(chainId, false), getParams: (address: string, chainId: number) => [ address, - JSON.stringify(getTestDataV4(chainId)), + JSON.stringify(getTestDataV4(chainId, false)), ], }, } as const @@ -144,7 +182,7 @@ const TEST_PAYLOADS = { const SignTypedDataInner = () => { const { isConnected, address, connector } = useAccount() const { chain } = useNetwork() - const [processing, setProcessing] = useState() + const [processing, setProcessing] = useState() const [error, setError] = useState() const [signature, setSignature] = useState() const [signedBy, setSignedBy] = useState() @@ -152,15 +190,15 @@ const SignTypedDataInner = () => { const disabled = useMemo(() => !chain || !connector || !address, [address, chain, connector]) const handleSignClick = useCallback( - (version: SignTypedDataVersion) => async () => { - setProcessing(version) + (testCase: TestCases) => async () => { setError(undefined) setSignature(undefined) setSignedBy(undefined) try { if (!connector || !chain || !address) return - const { method, getData, getParams } = TEST_PAYLOADS[version] + const { version, method, getData, getParams } = TEST_PAYLOADS[testCase] + setProcessing(version) const params = getParams(address, chain.id) const data = getData(chain.id) @@ -192,28 +230,44 @@ const SignTypedDataInner = () => {
+ +