2025-03-26 11:42:51 -07:00

117 lines
3.3 KiB
TypeScript

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));
}
export function getSecretEnvVars() {
const seedPhrase = process.env.SEED_PHRASE;
const fid = process.env.FID;
if (!seedPhrase || !fid) {
return null;
}
return { seedPhrase, fid };
}
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;
if (!appUrl) {
throw new Error('NEXT_PUBLIC_URL not configured');
}
// Get the domain from the URL (without https:// prefix)
const domain = new URL(appUrl).hostname;
console.log('Using domain for manifest:', domain);
const secretEnvVars = getSecretEnvVars();
if (!secretEnvVars) {
console.warn('No seed phrase or FID found in environment variables -- generating unsigned metadata');
}
let accountAssociation;
if (secretEnvVars) {
// Generate account from seed phrase
const account = mnemonicToAccount(secretEnvVars.seedPhrase);
const custodyAddress = account.address;
const header = {
fid: parseInt(secretEnvVars.fid),
type: 'custody',
key: custodyAddress,
};
const encodedHeader = Buffer.from(JSON.stringify(header), 'utf-8').toString('base64');
const payload = {
domain
};
const encodedPayload = Buffer.from(JSON.stringify(payload), 'utf-8').toString('base64url');
const signature = await account.signMessage({
message: `${encodedHeader}.${encodedPayload}`
});
const encodedSignature = Buffer.from(signature, 'utf-8').toString('base64url');
accountAssociation = {
header: encodedHeader,
payload: encodedPayload,
signature: encodedSignature
};
}
// Determine webhook URL based on whether Neynar is enabled
const neynarApiKey = process.env.NEYNAR_API_KEY;
const neynarClientId = process.env.NEYNAR_CLIENT_ID;
const webhookUrl = neynarApiKey && neynarClientId
? `https://api.neynar.com/f/app/${neynarClientId}/event`
: `${appUrl}/api/webhook`;
return {
accountAssociation,
frame: {
version: "1",
name: process.env.NEXT_PUBLIC_FRAME_NAME || "Frames v2 Demo",
iconUrl: process.env.NEXT_PUBLIC_FRAME_ICON_IMAGE_URL || `${appUrl}/icon.png`,
homeUrl: appUrl,
imageUrl: `${appUrl}/opengraph-image`,
buttonTitle: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT || "Launch Frame",
splashImageUrl: process.env.NEXT_PUBLIC_FRAME_SPLASH_IMAGE_URL || `${appUrl}/splash.png`,
splashBackgroundColor: "#f7f7f7",
webhookUrl,
},
};
}