diff --git a/bin/init.js b/bin/init.js
index febbf61..ebf5730 100644
--- a/bin/init.js
+++ b/bin/init.js
@@ -6,7 +6,7 @@ import { dirname } from 'path';
import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';
-import { mnemonicToAccount } from 'viem/accounts';
+import crypto from 'crypto';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -295,6 +295,7 @@ export async function init() {
"eslint": "^8",
"eslint-config-next": "15.0.3",
"localtunnel": "^2.0.2",
+ "pino-pretty": "^13.0.0",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
@@ -322,6 +323,7 @@ export async function init() {
fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_NAME="${answers.projectName}"`);
fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_DESCRIPTION="${answers.description}"`);
fs.appendFileSync(envPath, `\nNEXT_PUBLIC_FRAME_BUTTON_TEXT="${answers.buttonText}"`);
+ fs.appendFileSync(envPath, `\nNEXTAUTH_SECRET="${crypto.randomBytes(32).toString('hex')}"`);
if (useNeynar && neynarApiKey && neynarClientId) {
fs.appendFileSync(envPath, `\nNEYNAR_API_KEY="${neynarApiKey}"`);
fs.appendFileSync(envPath, `\nNEYNAR_CLIENT_ID="${neynarClientId}"`);
diff --git a/src/app/app.tsx b/src/app/app.tsx
index c9071e9..e58eeaa 100644
--- a/src/app/app.tsx
+++ b/src/app/app.tsx
@@ -1,7 +1,7 @@
"use client";
import dynamic from "next/dynamic";
-
+import { APP_NAME } from "~/lib/constants";
// note: dynamic import is required for components that use the Frame SDK
const Demo = dynamic(() => import("~/components/Demo"), {
@@ -9,7 +9,7 @@ const Demo = dynamic(() => import("~/components/Demo"), {
});
export default function App(
- { title }: { title?: string } = { title: process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo" }
+ { title }: { title?: string } = { title: APP_NAME }
) {
return ;
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 288c052..b7a8dde 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -3,10 +3,11 @@ import type { Metadata } from "next";
import { getSession } from "~/auth"
import "~/app/globals.css";
import { Providers } from "~/app/providers";
+import { APP_NAME, APP_DESCRIPTION } from "~/lib/constants";
export const metadata: Metadata = {
- title: process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo",
- description: process.env.NEXT_PUBLIC_FRAME_DESCRIPTION || "A Farcaster Frames v2 demo app",
+ title: APP_NAME,
+ description: APP_DESCRIPTION,
};
export default async function RootLayout({
diff --git a/src/app/opengraph-image.tsx b/src/app/opengraph-image.tsx
index 01b2560..86b07cc 100644
--- a/src/app/opengraph-image.tsx
+++ b/src/app/opengraph-image.tsx
@@ -1,6 +1,7 @@
import { ImageResponse } from "next/og";
+import { APP_NAME } from "~/lib/constants";
-export const alt = process.env.NEXT_PUBLIC_FRAME_NAME || "Frames V2 Demo";
+export const alt = APP_NAME;
export const size = {
width: 600,
height: 400,
diff --git a/src/app/page.tsx b/src/app/page.tsx
index b8c5756..5214b54 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,25 +1,19 @@
import { Metadata } from "next";
import App from "./app";
-
-const appUrl = process.env.NEXT_PUBLIC_URL;
-
-// frame preview metadata
-const appName = process.env.NEXT_PUBLIC_FRAME_NAME;
-const splashImageUrl = `${appUrl}/splash.png`;
-const iconUrl = `${appUrl}/icon.png`;
+import { APP_URL, APP_NAME, APP_DESCRIPTION, APP_OG_IMAGE_URL, APP_ICON_URL, APP_SPLASH_URL, APP_SPLASH_BACKGROUND_COLOR, APP_BUTTON_TEXT } from "~/lib/constants";
const framePreviewMetadata = {
version: "next",
- imageUrl: `${appUrl}/opengraph-image`,
+ imageUrl: APP_OG_IMAGE_URL,
button: {
- title: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT,
+ title: APP_BUTTON_TEXT,
action: {
type: "launch_frame",
- name: appName,
- url: appUrl,
- splashImageUrl,
- iconUrl,
- splashBackgroundColor: "#f7f7f7",
+ name: APP_NAME,
+ url: APP_URL,
+ splashImageUrl: APP_SPLASH_URL,
+ iconUrl: APP_ICON_URL,
+ splashBackgroundColor: APP_SPLASH_BACKGROUND_COLOR,
},
},
};
@@ -28,10 +22,10 @@ export const revalidate = 300;
export async function generateMetadata(): Promise {
return {
- title: appName,
+ title: APP_NAME,
openGraph: {
- title: appName,
- description: process.env.NEXT_PUBLIC_FRAME_DESCRIPTION,
+ title: APP_NAME,
+ description: APP_DESCRIPTION,
},
other: {
"fc:frame": JSON.stringify(framePreviewMetadata),
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
new file mode 100644
index 0000000..201983c
--- /dev/null
+++ b/src/lib/constants.ts
@@ -0,0 +1,8 @@
+export const APP_URL = process.env.NEXT_PUBLIC_URL;
+export const APP_NAME = process.env.NEXT_PUBLIC_FRAME_NAME;
+export const APP_DESCRIPTION = process.env.NEXT_PUBLIC_FRAME_DESCRIPTION;
+export const APP_ICON_URL = `${APP_URL}/icon.png`;
+export const APP_OG_IMAGE_URL = `${APP_URL}/opengraph-image`;
+export const APP_SPLASH_URL = `${APP_URL}/splash.png`;
+export const APP_SPLASH_BACKGROUND_COLOR = "#f7f7f7";
+export const APP_BUTTON_TEXT = process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT;
diff --git a/src/lib/kv.ts b/src/lib/kv.ts
index b49c45b..55477e9 100644
--- a/src/lib/kv.ts
+++ b/src/lib/kv.ts
@@ -1,5 +1,6 @@
import { FrameNotificationDetails } from "@farcaster/frame-sdk";
import { Redis } from "@upstash/redis";
+import { APP_NAME } from "./constants";
// In-memory fallback storage
const localStore = new Map();
@@ -12,7 +13,7 @@ const redis = useRedis ? new Redis({
}) : null;
function getUserNotificationDetailsKey(fid: number): string {
- return `${process.env.NEXT_PUBLIC_FRAME_NAME}:user:${fid}`;
+ return `${APP_NAME}:user:${fid}`;
}
export async function getUserNotificationDetails(
diff --git a/src/lib/neynar.ts b/src/lib/neynar.ts
index e0e6cf1..44fbb5f 100644
--- a/src/lib/neynar.ts
+++ b/src/lib/neynar.ts
@@ -1,4 +1,5 @@
import { NeynarAPIClient, Configuration } from '@neynar/nodejs-sdk';
+import { APP_URL } from './constants';
let neynarClient: NeynarAPIClient | null = null;
@@ -41,7 +42,7 @@ export async function sendNeynarFrameNotification({
const notification = {
title,
body,
- target_url: process.env.NEXT_PUBLIC_URL!,
+ target_url: APP_URL,
};
const result = await client.publishFrameNotifications({
diff --git a/src/lib/notifs.ts b/src/lib/notifs.ts
index 3789d0a..a1dffda 100644
--- a/src/lib/notifs.ts
+++ b/src/lib/notifs.ts
@@ -3,8 +3,7 @@ import {
sendNotificationResponseSchema,
} from "@farcaster/frame-sdk";
import { getUserNotificationDetails } from "~/lib/kv";
-
-const appUrl = process.env.NEXT_PUBLIC_URL || "";
+import { APP_URL } from "./constants";
type SendFrameNotificationResult =
| {
@@ -38,7 +37,7 @@ export async function sendFrameNotification({
notificationId: crypto.randomUUID(),
title,
body,
- targetUrl: appUrl,
+ targetUrl: APP_URL,
tokens: [notificationDetails.token],
} satisfies SendNotificationRequest),
});
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 32b437f..392cd65 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -1,6 +1,8 @@
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { mnemonicToAccount } from 'viem/accounts';
+import { APP_BUTTON_TEXT, APP_ICON_URL, APP_NAME, APP_OG_IMAGE_URL, APP_SPLASH_BACKGROUND_COLOR, APP_URL } from './constants';
+import { APP_SPLASH_URL } from './constants';
interface FrameMetadata {
accountAssociation?: {
@@ -48,13 +50,12 @@ export async function getFarcasterMetadata(): Promise {
}
}
- const appUrl = process.env.NEXT_PUBLIC_URL;
- if (!appUrl) {
+ if (!APP_URL) {
throw new Error('NEXT_PUBLIC_URL not configured');
}
// Get the domain from the URL (without https:// prefix)
- const domain = new URL(appUrl).hostname;
+ const domain = new URL(APP_URL).hostname;
console.log('Using domain for manifest:', domain);
const secretEnvVars = getSecretEnvVars();
@@ -97,19 +98,19 @@ export async function getFarcasterMetadata(): Promise {
const neynarClientId = process.env.NEYNAR_CLIENT_ID;
const webhookUrl = neynarApiKey && neynarClientId
? `https://api.neynar.com/f/app/${neynarClientId}/event`
- : `${appUrl}/api/webhook`;
+ : `${APP_URL}/api/webhook`;
return {
accountAssociation,
frame: {
version: "1",
- name: process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo",
- iconUrl: `${appUrl}/icon.png`,
- homeUrl: appUrl,
- imageUrl: `${appUrl}/opengraph-image`,
- buttonTitle: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT || "Launch Frame",
- splashImageUrl: `${appUrl}/splash.png`,
- splashBackgroundColor: "#f7f7f7",
+ name: APP_NAME ?? "Frames v2 Demo",
+ iconUrl: APP_ICON_URL,
+ homeUrl: APP_URL,
+ imageUrl: APP_OG_IMAGE_URL,
+ buttonTitle: APP_BUTTON_TEXT ?? "Launch Frame",
+ splashImageUrl: APP_SPLASH_URL,
+ splashBackgroundColor: APP_SPLASH_BACKGROUND_COLOR,
webhookUrl,
},
};