From 664ebea9d243160ff796aab6febd22cc6cecf5ca Mon Sep 17 00:00:00 2001 From: Diana Savatina Date: Fri, 13 Dec 2024 12:11:06 +0000 Subject: [PATCH] feat: WalletConnect integration, part 7, sign --- .../beacon/SignPayloadRequestModal.test.tsx | 2 +- .../beacon/useHandleBeaconMessage.test.tsx | 4 +- apps/web/src/components/SendFlow/utils.tsx | 11 +++++ .../WalletConnect/useHandleWcRequest.tsx | 47 +++++++++++++++--- .../beacon/useHandleBeaconMessage.test.tsx | 4 +- .../beacon/useHandleBeaconMessage.tsx | 19 +++++++- .../common/SignPayloadRequestModal.tsx | 48 +++++++++---------- apps/web/src/components/common/index.ts | 1 + 8 files changed, 98 insertions(+), 38 deletions(-) create mode 100644 apps/web/src/components/common/index.ts diff --git a/apps/desktop/src/utils/beacon/SignPayloadRequestModal.test.tsx b/apps/desktop/src/utils/beacon/SignPayloadRequestModal.test.tsx index 3600c8f2cf..c84effde80 100644 --- a/apps/desktop/src/utils/beacon/SignPayloadRequestModal.test.tsx +++ b/apps/desktop/src/utils/beacon/SignPayloadRequestModal.test.tsx @@ -44,7 +44,7 @@ describe("", () => { render(, { store }); await waitFor(() => - expect(screen.getByText("mockDappName/dApp Pairing Request")).toBeVisible() + expect(screen.getByText("Sign Payload Request from mockDappName")).toBeVisible() ); }); diff --git a/apps/desktop/src/utils/beacon/useHandleBeaconMessage.test.tsx b/apps/desktop/src/utils/beacon/useHandleBeaconMessage.test.tsx index 05f3a45112..896084e8d1 100644 --- a/apps/desktop/src/utils/beacon/useHandleBeaconMessage.test.tsx +++ b/apps/desktop/src/utils/beacon/useHandleBeaconMessage.test.tsx @@ -107,7 +107,7 @@ describe("", () => { 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 () => { @@ -128,7 +128,7 @@ describe("", () => { 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()); diff --git a/apps/web/src/components/SendFlow/utils.tsx b/apps/web/src/components/SendFlow/utils.tsx index a2c639c54d..dfc8ff88f3 100644 --- a/apps/web/src/components/SendFlow/utils.tsx +++ b/apps/web/src/components/SendFlow/utils.tsx @@ -1,3 +1,4 @@ +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"; @@ -5,6 +6,7 @@ import { type Account, type AccountOperations, type EstimatedAccountOperations, + type ImplicitAccount, type Operation, estimate, executeOperations, @@ -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 }, diff --git a/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx b/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx index c1a4b68da8..47c5facca4 100644 --- a/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx +++ b/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx @@ -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 @@ -18,6 +29,7 @@ export const useHandleWcRequest = () => { const { openWith } = useDynamicModalContext(); const { handleAsyncActionUnsafe } = useAsyncActionHandler(); const getAccount = useGetOwnedAccountSafe(); + const getImplicitAccount = useGetImplicitAccount(); const findNetwork = useFindNetwork(); return async ( @@ -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 = ; + onClose = () => { + throw new WalletConnectError("Rejected by user", "USER_REJECTED", session); + }; + return openWith(modal, { onClose }); } case "tezos_send": { diff --git a/apps/web/src/components/beacon/useHandleBeaconMessage.test.tsx b/apps/web/src/components/beacon/useHandleBeaconMessage.test.tsx index 63c484b9ca..00eb28aeed 100644 --- a/apps/web/src/components/beacon/useHandleBeaconMessage.test.tsx +++ b/apps/web/src/components/beacon/useHandleBeaconMessage.test.tsx @@ -113,7 +113,7 @@ describe("", () => { 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 () => { @@ -134,7 +134,7 @@ describe("", () => { 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()); diff --git a/apps/web/src/components/beacon/useHandleBeaconMessage.tsx b/apps/web/src/components/beacon/useHandleBeaconMessage.tsx index a5af8daeeb..a2ae139318 100644 --- a/apps/web/src/components/beacon/useHandleBeaconMessage.tsx +++ b/apps/web/src/components/beacon/useHandleBeaconMessage.tsx @@ -10,6 +10,7 @@ import { WalletClient, useAsyncActionHandler, useFindNetwork, + useGetImplicitAccount, useGetOwnedAccountSafe, useRemoveBeaconPeerBySenderId, } from "@umami/state"; @@ -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 @@ -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(); @@ -83,7 +89,16 @@ export const useHandleBeaconMessage = () => { break; } case BeaconMessageType.SignPayloadRequest: { - modal = ; + 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 = ; onClose = async () => { await WalletClient.respond({ id: message.id, diff --git a/apps/web/src/components/common/SignPayloadRequestModal.tsx b/apps/web/src/components/common/SignPayloadRequestModal.tsx index 332ca3724e..70332f2cb7 100644 --- a/apps/web/src/components/common/SignPayloadRequestModal.tsx +++ b/apps/web/src/components/common/SignPayloadRequestModal.tsx @@ -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, @@ -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(); @@ -64,7 +64,7 @@ export const SignPayloadRequestModal = ({ request }: { request: SignPayloadReque - {`${request.appMetadata.name}/dApp Pairing Request`} + {`Sign Payload Request from ${opts.appName}`} @@ -90,7 +90,7 @@ export const SignPayloadRequestModal = ({ request }: { request: SignPayloadReque backgroundColor={color("100")} > - {showRaw ? request.payload : parsedPayload.trim()} + {showRaw ? opts.payload : parsedPayload.trim()} @@ -105,7 +105,7 @@ export const SignPayloadRequestModal = ({ request }: { request: SignPayloadReque - + diff --git a/apps/web/src/components/common/index.ts b/apps/web/src/components/common/index.ts new file mode 100644 index 0000000000..94068949fa --- /dev/null +++ b/apps/web/src/components/common/index.ts @@ -0,0 +1 @@ +export * from "./SignPayloadRequestModal";