From 0732b09991889b30b541a120353c8148bdff40ce Mon Sep 17 00:00:00 2001 From: Ben Kremer Date: Fri, 4 Feb 2022 16:02:24 +0100 Subject: [PATCH] refactor: sets up ClientContext --- dapps/react-dapp-v2/src/HooksApp.tsx | 119 ++++------- .../src/contexts/ClientContext.tsx | 188 ++++++++++++++++++ dapps/react-dapp-v2/src/index.tsx | 5 +- 3 files changed, 227 insertions(+), 85 deletions(-) create mode 100644 dapps/react-dapp-v2/src/contexts/ClientContext.tsx diff --git a/dapps/react-dapp-v2/src/HooksApp.tsx b/dapps/react-dapp-v2/src/HooksApp.tsx index 2e08626..8eb2645 100644 --- a/dapps/react-dapp-v2/src/HooksApp.tsx +++ b/dapps/react-dapp-v2/src/HooksApp.tsx @@ -1,8 +1,7 @@ import React, { useEffect, useState } from "react"; -import Client, { CLIENT_EVENTS } from "@walletconnect/client"; import QRCodeModal from "@walletconnect/legacy-modal"; -import { PairingTypes, SessionTypes } from "@walletconnect/types"; +import { SessionTypes } from "@walletconnect/types"; import { ERROR, getAppMetadata } from "@walletconnect/utils"; import * as encoding from "@walletconnect/encoding"; import { apiGetChainNamespace, ChainsMap } from "caip-api"; @@ -17,11 +16,8 @@ import Modal from "./components/Modal"; import { DEFAULT_APP_METADATA, DEFAULT_MAIN_CHAINS, - DEFAULT_LOGGER, DEFAULT_EIP155_METHODS, DEFAULT_COSMOS_METHODS, - DEFAULT_PROJECT_ID, - DEFAULT_RELAY_URL, DEFAULT_TEST_CHAINS, } from "./constants"; import { @@ -52,6 +48,7 @@ import { SLayout, SToggleContainer, } from "./components/app"; +import { useWalletConnectClient } from "./contexts/ClientContext"; interface FormattedRpcResponse { method: string; @@ -61,105 +58,59 @@ interface FormattedRpcResponse { } export default function App() { - const [loading, setLoading] = useState(false); const [pending, setPending] = useState(false); - const [fetching, setFetching] = useState(false); const [isTestnet, setIsTestnet] = useState(getInitialStateTestnet()); const [modal, setModal] = useState(""); - const [client, setClient] = useState(); - const [session, setSession] = useState(); - const [accounts, setAccounts] = useState([]); - const [pairings, setPairings] = useState([]); + const [result, setResult] = useState<{ method: string; valid: boolean; } | null>(); - const [balances, setBalances] = useState({}); + const [chainData, setChainData] = useState({}); - const [chains, setChains] = useState([]); const closeModal = () => setModal(""); const openPairingModal = () => setModal("pairing"); const openPingModal = () => setModal("ping"); const openRequestModal = () => setModal("request"); - const init = async () => { - try { - setLoading(true); - await loadChainData(); + const { + client, + session, + chains, + accounts, + balances, + fetching, + loading, + setSession, + setPairings, + setAccounts, + setChains, + setFetching, + setBalances, + } = useWalletConnectClient(); - const _client = await Client.init({ - logger: DEFAULT_LOGGER, - relayUrl: DEFAULT_RELAY_URL, - projectId: DEFAULT_PROJECT_ID, - }); - setClient(_client); - await subscribeToEvents(_client); - await checkPersistedState(_client); - } catch (err) { - throw err; - } finally { - setLoading(false); - } - }; + console.log({ + client, + session, + chains, + accounts, + balances, + fetching, + loading, + setSession, + setPairings, + setAccounts, + setChains, + setFetching, + setBalances, + }); useEffect(() => { - init(); + loadChainData(); }, []); - const subscribeToEvents = async (_client: Client) => { - if (typeof _client === "undefined") { - throw new Error("WalletConnect is not initialized"); - } - - let _session = {} as SessionTypes.Settled; - - if (_client.session.topics.length) { - _session = await _client.session.get(_client.session.topics[0]); - } - - _client.on(CLIENT_EVENTS.pairing.proposal, async (proposal: PairingTypes.Proposal) => { - const { uri } = proposal.signal.params; - console.log("EVENT", "QR Code Modal open"); - QRCodeModal.open(uri, () => { - console.log("EVENT", "QR Code Modal closed"); - }); - }); - - _client.on(CLIENT_EVENTS.pairing.created, async (proposal: PairingTypes.Settled) => { - if (typeof client === "undefined") return; - setPairings(client.pairing.topics); - }); - - _client.on(CLIENT_EVENTS.session.deleted, (deletedSession: SessionTypes.Settled) => { - if (deletedSession.topic !== _session?.topic) return; - console.log("EVENT", "session_deleted"); - // TODO: - // this.resetApp(); - window.location.reload(); - }); - }; - - const checkPersistedState = async (_client: Client) => { - if (typeof _client === "undefined") { - throw new Error("WalletConnect is not initialized"); - } - // populates existing pairings to state - setPairings(_client.pairing.topics); - if (typeof session !== "undefined") return; - // populates existing session to state (assume only the top one) - if (_client.session.topics.length) { - const session = await _client.session.get(_client.session.topics[0]); - const chains = session.state.accounts.map(account => - account.split(":").slice(0, -1).join(":"), - ); - setAccounts(session.state.accounts); - setChains(chains); - onSessionConnected(session); - } - }; - const connect = async (pairing?: { topic: string }) => { if (typeof client === "undefined") { throw new Error("WalletConnect is not initialized"); diff --git a/dapps/react-dapp-v2/src/contexts/ClientContext.tsx b/dapps/react-dapp-v2/src/contexts/ClientContext.tsx new file mode 100644 index 0000000..097932e --- /dev/null +++ b/dapps/react-dapp-v2/src/contexts/ClientContext.tsx @@ -0,0 +1,188 @@ +import Client, { CLIENT_EVENTS } from "@walletconnect/client"; +import { PairingTypes, SessionTypes } from "@walletconnect/types"; +import QRCodeModal from "@walletconnect/legacy-modal"; +import { createContext, ReactNode, useContext, useEffect, useState } from "react"; +import { DEFAULT_LOGGER, DEFAULT_PROJECT_ID, DEFAULT_RELAY_URL } from "../constants"; +import { AccountBalances, apiGetAccountAssets } from "../helpers"; + +/** + * Types + */ +interface IContext { + client: Client | undefined; + session: SessionTypes.Created | undefined; + loading: boolean; + fetching: boolean; + chains: string[]; + pairings: string[]; + accounts: string[]; + balances: AccountBalances; + setSession: any; + setPairings: any; + setAccounts: any; + setChains: any; + setFetching: any; + setBalances: any; +} + +/** + * Context + */ +export const ClientContext = createContext({} as IContext); + +/** + * Provider + */ +export function ClientContextProvider({ children }: { children: ReactNode | ReactNode[] }) { + const [client, setClient] = useState(); + const [pairings, setPairings] = useState([]); + const [session, setSession] = useState(); + + // UI State + const [fetching, setFetching] = useState(false); + const [loading, setLoading] = useState(false); + + // Other entities + const [balances, setBalances] = useState({}); + const [accounts, setAccounts] = useState([]); + const [chains, setChains] = useState([]); + + const subscribeToEvents = async (_client: Client) => { + if (typeof _client === "undefined") { + throw new Error("WalletConnect is not initialized"); + } + + let _session = {} as SessionTypes.Settled; + + if (_client.session.topics.length) { + _session = await _client.session.get(_client.session.topics[0]); + } + + _client.on(CLIENT_EVENTS.pairing.proposal, async (proposal: PairingTypes.Proposal) => { + const { uri } = proposal.signal.params; + console.log("EVENT", "QR Code Modal open"); + QRCodeModal.open(uri, () => { + console.log("EVENT", "QR Code Modal closed"); + }); + }); + + _client.on(CLIENT_EVENTS.pairing.created, async (proposal: PairingTypes.Settled) => { + if (typeof client === "undefined") return; + setPairings(client.pairing.topics); + }); + + _client.on(CLIENT_EVENTS.session.deleted, (deletedSession: SessionTypes.Settled) => { + if (deletedSession.topic !== _session?.topic) return; + console.log("EVENT", "session_deleted"); + // TODO: + // this.resetApp(); + window.location.reload(); + }); + }; + + const checkPersistedState = async (_client: Client) => { + if (typeof _client === "undefined") { + throw new Error("WalletConnect is not initialized"); + } + // populates existing pairings to state + setPairings(_client.pairing.topics); + if (typeof session !== "undefined") return; + // populates existing session to state (assume only the top one) + if (_client.session.topics.length) { + const session = await _client.session.get(_client.session.topics[0]); + const chains = session.state.accounts.map(account => + account.split(":").slice(0, -1).join(":"), + ); + setAccounts(session.state.accounts); + setChains(chains); + onSessionConnected(session); + } + }; + + const onSessionConnected = async (incomingSession: SessionTypes.Settled) => { + setSession(incomingSession); + onSessionUpdate(incomingSession.state.accounts, incomingSession.permissions.blockchain.chains); + }; + + const onSessionUpdate = async (accounts: string[], chains: string[]) => { + setChains(chains); + setAccounts(accounts); + await getAccountBalances(accounts); + }; + + const getAccountBalances = async (_accounts: string[]) => { + setFetching(true); + try { + const arr = await Promise.all( + _accounts.map(async account => { + const [namespace, reference, address] = account.split(":"); + const chainId = `${namespace}:${reference}`; + const assets = await apiGetAccountAssets(address, chainId); + return { account, assets }; + }), + ); + + const balances: AccountBalances = {}; + arr.forEach(({ account, assets }) => { + balances[account] = assets; + }); + setBalances(balances); + } catch (e) { + console.error(e); + } finally { + setFetching(false); + } + }; + + useEffect(() => { + const init = async () => { + try { + const _client = await Client.init({ + logger: DEFAULT_LOGGER, + relayUrl: DEFAULT_RELAY_URL, + projectId: DEFAULT_PROJECT_ID, + }); + setClient(_client); + await subscribeToEvents(_client); + await checkPersistedState(_client); + } catch (err) { + throw err; + } finally { + setLoading(false); + } + }; + + init(); + }, []); + + return ( + + {children} + + ); +} + +export function useWalletConnectClient() { + const context = useContext(ClientContext); + if (context === undefined) { + throw new Error("useWalletConnectClient must be used within a ClientContextProvider"); + } + return context; +} diff --git a/dapps/react-dapp-v2/src/index.tsx b/dapps/react-dapp-v2/src/index.tsx index f09c3f8..535ba1b 100644 --- a/dapps/react-dapp-v2/src/index.tsx +++ b/dapps/react-dapp-v2/src/index.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; import { createGlobalStyle } from "styled-components"; +import { ClientContextProvider } from "./contexts/ClientContext"; import HooksApp from "./HooksApp"; import { globalStyle } from "./styles"; @@ -18,7 +19,9 @@ declare global { ReactDOM.render( <> - + + + , document.getElementById("root"), );