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, 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 { 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 [address] = await web3Provider.eth.getAccounts(); const tx = { from: address, to: address, gasPrice: "20000000000", value: "0", }; const { transactionHash } = await web3Provider.eth.sendTransaction(tx); return { method: "eth_sendTransaction", address, valid: true, result: transactionHash, }; }; const testSignTransaction: () => Promise = async () => { if (!web3Provider) { throw new Error("web3Provider not connected"); } const [address] = await web3Provider.eth.getAccounts(); const tx = { from: address, to: address, gasPrice: "20000000000", value: "0", }; // The return signature here is `RLPEncodedTransaction` but it actually returns as string (?). const result = (await web3Provider.eth.signTransaction(tx)) as unknown as string; return { method: "eth_signTransaction", address, valid: true, result, }; }; 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.eth.getAccounts(); const signature = await web3Provider.eth.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.eth.getAccounts(); const signature = await web3Provider.eth.sign(hexMsg, address); 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"); } if (!web3Provider.currentProvider) { throw new Error("web3Provider.currentProvider is not set"); } 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.eth.getAccounts(); // eth_signTypedData params const params = [address, message]; // FIXME: // Property 'request' does not exist on type 'string | HttpProvider | IpcProvider | WebsocketProvider | AbstractProvider'. // Property 'request' does not exist on type 'string'.ts(2339) // @ts-expect-error const signature = await web3Provider.currentProvider.request({ method: "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()} ); }