Skip to content

Commit

Permalink
Merge pull request #2197 from trilitech/add-wc-prepare-for-sdk
Browse files Browse the repository at this point in the history
feat: WalletConnect integration, part 3, Beacon modals generalised for any SDK
  • Loading branch information
dianasavvatina authored Dec 9, 2024
2 parents 013b870 + b2a5432 commit 9fa9e79
Show file tree
Hide file tree
Showing 18 changed files with 283 additions and 144 deletions.
31 changes: 18 additions & 13 deletions apps/web/src/components/SendFlow/Beacon/BatchSignPage.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { type PartialTezosOperation } from "@airgap/beacon-wallet";
import {
Accordion,
AccordionButton,
Expand All @@ -12,28 +13,36 @@ import {
ModalFooter,
Text,
} from "@chakra-ui/react";
import { FormProvider } from "react-hook-form";
import { FormProvider, useForm } from "react-hook-form";

import { type BeaconSignPageProps } from "./BeaconSignPageProps";
import { Header } from "./Header";
import { useSignWithBeacon } from "./useSignWithBeacon";
import { useColor } from "../../../styles/useColor";
import { AddressTile } from "../../AddressTile/AddressTile";
import { JsValueWrap } from "../../JsValueWrap";
import { useSignWithBeacon } from "../Beacon/useSignWithBeacon";
import { SignButton } from "../SignButton";
import { SignPageFee } from "../SignPageFee";
import { type SdkSignPageProps } from "../utils";

export const BatchSignPage = ({ operation, message }: BeaconSignPageProps) => {
const { isSigning, onSign, network, fee, form } = useSignWithBeacon(operation, message);
export const BatchSignPage = (
signProps: SdkSignPageProps,
operationDetails: PartialTezosOperation[]
) => {
const color = useColor();
const { signer } = operation;
const transactionCount = operation.operations.length;

const beaconCalculatedProps = useSignWithBeacon({ ...signProps });
const calculatedProps = beaconCalculatedProps;

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}>
<ModalContent>
<form>
<Header message={message} />
<Header headerProps={signProps.headerProps} />

<ModalBody>
<Accordion allowToggle>
Expand All @@ -45,11 +54,7 @@ export const BatchSignPage = ({ operation, message }: BeaconSignPageProps) => {
<AccordionIcon />
</AccordionButton>
<AccordionPanel>
<JsValueWrap
overflowY="auto"
maxHeight="200px"
value={message.operationDetails}
/>
<JsValueWrap overflowY="auto" maxHeight="200px" value={operationDetails} />
</AccordionPanel>
</AccordionItem>
</Accordion>
Expand Down
28 changes: 16 additions & 12 deletions apps/web/src/components/SendFlow/Beacon/BeaconSignPage.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,43 @@
import { CustomError } from "@umami/utils";

import { type BeaconSignPageProps } from "./BeaconSignPageProps";
import { type SdkSignPageProps } from "../utils";
import { ContractCallSignPage } from "./ContractCallSignPage";
import { DelegationSignPage } from "./DelegationSignPage";
import { FinalizeUnstakeSignPage } from "./FinalizeUnstakeSignPage";
import { OriginationOperationSignPage } from "./OriginationOperationSignPage";
import { StakeSignPage } from "./StakeSignPage";
import { TezSignPage as BeaconTezSignPage } from "./TezSignPage";
import { TezSignPage } from "./TezSignPage";
import { UndelegationSignPage } from "./UndelegationSignPage";
import { UnstakeSignPage } from "./UnstakeSignPage";
import { useSignWithBeacon } from "../Beacon/useSignWithBeacon";

export const BeaconSignPage = ({ operation, message }: BeaconSignPageProps) => {
const operationType = operation.operations[0].type;
export const SingleSignPage = (signProps: SdkSignPageProps) => {
const operationType = signProps.operation.operations[0].type;

const beaconCalculatedProps = useSignWithBeacon({ ...signProps });
const calculatedProps = beaconCalculatedProps;

switch (operationType) {
case "tez": {
return <BeaconTezSignPage message={message} operation={operation} />;
return <TezSignPage {...signProps} {...calculatedProps} />;
}
case "contract_call": {
return <ContractCallSignPage message={message} operation={operation} />;
return <ContractCallSignPage {...signProps} {...calculatedProps} />;
}
case "delegation": {
return <DelegationSignPage message={message} operation={operation} />;
return <DelegationSignPage {...signProps} {...calculatedProps} />;
}
case "undelegation": {
return <UndelegationSignPage message={message} operation={operation} />;
return <UndelegationSignPage {...signProps} {...calculatedProps} />;
}
case "contract_origination":
return <OriginationOperationSignPage message={message} operation={operation} />;
return <OriginationOperationSignPage {...signProps} {...calculatedProps} />;
case "stake":
return <StakeSignPage message={message} operation={operation} />;
return <StakeSignPage {...signProps} {...calculatedProps} />;
case "unstake":
return <UnstakeSignPage message={message} operation={operation} />;
return <UnstakeSignPage {...signProps} {...calculatedProps} />;
case "finalize_unstake":
return <FinalizeUnstakeSignPage message={message} operation={operation} />;
return <FinalizeUnstakeSignPage {...signProps} {...calculatedProps} />;
/**
* FA1/2 are impossible to get here because we don't parse them
* instead we get a generic contract call
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +0,0 @@
import { type OperationRequestOutput } from "@airgap/beacon-wallet";
import { type EstimatedAccountOperations } from "@umami/core";

export type BeaconSignPageProps = {
operation: EstimatedAccountOperations;
message: OperationRequestOutput;
};
18 changes: 12 additions & 6 deletions apps/web/src/components/SendFlow/Beacon/ContractCallSignPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,40 @@ 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 { type BeaconSignPageProps } from "./BeaconSignPageProps";
import { Header } from "./Header";
import { useSignWithBeacon } from "./useSignWithBeacon";
import { useColor } from "../../../styles/useColor";
import { AddressTile } from "../../AddressTile/AddressTile";
import { AdvancedSettingsAccordion } from "../../AdvancedSettingsAccordion";
import { TezTile } from "../../AssetTiles/TezTile";
import { JsValueWrap } from "../../JsValueWrap";
import { SignButton } from "../SignButton";
import { SignPageFee } from "../SignPageFee";
import { type CalculatedSignProps, type SdkSignPageProps } from "../utils";

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

return (
<FormProvider {...form}>
<ModalContent>
<form>
<Header message={message} />
<Header headerProps={headerProps} />
<ModalBody>
<TezTile mutezAmount={mutezAmount} />

Expand Down
18 changes: 12 additions & 6 deletions apps/web/src/components/SendFlow/Beacon/DelegationSignPage.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
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 { type BeaconSignPageProps } from "./BeaconSignPageProps";
import { Header } from "./Header";
import { useSignWithBeacon } from "./useSignWithBeacon";
import { AddressTile } from "../../AddressTile/AddressTile";
import { AdvancedSettingsAccordion } from "../../AdvancedSettingsAccordion";
import { SignButton } from "../SignButton";
import { SignPageFee } from "../SignPageFee";
import { type CalculatedSignProps, type SdkSignPageProps } from "../utils";

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

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

return (
<FormProvider {...form}>
<ModalContent>
<form>
<Header message={message} />
<Header headerProps={headerProps} />
<ModalBody>
<FormLabel>From</FormLabel>
<AddressTile address={operation.signer.address} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
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 { type BeaconSignPageProps } from "./BeaconSignPageProps";
import { Header } from "./Header";
import { useSignWithBeacon } from "./useSignWithBeacon";
import { AddressTile } from "../../AddressTile/AddressTile";
import { TezTile } from "../../AssetTiles/TezTile";
import { SignButton } from "../SignButton";
import { SignPageFee } from "../SignPageFee";
import { type CalculatedSignProps, type SdkSignPageProps } from "../utils";

export const FinalizeUnstakeSignPage = ({ operation, message }: BeaconSignPageProps) => {
const { isSigning, onSign, network, fee, form } = useSignWithBeacon(operation, message);
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
);
return (
<FormProvider {...form}>
<ModalContent>
<form>
<Header message={message} />
<Header headerProps={headerProps} />
<ModalBody>
<Flex alignItems="center" justifyContent="end" marginTop="12px">
<SignPageFee fee={fee} />
Expand Down
10 changes: 5 additions & 5 deletions apps/web/src/components/SendFlow/Beacon/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { type OperationRequestOutput } from "@airgap/beacon-wallet";
import { AspectRatio, Flex, Heading, Image, Text } from "@chakra-ui/react";
import { capitalize } from "lodash";

import { CodeSandboxIcon } from "../../../assets/icons";
import { useColor } from "../../../styles/useColor";
import { SignPageHeader } from "../SignPageHeader";
import { type SignHeaderProps } from "../utils";

export const Header = ({ message }: { message: OperationRequestOutput }) => {
export const Header = ({ headerProps }: { headerProps: SignHeaderProps }) => {
const color = useColor();

return (
Expand All @@ -16,7 +16,7 @@ export const Header = ({ message }: { message: OperationRequestOutput }) => {
Network:
</Heading>
<Text color={color("700")} fontWeight="400" size="sm">
{capitalize(message.network.type)}
{capitalize(headerProps.network.name)}
</Text>
</Flex>

Expand All @@ -32,10 +32,10 @@ export const Header = ({ message }: { message: OperationRequestOutput }) => {
borderRadius="4px"
objectFit="cover"
fallback={<CodeSandboxIcon width="36px" height="36px" />}
src={message.appMetadata.icon}
src={headerProps.appIcon}
/>
</AspectRatio>
<Heading size="sm">{message.appMetadata.name}</Heading>
<Heading size="sm">{headerProps.appName}</Heading>
</Flex>
</SignPageHeader>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { WalletClient, useGetSecretKey } from "@umami/state";
import { executeParams } from "@umami/test-utils";
import { GHOSTNET, makeToolkit, prettyTezAmount } from "@umami/tezos";

import { OriginationOperationSignPage } from "./OriginationOperationSignPage";
import {
act,
dynamicModalContextMock,
Expand All @@ -16,6 +15,8 @@ import {
waitFor,
} from "../../../testUtils";
import { SuccessStep } from "../SuccessStep";
import { type SdkSignPageProps, type SignHeaderProps } from "../utils";
import { SingleSignPage } from "./BeaconSignPage";

const message = {
id: "messageid",
Expand All @@ -30,6 +31,16 @@ const operation = {
operations: [mockContractOrigination(0)],
estimates: [executeParams({ fee: 123 })],
};
const headerProps: SignHeaderProps = {
network: GHOSTNET,
appName: message.appMetadata.name,
appIcon: message.appMetadata.icon,
};
const signProps: SdkSignPageProps = {
headerProps: headerProps,
operation: operation,
requestId: { sdkType: "beacon", id: message.id },
};

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

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

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

await renderInModal(<OriginationOperationSignPage message={message} operation={operation} />);

await act(() => user.type(screen.getByLabelText("Password"), "Password"));
await renderInModal(<SingleSignPage {...signProps} />);

const signButton = screen.getByRole("button", {
name: "Confirm Transaction",
});
await waitFor(() => expect(signButton).toBeDisabled());

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

await waitFor(() => expect(signButton).toBeEnabled());
await act(() => user.click(signButton));

Expand Down
Loading

0 comments on commit 9fa9e79

Please sign in to comment.