From 5420b8fca0879142e78014290560b37ba53ca82c Mon Sep 17 00:00:00 2001 From: veganbeef Date: Fri, 30 May 2025 16:35:31 -0700 Subject: [PATCH 1/4] feat: update to use MiniAppProvider --- bin/init.js | 4 +- package.json | 2 +- src/app/providers.tsx | 10 +- src/components/Demo.tsx | 14 +- src/components/providers/FrameProvider.tsx | 177 --------------------- 5 files changed, 20 insertions(+), 187 deletions(-) delete mode 100644 src/components/providers/FrameProvider.tsx diff --git a/bin/init.js b/bin/init.js index 388d81b..4bc70b9 100644 --- a/bin/init.js +++ b/bin/init.js @@ -329,6 +329,7 @@ export async function init() { "@farcaster/frame-sdk": ">=0.0.31 <1.0.0", "@farcaster/frame-wagmi-connector": ">=0.0.19 <1.0.0", "@farcaster/mini-app-solana": "^0.0.5", + "@neynar/react": "^0.9.7", "@radix-ui/react-label": "^2.1.1", "@solana/wallet-adapter-react": "^0.15.38", "@tanstack/react-query": "^5.61.0", @@ -363,10 +364,9 @@ export async function init() { "typescript": "^5" }; - // Add Neynar dependencies if selected + // Add Neynar SDK if selected if (useNeynar) { packageJson.dependencies['@neynar/nodejs-sdk'] = '^2.19.0'; - packageJson.dependencies['@neynar/react'] = '^0.9.7'; } fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); diff --git a/package.json b/package.json index 599fed0..968e0dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@neynar/create-farcaster-mini-app", - "version": "1.3.0", + "version": "1.3.1", "type": "module", "private": false, "access": "public", diff --git a/src/app/providers.tsx b/src/app/providers.tsx index e506826..7b5160a 100644 --- a/src/app/providers.tsx +++ b/src/app/providers.tsx @@ -1,9 +1,9 @@ "use client"; import dynamic from "next/dynamic"; -import type { Session } from "next-auth" -import { SessionProvider } from "next-auth/react" -import { FrameProvider } from "~/components/providers/FrameProvider"; +import type { Session } from "next-auth"; +import { SessionProvider } from "next-auth/react"; +import { MiniAppProvider } from "@neynar/react"; import { SafeFarcasterSolanaProvider } from "~/components/providers/SafeFarcasterSolanaProvider"; const WagmiProvider = dynamic( @@ -18,11 +18,11 @@ export function Providers({ session, children }: { session: Session | null, chil return ( - + {children} - + ); diff --git a/src/components/Demo.tsx b/src/components/Demo.tsx index 980b046..4c46d81 100644 --- a/src/components/Demo.tsx +++ b/src/components/Demo.tsx @@ -29,14 +29,24 @@ import { truncateAddress } from "~/lib/truncateAddress"; import { base, degen, mainnet, optimism, unichain } from "wagmi/chains"; import { BaseError, UserRejectedRequestError } from "viem"; import { useSession } from "next-auth/react"; +import { useMiniApp } from "@neynar/react"; import { Label } from "~/components/ui/label"; -import { useFrame } from "~/components/providers/FrameProvider"; import { PublicKey, SystemProgram, Transaction } from '@solana/web3.js'; export default function Demo( { title }: { title?: string } = { title: "Frames v2 Demo" } ) { - const { isSDKLoaded, context, added, notificationDetails, lastEvent, addFrame, addFrameResult, openUrl, close } = useFrame(); + const { + isSDKLoaded, + context, + added, + notificationDetails, + lastEvent, + addFrame, + addFrameResult, + openUrl, + close, + } = useMiniApp(); const [isContextOpen, setIsContextOpen] = useState(false); const [txHash, setTxHash] = useState(null); const [sendNotificationResult, setSendNotificationResult] = useState(""); diff --git a/src/components/providers/FrameProvider.tsx b/src/components/providers/FrameProvider.tsx deleted file mode 100644 index a4d29d9..0000000 --- a/src/components/providers/FrameProvider.tsx +++ /dev/null @@ -1,177 +0,0 @@ -"use client"; - -import { useEffect, useState, useCallback } from "react"; -import sdk, { type Context, type FrameNotificationDetails, AddMiniApp } from "@farcaster/frame-sdk"; -import { createStore } from "mipd"; -import React from "react"; -import { logEvent } from "../../lib/amplitude"; - -interface FrameContextType { - isSDKLoaded: boolean; - context: Context.FrameContext | undefined; - openUrl: (url: string) => Promise; - close: () => Promise; - added: boolean; - notificationDetails: FrameNotificationDetails | null; - lastEvent: string; - addFrame: () => Promise; - addFrameResult: string; -} - -const FrameContext = React.createContext(undefined); - -export function useFrame() { - const [isSDKLoaded, setIsSDKLoaded] = useState(false); - const [context, setContext] = useState(); - const [added, setAdded] = useState(false); - const [notificationDetails, setNotificationDetails] = useState(null); - const [lastEvent, setLastEvent] = useState(""); - const [addFrameResult, setAddFrameResult] = useState(""); - - // SDK actions only work in mini app clients, so this pattern supports browser actions as well - const openUrl = useCallback(async (url: string) => { - if (context) { - await sdk.actions.openUrl(url); - } else { - window.open(url, '_blank'); - } - }, [context]); - - const close = useCallback(async () => { - if (context) { - await sdk.actions.close(); - } else { - window.close(); - } - }, [context]); - - const addFrame = useCallback(async () => { - try { - setNotificationDetails(null); - const result = await sdk.actions.addFrame(); - - if (result.notificationDetails) { - setNotificationDetails(result.notificationDetails); - } - setAddFrameResult( - result.notificationDetails - ? `Added, got notificaton token ${result.notificationDetails.token} and url ${result.notificationDetails.url}` - : "Added, got no notification details" - ); - } catch (error) { - if (error instanceof AddMiniApp.RejectedByUser || error instanceof AddMiniApp.InvalidDomainManifest) { - setAddFrameResult(`Not added: ${error.message}`); - }else { - setAddFrameResult(`Error: ${error}`); - } - } - }, []); - - useEffect(() => { - const load = async () => { - const context = await sdk.context; - setContext(context); - setIsSDKLoaded(true); - - const amplitudeBaseEvent = { - fid: context.user.fid, - username: context.user.username, - clientFid: context.client.clientFid, - }; - const amplitudeUserId = `${context.user.fid}-${context.client.clientFid}`; - - logEvent("Frame Opened", { - ...amplitudeBaseEvent, - location: context.location, - added: context.client.added, - }, amplitudeUserId); - - // Set up event listeners - sdk.on("frameAdded", ({ notificationDetails }) => { - console.log("Frame added", notificationDetails); - setAdded(true); - setNotificationDetails(notificationDetails ?? null); - setLastEvent("Frame added"); - logEvent("Frame Added", amplitudeBaseEvent, amplitudeUserId); - }); - - sdk.on("frameAddRejected", ({ reason }) => { - console.log("Frame add rejected", reason); - setAdded(false); - setLastEvent(`Frame add rejected: ${reason}`); - logEvent("Frame Add Rejected", amplitudeBaseEvent, amplitudeUserId); - }); - - sdk.on("frameRemoved", () => { - console.log("Frame removed"); - setAdded(false); - setLastEvent("Frame removed"); - logEvent("Frame Removed", amplitudeBaseEvent, amplitudeUserId); - }); - - sdk.on("notificationsEnabled", ({ notificationDetails }) => { - console.log("Notifications enabled", notificationDetails); - setNotificationDetails(notificationDetails ?? null); - setLastEvent("Notifications enabled"); - logEvent("Notifications Enabled", amplitudeBaseEvent, amplitudeUserId); - }); - - sdk.on("notificationsDisabled", () => { - console.log("Notifications disabled"); - setNotificationDetails(null); - setLastEvent("Notifications disabled"); - logEvent("Notifications Disabled", amplitudeBaseEvent, amplitudeUserId); - }); - - sdk.on("primaryButtonClicked", () => { - console.log("Primary button clicked"); - setLastEvent("Primary button clicked"); - }); - - // Call ready action - console.log("Calling ready"); - sdk.actions.ready({}); - - // Set up MIPD Store - const store = createStore(); - store.subscribe((providerDetails) => { - console.log("PROVIDER DETAILS", providerDetails); - }); - }; - - if (sdk && !isSDKLoaded) { - console.log("Calling load"); - setIsSDKLoaded(true); - load(); - return () => { - sdk.removeAllListeners(); - }; - } - }, [isSDKLoaded]); - - return { - isSDKLoaded, - context, - added, - notificationDetails, - lastEvent, - addFrame, - addFrameResult, - openUrl, - close, - }; -} - -export function FrameProvider({ children }: { children: React.ReactNode }) { - const frameContext = useFrame(); - - if (!frameContext.isSDKLoaded) { - return
Loading...
; - } - - return ( - - {children} - - ); -} \ No newline at end of file From 7b87bc4dfa215352616b29a27368a7fee725792d Mon Sep 17 00:00:00 2001 From: veganbeef Date: Fri, 30 May 2025 16:49:45 -0700 Subject: [PATCH 2/4] fix: remove amplitude lib (moved to react package) --- src/lib/amplitude.ts | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 src/lib/amplitude.ts diff --git a/src/lib/amplitude.ts b/src/lib/amplitude.ts deleted file mode 100644 index 4dbca45..0000000 --- a/src/lib/amplitude.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { APP_URL } from "./constants"; - -// Amplitude tracking -- only runs if configured via the CLI or in the .env file -export function logEvent( - eventType: string, - eventProperties: Record = {}, - deviceId: string | null = null -) { - if (process.env.NEXT_PUBLIC_ANALYTICS_ENABLED?.toLowerCase() !== 'true' || process.env.NODE_ENV !== "production") { - return; - } - - const event = { - event_type: eventType, - api_key: '0c4fe46171b9bb8eca2ca61eb71f2e19', - time: Date.now(), - user_id: APP_URL, - ...(deviceId && { device_id: deviceId }), - ...(Object.keys(eventProperties).length && { event_properties: eventProperties }) - }; - - fetch('https://api2.amplitude.com/2/httpapi', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - api_key: '0c4fe46171b9bb8eca2ca61eb71f2e19', - events: [event] - }) - }).catch(error => { - console.error('Amplitude tracking error:', error); - }); -} \ No newline at end of file From 39437af6f077a9371a958e170c1eb58a763023f1 Mon Sep 17 00:00:00 2001 From: veganbeef Date: Mon, 2 Jun 2025 19:55:44 -0700 Subject: [PATCH 3/4] feat: add celo --- src/components/providers/WagmiProvider.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/providers/WagmiProvider.tsx b/src/components/providers/WagmiProvider.tsx index f1472d5..d3cf282 100644 --- a/src/components/providers/WagmiProvider.tsx +++ b/src/components/providers/WagmiProvider.tsx @@ -1,5 +1,5 @@ import { createConfig, http, WagmiProvider } from "wagmi"; -import { base, degen, mainnet, optimism, unichain } from "wagmi/chains"; +import { base, degen, mainnet, optimism, unichain, celo } from "wagmi/chains"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { farcasterFrame } from "@farcaster/frame-wagmi-connector"; import { coinbaseWallet, metaMask } from 'wagmi/connectors'; @@ -42,13 +42,14 @@ function useCoinbaseWalletAutoConnect() { } export const config = createConfig({ - chains: [base, optimism, mainnet, degen, unichain], + chains: [base, optimism, mainnet, degen, unichain, celo], transports: { [base.id]: http(), [optimism.id]: http(), [mainnet.id]: http(), [degen.id]: http(), [unichain.id]: http(), + [celo.id]: http(), }, connectors: [ farcasterFrame(), From 3bf80740c046dc69643cd7acf507cfad6458b5bf Mon Sep 17 00:00:00 2001 From: veganbeef Date: Wed, 4 Jun 2025 09:33:55 -0700 Subject: [PATCH 4/4] fix: update neynar/react version number --- bin/init.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/init.js b/bin/init.js index 4bc70b9..6f1b11a 100644 --- a/bin/init.js +++ b/bin/init.js @@ -329,7 +329,7 @@ export async function init() { "@farcaster/frame-sdk": ">=0.0.31 <1.0.0", "@farcaster/frame-wagmi-connector": ">=0.0.19 <1.0.0", "@farcaster/mini-app-solana": "^0.0.5", - "@neynar/react": "^0.9.7", + "@neynar/react": "^1.2.2", "@radix-ui/react-label": "^2.1.1", "@solana/wallet-adapter-react": "^0.15.38", "@tanstack/react-query": "^5.61.0", @@ -339,10 +339,10 @@ export async function init() { "dotenv": "^16.4.7", "lucide-react": "^0.469.0", "mipd": "^0.0.7", - "next": "15.0.3", + "next": "^15", "next-auth": "^4.24.11", - "react": "^18", - "react-dom": "^18", + "react": "^19", + "react-dom": "^19", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", "viem": "^2.23.6", @@ -352,8 +352,8 @@ export async function init() { packageJson.devDependencies = { "@types/node": "^20", - "@types/react": "^18", - "@types/react-dom": "^18", + "@types/react": "^19", + "@types/react-dom": "^19", "crypto": "^1.0.1", "eslint": "^8", "eslint-config-next": "15.0.3",