diff --git a/apps/web/src/components/SendFlow/WalletConnect/useSignWithWc.tsx b/apps/web/src/components/SendFlow/WalletConnect/useSignWithWc.tsx
new file mode 100644
index 0000000000..e3f688526c
--- /dev/null
+++ b/apps/web/src/components/SendFlow/WalletConnect/useSignWithWc.tsx
@@ -0,0 +1,53 @@
+import { type TezosToolkit } from "@taquito/taquito";
+import { useDynamicModalContext } from "@umami/components";
+import { executeOperations, totalFee } from "@umami/core";
+import { useAsyncActionHandler, walletKit } from "@umami/state";
+import { formatJsonRpcResult } from "@walletconnect/jsonrpc-utils";
+import { useForm } from "react-hook-form";
+
+import { SuccessStep } from "../SuccessStep";
+import { type CalculatedSignProps, type SdkSignPageProps } from "../utils";
+
+export const useSignWithWalletConnect = ({
+ operation,
+ headerProps,
+ requestId,
+}: SdkSignPageProps): CalculatedSignProps => {
+ const { isLoading: isSigning, handleAsyncAction } = useAsyncActionHandler();
+ const { openWith } = useDynamicModalContext();
+
+ const form = useForm({ defaultValues: { executeParams: operation.estimates } });
+
+ if (requestId.sdkType !== "walletconnect") {
+ return {
+ fee: 0,
+ isSigning: false,
+ onSign: async () => {},
+ network: null,
+ };
+ }
+
+ const onSign = async (tezosToolkit: TezosToolkit) =>
+ handleAsyncAction(
+ async () => {
+ const { opHash } = await executeOperations(
+ { ...operation, estimates: form.watch("executeParams") },
+ tezosToolkit
+ );
+
+ const response = formatJsonRpcResult(requestId.id, { hash: opHash });
+ await walletKit.respondSessionRequest({ topic: requestId.topic, response });
+ return openWith();
+ },
+ error => ({
+ description: `Failed to confirm Beacon operation: ${error.message}`,
+ })
+ );
+
+ return {
+ fee: totalFee(form.watch("executeParams")),
+ isSigning,
+ onSign,
+ network: headerProps.network,
+ };
+};
diff --git a/apps/web/src/components/SendFlow/common/BatchSignPage.tsx b/apps/web/src/components/SendFlow/common/BatchSignPage.tsx
index aec74b54cc..fd451cfe9b 100644
--- a/apps/web/src/components/SendFlow/common/BatchSignPage.tsx
+++ b/apps/web/src/components/SendFlow/common/BatchSignPage.tsx
@@ -23,6 +23,7 @@ import { useSignWithBeacon } from "../Beacon/useSignWithBeacon";
import { SignButton } from "../SignButton";
import { SignPageFee } from "../SignPageFee";
import { type SdkSignPageProps } from "../utils";
+import { useSignWithWalletConnect } from "../WalletConnect/useSignWithWc";
export const BatchSignPage = (
signProps: SdkSignPageProps,
@@ -31,7 +32,9 @@ export const BatchSignPage = (
const color = useColor();
const beaconCalculatedProps = useSignWithBeacon({ ...signProps });
- const calculatedProps = beaconCalculatedProps;
+ const walletConnectCalculatedProps = useSignWithWalletConnect({ ...signProps });
+ const calculatedProps =
+ signProps.requestId.sdkType === "beacon" ? beaconCalculatedProps : walletConnectCalculatedProps;
const { isSigning, onSign, network, fee } = calculatedProps;
const { signer, operations } = signProps.operation;
diff --git a/apps/web/src/components/SendFlow/common/OriginationOperationSignPage.tsx b/apps/web/src/components/SendFlow/common/OriginationOperationSignPage.tsx
index 87a978c168..77d5bea397 100644
--- a/apps/web/src/components/SendFlow/common/OriginationOperationSignPage.tsx
+++ b/apps/web/src/components/SendFlow/common/OriginationOperationSignPage.tsx
@@ -37,7 +37,6 @@ export const OriginationOperationSignPage = ({
}: SdkSignPageProps & CalculatedSignProps) => {
const color = useColor();
const { code, storage } = operation.operations[0] as ContractOrigination;
-
const form = useForm({ defaultValues: { executeParams: operation.estimates } });
return (
diff --git a/apps/web/src/components/SendFlow/common/SingleSignPage.tsx b/apps/web/src/components/SendFlow/common/SingleSignPage.tsx
index 4f70346847..11250ce2f5 100644
--- a/apps/web/src/components/SendFlow/common/SingleSignPage.tsx
+++ b/apps/web/src/components/SendFlow/common/SingleSignPage.tsx
@@ -10,12 +10,15 @@ import { TezSignPage } from "./TezSignPage";
import { UndelegationSignPage } from "./UndelegationSignPage";
import { UnstakeSignPage } from "./UnstakeSignPage";
import { useSignWithBeacon } from "../Beacon/useSignWithBeacon";
+import { useSignWithWalletConnect } from "../WalletConnect/useSignWithWc";
export const SingleSignPage = (signProps: SdkSignPageProps) => {
const operationType = signProps.operation.operations[0].type;
const beaconCalculatedProps = useSignWithBeacon({ ...signProps });
- const calculatedProps = beaconCalculatedProps;
+ const walletConnectCalculatedProps = useSignWithWalletConnect({ ...signProps });
+ const calculatedProps =
+ signProps.requestId.sdkType === "beacon" ? beaconCalculatedProps : walletConnectCalculatedProps;
switch (operationType) {
case "tez": {
diff --git a/apps/web/src/components/WalletConnect/WalletConnectProvider.tsx b/apps/web/src/components/WalletConnect/WalletConnectProvider.tsx
index dfcb2ae58a..9627069e5b 100644
--- a/apps/web/src/components/WalletConnect/WalletConnectProvider.tsx
+++ b/apps/web/src/components/WalletConnect/WalletConnectProvider.tsx
@@ -19,6 +19,7 @@ import { getSdkError } from "@walletconnect/utils";
import { type PropsWithChildren, useCallback, useEffect, useRef } from "react";
import { SessionProposalModal } from "./SessionProposalModal";
+import { useHandleWcRequest } from "./useHandleWcRequest";
enum WalletKitState {
NOT_INITIALIZED,
@@ -36,6 +37,8 @@ export const WalletConnectProvider = ({ children }: PropsWithChildren) => {
const availableNetworks: Network[] = useAvailableNetworks();
+ const handleWcRequest = useHandleWcRequest();
+
const onSessionProposal = useCallback(
(proposal: WalletKitTypes.SessionProposal) =>
handleAsyncActionUnsafe(async () => {
@@ -87,8 +90,8 @@ export const WalletConnectProvider = ({ children }: PropsWithChildren) => {
);
const onSessionRequest = useCallback(
- async (event: WalletKitTypes.SessionRequest) => {
- try {
+ async (event: WalletKitTypes.SessionRequest) =>
+ handleAsyncActionUnsafe(async () => {
const activeSessions: Record = walletKit.getActiveSessions();
if (!(event.topic in activeSessions)) {
console.error("WalletConnect session request failed. Session not found", event);
@@ -101,8 +104,8 @@ export const WalletConnectProvider = ({ children }: PropsWithChildren) => {
description: `Session request from dApp ${session.peer.metadata.name}`,
status: "info",
});
- throw new CustomError("Not implemented");
- } catch (error) {
+ await handleWcRequest(event, session);
+ }).catch(async error => {
const { id, topic } = event;
const activeSessions: Record = walletKit.getActiveSessions();
console.error("WalletConnect session request failed", event, error);
@@ -121,9 +124,8 @@ export const WalletConnectProvider = ({ children }: PropsWithChildren) => {
// dApp is waiting so we need to notify it
const response = formatJsonRpcError(id, getSdkError("INVALID_METHOD").message);
await walletKit.respondSessionRequest({ topic, response });
- }
- },
- [toast]
+ }),
+ [handleAsyncActionUnsafe, handleWcRequest, toast]
);
useEffect(() => {
diff --git a/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx b/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx
new file mode 100644
index 0000000000..cb1403277f
--- /dev/null
+++ b/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx
@@ -0,0 +1,122 @@
+import { useToast } from "@chakra-ui/react";
+import { useDynamicModalContext } from "@umami/components";
+import { type ImplicitAccount, estimate, toAccountOperations } from "@umami/core";
+import {
+ useAsyncActionHandler,
+ useFindNetwork,
+ useGetOwnedAccountSafe,
+ walletKit,
+} from "@umami/state";
+import { formatJsonRpcError } from "@walletconnect/jsonrpc-utils";
+import { type SessionTypes, type SignClientTypes, type Verify } from "@walletconnect/types";
+import { getSdkError } from "@walletconnect/utils";
+
+import { BatchSignPage } from "../SendFlow/common/BatchSignPage";
+import { SingleSignPage } from "../SendFlow/common/SingleSignPage";
+import { type SdkSignPageProps, type SignHeaderProps } from "../SendFlow/utils";
+
+/**
+ * @returns a function that handles a beacon message and opens a modal with the appropriate content
+ *
+ * For operation requests it will also try to convert the operation(s) to our {@link Operation} format,
+ * estimate the fee and open the BeaconSignPage only if it succeeds
+ */
+export const useHandleWcRequest = () => {
+ const { openWith } = useDynamicModalContext();
+ const { handleAsyncActionUnsafe } = useAsyncActionHandler();
+ const getAccount = useGetOwnedAccountSafe();
+ const findNetwork = useFindNetwork();
+ const toast = useToast();
+
+ return async (
+ event: {
+ verifyContext: Verify.Context;
+ } & SignClientTypes.BaseEventArgs<{
+ request: {
+ method: string;
+ params: any;
+ expiryTimestamp?: number;
+ };
+ chainId: string;
+ }>,
+ session: SessionTypes.Struct
+ ) => {
+ await handleAsyncActionUnsafe(
+ async () => {
+ const { id, topic, params } = event;
+ const { request, chainId } = params;
+
+ let modal;
+ let onClose;
+
+ switch (request.method) {
+ case "tezos_getAccounts": {
+ const response = formatJsonRpcError(id, getSdkError("INVALID_METHOD").message);
+ await walletKit.respondSessionRequest({ topic, response });
+ return;
+ }
+
+ case "tezos_sign": {
+ // onClose = async () => {
+ // const response = formatJsonRpcError(id, getSdkError("USER_REJECTED").message);
+ // await walletKit.respondSessionRequest({ topic, response });
+ // };
+ // return openWith(, { onClose });
+ const response = formatJsonRpcError(id, getSdkError("INVALID_METHOD").message);
+ await walletKit.respondSessionRequest({ topic, response });
+ return;
+ }
+
+ case "tezos_send": {
+ if (!request.params.account) {
+ throw new Error("Missing account in request");
+ }
+ const signer = getAccount(request.params.account);
+ if (!signer) {
+ throw new Error(`Unknown account, no signer: ${request.params.account}`);
+ }
+ const operation = toAccountOperations(
+ request.params.operations,
+ signer as ImplicitAccount
+ );
+ const network = findNetwork(chainId.split(":")[1]);
+ if (!network) {
+ const response = formatJsonRpcError(id, getSdkError("INVALID_EVENT").message);
+ await walletKit.respondSessionRequest({ topic, response });
+ toast({ description: `Unsupported network: ${chainId}`, status: "error" });
+ return;
+ }
+ const estimatedOperations = await estimate(operation, network);
+ const headerProps: SignHeaderProps = {
+ network,
+ appName: session.peer.metadata.name,
+ appIcon: session.peer.metadata.icons[0],
+ };
+ const signProps: SdkSignPageProps = {
+ headerProps: headerProps,
+ operation: estimatedOperations,
+ requestId: { sdkType: "walletconnect", id: id, topic },
+ };
+
+ if (operation.operations.length === 1) {
+ modal = ;
+ } else {
+ modal = ;
+ }
+ onClose = async () => {
+ const response = formatJsonRpcError(id, getSdkError("USER_REJECTED").message);
+ await walletKit.respondSessionRequest({ topic, response });
+ };
+
+ return openWith(modal, { onClose });
+ }
+ default:
+ throw new Error(`Unsupported method ${request.method}`);
+ }
+ }
+ // error => ({
+ // description: `Error while processing WalletConnect request: ${error.message}`,
+ // })
+ );
+ };
+};