diff --git a/src/Router.tsx b/src/Router.tsx index b97995421..2442eb35c 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -18,7 +18,7 @@ import { withSideMenu } from "./views/withSideMenu"; import HelpView from "./views/help/HelpView"; import AddressBookView from "./views/addressBook/AddressBookView"; import BatchPage from "./views/batch/BatchPage"; -import { resetBeacon, useBeaconInit } from "./utils/beacon/beacon"; +import { BeaconProvider, resetBeacon } from "./utils/beacon/beacon"; import TokensView from "./views/tokens/TokensView"; import { useDeeplinkHandler } from "./utils/useDeeplinkHandler"; import { AnnouncementBanner } from "./components/AnnouncementBanner"; @@ -39,27 +39,27 @@ const loggedOutRouter = createHashRouter([ ]); const MemoizedRouter = React.memo(() => { - const beaconNotificationModal = useBeaconInit(); const dynamicModal = useDynamicModal(); return ( - - - )} /> - )} /> - )} /> - )} /> - )} /> - )} /> - )} /> - )} /> - )} /> - } /> - - {dynamicModal.content} - {beaconNotificationModal} + + + + )} /> + )} /> + )} /> + )} /> + )} /> + )} /> + )} /> + )} /> + )} /> + } /> + + {dynamicModal.content} + ); diff --git a/src/components/SendFlow/Beacon/BeaconSignPage.tsx b/src/components/SendFlow/Beacon/BeaconSignPage.tsx index 9f8d3091b..5e5993317 100644 --- a/src/components/SendFlow/Beacon/BeaconSignPage.tsx +++ b/src/components/SendFlow/Beacon/BeaconSignPage.tsx @@ -29,7 +29,6 @@ const BeaconSignPage: React.FC = ({ operation, onBeaconSucc case "fa1.2": case "fa2": case "contract_origination": - // this line will not reach, but better safe than sorry throw new Error("Unsupported operation type"); } }; diff --git a/src/components/SendFlow/Beacon/useSignWithBeacon.tsx b/src/components/SendFlow/Beacon/useSignWithBeacon.tsx index ab23922c3..e96c51bf8 100644 --- a/src/components/SendFlow/Beacon/useSignWithBeacon.tsx +++ b/src/components/SendFlow/Beacon/useSignWithBeacon.tsx @@ -13,10 +13,10 @@ export const useSignWithBeacon = ( operation: ImplicitOperations, onBeaconSuccess: (hash: string) => Promise ) => { + const { onClose } = useContext(DynamicModalContext); const [fee, setFee] = useState(null); const network = useSelectedNetwork(); const { isLoading: isSigning, handleAsyncAction } = useAsyncActionHandler(); - const { handleAsyncAction: handleFeeEstimation } = useAsyncActionHandler(); const { openWith } = useContext(DynamicModalContext); const form = useForm<{ sender: string; signer: string }>({ mode: "onBlur", @@ -27,20 +27,25 @@ export const useSignWithBeacon = ( }); useEffect(() => { - const estimateFee = () => - handleFeeEstimation( + const estimateFee = () => { + handleAsyncAction( async () => { const fee = await estimate(operation, network); setFee(fee); }, - err => ({ - title: "Error", - description: `Error while processing beacon request: ${err.message}`, - status: "error", - }) + err => { + onClose(); + return { + title: "Error", + description: `Error while processing beacon request: ${err.message}`, + status: "error", + }; + } ); + }; estimateFee(); - }, [network, operation, handleFeeEstimation]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [network, operation]); const onSign = async (tezosToolkit: TezosToolkit) => handleAsyncAction(async () => { diff --git a/src/utils/beacon/BeaconNotification/BeaconRequestNotification.tsx b/src/utils/beacon/BeaconNotification/BeaconRequestNotification.tsx index 97ea442c3..fbc5a4990 100644 --- a/src/utils/beacon/BeaconNotification/BeaconRequestNotification.tsx +++ b/src/utils/beacon/BeaconNotification/BeaconRequestNotification.tsx @@ -3,17 +3,13 @@ import { BeaconRequestOutputMessage, OperationRequestOutput, OperationResponseInput, + PartialTezosOperation, TezosOperationType, } from "@airgap/beacon-wallet"; import { useToast } from "@chakra-ui/react"; import React from "react"; import { ImplicitOperations } from "../../../components/sendForm/types"; -import { - isValidContractPkh, - parseContractPkh, - parseImplicitPkh, - parsePkh, -} from "../../../types/Address"; +import { isValidContractPkh, parseContractPkh, parseImplicitPkh } from "../../../types/Address"; import { Operation } from "../../../types/Operation"; import { useGetImplicitAccountSafe } from "../../hooks/accountHooks"; import { walletClient } from "../beacon"; @@ -78,8 +74,50 @@ export const BeaconNotification: React.FC<{ } }; +const partialOperationToOperation = ( + partialOperation: PartialTezosOperation, + signer: ImplicitAccount +): Operation | null => { + switch (partialOperation.kind) { + case TezosOperationType.TRANSACTION: { + const { destination, amount, parameters } = partialOperation; + const isContractCall = isValidContractPkh(destination) && parameters; + if (isContractCall) { + return { + type: "contract_call", + amount, + contract: parseContractPkh(destination), + entrypoint: parameters.entrypoint, + args: parameters.value, + }; + } else { + return { + type: "tez", + amount, + recipient: parseImplicitPkh(partialOperation.destination), + }; + } + } + case TezosOperationType.DELEGATION: { + const { delegate } = partialOperation; + + if (delegate) { + return { + type: "delegation", + sender: signer.address, + recipient: parseImplicitPkh(delegate), + }; + } else { + return { type: "undelegation", sender: signer.address }; + } + } + default: + return null; + } +}; + const toOperation = ( - { operationDetails, sourceAddress }: OperationRequestOutput, + { operationDetails }: OperationRequestOutput, signer: ImplicitAccount ): ImplicitOperations => { if (operationDetails.length === 0) { @@ -92,35 +130,7 @@ const toOperation = ( const partialOperation = operationDetails[0]; - let operation: Operation | undefined = undefined; - - if (partialOperation.kind === TezosOperationType.TRANSACTION) { - const { destination, amount, parameters } = partialOperation; - operation = - isValidContractPkh(destination) && parameters - ? { - type: "contract_call", - amount, - contract: parseContractPkh(destination), - entrypoint: parameters.entrypoint, - args: parameters.value, - } - : { - type: "tez", - amount, - recipient: parseImplicitPkh(partialOperation.destination), - }; - } else if (partialOperation.kind === TezosOperationType.DELEGATION) { - const sender = parsePkh(sourceAddress); - operation = partialOperation.delegate - ? { - type: "delegation", - sender, - recipient: parseImplicitPkh(partialOperation.delegate), - } - : { type: "undelegation", sender }; - } - + const operation = partialOperationToOperation(operationDetails[0], signer); if (!operation) { throw new Error(`Unsupported operation: ${partialOperation.kind}`); } diff --git a/src/utils/beacon/beacon.tsx b/src/utils/beacon/beacon.tsx index 870b7e405..59bcfc005 100644 --- a/src/utils/beacon/beacon.tsx +++ b/src/utils/beacon/beacon.tsx @@ -1,15 +1,10 @@ -import { - BeaconRequestOutputMessage, - ConnectionContext, - ExtendedP2PPairingResponse, - Serializer, - WalletClient, -} from "@airgap/beacon-wallet"; -import { Modal, useDisclosure, useToast } from "@chakra-ui/react"; -import { useEffect, useRef } from "react"; +import { ExtendedP2PPairingResponse, Serializer, WalletClient } from "@airgap/beacon-wallet"; +import { useToast } from "@chakra-ui/react"; +import { useContext, useEffect } from "react"; import { useQuery, useQueryClient } from "react-query"; import { BeaconNotification } from "./BeaconNotification"; import { makePeerInfo, PeerInfo } from "./types"; +import { DynamicModalContext } from "../../components/DynamicModal"; const makeClient = () => new WalletClient({ @@ -58,52 +53,22 @@ export const useAddPeer = () => { }; }; -export const useBeaconModalNotification = () => { - const { isOpen, onOpen, onClose } = useDisclosure(); - const beaconMessage = useRef(); - - return { - modalElement: ( - - {beaconMessage.current && ( - - )} - - ), - - onOpen: (message: BeaconRequestOutputMessage, _: ConnectionContext) => { - beaconMessage.current = message; - onOpen(); - }, - }; -}; - -// Need this ignore BS because useEffect runs twice in development: -// https://react.dev/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development -export const useBeaconInit = () => { - const { modalElement: beaconModal, onOpen } = useBeaconModalNotification(); - const ignore = useRef(false); - const handleBeaconMessage = useRef(onOpen); - +export const BeaconProvider: React.FC<{ + children: React.ReactNode; +}> = ({ children }) => { + const { openWith, onClose } = useContext(DynamicModalContext); useEffect(() => { - if (!ignore.current) { - walletClient - .init() - .then(() => { - walletClient.connect(handleBeaconMessage.current); - }) - .catch(console.error) - .finally(() => { - ignore.current = false; + walletClient + .init() + .then(() => { + walletClient.connect(message => { + openWith(); }); - } - - return () => { - ignore.current = true; - }; - }, []); + }) + .catch(console.error); + }, [onClose, openWith]); - return beaconModal; + return <>{children}; }; export const resetBeacon = async () => {