From 322734766a817f524e559e481035fab20e9b68dd Mon Sep 17 00:00:00 2001 From: Diana Savatina Date: Tue, 1 Oct 2024 14:03:39 +0100 Subject: [PATCH] feat: WalletConnect integration, part 2, pairing list --- apps/desktop/src/Router.tsx | 4 +- apps/desktop/src/utils/beacon/BeaconPeers.tsx | 12 +- .../utils/beacon/PermissionRequestModal.tsx | 4 +- .../utils/beacon/useHandleBeaconMessage.tsx | 6 +- .../src/components/Menu/AppsMenu/AppsMenu.tsx | 3 +- .../WalletConnect/SessionProposalModal.tsx | 13 +- .../WalletConnect/WalletConnectPeers.tsx | 125 ++++++++++++++++++ .../WalletConnect/WalletConnectProvider.tsx | 6 +- .../src/components/WalletConnect/index.tsx | 1 + .../web/src/components/beacon/BeaconPeers.tsx | 10 +- .../beacon/PermissionRequestModal.tsx | 4 +- .../beacon/useHandleBeaconMessage.tsx | 4 +- packages/state/package.json | 1 + packages/state/src/hooks/WalletConnect.ts | 27 ++++ packages/state/src/hooks/beacon.test.ts | 20 +-- packages/state/src/hooks/beacon.ts | 41 +++--- packages/state/src/hooks/index.ts | 1 + .../src/hooks/removeAccountDependencies.ts | 4 +- packages/state/src/reducer.ts | 2 + packages/state/src/slices/WalletConnect.ts | 30 +++++ packages/state/src/slices/beacon.ts | 4 +- packages/state/src/slices/index.ts | 1 + pnpm-lock.yaml | 5 +- 23 files changed, 268 insertions(+), 60 deletions(-) create mode 100644 apps/web/src/components/WalletConnect/WalletConnectPeers.tsx create mode 100644 packages/state/src/hooks/WalletConnect.ts create mode 100644 packages/state/src/slices/WalletConnect.ts diff --git a/apps/desktop/src/Router.tsx b/apps/desktop/src/Router.tsx index aa81ff12f..be27e89db 100644 --- a/apps/desktop/src/Router.tsx +++ b/apps/desktop/src/Router.tsx @@ -1,7 +1,7 @@ /* istanbul ignore file */ import { DynamicModalContext, useDynamicModal } from "@umami/components"; import { useDataPolling } from "@umami/data-polling"; -import { WalletClient, useImplicitAccounts, useResetConnections } from "@umami/state"; +import { WalletClient, useImplicitAccounts, useResetBeaconConnections } from "@umami/state"; import { noop } from "lodash"; import { useEffect } from "react"; import { HashRouter, Navigate, Route, Routes } from "react-router-dom"; @@ -59,7 +59,7 @@ const LoggedInRouterWithPolling = () => { }; const LoggedOutRouter = () => { - const resetBeaconConnections = useResetConnections(); + const resetBeaconConnections = useResetBeaconConnections(); useEffect(() => { WalletClient.destroy().then(resetBeaconConnections).catch(noop); diff --git a/apps/desktop/src/utils/beacon/BeaconPeers.tsx b/apps/desktop/src/utils/beacon/BeaconPeers.tsx index e6016208e..2c8b86f88 100644 --- a/apps/desktop/src/utils/beacon/BeaconPeers.tsx +++ b/apps/desktop/src/utils/beacon/BeaconPeers.tsx @@ -10,7 +10,7 @@ import { Image, Text, } from "@chakra-ui/react"; -import { useGetConnectionInfo, usePeers, useRemovePeer } from "@umami/state"; +import { useBeaconPeers, useGetBeaconConnectionInfo, useRemoveBeaconPeer } from "@umami/state"; import { parsePkh } from "@umami/tezos"; import capitalize from "lodash/capitalize"; import { Fragment } from "react"; @@ -22,10 +22,10 @@ import colors from "../../style/colors"; /** * Component displaying a list of connected dApps. * - * Loads dApps data from {@link usePeers} hook & zips it with generated dAppIds. + * Loads dApps data from {@link useBeaconPeers} hook & zips it with generated dAppIds. */ export const BeaconPeers = () => { - const { peers } = usePeers(); + const { peers } = useBeaconPeers(); if (peers.length === 0) { return ( @@ -57,7 +57,7 @@ export const BeaconPeers = () => { * @param onRemove - action for deleting dApp connection. */ const PeerRow = ({ peerInfo }: { peerInfo: ExtendedPeerInfo }) => { - const removePeer = useRemovePeer(); + const removeBeaconPeer = useRemoveBeaconPeer(); return ( @@ -76,7 +76,7 @@ const PeerRow = ({ peerInfo }: { peerInfo: ExtendedPeerInfo }) => { } - onClick={() => removePeer(peerInfo)} + onClick={() => removeBeaconPeer(peerInfo)} size="xs" variant="circle" /> @@ -94,7 +94,7 @@ const PeerRow = ({ peerInfo }: { peerInfo: ExtendedPeerInfo }) => { * @param peerInfo - peerInfo provided by beacon Api + computed dAppId. */ const StoredPeerInfo = ({ peerInfo }: { peerInfo: ExtendedPeerInfo }) => { - const connectionInfo = useGetConnectionInfo(peerInfo.senderId); + const connectionInfo = useGetBeaconConnectionInfo(peerInfo.senderId); if (!connectionInfo) { return null; diff --git a/apps/desktop/src/utils/beacon/PermissionRequestModal.tsx b/apps/desktop/src/utils/beacon/PermissionRequestModal.tsx index ea75d6f4c..75edc019e 100644 --- a/apps/desktop/src/utils/beacon/PermissionRequestModal.tsx +++ b/apps/desktop/src/utils/beacon/PermissionRequestModal.tsx @@ -27,7 +27,7 @@ import { import { useDynamicModalContext } from "@umami/components"; import { WalletClient, - useAddConnection, + useAddBeaconConnection, useAsyncActionHandler, useGetImplicitAccount, } from "@umami/state"; @@ -39,7 +39,7 @@ import { OwnedImplicitAccountsAutocomplete } from "../../components/AddressAutoc import colors from "../../style/colors"; export const PermissionRequestModal = ({ request }: { request: PermissionRequestOutput }) => { - const addConnectionToBeaconSlice = useAddConnection(); + const addConnectionToBeaconSlice = useAddBeaconConnection(); const getAccount = useGetImplicitAccount(); const { onClose } = useDynamicModalContext(); const { handleAsyncAction } = useAsyncActionHandler(); diff --git a/apps/desktop/src/utils/beacon/useHandleBeaconMessage.tsx b/apps/desktop/src/utils/beacon/useHandleBeaconMessage.tsx index 834f9fe44..f95d58e93 100644 --- a/apps/desktop/src/utils/beacon/useHandleBeaconMessage.tsx +++ b/apps/desktop/src/utils/beacon/useHandleBeaconMessage.tsx @@ -11,7 +11,7 @@ import { useAsyncActionHandler, useFindNetwork, useGetOwnedAccountSafe, - useRemovePeerBySenderId, + useRemoveBeaconPeerBySenderId, } from "@umami/state"; import { type Network } from "@umami/tezos"; @@ -23,7 +23,7 @@ import { BeaconSignPage } from "../../components/SendFlow/Beacon/BeaconSignPage" /** * @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, + * For operation requests it will also try to convert the operation(s)n to our {@link Operation} format, * estimate the fee and open the BeaconSignPage only if it succeeds */ export const useHandleBeaconMessage = () => { @@ -31,7 +31,7 @@ export const useHandleBeaconMessage = () => { const { handleAsyncAction } = useAsyncActionHandler(); const getAccount = useGetOwnedAccountSafe(); const findNetwork = useFindNetwork(); - const removePeer = useRemovePeerBySenderId(); + const removePeer = useRemoveBeaconPeerBySenderId(); // we should confirm that we support the network that the beacon request is coming from const checkNetwork = ({ diff --git a/apps/web/src/components/Menu/AppsMenu/AppsMenu.tsx b/apps/web/src/components/Menu/AppsMenu/AppsMenu.tsx index 1bef31a11..c278bba21 100644 --- a/apps/web/src/components/Menu/AppsMenu/AppsMenu.tsx +++ b/apps/web/src/components/Menu/AppsMenu/AppsMenu.tsx @@ -2,7 +2,7 @@ import { Button, Text } from "@chakra-ui/react"; import { useAddPeer } from "@umami/state"; import { BeaconPeers } from "../../beacon"; -import { useOnWalletConnect } from "../../WalletConnect"; +import { WcPeers, useOnWalletConnect } from "../../WalletConnect"; import { DrawerContentWrapper } from "../DrawerContentWrapper"; export const AppsMenu = () => { @@ -36,6 +36,7 @@ export const AppsMenu = () => { title="Apps" > + ); }; diff --git a/apps/web/src/components/WalletConnect/SessionProposalModal.tsx b/apps/web/src/components/WalletConnect/SessionProposalModal.tsx index 80a14e577..b7798a3af 100644 --- a/apps/web/src/components/WalletConnect/SessionProposalModal.tsx +++ b/apps/web/src/components/WalletConnect/SessionProposalModal.tsx @@ -16,7 +16,13 @@ import { } from "@chakra-ui/react"; import { type WalletKitTypes } from "@reown/walletkit"; import { useDynamicModalContext } from "@umami/components"; -import { useAsyncActionHandler, useGetImplicitAccount, walletKit } from "@umami/state"; +import { + useAsyncActionHandler, + useGetImplicitAccount, + useToggleWcPeerListUpdated, + walletKit, +} from "@umami/state"; +import { type SessionTypes } from "@walletconnect/types"; import { buildApprovedNamespaces, getSdkError } from "@walletconnect/utils"; import { FormProvider, useForm } from "react-hook-form"; @@ -34,6 +40,7 @@ export const SessionProposalModal = ({ network: NetworkType; }) => { const getAccount = useGetImplicitAccount(); + const toggleWcPeerListUpdated = useToggleWcPeerListUpdated(); const color = useColor(); const { onClose } = useDynamicModalContext(); @@ -64,11 +71,13 @@ export const SessionProposalModal = ({ }, }); - await walletKit.approveSession({ + const session: SessionTypes.Struct = await walletKit.approveSession({ id: proposal.id, namespaces, sessionProperties: {}, }); + console.log("Approved session", session); + toggleWcPeerListUpdated(); onClose(); }); diff --git a/apps/web/src/components/WalletConnect/WalletConnectPeers.tsx b/apps/web/src/components/WalletConnect/WalletConnectPeers.tsx new file mode 100644 index 000000000..fe2518bcc --- /dev/null +++ b/apps/web/src/components/WalletConnect/WalletConnectPeers.tsx @@ -0,0 +1,125 @@ +import { Center, Divider, Flex, IconButton, Image, Text, VStack } from "@chakra-ui/react"; +import { useDisconnectWalletConnectPeer, useGetWcPeerListToggle, walletKit } from "@umami/state"; +import { parsePkh } from "@umami/tezos"; +import { type SessionTypes } from "@walletconnect/types"; +import { getSdkError } from "@walletconnect/utils"; +import capitalize from "lodash/capitalize"; + +import { CodeSandboxIcon, StubIcon as TrashIcon } from "../../assets/icons"; +import { useColor } from "../../styles/useColor"; +import { AddressPill } from "../AddressPill"; +import { EmptyMessage } from "../EmptyMessage"; + +/** + * Component displaying a list of connected dApps. + * + * Loads dApps data from WalletConnct API and displays it in a list. + */ +export const WcPeers = () => { + const sessions: Record = walletKit.getActiveSessions(); + useGetWcPeerListToggle(); + + if (Object.keys(sessions).length === 0) { + return ( + + ); + } + + return ( + } + spacing="0" + > + { + // loop peers and print PeerRow + Object.entries(sessions).map(([topic, sessionInfo]) => ( + + )) + } + + ); +}; + +/** + * Component for displaying info about single connected dApp. + * + * @param sessionInfo - sessionInfo provided by wc Api + computed dAppId. + * @param onRemove - action for deleting dApp connection. + */ +const PeerRow = ({ sessionInfo }: { sessionInfo: SessionTypes.Struct }) => { + const color = useColor(); + const disconnectWalletConnectPeer = useDisconnectWalletConnectPeer(); + + return ( +
+ +
+ } + src={sessionInfo.peer.metadata.icons[0]} + /> +
+
+ + {sessionInfo.peer.metadata.name} + + +
+
+ } + onClick={() => + disconnectWalletConnectPeer({ + topic: sessionInfo.topic, + reason: getSdkError("USER_DISCONNECTED"), + }) + } + variant="iconButtonSolid" + /> +
+ ); +}; + +/** + * Component for displaying additional info about connection with a dApp. + * + * Displays {@link AddressPill} with a connected account and network type. + * + * @param sessionInfo - sessionInfo provided by WalletConnect Api. + * Account is stored in format: tezos:ghostnet:tz1... + * Network is stored in format: tezos:mainnet + */ +const StoredSessionInfo = ({ sessionInfo }: { sessionInfo: SessionTypes.Struct }) => ( + + + + + Network: + + + {capitalize(sessionInfo.namespaces.tezos.chains?.[0].split(":")[1] ?? "")} + + +); diff --git a/apps/web/src/components/WalletConnect/WalletConnectProvider.tsx b/apps/web/src/components/WalletConnect/WalletConnectProvider.tsx index d9ac9c577..2bf025f37 100644 --- a/apps/web/src/components/WalletConnect/WalletConnectProvider.tsx +++ b/apps/web/src/components/WalletConnect/WalletConnectProvider.tsx @@ -6,6 +6,7 @@ import { createWalletKit, useAsyncActionHandler, useAvailableNetworks, + useToggleWcPeerListUpdated, walletKit, } from "@umami/state"; import { type Network } from "@umami/tezos"; @@ -20,6 +21,7 @@ export const WalletConnectProvider = ({ children }: PropsWithChildren) => { const [isWalletKitCreated, setWalletKitCreated] = useState(false); const { handleAsyncActionUnsafe } = useAsyncActionHandler(); const { openWith } = useDynamicModalContext(); + const toggleWcPeerListUpdated = useToggleWcPeerListUpdated(); const toast = useToast(); const availableNetworks: Network[] = useAvailableNetworks(); @@ -69,8 +71,10 @@ export const WalletConnectProvider = ({ children }: PropsWithChildren) => { description: "Session deleted by peer dApp", status: "info", }); + // now re-render peer list + toggleWcPeerListUpdated(); }, - [toast] + [toast, toggleWcPeerListUpdated] ); const onSessionRequest = useCallback( diff --git a/apps/web/src/components/WalletConnect/index.tsx b/apps/web/src/components/WalletConnect/index.tsx index 1a1760470..bac0f3691 100644 --- a/apps/web/src/components/WalletConnect/index.tsx +++ b/apps/web/src/components/WalletConnect/index.tsx @@ -1 +1,2 @@ export * from "./WalletConnectProvider"; +export * from "./WalletConnectPeers"; diff --git a/apps/web/src/components/beacon/BeaconPeers.tsx b/apps/web/src/components/beacon/BeaconPeers.tsx index 192e551e4..904f0208c 100644 --- a/apps/web/src/components/beacon/BeaconPeers.tsx +++ b/apps/web/src/components/beacon/BeaconPeers.tsx @@ -1,6 +1,6 @@ import { type ExtendedPeerInfo } from "@airgap/beacon-wallet"; import { Center, Divider, Flex, Heading, IconButton, Image, Text, VStack } from "@chakra-ui/react"; -import { useGetConnectionInfo, usePeers, useRemovePeer } from "@umami/state"; +import { useBeaconPeers, useGetBeaconConnectionInfo, useRemoveBeaconPeer } from "@umami/state"; import { parsePkh } from "@umami/tezos"; import capitalize from "lodash/capitalize"; @@ -12,10 +12,10 @@ import { EmptyMessage } from "../EmptyMessage"; /** * Component displaying a list of connected dApps. * - * Loads dApps data from {@link usePeers} hook & zips it with generated dAppIds. + * Loads dApps data from {@link useBeaconPeers} hook & zips it with generated dAppIds. */ export const BeaconPeers = () => { - const { peers } = usePeers(); + const { peers } = useBeaconPeers(); if (peers.length === 0) { return ( @@ -53,7 +53,7 @@ export const BeaconPeers = () => { */ const PeerRow = ({ peerInfo }: { peerInfo: ExtendedPeerInfo }) => { const color = useColor(); - const removePeer = useRemovePeer(); + const removePeer = useRemoveBeaconPeer(); return (
{ * @param peerInfo - peerInfo provided by beacon Api + computed dAppId. */ const StoredPeerInfo = ({ peerInfo }: { peerInfo: ExtendedPeerInfo }) => { - const connectionInfo = useGetConnectionInfo(peerInfo.senderId); + const connectionInfo = useGetBeaconConnectionInfo(peerInfo.senderId); if (!connectionInfo) { return null; diff --git a/apps/web/src/components/beacon/PermissionRequestModal.tsx b/apps/web/src/components/beacon/PermissionRequestModal.tsx index f1e6e3fac..6fdb382d6 100644 --- a/apps/web/src/components/beacon/PermissionRequestModal.tsx +++ b/apps/web/src/components/beacon/PermissionRequestModal.tsx @@ -26,7 +26,7 @@ import { import { useDynamicModalContext } from "@umami/components"; import { WalletClient, - useAddConnection, + useAddBeaconConnection, useAsyncActionHandler, useGetImplicitAccount, } from "@umami/state"; @@ -41,7 +41,7 @@ import { JsValueWrap } from "../JsValueWrap"; export const PermissionRequestModal = ({ request }: { request: PermissionRequestOutput }) => { const color = useColor(); - const addConnectionToBeaconSlice = useAddConnection(); + const addConnectionToBeaconSlice = useAddBeaconConnection(); const getAccount = useGetImplicitAccount(); const { onClose } = useDynamicModalContext(); const { handleAsyncAction } = useAsyncActionHandler(); diff --git a/apps/web/src/components/beacon/useHandleBeaconMessage.tsx b/apps/web/src/components/beacon/useHandleBeaconMessage.tsx index 834f9fe44..402ba662b 100644 --- a/apps/web/src/components/beacon/useHandleBeaconMessage.tsx +++ b/apps/web/src/components/beacon/useHandleBeaconMessage.tsx @@ -11,7 +11,7 @@ import { useAsyncActionHandler, useFindNetwork, useGetOwnedAccountSafe, - useRemovePeerBySenderId, + useRemoveBeaconPeerBySenderId, } from "@umami/state"; import { type Network } from "@umami/tezos"; @@ -31,7 +31,7 @@ export const useHandleBeaconMessage = () => { const { handleAsyncAction } = useAsyncActionHandler(); const getAccount = useGetOwnedAccountSafe(); const findNetwork = useFindNetwork(); - const removePeer = useRemovePeerBySenderId(); + const removePeer = useRemoveBeaconPeerBySenderId(); // we should confirm that we support the network that the beacon request is coming from const checkNetwork = ({ diff --git a/packages/state/package.json b/packages/state/package.json index 45122799d..73289ac9d 100644 --- a/packages/state/package.json +++ b/packages/state/package.json @@ -83,6 +83,7 @@ "@umami/tzkt": "workspace:^", "@walletconnect/core": "^2.16.2", "@walletconnect/jsonrpc-utils": "^1.0.8", + "@walletconnect/types": "^2.16.2", "@walletconnect/utils": "^2.16.2", "axios": "^1.7.7", "bip39": "^3.1.0", diff --git a/packages/state/src/hooks/WalletConnect.ts b/packages/state/src/hooks/WalletConnect.ts new file mode 100644 index 000000000..1e23942e4 --- /dev/null +++ b/packages/state/src/hooks/WalletConnect.ts @@ -0,0 +1,27 @@ +import { type ErrorResponse } from "@walletconnect/jsonrpc-utils"; +import { useDispatch } from "react-redux"; + +import { useAppSelector } from "./useAppSelector"; +import { wcActions } from "../slices"; +import { walletKit } from "../walletConnect"; + +// get a toggle to monitor updates in peer list +export const useGetWcPeerListToggle = () => { + const wcData = useAppSelector(s => s.walletconnect); + return wcData.peerListUpdatedToggle; +}; + +// report that the peer list is updated +export const useToggleWcPeerListUpdated = () => { + const dispatch = useDispatch(); + return () => dispatch(wcActions.togglePeerListUpdated()); +}; + +// remove dApp connection from WalletConnect SDK +export const useDisconnectWalletConnectPeer = () => { + const togglePeerListUpdated = useToggleWcPeerListUpdated(); + return async (params: { topic: string; reason: ErrorResponse }) => { + console.log("disconnecting WC session on user request", params); + await walletKit.disconnectSession(params).then(() => togglePeerListUpdated()); + }; +}; diff --git a/packages/state/src/hooks/beacon.test.ts b/packages/state/src/hooks/beacon.test.ts index 649e27f03..85936430e 100644 --- a/packages/state/src/hooks/beacon.test.ts +++ b/packages/state/src/hooks/beacon.test.ts @@ -3,10 +3,10 @@ import { mockMnemonicAccount, mockSocialAccount } from "@umami/core"; import { type RawPkh } from "@umami/tezos"; import { - useAddConnection, - useGetConnectionInfo, - useRemoveConnection, - useResetConnections, + useAddBeaconConnection, + useGetBeaconConnectionInfo, + useRemoveBeaconConnection, + useResetBeaconConnections, } from "./beacon"; import { beaconActions } from "../slices"; import { type UmamiStore, makeStore } from "../store"; @@ -33,7 +33,7 @@ const connectionInfo = (accountPkh: RawPkh, networkType: NetworkType) => ({ describe("useGetConnectedAccount", () => { it("returns undefined when no connection for dAppId is stored", () => { - const view = renderHook(() => useGetConnectionInfo(dAppId1), { store }); + const view = renderHook(() => useGetBeaconConnectionInfo(dAppId1), { store }); expect(view.result.current).toBeUndefined(); }); @@ -42,7 +42,7 @@ describe("useGetConnectedAccount", () => { addConnection(dAppId1, pkh1, NetworkType.MAINNET); addConnection(dAppId2, pkh2, NetworkType.GHOSTNET); - const view = renderHook(() => useGetConnectionInfo(dAppId2), { store }); + const view = renderHook(() => useGetBeaconConnectionInfo(dAppId2), { store }); expect(view.result.current).toEqual(connectionInfo(pkh2, NetworkType.GHOSTNET)); }); @@ -55,7 +55,7 @@ describe("useResetConnections", () => { const { result: { current: resetBeaconSlice }, - } = renderHook(() => useResetConnections(), { store }); + } = renderHook(() => useResetBeaconConnections(), { store }); resetBeaconSlice(); expect(store.getState().beacon).toEqual({}); @@ -68,7 +68,7 @@ describe("useAddConnection", () => { const { result: { current: addConnectionHook }, - } = renderHook(() => useAddConnection(), { store }); + } = renderHook(() => useAddBeaconConnection(), { store }); addConnectionHook(dAppId2, pkh2, NetworkType.MAINNET); expect(store.getState().beacon).toEqual({ @@ -82,7 +82,7 @@ describe("useAddConnection", () => { const { result: { current: addConnectionHook }, - } = renderHook(() => useAddConnection(), { store }); + } = renderHook(() => useAddBeaconConnection(), { store }); addConnectionHook(dAppId1, pkh2, NetworkType.MAINNET); expect(store.getState().beacon).toEqual({ @@ -98,7 +98,7 @@ describe("useRemoveConnection", () => { const { result: { current: removeConnection }, - } = renderHook(() => useRemoveConnection(), { store }); + } = renderHook(() => useRemoveBeaconConnection(), { store }); removeConnection(dAppId2); expect(store.getState().beacon).toEqual({ diff --git a/packages/state/src/hooks/beacon.ts b/packages/state/src/hooks/beacon.ts index b31f4e788..1eb141a6b 100644 --- a/packages/state/src/hooks/beacon.ts +++ b/packages/state/src/hooks/beacon.ts @@ -12,18 +12,21 @@ import { useDispatch } from "react-redux"; import { useAppSelector } from "./useAppSelector"; import { WalletClient, parsePeerInfo } from "../beacon"; -import { type DAppConnectionInfo, beaconActions } from "../slices"; +import { type DAppBeaconConnectionInfo, beaconActions } from "../slices"; + /** * Returns connected account pkh & network by a given dAppId. * * @param dAppId - generated from dApp public key. */ -export const useGetConnectionInfo = (dAppId: string): DAppConnectionInfo | undefined => { +export const useGetBeaconConnectionInfo = ( + dAppId: string +): DAppBeaconConnectionInfo | undefined => { const beaconConnections = useAppSelector(s => s.beacon); return beaconConnections[dAppId]; }; -export const useGetPeersForAccounts = () => { +export const useGetBeaconPeersForAccounts = () => { const beaconConnections = useAppSelector(s => s.beacon); return (pkhs: RawPkh[]) => @@ -37,7 +40,7 @@ export const useGetPeersForAccounts = () => { /** * Returns function for removing all connections from {@link beaconSlice}. */ -export const useResetConnections = () => { +export const useResetBeaconConnections = () => { const dispatch = useDispatch(); return () => dispatch(beaconActions.reset()); }; @@ -45,7 +48,7 @@ export const useResetConnections = () => { /** * Returns function for adding connection info to {@link beaconSlice}. */ -export const useAddConnection = () => { +export const useAddBeaconConnection = () => { const dispatch = useDispatch(); return (dAppId: string, accountPkh: RawPkh, networkType: NetworkType) => dispatch(beaconActions.addConnection({ dAppId, accountPkh, networkType })); @@ -54,17 +57,17 @@ export const useAddConnection = () => { /** * Returns function for removing connection from {@link beaconSlice}. */ -export const useRemoveConnection = () => { +export const useRemoveBeaconConnection = () => { const dispatch = useDispatch(); return (dAppId: string) => dispatch(beaconActions.removeConnection(dAppId)); }; -export const usePeers = () => { +export const useBeaconPeers = () => { const query = useQuery({ queryKey: ["beaconPeers"], queryFn: async () => { - const peers = await WalletClient.getPeers(); - return peers as ExtendedPeerInfo[]; + const beaconPeers: ExtendedPeerInfo[] = (await WalletClient.getPeers()) as ExtendedPeerInfo[]; + return beaconPeers; }, initialData: [], }); @@ -72,9 +75,9 @@ export const usePeers = () => { return { peers: query.data, refresh: query.refetch }; }; -export const useRemovePeer = () => { - const { refresh } = usePeers(); - const removeConnectionFromBeaconSlice = useRemoveConnection(); +export const useRemoveBeaconPeer = () => { + const { refresh } = useBeaconPeers(); + const removeConnectionFromBeaconSlice = useRemoveBeaconConnection(); return (peerInfo: ExtendedPeerInfo) => WalletClient.removePeer(peerInfo as ExtendedP2PPairingResponse, true) @@ -82,23 +85,23 @@ export const useRemovePeer = () => { .finally(() => void refresh()); }; -export const useRemovePeerBySenderId = () => { - const { peers } = usePeers(); - const removePeer = useRemovePeer(); +export const useRemoveBeaconPeerBySenderId = () => { + const { peers } = useBeaconPeers(); + const removePeer = useRemoveBeaconPeer(); return (senderId: string) => Promise.all(peers.filter(peerInfo => senderId === peerInfo.senderId).map(removePeer)); }; -export const useRemovePeersByAccounts = () => { - const getPeersForAccounts = useGetPeersForAccounts(); - const removePeerBySenderId = useRemovePeerBySenderId(); +export const useRemoveBeaconPeersByAccounts = () => { + const getPeersForAccounts = useGetBeaconPeersForAccounts(); + const removePeerBySenderId = useRemoveBeaconPeerBySenderId(); return (pkhs: RawPkh[]) => Promise.all(getPeersForAccounts(pkhs).map(removePeerBySenderId)); }; export const useAddPeer = () => { - const { refresh } = usePeers(); + const { refresh } = useBeaconPeers(); const toast = useToast(); return (payload: string) => diff --git a/packages/state/src/hooks/index.ts b/packages/state/src/hooks/index.ts index 5f5eeb14a..b27025015 100644 --- a/packages/state/src/hooks/index.ts +++ b/packages/state/src/hooks/index.ts @@ -18,3 +18,4 @@ export * from "./removeAccountDependencies"; export * from "./setAccountData"; export * from "./staking"; export * from "./tokens"; +export * from "./WalletConnect"; diff --git a/packages/state/src/hooks/removeAccountDependencies.ts b/packages/state/src/hooks/removeAccountDependencies.ts index cbf6ff37c..b62b2b0dc 100644 --- a/packages/state/src/hooks/removeAccountDependencies.ts +++ b/packages/state/src/hooks/removeAccountDependencies.ts @@ -1,6 +1,6 @@ import { type Account, type ImplicitAccount } from "@umami/core"; -import { useRemovePeersByAccounts } from "./beacon"; +import { useRemoveBeaconPeersByAccounts } from "./beacon"; import { useCurrentAccount, useImplicitAccounts } from "./getAccountData"; import { useMultisigAccounts } from "./multisig"; import { useAppDispatch } from "./useAppDispatch"; @@ -45,7 +45,7 @@ export const useRemoveDependenciesAndMultisigs = () => { */ const useRemoveAccountsDependencies = () => { const dispatch = useAppDispatch(); - const removePeersByAccounts = useRemovePeersByAccounts(); + const removePeersByAccounts = useRemoveBeaconPeersByAccounts(); const currentAccount = useCurrentAccount(); const implicitAccounts = useImplicitAccounts(); diff --git a/packages/state/src/reducer.ts b/packages/state/src/reducer.ts index c1e376bcf..ffe888543 100644 --- a/packages/state/src/reducer.ts +++ b/packages/state/src/reducer.ts @@ -4,6 +4,7 @@ import storage from "redux-persist/lib/storage"; import { createAsyncMigrate } from "./createAsyncMigrate"; import { VERSION, accountsMigrations, mainStoreMigrations } from "./migrations"; +import { wcSlice } from "./slices"; import { accountsSlice } from "./slices/accounts/accounts"; import { announcementSlice } from "./slices/announcement"; import { assetsSlice } from "./slices/assets"; @@ -38,6 +39,7 @@ const rootReducers = combineReducers({ assets: assetsSlice.reducer, batches: batchesSlice.reducer, beacon: beaconSlice.reducer, + walletconnect: wcSlice.reducer, contacts: contactsSlice.reducer, errors: errorsSlice.reducer, multisigs: multisigsSlice.reducer, diff --git a/packages/state/src/slices/WalletConnect.ts b/packages/state/src/slices/WalletConnect.ts new file mode 100644 index 000000000..ff40d581e --- /dev/null +++ b/packages/state/src/slices/WalletConnect.ts @@ -0,0 +1,30 @@ +import { createSlice } from "@reduxjs/toolkit"; + +export type WalletConnectInfo = { + peerListUpdatedToggle: boolean; +}; + +// mapping topic -> connection info +export type State = WalletConnectInfo; + +export const wcInitialState: State = { peerListUpdatedToggle: false }; + +/** + * Stores connection info between dApps and accounts. + * + * dApps are identified by topic (a unique string id generated from dApp public key). + */ +export const wcSlice = createSlice({ + name: "walletconnect", + initialState: wcInitialState, + reducers: { + reset: () => wcInitialState, + + togglePeerListUpdated: state => { + state.peerListUpdatedToggle = !state.peerListUpdatedToggle; + console.log("peerList is updated"); + }, + }, +}); + +export const wcActions = wcSlice.actions; diff --git a/packages/state/src/slices/beacon.ts b/packages/state/src/slices/beacon.ts index 0e4216fdd..1ef38049e 100644 --- a/packages/state/src/slices/beacon.ts +++ b/packages/state/src/slices/beacon.ts @@ -3,12 +3,12 @@ import { createSlice } from "@reduxjs/toolkit"; import { type RawPkh } from "@umami/tezos"; import { fromPairs } from "lodash"; -export type DAppConnectionInfo = { +export type DAppBeaconConnectionInfo = { accountPkh: RawPkh; networkType: NetworkType; }; -type State = Record; +type State = Record; export const beaconInitialState: State = {}; diff --git a/packages/state/src/slices/index.ts b/packages/state/src/slices/index.ts index e3346a731..b2a674b95 100644 --- a/packages/state/src/slices/index.ts +++ b/packages/state/src/slices/index.ts @@ -9,3 +9,4 @@ export * from "./multisigs"; export * from "./networks"; export * from "./tokens"; export * from "./protocolSettings"; +export * from "./WalletConnect"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 980bdfba4..e90270cd0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1673,6 +1673,9 @@ importers: '@walletconnect/jsonrpc-utils': specifier: ^1.0.8 version: 1.0.8 + '@walletconnect/types': + specifier: ^2.16.2 + version: 2.16.2 '@walletconnect/utils': specifier: ^2.16.2 version: 2.16.2 @@ -1763,7 +1766,7 @@ importers: version: 8.57.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.1.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.1.0)(typescript@5.5.4)) + version: 29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.4)) madge: specifier: ^8.0.0 version: 8.0.0(typescript@5.5.4)