Skip to content

Commit

Permalink
feat: WalletConnect integration, part 8, verify
Browse files Browse the repository at this point in the history
  • Loading branch information
dianasavvatina committed Dec 20, 2024
1 parent 6eb3e0c commit a4fff5c
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 48 deletions.
63 changes: 36 additions & 27 deletions apps/web/src/components/SendFlow/common/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,49 @@ import { capitalize } from "lodash";

import { CodeSandboxIcon } from "../../../assets/icons";
import { useColor } from "../../../styles/useColor";
import { VerifyInfobox } from "../../WalletConnect/VerifyInfobox";
import { SignPageHeader } from "../SignPageHeader";
import { type SignHeaderProps } from "../utils";

export const Header = ({ headerProps }: { headerProps: SignHeaderProps }) => {
const color = useColor();

return (
<SignPageHeader>
<Flex alignItems="center" justifyContent="center" marginTop="10px">
<Heading marginRight="4px" color={color("700")} size="sm">
Network:
</Heading>
<Text color={color("700")} fontWeight="400" size="sm">
{capitalize(headerProps.network.name)}
</Text>
</Flex>
<>
<SignPageHeader>
<Flex alignItems="center" justifyContent="center" marginTop="10px">
<Heading marginRight="4px" color={color("700")} size="sm">
Network:
</Heading>
<Text color={color("700")} fontWeight="400" size="sm">
{capitalize(headerProps.network.name)}
</Text>
</Flex>

<Flex
alignItems="center"
marginTop="16px"
padding="15px"
borderRadius="4px"
backgroundColor={color("100")}
>
<AspectRatio width="30px" marginRight="12px" ratio={1}>
<Image
borderRadius="4px"
objectFit="cover"
fallback={<CodeSandboxIcon width="36px" height="36px" />}
src={headerProps.appIcon}
/>
</AspectRatio>
<Heading size="sm">{headerProps.appName}</Heading>
</Flex>
</SignPageHeader>
<Flex
alignItems="center"
marginTop="16px"
padding="15px"
borderRadius="4px"
backgroundColor={color("100")}
>
<AspectRatio width="30px" marginRight="12px" ratio={1}>
<Image
borderRadius="4px"
objectFit="cover"
fallback={<CodeSandboxIcon width="36px" height="36px" />}
src={headerProps.appIcon}
/>
</AspectRatio>
<Heading size="sm">{headerProps.appName}</Heading>
</Flex>
</SignPageHeader>
{headerProps.requestId.sdkType === "walletconnect" ? (
<VerifyInfobox
isScam={headerProps.isScam ?? false}
validationStatus={headerProps.validationStatus ?? "UNKNOWN"}
/>
) : null}
</>
);
};
2 changes: 2 additions & 0 deletions apps/web/src/components/SendFlow/common/TezSignPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ describe("<TezSignPage />", () => {
expect(screen.getByText("Ghostnet")).toBeInTheDocument();
expect(screen.queryByText("Mainnet")).not.toBeInTheDocument();

expect(screen.queryByText("verifyinfobox")).not.toBeInTheDocument();

const signButton = screen.getByRole("button", {
name: "Confirm Transaction",
});
Expand Down
4 changes: 4 additions & 0 deletions apps/web/src/components/SendFlow/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ export type SignHeaderProps = {
network: Network;
appName: string;
appIcon?: string;
isScam?: boolean;
validationStatus?: "VALID" | "INVALID" | "UNKNOWN";
requestId: SignRequestId;
};

Expand All @@ -89,6 +91,8 @@ export type SignPayloadProps = {
appName: string;
appIcon?: string;
payload: string;
isScam?: boolean;
validationStatus?: "VALID" | "INVALID" | "UNKNOWN";
signer: ImplicitAccount;
signingType: SigningType;
};
Expand Down
8 changes: 1 addition & 7 deletions apps/web/src/components/WalletConnect/ProjectInfoCard.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Avatar, Box, Card, Flex, Icon, Link, Text } from "@chakra-ui/react";
import { Avatar, Box, Card, Link, Text } from "@chakra-ui/react";
import { type SignClientTypes } from "@walletconnect/types";

import { PencilIcon } from "../../assets/icons";

type Props = {
metadata: SignClientTypes.Metadata;
intention?: string;
Expand Down Expand Up @@ -38,10 +36,6 @@ export const ProjectInfoCard = ({ metadata, intention }: Props) => {
{url}
</Link>
</Box>
<Flex alignItems="center" justifyContent="center" marginTop="16px">
<Icon as={PencilIcon} verticalAlign="bottom" />
<Card marginLeft="8px">Cannot Verify: to be implemented</Card>
</Flex>
</Box>
);
};
10 changes: 7 additions & 3 deletions apps/web/src/components/WalletConnect/SessionProposalModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
useToggleWcPeerListUpdated,
walletKit,
} from "@umami/state";
import { type SessionTypes } from "@walletconnect/types";
import { type SessionTypes, type Verify } from "@walletconnect/types";
import { buildApprovedNamespaces, getSdkError } from "@walletconnect/utils";
import { FormProvider, useForm } from "react-hook-form";

Expand All @@ -46,6 +46,10 @@ export const SessionProposalModal = ({
const { onClose } = useDynamicModalContext();
const { isLoading, handleAsyncAction } = useAsyncActionHandler();

const verifyContext: Verify.Context = proposal.verifyContext;
const isScam = verifyContext.verified.isScam ?? false;
const validationStatus = verifyContext.verified.validation;

const form = useForm<{ address: string }>({
mode: "onBlur",
});
Expand Down Expand Up @@ -97,6 +101,7 @@ export const SessionProposalModal = ({
<ModalBody>
<Card>
<ProjectInfoCard metadata={proposal.params.proposer.metadata} />
<VerifyInfobox isScam={isScam} validationStatus={validationStatus} />
<Divider />
<Box marginBottom="16px" fontSize="xl" fontWeight="semibold">
Requested permissions
Expand Down Expand Up @@ -132,7 +137,6 @@ export const SessionProposalModal = ({
<Text marginLeft="8px">{network}</Text>
</Box>
<Divider />
<VerifyInfobox />
</Card>
</ModalBody>
<ModalFooter>
Expand All @@ -141,7 +145,7 @@ export const SessionProposalModal = ({
</Button>
<Button
width="100%"
isDisabled={!isValid}
isDisabled={!isValid || isScam}
isLoading={isLoading}
loadingText="Approving..."
onClick={onApprove}
Expand Down
69 changes: 58 additions & 11 deletions apps/web/src/components/WalletConnect/VerifyInfobox.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,64 @@
import { Box, Card, HStack, Icon, VStack } from "@chakra-ui/react";

import { AlertCircleIcon } from "../../assets/icons";
import { AlertCircleIcon, AlertTriangleIcon, VerifiedIcon } from "../../assets/icons";

export const VerifyInfobox = () => (
<Box textAlign="center">
<VStack spacing="16px">
<HStack margin="auto">
<Icon as={AlertCircleIcon} verticalAlign="bottom" />
<Card marginLeft="8px">Unknown domain</Card>
</HStack>
<Box margin="auto">
<Card>This domain was not verified. To be implemented.</Card>
</Box>
export const VerifyInfobox = ({
isScam,
validationStatus,
}: {
isScam: boolean;
validationStatus: "UNKNOWN" | "INVALID" | "VALID";
}) => (
<Box textAlign="left" data-testid="verifyinfobox">
<VStack margin="auto" marginTop="16px" marginBottom="16px" spacing="16px">
{isScam ? (
<HStack
margin="auto"
padding="8px"
border="1px solid"
borderColor="red.500"
borderRadius="md"
>
<Icon as={AlertTriangleIcon} verticalAlign="bottom" />
<Card marginLeft="8px">
This domain is suspected to be a SCAM. Potential threat detected.
</Card>
</HStack>
) : validationStatus === "UNKNOWN" ? (
<HStack
margin="auto"
padding="8px"
border="1px solid"
borderColor="yellow.500"
borderRadius="md"
>
<Icon as={AlertCircleIcon} verticalAlign="bottom" />
<Card marginLeft="8px">This domain is unknown. Cannot verify it.</Card>
</HStack>
) : validationStatus === "INVALID" ? (
<HStack
margin="auto"
padding="8px"
border="1px solid"
borderColor="yellow.500"
borderRadius="md"
>
<Icon as={AlertTriangleIcon} verticalAlign="bottom" />
<Card marginLeft="8px">This domain is invalid. </Card>
</HStack>
) : (
// VALID
<HStack
margin="auto"
padding="8px"
border="1px solid"
borderColor="green.500"
borderRadius="md"
>
<Icon as={VerifiedIcon} verticalAlign="bottom" />
<Card marginLeft="8px">This domain is verified. </Card>
</HStack>
)}
</VStack>
</Box>
);
4 changes: 4 additions & 0 deletions apps/web/src/components/WalletConnect/useHandleWcRequest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ export const useHandleWcRequest = () => {
appName: session.peer.metadata.name,
appIcon: session.peer.metadata.icons[0],
payload: request.params.payload,
isScam: event.verifyContext.verified.isScam,
validationStatus: event.verifyContext.verified.validation,
signer: signer,
signingType: SigningType.RAW,
requestId: { sdkType: "walletconnect", id: id, topic },
Expand Down Expand Up @@ -126,6 +128,8 @@ export const useHandleWcRequest = () => {
network,
appName: session.peer.metadata.name,
appIcon: session.peer.metadata.icons[0],
isScam: event.verifyContext.verified.isScam,
validationStatus: event.verifyContext.verified.validation,
requestId: { sdkType: "walletconnect", id: id, topic },
};
const signProps: SdkSignPageProps = {
Expand Down
47 changes: 47 additions & 0 deletions apps/web/src/components/common/SignPayloadRequestModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ describe("<SignPayloadRequestModal />", () => {
);
await waitFor(() => expect(screen.getByText(new RegExp(decodedPayload))).toBeVisible());

expect(screen.getByTestId("verifyinfobox")).toBeVisible();
expect(screen.getByText("This domain is unknown. Cannot verify it.")).toBeInTheDocument();

await act(() => user.click(screen.getByLabelText("Password")));
await act(() => user.type(screen.getByLabelText("Password"), "123123123"));
const confirmButton = screen.getByRole("button", { name: "Sign" });
Expand All @@ -124,4 +127,48 @@ describe("<SignPayloadRequestModal />", () => {
expect(walletKit.respondSessionRequest).toHaveBeenCalledWith({ topic: "mockTopic", response })
);
});
it("Verify info box is visible for default", async () => {
await renderInModal(<SignPayloadRequestModal opts={wcOpts} />, store);
await waitFor(() => {
expect(screen.getByText("This domain is unknown. Cannot verify it.")).toBeInTheDocument();
});
});
it("Verify info box is visible for UNKNOWN", async () => {
await renderInModal(
<SignPayloadRequestModal opts={{ ...wcOpts, isScam: false, validationStatus: "UNKNOWN" }} />,
store
);
await waitFor(() => {
expect(screen.getByText("This domain is unknown. Cannot verify it.")).toBeInTheDocument();
});
});
it("Verify info box is visible for INVALID", async () => {
await renderInModal(
<SignPayloadRequestModal opts={{ ...wcOpts, isScam: false, validationStatus: "INVALID" }} />,
store
);
await waitFor(() => {
expect(screen.getByText("This domain is invalid.")).toBeInTheDocument();
});
});
it("Verify info box is visible for VALID", async () => {
await renderInModal(
<SignPayloadRequestModal opts={{ ...wcOpts, isScam: false, validationStatus: "VALID" }} />,
store
);
await waitFor(() => {
expect(screen.getByText("This domain is verified.")).toBeInTheDocument();
});
});
it("Verify info box is visible for SCAM", async () => {
await renderInModal(
<SignPayloadRequestModal opts={{ ...wcOpts, isScam: true, validationStatus: "UNKNOWN" }} />,
store
);
await waitFor(() => {
expect(
screen.getByText("This domain is suspected to be a SCAM. Potential threat detected.")
).toBeInTheDocument();
});
});
});
8 changes: 8 additions & 0 deletions apps/web/src/components/common/SignPayloadRequestModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { FormProvider, useForm } from "react-hook-form";
import { useColor } from "../../styles/useColor";
import { SignButton } from "../SendFlow/SignButton";
import { type SignPayloadProps } from "../SendFlow/utils";
import { VerifyInfobox } from "../WalletConnect/VerifyInfobox";

export const SignPayloadRequestModal = ({ opts }: { opts: SignPayloadProps }) => {
const { goBack } = useDynamicModalContext();
Expand Down Expand Up @@ -66,6 +67,13 @@ export const SignPayloadRequestModal = ({ opts }: { opts: SignPayloadProps }) =>
<ModalHeader marginBottom="32px" textAlign="center">
{`Sign Payload Request from ${opts.appName}`}
</ModalHeader>
{opts.requestId.sdkType === "walletconnect" ? (
<VerifyInfobox
isScam={opts.isScam ?? false}
validationStatus={opts.validationStatus ?? "UNKNOWN"}
/>
) : null}

<ModalCloseButton />

<ModalBody>
Expand Down

0 comments on commit a4fff5c

Please sign in to comment.