Files
impersonator/contexts/SafeInjectContext.tsx
2024-05-07 00:18:18 +10:00

172 lines
4.4 KiB
TypeScript

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);