From a66e219438b3071cc56788e6a2f971e25017af35 Mon Sep 17 00:00:00 2001 From: lucas-neynar Date: Tue, 18 Mar 2025 10:02:05 -0700 Subject: [PATCH] feat: use saved frame manifest from build script --- README.md | 21 ++++++++++-- package-lock.json | 22 ++++++++++-- package.json | 5 +-- scripts/build.js | 34 ++++++++++-------- src/app/.well-known/farcaster.json/route.ts | 4 +-- src/app/page.tsx | 11 +++--- src/lib/utils.ts | 38 ++++++++++++++++++--- 7 files changed, 102 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 7dbc19a..b602fa3 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,26 @@ A Farcaster Frames v2 quickstart npx script. -## Getting Started - This is a [NextJS](https://nextjs.org/) + TypeScript + React app. +## Getting Started + To create a new frames project, run: ```{bash} -npx create-neynar-farcaster-frame +npx create-neynar-farcaster-frame@latest ``` + +To run the project: +```{bash} +cd yourProjectName +npm run dev +``` + +## Building for Production + +To create a production build, run: +```{bash} +npm run build +``` + +The above command will generate a `.env` file based on the `.env.local` file and user input. Be sure to configure those environment variables on your hosting platform. diff --git a/package-lock.json b/package-lock.json index d803322..06e8637 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "create-neynar-farcaster-frame", - "version": "1.0.7", + "version": "1.0.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "create-neynar-farcaster-frame", - "version": "1.0.7", + "version": "1.0.11", "dependencies": { "dotenv": "^16.4.7", "inquirer": "^12.4.3", @@ -17,6 +17,7 @@ }, "devDependencies": { "@neynar/nodejs-sdk": "^2.19.0", + "@types/node": "^22.13.10", "typescript": "^5.6.3" } }, @@ -630,6 +631,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.13.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", + "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, "node_modules/abitype": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", @@ -2414,6 +2425,13 @@ "node": ">=8" } }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", diff --git a/package.json b/package.json index 2393d0f..43bf41c 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "viem": "^2.23.6" }, "devDependencies": { - "typescript": "^5.6.3", - "@neynar/nodejs-sdk": "^2.19.0" + "@neynar/nodejs-sdk": "^2.19.0", + "@types/node": "^22.13.10", + "typescript": "^5.6.3" } } diff --git a/scripts/build.js b/scripts/build.js index 8be2a5c..a70738a 100755 --- a/scripts/build.js +++ b/scripts/build.js @@ -129,22 +129,28 @@ async function main() { } ]); - // Get seed phrase from user - const { seedPhrase } = await inquirer.prompt([ - { - type: 'password', - name: 'seedPhrase', - message: 'Enter your seed phrase (this will only be used to sign the frame manifest):', - validate: async (input) => { - try { - await validateSeedPhrase(input); - return true; - } catch (error) { - return error.message; + // Get seed phrase from user if not already in .env.local + let seedPhrase = process.env.SEED_PHRASE; + if (!seedPhrase) { + const { seedPhrase: inputSeedPhrase } = await inquirer.prompt([ + { + type: 'password', + name: 'seedPhrase', + message: 'Enter your seed phrase (this will only be used to sign the frame manifest):', + validate: async (input) => { + try { + await validateSeedPhrase(input); + return true; + } catch (error) { + return error.message; + } } } - } - ]); + ]); + seedPhrase = inputSeedPhrase; + } else { + console.log('Using existing seed phrase from .env.local'); + } // Validate seed phrase and get account address const accountAddress = await validateSeedPhrase(seedPhrase); diff --git a/src/app/.well-known/farcaster.json/route.ts b/src/app/.well-known/farcaster.json/route.ts index e42b32d..19ce6d7 100644 --- a/src/app/.well-known/farcaster.json/route.ts +++ b/src/app/.well-known/farcaster.json/route.ts @@ -1,9 +1,9 @@ import { NextResponse } from 'next/server'; -import { generateFarcasterMetadata } from '../../../lib/utils'; +import { getFarcasterMetadata } from '../../../lib/utils'; export async function GET() { try { - const config = await generateFarcasterMetadata(); + const config = await getFarcasterMetadata(); return NextResponse.json(config); } catch (error) { console.error('Error generating metadata:', error); diff --git a/src/app/page.tsx b/src/app/page.tsx index 9cb8d85..e9794cf 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -4,16 +4,15 @@ import App from "./app"; const appUrl = process.env.NEXT_PUBLIC_URL; // frame preview metadata -// question: do we need metadata both in this file and in the .well-known/farcaster.json file? -const appName = process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo"; +const appName = process.env.NEXT_PUBLIC_FRAME_NAME; const splashImageUrl = process.env.NEXT_PUBLIC_FRAME_SPLASH_IMAGE_URL || `${appUrl}/splash.png`; const iconUrl = process.env.NEXT_PUBLIC_FRAME_ICON_IMAGE_URL || `${appUrl}/icon.png`; -const frame = { +const framePreviewMetadata = { version: "next", imageUrl: `${appUrl}/opengraph-image`, button: { - title: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT || "Launch Frame", + title: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT, action: { type: "launch_frame", name: appName, @@ -32,10 +31,10 @@ export async function generateMetadata(): Promise { title: appName, openGraph: { title: appName, - description: process.env.NEXT_PUBLIC_FRAME_DESCRIPTION || "A Farcaster Frames v2 demo app.", + description: process.env.NEXT_PUBLIC_FRAME_DESCRIPTION, }, other: { - "fc:frame": JSON.stringify(frame), + "fc:frame": JSON.stringify(framePreviewMetadata), }, }; } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index a81b9fe..a2436f3 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,9 +1,28 @@ -import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; import { mnemonicToAccount } from 'viem/accounts'; +interface FrameMetadata { + accountAssociation?: { + header: string; + payload: string; + signature: string; + }; + frame: { + version: string; + name: string; + iconUrl: string; + homeUrl: string; + imageUrl: string; + buttonTitle: string; + splashImageUrl: string; + splashBackgroundColor: string; + webhookUrl: string; + }; +} + export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) + return twMerge(clsx(inputs)); } export function getSecretEnvVars() { @@ -17,7 +36,18 @@ export function getSecretEnvVars() { return { seedPhrase, fid }; } -export async function generateFarcasterMetadata() { +export async function getFarcasterMetadata(): Promise { + // First check for FRAME_METADATA in .env and use that if it exists + if (process.env.FRAME_METADATA) { + try { + const metadata = JSON.parse(process.env.FRAME_METADATA); + console.log('Using pre-signed frame metadata from environment'); + return metadata; + } catch (error) { + console.warn('Failed to parse FRAME_METADATA from environment:', error); + } + } + const appUrl = process.env.NEXT_PUBLIC_URL; if (!appUrl) { throw new Error('NEXT_PUBLIC_URL not configured');