From fb3f8561c90175dcf261baa75b130efdb0556600 Mon Sep 17 00:00:00 2001 From: Apoorv Lathey Date: Wed, 21 Sep 2022 03:10:30 +0530 Subject: [PATCH] add iframe support for gnosis safe provider (#8) --- .env.sample | 1 + src/components/Body/Tab.tsx | 29 ++ src/components/Body/index.tsx | 368 +++++++++++++++------ src/components/Body/networkInfo.ts | 16 +- src/contexts/SafeInjectContext.tsx | 171 ++++++++++ src/helpers/communicator.ts | 120 +++++++ src/helpers/messageFormatter.ts | 51 +++ src/helpers/utils.ts | 20 ++ src/index.tsx | 5 +- src/types.ts | 515 +++++++++++++++++++++++++++++ 10 files changed, 1201 insertions(+), 95 deletions(-) create mode 100644 .env.sample create mode 100644 src/components/Body/Tab.tsx create mode 100644 src/contexts/SafeInjectContext.tsx create mode 100644 src/helpers/communicator.ts create mode 100644 src/helpers/messageFormatter.ts create mode 100644 src/helpers/utils.ts create mode 100644 src/types.ts diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..61aa907 --- /dev/null +++ b/.env.sample @@ -0,0 +1 @@ +REACT_APP_INFURA_KEY= \ No newline at end of file diff --git a/src/components/Body/Tab.tsx b/src/components/Body/Tab.tsx new file mode 100644 index 0000000..182d25b --- /dev/null +++ b/src/components/Body/Tab.tsx @@ -0,0 +1,29 @@ +import { Box } from "@chakra-ui/react"; + +const Tab = ({ + children, + tabIndex, + selectedTabIndex, + setSelectedTabIndex, +}: { + children: React.ReactNode; + tabIndex: number; + selectedTabIndex: number; + setSelectedTabIndex: Function; +}) => { + return ( + setSelectedTabIndex(tabIndex)} + > + {children} + + ); +}; + +export default Tab; diff --git a/src/components/Body/index.tsx b/src/components/Body/index.tsx index 5d5d133..b6a63b3 100644 --- a/src/components/Body/index.tsx +++ b/src/components/Body/index.tsx @@ -48,6 +48,8 @@ import WalletConnect from "@walletconnect/client"; import { IClientMeta } from "@walletconnect/types"; import { ethers } from "ethers"; import axios from "axios"; +import { useSafeInject } from "../../contexts/SafeInjectContext"; +import Tab from "./Tab"; import networkInfo from "./networkInfo"; const slicedText = (txt: string) => { @@ -81,11 +83,22 @@ const TD = ({ txt }: { txt: string }) => ( function Body() { const { colorMode } = useColorMode(); const bgColor = { light: "white", dark: "gray.700" }; - const addressFromURL = new URLSearchParams(window.location.search).get("address"); + const addressFromURL = new URLSearchParams(window.location.search).get( + "address" + ); const toast = useToast(); const { onOpen, onClose, isOpen } = useDisclosure(); const { isOpen: tableIsOpen, onToggle: tableOnToggle } = useDisclosure(); + const { + setAddress: setIFrameAddress, + appUrl, + setAppUrl, + setRpcUrl, + iframeRef, + latestTransaction, + } = useSafeInject(); + const [provider, setProvider] = useState(); const [showAddress, setShowAddress] = useState(addressFromURL ?? ""); // gets displayed in input. ENS name remains as it is const [address, setAddress] = useState(addressFromURL ?? ""); // internal resolved address @@ -97,6 +110,11 @@ function Body() { const [isConnected, setIsConnected] = useState(false); const [loading, setLoading] = useState(false); + const [selectedTabIndex, setSelectedTabIndex] = useState(0); + const [isIFrameLoading, setIsIFrameLoading] = useState(false); + const [inputAppUrl, setInputAppUrl] = useState(); + const [iframeKey, setIframeKey] = useState(0); // hacky way to reload iframe when key changes + const [tenderlyForkId, setTenderlyForkId] = useState(""); const [sendTxnData, setSendTxnData] = useState< { @@ -121,8 +139,9 @@ function Body() { setUri(_connector.uri); setPeerMeta(_connector.peerMeta); setIsConnected(true); - const chainId = (_connector.chainId as unknown as { chainID: number }) - .chainID || _connector.chainId; + const chainId = + (_connector.chainId as unknown as { chainID: number }).chainID || + _connector.chainId; for (let i = 0; i < networkInfo.length; i++) { if (getChainId(i) === chainId) { @@ -138,7 +157,9 @@ function Body() { } setProvider( - new ethers.providers.JsonRpcProvider(process.env.REACT_APP_PROVIDER_URL) + new ethers.providers.JsonRpcProvider( + `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}` + ) ); const storedTenderlyForkId = localStorage.getItem("tenderlyForkId"); @@ -160,6 +181,52 @@ function Body() { localStorage.setItem("showAddress", showAddress); }, [showAddress]); + useEffect(() => { + setIFrameAddress(address); + }, [address]); + + useEffect(() => { + setRpcUrl(networkInfo[networkIndex].rpc); + }, [networkIndex]); + + useEffect(() => { + if (latestTransaction) { + const newTxn = { + from: address, + ...latestTransaction, + }; + + setSendTxnData((data) => { + if (data.some((d) => d.id === newTxn.id)) { + return data; + } else { + return [ + { ...newTxn, value: parseInt(newTxn.value, 16).toString() }, + ...data, + ]; + } + }); + + if (tenderlyForkId.length > 0) { + axios + .post("https://rpc.tenderly.co/fork/" + tenderlyForkId, { + jsonrpc: "2.0", + id: newTxn.id, + method: "eth_sendTransaction", + params: [ + { + from: newTxn.from, + to: newTxn.to, + value: newTxn.value, + data: newTxn.data, + }, + ], + }) + .then((res) => console.log(res.data)); + } + } + }, [latestTransaction, tenderlyForkId]); + const resolveAndValidateAddress = async () => { let isValid; let _address = address; @@ -244,6 +311,22 @@ function Body() { } }; + const initIFrame = async () => { + setIsIFrameLoading(true); + if (inputAppUrl === appUrl) { + setIsIFrameLoading(false); + return; + } + + const { isValid } = await resolveAndValidateAddress(); + if (!isValid) { + setIsIFrameLoading(false); + return; + } + + setAppUrl(inputAppUrl); + }; + const subscribeToEvents = () => { console.log("ACTION", "subscribeToEvents"); @@ -392,13 +475,35 @@ function Body() { }; const updateAddress = async () => { - setLoading(true); + if (selectedTabIndex === 0) { + setLoading(true); + } else { + setIsIFrameLoading(true); + } const { isValid, _address } = await resolveAndValidateAddress(); if (isValid) { + if (selectedTabIndex === 0) { + updateSession({ + newAddress: _address, + }); + } else { + setIFrameAddress(_address); + setIframeKey((key) => key + 1); + setIsIFrameLoading(false); + } + } + }; + + const updateNetwork = (_networkIndex: number) => { + setNetworkIndex(_networkIndex); + + if (selectedTabIndex === 0) { updateSession({ - newAddress: _address, + newChainId: getChainId(_networkIndex), }); + } else { + setIframeKey((key) => key + 1); } }; @@ -482,8 +587,7 @@ function Body() { Enter Address or ENS to Impersonate { @@ -495,7 +599,8 @@ function Body() { bg={bgColor[colorMode]} isInvalid={!isAddressValid} /> - {isConnected && ( + {((selectedTabIndex === 0 && isConnected) || + (selectedTabIndex === 1 && appUrl)) && ( - {loading && ( -
- - - - - {!isConnected && ( - - - - )} - -
- )} - {peerMeta && ( +
+ + {["WalletConnect", "IFrame"].map((t, i) => ( + + {t} + + ))} + +
+ {selectedTabIndex === 0 ? ( <> - - {isConnected ? "✅ Connected To:" : "⚠ Allow to Connect"} - - - - {peerMeta.name} - {peerMeta.description} - - {peerMeta.url} - - {!isConnected && ( - - - + + + WalletConnect URI + + Visit any dApp and select WalletConnect. + + Click "Copy to Clipboard" beneath the QR code, and paste + it here. + + + } + hasArrow + placement="top" + > + + + + + + setUri(e.target.value)} + bg={bgColor[colorMode]} + isDisabled={isConnected} + /> + +
+ +
+ {loading && ( +
+ + + + + {!isConnected && ( + + + + )} + +
+ )} + {peerMeta && ( + <> + + {isConnected ? "✅ Connected To:" : "⚠ Allow to Connect"} + + + {peerMeta.name} + {peerMeta.description} + + {peerMeta.url} + + {!isConnected && ( + + + + + )} + {isConnected && ( + + + + )} + + + )} + + ) : ( + <> + + + dapp URL + + Paste the URL of dapp you want to connect to + + Note: Some dapps might not support it, so use + WalletConnect in that case + + + } + hasArrow + placement="top" + > + + + + + + setInputAppUrl(e.target.value)} + bg={bgColor[colorMode]} + /> + +
+ +
+
+ {appUrl && ( +