From 3033e75a9e51fec697bfbbcbaff2321287449fb5 Mon Sep 17 00:00:00 2001 From: Shreerang Kale Date: Thu, 24 Oct 2024 17:44:17 +0530 Subject: [PATCH] Add wallet connect provider for making payments --- .../components/projects/create/Configure.tsx | 4 + .../src/context/WalletConnectContext.tsx | 193 ++++++++++++++++++ packages/frontend/src/index.tsx | 19 +- packages/frontend/src/utils/web3modal.ts | 7 + 4 files changed, 215 insertions(+), 8 deletions(-) create mode 100644 packages/frontend/src/context/WalletConnectContext.tsx create mode 100644 packages/frontend/src/utils/web3modal.ts diff --git a/packages/frontend/src/components/projects/create/Configure.tsx b/packages/frontend/src/components/projects/create/Configure.tsx index 54a67a40..3a17b14e 100644 --- a/packages/frontend/src/components/projects/create/Configure.tsx +++ b/packages/frontend/src/components/projects/create/Configure.tsx @@ -22,6 +22,7 @@ import { useToast } from 'components/shared/Toast'; import { useGQLClient } from '../../../context/GQLClientContext'; import EnvironmentVariablesForm from 'pages/org-slug/projects/id/settings/EnvironmentVariablesForm'; import { EnvironmentVariablesFormValues } from 'types/types'; +import { useWalletConnectClient } from 'context/WalletConnectContext'; type ConfigureDeploymentFormValues = { option: string; @@ -34,6 +35,8 @@ type ConfigureFormValues = ConfigureDeploymentFormValues & EnvironmentVariablesFormValues; const Configure = () => { + const { onConnect } = useWalletConnectClient() + const [isLoading, setIsLoading] = useState(false); const [deployers, setDeployers] = useState([]); @@ -349,6 +352,7 @@ const Configure = () => { + ); diff --git a/packages/frontend/src/context/WalletConnectContext.tsx b/packages/frontend/src/context/WalletConnectContext.tsx new file mode 100644 index 00000000..ca45e184 --- /dev/null +++ b/packages/frontend/src/context/WalletConnectContext.tsx @@ -0,0 +1,193 @@ +import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'; + +import SignClient from '@walletconnect/sign-client'; +import { getSdkError } from '@walletconnect/utils'; +import { SessionTypes } from '@walletconnect/types'; +import { StargateClient } from '@cosmjs/stargate'; + +import { walletConnectModal } from '../utils/web3modal'; +import { VITE_WALLET_CONNECT_ID } from 'utils/constants'; + +interface ClientInterface { + signClient: SignClient | undefined; + session: SessionTypes.Struct | undefined; + loadingSession: boolean; + onConnect: () => Promise; + onDisconnect: () => Promise; + onSessionDelete: () => void; + accounts: {address: string, balance?: string}[] | undefined; +} + +const ClientContext = createContext({} as ClientInterface); + +export const useWalletConnectClient = () => { + return useContext(ClientContext); +}; + +export const WalletConnectClientProvider = ({ children }: {children: JSX.Element}) => { + const [signClient, setSignClient] = useState(); + const [session, setSession] = useState(); + const [loadingSession, setLoadingSession] = useState(true); + const [accounts, setAccounts] = useState<{address: string, balance?: string}[]>(); + + const isSignClientInitializing = useRef(false); + + const createCosmosClient = useCallback(async (endpoint: string) => { + return await StargateClient.connect(endpoint); + }, []); + + const onSessionConnect = useCallback(async(session: SessionTypes.Struct) => { + setSession(session); + }, []); + + useEffect(()=>{ + console.log(accounts) + }, [accounts]) + + const subscribeToEvents = useCallback( + async (client: SignClient) => { + client.on('session_update', ({ topic, params }) => { + const { namespaces } = params; + const currentSession = client.session.get(topic); + const updatedSession = { ...currentSession, namespaces }; + setSession(updatedSession); + });}, + [setSession] + ); + + const onConnect = async () => { + console.log({signClient}) + try { + const { uri, approval } = await signClient!.connect({}); + + console.log({uri}) + console.log({uri}) + + if (uri) { + walletConnectModal.openModal({ uri }); + const session = await approval(); + onSessionConnect(session); + walletConnectModal.closeModal(); + } + } catch (e) { + console.error(e); + } + }; + + const onDisconnect = useCallback(async () => { + if (typeof signClient === 'undefined') { + throw new Error('WalletConnect is not initialized'); + } + if (typeof session === 'undefined') { + throw new Error('Session is not connected'); + } + + await signClient.disconnect({ + topic: session.topic, + reason: getSdkError('USER_DISCONNECTED'), + }); + + onSessionDelete(); + }, [signClient, session]); + + const onSessionDelete = () => { + setAccounts(undefined); + setSession(undefined); + }; + + const checkPersistedState = useCallback( + async (signClient: SignClient) => { + if (typeof signClient === 'undefined') { + throw new Error('WalletConnect is not initialized'); + } + + if (typeof session !== 'undefined') return; + if (signClient.session.length) { + const lastKeyIndex = signClient.session.keys.length - 1; + const previousSsession = signClient.session.get( + signClient.session.keys[lastKeyIndex] + ); + + await onSessionConnect(previousSsession); + return previousSsession; + } + },[session, onSessionConnect]); + + const createClient = useCallback( async () => { + isSignClientInitializing.current = true; + try{ + const signClient = await SignClient.init({ + projectId: VITE_WALLET_CONNECT_ID, + metadata: { + name: 'Laconic Pay', + description: 'App for payments', + url: window.location.href, + icons: ['https://avatars.githubusercontent.com/u/92608123'] + } + }); + + setSignClient(signClient); + await checkPersistedState(signClient); + await subscribeToEvents(signClient); + setLoadingSession(false); + + } catch(e) { + console.error('error in createClient', e); + } + isSignClientInitializing.current = false; + }, [setSignClient, checkPersistedState, subscribeToEvents]); + + useEffect(() => { + if (!signClient && !isSignClientInitializing.current) { + createClient(); + } + }, [signClient, createClient]); + + useEffect(() => { + const populateAccounts = async () => { + if (!session) { + return; + } + const cosmosAddresses = session.namespaces['cosmos'].accounts; + + const cosmosAccounts = cosmosAddresses.map((address) => ({ + address, + })); + + const allAccounts = cosmosAccounts; + + setAccounts(allAccounts); + }; + + populateAccounts(); + }, [session, createCosmosClient]); + + useEffect(() => { + if(!signClient){ + return; + } + + signClient.on('session_delete', onSessionDelete); + + return () => { + signClient.off('session_delete', onSessionDelete); + }; + }); + + return ( + + {children} + + ); +}; diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx index 66812111..787cef61 100644 --- a/packages/frontend/src/index.tsx +++ b/packages/frontend/src/index.tsx @@ -16,6 +16,7 @@ import { Toaster } from 'components/shared/Toast'; import { LogErrorBoundary } from 'utils/log-error'; import { BASE_URL } from 'utils/constants'; import Web3ModalProvider from './context/Web3Provider'; +import { WalletConnectClientProvider } from 'context/WalletConnectContext'; console.log(`v-0.0.9`); @@ -31,14 +32,16 @@ const gqlClient = new GQLClient({ gqlEndpoint }); root.render( - - - - - - - - + + + + + + + + + + , ); diff --git a/packages/frontend/src/utils/web3modal.ts b/packages/frontend/src/utils/web3modal.ts new file mode 100644 index 00000000..b89a6aff --- /dev/null +++ b/packages/frontend/src/utils/web3modal.ts @@ -0,0 +1,7 @@ +import { WalletConnectModal } from '@walletconnect/modal'; +import { VITE_WALLET_CONNECT_ID } from 'utils/constants'; + +export const walletConnectModal = new WalletConnectModal({ + projectId: VITE_WALLET_CONNECT_ID!, + chains:['cosmos:theta-testnet-001'] +});