2025-03-26 11:42:48 -07:00

225 lines
6.1 KiB
TypeScript

import { useEffect, useCallback, useState } from "react";
import sdk, { type FrameContext } from "@farcaster/frame-sdk";
import {
useAccount,
useSendTransaction,
useSignMessage,
useSignTypedData,
useWaitForTransactionReceipt,
} from "wagmi";
import { Button } from "~/components/ui/Button";
import { truncateAddress } from "~/lib/truncateAddress";
export default function Demo() {
const [isSDKLoaded, setIsSDKLoaded] = useState(false);
const [context, setContext] = useState<FrameContext>();
const [isContextOpen, setIsContextOpen] = useState(false);
const [txHash, setTxHash] = useState<string | null>(null);
const { address, isConnected } = useAccount();
const {
sendTransaction,
error: sendTxError,
isError: isSendTxError,
isPending: isSendTxPending,
} = useSendTransaction();
const {
signMessage,
error: signError,
isError: isSignError,
isPending: isSignPending,
} = useSignMessage();
const {
signTypedData,
error: signTypedError,
isError: isSignTypedError,
isPending: isSignTypedPending,
} = useSignTypedData();
const { isLoading: isConfirming, isSuccess: isConfirmed } =
useWaitForTransactionReceipt({
hash: txHash as `0x${string}`,
});
useEffect(() => {
const load = async () => {
setContext(await sdk.context);
sdk.actions.ready();
};
if (sdk && !isSDKLoaded) {
setIsSDKLoaded(true);
load();
}
}, [isSDKLoaded]);
const openUrl = useCallback(() => {
sdk.actions.openUrl("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
}, []);
const close = useCallback(() => {
sdk.actions.close();
}, []);
const sendTx = useCallback(() => {
sendTransaction(
{
to: "0x4bBFD120d9f352A0BEd7a014bd67913a2007a878",
data: "0x9846cd9efc000023c0",
},
{
onSuccess: (hash) => {
setTxHash(hash);
},
}
);
}, [sendTransaction]);
const sign = useCallback(() => {
signMessage({ message: "Hello from Frames v2!" });
}, [signMessage]);
const signTyped = useCallback(() => {
signTypedData({
domain: {
name: "Frames v2 Demo",
version: "1",
chainId: 8453,
},
types: {
Message: [{ name: "content", type: "string" }],
},
message: {
content: "Hello from Frames v2!",
},
primaryType: "Message",
});
}, [signTypedData]);
const toggleContext = useCallback(() => {
setIsContextOpen((prev) => !prev);
}, []);
const renderError = (error: Error | null) => {
if (!error) return null;
return <div className="text-red-500 text-xs mt-1">{error.message}</div>;
};
if (!isSDKLoaded) {
return <div>Loading...</div>;
}
return (
<div className="w-[300px] mx-auto py-4 px-2">
<h1 className="text-2xl font-bold text-center mb-4">Frames v2 Demo</h1>
<div className="mb-4">
<h2 className="font-2xl font-bold">Context</h2>
<button
onClick={toggleContext}
className="flex items-center gap-2 transition-colors"
>
<span
className={`transform transition-transform ${
isContextOpen ? "rotate-90" : ""
}`}
>
</span>
Tap to expand
</button>
{isContextOpen && (
<div className="p-4 mt-2 bg-gray-100 dark:bg-gray-800 rounded-lg">
<pre className="font-mono text-xs whitespace-pre-wrap break-words max-w-[260px] overflow-x-">
{JSON.stringify(context, null, 2)}
</pre>
</div>
)}
</div>
<div>
<h2 className="font-2xl font-bold">Actions</h2>
<div className="mb-4">
<div className="p-2 bg-gray-100 dark:bg-gray-800 rounded-lg my-2">
<pre className="font-mono text-xs whitespace-pre-wrap break-words max-w-[260px] overflow-x-">
sdk.actions.openUrl
</pre>
</div>
<Button onClick={openUrl}>Open Link</Button>
</div>
<div className="mb-4">
<div className="p-2 bg-gray-100 dark:bg-gray-800 rounded-lg my-2">
<pre className="font-mono text-xs whitespace-pre-wrap break-words max-w-[260px] overflow-x-">
sdk.actions.close
</pre>
</div>
<Button onClick={close}>Close Frame</Button>
</div>
</div>
<div>
<h2 className="font-2xl font-bold">Wallet</h2>
{address && (
<div className="my-2 text-xs">
Address: <pre className="inline">{truncateAddress(address)}</pre>
</div>
)}
{isConnected && (
<>
<div className="mb-4">
<Button
onClick={sendTx}
disabled={!isConnected || isSendTxPending}
isLoading={isSendTxPending}
>
Send Transaction
</Button>
{isSendTxError && renderError(sendTxError)}
{txHash && (
<div className="mt-2 text-xs">
<div>Hash: {truncateAddress(txHash)}</div>
<div>
Status:{" "}
{isConfirming
? "Confirming..."
: isConfirmed
? "Confirmed!"
: "Pending"}
</div>
</div>
)}
</div>
<div className="mb-4">
<Button
onClick={sign}
disabled={!isConnected || isSignPending}
isLoading={isSignPending}
>
Sign Message
</Button>
{isSignError && renderError(signError)}
</div>
<div className="mb-4">
<Button
onClick={signTyped}
disabled={!isConnected || isSignTypedPending}
isLoading={isSignTypedPending}
>
Sign Typed Data
</Button>
{isSignTypedError && renderError(signTypedError)}
</div>
</>
)}
</div>
</div>
);
}