mirror of
https://github.com/neynarxyz/create-farcaster-mini-app.git
synced 2025-11-16 08:08:56 -05:00
feat: use saved frame manifest from build script
This commit is contained in:
parent
583054cefb
commit
a66e219438
21
README.md
21
README.md
@ -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
22
package-lock.json
generated
@ -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",
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user