import React, { useState } from "react"; import { version } from "@walletconnect/client/package.json"; import * as encoding from "@walletconnect/encoding"; import Banner from "./components/Banner"; import Blockchain from "./components/Blockchain"; import Column from "./components/Column"; import Header from "./components/Header"; import Modal from "./components/Modal"; import { DEFAULT_MAIN_CHAINS, DEFAULT_TEST_CHAINS } from "./constants"; import { AccountAction, formatTestTransaction, getLocalStorageTestnetFlag, setLocaleStorageTestnetFlag, } from "./helpers"; import Toggle from "./components/Toggle"; import RequestModal from "./modals/RequestModal"; import PingModal from "./modals/PingModal"; import { SAccounts, SAccountsContainer, SButtonContainer, SContent, SLanding, SLayout, SToggleContainer, } from "./components/app"; import { useWalletConnectClient } from "./contexts/ClientContext"; import { BigNumber, utils } from "ethers"; interface IFormattedRpcResponse { method: string; address: string; valid: boolean; result: string; } export default function App() { const [isTestnet, setIsTestnet] = useState(getLocalStorageTestnetFlag()); const [isRpcRequestPending, setIsRpcRequestPending] = useState(false); const [rpcResult, setRpcResult] = useState(); const [modal, setModal] = useState(""); const closeModal = () => setModal(""); const openPingModal = () => setModal("ping"); const openRequestModal = () => setModal("request"); // Initialize the WalletConnect client. const { client, session, disconnect, chain, accounts, balances, chainData, isFetchingBalances, isInitializing, onEnable, web3Provider, } = useWalletConnectClient(); const ping = async () => { if (typeof client === "undefined") { throw new Error("WalletConnect Client is not initialized"); } try { setIsRpcRequestPending(true); const _session = await client.session.get(client.session.topics[0]); await client.session.ping(_session.topic); setRpcResult({ address: "", method: "ping", valid: true, result: "success", }); } catch (error) { console.error("RPC request failed:", error); } finally { setIsRpcRequestPending(false); } }; const onPing = async () => { openPingModal(); await ping(); }; const testSendTransaction: () => Promise = async () => { if (!web3Provider) { throw new Error("web3Provider not connected"); } const { chainId } = await web3Provider.getNetwork(); const [address] = await web3Provider.listAccounts(); const balance = await web3Provider.getBalance(address); const tx = await formatTestTransaction("eip155:" + chainId + ":" + address); if (balance.lt(BigNumber.from(tx.gasPrice).mul(tx.gasLimit))) { return { method: "eth_sendTransaction", address, valid: false, result: "Insufficient funds for intrinsic transaction cost", }; } const txHash = await web3Provider.send("eth_sendTransaction", [tx]); return { method: "eth_sendTransaction", address, valid: true, result: txHash, }; }; const testSignTransaction: () => Promise = async () => { if (!web3Provider) { throw new Error("web3Provider not connected"); } const { chainId } = await web3Provider.getNetwork(); const [address] = await web3Provider.listAccounts(); const tx = await formatTestTransaction("eip155:" + chainId + ":" + address); const signature = await web3Provider.send("eth_signTransaction", [tx]); return { method: "eth_signTransaction", address, valid: true, result: signature, }; }; const testSignMessage: () => Promise = async () => { if (!web3Provider) { throw new Error("web3Provider not connected"); } const msg = "hello world"; const hexMsg = encoding.utf8ToHex(msg, true); const [address] = await web3Provider.listAccounts(); const signature = await web3Provider.send("personal_sign", [hexMsg, address]); const valid = utils.verifyMessage(msg, signature) === address; return { method: "personal_sign", address, valid, result: signature, }; }; const testEthSign: () => Promise = async () => { if (!web3Provider) { throw new Error("web3Provider not connected"); } const msg = "hello world"; const hexMsg = encoding.utf8ToHex(msg, true); const [address] = await web3Provider.listAccounts(); const signature = await web3Provider.send("eth_sign", [address, hexMsg]); const valid = utils.verifyMessage(msg, signature) === address; return { method: "eth_sign (standard)", address, valid, result: signature, }; }; const testSignTypedData: () => Promise = async () => { if (!web3Provider) { throw new Error("web3Provider not connected"); } const typedData = { types: { Person: [ { name: "name", type: "string" }, { name: "wallet", type: "address" }, ], Mail: [ { name: "from", type: "Person" }, { name: "to", type: "Person" }, { name: "contents", type: "string" }, ], }, primaryType: "Mail", domain: { name: "Ether Mail", version: "1", chainId: 1, verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", }, message: { from: { name: "Cow", wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", }, to: { name: "Bob", wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", }, contents: "Hello, Bob!", }, }; const message = JSON.stringify(typedData); const [address] = await web3Provider.listAccounts(); // eth_signTypedData params const params = [address, message]; // send message const signature = await web3Provider.send("eth_signTypedData", params); const valid = utils.verifyTypedData(typedData.domain, typedData.types, typedData.message, signature) === address; return { method: "eth_signTypedData", address, valid, result: signature, }; }; const getEthereumActions = (): AccountAction[] => { const wrapRpcRequest = (rpcRequest: () => Promise) => async () => { openRequestModal(); try { setIsRpcRequestPending(true); const result = await rpcRequest(); setRpcResult(result); } catch (error) { console.error("RPC request failed:", error); } finally { setIsRpcRequestPending(false); } }; return [ { method: "eth_sendTransaction", callback: wrapRpcRequest(testSendTransaction) }, { method: "eth_signTransaction", callback: wrapRpcRequest(testSignTransaction) }, { method: "personal_sign", callback: wrapRpcRequest(testSignMessage) }, { method: "eth_sign (standard)", callback: wrapRpcRequest(testEthSign) }, { method: "eth_signTypedData", callback: wrapRpcRequest(testSignTypedData) }, ]; }; const getBlockchainActions = (chainId: string) => { const [namespace] = chainId.split(":"); switch (namespace) { case "eip155": return getEthereumActions(); case "cosmos": return []; default: break; } }; // Toggle between displaying testnet or mainnet chains as selection options. const toggleTestnets = () => { const nextIsTestnetState = !isTestnet; setIsTestnet(nextIsTestnetState); setLocaleStorageTestnetFlag(nextIsTestnetState); }; // Renders the appropriate model for the given request that is currently in-flight. const renderModal = () => { switch (modal) { case "request": return ; case "ping": return ; default: return null; } }; const renderContent = () => { const chainOptions = isTestnet ? DEFAULT_TEST_CHAINS : DEFAULT_MAIN_CHAINS; return !accounts.length && !Object.keys(balances).length ? (
{`Using v${version || "2.0.0-beta"}`}
Select an Ethereum chain:

Testnets Only?

{chainOptions.map(chainId => ( ))}
) : (

Account

{accounts.map(account => { return ( ); })}
); }; return (
{isInitializing ? "Loading..." : renderContent()} {renderModal()} ); }