mirror of
https://github.com/neynarxyz/create-farcaster-mini-app.git
synced 2025-11-16 08:08:56 -05:00
225 lines
6.1 KiB
TypeScript
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>
|
|
);
|
|
}
|