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