Merge branch 'master' of https://github.com/apoorvlathey/impersonator
This commit is contained in:
@@ -1,2 +1,4 @@
|
||||
REACT_APP_INFURA_KEY=
|
||||
REACT_APP_GITCOIN_GRANTS_ACTIVE=
|
||||
REACT_APP_WC_PROJECT_ID=
|
||||
REACT_APP_GITCOIN_GRANTS_ACTIVE=
|
||||
REACT_APP_GITCOIN_GRANTS_LINK=
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
.env
|
||||
.vercel
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
@@ -22,3 +23,4 @@
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.vercel
|
||||
|
||||
19826
package-lock.json
generated
19826
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
@@ -8,9 +8,10 @@
|
||||
"@chakra-ui/react": "^1.6.5",
|
||||
"@emotion/react": "^11",
|
||||
"@emotion/styled": "^11",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
||||
"@fortawesome/react-fontawesome": "^0.1.15",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.18",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
@@ -18,12 +19,17 @@
|
||||
"@types/node": "^17.0.10",
|
||||
"@types/react": "^17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@walletconnect/client": "^1.6.2",
|
||||
"@walletconnect/client": "^1.8.0",
|
||||
"@walletconnect/core": "^2.7.3",
|
||||
"@walletconnect/legacy-types": "^2.0.0",
|
||||
"@walletconnect/types": "^2.7.3",
|
||||
"@walletconnect/utils": "^2.7.3",
|
||||
"@walletconnect/web3wallet": "^1.7.1",
|
||||
"axios": "^0.24.0",
|
||||
"chakra-react-select": "^4.4.3",
|
||||
"ethereum-checksum-address": "^0.0.6",
|
||||
"ethers": "^5.4.5",
|
||||
"evm-rpcs-list": "^2.0.1",
|
||||
"evm-rpcs-list": "^2.0.2",
|
||||
"framer-motion": "^4",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
@@ -45,19 +51,18 @@
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0 <15.0.0"
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"resolutions": {
|
||||
"react-error-overlay": "6.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"react-error-overlay": "6.0.9"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB |
@@ -17,10 +17,10 @@
|
||||
</script>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/logo.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
|
||||
BIN
public/logo.png
Normal file
BIN
public/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB |
@@ -1,21 +1,11 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"name": "Impersonator",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"src": "logo.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
"sizes": "418x418"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 46 KiB |
160
src/components/Body/AddressInput/AddressBook/index.tsx
Normal file
160
src/components/Body/AddressInput/AddressBook/index.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
HStack,
|
||||
ModalCloseButton,
|
||||
ModalBody,
|
||||
Text,
|
||||
Input,
|
||||
Center,
|
||||
Button,
|
||||
Box,
|
||||
} from "@chakra-ui/react";
|
||||
import { DeleteIcon } from "@chakra-ui/icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSave } from "@fortawesome/free-solid-svg-icons";
|
||||
import { slicedText } from "../../TransactionRequests";
|
||||
|
||||
const STORAGE_KEY = "address-book";
|
||||
|
||||
interface SavedAddressInfo {
|
||||
address: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface AddressBookParams {
|
||||
isAddressBookOpen: boolean;
|
||||
closeAddressBook: () => void;
|
||||
showAddress: string;
|
||||
setShowAddress: (value: string) => void;
|
||||
setAddress: (value: string) => void;
|
||||
}
|
||||
|
||||
function AddressBook({
|
||||
isAddressBookOpen,
|
||||
closeAddressBook,
|
||||
showAddress,
|
||||
setShowAddress,
|
||||
setAddress,
|
||||
}: AddressBookParams) {
|
||||
const [newAddressInput, setNewAddressInput] = useState<string>("");
|
||||
const [newLableInput, setNewLabelInput] = useState<string>("");
|
||||
const [savedAddresses, setSavedAddresses] = useState<SavedAddressInfo[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setSavedAddresses(JSON.parse(localStorage.getItem(STORAGE_KEY) ?? "[]"));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setNewAddressInput(showAddress);
|
||||
}, [showAddress]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(savedAddresses));
|
||||
}, [savedAddresses]);
|
||||
|
||||
// reset label when modal is reopened
|
||||
useEffect(() => {
|
||||
setNewLabelInput("");
|
||||
}, [isAddressBookOpen]);
|
||||
|
||||
return (
|
||||
<Modal isOpen={isAddressBookOpen} onClose={closeAddressBook} isCentered>
|
||||
<ModalOverlay bg="none" backdropFilter="auto" backdropBlur="5px" />
|
||||
<ModalContent
|
||||
minW={{
|
||||
base: 0,
|
||||
sm: "30rem",
|
||||
md: "40rem",
|
||||
lg: "60rem",
|
||||
}}
|
||||
pb="6"
|
||||
>
|
||||
<ModalHeader>Address Book</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<HStack>
|
||||
<Input
|
||||
placeholder="address / ens"
|
||||
value={newAddressInput}
|
||||
onChange={(e) => setNewAddressInput(e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
placeholder="label"
|
||||
value={newLableInput}
|
||||
onChange={(e) => setNewLabelInput(e.target.value)}
|
||||
/>
|
||||
</HStack>
|
||||
<Center mt="3">
|
||||
<Button
|
||||
colorScheme={"blue"}
|
||||
isDisabled={
|
||||
newAddressInput.length === 0 || newLableInput.length === 0
|
||||
}
|
||||
onClick={() =>
|
||||
setSavedAddresses([
|
||||
...savedAddresses,
|
||||
{
|
||||
address: newAddressInput,
|
||||
label: newLableInput,
|
||||
},
|
||||
])
|
||||
}
|
||||
>
|
||||
<HStack>
|
||||
<FontAwesomeIcon icon={faSave} />
|
||||
<Text>Save</Text>
|
||||
</HStack>
|
||||
</Button>
|
||||
</Center>
|
||||
{savedAddresses.length > 0 && (
|
||||
<Box mt="6" px="20">
|
||||
<Text fontWeight={"bold"}>Select from saved addresses:</Text>
|
||||
<Box mt="3" px="10">
|
||||
{savedAddresses.map(({ address, label }, i) => (
|
||||
<HStack key={i} mt="2">
|
||||
<Button
|
||||
key={i}
|
||||
w="100%"
|
||||
onClick={() => {
|
||||
setShowAddress(address);
|
||||
setAddress(address);
|
||||
closeAddressBook();
|
||||
}}
|
||||
>
|
||||
{label} (
|
||||
{address.indexOf(".eth") >= 0
|
||||
? address
|
||||
: slicedText(address)}
|
||||
)
|
||||
</Button>
|
||||
<Button
|
||||
ml="2"
|
||||
_hover={{
|
||||
bg: "red.500",
|
||||
}}
|
||||
onClick={() => {
|
||||
const _savedAddresses = savedAddresses;
|
||||
_savedAddresses.splice(i, 1);
|
||||
|
||||
// using spread operator, else useEffect doesn't detect state change
|
||||
setSavedAddresses([..._savedAddresses]);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</Button>
|
||||
</HStack>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddressBook;
|
||||
106
src/components/Body/AddressInput/index.tsx
Normal file
106
src/components/Body/AddressInput/index.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import {
|
||||
FormControl,
|
||||
FormLabel,
|
||||
InputGroup,
|
||||
Input,
|
||||
InputRightElement,
|
||||
Button,
|
||||
HStack,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { DeleteIcon } from "@chakra-ui/icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faBook } from "@fortawesome/free-solid-svg-icons";
|
||||
import AddressBook from "./AddressBook";
|
||||
|
||||
interface AddressInputParams {
|
||||
showAddress: string;
|
||||
setShowAddress: (value: string) => void;
|
||||
setAddress: (value: string) => void;
|
||||
setIsAddressValid: (value: boolean) => void;
|
||||
bg: string;
|
||||
isAddressValid: boolean;
|
||||
selectedTabIndex: number;
|
||||
isConnected: boolean;
|
||||
appUrl: string | undefined;
|
||||
isIFrameLoading: boolean;
|
||||
updateAddress: () => void;
|
||||
}
|
||||
|
||||
function AddressInput({
|
||||
showAddress,
|
||||
setShowAddress,
|
||||
setAddress,
|
||||
setIsAddressValid,
|
||||
bg,
|
||||
isAddressValid,
|
||||
selectedTabIndex,
|
||||
isConnected,
|
||||
appUrl,
|
||||
isIFrameLoading,
|
||||
updateAddress,
|
||||
}: AddressInputParams) {
|
||||
const {
|
||||
isOpen: isAddressBookOpen,
|
||||
onOpen: openAddressBook,
|
||||
onClose: closeAddressBook,
|
||||
} = useDisclosure();
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<FormLabel>Enter Address or ENS to Impersonate</FormLabel>
|
||||
<HStack>
|
||||
<InputGroup>
|
||||
<Input
|
||||
placeholder="vitalik.eth"
|
||||
autoComplete="off"
|
||||
value={showAddress}
|
||||
onChange={(e) => {
|
||||
const _showAddress = e.target.value;
|
||||
setShowAddress(_showAddress);
|
||||
setAddress(_showAddress);
|
||||
setIsAddressValid(true); // remove inValid warning when user types again
|
||||
}}
|
||||
bg={bg}
|
||||
isInvalid={!isAddressValid}
|
||||
/>
|
||||
{(selectedTabIndex === 0 && isConnected) ||
|
||||
(selectedTabIndex === 1 && appUrl && !isIFrameLoading) ? (
|
||||
<InputRightElement width="4.5rem" mr="1rem">
|
||||
<Button h="1.75rem" size="sm" onClick={updateAddress}>
|
||||
Update
|
||||
</Button>
|
||||
</InputRightElement>
|
||||
) : (
|
||||
showAddress && (
|
||||
<InputRightElement px="1rem" mr="0.5rem">
|
||||
<Button
|
||||
h="1.75rem"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setShowAddress("");
|
||||
setAddress("");
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</Button>
|
||||
</InputRightElement>
|
||||
)
|
||||
)}
|
||||
</InputGroup>
|
||||
<Button onClick={openAddressBook}>
|
||||
<FontAwesomeIcon icon={faBook} />
|
||||
</Button>
|
||||
<AddressBook
|
||||
isAddressBookOpen={isAddressBookOpen}
|
||||
closeAddressBook={closeAddressBook}
|
||||
showAddress={showAddress}
|
||||
setShowAddress={setShowAddress}
|
||||
setAddress={setAddress}
|
||||
/>
|
||||
</HStack>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddressInput;
|
||||
43
src/components/Body/BrowserExtensionTab.tsx
Normal file
43
src/components/Body/BrowserExtensionTab.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
Center,
|
||||
Box,
|
||||
Text,
|
||||
chakra,
|
||||
HStack,
|
||||
Link,
|
||||
Image,
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
function BrowserExtensionTab() {
|
||||
return (
|
||||
<Center flexDir={"column"} mt="3">
|
||||
<Box w="full" fontWeight={"semibold"} fontSize={"xl"}>
|
||||
<Text>
|
||||
⭐ Download the browser extension from:{" "}
|
||||
<chakra.a
|
||||
color="blue.200"
|
||||
href="https://chrome.google.com/webstore/detail/impersonator/hgihfkmoibhccfdohjdbklmmcknjjmgl"
|
||||
target={"_blank"}
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Chrome Web Store
|
||||
</chakra.a>
|
||||
</Text>
|
||||
</Box>
|
||||
<HStack mt="2" w="full" fontSize={"lg"}>
|
||||
<Text>Read more:</Text>
|
||||
<Link
|
||||
color="cyan.200"
|
||||
fontWeight={"semibold"}
|
||||
href="https://twitter.com/apoorvlathey/status/1577624123177508864"
|
||||
isExternal
|
||||
>
|
||||
Launch Tweet
|
||||
</Link>
|
||||
</HStack>
|
||||
<Image mt="2" src="/extension.png" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
export default BrowserExtensionTab;
|
||||
25
src/components/Body/CopyToClipboard.tsx
Normal file
25
src/components/Body/CopyToClipboard.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Button, useToast } from "@chakra-ui/react";
|
||||
import { CopyIcon } from "@chakra-ui/icons";
|
||||
|
||||
const CopyToClipboard = ({ txt }: { txt: string }) => {
|
||||
const toast = useToast();
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(txt);
|
||||
toast({
|
||||
title: "Copied to clipboard",
|
||||
status: "success",
|
||||
isClosable: true,
|
||||
duration: 1000,
|
||||
});
|
||||
}}
|
||||
size="sm"
|
||||
>
|
||||
<CopyIcon />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default CopyToClipboard;
|
||||
29
src/components/Body/IFrameConnectTab/AppUrlLabel.tsx
Normal file
29
src/components/Body/IFrameConnectTab/AppUrlLabel.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { FormLabel, Tooltip, Text, Box } from "@chakra-ui/react";
|
||||
import { InfoIcon } from "@chakra-ui/icons";
|
||||
|
||||
function AppUrlLabel() {
|
||||
return (
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AppUrlLabel;
|
||||
59
src/components/Body/IFrameConnectTab/ShareModal.tsx
Normal file
59
src/components/Body/IFrameConnectTab/ShareModal.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
HStack,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Text,
|
||||
Input,
|
||||
} from "@chakra-ui/react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faShareAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
import CopyToClipboard from "../CopyToClipboard";
|
||||
|
||||
interface ShareModalParams {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
appUrl: string;
|
||||
showAddress: string;
|
||||
}
|
||||
|
||||
function ShareModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
appUrl,
|
||||
showAddress,
|
||||
}: ShareModalParams) {
|
||||
const urlToShare = `https://impersonator.xyz/?address=${showAddress}&url=${encodeURI(
|
||||
appUrl
|
||||
)}`;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} isCentered>
|
||||
<ModalOverlay bg="none" backdropFilter="auto" backdropBlur="5px" />
|
||||
<ModalContent>
|
||||
<ModalHeader>
|
||||
<HStack>
|
||||
<FontAwesomeIcon icon={faShareAlt} />
|
||||
<Text>Share</Text>
|
||||
</HStack>
|
||||
</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<Text>
|
||||
Share this link so that anyone can auto-connect to this dapp with
|
||||
your provided address!
|
||||
</Text>
|
||||
<HStack my="3">
|
||||
<Input value={urlToShare} isReadOnly />
|
||||
<CopyToClipboard txt={urlToShare} />
|
||||
</HStack>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ShareModal;
|
||||
@@ -0,0 +1,45 @@
|
||||
import { GridItem, Center, Image, Text } from "@chakra-ui/react";
|
||||
import { SafeDappInfo } from "../../../../types";
|
||||
|
||||
interface DappTileParams {
|
||||
initIFrame: (_inputAppUrl?: string | undefined) => Promise<void>;
|
||||
setInputAppUrl: (value: string | undefined) => void;
|
||||
closeSafeApps: () => void;
|
||||
dapp: SafeDappInfo;
|
||||
}
|
||||
|
||||
function DappTile({
|
||||
initIFrame,
|
||||
setInputAppUrl,
|
||||
closeSafeApps,
|
||||
dapp,
|
||||
}: DappTileParams) {
|
||||
return (
|
||||
<GridItem
|
||||
border="2px solid"
|
||||
borderColor={"gray.500"}
|
||||
bg={"white"}
|
||||
color={"black"}
|
||||
_hover={{
|
||||
cursor: "pointer",
|
||||
bgColor: "gray.600",
|
||||
color: "white",
|
||||
}}
|
||||
rounded="lg"
|
||||
onClick={() => {
|
||||
initIFrame(dapp.url);
|
||||
setInputAppUrl(dapp.url);
|
||||
closeSafeApps();
|
||||
}}
|
||||
>
|
||||
<Center flexDir={"column"} h="100%" p="1rem">
|
||||
<Image bg="white" w="2rem" src={dapp.iconUrl} borderRadius="full" />
|
||||
<Text mt="0.5rem" textAlign={"center"}>
|
||||
{dapp.name}
|
||||
</Text>
|
||||
</Center>
|
||||
</GridItem>
|
||||
);
|
||||
}
|
||||
|
||||
export default DappTile;
|
||||
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
Center,
|
||||
InputGroup,
|
||||
Input,
|
||||
InputRightElement,
|
||||
Button,
|
||||
} from "@chakra-ui/react";
|
||||
import { CloseIcon } from "@chakra-ui/icons";
|
||||
|
||||
interface DappsSearchParams {
|
||||
searchSafeDapp: string | undefined;
|
||||
setSearchSafeDapp: (value: string) => void;
|
||||
}
|
||||
|
||||
function DappsSearch({ searchSafeDapp, setSearchSafeDapp }: DappsSearchParams) {
|
||||
return (
|
||||
<Center pb="0.5rem">
|
||||
<InputGroup maxW="30rem">
|
||||
<Input
|
||||
placeholder="search 🔎"
|
||||
value={searchSafeDapp}
|
||||
onChange={(e) => setSearchSafeDapp(e.target.value)}
|
||||
/>
|
||||
{searchSafeDapp && (
|
||||
<InputRightElement width="3rem">
|
||||
<Button
|
||||
size="xs"
|
||||
variant={"ghost"}
|
||||
onClick={() => setSearchSafeDapp("")}
|
||||
>
|
||||
<CloseIcon />
|
||||
</Button>
|
||||
</InputRightElement>
|
||||
)}
|
||||
</InputGroup>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
export default DappsSearch;
|
||||
146
src/components/Body/IFrameConnectTab/SupportedDapps/index.tsx
Normal file
146
src/components/Body/IFrameConnectTab/SupportedDapps/index.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
SimpleGrid,
|
||||
Spinner,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import axios from "axios";
|
||||
import DappsSearch from "./DappsSearch";
|
||||
import DappTile from "./DappTile";
|
||||
import { SafeDappInfo } from "../../../../types";
|
||||
|
||||
interface SupportedDappsParams {
|
||||
networkId: number;
|
||||
initIFrame: (_inputAppUrl?: string | undefined) => Promise<void>;
|
||||
setInputAppUrl: (value: string | undefined) => void;
|
||||
}
|
||||
|
||||
function SupportedDapps({
|
||||
networkId,
|
||||
initIFrame,
|
||||
setInputAppUrl,
|
||||
}: SupportedDappsParams) {
|
||||
const {
|
||||
isOpen: isSafeAppsOpen,
|
||||
onOpen: openSafeAapps,
|
||||
onClose: closeSafeApps,
|
||||
} = useDisclosure();
|
||||
|
||||
const [safeDapps, setSafeDapps] = useState<{
|
||||
[networkId: number]: SafeDappInfo[];
|
||||
}>({});
|
||||
const [searchSafeDapp, setSearchSafeDapp] = useState<string>();
|
||||
const [filteredSafeDapps, setFilteredSafeDapps] = useState<SafeDappInfo[]>();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSafeDapps = async (networkId: number) => {
|
||||
const response = await axios.get<SafeDappInfo[]>(
|
||||
`https://safe-client.gnosis.io/v1/chains/${networkId}/safe-apps`
|
||||
);
|
||||
setSafeDapps((dapps) => ({
|
||||
...dapps,
|
||||
[networkId]: response.data.filter((d) => ![29, 11].includes(d.id)), // Filter out Transaction Builder and WalletConnect
|
||||
}));
|
||||
};
|
||||
|
||||
if (isSafeAppsOpen && !safeDapps[networkId]) {
|
||||
fetchSafeDapps(networkId);
|
||||
}
|
||||
}, [isSafeAppsOpen, safeDapps, networkId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (safeDapps[networkId]) {
|
||||
setFilteredSafeDapps(
|
||||
safeDapps[networkId].filter((dapp) => {
|
||||
if (!searchSafeDapp) return true;
|
||||
|
||||
return (
|
||||
dapp.name
|
||||
.toLowerCase()
|
||||
.indexOf(searchSafeDapp.toLocaleLowerCase()) !== -1 ||
|
||||
dapp.url
|
||||
.toLowerCase()
|
||||
.indexOf(searchSafeDapp.toLocaleLowerCase()) !== -1
|
||||
);
|
||||
})
|
||||
);
|
||||
} else {
|
||||
setFilteredSafeDapps(undefined);
|
||||
}
|
||||
}, [safeDapps, networkId, searchSafeDapp]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box pb="0.5rem">
|
||||
<Button size="sm" onClick={openSafeAapps}>
|
||||
Supported dapps
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Modal isOpen={isSafeAppsOpen} onClose={closeSafeApps} isCentered>
|
||||
<ModalOverlay bg="none" backdropFilter="auto" backdropBlur="3px" />
|
||||
<ModalContent
|
||||
minW={{
|
||||
base: 0,
|
||||
sm: "30rem",
|
||||
md: "40rem",
|
||||
lg: "60rem",
|
||||
}}
|
||||
>
|
||||
<ModalHeader>Select a dapp</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody maxH="30rem" overflow={"clip"}>
|
||||
{(!safeDapps || !safeDapps[networkId]) && (
|
||||
<Center py="3rem" w="100%">
|
||||
<Spinner />
|
||||
</Center>
|
||||
)}
|
||||
<Box pb="2rem" px={{ base: 0, md: "2rem" }}>
|
||||
{safeDapps && safeDapps[networkId] && (
|
||||
<DappsSearch
|
||||
searchSafeDapp={searchSafeDapp}
|
||||
setSearchSafeDapp={setSearchSafeDapp}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
minH="30rem"
|
||||
maxH="30rem"
|
||||
overflow="scroll"
|
||||
overflowX="auto"
|
||||
overflowY="auto"
|
||||
>
|
||||
<SimpleGrid
|
||||
pt="1rem"
|
||||
columns={{ base: 2, md: 3, lg: 4 }}
|
||||
gap={6}
|
||||
>
|
||||
{filteredSafeDapps &&
|
||||
filteredSafeDapps.map((dapp, i) => (
|
||||
<DappTile
|
||||
key={i}
|
||||
initIFrame={initIFrame}
|
||||
setInputAppUrl={setInputAppUrl}
|
||||
closeSafeApps={closeSafeApps}
|
||||
dapp={dapp}
|
||||
/>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default SupportedDapps;
|
||||
142
src/components/Body/IFrameConnectTab/index.tsx
Normal file
142
src/components/Body/IFrameConnectTab/index.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
Button,
|
||||
Box,
|
||||
Center,
|
||||
Spacer,
|
||||
HStack,
|
||||
FormControl,
|
||||
Input,
|
||||
Text,
|
||||
useDisclosure,
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
} from "@chakra-ui/react";
|
||||
import { DeleteIcon } from "@chakra-ui/icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faShareAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
import SupportedDapps from "./SupportedDapps";
|
||||
import AppUrlLabel from "./AppUrlLabel";
|
||||
import ShareModal from "./ShareModal";
|
||||
|
||||
interface IFrameConnectTabParams {
|
||||
networkId: number;
|
||||
initIFrame: (_inputAppUrl?: string | undefined) => Promise<void>;
|
||||
inputAppUrl: string | undefined;
|
||||
setInputAppUrl: (value: string | undefined) => void;
|
||||
appUrl: string | undefined;
|
||||
bg: string;
|
||||
isIFrameLoading: boolean;
|
||||
setIsIFrameLoading: (value: boolean) => void;
|
||||
iframeKey: number;
|
||||
iframeRef: React.RefObject<HTMLIFrameElement> | null;
|
||||
showAddress: string;
|
||||
}
|
||||
|
||||
function IFrameConnectTab({
|
||||
networkId,
|
||||
initIFrame,
|
||||
setInputAppUrl,
|
||||
inputAppUrl,
|
||||
bg,
|
||||
isIFrameLoading,
|
||||
appUrl,
|
||||
iframeKey,
|
||||
iframeRef,
|
||||
setIsIFrameLoading,
|
||||
showAddress,
|
||||
}: IFrameConnectTabParams) {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormControl my={4}>
|
||||
<HStack>
|
||||
<AppUrlLabel />
|
||||
<Spacer />
|
||||
<SupportedDapps
|
||||
networkId={networkId}
|
||||
initIFrame={initIFrame}
|
||||
setInputAppUrl={setInputAppUrl}
|
||||
/>
|
||||
</HStack>
|
||||
<HStack mt="2">
|
||||
<InputGroup>
|
||||
<Input
|
||||
pr="3.5rem"
|
||||
placeholder="https://app.uniswap.org/"
|
||||
aria-label="dapp-url"
|
||||
autoComplete="off"
|
||||
value={inputAppUrl}
|
||||
onChange={(e) => setInputAppUrl(e.target.value)}
|
||||
bg={bg}
|
||||
/>
|
||||
{inputAppUrl && (
|
||||
<InputRightElement px="1rem" mr="0.5rem">
|
||||
<Button
|
||||
h="1.75rem"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setInputAppUrl("");
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</Button>
|
||||
</InputRightElement>
|
||||
)}
|
||||
</InputGroup>
|
||||
{appUrl && (
|
||||
<>
|
||||
<Button onClick={onOpen}>
|
||||
<HStack>
|
||||
<FontAwesomeIcon icon={faShareAlt} />
|
||||
<Text>Share</Text>
|
||||
</HStack>
|
||||
</Button>
|
||||
<ShareModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
appUrl={appUrl}
|
||||
showAddress={showAddress}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</HStack>
|
||||
</FormControl>
|
||||
<Center>
|
||||
<Button onClick={() => initIFrame()} isLoading={isIFrameLoading}>
|
||||
Connect
|
||||
</Button>
|
||||
</Center>
|
||||
<Center
|
||||
mt="1rem"
|
||||
ml={{ base: "-385", sm: "-315", md: "-240", lg: "-60" }}
|
||||
px={{ base: "10rem", lg: 0 }}
|
||||
w="70rem"
|
||||
>
|
||||
{appUrl && (
|
||||
<Box
|
||||
as="iframe"
|
||||
w={{
|
||||
base: "22rem",
|
||||
sm: "45rem",
|
||||
md: "55rem",
|
||||
lg: "1500rem",
|
||||
}}
|
||||
h={{ base: "33rem", md: "35rem", lg: "38rem" }}
|
||||
title="app"
|
||||
src={appUrl}
|
||||
key={iframeKey}
|
||||
borderWidth="1px"
|
||||
borderStyle={"solid"}
|
||||
borderColor="white"
|
||||
bg="white"
|
||||
ref={iframeRef}
|
||||
onLoad={() => setIsIFrameLoading(false)}
|
||||
/>
|
||||
)}
|
||||
</Center>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default IFrameConnectTab;
|
||||
62
src/components/Body/NetworkInput.tsx
Normal file
62
src/components/Body/NetworkInput.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Box } from "@chakra-ui/react";
|
||||
import { Select as RSelect, SingleValue } from "chakra-react-select";
|
||||
import { SelectedNetworkOption } from "../../types";
|
||||
|
||||
interface NetworkOption {
|
||||
name: string;
|
||||
rpcs: string[];
|
||||
chainId: number;
|
||||
}
|
||||
|
||||
interface NetworkInputParams {
|
||||
primaryNetworkOptions: NetworkOption[];
|
||||
secondaryNetworkOptions: NetworkOption[];
|
||||
selectedNetworkOption: SingleValue<SelectedNetworkOption>;
|
||||
setSelectedNetworkOption: (value: SingleValue<SelectedNetworkOption>) => void;
|
||||
}
|
||||
|
||||
function NetworkInput({
|
||||
primaryNetworkOptions,
|
||||
secondaryNetworkOptions,
|
||||
selectedNetworkOption,
|
||||
setSelectedNetworkOption,
|
||||
}: NetworkInputParams) {
|
||||
return (
|
||||
<Box mt={4} cursor="pointer">
|
||||
<RSelect
|
||||
options={[
|
||||
{
|
||||
label: "",
|
||||
options: primaryNetworkOptions.map((network) => ({
|
||||
label: network.name,
|
||||
value: network.chainId,
|
||||
})),
|
||||
},
|
||||
{
|
||||
label: "",
|
||||
options: secondaryNetworkOptions.map((network) => ({
|
||||
label: network.name,
|
||||
value: network.chainId,
|
||||
})),
|
||||
},
|
||||
]}
|
||||
value={selectedNetworkOption}
|
||||
onChange={setSelectedNetworkOption}
|
||||
placeholder="Select chain..."
|
||||
size="md"
|
||||
tagVariant="solid"
|
||||
chakraStyles={{
|
||||
groupHeading: (provided, state) => ({
|
||||
...provided,
|
||||
h: "1px",
|
||||
borderTop: "1px solid white",
|
||||
}),
|
||||
}}
|
||||
closeMenuOnSelect
|
||||
useBasicStyles
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default NetworkInput;
|
||||
61
src/components/Body/NotificationBar.tsx
Normal file
61
src/components/Body/NotificationBar.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
Alert,
|
||||
AlertIcon,
|
||||
CloseButton,
|
||||
Text,
|
||||
Link,
|
||||
HStack,
|
||||
} from "@chakra-ui/react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
|
||||
import { ExternalLinkIcon } from "@chakra-ui/icons";
|
||||
|
||||
const CLOSED_KEY = "discord-notif-closed";
|
||||
|
||||
function NotificationBar() {
|
||||
const isClosed = localStorage.getItem(CLOSED_KEY);
|
||||
|
||||
const [isVisible, setIsVisible] = useState(
|
||||
isClosed === "true" ? false : true
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isVisible) {
|
||||
localStorage.setItem(CLOSED_KEY, "true");
|
||||
}
|
||||
}, [isVisible]);
|
||||
|
||||
return isVisible ? (
|
||||
<Alert status="info">
|
||||
<AlertIcon />
|
||||
<HStack flex={1}>
|
||||
<Text>Share feature requests, report bugs and be the first to</Text>{" "}
|
||||
<Text fontWeight={"semibold"}>try beta versions</Text>
|
||||
<Text> by joining us on</Text>
|
||||
<Link
|
||||
href={"https://discord.gg/4VTnuVzfmm"}
|
||||
color="cyan.300"
|
||||
isExternal
|
||||
>
|
||||
<HStack>
|
||||
<FontAwesomeIcon icon={faDiscord} size="1x" />
|
||||
<Text>Discord</Text>
|
||||
<ExternalLinkIcon />
|
||||
</HStack>
|
||||
</Link>
|
||||
</HStack>
|
||||
<CloseButton
|
||||
alignSelf="flex-start"
|
||||
position="relative"
|
||||
right={-1}
|
||||
top={-1}
|
||||
onClick={() => setIsVisible(false)}
|
||||
/>
|
||||
</Alert>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
|
||||
export default NotificationBar;
|
||||
41
src/components/Body/TabsSelect.tsx
Normal file
41
src/components/Body/TabsSelect.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Center, HStack } from "@chakra-ui/react";
|
||||
import Tab from "./Tab";
|
||||
|
||||
const tabs = ["WalletConnect", "iFrame", "Extension"];
|
||||
|
||||
interface TabsSelectParams {
|
||||
selectedTabIndex: number;
|
||||
setSelectedTabIndex: (value: number) => void;
|
||||
}
|
||||
|
||||
function TabsSelect({
|
||||
selectedTabIndex,
|
||||
setSelectedTabIndex,
|
||||
}: TabsSelectParams) {
|
||||
return (
|
||||
<Center flexDir="column">
|
||||
<HStack
|
||||
mt="1rem"
|
||||
minH="3rem"
|
||||
px="1.5rem"
|
||||
spacing={"8"}
|
||||
background="gray.700"
|
||||
borderRadius="xl"
|
||||
>
|
||||
{tabs.map((t, i) => (
|
||||
<Tab
|
||||
key={i}
|
||||
tabIndex={i}
|
||||
selectedTabIndex={selectedTabIndex}
|
||||
setSelectedTabIndex={setSelectedTabIndex}
|
||||
isNew={i === 2}
|
||||
>
|
||||
{t}
|
||||
</Tab>
|
||||
))}
|
||||
</HStack>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
export default TabsSelect;
|
||||
84
src/components/Body/TenderlySettings.tsx
Normal file
84
src/components/Body/TenderlySettings.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import {
|
||||
Input,
|
||||
Button,
|
||||
Box,
|
||||
Text,
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
Tooltip,
|
||||
HStack,
|
||||
chakra,
|
||||
ListItem,
|
||||
List,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { SettingsIcon, InfoIcon } from "@chakra-ui/icons";
|
||||
|
||||
interface TenderlySettingsParams {
|
||||
tenderlyForkId: string;
|
||||
setTenderlyForkId: (value: string) => void;
|
||||
}
|
||||
|
||||
function TenderlySettings({
|
||||
tenderlyForkId,
|
||||
setTenderlyForkId,
|
||||
}: TenderlySettingsParams) {
|
||||
const { onOpen, onClose, isOpen } = useDisclosure();
|
||||
|
||||
return (
|
||||
<Popover
|
||||
placement="bottom-start"
|
||||
isOpen={isOpen}
|
||||
onOpen={onOpen}
|
||||
onClose={onClose}
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<Box>
|
||||
<Button>
|
||||
<SettingsIcon
|
||||
transition="900ms rotate ease-in-out"
|
||||
transform={isOpen ? "rotate(33deg)" : "rotate(0deg)"}
|
||||
/>
|
||||
</Button>
|
||||
</Box>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent border={0} boxShadow="xl" rounded="xl" overflowY="auto">
|
||||
<Box px="1rem" py="1rem">
|
||||
<HStack>
|
||||
<Text>(optional) Tenderly Fork Id:</Text>
|
||||
<Tooltip
|
||||
label={
|
||||
<>
|
||||
<Text>Simulate sending transactions on forked node.</Text>
|
||||
<chakra.hr bg="gray.400" />
|
||||
<List>
|
||||
<ListItem>
|
||||
Create a fork on Tenderly and grab it's id from the URL.
|
||||
</ListItem>
|
||||
</List>
|
||||
</>
|
||||
}
|
||||
hasArrow
|
||||
placement="top"
|
||||
>
|
||||
<InfoIcon />
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
<Input
|
||||
mt="0.5rem"
|
||||
aria-label="fork-rpc"
|
||||
placeholder="xxxx-xxxx-xxxx-xxxx"
|
||||
autoComplete="off"
|
||||
value={tenderlyForkId}
|
||||
onChange={(e) => {
|
||||
setTenderlyForkId(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export default TenderlySettings;
|
||||
154
src/components/Body/TransactionRequests.tsx
Normal file
154
src/components/Body/TransactionRequests.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
HStack,
|
||||
Text,
|
||||
Heading,
|
||||
Tooltip,
|
||||
Td,
|
||||
Collapse,
|
||||
useDisclosure,
|
||||
Button,
|
||||
Table,
|
||||
Thead,
|
||||
Tr,
|
||||
Th,
|
||||
Tbody,
|
||||
Link,
|
||||
} from "@chakra-ui/react";
|
||||
import {
|
||||
InfoIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
DeleteIcon,
|
||||
UnlockIcon,
|
||||
} from "@chakra-ui/icons";
|
||||
import CopyToClipboard from "./CopyToClipboard";
|
||||
import { TxnDataType } from "../../types";
|
||||
|
||||
export const slicedText = (txt: string) => {
|
||||
return txt.length > 6
|
||||
? `${txt.slice(0, 4)}...${txt.slice(txt.length - 2, txt.length)}`
|
||||
: txt;
|
||||
};
|
||||
|
||||
const TD = ({ txt }: { txt: string }) => (
|
||||
<Td>
|
||||
<HStack>
|
||||
<Tooltip label={txt} hasArrow placement="top">
|
||||
<Text>{slicedText(txt)}</Text>
|
||||
</Tooltip>
|
||||
<CopyToClipboard txt={txt} />
|
||||
</HStack>
|
||||
</Td>
|
||||
);
|
||||
|
||||
const TData = ({
|
||||
calldata,
|
||||
address,
|
||||
networkId,
|
||||
}: {
|
||||
calldata: string;
|
||||
address: string;
|
||||
networkId: number;
|
||||
}) => (
|
||||
<Td>
|
||||
<HStack>
|
||||
<Tooltip label={calldata} hasArrow placement="top">
|
||||
<Text>{slicedText(calldata)}</Text>
|
||||
</Tooltip>
|
||||
<CopyToClipboard txt={calldata} />
|
||||
<Button title="Decode" size="sm">
|
||||
<Link
|
||||
href={`https://calldata-decoder.apoorv.xyz/?calldata=${calldata}&address=${address}&chainId=${networkId}`}
|
||||
isExternal
|
||||
>
|
||||
<UnlockIcon />
|
||||
</Link>
|
||||
</Button>
|
||||
</HStack>
|
||||
</Td>
|
||||
);
|
||||
|
||||
interface TransactionRequestsParams {
|
||||
sendTxnData: TxnDataType[];
|
||||
setSendTxnData: (value: TxnDataType[]) => void;
|
||||
networkId: number;
|
||||
}
|
||||
|
||||
function TransactionRequests({
|
||||
sendTxnData,
|
||||
setSendTxnData,
|
||||
networkId,
|
||||
}: TransactionRequestsParams) {
|
||||
const { isOpen: tableIsOpen, onToggle: tableOnToggle } = useDisclosure();
|
||||
|
||||
return (
|
||||
<Box
|
||||
minW={["0", "0", "2xl", "2xl"]}
|
||||
overflowX={"auto"}
|
||||
mt="2rem"
|
||||
pt="0.5rem"
|
||||
pl="1rem"
|
||||
border={"1px solid"}
|
||||
borderColor={"white.800"}
|
||||
rounded="lg"
|
||||
>
|
||||
<Flex py="2" pl="2" pr="4">
|
||||
<HStack cursor={"pointer"} onClick={tableOnToggle}>
|
||||
<Text fontSize={"xl"}>
|
||||
{tableIsOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
</Text>
|
||||
<Heading size={"md"}>eth_sendTransactions</Heading>
|
||||
<Tooltip
|
||||
label={
|
||||
<>
|
||||
<Text>
|
||||
"eth_sendTransaction" requests by the dApp are shown here
|
||||
(latest on top)
|
||||
</Text>
|
||||
</>
|
||||
}
|
||||
hasArrow
|
||||
placement="top"
|
||||
>
|
||||
<Box pb="0.8rem">
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
<Flex flex="1" />
|
||||
{sendTxnData.length > 0 && (
|
||||
<Button onClick={() => setSendTxnData([])}>
|
||||
<DeleteIcon />
|
||||
<Text pl="0.5rem">Clear</Text>
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
<Collapse in={tableIsOpen} animateOpacity>
|
||||
<Table variant="simple">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>from</Th>
|
||||
<Th>to</Th>
|
||||
<Th>data</Th>
|
||||
<Th>value</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{sendTxnData.map((d) => (
|
||||
<Tr key={d.id}>
|
||||
<TD txt={d.from} />
|
||||
<TD txt={d.to} />
|
||||
<TData calldata={d.data} address={d.to} networkId={networkId} />
|
||||
<TD txt={d.value} />
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default TransactionRequests;
|
||||
38
src/components/Body/WalletConnectTab/ConnectionDetails.tsx
Normal file
38
src/components/Body/WalletConnectTab/ConnectionDetails.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Box, Text, Button, VStack, Avatar, Link } from "@chakra-ui/react";
|
||||
import { SessionTypes } from "@walletconnect/types";
|
||||
|
||||
interface ConnectionDetailsParams {
|
||||
web3WalletSession: SessionTypes.Struct;
|
||||
killSession: () => void;
|
||||
}
|
||||
|
||||
function ConnectionDetails({
|
||||
web3WalletSession,
|
||||
killSession,
|
||||
}: ConnectionDetailsParams) {
|
||||
return (
|
||||
<>
|
||||
<Box mt={4} fontSize={24} fontWeight="semibold">
|
||||
✅ Connected To:
|
||||
</Box>
|
||||
<VStack>
|
||||
<Avatar src={web3WalletSession.peer?.metadata?.icons[0]} />
|
||||
<Text fontWeight="bold">{web3WalletSession.peer?.metadata?.name}</Text>
|
||||
<Text fontSize="sm">
|
||||
{web3WalletSession.peer?.metadata?.description}
|
||||
</Text>
|
||||
<Link
|
||||
href={web3WalletSession.peer?.metadata?.url}
|
||||
textDecor="underline"
|
||||
>
|
||||
{web3WalletSession.peer?.metadata?.url}
|
||||
</Link>
|
||||
<Box pt={6}>
|
||||
<Button onClick={() => killSession()}>Disconnect ☠</Button>
|
||||
</Box>
|
||||
</VStack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConnectionDetails;
|
||||
39
src/components/Body/WalletConnectTab/Loading.tsx
Normal file
39
src/components/Body/WalletConnectTab/Loading.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
Button,
|
||||
VStack,
|
||||
CircularProgress,
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
interface LoadingParams {
|
||||
isConnected: boolean;
|
||||
setLoading: (value: boolean) => void;
|
||||
reset: (persistUri?: boolean) => void;
|
||||
}
|
||||
|
||||
function Loading({ isConnected, setLoading, reset }: LoadingParams) {
|
||||
return (
|
||||
<Center>
|
||||
<VStack>
|
||||
<Box>
|
||||
<CircularProgress isIndeterminate />
|
||||
</Box>
|
||||
{!isConnected && (
|
||||
<Box pt={6}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setLoading(false);
|
||||
reset(true);
|
||||
}}
|
||||
>
|
||||
Stop Loading ☠
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</VStack>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
export default Loading;
|
||||
92
src/components/Body/WalletConnectTab/URIInput.tsx
Normal file
92
src/components/Body/WalletConnectTab/URIInput.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
FormControl,
|
||||
HStack,
|
||||
FormLabel,
|
||||
Tooltip,
|
||||
Box,
|
||||
Text,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
Button,
|
||||
} from "@chakra-ui/react";
|
||||
import { InfoIcon, DeleteIcon } from "@chakra-ui/icons";
|
||||
|
||||
interface URIInputParams {
|
||||
uri: string;
|
||||
setUri: (value: string) => void;
|
||||
bg: string;
|
||||
isConnected: boolean;
|
||||
initWalletConnect: () => void;
|
||||
}
|
||||
|
||||
function URIInput({
|
||||
uri,
|
||||
setUri,
|
||||
bg,
|
||||
isConnected,
|
||||
initWalletConnect,
|
||||
}: URIInputParams) {
|
||||
const [pasted, setPasted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (pasted) {
|
||||
initWalletConnect();
|
||||
setPasted(false);
|
||||
}
|
||||
}, [uri]);
|
||||
|
||||
return (
|
||||
<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>
|
||||
<Box>
|
||||
<InputGroup>
|
||||
<Input
|
||||
pr={isConnected ? "0" : "3.5rem"}
|
||||
placeholder="wc:xyz123"
|
||||
aria-label="uri"
|
||||
autoComplete="off"
|
||||
value={uri}
|
||||
onChange={(e) => setUri(e.target.value)}
|
||||
onPaste={(e) => {
|
||||
e.preventDefault();
|
||||
setPasted(true);
|
||||
setUri(e.clipboardData.getData("text"));
|
||||
}}
|
||||
bg={bg}
|
||||
isDisabled={isConnected}
|
||||
/>
|
||||
{uri && !isConnected && (
|
||||
<InputRightElement px="1rem" mr="0.5rem">
|
||||
<Button h="1.75rem" size="sm" onClick={() => setUri("")}>
|
||||
<DeleteIcon />
|
||||
</Button>
|
||||
</InputRightElement>
|
||||
)}
|
||||
</InputGroup>
|
||||
</Box>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
export default URIInput;
|
||||
63
src/components/Body/WalletConnectTab/index.tsx
Normal file
63
src/components/Body/WalletConnectTab/index.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Center, Button } from "@chakra-ui/react";
|
||||
import { SessionTypes } from "@walletconnect/types";
|
||||
import ConnectionDetails from "./ConnectionDetails";
|
||||
import Loading from "./Loading";
|
||||
import URIInput from "./URIInput";
|
||||
|
||||
interface WalletConnectTabParams {
|
||||
uri: string;
|
||||
setUri: (value: string) => void;
|
||||
bg: string;
|
||||
isConnected: boolean;
|
||||
initWalletConnect: () => void;
|
||||
loading: boolean;
|
||||
setLoading: (value: boolean) => void;
|
||||
reset: (persistUri?: boolean) => void;
|
||||
killSession: () => void;
|
||||
web3WalletSession: SessionTypes.Struct | undefined;
|
||||
}
|
||||
|
||||
function WalletConnectTab({
|
||||
uri,
|
||||
setUri,
|
||||
bg,
|
||||
isConnected,
|
||||
initWalletConnect,
|
||||
loading,
|
||||
setLoading,
|
||||
reset,
|
||||
killSession,
|
||||
web3WalletSession,
|
||||
}: WalletConnectTabParams) {
|
||||
return (
|
||||
<>
|
||||
<URIInput
|
||||
uri={uri}
|
||||
setUri={setUri}
|
||||
bg={bg}
|
||||
isConnected={isConnected}
|
||||
initWalletConnect={initWalletConnect}
|
||||
/>
|
||||
<Center>
|
||||
<Button onClick={() => initWalletConnect()} isDisabled={isConnected}>
|
||||
Connect
|
||||
</Button>
|
||||
</Center>
|
||||
{loading && (
|
||||
<Loading
|
||||
isConnected={isConnected}
|
||||
setLoading={setLoading}
|
||||
reset={reset}
|
||||
/>
|
||||
)}
|
||||
{web3WalletSession && isConnected && (
|
||||
<ConnectionDetails
|
||||
web3WalletSession={web3WalletSession}
|
||||
killSession={killSession}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default WalletConnectTab;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,11 +10,12 @@ import {
|
||||
HStack,
|
||||
Box,
|
||||
Stack,
|
||||
Center,
|
||||
} from "@chakra-ui/react";
|
||||
import { ExternalLinkIcon } from "@chakra-ui/icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faTwitter } from "@fortawesome/free-brands-svg-icons";
|
||||
import { faTwitter, faDiscord } from "@fortawesome/free-brands-svg-icons";
|
||||
|
||||
const Social = ({ icon, link }: { icon: IconProp; link: string }) => {
|
||||
return (
|
||||
@@ -40,7 +41,7 @@ function Footer() {
|
||||
<>
|
||||
<Text>Support it on</Text>
|
||||
<Link
|
||||
href="https://gitcoin.co/grants/3613/impersonator"
|
||||
href={process.env.REACT_APP_GITCOIN_GRANTS_LINK}
|
||||
isExternal
|
||||
>
|
||||
<HStack fontWeight="bold" textDecor="underline">
|
||||
@@ -76,6 +77,15 @@ function Footer() {
|
||||
<ExternalLinkIcon />
|
||||
</Link>
|
||||
</Heading>
|
||||
<Center pt="1">
|
||||
<Link
|
||||
href={"https://discord.gg/4VTnuVzfmm"}
|
||||
color="twitter.200"
|
||||
isExternal
|
||||
>
|
||||
<FontAwesomeIcon icon={faDiscord} size="2x" />
|
||||
</Link>
|
||||
</Center>
|
||||
</VStack>
|
||||
<Spacer flex="1" />
|
||||
</Flex>
|
||||
|
||||
@@ -6,6 +6,9 @@ import {
|
||||
Spacer,
|
||||
Box,
|
||||
Link,
|
||||
HStack,
|
||||
Text,
|
||||
Image,
|
||||
} from "@chakra-ui/react";
|
||||
import { SunIcon, MoonIcon } from "@chakra-ui/icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@@ -28,7 +31,10 @@ function Navbar() {
|
||||
fontSize={{ base: "2xl", sm: "3xl", md: "4xl" }}
|
||||
pr="2rem"
|
||||
>
|
||||
🎭 Impersonator 🕵️
|
||||
<HStack>
|
||||
<Image src="/logo.png" w="3rem" mr="1rem" />
|
||||
<Text>Impersonator</Text>
|
||||
</HStack>
|
||||
</Heading>
|
||||
<Flex flex="1" justifyContent="flex-end" alignItems={"center"}>
|
||||
<Button onClick={toggleColorMode} rounded="full" h="40px" w="40px">
|
||||
|
||||
22
src/types.ts
22
src/types.ts
@@ -1,5 +1,27 @@
|
||||
import { BigNumberish, BytesLike } from "ethers";
|
||||
|
||||
export interface SelectedNetworkOption {
|
||||
label: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface SafeDappInfo {
|
||||
id: number;
|
||||
url: string;
|
||||
name: string;
|
||||
iconUrl: string;
|
||||
}
|
||||
|
||||
export interface TxnDataType {
|
||||
id: number;
|
||||
from: string;
|
||||
to: string;
|
||||
data: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
// ======= iFrame Provider ======
|
||||
|
||||
export declare const INTERFACE_MESSAGES: {
|
||||
readonly ENV_INFO: "ENV_INFO";
|
||||
readonly ON_SAFE_INFO: "ON_SAFE_INFO";
|
||||
|
||||
Reference in New Issue
Block a user