feat: use saved frame manifest from build script

This commit is contained in:
lucas-neynar 2025-03-18 10:02:05 -07:00
parent 583054cefb
commit a66e219438
No known key found for this signature in database
7 changed files with 102 additions and 33 deletions

View File

@ -2,11 +2,26 @@
A Farcaster Frames v2 quickstart npx script. A Farcaster Frames v2 quickstart npx script.
## Getting Started
This is a [NextJS](https://nextjs.org/) + TypeScript + React app. This is a [NextJS](https://nextjs.org/) + TypeScript + React app.
## Getting Started
To create a new frames project, run: To create a new frames project, run:
```{bash} ```{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.

22
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "create-neynar-farcaster-frame", "name": "create-neynar-farcaster-frame",
"version": "1.0.7", "version": "1.0.11",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "create-neynar-farcaster-frame", "name": "create-neynar-farcaster-frame",
"version": "1.0.7", "version": "1.0.11",
"dependencies": { "dependencies": {
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"inquirer": "^12.4.3", "inquirer": "^12.4.3",
@ -17,6 +17,7 @@
}, },
"devDependencies": { "devDependencies": {
"@neynar/nodejs-sdk": "^2.19.0", "@neynar/nodejs-sdk": "^2.19.0",
"@types/node": "^22.13.10",
"typescript": "^5.6.3" "typescript": "^5.6.3"
} }
}, },
@ -630,6 +631,16 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/abitype": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz",
@ -2414,6 +2425,13 @@
"node": ">=8" "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": { "node_modules/universalify": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",

View File

@ -28,7 +28,8 @@
"viem": "^2.23.6" "viem": "^2.23.6"
}, },
"devDependencies": { "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"
} }
} }

View File

@ -129,22 +129,28 @@ async function main() {
} }
]); ]);
// Get seed phrase from user // Get seed phrase from user if not already in .env.local
const { seedPhrase } = await inquirer.prompt([ let seedPhrase = process.env.SEED_PHRASE;
{ if (!seedPhrase) {
type: 'password', const { seedPhrase: inputSeedPhrase } = await inquirer.prompt([
name: 'seedPhrase', {
message: 'Enter your seed phrase (this will only be used to sign the frame manifest):', type: 'password',
validate: async (input) => { name: 'seedPhrase',
try { message: 'Enter your seed phrase (this will only be used to sign the frame manifest):',
await validateSeedPhrase(input); validate: async (input) => {
return true; try {
} catch (error) { await validateSeedPhrase(input);
return error.message; 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 // Validate seed phrase and get account address
const accountAddress = await validateSeedPhrase(seedPhrase); const accountAddress = await validateSeedPhrase(seedPhrase);

View File

@ -1,9 +1,9 @@
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { generateFarcasterMetadata } from '../../../lib/utils'; import { getFarcasterMetadata } from '../../../lib/utils';
export async function GET() { export async function GET() {
try { try {
const config = await generateFarcasterMetadata(); const config = await getFarcasterMetadata();
return NextResponse.json(config); return NextResponse.json(config);
} catch (error) { } catch (error) {
console.error('Error generating metadata:', error); console.error('Error generating metadata:', error);

View File

@ -4,16 +4,15 @@ import App from "./app";
const appUrl = process.env.NEXT_PUBLIC_URL; const appUrl = process.env.NEXT_PUBLIC_URL;
// frame preview metadata // 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;
const appName = process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo";
const splashImageUrl = process.env.NEXT_PUBLIC_FRAME_SPLASH_IMAGE_URL || `${appUrl}/splash.png`; 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 iconUrl = process.env.NEXT_PUBLIC_FRAME_ICON_IMAGE_URL || `${appUrl}/icon.png`;
const frame = { const framePreviewMetadata = {
version: "next", version: "next",
imageUrl: `${appUrl}/opengraph-image`, imageUrl: `${appUrl}/opengraph-image`,
button: { button: {
title: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT || "Launch Frame", title: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT,
action: { action: {
type: "launch_frame", type: "launch_frame",
name: appName, name: appName,
@ -32,10 +31,10 @@ export async function generateMetadata(): Promise<Metadata> {
title: appName, title: appName,
openGraph: { openGraph: {
title: appName, title: appName,
description: process.env.NEXT_PUBLIC_FRAME_DESCRIPTION || "A Farcaster Frames v2 demo app.", description: process.env.NEXT_PUBLIC_FRAME_DESCRIPTION,
}, },
other: { other: {
"fc:frame": JSON.stringify(frame), "fc:frame": JSON.stringify(framePreviewMetadata),
}, },
}; };
} }

View File

@ -1,9 +1,28 @@
import { clsx, type ClassValue } from "clsx" import { type ClassValue, clsx } from 'clsx';
import { twMerge } from "tailwind-merge" import { twMerge } from 'tailwind-merge';
import { mnemonicToAccount } from 'viem/accounts'; 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[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs));
} }
export function getSecretEnvVars() { export function getSecretEnvVars() {
@ -17,7 +36,18 @@ export function getSecretEnvVars() {
return { seedPhrase, fid }; return { seedPhrase, fid };
} }
export async function generateFarcasterMetadata() { export async function getFarcasterMetadata(): Promise<FrameMetadata> {
// 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; const appUrl = process.env.NEXT_PUBLIC_URL;
if (!appUrl) { if (!appUrl) {
throw new Error('NEXT_PUBLIC_URL not configured'); throw new Error('NEXT_PUBLIC_URL not configured');