mirror of
https://github.com/neynarxyz/create-farcaster-mini-app.git
synced 2025-12-09 02:42:30 -05:00
Merge branch 'main' into veganbeef/deploy-script-update
This commit is contained in:
16
src/app/api/auth/nonce/route.ts
Normal file
16
src/app/api/auth/nonce/route.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getNeynarClient } from '~/lib/neynar';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const client = getNeynarClient();
|
||||
const response = await client.fetchNonce();
|
||||
return NextResponse.json(response);
|
||||
} catch (error) {
|
||||
console.error('Error fetching nonce:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch nonce' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
43
src/app/api/auth/session-signers/route.ts
Normal file
43
src/app/api/auth/session-signers/route.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getNeynarClient } from '~/lib/neynar';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const message = searchParams.get('message');
|
||||
const signature = searchParams.get('signature');
|
||||
|
||||
if (!message || !signature) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Message and signature are required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const client = getNeynarClient();
|
||||
const data = await client.fetchSigners({ message, signature });
|
||||
const signers = data.signers;
|
||||
|
||||
// Fetch user data if signers exist
|
||||
let user = null;
|
||||
if (signers && signers.length > 0 && signers[0].fid) {
|
||||
const {
|
||||
users: [fetchedUser],
|
||||
} = await client.fetchBulkUsers({
|
||||
fids: [signers[0].fid],
|
||||
});
|
||||
user = fetchedUser;
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
signers,
|
||||
user,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in session-signers API:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch signers' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
42
src/app/api/auth/signer/route.ts
Normal file
42
src/app/api/auth/signer/route.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getNeynarClient } from '~/lib/neynar';
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
const neynarClient = getNeynarClient();
|
||||
const signer = await neynarClient.createSigner();
|
||||
return NextResponse.json(signer);
|
||||
} catch (error) {
|
||||
console.error('Error fetching signer:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch signer' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const signerUuid = searchParams.get('signerUuid');
|
||||
|
||||
if (!signerUuid) {
|
||||
return NextResponse.json(
|
||||
{ error: 'signerUuid is required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const neynarClient = getNeynarClient();
|
||||
const signer = await neynarClient.lookupSigner({
|
||||
signerUuid,
|
||||
});
|
||||
return NextResponse.json(signer);
|
||||
} catch (error) {
|
||||
console.error('Error fetching signed key:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch signed key' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
91
src/app/api/auth/signer/signed_key/route.ts
Normal file
91
src/app/api/auth/signer/signed_key/route.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getNeynarClient } from '~/lib/neynar';
|
||||
import { mnemonicToAccount } from 'viem/accounts';
|
||||
import {
|
||||
SIGNED_KEY_REQUEST_TYPE,
|
||||
SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN,
|
||||
} from '~/lib/constants';
|
||||
|
||||
const postRequiredFields = ['signerUuid', 'publicKey'];
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const body = await request.json();
|
||||
|
||||
// Validate required fields
|
||||
for (const field of postRequiredFields) {
|
||||
if (!body[field]) {
|
||||
return NextResponse.json(
|
||||
{ error: `${field} is required` },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { signerUuid, publicKey, redirectUrl } = body;
|
||||
|
||||
if (redirectUrl && typeof redirectUrl !== 'string') {
|
||||
return NextResponse.json(
|
||||
{ error: 'redirectUrl must be a string' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the app's account from seed phrase
|
||||
const seedPhrase = process.env.SEED_PHRASE;
|
||||
const shouldSponsor = process.env.SPONSOR_SIGNER === 'true';
|
||||
|
||||
if (!seedPhrase) {
|
||||
return NextResponse.json(
|
||||
{ error: 'App configuration missing (SEED_PHRASE or FID)' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
const neynarClient = getNeynarClient();
|
||||
|
||||
const account = mnemonicToAccount(seedPhrase);
|
||||
|
||||
const {
|
||||
user: { fid },
|
||||
} = await neynarClient.lookupUserByCustodyAddress({
|
||||
custodyAddress: account.address,
|
||||
});
|
||||
|
||||
const appFid = fid;
|
||||
|
||||
// Generate deadline (24 hours from now)
|
||||
const deadline = Math.floor(Date.now() / 1000) + 86400;
|
||||
|
||||
// Generate EIP-712 signature
|
||||
const signature = await account.signTypedData({
|
||||
domain: SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN,
|
||||
types: {
|
||||
SignedKeyRequest: SIGNED_KEY_REQUEST_TYPE,
|
||||
},
|
||||
primaryType: 'SignedKeyRequest',
|
||||
message: {
|
||||
requestFid: BigInt(appFid),
|
||||
key: publicKey,
|
||||
deadline: BigInt(deadline),
|
||||
},
|
||||
});
|
||||
|
||||
const signer = await neynarClient.registerSignedKey({
|
||||
appFid,
|
||||
deadline,
|
||||
signature,
|
||||
signerUuid,
|
||||
...(redirectUrl && { redirectUrl }),
|
||||
...(shouldSponsor && { sponsor: { sponsored_by_neynar: true } }),
|
||||
});
|
||||
|
||||
return NextResponse.json(signer);
|
||||
} catch (error) {
|
||||
console.error('Error registering signed key:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to register signed key' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
38
src/app/api/auth/signers/route.ts
Normal file
38
src/app/api/auth/signers/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getNeynarClient } from '~/lib/neynar';
|
||||
|
||||
const requiredParams = ['message', 'signature'];
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const params: Record<string, string | null> = {};
|
||||
for (const param of requiredParams) {
|
||||
params[param] = searchParams.get(param);
|
||||
if (!params[param]) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: `${param} parameter is required`,
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const message = params.message as string;
|
||||
const signature = params.signature as string;
|
||||
|
||||
try {
|
||||
const client = getNeynarClient();
|
||||
const data = await client.fetchSigners({ message, signature });
|
||||
const signers = data.signers;
|
||||
return NextResponse.json({
|
||||
signers,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching signers:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch signers' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
46
src/app/api/auth/update-session/route.ts
Normal file
46
src/app/api/auth/update-session/route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from '~/auth';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.fid) {
|
||||
return NextResponse.json(
|
||||
{ error: 'No authenticated session found' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { signers, user } = body;
|
||||
|
||||
if (!signers || !user) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Signers and user are required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// For NextAuth to update the session, we need to trigger the JWT callback
|
||||
// This is typically done by calling the session endpoint with updated data
|
||||
// However, we can't directly modify the session token from here
|
||||
|
||||
// Instead, we'll store the data temporarily and let the client refresh the session
|
||||
// The session will be updated when the JWT callback is triggered
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Session update prepared',
|
||||
signers,
|
||||
user,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error preparing session update:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to prepare session update' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,38 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
import type { Session } from "next-auth";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
import { MiniAppProvider } from "@neynar/react";
|
||||
import { SafeFarcasterSolanaProvider } from "~/components/providers/SafeFarcasterSolanaProvider";
|
||||
import { ANALYTICS_ENABLED } from "~/lib/constants";
|
||||
import dynamic from 'next/dynamic';
|
||||
import type { Session } from 'next-auth';
|
||||
import { SessionProvider } from 'next-auth/react';
|
||||
import { MiniAppProvider } from '@neynar/react';
|
||||
import { SafeFarcasterSolanaProvider } from '~/components/providers/SafeFarcasterSolanaProvider';
|
||||
import { ANALYTICS_ENABLED } from '~/lib/constants';
|
||||
import { AuthKitProvider } from '@farcaster/auth-kit';
|
||||
|
||||
const WagmiProvider = dynamic(
|
||||
() => import("~/components/providers/WagmiProvider"),
|
||||
() => import('~/components/providers/WagmiProvider'),
|
||||
{
|
||||
ssr: false,
|
||||
}
|
||||
);
|
||||
|
||||
export function Providers({ session, children }: { session: Session | null, children: React.ReactNode }) {
|
||||
const solanaEndpoint = process.env.SOLANA_RPC_ENDPOINT || "https://solana-rpc.publicnode.com";
|
||||
export function Providers({
|
||||
session,
|
||||
children,
|
||||
}: {
|
||||
session: Session | null;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const solanaEndpoint =
|
||||
process.env.SOLANA_RPC_ENDPOINT || 'https://solana-rpc.publicnode.com';
|
||||
return (
|
||||
<SessionProvider session={session}>
|
||||
<WagmiProvider>
|
||||
<MiniAppProvider analyticsEnabled={ANALYTICS_ENABLED} backButtonEnabled={true}>
|
||||
<MiniAppProvider
|
||||
analyticsEnabled={ANALYTICS_ENABLED}
|
||||
backButtonEnabled={true}
|
||||
>
|
||||
<SafeFarcasterSolanaProvider endpoint={solanaEndpoint}>
|
||||
{children}
|
||||
<AuthKitProvider config={{}}>{children}</AuthKitProvider>
|
||||
</SafeFarcasterSolanaProvider>
|
||||
</MiniAppProvider>
|
||||
</WagmiProvider>
|
||||
|
||||
Reference in New Issue
Block a user