Skip to content

Commit

Permalink
wip: request handling
Browse files Browse the repository at this point in the history
  • Loading branch information
dianasavvatina committed Oct 23, 2024
1 parent 35ebbd9 commit 244090c
Show file tree
Hide file tree
Showing 14 changed files with 290 additions and 57 deletions.
5 changes: 2 additions & 3 deletions apps/web/src/components/SendFlow/Beacon/useSignWithBeacon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { BeaconMessageType, type OperationResponseInput } from "@airgap/beacon-w
import { type TezosToolkit } from "@taquito/taquito";
import { useDynamicModalContext } from "@umami/components";
import { executeOperations, totalFee } from "@umami/core";
import { WalletClient, useAsyncActionHandler, useFindNetwork } from "@umami/state";
import { WalletClient, useAsyncActionHandler } from "@umami/state";
import { useForm } from "react-hook-form";

import { SuccessStep } from "../SuccessStep";
Expand All @@ -15,7 +15,6 @@ export const useSignWithBeacon = ({
}: SdkSignPageProps): CalculatedSignProps => {
const { isLoading: isSigning, handleAsyncAction } = useAsyncActionHandler();
const { openWith } = useDynamicModalContext();
const findNetwork = useFindNetwork();

const form = useForm({ defaultValues: { executeParams: operation.estimates } });

Expand Down Expand Up @@ -45,7 +44,7 @@ export const useSignWithBeacon = ({
fee: totalFee(form.watch("executeParams")),
isSigning,
onSign,
network: findNetwork(headerProps.networkName),
network: headerProps.network,
form,
};
};
55 changes: 55 additions & 0 deletions apps/web/src/components/SendFlow/WalletConnect/useSignWithWc.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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,
form,
};
}

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(<SuccessStep hash={opHash} />);
},
error => ({
description: `Failed to confirm Beacon operation: ${error.message}`,
})
);

return {
fee: totalFee(form.watch("executeParams")),
isSigning,
onSign,
network: headerProps.network,
form,
};
};
6 changes: 5 additions & 1 deletion apps/web/src/components/SendFlow/sdk/BatchSignPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,18 @@ 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,
operationDetails: PartialTezosOperation[]
) => {
const color = useColor();

const calculatedProps = useSignWithBeacon({ ...signProps });
const beaconCalculatedProps = useSignWithBeacon({ ...signProps });
const walletConnectCalculatedProps = useSignWithWalletConnect({ ...signProps });
const calculatedProps =
signProps.requestId.sdkType === "beacon" ? beaconCalculatedProps : walletConnectCalculatedProps;

const { isSigning, onSign, network, fee, form } = calculatedProps;
const { signer, operations } = signProps.operation;
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/SendFlow/sdk/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const Header = ({ headerProps }: { headerProps: SignHeaderProps }) => {
Network:
</Heading>
<Text color={color("700")} fontWeight="400" size="sm">
{capitalize(headerProps.networkName)}
{capitalize(headerProps.network.name)}
</Text>
</Flex>

Expand Down
6 changes: 5 additions & 1 deletion apps/web/src/components/SendFlow/sdk/SingleSignPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,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 calculatedProps = useSignWithBeacon({ ...signProps });
const beaconCalculatedProps = useSignWithBeacon({ ...signProps });
const walletConnectCalculatedProps = useSignWithWalletConnect({ ...signProps });
const calculatedProps =
signProps.requestId.sdkType === "beacon" ? beaconCalculatedProps : walletConnectCalculatedProps;

switch (operationType) {
case "tez": {
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/SendFlow/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
useGetOwnedAccount,
useSelectedNetwork,
} from "@umami/state";
import { type ExecuteParams, type RawPkh } from "@umami/tezos";
import { type ExecuteParams, type Network, type RawPkh } from "@umami/tezos";
import { repeat } from "lodash";
import { useState } from "react";
import { useForm, useFormContext } from "react-hook-form";
Expand Down Expand Up @@ -72,7 +72,7 @@ export type SignRequestId =
};

export type SignHeaderProps = {
networkName: string;
network: Network;
appName: string;
appIcon?: string;
};
Expand Down
8 changes: 4 additions & 4 deletions apps/web/src/components/WalletConnect/ProjectInfoCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Avatar, Box, Card, Flex, Heading, Icon, Link, Text } from "@chakra-ui/react";
import { Avatar, Box, Card, Flex, Icon, Link, Text } from "@chakra-ui/react";
import { type SignClientTypes } from "@walletconnect/types";

import { PencilIcon } from "../../assets/icons";
Expand All @@ -20,12 +20,12 @@ export const ProjectInfoCard = ({ metadata, intention }: Props) => {
<Avatar marginX="auto" size="lg" src={icons[0]} />
</Box>
<Box marginTop="16px">
<Text data-testid="session-info-card-text">
<Card data-testid="session-info-card-text">
<Text as="span" fontWeight="bold">
{name}
</Text>{" "}
<Heading size="md">wants to {intention ?? "connect"}</Heading>
</Text>
<Text size="md">wants to {intention ?? "connect"}</Text>
</Card>
</Box>
<Box marginTop="16px">
<Link
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/components/WalletConnect/WalletConnectPeers.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Center, Divider, Flex, Heading, IconButton, Image, Text, VStack } from "@chakra-ui/react";
import { Center, Divider, Flex, IconButton, Image, Text, VStack } from "@chakra-ui/react";
import { useGetWcConnectionInfo, useRemoveWcPeer, useWcPeers } from "@umami/state";
import { parsePkh } from "@umami/tezos";
import { type SessionTypes } from "@walletconnect/types";
Expand Down Expand Up @@ -79,9 +79,9 @@ const PeerRow = ({ peerInfo }: { peerInfo: SessionTypes.Struct }) => {
/>
</Center>
<Center alignItems="flex-start" flexDirection="column" gap="6px">
<Heading color={color("900")} size="lg">
<Text color={color("900")} size="lg">
{peerInfo.peer.metadata.name}
</Heading>
</Text>
<StoredPeerInfo peerInfo={peerInfo} />
</Center>
</Flex>
Expand Down
42 changes: 33 additions & 9 deletions apps/web/src/components/WalletConnect/WalletConnectProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
createWalletKit,
useAsyncActionHandler,
useAvailableNetworks,
useGetAllWcConnectionInfo,
useRemoveWcConnection,
useWcPeers,
walletKit,
Expand All @@ -16,6 +17,7 @@ import { getSdkError } from "@walletconnect/utils";
import { type PropsWithChildren, useEffect } from "react";

import { SessionProposalModal } from "./SessionProposalModal";
import { useHandleWcRequest } from "./useHandleWcRequest";

export const WalletConnectProvider = ({ children }: PropsWithChildren) => {
const onSessionProposal = useOnSessionProposal();
Expand Down Expand Up @@ -73,18 +75,40 @@ const useOnSessionProposal = () => {
};

const useOnSessionRequest = () => {
const { handleAsyncAction } = useAsyncActionHandler();
const { handleAsyncActionUnsafe } = useAsyncActionHandler();
const { peers } = useWcPeers();
const state = useGetAllWcConnectionInfo();
const handleWcRequest = useHandleWcRequest();
const toast = useToast();

return (event: WalletKitTypes.SessionRequest) =>
handleAsyncAction(async () => {
console.log("TODO: Session request received. Handling to be implemented", event);

const response = formatJsonRpcError(event.id, getSdkError("USER_REJECTED_METHODS").message);
await walletKit.respondSessionRequest({ topic: event.topic, response });
}).catch(async () => {
handleAsyncActionUnsafe(async () => {
console.log("Session request and state", event, state, peers);
if (event.topic in state) {
console.log("Session request from dApp", state[event.topic], peers[event.topic]);
toast({
description: `Session request from dApp ${peers[event.topic].peer.metadata.name}`,
status: "info",
});
}
await handleWcRequest(event);
}).catch(async error => {
const { id, topic } = event;
console.error("WalletConnect session request failed", event, peers, error);
if (event.topic in peers) {
toast({
description: `Session request for dApp ${peers[topic].peer.metadata.name} failed. It was rejected.`,
status: "error",
});
} else {
toast({
description: `Session request for dApp ${topic} failed. It was rejected. Peer not found by topic.`,
status: "error",
});
}
// dApp is waiting so we need to notify it
const response = formatJsonRpcError(event.id, getSdkError("INVALID_METHOD").message);
await walletKit.respondSessionRequest({ topic: event.topic, response });
const response = formatJsonRpcError(id, getSdkError("INVALID_METHOD").message);
await walletKit.respondSessionRequest({ topic, response });
});
};

Expand Down
138 changes: 138 additions & 0 deletions apps/web/src/components/WalletConnect/useHandleWcRequest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { useToast } from "@chakra-ui/react";
import { useDynamicModalContext } from "@umami/components";
import { type ImplicitAccount, estimate, toAccountOperations } from "@umami/core";
import {
useAsyncActionHandler,
useFindNetwork,
useGetAllWcConnectionInfo,
useGetOwnedAccountSafe,
useWcPeers,
walletKit,
} from "@umami/state";
import { formatJsonRpcError } from "@walletconnect/jsonrpc-utils";
import { type SignClientTypes, type Verify } from "@walletconnect/types";
import { getSdkError } from "@walletconnect/utils";

import { BatchSignPage } from "../SendFlow/sdk/BatchSignPage";
import { SingleSignPage } from "../SendFlow/sdk/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 { peers } = useWcPeers();
const state = useGetAllWcConnectionInfo();
const toast = useToast();

return async (
event: {
verifyContext: Verify.Context;
} & SignClientTypes.BaseEventArgs<{
request: {
method: string;
params: any;
expiryTimestamp?: number;
};
chainId: string;
}>
) => {
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: event.topic, response });
return;
}

case "tezos_sign": {
// onClose = async () => {
// const response = formatJsonRpcError(id, getSdkError("USER_REJECTED").message);
// await walletKit.respondSessionRequest({ topic, response });
// };
// return openWith(<SignPayloadRequestModal request={"FIXME"} />, { onClose });
const response = formatJsonRpcError(id, getSdkError("INVALID_METHOD").message);
await walletKit.respondSessionRequest({ topic: event.topic, response });
return;
}

case "tezos_send": {
if (!request.params.account) {
throw new Error("Missing account in request");
}
if (!(topic in state)) {
throw new Error(`Unknown dapp: ${topic}`);
}
const dappInfo = state[topic];
if (request.params.account !== dappInfo.accountPkh) {
throw new Error(`Unknown account: ${request.params.account}, topic: ${topic}`);
}
const signer = getAccount(request.params.account);
if (!signer) {
throw new Error(`Unknown account, no signer: ${request.params.account}`);
}
// const sessions = walletKit.getActiveSessions();
if (!(topic in peers)) {
throw new Error(`Unknown topic: ${topic}`);
}
const session = peers[topic];
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: event.topic, response });
toast({ description: `Unsupported network: ${chainId}`, status: "error" });
return;
}
const estimatedOperations = await estimate(operation, network);
console.log("got request", request);
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: event.topic },
};

if (operation.operations.length === 1) {
modal = <SingleSignPage {...signProps} />;
} else {
modal = <BatchSignPage {...signProps} {...event.params.request.params} />;
}
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}`,
// })
);
};
};
Loading

0 comments on commit 244090c

Please sign in to comment.