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 | null; latestTransaction: TransactionWithId | undefined; setAddress: React.Dispatch>; setAppUrl: React.Dispatch>; setRpcUrl: React.Dispatch>; sendMessageToIFrame: Function; }; export const SafeInjectContext = createContext({ 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 = ({ children, }) => { const [address, setAddress] = useState(); const [appUrl, setAppUrl] = useState(); const [rpcUrl, setRpcUrl] = useState(); const [provider, setProvider] = useState(); const [latestTransaction, setLatestTransaction] = useState(); const iframeRef = useRef(null); const communicator = useAppCommunicator(iframeRef); const sendMessageToIFrame = useCallback( function ( message: InterfaceMessageProps, 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 ( {children} ); }; export const useSafeInject = () => useContext(SafeInjectContext);