add iframe support for gnosis safe provider (#8)
This commit is contained in:
1
.env.sample
Normal file
1
.env.sample
Normal file
@@ -0,0 +1 @@
|
||||
REACT_APP_INFURA_KEY=
|
||||
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}`,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
171
src/contexts/SafeInjectContext.tsx
Normal file
171
src/contexts/SafeInjectContext.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import { providers, utils } from "ethers";
|
||||
import { useAppCommunicator } from "../helpers/communicator";
|
||||
import {
|
||||
InterfaceMessageIds,
|
||||
InterfaceMessageProps,
|
||||
Methods,
|
||||
MethodToResponse,
|
||||
RequestId,
|
||||
RPCPayload,
|
||||
SignMessageParams,
|
||||
SignTypedMessageParams,
|
||||
Transaction,
|
||||
} from "../types";
|
||||
|
||||
interface TransactionWithId extends Transaction {
|
||||
id: number;
|
||||
}
|
||||
|
||||
type SafeInjectContextType = {
|
||||
address: string | undefined;
|
||||
appUrl: string | undefined;
|
||||
rpcUrl: string | undefined;
|
||||
iframeRef: React.RefObject<HTMLIFrameElement> | null;
|
||||
latestTransaction: TransactionWithId | undefined;
|
||||
setAddress: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
setAppUrl: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
setRpcUrl: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
sendMessageToIFrame: Function;
|
||||
};
|
||||
|
||||
export const SafeInjectContext = createContext<SafeInjectContextType>({
|
||||
address: undefined,
|
||||
appUrl: undefined,
|
||||
rpcUrl: undefined,
|
||||
iframeRef: null,
|
||||
latestTransaction: undefined,
|
||||
setAddress: () => {},
|
||||
setAppUrl: () => {},
|
||||
setRpcUrl: () => {},
|
||||
sendMessageToIFrame: () => {},
|
||||
});
|
||||
|
||||
export interface FCProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const SafeInjectProvider: React.FunctionComponent<FCProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [address, setAddress] = useState<string>();
|
||||
const [appUrl, setAppUrl] = useState<string>();
|
||||
const [rpcUrl, setRpcUrl] = useState<string>();
|
||||
const [provider, setProvider] = useState<providers.StaticJsonRpcProvider>();
|
||||
const [latestTransaction, setLatestTransaction] =
|
||||
useState<TransactionWithId>();
|
||||
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
const communicator = useAppCommunicator(iframeRef);
|
||||
|
||||
const sendMessageToIFrame = useCallback(
|
||||
function <T extends InterfaceMessageIds>(
|
||||
message: InterfaceMessageProps<T>,
|
||||
requestId?: RequestId
|
||||
) {
|
||||
const requestWithMessage = {
|
||||
...message,
|
||||
requestId: requestId || Math.trunc(window.performance.now()),
|
||||
version: "0.4.2",
|
||||
};
|
||||
|
||||
if (iframeRef) {
|
||||
iframeRef.current?.contentWindow?.postMessage(
|
||||
requestWithMessage,
|
||||
appUrl!
|
||||
);
|
||||
}
|
||||
},
|
||||
[iframeRef, appUrl]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!rpcUrl) return;
|
||||
|
||||
setProvider(new providers.StaticJsonRpcProvider(rpcUrl));
|
||||
}, [rpcUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!provider) return;
|
||||
|
||||
communicator?.on(Methods.getSafeInfo, async () => ({
|
||||
safeAddress: address,
|
||||
chainId: (await provider.getNetwork()).chainId,
|
||||
owners: [],
|
||||
threshold: 1,
|
||||
isReadOnly: false,
|
||||
}));
|
||||
|
||||
communicator?.on(Methods.getEnvironmentInfo, async () => ({
|
||||
origin: document.location.origin,
|
||||
}));
|
||||
|
||||
communicator?.on(Methods.rpcCall, async (msg) => {
|
||||
const params = msg.data.params as RPCPayload;
|
||||
|
||||
try {
|
||||
const response = (await provider.send(
|
||||
params.call,
|
||||
params.params
|
||||
)) as MethodToResponse["rpcCall"];
|
||||
return response;
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
});
|
||||
|
||||
communicator?.on(Methods.sendTransactions, (msg) => {
|
||||
// @ts-expect-error explore ways to fix this
|
||||
const transactions = (msg.data.params.txs as Transaction[]).map(
|
||||
({ to, ...rest }) => ({
|
||||
to: utils.getAddress(to), // checksummed
|
||||
...rest,
|
||||
})
|
||||
);
|
||||
setLatestTransaction({
|
||||
id: parseInt(msg.data.id.toString()),
|
||||
...transactions[0],
|
||||
});
|
||||
// openConfirmationModal(transactions, msg.data.params.params, msg.data.id)
|
||||
});
|
||||
|
||||
communicator?.on(Methods.signMessage, async (msg) => {
|
||||
const { message } = msg.data.params as SignMessageParams;
|
||||
|
||||
// openSignMessageModal(message, msg.data.id, Methods.signMessage)
|
||||
});
|
||||
|
||||
communicator?.on(Methods.signTypedMessage, async (msg) => {
|
||||
const { typedData } = msg.data.params as SignTypedMessageParams;
|
||||
|
||||
// openSignMessageModal(typedData, msg.data.id, Methods.signTypedMessage)
|
||||
});
|
||||
}, [communicator, address, provider]);
|
||||
|
||||
return (
|
||||
<SafeInjectContext.Provider
|
||||
value={{
|
||||
address,
|
||||
appUrl,
|
||||
rpcUrl,
|
||||
iframeRef,
|
||||
latestTransaction,
|
||||
setAddress,
|
||||
setAppUrl,
|
||||
setRpcUrl,
|
||||
sendMessageToIFrame,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SafeInjectContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useSafeInject = () => useContext(SafeInjectContext);
|
||||
120
src/helpers/communicator.ts
Normal file
120
src/helpers/communicator.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { MutableRefObject, useEffect, useState } from "react";
|
||||
import { MessageFormatter } from "./messageFormatter";
|
||||
import {
|
||||
SDKMessageEvent,
|
||||
MethodToResponse,
|
||||
Methods,
|
||||
ErrorResponse,
|
||||
RequestId,
|
||||
} from "../types";
|
||||
import { getSDKVersion } from "./utils";
|
||||
|
||||
type MessageHandler = (
|
||||
msg: SDKMessageEvent
|
||||
) =>
|
||||
| void
|
||||
| MethodToResponse[Methods]
|
||||
| ErrorResponse
|
||||
| Promise<MethodToResponse[Methods] | ErrorResponse | void>;
|
||||
|
||||
export enum LegacyMethods {
|
||||
getEnvInfo = "getEnvInfo",
|
||||
}
|
||||
|
||||
type SDKMethods = Methods | LegacyMethods;
|
||||
|
||||
class AppCommunicator {
|
||||
private iframeRef: MutableRefObject<HTMLIFrameElement | null>;
|
||||
private handlers = new Map<SDKMethods, MessageHandler>();
|
||||
|
||||
constructor(iframeRef: MutableRefObject<HTMLIFrameElement | null>) {
|
||||
this.iframeRef = iframeRef;
|
||||
|
||||
window.addEventListener("message", this.handleIncomingMessage);
|
||||
}
|
||||
|
||||
on = (method: SDKMethods, handler: MessageHandler): void => {
|
||||
this.handlers.set(method, handler);
|
||||
};
|
||||
|
||||
private isValidMessage = (msg: SDKMessageEvent): boolean => {
|
||||
if (msg.data.hasOwnProperty("isCookieEnabled")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const sentFromIframe = this.iframeRef.current?.contentWindow === msg.source;
|
||||
const knownMethod = Object.values(Methods).includes(msg.data.method);
|
||||
|
||||
return sentFromIframe && knownMethod;
|
||||
};
|
||||
|
||||
private canHandleMessage = (msg: SDKMessageEvent): boolean => {
|
||||
return Boolean(this.handlers.get(msg.data.method));
|
||||
};
|
||||
|
||||
send = (data: unknown, requestId: RequestId, error = false): void => {
|
||||
const sdkVersion = getSDKVersion();
|
||||
const msg = error
|
||||
? MessageFormatter.makeErrorResponse(
|
||||
requestId,
|
||||
data as string,
|
||||
sdkVersion
|
||||
)
|
||||
: MessageFormatter.makeResponse(requestId, data, sdkVersion);
|
||||
// console.log("send", { msg });
|
||||
this.iframeRef.current?.contentWindow?.postMessage(msg, "*");
|
||||
};
|
||||
|
||||
handleIncomingMessage = async (msg: SDKMessageEvent): Promise<void> => {
|
||||
const validMessage = this.isValidMessage(msg);
|
||||
const hasHandler = this.canHandleMessage(msg);
|
||||
|
||||
if (validMessage && hasHandler) {
|
||||
// console.log("incoming", { msg: msg.data });
|
||||
|
||||
const handler = this.handlers.get(msg.data.method);
|
||||
try {
|
||||
// @ts-expect-error Handler existence is checked in this.canHandleMessage
|
||||
const response = await handler(msg);
|
||||
|
||||
// If response is not returned, it means the response will be send somewhere else
|
||||
if (typeof response !== "undefined") {
|
||||
this.send(response, msg.data.id);
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.send(err.message, msg.data.id, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
clear = (): void => {
|
||||
window.removeEventListener("message", this.handleIncomingMessage);
|
||||
};
|
||||
}
|
||||
|
||||
const useAppCommunicator = (
|
||||
iframeRef: MutableRefObject<HTMLIFrameElement | null>
|
||||
): AppCommunicator | undefined => {
|
||||
const [communicator, setCommunicator] = useState<AppCommunicator | undefined>(
|
||||
undefined
|
||||
);
|
||||
useEffect(() => {
|
||||
let communicatorInstance: AppCommunicator;
|
||||
const initCommunicator = (
|
||||
iframeRef: MutableRefObject<HTMLIFrameElement>
|
||||
) => {
|
||||
communicatorInstance = new AppCommunicator(iframeRef);
|
||||
setCommunicator(communicatorInstance);
|
||||
};
|
||||
|
||||
initCommunicator(iframeRef as MutableRefObject<HTMLIFrameElement>);
|
||||
|
||||
return () => {
|
||||
communicatorInstance?.clear();
|
||||
};
|
||||
}, [iframeRef]);
|
||||
|
||||
return communicator;
|
||||
};
|
||||
|
||||
export { useAppCommunicator };
|
||||
51
src/helpers/messageFormatter.ts
Normal file
51
src/helpers/messageFormatter.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
ErrorResponse,
|
||||
SDKRequestData,
|
||||
RequestId,
|
||||
SuccessResponse,
|
||||
MethodToResponse,
|
||||
Methods,
|
||||
} from "../types";
|
||||
import { getSDKVersion, generateRequestId } from "./utils";
|
||||
|
||||
class MessageFormatter {
|
||||
static makeRequest = <M extends Methods = Methods, P = unknown>(
|
||||
method: M,
|
||||
params: P
|
||||
): SDKRequestData<M, P> => {
|
||||
const id = generateRequestId();
|
||||
|
||||
return {
|
||||
id,
|
||||
method,
|
||||
params,
|
||||
env: {
|
||||
sdkVersion: getSDKVersion(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
static makeResponse = (
|
||||
id: RequestId,
|
||||
data: MethodToResponse[Methods],
|
||||
version: string
|
||||
): SuccessResponse => ({
|
||||
id,
|
||||
success: true,
|
||||
version,
|
||||
data,
|
||||
});
|
||||
|
||||
static makeErrorResponse = (
|
||||
id: RequestId,
|
||||
error: string,
|
||||
version: string
|
||||
): ErrorResponse => ({
|
||||
id,
|
||||
success: false,
|
||||
error,
|
||||
version,
|
||||
});
|
||||
}
|
||||
|
||||
export { MessageFormatter };
|
||||
20
src/helpers/utils.ts
Normal file
20
src/helpers/utils.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export const getSDKVersion = () => {
|
||||
return "7.6.0"; // IMPORTANT: needs to be >= 1.0.0
|
||||
};
|
||||
|
||||
// i.e. 0-255 -> '00'-'ff'
|
||||
const dec2hex = (dec: number): string => dec.toString(16).padStart(2, "0");
|
||||
|
||||
const generateId = (len: number): string => {
|
||||
const arr = new Uint8Array((len || 40) / 2);
|
||||
window.crypto.getRandomValues(arr);
|
||||
return Array.from(arr, dec2hex).join("");
|
||||
};
|
||||
|
||||
export const generateRequestId = (): string => {
|
||||
if (typeof window !== "undefined") {
|
||||
return generateId(10);
|
||||
}
|
||||
|
||||
return new Date().getTime().toString(36);
|
||||
};
|
||||
@@ -2,10 +2,13 @@ import ReactDOM from "react-dom";
|
||||
import App from "./App";
|
||||
import { ChakraProvider } from "@chakra-ui/react";
|
||||
import theme from "./theme";
|
||||
import { SafeInjectProvider } from "./contexts/SafeInjectContext";
|
||||
|
||||
ReactDOM.render(
|
||||
<ChakraProvider theme={theme}>
|
||||
<App />
|
||||
<SafeInjectProvider>
|
||||
<App />
|
||||
</SafeInjectProvider>
|
||||
</ChakraProvider>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
|
||||
515
src/types.ts
Normal file
515
src/types.ts
Normal file
@@ -0,0 +1,515 @@
|
||||
import { BigNumberish, BytesLike } from "ethers";
|
||||
|
||||
export declare const INTERFACE_MESSAGES: {
|
||||
readonly ENV_INFO: "ENV_INFO";
|
||||
readonly ON_SAFE_INFO: "ON_SAFE_INFO";
|
||||
readonly TRANSACTION_CONFIRMED: "TRANSACTION_CONFIRMED";
|
||||
readonly TRANSACTION_REJECTED: "TRANSACTION_REJECTED";
|
||||
};
|
||||
export type InterfaceMessageIds = keyof typeof INTERFACE_MESSAGES;
|
||||
|
||||
export declare type LowercaseNetworks =
|
||||
| "mainnet"
|
||||
| "morden"
|
||||
| "ropsten"
|
||||
| "rinkeby"
|
||||
| "goerli"
|
||||
| "kovan"
|
||||
| "xdai"
|
||||
| "energy_web_chain"
|
||||
| "volta"
|
||||
| "unknown";
|
||||
export interface SafeInfo {
|
||||
safeAddress: string;
|
||||
network: LowercaseNetworks;
|
||||
ethBalance: string;
|
||||
}
|
||||
export interface InterfaceMessageToPayload {
|
||||
[INTERFACE_MESSAGES.ON_SAFE_INFO]: SafeInfo;
|
||||
[INTERFACE_MESSAGES.TRANSACTION_CONFIRMED]: {
|
||||
safeTxHash: string;
|
||||
};
|
||||
[INTERFACE_MESSAGES.ENV_INFO]: {
|
||||
txServiceUrl: string;
|
||||
};
|
||||
[INTERFACE_MESSAGES.TRANSACTION_REJECTED]: Record<string, unknown>;
|
||||
}
|
||||
export type InterfaceMessageProps<T extends InterfaceMessageIds> = {
|
||||
messageId: T;
|
||||
data: InterfaceMessageToPayload[T];
|
||||
};
|
||||
export declare type RequestId = number | string;
|
||||
|
||||
// Messaging
|
||||
export enum Methods {
|
||||
sendTransactions = "sendTransactions",
|
||||
rpcCall = "rpcCall",
|
||||
getChainInfo = "getChainInfo",
|
||||
getSafeInfo = "getSafeInfo",
|
||||
getTxBySafeTxHash = "getTxBySafeTxHash",
|
||||
getSafeBalances = "getSafeBalances",
|
||||
signMessage = "signMessage",
|
||||
signTypedMessage = "signTypedMessage",
|
||||
getEnvironmentInfo = "getEnvironmentInfo",
|
||||
requestAddressBook = "requestAddressBook",
|
||||
wallet_getPermissions = "wallet_getPermissions",
|
||||
wallet_requestPermissions = "wallet_requestPermissions",
|
||||
}
|
||||
export declare type SDKRequestData<M extends Methods = Methods, P = unknown> = {
|
||||
id: RequestId;
|
||||
params: P;
|
||||
env: {
|
||||
sdkVersion: string;
|
||||
};
|
||||
method: M;
|
||||
};
|
||||
export declare type SDKMessageEvent = MessageEvent<SDKRequestData>;
|
||||
export declare type SendTransactionsResponse = {
|
||||
safeTxHash: string;
|
||||
};
|
||||
export enum RPC_AUTHENTICATION {
|
||||
API_KEY_PATH = "API_KEY_PATH",
|
||||
NO_AUTHENTICATION = "NO_AUTHENTICATION",
|
||||
UNKNOWN = "UNKNOWN",
|
||||
}
|
||||
export type RpcUri = {
|
||||
authentication: RPC_AUTHENTICATION;
|
||||
value: string;
|
||||
};
|
||||
export type BlockExplorerUriTemplate = {
|
||||
address: string;
|
||||
txHash: string;
|
||||
api: string;
|
||||
};
|
||||
export type NativeCurrency = {
|
||||
name: string;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
logoUri: string;
|
||||
};
|
||||
export type Theme = {
|
||||
textColor: string;
|
||||
backgroundColor: string;
|
||||
};
|
||||
export enum GAS_PRICE_TYPE {
|
||||
ORACLE = "ORACLE",
|
||||
FIXED = "FIXED",
|
||||
UNKNOWN = "UNKNOWN",
|
||||
}
|
||||
export type GasPriceOracle = {
|
||||
type: GAS_PRICE_TYPE.ORACLE;
|
||||
uri: string;
|
||||
gasParameter: string;
|
||||
gweiFactor: string;
|
||||
};
|
||||
export type GasPriceFixed = {
|
||||
type: GAS_PRICE_TYPE.FIXED;
|
||||
weiValue: string;
|
||||
};
|
||||
export type GasPriceUnknown = {
|
||||
type: GAS_PRICE_TYPE.UNKNOWN;
|
||||
};
|
||||
export type GasPrice = (GasPriceOracle | GasPriceFixed | GasPriceUnknown)[];
|
||||
export enum FEATURES {
|
||||
ERC721 = "ERC721",
|
||||
SAFE_APPS = "SAFE_APPS",
|
||||
CONTRACT_INTERACTION = "CONTRACT_INTERACTION",
|
||||
DOMAIN_LOOKUP = "DOMAIN_LOOKUP",
|
||||
SPENDING_LIMIT = "SPENDING_LIMIT",
|
||||
EIP1559 = "EIP1559",
|
||||
SAFE_TX_GAS_OPTIONAL = "SAFE_TX_GAS_OPTIONAL",
|
||||
TX_SIMULATION = "TX_SIMULATION",
|
||||
}
|
||||
export type _ChainInfo = {
|
||||
transactionService: string;
|
||||
chainId: string; // Restricted by what is returned by the CGW
|
||||
chainName: string;
|
||||
shortName: string;
|
||||
l2: boolean;
|
||||
description: string;
|
||||
rpcUri: RpcUri;
|
||||
safeAppsRpcUri: RpcUri;
|
||||
publicRpcUri: RpcUri;
|
||||
blockExplorerUriTemplate: BlockExplorerUriTemplate;
|
||||
nativeCurrency: NativeCurrency;
|
||||
theme: Theme;
|
||||
ensRegistryAddress?: string;
|
||||
gasPrice: GasPrice;
|
||||
disabledWallets: string[];
|
||||
features: FEATURES[];
|
||||
};
|
||||
export declare type ChainInfo = Pick<
|
||||
_ChainInfo,
|
||||
| "chainName"
|
||||
| "chainId"
|
||||
| "shortName"
|
||||
| "nativeCurrency"
|
||||
| "blockExplorerUriTemplate"
|
||||
>;
|
||||
export enum TransactionStatus {
|
||||
AWAITING_CONFIRMATIONS = "AWAITING_CONFIRMATIONS",
|
||||
AWAITING_EXECUTION = "AWAITING_EXECUTION",
|
||||
CANCELLED = "CANCELLED",
|
||||
FAILED = "FAILED",
|
||||
SUCCESS = "SUCCESS",
|
||||
PENDING = "PENDING",
|
||||
WILL_BE_REPLACED = "WILL_BE_REPLACED",
|
||||
}
|
||||
export type AddressEx = {
|
||||
value: string;
|
||||
name?: string;
|
||||
logoUri?: string;
|
||||
};
|
||||
export enum TransferDirection {
|
||||
INCOMING = "INCOMING",
|
||||
OUTGOING = "OUTGOING",
|
||||
UNKNOWN = "UNKNOWN",
|
||||
}
|
||||
export enum TransactionTokenType {
|
||||
ERC20 = "ERC20",
|
||||
ERC721 = "ERC721",
|
||||
NATIVE_COIN = "NATIVE_COIN",
|
||||
}
|
||||
export type Erc20Transfer = {
|
||||
type: TransactionTokenType.ERC20;
|
||||
tokenAddress: string;
|
||||
tokenName?: string;
|
||||
tokenSymbol?: string;
|
||||
logoUri?: string;
|
||||
decimals?: number;
|
||||
value: string;
|
||||
};
|
||||
export type Erc721Transfer = {
|
||||
type: TransactionTokenType.ERC721;
|
||||
tokenAddress: string;
|
||||
tokenId: string;
|
||||
tokenName?: string;
|
||||
tokenSymbol?: string;
|
||||
logoUri?: string;
|
||||
};
|
||||
export type NativeCoinTransfer = {
|
||||
type: TransactionTokenType.NATIVE_COIN;
|
||||
value: string;
|
||||
};
|
||||
export type TransferInfo = Erc20Transfer | Erc721Transfer | NativeCoinTransfer;
|
||||
export interface Transfer {
|
||||
type: "Transfer";
|
||||
sender: AddressEx;
|
||||
recipient: AddressEx;
|
||||
direction: TransferDirection;
|
||||
transferInfo: TransferInfo;
|
||||
}
|
||||
export type ParamValue = string | ParamValue[];
|
||||
export enum Operation {
|
||||
CALL = 0,
|
||||
DELEGATE = 1,
|
||||
}
|
||||
export type InternalTransaction = {
|
||||
operation: Operation;
|
||||
to: string;
|
||||
value?: string;
|
||||
data?: string;
|
||||
dataDecoded?: DataDecoded;
|
||||
};
|
||||
export type ValueDecodedType = InternalTransaction[];
|
||||
export type Parameter = {
|
||||
name: string;
|
||||
type: string;
|
||||
value: ParamValue;
|
||||
valueDecoded?: ValueDecodedType;
|
||||
};
|
||||
export type DataDecoded = {
|
||||
method: string;
|
||||
parameters?: Parameter[];
|
||||
};
|
||||
export enum SettingsInfoType {
|
||||
SET_FALLBACK_HANDLER = "SET_FALLBACK_HANDLER",
|
||||
ADD_OWNER = "ADD_OWNER",
|
||||
REMOVE_OWNER = "REMOVE_OWNER",
|
||||
SWAP_OWNER = "SWAP_OWNER",
|
||||
CHANGE_THRESHOLD = "CHANGE_THRESHOLD",
|
||||
CHANGE_IMPLEMENTATION = "CHANGE_IMPLEMENTATION",
|
||||
ENABLE_MODULE = "ENABLE_MODULE",
|
||||
DISABLE_MODULE = "DISABLE_MODULE",
|
||||
SET_GUARD = "SET_GUARD",
|
||||
DELETE_GUARD = "DELETE_GUARD",
|
||||
}
|
||||
export type SetFallbackHandler = {
|
||||
type: SettingsInfoType.SET_FALLBACK_HANDLER;
|
||||
handler: AddressEx;
|
||||
};
|
||||
export type AddOwner = {
|
||||
type: SettingsInfoType.ADD_OWNER;
|
||||
owner: AddressEx;
|
||||
threshold: number;
|
||||
};
|
||||
export type SettingsInfo =
|
||||
| SetFallbackHandler
|
||||
| AddOwner
|
||||
| RemoveOwner
|
||||
| SwapOwner
|
||||
| ChangeThreshold
|
||||
| ChangeImplementation
|
||||
| EnableModule
|
||||
| DisableModule
|
||||
| SetGuard
|
||||
| DeleteGuard;
|
||||
export type RemoveOwner = {
|
||||
type: SettingsInfoType.REMOVE_OWNER;
|
||||
owner: AddressEx;
|
||||
threshold: number;
|
||||
};
|
||||
export type SwapOwner = {
|
||||
type: SettingsInfoType.SWAP_OWNER;
|
||||
oldOwner: AddressEx;
|
||||
newOwner: AddressEx;
|
||||
};
|
||||
export type ChangeThreshold = {
|
||||
type: SettingsInfoType.CHANGE_THRESHOLD;
|
||||
threshold: number;
|
||||
};
|
||||
export type ChangeImplementation = {
|
||||
type: SettingsInfoType.CHANGE_IMPLEMENTATION;
|
||||
implementation: AddressEx;
|
||||
};
|
||||
export type EnableModule = {
|
||||
type: SettingsInfoType.ENABLE_MODULE;
|
||||
module: AddressEx;
|
||||
};
|
||||
export type DisableModule = {
|
||||
type: SettingsInfoType.DISABLE_MODULE;
|
||||
module: AddressEx;
|
||||
};
|
||||
export type SetGuard = {
|
||||
type: SettingsInfoType.SET_GUARD;
|
||||
guard: AddressEx;
|
||||
};
|
||||
export type DeleteGuard = {
|
||||
type: SettingsInfoType.DELETE_GUARD;
|
||||
};
|
||||
export type SettingsChange = {
|
||||
type: "SettingsChange";
|
||||
dataDecoded: DataDecoded;
|
||||
settingsInfo?: SettingsInfo;
|
||||
};
|
||||
export interface Custom {
|
||||
type: "Custom";
|
||||
to: AddressEx;
|
||||
dataSize: string;
|
||||
value: string;
|
||||
methodName?: string;
|
||||
actionCount?: number;
|
||||
isCancellation: boolean;
|
||||
}
|
||||
export type MultiSend = {
|
||||
type: "Custom";
|
||||
to: AddressEx;
|
||||
dataSize: string;
|
||||
value: string;
|
||||
methodName: "multiSend";
|
||||
actionCount: number;
|
||||
isCancellation: boolean;
|
||||
};
|
||||
export type Cancellation = Custom & {
|
||||
isCancellation: true;
|
||||
};
|
||||
export type Creation = {
|
||||
type: "Creation";
|
||||
creator: AddressEx;
|
||||
transactionHash: string;
|
||||
implementation?: AddressEx;
|
||||
factory?: AddressEx;
|
||||
};
|
||||
export type TransactionInfo =
|
||||
| Transfer
|
||||
| SettingsChange
|
||||
| Custom
|
||||
| MultiSend
|
||||
| Cancellation
|
||||
| Creation;
|
||||
export type TransactionData = {
|
||||
hexData?: string;
|
||||
dataDecoded?: DataDecoded;
|
||||
to: AddressEx;
|
||||
value?: string;
|
||||
operation: Operation;
|
||||
addressInfoIndex?: { [key: string]: AddressEx };
|
||||
trustedDelegateCallTarget: boolean;
|
||||
};
|
||||
export type ModuleExecutionDetails = {
|
||||
type: "MODULE";
|
||||
address: AddressEx;
|
||||
};
|
||||
export type MultisigConfirmation = {
|
||||
signer: AddressEx;
|
||||
signature?: string;
|
||||
submittedAt: number;
|
||||
};
|
||||
export enum TokenType {
|
||||
ERC20 = "ERC20",
|
||||
ERC721 = "ERC721",
|
||||
NATIVE_TOKEN = "NATIVE_TOKEN",
|
||||
}
|
||||
export type TokenInfo = {
|
||||
type: TokenType;
|
||||
address: string;
|
||||
decimals: number;
|
||||
symbol: string;
|
||||
name: string;
|
||||
logoUri: string;
|
||||
};
|
||||
export type MultisigExecutionDetails = {
|
||||
type: "MULTISIG";
|
||||
submittedAt: number;
|
||||
nonce: number;
|
||||
safeTxGas: string;
|
||||
baseGas: string;
|
||||
gasPrice: string;
|
||||
gasToken: string;
|
||||
refundReceiver: AddressEx;
|
||||
safeTxHash: string;
|
||||
executor?: AddressEx;
|
||||
signers: AddressEx[];
|
||||
confirmationsRequired: number;
|
||||
confirmations: MultisigConfirmation[];
|
||||
rejectors?: AddressEx[];
|
||||
gasTokenInfo?: TokenInfo;
|
||||
};
|
||||
export type DetailedExecutionInfo =
|
||||
| ModuleExecutionDetails
|
||||
| MultisigExecutionDetails;
|
||||
export type SafeAppInfo = {
|
||||
name: string;
|
||||
url: string;
|
||||
logoUri: string;
|
||||
};
|
||||
export type TransactionDetails = {
|
||||
txId: string;
|
||||
executedAt?: number;
|
||||
txStatus: TransactionStatus;
|
||||
txInfo: TransactionInfo;
|
||||
txData?: TransactionData;
|
||||
detailedExecutionInfo?: DetailedExecutionInfo;
|
||||
txHash?: string;
|
||||
safeAppInfo?: SafeAppInfo;
|
||||
};
|
||||
export declare type GatewayTransactionDetails = TransactionDetails;
|
||||
export type SafeBalanceResponse = {
|
||||
fiatTotal: string;
|
||||
items: Array<{
|
||||
tokenInfo: TokenInfo;
|
||||
balance: string;
|
||||
fiatBalance: string;
|
||||
fiatConversion: string;
|
||||
}>;
|
||||
};
|
||||
export declare type SafeBalances = SafeBalanceResponse;
|
||||
export declare type EnvironmentInfo = {
|
||||
origin: string;
|
||||
};
|
||||
export declare type AddressBookItem = {
|
||||
address: string;
|
||||
chainId: string;
|
||||
name: string;
|
||||
};
|
||||
export declare type PermissionCaveat = {
|
||||
type: string;
|
||||
value?: unknown;
|
||||
name?: string;
|
||||
};
|
||||
export declare type Permission = {
|
||||
parentCapability: string;
|
||||
invoker: string;
|
||||
date?: number;
|
||||
caveats?: PermissionCaveat[];
|
||||
};
|
||||
export interface MethodToResponse {
|
||||
[Methods.sendTransactions]: SendTransactionsResponse;
|
||||
[Methods.rpcCall]: unknown;
|
||||
[Methods.getSafeInfo]: SafeInfo;
|
||||
[Methods.getChainInfo]: ChainInfo;
|
||||
[Methods.getTxBySafeTxHash]: GatewayTransactionDetails;
|
||||
[Methods.getSafeBalances]: SafeBalances[];
|
||||
[Methods.signMessage]: SendTransactionsResponse;
|
||||
[Methods.signTypedMessage]: SendTransactionsResponse;
|
||||
[Methods.getEnvironmentInfo]: EnvironmentInfo;
|
||||
[Methods.requestAddressBook]: AddressBookItem[];
|
||||
[Methods.wallet_getPermissions]: Permission[];
|
||||
[Methods.wallet_requestPermissions]: Permission[];
|
||||
}
|
||||
export declare type ErrorResponse = {
|
||||
id: RequestId;
|
||||
success: false;
|
||||
error: string;
|
||||
version?: string;
|
||||
};
|
||||
export type SuccessResponse<T = MethodToResponse[Methods]> = {
|
||||
id: RequestId;
|
||||
data: T;
|
||||
version?: string;
|
||||
success: true;
|
||||
};
|
||||
export declare const RPC_CALLS: {
|
||||
readonly eth_call: "eth_call";
|
||||
readonly eth_gasPrice: "eth_gasPrice";
|
||||
readonly eth_getLogs: "eth_getLogs";
|
||||
readonly eth_getBalance: "eth_getBalance";
|
||||
readonly eth_getCode: "eth_getCode";
|
||||
readonly eth_getBlockByHash: "eth_getBlockByHash";
|
||||
readonly eth_getBlockByNumber: "eth_getBlockByNumber";
|
||||
readonly eth_getStorageAt: "eth_getStorageAt";
|
||||
readonly eth_getTransactionByHash: "eth_getTransactionByHash";
|
||||
readonly eth_getTransactionReceipt: "eth_getTransactionReceipt";
|
||||
readonly eth_getTransactionCount: "eth_getTransactionCount";
|
||||
readonly eth_estimateGas: "eth_estimateGas";
|
||||
};
|
||||
|
||||
export declare type RpcCallNames = keyof typeof RPC_CALLS;
|
||||
export declare type RPCPayload<P = unknown[]> = {
|
||||
call: RpcCallNames;
|
||||
params: P | unknown[];
|
||||
};
|
||||
export interface MethodToResponse {
|
||||
[Methods.sendTransactions]: SendTransactionsResponse;
|
||||
[Methods.rpcCall]: unknown;
|
||||
[Methods.getSafeInfo]: SafeInfo;
|
||||
[Methods.getChainInfo]: ChainInfo;
|
||||
[Methods.getTxBySafeTxHash]: GatewayTransactionDetails;
|
||||
[Methods.getSafeBalances]: SafeBalances[];
|
||||
[Methods.signMessage]: SendTransactionsResponse;
|
||||
[Methods.signTypedMessage]: SendTransactionsResponse;
|
||||
[Methods.getEnvironmentInfo]: EnvironmentInfo;
|
||||
[Methods.requestAddressBook]: AddressBookItem[];
|
||||
[Methods.wallet_getPermissions]: Permission[];
|
||||
[Methods.wallet_requestPermissions]: Permission[];
|
||||
}
|
||||
export declare type SignMessageParams = {
|
||||
message: string;
|
||||
};
|
||||
export interface TypedDataDomain {
|
||||
name?: string;
|
||||
version?: string;
|
||||
chainId?: BigNumberish;
|
||||
verifyingContract?: string;
|
||||
salt?: BytesLike;
|
||||
}
|
||||
export interface TypedDataTypes {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
export declare type TypedMessageTypes = {
|
||||
[key: string]: TypedDataTypes[];
|
||||
};
|
||||
export declare type EIP712TypedData = {
|
||||
domain: TypedDataDomain;
|
||||
types: TypedMessageTypes;
|
||||
message: Record<string, any>;
|
||||
};
|
||||
export declare type SignTypedMessageParams = {
|
||||
typedData: EIP712TypedData;
|
||||
};
|
||||
export interface Transaction {
|
||||
to: string;
|
||||
value: string;
|
||||
data: string;
|
||||
}
|
||||
Reference in New Issue
Block a user