refactor: sets up ClientContext
This commit is contained in:
parent
f819161934
commit
0732b09991
@ -1,8 +1,7 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import Client, { CLIENT_EVENTS } from "@walletconnect/client";
|
|
||||||
import QRCodeModal from "@walletconnect/legacy-modal";
|
import QRCodeModal from "@walletconnect/legacy-modal";
|
||||||
import { PairingTypes, SessionTypes } from "@walletconnect/types";
|
import { SessionTypes } from "@walletconnect/types";
|
||||||
import { ERROR, getAppMetadata } from "@walletconnect/utils";
|
import { ERROR, getAppMetadata } from "@walletconnect/utils";
|
||||||
import * as encoding from "@walletconnect/encoding";
|
import * as encoding from "@walletconnect/encoding";
|
||||||
import { apiGetChainNamespace, ChainsMap } from "caip-api";
|
import { apiGetChainNamespace, ChainsMap } from "caip-api";
|
||||||
@ -17,11 +16,8 @@ import Modal from "./components/Modal";
|
|||||||
import {
|
import {
|
||||||
DEFAULT_APP_METADATA,
|
DEFAULT_APP_METADATA,
|
||||||
DEFAULT_MAIN_CHAINS,
|
DEFAULT_MAIN_CHAINS,
|
||||||
DEFAULT_LOGGER,
|
|
||||||
DEFAULT_EIP155_METHODS,
|
DEFAULT_EIP155_METHODS,
|
||||||
DEFAULT_COSMOS_METHODS,
|
DEFAULT_COSMOS_METHODS,
|
||||||
DEFAULT_PROJECT_ID,
|
|
||||||
DEFAULT_RELAY_URL,
|
|
||||||
DEFAULT_TEST_CHAINS,
|
DEFAULT_TEST_CHAINS,
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
import {
|
import {
|
||||||
@ -52,6 +48,7 @@ import {
|
|||||||
SLayout,
|
SLayout,
|
||||||
SToggleContainer,
|
SToggleContainer,
|
||||||
} from "./components/app";
|
} from "./components/app";
|
||||||
|
import { useWalletConnectClient } from "./contexts/ClientContext";
|
||||||
|
|
||||||
interface FormattedRpcResponse {
|
interface FormattedRpcResponse {
|
||||||
method: string;
|
method: string;
|
||||||
@ -61,105 +58,59 @@ interface FormattedRpcResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [pending, setPending] = useState(false);
|
const [pending, setPending] = useState(false);
|
||||||
const [fetching, setFetching] = useState(false);
|
|
||||||
const [isTestnet, setIsTestnet] = useState(getInitialStateTestnet());
|
const [isTestnet, setIsTestnet] = useState(getInitialStateTestnet());
|
||||||
|
|
||||||
const [modal, setModal] = useState("");
|
const [modal, setModal] = useState("");
|
||||||
const [client, setClient] = useState<Client>();
|
|
||||||
const [session, setSession] = useState<SessionTypes.Created>();
|
|
||||||
const [accounts, setAccounts] = useState<string[]>([]);
|
|
||||||
const [pairings, setPairings] = useState<string[]>([]);
|
|
||||||
const [result, setResult] = useState<{
|
const [result, setResult] = useState<{
|
||||||
method: string;
|
method: string;
|
||||||
valid: boolean;
|
valid: boolean;
|
||||||
} | null>();
|
} | null>();
|
||||||
const [balances, setBalances] = useState<AccountBalances>({});
|
|
||||||
const [chainData, setChainData] = useState<ChainNamespaces>({});
|
const [chainData, setChainData] = useState<ChainNamespaces>({});
|
||||||
const [chains, setChains] = useState<string[]>([]);
|
|
||||||
|
|
||||||
const closeModal = () => setModal("");
|
const closeModal = () => setModal("");
|
||||||
const openPairingModal = () => setModal("pairing");
|
const openPairingModal = () => setModal("pairing");
|
||||||
const openPingModal = () => setModal("ping");
|
const openPingModal = () => setModal("ping");
|
||||||
const openRequestModal = () => setModal("request");
|
const openRequestModal = () => setModal("request");
|
||||||
|
|
||||||
const init = async () => {
|
const {
|
||||||
try {
|
client,
|
||||||
setLoading(true);
|
session,
|
||||||
await loadChainData();
|
chains,
|
||||||
|
accounts,
|
||||||
|
balances,
|
||||||
|
fetching,
|
||||||
|
loading,
|
||||||
|
setSession,
|
||||||
|
setPairings,
|
||||||
|
setAccounts,
|
||||||
|
setChains,
|
||||||
|
setFetching,
|
||||||
|
setBalances,
|
||||||
|
} = useWalletConnectClient();
|
||||||
|
|
||||||
const _client = await Client.init({
|
console.log({
|
||||||
logger: DEFAULT_LOGGER,
|
client,
|
||||||
relayUrl: DEFAULT_RELAY_URL,
|
session,
|
||||||
projectId: DEFAULT_PROJECT_ID,
|
chains,
|
||||||
|
accounts,
|
||||||
|
balances,
|
||||||
|
fetching,
|
||||||
|
loading,
|
||||||
|
setSession,
|
||||||
|
setPairings,
|
||||||
|
setAccounts,
|
||||||
|
setChains,
|
||||||
|
setFetching,
|
||||||
|
setBalances,
|
||||||
});
|
});
|
||||||
setClient(_client);
|
|
||||||
await subscribeToEvents(_client);
|
|
||||||
await checkPersistedState(_client);
|
|
||||||
} catch (err) {
|
|
||||||
throw err;
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
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 }) => {
|
const connect = async (pairing?: { topic: string }) => {
|
||||||
if (typeof client === "undefined") {
|
if (typeof client === "undefined") {
|
||||||
throw new Error("WalletConnect is not initialized");
|
throw new Error("WalletConnect is not initialized");
|
||||||
|
188
dapps/react-dapp-v2/src/contexts/ClientContext.tsx
Normal file
188
dapps/react-dapp-v2/src/contexts/ClientContext.tsx
Normal file
@ -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<IContext>({} as IContext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider
|
||||||
|
*/
|
||||||
|
export function ClientContextProvider({ children }: { children: ReactNode | ReactNode[] }) {
|
||||||
|
const [client, setClient] = useState<Client>();
|
||||||
|
const [pairings, setPairings] = useState<string[]>([]);
|
||||||
|
const [session, setSession] = useState<SessionTypes.Created>();
|
||||||
|
|
||||||
|
// UI State
|
||||||
|
const [fetching, setFetching] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
// Other entities
|
||||||
|
const [balances, setBalances] = useState<AccountBalances>({});
|
||||||
|
const [accounts, setAccounts] = useState<string[]>([]);
|
||||||
|
const [chains, setChains] = useState<string[]>([]);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<ClientContext.Provider
|
||||||
|
value={{
|
||||||
|
pairings,
|
||||||
|
fetching,
|
||||||
|
loading,
|
||||||
|
balances,
|
||||||
|
accounts,
|
||||||
|
chains,
|
||||||
|
client,
|
||||||
|
session,
|
||||||
|
setSession,
|
||||||
|
setPairings,
|
||||||
|
setAccounts,
|
||||||
|
setChains,
|
||||||
|
setFetching,
|
||||||
|
setBalances,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ClientContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useWalletConnectClient() {
|
||||||
|
const context = useContext(ClientContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error("useWalletConnectClient must be used within a ClientContextProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
import { createGlobalStyle } from "styled-components";
|
import { createGlobalStyle } from "styled-components";
|
||||||
|
import { ClientContextProvider } from "./contexts/ClientContext";
|
||||||
|
|
||||||
import HooksApp from "./HooksApp";
|
import HooksApp from "./HooksApp";
|
||||||
import { globalStyle } from "./styles";
|
import { globalStyle } from "./styles";
|
||||||
@ -18,7 +19,9 @@ declare global {
|
|||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<>
|
<>
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
|
<ClientContextProvider>
|
||||||
<HooksApp />
|
<HooksApp />
|
||||||
|
</ClientContextProvider>
|
||||||
</>,
|
</>,
|
||||||
document.getElementById("root"),
|
document.getElementById("root"),
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user