add iframe support for gnosis safe provider (#8)
This commit is contained in:
29
src/components/Body/Tab.tsx
Normal file
29
src/components/Body/Tab.tsx
Normal file
@@ -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 (
|
||||
<Box
|
||||
fontWeight={"semibold"}
|
||||
color={tabIndex === selectedTabIndex ? "white" : "whiteAlpha.700"}
|
||||
_hover={{
|
||||
color: "whiteAlpha.900",
|
||||
}}
|
||||
cursor="pointer"
|
||||
onClick={() => setSelectedTabIndex(tabIndex)}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tab;
|
||||
@@ -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<ethers.providers.JsonRpcProvider>();
|
||||
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<string>();
|
||||
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() {
|
||||
<FormLabel>Enter Address or ENS to Impersonate</FormLabel>
|
||||
<InputGroup>
|
||||
<Input
|
||||
placeholder="Address"
|
||||
aria-label="address"
|
||||
placeholder="vitalik.eth"
|
||||
autoComplete="off"
|
||||
value={showAddress}
|
||||
onChange={(e) => {
|
||||
@@ -495,7 +599,8 @@ function Body() {
|
||||
bg={bgColor[colorMode]}
|
||||
isInvalid={!isAddressValid}
|
||||
/>
|
||||
{isConnected && (
|
||||
{((selectedTabIndex === 0 && isConnected) ||
|
||||
(selectedTabIndex === 1 && appUrl)) && (
|
||||
<InputRightElement width="4.5rem" mr="1rem">
|
||||
<Button h="1.75rem" size="sm" onClick={updateAddress}>
|
||||
Update
|
||||
@@ -504,49 +609,15 @@ function Body() {
|
||||
)}
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
<FormControl my={4}>
|
||||
<HStack>
|
||||
<FormLabel>WalletConnect URI</FormLabel>
|
||||
<Tooltip
|
||||
label={
|
||||
<>
|
||||
<Text>Visit any dApp and select WalletConnect.</Text>
|
||||
<Text>
|
||||
Click "Copy to Clipboard" beneath the QR code, and paste it
|
||||
here.
|
||||
</Text>
|
||||
</>
|
||||
}
|
||||
hasArrow
|
||||
placement="top"
|
||||
>
|
||||
<Box pb="0.8rem">
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
<Input
|
||||
placeholder="wc:xyz123"
|
||||
aria-label="uri"
|
||||
autoComplete="off"
|
||||
value={uri}
|
||||
onChange={(e) => setUri(e.target.value)}
|
||||
bg={bgColor[colorMode]}
|
||||
isDisabled={isConnected}
|
||||
/>
|
||||
</FormControl>
|
||||
<Select
|
||||
mb={4}
|
||||
mt={4}
|
||||
placeholder="Select Network"
|
||||
variant="filled"
|
||||
_hover={{ cursor: "pointer" }}
|
||||
value={networkIndex}
|
||||
onChange={(e) => {
|
||||
const _networkIndex = parseInt(e.target.value);
|
||||
setNetworkIndex(_networkIndex);
|
||||
updateSession({
|
||||
newChainId: getChainId(_networkIndex),
|
||||
});
|
||||
updateNetwork(_networkIndex);
|
||||
}}
|
||||
>
|
||||
{networkInfo.map((network, i) => (
|
||||
@@ -555,56 +626,169 @@ function Body() {
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<Button onClick={initWalletConnect} isDisabled={isConnected}>
|
||||
Connect
|
||||
</Button>
|
||||
{loading && (
|
||||
<Center>
|
||||
<VStack>
|
||||
<Box>
|
||||
<CircularProgress isIndeterminate />
|
||||
</Box>
|
||||
{!isConnected && (
|
||||
<Box pt={6}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setLoading(false);
|
||||
reset();
|
||||
}}
|
||||
>
|
||||
Stop Loading ☠
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</VStack>
|
||||
</Center>
|
||||
)}
|
||||
{peerMeta && (
|
||||
<Center flexDir="column">
|
||||
<HStack
|
||||
mt="1rem"
|
||||
minH="3rem"
|
||||
px="1.5rem"
|
||||
spacing={"8"}
|
||||
background="gray.700"
|
||||
borderRadius="xl"
|
||||
>
|
||||
{["WalletConnect", "IFrame"].map((t, i) => (
|
||||
<Tab
|
||||
key={i}
|
||||
tabIndex={i}
|
||||
selectedTabIndex={selectedTabIndex}
|
||||
setSelectedTabIndex={setSelectedTabIndex}
|
||||
>
|
||||
{t}
|
||||
</Tab>
|
||||
))}
|
||||
</HStack>
|
||||
</Center>
|
||||
{selectedTabIndex === 0 ? (
|
||||
<>
|
||||
<Box mt={4} fontSize={24} fontWeight="semibold">
|
||||
{isConnected ? "✅ Connected To:" : "⚠ Allow to Connect"}
|
||||
</Box>
|
||||
<VStack>
|
||||
<Avatar src={peerMeta.icons[0]} alt={peerMeta.name} />
|
||||
<Text fontWeight="bold">{peerMeta.name}</Text>
|
||||
<Text fontSize="sm">{peerMeta.description}</Text>
|
||||
<Link href={peerMeta.url} textDecor="underline">
|
||||
{peerMeta.url}
|
||||
</Link>
|
||||
{!isConnected && (
|
||||
<Box pt={6}>
|
||||
<Button onClick={approveSession} mr={10}>
|
||||
Connect ✔
|
||||
</Button>
|
||||
<Button onClick={rejectSession}>Reject ❌</Button>
|
||||
<FormControl my={4}>
|
||||
<HStack>
|
||||
<FormLabel>WalletConnect URI</FormLabel>
|
||||
<Tooltip
|
||||
label={
|
||||
<>
|
||||
<Text>Visit any dApp and select WalletConnect.</Text>
|
||||
<Text>
|
||||
Click "Copy to Clipboard" beneath the QR code, and paste
|
||||
it here.
|
||||
</Text>
|
||||
</>
|
||||
}
|
||||
hasArrow
|
||||
placement="top"
|
||||
>
|
||||
<Box pb="0.8rem">
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
<Input
|
||||
placeholder="wc:xyz123"
|
||||
aria-label="uri"
|
||||
autoComplete="off"
|
||||
value={uri}
|
||||
onChange={(e) => setUri(e.target.value)}
|
||||
bg={bgColor[colorMode]}
|
||||
isDisabled={isConnected}
|
||||
/>
|
||||
</FormControl>
|
||||
<Center>
|
||||
<Button onClick={initWalletConnect} isDisabled={isConnected}>
|
||||
Connect
|
||||
</Button>
|
||||
</Center>
|
||||
{loading && (
|
||||
<Center>
|
||||
<VStack>
|
||||
<Box>
|
||||
<CircularProgress isIndeterminate />
|
||||
</Box>
|
||||
{!isConnected && (
|
||||
<Box pt={6}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setLoading(false);
|
||||
reset();
|
||||
}}
|
||||
>
|
||||
Stop Loading ☠
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</VStack>
|
||||
</Center>
|
||||
)}
|
||||
{peerMeta && (
|
||||
<>
|
||||
<Box mt={4} fontSize={24} fontWeight="semibold">
|
||||
{isConnected ? "✅ Connected To:" : "⚠ Allow to Connect"}
|
||||
</Box>
|
||||
<VStack>
|
||||
<Avatar src={peerMeta.icons[0]} alt={peerMeta.name} />
|
||||
<Text fontWeight="bold">{peerMeta.name}</Text>
|
||||
<Text fontSize="sm">{peerMeta.description}</Text>
|
||||
<Link href={peerMeta.url} textDecor="underline">
|
||||
{peerMeta.url}
|
||||
</Link>
|
||||
{!isConnected && (
|
||||
<Box pt={6}>
|
||||
<Button onClick={approveSession} mr={10}>
|
||||
Connect ✔
|
||||
</Button>
|
||||
<Button onClick={rejectSession}>Reject ❌</Button>
|
||||
</Box>
|
||||
)}
|
||||
{isConnected && (
|
||||
<Box pt={6}>
|
||||
<Button onClick={killSession}>Disconnect ☠</Button>
|
||||
</Box>
|
||||
)}
|
||||
</VStack>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FormControl my={4}>
|
||||
<HStack>
|
||||
<FormLabel>dapp URL</FormLabel>
|
||||
<Tooltip
|
||||
label={
|
||||
<>
|
||||
<Text>Paste the URL of dapp you want to connect to</Text>
|
||||
<Text>
|
||||
Note: Some dapps might not support it, so use
|
||||
WalletConnect in that case
|
||||
</Text>
|
||||
</>
|
||||
}
|
||||
hasArrow
|
||||
placement="top"
|
||||
>
|
||||
<Box pb="0.8rem">
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
<Input
|
||||
placeholder="https://app.uniswap.org/"
|
||||
aria-label="dapp-url"
|
||||
autoComplete="off"
|
||||
value={inputAppUrl}
|
||||
onChange={(e) => setInputAppUrl(e.target.value)}
|
||||
bg={bgColor[colorMode]}
|
||||
/>
|
||||
</FormControl>
|
||||
<Center>
|
||||
<Button onClick={() => initIFrame()} isLoading={isIFrameLoading}>
|
||||
Connect
|
||||
</Button>
|
||||
</Center>
|
||||
<Center mt="1rem" ml="-60" w="70rem">
|
||||
{appUrl && (
|
||||
<iframe
|
||||
title="app"
|
||||
src={appUrl}
|
||||
key={iframeKey}
|
||||
width="1500rem"
|
||||
height="600rem"
|
||||
style={{
|
||||
border: "1px solid white",
|
||||
background: "white",
|
||||
}}
|
||||
ref={iframeRef}
|
||||
onLoad={() => setIsIFrameLoading(false)}
|
||||
/>
|
||||
)}
|
||||
{isConnected && (
|
||||
<Box pt={6}>
|
||||
<Button onClick={killSession}>Disconnect ☠</Button>
|
||||
</Box>
|
||||
)}
|
||||
</VStack>
|
||||
</Center>
|
||||
</>
|
||||
)}
|
||||
<Center>
|
||||
|
||||
@@ -2,50 +2,62 @@ const networkInfo = [
|
||||
{
|
||||
chainID: 1,
|
||||
name: "Ethereum Mainnet",
|
||||
rpc: `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
|
||||
},
|
||||
{
|
||||
chainID: 42161,
|
||||
name: "Arbitrum One",
|
||||
rpc: "https://arb1.arbitrum.io/rpc",
|
||||
},
|
||||
{
|
||||
chainID: 10,
|
||||
name: "Optimistic Ethereum",
|
||||
name: "Optimism",
|
||||
rpc: "https://mainnet.optimism.io",
|
||||
},
|
||||
{
|
||||
chainID: 137,
|
||||
name: "Polygon",
|
||||
rpc: "https://polygon-rpc.com",
|
||||
},
|
||||
{
|
||||
chainID: 56,
|
||||
name: "Binance Smart Chain",
|
||||
rpc: "https://bscrpc.com",
|
||||
},
|
||||
{
|
||||
chainID: 250,
|
||||
name: "Fantom Opera",
|
||||
rpc: "https://rpc.fantom.network",
|
||||
},
|
||||
{
|
||||
chainID: 43114,
|
||||
name: "Avalanche",
|
||||
rpc: "https://rpc.ankr.com/avalanche",
|
||||
},
|
||||
{
|
||||
chainID: 100,
|
||||
name: "xDAI",
|
||||
name: "Gnosis",
|
||||
rpc: "https://rpc.ankr.com/gnosis",
|
||||
},
|
||||
{
|
||||
chainID: 42,
|
||||
name: "Kovan Testnet",
|
||||
rpc: `https://kovan.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
|
||||
},
|
||||
{
|
||||
chainID: 3,
|
||||
name: "Ropsten Testnet",
|
||||
rpc: `https://ropsten.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
|
||||
},
|
||||
{
|
||||
chainID: 4,
|
||||
name: "Rinkeby Testnet",
|
||||
rpc: `https://rinkeby.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
|
||||
},
|
||||
{
|
||||
chainID: 5,
|
||||
name: "Goerli Testnet",
|
||||
rpc: `https://goerli.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user