Skip to content

Commit

Permalink
feat: WalletConnect integration, part 1, session proposal
Browse files Browse the repository at this point in the history
This is the first part of the WalletConnect integration. It includes the following components:
 - initiating WalletKit by WalletConnect
 - subscribing to basic events
 - handling session proposal on a basic level

Limitations:
 - all requests are rejected
 - no pairing list
 - no way to disconnect
 - no verification of dapp, no check for scam
  • Loading branch information
dianasavvatina committed Oct 2, 2024
1 parent 82b2cd2 commit 22d7d7c
Show file tree
Hide file tree
Showing 16 changed files with 914 additions and 23 deletions.
4 changes: 4 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@reduxjs/toolkit": "^2.2.7",
"@reown/walletkit": "^1.0.1",
"@tanstack/react-query": "^5.56.2",
"@taquito/beacon-wallet": "^20.0.1",
"@taquito/ledger-signer": "^20.0.1",
Expand All @@ -49,6 +50,9 @@
"@umami/state": "workspace:^",
"@umami/tezos": "workspace:^",
"@umami/tzkt": "workspace:^",
"@walletconnect/jsonrpc-utils": "^1.0.8",
"@walletconnect/types": "^2.16.2",
"@walletconnect/utils": "^2.16.2",
"bignumber.js": "^9.1.2",
"bip39": "^3.1.0",
"cross-env": "^7.0.3",
Expand Down
5 changes: 4 additions & 1 deletion apps/web/src/components/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import { useCurrentAccount } from "@umami/state";
import { Layout } from "../../Layout";
import { Welcome } from "../../views/Welcome";
import { BeaconProvider } from "../beacon";
import { WalletConnectProvider } from "../WalletConnect/WalletConnectProvider";

export const App = () => {
const currentAccount = useCurrentAccount();

return currentAccount ? (
<BeaconProvider>
<Layout />
<WalletConnectProvider>
<Layout />
</WalletConnectProvider>
</BeaconProvider>
) : (
<Welcome />
Expand Down
9 changes: 7 additions & 2 deletions apps/web/src/components/Menu/AppsMenu/AppsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Button, Divider, Text } from "@chakra-ui/react";
import { useAddPeer } from "@umami/state";

import { BeaconPeers } from "../../beacon";
import { onConnect } from "../../WalletConnect";
import { DrawerContentWrapper } from "../DrawerContentWrapper";

export const AppsMenu = () => {
Expand All @@ -10,13 +11,17 @@ export const AppsMenu = () => {
return (
<DrawerContentWrapper title="Apps">
<Text marginTop="12px" size="lg">
Connect with Pairing Request
Connect with Pairing Request for Beacon or WalletConnect
</Text>
<Button
width="fit-content"
marginTop="18px"
padding="0 24px"
onClick={() => navigator.clipboard.readText().then(addPeer)}
onClick={() =>
navigator.clipboard
.readText()
.then(payload => (payload.startsWith("wc:") ? onConnect(payload) : addPeer(payload)))
}
variant="secondary"
>
Connect
Expand Down
60 changes: 60 additions & 0 deletions apps/web/src/components/WalletConnect/ModalFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Box, Button, Card, HStack } from "@chakra-ui/react";
import { useFormContext } from "react-hook-form";
export interface LoaderProps {
color?: "default" | "primary" | "secondary" | "success" | "warning" | "error" | "white";
active?: boolean;
}
interface Props {
onApprove: () => void;
onReject: () => void;
infoBoxCondition?: boolean;
infoBoxText?: string;
approveLoader?: LoaderProps;
rejectLoader?: LoaderProps;
disableApprove?: boolean;
disableReject?: boolean;
}

export const ModalFooter = ({
onApprove,
approveLoader,
onReject,
rejectLoader,
infoBoxCondition,
infoBoxText,
disableApprove,
disableReject,
}: Props) => {
const form = useFormContext();
return (
<Box as="footer" padding="16px">
{infoBoxCondition && (
<Box marginBottom="16px" textAlign="left">
<Card marginLeft="8px">{infoBoxText || ""}</Card>
</Box>
)}
<HStack alignItems="center" justifyContent="space-between">
<Button
colorScheme="gray"
data-testid="session-reject-button"
isDisabled={disableReject || rejectLoader?.active}
onClick={onReject}
variant="solid"
>
{rejectLoader && rejectLoader.active ? "Loading..." : "Reject"}
</Button>
<Button
colorScheme="blue"
data-testid="session-approve-button"
isDisabled={
disableApprove || approveLoader?.active || Boolean(form.formState.errors.password)
}
onClick={form.handleSubmit(onApprove)}
variant="solid"
>
{approveLoader && approveLoader.active ? "Loading..." : "Approve"}
</Button>
</HStack>
</Box>
);
};
48 changes: 48 additions & 0 deletions apps/web/src/components/WalletConnect/ProjectInfoCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Avatar, Box, Card, Flex, Heading, Icon, Link, Text } from "@chakra-ui/react";
import { type SignClientTypes } from "@walletconnect/types";

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

interface IProps {
metadata: SignClientTypes.Metadata;
intention?: string;
}

/**
* dApp project info card. Contains verification info to help user decide if the dApp is safe to connect.
*/
export const ProjectInfoCard = ({ metadata, intention }: IProps) => {
const { icons, name, url } = metadata;

return (
<Box textAlign="center">
<Box>
<Avatar marginX="auto" size="lg" src={icons[0]} />
</Box>
<Box marginTop="16px">
<Text data-testid="session-info-card-text">
<Text as="span" fontWeight="bold">
{name}
</Text>{" "}
<br />
<Heading size="md">wants to {intention ?? "connect"}</Heading>
</Text>
</Box>
<Box marginTop="16px">
<Link
verticalAlign="middle"
marginLeft="8px"
data-testid="session-info-card-url"
href={url}
isExternal
>
{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>
);
};
55 changes: 55 additions & 0 deletions apps/web/src/components/WalletConnect/RequestModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Divider } from "@chakra-ui/react";
import { type CoreTypes } from "@walletconnect/types";
import { type ReactNode } from "react";

import { type LoaderProps, ModalFooter } from "./ModalFooter";
import { ProjectInfoCard } from "./ProjectInfoCard";
import { RequestModalContainer } from "./RequestModalContainer";
import { VerifyInfobox } from "./VerifyInfobox";

interface IProps {
children: ReactNode;
metadata: CoreTypes.Metadata;
onApprove: () => void;
onReject: () => void;
intention?: string;
infoBoxCondition?: boolean;
infoBoxText?: string;
approveLoader?: LoaderProps;
rejectLoader?: LoaderProps;
disableApprove?: boolean;
disableReject?: boolean;
}
export const RequestModal = ({
children,
metadata,
onApprove,
onReject,
approveLoader,
rejectLoader,
intention,
infoBoxCondition,
infoBoxText,
disableApprove,
disableReject,
}: IProps) => (
<>
<RequestModalContainer title="">
<ProjectInfoCard intention={intention} metadata={metadata} />
<Divider />
{children}
<Divider />
<VerifyInfobox />
</RequestModalContainer>
<ModalFooter
approveLoader={approveLoader}
disableApprove={disableApprove}
disableReject={disableReject}
infoBoxCondition={infoBoxCondition}
infoBoxText={infoBoxText}
onApprove={onApprove}
onReject={onReject}
rejectLoader={rejectLoader}
/>
</>
);
20 changes: 20 additions & 0 deletions apps/web/src/components/WalletConnect/RequestModalContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Card, ModalBody, ModalHeader } from "@chakra-ui/react";
import { Fragment, type ReactNode } from "react";

interface IProps {
title?: string;
children: ReactNode | ReactNode[];
}

export const RequestModalContainer = ({ children, title }: IProps) => (
<Fragment>
{title ? (
<ModalHeader>
<Card>{title}</Card>
</ModalHeader>
) : null}
<ModalBody>
<Card>{children}</Card>
</ModalBody>
</Fragment>
);
Loading

0 comments on commit 22d7d7c

Please sign in to comment.