feat: auto-generate manifest

This commit is contained in:
lucas-neynar
2025-03-13 12:39:59 -07:00
parent 51f8c92eb3
commit b168e34521
14 changed files with 289 additions and 29 deletions

View File

@@ -1,17 +1,24 @@
import { readFileSync } from 'fs';
import { join } from 'path';
export async function GET() {
const appUrl = process.env.NEXT_PUBLIC_URL;
let accountAssociation; // TODO: add type
try {
const manifestPath = join(process.cwd(), 'public/manifest.json');
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
accountAssociation = manifest;
} catch (error) {
console.warn('Warning: manifest.json not found or invalid. Frame will not be associated with an account.');
accountAssociation = null;
}
const config = {
accountAssociation: {
header:
"eyJmaWQiOjM2MjEsInR5cGUiOiJjdXN0b2R5Iiwia2V5IjoiMHgyY2Q4NWEwOTMyNjFmNTkyNzA4MDRBNkVBNjk3Q2VBNENlQkVjYWZFIn0",
payload: "eyJkb21haW4iOiJmcmFtZXMtdjIudmVyY2VsLmFwcCJ9",
signature:
"MHhiNDIwMzQ1MGZkNzgzYTExZjRiOTllZTFlYjA3NmMwOTdjM2JkOTY1NGM2ODZjYjkyZTAyMzk2Y2Q0YjU2MWY1MjY5NjI5ZGQ5NTliYjU0YzEwOGI4OGVmNjdjMTVlZTdjZDc2YTRiMGU5NzkzNzA3YzkxYzFkOWFjNTg0YmQzNjFi",
},
...(accountAssociation && { accountAssociation }),
frame: {
version: "1",
name: "Frames v2 Demo",
name: process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo",
iconUrl: `${appUrl}/icon.png`,
homeUrl: appUrl,
imageUrl: `${appUrl}/frames/hello/opengraph-image`,

View File

@@ -2,6 +2,8 @@
import dynamic from "next/dynamic";
// note: dynamic import is required for components that use the Frame SDK
const Demo = dynamic(() => import("~/components/Demo"), {
ssr: false,
});

View File

@@ -19,7 +19,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
title: "Launch Frame",
action: {
type: "launch_frame",
name: "Farcaster Frames v2 Demo",
name: process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo",
url: `${appUrl}/frames/hello/${name}/`,
splashImageUrl: `${appUrl}/splash.png`,
splashBackgroundColor: "#f7f7f7",

View File

@@ -10,7 +10,7 @@ const frame = {
title: "Launch Frame",
action: {
type: "launch_frame",
name: "Farcaster Frames v2 Demo",
name: process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo",
url: `${appUrl}/frames/hello/`,
splashImageUrl: `${appUrl}/splash.png`,
splashBackgroundColor: "#f7f7f7",

View File

@@ -5,8 +5,8 @@ import "~/app/globals.css";
import { Providers } from "~/app/providers";
export const metadata: Metadata = {
title: "Farcaster Frames v2 Demo",
description: "A Farcaster Frames v2 demo app",
title: process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo",
description: process.env.NEXT_PUBLIC_FRAME_DESCRIPTION || "A Farcaster Frames v2 demo app",
};
export default async function RootLayout({

View File

@@ -1,6 +1,6 @@
import { ImageResponse } from "next/og";
export const alt = "Farcaster Frames V2 Demo";
export const alt = process.env.NEXT_PUBLIC_FRAME_NAME || "Frames V2 Demo";
export const size = {
width: 600,
height: 400,
@@ -8,11 +8,13 @@ export const size = {
export const contentType = "image/png";
// dynamically generated OG image for frame preview
// TODO: make this dynamic with user info (like robin's example)
export default async function Image() {
return new ImageResponse(
(
<div tw="h-full w-full flex flex-col justify-center items-center relative bg-white">
<h1 tw="text-6xl">Frames v2 Demo</h1>
<h1 tw="text-6xl">{alt}</h1>
</div>
),
{

View File

@@ -3,6 +3,9 @@ import App from "./app";
const appUrl = process.env.NEXT_PUBLIC_URL;
// frame preview metadata
const appName = process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo";
const frame = {
version: "next",
imageUrl: `${appUrl}/opengraph-image`,
@@ -10,7 +13,7 @@ const frame = {
title: "Launch Frame",
action: {
type: "launch_frame",
name: "Farcaster Frames v2 Demo",
name: appName,
url: appUrl,
splashImageUrl: `${appUrl}/splash.png`,
splashBackgroundColor: "#f7f7f7",
@@ -22,10 +25,10 @@ export const revalidate = 300;
export async function generateMetadata(): Promise<Metadata> {
return {
title: "Farcaster Frames v2 Demo",
title: appName,
openGraph: {
title: "Farcaster Frames v2 Demo",
description: "A Farcaster Frames v2 demo app.",
title: appName,
description: process.env.NEXT_PUBLIC_FRAME_DESCRIPTION || "A Farcaster Frames v2 demo app.",
},
other: {
"fc:frame": JSON.stringify(frame),

View File

@@ -29,6 +29,7 @@ export const authOptions: AuthOptions = {
// In a production app with a server, these should be fetched from
// your Farcaster data indexer rather than have them accepted as part
// of credentials.
// question: should these natively use the Neynar API?
name: {
label: "Name",
type: "text",
@@ -49,6 +50,7 @@ export const authOptions: AuthOptions = {
const verifyResponse = await appClient.verifySignInMessage({
message: credentials?.message as string,
signature: credentials?.signature as `0x${string}`,
// question: what domain should this be?
domain: new URL(process.env.NEXTAUTH_URL ?? '').hostname,
nonce: csrfToken,
});