Skip to content

Commit

Permalink
feat: WalletConnect integration, request
Browse files Browse the repository at this point in the history
requests are supported. Tested:
 - send tez
 - delegate / UndelegationSignPage
 - originate / call contract
 - stake / unstake / finalize unstake
  • Loading branch information
dianasavvatina committed Nov 14, 2024
1 parent af864af commit 7c0b44d
Show file tree
Hide file tree
Showing 20 changed files with 330 additions and 87 deletions.
6 changes: 2 additions & 4 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,6 @@ export const useSignWithBeacon = ({
fee: totalFee(form.watch("executeParams")),
isSigning,
onSign,
network: findNetwork(headerProps.networkName),
form,
network: headerProps.network,
};
};
53 changes: 53 additions & 0 deletions apps/web/src/components/SendFlow/WalletConnect/useSignWithWc.tsx
Original file line number Diff line number Diff line change
@@ -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(<SuccessStep hash={opHash} />);
},
error => ({
description: `Failed to confirm Beacon operation: ${error.message}`,
})
);

return {
fee: totalFee(form.watch("executeParams")),
isSigning,
onSign,
network: headerProps.network,
};
};
11 changes: 8 additions & 3 deletions apps/web/src/components/SendFlow/sdk/BatchSignPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
ModalFooter,
Text,
} from "@chakra-ui/react";
import { FormProvider } from "react-hook-form";
import { FormProvider, useForm } from "react-hook-form";

import { Header } from "./Header";
import { useColor } from "../../../styles/useColor";
Expand All @@ -23,18 +23,23 @@ 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 { isSigning, onSign, network, fee } = calculatedProps;
const { signer, operations } = signProps.operation;
const transactionCount = operations.length;
const form = useForm({ defaultValues: { executeParams: signProps.operation.estimates } });

return (
<FormProvider {...form}>
Expand Down
16 changes: 10 additions & 6 deletions apps/web/src/components/SendFlow/sdk/ContractCallSignPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
ModalFooter,
} from "@chakra-ui/react";
import { type ContractCall } from "@umami/core";
import { FormProvider } from "react-hook-form";
import { FormProvider, useForm } from "react-hook-form";

import { Header } from "./Header";
import { useColor } from "../../../styles/useColor";
Expand All @@ -24,18 +24,22 @@ import { SignButton } from "../SignButton";
import { SignPageFee } from "../SignPageFee";
import { type CalculatedSignProps, type SdkSignPageProps } from "../utils";

export const ContractCallSignPage = (
{ operation, headerProps }: SdkSignPageProps,
calculatedSignProps: CalculatedSignProps
) => {
export const ContractCallSignPage = ({
operation,
headerProps,
isSigning,
onSign,
network,
fee,
}: SdkSignPageProps & CalculatedSignProps) => {
const {
amount: mutezAmount,
contract,
entrypoint,
args,
} = operation.operations[0] as ContractCall;
const color = useColor();
const { isSigning, onSign, network, fee, form } = calculatedSignProps;
const form = useForm({ defaultValues: { executeParams: operation.estimates } });

return (
<FormProvider {...form}>
Expand Down
16 changes: 10 additions & 6 deletions apps/web/src/components/SendFlow/sdk/DelegationSignPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react";
import { type Delegation } from "@umami/core";
import { FormProvider } from "react-hook-form";
import { FormProvider, useForm } from "react-hook-form";

import { Header } from "./Header";
import { AddressTile } from "../../AddressTile/AddressTile";
Expand All @@ -9,13 +9,17 @@ import { SignButton } from "../SignButton";
import { SignPageFee } from "../SignPageFee";
import { type CalculatedSignProps, type SdkSignPageProps } from "../utils";

export const DelegationSignPage = (
{ operation, headerProps }: SdkSignPageProps,
calculatedSignProps: CalculatedSignProps
) => {
export const DelegationSignPage = ({
operation,
headerProps,
isSigning,
onSign,
network,
fee,
}: SdkSignPageProps & CalculatedSignProps) => {
const { recipient } = operation.operations[0] as Delegation;

const { isSigning, onSign, network, fee, form } = calculatedSignProps;
const form = useForm({ defaultValues: { executeParams: operation.estimates } });

return (
<FormProvider {...form}>
Expand Down
16 changes: 10 additions & 6 deletions apps/web/src/components/SendFlow/sdk/FinalizeUnstakeSignPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react";
import { useAccountTotalFinalizableUnstakeAmount } from "@umami/state";
import { FormProvider } from "react-hook-form";
import { FormProvider, useForm } from "react-hook-form";

import { Header } from "./Header";
import { AddressTile } from "../../AddressTile/AddressTile";
Expand All @@ -9,11 +9,15 @@ import { SignButton } from "../SignButton";
import { SignPageFee } from "../SignPageFee";
import { type CalculatedSignProps, type SdkSignPageProps } from "../utils";

export const FinalizeUnstakeSignPage = (
{ operation, headerProps }: SdkSignPageProps,
calculatedSignProps: CalculatedSignProps
) => {
const { isSigning, onSign, network, fee, form } = calculatedSignProps;
export const FinalizeUnstakeSignPage = ({
operation,
headerProps,
isSigning,
onSign,
network,
fee,
}: SdkSignPageProps & CalculatedSignProps) => {
const form = useForm({ defaultValues: { executeParams: operation.estimates } });
const totalFinalizableAmount = useAccountTotalFinalizableUnstakeAmount(
operation.signer.address.pkh
);
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { BatchWalletOperation } from "@taquito/taquito/dist/types/wallet/ba
import { executeOperations, mockContractOrigination, mockImplicitAccount } from "@umami/core";
import { WalletClient, useGetSecretKey } from "@umami/state";
import { executeParams } from "@umami/test-utils";
import { GHOSTNET, makeToolkit, prettyTezAmount } from "@umami/tezos";
import { GHOSTNET, type Network, makeToolkit, prettyTezAmount } from "@umami/tezos";

import { OriginationOperationSignPage } from "./OriginationOperationSignPage";
import {
Expand All @@ -16,7 +16,7 @@ import {
waitFor,
} from "../../../testUtils";
import { SuccessStep } from "../SuccessStep";
import { type SdkSignPageProps } from "../utils";
import { type CalculatedSignProps, type SdkSignPageProps } from "../utils";

const message = {
id: "messageid",
Expand All @@ -31,9 +31,14 @@ const operation = {
operations: [mockContractOrigination(0)],
estimates: [executeParams({ fee: 123 })],
};
const network: Network = {
name: NetworkType.GHOSTNET,
rpcUrl: "https://ghostnet.tezos.org.ua",
tzktApiUrl: "https://api.tzkt.io",
};
const headerProps = {
requestId: message.id,
networkName: message.network.type,
network: network,
appName: message.appMetadata.name,
appIcon: message.appMetadata.icon,
};
Expand All @@ -42,6 +47,12 @@ const signProps: SdkSignPageProps = {
operation: operation,
requestId: { sdkType: "beacon", id: message.id },
};
const calculatedProps: CalculatedSignProps = {
fee: 123,
isSigning: true,
onSign: async () => {},
network: headerProps.network,
};

jest.mock("@umami/core", () => ({
...jest.requireActual("@umami/core"),
Expand All @@ -60,7 +71,7 @@ jest.mock("@umami/state", () => ({

describe("<OriginationOperationSignPage />", () => {
it("renders fee", async () => {
await renderInModal(<OriginationOperationSignPage {...signProps} />);
await renderInModal(<OriginationOperationSignPage {...signProps} {...calculatedProps} />);

await waitFor(() => expect(screen.getByText(prettyTezAmount(123))).toBeVisible());
});
Expand All @@ -74,7 +85,7 @@ describe("<OriginationOperationSignPage />", () => {
jest.mocked(executeOperations).mockResolvedValue({ opHash: "ophash" } as BatchWalletOperation);
jest.spyOn(WalletClient, "respond").mockResolvedValue();

await renderInModal(<OriginationOperationSignPage {...signProps} />);
await renderInModal(<OriginationOperationSignPage {...signProps} {...calculatedProps} />);

await act(() => user.type(screen.getByLabelText("Password"), "Password"));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from "@chakra-ui/react";
import { type ContractOrigination } from "@umami/core";
import { capitalize } from "lodash";
import { FormProvider } from "react-hook-form";
import { FormProvider, useForm } from "react-hook-form";

import { CodeSandboxIcon } from "../../../assets/icons";
import { useColor } from "../../../styles/useColor";
Expand All @@ -27,13 +27,17 @@ import { SignButton } from "../SignButton";
import { SignPageFee } from "../SignPageFee";
import { type CalculatedSignProps, type SdkSignPageProps } from "../utils";

export const OriginationOperationSignPage = (
{ operation, headerProps }: SdkSignPageProps,
calculatedSignProps: CalculatedSignProps
) => {
const { isSigning, onSign, network, form, fee } = calculatedSignProps;
export const OriginationOperationSignPage = ({
operation,
headerProps,
isSigning,
onSign,
network,
fee,
}: SdkSignPageProps & CalculatedSignProps) => {
const color = useColor();
const { code, storage } = operation.operations[0] as ContractOrigination;
const form = useForm({ defaultValues: { executeParams: operation.estimates } });

return (
<FormProvider {...form}>
Expand All @@ -48,7 +52,7 @@ export const OriginationOperationSignPage = (
Network:
</Heading>
<Text color={color("700")} fontWeight="400" size="sm">
{capitalize(headerProps.networkName)}
{capitalize(headerProps.network.name)}
</Text>
</Flex>
</ModalHeader>
Expand Down
7 changes: 6 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,16 @@ 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;
console.log("SingleSignPage, signProps, calculatedProps", signProps, calculatedProps);

switch (operationType) {
case "tez": {
Expand Down
16 changes: 10 additions & 6 deletions apps/web/src/components/SendFlow/sdk/StakeSignPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react";
import { type Stake } from "@umami/core";
import { FormProvider } from "react-hook-form";
import { FormProvider, useForm } from "react-hook-form";

import { Header } from "./Header";
import { AddressTile } from "../../AddressTile/AddressTile";
Expand All @@ -9,13 +9,17 @@ import { SignButton } from "../SignButton";
import { SignPageFee } from "../SignPageFee";
import { type CalculatedSignProps, type SdkSignPageProps } from "../utils";

export const StakeSignPage = (
{ operation, headerProps }: SdkSignPageProps,
calculatedSignProps: CalculatedSignProps
) => {
export const StakeSignPage = ({
operation,
headerProps,
isSigning,
onSign,
network,
fee,
}: SdkSignPageProps & CalculatedSignProps) => {
const { amount: mutezAmount } = operation.operations[0] as Stake;

const { isSigning, onSign, network, fee, form } = calculatedSignProps;
const form = useForm({ defaultValues: { executeParams: operation.estimates } });

return (
<FormProvider {...form}>
Expand Down
Loading

0 comments on commit 7c0b44d

Please sign in to comment.