# Add a Verification Source: https://docs.neynar.com/docs/add-a-verification Walkthrough on how to add an ethereum or solana address as a verification to a user's farcaster profile * If you want to integrate Farcaster auth for your users, easiest way to start is [Sign in with Neynar](/docs/how-to-let-users-connect-farcaster-accounts-with-write-access-for-free-using-sign-in-with-neynar-siwn) (Neynar pays all onchain fee) * If you want dedicated signers for your user or bot, simplest to clone this [example app](https://github.com/neynarxyz/farcaster-examples/tree/main/managed-signers) for quickstart This guide demonstrates adding an Ethereum address verification with the Neynar SDK. Check out this [Getting Started Guide](/docs/getting-started-with-neynar) to learn how to set up your environment and get an API key. First, initialize the client: ```javascript Javascript // npm i @neynar/nodejs-sdk import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk"; // make sure to set your NEYNAR_API_KEY .env // don't have an API key yet? get one at neynar.com const config = new Configuration({ apiKey:process.env.NEYNAR_API_KEY, }); const client = new NeynarAPIClient(config); const signer = process.env.NEYNAR_SIGNER; ``` Then like a cast: ```javascript Javascript const hash = "0x6932a9256f34e18892d498abb6d00ccf9f1c50d6"; client.publishReaction({ signerUuid: signer, reactionType: "like", target:hash }); ``` Recasting works the same way, just replace "like" with "recast": ```javascript Javascript const hash = "0x6932a9256f34e18892d498abb6d00ccf9f1c50d6"; client.publishReaction({ signerUuid: signer, reactionType: "like", target:hash }); ``` The end result should look like this: ```javascript Javascript { success: true; } ``` To verify that the reaction was published, you can fetch the cast's reactions: ```javascript Javascript const types = ReactionsType.All; const reactions = await client.fetchCastReactions({ hash, types: [types] }); console.log(reactions); ``` Which would print out ```json { "result": { "casts": [ { "type": "like", "hash": "0x691fabb3fc58bd4022d4358b2bc4f44469ad959a", "reactor": { "fid": "4640", "username": "picture", "displayName": "Picture", "pfp": { "url": "https://lh3.googleusercontent.com/erYudyT5dg9E_esk8I1kqB4bUJjWAmlNu4VRnv9iUuq_by7QjoDtZzj_mjPqel4NYQnvqYr1R54m9Oxp9moHQkierpY8KcYLxyIJ" }, "followerCount": "45", "followingCount": "57" }, "timestamp": "2023-12-10T15:26:45.000Z", "castHash": "0x6932a9256f34e18892d498abb6d00ccf9f1c50d6" } ], "next": { "cursor": null } } } ``` That's it! You can now like or recast any cast on Farcaster. PS - to learn more about how writes technically work on Farcaster, read [Write to Farcaster with Neynar Managed Signers](/docs/write-to-farcaster-with-neynar-managed-signers) ### Ready to start building? Get your subscription at [neynar.com](https://neynar.com) and reach out to us on [Slack](https://neynar.com/slack) with any questions! # Address User Score Contract Source: https://docs.neynar.com/docs/address-user-score-contract Get user quality score for an address connected to a Farcaster profile. Read prior context on user scores in [Neynar User Quality Score](/docs/neynar-user-quality-score) User scores are particularly useful if anonymous addresses are interacting with your contract and you want to restrict interaction to high quality addresses. Neynar already supports user quality scores offchain (read more in [Neynar User Quality Score](/docs/neynar-user-quality-score)), this brings them onchain and makes it available to smart contracts. Now, on the Base Mainnet and Sepolia testnet, smart contracts can query the fid linked to any ETH address and the quality score for that FID. ## Contract | **Chain** | **Address** | **Deploy Transaction** | | ------------ | ------------------------------------------ | ------------------------------------------------------------------ | | Base Mainnet | 0xd3C43A38D1D3E47E9c420a733e439B03FAAdebA8 | 0x059259c15f660a4b5bd10695b037692654415f60e13569c7a06e99cfd55a54b0 | | Base Sepolia | 0x7104CFfdf6A1C9ceF66cA0092c37542821C1EA50 | 0xfdf68b600f75b4688e5432442f266cb291b9ddfe2ec05d2fb8c7c64364cf2c73 | * Read the Proxy Contract on the Base Explorer ([link](https://basescan.org/address/0xd3C43A38D1D3E47E9c420a733e439B03FAAdebA8#readProxyContract)). This is the upgradeable proxy contract you should use. * User score code on the Base Explorer ([link](https://basescan.org/address/0xd3C43A38D1D3E47E9c420a733e439B03FAAdebA8#code)). This is an upgradeable implementation contract. There is no state here. This is the code that the proxy contract is currently using. ## Interface ```solidity Solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; interface INeynarUserScoresReader { function getScore(address verifier) external view returns (uint24 score); function getScoreWithEvent(address verifier) external returns (uint24 score); function getScores(address[] calldata verifiers) external view returns (uint24[] memory scores); function getScore(uint256 fid) external view returns (uint24 score); function getScoreWithEvent(uint256 fid) external returns (uint24 score); function getScores(uint256[] calldata fids) external view returns (uint24[] memory scores); } ``` If the `getScore` call returns `0`there is no user score for that address. If you can spare the gas and would like us to know that you are using our contract, please use `getScoreWithEvent`. ## Sample use A simple example of a HelloWorld contract: ```solidity Solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; interface INeynarUserScoresReader { function getScore(address verifier) external view returns (uint24 score); function getScoreWithEvent(address verifier) external returns (uint24 score); function getScores(address[] calldata verifiers) external view returns (uint24[] memory scores); function getScore(uint256 fid) external view returns (uint24 score); function getScoreWithEvent(uint256 fid) external returns (uint24 score); function getScores(uint256[] calldata fids) external view returns (uint24[] memory scores); } contract HelloWorld { INeynarUserScoresReader immutable verifications; constructor(INeynarUserScoresReader _userScores) { userScores = _userScores; } function requireHighScore() public view returns (uint256) { uint256 score = userScores.getScoreWithEvent(msg.sender); if (score < 950000) { revert("!top 5% percentile account"); } return score; } } ``` ## Future This experiment will see what we can unlock by bringing more Farcaster data on-chain. If you build something using this, please [reach out](https://neynar.com/slack). We want to hear what you're building and see how we can make it easier. ## Further reading # Create Farcaster Frames Using Neynar & Frog Source: https://docs.neynar.com/docs/analytics-frame-neynar-frog In this guide, we’ll learn how to make a frame with the neynar SDK and Frog.fm, within a few minutes! For this demo, it will be a simple rock-paper-scissors game but it will give you an idea of how to create multi-page frames, interact with buttons, and get analytics for your frame with no extra effort. Before we begin, you can access the [complete source code](https://github.com/avneesh0612/rps-frames) for this guide on GitHub. Let's get started! ## Creating a new frames project We will use vercel and [frog](https://frog.fm/) for building the frame in this guide, but feel free to use anything else as well! Enter this command in your terminal to create a new app: ```powershell PowerShell bunx create-frog -t vercel ``` Enter a name for your project and it will spin up a new project for you. Once the project is created install the dependencies: ```powershell PowerShell cd bun install ``` ### Creating the frame home page Head over to the `api/index.ts` file. Here, you'll see a starter frame on the / route. But first, let's change the Frog configuration to use `/api` as the base path and use neynar for hubs like this: ```typescript index.tsx export const app = new Frog({ assetsPath: "/", basePath: "/api", hub: neynar({ apiKey: 'NEYNAR_FROG_FM' }) }); ``` ### Make sure to update the API key to your API key to get analytics You also need to import neynar from "frog/hubs": ```typescript index.tsx import { neynar } from "frog/hubs"; ``` Now, change the frame on the `/` route to match this: ```typescript index.tsx app.frame("/", (c) => { return c.res({ action: "/result", image: (
Choose your weapon
), intents: [ , , , ], }); }); ```
This will render an image saying choose your weapon and three buttons saying rock paper and scissors. When any of these buttons are clicked a request to the `/result` route is made which we define in the `action` parameter. Frame home page ### Make sure that you sign in using your warpcast account, so that the requests can be validated Now, let's build the `/result` route like this: ```typescript index.tsx app.frame("/result", (c) => { const rand = Math.floor(Math.random() * 3); const choices = ["rock", "paper", "scissors"]; const userChoice = choices[(c.buttonIndex || 1) - 1]; const computerChoice = choices[rand]; let msg = ""; if (userChoice === computerChoice) { msg = "draw"; } if ( (userChoice === "rock" && computerChoice === "scissors") || (userChoice === "paper" && computerChoice === "rock") || (userChoice === "scissors" && computerChoice === "paper") ) { msg = "You win"; } if ( (userChoice === "rock" && computerChoice === "paper") || (userChoice === "paper" && computerChoice === "scissors") || (userChoice === "scissors" && computerChoice === "rock") ) { msg = "You lose"; } return c.res({ image: (
{userChoice} vs {computerChoice}
{msg}
), intents: [], }); }); ```
Here, we first get the button index from the request and use it to get the user's choice. We have then added some logic for generating a random choice for the game. Then, in the image we display the two choices and the result of the game. We have also added a play again game which simply pushes the user to the `/` route. Frame result page ## Analytics Since we are using neynar hub with Frog, we also get analytics out of the box. Head over to the usage tab and click on the frame that you are currently using. It will provide you with various analytics like total interactors, interactions per cast, etc. Frame analytics ## Deployment You can deploy your frame using the [vercel CLI](https://vercel.com/docs/cli) like this: ```powershell PowerShell bun run deploy ``` Alternatively, you can also create a new GitHub repository and sync your vercel deployments. ## Conclusion This guide taught us how to create a rock-paper-scissors game on Farcaster frames! If you want to look at the completed code, check out the [GitHub repository](https://github.com/avneesh0612/rps-frames). Lastly, make sure to sure what you built with us on Farcaster by tagging [@neynar](https://warpcast.com/neynar) and if you have any questions, reach out to us on [warpcast](https://warpcast.com/~/channel/neynar) or [Slack](https://neynar.com/slack)! # Mini App Hosts Notifications Source: https://docs.neynar.com/docs/app-host-notifications Token management and inbound webhook handling for apps that host mini apps ## Overview Farcaster client applications can host mini apps. Allowing those mini apps to push notifications to users requires the host to generate user notifcation tokens, send signed messages to the mini-apps webhook, and accept notifications via webhook. Neynar provides a system to manage this complexity on behalf of the client app (mini app host). ## Basic flow In the following example we'll imagine * "Hostcaster" - a Farcaster client that wants to support mini apps * "Cowclicker" - a mini app ### Enabling Notfications 1. User wants to enable notifications for Cowclicker 2. Hostcaster posts a "notifications\_enabled" event to Neynar 3. Neynar generates a unique token, bundles it in a signed message, and posts it to Clowclicker's webhook URL ### Sending Notifications 1. Cowclicker wants to send a notification to a Hostcaster user for whom they have a valid notification token 2. Cowclicker sends the notification event to Neynar's webhook including the token 3. Neynar validates the token, hydrates some data, and queues the notification for Hostcaster 4. Hostcaster listens to a Kafka topic and/or polls an API for a user's notifications ## Message signing An event sent to a mini-app webhook must be a signed JFS messages. There are two supported signing approaches in this system. Hostcaster can sign the message with a user's key if they have the ability to do so. Or, if Hostcaster instead uses the Neynar-hosted signer system then they can provide their signer\_uuid when posting the unsigned event. ### Neynar-signed route This approach requires only one API request: 1. [POST /v2/farcaster/app\_host/user/event](/reference/app-host-post-event) with a valid signer\_uuid and the required event data ```bash cURL curl --request POST \ --url https://api.neynar.com/v2/farcaster/app_host/user/event \ --header 'Content-Type: application/json' \ --header 'x-api-key: YOUR_KEY' \ --data '{ "signer_uuid": "01973000-b000-ee00-e0e0-0ee0e00e00ee", "app_domain": "cowclicker.gov", "fid": 10101, "event": "notifications_enabled" }' ``` ### Self-sign route 1. [GET /v2/farcaster/app\_host/user/event](/reference/app-host-get-event) to retrieve the message to be signed. This is particularly important for `notification_enabled` events because this is when a notification token is generated by Neynar 2. Sign the message, serialize the entire bundle per JFS spec 3. [POST /v2/farcaster/app\_host/user/event](/reference/app-host-post-event) with the signed message ```bash cURL curl --request GET \ --url 'https://api.neynar.com/v2/farcaster/app_host/user/event?app_domain=cowclicker.gov&fid=10101&event=notifications_enabled' \ --header 'x-api-key: YOUR_KEY' ``` ...Sign and serialize the message... ```bash cURL curl --request POST \ --url https://api.neynar.com/v2/farcaster/app_host/user/event \ --header 'Content-Type: application/json' \ --header 'x-api-key: YOUR_KEY' \ --data '{ "app_domain": "cowclicker.gov", "signed_message": "eyJmaWZXkifQ==.eyJleI1Mjd9.nx1CPzKje4N2Bw====" }' ``` ## Notifications via Kafka To access your stream of notifications from a Kafka queue you'll need to work with a Neynar representative to obtain the necessary credentials. You will be granted access to a Kafka topic with a name like `app_host_notifications_Your_App_Name` which contains a message for each notification. If an app sends the same message to 100 users, you will see 100 messages in Kafka similar to the example below. Sample message: ```json JSON { "notification": { "notification_id": "rate-test-1755216706-10", "title": "Sample Notification Test 10", "body": "This is notification message body", "target_url": "https://test.com/notification/10" }, "app_domain": "demo.neynar.com", "notification_neynar_id": "0198ab11-7c61-xxxx-xxxx-0affb68fda66", "fid": "18949", "timestamp": "2025-08-15T00:11:46.657Z", "app": { "name": "Wownar", "icon_url": "https://demo.neynar.com/logos/neynar.svg" } } ``` ## Further Reading * [Sending Notifications](https://miniapps.farcaster.xyz/docs/guides/notifications#send-a-notification) from the Farcaster mini app guide * [JSON Farcaster Signatures](https://github.com/farcasterxyz/protocol/discussions/208) # Host Farcaster Mini Apps — Overview Source: https://docs.neynar.com/docs/app-host-overview A guide to hosting Farcaster Mini Apps: understand concepts, implement core features, and integrate with your Farcaster client. ## What are Farcaster Mini Apps? Farcaster Mini Apps are web applications that render inside Farcaster clients, providing interactive experiences beyond traditional social media posts. Think of them as mini websites that users can interact with directly within their Farcaster feed. ## What does it mean to host Mini Apps? As a Farcaster client developer, hosting Mini Apps means: * **Embedding Mini Apps** in your client interface (typically as webviews) * **Managing the app lifecycle** (launch, navigation, close) * **Providing wallet integration** so Mini Apps can access user's crypto wallets * **Implementing authentication** to verify user identity to Mini Apps * **Handling notifications** when Mini Apps want to alert users ## Essential implementation areas Review and plan to implement each of these core host capabilities from the [Farcaster Mini Apps specification](https://miniapps.farcaster.xyz/docs/specification#actions): ### 🔐 Authentication & Identity * **[Sign In with Farcaster (SIWF)](https://github.com/farcasterxyz/protocol/discussions/110)** - Let Mini Apps authenticate users * Use [Sign In with Neynar (SIWN)](/docs/how-to-let-users-connect-farcaster-accounts-with-write-access-for-free-using-sign-in-with-neynar-siwn) for rapid development without managing your own signers * **Auth address generation** - Create approved addresses for secure interactions ([Neynar tutorial](/docs/auth-address-signature-generation)) * **[User context](https://miniapps.farcaster.xyz/docs/sdk/context)** - Provide user profile and social graph data ### 💰 Wallet Integration * **[Ethereum provider injection](https://miniapps.farcaster.xyz/docs/sdk/wallet)** - EIP-1193 compatible wallet access ([EIP-1193 spec](https://eips.ethereum.org/EIPS/eip-1193)) * **[Solana provider](https://miniapps.farcaster.xyz/docs/sdk/solana)** (experimental) - Multi-chain wallet support * **Transaction signing** - Secure transaction flows within Mini Apps ### 🎨 Host UI & UX * **[App surface rendering](https://miniapps.farcaster.xyz/docs/specification#app-surface)** - Webview management with proper sizing (424x695px web, device dimensions mobile) * **Header display** - Show Mini App name and author * **Splash screens** - Loading states with custom branding * **Navigation controls** - Launch, close, and navigation between Mini Apps * **Official libraries**: [React Native](https://github.com/farcasterxyz/miniapps/tree/main/packages/frame-host-react-native) | [Web](https://github.com/farcasterxyz/miniapps/tree/main/packages/frame-host) ### 🔔 Notification System * **Token management** - Generate and manage notification tokens per user ([Neynar implementation guide](/docs/app-host-notifications)) * **Event handling** - Process Mini App lifecycle events (added, removed, notifications enabled/disabled) using the [Frame Notifications API](/reference/app-host-post-event) * **Push delivery** - Display notifications to users in your client ### 📱 Mini App Discovery * **App catalog** - Browse available Mini Apps ([Frame Catalog API](/reference/fetch-frame-catalog)) * **Search functionality** - Help users find relevant Mini Apps ([Search Frames API](/reference/search-frames)) * **Installation flow** - Let users add Mini Apps to their client ## Next steps 1. **Start with authentication** - Implement [SIWF](https://github.com/farcasterxyz/protocol/discussions/110) or use [SIWN](/docs/how-to-let-users-connect-farcaster-accounts-with-write-access-for-free-using-sign-in-with-neynar-siwn) for quick setup 2. **Build the UI surface** - Create webview container with [proper sizing and navigation](https://miniapps.farcaster.xyz/docs/specification#app-surface) 3. **Add wallet integration** - Inject [Ethereum provider](https://miniapps.farcaster.xyz/docs/sdk/wallet) for transaction support 4. **Add discovery** - Integrate [catalog](/reference/fetch-frame-catalog) and [search APIs](/reference/search-frames) for Mini App browsing 5. **Enable notifications** - Implement [token management and event handling](/docs/app-host-notifications) ## Additional resources * **[Farcaster Mini Apps Specification](https://miniapps.farcaster.xyz/docs/specification)** - Complete technical specification including SDK details * **[Mini App SDK Documentation](https://miniapps.farcaster.xyz/docs/sdk/changelog)** - JavaScript SDK for Mini App developers * **[Examples Repository](https://github.com/farcasterxyz/miniapps/tree/main/examples)** - Reference implementations # Appendix Source: https://docs.neynar.com/docs/appendix Understanding the new user registration process Neynar simplifies the account creation process on Farcaster by pre-registering several new accounts and securely storing their credentials in a database. For detailed instructions on how to register or create an account on Farcaster, please refer to the [Farcaster account creation guide.](https://docs.farcaster.xyz/developers/guides/accounts/create-account#create-an-account) When a developer requests a new account—Step 1—Neynar provides the FID (Farcaster ID) of a pre-registered account to the developer. To transfer the pre-registered account to the end user, a specific process must be followed. This approach is chosen because Neynar does not store or manage the end user's account mnemonic for security reasons. The developer is required to generate a signature using the FID of the pre-registered account and the custody address of the end user. This can be achieved by utilizing the `signTransfer` method described in the script—Step 2. Upon making a `POST - /v2/farcaster/user` request, two significant actions are initiated: 1. **Fname Transfer** * This step is executed only when the fname is included in the request body. Developers have the option to collect the fname from the user during account creation. * If the developer decides not to collect the fname initially, the transfer can be conducted at a later stage. For more information, consult the [guide on fname transfer](https://docs.farcaster.xyz/reference/fname/api#register-or-transfer-an-fname) 2. **Account transfer** * This step outlines the procedure for transferring the account to the end user. Detailed guidance on account transfer can be found in the [Farcaster account transfer documentation.](https://docs.farcaster.xyz/reference/contracts/reference/id-registry#transferfor) # Archive Casts Source: https://docs.neynar.com/docs/archiving-casts-with-neynar Archiving Farcaster data with Neynar Casts in the Farcaster protocol are pruned when user runs out of storage. This guide demonstrates how to archive casts of a specific FID with the Neynar SDK. Check out this [Getting started guide](/docs/getting-started-with-neynar) to learn how to set up your environment and get an API key. Check out this [example repository](https://github.com/neynarxyz/farcaster-examples/tree/main/archiver-script) to see the code in action. First, initialize the client: ```javascript Javascript // npm i @neynar/nodejs-sdk import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk"; // make sure to set your NEYNAR_API_KEY .env // don't have an API key yet? get one at neynar.com const config = new Configuration({ apiKey:process.env.NEYNAR_API_KEY, }); const client = new NeynarAPIClient(config); ``` Next, let's make a function to clean the incoming casts: ```javascript Javascript const parser = (cast) => { return { fid: parseInt(cast.author.fid), parentFid: parseInt(cast.parentAuthor.fid) ? parseInt(cast.parentAuthor.fid) : undefined, hash: cast.hash || undefined, threadHash: cast.threadHash || undefined, parentHash: cast.parentHash || undefined, parentUrl: cast.parentUrl || undefined, text: cast.text || undefined, }; }; ``` Then, the function to archive the casts: ```javascript Javascript const dumpCast = (cast) => { const parsed = parser(cast); const data = `${JSON.stringify(parsed)}\n`; fs.appendFileSync("data.ndjson", data); }; ``` Finally, let's fetch the casts and archive them: ```javascript Javascript const fetchAndDump = async (fid, cursor) => { const data = await client.fetchCastsForUser({fid, limit: 150, ...(cursor && cursor.trim() !== "" ? { cursor } : {}), }); data.result.casts.map(dumpCast); // If there is no next cursor, we are done if (data.result.next.cursor === null) return; await fetchAndDump(fid, data.result.next.cursor); }; // archive all @rish.eth's casts in a file called data.ndjson const fid = 194; fetchAndDump(fid); ``` Result: a file called `data.ndjson` with all the casts of the user with FID 194. It looks something like this: ```json JSON {"fid":194,"parentFid":3,"hash":"0x544421c091f5af9d1610de0ae223b52602dd631e","threadHash":"0xb0758588c9412f72efe7e703e9d0cb5f2d0a6cfd","parentHash":"0xb0758588c9412f72efe7e703e9d0cb5f2d0a6cfd","text":"that order is pretty key"} {"fid":194,"parentFid":194,"hash":"0x98f52d36161f3d0c8dee6e242936c431face35f0","threadHash":"0x5727a985687c10b6a37e9439b2b7a3ce141c6237","parentHash":"0xcb6cab80cc7d7a2ca957d1c95c9a3459f9e3a9dc","text":"turns out not an email spam issue 😮‍💨, email typo :)"} {"fid":194,"parentFid":20071,"hash":"0xcb6cab80cc7d7a2ca957d1c95c9a3459f9e3a9dc","threadHash":"0x5727a985687c10b6a37e9439b2b7a3ce141c6237","parentHash":"0xf34c18b87f8eaca2cb72131a0c0429a48b66ef52","text":"hmm interesting. our system shows the email as sent. Maybe we're getting marked as spam now? 🤦🏽‍♂️\n\nLet me DM you on telegram"} {"fid":194,"parentFid":20071,"hash":"0x62c484064c9ca1177f8addb56bdaffdbede97a29","threadHash":"0x5727a985687c10b6a37e9439b2b7a3ce141c6237","parentHash":"0x7af582a591575acc474fa1f8c52a2a03258986b9","text":"are you still waiting on this? you should have gotten the email within the first minute. we automated this last week so there's no wait anymore. lmk if you're still having issues :)"} {"fid":194,"parentFid":3,"hash":"0xbc63b955c40ace8aca4b1608115fd12f643395b1","threadHash":"0x5727a985687c10b6a37e9439b2b7a3ce141c6237","parentHash":"0x5727a985687c10b6a37e9439b2b7a3ce141c6237","text":"@bountybot adding 150 USDC to this bounty \n\nfor anyone building on this, please reach out with any questions. We've always wanted to do this but haven't been able to prioritize. Think this can be quite impactful! :)"} ``` That's it! You now can save that in S3 or IPFS for long-term archival! ### Ready to start building? Get your subscription at [neynar.com](https://neynar.com) and reach out to us on [Slack](https://neynar.com/slack) with any questions! # Auth Address Signature Generation Source: https://docs.neynar.com/docs/auth-address-signature-generation Generate a Signed Key Request using viem for registering auth addresses in Farcaster with Neynar This guide walks you through generating a Signed Key Request using [viem](https://viem.sh/) that need to be passed in while [registering auth address](https://docs.neynar.com/reference/register-signed-key-for-developer-managed-auth-address) ## System & Installation Requirements ### Prerequisites * Node.js >= **18.x** (LTS recommended) * npm >= **9.x** OR yarn >= **1.22.x** [Download and install node (if not installed)](https://nodejs.org/en/download) ### Initialize project (optional) ```bash mkdir signed-key-request cd signed-key-request npm init -y ``` ### Install `viem` ```bash npm install viem ``` OR with yarn: ```bash yarn add viem ``` *** ## Code Breakdown and Steps **You can find full code at the [end of this guide](#full-final-code).** The code starts by importing the necessary libraries: ```javascript Javascript import { encodeAbiParameters } from "viem"; import { mnemonicToAccount, generateMnemonic, english } from "viem/accounts"; ``` Generates a mnemonic and converts it to an Ethereum address (`auth_address`) ```javascript Javascript const mnemonic = generateMnemonic(english); const auth_address_acc = mnemonicToAccount(mnemonic); const auth_address = auth_address_acc.address; ``` Describes the EIP-712 domain (context of signature). ```javascript Javascript const SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN = { name: "Farcaster SignedKeyRequestValidator", version: "1", chainId: 10, verifyingContract: "0x00000000fc700472606ed4fa22623acf62c60553", }; ``` Defines the structure of the message to be signed. ```javascript Javascript const SIGNED_KEY_REQUEST_TYPE = [ { name: "requestFid", type: "uint256" }, { name: "key", type: "bytes" }, { name: "deadline", type: "uint256" }, ]; ``` Encodes `auth_address` as **32 bytes**. ```javascript Javascript const key = encodeAbiParameters( [{ name: "auth_address", type: "address" }], [auth_address] ); ``` Replace `"MNEMONIC_HERE"` with your app mnemonic phrase and fid with your app's fid. ```javascript Javascript const fid = 0; const account = mnemonicToAccount("MNEMONIC_HERE"); ``` Sets a 24-hour expiration time. ```javascript Javascript const deadline = Math.floor(Date.now() / 1000) + 86400; ``` Signs the message per EIP-712 standard. ```javascript Javascript const signature = await account.signTypedData({ domain: SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN, types: { SignedKeyRequest: SIGNED_KEY_REQUEST_TYPE, }, primaryType: "SignedKeyRequest", message: { requestFid: BigInt(fid), key, deadline: BigInt(deadline), }, }); ``` If you want to sponsor the auth address, you can sign the EIP-712 signature again with a basic Ethereum signature. [This route](https://docs.neynar.com/reference/register-signed-key-for-developer-managed-auth-address) needs to be sponsored if not provided then neynar will sponsor it for you and you will be charged in compute units. ```javascript Javascript const sponsorSignature = await account.signMessage({ message: { raw: signature }, }); ``` Prints useful values for further use. ```javascript Javascript console.log("auth_address", auth_address); console.log("app_fid", fid); console.log("signature", signature); console.log("deadline", deadline); console.log("sponsor.signature", sponsorSignature); console.log("sponsor.fid", fid); ``` Save the code in a file, e.g., `generateSignedKeyRequest.js`, and run it using Node.js: ```bash Bash node generateSignedKeyRequest.js ``` Use the generated values to make a cURL request to register the auth address. Replace `` with your actual API key and `` with your redirect URL(if needed). ```bash Bash curl --request POST \ --url https://api.neynar.com/v2/farcaster/auth_address/developer_managed/signed_key/ \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "address": "0x5a927ac639636e534b678e81768ca19e2c6280b7", "app_fid": 3, "deadline": 123, "signature": "0x16161933625ac90b7201625bfea0d816de0449ea1802d97a38c53eef3c9c0c424fefbc5c6fb5eabe3d4f161a36d18cda585cff7e77c677c5d34a9c87e68ede011c", "redirect_url": "", "sponsor": { "fid": 3, "signature": "", "sponsored_by_neynar": true } }' ``` *** ## Full Final Code ```javascript Javascript import { encodeAbiParameters } from "viem"; import { mnemonicToAccount, generateMnemonic, english } from "viem/accounts"; (async () => { const mnemonic = generateMnemonic(english); const auth_address_acc = mnemonicToAccount(mnemonic); const auth_address = auth_address_acc.address; const SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN = { name: "Farcaster SignedKeyRequestValidator", version: "1", chainId: 10, verifyingContract: "0x00000000fc700472606ed4fa22623acf62c60553", }; const SIGNED_KEY_REQUEST_TYPE = [ { name: "requestFid", type: "uint256" }, { name: "key", type: "bytes" }, { name: "deadline", type: "uint256" }, ]; const key = encodeAbiParameters( [{ name: "auth_address", type: "address" }], [auth_address] ); const fid = 0; const account = mnemonicToAccount("MNEMONIC_HERE"); const deadline = Math.floor(Date.now() / 1000) + 86400; const signature = await account.signTypedData({ domain: SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN, types: { SignedKeyRequest: SIGNED_KEY_REQUEST_TYPE, }, primaryType: "SignedKeyRequest", message: { requestFid: BigInt(fid), key, deadline: BigInt(deadline), }, }); const sponsorSignature = await account.signMessage({ message: { raw: signature }, }); console.log("auth_address", auth_address); console.log("app_fid", fid); console.log("signature", signature); console.log("deadline", deadline); console.log("sponsor.signature", sponsorSignature); console.log("sponsor.fid", fid); })(); ``` *** Enjoy building! 🚀 For additional help, [feel free to contact us](https://neynar.com/slack). # Build Interactive Farcaster Frames with Neynar Source: https://docs.neynar.com/docs/building-frames Learn how to build interactive Farcaster frames 100x faster using Neynar's Frame Studio, no-code templates, and comprehensive Frame APIs. Create dynamic social experiences with validation, hosting, and embedding capabilities. Neynar supports building frames in a few different ways: * [Neynar Frame Studio](https://neynar.com/nfs): allows building frames with no code and pre-made templates * Frame APIs: * [Validating frame actions](/reference/validate-frame-action), user and cast data in one API call * [CRUD](/reference/publish-neynar-frame) for hosted frames * Embedding frames in your client and [posting actions](/reference/post-frame-action) ## Neynar Frame architecture ### Pages for a frame A page represents the most basic unit of a frame. A frame consists of one or more pages. We've thoughtfully crafted a JSON format for a frames' page to abstract away building on the rapidly moving foundation of Farcaster Frames. *For context, Frames were launched a few weeks before writing this and have already seen 3-to 4 spec upgrades. We don't want Neynar developers to have to worry about handling those.* Here's an example of a single page with four buttons. You can create these in the Frame Studio with no code or quickly create frames using our REST APIs. ```Text JSON { "uuid": "5ec484f5-efaf-4bda-9a3f-0579232a386a", "image": "https://i.imgur.com/gpn83Gm.png", "title": "Farcaster Dev Call", "buttons": [ { "title": "Notes", "next_page": { "redirect_url": "https://warpcast.notion.site/Feb-1st-934e190578144aba8273b2bbdc29e5ab" }, "action_type": "post_redirect" }, { "title": "Calendar", "next_page": { "redirect_url": "https://calendar.google.com/calendar/u/0/r?cid=NjA5ZWM4Y2IwMmZiMWM2ZDYyMTkzNWM1YWNkZTRlNWExN2YxOWQ2NDU3NTA3MjQwMTk3YmJlZGFjYTQ3MjZlOEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t" }, "action_type": "post_redirect" }, { "title": "Zoom", "next_page": { "redirect_url": "https://zoom.us/j/98052336425?pwd=aFYyRk9ZSDhqR1h5eVJENmtGSGo4UT09#success" }, "action_type": "post_redirect" }, { "title": "Recordings", "next_page": { "redirect_url": "https://www.youtube.com/playlist?list=PL0eq1PLf6eUeZnPtyKMS6uN9I5iRIlnvq" }, "action_type": "post_redirect" } ], "version": "vNext" } ``` ## Hosting Neynar hosts pages on behalf of developers. We convert this JSON format into the Metatags expected by the [Farcaster Frames Specification](https://warpcast.notion.site/Farcaster-Frames-4bd47fe97dc74a42a48d3a234636d8c5). If you have questions/feedback, please reach out to [@rish](https://warpcast.com/rish)or [@manan](https://warpcast.com/manan) on Farcaster. We will continue improving the frames experience! # Cast Action with Analytics Source: https://docs.neynar.com/docs/cast-action-with-analytics-neynar In this guide, we’ll make a cast action with the neynar SDK and frog.fm, within a few minutes! The cast action will fetch the follower count of the cast's author using its fid and display it. ## Cast Action We'll also validate the requests using Neynar's frame validator which provides analytics as well! Before we begin, you can access the [complete source code](https://github.com/neynarxyz/farcaster-examples/tree/main/cast-action) for this guide on GitHub. Let's get started! ### Creating a new frames project We will use [bun](https://bun.sh/) and [frog](https://frog.fm/) for building the cast action in this guide, but feel free to use [framejs](https://framesjs.org/), [onchainkit](https://onchainkit.xyz/), or anything else as well! Enter this command in your terminal to create a new app: ```powershell PowerShell bunx create-frog -t bun ``` Enter a name for your project and it will spin up a new project for you. Once the project is created install the dependencies: ```powershell PowerShell cd bun install ``` Now, let's install the dependencies that we are going to need to build out this action: ```powershell PowerShell bun add @neynar/nodejs-sdk dotenv ``` #### Creating the cast action route Head over to the `src/index.ts` file. Here, you'll be able to see a starter frame on the / route. But first, let's change the Frog configuration to use `/api` as the base path and use neynar for hubs like this: ```typescript index.tsx export const app = new Frog({ hub: neynar({ apiKey: "NEYNAR_FROG_FM" }), basePath: "/api", }); ``` You also might need to import neynar from "frog/hubs": ```typescript index.tsx import { neynar } from "frog/hubs"; ``` Now, we'll create a new post route which will handle our cast actions. So, create a new route like this: ```typescript index.tsx app.hono.post("/followers", async (c) => { try { let message = "GM"; return c.json({ message }); } catch (error) { console.error(error); } }); ``` This route will return a GM message every time the action is clicked, but let's now use the neynar SDK to get the follower count of the cast's author! Create a new `src/lib/neynarClient.ts` file and add the following: ```typescript neynarClient.ts import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk"; import { config } from "dotenv"; config(); if (!process.env.NEYNAR_API_KEY) { throw new Error("Make sure you set NEYNAR_API_KEY in your .env file"); } // make sure to set your NEYNAR_API_KEY .env // don't have an API key yet? get one at neynar.com const configuration = new Configuration({ apiKey: process.env.NEYNAR_API_KEY, }); const neynarClient = new NeynarAPIClient(configuration); export default neynarClient; ``` Here, we initialise the neynarClient with the neynar api key which you can get from your dashboard: Neynar API key Add the api key in a `.env` file with the name `NEYNAR_API_KEY`. Head back to the `src/index.tsx` file and add the following in the followers route instead of the GM message: ```typescript index.tsx try { const body = await c.req.json(); const result = await neynarClient.validateFrameAction({ messageBytesInHex:body.trustedData.messageBytes} ); const { users } = await neynarClient.fetchBulkUsers({ fids: [Number(result.action.cast.author.fid)], }); if (!users) { return c.json({ message: "Error. Try Again." }, 500); } let message = `Count:${users[0].follower_count}`; return c.json({ message }); } catch (e) { return c.json({ message: "Error. Try Again." }, 500); } ``` Here, we use the neynar client that we just initialised to first validate the action and get the data from the message bytes. Then, we use it to fetch the user information using the `fetchBulkUsers` function. Finally, we return a message with the follower count! #### Creating a frame with add cast action button I am also adding a simple frame that allows anyone to install the action. But for that, you need to host your server somewhere, for local development you can use ngrok. If you don’t already have it installed, install it from [here](https://ngrok.com/downloads/mac-os). Once it’s installed, authenticate using your auth token and serve your app using this command: ```powershell PowerShell ngrok http http://localhost:5173/ ``` This command will give you a URL which will forward the requests to your localhost: Ngrok URL You can now head over to the [cast action playground](https://warpcast.com/~/developers/cast-actions) and generate a new URL by adding in the info as such: Farcaster Cast action playground Copy the install URL and paste it into a new variable in the `index.tsx` like this: ```typescript index.tsx const ADD_URL = "https://warpcast.com/~/add-cast-action?actionType=post&name=Followers&icon=person&postUrl=https%3A%2F%2F05d3-2405-201-800c-6a-70a7-56e4-516c-2d3c.ngrok-free.app%2Fapi%2Ffollowers"; ``` Finally, you can replace the / route with the following to have a simple frame which links to this URL: ```typescript index.tsx app.frame("/", (c) => { return c.res({ image: (

gm! Add cast action to view followers count

), intents: [Add Action], }); }); ```
If you now start your server using `bun run dev` and head over to [http://localhost:5173/dev](http://localhost:5173/dev) you'll be able to see a frame somewhat like this: Frog frame dev env Click on Add action and it'll prompt you to add a new action like this: Add farcaster action Once you have added the action, you can start using it on Warpcast to see the follower count of various people! 🥳 ### Analytics Since we are using the validateFrameAction function, we also get analytics out of the box. Head over to the usage tab and click on the frame that you are currently using. It will provide you with various analytics like total interactors, interactions per cast, etc. Frame analytics ### Conclusion This guide taught us how to create a Farcaster cast action that shows the follower count of the cast's author! If you want to look at the completed code, check out the [GitHub repository](https://github.com/neynarxyz/farcaster-examples/tree/main/cast-action). Lastly, make sure to share what you built with us on Farcaster by tagging [@neynar](https://warpcast.com/neynar), and if you have any questions, reach out to us on [warpcast](https://warpcast.com/~/channel/neynar) or [Slack](https://neynar.com/slack)! # Cast From Inside a Frame Source: https://docs.neynar.com/docs/casting-from-a-frame In this guide, we'll look at how to build a frame using which people can create casts and perform other actions on the Farcaster app. For this guide, I will be using Next.js and frames.js but feel free to refer to this guide for any other framework with the necessary changes. For this guide, we'll go over: Before we begin, you can access the [complete source code](https://github.com/avneesh0612/frame-cast) for this guide on GitHub. Let's get started! ## Setting up ### App We're going to use next.js so that we can have everything from our login page to our frame in the same project. So, create a new project using the following command: ```powershell PowerShell npx create-next-app@latest cast-frame-signer ``` You can choose the configuration based on your personal preference, I am using this config for the guide: Frame creation Once the app is created, install the packages that we are going to need for the command: ```powershell npm npm i @neynar/react @neynar/nodejs-sdk axios @prisma/client prisma frames.js ``` ```powershell yarn yarn add @neynar/react @neynar/nodejs-sdk axios ``` ```powershell bash bun add @neynar/react @neynar/nodejs-sdk axios ``` ### Database We're also going to need a database to store all the signers so that we can later access it to create casts and perform other actions on the user's behalf. I am going to use MongoDB as the database, and prisma as an ORM. So, go ahead and create an account/sign into your MongoDB account here. Once you've signed up go ahead and setup a new cluster on Mongodb. You can follow this [guide](https://www.mongodb.com/resources/products/fundamentals/mongodb-cluster-setup) to do it. Once you've setup your cluster follow these steps to get your connection URL: Frame creation Frame creation Frame creation Now, let's go ahead and set up Prisma in our project. Run the following command to initialise: ```powershell PowerShell npx prisma init --datasource-provider MongoDB ``` Once the initialisation is complete head over to the `.env` file and add the connection url that you just copied and replace the part after `.mongodb.net/` with `/db?retryWrites=true&w=majority`. ### Make sure to add .env into .gitignore Firstly, let's first define our database schema. Head over to `prisma/schema.prisma` and add a user model like this: ```Text schema.prisma model User { fid String @id @default(cuid()) @map("_id") signerUUID String @unique } ``` Once you have added the model, run these two commands: ```powershell PowerShell npx prisma db push npx prisma generate ``` Now, we can add the Prisma client which we'll use to interact with our database. To do that, create a new `lib/prisma.ts` file in the `src` folder and add the following: ```typescript prisma.ts import { PrismaClient } from "@prisma/client"; let prisma: PrismaClient; if (process.env.NODE_ENV === "production") { prisma = new PrismaClient(); } else { const globalWithPrisma = global as typeof globalThis & { prisma: PrismaClient; }; if (!globalWithPrisma.prisma) { globalWithPrisma.prisma = new PrismaClient(); } prisma = globalWithPrisma.prisma; } export default prisma; ``` ## Adding sign-in with neynar Now that we've setup all the boilerplate let's start coding the actual part. We'll first go ahead and add sign in with neynar. To do that we need to wrap our app in a provider, so, head over to the `layout.tsx` file and wrap your app in a `NeynarContextProvider` like this: ```typescript layout.tsx "use client"; import { NeynarContextProvider, Theme } from "@neynar/react"; import "@neynar/react/dist/style.css"; import { Inter } from "next/font/google"; import "./globals.css"; const inter = Inter({ subsets: ["latin"] }); export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( {}, onSignout() {}, }, }} > {children} ); } ``` We are passing some settings here like `clientId`, `defaultTheme` and `eventsCallbacks`. * `clientId`: This is going to be the client ID you get from your neynar, add it to your `.env.local` file as `NEXT_PUBLIC_NEYNAR_CLIENT_ID`. Frame creation ### Make sure to add localhost to the authorized origins * `defaultTheme`: default theme lets you change the theme of your sign-in button, currently, we have only light mode but dark mode is going to be live soon. * `eventsCallbacks`: This allows you to perform certain actions when the user signs out or auth is successful. I've also added a styles import from the neynar react package here which is needed for the styles of the sign-in button. Finally, let's add the sign-in button in the `page.tsx` file like this: ```typescript page.tsx "use client"; import { NeynarAuthButton } from "@neynar/react"; export default function Login() { return (
); } ```
If you head over to your app you'll be able to see a sign-in button on the screen. Go ahead and try signing in! Frame creation Now that our sign-in button is working let's add a call to add the users' signers to the database. In `layout.tsx` add this to the `onAuthSuccess` function in `NeynarContextProvider`'s settings: ```typescript layout.tsx eventsCallbacks: { onAuthSuccess: ({ user }) => { axios.post("/api/add-user", { signerUuid: user?.signer_uuid, fid: user?.fid, }); }, onSignout() {}, }, ``` This will call an `/api/add-user`API route which we are yet to create with the user's signer and fid every time a user successfully signs in. Now, create a new `/api/add-user/route.ts` file in the `app` folder and add the following: ```typescript add-user/route.ts import { NextRequest, NextResponse } from "next/server"; import { isApiErrorResponse } from "@neynar/nodejs-sdk"; import neynarClient from "@/lib/neynarClient"; import prisma from "@/lib/prisma"; export async function POST(request: NextRequest) { const { signerUuid, fid } = (await request.json()) as { signerUuid: string; fid: string; }; try { const { fid: userFid } = await neynarClient.lookupSigner({ signerUuid }); if (!userFid) { return NextResponse.json({ message: "User not found" }, { status: 404 }); } if (fid !== String(userFid)) { return NextResponse.json( { message: "Invalid user data" }, { status: 400 } ); } const user = await prisma.user.findUnique({ where: { fid: String(userFid), }, }); if (!user) { await prisma.user.create({ data: { signerUUID: signerUuid, fid: String(userFid), }, }); } return NextResponse.json({ message: "User added" }, { status: 200 }); } catch (err) { if (isApiErrorResponse(err)) { return NextResponse.json( { ...err.response.data }, { status: err.response.status } ); } else return NextResponse.json( { message: "Something went wrong" }, { status: 500 } ); } } ``` Here we are verifying the signer is valid and if it's valid we add it to the database. As you can see we are importing a `neynarClient` function which we have not yet created so we have to do that. Create a new `lib/neynarClient.ts` file and add the following: ```Text neynarClient.ts import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk"; // make sure to set your NEYNAR_API_KEY .env // don't have an API key yet? get one at neynar.com const config = new Configuration({ apiKey: process.env.NEYNAR_API_KEY, }); const neynarClient = new NeynarAPIClient(config); export default neynarClient; ``` ## Creating the frame Firstly, let's create the homescreen of the frame which will be a simple frame with a few buttons and a simple text. So, create a new file `frame/route.tsx` file in the `app` folder and add the following: ```js import { createFrames, Button } from "frames.js/next"; const frames = createFrames({}); const HOST = process.env.HOST || "http://localhost:3000"; const handleRequest = frames(async () => { return { image: (

Cast from a frame!

), buttons: [ , ], }; }); export const GET = handleRequest; export const POST = handleRequest; ``` If you open it up in a debugger it will show you a frame like this: Frame creation Now, create a new file `start/route.tsx` in the `frame` folder and add the following: ```typescript start/route.tsx import prisma from "@/lib/prisma"; import { createFrames, Button } from "frames.js/next"; const frames = createFrames({}); const HOST = process.env.HOST || "http://localhost:3000"; const handleRequest = frames(async (payload) => { const fid = payload.message?.requesterFid; const user = await prisma.user.findUnique({ where: { fid: String(fid), }, }); if (!user) { return { image: (

User not found!

), buttons: [ , ], }; } return { image: (

Cast from a frame!

), buttons: [ , ], textInput: "Text to cast...", }; }); export const GET = handleRequest; export const POST = handleRequest; ```
This is just the home page for the frame that we are creating let's create a new `/frame/publish` route where the cast is made using the text that the user entered on the home screen. Create a new file `route.tsx` in the `frame/publish/` folder and add the following: ```typescript publish/route.tsx import neynarClient from "@/lib/neynarClient"; import prisma from "@/lib/prisma"; import { createFrames, Button } from "frames.js/next"; const frames = createFrames({}); const HOST = process.env.HOST || "http://localhost:3000"; const handleRequest = frames(async (payload) => { const text = payload.message?.inputText; const fid = payload.message?.requesterFid; const user = await prisma.user.findUnique({ where: { fid: String(fid), }, }); if (!user) { return { image: (
User not found!
), buttons: [ , ], }; } const cast = await neynarClient.publishCast({signerUuid:user?.signerUUID,text:text || "gm"}); return { image: (
Casted successfully! 🎉 {cast?.hash}
), }; }); export const GET = handleRequest; export const POST = handleRequest; ```
Here, we are first confirming that the user's signer exists. And if it does we use it to create a new cast using the `publishCast` function from the client we initialised. If you try publishing a new cast on the frame it will create the cast and show you something like this. Frame creation ## Conclusion This guide taught us how to create a frame from which the users can create casts, check out the [GitHub repository](https://github.com/avneesh0612/frame-cast) if you want to look at the full code. Lastly, make sure to share what you built with us on Farcaster by tagging [@neynar](https://warpcast.com/neynar) and if you have any questions, reach out to us on [warpcast](https://warpcast.com/~/channel/neynar) or [Slack](https://neynar.com/slack)! # Find User Subscriptions with Neynar Hypersub Source: https://docs.neynar.com/docs/common-subscriptions-fabric Finding Hypersub subscriptions on Social Token Protocol (STP) using Neynar ### Related set of APIs [Fetch Subscribers for FID](/reference/fetch-subscribers-for-fid) ## How to Find Common Subscriptions In this guide, we'll take two FIDs and then find their common subscriptions on fabric. We'll use JavaScript for this guide, but the same logic would work for any other language you use! So, let's get started by creating a new file and defining our constants: ```typescript index.ts const fid1 = 194; const fid2 = 191; const url1 = `https://api.neynar.com/v2/farcaster/user/subscribed_to?fid=${fid1}&viewer_fid=3&subscription_provider=fabric_stp`; const url2 = `https://api.neynar.com/v2/farcaster/user/subscribed_to?fid=${fid2}&viewer_fid=3&subscription_provider=fabric_stp`; ``` You can replace the FIDs with the ones you want to check the subscriptions for and leave the URLs as they are. The URL is the API route to get all the channels a user is subscribed to. You can find more info about the API route in the [API reference](/reference/fetch-subscribed-to-for-fid). Then, call the APIs using fetch like this: ```typescript index.ts const fetchUrls = async () => { const options = { method: "GET", headers: { accept: "application/json", api_key: "NEYNAR_API_DOCS" }, }; const response = await Promise.all([ fetch(url1, options), fetch(url2, options), ]); const data = await Promise.all(response.map((res) => res.json())); return data; }; ``` Here, make sure to replace the API key with your API key instead of the docs API key in production. Finally, let's filter out the data to find the common subscriptions like this: ```typescript index.ts fetchUrls().then((data) => { const [subscribedTo1, subscribedTo2] = data; const commonSubscriptions = subscribedTo1.subscribed_to.filter( (item1: { contract_address: string }) => subscribedTo2.subscribed_to.some( (item2: { contract_address: string }) => item2.contract_address === item1.contract_address ) ); console.log(commonSubscriptions); }); ``` Here, we use the filter function on the data that we just fetched and match the channel's contract address since that will be unique for every channel. Now, we can test the script by running it. Common Subscriptions The two FIDs we used were subscribed to Terminally Onchain, so that shows up. If you want to look at the complete script, you can look at this [GitHub Gist](https://gist.github.com/avneesh0612/f9fa2da025fa764c6dc65de5f3d5ecec). If you want to know more about the subscription APIs take a look at [Fetch Subscribers for FID](/reference/fetch-subscribers-for-fid). Lastly, please share what you built with us on Farcaster by tagging [@neynar](https://warpcast.com/neynar), and if you have any questions, reach out to us on [warpcast](https://warpcast.com/~/channel/neynar) or [Slack](https://neynar.com/slack)! # Convert a Web App to a Farcaster Mini App Source: https://docs.neynar.com/docs/convert-web-app-to-mini-app Update any JavaScript web app to be a Farcaster mini app If looking to create a new mini app from scratch, see [Create Farcaster Mini App in 60s](/docs/create-farcaster-miniapp-in-60s). Converting an existing JavaScript-based web app to a Farcaster mini app involves the following steps: * install the [@neynar/react](https://www.npmjs.com/package/@neynar/react) npm package and use the `` provider in your app * alternatively, install the [Mini App SDK](https://www.npmjs.com/package/@farcaster/miniapp-sdk) and call `sdk.actions.ready()` * integrate with the SDK's ethereum provider exposed via `sdk.wallet.ethProvider` * add a `farcaster.json` file with mini app metadata and a signature proving ownership * add a custom HTML `` tag specifying how embeds should be rendered ## Installing the SDK ### Using @neynar/react The recommended way to integrate your app with Farcaster is using the `@neynar/react` package, which includes the Mini App SDK along with custom Neynar components and built-in analytics: ```bash npm install @neynar/react ``` Then wrap your app with the `` provider: ```javascript import { MiniAppProvider } from '@neynar/react'; export default function App() { return ( {/* Your app components */} ); } ``` With the MiniAppProvider provider in place, you can access Mini App SDK functionality with the `useMiniApp()` react hook: ```javascript import { useMiniApp } from '@neynar/react'; export default function HomePage() { const { isSDKLoaded, context } = useMiniApp(); return (<> {isSDKLoaded && (
{context}
)} ) } ``` ### Using the Mini App SDK Alternatively, you can use the Mini App SDK (formerly the Frame SDK): ```bash npm install @farcaster/miniapp-sdk ``` Then call the ready function when your interface is loaded and ready to be displayed: ```javascript import { sdk } from '@farcaster/miniapp-sdk'; await sdk.actions.ready(); ``` You should call `ready()` as early as possible in the app, but after any pageload processes that might cause the UI to re-render or update significantly. In a React app, it's generally best to call `ready()` inside the page-level component at the root of your UI, e.g. in your homepage component. Here's an example of how you might do this in a standard React app: ```javascript {9} import { useEffect, useState } from "react"; import { sdk } from '@farcaster/miniapp-sdk'; export default function Home() { const [isLoaded, setIsLoaded] = useState(false); useEffect(() => { const load = async () => { await sdk.actions.ready(); setIsLoaded(true); }; if (sdk && !isLoaded) { load(); } }, [isLoaded]); return (...) } ``` ## Connecting to the wallet provider It's recommended to use `wagmi` for your wallet provider, as the Farcaster team provides the [@farcaster/miniapp-wagmi-connector package](https://www.npmjs.com/package/@farcaster/miniapp-wagmi-connector) for easy configuration. Run `npm i @farcaster/miniapp-wagmi-connector` to install, and then connecting is as simple as adding the connector to the wagmi config: ```javascript {11} import { http, createConfig } from 'wagmi'; import { base } from 'wagmi/chains'; import { farcasterMiniApp } from '@farcaster/miniapp-wagmi-connector'; export const wagmiConfig = createConfig({ chains: [base], transports: { [base.id]: http(), }, connectors: [ farcasterMiniApp() // add other wallet connectors like metamask or coinbase wallet if desired ] }); ``` With the above configuration, you can access the mini app user's connected wallet with normal wagmi hooks like `useAccount()`. ## Connecting to Solana For Solana support, install the package and wrap your app with the Solana provider: ```bash Bash npm install @farcaster/mini-app-solana ``` ```typescript App.tsx import { FarcasterSolanaProvider } from '@farcaster/mini-app-solana'; function App() { const solanaEndpoint = 'https://solana-rpc.publicnode.com'; return ( {/* Your app components */} ); } ``` Use Solana wallet hooks in your components: ```typescript SolanaExample.tsx import { useSolanaConnection, useSolanaWallet } from '@farcaster/mini-app-solana'; import { Transaction, SystemProgram, PublicKey } from '@solana/web3.js'; function SolanaExample() { const { publicKey, signMessage, sendTransaction } = useSolanaWallet(); const { connection } = useSolanaConnection(); const handleSign = async () => { if (!signMessage) return; const message = new TextEncoder().encode("Hello Solana!"); const signature = await signMessage(message); console.log('Signed:', btoa(String.fromCharCode(...signature))); }; const handleSend = async () => { if (!publicKey || !sendTransaction) return; const { blockhash } = await connection.getLatestBlockhash(); const transaction = new Transaction(); transaction.add( SystemProgram.transfer({ fromPubkey: publicKey, toPubkey: new PublicKey('DESTINATION_ADDRESS'), lamports: 1000000, // 0.001 SOL }) ); transaction.recentBlockhash = blockhash; transaction.feePayer = publicKey; const signature = await sendTransaction(transaction, connection); console.log('Transaction:', signature); }; return (
); } ```
The Solana provider will only be available when the user's wallet supports Solana. Always check `hasSolanaProvider` before rendering Solana-specific UI components. ## Adding and signing the farcaster.json file Mini apps are expected to serve a farcaster.json file, also known as a "manifest", at `/.well-known/farcaster.json`, published at the root of the mini app's domain. The manifest consists of a `miniapp` section containing metadata specific to the mini app and an `accountAssociation` section consisting of a JSON Farcaster Signature (JFS) to verify ownership of the domain and mini app. The `miniapp` metadata object only has four required fields (`version`, `name`, `homeUrl`, and `iconUrl`), but providing more is generally better to help users and clients discover your mini app. See the full list of options [here in the Farcaster docs](https://miniapps.farcaster.xyz/docs/specification#frame). Start by publishing just the miniapp portion of the manifest: ```json { "miniapp": { "version": "1", "name": "Yoink!", "iconUrl": "https://yoink.party/logo.png", "homeUrl": "https://yoink.party/framesV2/", "imageUrl": "https://yoink.party/framesV2/opengraph-image", "buttonTitle": "🚩 Start", "splashImageUrl": "https://yoink.party/logo.png", "splashBackgroundColor": "#f5f0ec", "webhookUrl": "https://yoink.party/api/webhook" } } ``` In a standard react app, you can do this by placing a JSON file in your public folder, to be served as a static file: ``` public/ ├── .well-known/ └── farcaster.json ``` Once your domain is live and serving something like the above example at `yourURL.com/.well-known/farcaster.json`, you need to generate an `accountAssociation` signed with your farcaster custody address: * go to [the manifest tool on Farcaster](https://farcaster.xyz/~/developers/mini-apps/manifest) in your desktop browser * enter your domain and scroll to the bottom * click "Claim Ownership", and follow the steps to sign the manifest with your Farcaster custody address using your phone * finally, copy the output manifest from the manifest tool and update your domain to serve the full, signed farcaster.json file, which should look something like this: ```json { "accountAssociation": { "header": "eyJmaWQiOjM2MjEsInR5cGUiOiJjdXN0b2R5Iiwia2V5IjoiMHgyY2Q4NWEwOTMyNjFmNTkyNzA4MDRBNkVBNjk3Q2VBNENlQkVjYWZFIn0", "payload": "eyJkb21haW4iOiJ5b2luay5wYXJ0eSJ9", "signature": "MHgwZmJiYWIwODg3YTU2MDFiNDU3MzVkOTQ5MDRjM2Y1NGUxMzVhZTQxOGEzMWQ5ODNhODAzZmZlYWNlZWMyZDYzNWY4ZTFjYWU4M2NhNTAwOTMzM2FmMTc1NDlmMDY2YTVlOWUwNTljNmZiNDUxMzg0Njk1NzBhODNiNjcyZWJjZTFi" }, "miniapp": { "version": "1", "name": "Yoink!", "iconUrl": "https://yoink.party/logo.png", "homeUrl": "https://yoink.party/framesV2/", "imageUrl": "https://yoink.party/framesV2/opengraph-image", "buttonTitle": "🚩 Start", "splashImageUrl": "https://yoink.party/logo.png", "splashBackgroundColor": "#f5f0ec", "webhookUrl": "https://yoink.party/api/webhook" } } ``` ## Configuring embed metadata To allow your mini app to render properly in social feeds, you must add a meta tag with the name "fc:frame" to the `` section of the HTML page serving your mini app. ```html ``` The full schema can be found [here in the Farcaster docs](https://miniapps.farcaster.xyz/docs/specification#schema), but the most common button action is `launch_miniapp`, so unless you have a specific use case, you can safely copy the following example: ```json { "version": "next", "imageUrl": "https://yoink.party/framesV2/opengraph-image", "button": { "title": "🚩 Start", "action": { "type": "launch_miniapp", "name": "Yoink!", "url": "https://yoink.party/framesV2", "splashImageUrl": "https://yoink.party/logo.png", "splashBackgroundColor": "#f5f0ec" } } } ``` # Bot Replying with Frames Source: https://docs.neynar.com/docs/create-a-farcaster-bot-to-reply-with-frames-using-neynar In this guide, we’ll take a look at how to create a Farcaster bot that replies to specific keywords with a frame created on the go specifically for the reply! Here’s an example of the same: Demo of the farcaster frames bot For this guide, we'll go over: Before we begin, you can access the [complete source code](https://github.com/neynarxyz/farcaster-examples/tree/main/frames-bot) for this guide on GitHub. Let's get started! ## Setting up our server ### Creating a bun server I am using a [bun server](https://bun.sh/) for the sake of simplicity of this guide, but you can use express, Next.js api routes or any server that you wish to use! Here's a [serverless example using next.js api routes](https://github.com/davidfurlong/farcaster-bot-template/tree/main) created by [@df](https://warpcast.com/df). Create a new server by entering the following commands in your terminal: ```powershell PowerShell mkdir frames-bot cd frames-bot bun init ``` We are going to need the `@neynar/nodejs-sdk`, so let’s install that as well: ```powershell PowerShell bun add @neynar/nodejs-sdk ``` Once the project is created and the packages are installed, you can open it in your favourite editor and add the following in `index.ts`: ```typescript index.ts const server = Bun.serve({ port: 3000, async fetch(req) { try { return new Response("Welcome to bun!"); } catch (e: any) { return new Response(e.message, { status: 500 }); } }, }); console.log(`Listening on localhost:${server.port}`); ``` This creates a server using bun which we will be using soon! Finally, run the server using the following command: ```powershell PowerShell bun run index.ts ``` ### Serve the app via ngrok We’ll serve the app using ngrok to use this URL in the webhook. If you don’t already have it installed, install it from [here](https://ngrok.com/download). Once it’s installed, authenticate using your auth token and serve your app using this command: ```powershell PowerShell ngrok http http://localhost:3000 ``` Serve your app using ngrok ### Free endpoints like ngrok, localtunnel, etc. can have issues because service providers start blocking events over a certain limit ## Creating a webhook We need to create a webhook on the neynar dashboard that will listen for certain words/mentions and call our server which will then reply to the cast. So, head to the neynar dashboard and go to the [webhooks tab](https://dev.neynar.com/webhook). Click on new webhook and enter the details as such: Create a new webhook on the neynar dashboard The target URL should be the URL you got from the ngrok command, and you can select whichever event you want to listen to. I’ve chosen to listen to all the casts with “farcasterframesbot” in it. Once you have entered all the info, click Create to create a webhook. ## Creating the bot Head over to the [app section](https://dev.neynar.com/app/list) in the [neynar dashboard](https://dev.neynar.com/) and copy the signer uuid for your account: Copy the signer uuid for the bot Create a new `.env` file in the root of your project and add the following: ```bash .env SIGNER_UUID=your_signer_uuid NEYNAR_API_KEY=your_neynar_api_key ``` Add the signer UUID to the `SIGNER_UUID` and the neynar api key to the `NEYNAR_API_KEY` which you can get from the overview section of the neynar dashboard: Copy neynar api key from the dashboard Create a `neynarClient.ts` file and add the following: ```typescript neynarClient.ts // npm i @neynar/nodejs-sdk import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk"; if (!process.env.NEYNAR_API_KEY) { throw new Error("Make sure you set NEYNAR_API_KEY in your .env file"); } // make sure to set your NEYNAR_API_KEY .env // don't have an API key yet? get one at neynar.com const config = new Configuration({ apiKey:process.env.NEYNAR_API_KEY, }); const neynarClient = new NeynarAPIClient(config); export default neynarClient; ``` Here we initialize the neynar client, which we can use to publish casts. Head back to `index.ts` and add this inside the try block: ```typescript index.ts if (!process.env.SIGNER_UUID) { throw new Error("Make sure you set SIGNER_UUID in your .env file"); } const body = await req.text(); const hookData = JSON.parse(body); const signerUuid= process.env.SIGNER_UUID; const text= `gm ${hookData.data.author.username}`; const replyTo= hookData.data.hash; const reply = await neynarClient.publishCast( { signerUuid, text, parent: replyTo, } ); console.log("reply:", reply.cast); ``` You also need to import the neynar client in the `index.ts` file: ```typescript index.ts import neynarClient from "./neynarClient"; ``` This will now reply to every cast that has the word “farcasterframesbot” in it with a gm. Pretty cool, right? Let’s take this a step further and reply with a frame instead of boring texts! ## Creating the frame We’ll now generate a unique frame for every user on the fly using neynar frames. To create the frame, add the following code in the `index.ts` before the reply: ```typescript index.ts const creationRequest: NeynarFrameCreationRequest = { name: `gm ${hookData.data.author.username}`, pages: [ { image: { url: "https://moralis.io/wp-content/uploads/web3wiki/638-gm/637aeda23eca28502f6d3eae_61QOyzDqTfxekyfVuvH7dO5qeRpU50X-Hs46PiZFReI.jpeg", aspect_ratio: "1:1", }, title: "Page title", buttons: [], input: { text: { enabled: false, }, }, uuid: "gm", version: "vNext", }, ], }; const frame = await neynarClient.publishNeynarFrame(creationRequest); ``` You can edit the metadata here, I have just added a simple gm image but you can go crazy with it! For example, check out some templates in the [frame studio](https://dev.neynar.com) . Anyway, let’s continue building; you also need to add the frame as an embed in the reply body like this: ```typescript index.ts const signerUuid= process.env.SIGNER_UUID; const text= `gm ${hookData.data.author.username}`; const replyTo= hookData.data.hash; const embeds = [ { url: frame.link, }, ]; const reply = await neynarClient.publishCast({ signerUuid, text, parent: replyTo, embeds } ); ``` Putting it all together, your final `index.ts` file should look similar to [this complete example](https://github.com/neynarxyz/farcaster-examples/blob/main/frames-bot/index.ts). Don't forget to restart your server after making these changes! ```powershell PowerShell bun run index.ts ``` You can now create a cast on Farcaster, and your webhook should work fine! ## Conclusion This guide taught us how to create a Farcaster bot that replies to specific keywords with a frame created on the go! If you want to look at the completed code, check out the [GitHub repository](https://github.com/neynarxyz/farcaster-examples/tree/main/frames-bot). Lastly, make sure to tag what you built with us on Farcaster by tagging [@neynar](https://warpcast.com/neynar), and if you have any questions, reach out to us on [warpcast](https://warpcast.com/~/channel/neynar) or [Slack](https://neynar.com/slack)! # Cast Stream Source: https://docs.neynar.com/docs/create-a-stream-of-casts Fetch stream of casts with Farcaster hubs In this guide, we'll create a stream of casts using Farcaster hubs and stream the casts published in real time. ### Create nodejs app Create a new node.js app using the following commands: ```bash mkdir stream-casts cd stream-casts bun init ``` I have used bun but feel free to use npm, yarn, pnpm, or anything of your choice! Once the app is created, run this command to install the "@farcaster/hub-nodejs" package: ```bash bun add @farcaster/hub-nodejs ``` ### Build the stream Now, let's get to building our stream. In the index.ts file add the following to initialise the client: ```typescript index.ts import { createDefaultMetadataKeyInterceptor, getSSLHubRpcClient, HubEventType } from '@farcaster/hub-nodejs'; const hubRpcEndpoint = "hub-grpc-api.neynar.com"; const client = getSSLHubRpcClient(hubRpcEndpoint, { interceptors: [ createDefaultMetadataKeyInterceptor('x-api-key', 'YOUR_NEYNAR_API_KEY'), ], 'grpc.max_receive_message_length': 20 * 1024 * 1024, }); ``` You need to replace "YOUR\_NEYNAR\_API\_KEY" with your API key. You can get it from your [neynar app page](https://dev.neynar.com/app). Once our client is initialized we can use it to subscribe to specific events, in our case we want to subscribe to the `MERGE_MESSAGE` event. You can check out the full details about the types of events in [The Snapchain Events Documentation](https://snapchain.farcaster.xyz/reference/datatypes/events#events). So, add the following in your code: ```typescript index.ts client.$.waitForReady(Date.now() + 5000, async (e) => { if (e) { console.error(`Failed to connect to ${hubRpcEndpoint}:`, e); process.exit(1); } else { console.log(`Connected to ${hubRpcEndpoint}`); const subscribeResult = await client.subscribe({ eventTypes: [HubEventType.MERGE_MESSAGE], }); client.close(); } }); ``` Finally, let's use the subscribeResult to stream and console log the cast texts: ```typescript index.ts if (subscribeResult.isOk()) { const stream = subscribeResult.value; for await (const event of stream) { if (event.mergeMessageBody.message.data.type === 1) { console.log(event.mergeMessageBody.message.data.castAddBody.text); } } } ``` We have to filter out the data by its type since the merge message events provide all protocol events like casts, reactions, profile updates, etc. 1 is for casts published. Here's what the completed code looks like: ```typescript import { createDefaultMetadataKeyInterceptor, getSSLHubRpcClient, HubEventType } from '@farcaster/hub-nodejs'; const hubRpcEndpoint = "hub-grpc-api.neynar.com"; const client = getSSLHubRpcClient(hubRpcEndpoint, { interceptors: [ createDefaultMetadataKeyInterceptor('x-api-key', 'YOUR_NEYNAR_API_KEY'), ], 'grpc.max_receive_message_length': 20 * 1024 * 1024, }); client.$.waitForReady(Date.now() + 5000, async (e) => { if (e) { console.error(`Failed to connect to ${hubRpcEndpoint}:`, e); process.exit(1); } else { console.log(`Connected to ${hubRpcEndpoint}`); const subscribeResult = await client.subscribe({ eventTypes: [HubEventType.MERGE_MESSAGE], }); if (subscribeResult.isOk()) { const stream = subscribeResult.value; for await (const event of stream) { if (event.mergeMessageBody.message.data.type === 1) { console.log(event.mergeMessageBody.message.data.castAddBody.text); } } } client.close(); } }); ``` ### Run the stream in your terminal Finally, you can run the script using `bun run index.ts` and it will provide you with a stream like this: Cast Stream ## Share with us! Lastly, make sure to share what you built with us on Farcaster by tagging [@neynar](https://warpcast.com/neynar) and if you have any questions, reach out to us on [warpcast](https://warpcast.com/~/channel/neynar) or [Slack](https://neynar.com/slack)! # Create in UI Source: https://docs.neynar.com/docs/create-farcaster-bot-ui Create a new Farcaster agent directly in Neynar dev portal ## Create new agent account easily in developer portal If you haven't created a new agent account yet, you can make it directly in the [Neynar dev portal](https://dev.neynar.com) . You can click inside an app and directly spin up a new bot from there. create agent Tap on "Create Agent" to make the agent. create agent Agent creation requires paying for account creation on the Farcaster protocol which Neynar does on your behalf. However, this is why we restrict the number of agents you can create per developer account. Best to not create more than one test agent through the portal in case you hit the limit prematurely. ## Start casting with agent account As soon as the agent account is created, you will see a `signer_uuid` for the agent. You can use that signer to cast from the agent account using Neynar's [Publist Cast](/reference/publish-cast) API. A simple cURL request like ```javascript Javascript curl --request POST \ --url https://api.neynar.com/v2/farcaster/cast \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --header 'x-api-key: NEYNAR_API_DOCS' \ --data ' { "signer_uuid": "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec", "text": "Writing to @farcaster via the @neynar APIs 🪐" } ' ``` should post a cast from that account. Ensure you are using the right `signer_uuid` and the API key associated with the same app that the signer is associated with. ## Listen to replies If your bot or agent needs to listen to replies, see how to use webhooks in [Listen for @bot mentions](/docs/listen-for-bot-mentions). Cast in the `/neynar` channel on Farcaster with any questions and tag `@rish` # Create Farcaster Mini App (v2 frame) in < 60s Source: https://docs.neynar.com/docs/create-farcaster-frame-in-60s Create a v2 Farcaster mini app in less than 60 seconds This doc has been updated. See [Create Farcaster Mini App in 60s](/docs/create-farcaster-miniapp-in-60s). # Farcaster Mini Apps Getting Started - Create mini apps in <60s Source: https://docs.neynar.com/docs/create-farcaster-miniapp-in-60s Create a Farcaster mini app in less than 60 seconds If looking to convert an existing web app into a mini app, see [Convert Web App to Mini App](/docs/convert-web-app-to-mini-app). This tutorial shows how to create a Farcaster mini app (previously known as Farcaster frames) with one simple command in less than 60s using the [Neynar Starter Kit](https://github.com/neynarxyz/create-farcaster-mini-app/).
Neynar Starter Kit demo app screenshot in dark mode Neynar Starter Kit demo app screenshot in light mode
Simply type `npx @neynar/create-farcaster-mini-app@latest` in any terminal window to get started with the template, or check out the [live demo of the Neynar Starter Kit](https://farcaster.xyz/miniapps/Qmodl2Stf9qh/starter-kit) on Farcaster. * package is open source ([github repo](https://github.com/neynarxyz/create-farcaster-mini-app)) * using neynar services is optional * demo API key is included if you haven't subscribed yet The flow: * generates signature required by mini app spec on your behalf and puts in the farcaster manifest * sets up splash image, CTA, etc. as part of workflow (incl. personalized share images, more on that below) * spins up a localtunnel hosted url so you can debug immediately, no need to ngrok or cloudflare on your own * if you use neynar: * automatically fetches user data * automatically sets sets up [notifications and analytics](/docs/send-notifications-to-mini-app-users) (just put in your client id from the dev portal) See \< 1 min video here that goes from scratch to testable mini app: ", "width": 600, "height": 400 } } } ``` ## Metadata Types ### Open Graph Properties The Open Graph properties available include: * `title`: The title of the content * `description`: A description of the content * `image`: URL to an image representing the content * `url`: The canonical URL of the content * `site_name`: The name of the site * And many other standard OG properties ### oEmbed Types The oEmbed data can be one of four types: General embeddable content with HTML Video content with playback capabilities Image content Simple link content Each type has specific properties relevant to that media type. ## Use Cases HTML metadata in frames and catalogs enables: * Rich previews of frame content in applications * Better understanding of frame content without loading the full frame * Enhanced display of catalog entries with proper titles, descriptions, and images * Improved accessibility for frame content ## Implementation Notes * HTML metadata is automatically extracted when frames and catalogs are processed * No additional parameters are needed to retrieve this data * The metadata follows standard Open Graph and oEmbed specifications This feature makes it easier to build rich, informative interfaces that display frame and catalog content with proper context and visual elements. # Farcaster Username Search in React Source: https://docs.neynar.com/docs/implementing-username-search-suggestion-in-your-farcaster-app Show good recommendations when users search for Farcaster users in your app ### This guide refers to [Search for Usernames](/reference/search-user) API. ## How to Implement Username Search Suggestions If you have a Farcaster React app, chances are your users will want to search for other users. This guide demonstrates how to implement user search recommendations in your Farcaster React app with the Neynar SDK. Check out this [Getting started guide](/docs/getting-started-with-neynar) to learn how to set up your environment and get an API key. Here's what the username search recommendation looks like: Username search We'll see the entire React component, and we'll dissect it afterwards. ```jsx JSX import { NeynarAPIClient,Configuration } from "@neynar/nodejs-sdk"; // make sure to set your NEYNAR_API_KEY .env // don't have an API key yet? get one at neynar.com const config = new Configuration({ apiKey: process.env.NEYNAR_API_KEY, }); const client = new NeynarAPIClient(config); import { useState, useEffect } from "react"; const App = () => { const [searchTerm, setSearchTerm] = useState(""); const [users, setUsers] = useState([]); useEffect(() => { const q = searchTerm; const limit = 10; const fetchUsers = async () => { try { const data = await client.searchUser({q, limit}); setUsers(data.result.users); } catch (error) { console.error("Error fetching users:", error); } }; if (searchTerm.length > 0) { // to prevent searching with an empty string fetchUsers(); } }, [searchTerm]); // This will trigger the useEffect when searchTerm changes return (
setSearchTerm(e.target.value)} />
    {users.map((user) => (
  • {user.username} - {user.display_name}
  • ))}
); }; export default App; ```
Alright, now that we've seen the React component, time to go through it slowly. We're using the [useState](https://react.dev/reference/react/useState) hook to keep track of the search term and the users we get back from the API. ```jsx JSX const [searchTerm, setSearchTerm] = useState(""); const [users, setUsers] = useState([]); ``` We're using the [useEffect](https://react.dev/reference/react/useEffect) hook to fetch the users when the search term changes. The API reference can be found in [Search User](/reference/search-user). ```jsx JSX useEffect(() => { const q = searchTerm; const limit = 10; const fetchUsers = async () => { try { const data = await client.searchUser({q, limit}); setUsers(data.result.users); } catch (error) { console.error("Error fetching users:", error); } }; if (searchTerm.length > 0) { // to prevent searching with an empty string fetchUsers(); } }, [searchTerm]); // This will trigger the useEffect when searchTerm changes ``` This input field listens to changes and updates the search term accordingly, thus triggering the `useEffect` hook. ```jsx JSX setSearchTerm(e.target.value)} /> ``` We're using the `map` function to iterate over the users and display them in a list. This list will be updated when the `users` state changes, which happens after the API call, which happens when the search term changes. ```jsx JSX
    {users.map((user) => (
  • {user.username} - {user.display_name}
  • ))}
```
That's it, you can now implement user search recommendations inside your Farcaster app! ### Ready to start building? Get your subscription at [neynar.com](https://neynar.com) and reach out to us on [Slack](https://neynar.com/slack) with any questions! # Indexer Service - Neynar API Source: https://docs.neynar.com/docs/indexer-service-pipe-farcaster-data Enterprise-grade Farcaster data indexing service ### Reach out for setup and pricing ## Stream Real Time Farcaster Data Reads real time data from Farcaster nodes and indexes it directly into your PostgreSQL database. Get complete Farcaster protocol data, custom filtering, and seamless integration for building scalable applications. ### **Benefits** * Full control over a Farcaster dataset that is synced in real-time * custom indexes, derivative tables, and custom APIs * No need to maintain a hub * No need to maintain an indexer with new protocol updates * Neynar handles all protocol changes for newly available data * Index subsets of data e.g. casts from users with [score](/docs/neynar-user-quality-score) > 0.5 * Index specific tables e.g. profiles, casts, verifications, etc. See list of all tables [here](https://dev.neynar.com/pricing#section_data_indexer_service). ### **Requirements** See [Requirements for indexer service](/docs/requirements-for-indexer-service) ### **Steps** * **Contact for setup** * Reach out to [@rish](https://warpcast.com/rish) or [@manan](https://warpcast.com/manan) on Farcaster * **Backfill** * Once Neynar receives the credentials from you, we will verify access to the database and permissions. * We will set up the schema and start the backfill process. * Expect 24-48 hours for the backfill to complete * **Livestream indexing** * Post backfill, all data will be indexed from the live stream from the hub ### **Why is my indexing lagging?** * **Are you correctly provisioned?** If you're indexing a large volume of data (especially full network data), your database needs to be properly provisioned for write-heavy workloads. Refer to our [provisioning requirements guide](https://docs.neynar.com/docs/requirements-for-indexer-service) for details based on the scale of your indexing. * **Are you running intense read queries on the same db?** If you're running heavy or complex read queries on the same tables our indexer is writing to, it can slow down the pipeline. We recommend setting up a read replica and run your queries against that, so writes are not blocked. * **Where is your database located?** Our indexer pipelines run in AWS US-East-1. If your destination database is hosted in a region far from this (e.g. Europe or Asia), network latency could increase. Consider hosting your database closer to US-East-1 for optimal performance. ### **Questions** For any questions, reach out to us on [Slack](https://join.slack.com/t/neynarcommunity/shared_invite/zt-3a4zrezxu-ePFVx2wKHvot1TLi2mVHkA)! # Write Data with Managed Signers Source: https://docs.neynar.com/docs/integrate-managed-signers Write to Farcaster protocol and let Neynar manage your signers for you ### If using login providers like Privy, it's best to use this signer product. Users won't see a double login and developers will get full write functionality to Farcaster. In this guide, we’ll take a look at how to integrate neynar managed signers with neynar in a next.js app *so your users can take actions on the Farcaster protocol.* Managed signers allow you to take full control of the connection, including the branding on Warpcast and everything else! ### To get started immediately, you can clone this [GitHub repository](https://github.com/neynarxyz/farcaster-examples/tree/main/managed-signers) Run the app as per the readme page in the repo ## Context This guide covers setting up a Next.js backend server to create and use Neynar Managed Signers for publishing a cast on Farcaster. The backend will: 1. Create a signer for the user. 2. Generate signature. 3. Provide API routes for frontend interactions. 4. Allow publishing casts on behalf of authenticated users. You can integrate this backend with a compatible frontend to enable a seamless authentication and authorization flow using Neynar Managed Signers. ## The ideal user flow **Terminology** To understand the ideal user flow, let's quickly go over some terminology: * Authentication: This is where an account proves they are who they say they are. Flows like Sign in with Farcaster (SIWF) or login providers like Privy allow this for app logins. * Authorization: this is where an account gives the app certain access privileges to take actions on behalf of the account. This is what Neynar signers allow for writing data to the protocol. Authorization requires authentication so that once a user is authenticated, they can then *authorize* an action. E.g. authenticating into your Google account to then authorize a 3rd party app like Slack to access your Google Calendar. If starting logged out, the full flow takes two distinct steps. **User journey** You can build a user flow using tools like: * [SIWN: Connect Farcaster accounts](/docs/how-to-let-users-connect-farcaster-accounts-with-write-access-for-free-using-sign-in-with-neynar-siwn) * or 3rd party login providers like Privy If using Privy * the 1st step on authentication happens on Privy login and the 2nd step of authorization happens on Neynar * the second step requires the user to scan a QR code or tap on a link to then generate a signer on a Farcaster client like Warpcast. Generating a signer requires paying onchain fees * Neynar [sponsors signers](/docs/two-ways-to-sponsor-a-farcaster-signer-via-neynar) by default so users pay \$0. **Let's go!** Now that we have context, let's get started! ## Requirements 1. **Node.js** ([LTS recommended](https://nodejs.org/en/download)) ## Installation 1. **Next.js** ```bash npx create-next-app@latest neynar-managed-signers && cd neynar-managed-signers ``` 2. **Dependencies** (to install via `npm`, `yarn`, etc.): ```bash npm npm i @farcaster/hub-nodejs @neynar/nodejs-sdk viem ``` ```bash yarn yarn add @farcaster/hub-nodejs @neynar/nodejs-sdk viem ``` 3. **Environment Variables**: Create a `.env.local` file in your Next.js project root and add: ```bash NEYNAR_API_KEY=... FARCASTER_DEVELOPER_MNEMONIC=... ``` * **NEYNAR\_API\_KEY**: Get from [Neynar Dashboard](https://dev.neynar.com/app). * **FARCASTER\_DEVELOPER\_MNEMONIC**: The mnemonic for your developer account on Farcaster. e.g. `@your_company_name` account on Farcaster (to state the obvious out loud, you won't need user mnemonics at any point) ## Directory Structure Make the following directory structure in your codebase ```bash └── app ├── api │ ├── signer │ │ └── route.ts │ └── cast │ └── route.ts └── ... └── lib └── neynarClient.ts └── utils ├── getFid.ts └── getSignedKey.ts └── .env.local └── ... ``` ## 1. `lib/neynarClient.ts` Copy the following into a file called `lib/neynarClient.ts` ```typescript Typescript import { Configuration, NeynarAPIClient } from "@neynar/nodejs-sdk"; if (!process.env.NEYNAR_API_KEY) { throw new Error("Make sure you set NEYNAR_API_KEY in your .env file"); } const config = new Configuration({ apiKey: process.env.NEYNAR_API_KEY, }); const neynarClient = new NeynarAPIClient(config); export default neynarClient; ``` ## 2. `utils/getFid.ts` Copy the following code into a file called `utils/getFid.ts` ```typescript Typescript import neynarClient from "@/lib/neynarClient"; import { mnemonicToAccount } from "viem/accounts"; export const getFid = async () => { if (!process.env.FARCASTER_DEVELOPER_MNEMONIC) { throw new Error("FARCASTER_DEVELOPER_MNEMONIC is not set."); } const account = mnemonicToAccount(process.env.FARCASTER_DEVELOPER_MNEMONIC); const { user: farcasterDeveloper } = await neynarClient.lookupUserByCustodyAddress({ custodyAddress: account.address, }); return Number(farcasterDeveloper.fid); }; ``` ## 3. `utils/getSignedKey.ts` Copy the following code into a file called `utils/getSignedKey.ts` ```typescript Typescript import neynarClient from "@/lib/neynarClient"; import { ViemLocalEip712Signer } from "@farcaster/hub-nodejs"; import { bytesToHex, hexToBytes } from "viem"; import { mnemonicToAccount } from "viem/accounts"; import { getFid } from "./getFid"; export const getSignedKey = async () => { const createSigner = await neynarClient.createSigner(); const { deadline, signature } = await generate_signature( createSigner.public_key ); if (deadline === 0 || signature === "") { throw new Error("Failed to generate signature"); } const fid = await getFid(); const signedKey = await neynarClient.registerSignedKey({ signerUuid: createSigner.signer_uuid, appFid: fid, deadline, signature, }); return signedKey; }; const generate_signature = async function (public_key: string) { if (typeof process.env.FARCASTER_DEVELOPER_MNEMONIC === "undefined") { throw new Error("FARCASTER_DEVELOPER_MNEMONIC is not defined"); } const FARCASTER_DEVELOPER_MNEMONIC = process.env.FARCASTER_DEVELOPER_MNEMONIC; const FID = await getFid(); const account = mnemonicToAccount(FARCASTER_DEVELOPER_MNEMONIC); const appAccountKey = new ViemLocalEip712Signer(account); const deadline = Math.floor(Date.now() / 1000) + 86400; // 24 hours const uintAddress = hexToBytes(public_key as `0x${string}`); const signature = await appAccountKey.signKeyRequest({ requestFid: BigInt(FID), key: uintAddress, deadline: BigInt(deadline), }); if (signature.isErr()) { return { deadline, signature: "", }; } const sigHex = bytesToHex(signature.value); return { deadline, signature: sigHex }; }; ``` We are doing a couple of things here, so let's break it down. We first use the `createSigner` to create a signer, and then we use the `appAccountKey.signKeyRequest` function from the `@farcaster/hub-nodejs` package to create a sign key request. Finally, we use the `registerSignedKey` function from the neynarClient to register the signedKey. `registerSignedKey` returns `signer_approved_url` that needs to be handled (More on this in step 7) ## 4. `app/api/signer/route.ts` Copy the following code into `app/api/signer/route.ts` ```typescript Typescript import { getSignedKey } from "@/utils/getSignedKey"; import { NextResponse } from "next/server"; export async function POST() { try { const signedKey = await getSignedKey(); return NextResponse.json(signedKey, { status: 200 }); } catch (error) { console.error(error); return NextResponse.json({ error: "An error occurred" }, { status: 500 }); } } ``` ## 5. `app/api/cast/route.ts` Copy the following code into `app/api/cast/route.ts` ```typescript Typescript import neynarClient from "@/lib/neynarClient"; import { NextResponse } from "next/server"; export async function POST(req: Request) { const body = await req.json(); try { const cast = await neynarClient.publishCast({ signerUuid: body.signer_uuid, text: body.text, }); return NextResponse.json(cast, { status: 200 }); } catch (error) { console.error(error); return NextResponse.json({ error: "An error occurred" }, { status: 500 }); } } ``` ## 6. Run the app Make sure nothing is running on port 3000. Ensure that your `.env.local` file is correctly set up before running the application. ```bash Shell yarn run dev ``` Now, the app is ready to serve requests. You can build a frontend to handle these requests, but for this demo, we'll use cURL. ## 7. cURL `/api/signer` ```bash Shell curl -X POST http://localhost:3000/api/signer \ -H "Content-Type: application/json" ``` Response ```json JSON { "signer_uuid": "1234-abcd-5678-efgh", "public_key": "0xabcdef1234567890...", "status": "pending_approval", "signer_approval_url": "https://client.warpcast.com/deeplinks/signed-key-request?token=0xf707aebde...d049" } ``` You can either store the `signer_uuid` in the database or [use fetchSigners api to fetch signers for the user](/docs/fetch-signers-1) (We recommend the latter method) Convert `signer_approved_url` to a QR code (For testing, you can use any online tool, e.g., [QRFY](https://qrfy.com/)) If the user is using the application on desktop, then ask the user to scan this QR code. If the user is on mobile, ask them to click the link. This will deeplink the user into Warpcast, and they will see the following screenshot. Signer approval The user will need to pay for this on-chain transaction. (If you don't want users to pay, [you can sponsor it yourself or let neynar sponsor the signer](/docs/two-ways-to-sponsor-a-farcaster-signer-via-neynar)) ## 8. cURL `/api/cast` ```bash Shell curl -X POST http://localhost:3000/api/cast \ -H "Content-Type: application/json" \ -d '{ "signer_uuid": "1234-abcd-5678-efgh", "text": "Hello Farcaster!" }' ``` Response ```json JSON { "success": true, "cast": { "hash": "0xcda4f957b4c68883080f0daf9e75cea1309147da", "author": { "object": "user", "fid": 195494, "username": "rishav", "display_name": "Neynet tester 1/14", "pfp_url": "https://cdn-icons-png.freepik.com/256/17028/17028049.png?semt=ais_hybrid", "custody_address": "0x7355b6af053e5d0fdcbc23cc8a45b0cd85034378", "profile": { "bio": { "text": "12/19" }, "location": { "latitude": 36.2, "longitude": 138.25, "address": { "city": "Nagawa", "state": "Nagano Prefecture", "country": "Japan", "country_code": "jp" } } }, "follower_count": 6, "following_count": 50, "verifications": [], "verified_addresses": { "eth_addresses": [], "sol_addresses": [] }, "verified_accounts": [ { "platform": "x", "username": "unverified" }, { "platform": "github", "username": "unverified" } ], "power_badge": false }, "text": "Hello Farcaster!" } } ``` ### Read more [Two Ways to Sponsor a Farcaster Signer via Neynar](/docs/two-ways-to-sponsor-a-farcaster-signer-via-neynar) to see how to *sponsor a signer* on behalf of the user ## Conclusion With this backend setup, your Next.js app can: * Generate Neynar Managed Signers for a single app. (`@your_company_name` account) * Provide API routes for frontend interactions to handle signers and publishing casts. This backend should be integrated with the corresponding frontend to enable a seamless login and cast publishing experience. For the completed code with frontend intergation, check out the [GitHub repository](https://github.com/neynarxyz/farcaster-examples/tree/main/managed-signers). If you encounter any issues, reach out to the [Neynar team for support](https://neynar.com/slack). # Like & Recast Source: https://docs.neynar.com/docs/liking-and-recasting-with-neynar-sdk Add "like" and "recast" reactions on Farcaster casts * If you want to integrate Farcaster auth for your users, easiest way to start is [Sign in with Neynar](/docs/how-to-let-users-connect-farcaster-accounts-with-write-access-for-free-using-sign-in-with-neynar-siwn) (Neynar pays all onchain fee) * If you want dedicated signers for your user or bot, simplest to clone this [example app](https://github.com/neynarxyz/farcaster-examples/tree/main/managed-signers) for quickstart This guide demonstrates how to like or recast a cast with the Neynar SDK. Check out this [Getting Started Guide](/docs/getting-started-with-neynar) to learn how to set up your environment and get an API key. First, initialize the client: ```javascript Javascript // npm i @neynar/nodejs-sdk import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk"; // make sure to set your NEYNAR_API_KEY .env // don't have an API key yet? get one at neynar.com const config = new Configuration({ apiKey:process.env.NEYNAR_API_KEY, }); const client = new NeynarAPIClient(config); const signer = process.env.NEYNAR_SIGNER; ``` Then, like a cast: ```javascript Javascript const hash = "0x6932a9256f34e18892d498abb6d00ccf9f1c50d6"; client.publishReaction({ signerUuid: signer, reactionType: "like", target:hash }); ``` Recasting works the same way, replace "like" with "recast": ```javascript Javascript const hash = "0x6932a9256f34e18892d498abb6d00ccf9f1c50d6"; client.publishReaction({ signerUuid: signer, reactionType: "like", target:hash }); ``` The response status code should return a 200 status code. To verify that the reaction was published, you can fetch the cast's reactions: ```javascript Javascript const types = ReactionsType.All; const reactions = await client.fetchCastReactions({ hash, types: [types] }); console.log(reactions); ``` Which would print out ```json { "result": { "casts": [ { "type": "like", "hash": "0x691fabb3fc58bd4022d4358b2bc4f44469ad959a", "reactor": { "fid": "4640", "username": "picture", "displayName": "Picture", "pfp": { "url": "https://lh3.googleusercontent.com/erYudyT5dg9E_esk8I1kqB4bUJjWAmlNu4VRnv9iUuq_by7QjoDtZzj_mjPqel4NYQnvqYr1R54m9Oxp9moHQkierpY8KcYLxyIJ" }, "followerCount": "45", "followingCount": "57" }, "timestamp": "2023-12-10T15:26:45.000Z", "castHash": "0x6932a9256f34e18892d498abb6d00ccf9f1c50d6" } ], "next": { "cursor": null } } } ``` That's it! You can now like or recast any cast on Farcaster. PS - to learn more about how writes technically works on Farcaster, read [Write to Farcaster with Neynar Managed Signers](/docs/write-to-farcaster-with-neynar-managed-signers) ### Ready to start building? Get your subscription at [neynar.com](https://neynar.com) and reach out to us on [Slack](https://neynar.com/slack) with any questions! # Listen for @bot Mentions Source: https://docs.neynar.com/docs/listen-for-bot-mentions Get notified when someone tags your bot in a cast The easiest way to listen for when your bot gets tagged in a cast is to set up a webhook in the Neynar developer portal and integrate it into your bot codebase. ### ngrok is having issues with webhook deliveries. Even though this guide uses ngrok, we suggest you use your own domain or a provider like cloudflare tunnels. ## Set up a webhook for your bot on Neynar Dev portal To create a new webhook without writing any code, head to the neynar dashboard and go to the [webhooks tab](https://dev.neynar.com/webhook). #### Get events when your bot gets tagged in a cast e.g. `@bot` Click on the new webhook and enter the `fid` of your bot in the `mentioned_fids` field for a `cast.created` filter. What this does is anytime a cast is created on the protocol, it checks if your bot that has that `fid` is *mentioned* in the cast. If it is, it fires a webhook event to your backend. #### Get events when someone replies to your bot In the same webhook as above, insert the `fid` of your bot in the `parent_author_fids` field. See screenshot below. This will fire an event for whenever someone casts a reply where your bot is the *parent cast's author*. You will notice that the same webhook now has two filters for the `cast.created` event. This is because webhook filters are logical `OR` filters meaning that the event will fire if any one of the conditions are fulfilled. In this case, the webhook server will notify your backend if someone * tags your bot i.e. `mentioned_fids` filter * replies to your bot i.e. `parent_author_fids`filter #### Expand as needed If you build more than one bot, you can continue adding those fids to these fields in comma separated format and you will get webhook events for any of the bots (or users, doesn't have to be bots). Now let's move on to processing the events you receive on your backend. ## Receive real time webhook events on your backend #### Setting up a POST url Your backend needs a POST url to listen for incoming webhook events. The webhook will fire to the specified `target_url`. For the purpose of this demo, we used [ngrok](https://ngrok.com/) to create a public URL. You can also use a service like [localtunnel](https://theboroer.github.io/localtunnel-www/) that will forward requests to your local server. **Note** that *free endpoints like ngrok, localtunnel, etc. usually have issues because service providers start blocking events. Ngrok is particularly notorious for blocking our webhook events.* This is best solved by using a url on your own domain. #### Server to process events received by the POST url Let's create a simple server that logs out the event. We will be using [Bun JavaScript](https://bun.sh). ```javascript Javascript const server = Bun.serve({ port: 3000, async fetch(req) { try { console.log(await req.json()); return new Response("gm!"); } catch (e: any) { return new Response(e.message, { status: 500 }); } }, }); console.log(`Listening on localhost:${server.port}`); ``` Next: run `bun serve index.ts`, and run ngrok with `ngrok http 3000`. Copy the ngrok URL and paste it into the "Target URL" field in the Neynar developer portal. The webhook will call the target URL every time the selected event occurs. Here, I've chosen to receive all casts that mention `@neynar` in the text by putting in @neynar's `fid`: 6131. Now the server will log out the event when it is fired. It will look something like this: ```javascript Javascript { created_at: 1708025006, type: "cast.created", data: { object: "cast", hash: "0xfe7908021a4c0d36d5f7359975f4bf6eb9fbd6f2", thread_hash: "0xfe7908021a4c0d36d5f7359975f4bf6eb9fbd6f2", parent_hash: null, parent_url: "chain://eip155:1/erc721:0xfd8427165df67df6d7fd689ae67c8ebf56d9ca61", root_parent_url: "chain://eip155:1/erc721:0xfd8427165df67df6d7fd689ae67c8ebf56d9ca61", parent_author: { fid: null, }, author: { object: "user", fid: 234506, custody_address: "0x3ee6076e78c6413c8a3e1f073db01f87b63923b0", username: "balzgolf", display_name: "Balzgolf", pfp_url: "https://i.imgur.com/U7ce6gU.jpg", profile: [Object ...], follower_count: 65, following_count: 110, verifications: [ "0x8c16c47095a003b726ce8deffc39ee9cb1b9f124" ], active_status: "inactive", }, text: "@neynar LFG", timestamp: "2024-02-15T19:23:22.000Z", embeds: [], reactions: { likes: [], recasts: [], }, replies: { count: 0, }, mentioned_profiles: [], }, } ``` These events will be delivered real time to your backend as soon as they appear on the protocol. If you see a cast on a client but you haven't received it on your backend: 1. make sure you're not using ngrok or a similar service, use your own domain. 2. check the cast hash/url on [https://explorer.neynar.com](https://explorer.neynar.com) to see where it's propagated on the network. If it hasn't propagated to the hubs, the network doesn't have the cast and thus the webhook didn't fire. 3. make sure your backend server is running to receive the events. Once you receive the event, return a `200` success to the webhook server else it will keep retrying the same event delivery. ## Create webhooks programmatically Now that you know how to set up webhooks manually on the dev portal, you might be wondering how to create them dynamically. You can do so using our [Webhook](/reference/lookup-webhook) APIs. They will allow you to create, delete or update webhooks. Few things to remember when creating webhooks programmatically: 1. You can add an almost infinite number of filters to the same webhook so no need to create new webhooks for new filters. 2. Filters are overwritten with new filters. So for e.g. if you are listening to mentions of fid 1 and now you want to listen to mentions of fid 1 and 2, you should pass in both 1 and 2 in the filters when you update. ## You're ready to build! That's it, it's that simple! Make sure to sure what you built with us on Farcaster by tagging [@neynar](https://warpcast.com/neynar) and if you have any questions, reach out to us on [warpcast](https://warpcast.com/~/channel/neynar) or [Slack](https://neynar.com/slack)! # Make Agents Prompt Transactions Source: https://docs.neynar.com/docs/make-agents-prompt-transactions Agents prompt transactions to humans with Farcaster frames ### Related API reference: [Create transaction pay frame](/reference/create-transaction-pay-frame) ## Agents need to transact To become full digital citizens of our digital universes, agents need to transact with other agents and humans. A large portion of such transactions will happen onchain. Today, it's hard to AI agents to prompt humans to transact with them. They can * set up a full app on a webpage (takes time and effort) * link out to a contract address (bad UX) * tell the user what to do in text prose (confusing) We are changing that with Farcaster frames. Developers can now *dynamically* generate transaction frames on the fly and prompt a transaction as part of the cast! ## Creating a transaction frame ### Pay transactions We are starting with simple pay transactions where the agent can prompt the user to pay for a certain action. To do so, agent (developers) can use the [Create transaction pay frame](/reference/create-transaction-pay-frame). It takes in 1. Transaction object with details of the receiver, network (limited to Base to start), token contract and amount 2. Configuration object that allows configuring what the frame page should show e.g. the line item for the transaction 3. Action object to configure the primary CTA of the frame 4. It even lets you allowlist a certain list of FIDs if your agent isn't open to transacting with everyone Your API request will look like below: ```javascript Javascript curl --request POST \ --url https://api.neynar.com/v2/farcaster/frame/transaction/pay \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --header 'x-api-key: NEYNAR_API_DOCS' \ --data ' { "transaction": { "to": { "network": "base", "address": "0x5a927ac639636e534b678e81768ca19e2c6280b7", "token_contract_address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "amount": 0.01 } }, "config": { "line_items": [ { "name": "eth is down", "description": "lets bring it back up", "image": "https://i.imgur.com/E12sUoO_d.webp?maxwidth=1520&fidelity=grand" } ], "action": { "text": "take some eth", "text_color": "#FFFFFF", "button_color": "#000000" } } } ' ``` and it will return a response that contains a frame URL: ```javascript Javascript { "transaction_frame": { "id": "01JP3SQS2R2YQ6FAJGH3C5K5HB", "url": "https://app.neynar.com/frame/pay/01JP3SQS2R2YQ6FAJGH3C5K5HB", "type": "pay", "app": { "name": "readme.com", "logo_url": "https://cdn-icons-png.flaticon.com/512/2815/2815428.png" }, "transaction": { "to": { "amount": 0.01, "address": "0x5a927ac639636e534b678e81768ca19e2c6280b7", "network": "base", "token_contract_address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" } }, "config": { "action": { "text": "take some eth", "text_color": "#FFFFFF", "button_color": "#000000" }, "line_items": [ { "name": "eth is down", "image": "https://i.imgur.com/nYvX36t.png", "description": "lets bring it back up" } ] }, "status": "created" } } ``` It will dynamically generate a frame like that at the above URL: You can now cast out this frame programmatically using our [Publish Cast](/reference/publish-cast) API. You might want to save the `frame_id` for future purposes to look up details for this frame. Once you use the frame url in a cast, it will automatically create a splash embed like the following: ### Other transaction types We are starting with pay transactions and will add other transaction types shortly! ## Fetching details for an existing transaction frame If you have an existing transaction frame you made in the past, you can fetch the details for it through [Get transaction pay frame](/reference/get-transaction-pay-frame). Pass in the `frame_id` in the request and it will return frame details. # Farcaster Feed of NFT Owners Source: https://docs.neynar.com/docs/making-a-farcaster-feed-of-miladies Make a Farcaster feed showing casts from a specific set of users ### Related API reference [Fetch Feed](/reference/fetch-feed) This guide demonstrates how to make a feed of Farcaster casts from users who own a specific NFT. Check out this [Getting started guide](/docs/getting-started-with-neynar) to learn how to set up your environment and get an API key. Before all that, initialize Neynar client: ```javascript Javascript // npm i @neynar/nodejs-sdk import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk"; import { FeedType,FilterType } from "@neynar/nodejs-sdk/build/api/index.js"; // make sure to set your NEYNAR_API_KEY .env // don't have an API key yet? get one at neynar.com const config = new Configuration({ apiKey: process.env.NEYNAR_API_KEY, }); const client = new NeynarAPIClient(config); ``` First, we need to get the addresses owning Milady. We can use the [Alchemy NFT API](https://docs.alchemy.com/reference/getownersforcontract-v3) to get the addresses of users who own the NFT. ```javascript Javascript const getAddr = async (nftAddr: string): Promise => { const apiKey = process.env.ALCHEMY_API_KEY; const baseUrl = `https://eth-mainnet.g.alchemy.com/nft/v3/${apiKey}/getOwnersForContract?`; const url = `${baseUrl}contractAddress=${nftAddr}&withTokenBalances=false`; const result = await fetch(url, { headers: { accept: "application/json" }, }); const data = await result.json(); return data.owners; }; // milady maker contract address const nftAddr = "0x5af0d9827e0c53e4799bb226655a1de152a425a5"; const addrs = await getAddr(nftAddr); ``` Next, get Farcaster FIDs of each address, then filter out any undefined values. ```javascript Javascript const fidLookup = async (addrs: string[]) => { const fids = await Promise.all( addrs.map(async (addr) => { try { const response = await client.fetchBulkUsersByEthOrSolAddress({ addresses: [addr], }); return response ? response.result.user.fid : undefined; } catch (error) { return undefined; } }) ); return fids.filter((fid) => fid !== undefined); }; const fids = await fidLookup(addrs); ``` Lastly, fetch the feed using the FIDs. ```javascript Javascript const feedType = FeedType.Filter; const filterType= FilterType.Fids; const feed = await client.fetchFeed({feedType, filterType, fids }); console.log(feed); ``` Example output: ```json Json { casts: [ { object: "cast_hydrated", hash: "0x4b02b1ef6daa9fe111d3ce871ec004936f19b979", thread_hash: "0x4b02b1ef6daa9fe111d3ce871ec004936f19b979", parent_hash: null, parent_url: "https://veryinter.net/person", parent_author: [Object ...], author: [Object ...], text: "What'd you buy for Black Friday / Cyber Monday?\n\nI got a new webcam and bought a Roomba+mop that I'm excited to fiddle with.", timestamp: "2023-11-27T14:46:01.000Z", embeds: [], reactions: [Object ...], replies: [Object ...], mentioned_profiles: [] }, { object: "cast_hydrated", hash: "0xf1210d9eb6b21bbf3847ca5983539ed9c2baee13", thread_hash: "0xf1210d9eb6b21bbf3847ca5983539ed9c2baee13", parent_hash: null, parent_url: null, parent_author: [Object ...], author: [Object ...], text: "Great couple days mostly off the internet. 🦃🤗\n\nAlso excited to be back in the mix.\n\nWhat will be the biggest stories to end the year?", timestamp: "2023-11-27T14:44:19.000Z", embeds: [], reactions: [Object ...], replies: [Object ...], mentioned_profiles: [] }, { object: "cast_hydrated", hash: "0x7d3ad4be401c0050cf20a060ebbd108383b6357c", thread_hash: "0x7d3ad4be401c0050cf20a060ebbd108383b6357c", parent_hash: null, parent_url: "https://foundation.app", parent_author: [Object ...], author: [Object ...], text: "Consisting of 50 1/1 works, Ver Clausi's new drop Blaamius imagines life after the Anthropocene. His rich, colorful illustrations that meld subject and scenery remind me of old sci-fi comics and H.R. Giger in the best possible way. \nPrice: 0.025\nhttps://foundation.app/collection/bla-cebc", timestamp: "2023-11-27T14:29:37.000Z", embeds: [ [Object ...] ], reactions: [Object ...], replies: [Object ...], mentioned_profiles: [] } ], next: { cursor: "eyJ0aW1lc3RhbXAiOiIyMDIzLTExLTI3IDE0OjI5OjM3LjAwMDAwMDAifQ==" } } ``` Farcaster feed of Milady owners! ### Ready to start building? Get your subscription at [neynar.com](https://neynar.com) and reach out to us on [Slack](https://neynar.com/slack) with any questions! # Mini App Authentication Source: https://docs.neynar.com/docs/mini-app-authentication Complete guide to authentication flows and developer-branded signer creation in Farcaster mini apps # Authentication & Signer Management This document provides a comprehensive overview of the authentication system and signer creation process in your Farcaster mini app. The system uses [**Sign in with Farcaster (SIWF)**](https://github.com/farcasterxyz/protocol/discussions/110) protocol to authenticate users and create signers for Farcaster protocol interactions. *Although titled "Mini app authentication", this can also be used in web apps if you'd like.* ## Overview The authentication system is built around **signers** - cryptographic keys that allow your application to act on behalf of a user within the Farcaster protocol. Full code for this flow can be found in the [Neynar Mini App Starter Kit](https://github.com/neynarxyz/create-farcaster-mini-app/blob/main/src/components/ui/NeynarAuthButton/index.tsx) ## Architecture Components The system involves four main components: ```mermaid sequenceDiagram participant A as Miniapp Client participant B as Miniapp Server participant C as Neynar Server participant D as Farcaster App A->>B: Step 1: Get Nonce B->>C: Fetch Nonce C-->>B: Return Nonce B-->>A: Return Nonce A->>A: Step 2: Inject nonce in SIWF message A->>D: Step 3: Ask user for signature D-->>A: Return signature A->>B: Step 4: Send SIWF message + signature B->>C: Step 5: Fetch Signers (/api/auth/signers) C-->>B: Return Signers B->>B: Step 6: Check if signer exists alt Signer not present B->>C: Step 7: Create Signer C-->>B: Return Signer B->>C: Step 8: Register Signed Key C-->>B: Return Approval URL end A->>A: Step 9: Start Polling A->>D: Step 10: Show QR (desktop) or Deep Link (mobile) D->>C: User Approves Signer A->>B: Poll for status B->>C: Check Approval Status C-->>B: Return Status B-->>A: Return Status alt Signer approved B->>C: Fetch Signers Again C-->>B: Return Signers A->>A: Step 11: Store in LocalStorage (frontend) B->>B: Store in Session (backend) end ``` ### API Endpoints | Endpoint | Method | Purpose | Step | | ----------------------------- | -------- | ----------------------------- | ---------------- | | `/api/auth/nonce` | GET | Generate authentication nonce | Step 1 | | `/api/auth/signers` | GET | Fetch user signers | Step 5 | | `/api/auth/session-signers` | GET | Fetch signers with user data | Step 5 (Backend) | | `/api/auth/signer` | POST | Create new signer | Step 7 | | `/api/auth/signer` | GET | Check signer status | Step 9 | | `/api/auth/signer/signed_key` | POST | Register signed key | Step 8 | | `/api/auth/[...nextauth]` | GET/POST | NextAuth handlers | Backend Flow | ## Complete Authentication Flow ### Step 1: Get the Nonce The authentication process begins by fetching a cryptographic nonce from the Neynar server. **Mini App Client → Mini App Server:** ```typescript const generateNonce = async () => { const response = await fetch('/api/auth/nonce'); const data = await response.json(); setNonce(data.nonce); }; ``` **Mini App Server → Neynar Server:** ```typescript // /api/auth/nonce/route.ts 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 } ); } } ``` ### Step 2: Inject Nonce in Sign in with Farcaster The nonce is used to create a [Sign in with Farcaster](https://github.com/farcasterxyz/protocol/discussions/110) message. ```typescript // Frontend Flow using Farcaster Auth Kit const { signIn, connect, data } = useSignIn({ nonce: nonce || undefined, onSuccess: onSuccessCallback, onError: onErrorCallback, }); // Backend Flow using Farcaster SDK const handleBackendSignIn = async () => { const result = await sdk.actions.signIn({ nonce }); // result contains message and signature }; ``` ### Step 3: Ask User for the Signature The user is prompted to sign the SIWF message through their Farcaster client. **Frontend Flow:** ```typescript useEffect(() => { if (nonce && !useBackendFlow) { connect(); // Triggers signing flow } }, [nonce, connect, useBackendFlow]); ``` **Backend Flow:** ```typescript // User signs through Farcaster mobile app const signInResult = await sdk.actions.signIn({ nonce }); const { message, signature } = signInResult; ``` ### Step 4: Receive Message and Signature Once the user signs the message, the client receives the signature. ```typescript const onSuccessCallback = useCallback( async (res: UseSignInData) => { console.log('✅ Authentication successful:', res); setMessage(res.message); setSignature(res.signature); }, [useBackendFlow, fetchUserData] ); ``` ### Step 5: Send to /api/auth/signers to Fetch Signers With the signed message and signature, fetch existing signers for the user. **Mini App Client → Mini App Server:** ```typescript const fetchAllSigners = async (message: string, signature: string) => { const endpoint = useBackendFlow ? `/api/auth/session-signers?message=${encodeURIComponent( message )}&signature=${signature}` : `/api/auth/signers?message=${encodeURIComponent( message )}&signature=${signature}`; const response = await fetch(endpoint); const signerData = await response.json(); return signerData; }; ``` **Mini App Server → Neynar Server:** ```typescript // /api/auth/signers/route.ts export async function GET(request: Request) { 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 } ); } try { const client = getNeynarClient(); const data = await client.fetchSigners({ message, signature }); return NextResponse.json({ signers: data.signers, }); } catch (error) { console.error('Error fetching signers:', error); return NextResponse.json( { error: 'Failed to fetch signers' }, { status: 500 } ); } } ``` **For Backend Flow with User Data:** ```typescript // /api/auth/session-signers/route.ts export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const message = searchParams.get('message'); const signature = searchParams.get('signature'); 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 } ); } } ``` ### Step 6: Check if Signers are Present Determine if the user has existing approved signers. ```typescript const hasApprovedSigners = signerData?.signers?.some( (signer: any) => signer.status === 'approved' ); if (hasApprovedSigners) { // User has signers, proceed to store them proceedToStorage(signerData); } else { // No signers, need to create new ones startSignerCreationFlow(); } ``` ### Step 7: Create a Signer If no signers exist, create a new signer. **Mini App Client → Mini App Server:** ```typescript const createSigner = async () => { const response = await fetch('/api/auth/signer', { method: 'POST', }); if (!response.ok) { throw new Error('Failed to create signer'); } return await response.json(); }; ``` **Mini App Server → Neynar Server:** ```typescript // /api/auth/signer/route.ts export async function POST() { try { const neynarClient = getNeynarClient(); const signer = await neynarClient.createSigner(); return NextResponse.json(signer); } catch (error) { console.error('Error creating signer:', error); return NextResponse.json( { error: 'Failed to create signer' }, { status: 500 } ); } } ``` ### Step 8: Register a Signed Key Register the signer's public key with the Farcaster protocol. **Mini App Client → Mini App Server:** ```typescript const generateSignedKeyRequest = async ( signerUuid: string, publicKey: string ) => { const response = await fetch('/api/auth/signer/signed_key', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ signerUuid, publicKey, redirectUrl: window.location.origin, // Optional redirect after approval }), }); if (!response.ok) { throw new Error('Failed to register signed key'); } return await response.json(); }; ``` **Mini App Server → Neynar Server:** ```typescript // /api/auth/signer/signed_key/route.ts export async function POST(request: Request) { const body = await request.json(); const { signerUuid, publicKey, redirectUrl } = body; // Validate required fields if (!signerUuid || !publicKey) { return NextResponse.json( { error: 'signerUuid and publicKey are required' }, { 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)' }, { status: 500 } ); } const neynarClient = getNeynarClient(); const account = mnemonicToAccount(seedPhrase); // Get app FID from custody address 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 } ); } } ``` ### Step 9: Start Polling Begin polling the signer status to detect when it's approved. ```typescript const startPolling = ( signerUuid: string, message: string, signature: string ) => { const interval = setInterval(async () => { try { const response = await fetch(`/api/auth/signer?signerUuid=${signerUuid}`); const signerData = await response.json(); if (signerData.status === 'approved') { clearInterval(interval); console.log('✅ Signer approved!'); // Refetch all signers await fetchAllSigners(message, signature); } } catch (error) { console.error('Error polling signer:', error); } }, 2000); // Poll every 2 seconds return interval; }; ``` **Polling API Implementation:** ```typescript // /api/auth/signer/route.ts (GET method) 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 signer status:', error); return NextResponse.json( { error: 'Failed to fetch signer status' }, { status: 500 } ); } } ``` ### Step 10: Show Signer Approval URL Display QR code for desktop users or deep link for mobile users. ```typescript const handleSignerApproval = (approvalUrl: string) => { const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent ); if (isMobile && context?.client) { // Mobile: Deep link to Farcaster app const farcasterUrl = approvalUrl.replace( 'https://client.farcaster.xyz/deeplinks/signed-key-request', 'https://farcaster.xyz/~/connect' ); // Use SDK to open URL in Farcaster app sdk.actions.openUrl(farcasterUrl); } else { // Desktop: Show QR code setSignerApprovalUrl(approvalUrl); setDialogStep('access'); setShowDialog(true); } }; ``` ### Step 11: Store Signers Once approved, store the signers in appropriate storage. **Frontend Flow (LocalStorage):** ```typescript const storeInLocalStorage = (user: any, signers: any[]) => { const authState = { isAuthenticated: true, user, signers, }; setItem(STORAGE_KEY, authState); setStoredAuth(authState); }; ``` **Backend Flow (NextAuth Session):** ```typescript const updateSessionWithSigners = async (signers: any[], user: any) => { if (useBackendFlow && message && signature && nonce) { const signInData = { message, signature, nonce, fid: user?.fid?.toString(), signers: JSON.stringify(signers), user: JSON.stringify(user), }; const result = await backendSignIn('neynar', { ...signInData, redirect: false, }); if (result?.ok) { console.log('✅ Session updated with signers'); } } }; ``` ## State Management & Storage ### Frontend Flow State (LocalStorage) ```typescript interface StoredAuthState { isAuthenticated: boolean; user: { fid: number; username: string; display_name: string; pfp_url: string; custody_address: string; profile: { bio: { text: string }; location?: any; }; follower_count: number; following_count: number; verifications: string[]; verified_addresses: { eth_addresses: string[]; sol_addresses: string[]; primary: { eth_address: string; sol_address: string; }; }; power_badge: boolean; score: number; } | null; signers: { object: 'signer'; signer_uuid: string; public_key: string; status: 'approved'; fid: number; }[]; } // Stored in localStorage with key 'neynar_authenticated_user' const STORAGE_KEY = 'neynar_authenticated_user'; ``` ### Backend Flow State (NextAuth Session) ```typescript interface Session { provider: 'neynar'; user: { fid: number; object: 'user'; username: string; display_name: string; pfp_url: string; custody_address: string; profile: { bio: { text: string }; location?: any; }; follower_count: number; following_count: number; verifications: string[]; verified_addresses: { eth_addresses: string[]; sol_addresses: string[]; primary: { eth_address: string; sol_address: string; }; }; power_badge: boolean; score: number; }; signers: { object: 'signer'; signer_uuid: string; public_key: string; status: 'approved'; fid: number; }[]; } ``` ## Security & Configuration ### EIP-712 Signature Validation The system uses EIP-712 typed data signing for secure signer registration: ```typescript // From /lib/constants.ts export const SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN = { name: 'Farcaster SignedKeyRequestValidator', version: '1', chainId: 10, verifyingContract: '0x00000000fc700472606ed4fa22623acf62c60553' as `0x${string}`, }; export const SIGNED_KEY_REQUEST_TYPE = [ { name: 'requestFid', type: 'uint256' }, { name: 'key', type: 'bytes' }, { name: 'deadline', type: 'uint256' }, ]; ``` ### Required Environment Variables ```bash # Neynar API configuration NEYNAR_API_KEY=your_neynar_api_key NEYNAR_CLIENT_ID=your_neynar_client_id # App signing key for signer registration SEED_PHRASE=your_twelve_word_mnemonic_phrase # Optional: Sponsor signer creation costs (recommended for production) SPONSOR_SIGNER=true ``` ### Flow Detection ```typescript // Determine which flow to use const { context } = useMiniApp(); const useBackendFlow = context !== undefined; // Frontend flow uses localStorage + Farcaster Auth Kit // Backend flow uses NextAuth sessions + Farcaster SDK ``` ### Integration Examples #### Checking Authentication Status ```typescript // Frontend Flow const isAuthenticated = storedAuth?.isAuthenticated && storedAuth?.signers?.some((s) => s.status === 'approved'); // Backend Flow const isAuthenticated = session?.provider === 'neynar' && session?.user?.fid && session?.signers?.some((s) => s.status === 'approved'); ``` #### Using Signers for Farcaster Actions ```typescript // Get signer const signer = (useBackendFlow ? session?.signers : storedAuth?.signers)[0]; if (signer) { // Use signer for publishing casts const client = getNeynarClient(); await client.publishCast({ signerUuid: signer.signer_uuid, text: 'Hello from my mini app!', }); } ``` ## Summary Authentication flow provides a comprehensive system for: 1. **Secure Authentication**: Using SIWF protocol with cryptographic nonces 2. **Signer Management**: Creating and approving signers for Farcaster actions 3. **Multi-Platform Support**: Works in web browsers and Farcaster mobile clients 4. **State Persistence**: Maintains authentication across sessions The system abstracts the complexity of Farcaster protocol interactions while providing a seamless user experience for both web and mobile environments. [Feel Free to reach out with any questions or for further assistance in integrating this authentication flow into your Farcaster mini app!](https://neynar.com/slack) ## Happy coding! 🎉 # Common UX Mistakes and Launch Strategies for Mini Apps Source: https://docs.neynar.com/docs/mini-app-common-mistakes Avoid these common pitfalls and learn proven launch strategies for viral Farcaster mini apps While the [main virality guide](/docs/mini-app-virality-guide) covers the core design principles, this page focuses on specific mistakes to avoid and proven launch strategies that can make or break your mini app's success. ## Common UX Mistakes That Kill Virality ### Don't Force Social Features ❌ **Bad:** Requiring users to share before accessing basic features
✅ **Good:** Making sharing the natural next step after achievements ❌ **Bad:** Generic "Share to unlock" mechanics
✅ **Good:** Exclusive rewards that make sharing genuinely valuable ### Don't Ignore Social Context ❌ **Bad:** Anonymous leaderboards and generic achievements
✅ **Good:** Social proof (profile pics, user stats) and friend-based competition ❌ **Bad:** Spammy notifications based on arbitrary timers
✅ **Good:** Social trigger notifications that create genuine FOMO ### Don't Waste the Wallet Advantage ❌ **Bad:** Financial incentives that feel disconnected from social mechanics
✅ **Good:** Token rewards that amplify friend competition and sharing ❌ **Bad:** Generic minting without social context
✅ **Good:** Collaborative minting or rewards users can send to friends that create network effects ## Launch Strategy Ideas: Building Momentum ### Pre-Launch: Create Anticipation * Use Neynar's APIs to identify influential accounts in your niche * Create a waitlist with referral bonuses and early token allocations * Seed content from beta users to create social proof (so your leaderboards aren't empty on day one) * Pre-mint exclusive NFTs for early supporters and testers ### Launch Day: Maximize Initial Virality * Use Neynar's notification system to alert waitlist users * Target high-follower users first to create cascade effects * Use limited-time minting opportunities to create urgency * Focus on getting early users to their first shareable moment and first token claim quickly ### Post-Launch: Sustain Growth * Create recurring events with daily activity streaks evolving reward structures * Use token incentives to reactivate dormant social connections * Build features that increase both network density and token circulation within your app * Analyze which social patterns and financial rewards drive the most viral sharing ## Key Takeaways The most successful mini apps avoid these common pitfalls: 1. **Don't force social features** - make them natural byproducts of fun experiences 2. **Always include social context** - anonymous experiences don't spread 3. **Leverage the wallet** - financial incentives should amplify social mechanics 4. **Plan your launch** - momentum matters more than perfect features 5. **Measure and iterate** - use analytics to understand what drives engagement *Remember: Viral mini apps are designed around social psychology first, technical features second. Focus on creating experiences that naturally encourage users to bring their friends into the fold.* # How to Build Viral Mini Apps Source: https://docs.neynar.com/docs/mini-app-virality-guide A developer's guide to designing user experiences that spread naturally through Farcaster's social graph using Neynar's infrastructure Many developers treat Farcaster mini apps like regular web apps that happen to live inside a social client. This fundamentally misses the unique opportunity of building on Farcaster. You're not just building an app; you're building inside a social network with rich user context, established social graphs, and a built-in crypto wallet. The mini apps that go viral understand this distinction. They're designed around social mechanics and financial incentives from the ground up, not bolted on as an afterthought. ## Design Principles ### #1: *Think Social-First, Not Social-Added* The traditional approach treats social features as an afterthought—build your app first, then add sharing later. Viral mini apps flip this paradigm by designing around social mechanics from day one, with every feature leveraging the user's social graph and network effects. Here are some practical examples: **Social competition:** In addition to a traditional leaderboard of all users, use Neynar's [following API](/reference/fetch-user-following) to query the accounts your users follow. ***Generic competition is boring; social competition is addictive.*** Show "3 people you follow are also playing" and their high scores, maybe allow users to challenge their friends or mutual follows, and suddenly your leaderboard becomes a much more rewarding experience. **Personalized onboarding:** When someone opens your app, immediately show them which of their friends are already using it. Encourage them to join or challenge their friends to get them started. **Friend activity feeds:** Don't just show what happened in your app - show what their network is doing through notifications, activity feeds, or popups/modals. "Your friend @charlie just completed a challenge" or "Hey @alice! @bob just beat your high score" creates FOMO and natural engagement. ### #2: *Make Sharing Inevitable* Viral mini apps can be thought of as ***effortless sharing engines*** - they don't ask users to share, they make sharing the obvious next step. **Dynamic Share Pages** Every achievement should generate a custom share URL with a user-specific embed image that serves dual purposes: celebration for the user and invitation for their network. Use the [Neynar Starter Kit](/docs/create-farcaster-miniapp-in-60s) to get this functionality out-of-the-box, or build it yourself using Neynar's APIs to pull in user avatars, usernames, and social proof. Structure your dynamically generated share image like this: * **Hero moment:** "You just beat 89% of players!" * **Social proof:** Show profile pics of friends who also played using their social graph * **Relevant entry point:** Clicking the "launch" button can send a new user directly to a page of your mini app challenging or referencing the user sharing the mini app, for example **Smart Cast Composition** Don't just share generic messages. Pre-fill the cast composer with social graph data to craft contextual casts for your users: * **First achievement:** "I just did X in this app and thought you would like it @friend1 @friend2 @friend3" * **Beat a friend:** "Just beat @friend's score in \[app]!" * **Clear invitation:** "Challenge your friends" cast pre-filled with tags of their best friends (as demonstrated in the [Neynar Starter Kit](/docs/create-farcaster-miniapp-in-60s) using the [best friends API](/reference/best-friends)) **The "Share to Claim" Pattern** Create exclusive rewards tied to social actions. This isn't about forcing sharing - it's about making sharing valuable. Use Neynar's casts API or reactions API and the wallet integration to create real financial incentives, either with established ERC20 tokens, or with reward tokens or NFTs made just for your app: * Bonus rewards for shares that get engagement or accounts that have more followers * Collaborative minting where friend groups unlock rewards together * Time-limited tokens tied to viral moments * Exclusive tokens or NFTs minted only for users who share ### #3: *Send Notifications That Bring Users Back* **Smart Re-engagement Through Social Triggers** Neynar's notification system lets you reach users in their Warpcast notification inbox. Use this strategically to keep users coming back by crafting notifications based on user actions and social graph data. **Social FOMO Triggers:** * "3 of your friends just played without you" * "You just lost your top spot to @friend" / "You're now ranked #X among your friends" * "@friend just joined and is catching up to your score" **Time-Sensitive Opportunities:** * "Daily challenge ends in 2 hours" * "Your friend challenged you to beat their score" * "Weekly leaderboard resets tomorrow" The key is triggering notifications based on social events, not arbitrary timers. People respond better to social context. Additionally, if you use the [Neynar Starter Kit](/docs/create-farcaster-miniapp-in-60s) or integrate the `MiniAppProvider` component from [@neynar/react](https://www.npmjs.com/package/@neynar/react), you can track notification open rates to understand what works and what doesn't for your specific use case. See the [notifications guide](/docs/send-notifications-to-mini-app-users) for more details. ### #4: *Use Financial Incentives That Feel Natural* **Token Rewards and Minting as Social Mechanics** The most viral Farcaster mini apps understand that users come with built-in wallets and respond to real value, not just points. Even if your app doesn't require transactions or cost money to use, you can still bake in financial incentives to drive growth. **Mint-to-Share Loops** Structure your rewards so that claiming tokens or NFTs naturally leads to sharing: * Mint exclusive badges for achievements, then land users on a share page * Time-limited tokens tied to viral moments ("First 100 to share get exclusive NFT") **Smart Financial Incentives** Use Farcaster's wallet integration to create seamless, social flows of value: * Encourage users to tip friends with an in-app currency * Staking mechanics where users lock up resources for extra functionality * Auction mechanics where social proof affects pricing **The Key:** Financial incentives should amplify social mechanics, not replace them. The best viral apps make earning tokens feel like a natural byproduct of having fun with friends. ## Technical Implementation Strategy ### Core Neynar Features for Viral Apps **Social Graph APIs:** Pull rich profile data, follower/following lists, and mutual connections to personalize every interaction. **Notifications with Analytics:** Re-engage users with social triggers and achievement celebrations, and track open rates of your notifications to know what drives the most engagement. **Mint Component:** Embed a mint button that lets users claim exclusive NFTs or tokens tied to achievements, then land on share pages to spread the word. **Share Component:** Embed a share button that composes a cast for the user pre-filled with best friend tags and a dynamically generated share image embed. ### The Neynar Starter Kit Advantage Instead of building everything from scratch, use the [Neynar Starter Kit](/docs/create-farcaster-miniapp-in-60s) to start from a mini app template that already includes all of the above features, which you can easily edit for your own purposes. Read about [common UX mistakes and launch strategies](/docs/mini-app-common-mistakes) that can make or break your mini app's virality. ## The Bottom Line Viral mini apps don't just happen—they're designed around social psychology and financial psychology. Every successfully viral mini app answers these fundamental questions: 1. **Why would someone want to share this?** (Achievement, status, challenge, financial reward) 2. **How can you make sharing effortless?** (Pre-filled casts, dynamic images, instant rewards) 3. **What social proof drives participation?** (Friends playing, mutual connections, token holders) 4. **How do you create habit loops?** (Social triggers over calendar reminders, plus rewards) 5. **What makes the financial incentives feel natural?** (Rewards that amplify social mechanics, not replace them) With Neynar's social graph infrastructure and Farcaster's built-in wallet integration, you have everything you need to answer these questions. The [Neynar Starter Kit](/docs/create-farcaster-miniapp-in-60s) handles both the technical complexity and wallet integration, so you can focus on designing experiences that naturally spread through Farcaster's social graph while creating real value for users. # Mint NFTs for Farcaster Users Source: https://docs.neynar.com/docs/mint-for-farcaster-users Mint NFTs directly to Farcaster users using their FID with Neynar's server wallets ### Related API: [Mint NFT](/reference/post-nft-mint) Want to reward your Farcaster community with NFTs? This guide shows you how to mint NFTs directly to Farcaster users using their FID (Farcaster ID) instead of wallet addresses. ### Currently Limited to Highlight This API currently only works with NFTs deployed through [Highlight](https://highlight.xyz) on EVM networks. We're working on expanding support to other NFT platforms, so if you have a specific request [let us know](https://neynar.com/slack). Server wallets need to be manually set up for each user. Contact us to get your server wallet configured. ## Simulate vs Execute: GET vs POST The API provides two modes of operation: * **GET (Simulate)**: Returns transaction calldata without executing - perfect for previewing costs and validating parameters * **POST (Execute)**: Actually executes the mint transaction using your server wallet ## Getting Transaction Calldata (Simulate) Use the GET endpoint to preview what the mint transaction will look like: ```javascript Node.js const response = await fetch('/farcaster/nft/mint?' + new URLSearchParams({ nft_contract_address: '0x8F01e875C816eC2C9d94E62E47771EbDB82d9A8B', network: 'base-sepolia', recipients: JSON.stringify([{ fid: 14206, quantity: 1 }]) })); const calldata = await response.json(); console.log(calldata[0]); ``` Example response: ```json { "recipient": { "fid": 14206, "quantity": 1 }, "abi": [...], "function_name": "mint", "args": [...], "to": "0x8F01e875C816eC2C9d94E62E47771EbDB82d9A8B", "data": "0x...", "value": "0", "network": "base-sepolia" } ``` ## Executing the Mint Transaction To actually mint the NFT, use the POST endpoint with your server wallet: ```javascript Node.js const response = await fetch('/farcaster/nft/mint', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-wallet-id': 'your-server-wallet-id' }, body: JSON.stringify({ nft_contract_address: '0x8F01e875C816eC2C9d94E62E47771EbDB82d9A8B', network: 'base-sepolia', recipients: [{ fid: 14206, quantity: 1 }], async: true }) }); const result = await response.json(); console.log(result.transactions[0].transaction_hash); ``` ## Async vs Sync Execution You can choose how to handle transaction execution: ### Sync Mode (Recommended) Set `async: false` to wait for transaction confirmation and get the receipt: ```json { "transactions": [{ "transaction_hash": "0xabc...", "recipient": { "fid": 14206, "quantity": 1 }, "receipt": { "status": "success", "gas_used": "150000", "block_number": "12345" } }] } ``` ### Async Mode Set `async: true` to get the transaction hash immediately and check status separately, will not work with large recipient lists: ```json { "transactions": [{ "transaction_hash": "0xabc...", "recipient": { "fid": 14206, "quantity": 1 } }] } ``` ## Batch Minting Mint to multiple Farcaster users in a single API call: ```javascript Node.js const recipients = [ { fid: 14206, quantity: 1 }, { fid: 14207, quantity: 2 }, { fid: 14208, quantity: 1 } ]; const response = await fetch('/farcaster/nft/mint', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-wallet-id': 'your-server-wallet-id' }, body: JSON.stringify({ nft_contract_address: '0x8F01e875C816eC2C9d94E62E47771EbDB82d9A8B', network: 'base-sepolia', recipients, async: true }) }); const result = await response.json(); // Returns 3 separate transactions console.log(`Minted to ${result.transactions.length} users`); ``` ## Server Wallets Server wallets are managed wallets you top up with funds that execute transactions on your behalf. Benefits include: * **No gas management**: We handle gas estimation and payment * **Reliable execution**: Built-in retry logic and error handling * **FID resolution**: Automatically resolves Farcaster IDs to wallet addresses ### Getting Set Up To use this API, you'll need: 1. A server wallet configured by our team 2. Your `x-wallet-id` header value 3. An NFT contract deployed on Highlight 4. Native gas tokens on the network of your choosing (top up the address we setup for you) [Contact us at](https://neynar.com/slack) to get started! ## Error Handling If you don't include the required `x-wallet-id` header, you'll get: ```json { "code": "RequiredField", "message": "x-wallet-id header is required" } ``` If you don't have a wallet id [reach out](https://neynar.com/slack) to get one setup. Each transaction in batch minting will either succeed with a `transaction_hash` or fail with an `error` field. That's it! You're now ready to reward your Farcaster community with NFTs using just their FIDs. Enjoy building! 🚀 For additional help, [feel free to contact us](https://neynar.com/slack). # Mutes, Blocks, and Bans Source: https://docs.neynar.com/docs/mutes-blocks-and-bans Hide users and their activity based on user or developer preference ## Mutes When Alice mutes Bob: * All of Bob's casts, likes, replies, and recasts are hidden from Alice in feeds, search, and reaction APIs when Alice's `viewer_fid` is specified. See the full list of endpoints below. * Notifications from Bob are hidden when fetching notifications for Alice. * Bob can still view and interact with Alice's casts. Mutes can be created or deleted via our allowlisted [Mute APIs](/reference/publish-mute). Note that Neynar mutes are separate from Farcaster mutes. Mute's are private – they are not explicitly disclosed by any of our APIs. ## Blocks When Alice blocks Bob: * Bob's casts, likes, replies, and recasts are hidden from Alice – *and vice versa* – when either user's`viewer_fid` is provided. * Both users are always hidden from each other in the [Notifications](/reference/fetch-all-notifications) based on the provided `fid`. Blocks are public and can be listed, created, and deleted via our [block list](/reference/fetch-block-list). Blocks are part of the Farcaster protocol and synced with the Farcaster app. ## Bans A user can be banned from a client at the discretion of the app owner. This will hide that user's account and all of their activity in the endpoints listed below for all API calls made from that app's API key. Bans are only viewable by the app owner, and can be listed, created, and deleted via our [Ban APIs](/reference/publish-bans). ## List of Endpoints The endpoints listed below respect mutes, blocks and bans. For these endpoints, the mutes, blocks and bans apply to the optional `viewer_fid` provided. ```typescript Typescript /v2/farcaster/following /v2/farcaster/cast/conversation /v2/farcaster/feed/channels /v2/farcaster/feed/following /v2/farcaster/feed/for_you /v2/farcaster/feed/frames /v2/farcaster/feed /v2/farcaster/feed/parent_urls /v2/farcaster/feed/trending /v2/farcaster/feed/users/replies_and_recasts /v2/farcaster/followers /v2/farcaster/followers/relevant /v2/farcaster/reactions/cast /v2/farcaster/reactions/user /v2/farcaster/cast/search /v2/farcaster/user/search /v2/farcaster/channel/followers /v2/farcaster/channel/followers/relevant ``` These endpoints apply mutes, blocks and bans to the `fid` provided. ```typescript Typescript /v2/farcaster/notifications /v2/farcaster/notifications/channel /v2/farcaster/notifications/parent_url ``` # Developer Ecosystem Source: https://docs.neynar.com/docs/neynar-developer-ecosystem-for-farcaster Building blocks for developing on Farcaster protocol with Neynar infrastructure Tools below can help you build on Farcaster more easily ## Libraries and SDKs * [@neynar/nodejs-sdk](https://github.com/neynarxyz/nodejs-sdk) by Neynar (official library) * [@neynar/react-native-signin](https://github.com/neynarxyz/siwn/tree/main/react-native-sign-in-with-neynar) by Neynar (official library) * [farcaster-js library](https://www.npmjs.com/package/@standard-crypto/farcaster-js) for Neynar by [Standard Crypto](https://warpcast.com/gavi/0x69ab635a) * [Next-js library](https://github.com/alex-grover/neynar-next) for Neynar by [Alex Grover](https://warpcast.com/alexgrover.eth/0x3b27d4e3) * [GoLang signer generation library](https://gist.github.com/benny-conn/57c073dab01488f3107d126979ee14fd) for Neynar by [Gallery](https://warpcast.com/robin/0x629551fd) * [SDK](https://github.com/cameronvoell/fcrn) to build on React Native, fetch feed with Neynar APIs by [Cameron Voell](https://warpcast.com/cyrcus/0x8cad5b56) * [farcaster-py library](https://github.com/vrypan/farcaster-py) for using Neynar Hubs by [vrypan.eth](https://warpcast.com/vrypan.eth) ## Open source clients and bots using Neynar Hubs and APIs ### Web * [Opencast](https://opencast.stephancill.co.za/) ([github](https://github.com/stephancill/opencast/tree/main)) by [Stephan Cill](https://warpcast.com/stephancill) * [Herocast](https://app.herocast.xyz/feeds) ([github](https://github.com/hellno/herocast/) by [Hellno](https://warpcast.com/hellno.eth/0xcf26ab28) * [Managed signer client](https://github.com/neynarxyz/farcaster-examples/tree/main/managed-signers) by Neynar ### Mobile * [Farcaster Expo example](https://github.com/dylsteck/farcaster-expo-example) by [Dylan Steck](https://warpcast.com/dylsteck.eth/0x9bbef5d4) * [Wownar Expo/RN example](https://github.com/neynarxyz/farcaster-examples/tree/main/wownar-react-native) by Neynar * [Farcaster Expo example](https://github.com/buidlerfoundation/farcaster-expo-example) by [Edric](https://warpcast.com/edricnguyen.eth/0x20ba4055) * [Farcaster Expo](https://github.com/mko4444/farcaster-expo) by [Matthew](https://warpcast.com/matthew/0x99a7a6b5) * [FCRN](https://github.com/cameronvoell/fcrn) by [Cameron Voell](https://warpcast.com/cyrcus/0x8cad5b56) * [React Native Farcaster Starter](https://github.com/natedevxyz/rn-farcaster-starter) by [Nate](https://warpcast.com/natedevxyz) ### Bots * [Open source repo](https://github.com/davidfurlong/farcaster-bot-template) to build bots that reply using Neynar webhooks by [David Furlong](https://warpcast.com/df) * [Follow all bot](https://github.com/wbarobinson/followall) that follows users by [Will Robinson](https://warpcast.com/dangerwillrobin) * [Frames bot](https://github.com/neynarxyz/farcaster-examples/tree/main/frames-bot) that replies with frames by Neynar ## Frames and Cast actions using Neynar frame validate ### Frames * [farcards](https://warpcast.com/~/channel/farcards) by [undefined](https://warpcast.com/undefined) ### Cast actions * [Follower count cast action](https://github.com/neynarxyz/farcaster-examples/tree/main/cast-action) by Neynar * [Bot or not](https://warpcast.com/~/channel/bot-or-not) by [Angel](https://warpcast.com/sayangel) ## Data science and analytics * [Neynar tables on Dune](https://dune.com/docs/data-tables/community/neynar/farcaster/) allowing Farcaster queries on Dune dashboards -- sample dashboards by [pixel](https://dune.com/pixelhack/farcaster), [yesyes](https://dune.com/yesyes/farcaster-users-onchain-activities) and [shoni](https://dune.com/alexpaden/farcaster-x-dune) * [Neynar SQL playground](https://neynar.notion.site/Neynar-Farcaster-SQL-playground-08ca6b55953b4d9386c59a91cb823af5?pvs=4) to query real-time Farcaster protocol data -- sample [dashboard by ghostlinkz](https://data.hubs.neynar.com/public/dashboards/U6aGGq6CQOZXIx6IO71NbaUFDMwX14nYs0OyhT88) ## Tools and debugging * [Vasco](https://vasco.wtf) Neynar and Warpcast debugging tools by [Pedro](https://warpcast.com/pedropregueiro) * [ChatGPT Neynar SQL bot](https://warpcast.com/iconia.eth/0x42ad25a9) to write analytics Neynar SQL queries for Farcaster data by [iconia](https://warpcast.com/iconia.eth/0x42ad25a9) * [ChatGPT Dune SQL bot](https://chat.openai.com/g/g-lKnQHXJKS-dune-x-farcaster-gpt) to write dune queries for Farcaster data by [iconia](https://warpcast.com/iconia.eth) * [Fario](https://github.com/vrypan/fario) Farcaster command line tools in Python on by [Vrypan](https://warpcast.com/vrypan.eth) * [CastStorage](https://caststorage.com) to check user storage by [Sam](https://warpcast.com/sammdec.eth) * [Farcaster Scraper](https://github.com/leo5imon/farcaster-scraper) to fetch, filter, and fine-tune your channel scraping by [Leo Simon](https://warpcast.com/leo5) * [ChatGPT bot to help write code](https://chat.openai.com/g/g-rSpJCtUwJ-code-caster) using Neynar APIs and Dune by [Emre](https://warpcast.com/ekinci.eth/0xf3b54700) ### APIs and hosted hubs For all the above, you can use APIs and hosted hubs from Neynar * [Neynar APIs](/reference/neynar-farcaster-api-overview) for reading and writing Farcaster data (profiles, casts, feed, etc.) * [Open source Farcaster channel data](https://github.com/neynarxyz/farcaster-channels/) including Warpcast channels -- community contributed * [Neynar hosted hubs](https://neynar.com) # Set up Neynar with Cursor and MCP server Source: https://docs.neynar.com/docs/neynar-farcaster-with-cursor Start developing on Farcaster with Neynar and AI enabled Cursor ### llms.txt * Full file [LLM Documentation - Complete Version](https://docs.neynar.com/llms-full.txt). FYI this file can be too large for LLMs on free plan, you might need to upgrade. * Abridged version [LLM Documentation - Compact Version](https://docs.neynar.com/llms.txt). ### MCP server Install by running `npx @mintlify/mcp@latest add neynar` in your terminal. You will then need to [add the MCP server to Cursor](https://docs.cursor.com/context/model-context-protocol#configuring-mcp-servers). The steps below will help you get started with Farcaster development in a few simple clicks: Create an empty folder on your computer e.g. `rish/Code/farcaster`. *This tutorial assumes you're starting from scratch. If you already have a build environment, your steps might vary.* Download Cursor from [cursor.com](https://www.cursor.com/) . This tutorial uses `Version: 0.43.5`, it's possible that future versions behave slightly differently. Open the `farcaster` folder you created in Cursor and then open the right chat pane. That's the icon next to the icon in the screenshot below. *This tutorial assumes you're using `claude-3.5-sonnet` as your AI model. With a different model, your results might differ.* Give it the following prompt. > I want to build a Farcaster app with Neynar. > > Neynar's openapi spec is here: @[https://github.com/neynarxyz/OAS/](https://github.com/neynarxyz/OAS/) > > Neynar's nodejs sdk is here: @[https://github.com/neynarxyz/nodejs-sdk](https://github.com/neynarxyz/nodejs-sdk) > > can you help set up the repo for me? we will use the latest version of neynar's sdk. No need to build a frontend for now, we will focus on backend only to start. Cursor should run a bunch of commands based on the above prompt and set up the directory for you already. The right pane will look something like the one below (it slightly differs on each run, so don't worry if it's different for you). At this point, your left pane should have the following directory structure (Cursor does something slightly different on each run, so don't worry if this isn't the same as this tutorial) To incorporate these changes into your repository, you can start by tapping "accept all" in the chat pane. You might need to troubleshoot this a bit, but accepting is a reasonable way to start. Insert your Neynar API key (subscribe at [neynar.com](https://neynar.com/#pricing) to get your key) in the `.env` file. Replace the placeholder with your API key directly, no quotes needed. You will need to run the backend server on your local machine. So go ahead and ask Cursor how to do that. > how do I run this? Cursor should give you a set of commands you can run from the chat pane directly. Tap "run" on each command and wait for it to finish running before moving to the next one. After running the `npm` commands above, if you run the curl commands, it should start printing results to your console! From here on, you can prompt Cursor as you need and continue to build on this foundation! If you have any questions, post them on the [/neynar](https://www.supercast.xyz/channel/neynar) channel on Farcaster. ## Troubleshooting After the above steps, you likely still have a few issues. Below, we describe the easiest way to debug with Cursor. * If you're getting errors in the terminal, you can simply click "Debug with AI" to have Cursor generate the prompt and output a solution. Alternatively, click "add to chat" and put in a better prompt yourself * Ensure that it has the correct files as context. `neynar-api-client.d.ts` needs to be added to context to suggest suitable solutions (or else it will just make things up!). You can search for the filename to add it easily. * For long files, it will remove them from context at each step and require you to re-add them. * When it outputs a solution, it will look like something below. You will notice that each code block has an "Apply" or "Run" action. * You will need to apply/run each block separately. Each apply/run action might create file changes that show up like below. If two actions occur on the same file, "accept" the first change and save the file before taking the next action. * Sometimes they also show up in this manner. Accept each change and save before trying again. * You will likely need to go back and forth with Cursor as you work through your code. While AI agents are helpful at getting the project started, they are still bad at navigating through repositories and picking the proper functions. # Tips Each time you run a command from the chat pane, Cursor opens a new terminal. You can close extra terminal windows that are *not* running your server without any adverse effects on your project. # Neynar User Score Source: https://docs.neynar.com/docs/neynar-user-quality-score Check for quality users using Neynar's user score ## What is the Neynar user score? Neynar user score is generated based on user behavior on the platform. It scores between 0 and 1 and reflects the confidence in the user being a high-quality user. Users can improve their scores by having high-quality interactions with other good users. Scores update weekly. If you want to see your score as a user, you can use the [Neynar Explorer](https://explorer.neynar.com) and insert your fid in the search bar. Sample url `explorer.neynar.com/`. ### Scores are also available onchain, see [Address \<> user score contract](/docs/address-user-score-contract) ## Interpreting the score The score is *not* proof of humanity. It's a measure of the account quality / value added to the network by that account. It's capable of discriminating between high and low quality AI activity. E.g. agents like bracky / clanker are bots that have high scores while accounts posting LLM slop have lower scores. You can see a distribution of users across score ranges on this [dashboard](https://data.hubs.neynar.com/public/dashboards/UPkT4B8bDMCo952MXrHWyFCh1OJqA9cfFZm0BCJo?org_slug=default). A screenshot from Dec 5, 2024 is below. Neynar user score distribution **We recommend starting with a threshold around 0.5 and then changing up or down as needed.** As of Dec 5, 2024, there are: * \~2.5k accounts with 0.9+ scores * \~27.5k accounts with 0.7+ scores *Hence, starting with a very high threshold will restrict the product to a tiny user cohort.* Developers should assess their own thresholds for their applications (Neynar does not determine thresholds in other apps). Scores update at least once a week, so new users might take a few days to show an updated score. If the user has not been active for a while, their scores will be reduced. ## Fetching the score for development ### Getting the score on webhook events If you're using Neynar webhooks to get data on your backend, you might want to separate high-quality data from low-quality data. A simple way to do this is to look at the `neynar_user_score` inside each user object. ```json user: { fid: 263530, object: "user", pfp_url: "https://imagedelivery.net/BXluQx4ige9GuW0Ia56BHw/68c1cd39-bcd2-4f5e-e520-717cda264d00/original", profile: { bio: { text: "Web3 builder" } }, username: "m00n620", power_badge: false, display_name: "tonywen.base.eth", experimental: { neynar_user_score: 0.9 // THIS IS THE SCORE }, verifications: [ "0xc34da1886584aa1807966c0e018709fafffed143" ], follower_count: 29, custody_address: "0x22c1898bddb8e829a73ca6b53e2f61e7f02a6e6d", following_count: 101, verified_accounts: null, verified_addresses: { eth_addresses: [ "0xc34da1886584aa1807966c0e018709fafffed143" ], sol_addresses: [] } } ``` ### Fetching the score on API calls If you're using APIs, you will see the same score in all user objects across all Neynar API endpoints. Try the following endpoints on our docs pages and look at the user object to see this in action: * [User by FIDs](/reference/fetch-bulk-users) to see this when fetching user data by fid * [By Eth or Sol addresses](/reference/fetch-bulk-users-by-eth-or-sol-address) If looking to restrict activity to a specific cohort of users, you can check user score on any Neynar API endpoint and then allow them to take actions as appropriate. ### Pulling the scores from hosted SQL [Neynar SQL playground](/docs/how-to-query-neynar-sql-playground-for-farcaster-data) has user scores available and you can pull from there for larger analysis as needed. [Reach out](https://neynar.com/slack) if you don't yet have access to it. Neynar user score in SQL ### Pulling score data onchain See [here](/docs/address-user-score-contract) for details. Reach out to us on [Slack](https://neynar.com/slack) if you need the data to be updated. ## Report errors If you know a score misrepresents a user, that's helpful information we can use to label our data. Please send feedback to `@rish` on [Warpcast DC](https://warpcast.com/rish) or [Slack](https://neynar.com/slack) . ## FAQs #### 1. How often do the scores update? There's two different things to note here: * (a) The algorithm runs **weekly** and calculates scores for accounts on the network based on their activity * (b) The algorithm itself is upgraded from time to time as activity on the network shifts. You can read about one such update in [Retraining Neynar User Score Algorithm](https://neynar.com/blog/retraining-neynar-user-score-algorithm) #### 2. As a user, how can I improve my score? The score is a reflection of account activity on the network. Since we have introduced this score, a few users have written helpful guides on how to contribute to the network in a positive manner: * see from @ted [here](https://warpcast.com/rish/0xbcbadc6f) * longer write up from @katekornish (now turned into a mini app by @jpfraneto) [here](https://startonfarcaster.xyz/) # Trigger Farcaster Direct Casts (DCs) from Neynar Webhooks Source: https://docs.neynar.com/docs/neynar-webhooks-warpcast-dcs In this guide, we'll send a DM based to users based on keywords in their casts using webhooks. Before we begin, you can access the [source code](https://github.com/neynarxyz/farcaster-examples/tree/main/cast-action) for this guide on [GitHub Gist](https://gist.github.com/avneesh0612/9fa31cdbb5aa86c46cdb1d50deef9001). Let's get started! ### Creating the webhook To create a new webhook, head to the [neynar dashboard](https://dev.neynar.com) and go to the [webhooks tab](https://dev.neynar.com/webhook). Click on the new webhook and enter the details as such: Webhook creation The webhook will fire to the specified `target_url`. To test it out, we can use a service like [ngrok](https://ngrok.com/) to create a public URL that will forward requests to your local server. ### Free endpoints like ngrok, localtunnel, etc. can have issues because service providers start blocking events over a certain limit ### Creating the server Let's create a simple server that logs out the event. We will be using [Bun JavaScript](https://bun.sh). ```typescript index.ts const server = Bun.serve({ port: 3000, async fetch(req) { try { console.log(await req.json()); return new Response("gm!"); } catch (e: any) { return new Response(e.message, { status: 500 }); } }, }); console.log(`Listening on localhost:${server.port}`); ``` Next: run `bun serve index.ts`, and run ngrok with `ngrok http 3000`. Copy the ngrok URL and paste it into the "Target URL" field in the Neynar developer portal. The webhook will call the target URL every time the selected event occurs. Here, I've chosen all the casts created with neynarDC present in the text. Now the server will log out the event when it is fired. It will look something like this: ```typescript index.ts { created_at: 1708025006, type: "cast.created", data: { object: "cast", hash: "0xfe7908021a4c0d36d5f7359975f4bf6eb9fbd6f2", thread_hash: "0xfe7908021a4c0d36d5f7359975f4bf6eb9fbd6f2", parent_hash: null, parent_url: "chain://eip155:1/erc721:0xfd8427165df67df6d7fd689ae67c8ebf56d9ca61", root_parent_url: "chain://eip155:1/erc721:0xfd8427165df67df6d7fd689ae67c8ebf56d9ca61", parent_author: { fid: null, }, author: { object: "user", fid: 234506, custody_address: "0x3ee6076e78c6413c8a3e1f073db01f87b63923b0", username: "balzgolf", display_name: "Balzgolf", pfp_url: "https://i.imgur.com/U7ce6gU.jpg", profile: [Object ...], follower_count: 65, following_count: 110, verifications: [ "0x8c16c47095a003b726ce8deffc39ee9cb1b9f124" ], active_status: "inactive", }, text: "neynarDC", timestamp: "2024-02-15T19:23:22.000Z", embeds: [], reactions: { likes: [], recasts: [], }, replies: { count: 0, }, mentioned_profiles: [], }, } ``` ### Adding DC functionality Firstly, you need a warpcast API key to send DCs. So, head over to [https://farcaster.xyz/\~/developers/api-keys](https://farcaster.xyz/~/developers/api-keys) and create a new API key. Once you have the API key add this fetch request in your try block: ```typescript index.ts const info = await req.json(); const DCreq = await fetch("https://api.warpcast.com/v2/ext-send-direct-cast", { method: "PUT", headers: { Authorization: "Bearer ", "Content-Type": "application/json", }, body: JSON.stringify({ recipientFid: info.data.author.fid, message: "gm", idempotencyKey: uuidv4(), }), }); const res = await DCreq.json(); console.log(res); ``` Here, you need to replace `` with the api key that you generated from the Warpcast dashboard. In the request, we need to provide the FID to send the message to, the message body, and an idempotencyKey to retry if the request fails. For the `recipientFid` we are using the FID of the author of the cast and the `idempotencyKey` is a random key generated by `uuid` which we need to install and import: ```powershell Powershell bun i uuid ``` ```typescript index.ts import { v4 as uuidv4 } from "uuid"; ``` If you restart the server and cast again, it will send a DC to the account creating the cast . ## Conclusion That's it, it's that simple! The next steps would be to have a public server that can handle the webhook events and use it to suit your needs. Lastly, please share what you built with us on Farcaster by tagging [@neynar](https://warpcast.com/neynar), and if you have any questions, reach out to us on [warpcast](https://warpcast.com/~/channel/neynar) or [Slack](https://neynar.com/slack)! # From Parquet Exports Source: https://docs.neynar.com/docs/parquet Ingest farcaster data into your database or data warehouse from parquet exports ### Contact for setup and pricing ## Frequency * Full exports are created every day * Incremental exports are created every 1s or 10s depending on the tier you chose ## Ingestion See [How to ingest](/docs/how-to-ingest) Full archives: `s3://tf-premium-parquet/public-postgres/farcaster/v2/full/` Incremental archives: `s3://tf-premium-parquet/public-postgres/farcaster/v2/incremental/` ## Notifications using SNS You can subscribe to SNS to get notifications whenever a new file is added to the s3 bucket: `arn:aws:sns:us-east-1:252622759102:tf-s3-premium-parquet` ## Schema ## Notes 1. Data is exported every 30 mins and once a day. You can start by downloading a daily snapshot and then ingesting the incremental 30m files. 2. Originally timestamps were stored with nanoseconds, but as of April 23, 2024 they are all using microseconds. 3. The “messages” table is very very large and we are not currently exporting it. Please talk with us if you need the table and we can figure out the best solution for you. # Parquet Schema Source: https://docs.neynar.com/docs/parquet-schema Schema for data available in parquet ingestion ### Data is piped in this [schema](https://docs.dune.com/data-catalog/community/farcaster/overview) Details below: ```typescript Typescript % for x in data/*.parquet; do echo $x; parquet schema $x | jq; done data/farcaster-casts-1713814200-1713814500.parquet { "type": "record", "name": "schema", "fields": [ { "name": "created_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "updated_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "deleted_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "timestamp", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "fid", "type": [ "null", "long" ], "default": null }, { "name": "hash", "type": [ "null", "bytes" ], "default": null }, { "name": "parent_hash", "type": [ "null", "bytes" ], "default": null }, { "name": "parent_fid", "type": [ "null", "long" ], "default": null }, { "name": "parent_url", "type": [ "null", "string" ], "default": null }, { "name": "text", "type": [ "null", "string" ], "default": null }, { "name": "embeds", "type": [ "null", "string" ], "default": null }, { "name": "mentions", "type": [ "null", "string" ], "default": null }, { "name": "mentions_positions", "type": [ "null", "string" ], "default": null }, { "name": "root_parent_hash", "type": [ "null", "bytes" ], "default": null }, { "name": "root_parent_url", "type": [ "null", "string" ], "default": null }, { "name": "id", "type": [ "null", "long" ], "default": null } ] } data/farcaster-fids-1713814200-1713814500.parquet { "type": "record", "name": "schema", "fields": [ { "name": "created_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "updated_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "custody_address", "type": [ "null", "bytes" ], "default": null }, { "name": "fid", "type": [ "null", "long" ], "default": null } ] } data/farcaster-fnames-1713814200-1713814500.parquet { "type": "record", "name": "schema", "fields": [ { "name": "created_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "updated_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "custody_address", "type": [ "null", "bytes" ], "default": null }, { "name": "expires_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "fid", "type": [ "null", "long" ], "default": null }, { "name": "deleted_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "fname", "type": [ "null", "string" ], "default": null } ] } data/farcaster-links-1713814200-1713814500.parquet { "type": "record", "name": "schema", "fields": [ { "name": "fid", "type": [ "null", "long" ], "default": null }, { "name": "target_fid", "type": [ "null", "long" ], "default": null }, { "name": "hash", "type": [ "null", "bytes" ], "default": null }, { "name": "timestamp", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "created_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "updated_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "deleted_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "type", "type": [ "null", "string" ], "default": null }, { "name": "display_timestamp", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "id", "type": [ "null", "long" ], "default": null } ] } data/farcaster-reactions-1713814200-1713814500.parquet { "type": "record", "name": "schema", "fields": [ { "name": "created_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "updated_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "deleted_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "timestamp", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "reaction_type", "type": [ "null", "long" ], "default": null }, { "name": "fid", "type": [ "null", "long" ], "default": null }, { "name": "hash", "type": [ "null", "bytes" ], "default": null }, { "name": "target_hash", "type": [ "null", "bytes" ], "default": null }, { "name": "target_fid", "type": [ "null", "long" ], "default": null }, { "name": "target_url", "type": [ "null", "string" ], "default": null }, { "name": "id", "type": [ "null", "long" ], "default": null } ] } data/farcaster-signers-1713814200-1713814500.parquet { "type": "record", "name": "schema", "fields": [ { "name": "created_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "updated_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "deleted_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "timestamp", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "fid", "type": [ "null", "long" ], "default": null }, { "name": "signer", "type": [ "null", "bytes" ], "default": null }, { "name": "name", "type": [ "null", "string" ], "default": null }, { "name": "app_fid", "type": [ "null", "long" ], "default": null }, { "name": "id", "type": [ "null", "long" ], "default": null } ] } data/farcaster-storage-1713814200-1713814500.parquet { "type": "record", "name": "schema", "fields": [ { "name": "created_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "updated_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "deleted_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "timestamp", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "fid", "type": [ "null", "long" ], "default": null }, { "name": "units", "type": [ "null", "long" ], "default": null }, { "name": "expiry", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "id", "type": [ "null", "long" ], "default": null } ] } data/farcaster-user_data-1713814200-1713814500.parquet { "type": "record", "name": "schema", "fields": [ { "name": "created_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "updated_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "deleted_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "timestamp", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "fid", "type": [ "null", "long" ], "default": null }, { "name": "hash", "type": [ "null", "bytes" ], "default": null }, { "name": "type", "type": [ "null", "long" ], "default": null }, { "name": "value", "type": [ "null", "string" ], "default": null }, { "name": "id", "type": [ "null", "long" ], "default": null } ] } data/farcaster-verifications-1713814200-1713814500.parquet { "type": "record", "name": "schema", "fields": [ { "name": "created_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "updated_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "deleted_at", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "timestamp", "type": [ "null", { "type": "long", "logicalType": "timestamp-micros" } ], "default": null }, { "name": "fid", "type": [ "null", "long" ], "default": null }, { "name": "hash", "type": [ "null", "bytes" ], "default": null }, { "name": "claim", "type": [ "null", "string" ], "default": null }, { "name": "id", "type": [ "null", "long" ], "default": null } ] } data/farcaster-profile_with_addresses-1713975600-1713975900.parquet { "type" : "record", "name" : "schema", "fields" : [ { "name" : "fname", "type" : [ "null", "string" ], "default" : null }, { "name" : "display_name", "type" : [ "null", "string" ], "default" : null }, { "name" : "avatar_url", "type" : [ "null", "string" ], "default" : null }, { "name" : "bio", "type" : [ "null", "string" ], "default" : null }, { "name" : "verified_addresses", "type" : [ "null", "string" ], "default" : null }, { "name" : "updated_at", "type" : [ "null", { "type" : "long", "logicalType" : "timestamp-micros" } ], "default" : null }, { "name" : "fid", "type" : [ "null", "long" ], "default" : null } ] } ``` # Write Casts to Channel Source: https://docs.neynar.com/docs/posting-dank-memes-to-farcasters-memes-channel-with-neynars-sdk Step-by-step guide to posting casts and content to specific Farcaster channels using Neynar SDK. Learn channel mechanics, parent URL mapping, and how to write engaging content to community channels like memes, with complete code examples and best practices. Channels are "subreddits inside Farcaster." Technically, a channel is a collection of casts that share a common parent\_url. For example, the [memes channel](https://warpcast.com/~/channel/memes) is a collection of casts that share the parent\_url `chain://eip155:1/erc721:0xfd8427165df67df6d7fd689ae67c8ebf56d9ca61`. ## How to Post to Farcaster Channels Got a dank meme you want to share with Farcaster? This guide demonstrates how to use the Neynar SDK to post a cast to a channel. Check out this [Getting started guide](/docs/getting-started-with-neynar) to learn how to set up your environment and get an API key. Before all that, initialize Neynar client: ```javascript Javascript // npm i @neynar/nodejs-sdk import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk"; import { FeedType } from "@neynar/nodejs-sdk/build/api"; // make sure to set your NEYNAR_API_KEY .env // don't have an API key yet? get one at neynar.com const config = new Configuration({ apiKey:process.env.NEYNAR_API_KEY", }); const client = new NeynarAPIClient(config); const signer = process.env.NEYNAR_SIGNER; ``` Poast meme to memes channel ### channel\_name to parent\_url mapping All parent\_url to channel\_name mappings can be found at this [Github repo](https://github.com/neynarxyz/farcaster-channels/blob/main/warpcast.json), along with other channel metadata. This repo is open source so feel free to submit PRs for additional channel data if you see anything missing. ```javascript Javascript const memesChannelUrl = "chain://eip155:1/erc721:0xfd8427165df67df6d7fd689ae67c8ebf56d9ca61"; const memeUrl = "https://i.imgur.com/cniMfvm.jpeg"; const signerUuid =signer; const text="me irl"; const embeds = [ { url: memeUrl, }, ]; const replyTo = memesChannelUrl; const result = await client.publishCast({signerUuid, text, embeds, parent: replyTo, }); ``` Example output: ```json { hash: "0xccabe27a04b1a63a7a24a035b0ffc2a2629e1af1", author: { object: "user", fid: 4640, custody_address: "0x86dd7e4af49829b895d24ea2ab581c7c32e87332", username: "picture", display_name: "Picture", pfp_url: "https://lh3.googleusercontent.com/erYudyT5dg9E_esk8I1kqB4bUJjWAmlNu4VRnv9iUuq_by7QjoDtZzj_mjPqel4NYQnvqYr1R54m9Oxp9moHQkierpY8KcYLxyIJ", profile: { bio: [Object ...] }, follower_count: 45, following_count: 124, verifications: [], active_status: "inactive" }, text: "me irl" } ``` There you go, make sure to share your good memes with the Farcaster! Meme posted to memes channel ### Ready to start building? Get your subscription at [neynar.com](https://neynar.com) and reach out to us on [Slack](https://neynar.com/slack) with any questions! # Rank High Quality Conversations Source: https://docs.neynar.com/docs/ranking-for-high-quality-conversations Neynar APIs rank high quality casts higher Neynar abstracts away ranking challenges across most of its APIs e.g. [Trending Feed](/reference/fetch-trending-feed) and [Conversation for a cast](/reference/lookup-cast-conversation). ## Feed There are a few different ways to rank casts for users. * [For you](/reference/fetch-feed-for-you) feed provides a ranked, personalized, *for you* feed for a given user * [Trending casts](/reference/fetch-trending-feed) feed provides trending casts globally or filtered to a channel. In this case, you can choose a provider of your choice - `neynar`, `openrank`, `mbd`. Neynar serves ranking from other providers without any changes. ## Conversation [Conversation for a cast](/reference/lookup-cast-conversation) API allows developers to fetch a conversation thread for a given cast. In addition, it's possible to: * rank casts in the conversation by order of quality by changing the `sort_type` parameter * hide low quality conversations below the fold by setting the `fold` param to *above* or *below*. Not passing in a param shows the full conversation without any fold. Rank high quality conversations All put together, this makes it possible to serve high quality information to users. If you've ideas on how we can improve ranking further, please message rish on [Warpcast DC](https://warpcast.com/rish) or [Slack](https://neynar.com/slack) . # SIWN: React Source: https://docs.neynar.com/docs/react-implementation In this guide, we'll take a look at how to implement sign-in with neynar in a React app. For this guide, I am going to be using next.js but the same would work for CRA, remix, or anything based on react! For this guide, we'll go over: 1. Setting up auth using the Neynar React SDK 2. Using the signer to create casts Before we begin, you can access the [complete source code](https://github.com/neynarxyz/farcaster-examples/tree/main/wownar-react-sdk) for this guide on GitHub. Let's get started! ## Creating the app ### Setting up the project Create a new next.js app using the following command: ```powershell Powershell npx create-next-app app-name ``` You can choose the configuration based on your personal preference, I am using this config for the guide: Once the app is created, install the packages that we are going to need for the command: ```shell npm npm i @neynar/react @neynar/nodejs-sdk axios ``` ```shell yarn yarn add @neynar/react @neynar/nodejs-sdk axios ``` ```shell bun bun add @neynar/react @neynar/nodejs-sdk axios ``` Install peer dependencies for `@neynar/react` ```shell npm npm i @pigment-css/react@^0.0.30 hls.js@^1.5.20 react@^19.0.0 react-dom@^19.0.0 swr@^2.3.2 ``` ```shell yarn yarn add @pigment-css/react@^0.0.30 hls.js@^1.5.20 react@^19.0.0 react-dom@^19.0.0 swr@^2.3.2 ``` ```shell bun bun add @pigment-css/react@^0.0.30 hls.js@^1.5.20 react@^19.0.0 react-dom@^19.0.0 swr@^2.3.2 ``` Once the dependencies are installed you can open it in your favourite and we can start working on adding sign-in with neynar! ### Adding the sign-in button Head over to the `layout.tsx` file and wrap your app in a `NeynarContextProvider` like this: ```typescript layout.tsx "use client"; import { NeynarContextProvider, Theme } from "@neynar/react"; import "@neynar/react/dist/style.css"; import { Inter } from "next/font/google"; import "./globals.css"; const inter = Inter({ subsets: ["latin"] }); export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( {}, onSignout() {}, }, }} > {children} ); } ``` We are passing some settings here like `clientId`, `defaultTheme` and `eventsCallbacks`. * `clientId`: This is going to be the client ID you get from your neynar, add it to your `.env.local` file as `NEXT_PUBLIC_NEYNAR_CLIENT_ID`. ### Make sure to add localhost to the authorized origins * `defaultTheme`: default theme lets you change the theme of your sign-in button, currently, we have only light mode but dark mode is going to be live soon. * `eventsCallbacks`: This allows you to perform certain actions when the user signs out or auth is successful. I've also added a styles import from the neynar react package here which is needed for the styles of the sign-in button. Finally, let's add the sign-in button in the `page.tsx` file like this: ```typescript page.tsx "use client"; import { NeynarAuthButton } from "@neynar/react"; export default function Home() { return (
); } ```
If you head over to your app you'll be able to see a sign-in button on the screen. Go ahead and try signing in! Now that our sign-in button is working let's use the signer we get to publish casts! ### Using the signer UUID to publish casts In the `page.tsx` file access the user data using the `useNeynarContext` hook like this: ```typescript page.tsx const { user } = useNeynarContext(); ``` The user object contains various information like the username, fid, display name, pfp url, signer uuid, etc. Then, we can use this user object to check whether the user has signed in and display a screen conditionally like this: ```typescript page.tsx "use client"; import { NeynarAuthButton, useNeynarContext } from "@neynar/react"; import Image from "next/image"; export default function Home() { const { user } = useNeynarContext(); return (
{user && ( <>
{user.pfp_url && ( User Profile Picture )}

{user?.display_name}

)}
); } ```
Now, let's add a text area and a cast button here like this: ```typescript page.tsx {user && ( <>
{user.pfp_url && ( User Profile Picture )}

{user?.display_name}