"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { Input } from "../components/ui/input"; import { signIn, signOut, getCsrfToken } from "next-auth/react"; import sdk, { SignIn as SignInCore, } from "@farcaster/frame-sdk"; import { useAccount, useSendTransaction, useSignMessage, useSignTypedData, useWaitForTransactionReceipt, useDisconnect, useConnect, useSwitchChain, useChainId, } from "wagmi"; import { config } from "~/components/providers/WagmiProvider"; import { Button } from "~/components/ui/Button"; import { truncateAddress } from "~/lib/truncateAddress"; import { base, degen, mainnet, optimism, unichain } from "wagmi/chains"; import { BaseError, UserRejectedRequestError } from "viem"; import { useSession } from "next-auth/react"; import { Label } from "~/components/ui/label"; import { useFrame } from "~/components/providers/FrameProvider"; export default function Demo( { title }: { title?: string } = { title: "Frames v2 Demo" } ) { const { isSDKLoaded, context, added, notificationDetails, lastEvent, addFrame, addFrameResult, openUrl, close } = useFrame(); const [isContextOpen, setIsContextOpen] = useState(false); const [txHash, setTxHash] = useState(null); const [sendNotificationResult, setSendNotificationResult] = useState(""); const { address, isConnected } = useAccount(); const chainId = useChainId(); useEffect(() => { console.log("isSDKLoaded", isSDKLoaded); console.log("context", context); console.log("address", address); console.log("isConnected", isConnected); console.log("chainId", chainId); }, [context, address, isConnected, chainId, isSDKLoaded]); const { sendTransaction, error: sendTxError, isError: isSendTxError, isPending: isSendTxPending, } = useSendTransaction(); const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash: txHash as `0x${string}`, }); const { signTypedData, error: signTypedError, isError: isSignTypedError, isPending: isSignTypedPending, } = useSignTypedData(); const { disconnect } = useDisconnect(); const { connect, connectors } = useConnect(); const { switchChain, error: switchChainError, isError: isSwitchChainError, isPending: isSwitchChainPending, } = useSwitchChain(); const nextChain = useMemo(() => { if (chainId === base.id) { return optimism; } else if (chainId === optimism.id) { return degen; } else if (chainId === degen.id) { return mainnet; } else if (chainId === mainnet.id) { return unichain; } else { return base; } }, [chainId]); const handleSwitchChain = useCallback(() => { switchChain({ chainId: nextChain.id }); }, [switchChain, nextChain.id]); const sendNotification = useCallback(async () => { setSendNotificationResult(""); if (!notificationDetails || !context) { return; } try { const response = await fetch("/api/send-notification", { method: "POST", mode: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ fid: context.user.fid, notificationDetails, }), }); if (response.status === 200) { setSendNotificationResult("Success"); return; } else if (response.status === 429) { setSendNotificationResult("Rate limited"); return; } const data = await response.text(); setSendNotificationResult(`Error: ${data}`); } catch (error) { setSendNotificationResult(`Error: ${error}`); } }, [context, notificationDetails]); const sendTx = useCallback(() => { sendTransaction( { // call yoink() on Yoink contract to: "0x4bBFD120d9f352A0BEd7a014bd67913a2007a878", data: "0x9846cd9efc000023c0", }, { onSuccess: (hash) => { setTxHash(hash); }, } ); }, [sendTransaction]); const signTyped = useCallback(() => { signTypedData({ domain: { name: "Frames v2 Demo", version: "1", chainId, }, types: { Message: [{ name: "content", type: "string" }], }, message: { content: "Hello from Frames v2!", }, primaryType: "Message", }); }, [chainId, signTypedData]); const toggleContext = useCallback(() => { setIsContextOpen((prev) => !prev); }, []); if (!isSDKLoaded) { return
Loading...
; } return (

{title}

Context

{isContextOpen && (
                {JSON.stringify(context, null, 2)}
              
)}

Actions

                sdk.actions.signIn
              
                sdk.actions.openUrl
              
                sdk.actions.viewProfile
              
                sdk.actions.close
              

Last event

              {lastEvent || "none"}
            

Add to client & notifications

Client fid {context?.client.clientFid}, {added ? " frame added to client," : " frame not added to client,"} {notificationDetails ? " notifications enabled" : " notifications disabled"}
                sdk.actions.addFrame
              
{addFrameResult && (
Add frame result: {addFrameResult}
)}
{sendNotificationResult && (
Send notification result: {sendNotificationResult}
)}

Wallet

{address && (
Address:
{truncateAddress(address)}
)} {chainId && (
Chain ID:
{chainId}
)}
{isConnected ? ( ) : context ? ( /* if context is not null, mini app is running in frame client */ ) : ( /* if context is null, mini app is running in browser */
)}
{isConnected && ( <>
{isSendTxError && renderError(sendTxError)} {txHash && (
Hash: {truncateAddress(txHash)}
Status:{" "} {isConfirming ? "Confirming..." : isConfirmed ? "Confirmed!" : "Pending"}
)}
{isSignTypedError && renderError(signTypedError)}
{isSwitchChainError && renderError(switchChainError)}
)}
); } function SignMessage() { const { isConnected } = useAccount(); const { connectAsync } = useConnect(); const { signMessage, data: signature, error: signError, isError: isSignError, isPending: isSignPending, } = useSignMessage(); const handleSignMessage = useCallback(async () => { if (!isConnected) { await connectAsync({ chainId: base.id, connector: config.connectors[0], }); } signMessage({ message: "Hello from Frames v2!" }); }, [connectAsync, isConnected, signMessage]); return ( <> {isSignError && renderError(signError)} {signature && (
Signature: {signature}
)} ); } function SendEth() { const { isConnected, chainId } = useAccount(); const { sendTransaction, data, error: sendTxError, isError: isSendTxError, isPending: isSendTxPending, } = useSendTransaction(); const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash: data, }); const toAddr = useMemo(() => { // Protocol guild address return chainId === base.id ? "0x32e3C7fD24e175701A35c224f2238d18439C7dBC" : "0xB3d8d7887693a9852734b4D25e9C0Bb35Ba8a830"; }, [chainId]); const handleSend = useCallback(() => { sendTransaction({ to: toAddr, value: 1n, }); }, [toAddr, sendTransaction]); return ( <> {isSendTxError && renderError(sendTxError)} {data && (
Hash: {truncateAddress(data)}
Status:{" "} {isConfirming ? "Confirming..." : isConfirmed ? "Confirmed!" : "Pending"}
)} ); } function SignIn() { const [signingIn, setSigningIn] = useState(false); const [signingOut, setSigningOut] = useState(false); const [signInResult, setSignInResult] = useState(); const [signInFailure, setSignInFailure] = useState(); const { data: session, status } = useSession(); const getNonce = useCallback(async () => { const nonce = await getCsrfToken(); if (!nonce) throw new Error("Unable to generate nonce"); return nonce; }, []); const handleSignIn = useCallback(async () => { try { setSigningIn(true); setSignInFailure(undefined); const nonce = await getNonce(); const result = await sdk.actions.signIn({ nonce }); setSignInResult(result); await signIn("credentials", { message: result.message, signature: result.signature, redirect: false, }); } catch (e) { if (e instanceof SignInCore.RejectedByUser) { setSignInFailure("Rejected by user"); return; } setSignInFailure("Unknown error"); } finally { setSigningIn(false); } }, [getNonce]); const handleSignOut = useCallback(async () => { try { setSigningOut(true); await signOut({ redirect: false }); setSignInResult(undefined); } finally { setSigningOut(false); } }, []); return ( <> {status !== "authenticated" && ( )} {status === "authenticated" && ( )} {session && (
Session
{JSON.stringify(session, null, 2)}
)} {signInFailure && !signingIn && (
SIWF Result
{signInFailure}
)} {signInResult && !signingIn && (
SIWF Result
{JSON.stringify(signInResult, null, 2)}
)} ); } function ViewProfile() { const [fid, setFid] = useState("3"); return ( <>
{ setFid(e.target.value); }} step="1" min="1" />
); } const renderError = (error: Error | null) => { if (!error) return null; if (error instanceof BaseError) { const isUserRejection = error.walk( (e) => e instanceof UserRejectedRequestError ); if (isUserRejection) { return
Rejected by user.
; } } return
{error.message}
; };