Skip to content

Commit

Permalink
feat: WalletConnect integration, part 7, sign
Browse files Browse the repository at this point in the history
  • Loading branch information
dianasavvatina committed Dec 18, 2024
1 parent cb2e26c commit 664ebea
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe("<SignPayloadRequestModal />", () => {
render(<SignPayloadRequestModal request={request} />, { store });

await waitFor(() =>
expect(screen.getByText("mockDappName/dApp Pairing Request")).toBeVisible()
expect(screen.getByText("Sign Payload Request from mockDappName")).toBeVisible()
);
});

Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/utils/beacon/useHandleBeaconMessage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ describe("<useHandleBeaconMessage />", () => {

act(() => handleMessage(message));

await screen.findByText("mockDappName/dApp Pairing Request");
await screen.findByText("Sign Payload Request from mockDappName");
});

it("sends an error response to the dapp on close", async () => {
Expand All @@ -128,7 +128,7 @@ describe("<useHandleBeaconMessage />", () => {

act(() => handleMessage(message));

await screen.findByText("mockDappName/dApp Pairing Request");
await screen.findByText("Sign Payload Request from mockDappName");

act(() => screen.getByRole("button", { name: "Close" }).click());

Expand Down
11 changes: 11 additions & 0 deletions apps/web/src/components/SendFlow/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { type SigningType } from "@airgap/beacon-wallet";
import { Button, type ButtonProps } from "@chakra-ui/react";
import { type TezosToolkit } from "@taquito/taquito";
import { useDynamicModalContext } from "@umami/components";
import {
type Account,
type AccountOperations,
type EstimatedAccountOperations,
type ImplicitAccount,
type Operation,
estimate,
executeOperations,
Expand Down Expand Up @@ -82,6 +84,15 @@ export type SdkSignPageProps = {
headerProps: SignHeaderProps;
};

export type SignPayloadProps = {
requestId: SignRequestId;
appName: string;
appIcon?: string;
payload: string;
signer: ImplicitAccount;
signingType: SigningType;
};

export const FormSubmitButton = ({ title = "Preview", ...props }: ButtonProps) => {
const {
formState: { isValid },
Expand Down
47 changes: 40 additions & 7 deletions apps/web/src/components/WalletConnect/useHandleWcRequest.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { SigningType } from "@airgap/beacon-wallet";
import { useDynamicModalContext } from "@umami/components";
import { type ImplicitAccount, estimate, toAccountOperations } from "@umami/core";
import { useAsyncActionHandler, useFindNetwork, useGetOwnedAccountSafe } from "@umami/state";
import {
useAsyncActionHandler,
useFindNetwork,
useGetImplicitAccount,
useGetOwnedAccountSafe,
} from "@umami/state";
import { WalletConnectError } from "@umami/utils";
import { type SessionTypes, type SignClientTypes, type Verify } from "@walletconnect/types";

import { SignPayloadRequestModal } from "../common/SignPayloadRequestModal";
import { BatchSignPage } from "../SendFlow/common/BatchSignPage";
import { SingleSignPage } from "../SendFlow/common/SingleSignPage";
import { type SdkSignPageProps, type SignHeaderProps } from "../SendFlow/utils";
import {
type SdkSignPageProps,
type SignHeaderProps,
type SignPayloadProps,
} from "../SendFlow/utils";

/**
* @returns a function that handles a beacon message and opens a modal with the appropriate content
Expand All @@ -18,6 +29,7 @@ export const useHandleWcRequest = () => {
const { openWith } = useDynamicModalContext();
const { handleAsyncActionUnsafe } = useAsyncActionHandler();
const getAccount = useGetOwnedAccountSafe();
const getImplicitAccount = useGetImplicitAccount();
const findNetwork = useFindNetwork();

return async (
Expand Down Expand Up @@ -50,11 +62,32 @@ export const useHandleWcRequest = () => {
}

case "tezos_sign": {
throw new WalletConnectError(
"Sign is not supported yet",
"WC_METHOD_UNSUPPORTED",
session
);
if (!request.params.account) {
throw new WalletConnectError("Missing account in request", "INVALID_EVENT", session);
}
const signer = getImplicitAccount(request.params.account);
const network = findNetwork(chainId.split(":")[1]);
if (!network) {
throw new WalletConnectError(
`Unsupported network ${chainId}`,
"UNSUPPORTED_CHAINS",
session
);
}
const signPayloadProps: SignPayloadProps = {
appName: session.peer.metadata.name,
appIcon: session.peer.metadata.icons[0],
payload: request.params.payload,
signer: signer,
signingType: SigningType.RAW,
requestId: { sdkType: "walletconnect", id: id, topic },
};

modal = <SignPayloadRequestModal opts={signPayloadProps} />;
onClose = () => {
throw new WalletConnectError("Rejected by user", "USER_REJECTED", session);
};
return openWith(modal, { onClose });
}

case "tezos_send": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ describe("<useHandleBeaconMessage />", () => {

act(() => handleMessage(message));

await screen.findByText("mockDappName/dApp Pairing Request");
await screen.findByText("Sign Payload Request from mockDappName");
});

it("sends an error response to the dapp on close", async () => {
Expand All @@ -134,7 +134,7 @@ describe("<useHandleBeaconMessage />", () => {

act(() => handleMessage(message));

await screen.findByText("mockDappName/dApp Pairing Request");
await screen.findByText("Sign Payload Request from mockDappName");

act(() => screen.getByRole("button", { name: "Close" }).click());

Expand Down
19 changes: 17 additions & 2 deletions apps/web/src/components/beacon/useHandleBeaconMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
WalletClient,
useAsyncActionHandler,
useFindNetwork,
useGetImplicitAccount,
useGetOwnedAccountSafe,
useRemoveBeaconPeerBySenderId,
} from "@umami/state";
Expand All @@ -20,7 +21,11 @@ import { PermissionRequestModal } from "./PermissionRequestModal";
import { SignPayloadRequestModal } from "../common/SignPayloadRequestModal";
import { BatchSignPage } from "../SendFlow/common/BatchSignPage";
import { SingleSignPage } from "../SendFlow/common/SingleSignPage";
import { type SdkSignPageProps, type SignHeaderProps } from "../SendFlow/utils";
import {
type SdkSignPageProps,
type SignHeaderProps,
type SignPayloadProps,
} from "../SendFlow/utils";

/**
* @returns a function that handles a beacon message and opens a modal with the appropriate content
Expand All @@ -32,6 +37,7 @@ export const useHandleBeaconMessage = () => {
const { openWith } = useDynamicModalContext();
const { handleAsyncAction } = useAsyncActionHandler();
const getAccount = useGetOwnedAccountSafe();
const getImplicitAccount = useGetImplicitAccount();
const findNetwork = useFindNetwork();
const removePeer = useRemoveBeaconPeerBySenderId();

Expand Down Expand Up @@ -83,7 +89,16 @@ export const useHandleBeaconMessage = () => {
break;
}
case BeaconMessageType.SignPayloadRequest: {
modal = <SignPayloadRequestModal request={message} />;
const signer = getImplicitAccount(message.sourceAddress);
const signPayloadProps: SignPayloadProps = {
appName: message.appMetadata.name,
appIcon: message.appMetadata.icon,
payload: message.payload,
signer: signer,
signingType: message.signingType,
requestId: { sdkType: "beacon", id: message.id },
};
modal = <SignPayloadRequestModal opts={signPayloadProps} />;
onClose = async () => {
await WalletClient.respond({
id: message.id,
Expand Down
48 changes: 24 additions & 24 deletions apps/web/src/components/common/SignPayloadRequestModal.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
BeaconMessageType,
type SignPayloadRequestOutput,
type SignPayloadResponseInput,
} from "@airgap/beacon-wallet";
import { BeaconMessageType, type SignPayloadResponseInput } from "@airgap/beacon-wallet";
import { WarningIcon } from "@chakra-ui/icons";
import {
Box,
Expand All @@ -20,41 +16,45 @@ import {
import { type TezosToolkit } from "@taquito/taquito";
import { useDynamicModalContext } from "@umami/components";
import { decodeBeaconPayload } from "@umami/core";
import { WalletClient, useGetImplicitAccount } from "@umami/state";
import { WalletClient, walletKit } from "@umami/state";
import { formatJsonRpcResult } from "@walletconnect/jsonrpc-utils";
import { useState } from "react";
import { FormProvider, useForm } from "react-hook-form";

import { useColor } from "../../styles/useColor";
import { SignButton } from "../SendFlow/SignButton";
import { type SignPayloadProps } from "../SendFlow/utils";

export const SignPayloadRequestModal = ({ request }: { request: SignPayloadRequestOutput }) => {
export const SignPayloadRequestModal = ({ opts }: { opts: SignPayloadProps }) => {
const { onClose } = useDynamicModalContext();
const getAccount = useGetImplicitAccount();
const signerAccount = getAccount(request.sourceAddress);
const toast = useToast();
const form = useForm();
const color = useColor();
const [showRaw, setShowRaw] = useState(false);

const { result: parsedPayload, error: parsingError } = decodeBeaconPayload(
request.payload,
request.signingType
opts.payload,
opts.signingType
);

const sign = async (tezosToolkit: TezosToolkit) => {
const result = await tezosToolkit.signer.sign(request.payload);

const response: SignPayloadResponseInput = {
type: BeaconMessageType.SignPayloadResponse,
id: request.id,
signingType: request.signingType,
signature: result.prefixSig,
};
const result = await tezosToolkit.signer.sign(opts.payload);

await WalletClient.respond(response);
if (opts.requestId.sdkType === "beacon") {
const response: SignPayloadResponseInput = {
type: BeaconMessageType.SignPayloadResponse,
id: opts.requestId.id.toString(),
signingType: opts.signingType,
signature: result.prefixSig,
};
await WalletClient.respond(response);
} else {
const response = formatJsonRpcResult(opts.requestId.id, { signature: result.prefixSig });
await walletKit.respondSessionRequest({ topic: opts.requestId.topic, response });
}

toast({
description: "Successfully submitted Beacon operation",
description: "Successfully signed the payload",
status: "success",
});
onClose();
Expand All @@ -64,7 +64,7 @@ export const SignPayloadRequestModal = ({ request }: { request: SignPayloadReque
<FormProvider {...form}>
<ModalContent>
<ModalHeader marginBottom="32px" textAlign="center">
{`${request.appMetadata.name}/dApp Pairing Request`}
{`Sign Payload Request from ${opts.appName}`}
</ModalHeader>
<ModalCloseButton />

Expand All @@ -90,7 +90,7 @@ export const SignPayloadRequestModal = ({ request }: { request: SignPayloadReque
backgroundColor={color("100")}
>
<Text color={color("600")} size="md">
{showRaw ? request.payload : parsedPayload.trim()}
{showRaw ? opts.payload : parsedPayload.trim()}
</Text>
</Box>

Expand All @@ -105,7 +105,7 @@ export const SignPayloadRequestModal = ({ request }: { request: SignPayloadReque
</ModalBody>

<ModalFooter justifyContent="center" display="flex" padding="16px 0 0 0">
<SignButton onSubmit={sign} signer={signerAccount} text="Sign" />
<SignButton onSubmit={sign} signer={opts.signer} text="Sign" />
</ModalFooter>
</ModalContent>
</FormProvider>
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./SignPayloadRequestModal";

0 comments on commit 664ebea

Please sign in to comment.