Merge branch 'main' into shreyas-formatting

This commit is contained in:
Shreyaschorge 2025-07-15 00:00:11 +05:30
commit 3815f45b44
No known key found for this signature in database
10 changed files with 296 additions and 713 deletions

View File

@ -488,18 +488,19 @@ export async function init(
};
packageJson.devDependencies = {
'@types/node': '^20',
'@types/react': '^19',
'@types/react-dom': '^19',
'@vercel/sdk': '^1.9.0',
crypto: '^1.0.1',
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',
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@vercel/sdk": "^1.9.0",
"crypto": "^1.0.1",
"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",
"ts-node": "^10.9.2"
};
// Add Neynar SDK if selected
@ -550,19 +551,19 @@ export async function init(
return content;
};
// Regex patterns that match whole lines with export const
// Regex patterns that match whole lines with export const (with TypeScript types)
const patterns = {
APP_NAME: /^export const APP_NAME\s*=\s*['"`][^'"`]*['"`];$/m,
APP_NAME: /^export const APP_NAME\s*:\s*string\s*=\s*['"`][^'"`]*['"`];$/m,
APP_DESCRIPTION:
/^export const APP_DESCRIPTION\s*=\s*['"`][^'"`]*['"`];$/m,
/^export const APP_DESCRIPTION\s*:\s*string\s*=\s*['"`][^'"`]*['"`];$/m,
APP_PRIMARY_CATEGORY:
/^export const APP_PRIMARY_CATEGORY\s*=\s*['"`][^'"`]*['"`];$/m,
APP_TAGS: /^export const APP_TAGS\s*=\s*\[[^\]]*\];$/m,
/^export const APP_PRIMARY_CATEGORY\s*:\s*string\s*=\s*['"`][^'"`]*['"`];$/m,
APP_TAGS: /^export const APP_TAGS\s*:\s*string\[\]\s*=\s*\[[^\]]*\];$/m,
APP_BUTTON_TEXT:
/^export const APP_BUTTON_TEXT\s*=\s*['"`][^'"`]*['"`];$/m,
USE_WALLET: /^export const USE_WALLET\s*=\s*(true|false);$/m,
/^export const APP_BUTTON_TEXT\s*:\s*string\s*=\s*['"`][^'"`]*['"`];$/m,
USE_WALLET: /^export const USE_WALLET\s*:\s*boolean\s*=\s*(true|false);$/m,
ANALYTICS_ENABLED:
/^export const ANALYTICS_ENABLED\s*=\s*(true|false);$/m,
/^export const ANALYTICS_ENABLED\s*:\s*boolean\s*=\s*(true|false);$/m,
};
// Update APP_NAME

View File

@ -1,6 +1,6 @@
{
"name": "@neynar/create-farcaster-mini-app",
"version": "1.5.11",
"version": "1.6.2",
"type": "module",
"private": false,
"access": "public",
@ -31,16 +31,11 @@
],
"scripts": {
"dev": "node scripts/dev.js",
"build": "node scripts/build.js",
"build": "next build",
"build:raw": "next build",
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"format:fix": "prettier --write . && eslint --fix . --max-warnings 50",
"typecheck": "tsc --noEmit",
"deploy:vercel": "node scripts/deploy.js",
"deploy:vercel": "ts-node scripts/deploy.ts",
"deploy:raw": "vercel --prod",
"cleanup": "node scripts/cleanup.js"
},
@ -55,11 +50,6 @@
"devDependencies": {
"@neynar/nodejs-sdk": "^2.19.0",
"@types/node": "^22.13.10",
"@typescript-eslint/eslint-plugin": "^8.35.1",
"@typescript-eslint/parser": "^8.35.1",
"eslint": "^8.57.0",
"eslint-config-next": "^15.0.0",
"prettier": "^3.3.3",
"typescript": "^5.6.3"
}
}

View File

@ -1,361 +0,0 @@
import { execSync } from 'child_process';
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import dotenv from 'dotenv';
import inquirer from 'inquirer';
// Load environment variables in specific order
// First load .env for main config
dotenv.config({ path: '.env' });
async function loadEnvLocal() {
try {
if (fs.existsSync('.env.local')) {
const { loadLocal } = await inquirer.prompt([
{
type: 'confirm',
name: 'loadLocal',
message:
'Found .env.local, likely created by the install script - would you like to load its values?',
default: false,
},
]);
const localEnv = dotenv.parse(fs.readFileSync('.env.local'));
if (loadLocal) {
console.log('Loading values from .env.local...');
// Copy all values to .env
const envContent = fs.existsSync('.env')
? fs.readFileSync('.env', 'utf8') + '\n'
: '';
let newEnvContent = envContent;
for (const [key, value] of Object.entries(localEnv)) {
// Update process.env
process.env[key] = value;
// Add to .env content if not already there
if (!envContent.includes(`${key}=`)) {
newEnvContent += `${key}="${value}"\n`;
}
}
// Write updated content to .env
fs.writeFileSync('.env', newEnvContent);
console.log('✅ Values from .env.local have been written to .env');
}
if (localEnv.SPONSOR_SIGNER) {
process.env.SPONSOR_SIGNER = localEnv.SPONSOR_SIGNER;
}
}
} catch (error) {
// Error reading .env.local, which is fine
console.log('Note: No .env.local file found');
}
}
// TODO: make sure rebuilding is supported
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = path.join(__dirname, '..');
async function validateDomain(domain) {
// Remove http:// or https:// if present
const cleanDomain = domain.replace(/^https?:\/\//, '');
// Basic domain validation
if (
!cleanDomain.match(
/^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+$/,
)
) {
throw new Error('Invalid domain format');
}
return cleanDomain;
}
async function queryNeynarApp(apiKey) {
if (!apiKey) {
return null;
}
try {
const response = await fetch(
'https://api.neynar.com/portal/app_by_api_key',
{
headers: {
'x-api-key': apiKey,
},
},
);
const data = await response.json();
return data;
} catch (error) {
console.error('Error querying Neynar app data:', error);
return null;
}
}
async function generateFarcasterMetadata(domain, webhookUrl) {
const tags = process.env.NEXT_PUBLIC_MINI_APP_TAGS?.split(',');
return {
accountAssociation: {
header: '',
payload: '',
signature: '',
},
frame: {
version: '1',
name: process.env.NEXT_PUBLIC_MINI_APP_NAME,
iconUrl: `https://${domain}/icon.png`,
homeUrl: `https://${domain}`,
imageUrl: `https://${domain}/api/opengraph-image`,
buttonTitle: process.env.NEXT_PUBLIC_MINI_APP_BUTTON_TEXT,
splashImageUrl: `https://${domain}/splash.png`,
splashBackgroundColor: '#f7f7f7',
webhookUrl,
description: process.env.NEXT_PUBLIC_MINI_APP_DESCRIPTION,
primaryCategory: process.env.NEXT_PUBLIC_MINI_APP_PRIMARY_CATEGORY,
tags,
},
};
}
async function main() {
try {
console.log('\n📝 Checking environment variables...');
console.log('Loading values from .env...');
// Load .env.local if user wants to
await loadEnvLocal();
// Get domain from user
const { domain } = await inquirer.prompt([
{
type: 'input',
name: 'domain',
message:
'Enter the domain where your mini app will be deployed (e.g., example.com):',
validate: async input => {
try {
await validateDomain(input);
return true;
} catch (error) {
return error.message;
}
},
},
]);
// Get frame name from user
const { frameName } = await inquirer.prompt([
{
type: 'input',
name: 'frameName',
message: 'Enter the name for your mini app (e.g., My Cool Mini App):',
default: process.env.NEXT_PUBLIC_MINI_APP_NAME,
validate: input => {
if (input.trim() === '') {
return 'Mini app name cannot be empty';
}
return true;
},
},
]);
// Get button text from user
const { buttonText } = await inquirer.prompt([
{
type: 'input',
name: 'buttonText',
message: 'Enter the text for your mini app button:',
default:
process.env.NEXT_PUBLIC_MINI_APP_BUTTON_TEXT || 'Launch Mini App',
validate: input => {
if (input.trim() === '') {
return 'Button text cannot be empty';
}
return true;
},
},
]);
// Get Neynar configuration
let neynarApiKey = process.env.NEYNAR_API_KEY;
let neynarClientId = process.env.NEYNAR_CLIENT_ID;
let useNeynar = true;
while (useNeynar) {
if (!neynarApiKey) {
const { neynarApiKey: inputNeynarApiKey } = await inquirer.prompt([
{
type: 'password',
name: 'neynarApiKey',
message:
'Enter your Neynar API key (optional - leave blank to skip):',
default: null,
},
]);
neynarApiKey = inputNeynarApiKey;
} else {
console.log('Using existing Neynar API key from .env');
}
if (!neynarApiKey) {
useNeynar = false;
break;
}
// Try to get client ID from API
if (!neynarClientId) {
const appInfo = await queryNeynarApp(neynarApiKey);
if (appInfo) {
neynarClientId = appInfo.app_uuid;
console.log('✅ Fetched Neynar app client ID');
break;
}
}
// We have a client ID (either from .env or fetched from API), so we can break out of the loop
if (neynarClientId) {
break;
}
// If we get here, the API key was invalid
console.log(
'\n⚠ Could not find Neynar app information. The API key may be incorrect.',
);
const { retry } = await inquirer.prompt([
{
type: 'confirm',
name: 'retry',
message: 'Would you like to try a different API key?',
default: true,
},
]);
// Reset for retry
neynarApiKey = null;
neynarClientId = null;
if (!retry) {
useNeynar = false;
break;
}
}
// Generate manifest
console.log('\n🔨 Generating mini app manifest...');
// Determine webhook URL based on environment variables
const webhookUrl =
neynarApiKey && neynarClientId
? `https://api.neynar.com/f/app/${neynarClientId}/event`
: `https://${domain}/api/webhook`;
const metadata = await generateFarcasterMetadata(domain, webhookUrl);
console.log('\n✅ Mini app manifest generated');
// Read existing .env file or create new one
const envPath = path.join(projectRoot, '.env');
let envContent = fs.existsSync(envPath)
? fs.readFileSync(envPath, 'utf8')
: '';
// Add or update environment variables
const newEnvVars = [
// Base URL
`NEXT_PUBLIC_URL=https://${domain}`,
// Mini app metadata
`NEXT_PUBLIC_MINI_APP_NAME="${frameName}"`,
`NEXT_PUBLIC_MINI_APP_DESCRIPTION="${
process.env.NEXT_PUBLIC_MINI_APP_DESCRIPTION || ''
}"`,
`NEXT_PUBLIC_MINI_APP_PRIMARY_CATEGORY="${
process.env.NEXT_PUBLIC_MINI_APP_PRIMARY_CATEGORY || ''
}"`,
`NEXT_PUBLIC_MINI_APP_TAGS="${
process.env.NEXT_PUBLIC_MINI_APP_TAGS || ''
}"`,
`NEXT_PUBLIC_MINI_APP_BUTTON_TEXT="${buttonText}"`,
// Analytics
`NEXT_PUBLIC_ANALYTICS_ENABLED="${
process.env.NEXT_PUBLIC_ANALYTICS_ENABLED || 'false'
}"`,
// Neynar configuration (if it exists in current env)
...(process.env.NEYNAR_API_KEY
? [`NEYNAR_API_KEY="${process.env.NEYNAR_API_KEY}"`]
: []),
...(neynarClientId ? [`NEYNAR_CLIENT_ID="${neynarClientId}"`] : []),
...(process.env.SPONSOR_SIGNER
? [`SPONSOR_SIGNER="${process.env.SPONSOR_SIGNER}"`]
: []),
// FID (if it exists in current env)
...(process.env.FID ? [`FID="${process.env.FID}"`] : []),
`NEXT_PUBLIC_USE_WALLET="${
process.env.NEXT_PUBLIC_USE_WALLET || 'false'
}"`,
// NextAuth configuration
`NEXTAUTH_SECRET="${
process.env.NEXTAUTH_SECRET || crypto.randomBytes(32).toString('hex')
}"`,
`NEXTAUTH_URL="https://${domain}"`,
// Mini app manifest with signature
`MINI_APP_METADATA=${JSON.stringify(metadata)}`,
];
// Filter out empty values and join with newlines
const validEnvVars = newEnvVars.filter(line => {
const [, value] = line.split('=');
return value && value !== '""';
});
// Update or append each environment variable
validEnvVars.forEach(varLine => {
const [key] = varLine.split('=');
if (envContent.includes(`${key}=`)) {
envContent = envContent.replace(new RegExp(`${key}=.*`), varLine);
} else {
envContent += `\n${varLine}`;
}
});
// Write updated .env file
fs.writeFileSync(envPath, envContent);
console.log('\n✅ Environment variables updated');
// Run next build
console.log('\nBuilding Next.js application...');
const nextBin = path.normalize(
path.join(projectRoot, 'node_modules', '.bin', 'next'),
);
execSync(`"${nextBin}" build`, {
cwd: projectRoot,
stdio: 'inherit',
shell: process.platform === 'win32',
});
console.log(
'\n✨ Build complete! Your mini app is ready for deployment. 🪐',
);
console.log(
'📝 Make sure to configure the environment variables from .env in your hosting provider',
);
} catch (error) {
console.error('\n❌ Error:', error.message);
process.exit(1);
}
}
main();

View File

@ -1,12 +1,13 @@
import { execSync, spawn } from 'child_process';
import crypto from 'crypto';
import fs from 'fs';
import os from 'os';
import path from 'path';
import os from 'os';
import { fileURLToPath } from 'url';
import { Vercel } from '@vercel/sdk';
import dotenv from 'dotenv';
import inquirer from 'inquirer';
import dotenv from 'dotenv';
import crypto from 'crypto';
import { Vercel } from '@vercel/sdk';
import { APP_NAME, APP_BUTTON_TEXT } from '../src/lib/constants';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = path.join(__dirname, '..');
@ -14,32 +15,10 @@ const projectRoot = path.join(__dirname, '..');
// Load environment variables in specific order
dotenv.config({ path: '.env' });
async function generateFarcasterMetadata(domain, webhookUrl) {
const trimmedDomain = domain.trim();
const tags = process.env.NEXT_PUBLIC_MINI_APP_TAGS?.split(',');
return {
frame: {
version: '1',
name: process.env.NEXT_PUBLIC_MINI_APP_NAME,
iconUrl: `https://${trimmedDomain}/icon.png`,
homeUrl: `https://${trimmedDomain}`,
imageUrl: `https://${trimmedDomain}/api/opengraph-image`,
buttonTitle: process.env.NEXT_PUBLIC_MINI_APP_BUTTON_TEXT,
splashImageUrl: `https://${trimmedDomain}/splash.png`,
splashBackgroundColor: '#f7f7f7',
webhookUrl: webhookUrl?.trim(),
description: process.env.NEXT_PUBLIC_MINI_APP_DESCRIPTION,
primaryCategory: process.env.NEXT_PUBLIC_MINI_APP_PRIMARY_CATEGORY,
tags,
},
};
}
async function loadEnvLocal() {
async function loadEnvLocal(): Promise<void> {
try {
if (fs.existsSync('.env.local')) {
const { loadLocal } = await inquirer.prompt([
const { loadLocal }: { loadLocal: boolean } = await inquirer.prompt([
{
type: 'confirm',
name: 'loadLocal',
@ -54,12 +33,7 @@ async function loadEnvLocal() {
const localEnv = dotenv.parse(fs.readFileSync('.env.local'));
const allowedVars = [
'NEXT_PUBLIC_MINI_APP_NAME',
'NEXT_PUBLIC_MINI_APP_DESCRIPTION',
'NEXT_PUBLIC_MINI_APP_PRIMARY_CATEGORY',
'NEXT_PUBLIC_MINI_APP_TAGS',
'NEXT_PUBLIC_MINI_APP_BUTTON_TEXT',
'NEXT_PUBLIC_ANALYTICS_ENABLED',
'SEED_PHRASE',
'NEYNAR_API_KEY',
'NEYNAR_CLIENT_ID',
'SPONSOR_SIGNER',
@ -83,12 +57,12 @@ async function loadEnvLocal() {
console.log('✅ Values from .env.local have been written to .env');
}
}
} catch (error) {
} catch (error: unknown) {
console.log('Note: No .env.local file found');
}
}
async function checkRequiredEnvVars() {
async function checkRequiredEnvVars(): Promise<void> {
console.log('\n📝 Checking environment variables...');
console.log('Loading values from .env...');
@ -98,20 +72,19 @@ async function checkRequiredEnvVars() {
{
name: 'NEXT_PUBLIC_MINI_APP_NAME',
message: 'Enter the name for your frame (e.g., My Cool Mini App):',
default: process.env.NEXT_PUBLIC_MINI_APP_NAME,
validate: input => input.trim() !== '' || 'Mini app name cannot be empty',
default: APP_NAME,
validate: (input: string) => input.trim() !== '' || 'Mini app name cannot be empty'
},
{
name: 'NEXT_PUBLIC_MINI_APP_BUTTON_TEXT',
message: 'Enter the text for your frame button:',
default:
process.env.NEXT_PUBLIC_MINI_APP_BUTTON_TEXT ?? 'Launch Mini App',
validate: input => input.trim() !== '' || 'Button text cannot be empty',
},
default: APP_BUTTON_TEXT ?? 'Launch Mini App',
validate: (input: string) => input.trim() !== '' || 'Button text cannot be empty'
}
];
const missingVars = requiredVars.filter(
varConfig => !process.env[varConfig.name],
(varConfig) => !process.env[varConfig.name]
);
if (missingVars.length > 0) {
@ -137,7 +110,7 @@ async function checkRequiredEnvVars() {
const newLine = envContent ? '\n' : '';
fs.appendFileSync(
'.env',
`${newLine}${varConfig.name}="${value.trim()}"`,
`${newLine}${varConfig.name}="${value.trim()}"`
);
}
@ -160,7 +133,7 @@ async function checkRequiredEnvVars() {
if (storeSeedPhrase) {
fs.appendFileSync(
'.env.local',
`\nSPONSOR_SIGNER="${sponsorSigner}"`,
`\nSPONSOR_SIGNER="${sponsorSigner}"`
);
console.log('✅ Sponsor signer preference stored in .env.local');
}
@ -181,39 +154,43 @@ async function checkRequiredEnvVars() {
}
}
async function getGitRemote() {
async function getGitRemote(): Promise<string | null> {
try {
const remoteUrl = execSync('git remote get-url origin', {
cwd: projectRoot,
encoding: 'utf8',
}).trim();
return remoteUrl;
} catch (error) {
return null;
} catch (error: unknown) {
if (error instanceof Error) {
return null;
}
throw error;
}
}
async function checkVercelCLI() {
async function checkVercelCLI(): Promise<boolean> {
try {
execSync('vercel --version', {
stdio: 'ignore',
shell: process.platform === 'win32',
stdio: 'ignore'
});
return true;
} catch (error) {
return false;
} catch (error: unknown) {
if (error instanceof Error) {
return false;
}
throw error;
}
}
async function installVercelCLI() {
async function installVercelCLI(): Promise<void> {
console.log('Installing Vercel CLI...');
execSync('npm install -g vercel', {
stdio: 'inherit',
shell: process.platform === 'win32',
stdio: 'inherit'
});
}
async function getVercelToken() {
async function getVercelToken(): Promise<string | null> {
try {
// Try to get token from Vercel CLI config
const configPath = path.join(os.homedir(), '.vercel', 'auth.json');
@ -221,8 +198,10 @@ async function getVercelToken() {
const authConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
return authConfig.token;
}
} catch (error) {
console.warn('Could not read Vercel token from config file');
} catch (error: unknown) {
if (error instanceof Error) {
console.warn('Could not read Vercel token from config file');
}
}
// Try environment variable
@ -241,14 +220,15 @@ async function getVercelToken() {
// The token isn't directly exposed, so we'll need to use CLI for some operations
console.log('✅ Verified Vercel CLI authentication');
return null; // We'll fall back to CLI operations
} catch (error) {
throw new Error(
'Not logged in to Vercel CLI. Please run this script again to login.',
);
} catch (error: unknown) {
if (error instanceof Error) {
throw new Error('Not logged in to Vercel CLI. Please run this script again to login.');
}
throw error;
}
}
async function loginToVercel() {
async function loginToVercel(): Promise<boolean> {
console.log('\n🔑 Vercel Login');
console.log('You can either:');
console.log('1. Log in to an existing Vercel account');
@ -259,22 +239,22 @@ async function loginToVercel() {
console.log('3. Complete the Vercel account setup in your browser');
console.log('4. Return here once your Vercel account is created\n');
console.log(
'\nNote: you may need to cancel this script with ctrl+c and run it again if creating a new vercel account',
'\nNote: you may need to cancel this script with ctrl+c and run it again if creating a new vercel account'
);
const child = spawn('vercel', ['login'], {
stdio: 'inherit',
});
await new Promise((resolve, reject) => {
child.on('close', code => {
await new Promise<void>((resolve, reject) => {
child.on('close', (code) => {
resolve();
});
});
console.log('\n📱 Waiting for login to complete...');
console.log(
"If you're creating a new account, please complete the Vercel account setup in your browser first.",
"If you're creating a new account, please complete the Vercel account setup in your browser first."
);
for (let i = 0; i < 150; i++) {
@ -282,11 +262,11 @@ async function loginToVercel() {
execSync('vercel whoami', { stdio: 'ignore' });
console.log('✅ Successfully logged in to Vercel!');
return true;
} catch (error) {
if (error.message.includes('Account not found')) {
} catch (error: unknown) {
if (error instanceof Error && error.message.includes('Account not found')) {
console.log(' Waiting for Vercel account setup to complete...');
}
await new Promise(resolve => setTimeout(resolve, 2000));
await new Promise((resolve) => setTimeout(resolve, 2000));
}
}
@ -297,9 +277,9 @@ async function loginToVercel() {
return false;
}
async function setVercelEnvVarSDK(vercelClient, projectId, key, value) {
async function setVercelEnvVarSDK(vercelClient: Vercel, projectId: string, key: string, value: string | object): Promise<boolean> {
try {
let processedValue;
let processedValue: string;
if (typeof value === 'object') {
processedValue = JSON.stringify(value);
} else {
@ -311,8 +291,8 @@ async function setVercelEnvVarSDK(vercelClient, projectId, key, value) {
idOrName: projectId,
});
const existingVar = existingVars.envs?.find(
env => env.key === key && env.target?.includes('production'),
const existingVar = existingVars.envs?.find((env: any) =>
env.key === key && env.target?.includes('production')
);
if (existingVar) {
@ -341,16 +321,16 @@ async function setVercelEnvVarSDK(vercelClient, projectId, key, value) {
}
return true;
} catch (error) {
console.warn(
`⚠️ Warning: Failed to set environment variable ${key}:`,
error.message,
);
return false;
} catch (error: unknown) {
if (error instanceof Error) {
console.warn(`⚠️ Warning: Failed to set environment variable ${key}:`, error.message);
return false;
}
throw error;
}
}
async function setVercelEnvVarCLI(key, value, projectRoot) {
async function setVercelEnvVarCLI(key: string, value: string | object, projectRoot: string): Promise<boolean> {
try {
// Remove existing env var
try {
@ -359,11 +339,11 @@ async function setVercelEnvVarCLI(key, value, projectRoot) {
stdio: 'ignore',
env: process.env,
});
} catch (error) {
} catch (error: unknown) {
// Ignore errors from removal
}
let processedValue;
let processedValue: string;
if (typeof value === 'object') {
processedValue = JSON.stringify(value);
} else {
@ -375,7 +355,7 @@ async function setVercelEnvVarCLI(key, value, projectRoot) {
fs.writeFileSync(tempFilePath, processedValue, 'utf8');
// Use appropriate command based on platform
let command;
let command: string;
if (process.platform === 'win32') {
command = `type "${tempFilePath}" | vercel env add ${key} production`;
} else {
@ -385,35 +365,29 @@ async function setVercelEnvVarCLI(key, value, projectRoot) {
execSync(command, {
cwd: projectRoot,
stdio: 'pipe', // Changed from 'inherit' to avoid interactive prompts
shell: true,
env: process.env,
env: process.env
});
fs.unlinkSync(tempFilePath);
console.log(`✅ Set environment variable: ${key}`);
return true;
} catch (error) {
} catch (error: unknown) {
const tempFilePath = path.join(projectRoot, `${key}_temp.txt`);
if (fs.existsSync(tempFilePath)) {
fs.unlinkSync(tempFilePath);
}
console.warn(
`⚠️ Warning: Failed to set environment variable ${key}:`,
error.message,
);
return false;
if (error instanceof Error) {
console.warn(`⚠️ Warning: Failed to set environment variable ${key}:`, error.message);
return false;
}
throw error;
}
}
async function setEnvironmentVariables(
vercelClient,
projectId,
envVars,
projectRoot,
) {
async function setEnvironmentVariables(vercelClient: Vercel | null, projectId: string | null, envVars: Record<string, string | object>, projectRoot: string): Promise<Array<{ key: string; success: boolean }>> {
console.log('\n📝 Setting up environment variables...');
const results = [];
const results: Array<{ key: string; success: boolean }> = [];
for (const [key, value] of Object.entries(envVars)) {
if (!value) continue;
@ -434,35 +408,30 @@ async function setEnvironmentVariables(
}
// Report results
const failed = results.filter(r => !r.success);
const failed = results.filter((r) => !r.success);
if (failed.length > 0) {
console.warn(`\n⚠ Failed to set ${failed.length} environment variables:`);
failed.forEach(r => console.warn(` - ${r.key}`));
failed.forEach((r) => console.warn(` - ${r.key}`));
console.warn(
'\nYou may need to set these manually in the Vercel dashboard.',
'\nYou may need to set these manually in the Vercel dashboard.'
);
}
return results;
}
async function waitForDeployment(
vercelClient,
projectId,
maxWaitTime = 300000,
) {
// 5 minutes
async function waitForDeployment(vercelClient: Vercel | null, projectId: string, maxWaitTime = 300000): Promise<any> { // 5 minutes
console.log('\n⏳ Waiting for deployment to complete...');
const startTime = Date.now();
while (Date.now() - startTime < maxWaitTime) {
try {
const deployments = await vercelClient.deployments.list({
const deployments = await vercelClient?.deployments.list({
projectId: projectId,
limit: 1,
});
if (deployments.deployments?.[0]) {
if (deployments?.deployments?.[0]) {
const deployment = deployments.deployments[0];
console.log(`📊 Deployment status: ${deployment.state}`);
@ -476,21 +445,24 @@ async function waitForDeployment(
}
// Still building, wait and check again
await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5 seconds
await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait 5 seconds
} else {
console.log('⏳ No deployment found yet, waiting...');
await new Promise((resolve) => setTimeout(resolve, 5000));
}
} catch (error: unknown) {
if (error instanceof Error) {
console.warn('⚠️ Could not check deployment status:', error.message);
await new Promise(resolve => setTimeout(resolve, 5000));
}
} catch (error) {
console.warn('⚠️ Could not check deployment status:', error.message);
await new Promise(resolve => setTimeout(resolve, 5000));
throw error;
}
}
throw new Error('Deployment timed out after 5 minutes');
}
async function deployToVercel(useGitHub = false) {
async function deployToVercel(useGitHub = false): Promise<void> {
try {
console.log('\n🚀 Deploying to Vercel...');
@ -506,81 +478,83 @@ async function deployToVercel(useGitHub = false) {
framework: 'nextjs',
},
null,
2,
),
2
)
);
}
// Set up Vercel project
console.log('\n📦 Setting up Vercel project...');
console.log(
'An initial deployment is required to get an assigned domain that can be used in the mini app manifest\n',
'An initial deployment is required to get an assigned domain that can be used in the mini app manifest\n'
);
console.log(
'\n⚠ Note: choosing a longer, more unique project name will help avoid conflicts with other existing domains\n',
'\n⚠ Note: choosing a longer, more unique project name will help avoid conflicts with other existing domains\n'
);
// Use spawn instead of execSync for better error handling
const { spawn } = await import('child_process');
const vercelSetup = spawn('vercel', [], {
cwd: projectRoot,
stdio: 'inherit',
shell: process.platform === 'win32',
});
cwd: projectRoot,
stdio: 'inherit',
shell: process.platform === 'win32' ? true : undefined
});
await new Promise((resolve, reject) => {
vercelSetup.on('close', code => {
await new Promise<void>((resolve, reject) => {
vercelSetup.on('close', (code) => {
if (code === 0 || code === null) {
console.log('✅ Vercel project setup completed');
resolve();
} else {
console.log('⚠️ Vercel setup command completed (this is normal)');
console.log('⚠️ Vercel setup command completed (this is normal)');
resolve(); // Don't reject, as this is often expected
}
});
vercelSetup.on('error', error => {
vercelSetup.on('error', (error) => {
console.log('⚠️ Vercel setup command completed (this is normal)');
resolve(); // Don't reject, as this is often expected
});
});
// Wait a moment for project files to be written
await new Promise(resolve => setTimeout(resolve, 2000));
await new Promise((resolve) => setTimeout(resolve, 2000));
// Load project info
let projectId;
let projectId: string;
try {
const projectJson = JSON.parse(
fs.readFileSync('.vercel/project.json', 'utf8'),
fs.readFileSync('.vercel/project.json', 'utf8')
);
projectId = projectJson.projectId;
} catch (error) {
throw new Error(
'Failed to load project info. Please ensure the Vercel project was created successfully.',
);
} catch (error: unknown) {
if (error instanceof Error) {
throw new Error('Failed to load project info. Please ensure the Vercel project was created successfully.');
}
throw error;
}
// Get Vercel token and initialize SDK client
let vercelClient = null;
let vercelClient: Vercel | null = null;
try {
const token = await getVercelToken();
if (token) {
vercelClient = new Vercel({
bearerToken: token,
bearerToken: token
});
console.log('✅ Initialized Vercel SDK client');
}
} catch (error) {
console.warn(
'⚠️ Could not initialize Vercel SDK, falling back to CLI operations',
);
} catch (error: unknown) {
if (error instanceof Error) {
console.warn('⚠️ Could not initialize Vercel SDK, falling back to CLI operations');
}
throw error;
}
// Get project details
console.log('\n🔍 Getting project details...');
let domain;
let projectName;
let domain: string | undefined;
let projectName: string | undefined;
if (vercelClient) {
try {
@ -590,10 +564,11 @@ async function deployToVercel(useGitHub = false) {
projectName = project.name;
domain = `${projectName}.vercel.app`;
console.log('🌐 Using project name for domain:', domain);
} catch (error) {
console.warn(
'⚠️ Could not get project details via SDK, using CLI fallback',
);
} catch (error: unknown) {
if (error instanceof Error) {
console.warn('⚠️ Could not get project details via SDK, using CLI fallback');
}
throw error;
}
}
@ -605,7 +580,7 @@ async function deployToVercel(useGitHub = false) {
{
cwd: projectRoot,
encoding: 'utf8',
},
}
);
const nameMatch = inspectOutput.match(/Name\s+([^\n]+)/);
@ -621,32 +596,24 @@ async function deployToVercel(useGitHub = false) {
console.log('🌐 Using project name for domain:', domain);
} else {
console.warn(
'⚠️ Could not determine project name from inspection, using fallback',
'⚠️ Could not determine project name from inspection, using fallback'
);
// Use a fallback domain based on project ID
domain = `project-${projectId.slice(-8)}.vercel.app`;
console.log('🌐 Using fallback domain:', domain);
}
}
} catch (error) {
console.warn('⚠️ Could not inspect project, using fallback domain');
// Use a fallback domain based on project ID
domain = `project-${projectId.slice(-8)}.vercel.app`;
console.log('🌐 Using fallback domain:', domain);
} catch (error: unknown) {
if (error instanceof Error) {
console.warn('⚠️ Could not inspect project, using fallback domain');
// Use a fallback domain based on project ID
domain = `project-${projectId.slice(-8)}.vercel.app`;
console.log('🌐 Using fallback domain:', domain);
}
throw error;
}
}
// Generate mini app metadata
console.log('\n🔨 Generating mini app metadata...');
const webhookUrl =
process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID
? `https://api.neynar.com/f/app/${process.env.NEYNAR_CLIENT_ID}/event`
: `https://${domain}/api/webhook`;
const miniAppMetadata = await generateFarcasterMetadata(domain, webhookUrl);
console.log('✅ Mini app metadata generated');
// Prepare environment variables
const nextAuthSecret =
process.env.NEXTAUTH_SECRET || crypto.randomBytes(32).toString('hex');
@ -656,21 +623,14 @@ async function deployToVercel(useGitHub = false) {
NEXTAUTH_URL: `https://${domain}`,
NEXT_PUBLIC_URL: `https://${domain}`,
...(process.env.NEYNAR_API_KEY && {
NEYNAR_API_KEY: process.env.NEYNAR_API_KEY,
}),
...(process.env.NEYNAR_CLIENT_ID && {
NEYNAR_CLIENT_ID: process.env.NEYNAR_CLIENT_ID,
}),
...(process.env.SPONSOR_SIGNER && {
SPONSOR_SIGNER: process.env.SPONSOR_SIGNER,
}),
...(miniAppMetadata && { MINI_APP_METADATA: miniAppMetadata }),
...(process.env.NEYNAR_API_KEY && { NEYNAR_API_KEY: process.env.NEYNAR_API_KEY }),
...(process.env.NEYNAR_CLIENT_ID && { NEYNAR_CLIENT_ID: process.env.NEYNAR_CLIENT_ID }),
...(process.env.SPONSOR_SIGNER && { SPONSOR_SIGNER: process.env.SPONSOR_SIGNER }),
...Object.fromEntries(
Object.entries(process.env).filter(([key]) =>
key.startsWith('NEXT_PUBLIC_'),
),
key.startsWith('NEXT_PUBLIC_')
)
),
};
@ -679,7 +639,7 @@ async function deployToVercel(useGitHub = false) {
vercelClient,
projectId,
vercelEnv,
projectRoot,
projectRoot
);
// Deploy the project
@ -702,8 +662,8 @@ async function deployToVercel(useGitHub = false) {
env: process.env,
});
await new Promise((resolve, reject) => {
vercelDeploy.on('close', code => {
await new Promise<void>((resolve, reject) => {
vercelDeploy.on('close', (code) => {
if (code === 0) {
console.log('✅ Vercel deployment command completed');
resolve();
@ -713,23 +673,23 @@ async function deployToVercel(useGitHub = false) {
}
});
vercelDeploy.on('error', error => {
vercelDeploy.on('error', (error) => {
console.error('❌ Vercel deployment error:', error.message);
reject(error);
});
});
// Wait for deployment to actually complete
let deployment;
let deployment: any;
if (vercelClient) {
try {
deployment = await waitForDeployment(vercelClient, projectId);
} catch (error) {
console.warn(
'⚠️ Could not verify deployment completion:',
error.message,
);
console.log(' Proceeding with domain verification...');
} catch (error: unknown) {
if (error instanceof Error) {
console.warn('⚠️ Could not verify deployment completion:', error.message);
console.log(' Proceeding with domain verification...');
}
throw error;
}
}
@ -740,11 +700,12 @@ async function deployToVercel(useGitHub = false) {
if (vercelClient && deployment) {
try {
actualDomain = deployment.url || domain;
console.log('🌐 Verified actual domain:', actualDomain);
} catch (error) {
console.warn(
'⚠️ Could not verify domain via SDK, using assumed domain',
);
console.log('🌐 Verified actual domain:', actualDomain);
} catch (error: unknown) {
if (error instanceof Error) {
console.warn('⚠️ Could not verify domain via SDK, using assumed domain');
}
throw error;
}
}
@ -752,33 +713,12 @@ async function deployToVercel(useGitHub = false) {
if (actualDomain !== domain) {
console.log('🔄 Updating environment variables with correct domain...');
const webhookUrl =
process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID
? `https://api.neynar.com/f/app/${process.env.NEYNAR_CLIENT_ID}/event`
: `https://${actualDomain}/api/webhook`;
const updatedEnv = {
const updatedEnv: Record<string, string | object> = {
NEXTAUTH_URL: `https://${actualDomain}`,
NEXT_PUBLIC_URL: `https://${actualDomain}`,
};
if (miniAppMetadata) {
const updatedMetadata = await generateFarcasterMetadata(
actualDomain,
fid,
await validateSeedPhrase(process.env.SEED_PHRASE),
process.env.SEED_PHRASE,
webhookUrl,
);
updatedEnv.MINI_APP_METADATA = updatedMetadata;
}
await setEnvironmentVariables(
vercelClient,
projectId,
updatedEnv,
projectRoot,
);
await setEnvironmentVariables(vercelClient, projectId, updatedEnv, projectRoot);
console.log('\n📦 Redeploying with correct domain...');
const vercelRedeploy = spawn('vercel', ['deploy', '--prod'], {
@ -787,8 +727,8 @@ async function deployToVercel(useGitHub = false) {
env: process.env,
});
await new Promise((resolve, reject) => {
vercelRedeploy.on('close', code => {
await new Promise<void>((resolve, reject) => {
vercelRedeploy.on('close', (code) => {
if (code === 0) {
console.log('✅ Redeployment completed');
resolve();
@ -798,7 +738,7 @@ async function deployToVercel(useGitHub = false) {
}
});
vercelRedeploy.on('error', error => {
vercelRedeploy.on('error', (error) => {
console.error('❌ Redeployment error:', error.message);
reject(error);
});
@ -809,20 +749,62 @@ async function deployToVercel(useGitHub = false) {
console.log('\n✨ Deployment complete! Your mini app is now live at:');
console.log(`🌐 https://${domain}`);
console.log(
'\n📝 You can manage your project at https://vercel.com/dashboard',
console.log('\n📝 You can manage your project at https://vercel.com/dashboard');
// Prompt user to sign manifest in browser and paste accountAssociation
console.log(`\n⚠ To complete your mini app manifest, you must sign it using the Farcaster developer portal.`);
console.log('1. Go to: https://farcaster.xyz/~/developers/mini-apps/manifest?domain=' + domain);
console.log('2. Click "Transfer Ownership" and follow the instructions to sign the manifest.');
console.log('3. Copy the resulting accountAssociation JSON from the browser.');
console.log('4. Paste it below when prompted.');
const { userAccountAssociation } = await inquirer.prompt([
{
type: 'editor',
name: 'userAccountAssociation',
message: 'Paste the accountAssociation JSON here:',
validate: (input: string) => {
try {
const parsed = JSON.parse(input);
if (parsed.header && parsed.payload && parsed.signature) {
return true;
}
return 'Invalid accountAssociation: must have header, payload, and signature';
} catch (e) {
return 'Invalid JSON';
}
}
}
]);
const parsedAccountAssociation = JSON.parse(userAccountAssociation);
// Write APP_ACCOUNT_ASSOCIATION to src/lib/constants.ts
const constantsPath = path.join(projectRoot, 'src', 'lib', 'constants.ts');
let constantsContent = fs.readFileSync(constantsPath, 'utf8');
// Replace the APP_ACCOUNT_ASSOCIATION line using a robust, anchored, multiline regex
const newAccountAssociation = `export const APP_ACCOUNT_ASSOCIATION: AccountAssociation | undefined = ${JSON.stringify(parsedAccountAssociation, null, 2)};`;
constantsContent = constantsContent.replace(
/^export const APP_ACCOUNT_ASSOCIATION\s*:\s*AccountAssociation \| undefined\s*=\s*[^;]*;/m,
newAccountAssociation
);
} catch (error) {
console.error('\n❌ Deployment failed:', error.message);
process.exit(1);
fs.writeFileSync(constantsPath, constantsContent);
console.log('\n✅ APP_ACCOUNT_ASSOCIATION updated in src/lib/constants.ts');
} catch (error: unknown) {
if (error instanceof Error) {
console.error('\n❌ Deployment failed:', error.message);
process.exit(1);
}
throw error;
}
}
async function main() {
async function main(): Promise<void> {
try {
console.log('🚀 Vercel Mini App Deployment (SDK Edition)');
console.log(
'This script will deploy your mini app to Vercel using the Vercel SDK.',
'This script will deploy your mini app to Vercel using the Vercel SDK.'
);
console.log('\nThe script will:');
console.log('1. Check for required environment variables');
@ -833,13 +815,16 @@ async function main() {
// Check if @vercel/sdk is installed
try {
await import('@vercel/sdk');
} catch (error) {
console.log('📦 Installing @vercel/sdk...');
execSync('npm install @vercel/sdk', {
cwd: projectRoot,
stdio: 'inherit',
});
console.log('✅ @vercel/sdk installed successfully');
} catch (error: unknown) {
if (error instanceof Error) {
console.log('📦 Installing @vercel/sdk...');
execSync('npm install @vercel/sdk', {
cwd: projectRoot,
stdio: 'inherit'
});
console.log('✅ @vercel/sdk installed successfully');
}
throw error;
}
await checkRequiredEnvVars();
@ -895,9 +880,13 @@ async function main() {
}
await deployToVercel(useGitHub);
} catch (error) {
console.error('\n❌ Error:', error.message);
process.exit(1);
} catch (error: unknown) {
if (error instanceof Error) {
console.error('\n❌ Error:', error.message);
process.exit(1);
}
throw error;
}
}

View File

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

View File

@ -118,7 +118,7 @@ export function AuthDialog({
const content = getStepContent();
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4">
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4">
<div className="bg-white dark:bg-gray-800 rounded-xl w-full max-w-md shadow-2xl border border-gray-200 dark:border-gray-700 max-h-[80vh] sm:max-h-[90vh] flex flex-col">
<div className="flex justify-between items-center p-4 sm:p-6 pb-3 sm:pb-4 border-b border-gray-200 dark:border-gray-700 flex-shrink-0">
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">

View File

@ -1,9 +1,10 @@
'use client';
import { useCallback, useState, useEffect } from 'react';
import { type ComposeCast } from '@farcaster/miniapp-sdk';
import { useMiniApp } from '@neynar/react';
import { Button } from './Button';
import { useMiniApp } from '@neynar/react';
import { type ComposeCast } from '@farcaster/miniapp-sdk';
import { APP_URL } from '~/lib/constants';
interface EmbedConfig {
path?: string;
@ -79,8 +80,7 @@ export function ShareButton({
return embed;
}
if (embed.path) {
const baseUrl =
process.env.NEXT_PUBLIC_URL || window.location.origin;
const baseUrl = APP_URL || window.location.origin;
const url = new URL(`${baseUrl}${embed.path}`);
// Add UTM parameters

View File

@ -3,6 +3,7 @@
import { useCallback, useState } from 'react';
import { type Haptics } from '@farcaster/miniapp-sdk';
import { useMiniApp } from '@neynar/react';
import { APP_URL } from '~/lib/constants';
import { Button } from '../Button';
import { NeynarAuthButton } from '../NeynarAuthButton/index';
import { ShareButton } from '../Share';
@ -96,7 +97,7 @@ export function ActionsTab() {
*/
const copyUserShareUrl = useCallback(async () => {
if (context?.user?.fid) {
const userShareUrl = `${process.env.NEXT_PUBLIC_URL}/share/${context.user.fid}`;
const userShareUrl = `${APP_URL}/share/${context.user.fid}`;
await navigator.clipboard.writeText(userShareUrl);
setNotificationState(prev => ({ ...prev, shareUrlCopied: true }));
setTimeout(
@ -130,9 +131,7 @@ export function ActionsTab() {
cast={{
text: 'Check out this awesome frame @1 @2 @3! 🚀🪐',
bestFriends: true,
embeds: [
`${process.env.NEXT_PUBLIC_URL}/share/${context?.user?.fid || ''}`,
],
embeds: [`${APP_URL}/share/${context?.user?.fid || ''}`],
}}
className="w-full"
/>

View File

@ -1,3 +1,5 @@
import { type AccountAssociation } from '@farcaster/miniapp-node';
/**
* Application constants and configuration values.
*
@ -14,63 +16,70 @@
* The base URL of the application.
* Used for generating absolute URLs for assets and API endpoints.
*/
export const APP_URL = process.env.NEXT_PUBLIC_URL!;
export const APP_URL: string = process.env.NEXT_PUBLIC_URL!;
/**
* The name of the mini app as displayed to users.
* Used in titles, headers, and app store listings.
*/
export const APP_NAME = 'shreyas-testing-mini-app';
export const APP_NAME: string = 'Starter Kit';
/**
* A brief description of the mini app's functionality.
* Used in app store listings and metadata.
*/
export const APP_DESCRIPTION = 'A Farcaster mini app created with Neynar';
export const APP_DESCRIPTION: string = 'A demo of the Neynar Starter Kit';
/**
* The primary category for the mini app.
* Used for app store categorization and discovery.
*/
export const APP_PRIMARY_CATEGORY = '';
export const APP_PRIMARY_CATEGORY: string = 'developer-tools';
/**
* Tags associated with the mini app.
* Used for search and discovery in app stores.
*/
export const APP_TAGS = ['neynar', 'starter-kit', 'demo'];
export const APP_TAGS: string[] = ['neynar', 'starter-kit', 'demo'];
// --- Asset URLs ---
/**
* URL for the app's icon image.
* Used in app store listings and UI elements.
*/
export const APP_ICON_URL = `${APP_URL}/icon.png`;
export const APP_ICON_URL: string = `${APP_URL}/icon.png`;
/**
* URL for the app's Open Graph image.
* Used for social media sharing and previews.
*/
export const APP_OG_IMAGE_URL = `${APP_URL}/api/opengraph-image`;
export const APP_OG_IMAGE_URL: string = `${APP_URL}/api/opengraph-image`;
/**
* URL for the app's splash screen image.
* Displayed during app loading.
*/
export const APP_SPLASH_URL = `${APP_URL}/splash.png`;
export const APP_SPLASH_URL: string = `${APP_URL}/splash.png`;
/**
* Background color for the splash screen.
* Used as fallback when splash image is loading.
*/
export const APP_SPLASH_BACKGROUND_COLOR = '#f7f7f7';
export const APP_SPLASH_BACKGROUND_COLOR: string = "#f7f7f7";
/**
* Account association for the mini app.
* Used to associate the mini app with a Farcaster account.
* If not provided, the mini app will be unsigned and have limited capabilities.
*/
export const APP_ACCOUNT_ASSOCIATION: AccountAssociation | undefined = undefined;
// --- UI Configuration ---
/**
* Text displayed on the main action button.
* Used for the primary call-to-action in the mini app.
*/
export const APP_BUTTON_TEXT = 'Launch Mini App';
export const APP_BUTTON_TEXT: string = 'Launch NSK';
// --- Integration Configuration ---
/**
@ -80,8 +89,7 @@ export const APP_BUTTON_TEXT = 'Launch Mini App';
* Neynar webhook endpoint. Otherwise, falls back to a local webhook
* endpoint for development and testing.
*/
export const APP_WEBHOOK_URL =
process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID
export const APP_WEBHOOK_URL: string = process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID
? `https://api.neynar.com/f/app/${process.env.NEYNAR_CLIENT_ID}/event`
: `${APP_URL}/api/webhook`;
@ -92,7 +100,7 @@ export const APP_WEBHOOK_URL =
* When false, wallet functionality is completely hidden from the UI.
* Useful for mini apps that don't require wallet integration.
*/
export const USE_WALLET = true;
export const USE_WALLET: boolean = true;
/**
* Flag to enable/disable analytics tracking.
@ -101,7 +109,7 @@ export const USE_WALLET = true;
* When false, analytics collection is disabled.
* Useful for privacy-conscious users or development environments.
*/
export const ANALYTICS_ENABLED = true;
export const ANALYTICS_ENABLED: boolean = true;
// PLEASE DO NOT UPDATE THIS
export const SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN = {

View File

@ -1,5 +1,6 @@
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { type Manifest } from '@farcaster/miniapp-node';
import {
APP_BUTTON_TEXT,
APP_DESCRIPTION,
@ -8,35 +9,12 @@ import {
APP_OG_IMAGE_URL,
APP_PRIMARY_CATEGORY,
APP_SPLASH_BACKGROUND_COLOR,
APP_SPLASH_URL,
APP_TAGS,
APP_URL,
APP_WEBHOOK_URL,
APP_ACCOUNT_ASSOCIATION,
} from './constants';
import { APP_SPLASH_URL } from './constants';
interface MiniAppMetadata {
version: string;
name: string;
iconUrl: string;
homeUrl: string;
imageUrl?: string;
buttonTitle?: string;
splashImageUrl?: string;
splashBackgroundColor?: string;
webhookUrl?: string;
description?: string;
primaryCategory?: string;
tags?: string[];
}
interface MiniAppManifest {
accountAssociation?: {
header: string;
payload: string;
signature: string;
};
frame: MiniAppMetadata;
}
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
@ -63,31 +41,10 @@ export function getMiniAppEmbedMetadata(ogImageUrl?: string) {
};
}
export async function getFarcasterMetadata(): Promise<MiniAppManifest> {
// First check for MINI_APP_METADATA in .env and use that if it exists
if (process.env.MINI_APP_METADATA) {
try {
const metadata = JSON.parse(process.env.MINI_APP_METADATA);
return metadata;
} catch (error) {
console.warn(
'Failed to parse MINI_APP_METADATA from environment:',
error,
);
}
}
if (!APP_URL) {
throw new Error('NEXT_PUBLIC_URL not configured');
}
export async function getFarcasterDomainManifest(): Promise<Manifest> {
return {
accountAssociation: {
header: '',
payload: '',
signature: '',
},
frame: {
accountAssociation: APP_ACCOUNT_ASSOCIATION,
miniapp: {
version: '1',
name: APP_NAME ?? 'Neynar Starter Kit',
iconUrl: APP_ICON_URL,