import { ConnectButton, getDefaultWallets, RainbowKitProvider, darkTheme, lightTheme, } from "@rainbow-me/rainbowkit"; import "@rainbow-me/rainbowkit/styles.css"; import { useTheme } from "next-themes"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { Trans } from "react-i18next"; import { configureChains, createClient, WagmiConfig } from "wagmi"; import { useAccount, useSignMessage } from "wagmi"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import { Icon } from "@calcom/ui/Icon"; import { SkeletonText } from "@calcom/ui/v2"; import showToast from "@calcom/ui/v2/core/notifications"; import { getProviders, ETH_MESSAGE, SUPPORTED_CHAINS } from "../utils/ethereum"; const { chains, provider } = configureChains(SUPPORTED_CHAINS, getProviders()); const { connectors } = getDefaultWallets({ appName: "Cal.com", chains, }); const wagmiClient = createClient({ autoConnect: true, connectors, provider, }); type RainbowGateProps = { children: React.ReactNode; setToken: (_: string) => void; chainId: number; tokenAddress: string; }; const RainbowGate: React.FC = (props) => { const { resolvedTheme: theme } = useTheme(); const [rainbowTheme, setRainbowTheme] = useState(theme === "dark" ? darkTheme() : lightTheme()); useEffect(() => { theme === "dark" ? setRainbowTheme(darkTheme()) : setRainbowTheme(lightTheme()); }, [theme]); return ( chain.id === props.chainId)} theme={rainbowTheme}> ); }; // The word "token" is used for two differenct concepts here: `setToken` is the token for // the Gate while `useToken` is a hook used to retrieve the Ethereum token. const BalanceCheck: React.FC = ({ chainId, setToken, tokenAddress }) => { const { t } = useLocale(); const { address } = useAccount(); const { data: signedMessage, isLoading: isSignatureLoading, isError: isSignatureError, signMessage, } = useSignMessage({ message: ETH_MESSAGE, }); const { data: contractData, isLoading: isContractLoading } = trpc.viewer.eth.contract.useQuery({ address: tokenAddress, chainId, }); const { data: balanceData, isLoading: isBalanceLoading } = trpc.viewer.eth.balance.useQuery( { address: address || "", tokenAddress, chainId }, { enabled: !!address, } ); // The token may have already been set in the query params, so we can extract it here const router = useRouter(); const { ethSignature, ...routerQuery } = router.query; const isLoading = isContractLoading || isBalanceLoading; // Any logic here will unlock the gate by setting the token to the user's wallet signature useEffect(() => { // If the `ethSignature` is found, remove it from the URL bar and propogate back up if (ethSignature !== undefined) { // Remove the `ethSignature` param but keep all others router.replace({ query: { ...routerQuery } }); setToken(ethSignature as string); } if (balanceData && balanceData.data) { if (balanceData.data.hasBalance) { if (signedMessage) { showToast("Wallet verified.", "success"); setToken(signedMessage); } else if (router.isReady && !ethSignature) { signMessage(); } } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [router.isReady, balanceData, setToken, signedMessage, signMessage]); return (

Token Gate

{isLoading && ( <> )} {!isLoading && contractData && contractData.data && ( <>

Connect your wallet if you own {contractData.data.name} ({contractData.data.symbol}) .

{balanceData && balanceData.data && ( <> {!balanceData.data.hasBalance && (

Your connected wallet doesn't contain enough {contractData.data.symbol}.

)} {balanceData.data.hasBalance && isSignatureLoading && (

{t("rainbow_sign_message_request")}

)} )} {isSignatureError && (

{t("rainbow_signature_error")}

)} )}
); }; export default RainbowGate;