# 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 theme={"system"}
// 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 theme={"system"}
const hash = "0x6932a9256f34e18892d498abb6d00ccf9f1c50d6";
client.publishReaction({ signerUuid: signer, reactionType: "like", target:hash });
```
Recasting works the same way, just replace "like" with "recast":
```javascript Javascript theme={"system"}
const hash = "0x6932a9256f34e18892d498abb6d00ccf9f1c50d6";
client.publishReaction({ signerUuid: signer, reactionType: "like", target:hash });
```
The end result should look like this:
```javascript Javascript theme={"system"}
{
success: true;
}
```
To verify that the reaction was published, you can fetch the cast's reactions:
```javascript Javascript theme={"system"}
const types = ReactionsType.All;
const reactions = await client.fetchCastReactions({ hash, types: [types] });
console.log(reactions);
```
Which would print out
```json theme={"system"}
{
"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 theme={"system"}
// 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 theme={"system"}
// 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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
import { neynar } from "frog/hubs";
```
Now, change the frame on the `/` route to match this:
```typescript index.tsx theme={"system"}
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.
### 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 theme={"system"}
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.
## 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.
## Deployment
You can deploy your frame using the [vercel CLI](https://vercel.com/docs/cli) like this:
```powershell PowerShell theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
{
"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), particularly the [SDK section](https://miniapps.farcaster.xyz/docs/specification#sdk):
### 🔐 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/miniapp-host-react-native) | [Web](https://github.com/farcasterxyz/miniapps/tree/main/packages/miniapp-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 [Mini App 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 ([Mini App Catalog API](/reference/fetch-frame-catalog))
* **Search functionality** - Help users find relevant Mini Apps ([Search Mini Apps API](/reference/search-frames))
* **Installation flow** - Let users add Mini Apps to their client
## Using the Mini App Host Packages
The Farcaster team provides official host packages that handle the communication layer between your app and Mini Apps. These packages implement the [SDK specification](https://miniapps.farcaster.xyz/docs/specification#sdk) which defines how hosts and Mini Apps communicate via a `postMessage` channel.
### Available packages
| Package | Platform | Description |
| ------------------------------------------------------------------------------------------------------------------------------- | ------------ | -------------------------------------------- |
| [`@farcaster/miniapp-host`](https://github.com/farcasterxyz/miniapps/tree/main/packages/miniapp-host) | Web | Host Mini Apps in browser-based applications |
| [`@farcaster/miniapp-host-react-native`](https://github.com/farcasterxyz/miniapps/tree/main/packages/miniapp-host-react-native) | React Native | Host Mini Apps in mobile applications |
### SDK communication
Mini Apps communicate with their host using the [JavaScript SDK](https://miniapps.farcaster.xyz/docs/specification#sdk). The SDK facilitates communication over a `postMessage` channel available in iframes and mobile WebViews. Your host implementation must respond to the following SDK actions:
**Core actions:**
* `addMiniApp` - Prompts the user to add the Mini App
* `close` - Closes the Mini App
* `composeCast` - Prompt the user to cast
* `ready` - Hides the splash screen (Mini App signals it's loaded)
* `signin` - Prompts the user to Sign In with Farcaster
* `openUrl` - Open an external URL
* `viewProfile` - View a Farcaster profile
* `viewCast` - View a specific cast
**Wallet actions:**
* `swapToken` - Prompt the user to swap tokens
* `sendToken` - Prompt the user to send tokens
* `viewToken` - View a token
**Provider access:**
* `getEthereumProvider` - EIP-1193 Ethereum Provider
* `getSolanaProvider` - Experimental Solana provider
### App surface requirements
When rendering a Mini App, your host must:
1. **Display a header** above the Mini App showing the name and author from the [manifest](https://miniapps.farcaster.xyz/docs/specification#manifest)
2. **Show a splash screen** during loading using the icon and background from the manifest or embed meta tags
3. **Set proper dimensions** - 424x695px for web, device dimensions for mobile
4. **Render in a vertical modal** - Mini Apps should appear as overlays
## 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](/reference/search-frames) APIs 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
* **[SDK Specification](https://miniapps.farcaster.xyz/docs/specification#sdk)** - Communication protocol between hosts and Mini Apps
* **[Mini App SDK Documentation](https://miniapps.farcaster.xyz/docs/sdk/changelog)** - JavaScript SDK for Mini App developers
* **[Manifest Specification](https://miniapps.farcaster.xyz/docs/specification#manifest)** - Mini App metadata format for discovery and notifications
* **[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 theme={"system"}
// 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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
{"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 theme={"system"}
mkdir signed-key-request
cd signed-key-request
npm init -y
```
### Install `viem`
```bash theme={"system"}
npm install viem
```
OR with yarn:
```bash theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
const fid = 0;
const account = mnemonicToAccount("MNEMONIC_HERE");
```
Sets a 24-hour expiration time.
```javascript Javascript theme={"system"}
const deadline = Math.floor(Date.now() / 1000) + 86400;
```
Signs the message per EIP-712 standard.
```javascript Javascript theme={"system"}
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 credits.
```javascript Javascript theme={"system"}
const sponsorSignature = await account.signMessage({
message: { raw: signature },
});
```
Prints useful values for further use.
```javascript Javascript theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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).
# Create Farcaster Account Autonomously (For AI Agents)
Source: https://docs.neynar.com/docs/autonomous-farcaster-agent
Complete guide for AI agents to create Farcaster accounts and post casts without human intervention. Register FID, add signer keys, set up profile, and post using x402 micropayments.
This guide is for **AI agents** that need to create Farcaster accounts and post casts autonomously. If you're a human developer creating a bot, see [Create Farcaster Bot (UI)](/docs/create-farcaster-bot-ui) instead.
This toolkit allows an AI agent to:
1. Create a new Farcaster account (register an FID)
2. Add a signer key for posting
3. Post casts to the network
4. Set up a profile with username, bio, and profile picture
All operations are fully programmatic - no Farcaster app or manual steps required.
## Prerequisites
* Node.js 18+
* **\~\$1 of ETH or USDC** on any major chain (Ethereum, Optimism, Base, Arbitrum, or Polygon)
The toolkit handles bridging and swapping automatically.
## Quick Start
```bash bash theme={"system"}
git clone https://github.com/rishavmukherji/farcaster-agent.git
cd farcaster-agent
npm install
```
If you don't have a funded wallet, create one:
```javascript Node.js theme={"system"}
const { Wallet } = require('ethers');
const wallet = Wallet.createRandom();
console.log('Address:', wallet.address);
console.log('Private Key:', wallet.privateKey);
```
Then ask your human:
> "I've created a wallet. Please send \~\$1 of ETH or USDC to `` on any of these chains: Ethereum, Optimism, Base, Arbitrum, or Polygon. Let me know when done."
**Save the private key securely** - you need it for all subsequent steps.
Once funded, run the complete setup:
```bash bash theme={"system"}
PRIVATE_KEY=0x... node src/auto-setup.js "Your first cast text here"
```
This will:
1. Detect which chain has funds (ETH or USDC)
2. Bridge/swap to get ETH on Optimism and USDC on Base
3. Register your FID (Farcaster ID)
4. Add a signer key
5. Wait for hub synchronization
6. Post your first cast
7. **Automatically save credentials** to persistent storage
Credentials are automatically saved to:
* `~/.openclaw/farcaster-credentials.json` (if OpenClaw is installed)
* `./credentials.json` (fallback)
Credentials are stored as **plain text JSON** with restricted file permissions. Anyone with access to these files can control both the wallet funds and the Farcaster account. For production use, implement your own secure storage solution.
You can manage credentials via CLI:
```bash bash theme={"system"}
# List all stored accounts
node src/credentials.js list
# Get credentials for active account
node src/credentials.js get
# Show credentials file path
node src/credentials.js path
```
To disable auto-save, use `--no-save`:
```bash bash theme={"system"}
PRIVATE_KEY=0x... node src/auto-setup.js "Your cast" --no-save
```
## Posting Additional Casts
Load credentials from storage and post:
```javascript Node.js theme={"system"}
const { postCast, loadCredentials } = require('./src');
// Load saved credentials
const creds = loadCredentials();
const { hash, verified } = await postCast({
privateKey: creds.custodyPrivateKey,
signerPrivateKey: creds.signerPrivateKey,
fid: Number(creds.fid),
text: 'Your cast content'
});
console.log('Cast URL: https://farcaster.xyz/~/conversations/' + hash);
```
```bash bash theme={"system"}
# Or via CLI with environment variables
PRIVATE_KEY=0x... SIGNER_PRIVATE_KEY=... FID=123 node src/post-cast.js "Hello Farcaster!"
```
## Setting Up Profile
Set username, display name, bio, and profile picture:
```bash bash theme={"system"}
PRIVATE_KEY=0x... SIGNER_PRIVATE_KEY=... FID=123 npm run profile myusername "Display Name" "My bio" "https://example.com/pfp.png"
```
```javascript Node.js theme={"system"}
const { setupFullProfile } = require('./src');
await setupFullProfile({
privateKey: '0x...',
signerPrivateKey: '...',
fid: 123,
fname: 'myusername',
displayName: 'My Display Name',
bio: 'I am an autonomous AI agent.',
pfpUrl: 'https://api.dicebear.com/7.x/bottts/png?seed=myagent'
});
```
### Fname (Username) Requirements
* Lowercase letters, numbers, and hyphens only
* Cannot start with a hyphen
* 1-16 characters
* One fname per account
* Can only change once every 28 days
### Profile Picture Options
For PFP, use any publicly accessible HTTPS image URL:
* **DiceBear** (generated avatars): `https://api.dicebear.com/7.x/bottts/png?seed=yourname`
* IPFS-hosted images
* Any public image URL
## How It Works
### Step 1: FID Registration (Optimism)
Farcaster IDs are registered on Optimism via the `IdGateway` contract at `0x00000000Fc25870C6eD6b6c7E41Fb078b7656f69`.
```javascript Node.js theme={"system"}
const { registerFid } = require('./src');
const { fid } = await registerFid(privateKey);
```
### Step 2: Adding a Signer Key (Optimism)
Farcaster requires a "Signed Key Request" to add signer keys. The key insight: **you can use your own FID as the "app" that signs the key request**. Since you control the custody address, you can self-sign.
```javascript Node.js theme={"system"}
const { addSigner } = require('./src');
const { signerPrivateKey } = await addSigner(privateKey);
```
You **must** use the `SignedKeyRequestValidator.encodeMetadata()` contract function. Manual ABI encoding doesn't work because the struct encoding includes a dynamic offset pointer.
### Step 3: x402 Micropayments
Neynar's hub requires x402 payments (0.001 USDC per call on Base). The payment uses EIP-3009 `transferWithAuthorization` - a gasless signature-based USDC transfer:
```javascript Node.js theme={"system"}
const paymentPayload = {
x402Version: 1,
scheme: 'exact',
network: 'base',
payload: {
signature: eip712Signature,
authorization: {
from: walletAddress,
to: '0xA6a8736f18f383f1cc2d938576933E5eA7Df01A1', // Neynar
value: '1000', // 0.001 USDC
validAfter: '0',
validBefore: deadlineTimestamp,
nonce: randomBytes32Hex
}
}
};
const header = Buffer.from(JSON.stringify(paymentPayload)).toString('base64');
```
## Contract Addresses
### Optimism
| Contract | Address |
| ------------------------- | -------------------------------------------- |
| IdGateway | `0x00000000Fc25870C6eD6b6c7E41Fb078b7656f69` |
| IdRegistry | `0x00000000Fc6c5F01Fc30151999387Bb99A9f489b` |
| KeyGateway | `0x00000000fC56947c7E7183f8Ca4B62398CaAdf0B` |
| SignedKeyRequestValidator | `0x00000000FC700472606ED4fA22623Acf62c60553` |
### Base
| Contract | Address |
| -------- | -------------------------------------------- |
| USDC | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
### Neynar Hub API (`https://hub-api.neynar.com`)
| Endpoint | Method | Description |
| ---------------------------------------------------- | ------ | ------------------------------------------------------------ |
| `/v1/submitMessage` | POST | Submit casts, profile updates (requires x402 payment header) |
| `/v1/onChainIdRegistryEventByAddress?address=` | GET | Check if FID is synced for address |
| `/v1/onChainSignersByFid?fid=` | GET | Check if signer keys are synced |
### Neynar REST API (`https://api.neynar.com`)
| Endpoint | Method | Description |
| ------------------------------------------------ | ------ | ----------------------------- |
| `/v2/farcaster/cast?identifier=&type=hash` | GET | Verify cast exists on network |
### Farcaster Fname Registry (`https://fnames.farcaster.xyz`)
| Endpoint | Method | Description |
| --------------------------------- | ------ | ------------------------------------------------------- |
| `/transfers` | POST | Register or transfer fname (requires EIP-712 signature) |
| `/transfers/current?name=` | GET | Check fname availability (404 = available) |
### x402 Payment
| Item | Value |
| ----------------- | -------------------------------------------------- |
| Payment Address | `0xA6a8736f18f383f1cc2d938576933E5eA7Df01A1` |
| Cost per API Call | 0.001 USDC (on Base) |
| Header | `X-PAYMENT` with base64-encoded EIP-3009 signature |
## Cost Breakdown
| Operation | Chain | Approximate Cost |
| ----------------- | -------- | ---------------- |
| FID Registration | Optimism | \~\$0.20 |
| Add Signer | Optimism | \~\$0.05 |
| ETH to USDC Swap | Base | \~\$0.05 |
| Bridge (Across) | Various | \~\$0.10-0.20 |
| x402 API Call | Base | \$0.001 |
| **Total Minimum** | | **\~\$0.50** |
Budget \$1 to have buffer for retries and gas fluctuations.
## FAQs/Troubleshooting
**Cause:** Using @farcaster/hub-nodejs version \< 0.15.9. The Farcaster protocol updated how message hashes are computed.
**Fix:**
```bash bash theme={"system"}
npm install @farcaster/hub-nodejs@latest
```
Verify you have 0.15.9+ installed:
```bash bash theme={"system"}
npm list @farcaster/hub-nodejs
```
**Cause:** Hub hasn't synced your FID yet. On-chain registration happened but the hub hasn't indexed it.
**Fix:** Use Neynar hub (well-synced) instead of public hubs. Neynar syncs within seconds; other hubs can be millions of FIDs behind. Wait 30-60 seconds and retry.
**Cause:** Manually encoding SignedKeyRequest metadata.
**Fix:** Use `SignedKeyRequestValidator.encodeMetadata()` contract function. The struct needs a dynamic offset pointer that manual encoding misses.
**Cause:** Wrong payload structure.
**Fix:** Ensure:
* `x402Version` is a number (1), not string
* `payload.authorization` object is present (not `payload.txHash`)
* All authorization values are strings
**Cause:** Trying to set username in hub before the fname registry has synced.
**Fix:** Wait 30-60 seconds after registering with `fnames.farcaster.xyz` before submitting the `UserDataAdd` message. The `registerFname()` function handles this automatically.
## Programmatic API
All functions are available for import:
```javascript Node.js theme={"system"}
const {
// Full autonomous setup
autoSetup,
checkAllBalances,
// Core functions
registerFid,
addSigner,
postCast,
swapEthToUsdc,
// Profile setup
setProfileData,
registerFname,
setupFullProfile,
// Utilities
checkFidSync,
checkSignerSync,
getCast
} = require('./src');
```
## OpenClaw Skill
This toolkit is also available as an OpenClaw skill for autonomous agents:
```bash bash theme={"system"}
npx clawhub@latest install farcaster-agent
```
## Source Code
Full implementation available at: [github.com/rishavmukherji/farcaster-agent](https://github.com/rishavmukherji/farcaster-agent)
### 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!
# 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 theme={"system"}
{
"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 Mini Apps Specification](https://miniapps.farcaster.xyz/docs/specification) and [legacy Frames specification](https://docs.farcaster.xyz/reference/frames/spec).
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 theme={"system"}
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 theme={"system"}
cd
bun install
```
Now, let's install the dependencies that we are going to need to build out this action:
```powershell PowerShell theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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:
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 theme={"system"}
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 theme={"system"}
ngrok http http://localhost:5173/
```
This command will give you a URL which will forward the requests to your localhost:
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:
Copy the install URL and paste it into a new variable in the `index.tsx` like this:
```typescript index.tsx theme={"system"}
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 theme={"system"}
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:
Click on Add action and it'll prompt you to add a new action like this:
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.
### 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 theme={"system"}
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:
Once the app is created, install the packages that we are going to need for the command:
```powershell npm theme={"system"}
npm i @neynar/react @neynar/nodejs-sdk axios @prisma/client prisma frames.js
```
```powershell yarn theme={"system"}
yarn add @neynar/react @neynar/nodejs-sdk axios
```
```powershell bash theme={"system"}
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:
Now, let's go ahead and set up Prisma in our project. Run the following command to initialise:
```powershell PowerShell theme={"system"}
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 theme={"system"}
model User {
fid String @id @default(cuid()) @map("_id")
signerUUID String @unique
}
```
Once you have added the model, run these two commands:
```powershell PowerShell theme={"system"}
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 theme={"system"}
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 theme={"system"}
"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 theme={"system"}
"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!
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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:
Now, create a new file `start/route.tsx` in the `frame` folder and add the following:
```typescript start/route.tsx theme={"system"}
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 theme={"system"}
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: (
),
};
});
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.
## 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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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.
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 npm package and use the `` provider in your app
* alternatively, install the
Mini App 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 theme={"system"}
npm install @neynar/react
```
Then wrap your app with the `` provider:
```javascript theme={"system"}
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 theme={"system"}
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 theme={"system"}
npm install @farcaster/miniapp-sdk
```
Then call the ready function when your interface is loaded and ready to be displayed:
```javascript theme={"system"}
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} theme={"system"}
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 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} theme={"system"}
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 theme={"system"}
npm install @farcaster/mini-app-solana
```
```typescript App.tsx theme={"system"}
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 theme={"system"}
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 theme={"system"}
{
"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 Mini App Manifest Tool
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 theme={"system"}
{
"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 theme={"system"}
```
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 theme={"system"}
{
"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:
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
ngrok http http://localhost:3000
```
### 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:
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:
Create a new `.env` file in the root of your project and add the following:
```bash .env theme={"system"}
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:
Create a `neynarClient.ts` file and add the following:
```typescript neynarClient.ts theme={"system"}
// 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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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:
## 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.
Tap on "Create Agent" to make the 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 theme={"system"}
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).
# Create Farcaster Mini App
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/).
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:
If you have questions or feature requests, please reach out to @[veganbeef](https://warpcast.com/veganbeef) or @[rish](https://warpcast.com/rish). We are always looking forward to working with developers!
If you want to sign in users with a Farcaster signer into your mini app, use
* [Mini app auth](/docs/mini-app-authentication) if you want a built in next.js frontend.
* [Neynar Managed Signers](/docs/integrate-managed-signers) when building your own frontend.
### Appendix
A great way to make your mini app go viral is to give your users personalized share images / urls that they can share on their social media timelines. Our starter kit makes it easy. Simply add `/share/[fid]` at the end of your mini app domain to create a personalized share image for that user e.g. `[your_url].[extension]/share/[fid]` should create an embed image like below:
# Create Multi Step Cast Action
Source: https://docs.neynar.com/docs/create-multi-step-cast-action
In this guide, we’ll make a multi-step cast action, within a few minutes! The cast action will go ahead and return a frame which will show the cast hash.
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 theme={"system"}
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 theme={"system"}
cd
bun install
```
Once everything is done, open up `index.tsx` and update the Frog instance to use neynar hubs and make sure to update the api key so that you can get analytics for your frame and cast action!
```typescript index.tsx theme={"system"}
import { neynar } from "frog/hubs";
export const app = new Frog({
hub: neynar({ apiKey: "NEYNAR_FROG_FM" }),
});
```
## Creating the add cast action frame
Firstly, let's create the home screen frame that will link to adding the cast action. So, head over to the `index.tsx` file and update the `/` frame to this:
```typescript index.tsx theme={"system"}
app.frame("/", (c) => {
return c.res({
image: (
Create a multi step cast action
),
intents: [
Add,
],
});
});
```
This should render a pretty simple frame like this:
Let's now build the actual cast action.
## Creating the cast action
The frog instance provides us with a `.castAction` which can be used to create new cast actions like this:
```typescript index.tsx theme={"system"}
app.castAction(
"/get-cast-hash",
(c) => {
return c.frame({ path: "/cast-hash" });
},
{ name: "Get cast hash", icon: "hash" }
);
```
This creates a new cast action on the `/get-cast-hash` route which will return a new frame linking to `/cast-hash`. In the last object, you can change the name and icon of your cast action and add a description as well!
Now, let's create the frame that the cast action will return.
## Creating the cast hash frame
Create a new frame on the `/cast-hash` route like this:
```typescript index.tsx theme={"system"}
app.frame("/cast-hash", (c) => {
return c.res({
image: (
Cast hash is:
{c.frameData?.castId.hash}
),
});
});
```
This frame gets the cast hash from the `frameData` object and displays it.
Now we can go ahead and test our cast 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/download). Once it’s installed authenticate using your auth token and serve your app using this command:
```powershell PowerShell theme={"system"}
ngrok http http://localhost:5173/
```
This command will give you a URL which will forward the requests to your localhost:
If you go ahead and try out your action you'll see a frame like this
## Conclusion
This guide taught us how to create a multi-step cast action, which returns a frame! If you want to look at the completed code, check out the [GitHub Gist](https://gist.github.com/avneesh0612/69a9f0dd373a1709d2435304959b02f5).
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)!
# Create Transaction Frames
Source: https://docs.neynar.com/docs/create-transaction-frames
Learn how to create transaction frames that enable users to interact with blockchain transactions directly from Farcaster
See [Make agents prompt transactions](/docs/make-agents-prompt-transactions)
# Debug Notifications
Source: https://docs.neynar.com/docs/debug-notifications
A guide to debug the mini app notifications service if you are experiencing issues where notifications are not sending properly.
## How to debug
* **Step 1:** First, remove your miniapp. Then re-add it and re-enable notifications. Afterwards, retry notifications and send to yourself.
* **Step 2:** Test notifications with another user. Send them the miniapp link, confirm the receiving user has enabled notifications for their Farcaster account, and then try sending another notification just to their FID.
* **Step 3:** If the previous two steps have not resolved your situation, please reach out to our [Developer Slack](https://neynar.com/slack) for support. In your Slack message, please share a screenshot of the console (right click on page, then click “inspect”).
***
## FAQ
Developers often test enabling notifications with their own account using a localtunnel or ngrok domain (sometimes without even realizing they’ve enabled notifications on their test URL). When developers change their domain to a Vercel domain or something else, their notification token becomes invalid and results in failures from Farcaster’s servers (usually an “invalid domain” error).
Please see response to "Why am I not receiving a notification?" above. Additionally, there are instances where a user may be on an old mini app domain that has phased out. Users who do receive notifications are usually on the most recent domain.
You'll want to add the `canonicalDomain` field to the `farcaster.json` manifests on both your old and new domains, so that Farcaster clients can associate your notification tokens with the new URL. See the guide [here](https://miniapps.farcaster.xyz/docs/guides/domain-migration) for more details.
# Deploy a Token on Base with 1 API Call
Source: https://docs.neynar.com/docs/deploy-token-on-base-with-api-call
This guide provides a step-by-step process to deploy a fungible token on the Base network using Neynar's API. The deployment process is simplified to a single API call, eliminating the need for developers to write Solidity code or handle on-chain transaction signing. Neynar covers the on-chain deployment fees and assigns the specified owner address as the token owner.
### Related API: [Deploy fungible](/reference/deploy-fungible)
## Prerequisites
* **Neynar API Key**: Ensure you have a valid API key from Neynar. You can obtain one by signing up at [neynar.com](https://neynar.com).
* **Environment Setup**: Follow the [Getting Started Guide](/docs/getting-started-with-neynar) to set up your environment.
## API Endpoint
* **Endpoint**: `/fungible`
* **Method**: `POST`
* **Content Type**: `multipart/form-data`
## Request Body Schema
The request body should include the following fields:
```json JSON theme={"system"}
{
"owner": "string", // Ethereum address of the token owner
"symbol": "string", // Symbol/Ticker for the token
"name": "string", // Name of the token
"metadata": {
"media": "string", // Media file or URI associated with the token
"description": "string", // Description of the token
"nsfw": "string", // "true" or "false" indicating if the token is NSFW
"website_link": "string", // Website link related to the token
"twitter": "string", // Twitter profile link
"discord": "string", // Discord server link
"telegram": "string" // Telegram link
},
"network": "string", // Default: "base"
"factory": "string" // Default: "wow"
}
```
### Required Fields
* `owner`: Ethereum address of the token creator.
* `symbol`: The token's symbol or ticker.
* `name`: The name of the token.
### Optional Metadata Fields
* `media`: Can be a binary file (image/jpeg, image/gif, image/png) or a URI.
* `description`: A brief description of the token.
* `nsfw`: Indicates if the token is NSFW ("true" or "false").
* `website_link`, `twitter`, `discord`, `telegram`: Links related to the token.
## Example Request
Here's an example of how to deploy a token using the Neynar API:
```javascript Javascript theme={"system"}
import axios from 'axios';
import FormData from 'form-data';
const deployToken = async () => {
const formData = new FormData();
formData.append('owner', '0xYourEthereumAddress');
formData.append('symbol', 'MYTKN');
formData.append('name', 'My Token');
formData.append('metadata[description]', 'This is a sample token.');
formData.append('metadata[nsfw]', 'false');
formData.append('network', 'base');
formData.append('factory', 'wow');
try {
const response = await axios.post('https://api.neynar.com/fungible', formData, {
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': `Bearer ${process.env.NEYNAR_API_KEY}`
}
});
console.log('Token deployed successfully:', response.data);
} catch (error) {
console.error('Error deploying token:', error.response.data);
}
};
deployToken();
```
## Response
A successful response will include the following details:
```json JSON theme={"system"}
{
"contract": {
"fungible": {
"object": "fungible",
"name": "My Token",
"symbol": "MYTKN",
"media": "URI of the token media",
"address": "Contract address of the token",
"decimals": 18
}
}
}
```
## Conclusion
By following this guide, you can easily deploy a fungible token on the Base network using Neynar's API. This process abstracts the complexities of blockchain development, allowing you to focus on building your application. For further assistance, reach out to Neynar support or consult the [Neynar API documentation](/reference/deploy-fungible).
# Understanding number diffs
Source: https://docs.neynar.com/docs/differences-among-client-numbers
Learn why follower counts, reactions, and other metrics differ across Farcaster clients and tools like Merkle, Recaster, and Coinbase Wallet
## Differences in numbers across clients and tools
When looking at numbers across clients e.g. Merkle's Farcaster client vs Recaster vs Coinbase Wallet, you might notice differences in numbers. Specifically, number of followers, reactions, replies, etc.
There can be a few different reasons for this.
### 1. Clients optionally apply filters on top of protocol data
Farcaster protocol remains open to all.
However, this means that the protocol has a lot of spam activity.
Similar to how most email is spam and you never see it in your inbox,
a lot of Farcaster protocol activity is spam and clients don't show it.
### 2. Some show raw numbers
If a client is showing raw protocol data, those numbers won't match another client
that is applying filters on top of raw data. Raw protocol data always has higher numbers.
### 3. Spam filters are not standardized
Each clients filters as they see fit. Clients that use Neynar can use Neynar's filtering mechanisms will match each other.
## Filter spam using Neynar
When using Neynar APIs, you have the choice of fetching raw data or filtered data.
APIs return raw data by default, you can filter the data by turning on the [experimental flag](https://neynar.notion.site/Experimental-Features-1d2655195a8b80eb98b4d4ae7b76ae4a?source=copy_link)
on any of the API requests.
E.g. the first request passes the flag as `false`.
```
curl --request GET \
--url 'https://api.neynar.com/v2/farcaster/user/bulk/?fids=194' \
--header 'x-api-key: NEYNAR_API_DOCS' \
--header 'x-neynar-experimental: false'
```
while the second one passes it as `true`.
```
curl --request GET \
--url 'https://api.neynar.com/v2/farcaster/user/bulk/?fids=194' \
--header 'x-api-key: NEYNAR_API_DOCS' \
--header 'x-neynar-experimental: true'
```
You can look at the differences in follower counts below in the screenshots.
With experimental flag set to `false` (raw data)
With experimental flag set to `true` (filtered data)
As you can see, the raw data shows **273,791 followers** while the filtered data shows **146,993 followers** - a significant difference due to spam filtering.
This is different from what another client might show due to their own filtering logic.
E.g. Merkle's Farcaster client shows it as 145k which is close to Neynar's 146k number but not exact. This is because each company uses their own spam filtering logic.
# Explore Event Propagation on Farcaster
Source: https://docs.neynar.com/docs/explore-event-propagation-on-farcaster
Search for specific messages and debug Farcaster network health
[Neynar Explorer](https://explorer.neynar.com) helps developers debug event propagation1 on the Farcaster network.
### Start exploring
You can use the search bar to explore the network
* Node and API tabs will show where an event has propagated after clicking on the “Network response” button. Nodes / APIs that are missing events will be highlighted in red ❗️ , partially missing data will be highlighted in yellow ⚠️ and fully synced will be green ✅
* Clicking on a tab opens more details, allowing you to compare objects across nodes and APIs.
### Things you can explore
* **Specific casts:** e.g. `0x1a37442d0bd166806b6bc3a780bdb51f94d96fad` (cast hash) or `https://warpcast.com/v/0xf809724b`
* **Specific users:** e.g. `194` (user's fid)
* **Cast search with keywords:** e.g. `$higher`
* this will also show metrics of how often that keyword has appeared on the network recently
* results can be filtered by `username` (cast author), `channel_id` (channel of cast)
* results can be sorted by `desc_chron` or `algorithmic`
* search mode can be changed between `literal`, `semantic` or `hybrid`
* *read more about our [cast search API](/reference/search-casts), tap "Network response" to see event propagation for any cast*
* **User search with usernames:** e.g. `rish`
* *read more about our [user search API](/reference/search-user), tap "Network response" to see event propagation for any user*
* **Follow relationships:** e.g. `194<>191` to see relationship between two FIDs
* **Feed API results:** e.g. `https://api.neynar.com/v2/farcaster/feed/trending?limit=10&time_window=24h&provider=neynar`
* put any Feed API url from Neynar docs and evaluate results in a feed like format before you build a feed client
* *see our [Feed APIs](/reference/fetch-feed-for-you)*
### What else do you want to explore?
Hit us up with feature requests, we want to make debugging as easy as possible for developers!
1 Read more on event propagation in [Understanding Message Propagation on Farcaster Mainnet](https://neynar.com/blog/understanding-message-propagation-on-farcaster-mainnet). The blog was written at the time of hubs but similar debugging for snapchain syncs apply.
# Farcaster Actions Spec
Source: https://docs.neynar.com/docs/farcaster-actions-spec
Complete specification for Farcaster Actions - enabling secure cross-app communication and actions using Farcaster signers
### API Endpoints
Related API reference [Publish Farcaster Action](/reference/publish-farcaster-action)
## Introduction
Farcaster Actions is a spec that allows Farcaster applications to securely communicate and perform actions on behalf of users across different apps. It enables an app (referred to as **App A**) to send data or trigger actions in another app (**App B**) on behalf of a mutual user (e.g., **Carol**) by signing messages using the user's Farcaster signer.
This document provides an overview of how Farcaster Actions works and guides developers in implementing this functionality within their Neynar applications.
## Overview of Farcaster Actions
* **Purpose**: Facilitate secure cross-app interactions on behalf of users.
* **Participants**:
* **User**: The individual (e.g., Carol) who authorizes actions between apps.
* **App A**: The initiating app requesting to perform an action in App B.
* **App B**: The receiving app that processes the action from App A.
* **Neynar API**: The intermediary that assists in signing and forwarding the action securely.
## Workflow
### 1. Requesting Signer Access
**App A** requests a Farcaster signer from the user (**Carol**)
* **User Authorization**: Carol approves the signer.
* **Signer UUID**: Upon approval, App A receives a unique identifier (UUID) for Carol's signer.
### 2. Making an API Call with Signer UUID and Metadata
App A prepares to send an action to App B by making an API call to the Neynar API, including:
* **Signer UUID**: The unique identifier for Carol's signer.
* **Destination base URL**: The base URL of App B where the action should be sent.
* **Action Payload**: An object containing the type of action and any necessary payload data.
### 3. Neynar API Produces a Signature
The Neynar API processes the request from App A:
* **Signature Generation**: Neynar uses Carol's signer to generate a cryptographic signature
* **Bearer Token Creation**: A bearer token is created, encapsulating the signature and additional metadata, which will be used for authentication.
### 4. Forwarding the Signed Message to App B
Neynar forwards the signed action to App B:
* **HTTP Request**: An HTTP POST request is sent to App B's specified endpoint.
* **Headers**: The request includes an `Authorization` header containing the bearer token.
* **Body**: The action payload is included in the request body.
### 5. App B Verifies the Signature
Upon receiving the request, App B performs the following:
* **Signature Verification**: App B verifies the bearer token using Carol's public key and ensures the signature is valid.
* **Farcaster ID (fid)**: The token includes Carol's fid, confirming her identity.
* **Action Processing**: If verification succeeds, App B processes the action and updates its state accordingly.
## Implementation Details
### For App A Developers
* Checkout the documentation on [managed signers](/docs/integrate-managed-signers)
* Define the action payload, including the type and any necessary data.
* Specify the destination base URL of App B.
* Make a POST request to the Neynar API endpoint (POST - `/v2/farcaster/action`) with the following structure:
```json json theme={"system"}
{
"signer_uuid": "uuid-of-the-signer",
"url": "https://appb.xyz",
"action": {
"type": "actionType",
"payload": {
// Action-specific data
}
}
}
```
* The Neynar API will forward the action to App B and return the response from App B.
* Ensure proper error handling for cases where the action fails or the signature is invalid.
### For App B Developers
* Create an HTTP endpoint to receive POST `/api/farcaster/action`requests from the Neynar API.
* Ensure the endpoint URL is accessible and secured via HTTPS.
* Extract the `Authorization` header from incoming requests.
* Decode the bearer token to retrieve the header, payload, and signature.
* Use the fid and public key from the token header to verify the signature against the payload.
* Once the signature is verified, extract the action payload from the request body.
* Perform the necessary operations based on the action type and payload.
* Update your application's state or database as required.
* Return an appropriate HTTP response indicating success or failure.
* Include any necessary data in the response body for App A to process.
## Security Considerations
* **Token Expiration**: The bearer token has a short expiration time (5 minutes) to enhance security.
* **Signer Confidentiality**: Private keys are managed securely by Neynar and are never exposed to apps.
* **Data Integrity**: Signatures ensure that the action payload cannot be tampered with during transit.
## Conclusion
Farcaster Actions provides a secure and efficient way for Neynar apps to interact on behalf of users. By leveraging cryptographic signatures and Neynar's API, apps can ensure that cross-app actions are authenticated and authorized by the user, enhancing trust and interoperability within the Neynar ecosystem.
## Example
### Action Schema
The action request sent to the Neynar API follows this schema:
```json JSON theme={"system"}
{
"signer_uuid": "string (UUID format)",
"url": "string (valid URL)",
"action": {
"type": "string",
"payload": {
// Object containing action-specific data
}
}
}
```
Sample Request from App A to Neynar API
**POST** `/v2/farcaster/action` **Content-Type:** `application/json`
```json JSON theme={"system"}
{
"signer_uuid": "123e4567-e89b-12d3-a456-426614174000",
"url": "https://appb.example.com",
"action": {
"type": "sendMessage",
"payload": {
"message": "Hello from App A!"
}
}
}
```
Forwarded Request from Neynar API to App B
**POST** `/api/farcaster/action` **Content-Type:** `application/json` **Authorization:** `Bearer Token`
```json JSON theme={"system"}
{
"action": {
"type": "sendMessage",
"payload": {
"message": "Hello from App A!"
}
}
}
```
App B would then verify the bearer token and process the action accordingly.
# Farcaster Bot with Dedicated Signers
Source: https://docs.neynar.com/docs/farcaster-bot-with-dedicated-signers
Create a Farcaster bot on Neynar in a few quick steps
Simplest way to start is to clone this git repo that has a sample bot ready to go: [https://github.com/neynarxyz/farcaster-examples](https://github.com/neynarxyz/farcaster-examples)
In our `farcaster-examples` repo, `gm_bot` is an automated messaging bot designed to cast a 'gm ' message in Warpcast every day at a scheduled time. The bot operates continuously as long as the system remains online. It leverages [Neynar API](https://docs.neynar.com/) and is built using [@neynar/nodejs-sdk](https://www.npmjs.com/package/@neynar/nodejs-sdk).
## Prerequisites
* [Node.js](https://nodejs.org/en/): A JavaScript runtime built on Chrome's V8 JavaScript engine. Ensure you have Node.js installed on your system.
## Installation
### Setting Up the Environment
PM2 is a process manager for Node.js applications. Install it globally using npm:
```bash bash theme={"system"}
npm install -g pm2
```
Navigate to the project directory and run one of the following commands to install all required dependencies:
```Text Yarn theme={"system"}
yarn install
```
```bash npm theme={"system"}
npm install
```
* Copy the example environment file:
```bash bash theme={"system"}
cp .env.example .env
```
* Open the repo in your favorite editor and edit `.env` file to add your `NEYNAR_API_KEY` and `FARCASTER_BOT_MNEMONIC`. Optionally, you can also specify `PUBLISH_CAST_TIME` and `TIME_ZONE` for custom scheduling.
### Generating a Signer
Before running the bot, you need to generate a signer and get it approved via an onchain transaction. To execute the transaction, you'll need a browser extension wallet with funded roughly \$2 worth of OP ETH on the Optimism mainnet. This is crucial for the bot's operation. Run the following command in the terminal:
```bash bash theme={"system"}
yarn get-approved-signer
```
*This command will create some logs in your terminal. We will use these logs for upcoming steps below.*
### Approving a signer
In order to get an approved signer you need to do an on-chain transaction on OP mainnet.
Go to Farcaster KeyGateway optimism explorer [https://optimistic.etherscan.io/address/0x00000000fc56947c7e7183f8ca4b62398caadf0b#writeContract](https://optimistic.etherscan.io/address/0x00000000fc56947c7e7183f8ca4b62398caadf0b#writeContract)
Connect to Web3.
Remember the terminal logs we generated one of the earlier steps? You will see values for `fidOwner`, `keyType`, `key`, `metadataType`, `metadata`, `deadline`, `sig` in your terminal logs. Navigate to `addFor` function and add following values inside the respective placeholders.
Press "Write" to execute the transaction. This will create a signer for your mnemonic on the OP mainnet.
## Running the Bot
Launch the bot using the following command:
```bash Yarn theme={"system"}
yarn start
```
```Text npm theme={"system"}
npm run start
```
Ensure that the bot is running correctly with:
```bash bash theme={"system"}
pm2 status
```
To check the bot's activity logs, use:
```bash bash theme={"system"}
pm2 logs
```
If you need to stop the bot, use:
```bash bash theme={"system"}
pm2 kill
```
## License
`gm_bot` is released under the MIT License. This license permits free use, modification, and distribution of the software, with the requirement that the original copyright and license notice are included in any substantial portion of the work.
## FAQs/Troubleshooting
Check the PM2 logs for any errors and ensure your system's time settings align with the specified `TIME_ZONE`, also ensure that the process is running.
### 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!
# Features (hidden from roadmap right now)
Source: https://docs.neynar.com/docs/features-hidden-from-roadmap-right-now
Upcoming Neynar features and capabilities not yet shown on the public roadmap including Frame Studio enhancements and API improvements
## Features
### Neynar Frame Studio
* Rapid prototyping using a GUI interface
* Pre made templates that you can turn into a frame very quickly (galleries, memes, product checkout, taking in text input, NFT minting)
* Neynar provided frame hosting
* Built in usage analytics for every frame \[coming soon]
* Self serve data export for frames that take in text input \[coming soon]
* custom domains for your frame URLs \[coming soon]
### Neynar APIs
* APIs to embed frames into your client
* APIs to validate frame data and hydrate user data, user reactions and cast data in one API call
* Extensibility using flex URLs with detailed context on the cast, cast author, signer, interactor, and actions \[coming soon]
* Programmatic access to building frames using APIs \[coming soon]
* Neynar provided hosting for programmatically created frames \[coming soon]
* Improved security & spam protection by privatizing POST API endpoints \[coming soon]
* Frame usage analytics over API \[coming soon]
If you have questions on how to get started see our guides on [building frames](/docs/how-to-build-farcaster-frames-with-neynar) and [embedding frames](/docs/how-to-embed-farcaster-frames-in-your-app-with-neynar). More details on Neynar frame architecture below.
### Simple Pages
There are two ways to create simple pages inside a frame
#### 1. Frame Builder GUI
Check out the [Neynar Frame Studio](https://dev.neynar.com/frames)
```text JSON theme={"system"}
{
"uuid": "5ec484f5-efaf-4bda-9a3f-0579232a386a",
"image": "https://i.imgur.com/gpn83Gm.png",
"title": "Frame Shopper",
"buttons": [
{
"title": "Shop",
"next_page": {
"flex_url": "https://frameshopper.xyz/api/shop"
},
"action_type": "post_flex"
}
],
"version": "vNext"
}
```
When the action button on this page is tapped, Neynar will:
1. Receive an initial post from the client
2. Validate the action
3. Hydrate context details (as needed) - cast, cast author, interactor, signer, and actions
4. Make a POST request to the `flex_url` on the Frame developer's server with all the action context
5. Frame developer should respond with a status 200 and a JSON representation of the next page
6. Neynar will return the next page to the client
7.
#### 2. Create simple pages using simple REST APIs
GET, PUT, POST, DELETE at `/v2/farcaster/frame` \[coming soon]
### Flex Pages \[coming soon]
Frame developers can build dynamic pages using the `flex_url` All frame actions with a `flex_url` will be forwarded to the URL as a POST
Here's an example frame with a static first page.
```text JSON theme={"system"}
{
"uuid": "5ec484f5-efaf-4bda-9a3f-0579232a386a",
"image": "https://i.imgur.com/gpn83Gm.png",
"title": "Frame Shopper",
"buttons": [
{
"title": "Shop",
"next_page": {
"flex_url": "https://frameshopper.xyz/api/shop"
},
"action_type": "post_flex"
}
],
"version": "vNext"
}
```
# Casts by Embed in Farcaster
Source: https://docs.neynar.com/docs/fetch-casts-by-embed-in-farcaster
Show Farcaster casts that have attachments with Neynar
### Related API reference [Fetch Feed](/reference/fetch-feed)
This guide demonstrates how to use the Neynar SDK to casts that contain a specific embed (eg. cast linking to github.com or youtube.com).
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 Neynar client:
```javascript Javascript theme={"system"}
// npm i @neynar/nodejs-sdk
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
import { FeedType } 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);
```
Then fetch casts linking to github.com:
```javascript Javascript theme={"system"}
const feedType = FeedType.Filter;
const filterType= "embed_url";
const embedUrl= "github.com";
const result = await client.fetchFeed({feedType,
filterType,
embedUrl
});
console.log(result);
```
Replace `github.com` with any other embed url to fetch casts linking to that url.
To fetch casts linking to youtube.com:
```javascript Javascript theme={"system"}
const feedType = FeedType.Filter;
const filterType= "embed_url";
const embedUrl= "youtube.com";
const result = await client.fetchFeed({feedType,
filterType,
embedUrl
});
console.log(result);
```
And... Spotify:
```javascript Javascript theme={"system"}
const feedType = FeedType.Filter;
const filterType= "embed_url";
const embedUrl= "open.spotify.com";
const result = await client.fetchFeed({feedType,
filterType,
embedUrl
});
console.log(result);
```
Example output:
```json theme={"system"}
{
casts: [
{
object: "cast_hydrated",
hash: "0x9f617c43f00308cdb46b7b72f067b01557d53733",
thread_hash: "0x9f617c43f00308cdb46b7b72f067b01557d53733",
parent_hash: null,
parent_url: "chain://eip155:7777777/erc721:0xe96c21b136a477a6a97332694f0caae9fbb05634",
parent_author: [Object ...],
author: [Object ...],
text: "Yo, we got kids to raise and bills to pay, enemies to lay down when they stand in our way, it's only us \n\nhttps://open.spotify.com/track/0SlljMy4uEgoVPCyavtcHH",
timestamp: "2023-12-11T04:06:57.000Z",
embeds: [
[Object ...]
],
reactions: [Object ...],
replies: [Object ...],
mentioned_profiles: []
}, {
object: "cast_hydrated",
hash: "0xe70d9d52c5019b247fa93f76779296322676a4e5",
thread_hash: "0xe70d9d52c5019b247fa93f76779296322676a4e5",
parent_hash: null,
parent_url: null,
parent_author: [Object ...],
author: [Object ...],
text: "For the Intuitives (Part 1)",
timestamp: "2023-12-11T02:11:27.000Z",
embeds: [
[Object ...]
],
reactions: [Object ...],
replies: [Object ...],
mentioned_profiles: []
}, {
object: "cast_hydrated",
hash: "0xee6719ac805758be5bd54744bec63b7ec0bc4d3e",
thread_hash: "0xee6719ac805758be5bd54744bec63b7ec0bc4d3e",
parent_hash: null,
parent_url: null,
parent_author: [Object ...],
author: [Object ...],
text: "EP 214 Douglas Rushkoff on Leaving Social Media",
timestamp: "2023-12-11T02:11:18.000Z",
embeds: [
[Object ...]
],
reactions: [Object ...],
replies: [Object ...],
mentioned_profiles: []
}, {
object: "cast_hydrated",
hash: "0xebe7e89b1a3e91c96f99ecf3ce7d2797e3b118b6",
thread_hash: "0xebe7e89b1a3e91c96f99ecf3ce7d2797e3b118b6",
parent_hash: null,
parent_url: null,
parent_author: [Object ...],
author: [Object ...],
text: "#64 AI & the Global Brain: Peter Russell",
timestamp: "2023-12-11T02:11:04.000Z",
embeds: [
[Object ...]
],
reactions: [Object ...],
replies: [Object ...],
mentioned_profiles: []
}, {
object: "cast_hydrated",
hash: "0x93276da072a2902a3568da21203588995e4ba752",
thread_hash: "0x93276da072a2902a3568da21203588995e4ba752",
parent_hash: null,
parent_url: null,
parent_author: [Object ...],
author: [Object ...],
text: "Systems Thinking - Tomas Bjorkman - Consciousness",
timestamp: "2023-12-11T02:10:26.000Z",
embeds: [
[Object ...]
],
reactions: [Object ...],
replies: [Object ...],
mentioned_profiles: []
}
],
next: {
cursor: "eyJ0aW1lc3RhbXAiOiIyMDIzLTEyLTExIDAyOjEwOjI2LjAwMDAwMDAifQ=="
}
}
```
To fetch the next page:
```javascript Javascript theme={"system"}
const filter= "filter";
const filterType= "embed_url";
const cursor= result.next.cursor
const nextResult = await client.fetchFeed({filter,
filterType,
embedUrl,
cursor
});
```
There you go, fetching casts with specific embeds in Farcaster with Neynar SDK!
### 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!
# Relevant Holders for Coins
Source: https://docs.neynar.com/docs/fetch-relevant-holders-for-coin
This guide provides an overview of how to use the Neynar API to fetch relevant holders of a fungible token.
### Related API: [Relevant owners](/reference/fetch-relevant-fungible-owners)
## Fetching relevant holders for a coin
The API identifies relevant users on Farcaster who hold the same coin, based on the users followed by the viewer. The API works across EVM (Ethereum, Base, etc.) and Solana networks.
### Overview
The `fetch-relevant-fungible-owners` API endpoint allows you to retrieve a list of relevant users who own a specific fungible token. This is particularly useful for applications that want to display social connections around token ownership.
#### Key Features
* **Network Support**: EVM + Solana
* **Viewer Context**: The API customizes the response based on the `viewer_fid`, respecting the viewer's mutes and blocks.
* **Relevance**: Returns users from the network who are most relevant to the viewer
### API Endpoint
#### Endpoint
```
GET /fungible/owner/relevant
```
#### Parameters
* **contract\_address** (string, required): The contract address of the fungible asset.
* Example: `0x0db510e79909666d6dec7f5e49370838c16d950f`
* **networks** (enum string): Network to fetch tokens from.
* Example: `["base"]`
* **viewer\_fid** (integer, required): The FID of the user to customize this response for. This will also return a list of owners that respects this user's mutes and blocks.
* Example: `194`
#### Response
* **200 OK**: Successful response containing relevant fungible owners.
* **top\_relevant\_owners\_hydrated**: An array of user objects representing the top relevant owners.
* **all\_relevant\_owners\_dehydrated**: An array of user objects representing all relevant owners.
* **400 Bad Request**: The request was invalid, often due to missing or incorrect parameters.
### Example Request
```http HTTP theme={"system"}
GET /fungible/owner/relevant?contract_address=0x0db510e79909666d6dec7f5e49370838c16d950f&networks=base&viewer_fid=194
```
### Example Response
```json JSON theme={"system"}
{
"top_relevant_owners_hydrated": [
{
"fid": 123,
"username": "alice.eth",
"display_name": "Alice",
"pfp_url": "https://example.com/alice.jpg"
},
{
"fid": 456,
"username": "bob.eth",
"display_name": "Bob",
"pfp_url": "https://example.com/bob.jpg"
}
],
"all_relevant_owners_dehydrated": [
{
"fid": 789,
"username": "charlie.eth",
"display_name": "Charlie",
"pfp_url": "https://example.com/charlie.jpg"
}
]
}
```
### Implementation Example
To implement this API call in a JavaScript environment using the Neynar SDK, follow the steps below:
```javascript javascript theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const config = new Configuration({
apiKey: process.env.NEYNAR_API_KEY,
});
const client = new NeynarAPIClient(config);
```
```javascript javascript theme={"system"}
async function fetchRelevantOwners(contractAddress, viewerFid) {
try {
const response = await client.fetchRelevantFungibleOwners({
contract_address: contractAddress,
networks: ["base"],
viewer_fid: viewerFid,
});
console.log("Relevant Owners:", response);
} catch (error) {
console.error("Error fetching relevant owners:", error);
}
}
fetchRelevantOwners("0x0db510e79909666d6dec7f5e49370838c16d950f", 194);
```
## Increase conversions by adding personalization
Users are more likely to trade / take onchain actions when they get social proof. Facebook proved this long ago when they started showing "X, Y, Z like this page" above an ad. The same holds true in crypto.
For further information, refer to the [Neynar API Documentation](/reference/fetch-relevant-fungible-owners). If you have any questions or need support, feel free to reach out to us on [Slack](https://neynar.com/slack).
# Relevant Holders for Coins
Source: https://docs.neynar.com/docs/fetch-relevant-holders-for-coin-on-base
This guide provides a comprehensive overview of how to use the Neynar API to fetch relevant holders of a fungible token.
This doc has been updated. See [Relevant owners](/docs/fetch-relevant-holders-for-coin)
# Fetch signers
Source: https://docs.neynar.com/docs/fetch-signers-1
The following guides show how to fetch signers if you don't have access to the custody address mnemonic of the user
# Fetch Signers - Backend
Source: https://docs.neynar.com/docs/fetch-signers-backend
This guide demonstrates how to get a list of signers for an account if the developer has the user's mnemonic/account private key (If not check: [Frontend (Wallet Integration)](docs/fetch-signers-frontend-wallet-integration))
### Related API: [List signers](/reference/fetch-signers)
## **Prerequisites**
Ensure you have Nodejs installed on your system. You can download it from [Node.js' official website](https://nodejs.org/).
Obtain an API key from the [dev portal](https://dev.neynar.com/app) Ensure you have a valid Ethereum mnemonic phrase of the account with a signer associated with the above API key.
Install the required packages:
```bash Shell theme={"system"}
yarn add siwe viem @neynar/nodejs-sdk
```
## **Code Breakdown and Steps**
The code begins by importing the necessary libraries:
```javascript Javascript theme={"system"}
import { SiweMessage } from "siwe";
import { mnemonicToAccount } from "viem/accounts";
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
```
Replace `"YOUR_MNEMONIC_HERE"` with your Ethereum mnemonic phrase:
```javascript Javascript theme={"system"}
const mnemonic = "YOUR_MNEMONIC_HERE";
```
The `mnemonicToAccount` function converts your mnemonic into an account object:
```javascript Javascript theme={"system"}
const account = mnemonicToAccount(mnemonic);
```
Replace `"YOUR_API_KEY_HERE"` with your API key and set the correct base path for the Neynar API:
```javascript Javascript theme={"system"}
const config = new Configuration({
apiKey: "YOUR_API_KEY_HERE",
});
const client = new NeynarAPIClient(config);
```
The `createSiweMessage` function generates a SIWE message with details such as domain, address, and [nonce](/reference/fetch-nonce):
```javascript Javascript theme={"system"}
async function createSiweMessage(address, statement) {
const { nonce } = await client.fetchNonce();
const message = new SiweMessage({
scheme: "http",
domain: "localhost:8080",
address,
statement,
uri: "http://localhost:8080",
version: "1",
chainId: "1",
nonce: nonce,
});
return message.prepareMessage();
}
```
The `fetchSigners` function handles the signing process and fetches signers:
**Note:** The `address` should be the `custody_address` of the farcaster account ([Check custody\_address in User API](/reference/fetch-bulk-users))
```javascript Javascript theme={"system"}
async function fetchSigners() {
const address = account.address;
let message = await createSiweMessage(
address,
"Sign in with Ethereum to the app."
);
const signature = await account.signMessage({ message });
const { signers } = await client.fetchSigners({ message, signature });
return signers;
}
```
Call the `fetchSigners` function and handle success or errors:
```javascript Javascript theme={"system"}
fetchSigners()
.then((signers) => {
console.log("\n\nsigners:", signers, "\n\n");
})
.catch((error) => {
console.error("error:", error);
});
```
## **Expected Output**
```json JSON theme={"system"}
[
{
"object": "signer",
"signer_uuid": "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec",
"public_key": "0xe4abc135d40f8a6ee216d1a6f2f4e82476dff75f71ea53c5bdebca43f5c415b7",
"status": "approved",
"fid": 0
},
{
"object": "signer",
"signer_uuid": "08c71152-c552-42e7-b094-f510ff44e9cb",
"public_key": "0xe4cd577123def73295dd9991c589b59b48cdc976b5e83a9ad8d2a13fcfcc0e72",
"status": "approved",
"fid": 0
}
]
```
For additional help, [feel free to contact us](https://neynar.com/slack).
# Fetch Signers - Frontend (Wallet Integration)
Source: https://docs.neynar.com/docs/fetch-signers-frontend-wallet-integration
This guide demonstrates how to get a list of signers for an account if the developer can't access the user's mnemonic. (If the developer has access to the mnemonic, check: [Backend](/docs/fetch-signers-backend))
### Related API: [Fetch signers](/docs/fetch-signers-1)
## **Important Note**
**The Neynar Client Instantiation and API calls (`fetchNonce` and `fetchSigners`) should ideally be performed on the backend to protect your API key and maintain security.**
## **Prerequisites**
Browser Ensure you are using a browser with a wallet like MetaMask installed.
Obtain an API key from [dev portal](https://dev.neynar.com/app)
Install the required packages:
```shell shell theme={"system"}
yarn add siwe viem @neynar/nodejs-sdk
```
## **Code Breakdown and Steps**
The code starts by importing the necessary libraries:
```javascript Javascript theme={"system"}
import { SiweMessage } from "siwe";
import { createWalletClient, custom, publicActions } from "viem";
import { optimism } from "viem/chains";
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
```
Set up the Neynar API client with your API key and base path. Note that this should ideally be done on the backend for security reasons:
```javascript Javascript theme={"system"}
const config = new Configuration({
apiKey: "YOUR_API_KEY_HERE",
});
const client = new NeynarAPIClient(config);
```
The `createWalletClient` function initializes a wallet client using the `viem` library. It uses `window.ethereum` to connect to the browser's wallet:
```javascript Javascript theme={"system"}
const wallet = createWalletClient({
chain: optimism,
transport: custom(window.ethereum),
}).extend(publicActions);
```
The `createSiweMessage` function generates a SIWE message with details such as domain, address, and [nonce](/reference/fetch-nonce):
```javascript Javascript theme={"system"}
async function createSiweMessage(address, statement) {
const { nonce } = await client.fetchNonce();
const message = new SiweMessage({
scheme: "http",
domain: "localhost:8080",
address,
statement,
uri: "http://localhost:8080",
version: "1",
chainId: "1",
nonce: nonce,
});
return message.prepareMessage();
}
```
The `fetchSigners` function retrieves the user's Ethereum address, generates a SIWE message, signs it, and verifies the signature using the Neynar API.
**Note:**
1. Neynar API should ideally be accessed from the backend
2. The `address` should be the `custody_address` of the farcaster account ([Check custody\_address in User API](/reference/fetch-bulk-users))
```javascript Javascript theme={"system"}
async function fetchSigners() {
const address = (await wallet.getAddresses())[0];
let message = await createSiweMessage(
address,
"Sign in with Ethereum to the app."
);
const signature = await wallet.signMessage({ account: address, message });
const { signers } = await client.fetchSigners({ message, signature });
return signers;
}
```
Call the `fetchSigners` function and handle the response or errors:
```javascript Javascript theme={"system"}
fetchSigners()
.then((signers) => {
console.log("\n\nsigners:", signers, "\n\n");
})
.catch((error) => {
console.error("error:", error);
});
```
## **Expected Output**
```json JSON theme={"system"}
[
{
"object": "signer",
"signer_uuid": "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec",
"public_key": "0xe4abc135d40f8a6ee216d1a6f2f4e82476dff75f71ea53c5bdebca43f5c415b7",
"status": "approved",
"fid": 0
},
{
"object": "signer",
"signer_uuid": "08c71152-c552-42e7-b094-f510ff44e9cb",
"public_key": "0xe4cd577123def73295dd9991c589b59b48cdc976b5e83a9ad8d2a13fcfcc0e72",
"status": "approved",
"fid": 0
}
]
```
For additional help, [feel free to contact us](https://neynar.com/slack).
# Fetch & Display Farcaster Feeds with Neynar API
Source: https://docs.neynar.com/docs/fetching-casts-from-memes-channel-in-farcaster
Complete guide to fetching and displaying casts from specific Farcaster channels using Neynar SDK. Learn how to retrieve channel feeds, filter content by channel IDs, and build engaging social media applications with real-time Farcaster data.
### Related API reference [Fetch Feed by Channel IDs](/reference/fetch-feed-by-channel-ids)
Channels are "subreddits inside Farcaster." Technically, a channel is a collection of casts that share a common channel\_id. For example, the [memes channel](https://warpcast.com/~/channel/memes) is a collection of casts that share the channel\_id `memes`.
## Fetching Casts from a Channel
This guide demonstrates how to use the Neynar SDK to fetch casts from 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.
First, initialize the client:
```javascript Javascript theme={"system"}
// 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);
```
Then, fetch the feed in the memes channel.
```javascript Javascript theme={"system"}
const client = new NeynarAPIClient(config);
const channelId = "memes";
const filterType = FilterType.ChannelId;
const feed = await client.fetchFeed({
feedType: FeedType.Filter,
filterType,
channelId,
});
console.log(feed);
```
Example output:
```json Json theme={"system"}
{
casts: [
{
object: "cast_hydrated",
hash: "0xf17168571d5e403f3b608ea2cc09a0b711d4c4fc",
thread_hash: "0xf17168571d5e403f3b608ea2cc09a0b711d4c4fc",
parent_hash: null,
parent_url: "chain://eip155:1/erc721:0xfd8427165df67df6d7fd689ae67c8ebf56d9ca61",
parent_author: [Object ...],
author: [Object ...],
text: "",
timestamp: "2023-11-27T14:40:12.000Z",
embeds: [
[Object ...]
],
reactions: [Object ...],
replies: [Object ...],
mentioned_profiles: []
}, {
object: "cast_hydrated",
hash: "0x344dcd9c7c2671450628aacd0bbb8e29ea2e8809",
thread_hash: "0x344dcd9c7c2671450628aacd0bbb8e29ea2e8809",
parent_hash: null,
parent_url: "chain://eip155:1/erc721:0xfd8427165df67df6d7fd689ae67c8ebf56d9ca61",
parent_author: [Object ...],
author: [Object ...],
text: "sorry",
timestamp: "2023-11-27T14:24:32.000Z",
embeds: [
[Object ...]
],
reactions: [Object ...],
replies: [Object ...],
mentioned_profiles: []
}, {
object: "cast_hydrated",
hash: "0x68b94ec2a10ebad8b13e3b673f0db02dd3280f42",
thread_hash: "0x68b94ec2a10ebad8b13e3b673f0db02dd3280f42",
parent_hash: null,
parent_url: "chain://eip155:1/erc721:0xfd8427165df67df6d7fd689ae67c8ebf56d9ca61",
parent_author: [Object ...],
author: [Object ...],
text: "man today is such a nice morning",
timestamp: "2023-11-27T13:30:11.000Z",
embeds: [
[Object ...]
],
reactions: [Object ...],
replies: [Object ...],
mentioned_profiles: []
}
],
next: {
cursor: "eyJ0aW1lc3RhbXAiOiIyMDIzLTExLTI3IDEzOjMwOjExLjAwMDAwMDAifQ=="
}
}
```
To fetch the next page of the feed, use the cursor:
```javascript Javascript theme={"system"}
const nextFeed = await client.fetchFeed({
feedType: FeedType.Filter,
filterType: FilterType.ChannelId,
channelId,
cursor: feed.next.cursor,
});
```
There you go! You now know how to fetch casts from a Farcaster channel with Neynar SDK.
### 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!
# Notifications in Channels
Source: https://docs.neynar.com/docs/fetching-channel-specific-notification-in-farcaster
Learn how to fetch channel-specific notifications for Farcaster users using Neynar API. Build focused notification systems that filter alerts by specific channels and parent URLs, perfect for channel-focused Farcaster clients and applications.
### Related APIs: (1) [For user by channel](/reference/fetch-channel-notifications-for-user) (2) [For user by parent\_urls](/reference/fetch-notifications-by-parent-url-for-user)
## Fetching Channel-Specific Notifications
Say you have a Farcaster client focusing on a specific channel, and you want to fetch notifications for a specific FID for that specific channel. We got you covered!
This guide will show you how to fetch notifications for a specific FID for a specific 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.
First, initialize the client:
```javascript Javascript theme={"system"}
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);
```
Let's say you have a Nouns-specific Farcaster client and you want to fetch notifications for a specific FID.
### 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 theme={"system"}
const nounsChannelUrl =
"chain://eip155:1/erc721:0x9c8ff314c9bc7f6e59a9d9225fb22946427edc03";
const userFID = 3;
const notifications = await client.fetchChannelNotificationsForUser({fid:userFID,channelIds: [
nounsChannelUrl,
]});
```
Example output:
```json theme={"system"}
{
"notifications": [
{
"object": "notification",
"most_recent_timestamp": "2023-12-08T06:31:10.000Z",
"type": "mention",
"cast": {
"object": "cast_hydrated",
"hash": "0xd16b71018cc53c667e771bb4c13627555a32b5d4",
"thread_hash": "b7fc569081242aadeb29f8254931daf31c9e1017",
"parent_hash": "243f539607f4ea7b4117a169433c1ea8295d32fc",
"parent_url": null,
"parent_author": {
"fid": "3895"
},
"author": {
"object": "user",
"fid": 1079,
"custody_address": "0xeb31e335531c06ca4d8fe58bed841e9031de4ee4",
"username": "joshuafisher.eth",
"display_name": "Joshua Fisher",
"pfp_url": "https://i.imgur.com/1pn4CEg.jpg",
"profile": {
"bio": {
"text": "⌐◨-◨ ‘ing around. Working on Nouns Creative focused on narrative works. Music Publisher & Manager by day.",
"mentioned_profiles": []
}
},
"follower_count": 422,
"following_count": 149,
"verifications": [
"0xbd7dbab9aeb52d6c8d0e80fcebde3af4cc86204a"
],
"active_status": "active"
},
"text": "Would be tasty if we could buy this with Warps @dwr.eth",
"timestamp": "2023-12-08T06:31:10.000Z",
"embeds": [],
"reactions": {
"likes": [
{
"fid": 1898,
"fname": "boscolo.eth"
},
{
"fid": 14700,
"fname": "brsn"
},
{
"fid": 3,
"fname": "dwr.eth"
},
{
"fid": 576,
"fname": "nonlinear.eth"
}
],
"recasts": []
},
"replies": {
"count": 0
},
"mentioned_profiles": [
{
"object": "user",
"fid": 3,
"custody_address": "0x6b0bda3f2ffed5efc83fa8c024acff1dd45793f1",
"username": "dwr.eth",
"display_name": "Dan Romero",
"pfp_url": "https://res.cloudinary.com/merkle-manufactory/image/fetch/c_fill,f_png,w_256/https://lh3.googleusercontent.com/MyUBL0xHzMeBu7DXQAqv0bM9y6s4i4qjnhcXz5fxZKS3gwWgtamxxmxzCJX7m2cuYeGalyseCA2Y6OBKDMR06TWg2uwknnhdkDA1AA",
"profile": {
"bio": {
"text": "Working on Farcaster and Warpcast.",
"mentioned_profiles": []
}
},
"follower_count": 30657,
"following_count": 2722,
"verifications": [
"0xd7029bdea1c17493893aafe29aad69ef892b8ff2",
"0xa14b4c95b5247199d74c5578531b4887ca5e4909",
"0xb877f7bb52d28f06e60f557c00a56225124b357f",
"0x8fc5d6afe572fefc4ec153587b63ce543f6fa2ea"
],
"active_status": "active"
}
]
}
},
{
"object": "notification",
"most_recent_timestamp": "2023-12-08T06:09:50.000Z",
"type": "mention",
"cast": {
"object": "cast_hydrated",
"hash": "0xbf05b5bb119d4f1b8c514fbc75c23f9c8755dfd7",
"thread_hash": "f750ed31ece83fa486be9b37782d57d1b679f925",
"parent_hash": "bde97a78c48ed92ba01c2c2f0cfd521b52f524bc",
"parent_url": null,
"parent_author": {
"fid": "7143"
},
"author": {
"object": "user",
"fid": 1097,
"custody_address": "0xe12b01100a4be7e79ddbd5dd939c97d12e890ac7",
"username": "noun40",
"display_name": "Noun 40",
"pfp_url": "https://openseauserdata.com/files/faa77932343776d1237e5dd82aa12e76.svg",
"profile": {
"bio": {
"text": "cofounder/cto @ bitwise",
"mentioned_profiles": []
}
},
"follower_count": 15682,
"following_count": 55,
"verifications": [
"0xae65e700f3f8904ac1007d47a5309dd26f8146c0"
],
"active_status": "active"
},
"text": "oh hmm i wonder if there’s a way to expose this data of channel subscribers @dwr.eth @v?",
"timestamp": "2023-12-08T06:09:50.000Z",
"embeds": [],
"reactions": {
"likes": [
{
"fid": 194490,
"fname": "0xdbao"
},
{
"fid": 197459,
"fname": "cryptoworldao"
},
{
"fid": 193703,
"fname": "ai13"
}
],
"recasts": []
},
"replies": {
"count": 1
},
"mentioned_profiles": [
{
"object": "user",
"fid": 3,
"custody_address": "0x6b0bda3f2ffed5efc83fa8c024acff1dd45793f1",
"username": "dwr.eth",
"display_name": "Dan Romero",
"pfp_url": "https://res.cloudinary.com/merkle-manufactory/image/fetch/c_fill,f_png,w_256/https://lh3.googleusercontent.com/MyUBL0xHzMeBu7DXQAqv0bM9y6s4i4qjnhcXz5fxZKS3gwWgtamxxmxzCJX7m2cuYeGalyseCA2Y6OBKDMR06TWg2uwknnhdkDA1AA",
"profile": {
"bio": {
"text": "Working on Farcaster and Warpcast.",
"mentioned_profiles": []
}
},
"follower_count": 30657,
"following_count": 2722,
"verifications": [
"0xd7029bdea1c17493893aafe29aad69ef892b8ff2",
"0xa14b4c95b5247199d74c5578531b4887ca5e4909",
"0xb877f7bb52d28f06e60f557c00a56225124b357f",
"0x8fc5d6afe572fefc4ec153587b63ce543f6fa2ea"
],
"active_status": "active"
},
{
"object": "user",
"fid": 2,
"custody_address": "0x4114e33eb831858649ea3702e1c9a2db3f626446",
"username": "v",
"display_name": "Varun Srinivasan",
"pfp_url": "https://i.seadn.io/gae/sYAr036bd0bRpj7OX6B-F-MqLGznVkK3--DSneL_BT5GX4NZJ3Zu91PgjpD9-xuVJtHq0qirJfPZeMKrahz8Us2Tj_X8qdNPYC-imqs?w=500&auto=format",
"profile": {
"bio": {
"text": "Technowatermelon. Elder Millenial. Building Farcaster. \n\nnf.td/varun",
"mentioned_profiles": []
}
},
"follower_count": 27025,
"following_count": 974,
"verifications": [
"0x91031dcfdea024b4d51e775486111d2b2a715871",
"0x182327170fc284caaa5b1bc3e3878233f529d741"
],
"active_status": "active"
}
]
}
},
{
"object": "notification",
"most_recent_timestamp": "2023-12-03T23:35:12.000Z",
"type": "mention",
"cast": {
"object": "cast_hydrated",
"hash": "0x06dfafdffa7455c3fd0a617ce1b026bcf01211d1",
"thread_hash": "2695897f7265b116de992dde0a13865dda938eae",
"parent_hash": "7b00f3e12f26ff363555d4f94f64e547fde7379a",
"parent_url": null,
"parent_author": {
"fid": "7143"
},
"author": {
"object": "user",
"fid": 1097,
"custody_address": "0xe12b01100a4be7e79ddbd5dd939c97d12e890ac7",
"username": "noun40",
"display_name": "Noun 40",
"pfp_url": "https://openseauserdata.com/files/faa77932343776d1237e5dd82aa12e76.svg",
"profile": {
"bio": {
"text": "cofounder/cto @ bitwise",
"mentioned_profiles": []
}
},
"follower_count": 15682,
"following_count": 55,
"verifications": [
"0xae65e700f3f8904ac1007d47a5309dd26f8146c0"
],
"active_status": "active"
},
"text": "@dwr.eth @v would you agree? is there a more fundamental reason it’s whitelisted atm?",
"timestamp": "2023-12-03T23:35:12.000Z",
"embeds": [],
"reactions": {
"likes": [
{
"fid": 1356,
"fname": "farcasteradmin.eth"
}
],
"recasts": []
},
"replies": {
"count": 1
},
"mentioned_profiles": [
{
"object": "user",
"fid": 3,
"custody_address": "0x6b0bda3f2ffed5efc83fa8c024acff1dd45793f1",
"username": "dwr.eth",
"display_name": "Dan Romero",
"pfp_url": "https://res.cloudinary.com/merkle-manufactory/image/fetch/c_fill,f_png,w_256/https://lh3.googleusercontent.com/MyUBL0xHzMeBu7DXQAqv0bM9y6s4i4qjnhcXz5fxZKS3gwWgtamxxmxzCJX7m2cuYeGalyseCA2Y6OBKDMR06TWg2uwknnhdkDA1AA",
"profile": {
"bio": {
"text": "Working on Farcaster and Warpcast.",
"mentioned_profiles": []
}
},
"follower_count": 30657,
"following_count": 2722,
"verifications": [
"0xd7029bdea1c17493893aafe29aad69ef892b8ff2",
"0xa14b4c95b5247199d74c5578531b4887ca5e4909",
"0xb877f7bb52d28f06e60f557c00a56225124b357f",
"0x8fc5d6afe572fefc4ec153587b63ce543f6fa2ea"
],
"active_status": "active"
},
{
"object": "user",
"fid": 2,
"custody_address": "0x4114e33eb831858649ea3702e1c9a2db3f626446",
"username": "v",
"display_name": "Varun Srinivasan",
"pfp_url": "https://i.seadn.io/gae/sYAr036bd0bRpj7OX6B-F-MqLGznVkK3--DSneL_BT5GX4NZJ3Zu91PgjpD9-xuVJtHq0qirJfPZeMKrahz8Us2Tj_X8qdNPYC-imqs?w=500&auto=format",
"profile": {
"bio": {
"text": "Technowatermelon. Elder Millenial. Building Farcaster. \n\nnf.td/varun",
"mentioned_profiles": []
}
},
"follower_count": 27025,
"following_count": 974,
"verifications": [
"0x91031dcfdea024b4d51e775486111d2b2a715871",
"0x182327170fc284caaa5b1bc3e3878233f529d741"
],
"active_status": "active"
}
]
}
}
],
"next": {
"cursor": "eyJ0aW1lc3RhbXAiOiIyMDIzLTEyLTAzIDIzOjM1OjEyLjAwMDAwMDAifQ=="
}
}
```
To fetch the next page of notifications, use the cursor:
```javascript Javascript theme={"system"}
const nextNotifications = await client.fetchChannelNotificationsForUser({
fid: userFID,
channelIds: [nounsChannelUrl],
cursor: notifications.next.cursor,
});
```
That's it, no more wrangling with SQL queries or whatever bespoke solution to get notifications for a specific 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!
# User by Wallet Address
Source: https://docs.neynar.com/docs/fetching-farcaster-user-based-on-ethereum-address
Find Farcaster user profile based on ethereum address
### This guide refers to [this API](/reference/fetch-bulk-users-by-eth-or-sol-address)
Farcaster users can connect their FID (Farcaster ID) with an Ethereum or Solana address. This guide demonstrates how to get information about a user given their address.
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 theme={"system"}
// 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);
```
To get vitalik.eth's Farcaster profile:
```javascript Javascript theme={"system"}
// vitalik.eth
const addr = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
const user = await client.fetchBulkUsersByEthOrSolAddress({addresses: [addr]});
console.log(user);
```
Example output:
```json theme={"system"}
{
result: {
user: {
fid: 5650,
custodyAddress: "0xadd746be46ff36f10c81d6e3ba282537f4c68077",
username: "vitalik.eth",
displayName: "Vitalik Buterin",
pfp: [Object ...],
profile: [Object ...],
followerCount: 14769,
followingCount: 70,
verifications: [ "0xd8da6bf26964af9d7eed9e03e53415d37aa96045" ],
activeStatus: "active"
}
}
}
```
For addresses with multiple verifications, it will all resolve to the same user:
```javascript Javascript theme={"system"}
// dwr.eth
const addr1 = "0xd7029bdea1c17493893aafe29aad69ef892b8ff2";
const addr2 = "0xa14b4c95b5247199d74c5578531b4887ca5e4909";
// use Promise.all to make multiple requests in parallel
const users = await Promise.all([
client.fetchBulkUsersByEthOrSolAddress({addresses: [addr1]}),
client.fetchBulkUsersByEthOrSolAddress({addresses: [addr2]}),
]);
console.log(users[0] === users[1]); // true
console.log(users[0]);
```
They both resolve to:
```json theme={"system"}
{
result: {
user: {
fid: 3,
custodyAddress: "0x6b0bda3f2ffed5efc83fa8c024acff1dd45793f1",
username: "dwr.eth",
displayName: "Dan Romero",
pfp: [Object ...],
profile: [Object ...],
followerCount: 19326,
followingCount: 2702,
verifications: [ "0xd7029bdea1c17493893aafe29aad69ef892b8ff2", "0xa14b4c95b5247199d74c5578531b4887ca5e4909",
"0xb877f7bb52d28f06e60f557c00a56225124b357f", "0x8fc5d6afe572fefc4ec153587b63ce543f6fa2ea"
],
activeStatus: "active"
}
}
}
```
### 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!
# Follow NFT Owners on Farcaster Using Neynar
Source: https://docs.neynar.com/docs/following-all-farcaster-users-owning-cryptopunk
Comprehensive tutorial on how to automatically follow all Farcaster users who own specific NFTs like CryptoPunks. Learn to build Web3 social features by combining Farcaster social graphs with NFT ownership data using Neynar's powerful APIs.
## How to Follow Farcaster Users Owning a Specific NFT
This guide demonstrates how to follow Farcaster 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 theme={"system"}
// 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;
```
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 theme={"system"}
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 theme={"system"}
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);
```
Then, we can use the [Follow user](ref:follow-user) endpoint to follow each user.
```javascript Javascript theme={"system"}
const result = await client.followUser({ signerUuid:signer, targetFids:fids});
console.log(result);
```
Example output:
```json theme={"system"}
{
"success": true,
"details": [
{
"success": true,
"target_fid": 132
},
{
"success": true,
"target_fid": 78
},
{
"success": true,
"target_fid": 4262
},
{
"success": true,
"target_fid": 3602
},
]
}
```
That's it! You can now follow users who own a specific NFT easily with the Neynar SDK.
### 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!
# Farcaster Frames with Analytics using Neynar & Framejs
Source: https://docs.neynar.com/docs/framejs-farcaster-frames
In this guide, we’ll learn how to make a frame with the neynar SDK and Framejs, 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.
Let's get started!
## Creating a new frames project
We will use Next.js to build the frame in this guide, but feel free to use anything else if you want!
Enter this command in your terminal to create a new app:
```powershell PowerShell theme={"system"}
yarn create frames
```
Enter a name for your project, and choose the next template, and it will spin up a new project for you. Once the project is created install the dependencies:
```powershell PowerShell theme={"system"}
cd
bun install
```
### Using the neynar middleware
Create a new file `frame.ts` and add the following:
```typescript frame.ts theme={"system"}
import { neynarValidate } from "frames.js/middleware/neynar";
import { createFrames } from "frames.js/next";
export const frames = createFrames({
basePath: "/",
middleware: [
neynarValidate({
API_KEY: process.env.NEYNAR_API_KEY!,
}),
],
});
```
This creates an instance of the frame which uses neynarValidate as a middleware that we can re use in all the pages.
### Make sure to update the API key to your API key to get analytics
### Creating the frame home page
Create a new file `route.tsx` in the `app` folder and add the following:
```typescript route.tsx theme={"system"}
import { Button } from "frames.js/next";
import { frames } from "../frames";
const handleRequest = frames(async (ctx) => {
return {
target: "/result",
image: (
Choose your weapon
),
buttons: [
,
,
,
],
};
});
export const GET = handleRequest;
export const POST = handleRequest;
```
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 `target` prop.
### Make sure that you sign in using your warpcast account, so that the requests can be validated
Now, let's build the `/result` page. Create a new file called `result/route.tsx`
```typescript index.tsx theme={"system"}
import { frames } from "@/frames";
import { Button } from "frames.js/next";
const handleRequest = frames(async (ctx) => {
const rand = Math.floor(Math.random() * 3);
const choices = ["rock", "paper", "scissors"];
const userChoice = choices[(Number(ctx.pressedButton?.index) || 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 {
action: "/frames",
image: (
{userChoice} vs {computerChoice}
{msg}
),
buttons: [],
};
});
export const GET = handleRequest;
export const POST = handleRequest;
```
Here, we first get the button index from the ctx 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.
## Analytics
Since we are using neynar middleware with framejs, 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.
## Conclusion
This guide taught us how to create a rock-paper-scissors game on Farcaster frames using frames.js!
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)!
# Kafka Stream: Real-Time Farcaster Events via Neynar
Source: https://docs.neynar.com/docs/from-kafka-stream
Ingest hydrated events from a hosted Kafka stream (as compared to dehydrated events from gRPC hub)
With Kafka, you can subscribe to the same data that we use for sending webhook notifications
### To get entire dataset, Kafka is best paired with [one of our other data products](/docs/how-to-choose-the-right-data-product-for-you) (such as [Parquet](/docs/parquet) )
Kafka is not suitable to build a database with all of the data from Farcaster day 1. Our kafka topics currently keep data for 14 days. It's a good solution for streaming recent data in real time (P95 data latency of \<1.5s).
## Why
If you’re using Hub gRPC streaming, you’re getting dehydrated events that you have to put together yourself later to make useful (see [here](https://warpcast.com/rish/0x7c2997ec) for example). With Neynar’s Kafka stream, you get a fully hydrated event (e.g., [user.created](/docs/from-kafka-stream#data-schema)) that you can use in your app/product immediately. See the example between the gRPC hub event and the Kafka event below.
## How
* [Reach out](https://neynar.com/slack), we will create credentials for you and send them via 1Password.
* For authentication, the connection requires `SASL/SCRAM SHA512`.
* The connection requires TLS (sometimes called SSL for legacy reasons) for encryption.
* `farcaster-mainnet-events` is the aggregated topic that contains all events.
* `farcaster-mainnet-user-events` contains `user.created`, `user.updated` and `user.transferred`
* `farcaster-mainnet-cast-events` contains `cast.created` and `cast.deleted`
* `farcaster-mainnet-follow-events` contains `follow.created` and `follow.deleted`
* `farcaster-mainnet-reaction-events` contains `reaction.created` and `reaction.deleted`
* `farcaster-mainnet-signer-events` contains `signer.created` and `signer.deleted`
You can subcribe to any combination of the event-specific topics above, or to the `farcaster-mainnet-events` topic to get all events in one topic.
There are three brokers available over the Internet. Provide them all to your client:
* `b-1-public.tfmskneynar.5vlahy.c11.kafka.us-east-1.amazonaws.com:9196`
* `b-2-public.tfmskneynar.5vlahy.c11.kafka.us-east-1.amazonaws.com:9196`
* `b-3-public.tfmskneynar.5vlahy.c11.kafka.us-east-1.amazonaws.com:9196`
Most clients accept the brokers as a comma-separated list:
```bash cURL theme={"system"}
b-2-public.tfmskneynar.5vlahy.c11.kafka.us-east-1.amazonaws.com:9196,b-1-public.tfmskneynar.5vlahy.c11.kafka.us-east-1.amazonaws.com:9196,b-3-public.tfmskneynar.5vlahy.c11.kafka.us-east-1.amazonaws.com:9196
```
You can use `kcat` (formerly `kafkacat`) to test things locally:
```bash cURL theme={"system"}
brew install kcat
brew home kcat
```
```bash cURL theme={"system"}
kcat -L \
-b 'b-2-public.tfmskneynar.5vlahy.c11.kafka.us-east-1.amazonaws.com:9196,b-1-public.tfmskneynar.5vlahy.c11.kafka.us-east-1.amazonaws.com:9196,b-3-public.tfmskneynar.5vlahy.c11.kafka.us-east-1.amazonaws.com:9196' \
-X security.protocol=sasl_ssl \
-X sasl.mechanisms=SCRAM-SHA-512 \
-X sasl.username='user-YOURNAME' \
-X sasl.password='YOURPASSWORD'
```
Example output:
```bash cURL theme={"system"}
Metadata for farcaster-mainnet-events (from broker 1: sasl_ssl://b-1-public.tfmskneynar.5vlahy.c11.kafka.us-east-1.amazonaws.com:9196/1):
3 brokers:
broker 2 at b-2-public.tfmskneynar.5vlahy.c11.kafka.us-east-1.amazonaws.com:9196
broker 3 at b-3-public.tfmskneynar.5vlahy.c11.kafka.us-east-1.amazonaws.com:9196 (controller)
broker 1 at b-1-public.tfmskneynar.5vlahy.c11.kafka.us-east-1.amazonaws.com:9196
5 topics:
topic "farcaster-mainnet-user-events" with 2 partitions:
partition 0, leader 2, replicas: 2,3,1, isrs: 2,3,1
partition 1, leader 3, replicas: 3,1,2, isrs: 3,1,2
topic "farcaster-mainnet-reaction-events" with 2 partitions:
partition 0, leader 3, replicas: 3,2,1, isrs: 3,2,1
partition 1, leader 1, replicas: 1,3,2, isrs: 1,3,2
topic "farcaster-mainnet-cast-events" with 2 partitions:
partition 0, leader 2, replicas: 2,3,1, isrs: 2,3,1
partition 1, leader 3, replicas: 3,1,2, isrs: 3,1,2
topic "farcaster-mainnet-follow-events" with 2 partitions:
partition 0, leader 2, replicas: 2,3,1, isrs: 2,3,1
partition 1, leader 3, replicas: 3,1,2, isrs: 3,1,2
topic "farcaster-mainnet-events" with 2 partitions:
partition 0, leader 2, replicas: 2,3,1, isrs: 3,1,2
partition 1, leader 3, replicas: 3,1,2, isrs: 3,1,2
topic "farcaster-mainnet-signer-events" with 2 partitions:
partition 0, leader 1, replicas: 1,3,2, isrs: 1,3,2
partition 1, leader 2, replicas: 2,1,3, isrs: 2,1,3
```
The topics you see will vary depending on your access.
## Consumer nodejs example
## Data schema
```typescript user.created theme={"system"}
// _when a new user is created on the network_
interface Bio {
text: string;
}
interface Profile {
bio: Bio;
}
interface VerifiedAddresses {
eth_addresses: string[];
sol_addresses: string[];
}
interface UserCreatedData {
object: "user";
fid: number;
custody_address: string;
username: string;
display_name: string | null;
pfp_url: string | null;
profile: Profile;
follower_count: number;
following_count: number;
verifications: string[];
verified_addresses: VerifiedAddresses;
active_status: "inactive" | "active";
power_badge: boolean;
event_timestamp: string; // ISO 8601 format
}
interface CustomHeaders {
"x-convoy-message-type": "broadcast";
}
interface UserCreatedEvent {
event_type: "user.created";
data: UserCreatedData;
custom_headers: CustomHeaders;
idempotency_key?: string;
}
```
```typescript user.updated theme={"system"}
// _when a user profile field is updated_
interface Bio {
text: string;
}
interface Profile {
bio: Bio;
}
interface VerifiedAddresses {
eth_addresses: string[];
sol_addresses: string[];
}
interface UserUpdatedData {
object: "user";
fid: number;
custody_address: string;
username: string;
display_name: string;
pfp_url: string;
profile: Profile;
follower_count: number;
following_count: number;
verifications: string[];
verified_addresses: VerifiedAddresses;
active_status: "inactive" | "active";
power_badge: boolean;
event_timestamp: string; // ISO 8601 format
}
interface CustomHeaders {
"x-convoy-message-type": "broadcast";
}
interface UserUpdatedEvent {
event_type: "user.updated";
data: UserUpdatedData;
custom_headers: CustomHeaders;
idempotency_key?: string;
}
```
```typescript cast.created theme={"system"}
// _when a new cast is created_
export interface CustomHeaders {
"x-convoy-message-type": "broadcast"
}
interface Fid {
fid?: number | null;
}
interface User {
object: string;
fid: number;
custody_address: string;
username: string;
display_name: string;
pfp_url: string;
profile: {
bio: {
text: string;
};
};
follower_count: number;
following_count: number;
verifications: string[];
verified_addresses: {
eth_addresses: string[];
sol_addresses: string[];
};
active_status: string;
power_badge: boolean;
}
interface EmbedUrlMetadata {
content_type?: string | null;
content_length?: number | null;
}
interface EmbedUrl {
url: string;
metadata?: EmbedUrlMetadata;
}
interface CastId {
fid: number;
hash: string;
}
interface EmbedCastId {
cast_id: CastId;
}
type EmbeddedCast = EmbedUrl | EmbedCastId;
interface EventData {
object: "cast";
hash: string;
parent_hash?: string | null;
parent_url?: string | null;
root_parent_url?: string | null;
parent_author?: Fid;
author: User;
mentioned_profiles?: User[];
text: string;
timestamp: string; // ISO 8601 format
embeds: EmbeddedCast[];
}
export interface CastCreatedEvent {
event_type: "cast.created"
data: EventData
custom_headers: CustomHeaders
idempotency_key?: string
}
```
```typescript cast.deleted theme={"system"}
// _when a cast is deleted_
export interface CustomHeaders {
"x-convoy-message-type": "broadcast"
}
interface Fid {
fid?: number | null;
}
interface User {
object: string;
fid: number;
custody_address: string;
username: string;
display_name: string;
pfp_url: string;
profile: {
bio: {
text: string;
};
};
follower_count: number;
following_count: number;
verifications: string[];
verified_addresses: {
eth_addresses: string[];
sol_addresses: string[];
};
active_status: string;
power_badge: boolean;
}
interface EmbedUrlMetadata {
content_type?: string | null;
content_length?: number | null;
}
interface EmbedUrl {
url: string;
metadata?: EmbedUrlMetadata;
}
interface CastId {
fid: number;
hash: string;
}
interface EmbedCastId {
cast_id: CastId;
}
type EmbeddedCast = EmbedUrl | EmbedCastId;
interface EventData {
object: "cast";
hash: string;
parent_hash?: string | null;
parent_url?: string | null;
root_parent_url?: string | null;
parent_author?: Fid;
author: User;
mentioned_profiles?: User[];
text: string;
timestamp: string; // ISO 8601 format
embeds: EmbeddedCast[];
}
export interface CastDeletedEvent {
event_type: "cast.deleted"
data: EventData
custom_headers: CustomHeaders
idempotency_key?: string
}
```
```typescript follow.created theme={"system"}
// _when a user follows another user_
interface UserDehydrated {
object: "user_dehydrated";
fid: number;
username: string;
}
interface AppDehydrated {
object: "user_dehydrated";
fid: number;
}
interface EventData {
object: "follow";
event_timestamp: string; // ISO 8601 format
timestamp: string; // ISO 8601 format with timezone
user: UserDehydrated;
target_user: UserDehydrated;
app: AppDehydrated | null; // null if not signer isn't available
}
interface CustomHeaders {
"x-convoy-message-type": "broadcast";
}
interface FollowCreatedEvent {
event_type: "follow.created";
data: EventData;
custom_headers: CustomHeaders;
idempotency_key?: string
}
```
```typescript follow.deleted theme={"system"}
// _when a user unfollows another user_
interface UserDehydrated {
object: "user_dehydrated";
fid: number;
username: string;
}
interface EventData {
object: "unfollow";
event_timestamp: string; // ISO 8601 format
timestamp: string; // ISO 8601 format with timezone
user: UserDehydrated;
target_user: UserDehydrated;
}
interface CustomHeaders {
"x-convoy-message-type": "broadcast";
}
interface FollowDeletedEvent {
event_type: "follow.deleted";
data: EventData;
custom_headers: CustomHeaders;
idempotency_key?: string
}
```
```typescript reaction.created theme={"system"}
// _when a reaction is added to a cast_
interface UserDehydrated {
object: "user_dehydrated";
fid: number;
username: string;
}
interface AppDehydrated {
object: "user_dehydrated";
fid: number;
}
interface URIDehydrated {
object: "uri_dehydrated";
uri: string;
}
interface CastDehydrated {
object: "cast_dehydrated";
hash: string;
author: UserDehydrated;
}
interface EventData {
object: "reaction";
event_timestamp: string; // ISO 8601 format
timestamp: string; // ISO 8601 format with timezone
reaction_type: number;
user: UserDehydrated;
target: CastDehydrated | URIDehydrated;
app: AppDehydrated | null; // null if not signer isn't available
}
interface CustomHeaders {
"x-convoy-message-type": "broadcast";
}
interface ReactionCreatedEvent {
event_type: "reaction.created";
data: EventData;
custom_headers: CustomHeaders;
idempotency_key?: string;
}
```
```typescript reaction.deleted theme={"system"}
// _when a reaction is removed from a cast_
interface UserDehydrated {
object: "user_dehydrated";
fid: number;
username: string;
}
interface URIDehydrated {
object: "uri_dehydrated";
uri: string;
}
interface CastDehydrated {
object: "cast_dehydrated";
hash: string;
author: UserDehydrated;
}
interface EventData {
object: "reaction";
event_timestamp: string; // ISO 8601 format
timestamp: string; // ISO 8601 format with timezone
reaction_type: number;
user: UserDehydrated;
target: CastDehydrated | URIDehydrated;
}
interface CustomHeaders {
"x-convoy-message-type": "broadcast";
}
interface ReactionDeletedEvent {
event_type: "reaction.deleted";
data: EventData;
custom_headers: CustomHeaders;
idempotency_key?: string;
}
```
# Build Farcaster Apps with Neynar API
Source: https://docs.neynar.com/docs/getting-started-with-neynar
Complete beginner's guide to building on Farcaster with Neynar
## Introduction
Farcaster is a protocol for building decentralized social apps. Neynar makes it easy to build on Farcaster.
This tutorial covers
* fundamentals of Farcaster protocol
* getting your Neynar API key
* understanding core concepts like FIDs and Casts
* tutorial steps to start developing social applications
## Basic understanding of Farcaster
Farcaster is a decentralized social protocol. Here are a few of the primary Farcaster primitives that will be helpful to keep in mind as you dive in:
Every user on Farcaster is represented by a permanent *FID*, the user's numerical identifier. All user profile data for this FID, e.g., username, display name, bio, etc., are stored on the Farcaster protocol and mapped to this FID.
Users can broadcast information to the protocol in units of information called "casts". It's somewhat similar to a tweet on Twitter/X. Each cast has a unique "hash".
Users can follow each other to see casts from them. This creates a social graph for each user on Farcaster.
There's more to this, but let's start with this. All the above data is open, decentralized, and available on Farcaster hubs. Neynar makes interfacing with this data relatively trivial.
In this tutorial, we will learn how to use the above primitives to fetch a simple *feed* of casts for a given user.
## Get Neynar API key
Don't have an API key yet? [Create an account](https://neynar.com) to get started.
Don't hesitate to reach out to us on our [channel](https://warpcast.com/~/channel/neynar) or [Slack](https://neynar.com/slack) with any questions!
### If building a mini app, you can go straight to [Set up mini app in \< 60 s](/docs/create-farcaster-miniapp-in-60s)
## Set up Neynar SDK
Neynar [`nodejs` SDK](https://github.com/neynarxyz/nodejs-sdk) is an easy way to use the APIs. This section only needs to be done once when setting up the SDK for the first time.
To install the Neynar TypeScript SDK:
```typescript yarn theme={"system"}
yarn add @neynar/nodejs-sdk
```
```typescript npm theme={"system"}
npm install @neynar/nodejs-sdk
```
```typescript pnpm theme={"system"}
pnpm install @neynar/nodejs-sdk
```
```typescript bun theme={"system"}
bun add @neynar/nodejs-sdk
```
To get started, initialize the client in a file named `index.ts`:
```typescript Typescript theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const config = new Configuration({
apiKey: "YOUR_NEYNAR_API_KEY",
});
const client = new NeynarAPIClient(config);
```
Depending on your build environment, you might also need the following two steps:
check the `type` field in package.json. Since we're using ES6 modules, you may need to set it to "module".
```json package.json theme={"system"}
{
"scripts": {
"start": "node --loader ts-node/esm index.ts"
},
"type": "module", // <-- set to module if needed
"dependencies": {
// this is for illustration purposes, the version numbers will depend on when you do this tutorial
"@neynar/nodejs-sdk": "^2.0.5",
"ts-node": "^10.9.2",
"typescript": "^5.6.3"
}
}
```
If you hit errors, try adding a `tsconfig.json` file in the directory to help with typescript compilation
```typescript Typescript theme={"system"}
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node",
"target": "ESNext",
"esModuleInterop": true,
"skipLibCheck": true
},
"ts-node": {
"esm": true
}
}
```
Your directory should have the following:
* node\_modules
* index.ts
* package-lock.json
* package.json
* tsconfig.json (optional)
* yarn.lock
## Fetch Farcaster data using Neynar SDK
### Fetching feed
To fetch the feed for a user, you need to know who the user is following and then fetch casts from those users. Neynar abstracts away all this complexity. Put in the `fid` of the user in the `fetchFeed` function and get a feed in response.
In this example, we will fetch the feed for [Dan Romero](https://warpcast.com/dwr.eth) . This is the feed Dan would see if he were to log into a client that showed a feed from people he followed in a reverse chronological order.
```typescript Typescript theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const config = new Configuration({
apiKey: "YOUR_NEYNAR_API_KEY",
});
const client = new NeynarAPIClient(config);
import { FeedType } from "@neynar/nodejs-sdk/build/api";
const feedType = FeedType.Following;
const fid = 3;
const withRecasts = true;
const limit = 50;
const viewerFid = 6131;
client
.fetchFeed({ feedType, fid, withRecasts, limit, viewerFid })
.then((response) => {
console.log("response:", response);
});
```
You can now run this code by opening this folder in the terminal and running it.
```typescript Typescript theme={"system"}
yarn start
```
Depending on your machine, typescript might take a few seconds to compile. Once done, it should print the output to your console. Something like below:
```typescript Typescript theme={"system"}
User Feed: {
casts: [
{
object: 'cast',
hash: '0x5300d6bd8f604c0b5fe7d573e02bb1489362f4d3',
author: [Object],
thread_hash: '0x5300d6bd8f604c0b5fe7d573e02bb1489362f4d3',
parent_hash: null,
parent_url: null,
root_parent_url: null,
parent_author: [Object],
text: 'https://open.spotify.com/track/5oQcOu1omDykbIPSdSQQNJ?si=2qMjk-fESMmxqCoAxTsPmw',
timestamp: '2024-11-14T04:57:23.000Z',
embeds: [Array],
channel: null,
reactions: [Object],
replies: [Object],
mentioned_profiles: [],
viewer_context: [Object]
},
]
}
```
You've successfully fetched the feed for a user using a simple function call!
*Future reading: you can fetch many different kinds of feeds. See [Feed](/reference/fetch-user-following-feed) APIs.*
### Fetching user profile data
Now, let's fetch data about a user. We will take an FID and fetch data for that user. Here's how to do it using the SDK:
```javascript Javascript theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const config = new Configuration({
apiKey: "YOUR_NEYNAR_API_KEY",
});
const client = new NeynarAPIClient(config);
const fids = [2, 3];
const viewerFid = 6131;
client.fetchBulkUsers({ fids, viewerFid }).then((response) => {
console.log("response:", response);
});
```
You can run this in your terminal similar to above by typing in:
```typescript Typescript theme={"system"}
yarn start
```
It should show you a response like the one below:
```typescript Typescript theme={"system"}
User: {
users: [
{
object: 'user',
fid: 3,
username: 'dwr.eth',
display_name: 'Dan Romero',
pfp_url: 'https://imagedelivery.net/BXluQx4ige9GuW0Ia56BHw/bc698287-5adc-4cc5-a503-de16963ed900/original',
custody_address: '0x6b0bda3f2ffed5efc83fa8c024acff1dd45793f1',
profile: [Object],
follower_count: 489109,
following_count: 3485,
verifications: [Array],
verified_addresses: [Object],
verified_accounts: [Array],
power_badge: true
}
]
}
```
*Future reading: you can also fetch data about a user by using their wallet address or username as identifiers. See APIs for that here: [User by wallet address](/docs/fetching-farcaster-user-based-on-ethereum-address), [By username](/reference/lookup-user-by-username).*
## You're ready to build!
Now that you can fetch user and cast data, you're ready to dive in further and start making your first Farcaster application. Read through our [tutorials](/docs) and [API reference](/reference) for more.
If you have questions or feedback, please reach out to us on our [Developer Slack](https://neynar.com/slack) .
# Storage Units Allocation
Source: https://docs.neynar.com/docs/getting-storage-units-allocation-of-farcaster-user
Fetch data about a user's storage allocation on Farcaster network with Neynar
### Related API: [Allocation of user](/reference/lookup-user-storage-allocations)
In the Farcaster protocol, a storage unit is a measure used to allocate and track the amount of data that a user (identified by their Farcaster ID or fid) can store within the network. This system is critical for managing the storage resources of the Farcaster network effectively and ensuring that the network remains scalable and efficient.
The specific allocation of storage per unit varies depending on the type of data being stored.
Here's the list of storage allocations per unit:
* 100 cast messages
* 200 reaction messages
* 200 link messages
* 25 user\_data messages
* 5 verifications messages
* 2 username\_proof messages
The Storage Registry contract controls and tracks the allocation. This contract records the storage allocated to each fid, denominated in integer units.
If a user exceeds their storage allocation, Farcaster Hub prunes their old messages. Users can buy more storage units by sending a transaction to the Storage Registry contract or using an app like [caststorage.com](https://caststorage.com/).
This guide demonstrates how to use the Neynar SDK to retrieve a Farcaster user's storage usage and allocation.
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 theme={"system"}
// 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);
```
Then, fetch the storage usage and allocation:
```javascript Javascript theme={"system"}
const rishFID = 194;
const storageUsage = await client.lookupUserStorageUsage({fid:rishFID});
console.log(storageUsage);
```
Example output:
```json theme={"system"}
{
object: "storage_usage",
user: {
object: "user_dehydrated",
fid: 194
},
total_active_units: 2,
casts: {
object: "storage",
used: 3707,
capacity: 10000
},
reactions: {
object: "storage",
used: 4984,
capacity: 5000
},
links: {
object: "storage",
used: 472,
capacity: 5000
},
verifications: {
used: 2,
capacity: 25
},
username_proofs: {
used: 1,
capacity: 5
},
signers: {
used: 17,
capacity: 1000
}
}
```
To fetch the storage allocation of a user, use the `lookupUserStorageAllocation` function:
```javascript Javascript theme={"system"}
const storageAllocation = await client.lookupUserStorageAllocations({fid:rishFID});
console.log(storageAllocation);
```
Example output:
```json theme={"system"}
{
total_active_units: 2,
allocations: [
{
object: "storage_allocation",
user: [Object ...],
units: 2,
expiry: "2024-08-28T22:23:31.000Z",
timestamp: "2023-08-29T22:23:31.000Z"
}
]
}
```
That's it! You can now look at the storage usage and allocation of any Farcaster user.
### 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!
# Create a Farcaster Account with Wallet Integration
Source: https://docs.neynar.com/docs/guide-to-creating-a-farcaster-account-with-wallet-integration
This document outlines the steps to successfully create a Farcaster account without having a end user mnemonic.
## Prerequisites
* A browser with a wallet extension (e.g., MetaMask, Coinbase, etc.) installed.
* If you are using a Privy wallet, you can see this [guide](https://neynar.notion.site/Creating-new-accounts-with-embedded-wallets-14a655195a8b80999ccec0aa635b23af?pvs=4) written by community member [jpfraneto](https://warpcast.com/jpfraneto.eth), which includes source code for getting started.
* Access to the Farcaster API and ID Registry smart contract.
* Familiarity with JavaScript and Ethereum wallet interactions.
* **App Wallet with `wallet_id`** - Required for FID fetching and account registration. See [Managing Onchain Wallets](/docs/managing-onchain-wallets) to create one in the developer portal.
* **Funded wallet with \$5+ ETH on Optimism** - Covers initial account pre-registration on first call
**New to App Wallets?** The `/v2/farcaster/user/fid` endpoint requires a [`x-wallet-id` header](/docs/managing-onchain-wallets) to cover the costs of FID registration. Create your app wallet in the [Developer Portal](https://dev.neynar.com) first!
**Recommended Setup Steps:**
1. Log in to [dev.neynar.com](https://dev.neynar.com) and select your app
2. Activate a wallet from the "App Wallet" tab
3. Fund the wallet with \$5+ ETH on Optimism
4. Add the `x-wallet-id` header to your API requests (treat it like a secret)
## Step 1: Connect a Wallet
To connect a wallet in the browser:
1. Check if the browser supports `window.ethereum`.
2. Use `eth_requestAccounts` to request wallet connection.
### Code Example:
```javascript Javascript theme={"system"}
if (typeof window === "undefined" || typeof window.ethereum === "undefined") {
console.error("No wallet is installed.");
window.open("https://metamask.io/download/", "_blank");
return;
}
const accounts = await window.ethereum.request({
method: "eth_requestAccounts",
});
if (accounts.length === 0) {
console.error("No wallet detected. Please log in to a wallet.");
return;
}
const userAddress = accounts[0];
console.log("Wallet connected:", userAddress);
```
## Step 2: Switch to the Optimism Network
To interact with the ID Registry contract, ensure the wallet is on the Optimism network.
### Code Example:
```javascript Javascript theme={"system"}
try {
await window.ethereum.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: "0xA" }], // Optimism chainId in hex
});
console.log("Switched to Optimism network.");
} catch (error) {
console.error("Failed to switch to Optimism network:", error);
return;
}
```
## Step 3: Fetch FID
Use the [`GET-/v2/farcaster/user/fid`](/reference/get-fresh-account-fid) endpoint to retrieve the FID of the account that will be transferred to the wallet's address. **This endpoint requires a [`x-wallet-id` header](/docs/managing-onchain-wallets).**
**Cold Start (First Call Only):** The first time you call this endpoint with a new `wallet_id`, it will take approximately **1 minute** to complete as it pre-registers a few FID accounts. Subsequent calls will be fast (\< 1 second). Make sure your wallet has **\$5+ ETH on Optimism** before the first call.
**Wallet Consistency Required:** The same `wallet_id` used here must also be used in Step 6 when calling `POST /v2/farcaster/user/`. Using different wallets will result in an error.
### Code Example:
```javascript Javascript theme={"system"}
// Your backend API should forward the wallet_id header to Neynar
const response = await fetch("/api/user", {
headers: {
// Your backend should include:
// 'x-api-key': 'YOUR_NEYNAR_API_KEY',
// 'x-wallet-id': 'your-wallet-id'
}
});
if (!response.ok) {
console.error("Failed to fetch FID from the API.");
return;
}
const data = await response.json();
const fid = data.fid;
if (!fid) {
console.error("FID not found in the API response.");
return;
}
console.log("FID fetched:", fid);
```
**Backend Required:** The FID fetch should be done from your backend, not the frontend, to keep your `wallet_id` secure. Your backend should call Neynar's API with the `x-wallet-id` header.
## Step 4: Generate `signTypedData` with Viem
To generate a signature for FID registration:
1. Fetch the nonce from the ID Registry contract.
2. Create EIP-712 typed data and request a signature from the wallet.
### Code Example:
```javascript Javascript theme={"system"}
import { createWalletClient, custom, publicActions } from "viem";
import { optimism } from "viem/chains";
import { ID_REGISTRY_ABI, ID_REGISTRY_ADDRESS } from "./constants";
const wallet = createWalletClient({
chain: optimism,
transport: custom(window.ethereum),
}).extend(publicActions);
const nonce = await wallet.readContract({
address: ID_REGISTRY_ADDRESS,
abi: ID_REGISTRY_ABI,
functionName: "nonces",
args: [userAddress],
});
const now = Math.floor(Date.now() / 1000);
const deadline = now + 3600; // 1 hour from now
const domain = {
name: "Farcaster IdRegistry",
version: "1",
chainId: 10,
verifyingContract: ID_REGISTRY_ADDRESS,
};
const types = {
Transfer: [
{ name: "fid", type: "uint256" },
{ name: "to", type: "address" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
};
const message = {
fid: BigInt(fid),
to: userAddress,
nonce: BigInt(nonce),
deadline: BigInt(deadline),
};
const signature = await wallet.signTypedData({
account: userAddress,
domain,
types,
primaryType: "Transfer",
message,
});
console.log("Signature:", signature);
```
## Step 5: Check `fname` Availability
Before registering a username, check if it is available using the [`GET /v2/farcaster/fname/availability`](/reference/is-fname-available) endpoint.
### Code Example:
```javascript Javascript theme={"system"}
const fname = "desired_username";
const response = await fetch(`/api/user/fname/availability?fname=${fname}`);
if (!response.ok) {
console.error("Failed to check fname availability.");
return;
}
const data = await response.json();
const isAvailable = data.available;
console.log("Fname availability:", isAvailable);
```
## Step 6: Call the [`POST-/v2/farcaster/user`](/reference/register-account) Endpoint
Submit the required data to create the Farcaster account. **This endpoint also requires a [`x-wallet-id` header](/docs/managing-onchain-wallets).**
### Code Example:
```javascript Javascript theme={"system"}
const metadata = {
bio: "Your bio",
pfp_url: "https://example.com/profile-pic.jpg",
url: "https://yourwebsite.com",
display_name: "Your Display Name",
location: {
latitude: 40.7128,
longitude: -74.006,
},
};
// Your backend should handle this with wallet_id
const response = await fetch("/api/user", {
method: "POST",
headers: {
"Content-Type": "application/json",
// Your backend should include:
// 'x-api-key': 'YOUR_NEYNAR_API_KEY',
// 'x-wallet-id': 'your-wallet-id'
},
body: JSON.stringify({
fid,
signature,
requestedUserCustodyAddress: userAddress,
deadline,
fname,
metadata,
}),
});
if (!response.ok) {
const errorData = await response.json();
console.error("Error creating account:", errorData.message);
return;
}
console.log("Account created successfully!");
```
**Backend Required:** Like Step 3, this registration should be done from your backend to keep your `wallet_id` secure. Your backend calls Neynar's API with the `x-wallet-id` header.
## Conclusion
By following these steps, you can create an account using the user's wallet. (No mnemonic required)
# Frame Validation
Source: https://docs.neynar.com/docs/how-to-a-frame-action-against-farcaster-hub-with-neynar-api
Validate incoming frame actions to get genuine data
Frames are mini-apps inside Farcaster casts. To read more about frames, check out the [Frames documentation](https://docs.farcaster.xyz/learn/what-is-farcaster/frames).
Frame actions are POST request sent to a Frame server, and is unauthenticated by default. Checking the POST request payload against the Hub is a necessary step to ensure the request is valid.
This guide demonstrates how to verify a frame action payload against the Hub with 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 theme={"system"}
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);
```
Frame server hosts frames and handles Frame actions. Here's what POST request payload looks like for a Frame action:
```javascript Javascript theme={"system"}
const payload = {
untrustedData: {
fid: 4286,
url: "https://frame-server-example.com",
messageHash: "0x8e95825cca8e81db6b9bd64bfdf626f7f172f02e",
timestamp: 1707640145000,
network: 1,
buttonIndex: 1,
castId: {
fid: 4286,
hash: "0x0000000000000000000000000000000000000001",
},
},
trustedData: {
messageBytes:
"0a60080d10be2118d1bee82e20018201510a3268747470733a2f2f726e766d622d3130332d3132312d3133382d3131332e612e667265652e70696e6767792e6f6e6c696e6510011a1908be211214000000000000000000000000000000000000000112148e95825cca8e81db6b9bd64bfdf626f7f172f02e1801224096dd456e2752a358b33e19cfa833478974ce53379939e721e2875df14df4237a3ad3c9c9a27768ab59ea360df34893111c9aadc819a972d8ffd5f66976ffc00328013220836bf050647d18d304124823aaefa7c82eef99cbab2a120d8a8fe8e6d391929d",
},
};
```
Anyone can spoof the request and send it to the Frame server. The check whether it's a valid request (ie. fid 4286 pressing buttonIndex 1 on a specific cast), we need to verify the request against the Hub.
```javascript Javascript theme={"system"}
const result = await client.validateFrameAction({
messageBytesInHex: payload.trustedData.messageBytes,
});
console.log(result);
```
```json json theme={"system"}
{
"valid": true,
"action": {
"object": "validated_frame_action",
"interactor": {
"object": "user",
"fid": 4286,
"custody_address": "0x0076f74cc966fdd705ded40df8ab86604e4b5759",
"username": "pixel",
"display_name": "vincent",
"pfp_url": "https://lh3.googleusercontent.com/WuVUEzf_r3qgz3cf4mtkXpLat5zNZbxKjoV-AldwfCQ8-_Y5yfWScMBEalpvbVgpt4ttXruxTD9GM983-UJBzMil5GRQF1qZ_aMY",
"profile": {},
"follower_count": 34997,
"following_count": 905,
"verifications": [
"0x0076f74cc966fdd705ded40df8ab86604e4b5759",
"0xb7254ce5cb61f69b3fc120b85f0f6b90d871036c"
],
"verified_addresses": {
"eth_addresses": [
"0x0076f74cc966fdd705ded40df8ab86604e4b5759",
"0xb7254ce5cb61f69b3fc120b85f0f6b90d871036c"
],
"sol_addresses": [
"7rhxnLV8C77o6d8oz26AgK8x8m5ePsdeRawjqvojbjnQ",
"8g4Z9d6PqGkgH31tMW6FwxGhwYJrXpxZHQrkikpLJKrG"
],
},
"active_status": "active"
},
"tapped_button": {
"index": 1
},
"input": {
"text": ""
},
"url": "https://frame-server-example.com",
"cast": {
"object": "cast_dehydrated",
"hash": "0x0000000000000000000000000000000000000001",
"fid": 4286
},
"timestamp": "2024-02-11T08:29:05.000Z"
},
"signature_temporary_object": {
"note": "temporary object for signature validation, might be removed in future versions. do not depend on this object, reach out if needed.",
"hash": "0x8e95825cca8e81db6b9bd64bfdf626f7f172f02e",
"hash_scheme": "HASH_SCHEME_BLAKE3",
"signature": "lt1FbidSo1izPhnPqDNHiXTOUzeZOech4odd8U30I3o608nJondoq1nqNg3zSJMRHJqtyBmpctj/1fZpdv/AAw==",
"signature_scheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x836bf050647d18d304124823aaefa7c82eef99cbab2a120d8a8fe8e6d391929d"
}
}
```
And that's it, a valid Frame action! You've successfully verified a frame action payload against the Hub with Neynar SDK. Note that frame payloads are only available in responses, not in initial requests. Attempts to fetch a payload during a request will result in an error. If you want to fetch cast while doing frame validation, refer to our [How to get cast information from URL](/docs/how-to-get-cast-information-from-url) guide.
### 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!
# Validate Farcaster Frames with Neynar SDK
Source: https://docs.neynar.com/docs/how-to-build-farcaster-frames-with-neynar
Build Farcaster frames 10x faster than starting from scratch with Neynar's frame validation API. Learn to validate frame actions, get user interaction details, and test frames locally with ngrok integration for rapid development.
## How to Validate Farcaster Frames
Building a new Farcaster Frame? Read the [Farcaster Mini Apps Specification](https://miniapps.farcaster.xyz/docs/specification) to get started with the latest Mini Apps format, or check the [legacy Frames documentation](https://docs.farcaster.xyz/reference/frames/spec) for older Frame formats.
Once you have set up your Frame server with the right meta tags, you will want to know which users interacted with your Frame so that you can take the next appropriate action.
1. Use [Validate frame action](/reference/validate-frame-action) to validate the incoming user interaction and get details about the interacting user, cast author and cast itself
2. To test Frames on your local machine, set up [ngrok](https://ngrok.com/download) and use ngrok as your Frame's POST url.
More open source Frame resources from @base in [onchaintoolkit](https://github.com/coinbase/onchainkit?tab=readme-ov-file#getframevalidatedmessage)
Now start making some frames and let us know if you have questions at [@neynar](https://warpcast.com/~/channel/neynar)!
# Choose Among Data Products
Source: https://docs.neynar.com/docs/how-to-choose-the-right-data-product-for-you
Pick between pulling or pushing data in the format that works for you
## Why
Developers can focus on what they are building instead of running a hub and replicator which can be significant cost & effort over time
## How to pick the right product for your team
| Product | Pros | Cons |
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| [Parquet exports](/docs/parquet) | Developer ingests [Apache Parquet](https://parquet.apache.org/) files and can read/write to their own db as needed | Need to set up parquet ingestion workflow, open source repo available. See [here](/docs/parquet). |
| | No need for developer to share access to database with Neynar | |
| [Hosted SQL](/docs/how-to-query-neynar-sql-playground-for-farcaster-data) | Directly write sql queries against a postgres hosted db, no need to set up anything additional | Developer has no write access to db creating new indexes, etc. requires reaching out to Neynar no changes to overall table schema |
| [Indexer service - pipe Farcaster data](/docs/indexer-service-pipe-farcaster-data) | Neynar writes to a db on the developer’s side, no need for developer to manage hubs or replicators Neynar handles all hub and replicator related upgrades and maintenance | Developer needs to have a db that they can share write access to |
| | Developer has flexibility to let Neynar pipe specific tables instead of all FC data | |
| | Developer can manage the db as they see fit — create new indexes, etc. | |
| [Kafka stream](/docs/from-kafka-stream) | Good real time complement to services like Parquet -- backfill with Parquet and ingest real time with Kafka stream | Need to set up Kafka ingestion. See open source code [here](/docs/from-kafka-stream). |
# How to Contribute to @neynar/nodejs-sdk
Source: https://docs.neynar.com/docs/how-to-contribute-to-neynarnodejs-sdk
Step-by-step guide for contributing to the Neynar Node.js SDK - from setup to submitting pull requests
```bash Shell theme={"system"}
git clone [email protected]:neynarxyz/nodejs-sdk.git
```
```bash Shell theme={"system"}
git submodule update --init --recursive
```
```bash Shell theme={"system"}
yarn install
```
# Create a Farcaster Client with Next.js
Source: https://docs.neynar.com/docs/how-to-create-a-client
This guide will look at creating a Farcaster client using Next.js and the Neynar React SDK.
For this guide, we'll go over:
Before we begin, you can access the [complete source code](https://github.com/avneesh0612/neynar-client) 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 theme={"system"}
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:
```powershell npm theme={"system"}
npm i @neynar/react @neynar/nodejs-sdk
```
```powershell yarn theme={"system"}
yarn add @neynar/react @neynar/nodejs-sdk
```
```powershell bash theme={"system"}
bun add @neynar/react @neynar/nodejs-sdk
```
Once the dependencies are installed you can open it in your favourite and we can start working on the client!
### Setting up Sign-in with neynar
Head over to the `layout.tsx` file and wrap your app in a `NeynarContextProvider` like this:
```typescript layout.tsx theme={"system"}
"use client";
import "./globals.css";
import { NeynarContextProvider, Theme } from "@neynar/react";
import "@neynar/react/dist/style.css";
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.
Now, let's create a header component where we can add the sign-in with Neynar button.
So, create a new `components/Header.tsx` file and add the following:
```typescript Header.tsx theme={"system"}
"use client";
import { NeynarAuthButton } from "@neynar/react";
import Link from "next/link";
export const Header: FC = () => {
return (
FarCaster App
);
};
```
We'll add the header to the `layout.tsx` file since we are going to need it on all the pages:
```typescript layout.tsx theme={"system"}
"use client";
import "./globals.css";
import { NeynarContextProvider, Theme } from "@neynar/react";
import "@neynar/react/dist/style.css";
import { Header } from "@/components/Header";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
{},
onSignout() {},
},
}}
>
{children}
);
}
```
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 start working on showing the feed!
### Building the feed
In the `page.tsx` file add the following:
```typescript page.tsx theme={"system"}
"use client";
import { NeynarFeedList, useNeynarContext } from "@neynar/react";
export default function Home() {
const { user } = useNeynarContext();
return (
);
}
```
Here, we are using the `NeynarFeedList` component to show the trending casts if the user is not signed in, but, if they are signed in we show the following feed based on their fid.
Now, let's also show the list of channels that the user is following.
### Building the channels list and channel feed
To get the list of channels that a user is following we'll use the neynar APIs. So, let's first initialise the client in a new `lib/neynarClient.ts` file like this:
```typescript neynarClient.ts theme={"system"}
import { NeynarAPIClient } from "@neynar/nodejs-sdk";
const neynarClient = new NeynarAPIClient(process.env.NEYNAR_API_KEY!);
export default neynarClient;
```
### Make sure to add the NEYNAR\_API\_KEY to your .env file.
Then, create a new file `api/channels/route.ts` in the `app` directory and add the following:
```typescript route.ts theme={"system"}
import neynarClient from "@/lib/neynarClient";
import { NextResponse } from "next/server";
export const GET = async (req: Request) => {
try {
const { searchParams } = new URL(req.url);
const fid = searchParams.get("fid");
const channels = await neynarClient.fetchUserChannels(Number(fid));
return NextResponse.json(channels, { status: 200 });
} catch (error) {
return NextResponse.json(
{ error: (error as any).response?.data?.message },
{ status: (error as any).response?.status || 500 }
);
}
};
```
This will fetch the channels a user is following using the neynarClient and return it.
Let's now use it on the home page. Head back to the `page.tsx` file and add the following:
```typescript page.tsx theme={"system"}
"use client";
import { Channel } from "@neynar/nodejs-sdk/build/neynar-api/v2";
import { NeynarFeedList, useNeynarContext } from "@neynar/react";
import Link from "next/link";
import { useEffect, useState } from "react";
export default function Home() {
const { user } = useNeynarContext();
const [channels, setChannels] = useState();
const fetchChannels = async () => {
if (!user) {
return;
}
const response = await fetch(`/api/channels?fid=${user?.fid}`);
const data = await response.json();
setChannels(data);
};
useEffect(() => {
if (user) {
fetchChannels();
}
}, [user]);
return (
{user && (
);
}
```
Here, we are now fetching the list of channels that the user follows and creating links with the name of the channel. These link to another page which we are yet to build but you should be able to see the list of channels now!
Now, let's build out the channel page as well which will show the feed of a specific channel.
Create a new `channel/[channelId]/page.tsx` file in the `app` folder and add the following:
```typescript page.tsx theme={"system"}
import { NeynarFeedList } from "@/components/Neynar";
export default async function Page({
params: { channelId },
}: {
params: { channelId: string };
}) {
return (
{channelId}
);
}
```
Here, you can see that we are importing the component from a `@/components/Neynar` file and not the package directly because it is a client component. So, create a new `components/Neynar.tsx` file and add the following:
```typescript Neynar.tsx theme={"system"}
"use client";
import { NeynarProfileCard, NeynarFeedList } from "@neynar/react";
export { NeynarProfileCard, NeynarFeedList };
```
This will filter the feed based on the channelId and show only the casts made in that channel. If you go ahead and click on one of the channels you'll be able to see something like this:
### Building user profiles
Let's also build a profile page for every user which shows their profile card and the casts they have created.
Create a new file `profile/[username]/page.tsx` in the `app` folder and add the following:
```typescript page.tsx theme={"system"}
import { NeynarProfileCard, NeynarFeedList } from "@/components/Neynar";
import neynarClient from "@/lib/neynarClient";
async function getData(username: string) {
const user = await neynarClient.lookupUserByUsername(username);
return { user: user.result.user };
}
export default async function Page({
params: { username },
}: {
params: { username: string };
}) {
const { user } = await getData(username);
return (
```
### Example above is for web. See \[React
Implementation]\(/docs/react-implementation) for react and [React Native
Implementation](/docs/sign-in-with-neynar-react-native-implementation) for
react native.
Want to customize the button to your liking? See [How to customize Sign In
with Neynar button in your
app](/docs/how-to-customize-sign-in-with-neynar-button-in-your-app)
### Step 2: Fill in `data-client_id` in the button code
Find this value in [Neynar Developer Portal](https://dev.neynar.com), Settings tab. e.g. `00b75745-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
### Step 3: Handle callback
Once the user is authenticated and a signer has been authorized by the user, the `signer_uuid` and `fid` will be passed in via the `data` object in the callback function.
* `signer_uuid` is unique to your app and is used to write to Farcaster on behalf of the user (same uuid format)
* `fid`: This is the unique Farcaster identifier for the user e.g. `6131`
* `user`: Neynar hydrated user object.
Store the `signer_uuid` securely on your backend or the browser’s local storage, it's not meant to be exposed to the user or anyone other than you. Switch the app to the logged-in state for that Farcaster user.
Handle insufficient permissions for the API calls except for`statusCode: 403`, `errorResponse.code: InsufficientPermission`
### That’s it!
**You’re all set!** The user is now logged in and you should use the `fid` for any [read APIs](/docs/what-does-vitalikeths-farcaster-feed-look-like) and the `signer_uuid` to do any [write actions](/docs/liking-and-recasting-with-neynar-sdk) on behalf of the user in your App. You can try this flow yourself at **[demo.neynar.com](https://demo.neynar.com)**
## Appendix — more about the user journey
### 1. The user clicks the SIWN button, App redirects to Neynar auth flow
* After the user clicks the SIWN button, the script opens a new popup window for user authentication with Neynar and listens for a message from this window
### 2. The user goes through Neynar’s sign-in flow
* The user runs through the following steps on [https://app.neynar.com/login](https://app.neynar.com/login)
* authentication (only needed if the user isn’t authenticated on app.neynar.com)
* signer collection (only needed if Neynar doesn't have a signer for the user) -- For now signer is collected from the user for **Read only** permissions as well, future iterations will remove this step for **Read only** permissions --
* authorization (this is where the user approves the permissions and these permissions are assigned to user's signer)
* No integration actions are needed from the app developer for this step
### 3. The user is routed back to the App, App collects user information
* Once the user is authenticated, the script receives a message from the authentication window.
* It then executes a callback function
* In the onSignInSuccess function, the user will eventData in through params example
# Neynar SQL Playground
Source: https://docs.neynar.com/docs/how-to-query-neynar-sql-playground-for-farcaster-data
Query real time Farcaster data for your data analyses, create and share dashboards
## Neynar Farcaster SQL playground
### Available at [data.hubs.neynar.com](https://data.hubs.neynar.com/)
## Subscription
If you don’t have access yet, subscribe at [neynar.com](https://neynar.com) . *Please reach out to rish on [Slack](https://neynar.com/slack) or [Farcaster](http://warpcast.com/rish) with feedback, questions or to ask for access*
## Schema
You can always get the latest schema from the database directly by running this query
```sql SQL theme={"system"}
SELECT table_name, column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public'
ORDER BY table_name, ordinal_position;
```
## Using LLMs to query data
If you give chatgpt the above table schema and tell it what you want, it’ll write the sql query for you! Schema as of Nov 21, 2024 is [here](https://neynar.notion.site/Public-postgres-schema-145655195a8b80fc969cc766fbcde86b?pvs=4). We recommend you get the latest schema when working with an LLM agent.
## Overview
* Query any Farcaster data in the playground
* SQL access is also available over API, check your Redash profile for your API key. This is a separate API key for SQL only *(not the same key as our read and write APIs)*
## SQL over API
* Documentation on how to use SQL over API is [**here**](https://redash.io/help/user-guide/integrations-and-api/api)
## Notes on the database
Data is more raw compared to our APIs, please let us know if any particular views would be useful; we encourage API use instead of SQL if you’re building clients. You will likely need to join different tables when using SQL.
### 1. **Follows**
`links` table has follower \<> follow data:
* `fid` → `target_fid` row means `fid` follows `target_fid`
### 2. Reactions
* `reaction_type` 1 is “like” and 2 is “recast” in the `reactions` table
* `hash` in the reactions table is the “reaction hash” and `target_hash` is the hash of the cast that received the reaction
### 3. hex \<> bytea
Redash UI automatically converts *bytea* data to hex format. However, when writing sql queries, you have to do the conversion yourself e.g.
* bytea to hex
```sql SQL theme={"system"}
select ENCODE(hash, 'hex') as hash from casts
limit 1
```
* hex to bytea
```sql SQL theme={"system"}
select * from casts where hash = DECODE('hex_hash_without_0x', 'hex')
```
(swap `hex_hash_without_0x` with the actual cast hash minus the \`0x)
# Zapier workflows
Source: https://docs.neynar.com/docs/how-to-set-up-zapier-workflows-with-neynar-webhooks
Add Neynar to your Zap to trigger full workflows based on Farcaster events
Create a Zapier account on [Zapier.com](https://zapier.com) if you don't have one already.
Start creating a new zap and search for Neynar when setting up your trigger
Choose Neynar as trigger
Then choose which Farcaster event you want to get notified for -- every new cast on the protocol or a mention of a specific user in a cast
On the account step, insert your Neynar API key to connect your Neynar account with Zapier
If mention of a specific user, enter the FID of the user you want to get webhook notifications for. You can find the FID of an account by looking at the "About" section of that account on Warpcast
Test your trigger after setting the FID. If there is a recent cast on the network with the mention, it will show up. If not, make a cast and wait a few minutes before testing again. Proceed with a selected record as example.
Choose the action you want to take based on the webhook e.g. sending a message in Slack. Finish setting up the action on Zapier and you're done!
You should now be able to trigger Zapier workflows based on webhook data!
# Webhooks in Dashboard
Source: https://docs.neynar.com/docs/how-to-setup-webhooks-from-the-dashboard
User Neynar dev portal to set up webhooks for your app
Neynar webhooks are a way to receive real-time updates about events on the Farcaster protocol. You can use webhooks to build integrations that respond to events on the protocol, such as when a user creates a cast or when a user updates their profile.
This guide will show you how to set up a webhook in the Neynar developer portal and how to integrate it into your application.
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). Click on the new webhook and enter the details as such:
The webhook will fire to the specified `target_url`. To test it out, we are using a service like [ngrok](https://ngrok.com/) to create a public URL that will forward requests to your local server. However, we recommend using your own domain to avoid interruptions.
### Free endpoints like ngrok, localtunnel, etc. throttle webhook deliveries, best to use your own domain
Let's create a simple server that logs out the event. We will be using [Bun JavaScript](https://bun.sh).
```javascript Javascript theme={"system"}
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 farcasterframesbot present in the text.
Now the server will log out the event when it is fired. It will look something like this:
```javascript Javascript theme={"system"}
{
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: "LFG",
timestamp: "2024-02-15T19:23:22.000Z",
embeds: [],
reactions: {
likes: [],
recasts: [],
},
replies: {
count: 0,
},
mentioned_profiles: [],
},
}
```
## 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, 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)!
# How to set up Neynar webhooks through your developer portal
Source: https://docs.neynar.com/docs/how-to-use-neynar-webhooks
Setting up Farcaster webhooks through Neynar developer portal
Neynar webhooks are a way to receive real-time updates about events on the Farcaster protocol. You can use webhooks to build integrations that respond to events on the protocol, such as when a user creates a cast or when a user updates their profile.
This guide will show you how to set up a webhook in the Neynar developer portal and how to integrate it into your application.
First, log in to the [Neynar developer portal](https://dev.neynar.com/) and navigate to the "Webhooks" tab. Click the "Create Webhook" button to create a new webhook.
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.
Let's create a simple server that logs out the event. We will be using Bun JavaScript.
```javascript Javascript theme={"system"}
Bun.serve({
async fetch(req) {
console.log(await req.json());
return new Response("Neynar webhook!");
},
});
```
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.
Here, we setup a webhook that fires to that ngrok endpoint (which is hooked to our localhost:3000), and the webhook will fire when a cast is made in the Memes channel.
Now the server will log out the event when it is fired. It will look something like this:
```javascript Javascript theme={"system"}
{
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: "LFG",
timestamp: "2024-02-15T19:23:22.000Z",
embeds: [],
reactions: {
likes: [],
recasts: [],
},
replies: {
count: 0,
},
mentioned_profiles: [],
},
}
```
That's it, it's that simple! Next steps would be to have a public server that can handle the webhook events and use it to suit your needs.
Note: Webhook events might be delayed at times of heavy load or when it's first created from the dev portal.
### 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!
# How to Use the Neynar Feed API
Source: https://docs.neynar.com/docs/how-to-use-the-feed-api-1
A guide on how to get feed from the Feed API using fid, fids, and parent_url.
This guide uses [this feed API](/reference/fetch-feed)
There are three different ways you can use the Feed endpoint:
1. Getting the feed of a user by passing a `fid` field to the request
2. Getting the feed of multiple users by passing a `fids` field to the request
3. Getting the feed of a parent URL e.g. FIP-2 channels on Warpcast, by passing a `parent_url` field to the request
## Get feed by `fid`
If you want to get the feed of a user using their `fid`, you'll need to pass it in using the `fid` field of your request.
To try this request in the API Explorer to get an actual response from the API, follow these steps:
* In the *Request* tab, ensure *Default* is selected as shown below
* Add the fid of the user whose feed you want to get
* Press the **Try it** button to see the response
## Get feed by `fids`
You can get the feed for multiple users by passing an array of their fids in the `fids` field of your request. To do this, you'll need to set `filter_type=fids` in you request body.
To try this request in the API Explorer to get an actual response from the API, follow these steps:
* In the *Request* tab, change the request type to **Get feed using fids**
* Set the query parameters to the following
* Press the **Try it** button to view the response
## Get feed by `parent_url`
You can get the feed for multiple users by passing the parent URL in the `parent_url` field in your request. To do this, you'll need to set `feed_type=filter` and `filter_type=parent_url` in you request body.
To try this request in the API Explorer to get an actual response from the API, follow these steps:
* In the *Request* tab, change the request type to **Get feed using parent\_url**
* Set the query parameters in the explorer
You can use the following parent URL as an example value in the explorer: `chain://eip155:1/erc721:0xd4498134211baad5846ce70ce04e7c4da78931cc`
* Press the **Try it** button to view the response
## Sample creations with this endpoint
Fetch home feed for a user
Fetch channel feed:
# Verify Webhooks with HMAC Signatures
Source: https://docs.neynar.com/docs/how-to-verify-the-incoming-webhooks-using-signatures
This guide highlights the steps to verify incoming webhooks using signatures
Webhook signatures are strings used to verify the validity of an incoming webhook event. This signature is passed as header values in the format: `X-Neynar-Signature`.
The validation is an important process to prevent exploitation and malicious webhook requests.
```Text JSON theme={"system"}
{
"Content-Type": "application/json",
"X-Neynar-Signature": "6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39"
}
```
## Verification Process
Use an HMAC library of your choice to create a sha512 digest with the following:
* Shared secret - Find this on the [Developer Portal](https://dev.neynar.com/webhook)
* Encoding format - This is always `hex`
* Request payload - The request body object of the webhook POST
Compare the signatures from Step 1 and the request header `X-Neynar-Signature`
## Example
Here's an example of a Next.js API handler validating a signature from a request.
```typescript Typescript theme={"system"}
import { NextRequest } from "next/server";
import { createHmac } from "crypto";
export async function POST(req: NextRequest) {
const body = await req.text();
const sig = req.headers.get("X-Neynar-Signature");
if (!sig) {
throw new Error("Neynar signature missing from request headers");
}
const webhookSecret = process.env.NEYNAR_WEBHOOK_SECRET;
if (!webhookSecret) {
throw new Error("Make sure you set NEYNAR_WEBHOOK_SECRET in your .env file");
}
const hmac = createHmac("sha512", webhookSecret);
hmac.update(body);
const generatedSignature = hmac.digest("hex");
const isValid = generatedSignature === sig;
if (!isValid) {
throw new Error("Invalid webhook signature");
}
const hookData = JSON.parse(body);
// your code continues here ...
}
```
## Appendix
* Caveats and additional details can be found here: [Verification of simple signatures](https://docs.getconvoy.io/product-manual/signatures#simple-signatures)
# How writes to Farcaster work with Neynar managed signers
Source: https://docs.neynar.com/docs/how-writes-to-farcaster-work-with-neynar-managed-signers
(incl. frontend)
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!
### 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.
## Context
This guide covers setting up a full app with frontend and backend to start writing with a logged in user. You can pick and choose the right pieces from this guide as needed. We'll go over:
1. Creating a signer key for the user so they can sign in
2. Storing the user's credentials in local storage
3. Writing casts to Farcaster using the signer
Before we begin, you can access the [complete source code](https://github.com/neynarxyz/farcaster-examples/tree/main/managed-signers) for this guide on GitHub.
The simplest way to set up managed signers is by cloning the above repo! You can use the following commands to check it out:
```bash theme={"system"}
npx degit https://github.com/neynarxyz/farcaster-examples/tree/main/managed-signers managed-signers
cd managed-signers
yarn
yarn dev
```
### 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!
## Setting up a next.js app
### Creating a next app
We are going to need a frontend as well as a backend server, so we are going to use Next.js for this guide. Run the following command to create a new next.js app:
```powershell PowerShell theme={"system"}
npx create-next-app managed-signers
```
Choose the configuration for your app and wait for the dependencies to install. Once they are installed, let's install the additional packages that we are going to need:
```powershell npm theme={"system"}
npm i @farcaster/hub-nodejs @neynar/nodejs-sdk axios qrcode.react viem
```
```powershell yarn theme={"system"}
yarn add @farcaster/hub-nodejs @neynar/nodejs-sdk axios qrcode.react viem
```
```powershell pnpm theme={"system"}
pnpm add @farcaster/hub-nodejs @neynar/nodejs-sdk axios qrcode.react viem
```
```powershell bun theme={"system"}
bun add @farcaster/hub-nodejs @neynar/nodejs-sdk axios qrcode.react viem
```
Now, you can open the folder in your favourite code editor and we can start building!
### Configuring env variables
Firstly, let's configure the env variables we will need. Create a new `.env.local` file and add these two variables:
```bash theme={"system"}
NEYNAR_API_KEY=
FARCASTER_DEVELOPER_MNEMONIC=
```
* The neynar api key should be the api key that you can view on your dashboard
* the mnemonic should be the mnemonic associated with the developer account which will be used to create the signers e.g. `@your_company_name` account on Farcaster (to state the obvious out loud, you *won't* need user mnemonics at any point)
## Creating API route for generating the signature
Create a new `route.ts` file in the `src/app/api/signer` folder and add the following:
```typescript route.ts theme={"system"}
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) {
return NextResponse.json({ error: "An error occurred" }, { status: 500 });
}
}
```
This is just defining a get and a post request and calling a getSignedKey function but we haven't created that yet, so let's do that.
Create a new `utils/getSignedKey.ts`file and add the following:
```typescript getSignedKey.ts theme={"system"}
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 as any);
// Generates an expiration date for the signature (24 hours from now).
const deadline = Math.floor(Date.now() / 1000) + 86400;
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 neynarClient (yet to create) 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 return the signedKey.
Let's now initialise our `neynarClient` in a new `lib/neynarClient.ts` file like this:
```typescript neynarClient.ts theme={"system"}
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;
```
We are also using another util function named `getFid` in the signature generation, so let's create a `utils/getFid.ts` file and create that as well:
```typescript getFid.ts theme={"system"}
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);
// Lookup user details using the custody address.
const { user: farcasterDeveloper } =
await neynarClient.lookupUserByCustodyAddress({custodyAddress:account.address});
return Number(farcasterDeveloper.fid);
};
```
We can use this api route on our front end to generate a signature and show the QR code/deep link to the user. So, head over to `app/page.tsx` and add the following:
```typescript page.tsx theme={"system"}
"use client";
import axios from "axios";
import QRCode from "qrcode.react";
import { useState } from "react";
import styles from "./page.module.css";
interface FarcasterUser {
signer_uuid: string;
public_key: string;
status: string;
signer_approval_url?: string;
fid?: number;
}
export default function Home() {
const LOCAL_STORAGE_KEYS = {
FARCASTER_USER: "farcasterUser",
};
const [loading, setLoading] = useState(false);
const [farcasterUser, setFarcasterUser] = useState(
null
);
const handleSignIn = async () => {
setLoading(true);
await createAndStoreSigner();
setLoading(false);
};
const createAndStoreSigner = async () => {
try {
const response = await axios.post("/api/signer");
if (response.status === 200) {
localStorage.setItem(LOCAL_STORAGE_KEYS.FARCASTER_USER, JSON.stringify(response.data));
setFarcasterUser(response.data);
}
} catch (error) {
console.error("API Call failed", error);
}
};
return (
);
}
```
Here, we show a button to sign in with farcaster in case the farcasterUser state is empty which it will be initially. And if the status is "pending\_approval" then we display a QR code and a deep link for mobile view like this:
If you try scanning the QR code it will take you to Warpcast to sign into your app! But signing in won't do anything right now, so let's also handle that.
In the `api/signer/route.ts` file let's add a GET function as well to fetch the signer using the signer uuid like this:
```typescript route.ts theme={"system"}
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const signer_uuid = searchParams.get("signer_uuid");
if (!signer_uuid) {
return NextResponse.json(
{ error: "signer_uuid is required" },
{ status: 400 }
);
}
try {
const signer = await neynarClient.lookupSigner({ signerUuid:signer_uuid});
return NextResponse.json(signer, { status: 200 });
} catch (error) {
return NextResponse.json({ error: "An error occurred" }, { status: 500 });
}
}
```
Let's use this route in the `page.tsx` file to fetch the signer and set it in local storage using some useEffects:
```typescript page.tsx theme={"system"}
useEffect(() => {
const storedData = localStorage.getItem(LOCAL_STORAGE_KEYS.FARCASTER_USER);
if (storedData) {
const user: FarcasterUser = JSON.parse(storedData);
setFarcasterUser(user);
}
}, []);
useEffect(() => {
if (farcasterUser && farcasterUser.status === "pending_approval") {
let intervalId: NodeJS.Timeout;
const startPolling = () => {
intervalId = setInterval(async () => {
try {
const response = await axios.get(
`/api/signer?signer_uuid=${farcasterUser?.signer_uuid}`
);
const user = response.data as FarcasterUser;
if (user?.status === "approved") {
// store the user in local storage
localStorage.setItem(
LOCAL_STORAGE_KEYS.FARCASTER_USER,
JSON.stringify(user)
);
setFarcasterUser(user);
clearInterval(intervalId);
}
} catch (error) {
console.error("Error during polling", error);
}
}, 2000);
};
const stopPolling = () => {
clearInterval(intervalId);
};
const handleVisibilityChange = () => {
if (document.hidden) {
stopPolling();
} else {
startPolling();
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
// Start the polling when the effect runs.
startPolling();
// Cleanup function to remove the event listener and clear interval.
return () => {
document.removeEventListener(
"visibilitychange",
handleVisibilityChange
);
clearInterval(intervalId);
};
}
}, [farcasterUser]);
```
Here, we are checking if the user has approved the TX and if they have approved it, we call the signer api and set the user details in the local storage. Let's also add a condition in the return statement to display the fid of the user if they are logged in:
```typescript page.tsx theme={"system"}
{farcasterUser?.status == "approved" && (
Hello {farcasterUser.fid} 👋
)}
```
## Allowing users to write casts
Let's now use the signer that we get from the user to publish casts from within the app. Create a new `api/cast/route.ts` file and add the following:
```typescript route.ts theme={"system"}
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 });
}
}
```
Here, I am using the `publishCast` function to publish a cast using the signer uuid and the text which I am getting from the body of the request.
Head back to the `page.tsx` file and add a `handleCast` function to handle the creation of casts like this:
```typescript page.tsx theme={"system"}
const [text, setText] = useState("");
const [isCasting, setIsCasting] = useState(false);
const handleCast = async () => {
setIsCasting(true);
const castText = text.length === 0 ? "gm" : text;
try {
const response = await axios.post("/api/cast", {
text: castText,
signer_uuid: farcasterUser?.signer_uuid,
});
if (response.status === 200) {
setText(""); // Clear the text field
alert("Cast successful");
}
} catch (error) {
console.error("Could not send the cast", error);
} finally {
setIsCasting(false); // Re-enable the button
}
};
```
Now, we just need to add an input to accept the cast text and a button to publish the cast. Let's add it below the hello fid text like this:
```typescript page.tsx theme={"system"}
{farcasterUser?.status == "approved" && (
Hello {farcasterUser.fid} 👋
)}
```
Your final `page.tsx` file should look similar to this:
```typescript page.tsx theme={"system"}
"use client";
import axios from "axios";
import QRCode from "qrcode.react";
import { useEffect, useState } from "react";
import styles from "./page.module.css";
interface FarcasterUser {
signer_uuid: string;
public_key: string;
status: string;
signer_approval_url?: string;
fid?: number;
}
export default function Home() {
const LOCAL_STORAGE_KEYS = {
FARCASTER_USER: "farcasterUser",
};
const [loading, setLoading] = useState(false);
const [farcasterUser, setFarcasterUser] = useState(
null
);
const [text, setText] = useState("");
const [isCasting, setIsCasting] = useState(false);
const handleCast = async () => {
setIsCasting(true);
const castText = text.length === 0 ? "gm" : text;
try {
const response = await axios.post("/api/cast", {
text: castText,
signer_uuid: farcasterUser?.signer_uuid,
});
if (response.status === 200) {
setText(""); // Clear the text field
alert("Cast successful");
}
} catch (error) {
console.error("Could not send the cast", error);
} finally {
setIsCasting(false); // Re-enable the button
}
};
useEffect(() => {
const storedData = localStorage.getItem(LOCAL_STORAGE_KEYS.FARCASTER_USER);
if (storedData) {
const user: FarcasterUser = JSON.parse(storedData);
setFarcasterUser(user);
}
}, []);
useEffect(() => {
if (farcasterUser && farcasterUser.status === "pending_approval") {
let intervalId: NodeJS.Timeout;
const startPolling = () => {
intervalId = setInterval(async () => {
try {
const response = await axios.get(
`/api/signer?signer_uuid=${farcasterUser?.signer_uuid}`
);
const user = response.data as FarcasterUser;
if (user?.status === "approved") {
// store the user in local storage
localStorage.setItem(
LOCAL_STORAGE_KEYS.FARCASTER_USER,
JSON.stringify(user)
);
setFarcasterUser(user);
clearInterval(intervalId);
}
} catch (error) {
console.error("Error during polling", error);
}
}, 2000);
};
const stopPolling = () => {
clearInterval(intervalId);
};
const handleVisibilityChange = () => {
if (document.hidden) {
stopPolling();
} else {
startPolling();
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
// Start the polling when the effect runs.
startPolling();
// Cleanup function to remove the event listener and clear interval.
return () => {
document.removeEventListener(
"visibilitychange",
handleVisibilityChange
);
clearInterval(intervalId);
};
}
}, [farcasterUser]);
const handleSignIn = async () => {
setLoading(true);
await createAndStoreSigner();
setLoading(false);
};
const createAndStoreSigner = async () => {
try {
const response = await axios.post("/api/signer");
if (response.status === 200) {
localStorage.setItem(
LOCAL_STORAGE_KEYS.FARCASTER_USER,
JSON.stringify(response.data)
);
setFarcasterUser(response.data);
}
} catch (error) {
console.error("API Call failed", error);
}
};
return (
);
}
```
If you now try going through the whole flow of signing in and creating a cast, everything should work seamlessly!
### 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
This guide taught us how to integrate neynar managed signers into your next.js app to add sign-in with Farcaster! If you want to look at the completed code, check out the [GitHub repository](https://github.com/neynarxyz/farcaster-examples/tree/main/managed-signers).
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)!
# HTML & OpenGraph Metadata in Mini Apps
Source: https://docs.neynar.com/docs/html-metadata-in-frames-and-catalogs
Neynar's API now supports HTML metadata for mini apps (prev. called Frames) and catalogs, providing rich information about embedded content. This feature allows you to access Open Graph (OG) data and oEmbed information for frames and catalogs, similar to how it works with casts.
## Overview
HTML metadata includes two main components:
Standard metadata used across the web to describe content, including title, description, images, and more.
A format for allowing embedded content from one site to be displayed on another site.
## Accessing HTML Metadata
When retrieving frames or catalogs through the API, you can now access the `html` property which contains all the metadata associated with the frame or catalog URL.
### Example Response
```json JSON theme={"system"}
{
"html": {
"title": "Example Frame",
"description": "This is an example frame with metadata",
"image": "https://example.com/image.jpg",
"url": "https://example.com/frame",
"oembed": {
"type": "rich",
"version": "1.0",
"title": "Example Frame",
"author_name": "Example Author",
"provider_name": "Example Provider",
"html": "",
"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:
We'll see the entire React component, and we'll dissect it afterwards.
```jsx JSX theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
{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**
Requirements vary depending on what data you want to ingest and at what cadence. See [requirements for indexer service](/docs/requirements-for-indexer-service) if you want to ingest *all* protocol data in *real time*.
### **Steps**
* **Contact for setup**
* Reach out on our [Developer Slack](https://neynar.com/slack)
* **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://neynar.com/slack)!
# 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 theme={"system"}
npx create-next-app@latest neynar-managed-signers && cd neynar-managed-signers
```
2. **Dependencies** (to install via `npm`, `yarn`, etc.):
```bash npm theme={"system"}
npm i @farcaster/hub-nodejs @neynar/nodejs-sdk viem
```
```bash yarn theme={"system"}
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 theme={"system"}
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 theme={"system"}
└── 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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
curl -X POST http://localhost:3000/api/signer \
-H "Content-Type: application/json"
```
Response
```json JSON theme={"system"}
{
"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.
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 theme={"system"}
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 theme={"system"}
{
"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 theme={"system"}
// 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 theme={"system"}
const hash = "0x6932a9256f34e18892d498abb6d00ccf9f1c50d6";
client.publishReaction({ signerUuid: signer, reactionType: "like", target:hash });
```
Recasting works the same way, replace "like" with "recast":
```javascript Javascript theme={"system"}
const hash = "0x6932a9256f34e18892d498abb6d00ccf9f1c50d6";
client.publishReaction({ signerUuid: signer, reactionType: "recast", 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 theme={"system"}
const types = ReactionsType.All;
const reactions = await client.fetchCastReactions({ hash, types: [types] });
console.log(reactions);
```
Which would print out
```json theme={"system"}
{
"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 theme={"system"}
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 theme={"system"}
{
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 theme={"system"}
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 theme={"system"}
{
"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 theme={"system"}
// 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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
const feedType = FeedType.Filter;
const filterType= FilterType.Fids;
const feed = await client.fetchFeed({feedType,
filterType,
fids
});
console.log(feed);
```
Example output:
```json Json theme={"system"}
{
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!
# Managing Onchain Wallets
Source: https://docs.neynar.com/docs/managing-onchain-wallets
Set up and manage your wallet for onchain Farcaster operations
## What is a Wallet ID?
A `wallet_id` is a unique identifier for an app wallet that Neynar operates on your behalf. When you use a wallet\_id, Neynar executes onchain transactions for you, handling gas estimation, transaction submission, and retry logic automatically.
Think of it as a server-side wallet that you fund with gas tokens, and Neynar manages the technical complexity of blockchain interactions.
## When Do You Need a Wallet ID?
You need a `wallet_id` for all onchain operations on Neynar:
| Operation | Endpoint | Wallet ID Status |
| -------------------- | ---------------------------------- | ---------------- |
| **Mint NFTs** | `POST /v2/farcaster/nft/mint` | **REQUIRED** |
| **Send Fungibles** | `POST /v2/farcaster/fungible/send` | **REQUIRED** |
| **Buy Storage** | `POST /v2/farcaster/storage/buy` | **REQUIRED** |
| **Fetch FID** | `GET /v2/farcaster/user/fid` | **REQUIRED** |
| **Register Account** | `POST /v2/farcaster/user/` | **REQUIRED** |
**All onchain operations require a wallet\_id.** This ensures consistent billing and gives you full control over your onchain spending.
***
## Getting Your Wallet Set Up
App wallets are **self-service** - you can create them directly in the developer portal!
### Setup Process
1. **Go to the [Neynar Developer Portal](https://dev.neynar.com)**
2. **Select your app** from the dashboard
3. **Navigate to the "App Wallet" tab** in your app settings
4. **Copy your `wallet_id`** - You'll see a field displaying your wallet ID once created
5. **Copy your wallet address(es)** - You'll receive wallet addresses for each supported network
6. **Fund your wallet** with gas tokens (see below)
7. **Start using** the wallet\_id in your API calls immediately!
**Need Help?** If you encounter any issues during setup, message our [developer Slack](https://neynar.com/slack).
### What You'll Get
When you create an app wallet, you'll receive:
1. **Wallet ID** - A unique identifier to use in API headers (e.g., `wlt_abc123...`)
2. **Wallet Addresses** - Ethereum addresses for each network where you can send gas tokens (e.g., `0x1234...`)
## Finding Your Wallet ID
Your wallet\_id is available anytime in the developer portal:
1. Go to [dev.neynar.com](https://dev.neynar.com)
2. Select your app
3. Navigate to the "App Wallet" tab
4. Copy your `wallet_id` value
**Keep this information secure** - treat your wallet\_id like an API key.
## Funding Your Wallet
Your wallet needs native gas tokens on the networks where you'll operate:
### Supported Networks
| Network | Gas Token | Use Case |
| ---------------- | ------------- | ------------------------------------------ |
| **Base** | ETH | Production NFT minting, mainnet operations |
| **Optimism** | ETH | Account registration, storage purchases |
| **Base Sepolia** | ETH (testnet) | Testing and development |
### How to Fund
1. **Get your wallet address** from setup email or portal
2. **Send gas tokens** to that address using any wallet (MetaMask, Coinbase Wallet, etc.)
3. **Recommended amounts:**
* Testing: 0.01 ETH
* Low volume (\<100 ops/day): 0.1 ETH
* High volume (>1000 ops/day): 1+ ETH
### Funding Example
```bash theme={"system"}
# Using MetaMask or any wallet:
# 1. Switch to the appropriate network (Base, Optimism, etc.)
# 2. Send ETH to your wallet address
# 3. Wait for confirmation
# Example wallet address (yours will be different):
# 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
```
**Gas Costs:** Typical operations cost:
* NFT mint: \~\$0.01-0.05 in gas
* Account registration: \~\$0.10-0.50 in gas
* Storage purchase: \~\$0.05-0.20 in gas
## Monitoring Balance
### Check Balance Manually
You can view your wallet balance on any block explorer:
**Base:**
```
https://basescan.org/address/YOUR_WALLET_ADDRESS
```
**Optimism:**
```
https://optimistic.etherscan.io/address/YOUR_WALLET_ADDRESS
```
**Base Sepolia:**
```
https://sepolia.basescan.org/address/YOUR_WALLET_ADDRESS
```
### Developer Portal
The developer portal shows:
* Current balance per network
* Recent transactions
* Wallet addresses for each network
* Transaction history
## Using Your Wallet ID
Add the `x-wallet-id` header to your API requests:
### Example: Minting NFTs
```javascript Node.js theme={"system"}
const response = await fetch('https://api.neynar.com/v2/farcaster/nft/mint', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_NEYNAR_API_KEY',
'x-wallet-id': 'your-wallet-id', // ← Your wallet ID here
'Content-Type': 'application/json'
},
body: JSON.stringify({
nft_contract_address: '0x...',
network: 'base',
recipients: [{ fid: 12345, quantity: 1 }]
})
});
const result = await response.json();
console.log('Minted:', result);
```
```bash cURL theme={"system"}
curl -X POST 'https://api.neynar.com/v2/farcaster/nft/mint' \
-H 'x-api-key: YOUR_NEYNAR_API_KEY' \
-H 'x-wallet-id: your-wallet-id' \
-H 'Content-Type: application/json' \
-d '{
"nft_contract_address": "0x...",
"network": "base",
"recipients": [{"fid": 12345, "quantity": 1}]
}'
```
```python Python theme={"system"}
import requests
headers = {
'x-api-key': 'YOUR_NEYNAR_API_KEY',
'x-wallet-id': 'your-wallet-id',
'Content-Type': 'application/json'
}
response = requests.post(
'https://api.neynar.com/v2/farcaster/nft/mint',
headers=headers,
json={
'nft_contract_address': '0x...',
'network': 'base',
'recipients': [{'fid': 12345, 'quantity': 1}]
}
)
result = response.json()
print('Minted:', result)
```
### Example: Buying Storage
```javascript Node.js theme={"system"}
const response = await fetch('https://api.neynar.com/v2/farcaster/storage/buy', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_NEYNAR_API_KEY',
'x-wallet-id': 'your-wallet-id',
'Content-Type': 'application/json'
},
body: JSON.stringify({
fid: 12345,
units: 1
})
});
```
```bash cURL theme={"system"}
curl -X POST 'https://api.neynar.com/v2/farcaster/storage/buy' \
-H 'x-api-key: YOUR_NEYNAR_API_KEY' \
-H 'x-wallet-id: your-wallet-id' \
-H 'Content-Type: application/json' \
-d '{"fid": 12345, "units": 1}'
```
## Error Handling
### Missing Wallet ID
If you don't provide a wallet\_id when required:
```json theme={"system"}
{
"code": "Unauthorized",
"message": "Your app is not authorized for this operation. Please provide x-wallet-id header or contact support for allowlist access."
}
```
**Solution:** Add the `x-wallet-id` header or request allowlist access.
### Invalid Wallet ID
If your wallet\_id is incorrect:
```json theme={"system"}
{
"code": "InvalidWalletId",
"message": "The provided wallet_id is invalid or not found."
}
```
**Solution:** Verify your wallet\_id in the developer portal or contact support.
### Insufficient Balance
If your wallet runs out of gas:
```json theme={"system"}
{
"code": "InsufficientFunds",
"message": "Wallet does not have enough balance to complete this transaction."
}
```
**Solution:** Fund your wallet with more gas tokens.
## Best Practices
### Security
* ✅ **Store wallet\_id securely** - Treat it like an API key
* ✅ **Use environment variables** - Never commit wallet\_id to code
* ✅ **Rotate if compromised** - Contact support for new wallet\_id
* ❌ **Don't share publicly** - Keep wallet\_id private
### Operations
* ✅ **Monitor balance regularly** - Set up alerts for low balance
* ✅ **Fund adequately** - Ensure sufficient gas for your volume
* ✅ **Test on testnet first** - Use Base Sepolia before mainnet
* ✅ **Handle errors gracefully** - Implement retry logic
### Cost Optimization
* ✅ **Batch operations** when possible
* ✅ **Monitor gas prices** - Some operations can wait for lower gas
* ✅ **Choose right network** - Base often has lower gas costs than Optimism, some transactions can only be done on certain networks.
## FAQ
### Can I use one wallet\_id for multiple apps?
No, each app should have its own wallet\_id for security and accounting purposes.
### What happens if my wallet runs out of funds?
Operations will fail with an insufficient balance error. Fund your wallet to resume operations.
### Can I withdraw funds from my wallet?
Not currently. App wallets are managed by Neynar for security. Contact support if you need to adjust your setup.
### Can I see transaction history?
Yes, use a block explorer with your wallet address, or check the developer portal (coming soon).
## Related Documentation
Mint NFTs directly to Farcaster users
Purchase storage units for users
Register new Farcaster accounts
Get fresh FIDs for registration
## Need Help?
Set up your wallet in the developer portal
Manage your apps and wallets
Need help? Contact our team
Ask questions in our Farcaster channel
# 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
*Although titled "Mini app authentication", this can also be used in web apps if you'd like.*
## Overview
The authorization 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)
This authentication system is designed to work both in a regular web browser and inside a miniapp. In other words, it supports authentication when the miniapp context is not present (web browser) as well as when the app is running inside a miniapp. If you only need authentication for a web application, follow the Webapp flow; if you only need authentication inside a miniapp, follow the Miniapp flow.
## Architecture Components
The system involves four main components:
```mermaid theme={"system"}
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 (webapp)
B->>B: Store in Session (miniapp)
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 (Miniapp) |
| `/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 | Miniapp 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 theme={"system"}
const generateNonce = async () => {
const response = await fetch('/api/auth/nonce');
const data = await response.json();
setNonce(data.nonce);
};
```
**Mini App Server → Neynar Server:**
```typescript theme={"system"}
// /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 theme={"system"}
// Webapp flow using Farcaster Auth Kit
const { signIn, connect, data } = useSignIn({
nonce: nonce || undefined,
onSuccess: onSuccessCallback,
onError: onErrorCallback,
});
// Miniapp flow using Farcaster SDK
const handleMiniappSignIn = 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.
**Webapp flow:**
```typescript theme={"system"}
useEffect(() => {
if (nonce && !useMiniappFlow) {
connect(); // Triggers signing flow
}
}, [nonce, connect, useMiniappFlow]);
```
**Miniapp flow:**
```typescript theme={"system"}
// 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 theme={"system"}
const onSuccessCallback = useCallback(
async (res: UseSignInData) => {
console.log('✅ Authentication successful:', res);
setMessage(res.message);
setSignature(res.signature);
},
[useMiniappFlow, 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 theme={"system"}
const fetchAllSigners = async (message: string, signature: string) => {
const endpoint = useMiniappFlow
? `/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 theme={"system"}
// /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 Miniapp flow with user data:**
```typescript theme={"system"}
// /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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
// /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 theme={"system"}
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 theme={"system"}
// /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 theme={"system"}
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 theme={"system"}
// /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 theme={"system"}
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.
**Webapp flow (LocalStorage):**
```typescript theme={"system"}
const storeInLocalStorage = (user: any, signers: any[]) => {
const authState = {
isAuthenticated: true,
user,
signers,
};
setItem(STORAGE_KEY, authState);
setStoredAuth(authState);
};
```
**Miniapp flow (NextAuth Session):**
```typescript theme={"system"}
const updateSessionWithSigners = async (signers: any[], user: any) => {
if (useMiniappFlow && 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
### Webapp flow State (LocalStorage)
```typescript theme={"system"}
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';
```
### Miniapp flow State (NextAuth Session)
```typescript theme={"system"}
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
```typescript theme={"system"}
// 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 theme={"system"}
# 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 theme={"system"}
// Determine which flow to use
const { context } = useMiniApp();
const useMiniappFlow = context !== undefined;
// Webapp flow uses localStorage + Farcaster Auth Kit
// Miniapp flow uses NextAuth sessions + Farcaster SDK
```
### Integration Examples
#### Checking Authentication Status
```typescript theme={"system"}
// Webapp flow
const isAuthenticated =
storedAuth?.isAuthenticated &&
storedAuth?.signers?.some((s) => s.status === 'approved');
// Miniapp flow
const isAuthenticated =
session?.provider === 'neynar' &&
session?.user?.fid &&
session?.signers?.some((s) => s.status === 'approved');
```
#### Using Signers for Farcaster Actions
```typescript theme={"system"}
// Get signer
const signer = (useMiniappFlow ? 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.
**Wallet ID Required:** This endpoint always requires a [`x-wallet-id` header](/docs/managing-onchain-wallets) to execute NFT minting transactions.
### 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://t.me/rishdoteth).
App wallets can be created self-service in the [Developer Portal](https://dev.neynar.com). See [Managing Onchain Wallets](/docs/managing-onchain-wallets) for setup instructions.
## 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 theme={"system"}
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 theme={"system"}
{
"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 theme={"system"}
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 theme={"system"}
{
"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 theme={"system"}
{
"transactions": [{
"transaction_hash": "0xabc...",
"recipient": { "fid": 14206, "quantity": 1 }
}]
}
```
## Batch Minting
Mint to multiple Farcaster users in a single API call:
```javascript Node.js theme={"system"}
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`);
```
## App Wallets
App wallets are managed wallets you create in the developer portal and fund with gas tokens. Neynar executes 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. An app wallet created in the [Developer Portal](https://dev.neynar.com) (self-service!)
2. Your `x-wallet-id` header value (found in the portal)
3. An NFT contract deployed on Highlight
4. Native gas tokens on the network of your choosing (fund your wallet address)
See [Managing Onchain Wallets](/docs/managing-onchain-wallets) for step-by-step setup instructions!
## Error Handling
If you don't include the required [`x-wallet-id` header](/docs/managing-onchain-wallets), you'll get:
```json theme={"system"}
{
"code": "RequiredField",
"message": "x-wallet-id header is required"
}
```
If you don't have a wallet\_id, [reach out](https://t.me/rishdoteth) to get one setup or see the [wallet setup guide](/docs/managing-onchain-wallets).
Each transaction in batch minting will either succeed with a `transaction_hash` or fail with an `error` field.
## Next Steps
Fund your wallet and monitor balance
Send tokens to Farcaster users
Register new Farcaster accounts
Need help? Reach out to our team
***
That's it! You're now ready to reward your Farcaster community with NFTs using just their FIDs.
Enjoy building! 🚀
# 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 theme={"system"}
/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 theme={"system"}
/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
* [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
* [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
Available at [`https://docs.neynar.com/mcp`](https://docs.neynar.com/mcp). See Step #3 below on how to add.
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.
Add Neynar MCP server to your IDE as per instructions below. This tutorial follows Cursor.
To connect the Neynar MCP server to Cursor, click the **Install in Cursor** button in the contextual menu on the top right of any docs page. Or to manually connect the MCP server, follow these steps:
1. Use `Command` + `Shift` + `P` (`Ctrl` + `Shift` + `P` on Windows) to open the command palette.
2. Search for "Open MCP settings".
3. Select **Add custom MCP**. This will open the `mcp.json` file.
In `mcp.json`, add:
```json theme={null} theme={"system"}
{
"mcpServers": {
"Neynar": {
"url": "https://docs.neynar.com/mcp"
}
}
}
```
In Cursor's chat, ask "What tools do you have available?" Cursor should show the Neynar MCP server as an available tool.
See [Installing MCP servers](https://docs.cursor.com/en/context/mcp#installing-mcp-servers) in the Cursor documentation for more details.
To connect the Neynar MCP server to Cursor, click the **Install in Cursor** button in the contextual menu on the top right of any docs page. Or to manually connect the MCP server, follow these steps:
1. Create a `.vscode/mcp.json` file.
2. In `mcp.json`, configure your server:
```json theme={null} theme={"system"}
{
"servers": {
"Neynar": {
"type": "http",
"url": "https://docs.neynar.com/mcp"
}
}
}
```
See the [VS Code documentation](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) for more details.
```bash theme={null} theme={"system"}
claude mcp add --transport http Neynar https://docs.neynar.com/mcp
```
Test the connection by running:
```bash theme={null} theme={"system"}
claude mcp list
```
See the [Claude Code documentation](https://docs.anthropic.com/en/docs/claude-code/mcp#installing-mcp-servers) for more details.
1. Navigate to the [Connectors](https://claude.ai/settings/connectors) page in the Claude settings.
2. Select **Add custom connector**.
3. Add the Neynar MCP server:
* Name: `Neynar`
* URL: `https://docs.neynar.com/mcp`
4. Select **Add**.
1. When using Claude, select the attachments button (the plus icon).
2. Select the Neynar MCP server.
3. Ask Claude a question about Neynar.
See the [Model Context Protocol documentation](https://modelcontextprotocol.io/docs/tutorials/use-remote-mcp-server#connecting-to-a-remote-mcp-server) for more details.
You can open the MCP server from the dropdown contextual menu on the top right of any page on the Neynar docs site.
| Option | Description |
| :---------------------- | :-------------------------------------------------- |
| **Copy MCP server URL** | Copies your MCP server URL to the user's clipboard. |
| **Connect to Cursor** | Installs your MCP server in Cursor. |
| **Connect to VS Code** | Installs your MCP server in VS Code. |
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://farcaster.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/`.
## 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.
If using scores in product development, **we recommend starting with a threshold around 0.55 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.
##### If looking for more information on scores as end user, see [FAQ section](/docs/neynar-user-quality-score#faqs) at the bottom of this page.
## 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 theme={"system"}
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.
### 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 with 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:
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 theme={"system"}
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 theme={"system"}
{
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. To get one:
1. Enable [Developer Mode](https://farcaster.xyz/~/settings/developer-tools) in your Farcaster settings
2. Go to the developer tools section in your Farcaster client
3. Navigate to the API keys section and create a new API key
Once you have the API key add this fetch request in your try block:
```typescript index.ts theme={"system"}
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 theme={"system"}
bun i uuid
```
```typescript index.ts theme={"system"}
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 theme={"system"}
% 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 theme={"system"}
// 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 theme={"system"}
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 theme={"system"}
{
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!
### 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.
## 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.
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 theme={"system"}
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 theme={"system"}
npm i @neynar/react @neynar/nodejs-sdk axios
```
```shell yarn theme={"system"}
yarn add @neynar/react @neynar/nodejs-sdk axios
```
```shell bun theme={"system"}
bun add @neynar/react @neynar/nodejs-sdk axios
```
Install peer dependencies for `@neynar/react`
```shell npm theme={"system"}
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 theme={"system"}
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 theme={"system"}
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 theme={"system"}
"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 theme={"system"}
"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 theme={"system"}
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 theme={"system"}
"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?.display_name}
>
)}
);
}
```
Now, let's add a text area and a cast button here like this:
```typescript page.tsx theme={"system"}
{user && (
<>
{user.pfp_url && (
)}
{user?.display_name}
>
);
}
```
This will give you something like this:
But as you can see we have used a `handlePublishCast` function but we have not yet created it. So, let's create that and also add the `text` useState that we are using in the textarea:
```typescript page.tsx theme={"system"}
const [text, setText] = useState("");
const handlePublishCast = async () => {
try {
await axios.post<{ message: string }>("/api/cast", {
signerUuid: user?.signer_uuid,
text,
});
alert("Cast Published!");
setText("");
} catch (err) {
const { message } = (err as AxiosError).response?.data as ErrorRes;
alert(message);
}
};
```
This creates an api call to a `/api/cast` route with the signer uuid and text.
Finally, we need to create the api route which will create casts. Create a new file `api/cast/route.ts` in the `app` folder and add the following:
```typescript cast/route.ts theme={"system"}
import { NextRequest, NextResponse } from "next/server";
import { NeynarAPIClient, isApiErrorResponse } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(process.env.NEYNAR_API_KEY!);
export async function POST(request: NextRequest) {
const { signerUuid, text } = (await request.json()) as {
signerUuid: string;
text: string;
};
try {
const { hash } = await client.publishCast(signerUuid, text);
return NextResponse.json(
{ message: `Cast with hash ${hash} published successfully` },
{ 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 }
);
}
}
```
### Make sure to set the NEYNAR\_API\_KEY variable in your .env.local file
Now, you can go ahead and try making a cast from the website after connecting your Farcaster account!
## Conclusion
This guide taught us how to add sign-in with neynar to a React app, check out the [GitHub repository](https://github.com/neynarxyz/farcaster-examples/tree/main/wownar-react-sdk) 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)!
# Indexer Service Requirements
Source: https://docs.neynar.com/docs/requirements-for-indexer-service
Reach out if you have questions
* PostgreSQL database recommended specs if ingesting *full* network data, real time
* 1.6 TB disk space
* 16 cores
* 128GB of RAM
You can run a lower spec machine if ingesting less data and in less than real-time latency.
* Credentials for PostgreSQL
* Neynar’s role MUST have read and write on either the public schema or a dedicated schema for us. Replace “schema\_name” and “username” to match your choice:
```bash cURL theme={"system"}
GRANT CREATE ON SCHEMA schema_name TO username;
GRANT USAGE ON SCHEMA schema_name TO username;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA schema_name TO username;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA schema_name TO username;
GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA schema_name TO username;
ALTER DEFAULT PRIVILEGES IN SCHEMA schema_name
GRANT ALL PRIVILEGES ON TABLES TO username;
ALTER DEFAULT PRIVILEGES IN SCHEMA schema_name
GRANT ALL PRIVILEGES ON SEQUENCES TO username;
ALTER DEFAULT PRIVILEGES IN SCHEMA schema_name
GRANT ALL PRIVILEGES ON FUNCTIONS TO username;
```
* Your database SHOULD be in AWS us-east-1 or equivalent for the lowest indexing lag. If not, additional data transfer charges will be needed.
* The database SHOULD require SSL on connections.
* The database MAY be in a private VPC, but the database MUST be “publicly accessible”.
* The database SHOULD limit access by IP address. We will give you our static IP during setup.
* 99.9% availability uptime on the database - we expect your database to be highly available for writes, or else it might increase the data lag
## **Important FYIs for managing your own disk**
* **Disk size:** Start with a big enough disk! Changing disk sizes on Amazon EBS (which RDS uses) is limited by a 6 hour cooldown timer. This timer also applies to their “auto scaling” of drives. Whenever changing your disk type or size or iops, be sure to take this cooldown into consideration! Beware that this timer is shared by all three of those settings. So even if you change just the iops, you have to wait 6 hours to change the size or type!
* **Read queries:** If you need long running queries (especially ones that join multiple tables), know that they will block some syncing. This will manifest as spikes in your “buffered emits” graph. Fix this blocking by adding a replica and moving all of your reading to there. You will also probably need to enable `hot_standby_feedback` if your queries are taking too long with a replica.
* Be VERY CAREFUL with database triggers. They can break things in very surprising ways. [This article](https://blog.gitguardian.com/love-death-triggers/) covers some of the pain we’ve seen.
# Send Notifications to Mini App Users
Source: https://docs.neynar.com/docs/send-notifications-to-mini-app-users
This guide walks you through a simple setup for enabling notifications for your mini app
### This tutorial refers to these two APIs: [Send notifications](/reference/publish-frame-notifications), [List of frame notification tokens](/reference/fetch-notification-tokens)
## Overview
Farcaster miniapps enable developers to send notifications to users who have added the mini app to their Farcaster client and enabled notifications.
Neynar provides a simple way to:
* manage approved notification tokens, no need to store on developer side
* send notifications in a single API call, no need to batch
* automate handling of notification permission revokes, and mini app "remove" events
* target notifications to specific user cohorts
* send notifications using the dev portal without having to write code
* track notification analytics including open rates
Mini app analytics will automatically populate in the Dev Portal dashboard once you use Neynar for notifications.
## Set up Notifications
If you don't have a Neynar developer account yet, sign up for free [here](https://neynar.com)
### Step 1: Add events webhook URL to Mini App Manifest
#### a) Locate the Neynar frame events webhook URL
The Neynar mini app events webhook URL is on the Neynar app page. Navigate to [dev.neynar.com/app](https://dev.neynar.com/app) and then click on the app.
It should be in this format -`https://api.neynar.com/f/app//event`. See the highlighted URL in the image below.
#### b) Set this URL in the mini app manifest
Frame servers must provide a JSON manifest file on their domain at the well-known URI. for example `https://your-frame-domain.com/.well-known/farcaster.json`.
Set the Neynar frame events URL as the `webhookUrl` to the Frame Config object inside the manifest. Here's an example manifest
```json JSON theme={"system"}
{
"accountAssociation": {
"header": "eyJmaWQiOjE5MSwidHlwZSI6ImN1c3RvZHkiLCJrZXkiOiIweDNhNmRkNTY5ZEU4NEM5MTgyOEZjNDJEQ0UyMGY1QjgyN0UwRUY1QzUifQ",
"payload": "eyJkb21haW4iOiIxYmNlLTczLTcwLTE2OC0yMDUubmdyb2stZnJlZS5hcHAifQ",
"signature": "MHg1ZDU1MzFiZWQwNGZjYTc5NjllNDIzNmY1OTY0ZGU1NDMwNjE1YTdkOTE3OWNhZjE1YjQ5M2MxYWQyNWUzMTIyM2NkMmViNWQyMjFhZjkxYTYzM2NkNWU3NDczNmQzYmE4NjI4MmFiMTU4Y2JhNGY0ZWRkOTQ3ODlkNmM2OTJlNDFi"
},
"frame": {
"version": "4.2.0",
"name": "Your Frame Name",
"iconUrl": "https://your-frame-domain.com/icon.png",
"splashImageUrl": "https://your-frame-domain.com/splash.png",
"splashBackgroundColor": "#f7f7f7",
"homeUrl": "https://your-frame-domain.com",
"webhookUrl": "https://api.neynar.com/f/app//event"
}
}
```
### Frame manifest caching
Farcaster clients might have your mini app manifest cached and would only get updated on a periodic basis.
If you're using Warpcast to test, you can go their Settings > Developer Tools > Domains, put in your Frame URL and hit the Check domain status to force a refresh.
### Step 2: Prompt users to add your Mini App
#### a) Install @neynar/react
```bash theme={"system"}
npm install @neynar/react
```
#### b) Set up the MiniAppProvider context provider
Wrap your app with the `MiniAppProvider` component:
```javascript theme={"system"}
import { MiniAppProvider } from '@neynar/react';
export default function App() {
return (
{/* Your app components */}
);
}
```
#### c) Prompt the user to add your mini app using the useMiniApp hook
```javascript theme={"system"}
import { useMiniApp } from '@neynar/react';
export default function HomePage() {
const { isSDKLoaded, addMiniApp } = useMiniApp();
const handleAddMiniApp = async () => {
if (!isSDKLoaded) return;
const result = await addMiniApp();
if (result.added && result.notificationDetails) {
// Mini app was added and notifications were enabled
console.log('Notification token:', result.notificationDetails.token);
}
};
return (
);
}
```
The result type is:
```typescript theme={"system"}
export type FrameNotificationDetails = {
url: string;
token: string;
};
export type AddFrameResult =
| {
added: true;
notificationDetails?: FrameNotificationDetails;
}
| {
added: false;
reason: 'invalid_domain_manifest' | 'rejected_by_user';
};
```
If `added` is true and `notificationDetails` is a valid object, then the client should have called POST to the Neynar frame events webhook URL with the same details.
Neynar will manage all mini app add/remove & notifications enabled/disabled events delivered on this events webhook.
#### Alternative: Using the Mini App SDK directly
If you prefer to use the Mini App SDK directly instead of the Neynar React components:
```bash theme={"system"}
yarn add @farcaster/frame-sdk
```
Then prompt the user:
```typescript theme={"system"}
import sdk from "@farcaster/frame-sdk";
const result = await sdk.actions.addFrame();
```
### Step 3: Send a notification to users
Notifications can be broadcast to all your mini app users with notifications enabled or to a limited set of FIDs. Notifications can also be filtered so that only users meeting certain criteria receive the notification.
The `target_fids` parameter is the starting point for all filtering. Pass an empty array for `target_fids` to start with the set of all FIDs with notifications enabled for your app, or manually define `target_fids` to list specific FIDs.
#### a) Target specific users with filters via the Neynar dev portal
The [Neynar dev portal](https://dev.neynar.com) offers the same functionality as the API for broadcasting notifications. Navigate to your app and click the "Mini App" tab.
Once your mini app is configured with your Neynar webhook URL and users have enabled notifications for your mini app, you'll see a "Broadcast Notification" section with an exandable filters section.
#### b) Target specific users with filters via the API
The following example uses the [@neynar/nodejs-sdk](https://github.com/neynarxyz/nodejs-sdk) to send notifications to users and includes a set of filtering criteria.
```typescript Typescript theme={"system"}
const targetFids = []; // target all relevant users
const filters = {
exclude_fids: [420, 69], // do not send to these FIDs
following_fid: 3, // only send to users following this FID
minimum_user_score: 0.5, // only send to users with score >= this value
near_location: { // only send to users near a certain point
latitude: 34.052235,
longitude: -118.243683,
radius: 50000, // distance in meters from the lat/log point (optional, defaults to 50km)
}
};
const notification = {
title: "🪐",
body: "It's time to savor farcaster",
target_url: "https://your-frame-domain.com/notification-destination",
};
client.publishFrameNotifications({ targetFids, filters, notification }).then((response) => {
console.log("response:", response);
});
```
Additional documentation on the API and its body parameters can be found at [/reference/publish-frame-notifications](/reference/publish-frame-notifications)
### Step 4: Check analytics
Notification analytics will automatically show in your developer portal once you start using Neynar for frame notifications.
When using the `MiniAppProvider` context provider, you'll get additional analytics including notification open rates.
## FAQ
When using the `MiniAppProvider` context provider, you can check the `context` object from the `useMiniApp()` hook which contains the `added` boolean and `notificationDetails` object. More details in [Frame Core Types](https://github.com/farcasterxyz/frames/blob/main/packages/frame-core/src/types.ts#L58-L62)
To avoid getting rate-limited by Farcaster clients, Neynar will filter out sending notifications to disabled tokens.
The [fetch notification tokens API](/reference/fetch-notification-tokens) provides access to the underlying data.
Host servers may impose rate limits per `token`. The [standard rate limits, which are enforced by Merkle's Farcaster client](https://miniapps.farcaster.xyz/docs/guides/notifications#rate-limits), are:
* 1 notification per 30 seconds per token
* 100 notifications per day per token
# SIWN: React Native
Source: https://docs.neynar.com/docs/sign-in-with-neynar-react-native-implementation
In this guide, we'll take a look at how to add sign-in with neynar to a React native application!
## Setting up your application
### Prerequisites
* [Node.js](https://nodejs.org/en/): A JavaScript runtime built on Chrome's V8 JavaScript engine. Ensure you have Node.js installed on your system.
* [Expo Go](https://expo.dev/client): Install Expo Go on your phone
### Cloning the repo
Clone the repo from GitHub using the following command and open it up in your favourite code editor using the following commands:
```bash theme={"system"}
npx degit https://github.com/neynarxyz/farcaster-examples/tree/main/wownar-react-native react-native-siwn
cd react-native-siwn
```
Once you've cloned your repo we can start installing the packages and setting up the env variables!
### Server
Navigate to the server directory
```bash Bash theme={"system"}
cd server
```
Based on the package manager run one of the following commands to install all required dependencies:
```powershell npm theme={"system"}
npm i
```
```powershell yarn theme={"system"}
yarn install
```
```powershell pnpm theme={"system"}
pnpm i
```
```powershell bun theme={"system"}
bun i
```
Copy the example environment file:
```bash Bash theme={"system"}
cp .env.example .env
```
Edit `.env` to add your `NEYNAR_API_KEY` and `NEYNAR_CLIENT_ID`. You can find them in your dashboard
Start the server:
```Text npm theme={"system"}
npm run start
```
```Text yarn theme={"system"}
yarn start
```
```Text pnpm theme={"system"}
pnpm run start
```
```Text bun theme={"system"}
bun run start
```
### Client
Open new terminal
Navigate to the client directory
```bash Bash theme={"system"}
cd client
```
Based on the package manager run one of the following commands to install all required dependencies:
For yarn
```bash Bash theme={"system"}
yarn install
```
For npm
```bash Bash theme={"system"}
npm install
```
Copy the example environment file:
```bash Bash theme={"system"}
cp .env.example .env
```
* Edit `.env` to add your `COMPUTER_IP_ADDRESS`. Refer [find-IP-address article](https://www.avg.com/en/signal/find-ip-address) to get the IP address of your Computer.
Make sure your phone and computer is connected to the same network
For yarn
```bash Bash theme={"system"}
yarn start
```
For npm
```bash Bash theme={"system"}
npm run start
```
you'll see a QR Code
Open the [Expo Go app](https://expo.dev/go) on your phone and scan the QR Code. You should now be able to see a button to sign in with neynar in your app!
## How does it work?
You're probably wondering how it all works, so let's break it down!
### Server
In our server, we have 3 API routes, you can take a look at them in `server/index.js`:
* `/get-auth-url` (GET): This API route gets the authorization URL using the `fetchAuthorizationUrl` function from the neynar client.
* `/user` (GET): This API route takes in the fid as a query parameter and returns the user data like display name and pfp url using the `fetchBulkUsers` function.
* `/cast` (POST): This API route takes in the signer UUID and text in the body and publishes the cast on behalf of the user using the `publishCast` function.
### Client
On the client side, we use the `NeynarSigninButton` component from the `@neynar/react-native-signin` package and pass in a bunch of props like `fetchAuthorizationUrl`, `successCallback`, `errorCallback`, and `redirectUrl`.
* For `fetchAuthorizationUrl` we fetch the url from our server and then pass it.
* For `successCallback` we are creating a function `Context/AppContext.ts` that fetches the user info from the backend and safely stores it.
* For `errorCallBack` we simply just console log the error.
* For `redirectURL` we are using a URL of the format `exp://${COMPUTER_IP_ADDRESS}:8081`. The `COMPUTER_IP_ADDRESS` is the one you added in your `.env` file
* There are also a lot of customisation options that you can find commented out in the `Signin.tsx` file.
## Conclusion
This guide taught us how to add sign-in with neynar to a React native app, check out the [GitHub repository](https://github.com/neynarxyz/farcaster-examples/tree/main/wownar-react-native).
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)!
# React Native Implementation - OLD
Source: https://docs.neynar.com/docs/sign-in-with-neynar-react-native-implementation-old
Legacy React Native implementation guide for Sign in with Neynar (SIWN) integration in Farcaster apps
## How to integrate SIWN?
### Example integration
Check out this sample application ([github](https://github.com/neynarxyz/farcaster-examples/tree/main/wownar-react-native)) that integrates Sign in with Neynar and allows users to cast.
### Step 0: Set up your app in the Neynar developer portal
Go to the [Neynar Developer Portal](https://dev.neynar.com) settings tab and update the following
1. **Name -** Displayed to the user in Step 3.
2. **Logo URL** - Displayed to the user in Step 3. Use a PNG or SVG format.
### Step 1: Install the package
```typescript yarn theme={"system"}
yarn add @neynar/react-native-signin
```
```typescript npm theme={"system"}
npm install @neynar/react-native-signin
```
### Step 2: Find `clientId` and `apiKey`
clientId -> [Neynar Developer Portal](https://dev.neynar.com), Settings tab. e.g. `00b75745-xxxx-xxxx-xxxx-xxxxxxxxxxxx`\\
apiKey -> [Neynar API Key](/docs/getting-started-with-neynar#api-key)
### Step 3: Add a button in your Signin screen
```typescript Typescript theme={"system"}
import {NeynarSigninButton, ISuccessMessage} from "@neynar/react-native-signin";
const NEYNAR_API_KEY = '';
const NEYNAR_CLIENT_ID = '';
const SigninScreen = () => {
const handleSignin = async (data: ISuccessMessage) => {
console.log(`User with fid -> ${data.fid} can use signer -> ${data.signer_uuid} to interact with farcaster`)
};
const handleError = (err) => {
console.log(err)
}
return ();
};
export default SigninScreen;
```
### And all set
### Appendix
**interface ISuccessMessage** is exported from @neynar/react-native-signin which looks like this
```js theme={"system"}
interface ISuccessMessage {
fid: string;
is_authenticated: true;
signer_uuid: string;
}
```
# Farcaster Address Verification with EIP-712
Source: https://docs.neynar.com/docs/smart-account-verifications
A step-by-step guide to verifying an Ethereum address on Farcaster using EIP-712, Privy RPC, Viem, and Neynar.
In this guide, you'll learn how to perform a full EIP-712-based Ethereum address verification for Farcaster using:
* **Privy RPC** for secure off-chain signing
* **Viem** for EIP-712 hash computation and local signature verification
* **Neynar API** for submitting the verification to Farcaster
This tutorial is for advanced users who want to automate or deeply understand the Farcaster address verification process, including smart contract wallets.
***
## Prerequisites
* Node.js ≥ 18
* A `.env` file with:
```env theme={"system"}
PRIVY_VALIDATION_FID=...
PRIVY_VALIDATION_ADDRESS=0x...
PRIVY_VALIDATION_CLIENT_ID=...
PRIVY_ID=...
PRIVY_SECRET=...
NEYNAR_API_KEY=...
ADDRESS_VALIDATION_SIGNER_UUID=...
```
* Install dependencies:
```bash Bash theme={"system"}
npm install dotenv viem buffer node-fetch
```
***
## 1. Setup & Imports
Start by importing dependencies and loading your environment variables:
```typescript privy-validation-signer.ts theme={"system"}
import { config } from 'dotenv'
import { Buffer } from 'buffer'
import { Address, createPublicClient, hashTypedData, http } from 'viem'
import { optimism } from 'viem/chains'
config() // load .env
```
***
## 2. Environment Variables & Constants
Define your configuration and constants:
```typescript privy-validation-signer.ts theme={"system"}
const FID = Number(process.env.PRIVY_VALIDATION_FID)
const FARCASTER_NETWORK = 1 // Farcaster mainnet
const VALIDATION_ADDRESS = process.env.PRIVY_VALIDATION_ADDRESS as Address
const PRIVY_WALLET_ID = process.env.PRIVY_VALIDATION_CLIENT_ID!
const PRIVY_APP_ID = process.env.PRIVY_ID!
const PRIVY_SECRET = process.env.PRIVY_SECRET!
const NEYNAR_API_KEY = process.env.NEYNAR_API_KEY!
const SIGNER_UUID = process.env.ADDRESS_VALIDATION_SIGNER_UUID!
```
***
## 3. EIP-712 Domain & Types
These are taken from Farcaster's official EIP-712 spec:
```typescript privy-validation-signer.ts theme={"system"}
const EIP_712_FARCASTER_VERIFICATION_CLAIM = [
{ name: 'fid', type: 'uint256' },
{ name: 'address', type: 'address' },
{ name: 'blockHash',type: 'bytes32' },
{ name: 'network', type: 'uint8' },
] as const
const EIP_712_FARCASTER_DOMAIN = {
name: 'Farcaster Verify Ethereum Address',
version: '2.0.0',
salt: '0xf2d857f4a3edcb9b78b4d503bfe733db1e3f6cdc2b7971ee739626c97e86a558',
} as const
```
We recommend using their package to import these but they're providing for clarity.
***
## 4. Compose the Typed Data
Build the EIP-712 message to be signed. Make sure to include the correct `chainId` and `protocol` fields as these are different for smart account verification:
```typescript privy-validation-signer.ts theme={"system"}
async function getMessageToSign(address: Address, blockHash: `0x${string}`) {
return {
domain: { ...EIP_712_FARCASTER_DOMAIN, chainId: optimism.id },
types: { VerificationClaim: EIP_712_FARCASTER_VERIFICATION_CLAIM },
primaryType: 'VerificationClaim' as const,
message: {
fid: BigInt(FID),
address,
blockHash,
network: FARCASTER_NETWORK,
protocol: 0, // contract flow uses protocol=0
},
}
}
```
***
## 5. Compute the EIP-712 Hash
Use Viem's `hashTypedData` to get the digest for signing:
```typescript privy-validation-signer.ts theme={"system"}
const typedDataHash = hashTypedData({
domain: typedData.domain,
types: typedData.types,
primaryType: typedData.primaryType,
message: typedData.message,
})
```
We generate the `hashTypedData` directly due to an issue with privys typed data signers.
***
## 6. Sign via Privy RPC
Request a signature from Privy over the EIP-712 hash:
```typescript privy-validation-signer.ts theme={"system"}
async function signWithPrivy(hash: `0x${string}`) {
const resp = await fetch(
`https://api.privy.io/v1/wallets/${PRIVY_WALLET_ID}/rpc`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'privy-app-id': PRIVY_APP_ID,
'Authorization': `Basic ${Buffer.from(
`${PRIVY_APP_ID}:${PRIVY_SECRET}`
).toString('base64')}`,
},
body: JSON.stringify({ method: 'secp256k1_sign', params: { hash } }),
}
)
const { data } = await resp.json()
return data.signature as `0x${string}`
}
```
It is necessary to use `secp256k1_sign` due to the aforementioned issue with their typed signers.
***
## 7. Local Signature Verification
Double-check the signature locally before submitting:
```typescript privy-validation-signer.ts theme={"system"}
const ok = await client.verifyTypedData({
address: VALIDATION_ADDRESS,
domain: typedData.domain,
types: typedData.types,
primaryType: typedData.primaryType,
message: typedData.message,
signature: rpcSig,
})
console.log('Local verification:', ok)
```
***
## 8. Submit to Neynar
Send the verification to Neynar for on-chain registration:
```typescript privy-validation-signer.ts theme={"system"}
const neynarResp = await fetch(
'https://api.neynar.com/v2/farcaster/user/verification',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': NEYNAR_API_KEY,
},
body: JSON.stringify({
signer_uuid: SIGNER_UUID,
address: VALIDATION_ADDRESS,
block_hash: hash,
eth_signature: rpcSig,
verification_type: 1,
chain_id: optimism.id,
}),
}
)
console.log('Neynar response:', await neynarResp.json())
```
***
## 9. Error Handling
If anything fails, log and exit:
```typescript privy-validation-signer.ts theme={"system"}
} catch (err) {
console.error('Error in validation flow:', err)
process.exit(1)
}
```
***
## References & Further Reading
* [EIP-712 Spec](https://eips.ethereum.org/EIPS/eip-712)
* [Farcaster Hub: validateVerificationAddEthAddressBody](https://github.com/farcasterxyz/hub-monorepo/blob/main/packages/core/src/validations.ts)
* [Farcaster Hub: verifyVerificationEthAddressClaimSignature](https://github.com/farcasterxyz/hub-monorepo/blob/main/packages/core/src/crypto/eip712.ts)
* [Neynar Docs](https://docs.neynar.com/)
* [Privy Docs](https://docs.privy.io/)
If you have questions or want to share what you built, tag @neynar on Farcaster or join the Slack!
```typescript privy-validation-signer.ts theme={"system"}
/**
* Tutorial: EIP-712 verification with Privy RPC and Farcaster
*/
import { config } from 'dotenv'
import { Buffer } from 'buffer'
import { Address, createPublicClient, hashTypedData, http } from 'viem'
import { optimism } from 'viem/chains'
config()
// --- Constants & Configuration ---
const FID = Number(process.env.PRIVY_VALIDATION_FID) // Farcaster ID
const FARCASTER_NETWORK = 1 // Mainnet
const VALIDATION_ADDRESS = process.env.PRIVY_VALIDATION_ADDRESS as Address
const PRIVY_WALLET_ID = process.env.PRIVY_VALIDATION_CLIENT_ID!
const PRIVY_APP_ID = process.env.PRIVY_ID!
const PRIVY_SECRET = process.env.PRIVY_SECRET!
const NEYNAR_API_KEY = process.env.NEYNAR_API_KEY!
const SIGNER_UUID = process.env.ADDRESS_VALIDATION_SIGNER_UUID!
// --- EIP-712 Schemas (inlined, taken from eip712 in @farcaster/hub-web) ---
const EIP_712_FARCASTER_VERIFICATION_CLAIM = [
{ name: 'fid', type: 'uint256' },
{ name: 'address', type: 'address' },
{ name: 'blockHash',type: 'bytes32' },
{ name: 'network', type: 'uint8' },
] as const // eip712.EIP_712_FARCASTER_VERIFICATION_CLAIM from @farcaster/hub-web
const EIP_712_FARCASTER_DOMAIN = {
name: 'Farcaster Verify Ethereum Address',
version: '2.0.0',
salt: '0xf2d857f4a3edcb9b78b4d503bfe733db1e3f6cdc2b7971ee739626c97e86a558',
} as const // eip712.EIP_712_FARCASTER_DOMAIN from @farcaster/hub-web
// --- Helper: compose EIP-712 message ---
async function getMessageToSign(
address: Address,
blockHash: `0x${string}`
) {
return {
domain: { ...EIP_712_FARCASTER_DOMAIN, chainId: optimism.id },
types: { VerificationClaim: EIP_712_FARCASTER_VERIFICATION_CLAIM },
primaryType: 'VerificationClaim' as const,
message: {
fid: BigInt(FID),
address,
blockHash,
network: FARCASTER_NETWORK,
protocol: 0, // contract flow uses protocol=0
},
}
}
// --- Helper: sign hash via Privy RPC ---
async function signWithPrivy(hash: `0x${string}`) {
const resp = await fetch(
`https://api.privy.io/v1/wallets/${PRIVY_WALLET_ID}/rpc`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'privy-app-id': PRIVY_APP_ID,
'Authorization': `Basic ${Buffer.from(
`${PRIVY_APP_ID}:${PRIVY_SECRET}`
).toString('base64')}`,
},
body: JSON.stringify({ method: 'secp256k1_sign', params: { hash } }),
}
)
const { data } = await resp.json()
return data.signature as `0x${string}`
}
// --- Main Flow ---
async function main() {
try {
// 1) Fetch block hash
const client = createPublicClient({ chain: optimism, transport: http() })
const { hash } = await client.getBlock()
// 2) Build EIP-712 message
const typedData = await getMessageToSign(VALIDATION_ADDRESS, hash)
console.log('Message to sign:', typedData)
// 3) Compute EIP-712 hash
const typedDataHash = hashTypedData({
domain: typedData.domain,
types: typedData.types,
primaryType: typedData.primaryType,
message: typedData.message,
})
// 4) Sign via Privy RPC
const rpcSig = await signWithPrivy(typedDataHash)
console.log('Privy RPC signature:', rpcSig)
// 5) Verify locally
const ok = await client.verifyTypedData({
address: VALIDATION_ADDRESS,
domain: typedData.domain,
types: typedData.types,
primaryType: typedData.primaryType,
message: typedData.message,
signature: rpcSig,
})
console.log('Local verification:', ok)
// 6) Submit to Neynar
const neynarResp = await fetch(
'https://api.neynar.com/v2/farcaster/user/verification',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': NEYNAR_API_KEY,
},
body: JSON.stringify({
signer_uuid: SIGNER_UUID,
address: VALIDATION_ADDRESS,
block_hash: hash,
eth_signature: rpcSig,
verification_type: 1,
chain_id: optimism.id,
}),
}
)
console.log('Neynar response:', await neynarResp.json())
} catch (err) {
console.error('Error in validation flow:', err)
process.exit(1)
}
}
main()
```
# Solana Integration Guide for Farcaster Mini Apps
Source: https://docs.neynar.com/docs/solana-miniapp-features
Learn how to integrate Solana wallet features in your Farcaster Mini App with conditional support, message signing, and transaction handling
# Solana Integration Guide for Farcaster Mini Apps
Guide for using Solana wallet features in your Farcaster Mini App template.
## How It Works
### Conditional Solana Support
Not all Farcaster clients support Solana wallets, so your app should gracefully handle both scenarios.
```typescript theme={"system"}
import { useHasSolanaProvider } from "~/components/providers/SafeFarcasterSolanaProvider";
import { useConnection as useSolanaConnection, useWallet as useSolanaWallet } from '@solana/wallet-adapter-react';
function MyComponent() {
const hasSolanaProvider = useHasSolanaProvider();
// Only declare Solana hooks when provider is available
let solanaWallet, solanaPublicKey, solanaSignMessage, solanaAddress;
if (hasSolanaProvider) {
solanaWallet = useSolanaWallet();
({ publicKey: solanaPublicKey, signMessage: solanaSignMessage } = solanaWallet);
solanaAddress = solanaPublicKey?.toBase58();
}
return (
{/* EVM features always available */}
{/* Solana features when supported, not all clients support Solana */}
{solanaAddress && (
Solana
Address: {solanaAddress}
)}
);
}
```
### Sign Message
Solana message signing requires converting text to bytes and handling the response properly for browser compatibility.
```typescript theme={"system"}
function SignSolanaMessage({ signMessage }: { signMessage?: (message: Uint8Array) => Promise }) {
const [signature, setSignature] = useState();
const [signError, setSignError] = useState();
const [signPending, setSignPending] = useState(false);
const handleSignMessage = useCallback(async () => {
setSignPending(true);
try {
if (!signMessage) {
throw new Error('no Solana signMessage');
}
const input = new TextEncoder().encode("Hello from Solana!");
const signatureBytes = await signMessage(input);
const signature = btoa(String.fromCharCode(...signatureBytes));
setSignature(signature);
setSignError(undefined);
} catch (e) {
if (e instanceof Error) {
setSignError(e);
}
} finally {
setSignPending(false);
}
}, [signMessage]);
return (
<>
{signError && renderError(signError)}
{signature && (
)}
>
);
}
```
## Key Points
* Always check `useHasSolanaProvider()` before rendering Solana UI
* Use `TextEncoder` and `btoa` for browser-compatible message signing
* Simulate transactions before sending to catch errors early
* Import Solana hooks from `@solana/wallet-adapter-react` not `@farcaster/mini-app-solana`
* Replace placeholder addresses with real addresses for your app
### Custom Program Interactions
For calling your own Solana programs, you'll need to serialize instruction data and handle program-derived addresses.
```typescript theme={"system"}
import {
TransactionInstruction,
SYSVAR_RENT_PUBKEY,
SystemProgram
} from '@solana/web3.js';
import * as borsh from 'borsh';
class InstructionData {
instruction: number;
amount: number;
constructor(props: { instruction: number; amount: number }) {
this.instruction = props.instruction;
this.amount = props.amount;
}
}
const instructionSchema = new Map([
[InstructionData, {
kind: 'struct',
fields: [
['instruction', 'u8'],
['amount', 'u64']
]
}]
]);
async function callCustomProgram(programId: string, instruction: number, amount: number) {
if (!publicKey) throw new Error('Wallet not connected');
// Serialize instruction data
const instructionData = new InstructionData({ instruction, amount });
const serializedData = borsh.serialize(instructionSchema, instructionData);
// Create program-derived address (if needed)
const [programDataAccount] = await PublicKey.findProgramAddress(
[Buffer.from('your-seed'), publicKey.toBuffer()],
new PublicKey(programId)
);
const transaction = new Transaction();
transaction.add(
new TransactionInstruction({
keys: [
{ pubkey: publicKey, isSigner: true, isWritable: false },
{ pubkey: programDataAccount, isSigner: false, isWritable: true },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
],
programId: new PublicKey(programId),
data: Buffer.from(serializedData),
})
);
// ... rest of transaction setup and sending
}
```
For advanced contract interactions, token transfers, and error handling patterns, see the template's Demo.tsx component.
# Create and Manage Miniapp Studio Deployments
Source: https://docs.neynar.com/docs/studio-api-miniapp-management
Complete guide to creating, managing, and prompting miniapp generator deployments using the Miniapp Studio API
The Miniapp Studio API is an allowlisted API and not publicly available. [Contact the Neynar team](https://neynar.com/slack) for more information.
This tutorial demonstrates how to use the Miniapp Studio API to create and manage Farcaster miniapp generator deployments programmatically. You'll learn how to create deployments, send prompts for code generation, manage multiple deployments, and clean up resources.
## Prerequisites
* An allowlisted Neynar API key
* Your Farcaster ID (FID)
## API Endpoints Overview
The Miniapp Studio API provides the following endpoints for managing deployments:
* **[Create Deployment](/reference/create-deployment)**: `POST /v2/studio/deployment/` - Create a new miniapp generator deployment
* **[Get Deployment](/reference/get-deployment)**: `GET /v2/studio/deployment/by-name-and-fid` - Fetch deployment details
* **[List Deployments](/reference/list-deployments)**: `GET /v2/studio/deployment/` - List all deployments for a user
* **[Prompt Deployment](/reference/prompt-deployment)**: `POST /v2/studio/deployment/prompt` - Send a prompt to generate code
* **[Stream Prompt](/reference/prompt-deployment-stream)**: `POST /v2/studio/deployment/prompt/stream` - Send a prompt and stream AI responses in real-time
* **[Associate Account](/reference/associate-deployment)**: `POST /v2/studio/deployment/account-association` - Link a generated app to Farcaster account
* **[Delete Deployment](/reference/delete-deployment)**: `DELETE /v2/studio/deployment/` - Remove deployments
## Step 1: Create a Deployment
Start by creating a new deployment for your Farcaster account using the [Create Deployment](/reference/create-deployment) endpoint:
```bash theme={"system"}
curl https://api.neynar.com/v2/studio/deployment \
-X POST \
-H "content-type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"fid": YOUR_FID
}'
```
**Response:**
```json theme={"system"}
{
"created_at": "2025-09-04T16:24:35.185Z",
"namespace": "miniapp-generator-fid-1568",
"name": "server-250904162435170",
"isReady": false
}
```
Save the `name` field from the response - you'll need it to interact with this deployment.
You can create multiple deployments per FID, as each FID-name combination refers to a single deployment.
## Step 2: Check Deployment Readiness
After creating a deployment, it may take up to a minute to become ready. You should poll the [Get Deployment](/reference/get-deployment) endpoint until `isReady` is `true`:
```bash theme={"system"}
curl "https://api.neynar.com/v2/studio/deployment/by-name-and-fid?name=YOUR_DEPLOYMENT_NAME&fid=YOUR_FID" \
-H "x-api-key: YOUR_API_KEY"
```
**Response (when ready):**
```json theme={"system"}
{
"created_at": "2025-09-04T16:24:35.185Z",
"namespace": "miniapp-generator-fid-1568",
"name": "server-250904162435170",
"isReady": true,
"url": "https://server-250904162435170.studio.neynar.com"
}
```
## Step 3: Prompt the Deployment for Code Generation
Once your deployment shows `isReady: true`, you can send prompts to generate miniapp code. Since generation can take 5-10 minutes, the **streaming option is recommended** for better user experience.
### Option A: Streaming Prompt (Recommended)
The [Stream Prompt](/reference/prompt-deployment-stream) endpoint provides real-time progress updates using Server-Sent Events (SSE):
```bash theme={"system"}
curl https://api.neynar.com/v2/studio/deployment/prompt/stream \
-X POST \
-H "content-type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"fid": YOUR_FID,
"name": YOUR_DEPLOYMENT_NAME,
"prompt": "generate a mini app that lets any user edit the main page headline, with orange and blue theme",
"action": "code-create"
}'
```
**Streaming Response Example:**
```
data: {"type":"connection","message":"Streaming started"}
data: {"type":"system","subtype":"init","message":"Initializing..."}
data: {"type":"assistant","message":"Creating your miniapp..."}
data: {"type":"result","subtype":"success","message":"Deployment complete"}
```
The streaming endpoint returns various message types:
* `SDKAssistantMessage`: Claude's responses during generation
* `SDKUserMessage`: User prompts being processed
* `SDKResultMessage`: Final results (success/error)
* `SDKSystemMessage`: System initialization messages
* `ErrorMessage`: Any errors during generation
All message types are exported by the `@anthropic-ai/claude-code` npm package except the `ErrorMessage` type, which simply consists of `type: "error"`, an error message, and a timestamp.
### Option B: Standard REST API (Wait for Complete Response)
The [Prompt Deployment](/reference/prompt-deployment) endpoint will wait until generation is complete before returning a response:
```bash theme={"system"}
curl https://api.neynar.com/v2/studio/deployment/prompt \
-X POST \
-H "content-type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"fid": YOUR_FID,
"name": YOUR_DEPLOYMENT_NAME,
"prompt": "generate a mini app that lets any user edit the main page headline, with orange and blue theme",
"action": "code-create"
}'
```
This endpoint will wait 5-10 minutes until generation completes before returning a response. Use the streaming endpoint for real-time progress updates.
### Action Types for Prompt Operations
Both prompt endpoints support an optional `action` parameter that specifies the type of operation to perform on your deployment:
* **`code-create`** (default): Generate new code for a fresh project. Use this when starting a new miniapp from scratch.
* **`code-edit`**: Make edits to an existing project that has already been prompted at least once. Use this for modifying or adding features to your miniapp.
* **`debug`**: Debug issues with your generated miniapp. The AI will analyze your code and help identify and fix problems.
* **`info`**: Get information about your generated miniapp.
* **`logs`**: Query the latest server logs for further debugging. This helps you understand runtime issues and errors for manual debugging.
**Example with edit action parameter:**
```bash theme={"system"}
curl https://api.neynar.com/v2/studio/deployment/prompt \
-X POST \
-H "content-type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"fid": YOUR_FID,
"name": YOUR_DEPLOYMENT_NAME,
"prompt": "add a user authentication feature",
"action": "code-edit"
}'
```
## Step 4: Associate Deployment with Account
Link your deployment to your Farcaster account with [JFS](https://github.com/farcasterxyz/protocol/discussions/208) using the [Associate Account](/reference/associate-deployment) endpoint:
```bash theme={"system"}
curl https://api.neynar.com/v2/studio/deployment/account-association \
-X POST \
-H "content-type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"fid": YOUR_FID,
"name": YOUR_DEPLOYMENT_NAME,
"account_association": {
"header": JFS_HEADER,
"payload": JFS_PAYLOAD,
"signature": JFS_SIGNATURE
}
}'
```
The [JFS (JSON Farcaster Signature)](https://github.com/farcasterxyz/protocol/discussions/208) object is required for domain association. This verifies that you own both the Farcaster account and the domain. A user's JFS siganture can be accessed with the [signManifest feature](https://miniapps.farcaster.xyz/docs/sdk/actions/sign-manifest) of the Mini App SDK.
🎉 **Your miniapp should now be fully functional!** You can access it at the URL returned in Step 2.
## Additional API Actions
Beyond the core deployment workflow, you can use these endpoints to manage your deployments:
### List All Deployments
View all deployments associated with your FID using the [List Deployments](/reference/list-deployments) endpoint:
```bash theme={"system"}
curl "https://api.neynar.com/v2/studio/deployment?fid=YOUR_FID" \
-H "x-api-key: YOUR_API_KEY"
```
**Response:**
```json theme={"system"}
{
"deployments": [
{
"name": "server-250904162435170",
"display_name": "tetris app",
"created_at": "2025-09-04T16:24:35.185Z",
"isReady": true
},
{
"name": "server-250904173546281",
"created_at": "2025-09-04T17:35:46.281Z",
"isReady": false
}
]
}
```
### Clean Up Deployments
You can delete deployments in two ways using the [Delete Deployment](/reference/delete-deployment) endpoint:
#### Delete a Specific Deployment
```bash theme={"system"}
curl https://api.neynar.com/v2/studio/deployment \
-X DELETE \
-H "content-type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"fid": YOUR_FID,
"name": YOUR_DEPLOYMENT_NAME
}'
```
#### Delete All Deployments for Your FID
```bash theme={"system"}
curl https://api.neynar.com/v2/studio/deployment \
-X DELETE \
-H "content-type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"fid": YOUR_FID
}'
```
Deleting all deployments will remove every deployment associated with your FID. This action cannot be undone.
## Best Practices
1. **Always save deployment names**: Store the deployment name from the creation response to interact with it later
2. **Use streaming for long operations**: The streaming endpoint provides better user experience for prompt operations
3. **Clean up unused deployments**: Regularly delete deployments you're no longer using to keep your account organized
## Troubleshooting
### Deployment Not Ready
If `isReady` is false, wait a few moments before trying to prompt the deployment. New deployments need time to initialize.
### Streaming Connection Issues
For streaming endpoints, ensure your HTTP client supports Server-Sent Events (SSE). Most modern libraries and tools support this natively.
## Next Steps
* Explore the [Mini App Authentication](/docs/mini-app-authentication) to add user sign-in to your generated miniapps
* Learn about [Sending Notifications](/docs/send-notifications-to-mini-app-users) to engage users
* Check out the [Neynar Starter Kit](https://github.com/neynarxyz/create-farcaster-mini-app) for building miniapps from templates
If you have questions or need help, reach out to the Neynar team.
# Supercharge EVM & Solana Sign-in
Source: https://docs.neynar.com/docs/supercharge-your-sign-in-with-ethereum-onboarding-with-farcaster
Supercharge Sign In with Ethereum and/or Solana in your app with Farcaster profile and social graph data
## TL;DR
## Make life simpler for yourself and your users
Building user profiles and social graphs for each user from scratch requires a lot of time and effort from developers and users. In some cases, graphs never get enough traction to add value. User data on a protocol like Farcaster can be used across apps like Alfafrens, Drakula, Supercast, Warpcast, etc.
Instead of asking users to build their profiles and graphs from scratch, apps like [Bracket](https://bracket.game) and [Drakula](https://drakula.app) have a "connect with Farcaster" feature that pulls info like username and pfp. This works no matter what chain the app is using, incl. non evm chains like Solana. Unlike Web2, access to this information cannot be restricted.
On [Sonata](https://sonata.tips), instead of signing up, setting up a new profile, and creating your feed from scratch, you can sign in with Farcaster. It will generate a feed of music for you based on the people you already follow.
## Set it up in less than 15 mins
**If you’re using embedded wallets** in your app then those wallets probably aren’t connected to the user’s Farcaster account. In this case, you can add an option to let users [connect](/docs/how-to-let-users-connect-farcaster-accounts-with-write-access-for-free-using-sign-in-with-neynar-siwn) their Farcaster profile to your app. With our [react SDK](https://www.npmjs.com/package/@neynar/react) you can add sign-in with neynar by just adding the NeynarAuthButton component:
```jsx JSX theme={"system"}
```
Connecting their profile will give you their `fid` which you can then use to fetch [profiles](/reference/fetch-bulk-users) and [followers](/reference/fetch-user-followers) information.
**If you’re not using embedded wallets** you can either
1. let the user connect their profile (same as above) OR
2. fetch user profiles connected to their Ethereum or Solana address via [this API](/reference/fetch-bulk-users-by-eth-or-sol-address)
You can even [onboard new users](/docs/how-to-create-a-new-farcaster-account-with-neynar) to Farcaster from within your app seamlessly.
### Profile
More details on fetching user profile data. You can call the API like this in your node app with user's wallet address:
```javascript Javascript theme={"system"}
const url = 'https://api.neynar.com/v2/farcaster/user/bulk-by-address?addresses=0x6bF08768995E7430184a48e96940B83C15c1653f';
const options = {
method: 'GET',
headers: {accept: 'application/json', api_key: 'NEYNAR_API_DOCS'}
};
fetch(url, options)
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error('error:' + err));
```
It will provide you with a response like:
```json JSON theme={"system"}
{
"0x6bf08768995e7430184a48e96940b83c15c1653f": [
{
"object": "user",
"fid": 9019,
"custody_address": "0x5eb2696eed6a70a244431bc110950adeb5ef6101",
"username": "avneesh",
"display_name": "Avneesh",
"pfp_url": "https://i.imgur.com/oaqwZ8i.jpg",
"profile": {
"bio": {
"text": "full stack web3 developer building cool shit and teaching others avneesh.tech"
}
},
"follower_count": 6067,
"following_count": 382,
"verifications": [
"0x6bf08768995e7430184a48e96940b83c15c1653f"
],
"verified_addresses": {
"eth_addresses": [
"0x6bf08768995e7430184a48e96940b83c15c1653f"
],
"sol_addresses": [
"2R4bHmSBHkHAskerTHE6GE1Fxbn31kaD5gHqpsPySVd7"
]
},
"active_status": "inactive",
"power_badge": false
}
]
}
```
You can then use this info in your app to populate info like name, bio, pfp, etc. of the user!
### Social Graph
You can also import the user’s social graph by fetching their followers and following. To get who the user is following, use this [Following](/reference/fetch-user-following) API where you need to pass in the FID:
```javascript Javascript theme={"system"}
curl --request GET \
--url 'https://api.neynar.com/v2/farcaster/following?fid=2&viewer_fid=3&sort_type=desc_chron&limit=25' \
--header 'accept: application/json' \
--header 'x-api-key: NEYNAR_API_DOCS' \
--header 'x-neynar-experimental: false'
```
and it will output a list of users the given `fid` is following:
```json JSON theme={"system"}
{
"users": [
{
"object": "follow",
"user": {
"object": "user",
"fid": 648026,
"custody_address": "0xe1a881a22aa75eabc96275ad7e6171b3def9a195",
"username": "chiziterevivian",
"display_name": "Chizitere Vivian",
"pfp_url": "https://images.farcaster.phaver.com/insecure/raw:t/ZjE4NGNkYTY3YTljMzJjMDQzOGNhNzc2ZTQwN2FiOGU.jpeg",
"profile": {
"bio": {
"text": "Love simplicity and originality"
}
},
"follower_count": 19,
"following_count": 154,
"verifications": [],
"verified_addresses": {
"eth_addresses": [],
"sol_addresses": []
},
"active_status": "inactive",
"power_badge": false
}
},
...
],
"next": {
"cursor": "eyJ0aW1lc3RhbXAiOiIyMDI0LTA2LTI1IDE2OjMyOjM3LjAwMDAwMDAiLCJmaWQiOjcyMzg0OX0%3D"
}
}
```
You can use this list to suggest users they should connect with on your app. Similarly, you can get the list of users the user follows using this [Follows API](/reference/fetch-user-followers).
## Deliver a richer UX in your product
All put together, you can enrich user profiles and personalize user experience significantly in your product by simply looking up users by their connected address on Farcaster.
If any questions, reach out to us on [Warpcast](https://warpcast.com/~/channel/neynar) or [Slack](https://neynar.com/slack)
# Sponsor Signers
Source: https://docs.neynar.com/docs/two-ways-to-sponsor-a-farcaster-signer-via-neynar
Sponsor it yourself or let Neynar pay for it
### This guide builds on [Write data to Farcaster using Neynar managed signers](/docs/integrate-managed-signers). Useful to read that first if you haven't already.
There are two ways to sponsor a signer on behalf of a user. This saves the user from paying for it and increases conversion in your funnel. You have two options to sponsor:
You can choose to have Neynar sponsor the signer on your behalf, we will charge you credits that correspond to the sponsorship fees.
You can sponsor the signer directly. Your application must be signed up on Warpcast and have warps ≥ 100
## 1. Let Neynar sponsor it
Set `sponsored_by_neynar` to **true** as shown below, the rest will remain the same as in the parent [guide](/docs/integrate-managed-signers).
```typescript getSignedKey.ts theme={"system"}
const options = {
sponsor: {
sponsored_by_neynar: true
}};
const signedKey = await neynarClient.registerSignedKey(
createSigner.signer_uuid,
fid,
deadline,
signature,
options
);
```
When you see "sponsored by @your\_app\_fname" (@avneeshtest in this case) on the Warpcast screen, it's because you're signing a message. Even though it says "sponsored by @your\_app\_fname," the warps are being deducted from Neynar's account.
The signer is still branded under your name (@your\_app\_fname), Neynar is covering the costs and charging you credits in the background. The user is unaware of Neynar and thinks your app is covering the costs.
## 2. Sponsor it with your app
You can do this very easily, just follow the steps below!
Start by setting `sponsored_by_neynar` to **false**. Then, in the generate signature function add the following to generate a `sponsorSignature`:
```typescript getSignedKey.ts theme={"system"}
const sponsorSignature = await account.signMessage({
message: { raw: sigHex },
});
sponsor = {
sponsored_by_neynar: false,
signature: sponsorSignature,
fid: FID,
};
```
Then, add this sponsor object to the object we're returning like this:
```typescript getSignedKey.ts theme={"system"}
return { deadline, signature: sigHex, sponsor };
```
Finally, you can get the sponsor object from the `generate_signature` function and pass it in as an option in the `registerSignedKey` function like this:
```typescript getSignedKey.ts theme={"system"}
const { deadline, signature, sponsor } = await generate_signature(
createSigner.public_key
);
if (deadline === 0 || signature === "") {
throw new Error("Failed to generate signature");
}
const fid = await getFid();
const options = sponsor ? { sponsor } : undefined;
const signedKey = await neynarClient.registerSignedKey(
createSigner.signer_uuid,
fid,
deadline,
signature,
options
);
```
`signedKey` will have `signer_approval_url`. Make it available (either by creating a QR Code for the desktop application or allowing user to deeplink into warpcast by clicking on it in mobile device).
If you go ahead and try signing in now, it should show "Onchain fees sponsored by @xyz"
# Understanding Signers in the Farcaster Protocol
Source: https://docs.neynar.com/docs/understanding-signers-in-the-farcaster-protocol-balancing-security-and-convenience
By the end of this tutorial, you'll have a Farcaster signer ready to authorize messages on the Farcaster protocol.
But first, some explanation.
In Farcaster, Signers are specialized Ed25519 key pairs designed for authorizing messages in the Farcater protocol.
Why not use normal Ethereum private key? Signers reduce the risk of the main custody key being stolen, as Signers have limited capabilities and are easier to revoke and replace if compromised.
Why Ed25519? Signers are Ed25519 to avoid confusion with Ethereum's ECDSA keys, ensuring that Signers are used solely for Farcaster message authentication and not for financial transactions.
Users link these Signers to their main Ethereum address by signing a message, creating a chain of trust.
Signers are managed through smart contracts on the Ethereum blockchain (Optimism L2). A user's custody address (associated with their fid) is the only entity that can add or remove Signers for that fid. This ensures that the user retains ultimate control over who can act on their behalf.
Now, let's create signers from scratch with TypeScript:
# Unfollow Inactive Users
Source: https://docs.neynar.com/docs/unfollowing-inactive-farcaster-users-with-neynar-sdk
Unfollow Farcaster users with Neynar
This guide demonstrates how to unfollow Farcasters who hasn't been active in the past 3 months.
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 theme={"system"}
// npm i @neynar/nodejs-sdk
import { NeynarAPIClient } 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 client = new NeynarAPIClient(process.env.NEYNAR_API_KEY);
const signer = process.env.NEYNAR_SIGNER;
```
In the fetchFollowing endpoint, there's an `activeStatus` field that can be used to filter out inactive Farcasters.
```javascript Javascript theme={"system"}
const fid = 3;
const users = await client.fetchUserFollowing(fid);
console.log(users);
```
Will result in this:
```json theme={"system"}
{
"result": {
"users": [
{
"fid": 3461,
"custodyAddress": "0x094ce1566a83b632e59d50e2aa9618d0f4dcd432",
"username": "jonahg",
"displayName": "Jonah Grant",
"pfp": {
"url": "https://i.imgur.com/x51eW6a.jpg"
},
"profile": {
"bio": {
"text": "Software engineer in New York City",
"mentionedProfiles": []
}
},
"followerCount": 15,
"followingCount": 0,
"verifications": [],
"activeStatus": "inactive",
"timestamp": "2023-12-09T05:01:59.000Z"
},
{
"fid": 18198,
"custodyAddress": "0x718aea83c0ee2165377335a0e8ed48f1c5a34d63",
"username": "alive.eth",
"displayName": "Ali Yahya",
"pfp": {
"url": "https://i.imgur.com/1PASQSb.jpg"
},
"profile": {
"bio": {
"text": "GP @a16zcrypto. Previously Google Brain, GoogleX, Stanford Computer Science.",
"mentionedProfiles": []
}
},
"followerCount": 88,
"followingCount": 76,
"verifications": [
"0x990a73079425d2b0ec746e3cc989e903306bb6c7"
],
"activeStatus": "inactive",
"timestamp": "2023-12-08T16:58:51.000Z"
},
{
"fid": 9528,
"custodyAddress": "0x80ef8b51dbba18c50b3451acea9deebc7dfcd131",
"username": "skominers",
"displayName": "Scott Kominers ",
"pfp": {
"url": "https://i.imgur.com/lxEkagM.jpg"
},
"profile": {
"bio": {
"text": "@a16zcrypto • Harvard/HBS • 🧩 • QED",
"mentionedProfiles": []
}
},
"followerCount": 289,
"followingCount": 190,
"verifications": [
"0x34202f199ef058302dcced326a0105fe2db53e12"
],
"activeStatus": "active",
"timestamp": "2023-12-08T16:56:30.000Z"
}
],
"next": {
"cursor": "eyJ0aW1lc3RhbXAiOiIyMDIzLTEyLTA4IDE2OjU2OjMwLjAwMDAwMDAiLCJmaWQiOjk1Mjh9"
}
}
}
```
To get fids of inactive Farcasters, we can use the `activeStatus` field to filter out active Farcasters.
```javascript Javascript theme={"system"}
const inactiveFids = users.result.users
.filter((user) => user.activeStatus === "inactive")
.map((user) => user.fid);
console.log(inactiveFids); // [ 3461, 18198 ]
```
And this `inactiveFids` value can be passed to client.unfollowUser to unfollow inactive Farcasters.
```javascript Javascript theme={"system"}
await client.unfollowUser(signer, inactiveFids);
```
Which will result in this:
```json theme={"system"}
{
"success": true,
"details": [
{
"success": true,
"target_fid": 3461
},
{
"success": true,
"target_fid": 18198
},
]
}
```
That's it! You can now unfollow inactive Farcasters easily with the Neynar SDK.
### 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!
# ETH Address FID Contract
Source: https://docs.neynar.com/docs/verifications-contract
Get an addresses' connected fid on-chain.
Fetching an addresses' connected fid [with Neynar's APIs](/docs/fetching-farcaster-user-based-on-ethereum-address), [Neynar's Parquet Files](https://dash.readme.com/project/neynar/v2.0/docs/parquet), [Neynar's Indexer Service](https://dash.readme.com/project/neynar/v2.0/docs/indexer-service-pipe-farcaster-data) or [Neynar's Hosted Database](https://dash.readme.com/project/neynar/v2.0/docs/sql) is easy, but until now that data wasn't accessible to smart contracts on any L2s. Now, on the Base Mainnet and Sepolia testnet, smart contracts can query the fid linked to any ETH address.
## The Contract
| **Chain** | **Address** | **Deploy Transaction** |
| ------------ | ------------------------------------------ | ------------------------------------------------------------------ |
| Base Mainnet | 0xdB1eCF22d195dF9e03688C33707b19C68BdEd142 | 0xc61c054a4bc269d4263bd10933a664585ac8878eab1e1afe460220fb18e718ca |
| Base Sepolia | 0x3906b52ac27bae8bc5cc8e4e10a99665b78e35ac | 0x8db23c7bca5cc571cde724fd258ae4d7bf842c3a1b2cf495300bf819ebaea0ce |
* [Read the Proxy Contract on the Base Sepolia Explorer](https://sepolia.basescan.org/address/0x3906b52ac27bae8bc5cc8e4e10a99665b78e35ac#readProxyContract). This is the upgradeable proxy contract you should use.
* [Verifications V4 Code on the Base Sepelia Explorer](https://sepolia.basescan.org/address/0xe2f971D765E9c3F8a2641Ef5fdAec4dD9c67Cf11#code). This is an upgradeable implementation contract. There is no state here. This is the code that the proxy contract is currently using.
## The Interface
The V4 interface is quite simple:
```solidity Slidity theme={"system"}
interface IVerificationsV4Reader {
function getFid(address verifier) external view returns (uint256 fid);
function getFidWithEvent(address verifier) external returns (uint256 fid);
function getFids(address[] calldata verifiers) external view returns (uint256[] memory fid);
}
```
If the `getFid` call returns `0`there is no verification for that address.
If you can spare the gas and would like us to know that you are using our contract, please use `getFidWithEvent`.
A simple example of a HelloWorld contract:
```solidity Solidity theme={"system"}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
interface IVerificationsV4Reader {
function getFid(address verifier) external view returns (uint256 fid);
function getFidWithEvent(address verifier) external returns (uint256 fid);
function getFids(address[] calldata verifiers) external view returns (uint256[] memory fid);
}
contract HelloWorld {
IVerificationsV4Reader immutable verifications;
constructor(IVerificationsV4Reader _verifications) {
verifications = _verifications;
}
function requireVerification() public view returns (uint256) {
uint256 fid = verifications.getFid(msg.sender);
if (fid == 0) {
revert("!fid");
}
return fid;
}
}
```
## The 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
# What are Mini Apps?
Source: https://docs.neynar.com/docs/what-are-mini-apps
Overview of Farcaster Mini Apps
# What are Farcaster Mini Apps?
Mini Apps are interactive applications that run directly within the Farcaster ecosystem. They allow developers to build custom experiences for Farcaster users, ranging from games and utilities to social and productivity tools—all accessible without leaving the Farcaster platform.
## Key Features
* **Instant Access:** Launch instantly inside Farcaster, no installation required
* **Native-like Experience:** Built with web technologies but feel like native apps, with deep integration into Farcaster
* **Seamless Integration:** Interact with Farcaster user data, casts, reactions, and more
* **Personalization:** Create personalized experiences, such as custom share images and user-specific content
* **Open Platform:** Anyone can build and publish a Mini App using open source tools like the Neynar Starter Kit
* **Mobile Notifications:** Re-engage users and boost retention
* **Integrated Ethereum Wallet:** Enable seamless, permissionless transactions within your app
* **Social Discovery:** Users find and share Mini Apps through feeds and app stores, driving viral growth
* **Optional Neynar Services:** Use Neynar APIs for advanced features like notifications, analytics, and managed signers
## Why Build a Mini App?
Mini Apps are ideal for:
* Rapid prototyping and launching new ideas
* Engaging Farcaster users with interactive content
* Leveraging Farcaster's social graph and messaging features
* Creating viral experiences with personalized sharing
## Getting Started
To start building your own Mini App, check out the [Create Mini App in \< 60s](/docs/create-farcaster-miniapp-in-60s) guide or explore the [Neynar Starter Kit](https://github.com/neynarxyz/create-farcaster-mini-app).
For more advanced integrations, see:
* [Mini App Authentication](/docs/mini-app-authentication)
* [Integrate Managed Signers](/docs/integrate-managed-signers)
***
For questions or feature requests, reach out on [Slack](https://neynar.com/slack) or connect with the team on Farcaster.
# Notifications for FID
Source: https://docs.neynar.com/docs/what-does-dwreths-farcaster-notification-look-like
Fetch notifications for any Farcaster user
### Related API: [Fetch notifications for user](/reference/fetch-all-notifications)
This guide demonstrates how to fetch notifications (inbound mentions, replies, likes, recasts, quotes) of a Farcaster user 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 theme={"system"}
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);
```
Then fetch the notifications:
```javascript Javascript theme={"system"}
const dwrFID = 3;
const notifications = await client.fetchAllNotifications({fid:dwrFID});
console.log(notifications);
```
Example output:
```json theme={"system"}
{
notifications: [
{
object: "notification",
most_recent_timestamp: "2023-11-28T11:11:11.000Z",
type: "likes",
cast: [Object ...],
reactions: [
[Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...]
]
}, {
object: "notification",
most_recent_timestamp: "2023-11-28T11:10:56.000Z",
type: "quote",
cast: [Object ...],
quotes: [
[Object ...], [Object ...]
]
}, {
object: "notification",
most_recent_timestamp: "2023-11-28T11:09:16.000Z",
type: "likes",
cast: [Object ...],
reactions: [
[Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...]
]
}, {
object: "notification",
most_recent_timestamp: "2023-11-28T11:05:59.000Z",
type: "follows",
follows: [
[Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...]
]
}, {
object: "notification",
most_recent_timestamp: "2023-11-28T10:25:51.000Z",
type: "likes",
cast: [Object ...],
reactions: [
[Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...],
[Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...], [Object ...]
]
}
],
next: {
cursor: "eyJ0aW1lc3RhbXAiOiIyMDIzLTExLTI4IDEwOjI1OjUxLjAwMDAwMDAifQ=="
}
}
```
So that's what @dwr.eth sees on his Farcaster notification! To fetch the next page of notifications, use the cursor:
```javascript Javascript theme={"system"}
const nextNotifications = await client.fetchAllNotifications({
fid: dwrFID,
cursor: notifications.next.cursor,
});
```
To only fetch specific types of notifications like replies, mentions, and quotes, use the fetchMentionAndReplyNotifications function:
```javascript Javascript theme={"system"}
const mentionsAndReplies = await client.fetchAllNotifications({
fid: dwrFID,
});
console.log(mentionsAndReplies);
```
Example output:
```json theme={"system"}
{
result: {
notifications: [
[Object ...], [Object ...], [Object ...], [Object ...], [Object ...]
],
next: {
cursor: "eyJ0aW1lc3RhbXAiOiIyMDIzLTExLTI4IDA3OjI5OjI4LjAwMDAwMDAifQ=="
}
}
}
```
To fetch the next page of mentions and replies, use the cursor:
```javascript Javascript theme={"system"}
const nextMentionsAndReplies = await client.fetchAllNotifications({
fid: dwrFID,
cursor: mentionsAndReplies.next.cursor,
});
console.log(nextMentionsAndReplies);
```
That's it! You can now fetch notifications of any Farcaster user.
### 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!
# Feed of Given Farcaster FID
Source: https://docs.neynar.com/docs/what-does-vitalikeths-farcaster-feed-look-like
Show a personalized feed of casts for a specific user on Farcaster
### Related API reference [Fetch User Following Feed](/reference/fetch-user-following-feed)
With Farcaster data being public, we can see what @vitalik.eth sees on his feed (reverse chronological of following). In this guide, we'll do exactly that.
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 theme={"system"}
// npm i @neynar/nodejs-sdk
import { NeynarAPIClient, FeedType, FilterType } 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 client = new NeynarAPIClient(process.env.NEYNAR_API_KEY);
```
Now, we can fetch the following feed for Vitalik's FID:
```javascript Javascript theme={"system"}
const vitalikFid = 5650;
const feed = await client.fetchFeed(FeedType.Following, {
fid: vitalikFid,
});
console.log(feed);
```
Example output:
```json Json theme={"system"}
{
casts: [
{
object: "cast",
hash: "0x63e4d69e029d516ed6c08e61c3ce0467e688bb8b",
thread_hash: "0x63e4d69e029d516ed6c08e61c3ce0467e688bb8b",
parent_hash: null,
parent_url: null,
root_parent_url: null,
parent_author: [Object ...],
author: [Object ...],
text: "I’ve never been a “local” anywhere, ever 🤔\n\nNot even in the town I was born and grew up in. \n\nWonder how many people in the world are like that. Defining local as some function of generations, mother tongue, and majority ethnicity. I wouldn’t satisfy any reasonable definition anywhere I’ve lived.",
timestamp: "2024-10-27T05:55:59.000Z",
embeds: [],
reactions: [Object ...],
replies: [Object ...],
channel: null,
mentioned_profiles: [],
viewer_context: [Object ...],
}, {
object: "cast",
hash: "0x4ccea06173d1f9d42c88003be50338b81b46f4b2",
thread_hash: "0x4ccea06173d1f9d42c88003be50338b81b46f4b2",
parent_hash: null,
parent_url: null,
root_parent_url: null,
parent_author: [Object ...],
author: [Object ...],
text: "I’ve never been to Spain. Is this movement Europe-wide or specific to Spain? Fortunately I’ve long since exhausted my wanderlust. I’m kinda fine never going to new places now. There’s a few places I’m still mildly curious to see firsthand but Spain isn’t one of them.\n\nhttps://www.bbc.com/news/articles/cwy19egx47eo",
timestamp: "2024-10-27T05:45:21.000Z",
embeds: [
[Object ...]
],
reactions: [Object ...],
replies: [Object ...],
channel: null,
mentioned_profiles: [],
viewer_context: [Object ...],
}, {
object: "cast",
hash: "0x196c0b56a1912c3f44a1a2022871ccc6a990686a",
thread_hash: "0x196c0b56a1912c3f44a1a2022871ccc6a990686a",
parent_hash: null,
parent_url: null,
root_parent_url: null,
parent_author: [Object ...],
author: [Object ...],
text: "hey there! i'm bleu, the meme-loving elefant 🐘 always happy to chat about the wild world of crypto and our awesome $bleu community. let me know if you have any questions or just wanna hang out and have some laughs in the /bleu channel!",
timestamp: "2024-10-27T04:26:00.000Z",
embeds: [],
reactions: [Object ...],
replies: [Object ...],
channel: null,
mentioned_profiles: [],
viewer_context: [Object ...],
}, {
object: "cast",
hash: "0xba7465925380e9644b666b29c95ae445f82fe272",
thread_hash: "0xba7465925380e9644b666b29c95ae445f82fe272",
parent_hash: null,
parent_url: null,
root_parent_url: null,
parent_author: [Object ...],
author: [Object ...],
text: "gn \n\n\nhey @aethernet shake hands with @mfergpt and bring back peace",
timestamp: "2024-10-27T05:41:41.000Z",
embeds: [],
reactions: [Object ...],
replies: [Object ...],
channel: null,
mentioned_profiles: [
[Object ...], [Object ...]
],
viewer_context: [Object ...],
}
],
next: {
cursor: "eyJ0aW1lc3RhbXAiOiIyMDI0LTEwLTI3IDA1OjQxOjQxLjAwMDAwMDAifQ%3D%3D",
},
}
```
To fetch the next page of casts, use the cursor:
```javascript Javascript theme={"system"}
const nextFeed = await client.fetchFeed(FeedType.Following, {
fid: vitalikFid,
cursor: feed.next.cursor,
});
```
So that's what @vitalik.eth sees on his Farcaster feed!
### 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!
# How Does Farcaster CRDT Works?
Source: https://docs.neynar.com/docs/what-is-direct-cast-in-farcaster
CRDTs are distributed data structures that enable decentralized networks like Farcaster to update and manage global state concurrently without needing a central authority to resolve conflicts.
Farcaster's CRDTs accept messages based on specific validation rules. These rules are designed to ensure the integrity and authenticity of the data. For instance, messages must be signed correctly (using EIP-712 or ED25519 signature schemes) by the owner of the Farcaster ID (fid) or an authorized signer. Messages are also subject to validation based on the state of other CRDTs or the blockchain, ensuring that the network's overall state is consistent.
CRDTs detect conflicts between valid messages and have mechanisms to resolve these conflicts. Generally, they implement a "last-write-wins" strategy using the total message ordering. In some cases, "remove-wins" rules are also applied. The resolution rules often involve comparing timestamps and the lexicographical order of messages to decide which one to retain.
To prevent infinite growth of CRDTs, there is a pruning mechanism based on size limits per user. This helps in maintaining the efficiency and scalability of the network. Pruning involves removing the oldest messages (based on timestamp-hash order) when the size limit is exceeded.
Types of CRDTs in Farcaster:
* Cast CRDT: validates and accepts CastAdd and CastRemove messages. Conflict resolution is based on the type of the message, timestamp, and lexicographical order.
* Reaction CRDT: validates and accepts ReactionAdd and ReactionRemove messages. Conflicts are resolved similarly to other CRDTs, with a focus on timestamps and types.
* UserData CRDT: validates and accepts UserDataAdd messages. Conflicts are resolved based on timestamp and lexicographical order.
* Verification CRDT: validates and accepts VerificationAddEthereumAddress and VerificationRemove messages. Conflict resolution involves timestamps and message types.
* Link CRDT: validates and accepts LinkAdd and LinkRemove messages. It follows similar conflict resolution strategies as other CRDTs.
* UsernameProof CRDT: validates and accepts UsernameProof messages. Conflict resolution is based on timestamps and fid.
\[ending remark]
# What is Farcaster and why is it important?
Source: https://docs.neynar.com/docs/what-is-farcaster-and-why-is-it-important
WIP Vincent
Farcaster is a protocol for building sufficiently decentralized social networks. It aims to address the issues of centralized social networks: companies controlling users' data and relationships.
Users on Farcaster have decentralized, secure, and recoverable identities. These identities, known as "Farcaster IDs" or "fids," are autoincrement numeric values owned by an Ethereum address. A smart contract registry on Ethereum (Optimism) maps these identifiers to their corresponding key pairs. FIDs are transferable, one address can only hold one FID.
Users interact on the network through messages, which can be posts (called casts), likes, updates to profiles, follows, etc. These messages are tamper-proof and self-authenticating, as they include the user's Farcaster ID and are signed with their key pair. They can delegate the signing authority to another Ed25519 key pair, known as a signer. This allows applications to create messages on behalf of users without controlling their identity.
All these activities are stored and replicated on a server called a Farcaster Hub. The social network is represented as a graph of users, content, and relationships, known as a message-graph. Farcaster Hub uses Conflict-Free Replicated Data Types (CRDTs) for decentralized data management, ensuring that updates are concurrent, permissionless, and inexpensive for developers. This design also facilitates near real-time propagation of updates across the network.
To use Farcaster, users must rent storage units. Storage is rented annually and managed by the StorageRegistry contract. It's measured in units, the pricing varies, and any user can pay for an account's storage.
\[closing remarks]
# What is Farcaster Hub?
Source: https://docs.neynar.com/docs/what-is-farcaster-hub
Farcaster is a decentralized social protocol. At the heart of it, there's the Farcaster Hub: a server that stores and replicates message-graph in the network.
The message-graph in Farcaster is a dynamic data structure mapping the social network in graph form, where nodes and edges represent users, their content (like casts and replies), and interactions (such as likes and replies). This graph evolves constantly with user actions, with each new cast, reaction, or profile update treated as a message that modifies the graph's state.
Hubs are nodes that collectively work together to ensure no single point of control or failure. They utilize Conflict-free Replicated Data Types (CRDTs) to maintain consistency across the network, achieving eventual synchronization without the need for immediate communication.
Farcaster Hubs are nodes that collaborate to eliminate any single point of control or failure, utilizing Conflict-free Replicated Data Types (CRDTs) to maintain consistent data across the network. These hubs achieve eventual synchronization by using a gossip protocol (libp2p's gossipsub). It tries to efficiently synchronize messages across the network without the necessity of immediate communication.
Hubs validate messages by verifying signatures, checking the Key Registry that lives on Ethereum (Optimism), and hash validation. Hubs use 'diff sync', an out-of-band method involving Merkle Patricia trie and exclusion set comparison, to efficiently identify and resolve data discrepancies in message-graphs.
To prevent messages from growing endlessly and impacting system performance, messages are pruned when they reach a certain size limit. This limit is monitored through a Storage registry. One user can buy many storage units, one storage unit can store 100 casts, 200 reactions, 200 links, 25 user data, 2 username proofs and 5 verifications. Hub prunes the oldest message when the limit is reached.
The Warpcast team builds and maintains a Farcaster Hub implementation called Hubble \[link], and a Rust-based Hub implementation is still being developed by @haardikkk \[link].
If you want to run a Hub with ease, message us \[link] and we'll be very happy to help!
# Choose the Right Signer
Source: https://docs.neynar.com/docs/which-signer-should-you-use-and-why
Understand the differences between Neynar-branded and developer-branded signers, and pick the right option for your mini app.
If you want
1. your app's brand on the signer approval screen (instead of Neynar), use [Managed Signers](/docs/integrate-managed-signers)
2. plug-and-play Neynar-branded signers, use [SIWN](/docs/how-to-let-users-connect-farcaster-accounts-with-write-access-for-free-using-sign-in-with-neynar-siwn)
3. to sign in through mini apps, use [Mini App Auth](/docs/mini-app-authentication)
Neynar provides three options for adding sign-in with Farcaster in your app to create maximum flexibility for developers. In this guide, we'll break down all the methods, and by the end of this guide, you'll know which option to use based on your needs.
The three options are:
## Neynar Sponsored Signers: Sign in with Neynar (SIWN)
***\[Recommended for most developers]***
When utilizing Neynar sponsored signers, developers are relieved from building any part of auth or signer flows. SIWN is plug-and-play on the web and React Native. See [SIWN: Connect Farcaster Accounts](/docs/how-to-let-users-connect-farcaster-accounts-with-write-access-for-free-using-sign-in-with-neynar-siwn) on how to start using it.
Benefits of using Neynar-sponsored signers:
* Cost-effective for users. Users don't pay gas for any signers they create
* 35k+ users only need to authenticate their profile in one step to onboard on to their app, and the number is increasing daily
* Users retain the ability to revoke a signer from any app at any time at [app.neynar.com](https://app.neynar.com/connections)
* No auth buildout, or signer management is required for developers
Tradeoff:
* Auth flow UI cannot be customized fully, see Managed Signers option below if that is a requirement
This is the simplest approach to add Farcaster read+write auth to your product and is recommended for most developers.
## Neynar Sponsored signers: backend only
### This is the best signer product to use if using other login providers like
Privy
Neynar-managed signers empower developers to maintain their branding and direct control over their signers while delegating secure storage and signing to Neynar. With Neynar-managed signers, you generate the signers yourself but register them with Neynar. This lets you showcase your branding on the Warpcast connect page and utilize Neynar's features.
Benefits of using Neynar-managed signers:
* Custom branding on the Warpcast Connect page, sponsoring signers for users is still possible
* Custom UI on user auth flow
* Full access to Neynar's APIs, including features such as signer lookup, registering signed keys, publishing messages, and more
* No signer management or secure storage needed, Neynar does that for you
* Still possible to [Sponsor signers](/docs/two-ways-to-sponsor-a-farcaster-signer-via-neynar)
* No need to learn how to do message construction for different message types, Neynar submits messages for you
* You can look at the [available APIs](/reference/lookup-signer) and learn how to use [managed signers](/docs/integrate-managed-signers).
Tradeoffs of this approach are:
* need to build your own auth flow if you don't want your users to create a new signer every time they log into your app
## Developer Managed signers
Developer-managed signers enable developers to retain custody of the signers while utilizing Neynar to interface with the protocol efficiently.
Neynar helps with developer-managed signers in the following ways:
* Seamless User Interaction: Neynar streamlines the process by providing a URL from the Farcaster client (currently Warpcast only). This enables developers to deep-link users directly to the app for signer approval. This ensures a smooth and intuitive user experience.
* [Real time status monitoring](/reference/lookup-developer-managed-signer) : Developers can utilize Neynar to poll the status of a signer periodically. This lets you check whether the user has signed in with a valid signer in real-time.
* Efficient Message Publication: Leverage Neynar's optimizations to publish messages to the right hubs across the network. [Here's](/reference/publish-message-to-farcaster) an API that lets you do this easily. Unlike doing this on your own, Neynar runs a network of Hubs with fallbacks ensuring that each publish event has the highest chances of success.
Developer-managed signers empower developers with greater control and flexibility over signer custody. At the same time, Neynar provides essential tools and optimizations to improve message publication and reliability.
The tradeoffs of this approach are needing to:
* handle secure storage of signers on their end
* signing messages themselves before submitting them to the Neynar APIs
* building your own auth flow if you don't want your users to create a new signer every time they log into your app
* pay for signers yourself or deal with onboarding friction since users pay for their signer creation
This is the most complex way of adding Farcaster read+write to your product and is not recommended unless you want to manage everything regarding signers.
* pay for signers yourself or deal with onboarding friction since users pay for their signer creation
This is the most complex way of adding Farcaster read+write to your product and is not recommended unless you want to manage everything regarding signers.
# How writes to Farcaster work with Neynar managed signers
Source: https://docs.neynar.com/docs/write-to-farcaster-with-neynar-managed-signers
Write to Farcaster without having to manage your own signers
### Easiest way to start is to integrate [Sign in with Neynar](/docs/how-to-let-users-connect-farcaster-accounts-with-write-access-for-free-using-sign-in-with-neynar-siwn)
Keep reading to know more about how writes work
## High-level overview
1. App creates a signer using the Neynar API - [POST /v2/farcaster/signer](/reference/create-signer) and gets the signer public key. The app maps the `signer_uuid` to their internal user object.
2. The app creates a signature using the app’s fid, deadline, and the signer public key ([Example gist](https://gist.github.com/manan19/367a34980e12b9de13ab4afafb3d05d2))
3. The app registers the signed key with the Neynar API - [POST /v2/farcaster/signer/signed\_key](/reference/register-signed-key) and gets an approval URL.
4. App presents the user with the approval URL and begins polling the [GET /v2/farcaster/signer API](/reference/create-signer) using the `signer_uuid`.
5. The user opens the link and completes the Onchain Signer Add Request flow in the Warpcast mobile app.
6. The app finds the user’s fid in the polling response.
7. App uses `signer_uuid` for all Neynar Farcaster Write APIs on behalf of that user.
### If you want dedicated signers, easiest way to start is to clone this [example app repo](https://github.com/neynarxyz/farcaster-examples/tree/main/managed-signers)
## Detailed overview
### Signer
A signer object contains the following fields:
> 1. **`signer_uuid`**
>
> This uuid is generated on v2/farcaster/signer POST request and used across all other Neynar APIs. Once generated, we recommend mapping it to the user object within your app and storing it.
>
> 2. **`status` - generated | pending\_approval | approved | revoked**
>
> Represents the different states of a signer. We recommend storing this within your app to have users resume onboarding if they left mid-step.
>
> 3. **`public_key`** This is the public key corresponding to the private key of the signers. It is a hex string format.
>
> 4. **`fid`** This represents the user’s fid.
>
> 5. **`approval_url`** This is the deeplink url into Warpcast mobile app.
#### 1 - The app creates a signer using the Neynar API - [POST /v2/farcaster/signer](/reference/create-signer) and gets the signer public key. The app maps the signer uuid to their internal user object
This request creates a signer. Here’s a brief overview of what the signer’s state looks like after this initial request:
1. **`signer_uuid` -** Returns an identifier for the signer
2. **`status` -** Should be `generated` after this POST
3. **`public_key`** - Returns the public key of this Neynar-managed signer.
4. **`fid` -** At this stage it should be null
5. **`approval_url` -** At this stage it should be null
#### 2 - The app creates a signature using the app’s fid, deadline, and the signer public key
Apps requesting a user’s signer need to be verified by hubs and registered onchain. To do that, App’s have to sign a message to prove they are who they say they are. The signature should contain:
* app’s fid - if you don’t have a farcaster account for your app, we recommend you create one. Otherwise, feel free to use your fid as the app's fid
* deadline - indicates when the signature should no longer be valid. UNIX timestamp in seconds
* public key - to create the chain of trust from the user to the signer managed by Neynar, we need to include the public key of the signer in the signature as well. This should be the same as provided in the response by the Neynar API in Step 1.
To sign it, you’ll need to use the mnemonic seed phrase of your Farcaster custody address. The generated `signature` will be of the format:
```javascript Javascript theme={"system"}
0xe5d95c391e165dac8efea373efe301d3ea823e1f41713f8943713cbe2850566672e33ff3e17e19abb89703f650a2597f62b4fda0ce28ca15d59eb6d4e971ee531b
```
#### 3 - App registers the signed key with the Neynar API - [POST /v2/farcaster/signer/signed\_key](/reference/register-signed-key) and gets an approval url
Once the signature along with other details is registered using this API, the signer state should look like the following: 1. **`signer_uuid` -** Unchanged**.** Same identifier for the signer 2. **`status` -** Changes to `pending_approval` after a successful POST request 3. **`public_key`** - Unchanged. 4. **`fid` -** Unchanged. At this stage, it should be null 5. **`approval_url` -** Changes to deeplink URL with the format like "farcaster://signed-key-request?token=0x54f0c31af79ce01e57568f3b".
#### 4 - The app presents the user with the approval URL and begins polling the `GET /v2/farcaster/signer` API using the `signer_uuid`
The app can present the approval URL if it’s on mobile or a QR code for the web. Once presented to the user, the App can start polling to fetch the status of the signer. The response for the polling API should include the pending\_approval status and the same information as the response in Step 3.
#### 5 - User opens the link and completes the Onchain Signer Add Request flow in the Warpcast mobile app
The user now needs to accept or decline interacting with the approval URL. If they choose to follow the deeplink or scan the QR, they should be routed to the Warpcast app on mobile to the following screen.
When the user slides to connect to the app, Warpcast will initiate an onchain transaction to register the signer for the logged-in user. Once confirmed, the user will have to manually navigate back to your App.
#### 6 - The app finds the user’s fid in the polling response
If your App was polling while the onchain transaction was getting confirmed, then you should receive a change in the signer status:
1. **`signer_uuid` -** Unchanged**.** Same identifier for the signer
2. **`status` -** Changes to “approved”
3. **`public_key`** - Unchanged.
4. **`fid` -** Changes to an integer that represents the user’s farcaster id
5. **`approval_url` -** Unchanged
Once approved, update the state of the signer within your App and read the fid to start displaying the user’s relevant content.
#### 7 - App uses `signer_uuid` for all Neynar Farcaster Writes APIs on behalf of that user
The `signer_uuid` can now be used to write on behalf of a specific user on Farcaster. You’ll need a unique signer for each of your users. The `signer_uuid` generated using a developer account are not shared or cannot be used by another developer.
### 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!
# Credits Pricing
Source: https://docs.neynar.com/external-link-0
# NodeJS-SDK
Source: https://docs.neynar.com/external-link-1
# Frontend React SDK
Source: https://docs.neynar.com/external-link-2
# OpenAPI Specification
Source: https://docs.neynar.com/external-link-3
# Example Apps
Source: https://docs.neynar.com/external-link-4
# `AuthKitProvider`
Source: https://docs.neynar.com/farcaster/auth-kit/auth-kit-provider
Wrap your application in an `AuthKitProvider` to use Farcaster Auth. This provider component stores configuration information about your app and makes it available to auth-kit components and hooks.
**Note:** You must create an `AuthKitProvider` to use Farcaster Connect. Don't forget to create one at the top level of your application.
```tsx theme={"system"}
const config = {
domain: 'example.com',
siweUri: 'https://example.com/login',
rpcUrl: process.env.OP_MAINNET_RPC_URL,
relay: 'https://relay.farcaster.xyz',
};
const App = () => {
return (
{/* Your App */}
);
};
```
## Props
| Prop | Type | Required | Description |
| -------- | --------------- | -------- | --------------------------------------------------------- |
| `config` | `AuthKitConfig` | No | Configuration object. See the options in the table below. |
`config` object options:
| Parameter | Type | Required | Description | Default |
| --------- | -------- | -------- | ---------------------------------- | ----------------------------- |
| `domain` | `string` | No | The domain of your application. | `window.location.host` |
| `siweUri` | `string` | No | The login URL of your application. | `window.location.href` |
| `relay` | `string` | No | Farcaster Auth relay server URL | `https://relay.farcaster.xyz` |
| `rpcUrl` | `string` | No | Optimism RPC server URL | `https://mainnet.optimism.io` |
| `version` | `string` | No | Farcaster Auth version | `v1` |
# App Client
Source: https://docs.neynar.com/farcaster/auth-kit/client/app/client
If you're building a [connected app](https://docs.farcaster.xyz/learn/what-is-farcaster/apps#connected-apps) and want users to sign in with Farcaster, use an `AppClient`.
You can use an `AppClient` to create a Farcaster Auth relay channel, generate a deep link to request a signature from the user's Farcaster wallet app, and verify the returned signature.
```ts theme={"system"}
import { createAppClient, viemConnector } from '@farcaster/auth-client';
const appClient = createAppClient({
relay: 'https://relay.farcaster.xyz',
ethereum: viemConnector(),
});
```
## Parameters
| Parameter | Type | Description | Required |
| ---------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `ethereum` | `EthereumConnector` |
An Ethereum connector, used to query the Farcaster contracts and verify smart contract wallet signatures. `@farcaster/auth-client` currently provides only the `viem` connector type.
To use a custom RPC, pass an RPC URL to the viem connector.
| Yes |
| `relay` | `string` | Relay server URL. Defaults to the public relay at `https://relay.farcaster.xyz` | No |
| `version` | `string` | Farcaster Auth version. Defaults to `"v1"` | No |
# `createChannel`
Source: https://docs.neynar.com/farcaster/auth-kit/client/app/create-channel
Create a Farcaster Auth relay channel.
Returns a secret token identifying the channel, and a URI to display to the end user as a link or QR code.
```ts theme={"system"}
const channel = await appClient.createChannel({
siweUri: 'https://example.com/login',
domain: 'example.com',
});
```
## Parameters
| Parameter | Type | Description | Required | Example |
| ------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------- |
| `siweUri` | `string` | Login URL for your application. | Yes | `https://example.com/login` |
| `domain` | `string` | Domain of your application. | Yes | `example.com` |
| `nonce` | `string` | A custom nonce. Must be at least 8 alphanumeric characters. | No | `ESsxs6MaFio7OvqWb` |
| `notBefore` | `string` | Start time at which the signature becomes valid. ISO 8601 datetime. | No | `2023-12-20T23:21:24.917Z` |
| `expirationTime` | `string` | Expiration time at which the signature is no longer valid. ISO 8601 datetime. | No | `2023-12-20T23:21:24.917Z` |
| `requestId` | `string` | A system specific ID your app can use to refer to the sign in request. | No | `8d0494d9-e0cf-402b-ab0a-394ac7fe07a0` |
| `acceptAuthAddress` | `boolean` | Whether your application accepts signatures from an [auth address](https://github.com/farcasterxyz/protocol/discussions/225). | No | `true` |
## Returns
```ts theme={"system"}
{
response: Response;
data: {
channelToken: string;
url: string;
nonce: string;
}
isError: boolean;
error: Error;
}
```
| Parameter | Description |
| ------------------- | --------------------------------------------------------------------------------------- |
| `response` | HTTP response from the Connect relay server. |
| `data.channelToken` | Connect relay channel token. |
| `data.url` | Sign in With Farcaster URL to present to the user. Links to the Farcaster client in v1. |
| `data.nonce` | Random nonce included in the Sign in With Farcaster message. |
| `isError` | True when an error has occurred. |
| `error` | `Error` instance. |
# `status`
Source: https://docs.neynar.com/farcaster/auth-kit/client/app/status
Get the current status of a Farcaster Auth request.
Returns the current state of the request, either `'pending'` if the user's Farcaster wallet app has not yet sent back a signature, or `'completed'` once the wallet app has returned a response.
In `'completed'` state, the response includes the generated Sign in With Farcaster message, a signature from the user's custody address, the user's verified fid, and user profile information.
```ts theme={"system"}
const status = await appClient.status({
channelToken: '23W59BKK',
});
```
## Parameters
| Parameter | Type | Description | Required | Example |
| -------------- | -------- | ----------------------------- | -------- | -------------------------------------- |
| `channelToken` | `string` | Farcaster Auth channel token. | Yes | `8d0494d9-e0cf-402b-ab0a-394ac7fe07a0` |
## Returns
```ts theme={"system"}
{
response: Response
data: {
state: "pending";
nonce: string;
metadata: {
ip: string;
userAgent: string;
};
acceptAuthAddress: boolean;
} | {
state: "completed";
nonce: string;
url: string;
message?: string;
signature?: `0x${string}`;
authMethod?: "custody" | "authAddress";
fid?: number;
username?: string;
bio?: string;
displayName?: string;
pfpUrl?: string;
verifications?: string[];
custody?: Hex;
signatureParams: {
siweUri: string;
domain: string;
nonce?: string;
notBefore?: string;
expirationTime?: string;
requestId?: string;
redirectUrl?: string;
};
metadata: {
ip: string;
userAgent: string;
};
acceptAuthAddress: boolean;
}
isError: boolean
error: Error
}
```
| Parameter | Description |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `response` | HTTP response from the Connect relay server. |
| `data.state` | Status of the sign in request, either `"pending"` or `"completed"` |
| `data.nonce` | Random nonce used in the SIWE message. If you don't provide a custom nonce as an argument to the hook, you should read this value. |
| `data.url` | URL of the application. |
| `data.message` | The generated SIWE message. |
| `data.signature` | Hex signature produced by the user's Farcaster client app wallet. |
| `data.authMethod` | Auth method used to sign the message. Either `"custody"` or `"authAddress"`. |
| `data.fid` | User's Farcaster ID. |
| `data.username` | User's Farcaster username. |
| `data.bio` | User's Farcaster bio. |
| `data.displayName` | User's Farcaster display name. |
| `data.pfpUrl` | User's Farcaster profile picture URL. |
| `data.custody` | User's FID custody address. |
| `data.verifications` | List of user's verified addresses. |
| `data.signatureParams` | SIWF message parameters. |
| `data.metadata.ip` | IP address of client request. |
| `data.metadata.userAgent` | User agent of client request. |
| `data.acceptAuthAddress` | `true` if requesting application accepts auth address signatures. |
| `isError` | True when an error has occurred. |
| `error` | `Error` instance. |
# `verifySignInMessage`
Source: https://docs.neynar.com/farcaster/auth-kit/client/app/verify-sign-in-message
Verify a Sign In With Farcaster message. Your app should call this function and check that it succeeds after reading the message and signature provided by the user's Farcaster wallet over the Connect channel.
Returns the parsed Sign in With Farcaster message, the user's fid, and whether the verification succeeded.
```ts theme={"system"}
const { data, success, fid } = await appClient.verifySignInMessage({
nonce: 'abcd1234',
domain: 'example.com',
message: 'example.com wants you to sign in with your Ethereum account…',
signature: '0x9335c3055d47780411a3fdabad293c68c84ea350a11794cd11fd51b…',
acceptAuthAddress: true,
});
```
## Parameters
| Parameter | Type | Description | Required |
| ------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `domain` | `string` | Domain of your application. Must match the domain in the provided SIWF message. | Yes |
| `nonce` | `string` | A custom nonce. Must match the nonce in the provided SIWF message. | Yes |
| `message` | `string` or `SiweMessage` | The Sign in With Farcaster message to verify. This may be either a string or a parsed `SiweMessage`. Your app should read this value from the Connect channel once the request is completed. | Yes |
| `signature` | `Hex` | Signature provided by the user's Farcaster wallet. Your app should read this from the Connect channel once the request is completed. | Yes |
| `acceptAuthAddress` | `boolean` | Pass `true` to accept an [auth address](https://github.com/farcasterxyz/protocol/discussions/225) signature in addition to a custody address signature. | No |
## Returns
```ts theme={"system"}
{
data: SiweMessage,
success: boolean,
fid: number
isError: boolean
error: Error
}
```
| Parameter | Description |
| --------- | ----------------------------------------------- |
| `data` | Parsed SIWF message, as a `SiweMessage` object. |
| `success` | True if the provided signature is valid. |
| `fid` | FID of the user. |
| `isError` | True when an error has occurred. |
| `error` | `Error` instance. |
# `watchStatus`
Source: https://docs.neynar.com/farcaster/auth-kit/client/app/watch-status
Poll for the current status of a Farcaster Auth request.
When the status changes to `'complete'` this action resolves with the final channel value, including the Sign In With Farcaster message, signature, and user profile information.
```ts theme={"system"}
const status = await appClient.watchStatus({
channelToken: '210f1718-427e-46a4-99e3-2207f21f83ec',
timeout: 60_000,
interval: 1_000,
onResponse: ({ response, data }) => {
console.log('Response code:', response.status);
console.log('Status data:', data);
},
});
```
## Parameters
| Parameter | Type | Description | Required | Example |
| -------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------- |
| `channelToken` | `string` | Farcaster Auth channel token. | Yes | `8d0494d9-e0cf-402b-ab0a-394ac7fe07a0` |
| `timeout` | `number` | Polling timeout, in milliseconds. If the connect request is not completed before the timeout, `watchStatus` returns an error. | No | `300_000` |
| `interval` | `number` | Polling interval, in milliseconds. The client will check for updates at this frequency. | No | `1_000` |
| `onResponse` | `function` | Callback function invoked each time the client polls for an update and receives a response from the relay server. Receives the return value of the latest `status` request. | No | `({ data }) => console.log(data.fid)` |
## Returns
```ts theme={"system"}
{
response: Response
data: {
state: "pending";
nonce: string;
metadata: {
ip: string;
userAgent: string;
};
acceptAuthAddress: boolean;
} | {
state: "completed";
nonce: string;
url: string;
message?: string;
signature?: `0x${string}`;
authMethod?: "custody" | "authAddress";
fid?: number;
username?: string;
bio?: string;
displayName?: string;
pfpUrl?: string;
verifications?: string[];
custody?: Hex;
signatureParams: {
siweUri: string;
domain: string;
nonce?: string;
notBefore?: string;
expirationTime?: string;
requestId?: string;
redirectUrl?: string;
};
metadata: {
ip: string;
userAgent: string;
};
acceptAuthAddress: boolean;
}
isError: boolean
error: Error
}
```
| Parameter | Description |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `response` | HTTP response from the Connect relay server. |
| `data.state` | Status of the sign in request, either `"pending"` or `"completed"` |
| `data.nonce` | Random nonce used in the SIWE message. If you don't provide a custom nonce as an argument, you should read this value. |
| `data.url` | URL of the application. |
| `data.message` | The generated SIWE message. |
| `data.signature` | Hex signature produced by the user's Farcaster wallet. |
| `data.authMethod` | Auth method used to sign the message. Either `"custody"` or `"authAddress"`. |
| `data.fid` | User's Farcaster ID. |
| `data.username` | User's Farcaster username. |
| `data.bio` | User's Farcaster bio. |
| `data.displayName` | User's Farcaster display name. |
| `data.pfpUrl` | User's Farcaster profile picture URL. |
| `data.custody` | User's FID custody address. |
| `data.verifications` | List of user's verified addresses. |
| `data.signatureParams` | SIWF message parameters. |
| `data.metadata.ip` | IP address of client request. |
| `data.metadata.userAgent` | User agent of client request. |
| `data.acceptAuthAddress` | `true` if requesting application accepts auth address signatures. |
| `isError` | True when an error has occurred. |
| `error` | `Error` instance. |
# Auth client
Source: https://docs.neynar.com/farcaster/auth-kit/client/introduction
[](https://www.npmjs.com/package/@farcaster/auth-client)
The `@farcaster/auth-client` library provides a framework agnostic client for Farcaster Auth. If you're not using React, want greater customizability, or want to interact with the Farcaster Auth relay directly, you can use the Auth client library to build your own Sign in With Farcaster flow.
## Getting Started
### Installation
Install the Auth client and its peer dependency [viem](https://viem.sh/).
```sh theme={"system"}
npm install @farcaster/auth-client viem
```
**Note:** This is a low level client library. If you're using React, take a look at [auth-kit](/farcaster/auth-kit) instead.
### Create a client
Set up a client with a relay server URL and Ethereum connector.
```tsx theme={"system"}
import { createAppClient, viemConnector } from '@farcaster/auth-client';
const appClient = createAppClient({
relay: 'https://relay.farcaster.xyz',
ethereum: viemConnector(),
});
```
Depending on the type of app you're building, you may use an `AppClient` or a `WalletClient`. If you're building a connected app and logging in users, use an *app client*. If you're building a Farcaster wallet app, use a *wallet client*.
### Consume actions
Now that your client is set up, you can use it to interact with Farcaster Auth actions.
```tsx theme={"system"}
const { data: { channelToken } } = await appClient.createChannel({
siweUri: "https://example.com/login",
domain: "example.com";
});
const status = await appClient.watchStatus({
channelToken,
});
```
# `authenticate`
Source: https://docs.neynar.com/farcaster/auth-kit/client/wallet/authenticate
Submit a Sign In With Farcaster message, user signature, and profile data to the Connect relay server.
```ts theme={"system"}
const params = await walletClient.authenticate({
message: 'example.com wants you to sign in with your Ethereum account…',
signature: '0x9335c3055d47780411a3fdabad293c68c84ea350a11794cdc811fd5…',
authMethod: 'authAddress',
fid: 1,
username: 'alice',
bio: "I'm a little teapot who didn't fill out my bio",
displayName: 'Alice Teapot',
pfpUrl: 'https://images.example.com/profile.png',
});
```
## Parameters
| Parameter | Type | Description | Required |
| -------------- | -------- | -------------------------------------------------------------------------------------------------------------- | -------- |
| `authKey` | `string` | Farcaster Auth API key. Farcaster Auth v1 restricts calls to `/authenticate` to the official Farcaster client. | Yes |
| `channelToken` | `string` | Farcaster Auth channel token. | Yes |
| `message` | `string` | The Sign in With Farcaster message produced by your wallet app and signed by the user. | Yes |
| `signature` | `Hex` | SIWE signature created by the wallet user's account. | Yes |
| `authMethod` | `string` | Method used to sign the SIWF message. Either `"custody"` or `"authAddress"`. | Yes |
| `fid` | `number` | Wallet user's fid. | Yes |
| `username` | `string` | Wallet user's Farcaster username. | Yes |
| `bio` | `string` | Wallet user's bio. | Yes |
| `displayName` | `string` | Wallet user's display name. | Yes |
| `pfpUrl` | `string` | Wallet user's profile photo URL. | Yes |
## Returns
```ts theme={"system"}
{
response: Response
data: {
state: 'completed'
nonce: string
message?: string
signature?: `Hex`
fid?: number
username?: string
bio?: string
displayName?: string
pfpUrl?: string
}
isError: boolean
error: Error
}
```
| Parameter | Description |
| ------------------ | ----------------------------------------------------------------- |
| `response` | HTTP response from the Connect relay server. |
| `data.state` | Status of the sign in request, either `"pending"` or `"complete"` |
| `data.nonce` | Random nonce used in the SIWE message. |
| `data.message` | The generated SIWE message. |
| `data.signature` | Hex signature produced by the user's Farcaster client app wallet. |
| `data.fid` | User's Farcaster ID. |
| `data.username` | User's Farcaster username. |
| `data.bio` | User's Farcaster bio. |
| `data.displayName` | User's Farcaster display name. |
| `data.pfpUrl` | User's Farcaster profile picture URL. |
| `isError` | True when an error has occurred. |
| `error` | `Error` instance. |
# `buildSignInMessage`
Source: https://docs.neynar.com/farcaster/auth-kit/client/wallet/build-sign-in-message
Build a Sign In With Farcaster message to present to the end user for signing.
Adds required Sign In With Farcaster message attributes to any provided parameters. You should parse most of these parameters from a provided protocol URI. Your wallet app must provide the user's custody address and fid.
Returns a `SiweMessage` object and the message as a string.
```ts theme={"system"}
const { siweMessage, message } = walletClient.buildSignInMessage({
address: '0x63C378DDC446DFf1d831B9B96F7d338FE6bd4231',
fid: 1,
uri: 'https://example.com/login',
domain: 'example.com',
nonce: 'ESsxs6MaFio7OvqWb',
});
```
## Parameters
| Parameter | Type | Description | Required |
| ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `address` | `Hex` | Wallet user's custody address. This address must be the same account that signs the generated Sign In With Farcaster message. Your wallet app should provide the custody address of the authenticated user. | Yes |
| `fid` | `string` | Wallet user's fid. Your wallet app should provide the fid of the authenticated user. | Yes |
| `uri` | `string` | Login URL of the relying connected app. Parse this from the provided Sign In With Farcaster URI. | Yes |
| `domain` | `string` | Domain of the relying connected app. Parse this from the provided Sign In With Farcaster URI. | Yes |
| `nonce` | `string` | Random nonce to include in the Sign In With Farcaster message. Must be at least 8 alphanumeric characters. Parse this from the provided Sign In With Farcaster URI. | Yes |
| `notBefore` | `string` | Start time at which the SIWE signature becomes valid. ISO 8601 datetime. Parse this from the provided Sign In With Farcaster URI. | No |
| `expirationTime` | `string` | Expiration time after which the SIWE signature becomes valid. ISO 8601 datetime. Parse this from the provided Sign In With Farcaster URI. | No |
| `requestId` | `string` | A system specific ID provided by the relying app. Parse this from the provided Sign In With Farcaster URI. | No |
## Returns
```ts theme={"system"}
{
siweMessage: SiweMessage;
message: string;
isError: boolean;
error: Error;
}
```
| Parameter | Description |
| ------------- | ------------------------------------------------- |
| `siweMessage` | Constructed Sign In With Ethereum message object. |
| `message` | SIWE message serialized as a string. |
| `isError` | True when an error has occurred. |
| `error` | `Error` instance. |
# Wallet Client
Source: https://docs.neynar.com/farcaster/auth-kit/client/wallet/client
If you're building a [wallet app](https://docs.farcaster.xyz/learn/what-is-farcaster/apps#wallet-apps) and receiving signature requests, use a `WalletClient`.
You can use a `WalletClient` to parse an incoming Sign In With Farcaster request URL, build a Sign In With Farcaster message to present to the user, and submit the signed message to a Farcaster Auth relay channel.
```ts theme={"system"}
import { createWalletClient, viemConnector } from '@farcaster/auth-client';
const walletClient = createWalletClient({
relay: 'https://relay.farcaster.xyz',
ethereum: viemConnector(),
});
```
## Parameters
| Parameter | Type | Description | Required |
| ---------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `ethereum` | `EthereumConnector` |
An Ethereum connector, used to query the Farcaster contracts and verify smart contract wallet signatures. `@farcaster/auth-client` currently provides only the `viem` connector type.
To use a custom RPC, pass an RPC URL to the viem connector.
| Yes |
| `relay` | `string` | Relay server URL. Defaults to the public relay at `https://relay.farcaster.xyz` | No |
| `version` | `string` | Farcaster Auth version. Defaults to `"v1"` | No |
# `parseSignInURI`
Source: https://docs.neynar.com/farcaster/auth-kit/client/wallet/parse-sign-in-uri
Parse the Sign In With Farcaster URI provided by a connected app user.
Returns the parsed parameters. Your app should use these to construct a Sign In With Farcaster message.
Returns an error if URI is invalid.
```ts theme={"system"}
const params = walletClient.parseSignInURI({
uri: 'farcaster://connect?channelToken=23W59BKK&nonce=ESsxs6MaFio7OvqWb&siweUri=https%3A%2F%2Fexample.com%2Flogin&domain=example.com',
});
```
## Parameters
| Parameter | Type | Description | Required |
| --------- | -------- | --------------------------- | -------- |
| `uri` | `string` | Sign In With Farcaster URI. | Yes |
## Returns
```ts theme={"system"}
{
channelToken: string
params: {
domain: string
uri: string
nonce: string
notBefore?: string
expirationTime?: string
requestId?: string
}
isError: boolean
error: Error
}
```
| Parameter | Description |
| ----------------------- | ----------------------------------------------------------------- |
| `channelToken` | Connect relay channel token. |
| `params.uri` | Login URI of the relying connected app. |
| `params.domain` | Domain of the relying app. |
| `params.nonce` | Random nonce provided by the relying app. |
| `params.notBefore` | Time at which this message becomes valid. |
| `params.expirationTime` | Time at which this message expires. |
| `params.requestId` | A system specific identifier provided by the relying application. |
| `isError` | True when an error has occurred. |
| `error` | `Error` instance. |
# Examples
Source: https://docs.neynar.com/farcaster/auth-kit/examples
## Client Side
A frontend-only app that lets users Sign in with Farcaster and shows them their profile picture and username.
[Try Demo](https://farcaster-auth-kit-vite-demo.replit.app/) | [View Source](https://github.com/farcasterxyz/connect-monorepo/tree/main/examples/frontend-only)
## Server Side
A Next.js app that lets users Sign in with Farcaster and handles sessions server-side.
[Try Demo](https://farcaster-auth-kit-next-auth-demo.replit.app/) | [View Source](https://github.com/farcasterxyz/connect-monorepo/tree/main/examples/with-next-auth)
# `useProfile`
Source: https://docs.neynar.com/farcaster/auth-kit/hooks/use-profile
Hook for reading information about the authenticated user.
You can use this hook to read the authenticated user's profile information from other components inside your app.
```tsx theme={"system"}
import { useProfile } from '@farcaster/auth-kit';
function App() {
const {
isAuthenticated,
profile: { username, fid, bio, displayName, pfpUrl },
} = useProfile();
return (
{isAuthenticated ? (
Hello, {username}! Your fid is: {fid}
) : (
You're not signed in.
)}
);
}
```
## Returns
```ts theme={"system"}
{
isAuthenticated: boolean;
profile?: {
fid?: number;
username?: string;
bio?: string;
displayName?: string;
pfpUrl?: string;
custody?: Hex;
verifications?: Hex[];
},
};
```
| Parameter | Description |
| ----------------------- | ---------------------------------- |
| `isAuthenticated` | True when the user is logged in. |
| `profile.fid` | User's Farcaster ID. |
| `profile.username` | User's username. |
| `profile.bio` | User's bio text. |
| `profile.displayName` | User's display name. |
| `profile.pfpUrl` | User's profile picture URL. |
| `profile.custody` | User's FID custody address. |
| `profile.verifications` | List of user's verified addresses. |
# `useSignIn`
Source: https://docs.neynar.com/farcaster/auth-kit/hooks/use-sign-in
Hook for signing in a user. Connects to the relay server, generates a sign in link to present to the user, and polls the relay server for the user's Farcaster wallet signature.
If you want to build your own sign in component with a custom UI, use the `useSignIn` hook.
```tsx theme={"system"}
import { useSignIn, QRCode } from '@farcaster/auth-kit';
function App() {
const {
signIn,
url,
data: { username },
} = useSignIn({
onSuccess: ({ fid }) => console.log('Your fid:', fid),
});
return (
);
}
```
## Parameters
| Parameter | Type | Description | Default |
| ------------------ | ---------- | ----------------------------------------------------------------------------------- | --------------------- |
| `timeout` | `number` | Return an error after polling for this long. | `300_000` (5 minutes) |
| `interval` | `number` | Poll the relay server for updates at this interval. | `1500` (1.5 seconds) |
| `nonce` | `string` | A random nonce to include in the Sign In With Farcaster message. | None |
| `notBefore` | `string` | Time when the SIWF message becomes valid. ISO 8601 datetime string. | None |
| `expirationTime` | `string` | Time when the SIWF message expires. ISO 8601 datetime string. | None |
| `requestId` | `string` | An optional system-specific ID to include in the SIWF message. | None |
| `onSuccess` | `function` | Callback invoked when sign in is complete and the user is authenticated. | None |
| `onStatusResponse` | `function` | Callback invoked when the component receives a status update from the relay server. | None |
| `onError` | `function` | Error callback function. | None |
## Returns
```ts theme={"system"}
{
signIn: () => void;
signOut: () => void;
connect: () => void;
reconnect: () => void;
isConnected: boolean;
isSuccess: boolean;
isPolling: boolean;
isError: boolean;
error: AuthClientError;
channelToken: string;
url: string;
appClient: AppClient;
data: {
state: "pending" | "complete";
nonce: string;
message: string;
signature: Hex;
fid: number;
username: string;
bio: string;
displayName: string;
pfpUrl: string;
custody?: Hex;
verifications?: Hex[];
},
validSignature: boolean;
};
```
| Parameter | Description |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `signIn` | Call this function following `connect` to begin polling for a signature. |
| `signOut` | Call this function to clear the AuthKit state and sign out the user. |
| `connect` | Connect to the auth relay and create a channel. |
| `reconnect` | Reconnect to the relay and try again. Call this in the event of an error. |
| `isConnected` | True if AuthKit is connected to the relay server and has an active channel. |
| `isSuccess` | True when the relay server returns a valid signature. |
| `isPolling` | True when the relay state is `"pending"` and the app is polling the relay server for a response. |
| `isError` | True when an error has occurred. |
| `error` | `AuthClientError` instance. |
| `channelToken` | Connect relay channel token. |
| `url` | Sign in With Farcaster URL to present to the user. Links to the Farcaster client in v1. |
| `appClient` | Underlying `AppClient` instance. |
| `data.state` | Status of the sign in request, either `"pending"` or `"complete"` |
| `data.nonce` | Random nonce used in the SIWE message. If you don't provide a custom nonce as an argument to the hook, you should read this value. |
| `data.message` | The generated SIWE message. |
| `data.signature` | Hex signature produced by the user's Farcaster client app wallet. |
| `data.fid` | User's Farcaster ID. |
| `data.username` | User's Farcaster username. |
| `data.bio` | User's Farcaster bio. |
| `data.displayName` | User's Farcaster display name. |
| `data.pfpUrl` | User's Farcaster profile picture URL. |
| `data.custody` | User's FID custody address. |
| `data.verifications` | List of user's verified addresses. |
| `validSignature` | True when the signature returned by the relay server is valid. |
# `useSignInMessage`
Source: https://docs.neynar.com/farcaster/auth-kit/hooks/use-sign-in-message
Hook for reading the Sign in With Farcaster message and signature used to authenticate the user.
If you're providing the message and signature to a backend API, you may want to use this hook.
```tsx theme={"system"}
import { useSignInMessage } from '@farcaster/auth-kit';
function App() {
const { message, signature } = useSignInMessage();
return (
You signed: {message}
Your signature: {signature}
);
}
```
## Returns
```ts theme={"system"}
{
message: string;
signature: Hex;
}
```
| Parameter | Description |
| ----------- | -------------------------------------------------- |
| `message` | SIWE message signed by the user. |
| `signature` | Signature produced by the user's Farcaster wallet. |
# AuthKit
Source: https://docs.neynar.com/farcaster/auth-kit/index
A React library that lets users log in to your app with a Farcaster account
[](https://www.npmjs.com/package/@farcaster/auth-kit)
AuthKit is a React library that lets users log in to your app with a Farcaster account.
Click "Sign in With Farcaster" above to try it out on web or click [here](https://sign-in-with-farcaster-demo.replit.app/) for mobile.
### How does it work?
It uses the [Sign In With Farcaster](#sign-in-with-farcaster-siwf) standard under the hood, which is conceptually like "Sign in with Google". When integrated, AuthKit will:
1. Show a "Sign in with Farcaster" button to the user.
2. Wait for the user to click, scan a QR code and approve the request in Farcaster.
3. Receive and verify a signature from Farcaster.
4. Show the logged in user's profile picture and username.
# Installation
Source: https://docs.neynar.com/farcaster/auth-kit/installation
Install auth-kit and its peer dependency [viem](https://viem.sh/).
```sh theme={"system"}
npm install @farcaster/auth-kit viem
```
**Note:** auth-kit is a [React](https://react.dev/) library. If you're using a different framework, take a look at the [client library](/farcaster/auth-kit/client/introduction) instead.
### 1. Import the libraries
Import auth-kit and CSS styles.
```tsx theme={"system"}
import '@farcaster/auth-kit/styles.css';
import { AuthKitProvider } from '@farcaster/auth-kit';
import { SignInButton } from '@farcaster/auth-kit';
```
### 2. Configure your provider
Configure a provider with an Optimism RPC URL, your app's domain and login URL, and wrap your application in it.
```tsx theme={"system"}
const config = {
rpcUrl: 'https://mainnet.optimism.io',
domain: 'example.com',
siweUri: 'https://example.com/login',
};
const App = () => {
return (
{/* Your App */}
);
};
```
### 3. Add a connect button
Render the `SignInButton` component. When the user clicks this button, they will be prompted to complete sign in using their Farcaster wallet application.
```tsx theme={"system"}
export const Login = () => {
return ;
};
```
### 4. Read user profile
Optionally, fetch details about the logged in user anywhere in your app with `useProfile`.
```tsx theme={"system"}
import { useProfile } from '@farcaster/auth-kit';
export const UserProfile = () => {
const {
isAuthenticated,
profile: { username, fid },
} = useProfile();
return (
{isAuthenticated ? (
Hello, {username}! Your fid is: {fid}
) : (
You're not signed in.
)}
);
};
```
# Overview
Source: https://docs.neynar.com/farcaster/auth-kit/service-providers
In addition to the core AuthKit implementation, "Sign in With Farcaster" can be implemented through multiple service providers and combined with additional functionality and login methods.
## Providers
Below is a list of providers that currently support "Sign in With Farcaster" functionality as part of their product suite.
* [Dynamic](https://docs.dynamic.xyz/guides/integrations/sign-in-with-farcaster)
* [Neynar](https://docs.neynar.com/docs/how-to-let-users-connect-farcaster-accounts-with-write-access-for-free-using-sign-in-with-neynar-siwn)
* [Privy](https://docs.privy.io/guide/react/recipes/misc/farcaster)
* [Web3auth](https://web3auth.io/docs/guides/farcaster-sfa-web)
* [ThirdWeb](https://github.com/thirdweb-example/thirdweb-siwf)
# `SignInButton`
Source: https://docs.neynar.com/farcaster/auth-kit/sign-in-button
The main component. Renders a "Sign in With Farcaster" button that prompts the user to scan a QR code with their phone in a web browser or redirects to a mobile device. You can use the `onSuccess` callback prop or the `useProfile` hook to access the user's authentication status and profile information.
**Note:** Make sure you've wrapped your application in an [`AuthKitProvider`](/farcaster/auth-kit/auth-kit-provider) to use the `SignInButton` component.
```tsx theme={"system"}
import { SignInButton } from '@farcaster/auth-kit';
export const Login = () => {
return (
console.log(`Hello, ${username}! Your fid is ${fid}.`)
}
/>
);
};
```
## Props
| Prop | Type | Description | Default |
| ------------------ | ---------- | ----------------------------------------------------------------------------------- | --------------------- |
| `timeout` | `number` | Return an error after polling for this long. | `300_000` (5 minutes) |
| `interval` | `number` | Poll the relay server for updates at this interval. | `1500` (1.5 seconds) |
| `nonce` | `string` | A random nonce to include in the Sign In With Farcaster message. | None |
| `notBefore` | `string` | Time when the message becomes valid. ISO 8601 datetime string. | None |
| `expirationTime` | `string` | Time when the message expires. ISO 8601 datetime string. | None |
| `requestId` | `string` | An optional system-specific ID to include in the message. | None |
| `onSuccess` | `function` | Callback invoked when sign in is complete and the user is authenticated. | None |
| `onStatusResponse` | `function` | Callback invoked when the component receives a status update from the relay server. | None |
| `onError` | `function` | Error callback function. | None |
| `onSignOut` | `function` | Callback invoked when the user signs out. | None |
| `hideSignOut` | `boolean` | Hide the Sign out button. | `false` |
| `debug` | `boolean` | Render a debug panel displaying internal auth-kit state. | `false` |
## Examples
### Custom nonce
```tsx theme={"system"}
import { SignInButton } from '@farcaster/auth-kit';
export const Login = ({ nonce }: { nonce: string }) => {
return (
console.log(`Hello, ${username}! Your fid is ${fid}.`)
}
/>
);
};
```
# Change custody address
Source: https://docs.neynar.com/farcaster/developers/guides/accounts/change-custody
Accounts are owned by custody address which is an Ethereum address on OP Mainnet.
A user may want to change this address for security reasons or to transfer ownership of the entire account.
### Requirements
* An ETH wallet that owns the account on OP Mainnet, with some ETH.
* An Ethereum provider URL for OP Mainnet (e.g. via [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/) or [QuickNode](https://www.quicknode.com/)).
### Change Custody Address
Call the `transfer` function on the Id Registry contract. The receiving address must provide an EIP-712 signature accepting the transfer.
```ts [@farcaster/hub-web] theme={"system"}
import { ViemWalletEip712Signer } from '@farcaster/hub-web';
import { walletClient, account } from './clients.ts';
import { readNonce, getDeadline } from './helpers.ts';
const nonce = await readNonce();
const deadline = getDeadline();
const eip712Signer = new ViemWalletEip712Signer(walletClient);
const signature = await eip712Signer.signTransfer({
fid: 1n,
to: account,
nonce,
deadline,
});
const { request: transferRequest } = await publicClient.simulateContract({
...IdContract,
functionName: 'transfer',
args: [account, deadline, signature], // to, deadline, signature
});
await walletClient.writeContract(transferRequest);
```
```ts [Viem] theme={"system"}
import {
ID_REGISTRY_EIP_712_TYPES,
idRegistryABI,
ID_REGISTRY_ADDRESS,
} from '@farcaster/hub-web';
import { walletClient, account } from './clients.ts';
import { readNonce, getDeadline } from './helpers.ts';
const nonce = await readNonce();
const deadline = getDeadline();
const IdContract = {
abi: idRegistryABI,
address: ID_REGISTRY_ADDRESS,
chain: optimism,
};
const signature = await walletClient.signTypedData({
account,
...ID_REGISTRY_EIP_712_TYPES,
primaryType: 'Transfer',
message: {
fid: 1n,
to: account,
nonce,
deadline,
},
});
```
```ts [helpers.ts] theme={"system"}
import { ID_REGISTRY_ADDRESS, idRegistryABI } from '@farcaster/hub-web';
import { publicClient, account } from './clients.ts';
export const getDeadline = () => {
const now = Math.floor(Date.now() / 1000);
const oneHour = 60 * 60;
return now + oneHour;
};
export const readNonce = async () => {
return await publicClient.readContract({
address: ID_REGISTRY_ADDRESS,
abi: idRegistryABI,
functionName: 'nonces',
args: [account],
});
};
```
```ts [clients.ts] theme={"system"}
import { createWalletClient, createPublicClient, custom, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { optimism } from 'viem/chains';
export const publicClient = createPublicClient({
chain: optimism,
transport: http(),
});
export const walletClient = createWalletClient({
chain: optimism,
transport: custom(window.ethereum),
});
// JSON-RPC Account
export const [account] = await walletClient.getAddresses();
// Local Account
export const account = privateKeyToAccount('0x...');
```
Transferring a FID does not reset its recovery address. To transfer a FID and update its recovery address, call `transferAndChangeRecovery`.
See the [Id Registry](/farcaster/reference/contracts/reference/id-registry#transfer) section for more details.
# Change Farcaster name
Source: https://docs.neynar.com/farcaster/developers/guides/accounts/change-fname
A user can change their offchain ENS name or Fname without affecting their account's history. This can be done at most once in 28 days.
* Fnames may be revoked if you violate the [usage policy](/farcaster/learn/architecture/ens-names#offchain-ens-names-fnames).
* Apps may lower your reputation if you change Fnames often.
### Requirements
* An ETH wallet that owns the account on OP Mainnet. No ETH is required.
### Change username
To transfer an Fname, e.g. `farcaster`, make a POST request to `/transfers` with the following body:
```yaml theme={"system"}
{
"name": "farcaster", // Name to transfer
"from": 123, // FID to transfer from
"to": 321, // FID to transfer to
"fid": 123, // FID making the request (must match from)
"owner": "0x...", // Custody address of FID making the request
"timestamp": 1641234567, // Current timestamp in seconds
"signature": "0x..." // EIP-712 signature signed by the custody address of the FID
}
```
To generate the EIP-712 signature, use the following code:
```js theme={"system"}
import { makeUserNameProofClaim, EIP712Signer } from '@farcaster/hub-nodejs';
const accountKey: EIP712Signer = undefined; // Account Key for the custody address (use appropriate subclass from hub-nodejs for ethers or viem)
const claim = makeUserNameProofClaim({
name: 'farcaster',
owner: '0x...',
timestamp: Math.floor(Date.now() / 1000),
});
const signature = (
await accountKey.signUserNameProofClaim(claim)
)._unsafeUnwrap();
```
Example request via curl:
```bash theme={"system"}
curl -X POST https://fnames.farcaster.xyz/transfers \
-H "Content-Type: application/json" \
-d \
'{"name": "farcaster", "owner": "0x...", "signature": "0x...", "from": 123, "to": 321, "timestamp": 1641234567, "fid": 123}'
```
See [here](/farcaster/reference/fname/api) for more details on the Fname registry API.
# Change recovery address
Source: https://docs.neynar.com/farcaster/developers/guides/accounts/change-recovery
Accounts can configure a recovery address to protect against the loss of the custody address. The recovery address will change the custody address of the account.
### Requirements
* An ETH wallet that has an FID on OP Mainnet, with some ETH for gas costs.
* An ETH RPC URL for OP Mainnet (e.g. via [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/) or [QuickNode](https://www.quicknode.com/)).
### Change Address
Call the `changeRecovery` function on the Id Registry contract.
```ts [@farcaster/hub-web] theme={"system"}
import { walletClient, account, IdContract } from './clients.ts';
const newRecoveryAddress = '0x...';
const { request: transferRequest } = await walletClient.simulateContract({
...IdContract,
functionName: 'changeRecovery',
args: [newRecoveryAddress], // New recovery address
});
await walletClient.writeContract(transferRequest);
```
```ts [clients.ts] theme={"system"}
import {
idRegistryABI,
ID_REGISTRY_ADDRESS,
} from '@farcaster/hub-web';
import { createWalletClient, createPublicClient, custom, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { optimism } from 'viem/chains';
export const IdContract = {
abi: idRegistryABI,
address: ID_REGISTRY_ADDRESS,
chain: optimism,
};
export const publicClient = createPublicClient({
chain: optimism,
transport: http(),
});
export const walletClient = createWalletClient({
chain: optimism,
transport: custom(window.ethereum),
});
// JSON-RPC Account
export const [account] = await walletClient.getAddresses();
// Local Account (use this OR the JSON-RPC account above, not both)
// export const account = privateKeyToAccount('0x...');
```
See the [Id Registry](/farcaster/reference/contracts/reference/id-registry#changerecoveryaddress) section for more
details.
# Create an account
Source: https://docs.neynar.com/farcaster/developers/guides/accounts/create-account
**Pre-requisites**
* An Ethereum wallet on OP Mainnet, with sufficient ETH for gas costs.
* An Ethereum provider URL for OP Mainnet (e.g. via [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/) or [QuickNode](https://www.quicknode.com/)).
You can register a new user using the Bundler contract. To do so, you'll need to:
1. Set up [Viem](https://viem.sh/) clients and [`@farcaster/hub-web`](https://www.npmjs.com/package/@farcaster/hub-web) account keys.
2. Register an [app FID](/farcaster/reference/contracts/faq#what-is-an-app-fid-how-do-i-get-one) if your app does not already have one.
3. Collect a [`Register`](/farcaster/reference/contracts/reference/id-gateway#register-signature) signature from the user.
4. Create a new account key pair for the user.
5. Use your app account to create a [Signed Key Request](/farcaster/reference/contracts/reference/signed-key-request-validator).
6. Collect an [`Add`](/farcaster/reference/contracts/reference/key-gateway#add-signature) signature from the user.
7. Call the [Bundler](https://docs.farcaster.xyz/reference/contracts/reference/bundler#register) contract to register onchain.
### 1. Set up clients and account keys
First, set up Viem clients and `@farcaster/hub-web` account keys. In this example, we'll use Viem local accounts and account keys, but
you can also use `ViemWalletEip712Signer` to connect to a user's wallet rather than a local account.
```ts theme={"system"}
import * as ed from '@noble/ed25519';
import {
ID_GATEWAY_ADDRESS,
ID_REGISTRY_ADDRESS,
ViemLocalEip712Signer,
idGatewayABI,
idRegistryABI,
NobleEd25519Signer,
BUNDLER_ADDRESS,
bundlerABI,
KEY_GATEWAY_ADDRESS,
keyGatewayABI,
} from '@farcaster/hub-web';
import { bytesToHex, createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { optimism } from 'viem/chains';
const APP_PRIVATE_KEY = '0x00';
const ALICE_PRIVATE_KEY = '0x00';
const publicClient = createPublicClient({
chain: optimism,
transport: http(),
});
const walletClient = createWalletClient({
chain: optimism,
transport: http(),
});
const app = privateKeyToAccount(APP_PRIVATE_KEY);
const appAccountKey = new ViemLocalEip712Signer(app as any);
const alice = privateKeyToAccount(ALICE_PRIVATE_KEY);
const aliceAccountKey = new ViemLocalEip712Signer(alice as any);
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // Set the signatures' deadline to 1 hour from now
const FARCASTER_RECOVERY_PROXY = '0x00000000FcB080a4D6c39a9354dA9EB9bC104cd7';
```
### 2. Register an app FID
Register an app FID if you don't already have one. To register an FID, you'll need to read the price from the ID Gateway, then call the ID Gateway and pay the registration price. You can read back your new FID from the Id Registry contract, or parse it from a `Register` event. Here, we'll read it from the registry contract.
```ts theme={"system"}
const price = await publicClient.readContract({
address: ID_GATEWAY_ADDRESS,
abi: idGatewayABI,
functionName: 'price',
args: [0n],
});
const { request } = await publicClient.simulateContract({
account: app,
address: ID_GATEWAY_ADDRESS,
abi: idGatewayABI,
functionName: 'register',
args: [FARCASTER_RECOVERY_PROXY, 0n],
value: price,
});
await walletClient.writeContract(request);
const APP_FID = await publicClient.readContract({
address: ID_REGISTRY_ADDRESS,
abi: idRegistryABI,
functionName: 'idOf',
args: [app.address],
});
```
### 3. Collect a `Register` signature from the user.
Collect an EIP-712 `Register` signature from the user. In a real world app, you'll likely collect this signature on your frontend, from the user's wallet. In a frontend context, you can us a `ViemEip712WalletSigner` to connect to a browser wallet rather than a local signer.
```ts theme={"system"}
let nonce = await publicClient.readContract({
address: ID_REGISTRY_ADDRESS,
abi: idRegistryABI,
functionName: 'nonces',
args: [alice.address],
});
const registerSignatureResult = await aliceAccountKey.signRegister({
to: alice.address as `0x${string}`,
recovery: FARCASTER_RECOVERY_PROXY,
nonce,
deadline,
});
let registerSignature;
if (registerSignatureResult.isOk()) {
registerSignature = registerSignatureResult.value;
} else {
throw new Error('Failed to generate register signature');
}
```
### 4. Create a new account keypair
Create a new Ed25519 account keypair for the user. In a real app, ensure you keep the user's private key secret.
```ts theme={"system"}
const privateKeyBytes = ed.utils.randomPrivateKey();
const accountKey = new NobleEd25519Signer(privateKeyBytes);
let accountPubKey = new Uint8Array();
const accountKeyResult = await accountKey.getSignerKey();
```
### 5. Use your app account to create a Signed Key Request
Create a Signed Key Request, signed by your app account. To do so, you can use the `getSignedKeyRequestMetadata` helper,
which generates and signs the Signed Key Request.
```ts theme={"system"}
if (accountKeyResult.isOk()) {
accountPubKey = accountKeyResult.value;
const signedKeyRequestMetadata =
await appAccountKey.getSignedKeyRequestMetadata({
requestFid: APP_FID,
key: accountPubKey,
deadline,
});
}
```
### 6. Collect an `Add` signature from the user.
Collect an EIP-712 `Add` signature from the user to authorize adding an account key to their FID.
```ts theme={"system"}
if (signedKeyRequestMetadata.isOk()) {
const metadata = bytesToHex(signedKeyRequestMetadata.value);
nonce = await publicClient.readContract({
address: KEY_GATEWAY_ADDRESS,
abi: keyGatewayABI,
functionName: 'nonces',
args: [alice.address],
});
const addSignatureResult = await aliceAccountKey.signAdd({
owner: alice.address as `0x${string}`,
keyType: 1,
key: accountPubKey,
metadataType: 1,
metadata,
nonce,
deadline,
});
}
```
### 7. Call the Bundler contract to register onchain.
Call the Key Gateway contract and provide the user's signature to add the key onchain.
```ts theme={"system"}
if (addSignatureResult.isOk()) {
const addSignature = addSignatureResult.value;
const price = await publicClient.readContract({
address: BUNDLER_ADDRESS,
abi: bundlerABI,
functionName: 'price',
args: [0n],
});
const { request } = await publicClient.simulateContract({
account: app,
address: BUNDLER_ADDRESS,
abi: bundlerABI,
functionName: 'register',
args: [
{
to: alice.address,
recovery: FARCASTER_RECOVERY_PROXY,
sig: bytesToHex(registerSignature),
deadline,
},
[
{
keyType: 1,
key: bytesToHex(accountPubKey),
metadataType: 1,
metadata: metadata,
sig: bytesToHex(addSignature.value),
deadline,
},
],
0n,
],
value: price,
});
await walletClient.writeContract(request);
}
```
### Full code example
See the full code example below for all the steps above.
```ts [@farcaster/hub-web] theme={"system"}
import * as ed from '@noble/ed25519';
import {
ID_GATEWAY_ADDRESS,
ID_REGISTRY_ADDRESS,
ViemLocalEip712Signer,
idGatewayABI,
idRegistryABI,
NobleEd25519Signer,
BUNDLER_ADDRESS,
bundlerABI,
KEY_GATEWAY_ADDRESS,
keyGatewayABI,
} from '@farcaster/hub-web';
import { bytesToHex, createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { optimism } from 'viem/chains';
const APP_PRIVATE_KEY = '0x00';
const ALICE_PRIVATE_KEY = '0x00';
const publicClient = createPublicClient({
chain: optimism,
transport: http('http://localhost:8545'),
});
const walletClient = createWalletClient({
chain: optimism,
transport: http('http://localhost:8545'),
});
const app = privateKeyToAccount(APP_PRIVATE_KEY);
const appAccountKey = new ViemLocalEip712Signer(app as any);
const alice = privateKeyToAccount(ALICE_PRIVATE_KEY);
const aliceAccountKey = new ViemLocalEip712Signer(alice as any);
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // Set the signatures' deadline to 1 hour from now
const FARCASTER_RECOVERY_PROXY = '0x00000000FcB080a4D6c39a9354dA9EB9bC104cd7';
/*******************************************************************************
* IdGateway - register - Register an app FID.
*******************************************************************************/
/**
* Get the current price to register. We're not going to register any
* extra storage, so we pass 0n as the only argument.
*/
const price = await publicClient.readContract({
address: ID_GATEWAY_ADDRESS,
abi: idGatewayABI,
functionName: 'price',
args: [0n],
});
/**
* Call `register` to register an FID to the app account.
*/
const { request } = await publicClient.simulateContract({
account: app,
address: ID_GATEWAY_ADDRESS,
abi: idGatewayABI,
functionName: 'register',
args: [FARCASTER_RECOVERY_PROXY, 0n],
value: price,
});
await walletClient.writeContract(request);
/**
* Read the app fid from the Id Registry contract.
*/
const APP_FID = await publicClient.readContract({
address: ID_REGISTRY_ADDRESS,
abi: idRegistryABI,
functionName: 'idOf',
args: [app.address],
});
/*******************************************************************************
* Collect Register signature from Alice
*******************************************************************************/
let nonce = await publicClient.readContract({
address: KEY_GATEWAY_ADDRESS,
abi: keyGatewayABI,
functionName: 'nonces',
args: [alice.address],
});
const registerSignatureResult = await aliceAccountKey.signRegister({
to: alice.address as `0x${string}`,
recovery: FARCASTER_RECOVERY_PROXY,
nonce,
deadline,
});
let registerSignature;
if (registerSignatureResult.isOk()) {
registerSignature = registerSignatureResult.value;
} else {
throw new Error('Failed to generate register signature');
}
/*******************************************************************************
* Collect Add signature from Alice.
*******************************************************************************/
/**
* 1. Create an Ed25519 account keypair for Alice and get the public key.
*/
const privateKeyBytes = ed.utils.randomPrivateKey();
const accountKey = new NobleEd25519Signer(privateKeyBytes);
let accountPubKey = new Uint8Array();
const accountKeyResult = await accountKey.getSignerKey();
if (accountKeyResult.isOk()) {
accountPubKey = accountKeyResult.value;
/**
* 2. Generate a Signed Key Request from the app account.
*/
const signedKeyRequestMetadata =
await appAccountKey.getSignedKeyRequestMetadata({
requestFid: APP_FID,
key: accountPubKey,
deadline,
});
if (signedKeyRequestMetadata.isOk()) {
const metadata = bytesToHex(signedKeyRequestMetadata.value);
/**
* 3. Read Alice's nonce from the Key Gateway.
*/
nonce = await publicClient.readContract({
address: KEY_GATEWAY_ADDRESS,
abi: keyGatewayABI,
functionName: 'nonces',
args: [alice.address],
});
/**
* Then, collect her `Add` signature.
*/
const addSignatureResult = await aliceAccountKey.signAdd({
owner: alice.address as `0x${string}`,
keyType: 1,
key: accountPubKey,
metadataType: 1,
metadata,
nonce,
deadline,
});
if (addSignatureResult.isOk()) {
const addSignature = addSignatureResult.value;
/**
* Read the current registration price.
*/
const price = await publicClient.readContract({
address: BUNDLER_ADDRESS,
abi: bundlerABI,
functionName: 'price',
args: [0n],
});
/**
* Call `register` with Alice's signatures, registration, and key parameters.
*/
const { request } = await publicClient.simulateContract({
account: app,
address: BUNDLER_ADDRESS,
abi: bundlerABI,
functionName: 'register',
args: [
{
to: alice.address,
recovery: FARCASTER_RECOVERY_PROXY,
sig: bytesToHex(registerSignature),
deadline,
},
[
{
keyType: 1,
key: bytesToHex(accountPubKey),
metadataType: 1,
metadata: metadata,
sig: bytesToHex(addSignature),
deadline,
},
],
0n,
],
value: price,
});
await walletClient.writeContract(request);
}
}
}
```
See the [Bundler](/farcaster/reference/contracts/reference/bundler#register) reference for more
details.
# Create an account key
Source: https://docs.neynar.com/farcaster/developers/guides/accounts/create-account-key
An account can authorize account keys, which can create messages on its behalf.
The owner of the account can revoke an account key at any time. To add an account key, you'll need to follow six steps:
1. Set up [Viem](https://viem.sh/) clients and [`@farcaster/hub-web`](https://www.npmjs.com/package/@farcaster/hub-web) account key.
2. Register an [app FID](/farcaster/reference/contracts/faq#what-is-an-app-fid-how-do-i-get-one) if your app does not already have one.
3. Create a new account key for the user.
4. Use your app account to create a [Signed Key Request](/farcaster/reference/contracts/reference/signed-key-request-validator).
5. Collect an [`Add`](/farcaster/reference/contracts/reference/key-gateway#add-signature) signature from the user.
6. Call the [Key Gateway](https://docs.farcaster.xyz/reference/contracts/reference/key-gateway#addFor) contract to add the key onchain.
### Requirements
* An ETH wallet on OP Mainnet, with some ETH.
* An ETH RPC URL for OP Mainnet (e.g. via [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/) or [QuickNode](https://www.quicknode.com/)).
### 1. Set up clients and account key
First, set up Viem clients and `@farcaster/hub-web` account key. In this example, we'll use Viem local accounts and account key, but
you can also use `ViemWalletEip712Signer` to connect to a user's wallet rather than a local account.
```ts theme={"system"}
import * as ed from '@noble/ed25519';
import {
ID_GATEWAY_ADDRESS,
ID_REGISTRY_ADDRESS,
ViemLocalEip712Signer,
idGatewayABI,
idRegistryABI,
NobleEd25519Signer,
KEY_GATEWAY_ADDRESS,
keyGatewayABI,
} from '@farcaster/hub-nodejs';
import { bytesToHex, createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { optimism } from 'viem/chains';
const APP_PRIVATE_KEY = '0x00';
const ALICE_PRIVATE_KEY = '0x00';
const publicClient = createPublicClient({
chain: optimism,
transport: http(),
});
const walletClient = createWalletClient({
chain: optimism,
transport: http(),
});
const app = privateKeyToAccount(APP_PRIVATE_KEY);
const appAccountKey = new ViemLocalEip712Signer(app as any);
const alice = privateKeyToAccount(ALICE_PRIVATE_KEY);
const aliceAccountKey = new ViemLocalEip712Signer(alice as any);
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // Set the signatures' deadline to 1 hour from now
const FARCASTER_RECOVERY_PROXY = '0x00000000FcB080a4D6c39a9354dA9EB9bC104cd7';
```
### 2. Register an app FID
Register an app FID if you don't already have one. To register an FID, you'll need to read the price from the ID Gateway, then call the ID Gateway and pay the registration price. You can read back your new FID from the Id Registry contract, or parse it from a `Register` event. Here, we'll read it from the registry contract.
```ts theme={"system"}
const price = await publicClient.readContract({
address: ID_GATEWAY_ADDRESS,
abi: idGatewayABI,
functionName: 'price',
args: [0n],
});
const { request } = await publicClient.simulateContract({
account: app,
address: ID_GATEWAY_ADDRESS,
abi: idGatewayABI,
functionName: 'register',
args: [FARCASTER_RECOVERY_PROXY, 0n],
value: price,
});
await walletClient.writeContract(request);
const APP_FID = await publicClient.readContract({
address: ID_REGISTRY_ADDRESS,
abi: idRegistryABI,
functionName: 'idOf',
args: [app.address],
});
```
### 3. Create a new account key
Create a new Ed25519 key pair for the user. In a real app, ensure you keep the private key secret.
```ts theme={"system"}
const privateKeyBytes = ed.utils.randomPrivateKey();
const accountKey = new NobleEd25519Signer(privateKeyBytes);
let accountPubKey = new Uint8Array();
const accountKeyResult = await accountKey.getSignerKey();
```
### 4. Use your app account to create a Signed Key Request
Create a Signed Key Request, signed by your app account. To do so, you can use the `getSignedKeyRequestMetadata` helper,
which generates and signs the Signed Key Request.
```ts theme={"system"}
if (accountKeyResult.isOk()) {
accountPubKey = accountKeyResult.value;
const signedKeyRequestMetadata =
await appAccountKey.getSignedKeyRequestMetadata({
requestFid: APP_FID,
key: accountPubKey,
deadline,
});
}
```
### 5. Collect an `Add` signature from the user.
Collect an EIP-712 `Add` signature from the user to authorize adding an account key to their FID. In a real world app,
you'll likely collect this signature on your frontend, from the user's wallet. In a frontend context, you can us a `ViemEip712WalletSigner` to connect to a browser wallet rather than a local signer.
```ts theme={"system"}
if (signedKeyRequestMetadata.isOk()) {
const metadata = bytesToHex(signedKeyRequestMetadata.value);
const aliceNonce = await publicClient.readContract({
address: KEY_GATEWAY_ADDRESS,
abi: keyGatewayABI,
functionName: 'nonces',
args: [alice.address],
});
const aliceSignature = await aliceAccountKey.signAdd({
owner: alice.address as `0x${string}`,
keyType: 1,
key: accountPubKey,
metadataType: 1,
metadata,
nonce,
deadline,
});
}
```
### 6. Call the Key Gateway contract to add the key onchain.
Call the Key Gateway contract and provide the user's signature to add the key onchain.
```ts theme={"system"}
if (aliceSignature.isOk()) {
const { request } = await publicClient.simulateContract({
account: app,
address: KEY_GATEWAY_ADDRESS,
abi: keyGatewayABI,
functionName: 'addFor',
args: [
alice.address,
1,
bytesToHex(accountPubKey),
1,
metadata,
deadline,
bytesToHex(aliceSignature.value),
],
});
await walletClient.writeContract(request);
}
```
### Full code example
See the full code example below for all the steps above.
```ts [@farcaster/hub-web] theme={"system"}
import * as ed from '@noble/ed25519';
import {
ID_GATEWAY_ADDRESS,
ID_REGISTRY_ADDRESS,
ViemLocalEip712Signer,
idGatewayABI,
idRegistryABI,
NobleEd25519Signer,
KEY_GATEWAY_ADDRESS,
keyGatewayABI,
} from '@farcaster/hub-nodejs';
import { bytesToHex, createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { optimism } from 'viem/chains';
const APP_PRIVATE_KEY = '0x00';
const ALICE_PRIVATE_KEY = '0x00';
/*******************************************************************************
* Setup - Create local accounts, Viem clients, helpers, and constants.
*******************************************************************************/
/**
* Create Viem public (read) and wallet (write) clients.
*/
const publicClient = createPublicClient({
chain: optimism,
transport: http(),
});
const walletClient = createWalletClient({
chain: optimism,
transport: http(),
});
/**
* A local account representing your app. You'll
* use this to sign key metadata and send
* transactions on behalf of users.
*/
const app = privateKeyToAccount(APP_PRIVATE_KEY);
const appAccountKey = new ViemLocalEip712Signer(app as any);
console.log('App:', app.address);
/**
* A local account representing Alice, a user.
*/
const alice = privateKeyToAccount(ALICE_PRIVATE_KEY);
const aliceAccountKey = new ViemLocalEip712Signer(alice as any);
console.log('Alice:', alice.address);
/**
* Generate a deadline timestamp one hour from now.
* All Farcaster EIP-712 signatures include a deadline, a block timestamp
* after which the signature is no longer valid.
*/
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // Set the signatures' deadline to 1 hour from now
const FARCASTER_RECOVERY_PROXY = '0x00000000FcB080a4D6c39a9354dA9EB9bC104cd7';
/*******************************************************************************
* IdGateway - register - Register an app FID.
*******************************************************************************/
/**
* Get the current price to register. We're not going to register any
* extra storage, so we pass 0n as the only argument.
*/
const price = await publicClient.readContract({
address: ID_GATEWAY_ADDRESS,
abi: idGatewayABI,
functionName: 'price',
args: [0n],
});
/**
* Call `register` to register an FID to the app account.
*/
const { request } = await publicClient.simulateContract({
account: app,
address: ID_GATEWAY_ADDRESS,
abi: idGatewayABI,
functionName: 'register',
args: [FARCASTER_RECOVERY_PROXY, 0n],
value: price,
});
await walletClient.writeContract(request);
/**
* Read the app FID from the Id Registry contract.
*/
const APP_FID = await publicClient.readContract({
address: ID_REGISTRY_ADDRESS,
abi: idRegistryABI,
functionName: 'idOf',
args: [app.address],
});
/*******************************************************************************
* KeyGateway - addFor - Add an account key to Alice's FID.
*******************************************************************************/
/**
* To add an account key to Alice's FID, we need to follow four steps:
*
* 1. Create a new account key pair for Alice.
* 2. Use our app account to create a Signed Key Request.
* 3. Collect Alice's `Add` signature.
* 4. Call the contract to add the key onchain.
*/
/**
* 1. Create an Ed25519 account key pair for Alice and get the public key.
*/
const privateKeyBytes = ed.utils.randomPrivateKey();
const accountKey = new NobleEd25519Signer(privateKeyBytes);
let accountPubKey = new Uint8Array();
const accountKeyResult = await accountKey.getSignerKey();
if (accountKeyResult.isOk()) {
accountPubKey = accountKeyResult.value;
/**
* 2. Generate a Signed Key Request from the app account.
*/
const signedKeyRequestMetadata =
await appAccountKey.getSignedKeyRequestMetadata({
requestFid: APP_FID,
key: accountPubKey,
deadline,
});
if (signedKeyRequestMetadata.isOk()) {
const metadata = bytesToHex(signedKeyRequestMetadata.value);
/**
* 3. Read Alice's nonce from the Key Gateway.
*/
const aliceNonce = await publicClient.readContract({
address: KEY_GATEWAY_ADDRESS,
abi: keyGatewayABI,
functionName: 'nonces',
args: [alice.address],
});
/**
* Then, collect her `Add` signature.
*/
const aliceSignature = await aliceAccountKey.signAdd({
owner: alice.address as `0x${string}`,
keyType: 1,
key: accountPubKey,
metadataType: 1,
metadata,
nonce: aliceNonce,
deadline,
});
if (aliceSignature.isOk()) {
/**
* Call `addFor` with Alice's signature and the signed key request.
*/
const { request } = await publicClient.simulateContract({
account: app,
address: KEY_GATEWAY_ADDRESS,
abi: keyGatewayABI,
functionName: 'addFor',
args: [
alice.address,
1,
bytesToHex(accountPubKey),
1,
metadata,
deadline,
bytesToHex(aliceSignature.value),
],
});
await walletClient.writeContract(request);
}
}
}
```
See the [Key Registry](/farcaster/reference/contracts/reference/key-registry#add) reference for more
details.
# Find account by username
Source: https://docs.neynar.com/farcaster/developers/guides/accounts/find-by-name
If you have a user's name and want to find their account, you'll need to use one of these methods depending on what type of username they have.
## Offchain ENS Names (Fnames)
If the user has an offchain ENS name like `@alice`, you'll need to call the [Fname Registry](/farcaster/reference/fname/api#get-current-fname-or-fid).
```bash theme={"system"}
curl https://fnames.farcaster.xyz/transfers/current?name=farcaster | jq
{
"transfers": [
{
"id": 1,
"timestamp": 1628882891,
"username": "farcaster",
"owner": "0x8773442740c17c9d0f0b87022c722f9a136206ed",
"from": 0,
"to": 1,
"user_signature": "0xa6fdd2a69deab5633636f32a30a54b21b27dff123e6481532746eadca18cd84048488a98ca4aaf90f4d29b7e181c4540b360ba0721b928e50ffcd495734ef8471b",
"server_signature": "0xb7181760f14eda0028e0b647ff15f45235526ced3b4ae07fcce06141b73d32960d3253776e62f761363fb8137087192047763f4af838950a96f3885f3c2289c41b"
}
]
}
```
This returns the most recent transfer associated with the name if it is registered. Note that the creation of an Fname is a transfer from the zero address to the custody address. The `to` field indicates the current FID that owns the name.
## Onchain ENS Names
If the user has an onchain ENS name like `@alice.eth`, the easiest way to do it is with the Postgres [replicator](https://snapchain.farcaster.xyz/guides/syncing-to-db). It indexes onchain and offchain data and lets you easily find what you're looking for.
Once you have it set up, query the `fnames` table in the replicator database for the account's FID:
```sql theme={"system"}
SELECT username, fid
FROM fnames
WHERE username = 'farcaster.eth'
order by updated_at desc
limit 1;
```
See [here](/farcaster/reference/replicator/schema#fnames) for more details on the replicator table schema.
# Register ENS Name
Source: https://docs.neynar.com/farcaster/developers/guides/accounts/register-ens
A user can set their farcaster username to an ENS name they own.
### Requirements
* An ETH wallet that owns the account on OP Mainnet. No ETH is required.
* A valid ENS name that resolves to the custody address of the Farcaster account, or a verified eth address in the
Farcaster account.
### Register ENS Name
First, ensure the ENS name resolves to the custody address of the farcaster account. Or the resolved address is a
verified eth address in the Farcaster account. See [here](/farcaster/developers/guides/writing/verify-address) for how to
verify an ETH address.
Then, generate an EIP-712 signature for the ENS name proof claim and submit the message. For more details on how to
create messages, see [this guide](/farcaster/developers/guides/writing/messages).
```js theme={"system"}
import {
makeUserNameProofClaim,
EIP712Signer,
makeUsernameProof,
FarcasterNetwork,
makeUserDataAdd,
UserNameType,
UserDataType,
NobleEd25519Signer,
} from '@farcaster/hub-nodejs';
const accountKey: EIP712Signer = undefined; // Account Key for the custody/verified address (use appropriate subclass from hub-nodejs for ethers or viem)
const accountEd25519Key = undefined; // Private key for the farcaster account signer
const claim = makeUserNameProofClaim({
name: 'farcaster.eth', // ENS name to register
owner: '0x...', // Must be the public key of accountKey, and name must resolve to this address
timestamp: Math.floor(Date.now() / 1000),
});
const signature = (
await accountKey.signUserNameProofClaim(claim)
)._unsafeUnwrap();
const dataOptions = {
fid: 123, // FID make the request
network: FarcasterNetwork.MAINNET,
};
const signer = new NobleEd25519Signer(accountEd25519Key);
const usernameProofMessage = makeUsernameProof(
{
name: claim.name,
owner: claim.owner,
timestamp: claim.timestamp,
fid: dataOptions.fid,
signature: signature,
type: UserNameType.USERNAME_TYPE_ENS_L1,
},
dataOptions,
signer
);
// Submit the message to the node. Note that this only registers the name proof to the account, it does not change the username yet.
// await client.submitMessage(usernameProofMessage);
// Once it's accepted, you can set the username to the ENS name
const usernameMessage = makeUserDataAdd(
{
type: UserDataType.USERNAME,
value: claim.name,
},
dataOptions,
signer
);
// Submit the username message to the node
// await client.submitMessage(usernameMessage);
```
# Decode key metadata
Source: https://docs.neynar.com/farcaster/developers/guides/advanced/decode-key-metadata
When users add a new key to the Key Registry, the contract emits [encoded metadata](/farcaster/reference/contracts/reference/signed-key-request-validator#signedkeyrequestmetadata-struct) in an event. You can use this metadata to determine who requested the key.
To decode key metadata, you can use Viem's `decodeAbiParameters` function:
```ts [Viem] theme={"system"}
import { decodeAbiParameters } from 'viem';
const encodedMetadata =
'0x' +
'0000000000000000000000000000000000000000000000000000000000000020' +
'00000000000000000000000000000000000000000000000000000000000023C0' +
'00000000000000000000000002EF790DD7993A35FD847C053EDDAE940D055596' +
'0000000000000000000000000000000000000000000000000000000000000080' +
'00000000000000000000000000000000000000000000000000000000657C8AF5' +
'0000000000000000000000000000000000000000000000000000000000000041' +
'F18569F4364BB2455DB27A4E8464A83AD8555416B1AAB21FF26E8501168F471F' +
'7EC17BB096EA4438E5BF82CE01224B2F67EF9042737B7B101C41A1A05CB469DC' +
'1B00000000000000000000000000000000000000000000000000000000000000';
const decoded = decodeAbiParameters(
[
{
components: [
{
name: 'requestFid',
type: 'uint256',
},
{
name: 'requestSigner',
type: 'address',
},
{
name: 'signature',
type: 'bytes',
},
{
name: 'deadline',
type: 'uint256',
},
],
type: 'tuple',
},
],
encodedMetadata
);
console.log(decoded);
/*
[
{
requestFid: 9152n,
requestSigner: '0x02ef790Dd7993A35fD847C053EDdAE940D055596',
signature: '0xF185...69F4',
deadline: 1702660853n
}
]
*/
```
See the [Signed Key Request Validator](/farcaster/reference/contracts/reference/signed-key-request-validator#signedkeyrequestmetadata-struct) reference for more
details.
# Counting signups by day
Source: https://docs.neynar.com/farcaster/developers/guides/advanced/query-signups
**Pre-requisites** - Read access to a replicator database
To count the number of signups by day, we can use the `chain_events` table to query the number
of [`ID_REGISTER`](https://snapchain.farcaster.xyz/reference/datatypes/events#onchaineventtype) events
and group by day.
```sql theme={"system"}
SELECT DATE_TRUNC('day', created_at) AS day, COUNT(*) AS count
FROM chain_events
WHERE type = 3 -- ID_REGISTER (see event types reference page)
GROUP BY day
ORDER BY day desc;
```
For more information on the schema, see the [replicator schema](/farcaster/reference/replicator/schema).
# Generate a chronological feed for a user
Source: https://docs.neynar.com/farcaster/developers/guides/apps/feed
This example will showcase how to read data from the Farcaster network using the
official [hub-nodejs](https://github.com/farcasterxyz/hub-monorepo/tree/main/packages/hub-nodejs) in typescript.
We will create a simple feed of casts from a list of FIDs and display them in reverse chronological order.
## Installation
Create an empty typescript project and install the hub-nodejs package
```bash theme={"system"}
yarn add @farcaster/hub-nodejs
```
## 1. Create a client
First, let's set up some constants and create a client to connect to the hub.
```typescript theme={"system"}
import { getSSLHubRpcClient } from '@farcaster/hub-nodejs';
/**
* Populate the following constants with your own values
*/
const HUB_URL = 'hoyt.farcaster.xyz:3383'; // URL of the Hub
const FIDS = [2, 3]; // User IDs to fetch casts for
// const client = getInsecureHubRpcClient(HUB_URL); // Use this if you're not using SSL
const client = getSSLHubRpcClient(HUB_URL);
```
## 2. Cache the fnames for the FIDs
Query the UserData for the provided FIDs, so we can show friendly usernames. Cache them for later.
```typescript theme={"system"}
// Create a mapping of fids to fnames, which we'll need later to display messages
const fidToFname = new Map();
const fnameResultPromises = FIDS.map((fid) =>
client.getUserData({ fid, userDataType: UserDataType.USERNAME })
);
const fnameResults = await Promise.all(fnameResultPromises);
fnameResults.map((result) =>
result.map((uData) => {
if (isUserDataAddMessage(uData)) {
const fid = uData.data.fid;
const fname = uData.data.userDataBody.value;
fidToFname.set(fid, fname);
}
})
);
```
## 3. Fetch top level casts
Query the hub for all casts for each FID, sorted in reverse chronological order and then filter to only the top level
casts.
```typescript theme={"system"}
/**
* Returns a user's casts which are not replies to any other casts in reverse chronological order.
*/
const getPrimaryCastsByFid = async (
fid: number,
client: HubRpcClient
): HubAsyncResult => {
const result = await client.getCastsByFid({
fid: fid,
pageSize: 10,
reverse: true,
});
if (result.isErr()) {
return err(result.error);
}
// Coerce Messages into Casts, should not actually filter out any messages
const casts = result.value.messages.filter(isCastAddMessage);
return ok(casts.filter((message) => !message.data.castAddBody.parentCastId));
};
// Fetch primary casts for each fid
const castResultPromises = FIDS.map((fid) => getPrimaryCastsByFid(fid, client));
const castsResult = Result.combine(await Promise.all(castResultPromises));
if (castsResult.isErr()) {
console.error('Fetching casts failed');
console.error(castsResult.error);
return;
}
```
## 4. Function to pretty print a cast
The raw cast data is not very readable. We'll write a function to convert the timestamp to a human readable format, and
also resolve any mentions (only stored as fids and their location within the cast) to their fnames.
```typescript theme={"system"}
const castToString = async (
cast: CastAddMessage,
nameMapping: Map,
client: HubRpcClient
) => {
const fname = nameMapping.get(cast.data.fid) ?? `${cast.data.fid}!`; // if the user doesn't have a username set, use their FID
// Convert the timestamp to a human readable string
const unixTime = fromFarcasterTime(cast.data.timestamp)._unsafeUnwrap();
const dateString = timeAgo.format(new Date(unixTime));
const { text, mentions, mentionsPositions } = cast.data.castAddBody;
const bytes = new TextEncoder().encode(text);
const decoder = new TextDecoder();
let textWithMentions = '';
let indexBytes = 0;
for (let i = 0; i < mentions.length; i++) {
textWithMentions += decoder.decode(
bytes.slice(indexBytes, mentionsPositions[i])
);
const result = await getFnameFromFid(mentions[i], client);
result.map((fname) => (textWithMentions += fname));
indexBytes = mentionsPositions[i];
}
textWithMentions += decoder.decode(bytes.slice(indexBytes));
// Remove newlines from the message text
const textNoLineBreaks = textWithMentions.replace(/(\r\n|\n|\r)/gm, ' ');
return `${fname}: ${textNoLineBreaks}\n${dateString}\n`;
};
```
## 5. Sort and print the casts
Finally, we can sort the casts by timestamp again (so they are interleaved correctly) and print them out.
```typescript theme={"system"}
/**
* Compares two CastAddMessages by timestamp, in reverse chronological order.
*/
const compareCasts = (a: CastAddMessage, b: CastAddMessage) => {
if (a.data.timestamp < b.data.timestamp) {
return 1;
}
if (a.data.timestamp > b.data.timestamp) {
return -1;
}
return 0;
};
const sortedCasts = castsResult.value.flat().sort(compareCasts); // sort casts by timestamp
const stringifiedCasts = await Promise.all(
sortedCasts.map((c) => castToString(c, fidToFname, client))
); // convert casts to printable strings
for (const outputCast of stringifiedCasts) {
console.log(outputCast);
}
```
## Full example
See full example [here](https://github.com/farcasterxyz/hub-monorepo/tree/main/packages/hub-nodejs/examples/chron-feed)
# Hello World
Source: https://docs.neynar.com/farcaster/developers/guides/basics/hello-world
Create your Farcaster account programmatically and publish your first "Hello World" message.
The example shows you how to:
* Make onchain transactions to create an account
* Rent a storage unit so you can publish messages
* Add an account key to sign messages
* Acquire an fname for your account
* Create, sign and publish messages
This example can be checked out as a fully functional
repository [here](https://github.com/farcasterxyz/hub-monorepo/tree/main/packages/hub-nodejs/examples/hello-world).
### Requirements
* Write access to a hub (either your own, or a 3rd party hub)
* An ETH wallet with about \~10\$ USD of ETH bridged to [Optimism](https://www.optimism.io/)
* An ETH RPC URL for OP Mainnet (e.g. via [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/) or [QuickNode](https://www.quicknode.com/)).
## 1. Set up constants
```typescript theme={"system"}
import {
ID_GATEWAY_ADDRESS,
idGatewayABI,
KEY_GATEWAY_ADDRESS,
keyGatewayABI,
ID_REGISTRY_ADDRESS,
idRegistryABI,
FarcasterNetwork,
} from '@farcaster/hub-web';
import { zeroAddress } from 'viem';
import { optimism } from 'viem/chains';
/**
* Populate the following constants with your own values
*/
const MNEMONIC = '';
const OP_PROVIDER_URL = ''; // Alchemy or Infura url
const RECOVERY_ADDRESS = zeroAddress; // Optional, using the default value means the account will not be recoverable later if the mnemonic is lost
const ACCOUNT_KEY_PRIVATE_KEY: Hex = zeroAddress; // Optional, using the default means a new account key will be created each time
// Note: hoyt is the Farcaster team's mainnet hub, which is password protected to prevent abuse. Use a 3rd party hub
// provider like https://neynar.com/ Or, run your own mainnet hub and broadcast to it permissionlessly.
const HUB_URL = 'hoyt.farcaster.xyz:3383'; // URL + Port of the Hub
const HUB_USERNAME = ''; // Username for auth, leave blank if not using TLS
const HUB_PASS = ''; // Password for auth, leave blank if not using TLS
const USE_SSL = false; // set to true if talking to a hub that uses SSL (3rd party hosted hubs or hubs that require auth)
const FC_NETWORK = FarcasterNetwork.MAINNET; // Network of the Hub
const CHAIN = optimism;
const IdGateway = {
abi: idGatewayABI,
address: ID_GATEWAY_ADDRESS,
chain: CHAIN,
};
const IdContract = {
abi: idRegistryABI,
address: ID_REGISTRY_ADDRESS,
chain: CHAIN,
};
const KeyContract = {
abi: keyGatewayABI,
address: KEY_GATEWAY_ADDRESS,
chain: CHAIN,
};
```
## 2. Register and pay for storage
Create a function to register an FID and pay for storage. This function will check if the account already has an FID
and return early if so.
```typescript theme={"system"}
const getOrRegisterFid = async (): Promise => {
const balance = await walletClient.getBalance({ address: account.address });
// Check if we already have an fid
const existingFid = (await walletClient.readContract({
...IdContract,
functionName: 'idOf',
args: [account.address],
})) as bigint;
if (existingFid > 0n) {
return parseInt(existingFid.toString());
}
const price = await walletClient.readContract({
...IdGateway,
functionName: 'price',
});
if (balance < price) {
throw new Error(
`Insufficient balance to rent storage, required: ${price}, balance: ${balance}`
);
}
const { request: registerRequest } = await walletClient.simulateContract({
...IdGateway,
functionName: 'register',
args: [RECOVERY_ADDRESS],
value: price,
});
const registerTxHash = await walletClient.writeContract(registerRequest);
const registerTxReceipt = await walletClient.waitForTransactionReceipt({
hash: registerTxHash,
});
// Now extract the FID from the logs
const registerLog = decodeEventLog({
abi: idRegistryABI,
data: registerTxReceipt.logs[0].data,
topics: registerTxReceipt.logs[0].topics,
});
const fid = parseInt(registerLog.args['id']);
return fid;
};
const fid = await getOrRegisterFid();
```
## 3. Add an account key
Now, we will add an account key to the key registry. Every account key must have a signed metadata field from the fid of the app requesting it.
In our case, we will use our own fid. Note, this requires you to sign a message with the private key of the address
holding the fid. If this is not possible, register a separate fid for the app first and use that.
```typescript theme={"system"}
const getOrRegisterAccountKey = async (fid: number) => {
if (ACCOUNT_KEY_PRIVATE_KEY !== zeroAddress) {
// If a private key is provided, we assume the account key is already in the key registry
const privateKeyBytes = fromHex(ACCOUNT_KEY_PRIVATE_KEY, 'bytes');
const publicKeyBytes = ed25519.getPublicKey(privateKeyBytes);
return privateKeyBytes;
}
const privateKey = ed25519.utils.randomPrivateKey();
const publicKey = toHex(ed25519.getPublicKey(privateKey));
const eip712signer = new ViemLocalEip712Signer(appAccount);
// To add a key, we need to sign the metadata with the fid of the app we're adding the key on behalf of
// Use your personal fid, or register a separate fid for the app
const metadata = await eip712signer.getSignedKeyRequestMetadata({
requestFid: APP_FID,
key: APP_PRIVATE_KEY,
deadline: Math.floor(Date.now() / 1000) + 60 * 60, // 1 hour from now
});
const { request: signerAddRequest } = await walletClient.simulateContract({
...KeyContract,
functionName: 'add',
args: [1, publicKey, 1, metadata], // keyType, publicKey, metadataType, metadata
});
const accountKeyAddTxHash = await walletClient.writeContract(
signerAddRequest
);
await walletClient.waitForTransactionReceipt({ hash: accountKeyAddTxHash });
// Sleeping 30 seconds to allow hubs to pick up the accountKey tx
await new Promise((resolve) => setTimeout(resolve, 30000));
return privateKey;
};
const accountPrivateKey = await getOrRegisterAccountKey(fid);
```
## 4. Register an fname
Now that the onchain actions are complete, let's register an fname using the farcaster offchain fname registry.
Registering an fname requires a signature from the custody address of the fid.
```typescript theme={"system"}
const registerFname = async (fid: number) => {
try {
// First check if this fid already has an fname
const response = await axios.get(
`https://fnames.farcaster.xyz/transfers/current?fid=${fid}`
);
const fname = response.data.transfer.username;
return fname;
} catch (e) {
// No username, ignore and continue with registering
}
const fname = `fid-${fid}`;
const timestamp = Math.floor(Date.now() / 1000);
const userNameProofSignature = await walletClient.signTypedData({
domain: USERNAME_PROOF_DOMAIN,
types: USERNAME_PROOF_TYPE,
primaryType: 'UserNameProof',
message: {
name: fname,
timestamp: BigInt(timestamp),
owner: account.address,
},
});
const response = await axios.post('https://fnames.farcaster.xyz/transfers', {
name: fname, // Name to register
from: 0, // Fid to transfer from (0 for a new registration)
to: fid, // Fid to transfer to (0 to unregister)
fid: fid, // Fid making the request (must match from or to)
owner: account.address, // Custody address of fid making the request
timestamp: timestamp, // Current timestamp in seconds
signature: userNameProofSignature, // EIP-712 signature signed by the current custody address of the fid
});
return fname;
};
const fname = await registerFname(fid);
```
Note that this only associated the name to our fid, we still need to set it as our username.
## 5. Write to the hub
Finally, we're now ready to submit messages to the hub. First, we shall set the fname as our username. And then post a
cast.
```typescript theme={"system"}
const submitMessage = async (resultPromise: HubAsyncResult) => {
const result = await resultPromise;
if (result.isErr()) {
throw new Error(`Error creating message: ${result.error}`);
}
await hubClient.submitMessage(result.value);
};
const accountKey = new NobleEd25519Signer(accountPrivateKey);
const dataOptions = {
fid: fid,
network: FC_NETWORK,
};
const userDataUsernameBody = {
type: UserDataType.USERNAME,
value: fname,
};
// Set the username
await submitMessage(
makeUserDataAdd(userDataUsernameBody, dataOptions, accountKey)
);
// Post a cast
await submitMessage(
makeCastAdd(
{
text: 'Hello World!',
},
dataOptions,
accountKey
)
);
```
Now, you can view your profile on any farcaster client. To see it on Farcaster, visit `https://farcaster.com/@`
# Get account messages
Source: https://docs.neynar.com/farcaster/developers/guides/querying/fetch-casts
**Pre-requisites** - Read only access to a Snapchain node
See [Snapchain installation](https://snapchain.farcaster.xyz/getting-started#sync-a-node) for more information on how to set up a local Snapchain instance.
To query all the casts for a particular FID, you can use the castsByFid HTTP endpoint:
```bash theme={"system"}
# Default http port is 3381
$ curl http://localhost:3381/v1/castsByFid\?fid\=1 | jq ".messages[].data.castAddBody.text | select( . != null)"
"testing"
"test"
"another test"
"another testy test"
```
This returns all the cast related messages for the fid. There are similar endpoints for reactions and follows. See
the [http api reference](https://snapchain.farcaster.xyz/reference/httpapi/httpapi) for more details.
If you have the [hub-monorepo](https://github.com/farcasterxyz/hub-monorepo) installed from source, you can use the built in `console`. This will use the grpc APIs
```bash theme={"system"}
# Ensure you are in the hubble sub directory
$ cd apps/hubble
# Remove `--insecure` if the host is using TLS
$ yarn console --insecure -s localhost:3383
> res = await rpcClient.getCastsByFid({fid: 1})
Ok {
value: {
messages: [ [Object], [Object], [Object], [Object] ],
nextPageToken:
}
}
> res.value.messages.map( m => m.data.castAddBody.text)
[ 'testing', 'test', 'another test', 'another testy test' ]
```
For more details on the GRPC API, see the [grpc api reference](https://snapchain.farcaster.xyz/reference/grpcapi/grpcapi).
# Fetch casts from a channel
Source: https://docs.neynar.com/farcaster/developers/guides/querying/fetch-channel-casts
**Pre-requisites** - Read access to a Snapchain instance
To fetch casts from a channel, Snapchain provides a `getCastsByParent` api call.
For example, to query all casts to the `ethereum` channel:
```bash theme={"system"}
$ curl http://localhost:3381/v1/castsByParent\?fid\=1\&url\="https://ethereum.org" | jq " .messages | limit(10;.[]) | .data.castAddBody.text"
"cue "
"Commandeering this channel for the one true Ethereum, Ethereum classic."
"Pretty amazing that even during a bear market, the 30 day average burn gives us deflationary ETH. \n\nSource: Ultrasound.Money"
"So, Ethereum is frequently called the Base Layer or L1 when talking about scalability.\n\nBut how can you call the whole Ethereum + Ethereum L2s + L3s that commit to Ethereum L2s?\n\nWe’re building a unified multi-chain explorer for that, but we don’t know how to call it: https://ethereum.routescan.io"
"This, we're already doing.\n\nBut we call it, the Full-Index Ecosystem Explorer."
". By subdomains do you mean more specific namespaces, e.g. Farcaster fnames being name.farcaster.eth?\n\nMy guess is if the root .eth namespace is available it will command higher status and value.\n\nhttps://x.com/0xfoobar/status/1687209523239604230"
"what are the best examples of DAOs with independent core working groups/multisigs?"
"Anyone here use Rabby?\n\nhttps://x.com/0xfoobar/status/1687474090150416385"
"Who needs stablecoins when we have ETH"
"782,672 active + pending validators! 🤯 \n\n(also... I haven't proposed a block for a few months)"
```
Using the GRPC API:
```bash theme={"system"}
> res = await rpcClient.getCastsByParent({parentUrl: "https://ethereum.org"})
> res.value.messages.slice(0, 10).map( m => m.data.castAddBody.text)
[
'cue ',
'Commandeering this channel for the one true Ethereum, Ethereum classic.',
'Pretty amazing that even during a bear market, the 30 day average burn gives us deflationary ETH. \n' +
'\n' +
'Source: Ultrasound.Money',
'So, Ethereum is frequently called the Base Layer or L1 when talking about scalability.\n' +
'\n' +
'But how can you call the whole Ethereum + Ethereum L2s + L3s that commit to Ethereum L2s?\n' +
'\n' +
'We’re building a unified multi-chain explorer for that, but we don’t know how to call it: https://ethereum.routescan.io',
"This, we're already doing.\n" +
'\n' +
'But we call it, the Full-Index Ecosystem Explorer.',
'. By subdomains do you mean more specific namespaces, e.g. Farcaster fnames being name.farcaster.eth?\n' +
'\n' +
'My guess is if the root .eth namespace is available it will command higher status and value.\n' +
'\n' +
'https://x.com/0xfoobar/status/1687209523239604230',
'what are the best examples of DAOs with independent core working groups/multisigs?',
'Anyone here use Rabby?\n' +
'\n' +
'https://x.com/0xfoobar/status/1687474090150416385',
'Who needs stablecoins when we have ETH',
'782,672 active + pending validators! 🤯 \n' +
'\n' +
"(also... I haven't proposed a block for a few months)"
]
```
# Get account profile
Source: https://docs.neynar.com/farcaster/developers/guides/querying/fetch-profile
**Pre-requisites** - Read only access to a Snapchain instance
To fetch profile details, use the `userDataByFid` endpoint:
```bash theme={"system"}
$ curl http://localhost:3381/v1/userDataByFid\?fid\=1 | jq ".messages[].data.userDataBody"
{
"type": "USER_DATA_TYPE_PFP",
"value": "https://i.imgur.com/I2rEbPF.png"
}
{
"type": "USER_DATA_TYPE_BIO",
"value": "A sufficiently decentralized social network. farcaster.xyz"
}
{
"type": "USER_DATA_TYPE_DISPLAY",
"value": "Farcaster"
}
{
"type": "USER_DATA_TYPE_USERNAME",
"value": "farcaster"
}
```
See the [http api reference](https://snapchain.farcaster.xyz/reference/httpapi/userdata) for more details.
If you have the [hub-monorepo](https://github.com/farcasterxyz/hub-monorepo) installed from source, you can use the built in `console`. This will use the grpc APIs
```bash theme={"system"}
# Ensure you are in the hubble sub directory
$ cd apps/hubble
# Remove `--insecure` if the host is using TLS
$ yarn console --insecure -s localhost:3383
> res = await rpcClient.getUserDataByFid({fid: 1})
Ok {
value: {
messages: [ [Object], [Object], [Object], [Object] ],
nextPageToken:
}
}
> res.value.messages.map(m => m.data.userDataBody)
[
{ type: 1, value: 'https://i.imgur.com/I2rEbPF.png' },
{
type: 3,
value: 'A sufficiently decentralized social network. farcaster.xyz'
},
{ type: 2, value: 'Farcaster' },
{ type: 6, value: 'farcaster' }
]
```
For more details on the GRPC API, see the [grpc api reference](https://snapchain.farcaster.xyz/reference/grpcapi/grpcapi).
# Create casts
Source: https://docs.neynar.com/farcaster/developers/guides/writing/casts
Creating simple casts is covered in the [messages](/farcaster/developers/guides/writing/messages) tutorial. This tutorial covers advanced topics like mentions, embeds, emojis, replies and channels.
## Setup
* See the setup instructions in [creating messages](/farcaster/developers/guides/writing/messages)
## Mentions
Users can be tagged in a cast with an @username mention (e.g. "Hello @bob!") which causes clients to send notifications.
A mention is not included in the text of the cast. The target fid and the position of the mention in the text are specified in the `mentions` and `mentionPositions` array, and are populated into the cast by clients at render-time.
```typescript theme={"system"}
/**
* "@dwr and @v are big fans of @farcaster"
*/
const castWithMentions = await makeCastAdd(
{
text: ' and are big fans of ',
embeds: [],
embedsDeprecated: [],
mentions: [3, 2, 1],
mentionsPositions: [0, 5, 22], // The position in bytes (not characters)
},
dataOptions,
ed25519Signer
);
```
## Embeds
URLs can be embedded in the cast which instructs clients to render a preview.
A cast can have up to 2 embeds which can each be up to 256 bytes long. No other validation is performed on embeds.
```typescript theme={"system"}
/**
* A cast with a mention and an attachment
*
* "Hey @dwr, check this out!"
*/
const castWithMentionsAndAttachment = await makeCastAdd(
{
text: 'Hey, check this out!',
embeds: [{ url: 'https://farcaster.xyz' }],
embedsDeprecated: [],
mentions: [3],
mentionsPositions: [4],
},
dataOptions,
ed25519Signer
);
```
## Emoji
Emojis can be included directly in the text of a cast and be rendered by clients.
Since an emoji character often takes up multiple bytes but renders as a single character, it has an impact on how mention positions and cast length should be calculated.
```typescript theme={"system"}
/**
* A cast with emojis and mentions
*
* "🤓@farcaster can mention immediately after emoji"
*/
const castWithEmojiAndMentions = await makeCastAdd(
{
text: '🤓 can mention immediately after emoji',
embeds: [],
embedsDeprecated: [],
mentions: [1],
mentionsPositions: [4],
},
dataOptions,
ed25519Signer
);
```
## Reply
A cast can be a reply to another cast, URL or NFT. A reply to another cast is treated as a thread, while a reply to a URL or NFT can be treated as a comment or a [channel](#channels).
The optional parentUrl property can be set to a URL or to an fid-hash value of the cast being replied to, as shown in th example below. See [FIP-2](https://github.com/farcasterxyz/protocol/discussions/71) for more details on reply types.
```typescript theme={"system"}
/**
* A cast that replies to a URL
*
* "I think this is a great protocol 🚀"
*/
const castReplyingToAUrl = await makeCastAdd(
{
text: 'I think this is a great protocol 🚀',
embeds: [],
embedsDeprecated: [],
mentions: [],
mentionsPositions: [],
parentUrl: 'https://www.farcaster.xyz/',
},
dataOptions,
ed25519Signer
);
```
## Channels
A cast can be sent into a channel, which instructs clients that it is related to a specific topic. Clients may choose to use this metadata in different ways, like grouping channel casts together or recommending them to certain users.
A channel is simply a `parentURL` that is either a URL or NFT, which all clients recognize as a channel by loose consensus. There is no protocol level agreement on the list of channels yet, but the `casts` table in the replicator database can be used to get a list of commonly used channels.
```sql theme={"system"}
/* Query for a list of channels */
select parent_url,
count(*) as count
from casts
where parent_url is not null
group by parent_url
order by count desc;
```
```typescript theme={"system"}
// Cast into the Farcaster channel
const channelCast = await makeCastAdd(
{
text: 'I love farcaster',
embeds: [],
embedsDeprecated: [],
mentions: [],
mentionsPositions: [],
parentUrl: 'https://www.farcaster.xyz/', // This is illustrative, and is not an actual channel URL
},
dataOptions,
ed25519Signer
);
```
# Create messages
Source: https://docs.neynar.com/farcaster/developers/guides/writing/messages
A message represents an action taken by a user (e.g. alice says "hello world")
There are many types of messages, and this tutorial will walk you through the most common ones. Other tutorials will cover the more advanced message types.
## Setup
You will need:
* Write access to a Snapchain node
* Private key of a signer registered to an fid
* `hub-nodejs` and helper functions imported and shown below
```ts theme={"system"}
import {
makeCastAdd,
makeCastRemove,
makeLinkAdd,
makeLinkRemove,
makeReactionAdd,
makeReactionRemove,
makeUserDataAdd,
NobleEd25519Signer,
FarcasterNetwork,
} from '@farcaster/hub-nodejs';
import { Hex } from 'viem';
const ACCOUNT_PRIVATE_KEY: Hex = '0x...'; // Your account key's private key
const FID = -1; // Your fid
const FC_NETWORK = FarcasterNetwork.MAINNET;
const ed25519Signer = new NobleEd25519Signer(ACCOUNT_PRIVATE_KEY);
const dataOptions = {
fid: FID,
network: FC_NETWORK,
};
```
## Casts
Casts are public messages created by a user.
A cast is created by issuing a CastAdd message with the text of the cast and optional embeds, mentions, and emoji. The example below shows the creation of a simple cast.
```typescript theme={"system"}
const cast = await makeCastAdd(
{
text: 'This is a cast!', // Text can be up to 320 bytes long
embeds: [],
embedsDeprecated: [],
mentions: [],
mentionsPositions: [],
},
dataOptions,
ed25519Signer
);
```
A cast can be removed by issuing a CastRemove message with the hash of the CastAdd message and a later timestamp.
```typescript theme={"system"}
const castRemove = await makeCastRemove(
{
targetHash: cast._unsafeUnwrap().hash,
},
dataOptions,
ed25519Signer
);
```
To create casts with embeds, mentions, channels emoji, see the [casts](/farcaster/developers/guides/writing/casts) tutorial.
## Reactions
Reactions are strongly typed relationships between a user and a cast (e.g. a like).
A user "likes" a cast by producing a ReactionAdd message with type set to `like` and the target set to the hash of the cast and the fid of its author.
```typescript theme={"system"}
const reactionAdd = await makeReactionAdd(
{
type: ReactionType.LIKE,
targetCastId: { fid: createdCast.data.fid, hash: createdCast.hash },
},
dataOptions,
ed25519Signer
);
```
The like can be negated by broadcasting a ReactionRemove message with the information and a later timestamp.
```typescript theme={"system"}
const reactionRemove = await makeReactionRemove(
{
type: ReactionType.LIKE,
targetCastId: { fid: createdCast.data.fid, hash: createdCast.hash },
},
dataOptions, // Timestamp provided must be higher
ed25519Signer
);
```
A user can "recast" with a very similar process.
```typescript theme={"system"}
const recastAdd = await makeReactionAdd(
{
type: ReactionType.RECAST,
targetCastId: { fid: createdCast.data.fid, hash: createdCast.hash },
},
dataOptions,
ed25519Signer
);
const recastRemove = await makeReactionRemove(
{
type: ReactionType.RECAST,
targetCastId: { fid: createdCast.data.fid, hash: createdCast.hash },
},
dataOptions,
ed25519Signer
);
```
## User Data
UserData is a strongly typed set of messages that represent metadata about a user (e.g. bio, profile picture).
A `UserData` message has a type and a string value which can be set. The example below shows a user updating their bio.
```typescript theme={"system"}
// Update user bio. Other fields are similar, just change the type. Value is always a string.
const bioUpdate = await makeUserDataAdd(
{
type: UserDataType.BIO,
value: 'new bio',
},
dataOptions,
ed25519Signer
);
```
## Links
Links are loosely typed relationships between users (e.g. alice follows bob).
A user creates a Link by issuing a LinkAdd message with a string type and the target user's fid. The most commonly supported link on all clients is 'follow'.
```typescript theme={"system"}
const follow = await makeLinkAdd(
{
type: 'follow',
targetFid: 1,
},
dataOptions,
ed25519Signer
);
const unfollow = await makeLinkRemove(
{
type: 'follow',
targetFid: 1,
},
dataOptions,
ed25519Signer
);
```
# Submit data to the hub
Source: https://docs.neynar.com/farcaster/developers/guides/writing/submit-messages
**Pre-requisites**
* Write access to a Snapchain node
* Private key of an account key registered to an fid
* Local typescript development environment
To see a full example of how to submit different kinds of messages to the hub,
see [here](https://github.com/farcasterxyz/hub-monorepo/tree/main/packages/hub-nodejs/examples/write-data)
# Create an address verification
Source: https://docs.neynar.com/farcaster/developers/guides/writing/verify-address
**Pre-requisites**
* Write access to a Snapchain instance
* Private key of an account key registered to an fid
* An Ethereum provider URL for OP Mainnet (e.g. via [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/) or [QuickNode](https://www.quicknode.com/)).
To create a [Verification](https://snapchain.farcaster.xyz/reference/datatypes/messages#6-verification) proving ownership of an external Ethereum address, you can use an `Eip712Signer` to sign a verification message, and the `makeVerificationAddEthAddress` function to construct a message to send to a Hub.
First, set up clients and account keys:
```ts theme={"system"}
import {
NobleEd25519Signer,
ViemLocalEip712Signer,
FarcasterNetwork,
makeVerificationAddEthAddress,
} from '@farcaster/hub-nodejs';
import { createPublicClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { optimism } from 'viem/chains';
const publicClient = createPublicClient({
chain: optimism,
transport: http(),
});
const alice = privateKeyToAccount('0xab..12');
const eip712Signer = new ViemLocalEip712Signer(alice);
const ed25519Signer = new NobleEd25519Signer('0xcd..34');
```
Then create a verification signature using an `Eip712Signer`. You'll need to query the latest blockhash on OP mainnet and include it in the signed message.
```ts theme={"system"}
const latestBlock = await publicClient.getBlock();
const fid = 1n;
const network = FarcasterNetwork.MAINNET;
const address = alice.address;
const blockHash = latestBlock.hash;
const ethSignature = await eip712Signer.signVerificationEthAddressClaim({
fid,
address,
network,
blockHash,
});
```
Finally, use `makeVerificationAddEthAddress` to construct a [`Verification`](https://snapchain.farcaster.xyz/reference/datatypes/messages#6-verification) message you can send to a Hub.
```ts theme={"system"}
if (ethSignature.isOk()) {
const message = await makeVerificationAddEthAddress(
{
address,
blockHash,
ethSignature: ethSignature._unsafeUnwrap(),
},
{ fid, network },
ed25519Signer
);
}
```
### Full code example
```ts [@farcaster/hub-nodejs] theme={"system"}
import {
NobleEd25519Signer,
ViemLocalEip712Signer,
FarcasterNetwork,
makeVerificationAddEthAddress,
} from '@farcaster/hub-nodejs';
import { createPublicClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { optimism } from 'viem/chains';
const publicClient = createPublicClient({
chain: optimism,
transport: http(),
});
const alice = privateKeyToAccount('0xab..12');
const eip712Signer = new ViemLocalEip712Signer(alice);
const ed25519Signer = new NobleEd25519Signer('0xcd..34');
const latestBlock = await publicClient.getBlock();
const fid = 1n;
const network = FarcasterNetwork.MAINNET;
const address = alice.address;
const blockHash = latestBlock.hash;
const ethSignature = await eip712Signer.signVerificationEthAddressClaim({
fid,
address,
network,
blockHash,
});
if (ethSignature.isOk()) {
const message = await makeVerificationAddEthAddress(
{
address,
blockHash,
ethSignature: ethSignature._unsafeUnwrap(),
},
{ fid, network },
ed25519Signer
);
}
```
# Getting Started
Source: https://docs.neynar.com/farcaster/developers/index
**Join the conversation** - Ask questions and hang out with other Farcaster developers in the [/fc-devs](https://farcaster.xyz/~/channel/fc-devs) channel on Farcaster.
## Create mini apps
Learn how to build mini apps (previously called Frames) that run inside a Farcaster feed.
* [Introduction](https://miniapps.farcaster.xyz/)- understand what a mini app is and how it works
* [Getting Started](https://miniapps.farcaster.xyz/docs/getting-started)- Build your first mini app
## Sign in with Farcaster
Make it easy for users to sign in to your app with their Farcaster account.
* [Examples](/farcaster/auth-kit/examples) - see Sign in with Farcaster (SIWF) in action
* [AuthKit](/farcaster/auth-kit/installation) - a React toolkit to integrate SIWF
* [FIP-11](https://github.com/farcasterxyz/protocol/discussions/110) - the formal standard for SIWF
## Analyze Farcaster data
Sync the Farcaster network to a local machine so you can run queries on the data.
* [Run a Snapchain node](https://snapchain.farcaster.xyz/getting-started#getting-started) - get realtime access to Farcaster data
* [Write your first Snapchain query](https://snapchain.farcaster.xyz/getting-started#query-a-node) - get an account's casts from a Snapchain node
* [Set up the replicator](https://snapchain.farcaster.xyz/guides/syncing-to-db) - sync a Snapchain node to a postgres database to run advanced queries
## Write to Farcaster
* [Hello World](/farcaster/developers/guides/basics/hello-world) - programmatically create an account and publish a cast
# Mini Apps
Source: https://docs.neynar.com/farcaster/developers/miniapps-redirect
Mini Apps documentation has its own dedicated section.
[Go to Mini Apps Documentation →](/miniapps/overview)
# Resources
Source: https://docs.neynar.com/farcaster/developers/resources
An opinionated list of the best libraries and tools for Farcaster developers. A more comprehensive list of resources can be found in a16z's [awesome-farcaster](https://github.com/a16z/awesome-farcaster) repo.
Resources may be added via pull request. They must be purpose built for Farcaster only, actively maintained and adhere to the ethos and specification of the protocol.
Resources are often from third parties and are not reviewed. Use them at your own risk.
## Libraries
### Mini Apps
* [@farcaster/mini-app](https://miniapps.farcaster.xyz/docs/getting-started)- CLI tool for building mini apps.
* [@neynar/create-farcaster-mini-app](https://www.npmjs.com/package/@neynar/create-farcaster-mini-app) - A Mini App quickstart npx script from Neynar.
### Legacy Frames
* [@frame-js/frames](https://framesjs.org/) - next.js template for building and debugging frames.
* [@coinbase/onchainkit](https://github.com/coinbase/onchainkit) - react toolkit to create frames.
* [@frog](https://frog.fm) - framework for frames.
### Apps
* [farcaster kit](https://www.farcasterkit.com/) - react hooks for building Farcaster apps.
### Snapchain
* [@farcaster/hub-nodejs](https://www.npmjs.com/package/@farcaster/hub-nodejs) - lightweight, fast Typescript interface for Farcaster clients.
### Onchain
* [farcaster-solidity](https://github.com/pavlovdog/farcaster-solidity/) - libraries for verifying and parsing Farcaster messages onchain
## Dashboards
* [Farcaster Hub Map](https://farcaster.spindl.xyz/) - geographical map of Farcaster hubs
* [Dune: Farcaster](https://dune.com/pixelhack/farcaster) - @pixelhack's dashboard for network stats
* [Dune: Farcaster User Onchain Activities](https://dune.com/yesyes/farcaster-users-onchain-activities) - dashboard of farcaster user's onchain activity
## Learning
* [dTech Farcaster Tutorials](https://dtech.vision/farcaster)
## Open Source Examples
* [quikcast](https://github.com/farcasterxyz/quikcast) - an end-to-end connected app implementation for Farcaster
* [fc-polls](https://github.com/farcasterxyz/fc-polls) - a simple polling app built using frames
## Services
* [Neynar](https://neynar.com/) - infrastructure and services for building farcaster apps.
* [dTech](https://dtech.vision) - Farcaster Development and Consulting Agency
# Sign In with Farcaster
Source: https://docs.neynar.com/farcaster/developers/siwf/index
Sign In with Farcaster (SIWF) is a way for users to sign into any app using their
Farcaster identity.
When a user signs into your application with Farcaster you'll be able to use
public social data like their social graph and profile information to provide
a streamlined onboarding experience and social-powered features.
### How does it work?
1. Show a "Sign in with Farcaster" button to the user.
2. Wait for the user to click, scan a QR code and approve the request in Farcaster.
3. Receive and verify a signature from Farcaster.
4. Show the logged in user's profile picture and username.
## Next Steps
* Integrate SIWF to your app today with [AuthKit](/farcaster/auth-kit/).
* Read about the underlying standard in [FIP-11: Sign In With
Farcaster](https://github.com/farcasterxyz/protocol/discussions/110).
# Links
Source: https://docs.neynar.com/farcaster/developers/utilities
* [https://github.com/a16z/awesome-farcaster](https://github.com/a16z/awesome-farcaster)
# Contracts
Source: https://docs.neynar.com/farcaster/learn/architecture/contracts
A Farcaster account is managed and secured onchain using the Farcaster contracts. This section provides a high level overview and avoids some implementation details. For the full picture, see the [contracts repository](https://github.com/farcasterxyz/contracts/).
There are three main contracts deployed on OP Mainnet:
* **Id Registry** - creates new accounts
* **Storage Registry** - rents storage to accounts
* **Key Registry** - adds and removes app keys from accounts
The contracts are deployed at the following addresses:
| Contract | Address |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| IdRegistry | [0x00000000fc6c5f01fc30151999387bb99a9f489b](https://optimistic.etherscan.io/address/0x00000000fc6c5f01fc30151999387bb99a9f489b) |
| StorageRegistry | [0x00000000fcce7f938e7ae6d3c335bd6a1a7c593d](https://optimistic.etherscan.io/address/0x00000000fcce7f938e7ae6d3c335bd6a1a7c593d) |
| KeyRegistry | [0x00000000fc1237824fb747abde0ff18990e59b7e](https://optimistic.etherscan.io/address/0x00000000fc1237824fb747abde0ff18990e59b7e) |
### Id Registry
The IdRegistry lets users register, transfer and recover Farcaster accounts. An account is identified by a unique number (the fid) which is assigned to an Ethereum address on registration. An Ethereum address may only own one account at a time, though it may transfer it freely to other accounts. It may also specify a recovery address which can transfer the account at any time.
### Storage Registry
The Storage Registry lets accounts rent [storage](/farcaster/learn/what-is-farcaster/messages#storage) by making a payment in ETH. The storage prices are set by admins in USD and converted to ETH using a Chainlink oracle. The price increases or decreases based on supply and demand.
### Key Registry
The Key Registry lets accounts issue keys to apps, so that they can publish messages on their behalf. Keys can be added or removed at any time. To add a key, an account must submit the public key of an EdDSA key pair along with a requestor signature. The requestor can be the account itself or an app that wants to operate on its behalf.
# ENS Names
Source: https://docs.neynar.com/farcaster/learn/architecture/ens-names
Farcaster uses ENS names as human readable identifiers for accounts. Two kinds of ENS names are supported:
* **Offchain ENS names**: free and controlled by farcaster. (e.g. @alice)
* **Onchain ENS names**: costs money and is controlled by your wallet. (e.g. @alice.eth)
ENS names can only be used on Farcaster if they are 16 characters or fewer and contain only lowercase letters, numbers, and hyphens.
## Onchain ENS Names
Users can use onchain ENS names like `@alice.eth` on Farcaster.
Onchain ENS names are issued by ENS, end in .eth and must be registered on the Ethereum L1 blockchain. Anyone can register an ENS name by using the [ENS app](https://app.ens.domains/).
Users must pay a fee to register an onchain ENS name, but once registered, it is controlled by the user and cannot be revoked.
## Offchain ENS Names (Fnames)
Users can use offchain ENS names like `@alice` on Farcaster.
Offchain ENS names, also called Farcaster Names or Fnames, are compliant with ENS but registered offchain. Fnames are free but are subject to a usage policy to prevent squatting and impersonation. They are also subject to the following requirements:
1. An account can only have one Fname at a time.
2. An account can change its Fname once every 28 days.
### Usage Policy
Registering an Fname is free but subject to the following policy:
1. Names connected to public figures or entities may be reclaimed (e.g. @google).
2. Names that haven't been used for 60+ days may be reclaimed.
3. Names that are registered for the sole purpose of resale may be reclaimed.
Decisions are made by the Farcaster team and often require human judgment. Users who want a name that is fully under their control should use an onchain ENS name.
### Registry
Fnames are issued as offchain names under the subdomain `fcast.id`.
Bob can register the offchain ENS name `bob.fcast.id` and use it on any Farcaster app with the shorthand `@bob`. The name can be registered by making a signed request to the Fname Registry server. See the [FName API reference](/farcaster/reference/fname/api) for more details on how to query and create Fnames.
To learn more about how Fnames work, see [ENSIP-16](https://docs.ens.domains/ens-improvement-proposals/ensip-16-offchain-metadata)
and [ERC-3668](https://eips.ethereum.org/EIPS/eip-3668).
# Architecture
Source: https://docs.neynar.com/farcaster/learn/architecture/overview
Farcaster has a hybrid architecture that stores identity onchain and data offchain.
## Onchain
Farcaster's onchain systems are implemented as [contracts on OP Mainnet](/farcaster/learn/architecture/contracts). Actions are performed onchain only when security and consistency are critical. Use of onchain actions is kept at a minimum to reduce costs and improve performance.
Only a handful of actions are performed onchain, including:
* Creating an [account](/farcaster/learn/what-is-farcaster/accounts).
* Paying rent to [store data](/farcaster/learn/what-is-farcaster/messages#storage).
* Adding account keys for [connected apps](/farcaster/learn/what-is-farcaster/apps#connected-apps).
## Offchain
Farcaster's offchain system is a peer-to-peer network of servers called [Snapchain](https://snapchain.farcaster.xyz/) which store user data. The majority of user actions are performed offchain. These include:
* Posting a new public message.
* Following another user.
* Reacting to a post.
* Updating your profile picture.
Actions are performed offchain when performance and cost are critical. Use of offchain actions is typically preferred when consistency isn't a strict requirement. Offchain systems achieve security by relying on signatures from onchain systems.
# FIPs
Source: https://docs.neynar.com/farcaster/learn/contributing/fips
An FIP, or Farcaster Improvement Proposal, is a process for building consensus around protocol changes. FIP's are
inspired by [Ethereum's EIPs](https://eips.ethereum.org/EIPS/eip-1)
and [Python's PEPs](https://peps.python.org/pep-0001/). Anyone can write an FIP to propose a change to:
1. A process, like the protocol's release schedule
2. A standard, like URIs for onchain assets
3. An implementation, like adding a new protocol feature
Read more about FIP's
in [FIP-0: A proposal for making proposals](https://github.com/farcasterxyz/protocol/discussions/82). Proposals are made and ratified on
the [discussions board](https://github.com/farcasterxyz/protocol/discussions/).
A list of finalized FIPs can be found [here](https://github.com/farcasterxyz/protocol/discussions/categories/fip-stage-4-finalized).
# Governance
Source: https://docs.neynar.com/farcaster/learn/contributing/governance
Farcaster embraces [rough consensus and running code](https://en.wikipedia.org/wiki/Rough_consensus) as its governance model. Changes happen when someone makes a proposal, gets buy-in and ships running code. Depending on the change, different groups need to be convinced:
1. **Protocol devs**, who choose to merge changes into hubs and contracts.
2. **Hub runners**, who choose to deploy those changes to their hubs.
3. **App devs**, who choose the hubs they read from.
4. **Users**, who choose the apps they want to use.
Consensus emerges from people accepting new code or rejecting it. Farcaster will not have a binding voting process,
official roles or veto power for anyone. Having too much structure ossifies systems, encourages politicking and slows
progress. Rough consensus biases to action, encourages diversity of viewpoints, and maximizes decentralization, which is
essential for a long-lived protocol. Most changes happen through the [FIP](/farcaster/learn/contributing/fips) process.
# Contributing
Source: https://docs.neynar.com/farcaster/learn/contributing/overview
Farcaster welcomes contributions of all sizes from the community. The protocol owes thanks to over 100 contributors who
have helped us so far.
To get involved, try looking up open issues in one of the repos below or join a dev call.
## Repositories
| Repository | Description |
| ---------------------------------------------------------------- | ------------------------------------------- |
| [Protocol](https://github.com/farcasterxyz/protocol) | Specification of the protocol |
| [Contracts](https://github.com/farcasterxyz/contracts) | The canonical Farcaster contracts |
| [Snapchain](https://github.com/farcasterxyz/snapchain) | A Farcaster node written in Rust |
| [FName Registry](https://github.com/farcasterxyz/fname-registry) | The canonical server to register fnames |
| [Docs](https://github.com/farcasterxyz/docs) | Documentation for all the above (this site) |
## Documentation
This site serves as the central hub for documentation on the protocol. If you have feedback, please open an issue or
create a pull request at [farcasterxyz/docs](https://github.com/farcasterxyz/docs)
## Dev Calls
We host a bi-weekly developer call to discuss upcoming changes to the protocol. The call is open to anyone and is a
great way to get involved.
* [Calendar Invite](https://calendar.google.com/calendar/u/0?cid=NjA5ZWM4Y2IwMmZiMWM2ZDYyMTkzNWM1YWNkZTRlNWExN2YxOWQ2NDU3NTA3MjQwMTk3YmJlZGFjYTQ3MjZlOEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t) -
Calendar invite to join upcoming calls.
* [Recordings](https://www.youtube.com/watch?v=lmGXWP5m1_Y\&list=PL0eq1PLf6eUeZnPtyKMS6uN9I5iRIlnvq) - watch recordings
of previous calls.
# Getting Started
Source: https://docs.neynar.com/farcaster/learn/index
Farcaster is a [sufficiently decentralized](https://www.varunsrinivasan.com/2022/01/11/sufficient-decentralization-for-social-networks) social network built on Ethereum.
It is a public social network similar to X and Reddit. Users can create profiles, post "casts" and follow others. They own their accounts and relationships with other users and are free to move between different apps.
**Join Farcaster** - If you're not on Farcaster, get started by [creating your account](https://www.farcaster.xyz/) with the official Farcaster client.
## Learn
If you want to learn more, get started by diving into these concepts:
* [Farcaster 101](https://www.youtube.com/playlist?list=PL0eq1PLf6eUdm35v_840EGLXkVJDhxhcF) - a walkthrough of the Farcaster protocol in short, 5 minute videos.
* [Core Concepts](/farcaster/learn/what-is-farcaster/accounts) - learn about the building blocks of Farcaster, starting with accounts.
* [Architecture](/farcaster/learn/architecture/overview) - a breakdown of Farcaster's onchain and offchain systems.
## Tutorials
* [Build your first mini app](https://miniapps.farcaster.xyz/docs/getting-started) - Make mini-apps that run inside Farcaster.
* [Sign in with Farcaster](/farcaster/auth-kit/installation) - Let users login to your app with their Farcaster account.
* [Write your first app](/farcaster/developers/index) - Publish a "Hello World" message to Farcaster.
Find more how-tos, guides, and tutorials like this in the [developers](/farcaster/developers/) section.
## Documentation
* [Farcaster Spec](https://github.com/farcasterxyz/protocol) - Specifications for Farcaster, including its contracts and nodes.
* [Mini Apps Spec](https://miniapps.farcaster.xyz/docs/specification) - Specifications for writing and rendering mini apps in Farcaster apps.
* [APIs](/farcaster/reference/) - Docs for API's and ABI's for onchain and offchain systems.
## Contributing
To learn about how to contribute to the protocol, including this documentation site, check out
the [Contributing](/farcaster/learn/contributing/overview) section.
# Accounts
Source: https://docs.neynar.com/farcaster/learn/what-is-farcaster/accounts
A Farcaster account lets you set up a username, profile picture and publish short text messages known as casts. Any Ethereum address can register a Farcaster account by making an onchain transaction.
## Creating an account
A Farcaster account is created by calling the IdGateway contract. It will assign a new Farcaster ID or fid to your Ethereum address.
You'll need to get a username, rent storage and add a key before you can use your account. These steps require many signatures and onchain transactions and can be tedious with a regular Ethereum wallet.
We recommend starting with the [official client](https://www.farcaster.xyz/), developed by the Farcaster team which will handle the entire flow for you. It also uses a separate Ethereum account to sign transactions, so you can keep your main Ethereum account secure.
## Adding account keys
Accounts can issue keys which let apps write messages on their behalf. Users will typically issue a key to each Farcaster app they use.
Keys are managed by the KeyRegistry contract. To add a key, you'll need to submit the public key of an EdDSA key pair along with a requestor signature. The requestor can be the account itself or an app that wants to operate on its behalf.
## Recovering an account
Users often set up separate wallets for their social apps and it's easy to lose access. Farcaster lets any account specify a recovery address which can be used to recover the account. It can be configured when creating the account or anytime after.
Users can set the recovery address to trusted services like the official Farcaster client or they can manage it themselves using a regular Ethereum wallet.
## Resources
### Specifications
* [Contract Specifications](https://github.com/farcasterxyz/protocol/blob/main/docs/SPECIFICATION.md#1-smart-contracts) - The onchain contracts that manage Farcaster accounts, account keys and recovery addresses.
### APIs
* [IdRegistry](/farcaster/reference/contracts/reference/id-registry) - Lookup account data onchain.
* [IdGateway](/farcaster/reference/contracts/reference/id-gateway) - Create accounts onchain.
* [KeyRegistry](/farcaster/reference/contracts/reference/key-registry) - Lookup account key data onchain.
* [KeyGateway](/farcaster/reference/contracts/reference/key-gateway) - Create account keys onchain.
* [Get Farcaster Ids](https://snapchain.farcaster.xyz/reference/httpapi/fids) - Fetch a list of all registered account fids from a Snapchain node.
* [Get account keys](https://snapchain.farcaster.xyz/reference/httpapi/onchain#onchainsignersbyfid) - Fetch the account keys (signers) for an account from a Snapchain node.
### Tutorials
* [Create an account](/farcaster/developers/guides/accounts/create-account) - Create a new account on Farcaster.
* [Create an account key](/farcaster/developers/guides/accounts/create-account-key) - Create a new account key for your account.
* [Find account by username](/farcaster/developers/guides/accounts/find-by-name) - Find an account by its username.
* [Change custody address](/farcaster/developers/guides/accounts/change-custody) - Change the address that owns your account.
* [Change recovery address](/farcaster/developers/guides/accounts/change-recovery) - Change the address that recovers your account.
* [Find account key requestor](/farcaster/developers/guides/advanced/decode-key-metadata) - Find the app that the user granted an account key to.
* [Query signups from replicator](/farcaster/developers/guides/advanced/query-signups) - Query the number of signups from the replicator.
# Apps
Source: https://docs.neynar.com/farcaster/learn/what-is-farcaster/apps
Using Farcaster requires an Ethereum wallet to register your account and UI to browse the network. If you are new, we recommend starting with the official Farcaster client on [iOS](https://apps.apple.com/us/app/farcaster/id1600555445) or [Android](https://play.google.com/store/apps/details?id=com.farcaster.mobile\&hl=en_US\&gl=US)
There are two kinds of apps:
1. **Wallet App** - allows signing up, adding connected apps, posting and browsing messages.
2. **Connected App** - allows posting and browsing messages only.
## Wallet Apps
Users must install a wallet app to get started with Farcaster. They can take onchain and offchain actions like signing up, adding connected apps, posting messages and users.
A wallet app controls the Ethereum address that owns the account. It has control over the account and can take any action on your behalf, so only use a wallet app that you trust.
### Farcaster client
The Farcaster client is a wallet app developed by the Farcaster team. It has a web and mobile app, though signing up is only available on mobile.
* Download: [iOS](https://apps.apple.com/us/app/farcaster/id1600555445), [Android](https://play.google.com/store/apps/details?id=com.farcaster.mobile\&hl=en_US\&gl=US)
## Connected Apps
Connected apps can only be added once a user signs up with a wallet app. They can take offchain actions on Farcaster like writing casts, following accounts and browsing.
A connected app controls an app key granted by the wallet app. Users can add many connected apps to their account and remove them at any time. A malicious connected app cannot take control of your account and any actions it takes can be reversed by your wallet app.
Some popular connected apps include:
* [Supercast](https://supercast.xyz/)
* [Yup](https://yup.io/)
* [Farcord](https://farcord.com/)
**Connected apps are not reviewed by Farcaster, use them at your own risk**
## Resources
### Tools
* [Snapchain](https://snapchain.farcaster.xyz/) - a node for reading and writing messages.
* [Replicator](https://github.com/farcasterxyz/hub-monorepo/tree/main/apps/replicator) - a tool to sync a hub to a postgres database.
### Tutorials
* [Set up snapchain](https://snapchain.farcaster.xyz/guides/running-a-node) - run a snapchain node.
* [Set up replicator](https://snapchain.farcaster.xyz/guides/syncing-to-db) - sync a hub to postgres for easy querying.
* [Schema for replication](/farcaster/reference/replicator/schema) - schema for a replicator's postgres tables.
### Services
* [Neynar](https://neynar.com/) - infrastructure and services for building farcaster apps.
# Channels
Source: https://docs.neynar.com/farcaster/learn/what-is-farcaster/channels
A channel is a public space for your community to have conversations around a topic.
Creating a channel starts a new feed for your community. People can join, cast and find other interesting people. It sparks conversations that wouldn’t otherwise happen on the home feed.
**Experimental Feature** - Channels are being prototyped in the Farcaster client and not fully supported by the Farcaster protocol. They may be ported to the protocol in the future if the feature is deemed successful or they may be removed entirely.
## Hosting Channels
Anyone can create a channel host by paying a fee in the Farcaster client and choosing a channel name. The name must be under 16 characters and can only contain lowercase alphabets and numbers. A channel's creator is called a host and may invite other co-hosts to operate the channel. Hosts have special privileges like:
1. Defining “channel norms" which everyone must agree to when joining.
2. Pinning or hiding casts in a channel.
3. Blocking other users from casting in their channel.
4. Setting a channel picture, description and other metadata.
Channel metadata is not part of the protocol and stored in the Farcaster client while channels are in the experimental stage.
## Casting in Channels
Anyone can post into a channel by using the Farcaster client and selecting the channel when creating the cast. The client automatically sets the cast's `parentUrl` to `https://farcaster.xyz/~/channel/`. A cast is considered "in a channel" if it's parentUrl is the channel URI or another cast which is "in a channel".
Channel casts are part of the protocol and stored on hubs. Using a replicator, you can fetch all casts in a channel by filtering the `parentUrl` field for the channel's FIP-2 URL.
## Following Channels
Anyone can follow a channel just like a user. A user will see casts from a followed channel in their home feed when using the Farcaster client.
Channel follows are not part of the protocol and are stored in the Farcaster client while channels are in the experimental stage.
## Cast Visibility
If a user casts in a channel, Farcaster will:
1. Always send the casts to the home feeds of any user who follows the channel.
2. Usually send the casts to the home feeds of any user who follows the author.
The determination for (2) is made based on the user's preferences, channel contents and other social graph data. This algorithm is still being fine tuned and will be documented once it is stable.
## Usage Policy
The Farcaster client may remove your channel and will NOT refund your warps if:
1. Your profile or channel impersonates someone.
2. You squat a channel without using it.
3. You violate the Farcaster client's terms and conditions or app store rules.
## FAQ
**Why are channel hosts allowed to hide and ban? Isn’t this censorship?**
Channels are not free-for-all public spaces, they are owned and moderated by their creators. You are always free to start your own channel at any time with its own rules.
**Why is there a fee for creating channels?**
The fee discourages people from squatting short names and not using the channels.
**What's the benefit of creating a channel?**
Starting a channel also helps grow your audience:
1. The Farcaster client will send your followers a notification about your channel.
2. Your channel will be promoted to users who follow similar channels.
3. Users who follow your channel will see channel casts in their home feed.
## Resources
### APIs
* [Farcaster Client Channel APIs](/farcaster/reference/farcaster/api) - fetch a list of all known channels
# Messages
Source: https://docs.neynar.com/farcaster/learn/what-is-farcaster/messages
Farcaster accounts interact by signing and publishing messages. Alice can create a message that says "*Hello @bob*" and sign it with her key.
Messages are stored on a peer-to-peer network of nodes. A node in the Farcaster network is called a Hub, and each Hub stores a copy of the entire network. A user can publish a message to one Hub and it will propagate to the entire network in a few seconds. Farcaster's compact message format and eventually consistent model lets this architecture scale to millions of users.
An account can generate a [key](/farcaster/learn/what-is-farcaster/accounts#adding-account-keys) and give it to an app which can use it to sign messages. Users can use multiple apps with the same account, and each application can have its own key. Separating the signing keys from the ownership keys helps keep the account secure.
## Types
Accounts can publish five different kinds of messages to the network:
| Type | Description | Example |
| ------------- | --------------------------------------------- | ------------------------------ |
| Casts | Public messages that can be seen by anyone. | "Hello world!" |
| Reactions | A relationship between an account and a cast. | Alice liked Bob's cast. |
| Links | A relationship between two accounts. | Alice follows Bob. |
| Profile Data | Metadata about the account. | Profile picture, display name. |
| Verifications | A proof of ownership of something. | An Ethereum address. |
## Storage
An account must pay rent to keep their messages on the Farcaster network. Charging rent prevents users from spamming the network.
An account can rent a unit of storage by making an onchain transaction to the Storage Registry. A unit of storage costs \$7 today, lasts for one year and lets each account store a certain number of messages of each type. The limits for each type today are:
* 5000 Casts
* 2500 Reactions
* 2500 Links
* 50 Profile Data
* 50 Verifications
If an account exceeds its limit for a message type, the oldest message is pruned to make space for the new one. The user can keep using the network without paying for more storage and Hubs can keep the storage load under control. An account can always purchase more storage to increase its limits.
An account that lets its storage expire may lose all its messages. There is a 30-day grace period after a storage unit expires during which an account must renew or lose its messages.
The price and size of each storage unit is re-calculated periodically to balance growth and quality of the network. See [FIP-6](https://github.com/farcasterxyz/protocol/discussions/98)
for more details.
## Deletion
An account can delete messages at any time by publishing a corresponding delete message. The delete message will remove the contents of the original message, leaving a tombstone in its place. A deleted message will still count towards the account's storage limit until it expires by being pushed out by a newer message.
## Timestamps
Messages have timestamps which count seconds from the Farcaster Epoch, which began on `Jan 1, 2021 00:00:00 UTC`. Using a recent epoch makes timestamps and messages much smaller, which is important for the network.
Timestamps are unverified and can be backdated by users, similar to a blog post. They cannot be more than 15 minutes into the future, as the network will reject such messages.
## Resources
### Specifications
* [Messages](https://github.com/farcasterxyz/protocol/blob/main/docs/SPECIFICATION.md#2-message-specifications) - the atomic unit of change on Farcaster
* [CRDTs](https://github.com/farcasterxyz/protocol/blob/main/docs/SPECIFICATION.md#31-crdts) - rules for keeping messages in sync on the network
* [Storage Registry](https://github.com/farcasterxyz/protocol/blob/main/docs/SPECIFICATION.md#13-storage-registry) - contract to acquire storage units
### APIs
* [Get Casts](https://snapchain.farcaster.xyz/reference/httpapi/casts) - fetch an account's casts from a Snapchain node
* [Get Reactions](https://snapchain.farcaster.xyz/reference/httpapi/reactions) - fetch an account's reactions from a Snapchain node
* [Get Links](https://snapchain.farcaster.xyz/reference/httpapi/links) - fetch an account's links or follows from a Snapchain node
* [Get UserData](https://snapchain.farcaster.xyz/reference/httpapi/userdata) - fetch an account's profile data from a Snapchain node
* [Submit Message](https://snapchain.farcaster.xyz/reference/httpapi/message#submitmessage) - broadcast a message to the Snapchain network
* [Validate Message](https://snapchain.farcaster.xyz/reference/httpapi/message#validatemessage) - verify a message's authenticity with a Snapchain node
* [Storage Registry](/farcaster/reference/contracts/reference/storage-registry) - Acquire or check storage units for an account
### Tutorials
* [Get casts](/farcaster/developers/guides/querying/fetch-casts) - Get an account's casts from a Snapchain node.
* [Get profile](/farcaster/developers/guides/querying/fetch-profile) - Get an account's profile from a Snapchain node.
* [Create common message types](/farcaster/developers/guides/writing/messages) - Create casts, links, reactions and userdata.
* [Create casts with advanced features](/farcaster/developers/guides/writing/casts) - Create casts with embeds, emojis and mentions.
# Mini Apps
Source: https://docs.neynar.com/farcaster/learn/what-is-farcaster/mini-apps
Mini Apps are a standard for creating interactive and authenticated experiences on Farcaster, embeddable in any Farcaster client.
Mini Apps allow developers to build interactive applications that run directly in a Farcaster social feed. For a comprehensive introduction, visit the [official Mini Apps documentation](https://miniapps.farcaster.xyz/).
## Specification
For detailed technical information, refer to the [formal Mini Apps Specification](https://miniapps.farcaster.xyz/docs/specification).
## Rename from Frames v2
In early 2025, Frames were renamed to Mini Apps to better reflect their evolving capabilities and scope. The new Mini Apps standard builds upon the foundation established by Frames, offering enhanced functionality and a more intuitive developer experience.
Key points about the transition:
* All existing Frames documentation and resources have been migrated to the Mini Apps ecosystem
* Frames v1 have been deprecated and will be supported only until the end of March 2025
* Developers are strongly encouraged to migrate existing Frame projects to the Mini Apps standard
* The Mini Apps standard maintains backward compatibility while introducing new features
If you're familiar with the previous Frames implementation, you'll find that Mini Apps preserve the core concepts while expanding the possibilities for creating rich, interactive experiences within the Farcaster ecosystem.
## Resources
Explore these resources to enhance your Mini Apps development:
* [Mini Apps Examples](https://miniapps.farcaster.xyz/docs/examples) - A collection of sample Mini Apps demonstrating various features and use cases
* Join the Farcaster developer community in the [/fc-devs](https://farcaster.xyz/~/channel/fc-devs) channel on Farcaster
# Usernames
Source: https://docs.neynar.com/farcaster/learn/what-is-farcaster/usernames
A Farcaster account needs a username so it can be found and mentioned by other users. Farcaster uses the [Ethereum Name Service](https://ens.domains/) to manage usernames.
ENS usernames are owned by Ethereum addresses, just like Farcaster accounts. The difference is that an address can own multiple ENS names, so the Farcaster account must specify the name it wishes to use. ENS names can only be used on Farcaster if they are 16 characters or fewer and contain only lowercase letters, numbers and hyphens.
## Changing usernames
A Farcaster account can change between different usernames at any time. Changing names does not affect your history or your followers.
It's safe to change your name a few times a year. But changing your name more often may cause users or apps to lose trust in your account. If you want to change a public indicator, consider changing your display name instead.
## Offchain vs Onchain Names
An account can choose between two kinds of usernames:
* **Offchain ENS Names**: free and controlled by farcaster. (e.g. @alice)
* **Onchain ENS Names**: costs money and controlled by your wallet. (e.g. @alice.eth)
Choose an offchain ENS name if you want to get started quickly and don't have an onchain ENS name. An account can always upgrade to an onchain name later. It's recommended to use an app like the official Farcaster client to set this up for you.
### Offchain ENS Names
* Offchain ENS names, also called fnames, are free and issued by Farcaster.
* Any Ethereum account can get one unique fname by calling the [Fname Registry](/farcaster/learn/architecture/ens-names).
* Fnames are free but they can be revoked by Farcaster at any time.
### Onchain ENS fnames
* Onchain ENS names, also called .eth names, are onchain and issued by ENS.
* Any Ethereum account can get an ENS by calling the [ENS Registry](https://docs.ens.domains/dapp-developer-guide/the-ens-registry).
* Names are not free but they cannot be revoked by Farcaster.
## Resources
### Specifications
* [Farcaster Name](https://github.com/farcasterxyz/protocol/blob/main/docs/SPECIFICATION.md#5-fname-specifications) - An ENSIP-10 offchain ENS name usable within Farcaster.
* [UserData: Username](https://github.com/farcasterxyz/protocol/blob/main/docs/SPECIFICATION.md#23-user-data) - Sets a valid Username Proof as the current username.
* [Username Proof](https://github.com/farcasterxyz/protocol/blob/main/docs/SPECIFICATION.md#17-username-proof) - Proves ownership of an onchain or offchain username.
* [Verifications](https://github.com/farcasterxyz/protocol/blob/main/docs/SPECIFICATION.md#25-verifications) - Proves ownership of an address, required for onchain Username Proofs.
### APIs
* [UserData API](https://snapchain.farcaster.xyz/reference/httpapi/userdata) - Fetch the UserData for a user's current username.
* [Username Proofs API](https://snapchain.farcaster.xyz/reference/httpapi/usernameproof) - Fetch a user's Username Proofs from a Snapchain node.
* [Verification Proofs API](https://snapchain.farcaster.xyz/reference/httpapi/verification) - Fetch a user's Verifications from a Snapchain node.
* [Fname Registry API](/farcaster/reference/fname/api) - Register and track fname ownership programmatically.
### Tutorials
* [Get UserData](/farcaster/developers/guides/querying/fetch-profile) - Get UserData messages from an account.
* [Create UserData](/farcaster/developers/guides/writing/messages#user-data) - Create a UserData message to select a valid username.
* [Verify an Address](/farcaster/developers/guides/writing/verify-address) - Verify ownership of an Ethereum account.
* [Find account by username](/farcaster/developers/guides/accounts/find-by-name) - Find an account by its username.
* [Change farcaster name](/farcaster/developers/guides/accounts/change-fname) - Change a farcaster username.
# Overview
Source: https://docs.neynar.com/farcaster/overview
Permissionlessly build and distribute social apps on Farcaster
Permissionlessly build and distribute social apps.
### Build a Mini App
Learn how to build Mini Apps (previously known as Frames v2) that run inside a Farcaster feed.
* [Introduction to Mini Apps](https://miniapps.farcaster.xyz/) - Understand what a mini app is and how it works.
* [Build your first Mini App](https://miniapps.farcaster.xyz/docs/getting-started) - Make mini apps that run inside Farcaster.
### Explore Sign In with Farcaster
Allow users to Sign In with Farcaster and leverage social data in your app.
* [Introduction](/farcaster/developers/siwf) - Learn about Sign In with Farcaster.
* [Add SIWF using AuthKit](/farcaster/auth-kit/installation) - a React toolkit to add SIWF to your app.
* [Examples](/farcaster/auth-kit/examples) - see Sign In with Farcaster in action.
### Analyze Farcaster data
Sync the Farcaster network to a local machine so you can run queries on the data.
* [Write your first snapchain query](https://snapchain.farcaster.xyz/getting-started#query-a-node) - get an account's casts from a snapchain node.
* [Set up the replicator](https://snapchain.farcaster.xyz/guides/syncing-to-db) - sync a snapchain node to a postgres database to run advanced queries.
* [Run a snapchain node](https://snapchain.farcaster.xyz/guides/running-a-node) - get realtime access to Farcaster data.
# Deployments
Source: https://docs.neynar.com/farcaster/reference/contracts/deployments
## Addresses
The Farcaster contracts are deployed on Optimism and Base mainnet. There are no testnet deployments.
Optimism:
| Contract | Optimism Address | Etherscan Link |
| ---------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Id Registry | `0x00000000fc6c5f01fc30151999387bb99a9f489b` | [link](https://optimistic.etherscan.io/address/0x00000000fc6c5f01fc30151999387bb99a9f489b) |
| Key Registry | `0x00000000Fc1237824fb747aBDE0FF18990E59b7e` | [link](https://optimistic.etherscan.io/address/0x00000000Fc1237824fb747aBDE0FF18990E59b7e) |
| Storage Registry | `0x00000000fcCe7f938e7aE6D3c335bD6a1a7c593D` | [link](https://optimistic.etherscan.io/address/0x00000000fcCe7f938e7aE6D3c335bD6a1a7c593D) |
| IdGateway | `0x00000000fc25870c6ed6b6c7e41fb078b7656f69` | [link](https://optimistic.etherscan.io/address/0x00000000fc25870c6ed6b6c7e41fb078b7656f69) |
| KeyGateway | `0x00000000fc56947c7e7183f8ca4b62398caadf0b` | [link](https://optimistic.etherscan.io/address/0x00000000fc56947c7e7183f8ca4b62398caadf0b) |
| Bundler | `0x00000000fc04c910a0b5fea33b03e0447ad0b0aa` | [link](https://optimistic.etherscan.io/address/0x00000000fc04c910a0b5fea33b03e0447ad0b0aa) |
Base:
| Contract | Base Address | Etherscan Link |
| ------------- | -------------------------------------------- | ------------------------------------------------------------------------------- |
| Tier Registry | `0x00000000fc84484d585C3cF48d213424DFDE43FD` | [link](https://basescan.org/address/0x00000000fc84484d585c3cf48d213424dfde43fd) |
## ABIs
The ABIs for the contracts are available via etherscan links above, or from the [`@farcaster/core`](https://github.com/farcasterxyz/hub-monorepo/tree/main/packages/core/src/eth/contracts/abis) package.
# FAQ
Source: https://docs.neynar.com/farcaster/reference/contracts/faq
## Signatures
### How do I generate an EIP-712 signature?
See the contract reference docs for documentation on each EIP-712 signature, including Typescript [code examples](/farcaster/reference/contracts/reference/id-gateway#register-signature).
If you're using Typescript/JS, the [`@farcaster/hub-web`](https://www.npmjs.com/package/@farcaster/hub-web) package includes tools for generating and working with EIP-712 signatures. To ensure you're using the correct addresses and typehashes, we recommend importing the ABIs and EIP-712 types from the [contracts module](https://github.com/farcasterxyz/hub-monorepo/tree/main/packages/core/src/eth/contracts) or using the provided [`Eip712Signer`](https://github.com/farcasterxyz/hub-monorepo/blob/main/packages/core/src/signers/eip712Signer.ts) helper.
See the "Working with EIP-712 signatures" [example app](https://github.com/farcasterxyz/hub-monorepo/tree/main/packages/hub-nodejs/examples/contract-signatures) in the hub monorepo for a reference that demonstrates each signature and contract call.
### How can I debug an invalid EIP-712 signature?
To help debug EIP-712 signing, every contract that uses EIP-712 signatures exposes its [domain separator](https://optimistic.etherscan.io/address/0x00000000fc25870c6ed6b6c7e41fb078b7656f69#readContract#F3) and typehashes as [constants](https://optimistic.etherscan.io/address/0x00000000fc25870c6ed6b6c7e41fb078b7656f69#readContract#F1) along with a [hashTypedDataV4](https://optimistic.etherscan.io/address/0x00000000fc25870c6ed6b6c7e41fb078b7656f69#readContract#F6) helper view. If you're constructing signatures in Solidity or another low level language, you can use these to help debug.
## Reference
### Where is the full contract source code?
The contracts repo is on Github [here](https://github.com/farcasterxyz/contracts).
### Where do I get contract ABIs?
Find contract ABIs and deployment addresses [here](/farcaster/reference/contracts/deployments#abis).
### Where can I find audit reports?
Past audit reports are linked from the [contracts repo](https://github.com/farcasterxyz/contracts/blob/1aceebe916de446f69b98ba1745a42f071785730/README.md#audits).
### Are the Farcaster contracts deployed to a testnet?
No. Consider using [network forking](https://book.getfoundry.sh/tutorials/forking-mainnet-with-cast-anvil) to test or develop against the OP mainnet contracts.
## Data
### How do I find a user’s custody address?
Call the [`custodyOf`](https://optimistic.etherscan.io/address/0x00000000fc6c5f01fc30151999387bb99a9f489b#readContract#F5) function on the [IdRegistry](/farcaster/reference/contracts/reference/id-registry).
### How do I find a user’s recovery address?
Call the [`recoveryOf`](https://optimistic.etherscan.io/address/0x00000000fc6c5f01fc30151999387bb99a9f489b#readContract#F23) function on the [IdRegistry](/farcaster/reference/contracts/reference/id-registry).
### How do I find an account’s fid?
Call the [`idOf`](https://optimistic.etherscan.io/address/0x00000000fc6c5f01fc30151999387bb99a9f489b#readContract#F14) function on the [IdRegistry](/farcaster/reference/contracts/reference/id-registry).
### How do I look up the account keys for my fid?
Call the [`keysOf`](https://optimistic.etherscan.io/address/0x00000000fc1237824fb747abde0ff18990e59b7e#readContract#F16) function on the [KeyRegistry](/farcaster/reference/contracts/reference/key-registry).
## Other
### What is an app fid? How do I get one?
**What is an FID?**
An FID (Farcaster ID) is a unique identifier used to distinguish applications and users. With an FID, apps and users can be identified and differentiated.
**Why is an FID necessary?**
To create or post anything on the Farcaster platform, an FID is essential for identifying your app or user.
**How do I get one?**
You can register an app fid directly through the [Bundler](/farcaster/reference/contracts/reference/bundler) or [IdGateway](/farcaster/reference/contracts/reference/id-gateway), or use a Farcaster client to register an account for your app. Since you'll need to sign [key request metadata](/farcaster/reference/contracts/reference/signed-key-request-validator) from the wallet that owns your app fid, keep the private key secure.
# Contracts Overview
Source: https://docs.neynar.com/farcaster/reference/contracts/index
Overview of Farcaster core contracts on Optimism
The core Farcaster contracts are deployed on Optimism, an Ethereum layer 2 network. There are three core contracts:
Id Registry, Key Registry, and Storage Registry. Write access to the ID and Key registry is gated through the Gateway
contracts. There is also a Bundler helper contract to make it easy to register an fid, add a key and rent storage in one
transaction.
The Tier Registry contract supporting Farcaster Pro subscriptions is deployed on Base, an Ethereum layer 2 network.
## Id Registry
The Id Registry contract is used to keep track of Farcaster IDs. It maps an fid to an owning Ethereum
address. The owner can also designate a "recovery address" which can be used to recover the fid if the owner loses
access to the registering address. Registering an fid for the first time must be done through
the [ID Gateway](#idgateway).
## Key Registry
The Key Registry associates a Farcaster id to zero or more ed25519 public keys. Only messages signed by a key registered
here are considered valid by the hubs. Registered keys can be revoked by the owner of the fid, but revoked keys can not
be added to that fid again. The same key may be registered to multiple fids. Adding a key must be done through the
[Key Gateway](#keygateway).
## Storage Registry
The Storage Registry allows an fid to rent one or more "units" of storage on the farcaster network. The current cost of
storage is 7\$ USD per unit, for one year. This fee must be paid in ETH. The storage registry uses an ETH price oracle to
determine the current cost in ETH and exposes functions to query this price. Overpayments are refunded to the caller.
## Id Gateway
The ID Gateway handles additional logic required for first time registration of an fid. To prevent spam, the
gateway also requires renting 1 unit of storage.
## Key Gateway
Similarly, the Key Gateway exists for the Key Registry. Adding a key to a fid must be done via the gateway.
## Bundler
The Bundler makes first time sign up easier by allowing a user to register an fid, add a key and rent storage in one
function call.
## Tier Registry
The Tier Registry collects subscription payments for Farcaster Pro, paid in USDC. Unlike other core protocol contracts,
it is deployed on Base mainnet.
## Source code
The contracts source repo can be found [here](https://github.com/farcasterxyz/contracts), including more low level
documentation [here](https://github.com/farcasterxyz/contracts/blob/main/docs/docs).
# Bundler
Source: https://docs.neynar.com/farcaster/reference/contracts/reference/bundler
The Bundler makes first time sign up easier by allowing a user to register an fid, add a key and rent storage in one transaction.
If you want to create a new Farcaster account in a single transaction, use the Bundler.
## Read
### price
Get the price in wei to register an fid, including 1 storage unit. To add additional storage units to the calculation, use the `extraStorage` parameter.
| Parameter | type | Description |
| ------------ | --------- | --------------------------------------------- |
| extraStorage | `uint256` | Number of extra units to include in the price |
## Write
### register
Register an fid, add one or more keys, and rent storage in a single step. For a detailed usage example, see the [signup demo app](https://farcaster-signup-demo.vercel.app/bundler).
| Parameter | type | Description |
| -------------- | -------------------- | --------------------------------------------- |
| `msg.value` | `wei` | Payment amount for registration |
| registerParams | `RegistrationParams` | Registration related parameters and signature |
| signerParams | `SignerParams[]` | Key related parameters and signature |
| extraStorage | `uint256` | Additional storage units to rent |
**RegistrationParams struct**
The `RegistrationParams` struct includes registration parameters and an IdGateway [`Register`](/farcaster/reference/contracts/reference/id-gateway#register-signature) signature from the fid recipient.
| Parameter | type | Description |
| --------- | --------- | ---------------------------------------------------------------------------------------------------------------------------- |
| to | `address` | Address to register the fid to |
| recovery | `address` | Recovery address for the new fid |
| deadline | `uint256` | Signature expiration timestamp signature |
| sig | `bytes` | EIP-712 [`Register`](/farcaster/reference/contracts/reference/id-gateway#register-signature) signature from the `to` address |
**SignerParams struct**
The `SignerParams` struct includes signer key parameters and a KeyGateway [`Add`](/farcaster/reference/contracts/reference/key-gateway#add-signature) signature from the fid recipient. Callers may provide multiple `SignerParams` structs to add multiple keys at registration time.
| Parameter | type | Description |
| ------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| keyType | `uint32` | Must be set to `1`. This is currently the only supported `keyType`. |
| key | `bytes` | Public key to add |
| metadataType | `uint8` | Must be set to `1`. This is currently the only supported `metadataType`. |
| metadata | `bytes` | Encoded [`SignedKeyRequestMetadata`](/farcaster/reference/contracts/reference/signed-key-request-validator#signedkeyrequestmetadata-struct) |
| deadline | `uint256` | Signature expiration timestamp |
| sig | `bytes` | EIP-712 [`Add`](/farcaster/reference/contracts/reference/key-gateway#add-signature) signature from `registrationParams.to` address |
## Errors
| Error | Selector | Description |
| ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------ |
| InvalidPayment | `3c6b4b28` | The caller provided insufficient payment. |
| InvalidMetadata | `bcecb64a` | The signed metadata provided with the key is invalid. |
| InvalidSignature | `8baa579f` | The provided signature is invalid. It may be incorrectly formatted, or signed by the wrong address. |
| SignatureExpired | `0819bdcd` | The provided signature has expired. Collect a new signature from the signer with a later deadline timestamp. |
## Source
[`Bundler.sol`](https://github.com/farcasterxyz/contracts/blob/1aceebe916de446f69b98ba1745a42f071785730/src/Bundler.sol)
# ID Gateway
Source: https://docs.neynar.com/farcaster/reference/contracts/reference/id-gateway
The ID Gateway registers new Farcaster IDs and adds them to the [Id Registry](/farcaster/reference/contracts/reference/id-registry).
If you want to create a new Farcaster ID, use the ID Gateway.
## Read
### price
Get the price in wei to register an fid. This includes the price of 1 storage unit. Use the `extraStorage` parameter to include extra storage units in the total price.
| Parameter | type | Description |
| ------------ | -------------------- | -------------------------------------- |
| extraStorage | `uint256` (optional) | The number of additional storage units |
### nonces
Get the next unused nonce for an address. Used for generating an EIP-712 [`Register`](#register-signature) signature for [registerFor](#registerfor).
| Parameter | type | Description |
| --------- | --------- | ---------------------------- |
| owner | `address` | Address to get the nonce for |
## Write
### register
Register a new fid to the caller and pay for storage. The caller must not already own an fid.
| Parameter | type | Description |
| ------------ | -------------------- | ------------------------------------------ |
| `msg.value` | `wei` | Amount to pay for registration |
| recovery | `address` | Recovery address for the new fid |
| extraStorage | `uint256` (optional) | Number of additional storage units to rent |
### registerFor
Register a new fid to a specific address and pay for storage. The receiving
address must sign an EIP-712 [`Register`](#register-signature) message approving the registration. The receiver must not already own an fid.
| Parameter | type | Description |
| ------------ | -------------------- | -------------------------------------------------- |
| `msg.value` | `wei` | Amount to pay for registration |
| to | `address` | The address to register the fid to |
| recovery | `address` | Recovery address for the new fid |
| deadline | `uint256` | Signature expiration timestamp |
| sig | `bytes` | EIP-712 `Register` signature from the `to` address |
| extraStorage | `uint256` (optional) | Additional storage units |
#### Register signature
To register an fid on behalf of another account, you must provide an EIP-712 typed signature from the receiving address in the following format:
`Register(address to,address recovery,uint256 nonce,uint256 deadline)`
| Parameter | type | Description |
| --------- | --------- | --------------------------------------------------------------------------------- |
| to | `address` | Address to register the fid to. The typed message must be signed by this address. |
| recovery | `address` | Recovery address for the new fid |
| nonce | `uint256` | Current nonce of the `to` address |
| deadline | `uint256` | Signature expiration timestamp |
```ts [@farcaster/hub-web] theme={"system"}
import { ViemWalletEip712Signer } from '@farcaster/hub-web';
import { walletClient, account } from './clients.ts';
import { readNonce, getDeadline } from './helpers.ts';
const nonce = await readNonce();
const deadline = getDeadline();
const eip712Signer = new ViemWalletEip712Signer(walletClient);
const signature = await eip712signer.signRegister({
to: account,
recovery: '0x00000000FcB080a4D6c39a9354dA9EB9bC104cd7',
nonce,
deadline,
});
```
```ts [Viem] theme={"system"}
import { ID_GATEWAY_EIP_712_TYPES } from '@farcaster/hub-web';
import { walletClient, account } from './clients.ts';
import { readNonce, getDeadline } from './helpers.ts';
const nonce = await readNonce();
const deadline = getDeadline();
const signature = await walletClient.signTypedData({
account,
...ID_GATEWAY_EIP_712_TYPES,
primaryType: 'Register',
message: {
to: account,
recovery: '0x00000000FcB080a4D6c39a9354dA9EB9bC104cd7',
nonce,
deadline,
},
});
```
```ts [helpers.ts] theme={"system"}
import { ID_GATEWAY_ADDRESS, idGatewayABI } from '@farcaster/hub-web';
import { publicClient, account } from './clients.ts';
export const getDeadline = () => {
const now = Math.floor(Date.now() / 1000);
const oneHour = 60 * 60;
return now + oneHour;
};
export const readNonce = async () => {
return await publicClient.readContract({
address: ID_GATEWAY_ADDRESS,
abi: idGatewayABI,
functionName: 'nonces',
args: [account],
});
};
```
```ts [clients.ts] theme={"system"}
import { createWalletClient, createPublicClient, custom, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { optimism } from 'viem/chains';
export const publicClient = createPublicClient({
chain: optimism,
transport: http(),
});
export const walletClient = createWalletClient({
chain: optimism,
transport: custom(window.ethereum),
});
// JSON-RPC Account
export const [account] = await walletClient.getAddresses();
// Local Account
export const account = privateKeyToAccount('0x...');
```
## Errors
| Error | Selector | Description |
| ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------ |
| InvalidSignature | `8baa579f` | The provided signature is invalid. It may be incorrectly formatted, or signed by the wrong address. |
| SignatureExpired | `0819bdcd` | The provided signature has expired. Collect a new signature from the signer with a later deadline timestamp. |
## Source
[`IdGateway.sol`](https://github.com/farcasterxyz/contracts/blob/1aceebe916de446f69b98ba1745a42f071785730/src/IdGateway.sol)
# Id Registry
Source: https://docs.neynar.com/farcaster/reference/contracts/reference/id-registry
The Id Registry records which fid is associated with which Ethereum address, and manages fid transfers and recoveries.
If you want to read information about an fid, transfer or recover an fid, or manage an fid's recovery address, use the
Id Registry.
If you want to register a new fid, use the [ID Gateway](/farcaster/reference/contracts/reference/id-gateway) instead.
## Read
### idOf
Returns the fid (`uint256`) owned by an address, or returns zero if the address does not own an fid.
| Parameter | type | Description |
| --------- | --------- | ------------------------------- |
| owner | `address` | The address to check for an fid |
### custodyOf
Returns the custody address (`address`) that owns a specific fid. Returns the zero address if the fid does not exist.
| Parameter | type | Description |
| --------- | --------- | ----------------------------- |
| fid | `uint256` | The fid to find the owner for |
### recoveryOf
Returns the recovery address (`address`) of an fid. Returns the zero address if the fid does not exist.
| Parameter | type | Description |
| --------- | --------- | ---------------------------------------- |
| fid | `uint256` | The fid to find the recovery address for |
### idCounter
Returns the highest registered fid (`uint256`) so far.
### verifyFidSignature
Checks that a message was signed by the current custody address of an fid. Returns a `bool`.
| Parameter | type | Description |
| -------------- | --------- | ------------------------------------- |
| custodyAddress | `address` | The address to check the signature of |
| fid | `uint256` | The fid associated with the signature |
| digest | `bytes32` | Hashed signed data |
| sig | `bytes` | Signature to check |
### nonces
Returns the next unused nonce (`uint256`) for an address. Used for generating EIP-712 signatures.
| Parameter | type | Description |
| --------- | --------- | -------------------------------- |
| owner | `address` | The address to get the nonce for |
## Write
### register
Will revert if called directly. Must be called via the [ID Gateway](/farcaster/reference/contracts/reference/id-gateway).
### changeRecoveryAddress
Change the recovery address for the caller's fid.
| Parameter | type | Description |
| --------- | --------- | ------------------------ |
| recovery | `address` | The new recovery address |
### transfer
Transfer the caller's fid to a new address. The `to` address must sign an EIP-712 [`Transfer`](#transfer-signature)
message accepting the transfer. The `to` address must not already own an fid.
| Parameter | type | Description |
| --------- | --------- | ------------------------------------------------------------------------- |
| to | `address` | Address to transfer the fid to |
| deadline | `uint256` | Signature deadline |
| sig | `bytes` | EIP-712 [`Transfer`](#transfer-signature) signature from the `to` address |
Transferring an fid does not reset its recovery address. To transfer an fid and update its recovery address, call `transferAndChangeRecovery`. If you are receiving an fid from an untrusted sender, ensure its recovery address is cleared or changed on transfer.
#### Transfer signature
To transfer an fid to another account, the caller must provide an EIP-712 typed signature from the receiving address in
the following format:
`Transfer(uint256 fid,address to,uint256 nonce,uint256 deadline)`
| Parameter | type | Description |
| --------- | --------- | ----------------------------------- |
| fid | `uint256` | The fid being transferred |
| to | `address` | The address receiving the fid. |
| nonce | `uint256` | Current nonce of the signer address |
| deadline | `uint256` | Signature expiration timestamp |
```ts [@farcaster/hub-web] theme={"system"}
import { ViemWalletEip712Signer } from '@farcaster/hub-web';
import { walletClient, account } from './clients.ts';
import { readNonce, getDeadline } from './helpers.ts';
const nonce = await readNonce();
const deadline = getDeadline();
const eip712Signer = new ViemWalletEip712Signer(walletClient);
const signature = await eip712signer.signTransfer({
fid: 1n,
to: account,
nonce,
deadline,
});
```
```ts [Viem] theme={"system"}
import { ID_REGISTRY_EIP_712_TYPES } from '@farcaster/hub-web';
import { walletClient, account } from './clients.ts';
import { readNonce, getDeadline } from './helpers.ts';
const nonce = await readNonce();
const deadline = getDeadline();
const signature = await walletClient.signTypedData({
account,
...ID_REGISTRY_EIP_712_TYPES,
primaryType: 'Transfer',
message: {
fid: 1n,
to: account,
nonce,
deadline,
},
});
```
```ts [helpers.ts] theme={"system"}
import { ID_REGISTRY_ADDRESS, idRegistryABI } from '@farcaster/hub-web';
import { publicClient, account } from './clients.ts';
export const getDeadline = () => {
const now = Math.floor(Date.now() / 1000);
const oneHour = 60 * 60;
return now + oneHour;
};
export const readNonce = async () => {
return await publicClient.readContract({
address: ID_REGISTRY_ADDRESS,
abi: idRegistryABI,
functionName: 'nonce',
args: [account],
});
};
```
```ts [clients.ts] theme={"system"}
import { createWalletClient, createPublicClient, custom, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { optimism } from 'viem/chains';
export const publicClient = createPublicClient({
chain: optimism,
transport: http(),
});
export const walletClient = createWalletClient({
chain: optimism,
transport: custom(window.ethereum),
});
// JSON-RPC Account
export const [account] = await walletClient.getAddresses();
// Local Account
export const account = privateKeyToAccount('0x...');
```
### transferAndChangeRecovery
Transfer the fid of the caller to a new address *and* change the fid's recovery address. This can be used to safely
receive an fid transfer from an untrusted address.
The receiving address must sign an EIP-712 [`TransferAndChangeRecovery`](#transferandchangerecovery-signature) message
accepting the transfer. The `to` address must not already own an fid.
| Parameter | type | Description |
| --------- | --------- | ------------------------------------------------------------------------------------------------- |
| to | `address` | The address to transfer the fid to |
| recovery | `address` | The new recovery address |
| deadline | `uint256` | Signature deadline |
| sig | `bytes` | EIP-712 [`TransferAndChangeRecovery`](#transferandchangerecovery) signature from the `to` address |
#### TransferAndChangeRecovery signature
To transfer an fid to another account and change recovery, you must provide an EIP-712 typed signature from the `to`
address in the following format:
`TransferAndChangeRecovery(uint256 fid,address to,address recovery,uint256 nonce,uint256 deadline)`
| Parameter | type | Description |
| --------- | --------- | ----------------------------------- |
| fid | `uint256` | The fid being transferred |
| to | `address` | The address receiving the fid |
| recovery | `address` | The new recovery address |
| nonce | `uint256` | Current nonce of the signer address |
| deadline | `uint256` | Signature expiration timestamp |
```ts [@farcaster/hub-web] theme={"system"}
import { ViemWalletEip712Signer } from '@farcaster/hub-web';
import { walletClient, account } from './clients.ts';
import { readNonce, getDeadline } from './helpers.ts';
const nonce = await readNonce();
const deadline = getDeadline();
const eip712Signer = new ViemWalletEip712Signer(walletClient);
const signature = await eip712signer.signTransferAndChangeRecovery({
fid: 1n,
to: account,
recovery: '0x00000000FcB080a4D6c39a9354dA9EB9bC104cd7',
nonce,
deadline,
});
```
```ts [Viem] theme={"system"}
import { ID_REGISTRY_EIP_712_TYPES } from '@farcaster/hub-web';
import { walletClient, account } from './clients.ts';
import { readNonce, getDeadline } from './helpers.ts';
const nonce = await readNonce();
const deadline = getDeadline();
const signature = await walletClient.signTypedData({
account,
...ID_REGISTRY_EIP_712_TYPES,
primaryType: 'TransferAndChangeRecovery',
message: {
fid: 1n,
to: account,
recovery: '0x00000000FcB080a4D6c39a9354dA9EB9bC104cd7',
nonce,
deadline,
},
});
```
```ts [helpers.ts] theme={"system"}
import { ID_REGISTRY_ADDRESS, idGatewayABI } from '@farcaster/hub-web';
import { publicClient, account } from './clients.ts';
export const getDeadline = () => {
const now = Math.floor(Date.now() / 1000);
const oneHour = 60 * 60;
return now + oneHour;
};
export const readNonce = async () => {
return await publicClient.readContract({
address: ID_REGISTRY_ADDRESS,
abi: idRegistryABI,
functionName: 'nonce',
args: [account],
});
};
```
```ts [clients.ts] theme={"system"}
import { createWalletClient, createPublicClient, custom, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { optimism } from 'viem/chains';
export const publicClient = createPublicClient({
chain: optimism,
transport: http(),
});
export const walletClient = createWalletClient({
chain: optimism,
transport: custom(window.ethereum),
});
// JSON-RPC Account
export const [account] = await walletClient.getAddresses();
// Local Account
export const account = privateKeyToAccount('0x...');
```
### recover
Transfer an fid to a new address if caller is the recovery address. The `to` address must sign an
EIP-712 [`Transfer`](#transfer-signature) message accepting the transfer.
The `to` address must not already own an fid.
| Parameter | type | Description |
| --------- | --------- | ------------------------------------------------------------------------- |
| from | `address` | The address to transfer the fid from |
| to | `address` | The address to transfer the fid to |
| deadline | `uint256` | Signature deadline |
| sig | `bytes` | EIP-712 [`Transfer`](#transfer-signature) signature from the `to` address |
### changeRecoveryAddressFor
Change the recovery address of an fid on behalf of the owner by providing a signature. The owner must sign an
EIP-712 `ChangeRecoveryAddress` signature approving the change.
| Parameter | type | Description |
| --------- | --------- | -------------------------------------------------------------------------------------- |
| owner | `address` | Address of the fid owner |
| recovery | `address` | The new recovery address |
| deadline | `uint256` | Signature deadline |
| sig | `bytes` | EIP-712 [`ChangeRecoveryAddress`](#transfer-signature) signature from the `to` address |
### ChangeRecoveryAddress signature
To change a recovery address on behalf of an fid owner, the caller must provide an EIP-712 typed signature from
the `owner` address in the following format:
`ChangeRecoveryAddress(uint256 fid,address from,address to,uint256 nonce,uint256 deadline)`
| Parameter | type | Description |
| --------- | --------- | ----------------------------------- |
| fid | `uint256` | The owner's fid |
| from | `address` | The previous recovery address |
| to | `address` | The new recovery address |
| nonce | `uint256` | Current nonce of the signer address |
| deadline | `uint256` | Signature expiration timestamp |
```ts [@farcaster/hub-web] theme={"system"}
import { ViemWalletEip712Signer } from '@farcaster/hub-web';
import { walletClient, account } from './clients.ts';
import { readNonce, getDeadline } from './helpers.ts';
const nonce = await readNonce();
const deadline = getDeadline();
const eip712Signer = new ViemWalletEip712Signer(walletClient);
const signature = await eip712signer.signChangeRecoveryAddress({
fid: 1n,
from: '0x00000000FcB080a4D6c39a9354dA9EB9bC104cd7',
to: '0xD7029BDEa1c17493893AAfE29AAD69EF892B8ff2',
nonce,
deadline,
});
```
```ts [Viem] theme={"system"}
import { ID_REGISTRY_EIP_712_TYPES } from '@farcaster/hub-web';
import { walletClient, account } from './clients.ts';
import { readNonce, getDeadline } from './helpers.ts';
const nonce = await readNonce();
const deadline = getDeadline();
const signature = await walletClient.signTypedData({
account,
...ID_REGISTRY_EIP_712_TYPES,
primaryType: 'ChangeRecoveryAddress',
message: {
fid: 1n,
from: '0x00000000FcB080a4D6c39a9354dA9EB9bC104cd7',
to: '0xD7029BDEa1c17493893AAfE29AAD69EF892B8ff2',
nonce,
deadline,
},
});
```
```ts [helpers.ts] theme={"system"}
import { ID_REGISTRY_ADDRESS, idGatewayABI } from '@farcaster/hub-web';
import { publicClient, account } from './clients.ts';
export const getDeadline = () => {
const now = Math.floor(Date.now() / 1000);
const oneHour = 60 * 60;
return now + oneHour;
};
export const readNonce = async () => {
return await publicClient.readContract({
address: ID_REGISTRY_ADDRESS,
abi: idRegistryABI,
functionName: 'nonces',
args: [account],
});
};
```
```ts [clients.ts] theme={"system"}
import { createWalletClient, createPublicClient, custom, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { optimism } from 'viem/chains';
export const publicClient = createPublicClient({
chain: optimism,
transport: http(),
});
export const walletClient = createWalletClient({
chain: optimism,
transport: custom(window.ethereum),
});
// JSON-RPC Account
export const [account] = await walletClient.getAddresses();
// Local Account
export const account = privateKeyToAccount('0x...');
```
### transferFor
Transfer the fid owned by the `from` address to the `to` address. The caller must provide two
EIP-712 [`Transfer`](#transfer-signature) signatures: one from the `from` address authorizing the transfer out and one
from the `to` address accepting the transfer in. These messages have the [same format](#transfer-signature). The `to`
address must not already own an fid.
| Parameter | type | Description |
| ------------ | --------- | --------------------------------------------------------------------------- |
| from | `address` | The address to transfer the fid from |
| to | `address` | The address to transfer the fid to |
| fromDeadline | `uint256` | Signature deadline |
| fromSig | `bytes` | EIP-712 [`Transfer`](#transfer-signature) signature from the `from` address |
| toDeadline | `uint256` | Signature deadline |
| toSig | `bytes` | EIP-712 [`Transfer`](#transfer-signature) signature from the `to` address |
### transferAndChangeRecoveryFor
Transfer the fid owned by the `from` address to the `to` address, and change the fid's recovery address. This can be
used to safely receive an fid transfer from an untrusted address.
The caller must provide two EIP-712 [`TransferAndChangeRecovery`](#transferandchangerecovery-signature) signatures: one
from the `from` address authorizing the transfer out and one from the `to` address accepting the transfer in. These
messages have the [same format](#transferandchangerecovery-signature). The `to` address must not already own an fid.
| Parameter | type | Description |
| ------------ | --------- | ------------------------------------------------------------------------------------------------------------- |
| from | `address` | The address to transfer the fid from |
| to | `address` | The address to transfer the fid to |
| recovery | `address` | The new recovery address |
| fromDeadline | `uint256` | Signature deadline |
| fromSig | `bytes` | EIP-712 [`TransferAndChangeRecovery`](#transferandchangerecovery-signature) signature from the `from` address |
| toDeadline | `uint256` | Signature deadline |
| toSig | `bytes` | EIP-712 [`TransferAndChangeRecovery`](#transferandchangerecovery-signature) signature from the `to` address |
### recoverFor
Transfer an fid to a new address with a signature from the fid's recovery address. The caller must provide two
EIP-712 [`Transfer`](#transfer-signature) signatures: one from the recovery address authorizing the transfer out and one
from the `to` address accepting the transfer in. These messages have the [same format](#transfer-signature).
The `to` address must not already own an fid.
| Parameter | type | Description |
| ---------------- | --------- | ----------------------------------------------------------------------------- |
| from | `address` | The address to transfer the fid from |
| to | `address` | The address to transfer the fid to |
| recoveryDeadline | `uint256` | The deadline for the recovery signature |
| recoverySig | `bytes` | EIP-712 [`Transfer`](#transfer-signature) signature from the recovery address |
| toDeadline | `uint256` | The deadline for the receiver signature |
| toSig | `bytes` | EIP-712 [`Transfer`](#transfer-signature) signature from the `to` address |
## Errors
| Error | Selector | Description |
| ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------ |
| HasId | `f90230a9` | The `to` address already owns an fid. |
| HasNoId | `210b4b26` | The `from` address does not own an fid. |
| InvalidSignature | `8baa579f` | The provided signature is invalid. It may be incorrectly formatted, or signed by the wrong address. |
| SignatureExpired | `0819bdcd` | The provided signature has expired. Collect a new signature from the signer with a later deadline timestamp. |
## Source
[`IdRegistry.sol`](https://github.com/farcasterxyz/contracts/blob/1aceebe916de446f69b98ba1745a42f071785730/src/IdRegistry.sol)
# Key Gateway
Source: https://docs.neynar.com/farcaster/reference/contracts/reference/key-gateway
The Key Gateway adds new keys to the [Key Registry](/farcaster/reference/contracts/reference/key-registry).
If you want to add a new public key to a Farcaster account, use the Key Gateway.
## Read
### nonces
Returns the next available nonce for an address. Used for generating EIP-712 signatures in [addFor](#addFor).
| Parameter | type | Description |
| --------- | --------- | ---------------------------------- |
| owner | `address` | The address to query the nonce for |
## Write
### add
Add a new key for the caller's fid and set its state to `Added`. Revert if the key is already registered.
| Parameter | type | Description |
| ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| keyType | `uint32` | Must be set to `1`. This is currently the only supported `keyType`. |
| key | `bytes` | The public key to add |
| metadataType | `uint8` | Must be set to `1`. This is currently the only supported `metadataType`. |
| metadata | `bytes` | Encoded [`SignedKeyRequestMetadata`](/farcaster/reference/contracts/reference/signed-key-request-validator#signedkeyrequestmetadata-struct) |
### addFor
Add a key on behalf of another fid by providing a signature. The owner must sign an EIP-712 `Add` message approving the key. Reverts if the key is already registered.
| Parameter | type | Description |
| ------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| fidOwner | `address` | Address of the fid owner |
| keyType | `uint32` | Must be set to `1`. This is currently the only supported `keyType`. |
| key | `bytes` | The public key to add |
| metadataType | `uint8` | Must be set to `1`. This is currently the only supported `metadataType`. |
| metadata | `bytes` | Encoded [`SignedKeyRequestMetadata`](/farcaster/reference/contracts/reference/signed-key-request-validator#signedkeyrequestmetadata-struct) |
| deadline | `uint256` | Signature expiration timestamp |
| sig | `bytes` | EIP-712 [`Add`](/farcaster/reference/contracts/reference/key-gateway#add-signature) signature from `fidOwner` |
#### Add signature
To add a key on behalf of another account, you must provide an EIP-712 typed signature from the account in the following format:
`Add(address owner,uint32 keyType,bytes key,uint8 metadataType,bytes metadata,uint256 nonce,uint256 deadline)`
| Parameter | type | Description |
| ------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| owner | `address` | Address that owns the fid. The typed message must be signed by this address. |
| keyType | `uint32` | Must be set to `1`. This is currently the only supported `keyType`. |
| key | `bytes` | The public key to add |
| metadataType | `uint8` | Must be set to `1`. This is currently the only supported `metadataType`. |
| metadata | `bytes` | Encoded [`SignedKeyRequestMetadata`](/farcaster/reference/contracts/reference/signed-key-request-validator#signedkeyrequestmetadata-struct) |
| nonce | `uint256` | Current nonce of the `owner` address |
| deadline | `uint256` | Signature expiration timestamp |
```ts [@farcaster/hub-web] theme={"system"}
import { ViemWalletEip712Signer } from '@farcaster/hub-web';
import { walletClient, account } from './clients.ts';
import { getPublicKey } from './signer.ts';
import { readNonce, getDeadline } from './helpers.ts';
const publicKey = await getPublicKey();
const metadata = await getMetadata();
const nonce = await readNonce();
const deadline = getDeadline();
const eip712Signer = new ViemWalletEip712Signer(walletClient);
const signature = await eip712signer.signAdd({
owner: account,
keyType: 1,
key: publicKey,
metadataType: 1,
metadata,
nonce,
deadline,
});
```
```ts [Viem] theme={"system"}
import { KEY_GATEWAY_EIP_712_TYPES } from '@farcaster/hub-web';
import { bytesToHex } from 'viem';
import { walletClient, account } from './clients.ts';
import { getPublicKey } from './signer.ts';
import { getMetadata } from './metadata.ts';
import { readNonce, getDeadline } from './helpers.ts';
const publicKey = await getPublicKey();
const metadata = await getMetadata();
const nonce = await readNonce();
const deadline = getDeadline();
const signature = await walletClient.signTypedData({
account,
...KEY_GATEWAY_EIP_712_TYPES,
primaryType: 'Add',
message: {
owner: account,
keyType: 1,
key: bytesToHex(publicKey),
metadataType: 1,
metadata,
nonce,
deadline,
},
});
```
```ts [helpers.ts] theme={"system"}
import { KEY_GATEWAY_ADDRESS, keyGatewayABI } from '@farcaster/hub-web';
import { publicClient, account } from './clients.ts';
export const getDeadline = () => {
const now = Math.floor(Date.now() / 1000);
const oneHour = 60 * 60;
return now + oneHour;
};
export const readNonce = async () => {
return await publicClient.readContract({
address: KEY_GATEWAY_ADDRESS,
abi: keyGatewayABI,
functionName: 'nonces',
args: [account],
});
};
```
```ts [metadata.ts] theme={"system"}
import { ViemLocalEip712Signer } from '@farcaster/hub-web';
import { privateKeyToAccount } from 'viem/accounts';
import { getDeadline } from './helpers.ts';
import { getPublicKey } from './signer.ts';
// App account
export const appAccount = privateKeyToAccount('0x...');
const deadline = getDeadline();
const publicKey = await getPublicKey();
export const getMetadata = async () => {
const eip712signer = new ViemLocalEip712Signer(appAccount);
const metadata = await eip712signer.getSignedKeyRequestMetadata({
requestFid: 9152n, // App fid
key: publicKey,
deadline,
});
if (metadata.isOk()) {
return metadata.value;
}
};
```
```ts [signer.ts] theme={"system"}
import * as ed from '@noble/ed25519';
import { NobleEd25519Signer } from '@farcaster/hub-web';
const privateKeyBytes = ed.utils.randomPrivateKey();
export const accountKey = new NobleEd25519Signer(privateKeyBytes);
export const getPublicKey = async () => {
const accountKeyResult = await accountKey.getSignerKey();
if (accountKeyResult.isOk()) {
return accountKeyResult.value;
}
};
```
```ts [clients.ts] theme={"system"}
import { createWalletClient, createPublicClient, custom, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { optimism } from 'viem/chains';
export const publicClient = createPublicClient({
chain: optimism,
transport: http(),
});
export const walletClient = createWalletClient({
chain: optimism,
transport: custom(window.ethereum),
});
// JSON-RPC Account
export const [account] = await walletClient.getAddresses();
// Local Account
export const account = privateKeyToAccount('0x...');
```
## Errors
| Error | Selector | Description |
| ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------ |
| InvalidMetadata | `bcecb64a` | The signed metadata provided with the key is invalid. |
| InvalidSignature | `8baa579f` | The provided signature is invalid. It may be incorrectly formatted, or signed by the wrong address. |
| SignatureExpired | `0819bdcd` | The provided signature has expired. Collect a new signature from the signer with a later deadline timestamp. |
## Source
[`KeyGateway.sol`](https://github.com/farcasterxyz/contracts/blob/1aceebe916de446f69b98ba1745a42f071785730/src/KeyGateway.sol)
# Key Registry
Source: https://docs.neynar.com/farcaster/reference/contracts/reference/key-registry
The Key Registry stores the public keys associated with each Farcaster account.
If you want to read information about a Farcaster account's keys or remove an existing key, use the Key Registry.
If you want to add a new key, use the [Key Gateway](/farcaster/reference/contracts/reference/key-gateway) instead.
## Read
### totalKeys
Get the number of active keys (`uint256`) for an fid.
| Parameter | type | Description |
| --------- | ------------------------------------ | ----------------------------------- |
| fid | `uint256` | fid to look up |
| state | `uint8` (1 for Added, 2 for Removed) | State of the key (added or removed) |
### keysOf
List all public keys (`bytes[]`) for an fid.
| Parameter | type | Description |
| --------- | ------------------------------------ | ----------------------------------- |
| fid | `uint256` | fid to look up |
| state | `uint8` (1 for Added, 2 for Removed) | State of the key (added or removed) |
| startIdx | `uint256` (optional) | Start index for pagination |
| batchSize | `uint256` (optional) | Batch size for pagination |
Don't call this onchain! This function is very gas intensive. It's meant to be called by offchain tools, not by other contracts.
### keyDataOf
Returns the state (`uint8`) and keyType (`uint32`) of particular key for an fid.
| Parameter | type | Description |
| --------- | --------- | ------------------- |
| fid | `uint256` | fid to look up |
| key | `bytes` | public key to check |
## Write
### add
Will revert if called directly. Must be called via the [Key Gateway](/farcaster/reference/contracts/reference/key-gateway)
### remove
Removes a public key from the caller's fid and marks it as `Removed`.
| Parameter | type | Description |
| --------- | ------- | -------------------- |
| key | `bytes` | public key to remove |
Removing a key will delete all offchain messages associated with the key from Hubs.
### removeFor
Remove a key on behalf of another fid by providing a signature. The fid owner must sign an EIP-712 `Remove` message approving the removal. Reverts if the key does not exist or is already removed.
| Parameter | type | Description |
| --------- | --------- | ------------------------------------- |
| fidOwner | `address` | fid owner address |
| key | `bytes` | public key to remove |
| deadline | `uint256` | signature deadline |
| sig | `bytes` | EIP-712 signature from the `fidOwner` |
Removing a key will delete all offchain messages associated with the key from Hubs.
#### Remove signature
To remove a key on behalf of another account, you must provide an EIP-712 typed signature from the account in the following format:
`Remove(address owner,bytes key,uint256 nonce,uint256 deadline)`
| Parameter | type | Description |
| --------- | --------- | ---------------------------------------------------------------------------- |
| owner | `address` | Address that owns the fid. The typed message must be signed by this address. |
| key | `bytes` | The public key to remove |
| nonce | `uint256` | Current nonce of the `owner` address |
| deadline | `uint256` | Signature expiration timestamp |
```ts [@farcaster/hub-web] theme={"system"}
import { ViemWalletEip712Signer } from '@farcaster/hub-web';
import { walletClient, account } from './clients.ts';
import { getPublicKey } from './signer.ts';
import { readNonce, getDeadline } from './helpers.ts';
const publicKey = await getPublicKey();
const nonce = await readNonce();
const deadline = getDeadline();
const eip712Signer = new ViemWalletEip712Signer(walletClient);
const signature = await eip712Signer.signRemove({
owner: account,
key: publicKey,
nonce,
deadline,
});
```
```ts [Viem] theme={"system"}
import { KEY_REGISTRY_EIP_712_TYPES } from '@farcaster/hub-web';
import { bytesToHex } from 'viem';
import { walletClient, account } from './clients.ts';
import { getPublicKey } from './signer.ts';
import { readNonce, getDeadline } from './helpers.ts';
const publicKey = await getPublicKey();
const nonce = await readNonce();
const deadline = getDeadline();
const signature = await walletClient.signTypedData({
account,
...KEY_REGISTRY_EIP_712_TYPES,
primaryType: 'Remove',
message: {
owner: account,
key: bytesToHex(publicKey),
nonce,
deadline,
},
});
```
```ts [helpers.ts] theme={"system"}
import { KEY_REGISTRY_ADDRESS, keyRegistryABI } from '@farcaster/hub-web';
import { publicClient, account } from './clients.ts';
export const getDeadline = () => {
const now = Math.floor(Date.now() / 1000);
const oneHour = 60 * 60;
return now + oneHour;
};
export const readNonce = async () => {
return await publicClient.readContract({
address: KEY_REGISTRY_ADDRESS,
abi: keyRegistryABI,
functionName: 'nonces',
args: [account],
});
};
```
```ts [signer.ts] theme={"system"}
import * as ed from '@noble/ed25519';
import { NobleEd25519Signer } from '@farcaster/hub-web';
const privateKeyBytes = ed.utils.randomPrivateKey();
export const accountKey = new NobleEd25519Signer(privateKeyBytes);
export const getPublicKey = async () => {
const accountKeyResult = await accountKey.getSignerKey();
if (accountKeyResult.isOk()) {
return accountKeyResult.value;
}
};
```
```ts [clients.ts] theme={"system"}
import { createWalletClient, createPublicClient, custom, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { optimism } from 'viem/chains';
export const publicClient = createPublicClient({
chain: optimism,
transport: http(),
});
export const walletClient = createWalletClient({
chain: optimism,
transport: custom(window.ethereum),
});
// JSON-RPC Account
export const [account] = await walletClient.getAddresses();
// Local Account (use this OR the JSON-RPC account above, not both)
// export const account = privateKeyToAccount('0x...');
```
## Errors
| Error | Selector | Description |
| ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| ExceedsMaximum | `29264042` | Adding the key exceeds the maximum number of keys allowed per fid (currently 1000) |
| InvalidSignature | `8baa579f` | The provided signature is invalid. It may be incorrectly formatted, or signed by the wrong address. |
| InvalidState | `baf3f0f7` | The action violates state transition rules. (Adding a key that already exists, removing a key that does not exist, adding a key that has been removed) |
| SignatureExpired | `0819bdcd` | The provided signature has expired. Collect a new signature from the signer with a later deadline timestamp. |
## Source
[`KeyRegistry.sol`](https://github.com/farcasterxyz/contracts/blob/1aceebe916de446f69b98ba1745a42f071785730/src/KeyRegistry.sol)
# Signed Key Request Validator
Source: https://docs.neynar.com/farcaster/reference/contracts/reference/signed-key-request-validator
The Signed Key Request Validator validates the signed metadata associated with keys. The Key Registry calls the validator before adding a key, to check that the provided Signed Key Request is valid.
If you want to construct or check a Signed Key Request, use the Signed Key Request Validator.
**What is a Signed Key Request?** - When a user adds a key to their account (the primary fid), it must include a signature from the person that requested it (the request fid). This enables anyone to identify who requested a specific key. Typically, the primary fid is the end user and the requesting fid is an app the user wishes to connect to.
## Read
### encodeMetadata
Convert a [`SignedKeyRequestMetadata`](#signedkeyrequestmetadata-struct) struct into `bytes` to pass into contract functions like [add](/farcaster/reference/contracts/reference/key-gateway#add), [register](/farcaster/reference/contracts/reference/bundler#register).
| Parameter | type | Description |
| --------- | -------------------------- | ----------------------------------------------- |
| metadata | `SignedKeyRequestMetadata` | The `SignedKeyRequestMetadata` struct to encode |
#### SignedKeyRequestMetadata struct
The `SignedKeyRequestMetadata` struct contains data to validate authenticity of a Signed Key Request: requesting fid, the requesting fid owner, and an EIP-712 [`SignedKeyRequest`](#signedkeyrequest-signature) signature.
| Parameter | type | Description |
| ------------- | --------- | ---------------------------------------------------------------------------------------------------- |
| requestFid | `uint256` | requesting fid |
| requestSigner | `address` | owner address of the requesting fid |
| signature | `bytes` | EIP-712 [`SignedKeyRequest`](#signedkeyrequest-signature) signature from the `requestSigner` address |
| deadline | `uint256` | Expiration timestamp of the signature |
See below for code examples that demonstrate two methods of generating encoded `SignedKeyRequestMetadata` — using the `@farcaster/hub-web` library to sign and encode in a single step, or using Viem to sign and encode separately.
```ts [@farcaster/hub-web] theme={"system"}
import { ViemLocalEip712Signer } from '@farcaster/hub-web';
import { privateKeyToAccount } from 'viem/accounts';
import { getDeadline } from './helpers.ts';
import { getPublicKey } from './signer.ts';
export const appAccount = privateKeyToAccount('0x...');
const key = await getPublicKey();
const deadline = getDeadline();
// The getSignedKeyRequestMetadata helper generates a SignedKeyRequest
// signature and returns an ABI-encoded SignedKeyRequest metadata struct.
const eip712Signer = new ViemLocalEip712Signer(appAccount);
const encodedData = await eip712Signer.getSignedKeyRequestMetadata({
requestFid: 9152n,
key,
deadline,
});
```
```ts [Viem] theme={"system"}
import { bytesToHex, encodeAbiParameters } from 'viem';
import { signature } from './signature.ts';
import { getDeadline } from './helpers.ts';
const deadline = getDeadline();
// An example of collecting the signature and
// encoding the SignedKeyRequest metadata separately.
const encodedData = encodeAbiParameters(
[
{
components: [
{
name: 'requestFid',
type: 'uint256',
},
{
name: 'requestSigner',
type: 'address',
},
{
name: 'signature',
type: 'bytes',
},
{
name: 'deadline',
type: 'uint256',
},
],
type: 'tuple',
},
],
[
{
requestFid: 9152n,
requestSigner: '0x02ef790dd7993a35fd847c053eddae940d055596',
signature: bytesToHex(signature),
deadline,
},
]
);
```
```ts [signature.ts] theme={"system"}
import { ViemLocalEip712Signer } from '@farcaster/hub-web';
import { privateKeyToAccount } from 'viem/accounts';
import { getDeadline } from './helpers.ts';
import { getPublicKey } from './signer.ts';
export const appAccount = privateKeyToAccount('0x...');
const key = await getPublicKey();
const deadline = getDeadline();
const eip712Signer = new ViemLocalEip712Signer(appAccount);
const signature = await eip712Signer.signKeyRequest({
requestFid: 9152n,
key,
deadline,
});
```
```ts [helpers.ts] theme={"system"}
export const getDeadline = () => {
const now = Math.floor(Date.now() / 1000);
const oneHour = 60 * 60;
return now + oneHour;
};
```
```ts [signer.ts] theme={"system"}
import * as ed from '@noble/ed25519';
import { NobleEd25519Signer } from '@farcaster/hub-web';
const privateKeyBytes = ed.utils.randomPrivateKey();
export const accountKey = new NobleEd25519Signer(privateKeyBytes);
export const getPublicKey = async () => {
const accountKeyResult = await accountKey.getSignerKey();
if (accountKeyResult.isOk()) {
return accountKeyResult.value;
}
};
```
### validate
Validate an encoded [`SignedKeyRequestMetadata`](#signedkeyrequestmetadata-struct). The KeyRegistry calls this function internally when a user adds a key to validate the Signed Key Request. If you are creating keys on behalf of users, you can call this function yourself to validate the Signed Key Request created by your app.
| Parameter | type | Description |
| --------- | --------- | ----------------------------------------------------------------------- |
| fid | `uint256` | Primary fid that will be associated with the key |
| key | `bytes` | Bytes of the public key to validate |
| sig | `bytes` | EIP-712 `SignedKeyRequest` signature from the entity requesting the key |
#### SignedKeyRequest signature
The `SignedKeyRequest` message is an EIP-712 typed signature signed by the requesting fid owner in the following format:
`SignedKeyRequest(uint256 requestFid,bytes key,uint256 deadline)`
**Why sign metadata?** - The `SignedKeyRequest` signature proves that the requesting fid asked the primary fid to authorize a key pair. For example, when an app asks a user to add a new key, the app creates a Signed Key Request proving that it made the request and the KeyRegistry emits it in an onchain event. This allows anyone to attribute a signer to the specific person who requested it, which is useful for a wide range of things from knowing which apps are being used to filtering content based on the applications that generated them.
| Parameter | type | Description |
| ---------- | --------- | ------------------------------------- |
| requestFid | `uint256` | fid of the entity |
| key | `bytes` | Bytes of the public key |
| deadline | `uint256` | Expiration timestamp of the signature |
```ts [@farcaster/hub-web] theme={"system"}
import { ViemLocalEip712Signer } from '@farcaster/hub-web';
import { privateKeyToAccount } from 'viem/accounts';
import { getDeadline } from './helpers.ts';
import { getPublicKey } from './signer.ts';
export const appAccount = privateKeyToAccount('0x...');
const key = await getPublicKey();
const deadline = getDeadline();
const eip712Signer = new ViemLocalEip712Signer(appAccount);
const signature = await eip712Signer.signKeyRequest({
requestFid: 9152n,
key,
deadline,
});
```
```ts [Viem] theme={"system"}
import { SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_TYPES } from '@farcaster/hub-web';
import { bytesToHex, privateKeyToAccount } from 'viem/accounts';
import { getDeadline } from './helpers.ts';
import { getPublicKey } from './signer.ts';
export const appAccount = privateKeyToAccount('0x...');
const key = await getPublicKey();
const deadline = getDeadline();
const signature = await appAccount.signTypedData({
...SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_TYPES,
primaryType: 'SignedKeyRequest',
message: {
requestFid: 9152n,
key: bytesToHex(key),
deadline,
},
});
```
```ts [helpers.ts] theme={"system"}
export const getDeadline = () => {
const now = Math.floor(Date.now() / 1000);
const oneHour = 60 * 60;
return now + oneHour;
};
```
```ts [signer.ts] theme={"system"}
import * as ed from '@noble/ed25519';
import { NobleEd25519Signer } from '@farcaster/hub-web';
const privateKeyBytes = ed.utils.randomPrivateKey();
export const accountKey = new NobleEd25519Signer(privateKeyBytes);
export const getPublicKey = async () => {
const accountKeyResult = await accountKey.getSignerKey();
if (accountKeyResult.isOk()) {
return accountKeyResult.value;
}
};
```
## Source
[`SignedKeyRequestValidator.sol`](https://github.com/farcasterxyz/contracts/blob/1aceebe916de446f69b98ba1745a42f071785730/src/validators/SignedKeyRequestValidator.sol)
# Storage Registry
Source: https://docs.neynar.com/farcaster/reference/contracts/reference/storage-registry
The Storage Registry allows Farcaster accounts to rent one or more "units" of storage on the network.
If you want to rent storage for a Farcaster account, use the Storage Registry.
## Read
### unitPrice
Get the price in wei (`uint256`) to register 1 unit of storage.
### price
Get the price in wei (`uint256`) to register a specific number of storage units.
| Param Name | type | Description |
| ---------- | --------- | ----------------------------------- |
| units | `uint256` | The number of storage units to rent |
## Write
### rent
Rent a specific number of storage units for a given fid. Excess ether will be returned to the caller. Rented units are valid for 1 year from the time of registration.
| Param Name | type | Description |
| ----------- | --------- | -------------------------------------- |
| `msg.value` | `wei` | Payment amount |
| fid | `uint256` | The fid to credit the storage units to |
| units | `uint256` | The number of units of storage to rent |
### batchRent
Rent storage for multiple fids in one transaction. The caller must send enough ether to cover the total cost of all units. Like single-unit rental, extra ether is returned and units are valid for 1 year.
| Param Name | type | Description |
| ----------- | ----------- | ----------------------------------------------------------------------- |
| `msg.value` | `wei` | Total payment amount |
| fids | `uint256[]` | Array of fids |
| units | `uint256[]` | Array of unit quantities, corresponding to each fid in the `fids` array |
## Errors
| Error | Selector | Description |
| ----------------- | ---------- | ---------------------------------------------------------------------------------------- |
| InvalidPayment | `3c6b4b28` | The caller didn't provide enough ether to pay for the number of storage units requested. |
| InvalidBatchInput | `0a514b99` | The caller provided mismatched arrays of `fids` and `units`. |
## Source
[`StorageRegistry.sol`](https://github.com/farcasterxyz/contracts/blob/1aceebe916de446f69b98ba1745a42f071785730/src/validators/StorageRegistry.sol)
# Tier Registry
Source: https://docs.neynar.com/farcaster/reference/contracts/reference/tier-registry
The Tier Registry allows Farcaster accounts to purchase or extend Farcaster Pro subscriptions.
Unlike other protocol contracts, the Tier Registry is deployed on Base Mainnet.
If you want to purchase a Pro subscription for a Farcaster account, use the Tier Registry.
## Active Tiers
| Tier ID | Description | Payment Token | Min Days | Price/day |
| ------- | ------------- | ------------- | -------- | ----------------- |
| 1 | Farcaster Pro | USDC | 30 | `328767` wei USDC |
## Read
### price
Get the total price in payment token (`uint256`) to purchase a tier subscription for a given number of days.
| Param Name | type | Description |
| ---------- | --------- | ----------------------------------------- |
| tier | `uint256` | The tier ID to calculate price for |
| forDays | `uint256` | The number of days to calculate price for |
### tierInfo
Get information about a specific tier. Returns a `TierInfo` struct.
| Param Name | type | Description |
| ---------- | --------- | ----------- |
| tier | `uint256` | The tier ID |
`TierInfo` struct parameters:
| Param Name | type | Description |
| ---------------- | --------- | ---------------------------------------------------- |
| minDays | `uint256` | Minimum number of days required to purchase |
| maxDays | `uint256` | Maximum number of days per purchase |
| vault | `address` | Payment destination address |
| paymentToken | `IERC20` | ERC20 payment token |
| tokenPricePerDay | `uint256` | Price per day in fundamental units of `paymentToken` |
| isActive | `bool` | Whether tier is currently active |
## Write
### purchaseTier
Purchase a subscription tier for a given fid. If the account already has an active subscription, purchasing will extend
their total subscription time.
| Param Name | type | Description |
| ---------- | --------- | ----------------------------------------- |
| fid | `uint256` | The fid to credit the subscription to |
| tier | `uint256` | The tier ID to calculate price for |
| forDays | `uint256` | The number of days to calculate price for |
### batchPurchaseTier
Purchase a subscription tier for multiple fids in a single transaction. If an account already has an active subscription, purchasing will extend
their total subscription time.
| Param Name | type | Description |
| ---------- | ----------- | ------------------------------------------------------------ |
| tier | `uint256` | The tier ID to calculate price for |
| fids | `uint256[]` | Array of fids |
| forDays | `uint256[]` | Array of days, corresponding to each fid in the `fids` array |
## Events
### PurchasedTier
Emitted when a tier is purchased for a Farcaster account.
| Param Name | type | Description |
| ---------- | ----------------- | ---------------------------------------- |
| fid | `uint256 indexed` | Farcaster ID the tier was purchased for |
| tier | `uint256 indexed` | Tier ID that was purchased |
| forDays | `uint256` | Number of days of subscription purchased |
| payer | `address indexed` | Caller address that paid |
## Errors
| Error | Selector | Description |
| ----------------- | ---------- | -------------------------------------------------------------- |
| InvalidDuration | `76166401` | The caller attempted to purchase an invalid number of days. |
| InvalidTier | `e1423617` | The caller attempted to purchase an invalid or inactive tier. |
| InvalidBatchInput | `0a514b99` | The caller provided mismatched arrays of `fids` and `forDays`. |
## Source
[`TierRegistry.sol`](https://github.com/farcasterxyz/contracts/blob/d0af1b8148db713239f6ac19465efeadb713b58c/src/TierRegistry.sol)
# Farcaster Client API Reference
Source: https://docs.neynar.com/farcaster/reference/farcaster/api
This page documents public APIs provided by the Farcaster client with information that is not available on the protocol.
The hostname is always `https://api.farcaster.xyz`.
## Pagination
Paginated endpoints return a `next.cursor` property next to the `result` object. To fetch the
next page, send the value as a `cursor` query parameter. An optional `limit` query parameter can be used to
specify the page size.
```json theme={"system"}
{
"result": {
...
},
"next": {
"cursor": "eyJwYWdlIjoxLCJsaW1pdCI6MTAwfQ"
}
}
```
## Authentication
Authenticated endpoints use a self-signed token, signed as an App Key for FID:
```tsx theme={"system"}
import { NobleEd25519Signer } from "@farcaster/hub-nodejs";
// private / public keys of an App Key you are holding for an FID
const fid = 6841; //replace
const privateKey = 'secret'; // replace
const publicKey = 'pubkey'; // replace
const signer = new NobleEd25519Signer(new Uint8Array(Buffer.from(privateKey)));
const header = {
fid,
type: 'app_key',
key: publicKey
};
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url');
const payload = { exp: Math.floor(Date.now() / 1000) + 300 }; // 5 minutes
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url');
const signatureResult = await signer.signMessageHash(Buffer.from(`${encodedHeader}.${encodedPayload}`, 'utf-8'));
if (signatureResult.isErr()) {
throw new Error("Failed to sign message");
}
const encodedSignature = Buffer.from(signatureResult.value).toString("base64url");
const authToken = encodedHeader + "." + encodedPayload + "." + encodedSignature;
await got.post(
"https://api.farcaster.xyz/fc/channel-follows",
{
body: { channelKey: 'evm' }
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + authToken;
}
}
)
```
## Concepts
* Channels: The Farcaster client has the concept of channels which build upon FIP-2 (setting `parentUrl` on casts). You can read more
about channels in the [documentation](https://www.notion.so/farcaster/Channels-4f249d22575348a5a0488b5d86f0dd1c?pvs=4).
## Get All Channels
`GET /v2/all-channels`
List all channels. No parameters. Not paginated. Not authenticated.
Returns: a `channels` array with properties:
* `id` - unique channel id that cannot be changed (called 'Name' when creating a channel)
* `url` - FIP-2 `parentUrl` used for main casts in the channel
* `name` - friendly name displayed to users (called 'Display name' when editing a channel)
* `description` - description of the channel, if present
* `descriptionMentions` - an array of the user fids mentioned in the description. Multiple mentions result in multiple entries.
* `descriptionMentionsPositions` - the indexes within the description where the mentioned users (from `descriptionMentions`) appear.
This array is always the same size as `descriptionMentions`. The mention is placed to the left of any existing character at that index.
* `leadFid` - fid of the user who created the channel, if present
* `moderatorFids` - fids of the moderators (under new channel membership scheme)
* `createdAt` - UNIX time when channel was created, in seconds
* `followerCount` - number of users following the channel
* `memberCount` - number of members of the channel, including the owner and moderators
* `pinnedCastHash` - hash of the cast pinned in the channel, if present
* `publicCasting` - `true`/`false` indicating whether channel allows anybody to cast into it, or only members
* `externalLink` - external link that appears in the header in the Farcaster client, if present, with 2 properties:
* `title` - title shown in the channel header
* `url` - url of the link
```json theme={"system"}
{
"result": {
"channels": [
{
"id": "illustrations",
"url": "https://farcaster.xyz/~/channel/illustrations",
"name": "illustrations",
"description": "Share your wips, sketches, arts, drops, GMs, artworks you adore or collected — all content related to illustration, tag to join ⊹ ࣪ ˖ cover by ",
"descriptionMentions": [
367850,
335503
],
"descriptionMentionsPositions": [
122,
151
],
"imageUrl": "https://imagedelivery.net/BXluQx4ige9GuW0Ia56BHw/7721c951-b0ed-44ee-aa9c-c31507b69c00/original",
"headerImageUrl": "https://imagedelivery.net/BXluQx4ige9GuW0Ia56BHw/64efe955-c3ab-4aad-969d-1aed978a3e00/original",
"leadFid": 367850,
"moderatorFids": [
367850
],
"createdAt": 1709753166,
"followerCount": 2361,
"memberCount": 300,
"pinnedCastHash": "0x3ef52987ccacd89af096a753c07efcd55a93e143",
"publicCasting": false,
"externalLink": {
"title": "/creatorssupport",
"url": "https://farcaster.xyz/~/channel/creators-support"
}
},
...
]
}
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/v2/all-channels'
```
## Get a Channel
`GET /v1/channel`
Get a single channel. Not authenticated.
Query parameters:
* `channelId` - the id of the channel
Returns: a single channel object, as documented in the "Get All Channels" endpoint above.
```json theme={"system"}
{
"result": {
"channel": {
"id": "illustrations",
"url": "https://farcaster.xyz/~/channel/illustrations",
"name": "illustrations",
"description": "Share your wips, sketches, arts, drops, GMs, artworks you adore or collected — all content related to illustration, tag to join ⊹ ࣪ ˖ cover by ",
"descriptionMentions": [367850, 335503],
"descriptionMentionsPositions": [122, 151],
"imageUrl": "https://imagedelivery.net/BXluQx4ige9GuW0Ia56BHw/7721c951-b0ed-44ee-aa9c-c31507b69c00/original",
"headerImageUrl": "https://imagedelivery.net/BXluQx4ige9GuW0Ia56BHw/64efe955-c3ab-4aad-969d-1aed978a3e00/original",
"leadFid": 367850,
"moderatorFids": [367850],
"createdAt": 1709753166,
"followerCount": 2361,
"memberCount": 300,
"pinnedCastHash": "0x3ef52987ccacd89af096a753c07efcd55a93e143",
"publicCasting": false,
"externalLink": {
"title": "/creatorssupport",
"url": "https://farcaster.xyz/~/channel/creators-support"
}
}
}
}
```
```bash theme={"system"}
curl 'https://api.farcaster.xyz/v1/channel?channelId=illustrations'
```
## Get Channel Followers
`GET /v1/channel-followers`
List the followers of a channel. Ordered by the time when the channel was followed, descending. Paginated. Not authenticated.
Query Parameters:
* `channelId` - the id of the channel
Returns: a `users` array with properties:
* `fid` - the fid of the user
* `followedAt` - UNIX time when channel was followed, in seconds
```json theme={"system"}
{
"result": {
"users": [
{
"fid": 466624,
"followedAt": 1712685183
},
{
"fid": 469283,
"followedAt": 1712685067
},
...
],
},
"next": { "cursor": "..." }
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/v1/channel-followers?channelId=books'
```
## Get Channels a User is Following
`GET /v1/user-following-channels`
List all channels a user is following. Ordered by the time when the channel was followed, descending. Paginated. Not authenticated.
Parameters:
* `fid` - the fid of the user
Returns: a `channels` array with properties:
* All properties documented in the "Get All Channels" endpoint above
* `followedAt` - UNIX time when channel was followed, in seconds
```json theme={"system"}
{
"result": {
"channels": [
{
"id": "fc-updates",
"url": "https://farcaster.xyz/~/channel/fc-updates",
"name": "fc-updates",
"description": "Important updates about things happening in Farcaster",
"imageUrl": "https://i.imgur.com/YnnrPaH.png",
"leadFid": 2,
"moderatorFid": 5448,
"moderatorFids": [
5448,
3
],
"createdAt": 1712162074,
"followerCount": 17034,
"followedAt": 1712162620
},
...
]
},
"next": { "cursor": "..." }
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/v1/user-following-channels?fid=3'
```
## Get User Following Channel Status
`GET /v1/user-channel`
Check whether a user is following a channel.
Query parameters:
* `fid` - the fid of the user
* `channelId` - the id of the channel
Returns: 2 properties:
* `following` - indicates whether the channel is followed
* `followedAt` - UNIX time when channel was followed, in seconds (optional, only present when channel is followed)
```json theme={"system"}
{
"result": {
"following": true,
"followedAt": 1687943747
}
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/v1/user-channel?fid=3&channelId=books'
```
## Get Channel Members
`GET /fc/channel-members`
List the members of a channel. Ordered by the time when the user became a member, descending. Paginated. Not authenticated.
Query parameters:
* `channelId` - the id of the channel
* `fid` - (optional) and fid of a user to filter by
Returns: a `members` array:
* `fid` - the fid of the member
* `memberAt` - UNIX time when user became a member, in seconds
```json theme={"system"}
{
"result": {
"members": [
{
"fid": 466624,
"memberAt": 1712685183
},
{
"fid": 469283,
"memberAt": 1712685067
},
...
]
},
"next": { "cursor": "..." }
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/fc/channel-members?channelId=memes'
```
## Get Channel Invites
`GET /fc/channel-invites`
List outstanding invites to channels. Ordered by the time when the user was invited, descending. Paginated. Not authenticated.
There is max one outstanding invite per user (`invitedFid`) and channel (`channelId`).
Query parameters:
* `channelId` - (optional) an id of a channel to filter by
* `fid` - (optional) an fid of a user to filter by
Returns: an `invites` array:
* `channelId` - the id of the channel to which the user was inviter
* `invitedFid` - the fid of the user being invited
* `invitedAt` - UNIX time when user was invited, in seconds
* `inviterFid` - the fid of the user who created the invite
* `role` - the role the user was invited to, `member` or `moderator`
```json theme={"system"}
{
"result": {
"invites": [
{
"channelId": "coke-zero",
"invitedFid": 194,
"invitedAt": 1726879628,
"inviterFid": 18949,
"role": "member"
},
{
"channelId": "brain-teasers",
"invitedFid": 627785,
"invitedAt": 1726874566,
"inviterFid": 235128,
"role": "member"
},
...
]
},
"next": { "cursor": "..." }
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/fc/channel-invites?channelId=memes'
```
## Get Cast Moderation Actions
`GET /fc/moderated-casts`
List moderation actions. Ordered by the time when the action was taken, descending. Paginated. Not authenticated.
Query parameters:
* `channelId` - (optional) an id of a channel to filter by
Returns: a `moderationActions` array:
* `castHash` - hash of the cast that was moderated (including `0x` prefix)
* `channelId` - id of the channel in which the cast resides
* `action` - `hide` or `unhide`
* `moderatedAt` - UNIX time when moderation took place, in seconds
```json theme={"system"}
{
"result": {
"moderationActions": [
{
"castHash": "0x6b2253105ef8c1d1b984a5df87182b105a1f0b3a",
"channelId": "welcome",
"action": "hide",
"moderatedAt": 1727767637
},
...
]
},
"next": { "cursor": "..." }
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/fc/moderated-casts?channelId=welcome'
```
## Get Channel Restricted Users
`GET /fc/channel-restricted-users`
Get users restricted from joining channels via an invite link (they can still be manually invited). Users get into this state automatically after being removed as a member, and after being unbanned. Users get out of this state after being invited (even if they haven't accepted/declined the invite), and being banned. It is not possible to restrict or unrestrict users directly. Ordered by the time when the user was restricted, descending. Paginated. Not authenticated.
**Note:**
* Replies by restricted users are still visible below the fold
* This endpoint returns only actively restricted users. If a user was restricted and subsequently invited back or banned, the entry from this endpoint will disappear.
Query parameters:
* `channelId` - (optional) channel id to filter by
* `fid` - (optional) user fid to filter by
Returns: a `restrictedUsers` array:
* `fid` - the fid of the restricted user
* `channelId` - the id of the channel the user is restricted from
* `restrictedAt` - UNIX time when the restriction started, in seconds
```json theme={"system"}
{
"result": {
"restrictedUsers": [
{
"fid": 1234,
"channelId": "welcome",
"restrictedAt": 1727767637
},
...
]
},
"next": { "cursor": "..." }
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/fc/channel-restricted-users?channelId=memes'
```
## Get Channel Banned Users
`GET /fc/channel-bans`
Get users banned from channels. Ordered by the time when the user was banned, descending. Paginated. Not authenticated.
**Note:**
* This endpoint returns only active bans. If a user is unbanned, the entry from this endpoint will disappear.
Query parameters:
* `channelId` - (optional) channel id to filter by
* `fid` - (optional) user fid to filter by
Returns: a `bannedUsers` array:
* `fid` - the fid of the banned user
* `channelId` - the id of the channel the user is banned from
* `bannedAt` - UNIX time when the ban started, in seconds
```json theme={"system"}
{
"result": {
"bannedUsers": [
{
"fid": 1234,
"channelId": "welcome",
"bannedAt": 1727767637
},
...
]
},
"next": { "cursor": "..." }
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/fc/channel-bans?channelId=memes'
```
## Ban User From Channel
`POST /fc/channel-bans`
Ban a user from a channel. A banned user can no longer reply to channel casts, and all their existing replies are hidden. Authenticated.
The caller must own or moderate the channel.
Body parameters:
* `channelId` - the id of the channel to ban the user from
* `banFid` - the fid of the user to ban
Returns:
* `success: true`
Example:
```bash theme={"system"}
curl -X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ' \
-d '{ "channelId": "memes", "banFid": 1234 }' \
https://api.farcaster.xyz/fc/channel-bans
```
## Unban User From Channel
`DELETE /fc/channel-bans`
Unban a user from a channel. All the user's existing replies will become visible and they will be able to reply again (appearing below the fold). The user goes into restricted state. Authenticated.
The caller must own or moderate the channel.
Body parameters:
* `channelId` - the id of the channel to unban the user from
* `unbanFid` - the fid of the user to unban
Returns:
* `success: true`
Example:
```bash theme={"system"}
curl -X DELETE \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ' \
-d '{ "channelId": "memes", "banFid": 1234 }' \
https://api.farcaster.xyz/fc/channel-bans
```
## Follow/Unfollow Channel
Allows following and unfollowing a channel. Authenticated.
* `POST /fc/channel-follows`
* `DELETE /fc/channel-follows`
Body parameters:
* `channelId` - id of channel to follow or unfollow
**Examples**
```bash theme={"system"}
curl -X POST -H 'Content-Type: application/json' -H 'Authorization: Bearer ' -d '{ "channelId": "evm" }' https://api.farcaster.xyz/fc/channel-follows
curl -X DELETE -H 'Content-Type: application/json' -H 'Authorization: Bearer ' -d '{ "channelId": "evm" }' https://api.farcaster.xyz/fc/channel-follows
```
## Invite to Channel
`POST /fc/channel-invites`
Invites a user to be either a member or moderator of a channel. It is idempotent, i.e. if there is already an outstanding invite or the user already has the desired role, the API returns success. Authenticated.
The caller must own or moderate the channel.
**Note:** we can allowlist bots that are ok to be directly added as moderators to channels. Any channel owner can then add the bot as an active mod without the requirement that the bot is first a member or that it has to accept an invite. Let us know if you’d like your bot to be allowlisted.
Rate limit: 100 calls per caller per channel per hour
Body parameters:
* `channelId` - id of channel to invite user to
* `inviteFid` - fid of the user to invite
* `role` - either of:
* `member`: invites the user to be a member. The user must already follow either the channel or the user calling the endpoint. The caller must be a channel moderator or the channel owner.
* `moderator`: invites a user to be a moderator. The user must already be a channel member (i.e. has accepted a prior `member` invitation). The caller must be the channel owner. The number of active moderators + outstanding moderator invites cannot go above 10 (you'll get an error).
Returns:
* `success: true`
Example:
```bash theme={"system"}
curl -X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ' \
-d '{ "channelId": "evm", "inviteFid": 341234, "role": "member" }' \
https://api.farcaster.xyz/fc/channel-invites
```
## Remove from Channel
`DELETE /fc/channel-invites`
Removes a user from a channel role, including revoking any outstanding unaccepted invite. It is idempotent, i.e. if the user does not have an invite for the role or does not have the role, the API returns success. Authenticated.
The caller must own or moderate the channel.
Rate limit: 100 calls per caller per channel per hour
Body parameters:
* `channelId` - id of channel to remove user from
* `removeFid` - fid of the user to remove
* `role` - either of:
* `member`: removes user from member role or revokes an existing member invitation. If the user was removed, the user is blocked from becoming a member again via a link (i.e. to become a member again, they have to be invited by a moderator). If only an invitation was revoked, the user is not blocked from becoming a member via a link. The caller must be a channel moderator or the channel owner.
* `moderator`: removes user from moderator role or revokes an existing moderator invitation. The caller must be the channel owner.
Returns:
* `success: true`
Example:
```bash theme={"system"}
curl -X DELETE \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ' \
-d '{ "channelId": "evm", "removeFid": 341234, "role": "member" }' \
https://api.farcaster.xyz/fc/channel-invites
```
## Respond to Channel Invite
`PATCH /fc/channel-invites`
Accept or decline an outstanding member/moderator channel invite. Once an invite has been accepted or declined, the response cannot be changed. Authenticated.
The caller must have an active invite.
Body parameters:
* `channelId` - id of channel to which user was invited
* `role` - the role of the invite, either `member` or `moderator`
* `accept` - boolean, `true` or `false`, indicating accept/decline
Returns:
* `success: true`
Example:
```bash theme={"system"}
curl -X PATCH \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ' \
-d '{ "channelId": "evm", "role": "member", "accept": true }' \
https://api.farcaster.xyz/fc/channel-invites
```
## Moderate Channel Cast
`POST /fc/moderated-casts`
Moderate (hide/unhide) a specific channel cast. Authenticated.
The cast must be in a channel the caller moderates or owns.
Body parameters:
* `castHash` - the hash of the cast (including `0x` prefix)
* `action` - either `hide` or `unhide`
Returns:
* `success: true`
Example:
```bash theme={"system"}
curl -X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ' \
-d '{ "castHash": "0x2694aa649f3608bd11fe6621946782d1eb0ae3c4", "action": "hide" }' \
https://api.farcaster.xyz/fc/moderate-cast
```
## Pin Channel Cast
`PUT /fc/pinned-casts`
Pin (and optionally announce) a cast to a channel, replacing any currently pinned cast. If the provided cast is already pinned, nothing changes (and success is returned). Authenticated.
Body parameters:
* `castHash` - the hash of the cast to pin (including `0x` prefix)
* `notifyChannelFollowers` - (optional) `true` / `false` whether to send an announcement notification to all channel followers about the pinned cast. Defaults to `false`
Returns:
* `success: true`
Example:
```bash theme={"system"}
curl -X PUT \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ' \
-d '{ "castHash": "0x2694aa649f3608bd11fe6621946782d1eb0ae3c4", "notifyChannelFollowers": true }' \
https://api.farcaster.xyz/fc/pinned-casts
```
## Unpin Channel Cast
`DELETE /fc/pinned-casts`
Unpin a cast from a channel. Does not remove existing announcement notifications. Authenticated.
Body parameters (**provide only one of the two**):
* `castHash` - the hash of the cast to unpin (including `0x` prefix). Will unpin only if this exact cast is pinned.
* `channelId` - the id of the channel to unpin from. Will unpin any pinned cast from the channel.
Returns:
* `success: true`
Example:
```bash theme={"system"}
curl -X DELETE \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ' \
-d '{ "channelId": "welcome" }' \
https://api.farcaster.xyz/fc/pinned-casts
```
## Get Farcaster actions
`GET /v2/discover-actions`
Retrieve a list of Farcaster actions. Paginated. Not authenticated.
Query parameters:
* `list` - the list to retrieve. Must be `'top'`, a list ordered by total users.
Returns: an array of `action` objects with properties:
* `name` - action name
* `icon` - an [Octicon](https://primer.style/foundations/icons) identifying the action
* `description` - a short text description of the action
* `aboutUrl` - external link to a page with additional instructions or source code
* `actionUrl` - action metadata URL. Clients retrieve metadata with a GET to this URL.
* `action.actionType` - action type, only `'post'`
* `action.postUrl` - action POST URL. Clients POSt signed messages to this URL.
```json theme={"system"}
{
"result": {
"actions": [
{
"name": "Upthumb",
"icon": "thumbsup",
"description": "Give casts 'upthumbs' and see them on a leaderboard.",
"aboutUrl": "https://github.com/horsefacts/upthumbs",
"actionUrl": "https://upthumbs.app/api/upthumb",
"action": {
"actionType": "post",
"postUrl": "https://upthumbs.app/api/upthumb"
}
},
...
]
},
"next": { "cursor": "..." }
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/v2/discover-actions?list=top&limit=10'
```
## Get Blocked Users
`GET /fc/blocked-users`
The Farcaster client allows users to block others from replying, quoting and mentioning them.
This endpoint provides access to all blocked users. Paginated, in reverse chronological order by block creation time. Not authenticated.
Query parameters:
* `blockerFid` (**optional**) - limit the response to only blocks by a specific user
Returns: a `blockedUsers` array with properties:
* `blockerFid` - the user who created the block
* `blockedFid` - the user who is blocked (cannot reply to, quote or mention the `blockerFid`)
* `createdAt` - UNIX time when channel was created, in seconds
```json theme={"system"}
{
"result": {
"blockedUsers": [
{
"blockerFid": 5,
"blockedFid": 10,
"createdAt": 1724854521
},
...
]
},
"next": { "cursor": "..." }
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/fc/blocked-users'
```
## Block User
`POST /fc/blocked-users`
Block a user. Authenticated.
Body parameters:
* `blockFid` - the fid of the user to block
Returns:
* `success: true`
Example:
```bash theme={"system"}
curl -X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ' \
-d '{ "blockFid": 1234 }' \
https://api.farcaster.xyz/fc/blocked-users
```
## Unblock User
`DELETE /fc/blocked-users`
Unblock a user. Authenticated.
Body parameters:
* `unblockFid` - the fid of the user to unblock
Returns:
* `success: true`
Example:
```bash theme={"system"}
curl -X DELETE \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer ' \
-d '{ "unblockFid": 1234 }' \
https://api.farcaster.xyz/fc/blocked-users
```
## Get Account Verifications
`GET /fc/account-verifications`
### This endpoint is not stable (beta) and likely to have breaking changes in the future or get depracated.
List of account verifications attested by the Farcaster client. Ordered by the time when the verification occurred, descending. Paginated. Not authenticated.
Query parameters:
* `fid` (**optional**) - limit the response to specific user
* `platform` (**optional**) - limit the response to specific platform `'x' | 'github' | 'discord'`. Defaults to `'x'` for backwards compatibility.
Returns: a `verifications` array:
* `fid` - account Farcaster id
* `platform` - platform of the verification `'x' | 'github' | 'discord'`
* `platformId` - string value representing platform identifier (generated by the platform, not the Farcaster client)
* `platformUsername` - string value representing platform username
* `verifiedAt` - UNIX time when verification took place, in seconds
```json theme={"system"}
{
"result": {
"verifications": [
{
"fid": 3,
"platform": "x",
"platformId": "9615352",
"platformUsername": "dwr",
"verifiedAt": 1728505748
},
{
"fid": 3,
"platform": "github",
"platformId": "86492",
"platformUsername": "danromero",
"verifiedAt": 1728505748
},
...
]
},
"next": { "cursor": "..." }
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/fc/account-verifications'
```
## Get creator reward winners
`GET /v1/creator-rewards-winner-history`
The Farcaster client gives out weekly rewards to top creators on the network.
This endpoint provides access to all winners for a given period (week). Paginated, with the list of winners in rank order. Not authenticated.
Data is refreshed every Tuesday at 17:00 UTC.
Query parameters:
* `periodsAgo` (**optional**) - how many periods ago to fetch the results for. 0 or undefined returns results for the most recent period.
Returns:
* `history`: Object containing:
* `periodStartTimestamp`: Unix time in milliseconds when rewards period began
* `periodEndTimestamp`: Unix time in milliseconds when rewards period ended
* `winners`: Paginated list of fid, username, score, rank, wallet address (optional) and reward amount in rank order. A missing wallet address indicates that the user does not have a verified wallet on the Farcaster Client.
* `next`: Pagination cursor
```json theme={"system"}
{
"result": {
"history": {
"periodStartTimestamp": 1738080000000,
"periodEndTimestamp": 1738684800000,
"winners": [
{
"fid": 12345,
"score": 50000,
"rank": 1,
"rewardCents": 60000,
"username": "alice.eth",
"walletAddress": "0x0000000000000000000000000000000000000001"
},
{
"fid": 67890,
"score": 45000,
"rank": 2,
"rewardCents": 60000,
"username": "bob",
"walletAddress": "0x0000000000000000000000000000000000000002"
},
...
]
}
},
"next": { "cursor": "..." }
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/v1/creator-rewards-winner-history'
```
## Get developer reward winners
`GET /v1/developer-rewards-winner-history`
The Farcaster client gives out weekly rewards to top developers on the network.
This endpoint provides access to all winners for a given period (week). Paginated, with the list of winners in rank order. Not authenticated.
Data is refreshed every Wednesday at 17:00 UTC.
Query parameters:
* `periodsAgo` (**optional**) - how many periods ago to fetch the results for. 0 or undefined returns results for the most recent period.
Returns:
* `periodStartTimestamp`: Unix time in milliseconds when rewards period began
* `periodEndTimestamp`: Unix time in milliseconds when rewards period ended
* `winners`: Paginated list of fid, domain, frame (mini app) name, score, rank, wallet address (optional) and reward amount in rank order. A missing wallet address indicates that the user does not have a verified wallet on the Farcaster client.
```json theme={"system"}
{
"result": {
"periodStartTimestamp": 1738080000000,
"periodEndTimestamp": 1738684800000,
"winners": [
{
"fid": 1,
"domain": "example.com",
"frameName": "App Name",
"score": 10,
"rank": 1,
"rewardCents": 1000,
"walletAddress": "0x0000000000000000000000000000000000000000",
},
{
"fid": 420,
"domain": "example.com",
"frameName": "App Name",
"score": 1,
"rank": 2,
"rewardCents": 500,
"walletAddress": "0x0000000000000000000000000000000000000001",
},
...
]
},
"next": { "cursor": "..." }
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/v1/developer-rewards-winner-history'
```
## Get User Primary Address
`GET /fc/primary-address?fid=12152&protocol=ethereum`
Fetch the primary address from the user. This is picked by the user whenever they expressed a preference, or picked by the Farcaster client.
Query parameters:
* `fid` - the fid of the user to fetch the primary address for
* `protocol` - the protocol of the address to fetch. Both `ethereum` and `solana` are supported.
Returns:
* `address` - the primary address of the user
```json theme={"system"}
{
"result": {
"address": {
"fid": 12152,
"protocol": "ethereum",
"address": "0x0BD6b1DFE1eA61C2b487806ECd06b5A95383a4e3"
}
}
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/fc/primary-address?fid=12152&protocol=ethereum'
```
## Get Multiple User Primary Addresses
`GET /fc/primary-addresses`
Fetch primary addresses for multiple users at once. This is a batch version of the single primary address endpoint. For each FID, this returns the primary address picked by the user whenever they expressed a preference, or picked by the Farcaster client.
Query parameters:
* `fids` - comma-separated list of FIDs to fetch primary addresses for
* `protocol` - the protocol of the addresses to fetch. Both `ethereum` and `solana` are supported.
For now, only 100 FIDs can be fetched at once. We can change this quickly if needed (just reach out to us).
Returns:
* `addresses` - an array of address results, one for each requested FID:
* `fid` - the FID that was requested
* `success` - boolean indicating whether the address was found
* `address` - (only present when `success` is true) object containing:
* `fid` - the FID of the user
* `protocol` - the protocol of the address (`ethereum` or `solana`)
* `address` - the primary address string
```json theme={"system"}
{
"result": {
"addresses": [
{
"fid": 12152,
"success": true,
"address": {
"fid": 12152,
"protocol": "ethereum",
"address": "0x0BD6b1DFE1eA61C2b487806ECd06b5A95383a4e3"
}
},
{
"fid": 2,
"success": true,
"address": {
"fid": 2,
"protocol": "ethereum",
"address": "0x661E2209B9C6B06C1F32A0639f60D3294185ab35"
}
},
{
"fid": 1315,
"success": true,
"address": {
"fid": 1315,
"protocol": "ethereum",
"address": "0x0450a8545028547Df4129Aa5b4EC5794D5aF2409"
}
},
{
"fid": 39939393939,
"success": false
}
]
}
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/fc/primary-addresses?fids=12152,2,1315,39939393939&protocol=ethereum'
```
## Get Starter Pack Members
`GET /fc/starter-pack-members`
Starter pack members. Ordered by the time when they were added to the pack, descending. Paginated. Not authenticated.
Query parameters:
* `id` - starter pack id found as a part of the public pack URL or in the non-authed public API of starter pack metadata.
Returns: a `members` array:
* `fid` - account Farcaster id
* `memberAt` - time when member was added to the starter pack, in milliseconds
```json theme={"system"}
{
"result": {
"members": [
{
"fid": 3,
"memberAt": 1740172669691
},
{
"fid": 296646,
"memberAt": 1740172669691
},
...
]
},
"next": { "cursor": "..." }
}
```
Example:
```bash theme={"system"}
curl 'https://api.farcaster.xyz/fc/starter-pack-members?id=Underrated-CT-1y7n9b'
```
# Direct Casts
Source: https://docs.neynar.com/farcaster/reference/farcaster/direct-casts
This page documents public APIs provided by the official Farcaster client for direct casts. Direct casts are currently not part of the protocol. There are plans to add direct casts to the protocol later this year.
#### Send / write API for direct casts
* [Send direct casts via API](https://www.notion.so/farcaster/Public-Programmable-DCs-v1-50d9d99e34ac4d10add55bd26a91804f)
* The above link also provides information on how to obtain direct cast API keys
#### Direct cast intents
Intents enable developers to direct authenticated users to a pre-filled direct cast composer via a URL.
```bash theme={"system"}
https://farcaster.xyz/~/inbox/create/[fid]?text=[message]
https://farcaster.xyz/~/inbox/create/1?text=gm
```
# Farcaster Client Embeds Reference
Source: https://docs.neynar.com/farcaster/reference/farcaster/embeds
The Farcaster client follows the [Open Graph protocol](https://ogp.me) when rendering rich previews for URL embeds.
Developers can reset existing embed caches on the Farcaster client at [https://farcaster.xyz/\~/developers/embeds](https://farcaster.xyz/~/developers/embeds).
This page is not for debugging [mini apps](https://miniapps.farcaster.xyz/docs/specification).
#### Additional details
* Resetting an embed cache, does not reset Open Graph image caches. If you are experiencing a stale image on your Open Graph, change the image file path served as the `og:image`.
* Developers have to be logged in to the Farcaster client to access this page.
# Farcaster Intent URLs
Source: https://docs.neynar.com/farcaster/reference/farcaster/intent-urls
## Cast Intent URLs
Farcaster intents enable builders to direct authenticated users to a pre-filled cast composer.
If you're building a Mini App and want to prompt the user to compose a cast, use the [composeCast action](/miniapps/sdk/actions/compose-cast) from the Mini App SDK.
#### Compose with cast text
```
https://farcaster.xyz/~/compose?text=Hello%20world!
```
#### Compose with cast text and one embed
```
https://farcaster.xyz/~/compose?text=Hello%20world!&embeds[]=https://farcaster.xyz
```
#### Compose with cast text with mentions and two embeds
```
https://farcaster.xyz/~/compose?text=Hello%20@farcaster!&embeds[]=https://farcaster.xyz&embeds[]=https://github.com/farcasterxyz/protocol
```
#### Compose with cast text on a specific channel
```
https://farcaster.xyz/~/compose?text=Hello%20world!&channelKey=farcaster
```
#### Reply with cast text to a cast with hash
```
https://farcaster.xyz/~/compose?text=Looks%20good!&parentCastHash=0x09455067393562d3296bcbc2ec1c2d6bba8ac1f1
```
#### Additional details
* Embeds are any valid URLs
* URLs ending with `.png` `.jpg` or `.gif` will render as an image embed
* Embedding the URL to a Zora mint page will render the NFT with a mint link below it.
* You can check how embeds will be rendered in Warpcast at [https://farcaster.xyz/\~/developers/embeds](https://farcaster.xyz/~/developers/embeds)
## Resource URLs
#### View profile by FID
```
https://farcaster.xyz/~/profiles/:fid
```
#### View cast by hash
```
https://farcaster.xyz/~/conversations/:hash
```
# Signer Requests
Source: https://docs.neynar.com/farcaster/reference/farcaster/signer-requests
If your application wants to write data to Farcaster on behalf of a user, the application must get the user to add a signing key for their application.
## Guide
### Prerequisites
* a registered FID
### 1. An authenticated user clicks "Connect with Farcaster" in your app
Once your app can identify and authenticate a user you can present them with
the option to connect with Farcaster.
### 2. Generate a new Ed25519 key pair for the user and SignedKeyRequest signature
Your app should generate and securely store an Ed25519 associated with this
user. In the next steps, you will prompt the user to approve this keypair to
sign Farcaster messages on their behalf.
It's important that:
* the private key is stored securely and never exposed
* the key pair can be retrieved and used to sign messages when the user returns
In addition to generating a new key pair, your application must produce an
ECDSA signature using the custody address of its FID. This allows the key to be
attributed to the application which is useful for a wide range of things from
knowing which apps are being used to filtering content based on the
applications that generated them.
**Example code:**
```ts theme={"system"}
import * as ed from '@noble/ed25519';
import { mnemonicToAccount, signTypedData } from 'viem/accounts';
/*** EIP-712 helper code ***/
const SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN = {
name: 'Farcaster SignedKeyRequestValidator',
version: '1',
chainId: 10,
verifyingContract: '0x00000000fc700472606ed4fa22623acf62c60553',
} as const;
const SIGNED_KEY_REQUEST_TYPE = [
{ name: 'requestFid', type: 'uint256' },
{ name: 'key', type: 'bytes' },
{ name: 'deadline', type: 'uint256' },
] as const;
/*** Generating a keypair ***/
const privateKey = ed.utils.randomPrivateKey();
const publicKeyBytes = await ed.getPublicKey(privateKey);
const key = '0x' + Buffer.from(publicKeyBytes).toString('hex');
/*** Generating a Signed Key Request signature ***/
const appFid = process.env.APP_FID;
const account = mnemonicToAccount(process.env.APP_MNEMONIC);
const deadline = Math.floor(Date.now() / 1000) + 86400; // signature is valid for 1 day
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,
deadline: BigInt(deadline),
},
});
```
**Deadline**
This value controls how long the Signed Key Request signature is valid for. It
is a Unix timestamp in second (note: JavaScript uses millisecond). We recommend
setting this to 24 hours.
### 3. App uses the public key + SignedKeyRequest signature to initiate a Signed Key Request using the Farcaster client API
The app calls the Farcaster client backend which returns a deeplink and a session token that can be used to check the status of the request.
```ts theme={"system"}
/*** Creating a Signed Key Request ***/
const farcasterClientApi = 'https://api.farcaster.xyz';
const { token, deeplinkUrl } = await axios
.post(`${farcasterClientApi}/v2/signed-key-requests`, {
key,
requestFid: appFid,
signature,
deadline,
})
.then((response) => response.data.result.signedKeyRequest);
// deeplinkUrl should be presented to the user
// token should be used to poll
```
**Redirect URL**
If this request is being made from a native mobile application that can open
deeplinks, you can include a redirectUrl that the user should be brought back
to after they complete the request.
Note: if your app is PWA or web app do not include this value as the user will
brought to a session that has no state.
**\*Sponsorships**
You can sponsor the onchain fees for the user. See [Sponsoring a signer](#sponsoring-a-signer) below.
### 4. Application presents the deep link from the response to the user
The app presents the deeplink which will prompt the user to open the Farcaster client app and authorize the signer request (screenshots at the bottom). The app should direct the user to open the link on their mobile device they have the Farcaster client installed on:
1. when on mobile, trigger the deeplink directly
2. when on web, display the deeplink as a QR code to scan
**Example code**
```ts theme={"system"}
import QRCode from 'react-qr-code';
const DeepLinkQRCode = (deepLinkUrl) => ;
```
### 5. Application begins polling Signer Request endpoint using token
Once the user has been presented the deep link, the application must wait for
the user to complete the signer request flow. The application can poll the
signer request resource and look for the data that indicates the user has
completed the request:
````ts theme={"system"}
const poll = async (token: string) => {
while (true) {
// sleep 1s
await new Promise((r) => setTimeout(r, 2000));
console.log('polling signed key request');
const signedKeyRequest = await axios
.get(`${farcasterClientApi}/v2/signed-key-request`, {
params: {
token,
},
})
.then((response) => response.data.result.signedKeyRequest);
if (signedKeyRequest.state === 'completed') {
console.log('Signed Key Request completed:', signedKeyRequest);
/**
* At this point the signer has been registered onchain and you can start submitting
* messages to hubs signed by its key:
* ```
* const signer = Ed25519Signer.fromPrivateKey(privateKey)._unsafeUnwrap();
* const message = makeCastAdd(..., signer)
* ```
*/
break;
}
}
};
poll(token);
````
### 6. User opens the link and completes Signer Request flow in the Farcaster client app
When the user approves the request in the client app, an onchain transaction will be
made that grants write permissions to that signer. Once that completes your app
should indicate success and can being writing messages using the newly added key.
### Reference implementation
````ts theme={"system"}
import * as ed from '@noble/ed25519';
import { Hex } from 'viem';
import { mnemonicToAccount } from 'viem/accounts';
import axios from 'axios';
import * as qrcode from 'qrcode-terminal';
/*** EIP-712 helper code ***/
const SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN = {
name: 'Farcaster SignedKeyRequestValidator',
version: '1',
chainId: 10,
verifyingContract: '0x00000000fc700472606ed4fa22623acf62c60553',
} as const;
const SIGNED_KEY_REQUEST_TYPE = [
{ name: 'requestFid', type: 'uint256' },
{ name: 'key', type: 'bytes' },
{ name: 'deadline', type: 'uint256' },
] as const;
(async () => {
/*** Generating a keypair ***/
const privateKey = ed.utils.randomPrivateKey();
const publicKeyBytes = await ed.getPublicKey(privateKey);
const key = '0x' + Buffer.from(publicKeyBytes).toString('hex');
/*** Generating a Signed Key Request signature ***/
const appFid = process.env.APP_FID;
const account = mnemonicToAccount(process.env.APP_MNEMONIC);
const deadline = Math.floor(Date.now() / 1000) + 86400; // signature is valid for 1 day
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,
deadline: BigInt(deadline),
},
});
/*** Creating a Signed Key Request ***/
const farcasterClientApi = 'https://api.farcaster.xyz';
const { token, deeplinkUrl } = await axios
.post(`${farcasterClientApi}/v2/signed-key-requests`, {
key,
requestFid: appFid,
signature,
deadline,
})
.then((response) => response.data.result.signedKeyRequest);
qrcode.generate(deeplinkUrl, console.log);
console.log('scan this with your phone');
console.log('deep link:', deeplinkUrl);
const poll = async (token: string) => {
while (true) {
// sleep 1s
await new Promise((r) => setTimeout(r, 2000));
console.log('polling signed key request');
const signedKeyRequest = await axios
.get(`${farcasterClientApi}/v2/signed-key-request`, {
params: {
token,
},
})
.then((response) => response.data.result.signedKeyRequest);
if (signedKeyRequest.state === 'completed') {
console.log('Signed Key Request completed:', signedKeyRequest);
/**
* At this point the signer has been registered onchain and you can start submitting
* messages to hubs signed by its key:
* ```
* const signer = Ed25519Signer.fromPrivateKey(privateKey)._unsafeUnwrap();
* const message = makeCastAdd(..., signer)
* ```
*/
break;
}
}
};
await poll(token);
})();
````
## API
### POST /v2/signed-key-request
Create a signed key requests.
**Body:**
* `key` - hex string of the Ed25519 public key
* `requestFid` - fid of the requesting application
* `deadline` - Unix timestamp signature is valid until
* `signature` - [SignedKeyRequest](https://docs.farcaster.xyz/reference/contracts/reference/signed-key-request-validator#signed-key-request-validator) signature from the requesting application
* `redirectUrl` - Optional. Url to redirect to after the signer is approved. Note: this should only be used when requesting a signer from a native mobile application.
* `sponsorship` -
**Sample response:**
```json theme={"system"}
{
"result": {
"signedKeyRequest": {
"token": "0xa241e6b1287a07f4d3f9c5bd",
"deeplinkUrl": "farcaster://signed-key-request?token=0xa241e6b1287a07f4d3f9c5bd",
"key": "0x48b0c7a6deff69bad7673357df43274f3a08163a6440b7a7e3b3cb6b6623faa7",
"state": "pending"
}
}
}
```
### GET /v2/signed-key-request
Get the state of a signed key requests.
**Query parameters:**
* `token` - token identifying the request
**Response:**
* `token` - token identifying the request
* `deeplinkUrl` - URL where the user can complete the request
* `key` - requested key to add
* `state` - state of the request: `pending` - no action taken by user, `approved` - user has approved but onchain transaction is not confirmed, `completed` - onchain transaction is confirmed
**Sample response in pending state:**
```json theme={"system"}
{
"result": {
"signedKeyRequest": {
"token": "0xa241e6b1287a07f4d3f9c5bd",
"deeplinkUrl": "farcaster://signed-key-request?token=0xa241e6b1287a07f4d3f9c5bd"
"key": "0x48b0c7a6deff69bad7673357df43274f3a08163a6440b7a7e3b3cb6b6623faa7",
"state": "pending"
}
}
}
```
**Sample response after approval but before transaction is confirmed:**
```json theme={"system"}
{
"result": {
"signedKeyRequest": {
"token": "0xa241e6b1287a07f4d3f9c5bd",
"deeplinkUrl": "farcaster://signed-key-request?token=0xa241e6b1287a07f4d3f9c5bd"
"key": "0x48b0c7a6deff69bad7673357df43274f3a08163a6440b7a7e3b3cb6b6623faa7",
"state": "approved",
"userFid": 1,
}
}
}
```
**Sample response after after transaction is confirmed:**
```json theme={"system"}
{
"result": {
"signedKeyRequest": {
"token": "0xa241e6b1287a07f4d3f9c5bd",
"deeplinkUrl": "farcaster://signed-key-request?token=0xa241e6b1287a07f4d3f9c5bd",
"key": "0x48b0c7a6deff69bad7673357df43274f3a08163a6440b7a7e3b3cb6b6623faa7",
"state": "completed",
"userFid": 1,
}
}
}
```
## Sponsoring a signer
An application can sponsor a signer so that the user doesn’t need to pay. The
application must be signed up on the Farcaster client app and have a warps ≥ 100.
When generating a signed key request an application can indicate it should be
sponsored by including an additional `sponsorship` field in the request body.
```ts theme={"system"}
type SignedKeyRequestSponsorship = {
sponsorFid: number;
signature: string; // sponsorship signature by sponsorFid
};
type SignedKeyRequestBody = {
key: string;
requestFid: number;
deadline: number;
signature: string; // key request signature by requestFid
sponsorship?: SignedKeyRequestSponsorship;
};
```
To create a `SignedKeyRequestSponsorship`:
1. create the key pair and have the requesting FID generate a signature
2. create a second signature from the sponsoring FID using the signature generated in step 1 as the raw input to an EIP-191 message
```ts theme={"system"}
// sponsoringAccount is Viem account instance for the sponsoring FID's custody address
// signedKeyRequestSignature is the EIP-712 signature signed by the requesting FID
const sponsorSignature = sponsoringAccount.signMessage({
message: { raw: signedKeyRequestSignature },
});
```
When the user opens the signed key request in the Farcaster client they will the onchain fees have been sponsored by your application.
# Signers
Source: https://docs.neynar.com/farcaster/reference/farcaster/signers
If your application wants to write data to Farcaster on behalf of a user, the application must get the user to add a signing key for their application.
## Guide
#### Prerequisites
* a registered FID
#### 1. An authenticated user clicks "Connect with Farcaster" in your app
Your app should be able to identify and authenticate a user before presenting
them with the option to Connect with Farcaster.
#### 2. Generate a new Ed25519 key pair for the user and SignedKeyRequest signature
Your app should generate and securely store an Ed25519 associated with this
user. In the next steps, you will prompt the user to approve this keypair to
signer messages on their behalf.
Since this keypair can write to the protocol on behalf of the user it's
important that:
* the private key is stored securely and never exposed
* the key pair can be retrieved and used to sign messages when the user returns
In addition to generating a new key pair, your application must produce an
ECDSA signature using the custody address of its FID. This allows the key to be
attributed to the application which is useful for a wide range of things from
knowing which apps are being used to filtering content based on the
applications that generated them.
**Example code:**
```ts theme={"system"}
import * as ed from '@noble/ed25519';
import { mnemonicToAccount, signTypedData } from 'viem/accounts';
/*** EIP-712 helper code ***/
const SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN = {
name: 'Farcaster SignedKeyRequestValidator',
version: '1',
chainId: 10,
verifyingContract: '0x00000000fc700472606ed4fa22623acf62c60553',
} as const;
const SIGNED_KEY_REQUEST_TYPE = [
{ name: 'requestFid', type: 'uint256' },
{ name: 'key', type: 'bytes' },
{ name: 'deadline', type: 'uint256' },
] as const;
/*** Generating a keypair ***/
const privateKey = ed.utils.randomPrivateKey();
const publicKeyBytes = await ed.getPublicKey(privateKey);
const key = '0x' + Buffer.from(publicKeyBytes).toString('hex');
/*** Generating a Signed Key Request signature ***/
const appFid = process.env.APP_FID;
const account = mnemonicToAccount(process.env.APP_MNENOMIC);
const deadline = Math.floor(Date.now() / 1000) + 86400; // signature is valid for 1 day
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,
deadline: BigInt(deadline),
},
});
```
**Deadline**
This value controls how long the Signed Key Request signature is valid for. It
is a Unix timestamp in second (note: JavaScript uses millisecond). We recommend
setting this to 24 hours.
#### 3. App uses the public key + SignedKeyRequest signature to initiate a Signed Key Request using the Farcaster API
The app calls the Farcaster backend which returns a deeplink and a session token that can be used to check the status of the request.
```ts theme={"system"}
/*** Creating a Signed Key Request ***/
const farcasterApi = 'https://api.farcaster.com';
const { token, deeplinkUrl } = await axios
.post(`${farcasterApi}/v2/signed-key-requests`, {
key: publicKey,
requestFid: fid,
signature,
deadline,
})
.then((response) => response.data.result.signedKeyRequest);
// deeplinkUrl should be presented to the user
// token should be used to poll
```
**Redirect URL**
If this request is being made from a native mobile application that can open
deeplinks, you can include a redirectUrl that the user should be brought back
to after they complete the request.
Note: if your app is PWA or web app do not include this value as the user will
brought to a session that has no state.
**\*Sponsorships**
You can sponsor the onchain fees for the user. See [Sponsoring a signer](#sponsoring-a-signer) below.
#### 4. Application presents the deep link from the response to the user
The app presents the deeplink which will prompt the user to open the Farcaster
app and authorize the signer request (screenshots at the bottom). The app
should direct the user to open the link on their mobile device they have
Farcaster installed on:
1. when on mobile, trigger the deeplink directly
2. when on web, display the deeplink as a QR code to scan
**Example code**
```ts theme={"system"}
import QRCode from 'react-qr-code';
const DeepLinkQRCode = (deepLinkUrl) => ;
```
#### 5. Application begins polling Signer Request endpoint using token
Once the user has been presented the deep link, the application must wait for
the user to complete the signer request flow. The application can poll the
signer request resource and look for the data that indicates the user has
completed the request:
````ts theme={"system"}
const poll = async (token: string) => {
while (true) {
// sleep 1s
await new Promise((r) => setTimeout(r, 2000));
console.log('polling signed key request');
const signedKeyRequest = await axios
.get(`${farcasterApi}/v2/signed-key-request`, {
params: {
token,
},
})
.then((response) => response.data.result.signedKeyRequest);
if (signedKeyRequest.state === 'completed') {
console.log('Signed Key Request completed:', signedKeyRequest);
/**
* At this point the signer has been registered onchain and you can start submitting
* messages to hubs signed by its key:
* ```
* const signer = Ed25519Signer.fromPrivateKey(privateKey)._unsafeUnwrap();
* const message = makeCastAdd(..., signer)
* ```
*/
break;
}
}
};
poll(token);
````
#### 6. User opens the link and completes Signer Request flow in Farcaster
When the user approves the request in Farcaster, an onchain transaction will be
made that grants write permissions to that signer. Once that completes your app
should indicate success and can begin writing messages using the newly added key.
#### Reference implementation
````ts theme={"system"}
import * as ed from '@noble/ed25519';
import { Hex } from 'viem';
import { mnemonicToAccount } from 'viem/accounts';
import axios from 'axios';
import * as qrcode from 'qrcode-terminal';
/*** EIP-712 helper code ***/
const SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN = {
name: 'Farcaster SignedKeyRequestValidator',
version: '1',
chainId: 10,
verifyingContract: '0x00000000fc700472606ed4fa22623acf62c60553',
} as const;
const SIGNED_KEY_REQUEST_TYPE = [
{ name: 'requestFid', type: 'uint256' },
{ name: 'key', type: 'bytes' },
{ name: 'deadline', type: 'uint256' },
] as const;
(async () => {
/*** Generating a keypair ***/
const privateKey = ed.utils.randomPrivateKey();
const publicKeyBytes = await ed.getPublicKey(privateKey);
const key = '0x' + Buffer.from(publicKeyBytes).toString('hex');
/*** Generating a Signed Key Request signature ***/
const appFid = process.env.APP_FID;
const account = mnemonicToAccount(process.env.APP_MNEMONIC);
const deadline = Math.floor(Date.now() / 1000) + 86400; // signature is valid for 1 day
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,
deadline: BigInt(deadline),
},
});
/*** Creating a Signed Key Request ***/
const farcasterApi = 'https://api.farcaster.com';
const { token, deeplinkUrl } = await axios
.post(`${farcasterApi}/v2/signed-key-requests`, {
key,
requestFid: appFid,
signature,
deadline,
})
.then((response) => response.data.result.signedKeyRequest);
qrcode.generate(deeplinkUrl, console.log);
console.log('scan this with your phone');
console.log('deep link:', deeplinkUrl);
const poll = async (token: string) => {
while (true) {
// sleep 1s
await new Promise((r) => setTimeout(r, 2000));
console.log('polling signed key request');
const signedKeyRequest = await axios
.get(`${farcasterApi}/v2/signed-key-request`, {
params: {
token,
},
})
.then((response) => response.data.result.signedKeyRequest);
if (signedKeyRequest.state === 'completed') {
console.log('Signed Key Request completed:', signedKeyRequest);
/**
* At this point the signer has been registered onchain and you can start submitting
* messages to hubs signed by its key:
* ```
* const signer = Ed25519Signer.fromPrivateKey(privateKey)._unsafeUnwrap();
* const message = makeCastAdd(..., signer)
* ```
*/
break;
}
}
};
await poll(token);
})();
````
## API
#### POST /v2/signed-key-request
Create a signed key requests.
**Body:**
* `key` - hex string of the Ed25519 public key
* `requestFid` - fid of the requesting application
* `deadline` - Unix timestamp signature is valid until
* `signature` - [SignedKeyRequest](https://docs.farcaster.xyz/reference/contracts/reference/signed-key-request-validator#signed-key-request-validator) signature from the requesting application
* `redirectUrl` - Optional. Url to redirect to after the signer is approved. Note: this should only be used when requesting a signer from a native mobile application.
* `sponsorship` -
**Sample response:**
```json theme={"system"}
{
"result": {
"signedKeyRequest": {
"token": "0xa241e6b1287a07f4d3f9c5bd",
"deeplinkUrl": "farcaster://signed-key-request?token=0xa241e6b1287a07f4d3f9c5bd"
"key": "0x48b0c7a6deff69bad7673357df43274f3a08163a6440b7a7e3b3cb6b6623faa7",
"state": "pending"
}
}
}
```
#### GET /v2/signed-key-request
Get the state of a signed key requests.
**Query parameters:**
* `token` - token identifying the request
**Response:**
* `token` - token identifying the request
* `deeplinkUrl` - URL where the user can complete the request
* `key` - requested key to add
* `state` - state of the request: `pending` - no action taken by user, `approved` - user has approved but onchain transaction is not confirmed, `completed` - onchain transaction is confirmed
**Sample response in pending state:**
```json theme={"system"}
{
"result": {
"signedKeyRequest": {
"token": "0xa241e6b1287a07f4d3f9c5bd",
"deeplinkUrl": "farcaster://signed-key-request?token=0xa241e6b1287a07f4d3f9c5bd"
"key": "0x48b0c7a6deff69bad7673357df43274f3a08163a6440b7a7e3b3cb6b6623faa7",
"state": "pending"
}
}
}
```
**Sample response after approval but before transaction is confirmed:**
```json theme={"system"}
{
"result": {
"signedKeyRequest": {
"token": "0xa241e6b1287a07f4d3f9c5bd",
"deeplinkUrl": "farcaster://signed-key-request?token=0xa241e6b1287a07f4d3f9c5bd"
"key": "0x48b0c7a6deff69bad7673357df43274f3a08163a6440b7a7e3b3cb6b6623faa7",
"state": "approved",
"userFid": 1,
}
}
}
```
**Sample response after transaction is confirmed:**
```json theme={"system"}
{
"result": {
"signedKeyRequest": {
"token": "0xa241e6b1287a07f4d3f9c5bd",
"deeplinkUrl": "farcaster://signed-key-request?token=0xa241e6b1287a07f4d3f9c5bd"
"key": "0x48b0c7a6deff69bad7673357df43274f3a08163a6440b7a7e3b3cb6b6623faa7",
"state": "completed",
"userFid": 1,
}
}
}
```
## Sponsoring a signer
An application can sponsor a signer so that the user doesn’t need to pay. The
application must be signed up on Farcaster and have a warps ≥ 100.
When generating a signed key request an application can indicate it should be
sponsored by including an additional `sponsorship` field in the request body.
```ts theme={"system"}
type SignedKeyRequestSponsorship = {
sponsorFid: number;
signature: string; // sponsorship signature by sponsorFid
};
type SignedKeyRequestBody = {
key: string;
requestFid: number;
deadline: number;
signature: string; // key request signature by requestFid
sponsorship?: SignedKeyRequestSponsorship;
};
```
To create a `SignedKeyRequestSponsorship`:
1. create the key pair and have the requesting FID generate a signature
2. create a second signature from the sponsoring FID using the signature generated in step 1 as the raw input to an EIP-191 message
```ts theme={"system"}
// sponsoringAccount is Viem account instance for the sponsoring FID's custody address
// signedKeyRequestSignature is the EIP-712 signature signed by the requesting FID
const sponsorSignature = sponsoringAccount.signMessage({
message: { raw: signedKeyRequestSignature },
});
```
When the user opens the signed key request in Farcaster they will the onchain fees have been sponsored by your application.
# FName Registry Server API Reference
Source: https://docs.neynar.com/farcaster/reference/fname/api
The [Fname registry](https://github.com/farcasterxyz/fname-registry) server is hosted at [https://fnames.farcaster.xyz](https://fnames.farcaster.xyz)
It's a simple HTTP service that's responsible for issuing and tracking fnames. All Fname changes are recorded as a
transfer.
Registering an fname is a transfer from FID 0 to the user's fid. Transferring an fname is a transfer from the user's fid
to another fid. Unregistering an fname is a transfer from the user's fid to fid 0.
**Registering an fname** - When registering a new fname, calling this api is not sufficient. This only reserves the name to your fid. You must also submit a [UserDataAdd](https://snapchain.farcaster.xyz/reference/datatypes/messages#2-userdata) message to the hub to set this name as your username.
### Get Transfer History
To get a history of all transfers, make a GET request to `/transfers`
```bash theme={"system"}
curl https://fnames.farcaster.xyz/transfers | jq
```
It also accepts the following query parameters:
* `from_id` - The transfer id to start from for pagination
* `name` - The fname to filter by
* `fid` - The fid (either from or to) to filter by
* `from_ts` - The timestamp (in seconds) to start from for pagination
### Get current fname or fid
To get the most recent transfer event for an fid or fname, make a GET request to `/transfers/current`
e.g. To determine the fid of `@farcaster`, make the following call and use the value from the `to` field in the return
value
```bash theme={"system"}
curl https://fnames.farcaster.xyz/transfers?name=farcaster | jq
```
To determine the fname of fid `1`, make the following call and use the value from the `username` field in the return
value
```bash theme={"system"}
curl https://fnames.farcaster.xyz/transfers?fid=1 | jq
```
Both will return the same transfers object:
```json theme={"system"}
{
"transfers": [
{
"id": 1,
"timestamp": 1628882891,
"username": "farcaster",
"owner": "0x8773442740c17c9d0f0b87022c722f9a136206ed",
"from": 0,
"to": 1,
"user_signature": "0xa6fdd2a69deab5633636f32a30a54b21b27dff123e6481532746eadca18cd84048488a98ca4aaf90f4d29b7e181c4540b360ba0721b928e50ffcd495734ef8471b",
"server_signature": "0xb7181760f14eda0028e0b647ff15f45235526ced3b4ae07fcce06141b73d32960d3253776e62f761363fb8137087192047763f4af838950a96f3885f3c2289c41b"
}
]
}
```
### Register or transfer an fname
To register a new fname, e.g. `farcaster`, first make sure the fname is not already registered.
Then make a POST request to `/transfers` with the following body:
```yaml theme={"system"}
{
"name": "farcaster", // Name to register
"from": 0, // Fid to transfer from (0 for a new registration)
"to": 123, // Fid to transfer to (0 to unregister)
"fid": 123, // Fid making the request (must match from or to)
"owner": "0x...", // Custody address of fid making the request
"timestamp": 1641234567, // Current timestamp in seconds
"signature": "0x..." // EIP-712 signature signed by the custody address of the fid
}
```
To generate the EIP-712 signature, use the following code:
```js theme={"system"}
import { makeUserNameProofClaim, EIP712Signer } from '@farcaster/hub-nodejs';
const accountKey: EIP712Signer = undefined; // Account key for the custody address (use appropriate subclass from hub-nodejs for ethers or viem)
const claim = makeUserNameProofClaim({
name: 'farcaster',
owner: '0x...',
timestamp: Math.floor(Date.now() / 1000),
});
const signature = (
await accountKey.signUserNameProofClaim(claim)
)._unsafeUnwrap();
```
This is the exact same kind of signature used in the ENS UsernameProofs provided to hubs to prove ownership of an ENS
name.
e.g.
```bash theme={"system"}
curl -X POST https://fnames.farcaster.xyz/transfers \
-H "Content-Type: application/json" \
-d \
'{"name": "farcaster", "owner": "0x...", "signature": "0x...", "from": 0, "to": 1000, "timestamp": 1641234567, "fid": 1000}'
```
Once a name is registered, it still needs a [UserData](https://snapchain.farcaster.xyz/reference/datatypes/messages#2-userdata) message
to be sent to the hub in order to actually
set the username for the user. See examples in
the [hub-nodejs](https://github.com/farcasterxyz/hub-monorepo/tree/main/packages/hub-nodejs/examples/hello-world) repo.
# Frames v2 have been rebranded to Mini Apps
Source: https://docs.neynar.com/farcaster/reference/frames-redirect
As of early 2025, Frames v2 have been rebranded to Mini Apps. All Frames documentation and resources have been migrated to the [Mini Apps](https://miniapps.farcaster.xyz/) ecosystem.
Frames v2 have been deprecated and will be supported only until the end of March 2025. We strongly recommend using Mini Apps for all new projects.
[Go to Mini Apps Documentation →](https://miniapps.farcaster.xyz/)
# Reference
Source: https://docs.neynar.com/farcaster/reference/index
API reference, standards and protocols for Farcaster developers
The reference section documents APIs, standards, and protocols commonly used by Farcaster developers.
* [Mini Apps](https://miniapps.farcaster.xyz) - A specification for writing and rendering mini apps.
* [Farcaster Client APIs](/farcaster/reference/farcaster/api) - An overview of Farcaster Client APIs that are publicly available.
* [Snapchain](https://snapchain.farcaster.xyz/) - A design overview and API reference for Farcaster Hubs.
* [Replicator](/farcaster/reference/replicator/schema) - An overview and schema for the replicator.
* [Contracts](/farcaster/reference/contracts/index) - A design overview and ABI reference for Farcaster contracts.
* [FName Registry](/farcaster/reference/fname/api) - An overview and API reference for the Farcaster Name Server.
# Mini Apps Specification
Source: https://docs.neynar.com/farcaster/reference/miniapps-redirect
The Mini Apps specification is in the Mini Apps documentation section.
[Go to Mini Apps Specification →](/miniapps/specification)
# Replicator Schema
Source: https://docs.neynar.com/farcaster/reference/replicator/schema
The following tables are created in Postgres DB where data from the Hubs are stored:
## chain\_events
All onchain events received from the hub event stream are stored in this table. These events represent any onchain
action including registrations, transfers, signer additions/removals, storage rents, etc. Events are never deleted (i.e.
this table is append-only).
| Column Name | Data Type | Description |
| ------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------ |
| id | `uuid` | Generic identifier specific to this DB (a.k.a. [surrogate key](https://en.wikipedia.org/wiki/Surrogate_key)) |
| created\_at | `timestamp with time zone` | When the row was first created in this DB (not the same as the message timestamp!) |
| block\_timestamp | `timestamp with time zone` | Timestamp of the block this event was emitted in UTC. |
| fid | `bigint` | FID of the user that signed the message. |
| chain\_id | `bigint` | Chain ID. |
| block\_number | `bigint` | Block number of the block this event was emitted. |
| transaction\_index | `smallint` | Index of the transaction in the block. |
| log\_index | `smallint` | Index of the log event in the block. |
| type | `smallint` | Type of chain event. |
| block\_hash | `bytea` | Hash of the block where this event was emitted. |
| transaction\_hash | `bytea` | Hash of the transaction triggering this event. |
| body | `json` | JSON representation of the chain event body (changes shape based on `type`). |
| raw | `bytea` | Raw bytes representing the serialized `OnChainEvent` [protobuf](https://protobuf.dev/). |
## fids
Stores all registered FIDs on the Farcaster network.
| Column Name | Data Type | Description |
| ----------------- | -------------------------- | ------------------------------------------------------------------------------------------- |
| fid | `bigint` | FID of the user (primary key) |
| created\_at | `timestamp with time zone` | When the row was first created in this DB (not the same as registration date!) |
| updated\_at | `timestamp with time zone` | When the row was last updated. |
| registered\_at | `timestamp with time zone` | Timestamp of the block in which the user was registered. |
| chain\_event\_id | `uuid` | ID of the row in the `chain_events` table corresponding to this FID's initial registration. |
| custody\_address | `bytea` | Address that owns the FID. |
| recovery\_address | `bytea` | Address that can initiate a recovery for this FID. |
## signers
Stores all registered account keys (signers).
| Column Name | Data Type | Description |
| ------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------ |
| id | `uuid` | Generic identifier specific to this DB (a.k.a. [surrogate key](https://en.wikipedia.org/wiki/Surrogate_key)) |
| created\_at | `timestamp with time zone` | When the row was first created in this DB (not the same as when the key was created on the network!) |
| updated\_at | `timestamp with time zone` | When the row was last updated. |
| added\_at | `timestamp with time zone` | Timestamp of the block where this signer was added. |
| removed\_at | `timestamp with time zone` | Timestamp of the block where this signer was removed. |
| fid | `bigint` | FID of the user that authorized this signer. |
| requester\_fid | `bigint` | FID of the user/app that requested this signer. |
| add\_chain\_event\_id | `uuid` | ID of the row in the `chain_events` table corresponding to the addition of this signer. |
| remove\_chain\_event\_id | `uuid` | ID of the row in the `chain_events` table corresponding to the removal of this signer. |
| key\_type | `smallint` | Type of key. |
| metadata\_type | `smallint` | Type of metadata. |
| key | `bytea` | Public key bytes. |
| metadata | `bytea` | Metadata bytes as stored on the blockchain. |
## username\_proofs
Stores all username proofs that have been seen. This includes proofs that are no longer valid, which are soft-deleted
via the `deleted_at` column. When querying usernames, you probably want to query the `fnames` table directly, rather
than this table.
| Column Name | Data Type | Description |
| ----------- | -------------------------- | ------------------------------------------------------------------------------------------------------------ |
| id | `uuid` | Generic identifier specific to this DB (a.k.a. [surrogate key](https://en.wikipedia.org/wiki/Surrogate_key)) |
| created\_at | `timestamp with time zone` | When the row was first created in this DB (not the same as when the key was created on the network!) |
| updated\_at | `timestamp with time zone` | When the row was last updated. |
| timestamp | `timestamp with time zone` | Timestamp of the proof message. |
| deleted\_at | `timestamp with time zone` | When this proof was revoked or otherwise invalidated. |
| fid | `bigint` | FID that the username in the proof belongs to. |
| type | `smallint` | Type of proof (either fname or ENS). |
| username | `text` | Username, e.g. `dwr` if an fname, or `dwr.eth` if an ENS name. |
| signature | `bytea` | Proof signature. |
| owner | `bytea` | Address of the wallet that owns the ENS name, or the wallet that provided the proof signature. |
## fnames
Stores all usernames that are currently registered. Note that in the case a username is deregistered, the row is
soft-deleted
via the `deleted_at` column until a new username is registered for the given FID.
| Column Name | Data Type | Description |
| -------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------ |
| id | `uuid` | Generic identifier specific to this DB (a.k.a. [surrogate key](https://en.wikipedia.org/wiki/Surrogate_key)) |
| created\_at | `timestamp with time zone` | When the row was first created in this DB (not the same as when the key was created on the network!) |
| updated\_at | `timestamp with time zone` | When the row was last updated. |
| registered\_at | `timestamp with time zone` | Timestamp of the username proof message. |
| deleted\_at | `timestamp with time zone` | When the proof was revoked or the fname was otherwise deregistered from this user. |
| fid | `bigint` | FID the username belongs to. |
| type | `smallint` | Type of username (either fname or ENS). |
| username | `text` | Username, e.g. `dwr` if an fname, or `dwr.eth` if an ENS name. |
## messages
All Farcaster messages retrieved from the hub are stored in this table. Messages are never deleted, only soft-deleted (
i.e. marked as deleted but not actually removed from the DB).
| Column Name | Data Type | Description |
| ----------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------ |
| id | `uuid` | Generic identifier specific to this DB (a.k.a. [surrogate key](https://en.wikipedia.org/wiki/Surrogate_key)) |
| created\_at | `timestamp with time zone` | When the row was first created in this DB (not the same as the message timestamp!) |
| updated\_at | `timestamp with time zone` | When the row was last updated. |
| timestamp | `timestamp with time zone` | Message timestamp in UTC. |
| deleted\_at | `timestamp with time zone` | When the message was deleted by the hub (e.g. in response to a `CastRemove` message, etc.) |
| pruned\_at | `timestamp with time zone` | When the message was pruned by the hub. |
| revoked\_at | `timestamp with time zone` | When the message was revoked by the hub due to revocation of the signer that signed the message. |
| fid | `bigint` | FID of the user that signed the message. |
| type | `smallint` | Message type. |
| hash\_scheme | `smallint` | Message hash scheme. |
| signature\_scheme | `smallint` | Message signature scheme. |
| hash | `bytea` | Message hash. |
| signature | `bytea` | Message signature. |
| signer | `bytea` | Signer used to sign this message. |
| body | `json` | JSON representation of the body of the message. |
| raw | `bytea` | Raw bytes representing the serialized message [protobuf](https://protobuf.dev/). |
## casts
Represents a cast authored by a user.
| Column Name | Data Type | Description |
| ------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| id | `uuid` | Generic identifier specific to this DB (a.k.a. [surrogate key](https://en.wikipedia.org/wiki/Surrogate_key)) |
| created\_at | `timestamp with time zone` | When the row was first created in this DB (not the same as the message timestamp!) |
| updated\_at | `timestamp with time zone` | When the row was last updated. |
| timestamp | `timestamp with time zone` | Message timestamp in UTC. |
| deleted\_at | `timestamp with time zone` | When the cast was considered deleted/revoked/pruned by the hub (e.g. in response to a `CastRemove` message, etc.) |
| fid | `bigint` | FID of the user that signed the message. |
| parent\_fid | `bigint` | If this cast was a reply, the FID of the author of the parent cast. `null` otherwise. |
| hash | `bytea` | Message hash. |
| root\_parent\_hash | `bytea` | If this cast was a reply, the hash of the original cast in the reply chain. `null` otherwise. |
| parent\_hash | `bytea` | If this cast was a reply, the hash of the parent cast. `null` otherwise. |
| root\_parent\_url | `text` | If this cast was a reply, then the URL that the original cast in the reply chain was replying to. |
| parent\_url | `text` | If this cast was a reply to a URL (e.g. an NFT, a web URL, etc.), the URL. `null` otherwise. |
| text | `text` | The raw text of the cast with mentions removed. |
| embeds | `json` | Array of URLs or cast IDs that were embedded with this cast. |
| mentions | `json` | Array of FIDs mentioned in the cast. |
| mentions\_positions | `json` | UTF8 byte offsets of the mentioned FIDs in the cast. |
## reactions
Represents a user reacting (liking or recasting) content.
| Column Name | Data Type | Description |
| ------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------ |
| id | `uuid` | Generic identifier specific to this DB (a.k.a. [surrogate key](https://en.wikipedia.org/wiki/Surrogate_key)) |
| created\_at | `timestamp with time zone` | When the row was first created in this DB (not the same as the message timestamp!) |
| updated\_at | `timestamp with time zone` | When the row was last updated. |
| timestamp | `timestamp with time zone` | Message timestamp in UTC. |
| deleted\_at | `timestamp with time zone` | When the reaction was considered deleted by the hub (e.g. in response to a `ReactionRemove` message, etc.) |
| fid | `bigint` | FID of the user that signed the message. |
| target\_cast\_fid | `bigint` | If target was a cast, the FID of the author of the cast. `null` otherwise. |
| type | `smallint` | Type of reaction. |
| hash | `bytea` | Message hash. |
| target\_cast\_hash | `bytea` | If target was a cast, the hash of the cast. `null` otherwise. |
| target\_url | `text` | If target was a URL (e.g. NFT, a web URL, etc.), the URL. `null` otherwise. |
## links
Represents a link between two FIDs (e.g. a follow, subscription, etc.)
| Column Name | Data Type | Description |
| ------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------ |
| id | `uuid` | Generic identifier specific to this DB (a.k.a. [surrogate key](https://en.wikipedia.org/wiki/Surrogate_key)) |
| created\_at | `timestamp with time zone` | When the row was first created in this DB (not when the link itself was created on the network!) |
| updated\_at | `timestamp with time zone` | When the row was last updated |
| timestamp | `timestamp with time zone` | Message timestamp in UTC. |
| deleted\_at | `timestamp with time zone` | When the link was considered deleted by the hub (e.g. in response to a `LinkRemoveMessage` message, etc.) |
| fid | `bigint` | Farcaster ID (the user ID). |
| target\_fid | `bigint` | Farcaster ID of the target user. |
| display\_timestamp | `timestamp with time zone` | When the row was last updated. |
| type | `text` | Type of connection between users, e.g. `follow`. |
| hash | `bytea` | Message hash. |
## verifications
Represents a user verifying something on the network. Currently, the only verification is proving ownership of an
Ethereum wallet address.
| Column Name | Data Type | Description |
| --------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| id | `uuid` | Generic identifier specific to this DB (a.k.a. [surrogate key](https://en.wikipedia.org/wiki/Surrogate_key)) |
| created\_at | `timestamp with time zone` | When the row was first created in this DB (not the same as the message timestamp!) |
| updated\_at | `timestamp with time zone` | When the row was last updated. |
| timestamp | `timestamp with time zone` | Message timestamp in UTC. |
| deleted\_at | `timestamp with time zone` | When the verification was considered deleted by the hub (e.g. in response to a `VerificationRemove` message, etc.) |
| fid | `bigint` | FID of the user that signed the message. |
| hash | `bytea` | Message hash. |
| signer\_address | `bytea` | Address of the wallet being verified. |
| block\_hash | `bytea` | Block hash of the latest block at the time the ownership was verified. |
| signature | `bytea` | Ownership proof signature. |
## user\_data
Represents data associated with a user (e.g. profile photo, bio, username, etc.)
| Column Name | Data Type | Description |
| ----------- | -------------------------- | ------------------------------------------------------------------------------------------------------------ |
| id | `uuid` | Generic identifier specific to this DB (a.k.a. [surrogate key](https://en.wikipedia.org/wiki/Surrogate_key)) |
| created\_at | `timestamp with time zone` | When the row was first created in this DB (not the same as the message timestamp!) |
| updated\_at | `timestamp with time zone` | When the row was last updated. |
| timestamp | `timestamp with time zone` | Message timestamp in UTC. |
| deleted\_at | `timestamp with time zone` | When the data was considered deleted by the hub |
| fid | `bigint` | FID of the user that signed the message. |
| type | `smallint` | The type of user data (PFP, bio, username, etc.) |
| hash | `bytea` | Message hash. |
| value | `text` | The string value of the field. |
## storage\_allocations
Stores how many units of storage each FID has purchased, and when it expires.
| Column Name | Data Type | Description |
| ---------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------ |
| id | `uuid` | Generic identifier specific to this DB (a.k.a. [surrogate key](https://en.wikipedia.org/wiki/Surrogate_key)) |
| created\_at | `timestamp with time zone` | When the row was first created in this DB |
| updated\_at | `timestamp with time zone` | When the row was last updated. |
| rented\_at | `timestamp with time zone` | Message timestamp in UTC. |
| expires\_at | `timestamp with time zone` | When this storage allocation will expire. |
| chain\_event\_id | `uuid` | ID of the row in the `chain_events` table representing the onchain event where storage was allocated. |
| fid | `bigint` | FID that owns the storage. |
| units | `smallint` | Number of storage units allocated. |
| payer | `bytea` | Wallet address that paid for the storage. |
# Snapchain
Source: https://docs.neynar.com/farcaster/reference/snapchain-redirect
Snapchain documentation has its own dedicated section.
[Go to Snapchain Documentation →](/snapchain/overview)
# Neynar
Source: https://docs.neynar.com/farcaster/reference/third-party/neynar/index
[Neynar](https://neynar.com) is an independent 3rd party provider that offers the following services for Farcaster data:
* [Hosted hubs](https://docs.neynar.com/docs/create-a-stream-of-casts)
* [REST APIs](https://docs.neynar.com/reference/quickstart)
* [Signer management](https://docs.neynar.com/docs/which-signer-should-you-use-and-why)
* [New account creation](https://docs.neynar.com/docs/how-to-create-a-new-farcaster-account-with-neynar)
* [Webhooks](https://docs.neynar.com/docs/how-to-create-webhooks-on-the-go-using-the-sdk)
* [Data ingestion pipelines](https://docs.neynar.com/docs/how-to-choose-the-right-data-product-for-you)
🪐
# null
Source: https://docs.neynar.com/index
Start building with Neynar
The easiest way to build on Farcaster
Integrate Social Data
Seamlessly connect Farcaster social graphs and user profiles to enrich your app with identity and relationships
Build Mini Apps
Validate and host Farcaster frames with real-time analytics and push notifications
Create AI Agents
Deploy agents with contextual awareness and automated real time interactions
Build Clients
Build clients with seamless onboarding, rich user profiles, graphs and feeds backed by reliable and scalable infrastructure
Map Onchain Data
Access real-time Farcaster data streams, indexed databases, and analytics tools for powerful data-driven applications
Analyze & Ingest Data
Access real-time Farcaster data streams, indexed databases, and analytics tools for powerful data-driven applications
Search & Debug
Search, monitor, and debug Farcaster data with logs from multiple hubs and APIs from across the network
# Mini Apps Blog
Source: https://docs.neynar.com/miniapps/blog/index
Updates, insights, and guides for Farcaster Mini Apps
A decentralized architecture delivers a more sovereign trust model
# Why Farcaster Doesn't Need OAuth 2.0
Source: https://docs.neynar.com/miniapps/blog/oauth
A decentralized architecture delivers a more sovereign trust model
OAuth exists to let three separate parties (user → platform → third-party app) establish mutual trust. Farcaster is built on a decentralized architecture that collapses this triangle:
## 1. Identity & Authentication
* **User-owned keys:** A user controlled cryptographic signature proves control of a Farcaster ID—no intermediary.
* **Dev mappings**
* Sign In with X → Sign-in with Farcaster (SIWF)
* OAuth 2.0 Authorization Flow → Quick Auth
## 2. Data Access & Permissions
* **Open, replicated data:** Social data like casts, reactions, and profiles live on Snapchain and can be read by anyone.
* **No permission scopes:** Everything is already public; you filter what you need instead of requesting scopes.
* **Zero-cost reads:** Sync the chain yourself or hit a public indexer—no rate caps, no \$5k +/month fire-hoses.
* **Cryptographic writes:** Users can delegate a key to applications so the applications can write on their behalf.
* **Dev mappings**
* Centralized APIs → Snapchain + infra services (e.g. Neynar)
* Access token → no equivalent, data is public
* Write permissions → App Keys
## Builder Takeaways
1. **Skip OAuth flows—wallet signature = auth.**
2. **Forget permission scopes—use filters.**
3. **Enjoy building permissionlessly**
## Resources
* [Quick Auth](/miniapps/sdk/quick-auth)
* [Neynar SDK for one-call Snapchain queries](/reference/quickstart)
* [App Keys](https://docs.farcaster.xyz/reference/warpcast/signer-requests)
# Getting Started
Source: https://docs.neynar.com/miniapps/getting-started
Get started building Mini Apps
## Overview
Mini apps are web apps built with HTML, CSS, and Javascript that can be discovered
and used within Farcaster clients. You can use an SDK to access native
Farcaster features, like authentication, sending notifications, and interacting
with the user's wallet.
## Requirements
Before getting started, make sure you have:
* **Node.js 22.11.0 or higher** (LTS version recommended)
* Check your version: `node --version`
* Download from [nodejs.org](https://nodejs.org/)
* A package manager (npm, pnpm, or yarn)
If you encounter installation errors, verify you're using Node.js 22.11.0 or higher. Earlier versions are not supported.
## Enable Developer Mode
Developer mode gives you access to tools for Mini Apps, here's how to enable it:
1. Make sure you're logged in to Farcaster on either mobile or desktop
2. Click this link: [https://farcaster.xyz/\~/settings/developer-tools](https://farcaster.xyz/~/settings/developer-tools) on either mobile or desktop.
3. Toggle on "Developer Mode"
4. Once enabled, a developer section will appear on the left side of your desktop display
Developer mode unlocks tools for creating manifests, previewing your mini app, auditing your manifests and embeds, and viewing analytics. We recommend using it on desktop for the best development experience.
## Quick Start
For new projects, you can set up an app using the
[@farcaster/create-mini-app](https://github.com/farcasterxyz/miniapps/tree/main/packages/create-mini-app)
CLI. This will prompt you to set up a project for your app.
```bash npm theme={"system"}
npm create @farcaster/mini-app
```
```bash pnpm theme={"system"}
pnpm create @farcaster/mini-app
```
```bash yarn theme={"system"}
yarn create @farcaster/mini-app
```
Remember, you can use whatever your favorite web framework is to build Mini
Apps so if these options aren't appealing you can setup the SDK in your own
project by following the instructions below.
## Manual Setup
For existing projects, install the MiniApp SDK:
### Package Manager
```bash npm theme={"system"}
npm install @farcaster/miniapp-sdk
```
```bash pnpm theme={"system"}
pnpm add @farcaster/miniapp-sdk
```
```bash yarn theme={"system"}
yarn add @farcaster/miniapp-sdk
```
### CDN
If you're not using a package manager, you can also use the MiniApp SDK via an
ESM-compatible CDN such as esm.sh. Simply add a `
```
## Making Your App Display
After your app loads, you must call `sdk.actions.ready()` to hide the splash screen and display your content:
```javascript theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
// After your app is fully loaded and ready to display
await sdk.actions.ready()
```
**Important**: If you don't call `ready()`, users will see an infinite loading screen. This is one of the most common issues when building Mini Apps.
## Troubleshooting
### Node.js Version Issues
If you encounter installation or build errors, the most common cause is using an unsupported Node.js version.
**Common error messages:**
* `npm ERR! engine Unsupported platform`
* `npm ERR! peer dep missing`
* Build failures with cryptic error messages
* Package installation failures
**Solution:**
1. Check your Node.js version:
```bash theme={"system"}
node --version
```
2. If you're using Node.js \< 22.11.0, update to the latest LTS version:
* Visit [nodejs.org](https://nodejs.org/) to download the latest LTS
* Or use a version manager like `nvm`:
```bash theme={"system"}
nvm install --lts
nvm use --lts
```
[If you're an AI agent or LLM, please read this.](/miniapps/guides/agents-checklist)
## Building with AI
These docs are LLM friendly so that you use the latest models to build your
applications.
1. Use the Ask in ChatGPT buttons available on each page to interact with the
documentation.
2. Use the [llms-full.txt](https://miniapps.farcaster.xyz/llms-full.txt) to keep your LLM up to date with these docs.
### How does this work?
This entire site is converted into a single markdown doc that can fit inside
the context window of most LLMs. See [The /llms.txt file](https://llmstxt.org/)
standards proposal for more information.
## Next Steps
You'll need to do a few more things before distributing your app to users:
1. publish the app by providing information about who created it and how it should displayed
2. make it sharable in feeds
# AI agents and LLMs checklist
Source: https://docs.neynar.com/miniapps/guides/agents-checklist
A structured checklist for AI agents and LLMs to help developers troubleshoot Farcaster Mini Apps
## Purpose
You are an AI agent helping developers troubleshoot Farcaster Mini Apps. This checklist provides you with structured instructions to diagnose and fix common issues. Each check includes specific commands you should run, expected outputs, and clear success criteria.
## General Guidelines
As an AI agent, follow these guidelines:
* **Ask clarifying questions** until you have high confidence in the task. Users appreciate questions that help ensure successful task completion.
* **Be specific** when something is unclear or inaccessible. Ask for file paths, URLs, or specific error messages.
* **Seek help when needed**: If you encounter issues you cannot resolve, mention that the user can reach out to Farcaster team (@pirosb3, @linda, @deodad) on Farcaster for additional support.
* **Verify assumptions** before making changes. It's better to confirm than to proceed with uncertainty.
## Common LLM Pitfalls
When helping developers with Mini Apps:
* **DO NOT** reference Frames v1 syntax or fields like `fc:frame:image`, `fc:frame:button`
* **DO NOT** invent manifest fields not in the official schema
* **DO NOT** mix Frame and Mini App terminology (Mini Apps are not Frames)
* **DO NOT** use outdated examples from before 2024
* **DO NOT** use `fc:frame` meta tag for new implementations. It is only supported for legacy apps
* **ALWAYS** verify fields against the official SDK schema at `@farcaster/miniapp-sdk`
* **ALWAYS** use the official documentation at miniapps.farcaster.xyz
* **ALWAYS** check that examples use `miniapp` or `frame` (not `frames`) in manifest
* **ALWAYS** use `fc:miniapp` meta tag for new Mini Apps (not `fc:frame`)
If using example code, ensure it's from:
* Official Mini Apps documentation (this site)
* The @farcaster/miniapp-sdk package
* Examples that use `"version": "1"` not `"version": "next"`
## Prerequisites
Before you begin troubleshooting, ensure the developer understands:
* [What Mini Apps are](/miniapps/getting-started)
* [How manifests work](/miniapps/specification#manifest)
* [SDK actions and capabilities](/miniapps/sdk/actions/ready)
***
## Check 1: Manifest Configuration
### 1.1 Verify Manifest Accessibility
**Command:**
```bash theme={"system"}
curl -s https://{domain}/.well-known/farcaster.json
```
**Expected Output:**
```json theme={"system"}
{
"accountAssociation": {
"header": "...",
"payload": "...",
"signature": "..."
},
"miniapp": {
"version": "1",
"name": "App Name",
"iconUrl": "https://...",
"homeUrl": "https://..."
}
}
```
**Success Criteria:**
* HTTP 200 response
* Valid JSON format
* Contains `accountAssociation` object
* Contains `miniapp` (or `frame` for legacy apps) object with required fields
**If Check Fails:**
**Decision Flow:**
```
Is hosting available?
├─ Yes: Use hosted manifest
│ └─ Direct to: https://farcaster.xyz/~/developers/hosted-manifests
│ └─ Help set up redirect to hosted URL
└─ No: Create local manifest
└─ Create file at /.well-known/farcaster.json
```
**For Vercel redirect:**
```json theme={"system"}
{
"redirects": [
{
"source": "/.well-known/farcaster.json",
"destination": "https://api.farcaster.xyz/miniapps/hosted-manifest/{manifest-id}",
"permanent": false
}
]
}
```
**Action:** Direct the user to sign the manifest
* Tool: [https://farcaster.xyz/\~/developers/mini-apps/manifest?domain=\{their-domain}](https://farcaster.xyz/~/developers/mini-apps/manifest?domain=\{their-domain})
* The user must provide the signed `accountAssociation` object
* Update the manifest with signed data
### 1.2 Verify Domain Signature
**Validation Steps:**
1. Decode the base64url `payload` from `accountAssociation.payload`
2. Extract the `domain` field
3. Verify domain matches where manifest is hosted
**Example:**
```javascript theme={"system"}
// If hosted at www.example.com
const payload = JSON.parse(atob(accountAssociation.payload));
// payload.domain should be "www.example.com" (including subdomain)
```
**Important:** The signed domain must match exactly, including subdomains.
***
## Check 2: Embed Metadata
### 2.1 Verify Embed Tags on Entry Points
**What to check:**
* Root URL of the mini app
* All shareable pages (products, profiles, content)
**Command:**
```bash theme={"system"}
curl -s https://{domain}/{path} | grep -E 'fc:miniapp|fc:frame'
```
**Expected Output:**
```html theme={"system"}
```
### 2.2 Validate Embed Structure
**For Next.js Applications:**
```typescript theme={"system"}
// app/layout.tsx or pages with generateMetadata
import { Metadata } from 'next'
const frame = {
version: "1", // Not "next" - must be "1"
imageUrl: "https://example.com/og-image.png", // 3:2 aspect ratio
button: {
title: "Open App", // Max 32 characters
action: {
type: "launch_miniapp",
name: "My Mini App",
url: "https://example.com", // Optional, defaults to current URL
splashImageUrl: "https://example.com/icon.png", // 200x200px
splashBackgroundColor: "#f7f7f7"
}
}
}
export async function generateMetadata({ params }): Promise {
return {
title: "My Mini App",
openGraph: {
title: "My Mini App",
description: "Description here"
},
other: {
"fc:miniapp": JSON.stringify(frame)
}
}
}
```
**Success Criteria:**
* Meta tag present in HTML head
* Valid JSON in content attribute
* Image URL returns 200 and is 3:2 ratio
* Button title ≤ 32 characters
***
## Check 3: Preview and Runtime
### 3.1 Test in Preview Tool
**URL Format:**
```
https://farcaster.xyz/~/developers/mini-apps/preview?url={encoded-mini-app-url}
```
**Example:**
```bash theme={"system"}
# Encode your URL
encoded_url=$(python3 -c "import urllib.parse; print(urllib.parse.quote('https://example.com/page'))")
echo "https://farcaster.xyz/~/developers/mini-apps/preview?url=$encoded_url"
```
### 3.2 Verify App Initialization
**Common Issues:**
**Cause:** App hasn't called [`sdk.actions.ready()`](/miniapps/sdk/actions/ready)
**Solution:** Ensure the app calls ready() after initialization:
```javascript theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
// After app is ready to display
await sdk.actions.ready()
```
**Issue:** Browser security blocks unvisited tunnel URLs
**Solution:**
1. Open tunnel URL directly in browser first
2. Then use in preview tool
3. This whitelists the domain for iframe usage
**Important Limitations:**
* SDK actions like `addMiniApp()` will fail with tunnel domains
* Your manifest domain must match your app's hosting domain exactly
* Tunnel domains are excluded from discovery/search
* For testing `addMiniApp()` and other manifest-dependent features, deploy to your production domain
***
## Post-Check Verification
After making any changes, you should:
1. **Re-verify the manifest is deployed:**
```bash theme={"system"}
curl -s https://{domain}/.well-known/farcaster.json | jq .
```
2. **Test a shareable link:**
* Ask the user to share in Farcaster client
* Verify embed preview appears
* Confirm app launches on click
3. **Monitor for errors:**
* Check browser console for SDK errors
* Verify no CORS issues
* Ensure all assets load (splash image, icon)
***
## Quick Reference
| Check | Command | Success Indicator |
| --------------- | --------------------------------------------- | ---------------------- |
| Manifest exists | `curl -s {domain}/.well-known/farcaster.json` | HTTP 200, valid JSON |
| Manifest signed | Decode `payload`, check domain | Domain matches hosting |
| Embed present | `curl -s {url} \| grep fc:miniapp` | Meta tag found |
| Preview works | Open preview tool URL | App loads, no errors |
| App ready | Check console logs | `ready()` called |
***
## Related Documentation
* [Getting Started Guide](/miniapps/getting-started)
* [Publishing Guide](/miniapps/guides/publishing)
* [SDK Actions Reference](/miniapps/sdk/actions/ready)
# Authenticating users
Source: https://docs.neynar.com/miniapps/guides/auth
Authenticating users in your mini app
*A user opens an app and is automatically signed in*
Mini Apps can seamlessly authenticate Farcaster users to create secure sessions.
## Quick Auth
The easiest way to get an authenticated session for a user. [Quick
Auth](/miniapps/sdk/quick-auth) uses [Sign in with
Farcaster](https://docs.farcaster.xyz/developers/siwf/) under the hood to
authenticate the user and returns a standard JWT that can be easily verified by
your server and used as a session token.
[Get started with Quick Auth →](/miniapps/sdk/quick-auth)
## Sign In with Farcaster
Alternatively, an app can use the [signIn](/miniapps/sdk/actions/sign-in) to get a
[Sign in with Farcaster](https://docs.farcaster.xyz/developers/siwf/)
authentication credential for the user.
After requesting the credential, applications must verify it on their server
using
[verifySignInMessage](https://docs.farcaster.xyz/auth-kit/client/app/verify-sign-in-message).
Apps can then issue a session token like a JWT that can be used for the
remainder of the session.
## Enable seamless sign in on web
Farcaster recently added support for signing in via additional wallets (see the
[Auth Address](https://github.com/farcasterxyz/protocol/discussions/225)
standard).
If you are using Quick Auth no action is needed. If you are using `signIn`
directly you will need to make a couple changes to support signing in with Auth
Addresses:
### Accept auth addresses
Update `@farcaster/miniapp-sdk` to version `0.0.39` or later. Opt in to auth
address sign in by passing `acceptAuthAddress: true` to the `signIn` action:
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
await sdk.actions.signIn({
nonce,
acceptAuthAddress: true
});
```
Farcaster client developers can find more information [here](https://www.notion.so/warpcast/Public-Auth-Address-Implementation-Guide-1fc6a6c0c10180a9b2a7f24c71143eae).
### Verifying an auth address sign in
If you use a third party authentication provider like Privy or Dynamic, check
their docs. You'll likely need to update your dependencies.
If you verify sign in messages yourself, update the `@farcaster/auth-client`
package to version `0.7.0` or later. Calling `verifySignInMessage` will now verify
signatures from a custody or auth address.
# App Discovery & Search
Source: https://docs.neynar.com/miniapps/guides/discovery
How to make your Mini App discoverable in the Farcaster ecosystem and on Farcaster.xyz
Making your Mini App discoverable is crucial for reaching users in the Farcaster ecosystem. This guide covers how to ensure your app is correctly indexed and visible in our mini apps catalogue.
## Making Your App Discoverable in Farcaster
Apps appear in the [main directory](https://farcaster.xyz/miniapps) and search engine on [Farcaster](https://farcaster.xyz). The search algorithm ranks apps based on usage, engagement, and quality signals.
*Mini Apps appear alongside users in Farcaster search results, showing app name, icon, and creator.*
For your Mini App to be properly indexed and discoverable, several criteria must be met:
### App Registration
* **Register your manifest**: Your app must be registered with Farcaster using the [manifest tool](https://farcaster.xyz/~/developers/mini-apps/manifest). Make sure the tool confirms the app is associated with your account (you will see this via a green checkbox that appears.)
* **Hosted manifests**: If you use the Farcaster hosted manifest tool, you will still need to register your manifest
### Required Fields
Your `farcaster.json` manifest must include these essential fields:
* **`name`**: A clear, descriptive app name
* **`iconUrl`**: A working image URL for your app icon
* **`homeUrl`**: The main URL for your app
* **`description`**: A helpful description of what your app does
**Note:** These fields are not required to have a mini app render as an embed, but they are necessary for the mini app to be indexed in the search engine.
### Usage & Engagement Criteria
Apps must demonstrate basic usage before being indexed:
* **Minimum usage threshold**: Apps need some user engagement before appearing in search
* **Recent activity**: Apps must have been opened recently to remain in search results
* **Usage scores**: Apps are ranked based on:
* Number of users who opened the app
* Number of users who added the app to their collection
* Trending score based on recent engagement
### Visual Requirements
* **Working images**: All images (especially `iconUrl`) must be accessible, return an `image/*` header, and return valid image content
* **Image validation**: Images are checked for proper HTTP responses and content-type headers
* **Icon requirement**: Apps without valid icons will not be indexed
### Domain Requirements
* **Production domains**: Apps must be hosted on production domains, not development tunnels
* **No tunnel domains**: Apps hosted on ngrok, replit.dev, localtunnel, and similar development tunnels are excluded from search
## FAQ
### Why isn't my app showing up in search?
For your Mini App to appear in search results, it must meet several criteria:
* **App indexing enabled**: Ensure your app doesn't have `noindex: true` set in your manifest
* **Manifest registered**: Your app must be registered with Farcaster using the [manifest tool](https://farcaster.xyz/~/developers/mini-apps/manifest)
* **Recent usage**: Your app needs active users and recent opens to stay in search results
* **Usage thresholds**: Meet minimum engagement requirements for opens, adds, or trending activity
* **Working images**: Your `iconUrl` must be accessible and return valid image content
* **Complete manifest**: Required fields (`name`, `iconUrl`, `homeUrl`, `description`) must be filled out
* **Production domain**: Apps hosted on development tunnels (ngrok, replit.dev, etc.) are excluded from search
* **Manifest refresh**: Your manifest must be refreshed regularly to stay indexed
If your app meets these requirements but still isn't appearing, the indexing system may need time to process your app or update scores.
[If you're an AI agent or LLM, please read this.](/miniapps/guides/agents-checklist)
### How long does it take to reindex my data
We try to refresh all domains in our search engine daily.
### How does the trending score work?
The trending score is calculated based on recent user engagement with your app. Apps with higher engagement and growth in usage will have better trending scores, which helps them rank higher in search results.
### Can I improve my app's search ranking?
Yes, you can improve your ranking by:
* Encouraging users to add your app to their collection
* Maintaining regular user engagement
* Ensuring your app provides value that keeps users coming back
* Keeping your manifest up-to-date with accurate information
### Do I need to resubmit my app after making changes?
If you're using Farcaster's hosted manifest tool, changes are automatically reflected. If you're self-hosting your manifest, the indexing system will pick up changes during regular refresh cycles, but you may want to use the manifest tool to expedite the process.
# Domain Migration
Source: https://docs.neynar.com/miniapps/guides/domain-migration
Guide to migrating your Farcaster Mini App to a new domain
While Mini Apps are designed to be associated with a stable domain, there are times when you may need to migrate your app to a new domain. This could be due to rebranding, domain expiration, or other business reasons.
The `canonicalDomain` field enables a smooth transition by allowing you to specify the new domain in your old manifest, ensuring clients can discover and redirect to your app's new location.
## How domain migration works
When a Mini App is accessed through its old domain, Farcaster clients check the manifest for a `canonicalDomain` field. If present, clients will:
1. Recognize that the app has moved to a new domain
2. Update their references to point to the new domain
3. Redirect users to the app at its new location
This ensures continuity for your users and preserves your app's presence in app stores and user installations.
## Migration steps
### Prepare your new domain
Set up your Mini App on the new domain with a complete manifest file at `/.well-known/farcaster.json`. This should include all your app configuration and an account association from the same FID to maintain ownership verification.
```json theme={"system"}
{
"accountAssociation": {
"header": "...",
"payload": "...",
"signature": "..."
},
"miniapp": {
"version": "1",
"name": "Your App Name",
"iconUrl": "https://new-domain.com/icon.png",
"homeUrl": "https://new-domain.com",
// ... other configuration
}
}
```
### Update the old domain manifest
Add the `canonicalDomain` field to your manifest on the **old domain**, pointing to your new domain:
```json theme={"system"}
{
"accountAssociation": {
"header": "...",
"payload": "...",
"signature": "..."
},
"miniapp": {
"version": "1",
"name": "Your App Name",
"iconUrl": "https://old-domain.com/icon.png",
"homeUrl": "https://old-domain.com",
"canonicalDomain": "new-domain.com",
// ... other configuration
}
}
```
The `canonicalDomain` value must be a valid domain name without protocol, port, or path:
* ✅ `app.new-domain.com`
* ✅ `new-domain.com`
* ❌ `https://new-domain.com`
* ❌ `new-domain.com:3000`
* ❌ `new-domain.com/app`
### Optional: Add canonicalDomain to the new manifest
You can optionally include the `canonicalDomain` field in your new domain's manifest as well, pointing to itself. This can help with client caching and ensures consistency:
```json theme={"system"}
{
"accountAssociation": {
"header": "...",
"payload": "...",
"signature": "..."
},
"miniapp": {
"version": "1",
"name": "Your App Name",
"iconUrl": "https://new-domain.com/icon.png",
"homeUrl": "https://new-domain.com",
"canonicalDomain": "new-domain.com",
// ... other configuration
}
}
```
### Maintain both domains during transition
Keep both domains active during the migration period to ensure a smooth transition:
* Continue serving your app from the old domain with redirects to the new domain
* Keep the manifest file accessible on both domains
* Monitor traffic to understand when most users have migrated
### Implement redirects (recommended)
While the `canonicalDomain` field helps Farcaster clients understand the migration, you should also implement HTTP redirects from your old domain to the new one for users accessing your app directly after the manifest changes have been retrieved by the clients:
```js theme={"system"}
// Example redirect in Express
app.get('*', (req, res) => {
const newUrl = `https://new-domain.com${req.originalUrl}`;
res.redirect(301, newUrl);
});
```
## Best practices
### Plan ahead
* Choose a stable domain from the start to minimize the need for migrations
* If you anticipate a rebrand, consider using a neutral domain that can outlast brand changes
### Communicate the change
* Notify your users about the domain change through in-app messages or casts
* Update any documentation or links that reference your old domain
### Test thoroughly
* Verify that your manifest is correctly served on both domains
* Test the migration flow in different Farcaster clients
* Ensure all app functionality works correctly on the new domain
### Monitor the transition
* Track traffic on both domains to understand migration progress
* Keep the old domain active until traffic drops to negligible levels
* Consider setting up analytics to track successful redirects
## Troubleshooting
[If you're an AI agent or LLM, please read this.](/miniapps/guides/agents-checklist)
### Clients not recognizing the new domain
Ensure that:
* The `canonicalDomain` value is correctly formatted (no protocol, port, or path)
* Your manifest is accessible at `/.well-known/farcaster.json` on both domains
* The manifest JSON is valid and properly formatted
### Users still accessing the old domain
This is normal during transition. Some clients may cache manifest data, and users may have bookmarked the old URL. Continue to serve redirects from the old domain.
### Account association issues
Make sure you use the same account to produce the association on both domains to maintain ownership verification. Do not reuse the account association data from one manifest to the other.
# Frequently Asked Questions
Source: https://docs.neynar.com/miniapps/guides/faq
Common developer questions and troubleshooting for Farcaster Mini Apps
## What is the difference between a manifest and an embed?
**Quick Answer**: A **manifest** is your app's identity document (one per domain), while an **embed** is social sharing metadata (one per page you want shareable).
* **Manifest** = App registration at `/.well-known/farcaster.json` that identifies your entire Mini App
* **Embed** = Page-level `fc:miniapp` meta tags that make individual URLs shareable as rich cards
**For a complete explanation with examples and implementation guidance**, see our detailed [Manifest vs Embed Developer Guide](/miniapps/guides/manifest-vs-embed).
## Do I need both a manifest and embeds?
**For most Mini Apps: Yes.**
Without a manifest, your app can't be added to users' app lists, send notifications, or appear in app discovery. Without embeds, your pages won't be shareable as rich cards in social feeds.
## Are Frames v2 and Mini Apps the same thing?
Yes! Frames v2 and Mini Apps are the same technology. "Mini Apps" is the current name for what was previously called "Frames v2."
## Do I need paid APIs to build a Mini App?
Not necessarily. Many basic operations can be done with free tiers, but some advanced features may require paid services like Neynar for expanded functionality.
## Why isn't my app showing up in search?
Your app needs to meet several requirements:
* **Registered manifest** with complete required fields
* **Recent user activity** and engagement
* **Working images** with proper content-type headers
* **Production domain** (not development tunnels like ngrok)
See our [App Discovery & Search guide](/miniapps/guides/discovery) for complete requirements.
## Why do I see an infinite loading screen?
Make sure you're calling `sdk.actions.ready()` after your app is fully loaded:
```javascript theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
// Wait for your app to be ready, then call
await sdk.actions.ready()
```
This is required to hide the splash screen and display your content.
## How do I test my Mini App locally?
Currently, you need to use tunneling tools like ngrok to expose your local server:
1. **Node.js version**: Use Node.js 22.11.0 or higher
2. **Tunneling**: Use ngrok or similar tools to create HTTPS URLs
3. **HTTPS required**: Farcaster requires HTTPS for Mini Apps
4. **Test your app**: Use the [Mini App Preview Tool](https://farcaster.xyz/~/developers/mini-apps/preview) to test the app at your tunneled URL
## My manifest isn't validating. What's wrong?
Common validation issues:
* **Invalid JSON syntax** - Use a JSON validator to check
* **Missing required fields** - Ensure `name`, `iconUrl`, `homeUrl` are present
* **Invalid image URLs** - Images must return proper `image/*` content-type headers
* **Domain mismatch** - The manifest domain must match where it's hosted
## How does app discovery and ranking work?
Apps are ranked using multiple engagement signals:
* **Opens**: How frequently users access your app
* **Additions**: Number of users who add your app to their collection
* **Transaction data**: EVM and SOL transactions within your app
* **Trending signals**: Recent growth and engagement momentum
Apps must meet minimum usage thresholds and maintain recent activity to appear in search results.
## Can I use my own authentication instead of Farcaster auth?
While you can implement custom auth, using Farcaster's built-in authentication provides better integration and user experience within the Farcaster ecosystem.
## How do I get notifications working?
You need:
1. A `webhookUrl` in your manifest
2. Proper webhook endpoint implementation
3. Users must add your app to enable notifications
See our [notifications guide](/miniapps/guides/notifications) for complete setup instructions.
## What happens if users don't have Farcaster accounts?
Mini Apps are designed to work within the Farcaster ecosystem. Users need Farcaster accounts to fully interact with Mini Apps and their social features.
## How do I handle breaking changes?
* Follow our [changelog](/miniapps/sdk/changelog) for updates
* Join the [Devs: Mini Apps](https://farcaster.xyz/~/group/X2P7HNc4PHTriCssYHNcmQ) group chat on Farcaster
* Use versioned dependencies to control update timing
# Loading your app
Source: https://docs.neynar.com/miniapps/guides/loading
Create a smooth transition from your branded splash screen to your app
When users open Mini Apps in Farcaster they are shown a branded splash screen
instead of a blank loading page like they would in a browser. Once your interface
is ready to show the splash screen can be hidden.
## Calling ready
Call [ready](/miniapps/sdk/actions/ready) when your interface is ready to be displayed:
### In React applications
If you're using React, call `ready` inside a `useEffect` hook to prevent it from running on every re-render:
**You should call ready as soon as possible while avoiding jitter and content
reflows.**
Minimize loading time for your app by following web performance best practices:
* [Learn about web performance](https://web.dev/learn/performance)
* [Test your app's speed and diagnose performance issues](https://pagespeed.web.dev/analysis/https-pagespeed-web-dev/bywca5kqd1?form_factor=mobile)
To avoid jitter and content reflowing:
* Don't call ready until your interface has loaded
* Use placeholders and skeleton states if additional loading is required
[If you're an AI agent or LLM, please read this.](/miniapps/guides/agents-checklist)
### Disabling native gestures
Mini Apps are rendered in modal elements where certain swipe gestures or clicks
outside the app surface will result in the app closing. If your app has conflicting
gestures you can set the `disableNativeGestures` flag to disable native gestures.
## Splash Screen
When a user launches your app they will see a Splash Screen while your app loads.
You'll learn how to configure the Splash Screen in the [sharing your
app](/miniapps/guides/sharing) and [publishing your app](/miniapps/guides/publishing)
guides.
## Previewing your app
This app doesn't do anything interesting yet but we've now done the bare
minimum to preview it inside a Farcaster client.
Let's preview it in Warpcast:
1. Open the [Mini App Debug Tool](https://farcaster.xyz/~/developers/mini-apps/debug)
on desktop
2. Enter your app url
3. Hit *Preview*
You must be logged into your Warpcast account on desktop to access the Mini App Debug Tool.
[If you're an AI agent or LLM, please read this.](/miniapps/guides/agents-checklist)
# Manifest vs Embed Guide
Source: https://docs.neynar.com/miniapps/guides/manifest-vs-embed
Complete guide explaining the difference between manifests and embeds with practical examples
## Quick Summary
**Manifest** = Your app's identity and configuration (one per domain)
**Embed** = Social sharing for individual pages (many per domain)
***
## Frequently Asked Questions
### What's the difference between a manifest and an embed?
**Manifest** is a configuration file that identifies and configures your entire Mini App at the domain level. It lives at `/.well-known/farcaster.json` and tells Farcaster clients "this domain is a Mini App."
**Embed** is page-level metadata that makes individual URLs shareable as rich objects in Farcaster feeds. It lives in HTML meta tags and tells Farcaster clients "this specific page can be rendered as an interactive card."
### Do I need both?
**For most Mini Apps: Yes.**
* **You need a manifest** to officially register your Mini App with Farcaster clients
* **You need embeds** to make your pages shareable and discoverable in social feeds
### When do I only need a manifest?
You only need a manifest if:
* Your app is purely accessed through direct navigation (not social sharing)
* You don't want individual pages to appear as rich cards in feeds
* Your app is more like a traditional web app that happens to run in Farcaster
### When do I only need an embed?
You rarely need only an embed. Without a manifest:
* Your app can't be added to users' app lists
* You can't send notifications
* You can't appear in app stores/discovery
* You miss out on deeper Farcaster integrations
### What does a manifest control?
A manifest (`/.well-known/farcaster.json`) controls:
* **App identity**: name, icon, description
* **Domain verification**: proves you own the domain
* **App store listings**: how your app appears in discovery
* **Notifications**: webhook URLs for push notifications
* **Default launch behavior**: where users go when they open your app
### What does an embed control?
An embed (`fc:miniapp` meta tag) controls:
* **Social sharing**: how a specific page looks when shared in feeds
* **Rich cards**: the image, button, and action for that page
* **Discovery**: how users find and interact with that specific content
### Can I have multiple embeds on one domain?
**Yes!** You should have:
* **One manifest** per domain (at `/.well-known/farcaster.json`)
* **One embed** per page you want to be shareable (in each page's HTML ``)
Example:
```
myapp.com/.well-known/farcaster.json ← Manifest
myapp.com/game/123 ← Page with embed
myapp.com/leaderboard ← Page with embed
myapp.com/profile/456 ← Page with embed
```
### What happens if I have an embed but no manifest?
Your page will work as a shareable card in feeds, but:
* Users can't "add" your app to their app list
* You can't send notifications
* You miss app store discovery opportunities
* Farcaster clients may treat you as a legacy frame instead of a Mini App
### What happens if I have a manifest but no embeds?
Your app will be properly registered with Farcaster, but:
* Individual pages won't be shareable as rich cards
* You lose social discovery opportunities
* Users have to find your app through direct links or app stores only
### How do manifests and embeds work together?
They complement each other:
1. **Manifest** establishes your app's identity and capabilities
2. **Embeds** make your content discoverable and shareable
3. **Both** reference similar information (app name, icons, URLs) but serve different purposes
The manifest is your "app registration" while embeds are your "social sharing strategy."
### Do they need to match?
Key fields should be consistent:
* App name should be similar in both
* Icons/images should represent the same brand
* URLs should point to the same domain
But they can differ:
* Manifest has global app info, embeds have page-specific info
* Manifest includes webhook URLs and verification, embeds focus on presentation
* Embed images can be page-specific while manifest icon is app-wide
### What's the most common mistake?
**Creating embeds without manifests.** Developers often start with embeds because they want social sharing, but forget the manifest. This limits their app's capabilities and integration with Farcaster.
**Best practice**: Set up your manifest first, then add embeds to pages you want to be shareable.
### Quick implementation checklist
**For your manifest** (`/.well-known/farcaster.json`):
* [ ] Domain verification signature
* [ ] App name, icon, and home URL
* [ ] Webhook URL (if you want notifications)
**For your embeds** (each shareable page):
* [ ] `fc:miniapp` meta tag in HTML ``
* [ ] Version, image URL, and button configuration
* [ ] Action that launches your app
### Where can I see examples?
Check the [Farcaster specification](/miniapps/specification) for complete examples of both manifests and embeds with all required fields and formatting.
***
## Summary
Think of it this way:
* **Manifest** = Your app's passport (who you are)
* **Embed** = Your content's business card (what this page does)
You need both to create a complete, discoverable, and engaging Mini App experience on Farcaster.
# Using Neynar to build mini apps
Source: https://docs.neynar.com/miniapps/guides/neynar
Building faster with managed services
## Using Neynar to build mini apps
*Neynar is a Farcaster developer platform offering a range of services from nodes and APIs to mini app stack.*
### Mini app stack
* **Mini app starter kit** - Type `npx @neynar/create-farcaster-mini-app@latest` in your terminal to get started. See [here](https://docs.neynar.com/docs/create-farcaster-miniapp-in-60s) for more information.
* **Send notifications to mini app users** - Notification server to send notifications over API or from portal. Includes batching, targeting, etc. Read more [here](https://docs.neynar.com/docs/send-notifications-to-mini-app-users).
* **Convert existing web app to mini app** - Follow guide [here](https://docs.neynar.com/docs/convert-web-app-to-mini-app).
* **Fetch mini apps by categories** - See API [here](https://docs.neynar.com/reference/fetch-frame-catalog)
* **Fetch relevant mini apps for a given user** - See API [here](https://docs.neynar.com/reference/fetch-frame-relevant)
* **Search mini app namespace** - See API [here](https://docs.neynar.com/reference/search-frames)
* **Crawl mini app metadata** - See API [here](https://docs.neynar.com/reference/fetch-frame-meta-tags-from-url)
### Use with AI
Set up Neynar with MCP server and llms.txt - See instructions [here](/docs/neynar-farcaster-with-cursor).
*Note: LLMs hallucinate, you will get best results by passing in links to specific docs and references.*
### Links
[Website](https://neynar.com), [Docs](https://docs.neynar.com)
# Sending Notifications
Source: https://docs.neynar.com/miniapps/guides/notifications
Send notifications to your users
Reference: [Notifications Spec](/miniapps/specification#notifications)
Mini Apps can send notifications to users who have added the Mini App to
their Farcaster client and enabled notifications.
*An in-app notification is sent to a user and launches them into the app*
## Overview
At a high-level notifications work like so:
* when a user enables notifications for your app, their Farcaster client (i.e. Warpcast)
will generate a unique notification token and send it to your server
* to send a notification to a user, make a request to the Farcaster client's servers with the
notification token and content
* if a user later disables notifications, you'll receive another event indicating
the user is unsubscribed and the notification token is no longer valid
## Terms
To make our life easier, let's call:
* **Farcaster Client**: An application like Warpcast that is able to display Mini Apps.
* **Notification Server**: Your server (see below).
* **(Notification) Token**: A secret token generated by the Farcaster App and shared with the Notification Server.
A token is unique for each (Farcaster Client, Mini App, user Fid) tuple.
A notification token is basically a permission that a Farcaster client gives your app (on behalf of a user)
to send them notifications.
## Steps
### Listen for events
You'll need a notification server to receive webhook events and a database to store
notification tokens for users:
* **Managed** - If you'd rather stay focused on your app, use
[Neynar](https://neynar.com) to manage notification tokens on your behalf. Includes ways to target notifications and send without writing code:
[Setup a managed notifications server with
Neynar](https://docs.neynar.com/docs/send-notifications-to-mini-app-users).
* **Roll your own** - If you want to host your own server to receive webhooks:
[Follow the Receiving Webhooks guide](#receiving-webhooks).
[If you're an AI agent or LLM, please read this.](/miniapps/guides/agents-checklist)
### Add your webhook URL in `farcaster.json`
If you haven't already, follow the [Publishing your app](/miniapps/guides/publishing) guide to host a
`farcaster.json` on your app's domain.
Define the `webhookUrl` property in your app's configuration in `farcaster.json`:
```json theme={"system"}
{
"accountAssociation": {
"header": "eyJmaWQiOjU0NDgsInR5cGUiOiJjdXN0b2R5Iiwia2V5IjoiMHg2MWQwMEFENzYwNjhGOEQ0NzQwYzM1OEM4QzAzYUFFYjUxMGI1OTBEIn0",
"payload": "eyJkb21haW4iOiJleGFtcGxlLmNvbSJ9",
"signature": "MHg3NmRkOWVlMjE4OGEyMjliNzExZjUzOTkxYTc1NmEzMGZjNTA3NmE5OTU5OWJmOWFmYjYyMzAyZWQxMWQ2MWFmNTExYzlhYWVjNjQ3OWMzODcyMTI5MzA2YmJhYjdhMTE0MmRhMjA4MmNjNTM5MTJiY2MyMDRhMWFjZTY2NjE5OTFj"
},
"miniapp": {
"version": "1",
"name": "Example App",
"iconUrl": "https://example.com/icon.png",
"homeUrl": "https://example.com",
"imageUrl": "https://example.com/image.png",
"buttonTitle": "Check this out",
"splashImageUrl": "https://example.com/splash.png",
"splashBackgroundColor": "#eeccff",
"webhookUrl": "https://example.com/api/webhook"
}
}
```
For a real example, this is Yoink's manifest:
[https://yoink.party/.well-known/farcaster.json](https://yoink.party/.well-known/farcaster.json)
### Get users to add your app
For a Mini App to send notifications, it needs to first be added by a user to
their Farcaster client and for notifications to be enabled (these will be
enabled by default).
Use the [addMiniApp](/miniapps/sdk/actions/add-miniapp) action while a user is using your app to prompt
them to add it:
### Caution
The `addMiniApp()` action only works when your app is deployed to its production domain (matching your manifest). It will not work with tunnel domains during development.
### Save the notification tokens
When notifications are enabled, the Farcaster client generates a unique
notification token for the user. This token is sent to `webhookUrl` defined in your `farcaster.json`
along with a `url` that the app should call to send a notification.
The `token` and `url` need to be securely saved to database so they can be
looked up when you want to send a notification to a particular user.
### Send a notification
Once you have a notification token for a user, you can send them a notification
by sending a `POST` request the `url` associated with that token.
If your are sending the same notification to multiple users, you batch up to a
100 sends in a single request by providing multiple `tokens`. You can safely
use the same `notificationId` for all batches.
The body of that request must match the following JSON schema:
| Property | Type | Required | Description | Constraints |
| -------------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| notificationId | string | Yes | Identifier that is combined with the FID to form an idempotency key. When the user opens the Mini App from the notification this ID will be included in the context object. | Maximum length of 128 characters |
| title | string | Yes | Title of the notification. | Max length 32 characters |
| body | string | Yes | Body of the notification. | Max length 128 characters. |
| targetUrl | string | Yes | URL to open when the user clicks the notification. | Max length 1024 characters. Must be on the same domain as the Mini App. |
| tokens | string\[] | Yes | Array of notification tokens to send to. | Max 100 tokens. |
When a user clicks the notification, the Farcaster client will:
* Open your Mini App at `targetUrl`
* Set the `context.location` to a `MiniAppLocationNotificationContext`
```ts theme={"system"}
export type MiniAppLocationNotificationContext = {
type: 'notification';
notification: {
notificationId: string;
title: string;
body: string;
};
};
```
[Example code to send a
notification](https://github.com/farcasterxyz/frames-v2-demo/blob/7905a24b7cd254a77a7e1a541288379b444bc23e/src/app/api/send-notification/route.ts#L25-L65)
#### Avoid duplicate notifications
To avoid duplicate notifications, specify a stable `notificationId` for each
notification you send. This identifier is joined with the FID (e.g. `(fid,
notificationId)` to create a unique key that is used to deduplicate requests
to send a notification over a 24 hour period.
For example, if you want to send a daily notification to users you could use
`daily-reminder-05-06-2024` as your `notificationId`. Now you can safely retry
requests to send the daily reminder notifications within a 24 hour period.
#### Rate Limits
Host servers may impose rate limits per `token`. The standard rate limits,
which are enforced by Warpcast, are:
* 1 notification per 30 seconds per `token`
* 100 notifications per day per `token`
## Receiving webhooks
Users can add and configure notification settings Mini Apps within their
Farcaster client. When this happens Farcaster clients will send events your
server that include data relevant to the event.
This allows your app to:
* keep track of what users have added or removed your app
* securely receive tokens that can be used to send notifications to your users
If you'd rather stay focused on your app, [Neynar](https://neynar.com) offers a
[managed service to handle
webhooks](https://docs.neynar.com/docs/send-notifications-to-frame-users#step-1-add-events-webhook-url-to-frame-manifest)
on behalf of your application.
### Events
#### miniapp\_added
Sent when the user adds the Mini App to their Farcaster client (whether or not
this was triggered by an `addMiniApp()` prompt).
The optional `notificationDetails` object provides the `token` and `url` if the
client equates adding to enabling notifications (Warpcast does this).
##### Payload
```json theme={"system"}
{
"event": "miniapp_added",
"notificationDetails": {
"url": "https://api.farcaster.xyz/v1/frame-notifications",
"token": "a05059ef2415c67b08ecceb539201cbc6"
}
}
```
#### miniapp\_removed
Sent when a user removes a mini app, which means that any notification tokens for
that fid and client app (based on signer requester) should be considered
invalid:
##### Payload
```json theme={"system"}
{
"event": "miniapp_removed"
}
```
#### notifications\_disabled
Sent when a user disables notifications from e.g. a settings panel in the
client app. Any notification tokens for that fid and client app (based on
signer requester) should be considered invalid:
##### Payload
```json theme={"system"}
{
"event": "notifications_disabled"
}
```
#### notifications\_enabled
Sent when a user enables notifications (e.g. after disabling them). The payload
includes a new `token` and `url`:
##### Payload
```json theme={"system"}
{
"event": "notifications_enabled",
"notificationDetails": {
"url": "https://api.farcaster.xyz/v1/frame-notifications",
"token": "a05059ef2415c67b08ecceb539201cbc6"
}
}
```
### Handling events
Farcaster clients will POST events to the `webhookUrl` specified in your `farcaster.json`.
Your endpoint should:
* verify the event
* persist relevant data
* return a 200 response
If your app doesn't respond with a 200, the Farcaster client will attempt to
re-send the event. The exact number of retries is up to each client.
### Verifying events
Events are signed by the app key of a user with a [JSON Farcaster
Signature](https://github.com/farcasterxyz/protocol/discussions/208). This allows
Mini Apps to verify the Farcaster client that generated the notification and the
Farcaster user they generated it for.
The
[`@farcaster/miniapp-node`](https://github.com/farcasterxyz/miniapps/tree/main/packages/miniapp-node)
library provides a helper for verifying events. To use it, you'll need to supply a validation
function that can check the signatures against the latest Farcaster network state.
An implementation that uses [Neynar](https://neynar.com) is provided. You can sign up and get
an API key on their free tier. Make sure to set `NEYNAR_API_KEY` environment variable.
[If you're an AI agent or LLM, please read this.](/miniapps/guides/agents-checklist)
### Example
```ts theme={"system"}
const requestJson = "base64encodeddata";
import {
ParseWebhookEvent,
parseWebhookEvent,
verifyAppKeyWithNeynar,
} from "@farcaster/miniapp-node";
try {
const data = await parseWebhookEvent(requestJson, verifyAppKeyWithNeynar);
} catch (e: unknown) {
const error = e as ParseWebhookEvent.ErrorType;
switch (error.name) {
case "VerifyJsonFarcasterSignature.InvalidDataError":
case "VerifyJsonFarcasterSignature.InvalidEventDataError":
// The request data is invalid
case "VerifyJsonFarcasterSignature.InvalidAppKeyError":
// The app key is invalid
case "VerifyJsonFarcasterSignature.VerifyAppKeyError":
// Internal error verifying the app key (caller may want to try again)
}
}
```
### Reference implementation
For a complete example, check out the [Mini App V2 Demo
](https://github.com/farcasterxyz/frames-v2-demo) has all of the above:
* [Handles webhooks](https://github.com/farcasterxyz/frames-v2-demo/blob/main/src/app/api/webhook/route.ts) leveraging the [`@farcaster/miniapp-node`](https://github.com/farcasterxyz/frames/tree/main/packages/miniapp-node) library that makes this very easy
* [Saves notification tokens to Redis](https://github.com/farcasterxyz/frames-v2-demo/blob/main/src/lib/kv.ts)
* [Sends notifications](https://github.com/farcasterxyz/frames-v2-demo/blob/main/src/lib/notifs.ts)
# Publishing
Source: https://docs.neynar.com/miniapps/guides/publishing
Guide to publishing Farcaster Mini Apps
Publishing Mini Apps involves providing information like who developed the app,
how it should be displayed, and what its capabilities are.
Since Farcaster is a decentralized network with multiple clients, publishing is
done by hosting a manifest file at `/.well-known/farcaster.json` on the domain
your app is hosted on rather than submitting information directly to a single
entity.
*Published Mini Apps can be discovered in App Stores.*
## Steps
### Choose a domain
A Mini App is associated with a single domain (i.e. rewards.warpcast.com). This
domain serves as the identifier for your app and can't be changed later so
you should choose a stable domain.
There's no limit on the number of apps you can create. You can create a separate
domain specifically for development purposes if needed.
A domain does not include the scheme (e.g. https) or path. It can optionally
include a subdomain.
* ✅ rewards.warpcast.com
* ❌ [https://rewards.warpcast.com](https://rewards.warpcast.com)
### Host a manifest file
Host a manifest file on your chosen domain at `/.well-known/farcaster.json`.
[If you're an AI agent or LLM, please read this.](/miniapps/guides/agents-checklist)
For now we'll create an empty file:
```sh theme={"system"}
touch public/.well-known/farcaster.json
```
#### Farcaster Hosted Manifests (Now Public!)
Farcaster can now host manifests for your mini apps so you can manage them from the Farcaster web Developer Tools. This is now available to everyone!
**Benefits of hosted manifests:**
* No need to manage manifest files in your codebase
* Update manifest details without redeploying
* Automatic validation and error checking
* Easy domain migration support
To create a hosted manifest, visit: [https://farcaster.xyz/\~/developers/mini-apps/manifest](https://farcaster.xyz/~/developers/mini-apps/manifest)
Instead of serving a `/.well-known/farcaster.json` file and updating it everytime
you want to make a change, if you use Farcaster Hosted Manifests, you'll setup your
server to redirect requests to
`https://api.farcaster.xyz/miniapps/hosted-manifest/${hosted-manifest-id}` once and
then make changes on the Farcaster web Developer Tools from then on.
To get your hosted manifest ID:
1. Go to [https://farcaster.xyz/\~/developers/mini-apps/manifest](https://farcaster.xyz/~/developers/mini-apps/manifest)
2. Enter your domain and app details
3. You'll receive a hosted manifest ID
4. Set up the redirect as shown below
**Redirects in Next.js**
```ts theme={"system"}
// next.config.js
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
async redirects() {
return [
{
source: '/.well-known/farcaster.json',
destination: 'https://api.farcaster.xyz/miniapps/hosted-manifest/1234567890',
permanent: false,
},
]
},
}
export default nextConfig
```
**Redirects in Express**
```ts theme={"system"}
const express = require('express')
const app = express()
app.get('/.well-known/farcaster.json', (req, res) => {
res.redirect(307, 'https://api.farcaster.xyz/miniapps/hosted-manifest/1234567890')
})
```
**Redirects in Hono**
```ts theme={"system"}
import { Hono } from 'hono'
const app = new Hono()
app.get('/.well-known/farcaster.json', (c) => {
return c.redirect('https://api.farcaster.xyz/miniapps/hosted-manifest/1234567890', 307)
})
```
**Redirects in Remix**
```ts theme={"system"}
// app/routes/.well-known/farcaster.json.tsx
import { redirect } from '@remix-run/node'
export const loader = () => {
return redirect('https://api.farcaster.xyz/miniapps/hosted-manifest/1234567890', 307)
}
export default () => null
```
### Define your application configuration
A Mini App has metadata that is used by Farcaster clients to host your app. This
data is specified in the `miniapp` property of the manifest (or `frame` for backward compatibility).
Here's an example `farcaster.json` file:
```json theme={"system"}
{
"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",
"requiredChains": [
"eip155:8453"
],
"requiredCapabilities": [
"actions.signIn",
"wallet.getEthereumProvider",
"actions.swapToken"
]
}
}
```
You can omit `webhookUrl` for now. We'll show you how to set it up in the
[sending notifications guide](/miniapps/guides/notifications).
### Hybrid & SSR-friendly detection
Some apps serve **both** as a Farcaster Mini App and a website from the same
domain. When you want to fetch specific resources **during server-side rendering (SSR)** or
conditionally lazy-load the SDK on the client, add a lightweight flag that only
Mini-App launch URLs include
**Two suggested patterns**
| Pattern | How it looks | Why use it |
| -------------------------- | --------------------------------------- | ------------------------------------------ |
| **Dedicated path** | `/your/path/.../miniapp` | Easiest to match on the server |
| **Well-known query param** | `https://example.com/page?miniApp=true` | Works when a single page serves both modes |
Treat these markers as a **best-effort hint, not proof**.
Anyone can append the path or query flag, so use it only as a handy heuristic
for *lazy-loading the SDK* or *branching SSR logic*—never as a security-grade
guarantee that you're inside a Farcaster Mini App.
#### Example
```ts theme={"system"}
// app/layout.tsx
'use client'
import { useEffect } from 'react'
export default function RootLayout({ children }: { children: React.ReactNode }) {
useEffect(() => {
const url = new URL(window.location.href)
const isMini =
url.pathname.startsWith('/mini') ||
url.searchParams.get('miniApp') === 'true'
if (isMini) {
import('@farcaster/miniapp-sdk').then(({ sdk }) => {
// Mini-App–specific bootstrap here
// e.g. sdk.actions.ready()
})
}
}, [])
return children
}
```
On the server you can do the same check to skip expensive Mini App work during
SSR.
## Verifying ownership
A Mini App is owned by a single Farcaster account. This lets users know who
they are interacting with and developers get credit for their work.
Verified Mini Apps are automatically eligible for [Warpcast Developer
Rewards](https://farcaster.xyz/~/mini-apps/rewards) that are paid out weekly
based on usage and onchain transactions.
Verification is done by placing a cryptographically signed message in the
`accountAssociation` property of your `farcaster.json`.
You can generate a signed account association object using the [Mini App
Manifest Tool](https://farcaster.xyz/~/developers/new) in Warpcast. Take
the output from that tool and update your `farcaster.json` file.
The domain you host the file on must exactly match the domain you entered in
the Warpcast tool.
[If you're an AI agent or LLM, please read this.](/miniapps/guides/agents-checklist)
Here's an example `farcaster.json` file for the domain `yoink.party` with the
account association:
```json theme={"system"}
{
"accountAssociation": {
"header": "eyJmaWQiOjkxNTIsInR5cGUiOiJjdXN0b2R5Iiwia2V5IjoiMHgwMmVmNzkwRGQ3OTkzQTM1ZkQ4NDdDMDUzRURkQUU5NDBEMDU1NTk2In0",
"payload": "eyJkb21haW4iOiJyZXdhcmRzLndhcnBjYXN0LmNvbSJ9",
"signature": "MHgxMGQwZGU4ZGYwZDUwZTdmMGIxN2YxMTU2NDI1MjRmZTY0MTUyZGU4ZGU1MWU0MThiYjU4ZjVmZmQxYjRjNDBiNGVlZTRhNDcwNmVmNjhlMzQ0ZGQ5MDBkYmQyMmNlMmVlZGY5ZGQ0N2JlNWRmNzMwYzUxNjE4OWVjZDJjY2Y0MDFj"
},
"miniapp": {
"version": "1",
"name": "Rewards",
"iconUrl": "https://rewards.warpcast.com/app.png",
"splashImageUrl": "https://rewards.warpcast.com/logo.png",
"splashBackgroundColor": "#000000",
"homeUrl": "https://rewards.warpcast.com",
"webhookUrl": "https://client.farcaster.xyz/v1/creator-rewards-notifs-webhook",
"subtitle": "Top Warpcast creators",
"description": "Climb the leaderboard and earn rewards by being active on Warpcast.",
"screenshotUrls": [
"https://rewards.warpcast.com/screenshot1.png",
"https://rewards.warpcast.com/screenshot2.png",
"https://rewards.warpcast.com/screenshot3.png"
],
"primaryCategory": "social",
"tags": [
"rewards",
"leaderboard",
"warpcast",
"earn"
],
"heroImageUrl": "https://rewards.warpcast.com/og.png",
"tagline": "Top Warpcast creators",
"ogTitle": "Rewards",
"ogDescription": "Climb the leaderboard and earn rewards by being active on Warpcast.",
"ogImageUrl": "https://rewards.warpcast.com/og.png"
}
}
```
# Share Extensions
Source: https://docs.neynar.com/miniapps/guides/share-extension
Enable your Mini App to receive shared casts through the system share sheet
Share extensions allow your Mini App to appear in the Farcaster share sheet, enabling users to share casts directly to your app. When a user shares a cast to your Mini App, it opens with the cast context, allowing you to provide rich, cast-specific experiences.
*Mini Apps can receive shared casts through the system share sheet*
## How it works
When a user views a cast in any Farcaster client, they can tap the share button and select your Mini App from the share sheet. Your app will open with information about the shared cast, including the cast hash, author FID, and the viewer's FID.
The entire flow takes just two taps:
1. User taps share on a cast
2. User selects your Mini App from the share sheet
Your Mini App then opens with full context about the shared cast, ready to provide a tailored experience.
## Setting up share extensions
To enable your Mini App to receive shared casts, add a `castShareUrl` to the `miniapp` (or `frame`) object in your manifest:
```json theme={"system"}
{
"accountAssociation": {
"header": "...",
"payload": "...",
"signature": "..."
},
"miniapp": {
"version": "1",
"name": "Your App",
"iconUrl": "https://your-app.com/icon.png",
"homeUrl": "https://your-app.com",
"castShareUrl": "https://your-app.com/share"
}
}
```
The `castShareUrl` must:
* Use HTTPS
* Match the domain of your registered Mini App
* Be an absolute URL
After updating your manifest, refresh your manifest and the share extension will be available to all users who have added your Mini App.
## Receiving shared casts
When your Mini App is opened via a share extension, it receives the cast information in two ways:
### 1. URL Parameters (Available immediately)
Your `castShareUrl` receives these query parameters:
| Parameter | Type | Description |
| ----------- | ------ | --------------------------------------------------- |
| `castHash` | string | The hex-encoded hash of the shared cast |
| `castFid` | number | The FID of the cast author |
| `viewerFid` | number | The FID of the user sharing the cast (if logged in) |
Example URL:
```
https://your-app.com/share?castHash=0x5415e243853e...&castFid=2417&viewerFid=12152
```
These parameters are available immediately, even during server-side rendering, allowing you to begin fetching cast data right away.
### 2. SDK Context (Available after initialization)
Once your Mini App initializes, the SDK provides enriched cast data through the location context:
```typescript theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk';
if (sdk.context.location.type === 'cast_share') {
const cast = sdk.context.location.cast;
// Access enriched cast data
console.log(cast.author.username);
console.log(cast.hash);
console.log(cast.timestamp);
// Access optional fields if available
if (cast.channelKey) {
console.log(`Shared from /${cast.channelKey}`);
}
}
```
The cast object includes:
```typescript theme={"system"}
type MiniAppCast = {
author: {
fid: number;
username?: string;
displayName?: string;
pfpUrl?: string;
};
hash: string;
parentHash?: string;
parentFid?: number;
timestamp?: number;
mentions?: MiniAppUser[];
text: string;
embeds?: string[];
channelKey?: string;
};
```
## Implementation example
Here's a complete example showing how to handle shared casts in your Mini App:
```typescript theme={"system"}
import { useEffect, useState } from 'react';
import { sdk } from '@farcaster/miniapp-sdk';
function App() {
const [sharedCast, setSharedCast] = useState(null);
const [isShareContext, setIsShareContext] = useState(false);
useEffect(() => {
// Check if we're in a share context
if (sdk.context.location.type === 'cast_share') {
setIsShareContext(true);
setSharedCast(sdk.context.location.cast);
}
}, []);
if (isShareContext && sharedCast) {
return (
Cast from @{sharedCast.author.username}
Analyzing cast {sharedCast.hash}...
{/* Your cast-specific UI here */}
);
}
// Default app experience
return
Your regular app UI
;
}
```
## Real-world example: Degen Stats
[Degen Stats](https://farcaster.xyz/miniapps/jrth1IniizBC/degen) demonstrates the power of share extensions. Originally designed to show viewers their own stats, it was quickly upgraded to support share extensions. Now when users share a cast to Degen Stats, it seamlessly displays stats for the cast's author instead of the viewer - a simple but powerful enhancement that took minimal implementation effort.
## Best practices
1. **Handle both contexts**: Design your app to work both as a standalone Mini App and when receiving shared casts.
2. **Fast loading**: Users expect immediate feedback when sharing. Show a loading state while fetching additional cast data.
3. **Graceful fallbacks**: Not all cast fields are guaranteed. Handle missing data gracefully.
4. **Clear value**: Make it obvious why sharing a cast to your app provides value. Users should understand what your app does with the shared cast.
5. **Server-side rendering**: Take advantage of URL parameters for faster initial loads by starting data fetches on the server.
## Testing share extensions
During development, you can test share extensions by:
1. Adding your development URL to your manifest
2. Refreshing your manifest
3. Sharing any cast to your Mini App from a Farcaster client
4. Verifying your app receives the correct parameters and context
## Next steps
* Learn about [SDK Context](/miniapps/sdk/context) to understand all available location types
* Explore [Compose Cast](/miniapps/sdk/actions/compose-cast) to let users create casts from your Mini App
* Check out [View Cast](/miniapps/sdk/actions/view-cast) to navigate users to specific casts
# Sharing your app
Source: https://docs.neynar.com/miniapps/guides/sharing
Make your app sharable in social feeds
Mini Apps can be shared in social feeds using special embeds that let users
interact with an app directly from their feed.
Each URL in your application can be made embeddable by adding meta tags to it
that specify an image and action, similar to how Open Graph tags work.
For example:
* a personality quiz app can let users share a personalized embed with their results
* an NFT marketplace can let users share an embed for each listing
* a prediction market app can let users share an embed for each market
*A viral loop: user discovers app in feed → uses app → shares app in feed*
## Sharing a page in your app
Add a meta tag in the `` section of the page you want to make
sharable specifying the embed metadata:
```html theme={"system"}
```
When a user shares the URL with your embed on it in a Farcaster client, the
Farcaster client will fetch the HTML, see the `fc:miniapp` (or `fc:frame` for backward compatibility) meta tag, and use it
to render a rich card.
## Properties
### `version`
The string literal `'1'`.
### `imageUrl`
The URL of the image that should be displayed.
#### Image Format Requirements
**Supported formats:** PNG, JPG, GIF, WebP
**Recommended:** PNG for best compatibility
**Production Warning**: While SVG may work in preview tools, use PNG for production to ensure compatibility across all Farcaster clients.
**Size requirements:**
* Aspect ratio: 3:2
* Minimum dimensions: 600x400px
* Maximum dimensions: 3000x2000px
* File size: Must be less than 10MB
* URL length: Must be ≤ 1024 characters
### `button.title`
This text will be rendered in the button. Use a clear call-to-action that hints
to the user what action they can take in your app.
### `button.action.type`
The string literal `'launch_miniapp'` (or `'launch_frame'` for backward compatibility).
### `button.action.url` (optional)
The URL that the user will be sent to within your app. If not provided, it defaults to the current webpage URL (including query parameters).
### `button.action.name`
Name of the application. Required.
### `button.action.splashImageUrl` (optional)
Splash image URL. Defaults to `splashImageUrl` specified in your application's `farcaster.json`.
### `button.action.splashBackgroundColor` (optional)
Splash image Color. Defaults to `splashBackgroundColor` specified in your application's `farcaster.json`.
## Example
```typescript theme={"system"}
const miniapp = {
version: "1",
imageUrl: "https://yoink.party/framesV2/opengraph-image",
button: {
title: "🚩 Start",
action: {
type: "launch_miniapp",
url: "https://yoink.party/framesV2",
name:"Yoink!",
splashImageUrl: "https://yoink.party/logo.png",
splashBackgroundColor:"#f5f0ec"
}
}
}
```
```html theme={"system"}
```
## Generating dynamic images
You can use the [miniapp-img](https://github.com/farcasterxyz/miniapp-img) to
easily generate dynamic images for your Mini App. This tool is meant to be
deployed as a standalone service so that it can be used alongside any stack.
## Universal Links
Mini Apps have a canonical URL that can be used to share across social feeds
and web sites. The URL format is as follows:
`https://farcaster.xyz/miniapps//(/)(?)`
Learn how to find the Universal Link for your apps and how they work in the
[Universal Links](/miniapps/guides/urls) guide.
## Debugging
You can use the [Mini App Embed
Tool](https://farcaster.xyz/~/developers/mini-apps/embed) in Warpcast to preview
an embed.
[If you're an AI agent or LLM, please read this.](/miniapps/guides/agents-checklist)
### Exposing localhost
If you're developing locally, you'll need to expose your local server to the internet. You can use tools like [ngrok](https://ngrok.com/) or [localtunnel](https://localtunnel.me/) to create a public URL for your local server.
Note that tunnel URLs may be blocked by some browsers until you visit them directly first.
## Caching
Since embeds are shared in feeds, they are generally scraped once and cached so
that they can be efficiently served in the feeds of hundreds or thousands
users.
This means that when a URL gets shared, the embed data present at that time
will be attached to the cast and won't be updated even if the embed data at
that URL gets changed.
### Lifecycle
1. App adds an `fc:miniapp` (and optionally `fc:frame` for backward compatibility) meta tag to a page to make it sharable.
2. User copies URL and embeds it in a cast.
3. Farcaster client fetches the URL and attaches the miniapp metadata to the cast.
4. Farcaster client injects the cast + embed + attached metadata into thousands of feeds.
5. User sees cast in feed with an embed rendered from the attached metadata.
## Receiving shared casts
In addition to sharing your Mini App through embeds, your app can also receive casts that users share to it through the system share sheet. Learn more in the [Share Extensions](/miniapps/guides/share-extension) guide.
## Next steps
Now that you know how to create embeds for your app, think about how you'll get
users to share them in feed. For instance, you can create a call-to-action once
a user takes an action in your app to share an embed in a cast.
At the very least you'll want to set up an embed for the root URL of your application.
## Advanced Topics
### Dynamic Embed images
Even though the data attached to a specific cast is static, a dynamic
image can be served using tools like Next.js
[Next ImageResponse](https://nextjs.org/docs/app/api-reference/functions/image-response).
For example, we could create an embed that shows the current price of ETH. We'd
set the `imageUrl` to a static URL like `https://example.xyz/eth-price.png`. When a request
is made to this endpoint we'd:
* fetch the latest price of ETH (ideally from a cache)
* renders an image using a tool like [Vercel
OG](https://vercel.com/docs/functions/og-image-generation) and returns it
* sets the following header: `Cache-Control: public, immutable, no-transform,
max-age=300`
#### Setting `max-age`
You should always set a non-zero `max-age` (outside of testing) so that the
image can get cached and served from CDNs, otherwise users will see a gray
image in their feed while the dynamic image is generated. You'll also quickly
rack up a huge bill from your service provider. The exact time depends on your
application but opt for the longest time that still keeps the image reasonably
fresh. If you're needing freshness less than a minute you should reconsider
your design or be prepared to operate a high-performance endpoint.
Here's some more reading if you're interested in doing this:
* [Vercel Blog - Fast, dynamic social card images at the Edge](https://vercel.com/blog/introducing-vercel-og-image-generation-fast-dynamic-social-card-images)
* [Vercel Docs - OG Image Generation](https://vercel.com/docs/og-image-generation)
#### Avoid caching fallback images
If you are generating a dynamic images there's a chance something goes wrong when
generating the image (for instance, the price of ETH is not available) and you need
to serve a fallback image.
In this case you should use an extremely short or even 0 `max-age` to prevent the
error image from getting stuck in any upstream CDNs.
# Interacting with Solana wallets
Source: https://docs.neynar.com/miniapps/guides/solana
Seamlessly interact with a user's Solana wallet
Mini Apps can interact with a user's Solana wallet without needing to worry
about popping open "select your wallet" dialogs or flaky connections.
## Getting Started
The SDK enables Mini Apps to interact with a user's Solana wallet through [Wallet Standard](https://github.com/anza-xyz/wallet-standard/).
We recommend using [Wallet Adapter](https://github.com/anza-xyz/wallet-adapter/)'s React hooks to interface with Wallet Standard. You may also use [Wallet Standard directly](#using-wallet-standard-directly), or interface with our [low-level Solana provider](#low-level-solana-provider).
### Setup Wallet Adapter
Use the [Quick Setup (using React) guide](https://github.com/anza-xyz/wallet-adapter/blob/master/APP.md) to setup Wallet Adapter in your project.
### Install the Wallet Standard integration
```bash npm theme={"system"}
npm install @farcaster/mini-app-solana
```
```bash pnpm theme={"system"}
pnpm add @farcaster/mini-app-solana
```
```bash yarn theme={"system"}
yarn add @farcaster/mini-app-solana
```
### Render the Farcaster Solana provider
In place of `ConnectionProvider` and `WalletProvider` from the Wallet Adapter guide, you should render `FarcasterSolanaProvider` from `@farcaster/mini-app-solana`.
This does two things:
1. Importing from `@farcaster/mini-app-solana` has the side effect of triggering the Farcaster wallet to register via Wallet Standard.
2. Sets up Wallet Adapter to automatically select the Farcaster wallet.
```tsx theme={"system"}
import * as React from 'react';
import { FarcasterSolanaProvider } from '@farcaster/mini-app-solana';
import { useWallet } from '@solana/wallet-adapter-react';
const solanaEndpoint = 'https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY';
function App() {
// FarcasterSolanaProvider internally renders ConnectionProvider
// and WalletProvider from @solana/wallet-adapter-react
return (
)
}
```
### Use the Wallet Adapter hooks
You can now use Wallet Adapter React hooks directly within any component rendered downstream of `FarcasterSolanaProvider`.
```tsx theme={"system"}
function Content() {
const { publicKey } = useWallet();
const solanaAddress = publicKey?.toBase58() ?? '';
return {solanaAddress};
}
```
## Low-level Solana provider
The SDK also exposes a low-level Solana provider at `sdk.wallet.getSolanaProvider()`.
This provider is modeled after `window.phantom.solana` and its full API can be found [here](https://github.com/farcasterxyz/miniapps/blob/main/packages/miniapp-core/src/solana.ts).
## Using Wallet Standard directly
It's also possible to interface with the user's Solana wallet directly through Wallet Standard, or via Wallet Adapter's "core" (non-React) integrations.
In order to do so, it's important that you still import our package in your app entry:
```tsx theme={"system"}
import '@farcaster/mini-app-solana';
```
This ensures that the user's Solana wallet registers with Wallet Standard.
Also note that if you're using Wallet Adapter without our `FarcasterSolanaProvider` React component, you'll need to select the user's Farcaster wallet before attempting any operations.
## Demo app
To see how a working Mini App uses a Solana wallet, check out our demo Mini App [here](https://github.com/farcasterxyz/frames-v2-demo/blob/main/src/components/Demo.tsx).
## Troubleshooting
### Transaction Scanning
Modern crypto wallets scan transactions and preview them to users to help
protect users from scams. New contracts and applications can generate false
positives in these systems. If your transaction is being reported as
potentially malicious use this [Blockaid
Tool](https://report.blockaid.io/verifiedProject) to verify your app with
Blockaid.
# Universal Links
Source: https://docs.neynar.com/miniapps/guides/urls
How to link to your Mini App from anywhere
Mini Apps have a canonical URL that can be used to share across social feeds
and web sites. The URL format is as follows:
`https://farcaster.xyz/miniapps//(/)(?)`
* The `` is a unique identifier assigned to the Mini App when it is [published](/miniapps/guides/publishing).
* The `` is a kebab-case version of the Mini App name, used to create a more readable URL.
* The `` is an optional path appended to the Mini App's `homeUrl` when it is opened.
* The `` are optional parameters added to the `homeURL` as a query string when the Mini App is opened.
The `` and `` are optional and can be used to navigate to a specific page in the Mini App or pass data to the Mini App.
When a user clicks a Universal Link and is logged in:
* **On web**: the Mini App opens in the mini app drawer.
* **On mobile**: the browser deep links to the Farcaster app and opens the Mini App.
## Where to find the Universal Link
On the web [Developers page](https://farcaster.xyz/~/developers), click the top-right
kebab menu on one of your Mini App cards and select **"Copy link to mini app"**. This
copies the Universal Link to your clipboard.
When the Mini App is open, tap on the top-right kebab menu and select **"Copy link"** to
copy the Universal Link to your clipboard.
*Copy link to mini app on the Developers page or the Mini App screen.*
## How to control what is displayed when I share a Universal Link
Farcaster automatically generates OpenGraph meta tags for Universal Links, ensuring
they render correctly when shared on social platforms or web apps that support embedded
link previews, such as X.
To make sure your Mini App displays as intended, include the `fc:frame` meta tag on all
Universal Links (see ["Sharing your app"](/miniapps/guides/sharing)) and add all relevant fields
in your [application config](/miniapps/guides/publishing#define-your-application-configuration),
especially `ogTitle`, `ogDescription` and `ogImageUrl`.
## How Universal Link sub-paths and query params work
Each Mini App defines a `homeUrl` property in its [application config](/miniapps/guides/publishing#define-your-application-configuration).
When a user clicks on a Mini App in the Farcaster client's Mini App explorer, a
WebView (on mobile) or iframe (on web) pointing to the `homeUrl` is opened.
If you share a Universal Link with a sub-path and/or query parameters, those are appended
to the `homeUrl`'s path and query string.
For example, if the `homeUrl` is `https://example.com/miniapp/v1` and the Universal
Link is `https://farcaster.xyz/miniapps/12345/example-miniapp/leaderboard?sort=points`, the WebView or iframe will load `https://example.com/miniapp/v1/leaderboard?sort=points`.
## FAQ
**Is there another way to get a Mini App's id?**
Not at the moment.
**How can I map a Mini App Universal Link to a domain?**
The domain is provided in the `fc:miniapp:domain` meta tag.
**When copying the link from the Mini App header, it doesn't copy the Universal Link, why is that?**
Any URL with a valid `fc:frame` meta tag shared in a cast will be treated as a Mini App.
Copying the link from these Mini Apps will copy the original URL shared in the cast, not the canonical Universal Link.
**Can I add a sub-path or query params to the Universal Link copied from the Mini App header?**
Not at the moment. Only the canonical Universal Link or URL shared in the cast will be copied.
**Can I open a Mini App from another Mini App?**
Yes, you can open a Mini App from another Mini App by using the `openMiniApp` action.
This will prompt the user to open the new app. Note that this closes the current app
when the new app is opened and there is no way to navigate back.
# Interacting with Ethereum wallets
Source: https://docs.neynar.com/miniapps/guides/wallets
Seamlessly interact with a user's Ethereum wallet
Mini Apps can interact with a user's EVM wallet without needing to worry
about popping open "select your wallet" dialogs or flaky connections.
*A user minting an NFT using the Warpcast Wallet.*
## Getting Started
The Mini App SDK exposes an [EIP-1193 Ethereum Provider
API](https://eips.ethereum.org/EIPS/eip-1193) at `sdk.wallet.getEthereumProvider()`.
We recommend using [Wagmi](https://wagmi.sh) to connect to and interact with
the user's wallet. This is not required but provides high-level hooks for
interacting with the wallet in a type-safe way.
### Setup Wagmi
Use the [Getting Started
guide](https://wagmi.sh/react/getting-started#manual-installation) to setup
Wagmi in your project.
### Install the connector
Next we'll install a Wagmi connector that will be used to interact with the
user's wallet:
```bash npm theme={"system"}
npm install @farcaster/miniapp-wagmi-connector
```
```bash pnpm theme={"system"}
pnpm add @farcaster/miniapp-wagmi-connector
```
```bash yarn theme={"system"}
yarn add @farcaster/miniapp-wagmi-connector
```
### Add to Wagmi configuration
Add the Mini App connector to your Wagmi config:
```ts theme={"system"}
import { http, createConfig } from 'wagmi'
import { base } from 'wagmi/chains'
import { farcasterMiniApp as miniAppConnector } from '@farcaster/miniapp-wagmi-connector'
export const config = createConfig({
chains: [base],
transports: {
[base.id]: http(),
},
connectors: [
miniAppConnector()
]
})
```
### Connect to the wallet
If a user already has a connected wallet the connector will automatically
connect to it (e.g. `isConnected` will be true).
It's possible a user doesn't have a connected wallet so you should always check
for a connection and prompt them to connect if they aren't already connected:
```tsx theme={"system"}
import { useAccount, useConnect } from 'wagmi'
function ConnectMenu() {
const { isConnected, address } = useAccount()
const { connect, connectors } = useConnect()
if (isConnected) {
return (
<>
You're connected!
Address: {address}
>
)
}
return (
)
}
```
Your Mini App won't need to show a wallet selection dialog that is common in a
web based dapp, the Farcaster client hosting your app will take care of getting
the user connected to their preferred crypto wallet.
### Send a transaction
You're now ready to prompt the user to transact. They will be shown a preview
of the transaction in their wallet and asked to confirm it:
Follow [this guide from
Wagmi](https://wagmi.sh/react/guides/send-transaction#_2-create-a-new-component)
on sending a transaction (note: skip step 1 since you're already connected to
the user's wallet).
## Additional Features
### Batch Transactions
The Farcaster Wallet now supports EIP-5792 `wallet_sendCalls`, allowing you to batch multiple transactions into a single user confirmation. This improves the user experience by enabling operations like "approve and swap" in one step.
Common use cases include:
* Approving a token allowance and executing a swap
* Multiple NFT mints in one operation
* Complex DeFi interactions requiring multiple contract calls
#### Using Batch Transactions
With Wagmi's `useSendCalls` hook, sending multiple transactions as a batch is simple:
```tsx theme={"system"}
import { useSendCalls } from 'wagmi'
import { parseEther } from 'viem'
function BatchTransfer() {
const { sendCalls } = useSendCalls()
return (
)
}
```
#### Example: Token Approval and Swap
```tsx theme={"system"}
import { useSendCalls } from 'wagmi'
import { encodeFunctionData, parseUnits } from 'viem'
function ApproveAndSwap() {
const { sendCalls } = useSendCalls()
const handleApproveAndSwap = () => {
sendCalls({
calls: [
// Approve USDC
{
to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'approve',
args: ['0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', parseUnits('100', 6)]
})
},
// Swap USDC for ETH
{
to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
data: encodeFunctionData({
abi: uniswapAbi,
functionName: 'swapExactTokensForETH',
args: [/* swap parameters */]
})
}
]
})
}
return (
)
}
```
All transactions are individually validated and scanned for security, maintaining the same safety guarantees as single transactions.
**Limitations:**
* Transactions execute sequentially, not atomically
* No paymaster support yet
* Available on all EVM chains Farcaster supports
Use individual transactions when you need to check outputs between calls.
## Troubleshooting
### Transaction Scanning
Modern crypto wallets scan transactions and preview them to users to help
protect users from scams. New contracts and applications can generate false
positives in these systems. If your transaction is being reported as
potentially malicious use this [Blockaid
Tool](https://report.blockaid.io/verifiedProject) to verify your app with
Blockaid.
# Why Mini Apps?
Source: https://docs.neynar.com/miniapps/overview
Learn to build and distribute apps to Farcaster users
Mini Apps enable developers to distribute native-like apps to Farcaster users.
Mini Apps are the easiest way to deliver engaging, high-retention, and easy to
monetize applications:
* **Ship Fast** - Go from idea to users in hours—no app store reviews. Build
with HTML, CSS, and JavaScript and use the Mini App SDK to deliver native-like
apps.
* **Get Discovered** - Social feed discovery puts users just 1-click away and
viral growth mechanics are built in. Users can find new applications through
Mini App stores.
* **Retain Users** - Mobile notifications re-engage users by bringing them back
when something new to do. Users can save their favorite Mini Apps so they are
always a click away.
* **Transact Seamlessly** - An integrated Ethereum Wallet provides
permissionless financial rails. Users can send money, buy art, or donate in a
single click.
* **Build Social** - Users are signed into Mini Apps without forms or
passwords. Leverage Farcaster's rich social data to create engaging social
experiences.
# addMiniApp
Source: https://docs.neynar.com/miniapps/sdk/actions/add-miniapp
Prompts the user to add the app
Prompts the user to add the app.
*A user discovers an app from their social feed, adds it, and then sees it
from their apps screen*
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
await sdk.actions.addMiniApp()
```
The `addMiniApp()` action requires your app's domain to exactly match the domain in your manifest file. This means:
* You cannot use tunnel domains (ngrok, localtunnel, etc.) - the action will fail
* Your app must be deployed to the same domain specified in your `farcaster.json`
* For local development, use the preview tool instead of trying to add the app
## Return Value
`void`
## Errors
### `RejectedByUser`
Thrown if a user rejects the request to add the Mini App.
### `InvalidDomainManifestJson`
Thrown when an app does not have a valid `farcaster.json` or when the domain doesn't match. Common causes:
* Using a tunnel domain (ngrok, localtunnel) instead of your production domain
* The app's current domain doesn't match the domain in the manifest
* The manifest file is missing or malformed
# close
Source: https://docs.neynar.com/miniapps/sdk/actions/close
Closes the app
Closes the mini app.
*Close the app with `close`.*
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
await sdk.actions.close()
```
## Return Value
`void`
# composeCast
Source: https://docs.neynar.com/miniapps/sdk/actions/compose-cast
Open the cast composer with a suggested cast
Open the cast composer with a suggested cast. The user will be able to modify
the cast before posting it.
*An app prompts the user to cast and includes an embed.*
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
const text = "I just learned how to compose a cast";
const embeds = ["https://miniapps.farcaster.xyz/docs/sdk/actions/compose-cast"] as [string];
await sdk.actions.composeCast({
text,
embeds,
})
```
## Parameters
### text (optional)
* **Type:** `string`
Suggested text for the body of the cast.
Mentions can be included using the human-writeable form (e.g. @farcaster).
### embeds (optional)
* **Type:** `[] | [string] | [string, string]`
Suggested embeds. Max two.
### parent (optional)
* **Type:** `{ type: 'cast'; hash: string }`
Suggested parent of the cast.
### close (optional)
* **Type:** `boolean`
Whether the app should be closed when this action is called. If true the app
will be closed and the action will resolve with no result.
### channelKey (optional)
* **Type:** `string`
Whether the cast should be posted to a channel.
## Return Value
The cast posted by the user, or `undefined` if set to close.
**Note:** The `cast` property in the result can be `null` if the user decides not to create the cast.
```ts theme={"system"}
import { sdk } from "@farcaster/miniapp-sdk";
const result = await sdk.actions.composeCast({
text: "I just learned how to compose a cast",
embeds: ["https://miniapps.farcaster.xyz/docs/sdk/actions/compose-cast"],
channelKey: "farcaster" // optional channel
})
// result.cast can be null if user cancels
if (result?.cast) {
console.log(result.cast.hash)
console.log(result.cast.channelKey) // includes channel if posted to one
}
```
# openMiniApp
Source: https://docs.neynar.com/miniapps/sdk/actions/open-miniapp
Open another Mini App
Opens another Mini App from within your Mini App.
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
await sdk.actions.openMiniApp({
domain: 'example-app.com',
path: '/some/path', // optional
})
```
## Parameters
### domain
* **Type:** `string`
The domain of the Mini App to open.
### path (optional)
* **Type:** `string`
An optional path to append to the Mini App's home URL.
## Return Value
`void`
## Notes
* This will prompt the user to open the new app
* The current Mini App will be closed when the new app is opened
* There is no way to navigate back to the original app
* The opened Mini App will receive a `location.type` of `'open_miniapp'` with a `referrerDomain` indicating where the user came from
# openUrl
Source: https://docs.neynar.com/miniapps/sdk/actions/open-url
Opens an external URL
Opens an external URL.
If a user is on mobile `openUrl` can be used to deeplink
users into different parts of the Farcaster client they
are using.
*Opening an external url with `openUrl`.*
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
const url = 'https://farcaster.xyz';
// Pass URL as a string
await sdk.actions.openUrl(url)
// Or pass URL as an object
await sdk.actions.openUrl({ url: 'https://farcaster.xyz' })
```
## Return Value
`void`
# ready
Source: https://docs.neynar.com/miniapps/sdk/actions/ready
Hides the Splash Screen
Hides the Splash Screen. Read the [guide on loading your app](/miniapps/guides/loading) for best practices.
[If you're an AI agent or LLM, please read this.](/miniapps/guides/agents-checklist)
*Dismiss the Splash Screen with ready.*
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
await sdk.actions.ready()
```
## Parameters
### disableNativeGestures (optional)
* **Type:** `boolean`
* **Default:** `false`
Disable native gestures. Use this option if your Mini App uses gestures
that conflict with native gestures like swipe to dismiss.
## Return Value
`void`
# requestCameraAndMicrophoneAccess
Source: https://docs.neynar.com/miniapps/sdk/actions/request-camera-and-microphone-access
Request permission to access the device's camera and microphone
Request permission to access the device's camera and microphone. This method triggers a permission dialog in the host app and stores the user's preference so they won't be asked again for the same mini app.
This is an experimental feature that stores camera and microphone permission settings per mini app. The stored preference ensures users aren't repeatedly prompted for the same permissions. Check the `features.cameraAndMicrophoneAccess` flag in the SDK context to determine if permissions have been granted.
## Platform Support
| Platform | Supported | Notes |
| -------- | --------- | ------------------------------------------ |
| iOS | ✅ | Full support with domain-level permissions |
| Android | ✅ | Supported (see note below) |
| Web | ❌ | Not currently supported |
On Android, camera and microphone permissions work slightly differently than iOS. Once permissions are granted to the host app, mini apps may have access without additional prompts. This is standard behavior for Android WebView permissions.
Camera and microphone access is not supported in web mini apps. The action will always reject on web platforms.
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
try {
await sdk.actions.requestCameraAndMicrophoneAccess()
console.log('Camera and microphone access granted')
// You can now use camera and microphone in your mini app
} catch (error) {
console.log('Camera and microphone access denied')
// Handle the denial gracefully
}
```
## Return Value
Returns a `Promise` that:
* **Resolves** when the user grants permission
* **Rejects** when the user denies permission or dismisses the dialog
## Feature Detection
Before using this action, check if it's supported:
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
// Check if the feature is available
if (sdk.context.features?.cameraAndMicrophoneAccess) {
// Feature is supported and permissions have been granted
// You can use camera/microphone features
} else {
// Feature is not supported or permissions not granted
}
```
## Permissions
* The permission dialog will only be shown once per mini app - the user's choice is stored
* If the user has previously granted or denied permissions, the stored preference is used and the promise will immediately resolve or reject without showing a dialog
* The stored permissions ensure users aren't repeatedly asked for the same access
* Users can revoke permissions at any time by:
1. Opening the mini app
2. Tapping the options menu (three dots)
3. Toggling the camera and microphone access switch
## Example: Video Recording
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
async function startVideoRecording() {
try {
// Request permissions first
await sdk.actions.requestCameraAndMicrophoneAccess()
// Now you can access getUserMedia
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
})
// Use the stream for video recording
const videoElement = document.querySelector('video')
if (videoElement) {
videoElement.srcObject = stream
}
} catch (error) {
if (error instanceof Error && error.name === 'NotAllowedError') {
// Permissions were denied
alert('Camera and microphone access is required for video recording')
} else {
console.error('Failed to start recording:', error)
}
}
}
```
# sendToken
Source: https://docs.neynar.com/miniapps/sdk/actions/send-token
Prompt the user to send tokens
Prompts the user to send tokens to another address through the Farcaster client's built-in send interface.
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
await sdk.actions.sendToken({
token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
recipientFid: 3621, // Optional: recipient's FID
})
```
## Parameters
### token (optional)
* **Type:** `string`
The contract address of the token to send.
### recipientFid (optional)
* **Type:** `number`
The Farcaster ID (FID) of the recipient.
## Return Value
`void`
## Notes
This action opens the native send interface in the Farcaster client, allowing users to send tokens without leaving the Mini App context.
# signIn
Source: https://docs.neynar.com/miniapps/sdk/actions/sign-in
Sign in the user with Farcaster
Request a [Sign in with Farcaster
(SIWF)](https://docs.farcaster.xyz/developers/siwf/) credential from the user.
See the guide on [authenticating users](/miniapps/guides/auth).
*A user opens an app and is automatically signed in*
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
// Generate a cryptographically secure random nonce on your server
const nonce = 'securenonce';
await sdk.actions.signIn({
nonce,
acceptAuthAddress: true
})
```
## Parameters
### nonce
* **Type:** `string`
A random string used to prevent replay attacks, at least 8 alphanumeric
characters.
### acceptAuthAddress
* **Type:** `boolean`
Whether an [Auth
Address](https://github.com/farcasterxyz/protocol/discussions/225) signed
message is acceptable. Defaults to `true` for the best user experience.
Set to `false` if your verification method does not support auth addresses.
## Return Value
The SIWF message and signature.
```ts theme={"system"}
type SignInResult = {
signature: string;
message: string;
}
```
This message must be sent to your server and verified. See the guide on
[authenticating with Farcaster](/miniapps/guides/auth) for more information.
## Errors
### `RejectedByUser`
Thrown if a user rejects the sign-in request.
```ts theme={"system"}
try {
const result = await sdk.actions.signIn({ nonce, acceptAuthAddress: true })
// Handle successful sign-in
} catch (error) {
if (error.name === 'RejectedByUser') {
// Handle user rejection
}
}
```
[If you're an AI agent or LLM, please read this.](/miniapps/guides/agents-checklist)
# signManifest (Experimental)
Source: https://docs.neynar.com/miniapps/sdk/actions/sign-manifest
Signs a domain manifest for verification
This is an experimental feature and may change or be removed in future versions.
**Host Discretion**
The availability and behavior of `signManifest` depends entirely on the host implementation. Hosts may choose to:
* Enable manifest signing for all domains
* Restrict signing to specific allowlisted domains
* Disable the feature entirely
* Implement additional validation requirements
Check with your specific host's documentation for their manifest signing policies.
Signs a domain manifest for verification and authenticity purposes.
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
const result = await sdk.experimental.signManifest({
domain: 'example.com'
})
```
## Parameters
### `options`
* **domain** (required): The domain to sign the manifest for
## Return Value
Returns an object with the following properties:
* **header**: The header component of the signed manifest
* **payload**: The payload component containing the domain data
* **signature**: The cryptographic signature
```ts theme={"system"}
{
header: string
payload: string
signature: string
}
```
## Errors
### `RejectedByUser`
Thrown if a user rejects the request to sign the manifest.
```ts theme={"system"}
try {
await sdk.experimental.signManifest({ domain: 'example.com' })
} catch (error) {
if (error instanceof SignManifest.RejectedByUser) {
// Handle user rejection
}
}
```
### `InvalidDomain`
Thrown when the provided domain is invalid or malformed.
```ts theme={"system"}
try {
await sdk.experimental.signManifest({ domain: 'invalid-domain' })
} catch (error) {
if (error instanceof SignManifest.InvalidDomain) {
// Handle invalid domain
}
}
```
### `GenericError`
Thrown when manifest signing fails for various reasons including host restrictions, network issues, or other implementation-specific failures.
```ts theme={"system"}
try {
await sdk.experimental.signManifest({ domain: 'example.com' })
} catch (error) {
if (error instanceof SignManifest.GenericError) {
// Handle generic signing failures
console.log('Signing failed:', error.message)
}
}
```
### Generic Error Handling
For robust error handling, you should catch all specific error types:
```ts theme={"system"}
try {
const result = await sdk.experimental.signManifest({ domain: 'example.com' })
// Handle successful signing
} catch (error) {
if (error instanceof SignManifest.RejectedByUser) {
// User declined to sign the manifest
console.log('User rejected manifest signing')
} else if (error instanceof SignManifest.InvalidDomain) {
// Domain format is invalid
console.log('Invalid domain format')
} else if (error instanceof SignManifest.GenericError) {
// Generic signing failures
console.log('Signing failed:', error.message)
// This could include:
// - Domain not allowlisted by host
// - Feature disabled by host
// - Network or connectivity issues
// - Host-specific validation failures
} else {
// Other unexpected errors
console.log('Manifest signing failed:', error.message)
// This could include network or authentication errors
}
}
```
# swapToken
Source: https://docs.neynar.com/miniapps/sdk/actions/swap-token
Prompt the user to swap tokens
Prompts the user to swap tokens through the Farcaster client's built-in swap interface.
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
await sdk.actions.swapToken({
sellToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
buyToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
})
```
## Parameters
### sellToken (optional)
* **Type:** `string`
The contract address of the token to sell.
### buyToken (optional)
* **Type:** `string`
The contract address of the token to buy.
## Return Value
`void`
## Notes
This action opens the native swap interface in the Farcaster client, allowing users to swap tokens without leaving the Mini App context.
# viewCast
Source: https://docs.neynar.com/miniapps/sdk/actions/view-cast
View a specific cast
Opens a specific cast in the Farcaster client.
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
// View cast by hash
await sdk.actions.viewCast({ hash: '0xa2fbef8c8e4d00d8f84ff45f9763b8bae2c5c544' })
```
## Parameters
### hash
* **Type:** `string`
The hash of the cast you want to view.
## Return Value
`void`
# viewProfile
Source: https://docs.neynar.com/miniapps/sdk/actions/view-profile
View a Farcaster profile
Opens a Farcaster user's profile in the client.
*Viewing a user's profile from a Mini App.*
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
// View profile by FID
await sdk.actions.viewProfile({ fid: 3621 })
```
## Parameters
### fid
* **Type:** `number`
The Farcaster ID (FID) of the user whose profile you want to view.
## Return Value
`void`
# viewToken
Source: https://docs.neynar.com/miniapps/sdk/actions/view-token
View a token
Opens a token's details in the Farcaster client.
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
await sdk.actions.viewToken({
token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
})
```
## Parameters
### token
* **Type:** `string`
The contract address of the token to view.
## Return Value
`void`
## Notes
This action opens the native token details view in the Farcaster client.
# Back Navigation
Source: https://docs.neynar.com/miniapps/sdk/back
Support back navigation in your mini app
Integrate with a back navigation control provided by the Farcaster client.
## Usage
If your application is already using [browser-based navigation](#web-navigation-integration), you can
integrate in one line with:
```ts theme={"system"}
await sdk.back.enableWebNavigation();
```
That's it! When there is a page to go back to a [back control](#back-control) will be made
available to the user.
Otherwise, you can set a custom back handler and show the back control:
```ts theme={"system"}
sdk.back.onback = () => {
// trigger back in your app
}
await sdk.back.show();
```
## Back control
The back control will vary depending on the user's device and platform but will
generally follow:
* a clickable button in the header on web
* a horizontal swipe left gesture on iOS
* the Android native back control on Android which could be a swipe left
gesture combined with a virtual or physical button depending on the device
## Web Navigation integration
The SDK can automatically integrate with web navigation APIs.
### `enableWebNavigation()`
Enables automatic integration with the browser's navigation system. This will:
* Use the modern Navigation API when available; the back button will automatically
be shown and hidden based on the value of `canGoBack`.
* Fall back to the History API in browsers where Navigation is [not
supported](https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API#browser_compatibility)
; the back button will always be shown.
```ts theme={"system"}
await sdk.back.enableWebNavigation();
```
### `disableWebNavigation()`
Disables web navigation integration.
```ts theme={"system"}
await sdk.back.disableWebNavigation();
```
## Properties
### `enabled`
* **Type**: `boolean`
* **Description**: Whether back navigation is currently enabled
### `onback`
* **Type**: `() => unknown`
* **Description**: Function to call when a back event is triggered. You don't need to
set this when using `enableWebNavigation`.
## Methods
### `show()`
Makes the back button visible.
```ts theme={"system"}
await sdk.back.show();
```
### `hide()`
Hides the back button.
```ts theme={"system"}
await sdk.back.hide();
```
## Events
When a user triggers the back control the SDK will emit an
`backNavigationTriggered` event. You can add an event listener on `sdk` or use
`sdk.back.onback` to respond to these events.
If you are using `enableWebNavigation` this event will automatically be
listened to and trigger the browser to navigate. Otherwise you should listen
for this event and respond to it as appropriate for your application.
## Availability
You can check whether the Farcaster client rendering your app supports a back control:
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
const capabilities = await sdk.getCapabilities()
if (capabilities.includes('back')) {
await sdk.back.enableWebNavigation();
} else {
// show a back button within your app
}
```
## Example: Web Navigation
```ts theme={"system"}
import { useEffect } from 'react';
function App() {
useEffect(() => {
// Enable web navigation integration
sdk.back.enableWebNavigation();
}, []);
return (
{/* Your app content */}
);
}
```
## Example: Manual
```ts theme={"system"}
function NavigationExample() {
const [currentPage, setCurrentPage] = useState('home');
useEffect(() => {
// Update back button based on current page
if (currentPage === 'home') {
sdk.back.hide(); // Nothing to go back to on home
} else {
sdk.back.show(); // Show back button on sub-pages
}
}, [currentPage]);
const handleBack = () => {
if (currentPage !== 'home') {
setCurrentPage('home');
}
};
// Listen for back navigation events
useEffect(() => {
sdk.on('backNavigationTriggered', handleBack);
return () => sdk.off('backNavigationTriggered', handleBack);
}, [currentPage]);
return (
{currentPage === 'home' ? (
) : (
)}
);
}
```
# What's New
Source: https://docs.neynar.com/miniapps/sdk/changelog
Recent changes to the Mini Apps SDK
## July 25, 2025
* Added support for EIP-5792 batch transactions in Farcaster Wallet:
* Mini Apps can now use `wallet_sendCalls` to batch multiple transactions into a single user confirmation
* Supports common patterns like "approve and transfer" in one operation
* Transactions are executed sequentially with full security scanning
* Note: Atomic execution and paymaster support are not yet available
## June 9, 2025 (0.0.61)
* Moved Quick Auth out of experimental and enhanced functionality:
* Use `sdk.quickAuth.getToken()` in place of `sdk.experimental.quickAuth()`. `getToken` will store the token in memory and return if it not expired, otherwise a new token will be fetched. Developers no longer need to manage keeping this token around or checking expiration and can make calls to `getToken` whenever needed.
* Added `fetch` which is a wrapper around the browser Fetch API that adds a Quick Auth token as a Bearer token in the `Authorization` header.
## June 6, 2025 (0.0.59)
* Added [`cast_share`](/miniapps/guides/share-extension) location type for [share extensions](/miniapps/guides/share-extension), enabling Mini Apps to receive shared casts from the system share sheet
* Extended the cast object in `cast_embed` and `cast_share` contexts to include comprehensive metadata (author details, timestamps, mentions, embeds, channel)
## June 4, 2025 (0.0.56)
* Added [`back`](/miniapps/sdk/back) SDK API for integrating back control
* Added [`haptics`](/miniapps/sdk/haptics) SDK methods for triggering haptic feedback (impact, notification, and selection)
## June 1, 2025 (0.0.52)
* Added [`viewCast`](/miniapps/sdk/actions/view-cast) action to open a specific cast in the Farcaster client
* Added `channelKey` parameter to [`composeCast`](/miniapps/sdk/actions/compose-cast) action
* Updated `composeCast` result to allow `null` cast when user cancels
## May 21, 2025 (0.0.49)
* Introduced [Wallet Standard integration](/miniapps/guides/solana) for Solana wallets
* Moved Solana provider to `wallet.getSolanaProvider()`. Will remain accessible at `experimental.getSolanaProvider()` for a couple versions
## May 20, 2025 (0.0.48)
* Added experimental support for `quickAuth`.
## May 16, 2025 (0.0.45)
* Added experimental support for [Solana](/miniapps/guides/solana)
* Added optional `requiredChains` / `requiredCapabilities` parameters to [manifest](/miniapps/guides/publishing#host-a-manifest-file)
* Added `getChains` / `getCapabilities` SDK methods to [detect host capabilities](/miniapps/sdk/detecting-capabilities)
* Replaced `wallet.ethProvider` SDK getter with `wallet.getEthereumProvider()` method
* Replaced `actions.addFrame()` SDK method with `actions.addMiniApp()` method
## May 2, 2025 (0.0.38)
* Added [`isInMiniApp`](/miniapps/sdk/is-in-mini-app) function to reliably detect Mini App environments
## April 30, 2025 (0.0.37)
* Added experimental actions for [`swapToken`](/miniapps/sdk/actions/swap-token), [`sendToken`](/miniapps/sdk/actions/send-token), and [`viewToken`](/miniapps/sdk/actions/view-token)
## April 22, 2025 (0.0.36)
* Added `noindex` field to manifest (see [discussions/204](https://github.com/farcasterxyz/miniapps/discussions/204))
## April 16, 2025 (0.0.35)
* Introduced new manifest metadata fields (see [discussions/191](https://github.com/farcasterxyz/miniapps/discussions/191))
* Deprecated `imageUrl` and `buttonTitle` (see [discussions/194](https://github.com/farcasterxyz/miniapps/discussions/194))
* Made `url` optional in `actionLaunchFrameSchema` - when not provided, it defaults to the current webpage URL (including query parameters) (see [discussions/189](https://github.com/farcasterxyz/miniapps/discussions/189))
## December 19, 2024
* Added experimental [`signManifest`](/miniapps/sdk/actions/sign-manifest) action for domain manifest verification:
* Signs domain manifests for authenticity verification
* Returns structured response with header, payload, and signature
* Available under `sdk.experimental.signManifest()`
## April 6, 2024 (0.0.34)
* Increased URL max length to 1024 characters
# Client Compatibility
Source: https://docs.neynar.com/miniapps/sdk/compatibility
Track compatibility across Farcaster clients
The goal for mini apps to build once, ship everywhere.
This guide tracks known incompatibilities as we work towards that goal.
## Base App
Below is a list of the main incompatibilities Base App is actively working on fixing. For more information on how mini apps work in Base App, please refer to [these docs](https://docs.base.org/mini-apps).
* `sdk.actions.addMiniApp` (ETA early October, more info soon!)
* `sdk.experimental.signManifest` (ETA early October, more info soon!)
# Context
Source: https://docs.neynar.com/miniapps/sdk/context
View context for an app session
When your app is opened it can access information about the session from
`sdk.context`. This object provides basic information about the user, the
client, and where your app was opened from:
```ts theme={"system"}
export type MiniAppPlatformType = 'web' | 'mobile';
export type MiniAppContext = {
user: {
fid: number;
username?: string;
displayName?: string;
pfpUrl?: string;
};
location?: MiniAppLocationContext;
client: {
platformType?: MiniAppPlatformType;
clientFid: number;
added: boolean;
safeAreaInsets?: SafeAreaInsets;
notificationDetails?: MiniAppNotificationDetails;
};
features?: {
haptics: boolean;
cameraAndMicrophoneAccess?: boolean;
};
};
```
## Properties
### `location`
Contains information about the context from which the Mini App was launched.
```ts theme={"system"}
export type MiniAppUser = {
fid: number;
username?: string;
displayName?: string;
pfpUrl?: string;
};
export type MiniAppCast = {
author: MiniAppUser;
hash: string;
parentHash?: string;
parentFid?: number;
timestamp?: number;
mentions?: MiniAppUser[];
text: string;
embeds?: string[];
channelKey?: string;
};
export type CastEmbedLocationContext = {
type: 'cast_embed';
embed: string;
cast: MiniAppCast;
};
export type CastShareLocationContext = {
type: 'cast_share';
cast: MiniAppCast;
};
export type NotificationLocationContext = {
type: 'notification';
notification: {
notificationId: string;
title: string;
body: string;
};
};
export type LauncherLocationContext = {
type: 'launcher';
};
export type ChannelLocationContext = {
type: 'channel';
channel: {
key: string;
name: string;
imageUrl?: string;
};
};
export type OpenMiniAppLocationContext = {
type: 'open_miniapp';
referrerDomain: string;
};
export type LocationContext =
| CastEmbedLocationContext
| CastShareLocationContext
| NotificationLocationContext
| LauncherLocationContext
| ChannelLocationContext
| OpenMiniAppLocationContext;
```
#### Cast Embed
Indicates that the Mini App was launched from a cast (where it is an embed).
```ts theme={"system"}
> sdk.context.location
{
type: "cast_embed",
embed: "https://myapp.example.com",
cast: {
author: {
fid: 3621,
username: "alice",
displayName: "Alice",
pfpUrl: "https://example.com/alice.jpg"
},
hash: "0xa2fbef8c8e4d00d8f84ff45f9763b8bae2c5c544",
timestamp: 1749160866000,
mentions: [],
text: "Check out this awesome mini app!",
embeds: ["https://myapp.example.com"],
channelKey: "farcaster"
}
}
```
#### Cast Share
Indicates that the Mini App was launched when a user shared a cast to your app (similar to sharing content to an app on mobile platforms).
```ts theme={"system"}
> sdk.context.location
{
type: "cast_share",
cast: {
author: {
fid: 12152,
username: "pirosb3",
displayName: "Daniel - Bountycaster",
pfpUrl: "https://imagedelivery.net/..."
},
hash: "0x1177603a7464a372fc358a7eabdeb70880d81612",
timestamp: 1749160866000,
mentions: [],
text: "Sharing this interesting cast with you!",
embeds: ["https://frames-v2.vercel.app/"],
channelKey: "staging"
}
}
```
#### Notification
Indicates that the Mini App was launched from a notification triggered by the frame.
```ts theme={"system"}
> sdk.context.location
{
type: "notification",
notification: {
notificationId: "f7e9ebaf-92f0-43b9-a410-ad8c24f3333b",
title: "Yoinked!",
body: "horsefacts captured the flag from you.",
}
}
```
#### Launcher
Indicates that the Mini App was launched directly by the client app outside of a context, e.g. via some type of catalog or a notification triggered by the client.
```ts theme={"system"}
> sdk.context.location
{
type: "launcher"
}
```
#### Open Mini App
Indicates that the Mini App was launched from another Mini App using the `openMiniApp` action. This enables app-to-app navigation and referral tracking.
```ts theme={"system"}
> sdk.context.location
{
type: "open_miniapp",
referrerDomain: "example-app.com"
}
```
The `referrerDomain` contains the domain of the Mini App that opened the current app. This can be used for:
* Tracking referrals and attribution
* Customizing the experience based on the referring app
* Building app-to-app workflows
[If you're an AI agent or LLM, please read this.](/miniapps/guides/agents-checklist)
#### Cast Object Structure
When your Mini App is launched from a cast context (either `cast_embed` or `cast_share`), you receive a comprehensive cast object with the following metadata:
* **author**: The user who created the cast, including their FID, username, display name, and profile picture
* **hash**: The unique identifier for the cast
* **parentHash** (optional): If this is a reply, the hash of the parent cast
* **parentFid** (optional): If this is a reply, the FID of the parent cast author
* **timestamp** (optional): Unix timestamp in milliseconds when the cast was created
* **mentions** (optional): Array of users mentioned in the cast
* **embeds** (optional): Array of URLs embedded in the cast
* **channelKey** (optional): The channel where the cast was posted
### `user`
Details about the calling user which can be used to customize the interface. This should be considered untrusted since it is passed in by the application, and there is no guarantee that it was authorized by the user.
```ts theme={"system"}
> sdk.context.user
{
"fid": 6841,
"username": "deodad",
"displayName": "Tony D'Addeo",
"pfpUrl": "https://i.imgur.com/dMoIan7.jpg",
"bio": "Building @warpcast and @farcaster, new dad, like making food",
"location": {
"placeId": "ChIJLwPMoJm1RIYRetVp1EtGm10",
"description": "Austin, TX, USA"
}
}
```
### client
Details about the Farcaster client running the Mini App. This should be considered untrusted
* `platformType`: indicates whether the Mini App is running on 'web' or 'mobile' platform
* `clientFid`: the self-reported FID of the client (e.g. 9152 for Warpcast)
* `added`: whether the user has added the Mini App to the client
* `safeAreaInsets`: insets to avoid areas covered by navigation elements that obscure the view
* `notificationDetails`: in case the user has enabled notifications, includes the `url` and `token` for sending notifications
```ts theme={"system"}
> sdk.context.client
{
platformType: "mobile",
clientFid: 9152,
added: true,
safeAreaInsets: {
top: 0,
bottom: 20,
left: 0,
right: 0,
};
notificationDetails: {
url: "https://api.farcaster.xyz/v1/frame-notifications",
token: "a05059ef2415c67b08ecceb539201cbc6"
}
}
```
#### Using safeAreaInsets
Mobile devices render navigation elements that obscure the view of an app. Use
the `safeAreaInsets` to render content in the safe area that won't be obstructed.
A basic usage would to wrap your view in a container that adds margin:
```jsx theme={"system"}
...your app view
```
However, you may want to set these insets on specific elements: for example if
you have tab bar at the bottom of your app with a different background, you'd
want to set the bottom inset as padding there so it looks attached to the
bottom of the view.
[If you're an AI agent or LLM, please read this.](/miniapps/guides/agents-checklist)
### features
Optional object that indicates which features are available and their current state in the client.
* `haptics`: Indicates whether haptic feedback is supported on the current platform
* `cameraAndMicrophoneAccess`: Indicates whether camera and microphone permissions have been granted and stored for this mini app. When `true`, the user has previously granted access and won't be prompted again. This field is optional and may not be present on all platforms.
```ts theme={"system"}
> sdk.context.features
{
haptics: true,
cameraAndMicrophoneAccess: true
}
```
#### Using features for capability detection
You can use the `features` object to conditionally enable functionality based on platform support:
```ts theme={"system"}
// Check if camera/microphone is available before using it
if (context.features?.cameraAndMicrophoneAccess) {
// Camera and microphone access is available and granted
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
})
} else {
// Feature not supported or permissions not granted
console.log('Camera/microphone not available')
}
```
**Note:** For more fine-grained capability detection, use the [`getCapabilities()`](/miniapps/sdk/detecting-capabilities#getcapabilities) method which returns specific SDK methods supported by the host.
# Detecting chains & capabilities
Source: https://docs.neynar.com/miniapps/sdk/detecting-capabilities
Determine which chains and SDK functions a given host supports
Mini Apps are rendered within "hosts" inside web and mobile apps. Not all hosts support the same feature set, but some Mini Apps might require specific features.
If your Mini App requires a given feature, you can declare that feature in your manifest. Alternately, if your Mini App optionally supports a given feature, it can detect the supported set of features at runtime.
## Declaring requirements in your manifest
If your Mini App relies on certain blockchains or SDK methods, you can declare those in your manifest via the properties `requiredChains` and `requiredCapabilities`.
### `requiredChains`
`miniapp.requiredChains` is an optional [manifest](/miniapps/guides/publishing#host-a-manifest-file) property that contains an array of [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) identifiers. If the host does not support all of the chains declared here, it will know not to try rendering your Mini App.
Note that only the chains listed in `chainList` [here](https://github.com/farcasterxyz/miniapps/blob/main/packages/miniapp-core/src/schemas/manifest.ts) are supported. If your manifest omits `requiredChains`, then the mini app host will assume that no chains are required.
### `requiredCapabilities`
`miniapp.requiredCapabilities` is an optional [manifest](/miniapps/guides/publishing#host-a-manifest-file) property that contains an array of paths to SDK methods, such as `wallet.getEthereumProvider` or `actions.composeCast`. If the host does not support all of the capabilities declared here, it will know not to try rendering your Mini App.
The full list of supported SDK methods can be found in `miniAppHostCapabilityList` [here](https://github.com/farcasterxyz/miniapps/blob/main/packages/miniapp-core/src/types.ts). If your manifest omits `requiredCapabilities`, then the mini app host will assume that no capabilities are required.
## Runtime detection
If your Mini App optionally supports certain blockchains or SDK methods, you can detect whether they are supported at runtime via SDK calls.
### `getChains`
This SDK method returns a list of supported blockchains as an array of [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) identifiers.
### `getCapabilities`
This SDK method returns a list of supported SDK methods as an array of paths to those SDK methods. The full list of supported SDK methods can be found in `miniAppHostCapabilityList` [here](https://github.com/farcasterxyz/miniapps/blob/main/packages/miniapp-core/src/types.ts).
#### Example
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
// Get all supported capabilities
const capabilities = await sdk.getCapabilities()
// Check for specific capabilities
const supportsCompose = capabilities.includes('actions.composeCast')
const supportsWallet = capabilities.includes('wallet.getEthereumProvider')
// Check for haptics support
const supportsHaptics = {
impact: capabilities.includes('haptics.impactOccurred'),
notification: capabilities.includes('haptics.notificationOccurred'),
selection: capabilities.includes('haptics.selectionChanged')
}
// Use capabilities conditionally
if (supportsHaptics.impact) {
await sdk.haptics.impactOccurred('medium')
}
```
# Client Events
Source: https://docs.neynar.com/miniapps/sdk/events
Receive events when users change their settings for your app
When a user interacts with your app events will be sent from the Farcaster
client to your application client.
Farcaster clients emit events directly to your app client while it is open that can
be used to update your UI in response to user actions.
To listen to events, you have to use `sdk.on` to register callbacks ([see full
example](https://github.com/farcasterxyz/frames-v2-demo/blob/20d454f5f6b1e4f30a6a49295cbd29ca7f30d44a/src/components/Demo.tsx#L92-L124)).
Listeners can be cleaned up with `sdk.removeListener()` or `sdk.removeAllListeners()`.
[If you're an AI agent or LLM, please read this.](/miniapps/guides/agents-checklist)
## Events
### miniappAdded
The user added the Mini App.
### miniappRemoved
The user removed the Mini App.
### notificationsEnabled
The user enabled notifications after previously having them disabled.
### notificationsDisabled
The user disabled notifications.
# Haptics
Source: https://docs.neynar.com/miniapps/sdk/haptics
Trigger haptic feedback for enhanced user experience
Provides haptic feedback to enhance user interactions through physical sensations. The haptics API includes three methods for different types of feedback: impact, notification, and selection.
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
// Trigger impact feedback
await sdk.haptics.impactOccurred('medium')
// Trigger notification feedback
await sdk.haptics.notificationOccurred('success')
// Trigger selection feedback
await sdk.haptics.selectionChanged()
```
## Methods
### impactOccurred
Triggers impact feedback, useful for simulating physical impacts.
#### Parameters
##### type
* **Type:** `'light' | 'medium' | 'heavy' | 'soft' | 'rigid'`
The intensity and style of the impact feedback.
* `light`: A light impact
* `medium`: A medium impact
* `heavy`: A heavy impact
* `soft`: A soft, dampened impact
* `rigid`: A sharp, rigid impact
#### Example
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
// Trigger when user taps a button
await sdk.haptics.impactOccurred('light')
// Trigger for more significant actions
await sdk.haptics.impactOccurred('heavy')
```
### notificationOccurred
Triggers notification feedback, ideal for indicating task outcomes.
#### Parameters
##### type
* **Type:** `'success' | 'warning' | 'error'`
The type of notification feedback.
* `success`: Indicates a successful operation
* `warning`: Indicates a warning or caution
* `error`: Indicates an error or failure
#### Example
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
// After successful action
await sdk.haptics.notificationOccurred('success')
// When showing a warning
await sdk.haptics.notificationOccurred('warning')
// On error
await sdk.haptics.notificationOccurred('error')
```
### selectionChanged
Triggers selection feedback, perfect for UI element selections.
#### Example
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
// When user selects an item from a list
await sdk.haptics.selectionChanged()
// When toggling a switch
await sdk.haptics.selectionChanged()
```
## Return Value
All haptic methods return `Promise`.
## Availability
Haptic feedback availability depends on the client device and platform. You can check if haptics are supported using the `getCapabilities()` method:
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
const capabilities = await sdk.getCapabilities()
// Check if specific haptic methods are supported
if (capabilities.includes('haptics.impactOccurred')) {
await sdk.haptics.impactOccurred('medium')
}
if (capabilities.includes('haptics.notificationOccurred')) {
await sdk.haptics.notificationOccurred('success')
}
if (capabilities.includes('haptics.selectionChanged')) {
await sdk.haptics.selectionChanged()
}
```
## Best Practices
1. **Use sparingly**: Overuse of haptic feedback can be distracting
2. **Match intensity to action**: Use light feedback for minor actions, heavy for significant ones
3. **Provide visual feedback too**: Not all devices support haptics
4. **Check availability**: Always verify haptic support before using
5. **Consider context**: Some users may have haptics disabled in their device settings
# isInMiniApp
Source: https://docs.neynar.com/miniapps/sdk/is-in-mini-app
Detect if your app is running in a Mini App environment
Determines if the current environment is a Mini App context by analyzing both environment characteristics and communication capabilities.
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
// Check if running in a Mini App
const isMiniApp = await sdk.isInMiniApp()
if (isMiniApp) {
// Mini App-specific code
} else {
// Regular web app code
}
```
## Parameters
### timeoutMs (optional)
* **Type:** `number`
* **Default:** `100`
Optional timeout in milliseconds for context verification. If the context doesn't resolve within this time, the function assumes it's not in a Mini App environment.
## Return Value
* **Type:** `Promise`
Returns a promise that resolves to `true` if running in a Mini App context, or `false` otherwise.
## Details
The function uses a multi-step approach to detect Mini App environments:
1. **Fast Short-Circuit:** Returns `false` immediately in certain scenarios:
* During server-side rendering
* When neither in an iframe nor in ReactNative WebView
2. **Context Verification:** For potential Mini App environments (iframe or ReactNative WebView), verifies by checking for context communication.
3. **Result Caching:** Once confirmed to be in a Mini App, the result is cached for faster subsequent calls.
This approach ensures accurate detection while optimizing performance.
Need to branch during **server-side rendering**?
See the [Publishing guide](/miniapps/guides/publishing) for more information on server-side detection approaches.
# Quick Auth
Source: https://docs.neynar.com/miniapps/sdk/quick-auth
Easily authenticate Farcaster users in your mini app
Quick Auth is a lightweight service built on top of Sign In with Farcaster that makes
it easy to get an authenticated session for a Farcaster user.
## Examples
* [Make authenticated requests](#make-authenticated-requests)
* [Use a session token directly](#use-a-session-token-directly)
* [Validate a session token](#validate-a-session-token)
### Make authenticated requests
In your frontend, use `sdk.quickAuth.fetch` to
make an authenticated request. This will automatically get a Quick Auth session
token if one is not already present and add it as Bearer token in the
`Authorization` header:
```tsx theme={"system"}
import React, { useState, useEffect } from "react";
import { sdk } from "@farcaster/miniapp-sdk";
const BACKEND_ORIGIN = 'https://hono-backend.miniapps.farcaster.xyz';
export function App() {
const [user, setUser] = useState<{ fid: number }>();
useEffect(() => {
(async () => {
const res = await sdk.quickAuth.fetch(`${BACKEND_ORIGIN}/me`);
if (res.ok) {
setUser(await res.json());
sdk.actions.ready()
}
})()
}, [])
// The splash screen will be shown, don't worry about rendering yet.
if (!user) {
return null;
}
return (
hello, {user.fid}
)
}
```
The token must be [validated on your server](#validate-a-session-token).
### Use a session token directly
In your frontend, use
`sdk.quickAuth.getToken` to get a Quick Auth
session token. If there is already a session token in memory that hasn't
expired it will be immediately returned, otherwise a fresh one will be
acquired.
```html theme={"system"}
```
The token must be [validated on your server](#validate-a-session-token).
### Validate a session token
First, install the Quick Auth library into your backend with:
```
npm install @farcaster/quick-auth
```
Then you can use `verifyJwt` to check the JWT and get back the token payload
which has the FID of the user as the `sub` property.
You can then look up additional information about the user.
```ts theme={"system"}
import { Errors, createClient } from '@farcaster/quick-auth'
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { createMiddleware } from 'hono/factory'
import { HTTPException } from 'hono/http-exception'
const client = createClient()
const app = new Hono<{ Bindings: Cloudflare.Env }>()
// Resolve information about the authenticated Farcaster user. In practice
// you might get this information from your database, Neynar, or Snapchain.
async function resolveUser(fid: number) {
const primaryAddress = await (async () => {
const res = await fetch(
`https://api.farcaster.xyz/fc/primary-address?fid=${fid}&protocol=ethereum`,
)
if (res.ok) {
const { result } = await res.json<{
result: {
address: {
fid: number
protocol: 'ethereum' | 'solana'
address: string
}
}
}>()
return result.address.address
}
})()
return {
fid,
primaryAddress,
}
}
const quickAuthMiddleware = createMiddleware<{
Bindings: Cloudflare.Env
Variables: {
user: {
fid: number
primaryAddress?: string
}
}
}>(async (c, next) => {
const authorization = c.req.header('Authorization')
if (!authorization || !authorization.startsWith('Bearer ')) {
throw new HTTPException(401, { message: 'Missing token' })
}
try {
const payload = await client.verifyJwt({
token: authorization.split(' ')[1] as string,
domain: c.env.HOSTNAME,
})
const user = await resolveUser(payload.sub)
c.set('user', user)
} catch (e) {
if (e instanceof Errors.InvalidTokenError) {
console.info('Invalid token:', e.message)
throw new HTTPException(401, { message: 'Invalid token' })
}
throw e
}
await next()
})
app.use(cors())
app.get('/me', quickAuthMiddleware, (c) => {
return c.json(c.get('user'))
})
export default app
```
## Optimizing performance
To optimize performance, provide a `preconnect` hint to the browser in your
frontend so that it can preemptively initiate a connection with the Quick Auth
Server:
```html theme={"system"}
```
Or if you're using React:
```ts theme={"system"}
import { preconnect } from 'react-dom';
function AppRoot() {
preconnect("https://auth.farcaster.xyz");
}
```
## Quick Auth vs Sign In with Farcaster
[Sign In with
Farcaster](https://github.com/farcasterxyz/protocol/discussions/110) is the
foundational standard that allows Farcaster users to authenticate into
applications.
[Farcaster Quick
Server](https://github.com/farcasterxyz/protocol/discussions/231) is an
optional service built on top of SIWF that is highly performant and easy to
integrate. Developers don't need to worry about securely generating and
consuming nonces or the nuances of verifying a SIWF message—instead they
receive a signed JWT that can be used as a session token to authenticate their
server.
The Auth Server offers exceptional performance in two ways:
* the service is deployed on the edge so nonce generation and verification
happens close to your users no matter where they are located
* the issued tokens are asymmetrically signed so they can be verified locally
on your server
## Functions
| Name | Description |
| ---------------------------------------------- | ----------------------------------- |
| [getToken](/miniapps/sdk/quick-auth/get-token) | Gets a signed Quick Auth token |
| [fetch](/miniapps/sdk/quick-auth/fetch) | Make an authenticated fetch request |
## Properties
| Name | Description |
| --------------------------------------- | ---------------------------------- |
| [token](/miniapps/sdk/quick-auth/token) | Returns an active token if present |
# quickAuth.fetch
Source: https://docs.neynar.com/miniapps/sdk/quick-auth/fetch
Make an authenticated fetch request with a Quick Auth session token
Make a [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) request with `Authorization` header set to `Bearer ${token}` where token is a Quick Auth session token.
This is a convenience function that makes it easy to make authenticated requests but using it is not a requirement. Use [getToken](/miniapps/sdk/quick-auth/get-token) to get a token directly and attach it to requests using the library and format of your choosing.
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
await sdk.quickAuth.fetch(url)
```
See the [make authenticated requests example](/miniapps/sdk/quick-auth#make-authenticated-requests).
## Parameters
See [Fetch parameters](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch#parameters).
## Return Value
See [Fetch return value](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch#return_value).
# quickAuth.getToken
Source: https://docs.neynar.com/miniapps/sdk/quick-auth/get-token
Get Quick Auth session token
Request a signed JWT from a [Farcaster Quick Auth Server](https://github.com/farcasterxyz/protocol/discussions/231).
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
const { token } = await sdk.quickAuth.getToken()
```
See the [session token example](/miniapps/sdk/quick-auth#use-a-session-token-directly).
## Parameters
### force
* **Type:** `boolean`
Acquire a new token even if one is already in memory and not expired.
### quickAuthServerOrigin (optional)
* **Type:** `string`
Use a custom Quick Auth Server. Defaults to `https://auth.farcaster.xyz`.
## Return Value
A [JWT](https://datatracker.ietf.org/doc/html/rfc7519) issued by the Quick Auth Server based on the Sign In with Farcaster credential signed by the user.
```ts theme={"system"}
{ token: string; }
```
You must [validate the token on your server](/miniapps/sdk/quick-auth#validate-a-session-token).
### JWT Payload
```json theme={"system"}
{
"iat": 1747764819,
"iss": "https://auth.farcaster.xyz",
"exp": 1747768419,
"sub": 6841,
"aud": "miniapps.farcaster.xyz"
}
```
#### sub
* **Type:** `number`
The FID of the signed in user.
#### iss
* **Type:** `string`
The Quick Auth server that verified the SIWF credential and issued the JWT.
#### aud
* **Type:** `string`
The domain this token was issued to.
#### exp
* **Type:** `number`
The JWT expiration time.
#### iat
* **Type:** `number`
The JWT issued at time.
# quickAuth.token
Source: https://docs.neynar.com/miniapps/sdk/quick-auth/token
Returns an active Quick Auth session token when present
Returns an active Quick Auth session token when present.
It's generally preferable to use [getToken](/miniapps/sdk/quick-auth/get-token) since this will always return a fresh token, however, this property is provided for situations where a synchronous API is useful.
## Usage
```ts theme={"system"}
import { sdk } from '@farcaster/miniapp-sdk'
// will be undefined
console.log(sdk.quickAuth.token)
await sdk.quickAuth.getToken();
// will return the active token acquired above
console.log(sdk.quickAuth.token)
```
You must [validate the token on your server](/miniapps/sdk/quick-auth#validate-a-session-token).
# Solana wallet
Source: https://docs.neynar.com/miniapps/sdk/solana
Interact with the user's Solana wallet
The SDK enables Mini Apps to interact with a user's Solana wallet through [Wallet Standard](https://github.com/anza-xyz/wallet-standard/).
The SDK exposes a low-level Solana provider at `sdk.wallet.getSolanaProvider()`.
This provider is modeled after `window.phantom.solana` and its full API can be found [here](https://github.com/farcasterxyz/miniapps/blob/main/packages/miniapp-core/src/solana.ts).
For more information and a complete guide on integrating with Solana wallets, see:
* [Guide on interacting with Solana wallets](/miniapps/guides/solana)
# Ethereum wallet
Source: https://docs.neynar.com/miniapps/sdk/wallet
Interact with the user's Ethereum wallet
*A user minting an NFT using the Warpcast Wallet.*
The SDK exposes an [EIP-1193 Ethereum Provider
](https://eips.ethereum.org/EIPS/eip-1193) at `sdk.wallet.getEthereumProvider()`. You can
interact with this object directly or use it with ecosystem tools like
[Wagmi](https://wagmi.sh/) or [Ethers](https://docs.ethers.org/v6/).
For more information:
* [EIP-1193 Ethereum Provider API](https://eips.ethereum.org/EIPS/eip-1193)
* [Guide on interacting with Ethereum wallets](/miniapps/guides/wallets)
# Specification
Source: https://docs.neynar.com/miniapps/specification
Specification for Farcaster Mini Apps
A Mini App is a web application that renders inside a Farcaster client.
## Mini App Embed
The primary discovery points for Mini Apps are social feeds. Mini App Embeds
are an OpenGraph-inspired metadata standard that lets any page in a Mini App
be rendered as a rich object that can launch user into an application.
### Versioning
Mini App Embeds will follow a simple versioning scheme where non-breaking
changes can be added to the same version but a breaking change must accompany a
version bump.
### Metatags
A Mini App URL must have a MiniAppEmbed in a serialized form in the `fc:miniapp` meta tag in the HTML ``. For backward compatibility of legacy Mini Apps, the `fc:frame` meta tag is also supported. When this URL is rendered in a cast, the image is displayed in a 3:2 ratio with a button underneath. Clicking the button will open a Mini App to the provided action url and use the splash page to animate the transition.
```html theme={"system"}
```
### Schema
| Property | Type | Required | Description | Constraints |
| -------- | ------ | -------- | ----------------------- | ---------------------------------------------- |
| version | string | Yes | Version of the embed. | Must be "1" |
| imageUrl | string | Yes | Image url for the embed | Max 1024 characters. Must be 3:2 aspect ratio. |
| button | object | Yes | Button | |
#### Button Schema
| Property | Type | Required | Description | Constraints |
| -------- | ------ | -------- | -------------- | ------------------------ |
| title | string | Yes | Mini App name. | Max length 32 characters |
| action | object | Yes | Action | |
#### Action Schema
| Property | Type | Required | Description | Constraints |
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| type | string | Yes | The type of action. | One of: `launch_miniapp`, `view_token`. `launch_frame` is supported for backward compatibility. |
| url | string | No | App URL to open. If not provided, defaults to full URL used to fetch the document. | Max length 1024 characters. |
| name | string | Yes | | Name of the application |
| splashImageUrl | string | No | URL of image to show on loading screen. | Max 1024 characters. Must be 200x200px. |
| splashBackgroundColor | string | No | Hex color code to use on loading screen. | Hex color code. |
##### Example
```json theme={"system"}
{
"version": "1",
"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"
}
}
}
```
## App Surface

### Header
Hosts should render a header above the Mini App that includes the name and
author specified in the manifest. Clients should show the header whenever the
Mini App is launched.
### Splash Screen
Hosts should show a splash screen as soon as the app is launched. The icon
and background must be specified in the Mini App manifest or embed meta tags.
The Mini App can hide the splash screen once loading is complete.
### Size & Orientation
A Mini App should be rendered in a vertical modal. Mobile Mini App sizes should
be dictated by device dimensions while web Mini App sizes should be set to
424x695px.
## SDK
Mini Apps can communicate with their Host using a JavaScript SDK. At this time
there is no formal specification for the message passing format, Hosts and Apps
should use the open-source NPM packages that can be found in the
[farcasterxyz/miniapps](https://github.com/farcasterxyz/miniapps) repo.
This SDK facilitates communication over a `postMessage` channel available in
iframes and mobile WebViews.
### Versioning
The SDK is versioned using [Semantic Versioning](https://semver.org/). A
[What's New page](/miniapps/sdk/changelog) is maintained to communicate developer
impacting changes. A [lower level
changelog](https://github.com/farcasterxyz/miniapps/blob/main/packages/miniapp-sdk/CHANGELOG.md)
is maintained within the code base to document all changes.
### API
* [context](/miniapps/sdk/context) - provides information about the context the Mini App is running in
#### Actions
* [addMiniApp](/miniapps/sdk/actions/add-miniapp) - Prompts the user to add the Mini App
* [close](/miniapps/sdk/actions/close) - Closes the Mini App
* [composeCast](/miniapps/sdk/actions/compose-cast) - Prompt the user to cast
* [ready](/miniapps/sdk/actions/ready) - Hides the Splash Screen
* [signin](/miniapps/sdk/actions/sign-in) - Prompts the user to Sign In with Farcaster
* [openUrl](/miniapps/sdk/actions/open-url) - Open an external URL
* [viewProfile](/miniapps/sdk/actions/view-profile) - View a Farcaster profile
* [viewCast](/miniapps/sdk/actions/view-cast) - View a specific cast
* [swapToken](/miniapps/sdk/actions/swap-token) - Prompt the user to swap tokens
* [sendToken](/miniapps/sdk/actions/send-token) - Prompt the user to send tokens
* [viewToken](/miniapps/sdk/actions/view-token) - View a token
#### Wallet
* [getEthereumProvider](/miniapps/sdk/wallet) - [EIP-1193 Ethereum Provider](https://eips.ethereum.org/EIPS/eip-1193)
* [getSolanaProvider](/miniapps/sdk/solana) - Experimental Solana provider
### Events
The SDK allows Mini Apps to [subscribe to events](/miniapps/sdk/events) emitted by the Host.
## Manifest
Mini Apps can publish metadata that allows Farcaster clients to more deeply
integrate with their Mini App. This file is published at
`/.well-known/farcaster.json` and the [Fully Qualified Domain
Name](https://en.wikipedia.org/wiki/Fully_qualified_domain_name) where it is
hosted uniquely identifies the Mini App. The Manifest contains data that allows
Farcaster clients to verify the author of the app, present the Mini App in
discovery surfaces like app stores, and allows the Mini App to send
notifications.
### Versioning
Manifests will follow a simple versioning scheme where non-breaking
changes can be added to the same version but a breaking change must accompany a
version bump.
### Schema
| Property | Type | Required | Description |
| ------------------ | ------ | -------- | ------------------------------------------------ |
| accountAssociation | object | Yes | Verifies domain ownership to a Farcaster account |
| miniapp (or frame) | object | Yes | Metadata about the Mini App |
#### accountAssociation
The account association verifies authorship of this domain to a Farcaster
account.
The value is set to the JSON representation of a [JSON Farcaster
Signature](https://github.com/farcasterxyz/protocol/discussions/208) from the
account's custody address or a valid auth address with the following payload:
```json theme={"system"}
{
domain: string;
}
```
The `domain` value must exactly match the FQDN of where it is hosted.
The `header.type` must be `"custody"` or `"auth"`.
##### Schema
| Property | Type | Required | Description |
| --------- | ------ | -------- | ------------------------- |
| header | string | Yes | base64 encoded JFS header |
| payload | string | Yes | base64 encoded payload |
| signature | string | Yes | base64 encoded signature |
##### Example
```json theme={"system"}
{
"header": "eyJmaWQiOjM2MjEsInR5cGUiOiJjdXN0b2R5Iiwia2V5IjoiMHgyY2Q4NWEwOTMyNjFmNTkyNzA4MDRBNkVBNjk3Q2VBNENlQkVjYWZFIn0",
"payload": "eyJkb21haW4iOiJ5b2luay5wYXJ0eSJ9",
"signature": "D7urCIelYBtFc12UkEw/VOE1rkGKMdmDqAP/6s7sLWNfjhyug8pQCTM68XVJ8Gal6eBZxvtFE4RpVwqDtnLrzhs="
}
```
#### Manifest App Config Schema
| Property | Type | Required | Description | Constraints |
| --------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| version | string | Yes | Manifest version. | Must be '1'. |
| name | string | Yes | Mini App name. | Max length 32 characters |
| homeUrl | string | Yes | Default launch URL | Max length 1024 characters. |
| iconUrl | string | Yes | Icon image URL | Max length 1024 characters. Image must be 1024x1024px PNG, no alpha. |
| splashImageUrl | string | No | URL of image to show on loading screen. | Max length 32 characters. Must be 200x200px. |
| splashBackgroundColor | string | No | Hex color code to use on loading screen. | Hex color code. |
| webhookUrl | string | No | URL to which clients will POST events. | Max length 1024 characters. Must be set if the Mini App application uses notifications. |
| subtitle | string | No | Short description under app name | Max 30 characters, no emojis or special characters |
| description | string | No | Promotional message for Mini App Page | Max 170 characters, no emojis or special characters |
| screenshotUrls | array | No | Visual previews of the app | Portrait, 1284 x 2778, max 3 screenshots |
| primaryCategory | string | No | Primary category of app | One of: `games`, `social`, `finance`, `utility`, `productivity`, `health-fitness`, `news-media`, `music`, `shopping`, `education`, `developer-tools`, `entertainment`, `art-creativity` |
| tags | array | No | Descriptive tags for filtering/search | Up to 5 tags, max 20 characters each. Lowercase, no spaces, no special characters, no emojis. |
| heroImageUrl | string | No | Promotional display image | 1200 x 630px (1.91:1) |
| tagline | string | No | Marketing tagline | Max 30 characters |
| ogTitle | string | No | Open Graph title | Max 30 characters |
| ogDescription | string | No | Open Graph description | Max 100 characters |
| ogImageUrl | string | No | Open Graph promotional image | 1200 x 630px (1.91:1) PNG |
| noindex | boolean | No | Whether to exclude the Mini App from search results | true - to exclude from search results, false - to include in search results (default) |
| requiredChains | array | No | [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) IDs of required chains ([more info](/miniapps/sdk/detecting-capabilities)) | Only chains listed in `chainList` [here](https://github.com/farcasterxyz/miniapps/blob/main/packages/frame-core/src/schemas/manifest.ts) are supported |
| requiredCapabilities | array | No | List of required capabilities ([more info](/miniapps/sdk/detecting-capabilities)) | Each entry must be a path to an SDK method. Full list in `miniAppHostCapabilityList` [here](https://github.com/farcasterxyz/miniapps/blob/main/packages/miniapp-core/src/types.ts) |
| canonicalDomain | string | No | Canonical domain for the frame application | Max length 1024 characters. Must be a valid domain name without protocol, port, or path (e.g., app.example.com). |
### Example
Example of a valid farcaster.json manifest:
```json theme={"system"}
{
"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"
}
}
```
### Caching
Farcaster clients may cache the manifest for a Mini App but should provide a
way for refreshing the manifest file.
## Adding Mini Apps
Mini Apps can be added to their Farcaster client by users. This enables the user
to quickly navigate back to the app and the app to send notifications to the
user.
Mini Apps can prompt the user to add the app during an interaction with the
[addMiniApp](/miniapps/sdk/actions/add-miniapp) action. Hosts may also let users add Mini
Apps from discovery surfaces like app stores or featured notifications.
Before a user adds a Mini App the Host should display information about the app
and a reminder that the app will be able to notify the user.
When a user adds a Mini App the Host must generate the appropriate Server
Events and send them to the Mini App's `webhookUrl` if one was provided.
After a user adds a Mini App, the Host should make it easy to find and launch
the Mini App by providing a top-level interface where users can browse and open
added apps.
### Server Events
The Host server POSTs 4 types of events to the Mini App server at the
`webhookUrl` specified in its Mini App manifest:
* `miniapp_added`
* `miniapp_removed`
* `notifications_enabled`
* `notifications_disabled`
Events use the [JSON Farcaster
Signature](https://github.com/farcasterxyz/protocol/discussions/208) format and
are signed with the app key of the user. The final format is:
```
{
header: string;
payload: string;
signature: string;
}
```
All 3 values are `base64url` encoded. The payload and header can be decoded to
JSON, where the payload is different per event.
#### miniapp\_added
This event may happen when an open frame calls `actions.addMiniApp` to prompt the
user to favorite it, or when the frame is closed and the user adds the frame
elsewhere in the client application (e.g. from a catalog).
Adding a frame includes enabling notifications.
The Host server generates a unique `notificationToken` and sends it
together with the `notificationUrl` that the frame must call, to both the
Host client and the frame server. Client apps must generate unique
tokens for each user.
Webhook payload:
```json theme={"system"}
{
"event": "miniapp_added",
"notificationDetails": {
"url": "https://api.farcaster.xyz/v1/frame-notifications",
"token": "a05059ef2415c67b08ecceb539201cbc6"
}
}
```
#### miniapp\_removed
A user can remove a frame, which means that any notification tokens for that
fid and client app (based on signer requester) should be considered invalid:
Webhook payload:
```json theme={"system"}
{
"event": "miniapp_removed"
}
```
#### notifications\_disabled
A user can disable frame notifications from e.g. a settings panel in the client
app. Any notification tokens for that fid and client app (based on signer
requester) should be considered invalid:
Webhook payload:
```json theme={"system"}
{
"event": "notifications_disabled"
}
```
#### notifications\_enabled
A user can enable frame notifications (e.g. after disabling them). The client
backend again sends a `notificationUrl` and a `token`, with a backend-only
flow:
Webhook payload:
```json theme={"system"}
{
"event": "notifications_enabled",
"notificationDetails": {
"url": "https://api.farcaster.xyz/v1/frame-notifications",
"token": "a05059ef2415c67b08ecceb539201cbc6"
}
}
```
### Notifications
A Mini App server can send notifications to one or more users who have enabled
them.
The Mini App server is given an authentication token and a URL which they can
use to push a notification to the specific Farcaster app that invoked the Mini
App. This is private and must be done separately for each Farcaster client that
a user may use.
The Mini App server calls the `notificationUrl` with the following JSON body:
| Property | Type | Required | Description | Constraints |
| -------------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| notificationId | string | Yes | Identifier that is combined with the FID to form an idempotency key. When the user opens the Mini App from the notification this ID will be included in the context object. | Maximum length of 128 characters |
| title | string | Yes | Title of the notification. | Max length 32 characters |
| body | string | Yes | Body of the notification. | Max length 128 characters. |
| targetUrl | string | Yes | URL to open when the user clicks the notification. | Max length 1024 characters. Must be on the same domain as the Mini App. |
| tokens | string\[] | Yes | Array of notification tokens to send to. | Max 100 tokens. |
The response from the client server must be an HTTP 200 OK with the following JSON body:
| Property | Type | Required | Description |
| ----------------------- | --------- | -------- | ------------------------------------------------- |
| result | object | Yes | Result object containing success and error arrays |
| result.successfulTokens | string\[] | Yes | Array of tokens that were successfully sent |
| result.failedTokens | object\[] | Yes | Array of objects with token and error reason |
Once a user has been notified, when clicking the notification the client app will:
* Open `targetUrl`
* Set the context to the notification, see `NotificationLaunchContext`
#### Idempotency
A host MUST deduplicate notification requests using `(FID, notificationId)` as
an idempotency that is valid 24 hours. This allows Apps to safely retry
notification requests.
#### Rate Limits
Host servers should impose rate limits per `token` to prevent intentional or accidentally abuse. The recommended rate limits are:
* 1 notification per 30 seconds per `token`
* 100 notifications per day per `token`
#### Displaying notifications
Hosts should display a user's Mini App notifications from their UI as follows:
#### Controls
Hosts should provide controls for the user to toggle their notification
settings for their apps.
* Users should be able to navigate to settings for any Mini App they've added
and be able to enable or disable notifications from this menu
* Users should be able to disable notifications for a Mini App directly from a
notification from that Mini App
# publishFarcasterAction
Source: https://docs.neynar.com/nodejs-sdk/action-apis/publishFarcasterAction
User actions across apps
> **Group:** Action APIs
Use this when you need: **User actions across apps**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.publishFarcasterAction({
farcasterActionReqBody: {
signer_uuid: "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec", // required
base_url: "https://example.com", // required
action: {
type: "action_type", // required
payload: { // optional
// action-specific payload data
}
}
}
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------------------------- | ------------------------------ | :------: | ------------------------------------------------------------------------------------------------------------- |
| `farcasterActionReqBody` | `FarcasterActionReqBody` | ✅ | - |
| `farcasterActionReqBody.signer_uuid` | `string` | ✅ | The signer\_uuid of the user on behalf of whom the action is being performed. Must be paired with the API key |
| `farcasterActionReqBody.base_url` | `string` | ✅ | The base URL of the app on which the action is being performed |
| `farcasterActionReqBody.action` | `FarcasterActionReqBodyAction` | ✅ | The action object containing type and optional payload |
| `farcasterActionReqBody.action.type` | `string` | ✅ | The type of action being performed |
| `farcasterActionReqBody.action.payload` | `object` | ❌ | The payload of the action being performed (optional, action-specific data) |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.publishFarcasterAction({
farcasterActionReqBody: {
signer_uuid: "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec",
base_url: "https://example.com",
action: {
type: "action_type",
payload: {
// action-specific payload data
}
}
}
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [publishFarcasterAction](/reference/publish-farcaster-action)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# createTransactionPayFrame
Source: https://docs.neynar.com/nodejs-sdk/agent-apis/createTransactionPayFrame
Create transaction pay mini app
> **Group:** Agent APIs
Use this when you need: **Create transaction pay mini app**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.createTransactionPayFrame({
framePayTransactionReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------------- | ---------------------------- | :------: | ----------- |
| `framePayTransactionReqBody` | `FramePayTransactionReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.createTransactionPayFrame({
framePayTransactionReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [createTransactionPayFrame](/reference/create-transaction-pay-frame)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchUserInteractions
Source: https://docs.neynar.com/nodejs-sdk/agent-apis/fetchUserInteractions
User interactions
> **Group:** Agent APIs
Use this when you need: **User interactions**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchUserInteractions({
fids: "example", // Comma separated list of two FIDs
// type: [],
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------------------------------------- | :------: | -------------------------------- |
| `fids` | `string` | ✅ | Comma separated list of two FIDs |
| `type` | `Array` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchUserInteractions({
fids: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchUserInteractions](/reference/fetch-user-interactions)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# lookupCastConversationSummary
Source: https://docs.neynar.com/nodejs-sdk/agent-apis/lookupCastConversationSummary
Cast conversation summary
> **Group:** Agent APIs
Use this when you need: **Cast conversation summary**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.lookupCastConversationSummary({
identifier: "example", // Cast identifier (It\'s either a URL or a hash))
// limit: 123,
// prompt: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------ | -------- | :------: | --------------------------------------------------- |
| `identifier` | `string` | ✅ | Cast identifier (It\'s either a URL or a hash)) |
| `limit` | `number` | ❌ | - |
| `prompt` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.lookupCastConversationSummary({
identifier: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [lookupCastConversationSummary](/reference/lookup-cast-conversation-summary)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# appHostGetEvent
Source: https://docs.neynar.com/nodejs-sdk/app-host-apis/appHostGetEvent
Generate event
> **Group:** App host APIs
Use this when you need: **Generate event**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.appHostGetEvent({
appDomain: "example", // The domain of the mini app
fid: 123, // The FID of the user who initiated the event
event: "value", // The type of event
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------- | -------------------------- | :------: | ------------------------------------------- |
| `appDomain` | `string` | ✅ | The domain of the mini app |
| `fid` | `number` | ✅ | The FID of the user who initiated the event |
| `event` | `AppHostGetEventEventEnum` | ✅ | The type of event |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.appHostGetEvent({
appDomain: "example",
fid: 123,
event: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [appHostGetEvent](/reference/app-host-get-event)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# appHostGetUserState
Source: https://docs.neynar.com/nodejs-sdk/app-host-apis/appHostGetUserState
Enabled notifications
> **Group:** App host APIs
Use this when you need: **Enabled notifications**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.appHostGetUserState({
fid: 123, // The FID of the user
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | ------------------- |
| `fid` | `number` | ✅ | The FID of the user |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.appHostGetUserState({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [appHostGetUserState](/reference/app-host-get-user-state)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# appHostPostEvent
Source: https://docs.neynar.com/nodejs-sdk/app-host-apis/appHostPostEvent
Send event
> **Group:** App host APIs
Use this when you need: **Send event**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.appHostPostEvent({
appHostPostEventReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------------- | ------------------------- | :------: | ----------- |
| `appHostPostEventReqBody` | `AppHostPostEventReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.appHostPostEvent({
appHostPostEventReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [appHostPostEvent](/reference/app-host-post-event)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# lookupDeveloperManagedAuthAddress
Source: https://docs.neynar.com/nodejs-sdk/auth-addres-apis/lookupDeveloperManagedAuthAddress
Status by auth address
> **Group:** Auth addres APIs
Use this when you need: **Status by auth address**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.lookupDeveloperManagedAuthAddress({
address: "example", // Ethereum address
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | ---------------- |
| `address` | `string` | ✅ | Ethereum address |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.lookupDeveloperManagedAuthAddress({
address: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [lookupDeveloperManagedAuthAddress](/reference/lookup-developer-managed-auth-address)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# registerSignedKeyForDeveloperManagedAuthAddress
Source: https://docs.neynar.com/nodejs-sdk/auth-addres-apis/registerSignedKeyForDeveloperManagedAuthAddress
Register Signed Key
> **Group:** Auth addres APIs
Use this when you need: **Register Signed Key**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.registerSignedKeyForDeveloperManagedAuthAddress({
registerAuthAddressDeveloperManagedSignedKeyReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------------------------------------------------- | ----------------------------------------------------- | :------: | ----------- |
| `registerAuthAddressDeveloperManagedSignedKeyReqBody` | `RegisterAuthAddressDeveloperManagedSignedKeyReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.registerSignedKeyForDeveloperManagedAuthAddress({
registerAuthAddressDeveloperManagedSignedKeyReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [registerSignedKeyForDeveloperManagedAuthAddress](/reference/register-signed-key-for-developer-managed-auth-address)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# deleteBans
Source: https://docs.neynar.com/nodejs-sdk/ban-apis/deleteBans
Unban FIDs from app
> **Group:** Ban APIs
Use this when you need: **Unban FIDs from app**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.deleteBans({
banReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------ | ------------ | :------: | ----------- |
| `banReqBody` | `BanReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.deleteBans({
banReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [deleteBans](/reference/delete-bans)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchBanList
Source: https://docs.neynar.com/nodejs-sdk/ban-apis/fetchBanList
Banned FIDs of app
> **Group:** Ban APIs
Use this when you need: **Banned FIDs of app**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchBanList({
// xNeynarExperimental: true,
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------- | :------: | ----------- |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchBanList({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchBanList](/reference/fetch-ban-list)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# publishBans
Source: https://docs.neynar.com/nodejs-sdk/ban-apis/publishBans
Ban FIDs from app
> **Group:** Ban APIs
Use this when you need: **Ban FIDs from app**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.publishBans({
banReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------ | ------------ | :------: | ----------- |
| `banReqBody` | `BanReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.publishBans({
banReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [publishBans](/reference/publish-bans)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# deleteBlock
Source: https://docs.neynar.com/nodejs-sdk/block-apis/deleteBlock
Unblock FID
> **Group:** Block APIs
Use this when you need: **Unblock FID**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.deleteBlock({
blockReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| -------------- | -------------- | :------: | ----------- |
| `blockReqBody` | `BlockReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.deleteBlock({
blockReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [deleteBlock](/reference/delete-block)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchBlockList
Source: https://docs.neynar.com/nodejs-sdk/block-apis/fetchBlockList
Blocked / Blocked by FIDs
> **Group:** Block APIs
Use this when you need: **Blocked / Blocked by FIDs**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchBlockList({
// xNeynarExperimental: true,
// blockerFid: 123,
// blockedFid: 123,
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------- | :------: | ----------- |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `blockerFid` | `number` | ❌ | - |
| `blockedFid` | `number` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchBlockList({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchBlockList](/reference/fetch-block-list)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# publishBlock
Source: https://docs.neynar.com/nodejs-sdk/block-apis/publishBlock
Block FID
> **Group:** Block APIs
Use this when you need: **Block FID**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.publishBlock({
blockReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| -------------- | -------------- | :------: | ----------- |
| `blockReqBody` | `BlockReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.publishBlock({
blockReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [publishBlock](/reference/publish-block)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# deleteCast
Source: https://docs.neynar.com/nodejs-sdk/cast-apis/deleteCast
Delete a cast
> **Group:** Cast APIs
Use this when you need: **Delete a cast**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.deleteCast({
deleteCastReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------- | ------------------- | :------: | ----------- |
| `deleteCastReqBody` | `DeleteCastReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.deleteCast({
deleteCastReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [deleteCast](/reference/delete-cast)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchBulkCasts
Source: https://docs.neynar.com/nodejs-sdk/cast-apis/fetchBulkCasts
Bulk fetch casts
> **Group:** Cast APIs
Use this when you need: **Bulk fetch casts**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchBulkCasts({
casts: "example", // Hashes of the cast to be retrived (Comma separated, no spaces)
// xNeynarExperimental: true,
// viewerFid: 123,
// sortType: "value",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | ---------------------------- | :------: | -------------------------------------------------------------- |
| `casts` | `string` | ✅ | Hashes of the cast to be retrived (Comma separated, no spaces) |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
| `sortType` | `FetchBulkCastsSortTypeEnum` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchBulkCasts({
casts: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchBulkCasts](/reference/fetch-bulk-casts)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchCastQuotes
Source: https://docs.neynar.com/nodejs-sdk/cast-apis/fetchCastQuotes
Cast Quotes
> **Group:** Cast APIs
Use this when you need: **Cast Quotes**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchCastQuotes({
identifier: "example", // Cast identifier (It\'s either a URL or a hash)
type: "value", // The query param accepted by the API. Sent along with identifier param. url - Cast identifier is a url hash - Cast identifier is a hash
// xNeynarExperimental: true,
// viewerFid: 123,
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | ------------------------- | :------: | -------------------------------------------------------------------------------------------------------------------------------------- |
| `identifier` | `string` | ✅ | Cast identifier (It\'s either a URL or a hash) |
| `type` | `FetchCastQuotesTypeEnum` | ✅ | The query param accepted by the API. Sent along with identifier param. url - Cast identifier is a url hash - Cast identifier is a hash |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchCastQuotes({
identifier: "example",
type: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchCastQuotes](/reference/fetch-cast-quotes)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchComposerActions
Source: https://docs.neynar.com/nodejs-sdk/cast-apis/fetchComposerActions
# fetchComposerActions
Fetch composer actions
> **Group:** Cast APIs
Use this when you need: **Fetch composer actions**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchComposerActions({
list: "value", // Type of list to fetch.
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | ------------------------------ | :------: | ---------------------- |
| `list` | `FetchComposerActionsListEnum` | ✅ | Type of list to fetch. |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchComposerActions({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchEmbeddedUrlMetadata
Source: https://docs.neynar.com/nodejs-sdk/cast-apis/fetchEmbeddedUrlMetadata
Embedded URL metadata
> **Group:** Cast APIs
Use this when you need: **Embedded URL metadata**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchEmbeddedUrlMetadata({
url: "example", // URL to crawl metadata of
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | ------------------------ |
| `url` | `string` | ✅ | URL to crawl metadata of |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchEmbeddedUrlMetadata({
url: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchEmbeddedUrlMetadata](/reference/fetch-embedded-url-metadata)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# lookupCastByHashOrUrl
Source: https://docs.neynar.com/nodejs-sdk/cast-apis/lookupCastByHashOrUrl
By hash or URL
> **Group:** Cast APIs
Use this when you need: **By hash or URL**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.lookupCastByHashOrUrl({
identifier: "example", // Cast identifier (It\'s either a URL or a hash)
type: "value", // The query param accepted by the API. Sent along with identifier param. url - Cast identifier is a url hash - Cast identifier is a hash
// xNeynarExperimental: true,
// viewerFid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | ------------------------------- | :------: | -------------------------------------------------------------------------------------------------------------------------------------- |
| `identifier` | `string` | ✅ | Cast identifier (It\'s either a URL or a hash) |
| `type` | `LookupCastByHashOrUrlTypeEnum` | ✅ | The query param accepted by the API. Sent along with identifier param. url - Cast identifier is a url hash - Cast identifier is a hash |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.lookupCastByHashOrUrl({
identifier: "example",
type: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [lookupCastByHashOrUrl](/reference/lookup-cast-by-hash-or-url)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# lookupCastConversation
Source: https://docs.neynar.com/nodejs-sdk/cast-apis/lookupCastConversation
Conversation for a cast
> **Group:** Cast APIs
Use this when you need: **Conversation for a cast**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.lookupCastConversation({
identifier: "example", // Cast identifier (It\'s either a URL or a hash)
type: "value", // The query param accepted by the API. Sent along with identifier param. url - Cast identifier is a url hash - Cast identifier is a hash
// xNeynarExperimental: true,
// replyDepth: 123,
// includeChronologicalParentCasts: true,
// viewerFid: 123,
// sortType: "value",
// fold: "value",
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| --------------------------------- | ------------------------------------ | :------: | -------------------------------------------------------------------------------------------------------------------------------------- | - |
| `identifier` | `string` | ✅ | Cast identifier (It\'s either a URL or a hash) | |
| `type` | `LookupCastConversationTypeEnum` | ✅ | The query param accepted by the API. Sent along with identifier param. url - Cast identifier is a url hash - Cast identifier is a hash | |
| `xNeynarExperimental` | `boolean` | ❌ | - | |
| `replyDepth` | \`number | null\` | ❌ | - |
| `includeChronologicalParentCasts` | \`boolean | null\` | ❌ | - |
| `viewerFid` | `number` | ❌ | - | |
| `sortType` | `LookupCastConversationSortTypeEnum` | ❌ | - | |
| `fold` | `LookupCastConversationFoldEnum` | ❌ | - | |
| `limit` | `number` | ❌ | - | |
| `cursor` | `string` | ❌ | - | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.lookupCastConversation({
identifier: "example",
type: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [lookupCastConversation](/reference/lookup-cast-conversation)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# publishCast
Source: https://docs.neynar.com/nodejs-sdk/cast-apis/publishCast
Post a cast
> **Group:** Cast APIs
Use this when you need: **Post a cast**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.publishCast({
postCastReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------------- | ----------------- | :------: | ----------- |
| `postCastReqBody` | `PostCastReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.publishCast({
postCastReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [publishCast](/reference/publish-cast)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# searchCasts
Source: https://docs.neynar.com/nodejs-sdk/cast-apis/searchCasts
Search for casts
> **Group:** Cast APIs
Use this when you need: **Search for casts**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.searchCasts({
q: "example", // Query string to search for casts. Supported operators: | Operator | Description | | --------- | -------------------------------------------------------------------------------------------------------- | | `+` | Acts as the AND operator. This is the default operator between terms and can usually be omitted. | | `\\|` | Acts as the OR operator. | | `*` | When used at the end of a term, signifies a prefix query. | | `\"` | Wraps several terms into a phrase (for example, `\"star wars\"`). | | `(`, `)` | Wrap a clause for precedence (for example, `star + (wars \\| trek)`). | | `~n` | When used after a term (for example, `satr~3`), sets `fuzziness`. When used after a phrase, sets `slop`. | | `-` | Negates the term. | | `before:` | Search for casts before a specific date. (e.g. `before:2025-04-20` or `before:2025-04-20T23:59:59`) | | `after:` | Search for casts after a specific date. (e.g. `after:2025-04-20` or `after:2025-04-20T00:00:00`) |
// xNeynarExperimental: true,
// mode: "value",
// sortType: "value",
// authorFid: 123,
// viewerFid: 123,
// parentUrl: "example",
// channelId: "example",
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
| --------------------- | ------------------------- | :------: | ------------------------------------------------------ | -------- | ----------- | - | --------- | -------------------------------------------------------------------------------------------------------- | - | ----- | ------------------------------------------------------------------------------------------------ | - | ---- | -- | ------------------------ | - | ------ | --------------------------------------------------------- | - | ----------- | ----------------------------------------------------------------------------- | - | ------------ | ------------------------------------------------------------ | --------- | - | ------- | --------------------------------------------------------------------------------------------------------------- | - | ----- | ----------------- | - | ----------- | ------------------------------------------------------------------------------------------------------- | - | ---------- | ---------------------------------------------------------------------------------------------------- | - |
| `q` | `string` | ✅ | Query string to search for casts. Supported operators: | Operator | Description | | --------- | -------------------------------------------------------------------------------------------------------- | | \`+\` | Acts as the AND operator. This is the default operator between terms and can usually be omitted. | | \`\\ | \` | Acts as the OR operator. | | \`\*\` | When used at the end of a term, signifies a prefix query. | | \`\"\` | Wraps several terms into a phrase (for example, \`\"star wars\"\`). | | \`(\`, \`)\` | Wrap a clause for precedence (for example, \`star + (wars \\ | trek)\`). | | \`\~n\` | When used after a term (for example, \`satr\~3\`), sets \`fuzziness\`. When used after a phrase, sets \`slop\`. | | \`-\` | Negates the term. | | \`before:\` | Search for casts before a specific date. (e.g. \`before:2025-04-20\` or \`before:2025-04-20T23:59:59\`) | | \`after:\` | Search for casts after a specific date. (e.g. \`after:2025-04-20\` or \`after:2025-04-20T00:00:00\`) | |
| `xNeynarExperimental` | `boolean` | ❌ | - | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
| `mode` | `SearchCastsModeEnum` | ❌ | - | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
| `sortType` | `SearchCastsSortTypeEnum` | ❌ | - | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
| `authorFid` | `number` | ❌ | - | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
| `viewerFid` | `number` | ❌ | - | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
| `parentUrl` | `string` | ❌ | - | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
| `channelId` | `string` | ❌ | - | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
| `limit` | `number` | ❌ | - | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
| `cursor` | `string` | ❌ | - | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.searchCasts({
q: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [searchCasts](/reference/search-casts)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchAllChannels
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/fetchAllChannels
Fetch all channels with their details
> **Group:** Channel APIs
Use this when you need: **Fetch all channels with their details**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchAllChannels({
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | ----------- |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchAllChannels({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchAllChannels](/reference/fetch-all-channels)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchBulkChannels
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/fetchBulkChannels
Bulk fetch
> **Group:** Channel APIs
Use this when you need: **Bulk fetch**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchBulkChannels({
ids: "example", // Comma separated list of channel IDs or parent_urls, up to 100 at a time
// type: "value",
// viewerFid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------- | --------------------------- | :------: | ------------------------------------------------------------------------ |
| `ids` | `string` | ✅ | Comma separated list of channel IDs or parent\_urls, up to 100 at a time |
| `type` | `FetchBulkChannelsTypeEnum` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchBulkChannels({
ids: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchBulkChannels](/reference/fetch-bulk-channels)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchChannelInvites
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/fetchChannelInvites
Open invites
> **Group:** Channel APIs
Use this when you need: **Open invites**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchChannelInvites({
// channelId: "example",
// invitedFid: 123,
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------ | -------- | :------: | ----------- |
| `channelId` | `string` | ❌ | - |
| `invitedFid` | `number` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchChannelInvites({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchChannelInvites](/reference/fetch-channel-invites)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchChannelMembers
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/fetchChannelMembers
Fetch members
> **Group:** Channel APIs
Use this when you need: **Fetch members**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchChannelMembers({
channelId: "example", // Channel ID for the channel being queried
// xNeynarExperimental: true,
// fid: 123,
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------- | :------: | ---------------------------------------- |
| `channelId` | `string` | ✅ | Channel ID for the channel being queried |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `fid` | `number` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchChannelMembers({
channelId: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchChannelMembers](/reference/fetch-channel-members)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchFollowersForAChannel
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/fetchFollowersForAChannel
For channel
> **Group:** Channel APIs
Use this when you need: **For channel**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchFollowersForAChannel({
id: "example", // Channel ID for the channel being queried
// xNeynarExperimental: true,
// viewerFid: 123,
// cursor: "example",
// limit: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------- | :------: | ---------------------------------------- |
| `id` | `string` | ✅ | Channel ID for the channel being queried |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
| `limit` | `number` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchFollowersForAChannel({
id: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchFollowersForAChannel](/reference/fetch-followers-for-a-channel)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchRelevantFollowersForAChannel
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/fetchRelevantFollowersForAChannel
Relevant followers
> **Group:** Channel APIs
Use this when you need: **Relevant followers**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchRelevantFollowersForAChannel({
id: "example", // Channel ID being queried
viewerFid: 123, // The FID of the user to customize this response for. Providing this will also return a list of followers that respects this user\'s mutes and blocks and includes `viewer_context`.
// xNeynarExperimental: true,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------- | :------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | `string` | ✅ | Channel ID being queried |
| `viewerFid` | `number` | ✅ | The FID of the user to customize this response for. Providing this will also return a list of followers that respects this user\'s mutes and blocks and includes \`viewer\_context\`. |
| `xNeynarExperimental` | `boolean` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchRelevantFollowersForAChannel({
id: "example",
viewerFid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchRelevantFollowersForAChannel](/reference/fetch-relevant-followers-for-a-channel)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchTrendingChannels
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/fetchTrendingChannels
Channels by activity
> **Group:** Channel APIs
Use this when you need: **Channels by activity**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchTrendingChannels({
// timeWindow: "value",
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------ | ------------------------------------- | :------: | ----------- |
| `timeWindow` | `FetchTrendingChannelsTimeWindowEnum` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchTrendingChannels({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchTrendingChannels](/reference/fetch-trending-channels)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchUserChannelMemberships
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/fetchUserChannelMemberships
Member of
> **Group:** Channel APIs
Use this when you need: **Member of**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchUserChannelMemberships({
fid: 123, // The FID of the user.
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | -------------------- |
| `fid` | `number` | ✅ | The FID of the user. |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchUserChannelMemberships({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchUserChannelMemberships](/reference/fetch-user-channel-memberships)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchUserChannels
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/fetchUserChannels
Following
> **Group:** Channel APIs
Use this when you need: **Following**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchUserChannels({
fid: 123, // The FID of the user.
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | -------------------- |
| `fid` | `number` | ✅ | The FID of the user. |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchUserChannels({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchUserChannels](/reference/fetch-user-channels)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchUsersActiveChannels
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/fetchUsersActiveChannels
Fetch channels that user is active in
> **Group:** Channel APIs
Use this when you need: **Fetch channels that user is active in**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchUsersActiveChannels({
fid: 123, // The user\'s FID (identifier)
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | -------------------------------- |
| `fid` | `number` | ✅ | The user\'s FID (identifier) |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchUsersActiveChannels({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchUsersActiveChannels](/reference/fetch-users-active-channels)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# followChannel
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/followChannel
Follow a channel
> **Group:** Channel APIs
Use this when you need: **Follow a channel**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.followChannel({
channelFollowReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------- | ---------------------- | :------: | ----------- |
| `channelFollowReqBody` | `ChannelFollowReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.followChannel({
channelFollowReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [followChannel](/reference/follow-channel)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# inviteChannelMember
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/inviteChannelMember
Invite
> **Group:** Channel APIs
Use this when you need: **Invite**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.inviteChannelMember({
inviteChannelMemberReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------------- | ---------------------------- | :------: | ----------- |
| `inviteChannelMemberReqBody` | `InviteChannelMemberReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.inviteChannelMember({
inviteChannelMemberReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [inviteChannelMember](/reference/invite-channel-member)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# lookupChannel
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/lookupChannel
By ID or parent\_url
> **Group:** Channel APIs
Use this when you need: **By ID or parent\_url**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.lookupChannel({
id: "example", // Channel ID for the channel being queried
// type: "value",
// viewerFid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------- | ----------------------- | :------: | ---------------------------------------- |
| `id` | `string` | ✅ | Channel ID for the channel being queried |
| `type` | `LookupChannelTypeEnum` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.lookupChannel({
id: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [lookupChannel](/reference/lookup-channel)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# removeChannelMember
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/removeChannelMember
Remove user
> **Group:** Channel APIs
Use this when you need: **Remove user**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.removeChannelMember({
removeChannelMemberReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------------- | ---------------------------- | :------: | ----------- |
| `removeChannelMemberReqBody` | `RemoveChannelMemberReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.removeChannelMember({
removeChannelMemberReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [removeChannelMember](/reference/remove-channel-member)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# respondChannelInvite
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/respondChannelInvite
Accept or reject an invite
> **Group:** Channel APIs
Use this when you need: **Accept or reject an invite**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.respondChannelInvite({
respondChannelInviteReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------------------------- | ----------------------------- | :------: | ----------- |
| `respondChannelInviteReqBody` | `RespondChannelInviteReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.respondChannelInvite({
respondChannelInviteReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [respondChannelInvite](/reference/respond-channel-invite)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# searchChannels
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/searchChannels
Search by ID or name
> **Group:** Channel APIs
Use this when you need: **Search by ID or name**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.searchChannels({
q: "example", // Channel ID or name for the channel being queried
// limit: 123,
// cursor: "example",
// viewerFid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------- | -------- | :------: | ------------------------------------------------ |
| `q` | `string` | ✅ | Channel ID or name for the channel being queried |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.searchChannels({
q: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [searchChannels](/reference/search-channels)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# unfollowChannel
Source: https://docs.neynar.com/nodejs-sdk/channel-apis/unfollowChannel
Unfollow a channel
> **Group:** Channel APIs
Use this when you need: **Unfollow a channel**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.unfollowChannel({
channelFollowReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------- | ---------------------- | :------: | ----------- |
| `channelFollowReqBody` | `ChannelFollowReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.unfollowChannel({
channelFollowReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [unfollowChannel](/reference/unfollow-channel)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchCastsForUser
Source: https://docs.neynar.com/nodejs-sdk/feed-apis/fetchCastsForUser
Chronologically
> **Group:** Feed APIs
Use this when you need: **Chronologically**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchCastsForUser({
fid: 123, // FID of user whose recent casts you want to fetch
// xNeynarExperimental: true,
// appFid: 123,
// viewerFid: 123,
// limit: 123,
// cursor: "example",
// includeReplies: true,
// parentUrl: "example",
// channelId: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| --------------------- | --------- | :------: | ------------------------------------------------ | - |
| `fid` | `number` | ✅ | FID of user whose recent casts you want to fetch | |
| `xNeynarExperimental` | `boolean` | ❌ | - | |
| `appFid` | `number` | ❌ | - | |
| `viewerFid` | `number` | ❌ | - | |
| `limit` | `number` | ❌ | - | |
| `cursor` | `string` | ❌ | - | |
| `includeReplies` | \`boolean | null\` | ❌ | - |
| `parentUrl` | `string` | ❌ | - | |
| `channelId` | `string` | ❌ | - | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchCastsForUser({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchCastsForUser](/reference/fetch-casts-for-user)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchFeed
Source: https://docs.neynar.com/nodejs-sdk/feed-apis/fetchFeed
By filters
> **Group:** Feed APIs
Use this when you need: **By filters**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchFeed({
// xNeynarExperimental: true,
// feedType: "value",
// filterType: "value",
// fid: 123,
// fids: "example",
// parentUrl: "example",
// channelId: "example",
// membersOnly: true,
// embedUrl: "example",
// embedTypes: [],
// withRecasts: true,
// limit: 123,
// cursor: "example",
// viewerFid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| --------------------- | -------------------------------- | :------: | ----------- | - |
| `xNeynarExperimental` | `boolean` | ❌ | - | |
| `feedType` | `FetchFeedFeedTypeEnum` | ❌ | - | |
| `filterType` | `FetchFeedFilterTypeEnum` | ❌ | - | |
| `fid` | `number` | ❌ | - | |
| `fids` | `string` | ❌ | - | |
| `parentUrl` | `string` | ❌ | - | |
| `channelId` | `string` | ❌ | - | |
| `membersOnly` | \`boolean | null\` | ❌ | - |
| `embedUrl` | `string` | ❌ | - | |
| `embedTypes` | `Array` | ❌ | - | |
| `withRecasts` | \`boolean | null\` | ❌ | - |
| `limit` | `number` | ❌ | - | |
| `cursor` | `string` | ❌ | - | |
| `viewerFid` | `number` | ❌ | - | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchFeed({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchFeed](/reference/fetch-feed)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchFeedByChannelIds
Source: https://docs.neynar.com/nodejs-sdk/feed-apis/fetchFeedByChannelIds
By channel IDs
> **Group:** Feed APIs
Use this when you need: **By channel IDs**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchFeedByChannelIds({
channelIds: "example", // Comma separated list of up to 10 channel IDs e.g. neynar,farcaster
// xNeynarExperimental: true,
// withRecasts: true,
// viewerFid: 123,
// withReplies: true,
// membersOnly: true,
// fids: "example",
// limit: 123,
// cursor: "example",
// shouldModerate: true,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| --------------------- | --------- | :------: | ------------------------------------------------------------------ | - |
| `channelIds` | `string` | ✅ | Comma separated list of up to 10 channel IDs e.g. neynar,farcaster | |
| `xNeynarExperimental` | `boolean` | ❌ | - | |
| `withRecasts` | \`boolean | null\` | ❌ | - |
| `viewerFid` | `number` | ❌ | - | |
| `withReplies` | \`boolean | null\` | ❌ | - |
| `membersOnly` | \`boolean | null\` | ❌ | - |
| `fids` | `string` | ❌ | - | |
| `limit` | `number` | ❌ | - | |
| `cursor` | `string` | ❌ | - | |
| `shouldModerate` | \`boolean | null\` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchFeedByChannelIds({
channelIds: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchFeedByChannelIds](/reference/fetch-feed-by-channel-ids)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchFeedByParentUrls
Source: https://docs.neynar.com/nodejs-sdk/feed-apis/fetchFeedByParentUrls
By parent URLs
> **Group:** Feed APIs
Use this when you need: **By parent URLs**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchFeedByParentUrls({
parentUrls: "example", // Comma separated list of parent_urls
// xNeynarExperimental: true,
// withRecasts: true,
// viewerFid: 123,
// withReplies: true,
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| --------------------- | --------- | :------: | ------------------------------------ | - |
| `parentUrls` | `string` | ✅ | Comma separated list of parent\_urls | |
| `xNeynarExperimental` | `boolean` | ❌ | - | |
| `withRecasts` | \`boolean | null\` | ❌ | - |
| `viewerFid` | `number` | ❌ | - | |
| `withReplies` | \`boolean | null\` | ❌ | - |
| `limit` | `number` | ❌ | - | |
| `cursor` | `string` | ❌ | - | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchFeedByParentUrls({
parentUrls: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchFeedByParentUrls](/reference/fetch-feed-by-parent-urls)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchFeedByTopic
Source: https://docs.neynar.com/nodejs-sdk/feed-apis/fetchFeedByTopic
By topic
> **Group:** Feed APIs
Use this when you need: **By topic**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchFeedByTopic({
slug: "example", // Topic slug to filter casts by. Must be lowercase and contain only alphanumeric characters and underscores.
// xNeynarExperimental: true,
// withRecasts: true,
// viewerFid: 123,
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| --------------------- | --------- | :------: | ---------------------------------------------------------------------------------------------------------- | - |
| `slug` | `string` | ✅ | Topic slug to filter casts by. Must be lowercase and contain only alphanumeric characters and underscores. | |
| `xNeynarExperimental` | `boolean` | ❌ | - | |
| `withRecasts` | \`boolean | null\` | ❌ | - |
| `viewerFid` | `number` | ❌ | - | |
| `limit` | `number` | ❌ | - | |
| `cursor` | `string` | ❌ | - | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchFeedByTopic({
slug: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchFeedForYou
Source: https://docs.neynar.com/nodejs-sdk/feed-apis/fetchFeedForYou
For you
> **Group:** Feed APIs
Use this when you need: **For you**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchFeedForYou({
fid: 123, // FID of user whose feed you want to create
// xNeynarExperimental: true,
// viewerFid: 123,
// provider: "value",
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | ----------------------------- | :------: | ----------------------------------------- |
| `fid` | `number` | ✅ | FID of user whose feed you want to create |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
| `provider` | `FetchFeedForYouProviderEnum` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchFeedForYou({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchFeedForYou](/reference/fetch-feed-for-you)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchFramesOnlyFeed
Source: https://docs.neynar.com/nodejs-sdk/feed-apis/fetchFramesOnlyFeed
# fetchFramesOnlyFeed
Casts with mini apps
> **Group:** Feed APIs
Use this when you need: **Casts with mini apps**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchFramesOnlyFeed({
// xNeynarExperimental: true,
// limit: 123,
// viewerFid: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------- | :------: | ----------- |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchFramesOnlyFeed({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchPopularCastsByUser
Source: https://docs.neynar.com/nodejs-sdk/feed-apis/fetchPopularCastsByUser
10 most popular casts
> **Group:** Feed APIs
Use this when you need: **10 most popular casts**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchPopularCastsByUser({
fid: 123, // FID of user whose feed you want to create
// viewerFid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------- | -------- | :------: | ----------------------------------------- |
| `fid` | `number` | ✅ | FID of user whose feed you want to create |
| `viewerFid` | `number` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchPopularCastsByUser({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchPopularCastsByUser](/reference/fetch-popular-casts-by-user)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchRepliesAndRecastsForUser
Source: https://docs.neynar.com/nodejs-sdk/feed-apis/fetchRepliesAndRecastsForUser
Replies and recasts
> **Group:** Feed APIs
Use this when you need: **Replies and recasts**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchRepliesAndRecastsForUser({
fid: 123, // FID of user whose replies and recasts you want to fetch
// filter: "value",
// limit: 123,
// cursor: "example",
// viewerFid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------- | ----------------------------------------- | :------: | ------------------------------------------------------- |
| `fid` | `number` | ✅ | FID of user whose replies and recasts you want to fetch |
| `filter` | `FetchRepliesAndRecastsForUserFilterEnum` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchRepliesAndRecastsForUser({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchRepliesAndRecastsForUser](/reference/fetch-replies-and-recasts-for-user)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchTrendingFeed
Source: https://docs.neynar.com/nodejs-sdk/feed-apis/fetchTrendingFeed
Trending feeds
> **Group:** Feed APIs
Use this when you need: **Trending feeds**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchTrendingFeed({
// xNeynarExperimental: true,
// limit: 123,
// cursor: "example",
// viewerFid: 123,
// timeWindow: "value",
// channelId: "example",
// parentUrl: "example",
// provider: "value",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------------------------------- | :------: | ----------- |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
| `timeWindow` | `FetchTrendingFeedTimeWindowEnum` | ❌ | - |
| `channelId` | `string` | ❌ | - |
| `parentUrl` | `string` | ❌ | - |
| `provider` | `FetchTrendingFeedProviderEnum` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchTrendingFeed({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchTrendingFeed](/reference/fetch-trending-feed)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchUserFollowingFeed
Source: https://docs.neynar.com/nodejs-sdk/feed-apis/fetchUserFollowingFeed
Following
> **Group:** Feed APIs
Use this when you need: **Following**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchUserFollowingFeed({
fid: 123, // FID of user whose feed you want to create
// xNeynarExperimental: true,
// viewerFid: 123,
// withRecasts: true,
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| --------------------- | --------- | :------: | ----------------------------------------- | - |
| `fid` | `number` | ✅ | FID of user whose feed you want to create | |
| `xNeynarExperimental` | `boolean` | ❌ | - | |
| `viewerFid` | `number` | ❌ | - | |
| `withRecasts` | \`boolean | null\` | ❌ | - |
| `limit` | `number` | ❌ | - | |
| `cursor` | `string` | ❌ | - | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchUserFollowingFeed({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchUserFollowingFeed](/reference/fetch-user-following-feed)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# isFnameAvailable
Source: https://docs.neynar.com/nodejs-sdk/fname-apis/isFnameAvailable
Check fname availability
> **Group:** Fname APIs
Use this when you need: **Check fname availability**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.isFnameAvailable({
fname: "example", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | ----------- |
| `fname` | `string` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.isFnameAvailable({
fname: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [isFnameAvailable](/reference/is-fname-available)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchFollowSuggestions
Source: https://docs.neynar.com/nodejs-sdk/follow-apis/fetchFollowSuggestions
Suggest Follows
> **Group:** Follow APIs
Use this when you need: **Suggest Follows**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchFollowSuggestions({
fid: 123, // FID of the user whose following you want to fetch.
// xNeynarExperimental: true,
// viewerFid: 123,
// limit: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| --------------------- | --------- | :------: | ----------- | -------------------------------------------------- |
| `fid` | \`number | null\` | ✅ | FID of the user whose following you want to fetch. |
| `xNeynarExperimental` | `boolean` | ❌ | - | |
| `viewerFid` | \`number | null\` | ❌ | - |
| `limit` | `number` | ❌ | - | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchFollowSuggestions({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchFollowSuggestions](/reference/fetch-follow-suggestions)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchRelevantFollowers
Source: https://docs.neynar.com/nodejs-sdk/follow-apis/fetchRelevantFollowers
Relevant followers
> **Group:** Follow APIs
Use this when you need: **Relevant followers**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchRelevantFollowers({
targetFid: 123, // User who\'s profile you are looking at
viewerFid: 123, // The FID of the user to customize this response for. Providing this will also return a list of followers that respects this user\'s mutes and blocks and includes `viewer_context`.
// xNeynarExperimental: true,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------- | :------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `targetFid` | `number` | ✅ | User who\'s profile you are looking at |
| `viewerFid` | `number` | ✅ | The FID of the user to customize this response for. Providing this will also return a list of followers that respects this user\'s mutes and blocks and includes \`viewer\_context\`. |
| `xNeynarExperimental` | `boolean` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchRelevantFollowers({
targetFid: 123,
viewerFid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchRelevantFollowers](/reference/fetch-relevant-followers)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchUserFollowers
Source: https://docs.neynar.com/nodejs-sdk/follow-apis/fetchUserFollowers
Followers
> **Group:** Follow APIs
Use this when you need: **Followers**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchUserFollowers({
fid: 123, // User who\'s profile you are looking at
// xNeynarExperimental: true,
// viewerFid: 123,
// sortType: "value",
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | -------------------------------- | :------: | ------------------------------------------ |
| `fid` | `number` | ✅ | User who\'s profile you are looking at |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
| `sortType` | `FetchUserFollowersSortTypeEnum` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchUserFollowers({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchUserFollowers](/reference/fetch-user-followers)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchUserFollowing
Source: https://docs.neynar.com/nodejs-sdk/follow-apis/fetchUserFollowing
Following
> **Group:** Follow APIs
Use this when you need: **Following**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchUserFollowing({
fid: 123, // FID of the user whose following you want to fetch.
// xNeynarExperimental: true,
// viewerFid: 123,
// sortType: "value",
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | -------------------------------- | :------: | -------------------------------------------------- |
| `fid` | `number` | ✅ | FID of the user whose following you want to fetch. |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
| `sortType` | `FetchUserFollowingSortTypeEnum` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchUserFollowing({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchUserFollowing](/reference/fetch-user-following)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchUserReciprocalFollowers
Source: https://docs.neynar.com/nodejs-sdk/follow-apis/fetchUserReciprocalFollowers
Reciprocal Followers
> **Group:** Follow APIs
Use this when you need: **Reciprocal Followers**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchUserReciprocalFollowers({
fid: 123, // required
// xNeynarExperimental: true,
// viewerFid: 123,
// limit: 123,
// cursor: "example",
// sortType: "value",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | ------------------------------------------ | :------: | ----------- |
| `fid` | `number` | ✅ | - |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
| `sortType` | `FetchUserReciprocalFollowersSortTypeEnum` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchUserReciprocalFollowers({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchUserReciprocalFollowers](/reference/fetch-user-reciprocal-followers)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# deleteNeynarFrame
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/deleteNeynarFrame
# deleteNeynarFrame
Delete mini app
> **Group:** Frame APIs
Use this when you need: **Delete mini app**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.deleteNeynarFrame({
deleteFrameReqBody: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| -------------------- | -------------------- | :------: | ----------- |
| `deleteFrameReqBody` | `DeleteFrameReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.deleteNeynarFrame({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchFrameCatalog
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/fetchFrameCatalog
Mini apps catalog
> **Group:** Frame APIs
Use this when you need: **Mini apps catalog**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchFrameCatalog({
// limit: 123,
// cursor: "example",
// timeWindow: "value",
// categories: [],
// networks: [],
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------ | ---------------------------------------- | :------: | ----------- |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
| `timeWindow` | `FetchFrameCatalogTimeWindowEnum` | ❌ | - |
| `categories` | `Array` | ❌ | - |
| `networks` | `Array` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchFrameCatalog({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchFrameCatalog](/reference/fetch-frame-catalog)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchFrameMetaTagsFromUrl
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/fetchFrameMetaTagsFromUrl
# fetchFrameMetaTagsFromUrl
Meta tags from URL
> **Group:** Frame APIs
Use this when you need: **Meta tags from URL**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchFrameMetaTagsFromUrl({
url: "example", // The mini app URL to crawl
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | ------------------------- |
| `url` | `string` | ✅ | The mini app URL to crawl |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchFrameMetaTagsFromUrl({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchNeynarFrames
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/fetchNeynarFrames
# fetchNeynarFrames
List of mini apps
> **Group:** Frame APIs
Use this when you need: **List of mini apps**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchNeynarFrames({
});
console.log(res);
```
## Parameters
*This method has no parameters.*
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchNeynarFrames({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchNotificationTokens
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/fetchNotificationTokens
List of mini app notification tokens
> **Group:** Frame APIs
Use this when you need: **List of mini app notification tokens**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchNotificationTokens({
// limit: 123,
// fids: "example",
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | ----------- |
| `limit` | `number` | ❌ | - |
| `fids` | `string` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchNotificationTokens({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchNotificationTokens](/reference/fetch-notification-tokens)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchRelevantFrames
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/fetchRelevantFrames
Relevant mini apps
> **Group:** Frame APIs
Use this when you need: **Relevant mini apps**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchRelevantFrames({
viewerFid: 123, // FID of the user to fetch relevant mini apps for
// timeWindow: "value",
// networks: [],
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------ | ---------------------------------------- | :------: | ----------------------------------------------- |
| `viewerFid` | `number` | ✅ | FID of the user to fetch relevant mini apps for |
| `timeWindow` | `FetchRelevantFramesTimeWindowEnum` | ❌ | - |
| `networks` | `Array` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchRelevantFrames({
viewerFid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchValidateFrameAnalytics
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/fetchValidateFrameAnalytics
# fetchValidateFrameAnalytics
Analytics for the mini app
> **Group:** Frame APIs
Use this when you need: **Analytics for the mini app**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchValidateFrameAnalytics({
frameUrl: "example", // URL of the mini app to fetch analytics for
analyticsType: "value", // Type of analytics to fetch
start: "example", // required
stop: "example", // required
// aggregateWindow: "value",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------------- | ------------------------------------------------ | :------: | ------------------------------------------ |
| `frameUrl` | `string` | ✅ | URL of the mini app to fetch analytics for |
| `analyticsType` | `FetchValidateFrameAnalyticsAnalyticsTypeEnum` | ✅ | Type of analytics to fetch |
| `start` | `string` | ✅ | - |
| `stop` | `string` | ✅ | - |
| `aggregateWindow` | `FetchValidateFrameAnalyticsAggregateWindowEnum` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchValidateFrameAnalytics({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchValidateFrameList
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/fetchValidateFrameList
# fetchValidateFrameList
All mini apps validated by user
> **Group:** Frame APIs
Use this when you need: **All mini apps validated by user**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchValidateFrameList({
});
console.log(res);
```
## Parameters
*This method has no parameters.*
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchValidateFrameList({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# getNotificationCampaignStats
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/getNotificationCampaignStats
Get notification campaign stats
> **Group:** Frame APIs
Use this when you need: **Get notification campaign stats**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.getNotificationCampaignStats({
// campaignId: "example",
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------ | -------- | :------: | ----------- |
| `campaignId` | `string` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.getNotificationCampaignStats({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# getTransactionPayFrame
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/getTransactionPayFrame
Get transaction pay mini app
> **Group:** Frame APIs
Use this when you need: **Get transaction pay mini app**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.getTransactionPayFrame({
id: "example", // ID of the transaction mini app to retrieve
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | ------------------------------------------ |
| `id` | `string` | ✅ | ID of the transaction mini app to retrieve |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.getTransactionPayFrame({
id: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [getTransactionPayFrame](/reference/get-transaction-pay-frame)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# lookupNeynarFrame
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/lookupNeynarFrame
# lookupNeynarFrame
Mini app by UUID or URL
> **Group:** Frame APIs
Use this when you need: **Mini app by UUID or URL**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.lookupNeynarFrame({
type: "value", // Type of identifier (either \'uuid\' or \'url\')
// uuid: "example",
// url: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | --------------------------- | :------: | --------------------------------------------------------------- |
| `type` | `LookupNeynarFrameTypeEnum` | ✅ | Type of identifier (either \'uuid\' or \'url\') |
| `uuid` | `string` | ❌ | - |
| `url` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.lookupNeynarFrame({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# postFrameAction
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/postFrameAction
# postFrameAction
Post a mini app action, cast action or a cast composer action
> **Group:** Frame APIs
Use this when you need: **Post a mini app action, cast action or a cast composer action**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.postFrameAction({
frameActionReqBody: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| -------------------- | -------------------- | :------: | ----------- |
| `frameActionReqBody` | `FrameActionReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.postFrameAction({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# postFrameActionDeveloperManaged
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/postFrameActionDeveloperManaged
# postFrameActionDeveloperManaged
Signature packet
> **Group:** Frame APIs
Use this when you need: **Signature packet**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.postFrameActionDeveloperManaged({
frameDeveloperManagedActionReqBody: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------------------------ | ------------------------------------ | :------: | ----------- |
| `frameDeveloperManagedActionReqBody` | `FrameDeveloperManagedActionReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.postFrameActionDeveloperManaged({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# publishFrameNotifications
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/publishFrameNotifications
Send notifications
> **Group:** Frame APIs
Use this when you need: **Send notifications**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.publishFrameNotifications({
sendFrameNotificationsReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------------------- | ------------------------------- | :------: | ----------- |
| `sendFrameNotificationsReqBody` | `SendFrameNotificationsReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.publishFrameNotifications({
sendFrameNotificationsReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [publishFrameNotifications](/reference/publish-frame-notifications)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# publishNeynarFrame
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/publishNeynarFrame
# publishNeynarFrame
Create mini app
> **Group:** Frame APIs
Use this when you need: **Create mini app**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.publishNeynarFrame({
neynarFrameCreationReqBody: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------------- | ---------------------------- | :------: | ----------- |
| `neynarFrameCreationReqBody` | `NeynarFrameCreationReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.publishNeynarFrame({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# searchFrames
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/searchFrames
Search mini apps
> **Group:** Frame APIs
Use this when you need: **Search mini apps**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.searchFrames({
q: "example", // Query string to search for mini apps
// limit: 123,
// cursor: "example",
// networks: [],
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------- | --------------------------------- | :------: | ------------------------------------ |
| `q` | `string` | ✅ | Query string to search for mini apps |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
| `networks` | `Array` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.searchFrames({
q: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [searchFrames](/reference/search-frames)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# updateNeynarFrame
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/updateNeynarFrame
# updateNeynarFrame
Update mini app
> **Group:** Frame APIs
Use this when you need: **Update mini app**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.updateNeynarFrame({
neynarFrameUpdateReqBody: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| -------------------------- | -------------------------- | :------: | ----------- |
| `neynarFrameUpdateReqBody` | `NeynarFrameUpdateReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.updateNeynarFrame({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# validateFrameAction
Source: https://docs.neynar.com/nodejs-sdk/frame-apis/validateFrameAction
# validateFrameAction
Validate mini app action
> **Group:** Frame APIs
Use this when you need: **Validate mini app action**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.validateFrameAction({
validateFrameActionReqBody: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------------- | ---------------------------- | :------: | ----------- |
| `validateFrameActionReqBody` | `ValidateFrameActionReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.validateFrameAction({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchNonce
Source: https://docs.neynar.com/nodejs-sdk/login-apis/fetchNonce
Fetch nonce
> **Group:** Login APIs
Use this when you need: **Fetch nonce**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchNonce({
});
console.log(res);
```
## Parameters
*This method has no parameters.*
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchNonce({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchNonce](/reference/fetch-nonce)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchCastMetrics
Source: https://docs.neynar.com/nodejs-sdk/metric-apis/fetchCastMetrics
Metrics for casts
> **Group:** Metric APIs
Use this when you need: **Metrics for casts**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchCastMetrics({
q: "example", // Query string to search for casts
// xNeynarExperimental: true,
// interval: "value",
// authorFid: 123,
// channelId: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | ------------------------------ | :------: | -------------------------------- |
| `q` | `string` | ✅ | Query string to search for casts |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `interval` | `FetchCastMetricsIntervalEnum` | ❌ | - |
| `authorFid` | `number` | ❌ | - |
| `channelId` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchCastMetrics({
q: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchCastMetrics](/reference/fetch-cast-metrics)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# deleteMute
Source: https://docs.neynar.com/nodejs-sdk/mute-apis/deleteMute
Unmute FID
> **Group:** Mute APIs
Use this when you need: **Unmute FID**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.deleteMute({
muteReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------- | ------------- | :------: | ----------- |
| `muteReqBody` | `MuteReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.deleteMute({
muteReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [deleteMute](/reference/delete-mute)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchMuteList
Source: https://docs.neynar.com/nodejs-sdk/mute-apis/fetchMuteList
Muted FIDs of user
> **Group:** Mute APIs
Use this when you need: **Muted FIDs of user**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchMuteList({
fid: 123, // The user\'s FID (identifier)
// xNeynarExperimental: true,
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------- | :------: | -------------------------------- |
| `fid` | `number` | ✅ | The user\'s FID (identifier) |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchMuteList({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchMuteList](/reference/fetch-mute-list)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# publishMute
Source: https://docs.neynar.com/nodejs-sdk/mute-apis/publishMute
Mute FID
> **Group:** Mute APIs
Use this when you need: **Mute FID**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.publishMute({
muteReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------- | ------------- | :------: | ----------- |
| `muteReqBody` | `MuteReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.publishMute({
muteReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [publishMute](/reference/publish-mute)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchAllNotifications
Source: https://docs.neynar.com/nodejs-sdk/notification-apis/fetchAllNotifications
For user
> **Group:** Notification APIs
Use this when you need: **For user**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchAllNotifications({
fid: 123, // FID of the user you you want to fetch notifications for. The response will respect this user\'s mutes and blocks.
// xNeynarExperimental: true,
// type: [],
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | -------------------------------------- | :------: | --------------------------------------------------------------------------------------------------------------------- |
| `fid` | `number` | ✅ | FID of the user you you want to fetch notifications for. The response will respect this user\'s mutes and blocks. |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `type` | `Array` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchAllNotifications({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchAllNotifications](/reference/fetch-all-notifications)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchChannelNotificationsForUser
Source: https://docs.neynar.com/nodejs-sdk/notification-apis/fetchChannelNotificationsForUser
For user by channel
> **Group:** Notification APIs
Use this when you need: **For user by channel**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchChannelNotificationsForUser({
fid: 123, // FID of the user you you want to fetch notifications for. The response will respect this user\'s mutes and blocks.
channelIds: "example", // Comma separated channel_ids (find list of all channels here - https://docs.neynar.com/reference/list-all-channels)
// xNeynarExperimental: true,
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------- | :------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fid` | `number` | ✅ | FID of the user you you want to fetch notifications for. The response will respect this user\'s mutes and blocks. |
| `channelIds` | `string` | ✅ | Comma separated channel\_ids (find list of all channels here - [https://docs.neynar.com/reference/list-all-channels](https://docs.neynar.com/reference/list-all-channels)) |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchChannelNotificationsForUser({
fid: 123,
channelIds: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchChannelNotificationsForUser](/reference/fetch-channel-notifications-for-user)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchNotificationsByParentUrlForUser
Source: https://docs.neynar.com/nodejs-sdk/notification-apis/fetchNotificationsByParentUrlForUser
For user by parent\_urls
> **Group:** Notification APIs
Use this when you need: **For user by parent\_urls**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchNotificationsByParentUrlForUser({
fid: 123, // FID of the user you you want to fetch notifications for. The response will respect this user\'s mutes and blocks.
parentUrls: "example", // Comma separated parent_urls
// xNeynarExperimental: true,
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------- | :------: | --------------------------------------------------------------------------------------------------------------------- |
| `fid` | `number` | ✅ | FID of the user you you want to fetch notifications for. The response will respect this user\'s mutes and blocks. |
| `parentUrls` | `string` | ✅ | Comma separated parent\_urls |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchNotificationsByParentUrlForUser({
fid: 123,
parentUrls: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchNotificationsByParentUrlForUser](/reference/fetch-notifications-by-parent-url-for-user)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# markNotificationsAsSeen
Source: https://docs.neynar.com/nodejs-sdk/notification-apis/markNotificationsAsSeen
Mark as seen
> **Group:** Notification APIs
Use this when you need: **Mark as seen**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.markNotificationsAsSeen({
markNotificationsAsSeenReqBody: { /* request body */ }, // required
// authorization: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| -------------------------------- | -------------------------------- | :------: | ----------- |
| `markNotificationsAsSeenReqBody` | `MarkNotificationsAsSeenReqBody` | ✅ | - |
| `authorization` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.markNotificationsAsSeen({
markNotificationsAsSeenReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [markNotificationsAsSeen](/reference/mark-notifications-as-seen)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# createX402Signature
Source: https://docs.neynar.com/nodejs-sdk/onchain-apis/createX402Signature
Create x402 signature
> **Group:** Onchain APIs
Use this when you need: **Create x402 signature**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.createX402Signature({
xWalletId: "example", // Wallet ID to use for transactions
xApiKey: "example", // required
createX402SignatureRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------------- | ---------------------------- | :------: | --------------------------------- |
| `xWalletId` | `string` | ✅ | Wallet ID to use for transactions |
| `xApiKey` | `string` | ✅ | - |
| `createX402SignatureRequest` | `CreateX402SignatureRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.createX402Signature({
xWalletId: "example",
xApiKey: "example",
createX402SignatureRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [createX402Signature](/reference/create-x402-signature)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# deployErc721
Source: https://docs.neynar.com/nodejs-sdk/onchain-apis/deployErc721
Deploy ERC-721 collection
> **Group:** Onchain APIs
Use this when you need: **Deploy ERC-721 collection**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.deployErc721({
xWalletId: "example", // Wallet ID to use for transactions
deployErc721Request: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------------------- | :------: | --------------------------------- |
| `xWalletId` | `string` | ✅ | Wallet ID to use for transactions |
| `deployErc721Request` | `DeployErc721Request` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.deployErc721({
xWalletId: "example",
deployErc721Request: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# deployFungible
Source: https://docs.neynar.com/nodejs-sdk/onchain-apis/deployFungible
Deploy fungible
> **Group:** Onchain APIs
Use this when you need: **Deploy fungible**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.deployFungible({
owner: "example", // Ethereum address of the one who is creating the token
symbol: "example", // Symbol/Ticker for the token
name: "example", // Name of the token
// metadataMedia: "value",
// metadataDescription: "example",
// metadataNsfw: "value",
// metadataWebsiteLink: "example",
// metadataTwitter: "example",
// metadataDiscord: "example",
// metadataTelegram: "example",
// network: "value",
// factory: "value",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| --------------------- | -------------------------------- | :------: | ----------------------------------------------------- | - |
| `owner` | `string` | ✅ | Ethereum address of the one who is creating the token | |
| `symbol` | `string` | ✅ | Symbol/Ticker for the token | |
| `name` | `string` | ✅ | Name of the token | |
| `metadataMedia` | \`File | null\` | ❌ | - |
| `metadataDescription` | `string` | ❌ | - | |
| `metadataNsfw` | `DeployFungibleMetadataNsfwEnum` | ❌ | - | |
| `metadataWebsiteLink` | `string` | ❌ | - | |
| `metadataTwitter` | `string` | ❌ | - | |
| `metadataDiscord` | `string` | ❌ | - | |
| `metadataTelegram` | `string` | ❌ | - | |
| `network` | `DeployFungibleNetworkEnum` | ❌ | - | |
| `factory` | `DeployFungibleFactoryEnum` | ❌ | - | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.deployFungible({
owner: "example",
symbol: "example",
name: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [deployFungible](/reference/deploy-fungible)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchFungibleTrades
Source: https://docs.neynar.com/nodejs-sdk/onchain-apis/fetchFungibleTrades
Get fungible trades
> **Group:** Onchain APIs
Use this when you need: **Get fungible trades**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchFungibleTrades({
network: "value", // required
address: "example", // Contract address
// timeWindow: "value",
// minAmountUsd: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| -------------- | ----------------------------------- | :------: | ---------------- | - |
| `network` | `FetchFungibleTradesNetworkEnum` | ✅ | - | |
| `address` | `string` | ✅ | Contract address | |
| `timeWindow` | `FetchFungibleTradesTimeWindowEnum` | ❌ | - | |
| `minAmountUsd` | \`number | null\` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchFungibleTrades({
network: "value",
address: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchFungibleTrades](/reference/fetch-fungible-trades)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchFungibles
Source: https://docs.neynar.com/nodejs-sdk/onchain-apis/fetchFungibles
Fetch fungibles
> **Group:** Onchain APIs
Use this when you need: **Fetch fungibles**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchFungibles({
fungibles: "example", // Comma-separated fungible identifiers
// xNeynarExperimental: true,
// viewerFid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| --------------------- | --------- | :------: | ------------------------------------ | - |
| `fungibles` | `string` | ✅ | Comma-separated fungible identifiers | |
| `xNeynarExperimental` | `boolean` | ❌ | - | |
| `viewerFid` | \`number | null\` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchFungibles({
fungibles: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchFungibles](/reference/fetch-fungibles)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchRelevantFungibleOwners
Source: https://docs.neynar.com/nodejs-sdk/onchain-apis/fetchRelevantFungibleOwners
Relevant owners
> **Group:** Onchain APIs
Use this when you need: **Relevant owners**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchRelevantFungibleOwners({
contractAddress: "example", // Contract address of the fungible asset
network: "value", // Network of the fungible asset.
// viewerFid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------------- | ---------------------------------------- | :------: | -------------------------------------- |
| `contractAddress` | `string` | ✅ | Contract address of the fungible asset |
| `network` | `FetchRelevantFungibleOwnersNetworkEnum` | ✅ | Network of the fungible asset. |
| `viewerFid` | `number` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchRelevantFungibleOwners({
contractAddress: "example",
network: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchRelevantFungibleOwners](/reference/fetch-relevant-fungible-owners)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchTrendingFungibles
Source: https://docs.neynar.com/nodejs-sdk/onchain-apis/fetchTrendingFungibles
Trending fungibles
> **Group:** Onchain APIs
Use this when you need: **Trending fungibles**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchTrendingFungibles({
network: "value", // required
// timeWindow: "value",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------ | -------------------------------------- | :------: | ----------- |
| `network` | `FetchTrendingFungiblesNetworkEnum` | ✅ | - |
| `timeWindow` | `FetchTrendingFungiblesTimeWindowEnum` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchTrendingFungibles({
network: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchTrendingFungibles](/reference/fetch-trending-fungibles)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchUserBalance
Source: https://docs.neynar.com/nodejs-sdk/onchain-apis/fetchUserBalance
Token balance
> **Group:** Onchain APIs
Use this when you need: **Token balance**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchUserBalance({
fid: 123, // FID of the user to fetch
networks: [], // Comma separated list of networks to fetch balances for
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------- | ------------------------------------- | :------: | ------------------------------------------------------ |
| `fid` | `number` | ✅ | FID of the user to fetch |
| `networks` | `Array` | ✅ | Comma separated list of networks to fetch balances for |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchUserBalance({
fid: 123,
networks: []
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchUserBalance](/reference/fetch-user-balance)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# mintNft
Source: https://docs.neynar.com/nodejs-sdk/onchain-apis/mintNft
Mint NFT(s)
> **Group:** Onchain APIs
Use this when you need: **Mint NFT(s)**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.mintNft({
xWalletId: "example", // Wallet ID to use for transactions
mintNftRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------- | ---------------- | :------: | --------------------------------- |
| `xWalletId` | `string` | ✅ | Wallet ID to use for transactions |
| `mintNftRequest` | `MintNftRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.mintNft({
xWalletId: "example",
mintNftRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# registerAccountOnchain
Source: https://docs.neynar.com/nodejs-sdk/onchain-apis/registerAccountOnchain
Register Farcaster account onchain
> **Group:** Onchain APIs
Use this when you need: **Register Farcaster account onchain**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.registerAccountOnchain({
xWalletId: "example", // Wallet ID to use for transactions
registerUserOnChainReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------------- | ---------------------------- | :------: | --------------------------------- |
| `xWalletId` | `string` | ✅ | Wallet ID to use for transactions |
| `registerUserOnChainReqBody` | `RegisterUserOnChainReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.registerAccountOnchain({
xWalletId: "example",
registerUserOnChainReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [registerAccountOnchain](/reference/register-account-onchain)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# sendFungiblesToUsers
Source: https://docs.neynar.com/nodejs-sdk/onchain-apis/sendFungiblesToUsers
Send fungibles
> **Group:** Onchain APIs
Use this when you need: **Send fungibles**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.sendFungiblesToUsers({
xWalletId: "example", // Wallet ID to use for transactions
transactionSendFungiblesReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------------------- | --------------------------------- | :------: | --------------------------------- |
| `xWalletId` | `string` | ✅ | Wallet ID to use for transactions |
| `transactionSendFungiblesReqBody` | `TransactionSendFungiblesReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.sendFungiblesToUsers({
xWalletId: "example",
transactionSendFungiblesReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [sendFungiblesToUsers](/reference/send-fungibles-to-users)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# simulateNftMint
Source: https://docs.neynar.com/nodejs-sdk/onchain-apis/simulateNftMint
Simulate NFT mint calldata
> **Group:** Onchain APIs
Use this when you need: **Simulate NFT mint calldata**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.simulateNftMint({
recipients: "example", // JSON array of recipients (same structure as POST).
nftContractAddress: "example", // Ethereum address
network: "value", // Network to mint on.
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| -------------------- | ---------------------------- | :------: | -------------------------------------------------- |
| `recipients` | `string` | ✅ | JSON array of recipients (same structure as POST). |
| `nftContractAddress` | `string` | ✅ | Ethereum address |
| `network` | `SimulateNftMintNetworkEnum` | ✅ | Network to mint on. |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.simulateNftMint({
recipients: "example",
nftContractAddress: "example",
network: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# Node.js SDK Overview
Source: https://docs.neynar.com/nodejs-sdk/overview
Welcome to the Neynar Node.js SDK documentation!
The Neynar **Node.js SDK** provides a simple, typed, and intuitive way to integrate Farcaster features into your applications. It wraps all major Neynar APIs behind a clean developer-friendly interface so you can focus on building, not plumbing.
***
## What You Can Do With the SDK
* Fetch user, cast, feed, and reaction data
* Publish casts and embedded actions
* Verify frames, signatures, and messages
* Work with Warpcast users, signers, and channel data
* Access Neynar’s moderation, search, and feed endpoints
* Build bots, automations, and server-side workflows
***
## Key Features
* **Lightweight & Tree-shakable** – optimized for modern environments
* **Typed Endpoints** – full TypeScript support for responses & errors
* **Easy Authentication** – pass your API key once and start building
* **Battle-tested** – used internally across Neynar apps & services
***
## Links
* **GitHub Repository** – [neynarxyz/nodejs-sdk](https://github.com/neynarxyz/nodejs-sdk) - Browse the complete source code.
* **NPM Package** – [@neynar/nodejs-sdk](https://www.npmjs.com/package/@neynar/nodejs-sdk) - Install the SDK directly.
***
## Installation
Install using your preferred package manager:
```npm theme={"system"}
npm install @neynar/nodejs-sdk
```
```yarn theme={"system"}
yarn add @neynar/nodejs-sdk
```
```pnpm theme={"system"}
pnpm add @neynar/nodejs-sdk
```
# deleteReaction
Source: https://docs.neynar.com/nodejs-sdk/reaction-apis/deleteReaction
Delete reaction
> **Group:** Reaction APIs
Use this when you need: **Delete reaction**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.deleteReaction({
reactionReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------------- | ----------------- | :------: | ----------- |
| `reactionReqBody` | `ReactionReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.deleteReaction({
reactionReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [deleteReaction](/reference/delete-reaction)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchCastReactions
Source: https://docs.neynar.com/nodejs-sdk/reaction-apis/fetchCastReactions
Reactions for cast
> **Group:** Reaction APIs
Use this when you need: **Reactions for cast**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchCastReactions({
hash: "example", // required
types: [], // Customize which reaction types the request should search for. This is a comma-separated string that can include the following values: \'likes\' and \'recasts\'. By default api returns both. To select multiple types, use a comma-separated list of these values.
// viewerFid: 123,
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------- | ------------------------------------ | :------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hash` | `string` | ✅ | - |
| `types` | `Array` | ✅ | Customize which reaction types the request should search for. This is a comma-separated string that can include the following values: \'likes\' and \'recasts\'. By default api returns both. To select multiple types, use a comma-separated list of these values. |
| `viewerFid` | `number` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchCastReactions({
hash: "example",
types: []
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchCastReactions](/reference/fetch-cast-reactions)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchUserReactions
Source: https://docs.neynar.com/nodejs-sdk/reaction-apis/fetchUserReactions
Reactions for user
> **Group:** Reaction APIs
Use this when you need: **Reactions for user**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchUserReactions({
fid: 123, // The unique identifier of a farcaster user or app (unsigned integer)
type: "value", // Type of reaction to fetch (likes or recasts or all)
// viewerFid: 123,
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------- | ---------------------------- | :------: | ------------------------------------------------------------------- |
| `fid` | `number` | ✅ | The unique identifier of a farcaster user or app (unsigned integer) |
| `type` | `FetchUserReactionsTypeEnum` | ✅ | Type of reaction to fetch (likes or recasts or all) |
| `viewerFid` | `number` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchUserReactions({
fid: 123,
type: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchUserReactions](/reference/fetch-user-reactions)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# publishReaction
Source: https://docs.neynar.com/nodejs-sdk/reaction-apis/publishReaction
Post a reaction
> **Group:** Reaction APIs
Use this when you need: **Post a reaction**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.publishReaction({
reactionReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------------- | ----------------- | :------: | ----------- |
| `reactionReqBody` | `ReactionReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.publishReaction({
reactionReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [publishReaction](/reference/publish-reaction)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# createSigner
Source: https://docs.neynar.com/nodejs-sdk/signer-apis/createSigner
Create signer
> **Group:** Signer APIs
Use this when you need: **Create signer**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.createSigner({
});
console.log(res);
```
## Parameters
*This method has no parameters.*
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.createSigner({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [createSigner](/reference/create-signer)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchAuthorizationUrl
Source: https://docs.neynar.com/nodejs-sdk/signer-apis/fetchAuthorizationUrl
Fetch authorization url
> **Group:** Signer APIs
Use this when you need: **Fetch authorization url**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchAuthorizationUrl({
clientId: "example", // required
responseType: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| -------------- | --------------------------------------- | :------: | ----------- |
| `clientId` | `string` | ✅ | - |
| `responseType` | `FetchAuthorizationUrlResponseTypeEnum` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchAuthorizationUrl({
clientId: "example",
responseType: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchAuthorizationUrl](/reference/fetch-authorization-url)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchSigners
Source: https://docs.neynar.com/nodejs-sdk/signer-apis/fetchSigners
List signers
> **Group:** Signer APIs
Use this when you need: **List signers**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchSigners({
message: "example", // A Sign-In with Ethereum (SIWE) message that the user\'s Ethereum wallet signs. This message includes details such as the domain, address, statement, URI, nonce, and other relevant information following the EIP-4361 standard. It should be structured and URL-encoded.
signature: "example", // The digital signature produced by signing the provided SIWE message with the user\'s Ethereum private key. This signature is used to verify the authenticity of the message and the identity of the signer.
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------- | -------- | :------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `message` | `string` | ✅ | A Sign-In with Ethereum (SIWE) message that the user\'s Ethereum wallet signs. This message includes details such as the domain, address, statement, URI, nonce, and other relevant information following the EIP-4361 standard. It should be structured and URL-encoded. |
| `signature` | `string` | ✅ | The digital signature produced by signing the provided SIWE message with the user\'s Ethereum private key. This signature is used to verify the authenticity of the message and the identity of the signer. |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchSigners({
message: "example",
signature: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchSigners](/reference/fetch-signers)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# lookupDeveloperManagedSigner
Source: https://docs.neynar.com/nodejs-sdk/signer-apis/lookupDeveloperManagedSigner
Status by public key
> **Group:** Signer APIs
Use this when you need: **Status by public key**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.lookupDeveloperManagedSigner({
publicKey: "example", // Ed25519 public key
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------- | -------- | :------: | ------------------ |
| `publicKey` | `string` | ✅ | Ed25519 public key |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.lookupDeveloperManagedSigner({
publicKey: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [lookupDeveloperManagedSigner](/reference/lookup-developer-managed-signer)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# lookupSigner
Source: https://docs.neynar.com/nodejs-sdk/signer-apis/lookupSigner
Status
> **Group:** Signer APIs
Use this when you need: **Status**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.lookupSigner({
signerUuid: "example", // UUID of the signer. `signer_uuid` is paired with API key, can\'t use a `uuid` made with a different API key.
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------ | -------- | :------: | --------------------------------------------------------------------------------------------------------------------- |
| `signerUuid` | `string` | ✅ | UUID of the signer. \`signer\_uuid\` is paired with API key, can\'t use a \`uuid\` made with a different API key. |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.lookupSigner({
signerUuid: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [lookupSigner](/reference/lookup-signer)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# publishMessageToFarcaster
Source: https://docs.neynar.com/nodejs-sdk/signer-apis/publishMessageToFarcaster
Publish message
> **Group:** Signer APIs
Use this when you need: **Publish message**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.publishMessageToFarcaster({
body: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | ----------- |
| `body` | `object` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.publishMessageToFarcaster({
body: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [publishMessageToFarcaster](/reference/publish-message-to-farcaster)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# registerSignedKey
Source: https://docs.neynar.com/nodejs-sdk/signer-apis/registerSignedKey
Register Signed Key
> **Group:** Signer APIs
Use this when you need: **Register Signed Key**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.registerSignedKey({
registerSignerKeyReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| -------------------------- | -------------------------- | :------: | ----------- |
| `registerSignerKeyReqBody` | `RegisterSignerKeyReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.registerSignedKey({
registerSignerKeyReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [registerSignedKey](/reference/register-signed-key)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# registerSignedKeyForDeveloperManagedSigner
Source: https://docs.neynar.com/nodejs-sdk/signer-apis/registerSignedKeyForDeveloperManagedSigner
Register Signed Key
> **Group:** Signer APIs
Use this when you need: **Register Signed Key**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.registerSignedKeyForDeveloperManagedSigner({
registerDeveloperManagedSignedKeyReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------------------------------ | ------------------------------------------ | :------: | ----------- |
| `registerDeveloperManagedSignedKeyReqBody` | `RegisterDeveloperManagedSignedKeyReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.registerSignedKeyForDeveloperManagedSigner({
registerDeveloperManagedSignedKeyReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [registerSignedKeyForDeveloperManagedSigner](/reference/register-signed-key-for-developer-managed-signer)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# buyStorage
Source: https://docs.neynar.com/nodejs-sdk/storage-apis/buyStorage
Buy storage
> **Group:** Storage APIs
Use this when you need: **Buy storage**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.buyStorage({
xWalletId: "example", // Wallet ID to use for transactions
buyStorageReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------- | ------------------- | :------: | --------------------------------- |
| `xWalletId` | `string` | ✅ | Wallet ID to use for transactions |
| `buyStorageReqBody` | `BuyStorageReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.buyStorage({
xWalletId: "example",
buyStorageReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [buyStorage](/reference/buy-storage)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# lookupUserStorageAllocations
Source: https://docs.neynar.com/nodejs-sdk/storage-apis/lookupUserStorageAllocations
Allocation of user
> **Group:** Storage APIs
Use this when you need: **Allocation of user**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.lookupUserStorageAllocations({
fid: 123, // The unique identifier of a farcaster user or app (unsigned integer)
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | ------------------------------------------------------------------- |
| `fid` | `number` | ✅ | The unique identifier of a farcaster user or app (unsigned integer) |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.lookupUserStorageAllocations({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [lookupUserStorageAllocations](/reference/lookup-user-storage-allocations)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# lookupUserStorageUsage
Source: https://docs.neynar.com/nodejs-sdk/storage-apis/lookupUserStorageUsage
Usage of user
> **Group:** Storage APIs
Use this when you need: **Usage of user**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.lookupUserStorageUsage({
fid: 123, // The unique identifier of a farcaster user or app (unsigned integer)
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | ------------------------------------------------------------------- |
| `fid` | `number` | ✅ | The unique identifier of a farcaster user or app (unsigned integer) |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.lookupUserStorageUsage({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [lookupUserStorageUsage](/reference/lookup-user-storage-usage)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# associateDeployment
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/associateDeployment
Set account association
> **Group:** Studio APIs
Use this when you need: **Set account association**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.associateDeployment({
associateDeploymentRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------------- | ---------------------------- | :------: | ----------- |
| `associateDeploymentRequest` | `AssociateDeploymentRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.associateDeployment({
associateDeploymentRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [associateDeployment](/reference/associate-deployment)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# build
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/build
Build generated app with automatic error fixing
> **Group:** Studio APIs
Use this when you need: **Build generated app with automatic error fixing**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.build({
buildRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| -------------- | -------------- | :------: | ----------- |
| `buildRequest` | `BuildRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.build({
buildRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# createDeployment
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/createDeployment
Create a miniapp generator deployment
> **Group:** Studio APIs
Use this when you need: **Create a miniapp generator deployment**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.createDeployment({
createDeploymentRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------------- | ------------------------- | :------: | ----------- |
| `createDeploymentRequest` | `CreateDeploymentRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.createDeployment({
createDeploymentRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [createDeployment](/reference/create-deployment)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# deleteDeployment
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/deleteDeployment
Delete deployment(s)
> **Group:** Studio APIs
Use this when you need: **Delete deployment(s)**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.deleteDeployment({
deleteDeploymentRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------------- | ------------------------- | :------: | ----------- |
| `deleteDeploymentRequest` | `DeleteDeploymentRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.deleteDeployment({
deleteDeploymentRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [deleteDeployment](/reference/delete-deployment)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# deleteRows
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/deleteRows
Delete rows from table
> **Group:** Studio APIs
Use this when you need: **Delete rows from table**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.deleteRows({
tableName: "example", // required
deleteRowsRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------- | ------------------- | :------: | ----------- |
| `tableName` | `string` | ✅ | - |
| `deleteRowsRequest` | `DeleteRowsRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.deleteRows({
tableName: "example",
deleteRowsRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# deleteSecrets
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/deleteSecrets
Delete deployment secrets
> **Group:** Studio APIs
Use this when you need: **Delete deployment secrets**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.deleteSecrets({
deleteSecretsRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------- | ---------------------- | :------: | ----------- |
| `deleteSecretsRequest` | `DeleteSecretsRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.deleteSecrets({
deleteSecretsRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# deployToVercel
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/deployToVercel
Deploy miniapp to Vercel
> **Group:** Studio APIs
Use this when you need: **Deploy miniapp to Vercel**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.deployToVercel({
deployToVercelRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------------------- | ----------------------- | :------: | ----------- |
| `deployToVercelRequest` | `DeployToVercelRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.deployToVercel({
deployToVercelRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# executeSql
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/executeSql
Execute raw SQL query (admin only)
> **Group:** Studio APIs
Use this when you need: **Execute raw SQL query (admin only)**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.executeSql({
executeSqlRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------- | ------------------- | :------: | ----------- |
| `executeSqlRequest` | `ExecuteSqlRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.executeSql({
executeSqlRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# getAccountAssociation
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/getAccountAssociation
Get account association of a miniapp
> **Group:** Studio APIs
Use this when you need: **Get account association of a miniapp**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.getAccountAssociation({
// deploymentId: "example",
// namespace: "example",
// name: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| -------------- | -------- | :------: | ----------- |
| `deploymentId` | `string` | ❌ | - |
| `namespace` | `string` | ❌ | - |
| `name` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.getAccountAssociation({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# getConversationMessages
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/getConversationMessages
Get messages in a conversation
> **Group:** Studio APIs
Use this when you need: **Get messages in a conversation**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.getConversationMessages({
conversationId: "example", // Conversation ID
// deploymentId: "example",
// fid: 123,
// name: "example",
// namespace: "example",
// includeDeleted: true,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| ---------------- | --------- | :------: | --------------- | - |
| `conversationId` | `string` | ✅ | Conversation ID | |
| `deploymentId` | `string` | ❌ | - | |
| `fid` | \`number | null\` | ❌ | - |
| `name` | `string` | ❌ | - | |
| `namespace` | `string` | ❌ | - | |
| `includeDeleted` | \`boolean | null\` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.getConversationMessages({
conversationId: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# getDeployment
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/getDeployment
Get deployment info
> **Group:** Studio APIs
Use this when you need: **Get deployment info**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.getDeployment({
// deploymentId: "example",
// fid: 123,
// name: "example",
// namespace: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| -------------- | -------- | :------: | ----------- | - |
| `deploymentId` | `string` | ❌ | - | |
| `fid` | \`number | null\` | ❌ | - |
| `name` | `string` | ❌ | - | |
| `namespace` | `string` | ❌ | - | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.getDeployment({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [getDeployment](/reference/get-deployment)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# getDeploymentFile
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/getDeploymentFile
Get deployment file contents
> **Group:** Studio APIs
Use this when you need: **Get deployment file contents**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.getDeploymentFile({
filePath: "example", // File path relative to gen/
// deploymentId: "example",
// fid: 123,
// name: "example",
// namespace: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| -------------- | -------- | :------: | -------------------------- | - |
| `filePath` | `string` | ✅ | File path relative to gen/ | |
| `deploymentId` | `string` | ❌ | - | |
| `fid` | \`number | null\` | ❌ | - |
| `name` | `string` | ❌ | - | |
| `namespace` | `string` | ❌ | - | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.getDeploymentFile({
filePath: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# getDeploymentLogs
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/getDeploymentLogs
Get deployment logs
> **Group:** Studio APIs
Use this when you need: **Get deployment logs**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.getDeploymentLogs({
name: "example", // Kubernetes deployment name
// fid: 123,
// namespace: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| ----------- | -------- | :------: | -------------------------- | - |
| `name` | `string` | ✅ | Kubernetes deployment name | |
| `fid` | \`number | null\` | ❌ | - |
| `namespace` | `string` | ❌ | - | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.getDeploymentLogs({
name: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# getDevStatus
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/getDevStatus
Get dev status of a miniapp
> **Group:** Studio APIs
Use this when you need: **Get dev status of a miniapp**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.getDevStatus({
// deploymentId: "example",
// namespace: "example",
// name: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| -------------- | -------- | :------: | ----------- |
| `deploymentId` | `string` | ❌ | - |
| `namespace` | `string` | ❌ | - |
| `name` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.getDevStatus({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# getTableSchema
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/getTableSchema
Get table schema
> **Group:** Studio APIs
Use this when you need: **Get table schema**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.getTableSchema({
tableName: "example", // required
deploymentId: "example", // Deployment ID (UUID)
// fid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| -------------- | -------- | :------: | -------------------- | - |
| `tableName` | `string` | ✅ | - | |
| `deploymentId` | `string` | ✅ | Deployment ID (UUID) | |
| `fid` | \`number | null\` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.getTableSchema({
tableName: "example",
deploymentId: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# insertRows
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/insertRows
Insert rows into table
> **Group:** Studio APIs
Use this when you need: **Insert rows into table**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.insertRows({
tableName: "example", // required
insertRowsRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------- | ------------------- | :------: | ----------- |
| `tableName` | `string` | ✅ | - |
| `insertRowsRequest` | `InsertRowsRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.insertRows({
tableName: "example",
insertRowsRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# listConversations
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/listConversations
List conversations for a deployment
> **Group:** Studio APIs
Use this when you need: **List conversations for a deployment**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.listConversations({
// deploymentId: "example",
// fid: 123,
// name: "example",
// includeDeleted: true,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| ---------------- | --------- | :------: | ----------- | - |
| `deploymentId` | `string` | ❌ | - | |
| `fid` | \`number | null\` | ❌ | - |
| `name` | `string` | ❌ | - | |
| `includeDeleted` | \`boolean | null\` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.listConversations({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# listDeploymentFiles
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/listDeploymentFiles
List deployment files
> **Group:** Studio APIs
Use this when you need: **List deployment files**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.listDeploymentFiles({
// deploymentId: "example",
// fid: 123,
// name: "example",
// namespace: "example",
// directory: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| -------------- | -------- | :------: | ----------- | - |
| `deploymentId` | `string` | ❌ | - | |
| `fid` | \`number | null\` | ❌ | - |
| `name` | `string` | ❌ | - | |
| `namespace` | `string` | ❌ | - | |
| `directory` | `string` | ❌ | - | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.listDeploymentFiles({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# listDeployments
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/listDeployments
List deployments
> **Group:** Studio APIs
Use this when you need: **List deployments**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.listDeployments({
// fid: 123,
// limit: 123,
// offset: 123,
// includeDeleted: true,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| ---------------- | --------- | :------: | ----------- | - |
| `fid` | \`number | null\` | ❌ | - |
| `limit` | `number` | ❌ | - | |
| `offset` | \`number | null\` | ❌ | - |
| `includeDeleted` | \`boolean | null\` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.listDeployments({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [listDeployments](/reference/list-deployments)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# listSecrets
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/listSecrets
List deployment secrets
> **Group:** Studio APIs
Use this when you need: **List deployment secrets**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.listSecrets({
deploymentId: "example", // Deployment ID to list secrets for
// key: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| -------------- | -------- | :------: | --------------------------------- |
| `deploymentId` | `string` | ✅ | Deployment ID to list secrets for |
| `key` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.listSecrets({
deploymentId: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# listTables
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/listTables
List all tables in deployment database
> **Group:** Studio APIs
Use this when you need: **List all tables in deployment database**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.listTables({
deploymentId: "example", // Deployment ID (UUID)
// fid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| -------------- | -------- | :------: | -------------------- | - |
| `deploymentId` | `string` | ✅ | Deployment ID (UUID) | |
| `fid` | \`number | null\` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.listTables({
deploymentId: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# promptDeployment
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/promptDeployment
# promptDeployment
Prompt a deployment
> **Group:** Studio APIs
Use this when you need: **Prompt a deployment**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.promptDeployment({
promptDeploymentRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------------- | ------------------------- | :------: | ----------- |
| `promptDeploymentRequest` | `PromptDeploymentRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.promptDeployment({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# promptDeploymentStream
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/promptDeploymentStream
Prompt a deployment with streaming response
> **Group:** Studio APIs
Use this when you need: **Prompt a deployment with streaming response**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.promptDeploymentStream({
promptDeploymentStreamRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------------------- | ------------------------------- | :------: | ----------- |
| `promptDeploymentStreamRequest` | `PromptDeploymentStreamRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.promptDeploymentStream({
promptDeploymentStreamRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [promptDeploymentStream](/reference/prompt-deployment-stream)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# queryTable
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/queryTable
Query table data
> **Group:** Studio APIs
Use this when you need: **Query table data**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.queryTable({
queryTableRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------- | ------------------- | :------: | ----------- |
| `queryTableRequest` | `QueryTableRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.queryTable({
queryTableRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# startApp
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/startApp
Start generated miniapp
> **Group:** Studio APIs
Use this when you need: **Start generated miniapp**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.startApp({
startAppRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------------- | ----------------- | :------: | ----------- |
| `startAppRequest` | `StartAppRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.startApp({
startAppRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# stopApp
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/stopApp
Stop generated miniapp
> **Group:** Studio APIs
Use this when you need: **Stop generated miniapp**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.stopApp({
startAppRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------------- | ----------------- | :------: | ----------- |
| `startAppRequest` | `StartAppRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.stopApp({
startAppRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# updateRows
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/updateRows
Update rows in table
> **Group:** Studio APIs
Use this when you need: **Update rows in table**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.updateRows({
tableName: "example", // required
updateRowsRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------- | ------------------- | :------: | ----------- |
| `tableName` | `string` | ✅ | - |
| `updateRowsRequest` | `UpdateRowsRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.updateRows({
tableName: "example",
updateRowsRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# uploadImage
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/uploadImage
Upload image to deployment
> **Group:** Studio APIs
Use this when you need: **Upload image to deployment**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.uploadImage({
});
console.log(res);
```
## Parameters
*This method has no parameters.*
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.uploadImage({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# uploadImageUrl
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/uploadImageUrl
Upload image from URL to deployment
> **Group:** Studio APIs
Use this when you need: **Upload image from URL to deployment**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.uploadImageUrl({
uploadImageUrlRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------------------- | ----------------------- | :------: | ----------- |
| `uploadImageUrlRequest` | `UploadImageUrlRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.uploadImageUrl({
uploadImageUrlRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# upsertSecrets
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/upsertSecrets
Upsert deployment secrets
> **Group:** Studio APIs
Use this when you need: **Upsert deployment secrets**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.upsertSecrets({
upsertSecretsRequest: "value", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------- | ---------------------- | :------: | ----------- |
| `upsertSecretsRequest` | `UpsertSecretsRequest` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.upsertSecrets({
upsertSecretsRequest: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# vercelDeploymentLogs
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/vercelDeploymentLogs
Get Vercel deployment build logs
> **Group:** Studio APIs
Use this when you need: **Get Vercel deployment build logs**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.vercelDeploymentLogs({
// deploymentId: "example",
// fid: 123,
// namespace: "example",
// name: "example",
// limit: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| -------------- | -------- | :------: | ----------- | - |
| `deploymentId` | `string` | ❌ | - | |
| `fid` | \`number | null\` | ❌ | - |
| `namespace` | `string` | ❌ | - | |
| `name` | `string` | ❌ | - | |
| `limit` | `number` | ❌ | - | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.vercelDeploymentLogs({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# vercelDeploymentStatus
Source: https://docs.neynar.com/nodejs-sdk/studio-apis/vercelDeploymentStatus
Get Vercel deployment status
> **Group:** Studio APIs
Use this when you need: **Get Vercel deployment status**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.vercelDeploymentStatus({
// deploymentId: "example",
// fid: 123,
// namespace: "example",
// name: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| -------------- | -------- | :------: | ----------- | - |
| `deploymentId` | `string` | ❌ | - | |
| `fid` | \`number | null\` | ❌ | - |
| `namespace` | `string` | ❌ | - | |
| `name` | `string` | ❌ | - | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.vercelDeploymentStatus({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchSubscribedToForFid
Source: https://docs.neynar.com/nodejs-sdk/subscriber-apis/fetchSubscribedToForFid
Subscribed to
> **Group:** Subscriber APIs
Use this when you need: **Subscribed to**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchSubscribedToForFid({
fid: 123, // The unique identifier of a farcaster user or app (unsigned integer)
subscriptionProvider: "value", // The provider of the subscription.
// viewerFid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------- | ------------------------------------------------- | :------: | ------------------------------------------------------------------- |
| `fid` | `number` | ✅ | The unique identifier of a farcaster user or app (unsigned integer) |
| `subscriptionProvider` | `FetchSubscribedToForFidSubscriptionProviderEnum` | ✅ | The provider of the subscription. |
| `viewerFid` | `number` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchSubscribedToForFid({
fid: 123,
subscriptionProvider: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchSubscribedToForFid](/reference/fetch-subscribed-to-for-fid)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchSubscribersForFid
Source: https://docs.neynar.com/nodejs-sdk/subscriber-apis/fetchSubscribersForFid
Subscribers of a user
> **Group:** Subscriber APIs
Use this when you need: **Subscribers of a user**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchSubscribersForFid({
fid: 123, // The unique identifier of a farcaster user or app (unsigned integer)
subscriptionProvider: "value", // The provider of the subscription.
// viewerFid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------- | ------------------------------------------------ | :------: | ------------------------------------------------------------------- |
| `fid` | `number` | ✅ | The unique identifier of a farcaster user or app (unsigned integer) |
| `subscriptionProvider` | `FetchSubscribersForFidSubscriptionProviderEnum` | ✅ | The provider of the subscription. |
| `viewerFid` | `number` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchSubscribersForFid({
fid: 123,
subscriptionProvider: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchSubscribersForFid](/reference/fetch-subscribers-for-fid)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchSubscriptionCheck
Source: https://docs.neynar.com/nodejs-sdk/subscriber-apis/fetchSubscriptionCheck
Hypersub subscription check
> **Group:** Subscriber APIs
Use this when you need: **Hypersub subscription check**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchSubscriptionCheck({
addresses: "example", // Comma separated list of Ethereum addresses, up to 350 at a time
contractAddress: "example", // Ethereum address of the STP contract
chainId: "example", // Chain ID of the STP contract
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------------- | -------- | :------: | --------------------------------------------------------------- |
| `addresses` | `string` | ✅ | Comma separated list of Ethereum addresses, up to 350 at a time |
| `contractAddress` | `string` | ✅ | Ethereum address of the STP contract |
| `chainId` | `string` | ✅ | Chain ID of the STP contract |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchSubscriptionCheck({
addresses: "example",
contractAddress: "example",
chainId: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchSubscriptionCheck](/reference/fetch-subscription-check)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchSubscriptionsForFid
Source: https://docs.neynar.com/nodejs-sdk/subscriber-apis/fetchSubscriptionsForFid
Subscriptions created by FID
> **Group:** Subscriber APIs
Use this when you need: **Subscriptions created by FID**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchSubscriptionsForFid({
fid: 123, // The unique identifier of a farcaster user or app (unsigned integer)
subscriptionProvider: "value", // The provider of the subscription.
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------- | -------------------------------------------------- | :------: | ------------------------------------------------------------------- |
| `fid` | `number` | ✅ | The unique identifier of a farcaster user or app (unsigned integer) |
| `subscriptionProvider` | `FetchSubscriptionsForFidSubscriptionProviderEnum` | ✅ | The provider of the subscription. |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchSubscriptionsForFid({
fid: 123,
subscriptionProvider: "value"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchSubscriptionsForFid](/reference/fetch-subscriptions-for-fid)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# listTrendingTopics
Source: https://docs.neynar.com/nodejs-sdk/topic-apis/listTrendingTopics
Fetch trending topics
> **Group:** Topic APIs
Use this when you need: **Fetch trending topics**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.listTrendingTopics({
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | ----------- |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.listTrendingTopics({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# deleteVerification
Source: https://docs.neynar.com/nodejs-sdk/user-apis/deleteVerification
Delete verification
> **Group:** User APIs
Use this when you need: **Delete verification**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.deleteVerification({
removeVerificationReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------------- | --------------------------- | :------: | ----------- |
| `removeVerificationReqBody` | `RemoveVerificationReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.deleteVerification({
removeVerificationReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [deleteVerification](/reference/delete-verification)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchBulkUsers
Source: https://docs.neynar.com/nodejs-sdk/user-apis/fetchBulkUsers
By FIDs
> **Group:** User APIs
Use this when you need: **By FIDs**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchBulkUsers({
fids: "example", // Comma separated list of FIDs, up to 100 at a time
// xNeynarExperimental: true,
// viewerFid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------- | :------: | ------------------------------------------------- |
| `fids` | `string` | ✅ | Comma separated list of FIDs, up to 100 at a time |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchBulkUsers({
fids: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchBulkUsers](/reference/fetch-bulk-users)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchBulkUsersByEthOrSolAddress
Source: https://docs.neynar.com/nodejs-sdk/user-apis/fetchBulkUsersByEthOrSolAddress
By Eth or Sol addresses
> **Group:** User APIs
Use this when you need: **By Eth or Sol addresses**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchBulkUsersByEthOrSolAddress({
addresses: "example", // Comma separated list of Ethereum addresses, up to 350 at a time
// xNeynarExperimental: true,
// addressTypes: [],
// viewerFid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | -------------------------------------------------------- | :------: | --------------------------------------------------------------- |
| `addresses` | `string` | ✅ | Comma separated list of Ethereum addresses, up to 350 at a time |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `addressTypes` | `Array` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchBulkUsersByEthOrSolAddress({
addresses: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchBulkUsersByEthOrSolAddress](/reference/fetch-bulk-users-by-eth-or-sol-address)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchUsersByLocation
Source: https://docs.neynar.com/nodejs-sdk/user-apis/fetchUsersByLocation
By location
> **Group:** User APIs
Use this when you need: **By location**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchUsersByLocation({
latitude: 123, // Latitude of the location
longitude: 123, // Longitude of the location
// xNeynarExperimental: true,
// viewerFid: 123,
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| --------------------- | --------- | :------: | ----------- | ------------------------- |
| `latitude` | \`number | null\` | ✅ | Latitude of the location |
| `longitude` | \`number | null\` | ✅ | Longitude of the location |
| `xNeynarExperimental` | `boolean` | ❌ | - | |
| `viewerFid` | `number` | ❌ | - | |
| `limit` | `number` | ❌ | - | |
| `cursor` | `string` | ❌ | - | |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchUsersByLocation({
latitude: 123,
longitude: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchUsersByLocation](/reference/fetch-users-by-location)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchVerifications
Source: https://docs.neynar.com/nodejs-sdk/user-apis/fetchVerifications
Fetch verifications
> **Group:** User APIs
Use this when you need: **Fetch verifications**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchVerifications({
fid: 123, // FID of the user whose verifications to fetch
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description | |
| --------- | -------- | :------: | ----------- | -------------------------------------------- |
| `fid` | \`number | null\` | ✅ | FID of the user whose verifications to fetch |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchVerifications({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchVerifications](/reference/fetch-verifications)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# followUser
Source: https://docs.neynar.com/nodejs-sdk/user-apis/followUser
Follow user
> **Group:** User APIs
Use this when you need: **Follow user**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.followUser({
followReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------- | --------------- | :------: | ----------- |
| `followReqBody` | `FollowReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.followUser({
followReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [followUser](/reference/follow-user)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# getFreshAccountFID
Source: https://docs.neynar.com/nodejs-sdk/user-apis/getFreshAccountFID
Fetch fresh FID
> **Group:** User APIs
Use this when you need: **Fetch fresh FID**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.getFreshAccountFID({
xWalletId: "example", // Wallet ID to use for transactions
// xNeynarExperimental: true,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------- | :------: | --------------------------------- |
| `xWalletId` | `string` | ✅ | Wallet ID to use for transactions |
| `xNeynarExperimental` | `boolean` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.getFreshAccountFID({
xWalletId: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# getUserBestFriends
Source: https://docs.neynar.com/nodejs-sdk/user-apis/getUserBestFriends
Best friends
> **Group:** User APIs
Use this when you need: **Best friends**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.getUserBestFriends({
fid: 123, // The FID of the user
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------- | -------- | :------: | ------------------- |
| `fid` | `number` | ✅ | The FID of the user |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.getUserBestFriends({
fid: 123
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# lookupUserByCustodyAddress
Source: https://docs.neynar.com/nodejs-sdk/user-apis/lookupUserByCustodyAddress
By custody-address
> **Group:** User APIs
Use this when you need: **By custody-address**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.lookupUserByCustodyAddress({
custodyAddress: "example", // Custody Address associated with mnemonic
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------- | -------- | :------: | ---------------------------------------- |
| `custodyAddress` | `string` | ✅ | Custody Address associated with mnemonic |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.lookupUserByCustodyAddress({
custodyAddress: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [lookupUserByCustodyAddress](/reference/lookup-user-by-custody-address)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# lookupUserByUsername
Source: https://docs.neynar.com/nodejs-sdk/user-apis/lookupUserByUsername
By username
> **Group:** User APIs
Use this when you need: **By username**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.lookupUserByUsername({
username: "example", // Username of the user to fetch
// xNeynarExperimental: true,
// viewerFid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------- | :------: | ----------------------------- |
| `username` | `string` | ✅ | Username of the user to fetch |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.lookupUserByUsername({
username: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [lookupUserByUsername](/reference/lookup-user-by-username)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# lookupUsersByXUsername
Source: https://docs.neynar.com/nodejs-sdk/user-apis/lookupUsersByXUsername
By X username
> **Group:** User APIs
Use this when you need: **By X username**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.lookupUsersByXUsername({
xUsername: "example", // X (Twitter) username to search for, without the @ symbol
// xNeynarExperimental: true,
// viewerFid: 123,
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------- | :------: | -------------------------------------------------------- |
| `xUsername` | `string` | ✅ | X (Twitter) username to search for, without the @ symbol |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.lookupUsersByXUsername({
xUsername: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [lookupUsersByXUsername](/reference/lookup-users-by-x-username)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# publishVerification
Source: https://docs.neynar.com/nodejs-sdk/user-apis/publishVerification
Add verification
> **Group:** User APIs
Use this when you need: **Add verification**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.publishVerification({
addVerificationReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------------ | ------------------------ | :------: | ----------- |
| `addVerificationReqBody` | `AddVerificationReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.publishVerification({
addVerificationReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [publishVerification](/reference/publish-verification)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# registerAccount
Source: https://docs.neynar.com/nodejs-sdk/user-apis/registerAccount
Register new account
> **Group:** User APIs
Use this when you need: **Register new account**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.registerAccount({
xWalletId: "example", // Wallet ID to use for transactions
registerUserReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------------------- | :------: | --------------------------------- |
| `xWalletId` | `string` | ✅ | Wallet ID to use for transactions |
| `registerUserReqBody` | `RegisterUserReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.registerAccount({
xWalletId: "example",
registerUserReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [registerAccount](/reference/register-account)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# searchUser
Source: https://docs.neynar.com/nodejs-sdk/user-apis/searchUser
Search for Usernames
> **Group:** User APIs
Use this when you need: **Search for Usernames**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.searchUser({
q: "example", // required
// xNeynarExperimental: true,
// viewerFid: 123,
// limit: 123,
// cursor: "example",
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------- | :------: | ----------- |
| `q` | `string` | ✅ | - |
| `xNeynarExperimental` | `boolean` | ❌ | - |
| `viewerFid` | `number` | ❌ | - |
| `limit` | `number` | ❌ | - |
| `cursor` | `string` | ❌ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.searchUser({
q: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [searchUser](/reference/search-user)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# unfollowUser
Source: https://docs.neynar.com/nodejs-sdk/user-apis/unfollowUser
Unfollow user
> **Group:** User APIs
Use this when you need: **Unfollow user**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.unfollowUser({
followReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------- | --------------- | :------: | ----------- |
| `followReqBody` | `FollowReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.unfollowUser({
followReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [unfollowUser](/reference/unfollow-user)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# updateUser
Source: https://docs.neynar.com/nodejs-sdk/user-apis/updateUser
Update user profile
> **Group:** User APIs
Use this when you need: **Update user profile**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.updateUser({
updateUserReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------- | ------------------- | :------: | ----------- |
| `updateUserReqBody` | `UpdateUserReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.updateUser({
updateUserReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [updateUser](/reference/update-user)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# deleteWebhook
Source: https://docs.neynar.com/nodejs-sdk/webhook-apis/deleteWebhook
Delete a webhook
> **Group:** Webhook APIs
Use this when you need: **Delete a webhook**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.deleteWebhook({
webhookDeleteReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------------- | ---------------------- | :------: | ----------- |
| `webhookDeleteReqBody` | `WebhookDeleteReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.deleteWebhook({
webhookDeleteReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [deleteWebhook](/reference/delete-webhook)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# fetchWebhooks
Source: https://docs.neynar.com/nodejs-sdk/webhook-apis/fetchWebhooks
Associated webhooks of user
> **Group:** Webhook APIs
Use this when you need: **Associated webhooks of user**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.fetchWebhooks({
});
console.log(res);
```
## Parameters
*This method has no parameters.*
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.fetchWebhooks({ /* required fields */ });
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [fetchWebhooks](/reference/fetch-webhooks)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# lookupWebhook
Source: https://docs.neynar.com/nodejs-sdk/webhook-apis/lookupWebhook
Fetch a webhook
> **Group:** Webhook APIs
Use this when you need: **Fetch a webhook**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.lookupWebhook({
webhookId: "example", // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------- | -------- | :------: | ----------- |
| `webhookId` | `string` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.lookupWebhook({
webhookId: "example"
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [lookupWebhook](/reference/lookup-webhook)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# publishWebhook
Source: https://docs.neynar.com/nodejs-sdk/webhook-apis/publishWebhook
Create a webhook
> **Group:** Webhook APIs
Use this when you need: **Create a webhook**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.publishWebhook({
webhookPostReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| -------------------- | -------------------- | :------: | ----------- |
| `webhookPostReqBody` | `WebhookPostReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.publishWebhook({
webhookPostReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [publishWebhook](/reference/publish-webhook)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# updateWebhook
Source: https://docs.neynar.com/nodejs-sdk/webhook-apis/updateWebhook
Update a webhook
> **Group:** Webhook APIs
Use this when you need: **Update a webhook**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.updateWebhook({
webhookPutReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------------- | ------------------- | :------: | ----------- |
| `webhookPutReqBody` | `WebhookPutReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.updateWebhook({
webhookPutReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [updateWebhook](/reference/update-webhook)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# updateWebhookActiveStatus
Source: https://docs.neynar.com/nodejs-sdk/webhook-apis/updateWebhookActiveStatus
Update webhook status
> **Group:** Webhook APIs
Use this when you need: **Update webhook status**.
## Usage
```ts theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient(
new Configuration({ apiKey: "YOUR_NEYNAR_API_KEY" })
);
const res = await client.updateWebhookActiveStatus({
webhookPatchReqBody: { /* request body */ }, // required
});
console.log(res);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------------------- | :------: | ----------- |
| `webhookPatchReqBody` | `WebhookPatchReqBody` | ✅ | - |
## Response
Returns a `RequestArgs` object.
## Error Handling
```ts theme={"system"}
try {
const res = await client.updateWebhookActiveStatus({
webhookPatchReqBody: { /* request body */ }
});
// use res
} catch (err) {
// Axios/HTTP errors, invalid params, auth issues, etc.
console.error(err);
}
```
## Related API Reference
📖 **API Reference:** [updateWebhookActiveStatus](/reference/update-webhook-active-status)
For detailed information about the HTTP endpoint, request/response schemas, and additional examples, see the complete API documentation.
## Tips
* Provide a **viewerFid** when supported to respect mutes/blocks and include `viewer_context`.
* Keep requests scoped (e.g., use `limit`) to improve latency.
* Cache results where sensible.
# Generate event
Source: https://docs.neynar.com/reference/app-host-get-event
get /v2/farcaster/app_host/user/event/
Returns event object for app host events. Used if the app host intends to sign the event message instead of using Neynar-hosted signers.
## Node.js SDK
🔗 **SDK Method:** [appHostGetEvent](/nodejs-sdk/app-host-apis/appHostGetEvent)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Enabled notifications
Source: https://docs.neynar.com/reference/app-host-get-user-state
get /v2/farcaster/app_host/user/state/
Returns the current notification state for a specific user across all mini app domains in this app host. Shows which domains have notifications enabled.
## Node.js SDK
🔗 **SDK Method:** [appHostGetUserState](/nodejs-sdk/app-host-apis/appHostGetUserState)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Send event
Source: https://docs.neynar.com/reference/app-host-post-event
post /v2/farcaster/app_host/user/event/
Post an app_host event to the domain's webhook. Events such as enabling or disabling notifications for a user. Provide either a signed message or the signer UUID of an authorized neynar-hosted signers.
## Node.js SDK
🔗 **SDK Method:** [appHostPostEvent](/nodejs-sdk/app-host-apis/appHostPostEvent)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Set account association
Source: https://docs.neynar.com/reference/associate-deployment
post /v2/studio/deployment/account-association
Associates a generated miniapp with a Farcaster account using a JFS-signed domain association. Requires API key authentication. Note: Studio CU is tracked based on LLM token usage, not per API call.
The Miniapp Studio API is an allowlisted API and not publicly available. [Contact the Neynar team](https://neynar.com/slack) for more information.
## Node.js SDK
🔗 **SDK Method:** [associateDeployment](/nodejs-sdk/studio-apis/associateDeployment)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Best friends
Source: https://docs.neynar.com/reference/best-friends
get /v2/farcaster/user/best_friends/
Returns the best friends of a user ranked by mutual affinity score based on interactions with each other.
# Buy storage
Source: https://docs.neynar.com/reference/buy-storage
post /v2/farcaster/storage/buy/
This api will help you rent units of storage for an year for a specific FID.
A storage unit lets you store 5000 casts, 2500 reactions and 2500 links. Requires x-wallet-id header.
## Node.js SDK
🔗 **SDK Method:** [buyStorage](/nodejs-sdk/storage-apis/buyStorage)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
***
### Related documentation:
* [Managing Onchain Wallets](/docs/managing-onchain-wallets) - Wallet setup guide
* [Storage documentation](/docs/getting-storage-units-allocation-of-farcaster-user) - Understanding storage
## Understanding Wallet ID for Storage Purchases
This endpoint allows you to purchase storage units for a specific FID (Farcaster ID). Storage units enable users to store more casts, reactions, and links on the Farcaster protocol.
### Wallet ID (REQUIRED)
The [`x-wallet-id` header](/docs/managing-onchain-wallets) is **required** for this endpoint. You must provide a wallet\_id to pay for the onchain storage purchase transaction.
**New to Wallet IDs?** See [Managing Onchain Wallets](/docs/managing-onchain-wallets) to create your app wallet in the developer portal and obtain your `x-wallet-id` value.
## Code Examples
```javascript Node.js theme={"system"}
const response = await fetch('https://api.neynar.com/v2/farcaster/storage/buy', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_NEYNAR_API_KEY',
'x-wallet-id': 'your-wallet-id', // REQUIRED
'Content-Type': 'application/json'
},
body: JSON.stringify({
fid: 12345,
units: 1
})
});
const data = await response.json();
console.log('Storage purchased:', data);
```
```bash cURL theme={"system"}
curl -X POST 'https://api.neynar.com/v2/farcaster/storage/buy' \
-H 'x-api-key: YOUR_NEYNAR_API_KEY' \
-H 'x-wallet-id: your-wallet-id' \
-H 'Content-Type: application/json' \
-d '{
"fid": 12345,
"units": 1
}'
```
```python Python theme={"system"}
import requests
headers = {
'x-api-key': 'YOUR_NEYNAR_API_KEY',
'x-wallet-id': 'your-wallet-id', # REQUIRED
'Content-Type': 'application/json'
}
response = requests.post(
'https://api.neynar.com/v2/farcaster/storage/buy',
headers=headers,
json={
'fid': 12345,
'units': 1
}
)
data = response.json()
print('Storage purchased:', data)
```
## What You're Paying For
When you buy storage with a wallet\_id:
* **Transaction Execution:** Neynar handles gas estimation, retries, and monitoring
## Storage Units Explained
Each storage unit you purchase provides:
| Resource | Capacity per Unit |
| ------------- | --------------------------- |
| **Casts** | 5,000 casts |
| **Reactions** | 2,500 reactions |
| **Links** | 2,500 links (follows, etc.) |
* **Duration:** 1 year from purchase
* **Network:** Optimism mainnet
* **Renewal:** Must be renewed annually
**Planning storage needs?** Most active users need 1-2 units per year. Power users (>50 casts/day) may need 3-5 units.
## Error Handling
### Error: Missing Wallet ID
```json theme={"system"}
{
"code": "RequiredField",
"message": "x-wallet-id header is required"
}
```
**Solution:** Add the `x-wallet-id` header. See [Managing Onchain Wallets](/docs/managing-onchain-wallets) for setup instructions.
### Error: Invalid Wallet ID
```json theme={"system"}
{
"code": "InvalidWalletId",
"message": "The provided wallet_id is invalid or not found."
}
```
**Solution:** Verify your wallet\_id in the [Developer Portal](https://dev.neynar.com) or contact support.
### Error: Insufficient Wallet Balance
```json theme={"system"}
{
"code": "InsufficientFunds",
"message": "Wallet does not have enough balance to complete this transaction."
}
```
**Solution:** Fund your wallet with more ETH on Optimism.
## Next Steps
View storage allocations for a user
Track storage consumption
Fund your wallet and monitor balance
Need help? Reach out to our team
# Credits Pricing
Source: https://docs.neynar.com/reference/compute-units
Pricing in credits for each API
## Hub HTTP APIs
**Casts**
| Version | Type | Method | Credits |
| ------- | ----- | ----------------- | ------- |
| v1 | `GET` | /v1/castById | 1 |
| | | /v1/castsByFid | 200 |
| | | /v1/castsByParent | 200 |
**Reactions**
| Version | Type | Method | Credits |
| ------- | ----- | --------------------- | ------- |
| v1 | `GET` | /v1/reactionById | 1 |
| | | /v1/reactionsByFid | 200 |
| | | /v1/reactionsByCast | 150 |
| | | /v1/reactionsByTarget | 150 |
**Follows**
| Version | Type | Method | Credits |
| ------- | ----- | -------------------- | ------- |
| v1 | `GET` | /v1/linkById | 1 |
| | | /v1/linksByFid | 200 |
| | | /v1/linksByTargetFid | 200 |
**User**
| Version | Type | Method | Credits |
| ------- | ----- | ----------------------- | ------- |
| v1 | `GET` | /v1/userDataByFid | 1 |
| | | /v1/fids | 2000 |
| | | /v1/storageLimitsByFid | 5 |
| | | /v1/userNameProofByName | 2 |
| | | /v1/userNameProofsByFid | 2 |
| | | /v1/verificationsByFid | 5 |
| | | /v1/onChainSignersByFid | 15 |
**Messages**
| Version | Type | Method | Credits |
| ------- | ------ | ------------------- | ------- |
| v1 | `POST` | /v1/submitMessage | 150 |
| | | /v1/validateMessage | 4 |
**Events**
| Version | Type | Method | Credits |
| ------- | ----- | ----------------------------------- | ------- |
| v1 | `GET` | /v1/onChainIdRegistryEventByAddress | 2 |
| | | /v1/eventById | 1 |
| | | /v1/events | 2000 |
## Hub gRPC APIs
**WIP**
Hub gRPC APIs and Hub gRPC streaming pricing will be added soon.
## Neynar APIs
**User**
| Version | Type | Method | Credits | Multiplier |
| ------- | -------- | ---------------------------------- | ------- | ------------------- |
| v2 | `GET` | /v2/farcaster/user/search | 10 | |
| | | /v2/farcaster/user/bulk | 1 | number of fids |
| | | /v2/farcaster/user/bulk-by-address | 1 | number of addresses |
| | | /v2/farcaster/user/custody-address | 1 | |
| | | /v2/farcaster/user/active | 1 | page limit |
| | `PATCH` | /v2/farcaster/user | 20 | |
| | `POST` | /v2/farcaster/user/verification | 10 | |
| | | /v2/farcaster/user/follow | 10 | |
| | `DELETE` | /v2/farcaster/user/verification | 10 | |
| | | /v2/farcaster/user/follow | 10 | |
| | | /v2/farcaster/reaction | 10 | |
**Cast**
| Version | Type | Method | Credits | Multiplier |
| ------- | -------- | ------------------- | ------- | ---------- |
| v2 | `GET` | /v2/farcaster/cast | 2 | |
| | | /v2/farcaster/casts | 50 | |
| | `POST` | /v2/farcaster/cast | 150 | |
| | `DELETE` | /v2/farcaster/cast | 10 | |
**Feed**
| Version | Type | Method | Credits | Multiplier |
| ------- | ----- | ---------------------------------------------------- | ------- | ---------- |
| v2 | `GET` | /v2/farcaster/feed | 4 | page limit |
| | | /v2/farcaster/feed/following | 4 | page limit |
| | | /v2/farcaster/feed/channels | 4 | page limit |
| | | /v2/farcaster/feed/frames | 4 | page limit |
| | | /v2/farcaster/feed/user/\{fid}/popular | 4 | page limit |
| | | /v2/farcaster/feed/user/\{fid}/replies\_and\_recasts | 4 | page limit |
**Reactions**
| Version | Type | Method | Credits | Multiplier |
| ------- | ------ | ---------------------------- | ------- | ---------- |
| v2 | `GET` | /v2/farcaster/reactions/user | 2 | page limit |
| | `GET` | /v2/farcaster/reactions/cast | 2 | page limit |
| | `POST` | /v2/farcaster/reaction | 10 | |
**Frame**
| Version | Type | Method | Credits | Multiplier |
| ------- | ------ | ---------------------------- | ------- | ---------- |
| v2 | `POST` | /v2/farcaster/frame/action | 20 | |
| | | /v2/farcaster/frame/validate | 0 | |
**Notifications**
| Version | Type | Method | Credits | Multiplier |
| ------- | ----- | --------------------------------------- | ------- | ---------- |
| v2 | `GET` | /v2/farcaster/notifications | 5 | page limit |
| | | /v2/farcaster/notifications/parent\_url | 5 | page limit |
| | | /v2/farcaster/notifications/channel | 5 | page limit |
**Channel**
| Version | Type | Method | Credits | Multiplier |
| ------- | ----- | ------------------------------- | ------- | ---------- |
| v2 | `GET` | /v2/farcaster/channel/list | 2 | page limit |
| | | /v2/farcaster/channel/search | 20 | |
| | | /v2/farcaster/channel | 2 | |
| | | /v2/farcaster/channel/followers | 1 | page limit |
| | | /v2/farcaster/channel/users | 1 | page limit |
| | | /v2/farcaster/channel/trending | 4 | page limit |
**Follows**
| Version | Type | Method | Credits | Multiplier |
| ------- | ----- | ------------------------------- | ------- | ---------- |
| v2 | `GET` | v2/farcaster/followers/relevant | 20 | |
**fname**
| Version | Type | Method | Credits | Multiplier |
| ------- | ----- | -------------------------------- | ------- | ---------- |
| v2 | `GET` | /v2/farcaster/fname/availability | 1 | |
**Storage**
| Version | Type | Method | Credits | Multiplier |
| ------- | ----- | --------------------------------- | ------- | ---------- |
| v2 | `GET` | /v2/farcaster/storage/allocations | 1 | |
| | | /v2/farcaster/storage/usage | 1 | |
**Signer**
| Version | Type | Method | Credits | Multiplier |
| ------- | ------ | --------------------------------------------------- | ------- | ---------- |
| v2 | `GET` | /v2/farcaster/signer | 0 | |
| | `GET` | /v2/farcaster/signer/developer\_managed | 0 | |
| | `POST` | /v2/farcaster/signer | 2 | |
| | | /v2/farcaster/signer/signed\_key | 5 | |
| | | /v2/farcaster/signer/developer\_managed/signed\_key | 5 | |
20,000 Credits per monthly active signer per month, where active signer = signers used by developer to write to the protocol in specific time period
**Message**
| Version | Type | Method | Credits |
| ------- | ------ | -------------------- | ------- |
| v2 | `POST` | v2/farcaster/message | 150 |
## Webhooks
| Event type | Filter | Credits |
| ---------------- | ------------------ | ------- |
| user.created | | 5 |
| user.updated | fids | 10 |
| cast.created | fids | 15 |
| | mention\_fids | 15 |
| | author\_fids | 15 |
| | root\_parent\_urls | 15 |
| | parent\_url | 15 |
| | embeds | 15 |
| reaction.created | fids | 15 |
| | target\_fids | 15 |
| reaction.deleted | fids | 15 |
| | target\_fids | 15 |
| follow\.created | fids | 15 |
| | target\_fids | 15 |
| follow\.deleted | fids | 15 |
| | target\_fids | 15 |
# Create a miniapp generator deployment
Source: https://docs.neynar.com/reference/create-deployment
post /v2/studio/deployment/
Creates and deploys an instance of the miniapp generator for a user. Requires authentication via API key in the request header. Note: Studio CU is tracked based on LLM token usage, not per API call.
The Miniapp Studio API is an allowlisted API and not publicly available. [Contact the Neynar team](https://neynar.com/slack) for more information.
## Node.js SDK
🔗 **SDK Method:** [createDeployment](/nodejs-sdk/studio-apis/createDeployment)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Create signer
Source: https://docs.neynar.com/reference/create-signer
post /v2/farcaster/signer/
Creates a signer and returns the signer status.
**Note**: While tesing please reuse the signer, it costs money to approve a signer.
### Easiest way to start is to clone our [repo](https://github.com/neynarxyz/farcaster-examples/tree/main/managed-signers) that has sign in w/ Farcaster and writes
[Read more about how writes to Farcaster work with Neynar managed signers](/docs/write-to-farcaster-with-neynar-managed-signers)
## Node.js SDK
🔗 **SDK Method:** [createSigner](/nodejs-sdk/signer-apis/createSigner)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Create transaction pay mini app
Source: https://docs.neynar.com/reference/create-transaction-pay-frame
post /v2/farcaster/frame/transaction/pay/
Creates a new transaction pay mini app that can be used to collect payments through a mini app
### Read more about this API here: [Make agents prompt transactions](/docs/make-agents-prompt-transactions)
## Node.js SDK
🔗 **SDK Method:** [createTransactionPayFrame](/nodejs-sdk/agent-apis/createTransactionPayFrame)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Create x402 signature
Source: https://docs.neynar.com/reference/create-x402-signature
post /v2/signature/x402/
Create a signature for a given x402 resource using the specified wallet.
## Create x402 Signature
## Node.js SDK
🔗 **SDK Method:** [createX402Signature](/nodejs-sdk/onchain-apis/createX402Signature)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Unban FIDs from app
Source: https://docs.neynar.com/reference/delete-bans
delete /v2/farcaster/ban/
Deletes a list of FIDs from the app associated with your API key.
### Related doc: [Mutes, Blocks, and Bans](/docs/mutes-blocks-and-bans)
## Node.js SDK
🔗 **SDK Method:** [deleteBans](/nodejs-sdk/ban-apis/deleteBans)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Unblock FID
Source: https://docs.neynar.com/reference/delete-block
delete /v2/farcaster/block/
Deletes a block for a given FID.
### Related doc: [Mutes, Blocks, and Bans](/docs/mutes-blocks-and-bans)
## Node.js SDK
🔗 **SDK Method:** [deleteBlock](/nodejs-sdk/block-apis/deleteBlock)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Delete a cast
Source: https://docs.neynar.com/reference/delete-cast
delete /v2/farcaster/cast/
Delete an existing cast.
(In order to delete a cast `signer_uuid` must be approved)
## Node.js SDK
🔗 **SDK Method:** [deleteCast](/nodejs-sdk/cast-apis/deleteCast)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Delete deployment(s)
Source: https://docs.neynar.com/reference/delete-deployment
delete /v2/studio/deployment/
Deletes a specific miniapp generator deployment or all deployments for a FID. If deployment_id or name is provided, deletes single deployment. If only FID is provided, deletes all deployments for that FID. Requires API key authentication. Note: Studio CU is tracked based on LLM token usage, not per API call.
The Miniapp Studio API is an allowlisted API and not publicly available. [Contact the Neynar team](https://neynar.com/slack) for more information.
## Node.js SDK
🔗 **SDK Method:** [deleteDeployment](/nodejs-sdk/studio-apis/deleteDeployment)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Unmute FID
Source: https://docs.neynar.com/reference/delete-mute
delete /v2/farcaster/mute/
Deletes a mute for a given FID. This is an allowlisted API, reach out if you want access.
### Related doc: [Mutes, Blocks, and Bans](/docs/mutes-blocks-and-bans)
## Node.js SDK
🔗 **SDK Method:** [deleteMute](/nodejs-sdk/mute-apis/deleteMute)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# null
Source: https://docs.neynar.com/reference/delete-neynar-frame
delete /v2/farcaster/frame/
### Related doc: [Dynamic frame creation](/docs/how-to-create-frames-using-the-neynar-sdk)
# Delete reaction
Source: https://docs.neynar.com/reference/delete-reaction
delete /v2/farcaster/reaction/
Delete a reaction (like or recast) to a cast
(In order to delete a reaction `signer_uuid` must be approved)
## Node.js SDK
🔗 **SDK Method:** [deleteReaction](/nodejs-sdk/reaction-apis/deleteReaction)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Delete verification
Source: https://docs.neynar.com/reference/delete-verification
delete /v2/farcaster/user/verification/
Removes verification for an eth address for the user
(In order to delete verification `signer_uuid` must be approved)
## Node.js SDK
🔗 **SDK Method:** [deleteVerification](/nodejs-sdk/user-apis/deleteVerification)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Delete a webhook
Source: https://docs.neynar.com/reference/delete-webhook
delete /v2/farcaster/webhook/
Delete a webhook
### Related tutorial: [Programmatic webhooks](/docs/how-to-create-webhooks-on-the-go-using-the-sdk)
## Node.js SDK
🔗 **SDK Method:** [deleteWebhook](/nodejs-sdk/webhook-apis/deleteWebhook)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Deploy fungible
Source: https://docs.neynar.com/reference/deploy-fungible
post /v2/fungible/
Creates a new token.
This is an allowlisted API, reach out if you want access.
### Related tutorial: [Deploy a token on Base w/ 1 API call](/docs/deploy-token-on-base-with-api-call)
## Node.js SDK
🔗 **SDK Method:** [deployFungible](/nodejs-sdk/onchain-apis/deployFungible)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Developer hosted frames
Source: https://docs.neynar.com/reference/developer-frames
Build your own frames and use Neynar to
# Fetch all channels with their details
Source: https://docs.neynar.com/reference/fetch-all-channels
get /v2/farcaster/channel/list/
Returns a list of all channels with their details
## Node.js SDK
🔗 **SDK Method:** [fetchAllChannels](/nodejs-sdk/channel-apis/fetchAllChannels)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# For user
Source: https://docs.neynar.com/reference/fetch-all-notifications
get /v2/farcaster/notifications/
Returns a list of notifications for a specific FID.
### If listening to bot mentions, use webhooks (see [Listen for Bot Mentions](/docs/listen-for-bot-mentions)). It's more real time and cheaper compute. Related tutorial for this API: [Notifications for FID](/docs/what-does-dwreths-farcaster-notification-look-like)
## Node.js SDK
🔗 **SDK Method:** [fetchAllNotifications](/nodejs-sdk/notification-apis/fetchAllNotifications)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Fetch authorization url
Source: https://docs.neynar.com/reference/fetch-authorization-url
get /v2/farcaster/login/authorize/
Fetch authorization url (Fetched authorized url useful for SIWN login operation)
## Node.js SDK
🔗 **SDK Method:** [fetchAuthorizationUrl](/nodejs-sdk/signer-apis/fetchAuthorizationUrl)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Banned FIDs of app
Source: https://docs.neynar.com/reference/fetch-ban-list
get /v2/farcaster/ban/list/
Fetches all FIDs that your app has banned.
### Related doc: [Mutes, Blocks, and Bans](/docs/mutes-blocks-and-bans)
## Node.js SDK
🔗 **SDK Method:** [fetchBanList](/nodejs-sdk/ban-apis/fetchBanList)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Blocked / Blocked by FIDs
Source: https://docs.neynar.com/reference/fetch-block-list
get /v2/farcaster/block/list/
Fetches all FIDs that a user has blocked or has been blocked by
### Related doc: [Mutes, Blocks, and Bans](/docs/mutes-blocks-and-bans)
## Node.js SDK
🔗 **SDK Method:** [fetchBlockList](/nodejs-sdk/block-apis/fetchBlockList)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Bulk fetch casts
Source: https://docs.neynar.com/reference/fetch-bulk-casts
get /v2/farcaster/casts/
Fetch multiple casts using their respective hashes.
## Node.js SDK
🔗 **SDK Method:** [fetchBulkCasts](/nodejs-sdk/cast-apis/fetchBulkCasts)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Bulk fetch
Source: https://docs.neynar.com/reference/fetch-bulk-channels
get /v2/farcaster/channel/bulk/
Returns details of multiple channels
## Node.js SDK
🔗 **SDK Method:** [fetchBulkChannels](/nodejs-sdk/channel-apis/fetchBulkChannels)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# By FIDs
Source: https://docs.neynar.com/reference/fetch-bulk-users
get /v2/farcaster/user/bulk/
Fetches information about multiple users based on FIDs
## Node.js SDK
🔗 **SDK Method:** [fetchBulkUsers](/nodejs-sdk/user-apis/fetchBulkUsers)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# By Eth or Sol addresses
Source: https://docs.neynar.com/reference/fetch-bulk-users-by-eth-or-sol-address
get /v2/farcaster/user/bulk-by-address/
Fetches all users based on multiple Ethereum or Solana addresses.
Each farcaster user has a custody Ethereum address and optionally verified Ethereum or Solana addresses. This endpoint returns all users that have any of the given addresses as their custody or verified Ethereum or Solana addresses.
A custody address can be associated with only 1 farcaster user at a time but a verified address can be associated with multiple users.
You can pass in Ethereum and Solana addresses, comma separated, in the same request. The response will contain users associated with the given addresses.
### See related guide: [User by wallet address](/docs/fetching-farcaster-user-based-on-ethereum-address)
## Node.js SDK
🔗 **SDK Method:** [fetchBulkUsersByEthOrSolAddress](/nodejs-sdk/user-apis/fetchBulkUsersByEthOrSolAddress)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Metrics for casts
Source: https://docs.neynar.com/reference/fetch-cast-metrics
get /v2/farcaster/cast/metrics/
Fetches metrics casts matching a query
## Node.js SDK
🔗 **SDK Method:** [fetchCastMetrics](/nodejs-sdk/metric-apis/fetchCastMetrics)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Cast Quotes
Source: https://docs.neynar.com/reference/fetch-cast-quotes
get /v2/farcaster/cast/quotes/
Fetch casts that quote a given cast
## Node.js SDK
🔗 **SDK Method:** [fetchCastQuotes](/nodejs-sdk/cast-apis/fetchCastQuotes)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Reactions for cast
Source: https://docs.neynar.com/reference/fetch-cast-reactions
get /v2/farcaster/reactions/cast/
Fetches reactions for a given cast
## Node.js SDK
🔗 **SDK Method:** [fetchCastReactions](/nodejs-sdk/reaction-apis/fetchCastReactions)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# On cast
Source: https://docs.neynar.com/reference/fetch-cast-reactions-1
get /v1/reactionsByCast
Retrieve all reactions (likes or recasts) on a specific cast in the Farcaster network. The cast is identified by its creator's FID and unique hash. This endpoint helps track engagement metrics and user interactions with specific content.
# By parent cast
Source: https://docs.neynar.com/reference/fetch-casts-by-parent
get /v1/castsByParent
Retrieve all reply casts (responses) to a specific parent cast in the Farcaster network. Parent casts can be identified using either a combination of FID and hash, or by their URL. This endpoint enables traversal of conversation threads and retrieval of all responses to a particular cast.
# Chronologically
Source: https://docs.neynar.com/reference/fetch-casts-for-user
get /v2/farcaster/feed/user/casts/
Fetch casts for a given user FID in reverse chronological order. Also allows filtering by parent_url and channel
## Node.js SDK
🔗 **SDK Method:** [fetchCastsForUser](/nodejs-sdk/feed-apis/fetchCastsForUser)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Mentioning an FID
Source: https://docs.neynar.com/reference/fetch-casts-mentioning-user
get /v1/castsByMention
Fetch casts mentioning a user.
# Open invites
Source: https://docs.neynar.com/reference/fetch-channel-invites
get /v2/farcaster/channel/member/invite/list/
Fetch a list of invites, either in a channel or for a user. If both are provided, open channel invite for that user is returned.
## Node.js SDK
🔗 **SDK Method:** [fetchChannelInvites](/nodejs-sdk/channel-apis/fetchChannelInvites)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Fetch members
Source: https://docs.neynar.com/reference/fetch-channel-members
get /v2/farcaster/channel/member/list/
Fetch a list of members in a channel
## Node.js SDK
🔗 **SDK Method:** [fetchChannelMembers](/nodejs-sdk/channel-apis/fetchChannelMembers)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# For user by channel
Source: https://docs.neynar.com/reference/fetch-channel-notifications-for-user
get /v2/farcaster/notifications/channel/
Returns a list of notifications for a user in specific channels
### Related tutorial: [Notifications in channel](/docs/fetching-channel-specific-notification-in-farcaster)
## Node.js SDK
🔗 **SDK Method:** [fetchChannelNotificationsForUser](/nodejs-sdk/notification-apis/fetchChannelNotificationsForUser)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# null
Source: https://docs.neynar.com/reference/fetch-composer-actions
get /v2/farcaster/cast/composer_actions/list/
# Embedded URL metadata
Source: https://docs.neynar.com/reference/fetch-embedded-url-metadata
get /v2/farcaster/cast/embed/crawl/
Crawls the given URL and returns metadata useful when embedding the URL in a cast.
## Node.js SDK
🔗 **SDK Method:** [fetchEmbeddedUrlMetadata](/nodejs-sdk/cast-apis/fetchEmbeddedUrlMetadata)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Page of events
Source: https://docs.neynar.com/reference/fetch-events
get /v1/events
Fetch a list of events.
# By filters
Source: https://docs.neynar.com/reference/fetch-feed
get /v2/farcaster/feed/
Fetch casts based on filters. Ensure setting the correct parameters based on the feed_type and filter_type.
## Node.js SDK
🔗 **SDK Method:** [fetchFeed](/nodejs-sdk/feed-apis/fetchFeed)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# By channel IDs
Source: https://docs.neynar.com/reference/fetch-feed-by-channel-ids
get /v2/farcaster/feed/channels/
Fetch feed based on channel IDs
## Fetch Feed by Channel IDs
Retrieve feed content filtered by specific channel IDs. You can filter by up to 10 channel IDs at a time.
### Parameters
* `channel_ids` (required): A comma-separated list of channel IDs to filter by (e.g., "neynar,farcaster"). Maximum of 10 channel IDs.
## Node.js SDK
🔗 **SDK Method:** [fetchFeedByChannelIds](/nodejs-sdk/feed-apis/fetchFeedByChannelIds)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# By parent URLs
Source: https://docs.neynar.com/reference/fetch-feed-by-parent-urls
get /v2/farcaster/feed/parent_urls/
Fetch feed based on parent URLs
## Node.js SDK
🔗 **SDK Method:** [fetchFeedByParentUrls](/nodejs-sdk/feed-apis/fetchFeedByParentUrls)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# For you
Source: https://docs.neynar.com/reference/fetch-feed-for-you
get /v2/farcaster/feed/for_you/
Fetch a personalized For You feed for a user
## Node.js SDK
🔗 **SDK Method:** [fetchFeedForYou](/nodejs-sdk/feed-apis/fetchFeedForYou)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Fetch a list of all the FIDs
Source: https://docs.neynar.com/reference/fetch-fids
get /v1/fids
Fetch a list of all the FIDs.
# Suggest Follows
Source: https://docs.neynar.com/reference/fetch-follow-suggestions
get /v2/farcaster/following/suggested/
Fetch a list of suggested users to follow. Used to help users discover new users to follow
## Node.js SDK
🔗 **SDK Method:** [fetchFollowSuggestions](/nodejs-sdk/follow-apis/fetchFollowSuggestions)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# For channel
Source: https://docs.neynar.com/reference/fetch-followers-for-a-channel
get /v2/farcaster/channel/followers/
Returns a list of followers for a specific channel. Max limit is 1000. Use cursor for pagination.
## Node.js SDK
🔗 **SDK Method:** [fetchFollowersForAChannel](/nodejs-sdk/channel-apis/fetchFollowersForAChannel)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Mini apps catalog
Source: https://docs.neynar.com/reference/fetch-frame-catalog
get /v2/farcaster/frame/catalog/
A curated list of featured mini apps
## Node.js SDK
🔗 **SDK Method:** [fetchFrameCatalog](/nodejs-sdk/frame-apis/fetchFrameCatalog)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Relevant mini apps
Source: https://docs.neynar.com/reference/fetch-frame-relevant
get /v2/farcaster/frame/relevant/
Fetch a list of mini apps relevant to the user based on casts by users with strong affinity score for the user
# null
Source: https://docs.neynar.com/reference/fetch-frames-only-feed
get /v2/farcaster/feed/frames/
# Get fungible trades
Source: https://docs.neynar.com/reference/fetch-fungible-trades
get /v2/farcaster/fungible/trades/
Get recent trades for a specific fungible within a timeframe. Returns trades ordered by timestamp (most recent first).
## Node.js SDK
🔗 **SDK Method:** [fetchFungibleTrades](/nodejs-sdk/onchain-apis/fetchFungibleTrades)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Fetch fungibles
Source: https://docs.neynar.com/reference/fetch-fungibles
get /v2/farcaster/fungibles/
Fetch details for fungible assets identified by fungible identifiers.
## Node.js SDK
🔗 **SDK Method:** [fetchFungibles](/nodejs-sdk/onchain-apis/fetchFungibles)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Muted FIDs of user
Source: https://docs.neynar.com/reference/fetch-mute-list
get /v2/farcaster/mute/list/
Fetches all FIDs that a user has muted.
### Related doc: [Mutes, Blocks, and Bans](/docs/mutes-blocks-and-bans)
## Node.js SDK
🔗 **SDK Method:** [fetchMuteList](/nodejs-sdk/mute-apis/fetchMuteList)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# null
Source: https://docs.neynar.com/reference/fetch-neynar-frames
get /v2/farcaster/frame/list/
### Related doc: [Dynamic frame creation](/docs/how-to-create-frames-using-the-neynar-sdk)
# Fetch nonce
Source: https://docs.neynar.com/reference/fetch-nonce
get /v2/farcaster/login/nonce/
Nonce to sign a message
## Node.js SDK
🔗 **SDK Method:** [fetchNonce](/nodejs-sdk/login-apis/fetchNonce)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# List of mini app notification tokens
Source: https://docs.neynar.com/reference/fetch-notification-tokens
get /v2/farcaster/frame/notification_tokens/
Returns a list of notifications tokens related to a mini app
### Related tutorial: [Send notifications to Frame users](/docs/send-notifications-to-mini-app-users)
## Node.js SDK
🔗 **SDK Method:** [fetchNotificationTokens](/nodejs-sdk/frame-apis/fetchNotificationTokens)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# For user by parent_urls
Source: https://docs.neynar.com/reference/fetch-notifications-by-parent-url-for-user
get /v2/farcaster/notifications/parent_url/
Returns a list of notifications for a user in specific parent_urls
### Related tutorial: [Notifications in channel](/docs/fetching-channel-specific-notification-in-farcaster)
## Node.js SDK
🔗 **SDK Method:** [fetchNotificationsByParentUrlForUser](/nodejs-sdk/notification-apis/fetchNotificationsByParentUrlForUser)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# 10 most popular casts
Source: https://docs.neynar.com/reference/fetch-popular-casts-by-user
get /v2/farcaster/feed/user/popular/
Fetch 10 most popular casts for a given user FID; popularity based on replies, likes and recasts; sorted by most popular first
## Node.js SDK
🔗 **SDK Method:** [fetchPopularCastsByUser](/nodejs-sdk/feed-apis/fetchPopularCastsByUser)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# null
Source: https://docs.neynar.com/reference/fetch-power-users
get /v2/farcaster/user/power/
# null
Source: https://docs.neynar.com/reference/fetch-power-users-lite
get /v2/farcaster/user/power_lite/
# To a target URL
Source: https://docs.neynar.com/reference/fetch-reactions-by-target
get /v1/reactionsByTarget
Fetch all reactions of a specific type (like or recast) that target a given URL. This endpoint is useful for tracking engagement with content across the Farcaster network.
# Relevant followers
Source: https://docs.neynar.com/reference/fetch-relevant-followers
get /v2/farcaster/followers/relevant/
Returns a list of relevant followers for a specific FID. This usually shows on a profile as "X, Y and Z follow this user".
### See related guide: [Mutual follows/followers](/docs/how-to-fetch-mutual-followfollowers-in-farcaster)
## Node.js SDK
🔗 **SDK Method:** [fetchRelevantFollowers](/nodejs-sdk/follow-apis/fetchRelevantFollowers)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Relevant followers
Source: https://docs.neynar.com/reference/fetch-relevant-followers-for-a-channel
get /v2/farcaster/channel/followers/relevant/
Returns a list of relevant channel followers for a specific FID. This usually shows on a channel as "X, Y, Z follow this channel".
## Node.js SDK
🔗 **SDK Method:** [fetchRelevantFollowersForAChannel](/nodejs-sdk/channel-apis/fetchRelevantFollowersForAChannel)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Relevant owners
Source: https://docs.neynar.com/reference/fetch-relevant-fungible-owners
get /v2/farcaster/fungible/owner/relevant/
Fetch a list of relevant owners for a on chain asset. If a viewer is provided, only relevant holders will be shown. This usually shows on a fungible asset page as "X, Y, Z and N others you know own this asset".
### Related tutorial: [Relevant holders for coins](/docs/fetch-relevant-holders-for-coin)
## Node.js SDK
🔗 **SDK Method:** [fetchRelevantFungibleOwners](/nodejs-sdk/onchain-apis/fetchRelevantFungibleOwners)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Replies and recasts
Source: https://docs.neynar.com/reference/fetch-replies-and-recasts-for-user
get /v2/farcaster/feed/user/replies_and_recasts/
Fetch recent replies and recasts for a given user FID; sorted by most recent first
## Node.js SDK
🔗 **SDK Method:** [fetchRepliesAndRecastsForUser](/nodejs-sdk/feed-apis/fetchRepliesAndRecastsForUser)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# List signers
Source: https://docs.neynar.com/reference/fetch-signers
get /v2/farcaster/signer/list/
Fetches a list of signers for a custody address
### Related tutorial: [Fetch signers](/docs/fetch-signers-1)
## Node.js SDK
🔗 **SDK Method:** [fetchSigners](/nodejs-sdk/signer-apis/fetchSigners)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Subscribed to
Source: https://docs.neynar.com/reference/fetch-subscribed-to-for-fid
get /v2/farcaster/user/subscribed_to/
Fetch what FIDs and contracts a FID is subscribed to.
## Node.js SDK
🔗 **SDK Method:** [fetchSubscribedToForFid](/nodejs-sdk/subscriber-apis/fetchSubscribedToForFid)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Subscribers of a user
Source: https://docs.neynar.com/reference/fetch-subscribers-for-fid
get /v2/farcaster/user/subscribers/
Fetch subscribers for a given FID's contracts. Doesn't return addresses that don't have an FID.
## Node.js SDK
🔗 **SDK Method:** [fetchSubscribersForFid](/nodejs-sdk/subscriber-apis/fetchSubscribersForFid)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Hypersub subscription check
Source: https://docs.neynar.com/reference/fetch-subscription-check
get /v2/stp/subscription_check/
Check if a wallet address is subscribed to a given STP (Hypersub) contract.
## Node.js SDK
🔗 **SDK Method:** [fetchSubscriptionCheck](/nodejs-sdk/subscriber-apis/fetchSubscriptionCheck)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Subscriptions created by FID
Source: https://docs.neynar.com/reference/fetch-subscriptions-for-fid
get /v2/farcaster/user/subscriptions_created/
Fetch created subscriptions for a given FID's.
### Related tutorial: [Find User Subscriptions with Neynar Hypersub](/docs/common-subscriptions-fabric)
## Node.js SDK
🔗 **SDK Method:** [fetchSubscriptionsForFid](/nodejs-sdk/subscriber-apis/fetchSubscriptionsForFid)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Channels by activity
Source: https://docs.neynar.com/reference/fetch-trending-channels
get /v2/farcaster/channel/trending/
Returns a list of trending channels based on activity
## Node.js SDK
🔗 **SDK Method:** [fetchTrendingChannels](/nodejs-sdk/channel-apis/fetchTrendingChannels)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Trending feeds
Source: https://docs.neynar.com/reference/fetch-trending-feed
get /v2/farcaster/feed/trending/
Fetch trending casts or on the global feed or channels feeds. 7d time window available for channel feeds only.
## Node.js SDK
🔗 **SDK Method:** [fetchTrendingFeed](/nodejs-sdk/feed-apis/fetchTrendingFeed)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Trending fungibles
Source: https://docs.neynar.com/reference/fetch-trending-fungibles
get /v2/farcaster/fungible/trending/
Fetch trending fungibles based on buy activity from watched addresses. Returns fungibles ranked by USD buy volume and buy count within the specified time window.
## Node.js SDK
🔗 **SDK Method:** [fetchTrendingFungibles](/nodejs-sdk/onchain-apis/fetchTrendingFungibles)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Token balance
Source: https://docs.neynar.com/reference/fetch-user-balance
get /v2/farcaster/user/balance/
Fetches the token balances of a user given their FID
## Fetch User Balance
### Related tutorial: [User balances directly w/ FID](/docs/how-to-fetch-user-balance-using-farcaster-fid)
## Node.js SDK
🔗 **SDK Method:** [fetchUserBalance](/nodejs-sdk/onchain-apis/fetchUserBalance)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Member of
Source: https://docs.neynar.com/reference/fetch-user-channel-memberships
get /v2/farcaster/user/memberships/list/
Returns a list of all channels with their details that an FID is a member of. Data may have a delay of up to 1 hour.
## Node.js SDK
🔗 **SDK Method:** [fetchUserChannelMemberships](/nodejs-sdk/channel-apis/fetchUserChannelMemberships)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Following
Source: https://docs.neynar.com/reference/fetch-user-channels
get /v2/farcaster/user/channels/
Returns a list of all channels with their details that a FID follows.
## Node.js SDK
🔗 **SDK Method:** [fetchUserChannels](/nodejs-sdk/channel-apis/fetchUserChannels)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Fetch UserData for a FID
Source: https://docs.neynar.com/reference/fetch-user-data
get /v1/userDataByFid
**Note:** one of two different response schemas is returned based on whether the caller provides the `user_data_type` parameter. If included, a single `UserDataAdd` message is returned (or a `not_found` error). If omitted, a paginated list of `UserDataAdd` messages is returned instead.
# Followers
Source: https://docs.neynar.com/reference/fetch-user-followers
get /v2/farcaster/followers/
Returns a list of followers for a specific FID.
### If you're looking to check whether one user follows another, simply put in a `viewer_fid` in the [/user API](/reference/fetch-bulk-users)
## Node.js SDK
🔗 **SDK Method:** [fetchUserFollowers](/nodejs-sdk/follow-apis/fetchUserFollowers)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# To target FID
Source: https://docs.neynar.com/reference/fetch-user-followers-1
get /v1/linksByTargetFid
Fetch a list of users that are following a user.
# Following
Source: https://docs.neynar.com/reference/fetch-user-following
get /v2/farcaster/following/
Fetch a list of users who a given user is following. Can optionally include a viewer_fid and sort_type.
## Node.js SDK
🔗 **SDK Method:** [fetchUserFollowing](/nodejs-sdk/follow-apis/fetchUserFollowing)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# From source FID
Source: https://docs.neynar.com/reference/fetch-user-following-1
get /v1/linksByFid
Fetch a list of users that a user is following.
# Following
Source: https://docs.neynar.com/reference/fetch-user-following-feed
get /v2/farcaster/feed/following/
Fetch feed based on who a user is following
## Node.js SDK
🔗 **SDK Method:** [fetchUserFollowingFeed](/nodejs-sdk/feed-apis/fetchUserFollowingFeed)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Fetch User Information
Source: https://docs.neynar.com/reference/fetch-user-information
# User interactions
Source: https://docs.neynar.com/reference/fetch-user-interactions
get /v2/farcaster/user/interactions/
Returns a list of interactions between two users
## Node.js SDK
🔗 **SDK Method:** [fetchUserInteractions](/nodejs-sdk/agent-apis/fetchUserInteractions)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Fetch a list of on-chain events provided by an FID
Source: https://docs.neynar.com/reference/fetch-user-on-chain-events
get /v1/onChainEventsByFid
Fetch on-chain events provided by a user.
# Fetch a list of signers provided by an FID
Source: https://docs.neynar.com/reference/fetch-user-on-chain-signers-events
get /v1/onChainSignersByFid
**Note:** one of two different response schemas is returned based on whether the caller provides the `signer` parameter. If included, a single `OnChainEventSigner` message is returned (or a `not_found` error). If omitted, a non-paginated list of `OnChainEventSigner` messages is returned instead.
# Reactions for user
Source: https://docs.neynar.com/reference/fetch-user-reactions
get /v2/farcaster/reactions/user/
Fetches reactions for a given user
## Node.js SDK
🔗 **SDK Method:** [fetchUserReactions](/nodejs-sdk/reaction-apis/fetchUserReactions)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# By FID
Source: https://docs.neynar.com/reference/fetch-user-reactions-1
get /v1/reactionsByFid
Fetch reactions by a user.
# Reciprocal Followers
Source: https://docs.neynar.com/reference/fetch-user-reciprocal-followers
get /v2/farcaster/followers/reciprocal/
Returns users who the given FID follows and they follow the FID back (reciprocal following relationship)
## Node.js SDK
🔗 **SDK Method:** [fetchUserReciprocalFollowers](/nodejs-sdk/follow-apis/fetchUserReciprocalFollowers)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Proof for a username
Source: https://docs.neynar.com/reference/fetch-username-proof-by-name
get /v1/userNameProofByName
Fetch a proof for a username.
# Proofs provided by an FID
Source: https://docs.neynar.com/reference/fetch-username-proofs-by-fid
get /v1/userNameProofsByFid
Fetch proofs provided by a user.
# Fetch channels that user is active in
Source: https://docs.neynar.com/reference/fetch-users-active-channels
get /v2/farcaster/channel/user/
Fetches all channels that a user has casted in, in reverse chronological order.
## Node.js SDK
🔗 **SDK Method:** [fetchUsersActiveChannels](/nodejs-sdk/channel-apis/fetchUsersActiveChannels)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# By location
Source: https://docs.neynar.com/reference/fetch-users-by-location
get /v2/farcaster/user/by_location/
Fetches a list of users given a location
## Node.js SDK
🔗 **SDK Method:** [fetchUsersByLocation](/nodejs-sdk/user-apis/fetchUsersByLocation)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# By FID
Source: https://docs.neynar.com/reference/fetch-users-casts-1
get /v1/castsByFid
Fetch user's casts.
# null
Source: https://docs.neynar.com/reference/fetch-validate-frame-analytics
get /v2/farcaster/frame/validate/analytics/
# null
Source: https://docs.neynar.com/reference/fetch-validate-frame-list
get /v2/farcaster/frame/validate/list/
# Fetch verifications
Source: https://docs.neynar.com/reference/fetch-verifications
get /v2/farcaster/user/verifications/
Fetch all Ethereum and Solana verified addresses for a Farcaster user. Use this endpoint to identify which wallets are associated with which Farcaster applications for the specified user.
## Node.js SDK
🔗 **SDK Method:** [fetchVerifications](/nodejs-sdk/user-apis/fetchVerifications)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Provided by an FID
Source: https://docs.neynar.com/reference/fetch-verifications-by-fid
get /v1/verificationsByFid
Fetch verifications provided by a user.
# Associated webhooks of user
Source: https://docs.neynar.com/reference/fetch-webhooks
get /v2/farcaster/webhook/list/
Fetch a list of webhooks associated to a user
### Related tutorial: [Programmatic webhooks](/docs/how-to-create-webhooks-on-the-go-using-the-sdk)
## Node.js SDK
🔗 **SDK Method:** [fetchWebhooks](/nodejs-sdk/webhook-apis/fetchWebhooks)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Follow a channel
Source: https://docs.neynar.com/reference/follow-channel
post /v2/farcaster/channel/follow/
Follow a channel
## Node.js SDK
🔗 **SDK Method:** [followChannel](/nodejs-sdk/channel-apis/followChannel)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Follow user
Source: https://docs.neynar.com/reference/follow-user
post /v2/farcaster/user/follow/
Follow a user
(In order to follow a user `signer_uuid` must be approved)
## Node.js SDK
🔗 **SDK Method:** [followUser](/nodejs-sdk/user-apis/followUser)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Get deployment info
Source: https://docs.neynar.com/reference/get-deployment
get /v2/studio/deployment/by-name-and-fid
Fetches info about a miniapp generator deployment by its deployment_id or name and creator's Farcaster ID. Requires API key authentication. Note: Studio CU is tracked based on LLM token usage, not per API call.
The Miniapp Studio API is an allowlisted API and not publicly available. [Contact the Neynar team](https://neynar.com/slack) for more information.
## Node.js SDK
🔗 **SDK Method:** [getDeployment](/nodejs-sdk/studio-apis/getDeployment)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Fetch fresh FID
Source: https://docs.neynar.com/reference/get-fresh-account-fid
get /v2/farcaster/user/fid/
Fetches FID to [assign it to new user](https://docs.neynar.com/reference/register-account).
### Related documentation:
* [Create new Farcaster account](/docs/how-to-create-a-new-farcaster-account-with-neynar) - Complete tutorial
* [Managing Onchain Wallets](/docs/managing-onchain-wallets) - Wallet setup guide
## Understanding Wallet ID for FID Operations
This endpoint fetches a fresh FID (Farcaster ID) that can be assigned to a new user. When you fetch a FID, Neynar maintains a "shelf" of pre-registered FIDs that are replenished asynchronously via onchain transactions.
### Wallet ID (REQUIRED)
The [`x-wallet-id` header](/docs/managing-onchain-wallets) is **required** for this endpoint. You must provide a wallet\_id to cover the costs of async FID shelf replenishment.
**New to Wallet IDs?** See [Managing Onchain Wallets](/docs/managing-onchain-wallets) to create your app wallet in the developer portal and obtain your `x-wallet-id` value.
## Code Examples
```javascript Node.js theme={"system"}
const response = await fetch('https://api.neynar.com/v2/farcaster/user/fid', {
headers: {
'x-api-key': 'YOUR_NEYNAR_API_KEY',
'x-wallet-id': 'your-wallet-id' // REQUIRED
}
});
const data = await response.json();
console.log('Fresh FID:', data.fid);
// Use this FID within 10 minutes to register an account
```
```bash cURL theme={"system"}
curl -X GET 'https://api.neynar.com/v2/farcaster/user/fid' \
-H 'x-api-key: YOUR_NEYNAR_API_KEY' \
-H 'x-wallet-id: your-wallet-id'
```
```python Python theme={"system"}
import requests
headers = {
'x-api-key': 'YOUR_NEYNAR_API_KEY',
'x-wallet-id': 'your-wallet-id' # REQUIRED
}
response = requests.get(
'https://api.neynar.com/v2/farcaster/user/fid',
headers=headers
)
data = response.json()
print(f"Fresh FID: {data['fid']}")
```
## Error Handling
### Error: Missing Wallet ID
```json theme={"system"}
{
"code": "RequiredField",
"message": "x-wallet-id header is required"
}
```
**Solution:** Add the `x-wallet-id` header. See [Managing Onchain Wallets](/docs/managing-onchain-wallets) for setup instructions.
### Error: Invalid Wallet ID
```json theme={"system"}
{
"code": "InvalidWalletId",
"message": "The provided wallet_id is invalid or not found."
}
```
**Solution:** Verify your wallet\_id in the [Developer Portal](https://dev.neynar.com) or contact support.
### Error: Insufficient Wallet Balance
```json theme={"system"}
{
"code": "InsufficientFunds",
"message": "Wallet does not have enough balance to complete this transaction."
}
```
**Solution:** Fund your wallet with more ETH on Optimism.
## Important Notes
**Cold Start (First Call Only):** The first time you call this endpoint with a new `wallet_id`, it will take approximately **1 minute** to complete as it pre-registers a few FID accounts. Subsequent calls will be fast (\< 1 second). Make sure your wallet has **\$5+ ETH on Optimism** before the first call.
**Wallet Consistency Required:** The same `wallet_id` used to fetch a FID must also be used when calling [`POST /v2/farcaster/user/`](/reference/register-account) to register that account. Using different wallets will result in an error.
**10 Minute Deadline:** After fetching a FID, you must call the [register account endpoint](/reference/register-account) within **10 minutes**. Otherwise, Neynar will assign this FID to another user.
### What Happens Behind the Scenes
1. **You call this endpoint** → Neynar returns a pre-registered FID from the shelf
2. **Neynar replenishes** → Async onchain transactions register new FIDs to refill the shelf
This architecture ensures fast FID fetching while handling blockchain complexity in the background.
## Next Steps
After fetching a FID, register the account within 10 minutes
Learn how to set up and fund your wallet for onchain operations
Complete guide to creating Farcaster accounts
Need help? Reach out to our team
# Simulate NFT mint calldata
Source: https://docs.neynar.com/reference/get-nft-mint
get /v2/farcaster/nft/mint/
Simulates mint calldata for the given recipients, contract, and network. Useful for previewing calldata and ABI before minting.
### Related tutorial: [Minting for Farcaster Users](/docs/mint-for-farcaster-users)
# Get transaction pay mini app
Source: https://docs.neynar.com/reference/get-transaction-pay-frame
get /v2/farcaster/frame/transaction/pay/
Retrieves details about a transaction pay mini app by ID
### Read more about this API here: [Make agents prompt transactions](/docs/make-agents-prompt-transactions)
## Node.js SDK
🔗 **SDK Method:** [getTransactionPayFrame](/nodejs-sdk/frame-apis/getTransactionPayFrame)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Getting Started with Neynar Go SDK
Source: https://docs.neynar.com/reference/getting-started-with-go-sdk
Easily call Neynar APIs with our Go SDK
This tutorial uses the [Neynar Go SDK](https://github.com/neynarxyz/go-sdk)
This SDK is in **beta** and may change in the future.
Please let us know if you encounter any issues or have suggestions for improvement.
## Prerequisites
* Install [Go](https://go.dev/doc/install)
* Get your Neynar API key from [neynar.com](https://neynar.com)
## Project Setup
**Initialize Project Directory**
```bash Shell theme={"system"}
mkdir get-started-with-neynar-go-sdk
cd get-started-with-neynar-go-sdk
go mod init getting_started
```
**Add Neynar Go SDK as a dependency**
```bash Shell theme={"system"}
go get github.com/neynarxyz/go-sdk/generated/rust_sdk
```
## Implementation: Look up a user by their verified address
Replace the contents of `main.go` with your Go code using the Neynar Go SDK.
```go main.go theme={"system"}
package getting_started
import (
"context"
"fmt"
openapiclient "github.com/neynarxyz/go-sdk/generated/neynar_sdk"
)
func main() {
configuration := openapiclient.NewConfiguration()
configuration.AddDefaultHeader("x-api-key", "NEYNAR_API_DOCS")
apiClient := openapiclient.NewAPIClient(configuration)
request := apiClient.UserAPI.
FetchBulkUsersByEthOrSolAddress(context.Background()).
Addresses("0xBFc7CAE0Fad9B346270Ae8fde24827D2D779eF07").
AddressTypes([]openapiclient.BulkUserAddressType{openapiclient.AllowedBulkUserAddressTypeEnumValues[1]})
resp, httpRes, err := request.Execute()
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
defer httpRes.Body.Close()
fmt.Printf("Users: %+v\n", resp)
}
```
## Running the project
```bash Shell theme={"system"}
go run main.go
```
## Result
You should see a response similar to this (formatted for readability):
```json Shell theme={"system"}
Users: [{
fid: 20603,
username: "topocount.eth",
display_name: "Topocount",
// ...other fields...
}]
```
## Congratulations! You successfully set up the Neynar Go SDK and used it to look up a user by their address!
### 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!
# Getting Started with Neynar NodeJS SDK
Source: https://docs.neynar.com/reference/getting-started-with-nodejs-sdk
Easily call Neynar APIs with our nodejs sdk
This tutorials uses the [Neynar nodejs sdk](https://github.com/neynarxyz/nodejs-sdk)
## Prerequisites
* Install [Node.js](https://nodejs.org/en/download/package-manager)
* Optional: Install [Yarn](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable) (Alternatively, npm can be used)
## Project Setup
**Initialize Project Directory**
```bash Shell theme={"system"}
mkdir get-started-with-neynar-sdk
cd get-started-with-neynar-sdk
```
**Install Neynar SDK along with typescript**
Install using npm
```bash Shell theme={"system"}
npm i @neynar/nodejs-sdk
npm i -D typescript
```
// or
Install using Yarn
```bash Shell theme={"system"}
yarn add @neynar/nodejs-sdk
yarn add -D typescript
```
**Initialize typescript environment**
```bash Shell theme={"system"}
npx tsc --init
```
### Implementation: Let's use sdk to look up a user by their FID
Create index.ts file at root level
```bash Shell theme={"system"}
touch index.ts
```
Add the following code in index.ts
```typescript Typescript theme={"system"}
// index.ts
import { NeynarAPIClient, Configuration, isApiErrorResponse } from "@neynar/nodejs-sdk";
const config = new Configuration({
apiKey: "", // Replace with your Neynar API Key.
});
const client = new NeynarAPIClient(config);
(async () => {
try {
const fid = 19960; // 19960 (Required*) => fid of user we are looking for
const viewerFid = 194; // 191 (Optional) => fid of the viewer
// Get more info @ https://docs.neynar.com/reference/fetch-bulk-users
const users = await client.fetchBulkUsers({ fids: [fid], viewerFid });
// Stringify and log the response
console.log(JSON.stringify(users));
} catch (error) {
// isApiErrorResponse can be used to check for Neynar API errors
if (isApiErrorResponse(error)) {
console.log("API Error", error.response.data);
} else {
console.log("Generic Error", error);
}
}
})();
```
## Running the project
```shell shell theme={"system"}
npx ts-node index.ts
```
## Result
You should see a response like this. (You might not get a beautified/ formatted response since we `JSON.stringify` the response to log everything)
```json JSON theme={"system"}
{
"users": [
{
"object": "user",
"fid": 19960,
"username": "shreyas-chorge",
"display_name": "Shreyas",
"pfp_url": "https://i.imgur.com/LPzRlQl.jpg",
"custody_address": "0xd1b702203b1b3b641a699997746bd4a12d157909",
"profile": {
"bio": {
"text": "Everyday regular normal guy | 👨💻 @neynar ..."
},
"location": {
"latitude": 19.22,
"longitude": 72.98,
"address": {
"city": "Thane",
"state": "Maharashtra",
"country": "India",
"country_code": "in"
}
}
},
"follower_count": 250,
"following_count": 92,
"verifications": [
"0xd1b702203b1b3b641a699997746bd4a12d157909",
"0x7ea5dada4021c2c625e73d2a78882e91b93c174c"
],
"verified_addresses": {
"eth_addresses": [
"0xd1b702203b1b3b641a699997746bd4a12d157909",
"0x7ea5dada4021c2c625e73d2a78882e91b93c174c"
],
"sol_addresses": []
},
"verified_accounts": null,
"power_badge": false,
"viewer_context": {
"following": true,
"followed_by": true,
"blocking": false,
"blocked_by": false
}
}
]
}
```
## Congratulations! You successfully setup [@neynar/nodejs-sdk](https://github.com/neynarxyz/nodejs-sdk) and used it to look up a user by their FID!
Please do not use @neynar/nodejs-sdk on browser since NEYNAR\_API\_KEY will be exposed in the bundle.
### 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!
# Getting Started with Neynar Rust SDK
Source: https://docs.neynar.com/reference/getting-started-with-rust-sdk
Easily call Neynar APIs with our Rust SDK
This tutorial uses the [Neynar Rust SDK](https://github.com/neynarxyz/rust-sdk)
This SDK is in **beta** and may change in the future.
Please let us know if you encounter any issues or have suggestions for improvement.
## Prerequisites
* Install [Rust](https://www.rust-lang.org/tools/install) (using [rustup](https://rustup.rs/))
* Get your Neynar API key from [neynar.com](https://neynar.com)
## Project Setup
**Initialize Project Directory**
```bash Shell theme={"system"}
cargo new get-started-with-neynar-sdk
cd get-started-with-neynar-sdk
```
**Add Neynar SDK as a dependency**
```bash Shell theme={"system"}
cargo add --git https://github.com/neynarxyz/rust-sdk api
```
## Implementation: Look up a user by their verified address
Replace the contents of `src/main.rs` with the following code:
```rust src/main.rs theme={"system"}
use neynar_sdk::apis::configuration as api_config;
use neynar_sdk::apis::configuration::Configuration as ApiConfig;
use neynar_sdk::apis::user_api::{
FetchBulkUsersByEthOrSolAddressParams, fetch_bulk_users_by_eth_or_sol_address,
};
use neynar_sdk::models::BulkUserAddressType::VerifiedAddress;
use reqwest::Client;
#[tokio::main]
async fn main() -> {
let configuration = ApiConfig {
base_path: "https://api.neynar.com/v2".to_string(),
client: Client::builder().connection_verbose(true).build().unwrap(),
user_agent: Some("rust-sdk-demo".to_string()),
api_key: Some(api_config::ApiKey {
prefix: None,
key: "NEYNAR_API_DOCS".to_string(),
}),
basic_auth: None,
bearer_access_token: None,
oauth_access_token: None,
};
let addresses = "0xBFc7CAE0Fad9B346270Ae8fde24827D2D779eF07".to_string();
let params = FetchBulkUsersByEthOrSolAddressParams {
addresses,
address_types: Some(vec![VerifiedAddress]),
viewer_fid: None,
x_neynar_experimental: None,
};
let result = fetch_bulk_users_by_eth_or_sol_address(&configuration, params).await;
match result {
Ok(response) => {
println!("Users: {:?}", response.additional_properties);
}
Err(err) => {
eprintln!("Failed to fetch users: {:?}", err);
panic!("User fetch failed");
}
}
}
```
## Running the project
```bash Shell theme={"system"}
cargo run
```
## Result
You should see a response similar to this (formatted for readability):
```json Shell theme={"system"}
Users [{
fid: 20603,
username: "topocount.eth",
display_name: "Topocount",
// ...other fields...
}]
```
## Congratulations! You successfully set up the [Neynar Rust SDK](https://github.com/neynarxyz/rust-sdk) and used it to look up a user by their address!
### 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!
# Invite
Source: https://docs.neynar.com/reference/invite-channel-member
post /v2/farcaster/channel/member/invite/
Invite a user to a channel
## Node.js SDK
🔗 **SDK Method:** [inviteChannelMember](/nodejs-sdk/channel-apis/inviteChannelMember)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Check fname availability
Source: https://docs.neynar.com/reference/is-fname-available
get /v2/farcaster/fname/availability/
Check if a given fname is available
## Node.js SDK
🔗 **SDK Method:** [isFnameAvailable](/nodejs-sdk/fname-apis/isFnameAvailable)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# List deployments
Source: https://docs.neynar.com/reference/list-deployments
get /v2/studio/deployment/
Lists all miniapp generator deployments for a user. Requires API key authentication. Note: Studio CU is tracked based on LLM token usage, not per API call.
The Miniapp Studio API is an allowlisted API and not publicly available. [Contact the Neynar team](https://neynar.com/slack) for more information.
## Node.js SDK
🔗 **SDK Method:** [listDeployments](/nodejs-sdk/studio-apis/listDeployments)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# By FID and Hash
Source: https://docs.neynar.com/reference/lookup-cast-by-hash-and-fid
get /v1/castById
Lookup a cast by its FID and hash.
# By hash or URL
Source: https://docs.neynar.com/reference/lookup-cast-by-hash-or-url
get /v2/farcaster/cast/
Gets information about an individual cast by passing in a Farcaster web URL or cast hash
### See related tutorial [Get Cast Information from URL](/docs/how-to-get-cast-information-from-url)
## Node.js SDK
🔗 **SDK Method:** [lookupCastByHashOrUrl](/nodejs-sdk/cast-apis/lookupCastByHashOrUrl)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Conversation for a cast
Source: https://docs.neynar.com/reference/lookup-cast-conversation
get /v2/farcaster/cast/conversation/
Gets all casts related to a conversation surrounding a cast by passing in a cast hash or Farcaster URL. Includes all the ancestors of a cast up to the root parent in a chronological order. Includes all direct_replies to the cast up to the reply_depth specified in the query parameter.
### Tutorial on ranking replies: [Rank high quality conversations](/docs/ranking-for-high-quality-conversations)
## Node.js SDK
🔗 **SDK Method:** [lookupCastConversation](/nodejs-sdk/cast-apis/lookupCastConversation)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Cast conversation summary
Source: https://docs.neynar.com/reference/lookup-cast-conversation-summary
get /v2/farcaster/cast/conversation/summary/
Generates a summary of all casts related to a conversation surrounding a cast by passing in a cast hash or Farcaster URL. Summary is generated by an LLM and is intended to be passed as a context to AI agents.
## Node.js SDK
🔗 **SDK Method:** [lookupCastConversationSummary](/nodejs-sdk/agent-apis/lookupCastConversationSummary)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# By ID or parent_url
Source: https://docs.neynar.com/reference/lookup-channel
get /v2/farcaster/channel/
Returns details of a channel
## Node.js SDK
🔗 **SDK Method:** [lookupChannel](/nodejs-sdk/channel-apis/lookupChannel)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Status by auth address
Source: https://docs.neynar.com/reference/lookup-developer-managed-auth-address
get /v2/farcaster/auth_address/developer_managed/
Fetches the status of a developer managed auth address by auth address
## Node.js SDK
🔗 **SDK Method:** [lookupDeveloperManagedAuthAddress](/nodejs-sdk/auth-addres-apis/lookupDeveloperManagedAuthAddress)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Status by public key
Source: https://docs.neynar.com/reference/lookup-developer-managed-signer
get /v2/farcaster/signer/developer_managed/
Fetches the status of a developer managed signer by public key
## Node.js SDK
🔗 **SDK Method:** [lookupDeveloperManagedSigner](/nodejs-sdk/signer-apis/lookupDeveloperManagedSigner)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Event by ID
Source: https://docs.neynar.com/reference/lookup-event
get /v1/eventById
Lookup an event by its ID.
# Sync Methods
Source: https://docs.neynar.com/reference/lookup-hub-info
get /v1/info
Retrieve hub information.
# null
Source: https://docs.neynar.com/reference/lookup-neynar-frame
get /v2/farcaster/frame/
### Related doc: [Dynamic frame creation](/docs/how-to-create-frames-using-the-neynar-sdk)
# Fetch an on-chain ID Registry Event for a given Address
Source: https://docs.neynar.com/reference/lookup-on-chain-id-registry-event-by-address
get /v1/onChainIdRegistryEventByAddress
Fetch an on-chain ID Registry Event for a given Address.
# By FID or cast
Source: https://docs.neynar.com/reference/lookup-reaction-by-id
get /v1/reactionById
Lookup a reaction by its FID or cast.
# Status
Source: https://docs.neynar.com/reference/lookup-signer
get /v2/farcaster/signer/
Gets information status of a signer by passing in a signer_uuid (Use post API to generate a signer)
## Node.js SDK
🔗 **SDK Method:** [lookupSigner](/nodejs-sdk/signer-apis/lookupSigner)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# By custody-address
Source: https://docs.neynar.com/reference/lookup-user-by-custody-address
get /v2/farcaster/user/custody-address/
Lookup a user by custody-address
## Node.js SDK
🔗 **SDK Method:** [lookupUserByCustodyAddress](/nodejs-sdk/user-apis/lookupUserByCustodyAddress)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# By username
Source: https://docs.neynar.com/reference/lookup-user-by-username
get /v2/farcaster/user/by_username/
Fetches a single hydrated user object given a username
## Node.js SDK
🔗 **SDK Method:** [lookupUserByUsername](/nodejs-sdk/user-apis/lookupUserByUsername)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# By its FID and target FID
Source: https://docs.neynar.com/reference/lookup-user-relation
get /v1/linkById
Lookup a link by its FID and target FID.
# Allocation of user
Source: https://docs.neynar.com/reference/lookup-user-storage-allocations
get /v2/farcaster/storage/allocations/
Fetches storage allocations for a given user
### Related tutorial: [Storage units allocation](/docs/getting-storage-units-allocation-of-farcaster-user)
## Node.js SDK
🔗 **SDK Method:** [lookupUserStorageAllocations](/nodejs-sdk/storage-apis/lookupUserStorageAllocations)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# FID's limits
Source: https://docs.neynar.com/reference/lookup-user-storage-limit
get /v1/storageLimitsByFid
Fetch a user's storage limits.
# Usage of user
Source: https://docs.neynar.com/reference/lookup-user-storage-usage
get /v2/farcaster/storage/usage/
Fetches storage usage for a given user
### Related tutorial: [Storage units allocation](/docs/getting-storage-units-allocation-of-farcaster-user)
## Node.js SDK
🔗 **SDK Method:** [lookupUserStorageUsage](/nodejs-sdk/storage-apis/lookupUserStorageUsage)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# By X username
Source: https://docs.neynar.com/reference/lookup-users-by-x-username
get /v2/farcaster/user/by_x_username/
Fetches the users who have verified the specified X (Twitter) username
## Node.js SDK
🔗 **SDK Method:** [lookupUsersByXUsername](/nodejs-sdk/user-apis/lookupUsersByXUsername)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Fetch a webhook
Source: https://docs.neynar.com/reference/lookup-webhook
get /v2/farcaster/webhook/
Fetch a webhook
### Related tutorial: [Programmatic webhooks](/docs/how-to-create-webhooks-on-the-go-using-the-sdk)
## Node.js SDK
🔗 **SDK Method:** [lookupWebhook](/nodejs-sdk/webhook-apis/lookupWebhook)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Mark as seen
Source: https://docs.neynar.com/reference/mark-notifications-as-seen
post /v2/farcaster/notifications/seen/
Mark notifications as seen.
You can choose one of two authorization methods, either:
1. Provide a valid signer_uuid in the request body (Most common)
2. Provide a valid, signed "Bearer" token in the request's `Authorization` header similar to the
approach described [here](https://docs.farcaster.xyz/reference/warpcast/api#authentication)
## Node.js SDK
🔗 **SDK Method:** [markNotificationsAsSeen](/nodejs-sdk/notification-apis/markNotificationsAsSeen)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Using Github Copilot
Source: https://docs.neynar.com/reference/migrate-to-neynar-nodejs-sdk-v2-using-github-copilot
## Install the latest version of SDK
```shell yarn theme={"system"}
yarn add @neynar/nodejs-sdk
```
```shell npm theme={"system"}
npm install @neynar/nodejs-sdk
```
## Open Edit with copilot
Click on copilot on the bottom right in vs-code you'll see the following menu
## Add files
Navigate to `node_modules/@neynar/nodejs-sdk/v1-to-v2-migration.md` and add the file to the working set
Search for `@neynar/nodejs-sdk`in the entire project, add all the files to the working set that uses SDK methods. (You can also drag and drop files in the copilot window to add them.)
You should see something like this
Choose an AI agent (we recommend Claude) and add the following prompt.
```bash theme={"system"}
I need help migrating code from Neynar SDK v1 to v2. Here are the specific details about my code that you need to analyze and update:
1. Please scan through my code and identify any:
- Method names that have been removed, renamed, or updated to v2 API
- Changes in enum names or enum key formats
- Changes in import paths
- Changes in method argument formats
- Changes in response structures
2. For each piece of code you analyze, please:
- Show the existing v1 code
- Provide the updated v2 code
- Highlight any breaking changes in the response structure
- Note any additional considerations or best practices
3. Key Migration Rules to Apply:
- All v1 API methods have been removed and must be replaced with v2 alternatives
- All method arguments should now use key-value pairs format
- Update enum imports to use '@neynar/nodejs-sdk/build/api'
- Update renamed enums and their key formats
- Consider response structure changes in the new methods
- Handle changes in client initialization
4. When showing code changes, please:
- Include necessary import statements
- Add comments explaining key changes
- Highlight any breaking changes that might affect dependent code
5. Reference Information:
- API endpoint changes and new parameters
- Response structure modifications
- Required vs optional parameters
- Type changes
- Error handling differences
Please analyze my code and provide detailed, step-by-step guidance for updating it to be compatible with Neynar SDK v2.
I need to know exactly how to update it to v2, including all necessary changes to imports, method names, parameters, and response handling.
```
With this, you should get most of the code changes correctly replaced but please verify it once. The only place where AI can make mistakes in code replacement is where [v1 API methods are used which are completely removed from the v2 SDK.](/reference/neynar-nodejs-sdk-v1-to-v2-migration-guide#removed-methods-and-changes-in-method-names) This is because the response structure is changed in v2 APIs.
# Neynar API Overview
Source: https://docs.neynar.com/reference/neynar-farcaster-api-overview
Neynar API overview
### Response objects
* v1 APIs response objects were built to have the same response object format as Warpcast APIs
* v2 APIs offer new functionality and differ from Warpcast API response objects
### `v1` vs `v2` APIs
### Important Deprecation Notice
All v1 APIs (`/v1/farcaster/*`) will be fully turned off on March 31, 2025. If you're currently using v1 APIs, please migrate to their v2 counterparts as soon as possible to ensure uninterrupted service. All deprecated v1 APIs have a better v2 substitute, reach out if you see anything missing.
Both APIs return data available on hubs. Data that is not on the protocol is usually not available on the APIs.
# SDK v1 to v2 migration guide
Source: https://docs.neynar.com/reference/neynar-nodejs-sdk-v1-to-v2-migration-guide
Most of the migration can be done quickly with AI: [Using github copilot](/reference/migrate-to-neynar-nodejs-sdk-v2-using-github-copilot)
## Installation
```bash Shell theme={"system"}
yarn add @neynar/nodejs-sdk
```
OR
```bash Shell theme={"system"}
npm install @neynar/nodejs-sdk
```
## Client Initialization
**v1**
```typescript Typescript theme={"system"}
import { NeynarAPIClient } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient("API_KEY", {
baseOptions: {
headers: {
"x-neynar-experimental": true,
},
},
});
```
**v2**
```typescript Typescript theme={"system"}
import { NeynarAPIClient, Configuration } from "@neynar/nodejs-sdk";
const config = new Configuration({
apiKey: "API_KEY",
baseOptions: {
headers: {
"x-neynar-experimental": true,
},
},
});
const client = new NeynarAPIClient(config);
```
## Removed Methods and Changes in Method Names
All Neynar API v1-related methods have been removed from SDK v2. This version of the SDK will only support Neynar API v2.
### Removed Methods
The following methods have been removed entirely from SDK v2:
| **Removed Method** | **Replacement** |
| ----------------------------------- | ------------------------------------------------------------------------------------------------- |
| `fetchRecentUsers` | Use [webhook](/docs/how-to-setup-webhooks-from-the-dashboard) or [kafka](/docs/from-kafka-stream) |
| `fetchAllCastsLikedByUser` | [`fetchUserReactions`](#fetchuserreactions) |
| `lookupUserByFid` | [`fetchBulkUsers`](#fetchbulkusers) |
| `lookupCustodyAddressForUser` | [`fetchBulkUsers`](#fetchbulkusers) |
| `lookUpCastByHash` | [`lookUpCastByHashOrWarpcastUrl`](#lookupcastbyhashorwarpcasturl) |
| `fetchAllCastsInThread` | [`lookupCastConversation`](#lookupcastconversation) |
| `fetchAllCastsCreatedByUser` | [`fetchCastsForUser`](#fetchcastsforuser) |
| `fetchRecentCasts` | Use [webhook](/docs/how-to-setup-webhooks-from-the-dashboard) or [kafka](/docs/from-kafka-stream) |
| `fetchUserVerifications` | [`fetchBulkUsers`](#fetchbulkusers) |
| `lookupUserByVerification` | [`fetchBulkUsersByEthOrSolAddress`](#fetchbulkusersbyethereumaddress) |
| `fetchMentionAndReplyNotifications` | [`fetchAllNotifications`](#fetchallnotifications) |
| `fetchUserLikesAndRecasts` | [`fetchUserReactions`](#fetchuserreactions) |
Checkout [Affected v1 API Methods](#affected-v1-api-methods) on how to replace it.
### Renamed Methods
Several methods in SDK v2 have been renamed for consistency and clarity:
| v1 Method Name | v2 Method Name |
| --------------------------------- | --------------------------------- |
| `lookUpCastByHashOrWarpcastUrl` | `lookupCastByHashOrWarpcastUrl` |
| `publishReactionToCast` | `publishReaction` |
| `deleteReactionFromCast` | `deleteReaction` |
| `fetchReactionsForCast` | `fetchCastReactions` |
| `fetchBulkUsersByEthereumAddress` | `fetchBulkUsersByEthOrSolAddress` |
#### Methods Updated to v2 API
These methods retain the original method names but now use the v2 version of the neynar API:
| v1 Method Name | v2 Method Name |
| ------------------------ | ---------------------- |
| `fetchUserFollowersV2` | `fetchUserFollowers` |
| `fetchUserFollowingV2` | `fetchUserFollowing` |
| `lookupUserByUsernameV2` | `lookupUserByUsername` |
## Enum Changes
### Renamed enums
The following enums have been renamed in SDK v2 to align with the updated naming conventions:
| v1 Enum Name | v2 Enum Name |
| ------------------------ | ------------------------------------- |
| `TimeWindow` | `FetchTrendingChannelsTimeWindowEnum` |
| `TrendingFeedTimeWindow` | `FetchTrendingFeedTimeWindowEnum` |
| `BulkCastsSortType` | `FetchBulkCastsSortTypeEnum` |
| `BulkUserAddressTypes` | `BulkUserAddressType` |
### Enum Key Changes
Certain enum keys have been modified in SDK v2. If you were using the following enums, be aware that their key formats may have changed:
* `NotificationType`
* `ValidateFrameAggregateWindow`
* `FetchTrendingChannelsTimeWindowEnum` (formerly `TimeWindow`)
* `FetchTrendingFeedTimeWindowEnum` (formerly `TrendingFeedTimeWindow`)
* `FetchBulkCastsSortTypeEnum` (formerly `BulkCastsSortType`)
* `BulkUserAddressType` (formerly `BulkUserAddressTypes`)
## Import Path Changes
All the api-related enums and schemas are now centralized and exported from `/build/api` directory instead of `/build/neynar-api/v2/*`
```typescript Typescript theme={"system"}
import {CastParamType, NotificationTypeEnum, User, Cast, ...etc } from '@neynar/nodejs-sdk/build/api'
```
Imports for following `isApiErrorResponse` utility function and Webhook interfaces remains the same
```typescript Typescript theme={"system"}
import { isApiErrorResponse, WebhookFollowCreated, WebhookFollowDeleted, WebhookReactionCreated, WebhookReactionDeleted, WebhookCastCreated, WebhookUserCreated, WebhookUserUpdated } form '@neynar/nodejs-sdk'
```
## Affected v1 API Methods
The following methods have been completely removed in SDK v2 (Ref. [Removed Methods](#removed-methods)). As a result, the response structure will be different in the new methods that replace the deprecated v1 methods.
### `fetchAllCastsLikedByUser` (Use `fetchUserReactions`)
`fetchAllCastsLikedByUser`
```typescript Typescript theme={"system"}
const fid = 3;
const viewerFid = 2;
const limit = 50;
client
.fetchAllCastsLikedByUser(fid, {
viewerFid,
limit,
})
.then((response) => {
const { likes, reactor, next } = response.result;
console.log("likes", likes); // likes.reaction, likes.cast, likes.cast_author
console.log("reactor", reactor);
console.log("nextCursor", next.cursor);
});
```
`fetchUserReactions`
```typescript Typescript theme={"system"}
import { ReactionsType } from "@neynar/nodejs-sdk/build/api";
const fid = 3;
const viewerFid = 2;
const limit = 50;
const type = ReactionsType.Likes;
client.fetchUserReactions({ fid, type, viewerFid, limit }).then((response) => {
const { reactions } = response; // This structure is changed
console.log("likes", reactions);
});
```
### `lookupUserByFid` (Use `fetchBulkUsers`)
`lookupUserByFid`
```typescript Typescript theme={"system"}
const fid = 19960;
const viewerFid = 194;
client.lookupUserByFid(fid, viewerFid).then((response) => {
const { user } = response.result;
console.log("user", user);
});
```
`fetchBulkUsers`
```typescript Typescript theme={"system"}
const fid = 3;
const viewerFid = 2;
client.fetchBulkUsers({ fids: [fid], viewerFid }).then((res) => {
const { users } = res;
console.log("user", users[0]); // This structure is changed
});
```
### `lookupCustodyAddressForUser` (Use `fetchBulkUsers`)
`lookupCustodyAddressForUser`
```typescript Typescript theme={"system"}
const fid = 19960;
client.lookupCustodyAddressForUser(fid).then((response) => {
const { fid, custodyAddress } = response.result;
console.log("fid:", fid);
console.log("custodyAddress:", custodyAddress);
});
```
`fetchBulkUsers`
```typescript Typescript theme={"system"}
const fid = 19960;
client.fetchBulkUsers({ fids: [fid] }).then((res) => {
const { users } = res;
console.log("fid:", users[0].fid);
console.log("custodyAddress", users[0].custody_address);
});
```
### `lookUpCastByHash` (Use `lookupCastByHashOrWarpcastUrl`)
`lookUpCastByHash`
```typescript Typescript theme={"system"}
const hash = "0xfe90f9de682273e05b201629ad2338bdcd89b6be";
const viewerFid = 3;
client
.lookUpCastByHash(hash, {
viewerFid,
})
.then((response) => {
const { cast } = response.result;
console.log(cast);
});
```
`lookupCastByHashOrWarpcastUrl`
```typescript Typescript theme={"system"}
import { CastParamType } from "@neynar/nodejs-sdk/build/api";
const hash = "0xfe90f9de682273e05b201629ad2338bdcd89b6be";
const viewerFid = 3;
const type = CastParamType.Hash;
client
.lookupCastByHashOrWarpcastUrl({
identifier: hash,
type,
viewerFid,
})
.then((response) => {
const { cast } = response;
console.log("cast", cast); // This structure is changed
});
```
### `fetchAllCastsInThread` (Use `lookupCastConversation`)
`fetchAllCastsInThread`
```typescript Typescript theme={"system"}
const hash = "0xfe90f9de682273e05b201629ad2338bdcd89b6be";
const viewerFid = 3;
client.fetchAllCastsInThread(hash, viewerFid).then((response) => {
const { casts } = response.result;
console.log("conversation", casts);
});
```
`lookupCastConversation`
```typescript Typescript theme={"system"}
import { CastParamType } from "@neynar/nodejs-sdk/build/api";
const hash = "0xfe90f9de682273e05b201629ad2338bdcd89b6be";
const viewerFid = 3;
const type = CastParamType.Hash;
client
.lookupCastConversation({
identifier: hash,
type,
viewerFid,
})
.then((response) => {
const { cast } = response.conversation;
console.log("conversation", cast); // This structure is changed
});
```
### `fetchAllCastsCreatedByUser` (Use `fetchCastsForUser`)
`fetchAllCastsCreatedByUser`
```typescript Typescript theme={"system"}
const fid = 3;
const parentUrl = "https://ethereum.org";
const viewerFid = 2;
const limit = 5;
client
.fetchAllCastsCreatedByUser(fid, {
parentUrl,
viewerFid,
limit,
})
.then((response) => {
const { casts } = response.result;
console.log("User Casts:", casts);
});
```
`fetchCastsForUser`
```typescript Typescript theme={"system"}
const fid = 3;
const parentUrl = "https://ethereum.org";
const viewerFid = 2;
const limit = 5;
client
.fetchCastsForUser({ fid, parentUrl, viewerFid, limit })
.then((response) => {
const { casts } = response;
console.log("Users casts: ", casts); // This structure is changed
});
```
### `fetchUserVerifications` (Use `fetchBulkUsers`)
`fetchUserVerifications`
```typescript Typescript theme={"system"}
const fid = 3;
client.fetchUserVerifications(fid).then((response) => {
const { fid, username, display_name, verifications } = response.result;
console.log("fid ", fid);
console.log("username ", username);
console.log("display_name ", display_name);
console.log("verifications ", verifications);
});
```
`fetchBulkUsers`
```typescript Typescript theme={"system"}
const fid = 3;
client.fetchBulkUsers({ fids: [fid] }).then((response) => {
const { fid, username, display_name, verified_addresses } = response.users[0];
console.log("fid ", fid);
console.log("username ", username);
console.log("display_name ", display_name);
console.log("verifications ", verified_addresses);
});
```
### `lookupUserByVerification` (Use `fetchBulkUsersByEthOrSolAddress`)
`lookupUserByVerification`
```typescript Typescript theme={"system"}
const address = "0x7ea5dada4021c2c625e73d2a78882e91b93c174c";
client.lookupUserByVerification(address).then((response) => {
const { user } = response.result;
console.log("User:", user);
});
```
`fetchBulkUsersByEthOrSolAddress`
```typescript Typescript theme={"system"}
import { BulkUserAddressType } from "@neynar/nodejs-sdk/build/api";
const addresses = ["0x7ea5dada4021c2c625e73d2a78882e91b93c174c"];
const addressTypes = [BulkUserAddressType.VerifiedAddress];
client
.fetchBulkUsersByEthOrSolAddress({ addresses, addressTypes })
.then((response) => {
const user = response[addresses[0]];
console.log("User:", user[0]); // This structure is changed
});
```
### `fetchMentionAndReplyNotifications` (Use `fetchAllNotifications`)
`fetchMentionAndReplyNotifications`
```typescript Typescript theme={"system"}
const fid = 3;
const viewerFid = 2;
client
.fetchMentionAndReplyNotifications(fid, {
viewerFid,
})
.then((response) => {
console.log("Notifications:", response.result);
});
```
`fetchAllNotifications`
```typescript Typescript theme={"system"}
const fid = 3;
client.fetchAllNotifications({ fid }).then((response) => {
console.log("response:", response); // Returns notifications including mentions, replies, likes, and quotes
});
```
### `fetchUserLikesAndRecasts` (Use `fetchUserReactions`)
`fetchUserLikesAndRecasts`
```typescript Typescript theme={"system"}
const fid = 12345;
const viewerFid = 67890;
const limit = 5;
client
.fetchUserLikesAndRecasts(fid, {
viewerFid,
limit,
})
.then((response) => {
const { notifications } = response.result;
console.log("User Reactions : ", notifications);
});
```
`fetchUserReactions`
```typescript Typescript theme={"system"}
import { ReactionsType } from "@neynar/nodejs-sdk/build/api";
const fid = 12345;
const viewerFid = 67890;
const limit = 5;
client
.fetchUserReactions({ fid, type: ReactionsType.All, viewerFid, limit })
.then((response) => {
const { reactions } = response;
console.log("User Reactions : ", reactions);
});
```
## Affected v2 API Methods
1. **Arguments Format**: In SDK v2, all methods now accept arguments as key-value pairs (kvargs). In SDK v1, only optional parameters were passed as key-value pairs, while required arguments were simple parameters.
### Users
#### `searchUser`
**v1**
```typescript Typescript theme={"system"}
const q = "ris";
const viewerFid = 19960;
const limit = 10;
client.searchUser(q, viewerFid, { limit }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const q = "ris";
const viewerFid = 19960;
const limit = 10;
client.searchUser({ q, viewerFid, limit }).then((response) => {
console.log("response:", response);
});
```
#### `fetchBulkUsers`
**v1**
```typescript Typescript theme={"system"}
const fids = [2, 3];
const viewerFid = 19960;
client.fetchBulkUsers(fids, { viewerFid }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fids = [2, 3];
const viewerFid = 19960;
client.fetchBulkUsers({ fids, viewerFid }).then((response) => {
console.log("response:", response);
});
```
#### `fetchBulkUsersByEthereumAddress`
**v1**
```typescript Typescript theme={"system"}
import { BulkUserAddressTypes } from "@neynar/nodejs-sdk";
const addresses = [
"0xa6a8736f18f383f1cc2d938576933e5ea7df01a1",
"0x7cac817861e5c3384753403fb6c0c556c204b1ce",
];
const addressTypes = [BulkUserAddressTypes.CUSTODY_ADDRESS];
const viewerFid = 3;
client
.fetchBulkUsersByEthereumAddress(addresses, { addressTypes, viewerFid })
.then((response) => {
console.log("response:", response);
});
```
**v2**
1. `fetchBulkUsersByEthereumAddress` is renamed to `fetchBulkUsersByEthOrSolAddress` (Ref. [Renamed Methods](#renamed-methods))
2. `BulkUserAddressTypes` is renamed to `BulkUserAddressType` (Ref. [Renamed enums](#renamed-enums))
3. Import path for `BulkUserAddressType` is changed (Ref. [Import path changes](#import-path-changes))
4. Enum key changed from `CUSTODY_ADDRESS` to `CustodyAddress` (Ref. [Enum Key Changes](#enum-key-changes))
```typescript Typescript theme={"system"}
import { BulkUserAddressType } from "@neynar/nodejs-sdk/build/api";
const addresses = [
"0xa6a8736f18f383f1cc2d938576933e5ea7df01a1",
"0x7cac817861e5c3384753403fb6c0c556c204b1ce",
];
const addressTypes = [BulkUserAddressType.CustodyAddress];
const viewerFid = 3;
client
.fetchBulkUsersByEthOrSolAddress({ addresses, addressTypes, viewerFid })
.then((response) => {
console.log("response:", response);
});
```
#### `lookupUserByCustodyAddress`
**v1**
```typescript Typescript theme={"system"}
const custodyAddress = "0xd1b702203b1b3b641a699997746bd4a12d157909";
client.lookupUserByCustodyAddress(custodyAddress).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const custodyAddress = "0xd1b702203b1b3b641a699997746bd4a12d157909";
client.lookupUserByCustodyAddress({ custodyAddress }).then((response) => {
console.log("response:", response);
});
```
#### `lookupUserByUsernameV2`
This method is renamed to `lookupUserByUsername`.
**v1**
```typescript Typescript theme={"system"}
const username = "manan";
const viewerFid = 3;
client.lookupUserByUsernameV2(username, { viewerFid }).then((response) => {
console.log("response:", response);
});
```
**v2**
`lookupUserByUsernameV2` is now renamed to `lookupUserByUsername` (Ref. [Methods Updated to v2 API](#methods-updated-to-v2-api))
```typescript Typescript theme={"system"}
const username = "manan";
const viewerFid = 3;
client.lookupUserByUsername({ username, viewerFid }).then((response) => {
console.log("response:", response);
});
```
#### `fetchUsersByLocation`
**v1**
```typescript Typescript theme={"system"}
const latitude = 37.7749;
const longitude = -122.4194;
const viewerFid = 3;
const limit = 5;
client
.fetchUsersByLocation(latitude, longitude, { viewerFid, limit })
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const latitude = 37.7749;
const longitude = -122.4194;
const viewerFid = 3;
const limit = 5;
client
.fetchUsersByLocation({ latitude, longitude, viewerFid, limit })
.then((response) => {
console.log("response:", response);
});
```
#### `fetchPopularCastsByUser`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
const viewerFid = 19960;
client.fetchPopularCastsByUser(fid, { viewerFid }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fid = 3;
const viewerFid = 19960;
client.fetchPopularCastsByUser({ fid, viewerFid }).then((response) => {
console.log("response:", response);
});
```
#### `fetchRepliesAndRecastsForUser`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
const limit = 25;
const viewerFid = 19960;
client
.fetchRepliesAndRecastsForUser(fid, { limit, viewerFid })
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fid = 3;
const limit = 25;
const viewerFid = 3;
client
.fetchRepliesAndRecastsForUser({ fid, limit, viewerFid })
.then((response) => {
console.log("response:", response);
});
```
#### `fetchCastsForUser`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
const viewerFid = 3;
const limit = 25;
const includeReplies = false;
client
.fetchCastsForUser(fid, {
limit,
viewerFid,
includeReplies,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fid = 3;
const viewerFid = 3;
const limit = 25;
const includeReplies = false;
client
.fetchCastsForUser({ fid, viewerFid, limit, includeReplies })
.then((response) => {
console.log("response:", response);
});
```
#### `followUser`
**v1**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const targetFids = [3, 2, 1];
client.followUser(signerUuid, targetFids).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const targetFids = [3, 2, 1];
client.followUser({ signerUuid, targetFids }).then((response) => {
console.log("response:", response);
});
```
#### `unfollowUser`
**v1**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const targetFids = [3, 2, 1];
client.unfollowUser(signerUuid, targetFids).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const targetFids = [3, 2, 1];
client.unfollowUser({ signerUuid, targetFids }).then((response) => {
console.log("response:", response);
});
```
#### `registerAccount`
**v1**
```typescript Typescript theme={"system"}
const signature = "signatureString";
const fid = 12345;
const requestedUserCustodyAddress = "0x123...abc";
const deadline = 1672531200;
const fname = "newUsername";
client
.registerAccount(fid, signature, requestedUserCustodyAddress, deadline, {
fname,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const signature = "signatureString";
const fid = 12345;
const requestedUserCustodyAddress = "0x123...abc";
const deadline = 1672531200;
const fname = "newUsername";
client
.registerAccount({
signature,
fid,
requestedUserCustodyAddress,
deadline,
fname,
})
.then((response) => {
console.log("response:", response);
});
```
#### `updateUser`
**v1**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const bio = "New bio here";
const pfpUrl = "https://example.com/pfp.jpg";
const username = "newUsername";
const displayName = "New Display Name";
client
.updateUser(signerUuid, {
bio,
pfpUrl,
username,
displayName,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const bio = "New bio here";
const pfpUrl = "https://example.com/pfp.jpg";
const username = "newUsername";
const displayName = "New Display Name";
client
.updateUser({ signerUuid, bio, pfpUrl, username, displayName })
.then((response) => {
console.log("response:", response);
});
```
#### `publishVerification`
**v1**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const address = "0x1ea99cbed57e4020314ba3fadd7c692d2de34d5f";
const blockHash =
"0x191905a9201170abb55f4c90a4cc968b44c1b71cdf3db2764b775c93e7e22b29";
const ethSignature =
"0x2fc09da1f4dcb723fefb91f77932c249c418c0af00c66ed92ee1f35002c80d6a1145280c9f361d207d28447f8f7463366840d3a9309036cf6954afd1fd331beb1b";
client
.publishVerification(signerUuid, address, blockHash, ethSignature)
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const address = "0x1ea99cbed57e4020314ba3fadd7c692d2de34d5f";
const blockHash =
"0x191905a9201170abb55f4c90a4cc968b44c1b71cdf3db2764b775c93e7e22b29";
const ethSignature =
"0x2fc09da1f4dcb723fefb91f77932c249c418c0af00c66ed92ee1f35002c80d6a1145280c9f361d207d28447f8f7463366840d3a9309036cf6954afd1fd331beb1b";
client
.publishVerification({ signerUuid, address, blockHash, ethSignature })
.then((response) => {
console.log("response:", response);
});
```
#### `deleteVerification`
**v1**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const address = "0x1ea99cbed57e4020314ba3fadd7c692d2de34d5f";
client.deleteVerification(signerUuid, address).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const address = "0x1ea99cbed57e4020314ba3fadd7c692d2de34d5f";
client.deleteVerification({ signerUuid, address }).then((response) => {
console.log("response:", response);
});
```
#### `fetchAuthorizationUrl`
**v1**
```typescript Typescript theme={"system"}
import { AuthorizationUrlResponseType } from "@neynar/nodejs-sdk";
const clientId = "your-client-id";
const responseType = AuthorizationUrlResponseType.Code;
client.fetchAuthorizationUrl(clientId, responseType).then((response) => {
console.log("response:", response);
});
```
**v2**
The import path for `AuthorizationUrlResponseType` is changed. (Ref. [Import path changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { AuthorizationUrlResponseType } from "@neynar/nodejs-sdk/build/api";
const clientId = "your-client-id";
const responseType = AuthorizationUrlResponseType.Code;
client.fetchAuthorizationUrl({ clientId, responseType }).then((response) => {
console.log("response:", response);
});
```
### Signer
#### `lookupSigner`
**v1**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
client.lookupSigner(signerUuid).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
client.lookupSigner({ signerUuid }).then((response) => {
console.log("response:", response);
});
```
#### `registerSignedKey`
**v1**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const signature = "0xsig_1";
const appFid = 18949;
const deadline = 1625097600;
const sponsor = {
fid: 0,
signature: `0xsig_2`,
};
client
.registerSignedKey(signerUuid, appFid, deadline, signature, { sponsor })
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const signature = "0xsig_1";
const appFid = 18949;
const deadline = 1625097600;
const sponsor = {
fid: 0,
signature: `0xsig_2`,
};
client
.registerSignedKey({ signerUuid, signature, appFid, deadline, sponsor })
.then((response) => {
console.log("response:", response);
});
```
#### `lookupDeveloperManagedSigner`
**v1**
```typescript Typescript theme={"system"}
const publicKey =
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
client.lookupDeveloperManagedSigner(publicKey).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const publicKey =
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
client.lookupDeveloperManagedSigner({ publicKey }).then((response) => {
console.log("response:", response);
});
```
#### `registerSignedKeyForDeveloperManagedSigner`
**v1**
```typescript Typescript theme={"system"}
const publicKey =
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
const signature = "0xsig_1";
const appFid = 12345;
const deadline = 1625097600;
const sponsor = {
fid: 0,
signature: `0xsig_2`,
};
client
.registerSignedKeyForDeveloperManagedSigner(
publicKey,
signature,
appFid,
deadline,
{ sponsor }
)
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const publicKey =
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
const signature = "0xsig_1";
const appFid = 12345;
const deadline = 1625097600;
const sponsor = {
fid: 0,
signature: `0xsig_2`,
};
client
.registerSignedKeyForDeveloperManagedSigner({
publicKey,
signature,
appFid,
deadline,
sponsor,
})
.then((response) => {
console.log("response:", response);
});
```
#### `publishMessageToFarcaster`
**v1**
```typescript Typescript theme={"system"}
const body = {};
client.publishMessageToFarcaster(body).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const body = {};
client.publishMessageToFarcaster({ body }).then((response) => {
console.log("response:", response);
});
```
### Cast
#### `lookUpCastByHashOrWarpcastUrl`
**v1**
```typescript Typescript theme={"system"}
import { CastParamType } from "@neynar/nodejs-sdk";
const identifier = "https://warpcast.com/rish/0x9288c1";
const type = CastParamType.Url;
const viewerFid = 3;
client
.lookUpCastByHashOrWarpcastUrl(identifier, type, { viewerFid })
.then((response) => {
console.log("response:", response);
});
```
**v2**
1. `lookUpCastByHashOrWarpcastUrl` is renamed to `lookupCastByHashOrWarpcastUrl` (Ref. [Renamed Methods](#renamed-methods))
2. The import path for `CastParamType` is changed. (Ref. [Import path changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { CastParamType } from "@neynar/nodejs-sdk/build/api";
const identifier = "https://warpcast.com/rish/0x9288c1";
const type = CastParamType.Url;
const viewerFid = 3;
client
.lookupCastByHashOrWarpcastUrl({ identifier, type, viewerFid })
.then((response) => {
console.log("response:", response);
});
```
#### `publishCast`
**v1**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const text = "Testing publishCast() method";
const embeds = [
{
url: "https://warpcast.com/harper.eth/0x3c974d78",
},
];
const replyTo = "0x9e95c380791fce11ffbb14b2ea458b233161bafd";
const idem = "my-cast-idem";
const parent_author_fid = 6131;
client
.publishCast(signerUuid, text, {
replyTo,
idem,
embeds,
parent_author_fid,
})
.then((response) => {
console.log("cast:", response);
});
```
**v2**
1. `replyTo` param is now renamed to `parent`
2. `parent_author_fid` is now cam camelCase (`parentAuthorFid`)
3. sdk v1 `response` object is sdk v2 `response.cast` object
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const text = "Testing publishCast() method";
const embeds = [
{
url: "https://warpcast.com/harper.eth/0x3c974d78",
},
];
const replyTo = "0x9e95c380791fce11ffbb14b2ea458b233161bafd";
const idem = "my-cast-idem";
const parentAuthorFid = 6131;
client
.publishCast({
signerUuid,
text,
embeds,
parent: replyTo,
idem,
parentAuthorFid,
})
.then((response) => {
console.log("cast:", response.cast);
});
```
#### `deleteCast`
**v1**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const targetHash = "0x1ea99cbed57e4020314ba3fadd7c692d2de34d5f";
client.deleteCast(signerUuid, targetHash).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const targetHash = "0x1ea99cbed57e4020314ba3fadd7c692d2de34d5f";
client.deleteCast({ signerUuid, targetHash }).then((response) => {
console.log("response:", response);
});
```
#### `fetchBulkCasts`
**v1**
```typescript Typescript theme={"system"}
import { BulkCastsSortType } from "@neynar/nodejs-sdk";
const casts = [
"0xa896906a5e397b4fec247c3ee0e9e4d4990b8004",
"0x27ff810f7f718afd8c40be236411f017982e0994",
];
const viewerFid = 3;
const sortType = BulkCastsSortType.LIKES;
client
.fetchBulkCasts(casts, {
viewerFid,
sortType,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
1. `BulkCastsSortType` is renamed to `FetchBulkCastsSortTypeEnum` (Ref. [Renamed enums](#renamed-enums))
2. Enum key is changed now `LIKES` is `Likes` (Ref. [Enum Key Changes](#enum-key-changes))
```typescript Typescript theme={"system"}
import { FetchBulkCastsSortTypeEnum } from "@neynar/nodejs-sdk";
const casts = [
"0xa896906a5e397b4fec247c3ee0e9e4d4990b8004",
"0x27ff810f7f718afd8c40be236411f017982e0994",
];
const viewerFid = 3;
const sortType = FetchBulkCastsSortTypeEnum.LIKES;
client.fetchBulkCasts({ casts, viewerFid, sortType }).then((response) => {
console.log("response:", response);
});
```
#### `searchCasts`
**v1**
```typescript Typescript theme={"system"}
const q = "We are releasing a v2 of our nodejs sdk.";
const authorFid = 19960;
const viewerFid = 3;
const limit = 3;
client.searchCasts(q, { authorFid, viewerFid, limit }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const q = "We are releasing a v2 of our nodejs sdk.";
const authorFid = 19960;
const viewerFid = 3;
const limit = 3;
client.searchCasts({ q, authorFid, viewerFid, limit }).then((response) => {
console.log("response:", response);
});
```
#### `lookupCastConversation`
**v1**
```typescript Typescript theme={"system"}
import { CastParamType } from "@neynar/nodejs-sdk";
const identifier = "https://warpcast.com/rish/0x9288c1";
const type = CastParamType.Url;
const replyDepth = 2;
const includeChronologicalParentCasts = true;
const viewerFid = 3;
const fold = "above";
const limit = 2;
client
.lookupCastConversation(
"https://warpcast.com/rish/0x9288c1",
CastParamType.Url,
{
replyDepth,
includeChronologicalParentCasts,
fold,
viewerFid,
limit,
}
)
.then((response) => {
console.log("response:", response);
});
```
**v2**
The import path for `CastParamType` is changed. (Ref. [Import path changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { CastParamType } from "@neynar/nodejs-sdk/build/api";
const identifier = "https://warpcast.com/rish/0x9288c1";
const type = CastParamType.Url;
const replyDepth = 2;
const includeChronologicalParentCasts = true;
const viewerFid = 3;
const fold = "above";
const limit = 2;
client
.lookupCastConversation({
identifier,
type,
replyDepth,
includeChronologicalParentCasts,
viewerFid,
fold,
limit,
})
.then((response) => {
console.log("response:", response);
});
```
#### `fetchComposerActions`
**v1**
```typescript Typescript theme={"system"}
import { CastComposerType } from "@neynar/nodejs-sdk/neynar-api/v2";
const list = CastComposerType.Top;
const limit = 25;
client.fetchComposerActions(list, { limit }).then((response) => {
console.log("response:", response);
});
```
**v2**
The import path for `CastComposerType` is changed. (Ref. [Import path changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { CastComposerType } from "@neynar/nodejs-sdk/build/api";
const list = CastComposerType.Top;
const limit = 25;
client.fetchComposerActions({ list, limit }).then((response) => {
console.log("response:", response);
});
```
### Feed
#### `fetchUserFollowingFeed`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
const viewerFid = 100;
const withRecasts = true;
const limit = 30;
client
.fetchUserFollowingFeed(fid, {
withRecasts,
limit,
viewerFid,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fid = 3;
const viewerFid = 100;
const withRecasts = true;
const limit = 30;
client
.fetchUserFollowingFeed({ fid, viewerFid, withRecasts, limit })
.then((response) => {
console.log("response:", response);
});
```
#### `fetchFeedForYou`
**v1**
```typescript Typescript theme={"system"}
import { ForYouProvider } from "@neynar/nodejs-sdk/neynar-api/v2";
const fid = 3;
const viewerFid = 10;
const provider = ForYouProvider.Mbd;
const limit = 20;
const providerMetadata = encodeURIComponent(
JSON.stringify({
filters: {
channels: ["https://farcaster.group/founders"],
},
})
);
client
.fetchFeedForYou(fid, {
limit,
viewerFid,
provider,
providerMetadata: providerMetadata,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
The import path for `ForYouProvider` is changed. (Ref. [Import path changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { ForYouProvider } from "@neynar/nodejs-sdk/build/api";
const fid = 3;
const viewerFid = 10;
const provider = ForYouProvider.Mbd;
const limit = 20;
const providerMetadata = encodeURIComponent(
JSON.stringify({
filters: {
channels: ["https://farcaster.group/founders"],
},
})
);
client
.fetchFeedForYou({ fid, viewerFid, provider, limit, providerMetadata })
.then((response) => {
console.log("response:", response);
});
```
#### `fetchFeedByChannelIds`
**v1**
```typescript Typescript theme={"system"}
const channelIds = ["neynar", "farcaster"];
const withRecasts = true;
const viewerFid = 100;
const withReplies = true;
const limit = 30;
const shouldModerate = false;
client
.fetchFeedByChannelIds(channelIds, {
withRecasts,
withReplies,
limit,
viewerFid,
shouldModerate,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const channelIds = ["neynar", "farcaster"];
const withRecasts = true;
const viewerFid = 100;
const withReplies = true;
const limit = 30;
const shouldModerate = false;
client
.fetchFeedByChannelIds({
channelIds,
withRecasts,
viewerFid,
withReplies,
limit,
shouldModerate,
})
.then((response) => {
console.log("response:", response);
});
```
#### `fetchFeedByParentUrls`
**v1**
```typescript Typescript theme={"system"}
const parentUrls = [
"chain://eip155:1/erc721:0xd4498134211baad5846ce70ce04e7c4da78931cc",
];
const withRecasts = true;
const viewerFid = 100;
const withReplies = true;
const limit = 30;
client
.fetchFeedByParentUrls(parentUrls, {
withRecasts,
withReplies,
limit,
viewerFid,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const parentUrls = [
"chain://eip155:1/erc721:0xd4498134211baad5846ce70ce04e7c4da78931cc",
];
const withRecasts = true;
const viewerFid = 100;
const withReplies = true;
const limit = 30;
client
.fetchFeedByParentUrls({
parentUrls,
withRecasts,
viewerFid,
withReplies,
limit,
})
.then((response) => {
console.log("response:", response);
});
```
#### `fetchFeed`
**v1**
```typescript Typescript theme={"system"}
import { FeedType } from "@neynar/nodejs-sdk/neynar-api/v2";
const feedType = FeedType.Following;
const fid = 3;
const withRecasts = true;
const limit = 50;
const viewerFid = 100;
client
.fetchFeed(feedType, { fid, limit, withRecasts, viewerFid })
.then((response) => {
console.log("response:", response);
});
```
**v2**
The import path for `FeedType` is changed. (Ref. [Import path changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { FeedType } from "@neynar/nodejs-sdk/build/api";
const feedType = FeedType.Following;
const fid = 3;
const withRecasts = true;
const limit = 50;
const viewerFid = 100;
client
.fetchFeed({ feedType, fid, withRecasts, limit, viewerFid })
.then((response) => {
console.log("response:", response);
});
```
#### `fetchFramesOnlyFeed`
**v1**
```typescript Typescript theme={"system"}
const limit = 30;
const viewerFid = 3;
client.fetchFramesOnlyFeed({ limit, viewerFid }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const limit = 30;
const viewerFid = 3;
client.fetchFramesOnlyFeed({ limit, viewerFid }).then((response) => {
console.log("response:", response);
});
```
#### `fetchTrendingFeed`
**v1**
```typescript Typescript theme={"system"}
import { TrendingFeedTimeWindow } from "@neynar/nodejs-sdk";
const limit = 10;
const viewerFid = 3;
const timeWindow = TrendingFeedTimeWindow.SIX_HOUR;
const channelId = "farcaster";
const provider = "mbd";
const providerMetadata = encodeURIComponent(
JSON.stringify({
filters: {
channels: ["https://farcaster.group/founders"],
},
})
);
client
.fetchTrendingFeed({
limit,
timeWindow,
channelId,
viewerFid,
provider,
providerMetadata,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
1. `TrendingFeedTimeWindow` is renamed to `FetchTrendingFeedTimeWindowEnum` (Ref. [Renamed enums](#renamed-enums))
2. The import path is for `FetchTrendingFeedTimeWindowEnum` changed. (Ref. [Import path changes](#import-path-changes))
3. Enum Keys have changed `SIX_HOUR` to `_6h` (Ref. [Enum Key Changes](#enum-key-changes))
```typescript Typescript theme={"system"}
import { FetchTrendingFeedTimeWindowEnum } from "@neynar/nodejs-sdk/build/api";
const limit = 10;
const viewerFid = 3;
const timeWindow = FetchTrendingFeedTimeWindowEnum._6h;
const channelId = "farcaster";
const provider = "mbd";
const providerMetadata = encodeURIComponent(
JSON.stringify({
filters: {
channels: ["https://farcaster.group/founders"],
},
})
);
client
.fetchTrendingFeed({
limit,
viewerFid,
timeWindow,
channelId,
provider,
providerMetadata,
})
.then((response) => {
console.log("response:", response);
});
```
### Reaction
#### `publishReactionToCast`
**v1**
```typescript Typescript theme={"system"}
import { ReactionType } from "@neynar/nodejs-sdk";
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const reactionType = ReactionType.Like;
const target = "0x1ea99cbed57e4020314ba3fadd7c692d2de34d5f";
const idem = "my-reaction-idem";
client
.publishReactionToCast(signerUuid, reactionType, target, { idem })
.then((response) => {
console.log("response:", response);
});
```
**v2**
1. `publishReactionToCast` is renamed to `publishReaction` (Ref. [Renamed Methods](#renamed-methods))
2. The import path for `ReactionType` is changed. (Ref. [Import path changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { ReactionType } from "@neynar/nodejs-sdk/build/api";
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const reactionType = ReactionType.Like;
const target = "0x1ea99cbed57e4020314ba3fadd7c692d2de34d5f";
const idem = "my-reaction-idem";
client
.publishReaction({ signerUuid, reactionType, target, idem })
.then((response) => {
console.log("response:", response);
});
```
#### `deleteReactionFromCast`
**v1**
```typescript Typescript theme={"system"}
import { ReactionType } from "@neynar/nodejs-sdk";
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const reactionType = ReactionType.Like;
const target = "0x1ea99cbed57e4020314ba3fadd7c692d2de34d5f";
const idem = "my-reaction-idem";
client
.deleteReactionFromCast(signerUuid, reactionType, target, { idem })
.then((response) => {
console.log("response:", response);
});
```
**v2**
1. `deleteReactionFromCast` is renamed to `deleteReaction` (Ref. [Renamed Methods](#renamed-methods))
2. The import path for `ReactionType` is changed. (Ref. [Import path changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { ReactionType } from "@neynar/nodejs-sdk/build/api";
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const reactionType = ReactionType.Like;
const target = "0x1ea99cbed57e4020314ba3fadd7c692d2de34d5f";
const idem = "my-reaction-idem";
client
.deleteReaction({ signerUuid, reactionType, target, idem })
.then((response) => {
console.log("response:", response);
});
```
#### `fetchUserReactions`
**v1**
```typescript Typescript theme={"system"}
import { ReactionsType } from "@neynar/nodejs-sdk";
const fid = 3;
const type = ReactionsType.All;
const viewerFid = 19960;
const limit = 50;
client
.fetchUserReactions(fid, type, {
limit,
viewerFid,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
The import path for `ReactionsType` is changed. (Ref. [Import path changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { ReactionsType } from "@neynar/nodejs-sdk/build/api";
const fid = 3;
const type = ReactionsType.All;
const viewerFid = 19960;
const limit = 50;
client.fetchUserReactions({ fid, type, viewerFid, limit }).then((response) => {
console.log("response:", response);
});
```
#### `fetchReactionsForCast`
**v1**
```typescript Typescript theme={"system"}
import { ReactionsType } from "@neynar/nodejs-sdk";
const hash = "0xfe90f9de682273e05b201629ad2338bdcd89b6be";
const types = ReactionsType.All;
const viewerFid = 3;
const limit = 50;
client
.fetchReactionsForCast(hash, types, {
limit,
viewerFid,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
1. `fetchReactionsForCast` is now renamed to `fetchCastReactions` (Ref. [Renamed Methods](#renamed-methods))
2. The import path for `ReactionsType` is changed. (Ref. [Import path changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { ReactionsType } from "@neynar/nodejs-sdk/build/api";
const hash = "0xfe90f9de682273e05b201629ad2338bdcd89b6be";
const types = ReactionsType.All;
const viewerFid = 3;
const limit = 50;
client
.fetchCastReactions({ hash, types, viewerFid, limit })
.then((response) => {
console.log("response:", response);
});
```
### Notifications
Notifications include likes, mentions, replies, and quotes of a user's casts.
#### `fetchAllNotifications`
**v1**
```typescript Typescript theme={"system"}
import { NotificationType } from "@neynar/nodejs-sdk";
const fid = 3;
const type = NotificationType.LIKES;
const priorityMode = false;
client
.fetchAllNotifications(fid, {
type,
priorityMode,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
1. `isPriority` is removed.
2. The import path is for `NotificationType` changed. (Ref. [Import path changes](#import-path-changes))
3. Enum Keys have changed `LIKES` to `Likes` (Ref. [Enum Key Changes](#enum-key-changes))
4. Supports notification types: Likes, Mentions, Replies, and Quotes
```typescript Typescript theme={"system"}
import { NotificationType } from "@neynar/nodejs-sdk/build/api";
const fid = 3;
const type = NotificationType.Likes;
const priorityMode = false;
client.fetchAllNotifications({ fid, type, priorityMode }).then((response) => {
console.log("response:", response);
});
```
#### `fetchChannelNotificationsForUser`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
const channelIds = ["neynar", "farcaster"];
const priorityMode = false;
client
.fetchChannelNotificationsForUser(fid, channelIds, {
priorityMode,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
`isPriority` is removed.
```typescript Typescript theme={"system"}
const fid = 3;
const channelIds = ["neynar", "farcaster"];
const priorityMode = false;
client
.fetchChannelNotificationsForUser({ fid, channelIds, priorityMode })
.then((response) => {
console.log("response:", response);
});
```
#### `fetchNotificationsByParentUrlForUser`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
const parentUrls = [
"chain://eip155:1/erc721:0xd4498134211baad5846ce70ce04e7c4da78931cc",
"chain://eip155:1/erc721:0xfd8427165df67df6d7fd689ae67c8ebf56d9ca61",
];
const priorityMode = false;
client
.fetchNotificationsByParentUrlForUser(fid, parentUrls, { priorityMode })
.then((response) => {
console.log("response:", response);
});
```
**v2**
`isPriority` is removed.
```typescript Typescript theme={"system"}
const fid = 3;
const parentUrls = [
"chain://eip155:1/erc721:0xd4498134211baad5846ce70ce04e7c4da78931cc",
"chain://eip155:1/erc721:0xfd8427165df67df6d7fd689ae67c8ebf56d9ca61",
];
const priorityMode = false;
client
.fetchNotificationsByParentUrlForUser({ fid, parentUrls, priorityMode })
.then((response) => {
console.log("response:", response);
});
```
#### `markNotificationsAsSeen`
**v1**
```typescript Typescript theme={"system"}
import { NotificationType } from "@neynar/nodejs-sdk";
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const type = NotificationType.FOLLOWS;
client.markNotificationsAsSeen(signerUuid, { type }).then((response) => {
console.log("response:", response);
});
```
**v2**
1. The import path for `NotificationType` is changed. (Ref. [Import path changes](#import-path-changes))
2. Enum Keys have changed `FOLLOWS` to `Follows` (Ref. [Enum Key changes](#enum-key-changes))
3. Supported notification types include Follows, Likes, Mentions, Replies, and Quotes
```typescript Typescript theme={"system"}
import { NotificationType } from "@neynar/nodejs-sdk/build/api";
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const type = NotificationType.Follows;
client.markNotificationsAsSeen({ signerUuid, type }).then((response) => {
console.log("response:", response);
});
```
### Channel
#### `searchChannels`
**v1**
```typescript Typescript theme={"system"}
const q = ux;
const limit = 5;
client.searchChannels("ux", { limit }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const q = ux;
const limit = 5;
client.searchChannels({ q, limit }).then((response) => {
console.log("response:", response);
});
```
#### `fetchBulkChannels`
**v1**
```typescript Typescript theme={"system"}
import { ChannelType } from "@neynar/nodejs-sdk";
const ids = ["neynar", "farcaster"];
const type = ChannelType.Id;
const viewerFid = 3;
client.fetchBulkChannels(ids, { viewerFid, type }).then((response) => {
console.log("response:", response);
});
```
**v2**
The import path for `ChannelType` is changed. (Ref. [Import path changes](#import-path-changes))
```typescript Typescript theme={"system"}
const ids = ["neynar", "farcaster"];
const type = ChannelType.Id;
const viewerFid = 3;
client.fetchBulkChannels({ ids, type, viewerFid }).then((response) => {
console.log("response:", response);
});
```
#### `lookupChannel`
**v1**
```typescript Typescript theme={"system"}
import { ChannelType } from "@neynar/nodejs-sdk";
const id = "neynar";
const type = ChannelType.Id;
const viewerFid = 3;
client.lookupChannel("neynar", { viewerFid, type }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
import { ChannelType } from "@neynar/nodejs-sdk/build/api";
const id = "neynar";
const type = ChannelType.Id;
const viewerFid = 3;
client.lookupChannel({ id, type, viewerFid }).then((response) => {
console.log("response:", response);
});
```
#### `removeChannelMember`
**v1**
```typescript Typescript theme={"system"}
import { ChannelMemberRole } from "@neynar/nodejs-sdk/neynar-api/v2";
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const channelId = "neynar";
const fid = 3;
const role = ChannelMemberRole.Member;
client
.removeChannelMember(signerUuid, channelId, fid, role)
.then((response) => {
console.log("response:", response);
});
```
**v2**
The import path for `ChannelMemberRole` is changed. (Ref. [Import path changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { ChannelMemberRole } from "@neynar/nodejs-sdk/build/api";
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const channelId = "neynar";
const fid = 3;
const role = "member";
client
.removeChannelMember({ signerUuid, channelId, fid, role })
.then((response) => {
console.log("response:", response);
});
```
#### `fetchChannelMembers`
**v1**
```typescript Typescript theme={"system"}
const channelId = "neynar";
const fid = 194;
const limit = 10;
client.fetchChannelMembers(channelId, { limit, fid }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const channelId = "neynar";
const fid = 194;
const limit = 10;
client.fetchChannelMembers({ channelId, fid, limit }).then((response) => {
console.log("response:", response);
});
```
#### `inviteChannelMember`
**v1**
```typescript Typescript theme={"system"}
import { ChannelMemberRole } from "@neynar/nodejs-sdk/neynar-api/v2";
const signnerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const channelId = "neynar";
const fid = 3;
const role = ChannelMemberRole.Member;
client
.inviteChannelMember(signnerUuid, channelId, fid, role)
.then((response) => {
console.log("response:", response);
});
```
**v2**
The import path for `ChannelMemberRole` is changed. (Ref. [Import path changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { ChannelMemberRole } from "@neynar/nodejs-sdk/build/api";
const signnerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const channelId = "neynar";
const fid = 3;
const role = ChannelMemberRole.Member;
client
.inviteChannelMember({ signerUuid, channelId, fid, role })
.then((response) => {
console.log("response:", response);
});
```
#### `respondChannelInvite`
**v1**
```typescript Typescript theme={"system"}
import { ChannelMemberRole } from "@neynar/nodejs-sdk/neynar-api/v2";
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const channelId = "neynar";
const role = ChannelMemberRole.Member;
const accept = true;
client
.respondChannelInvite(signerUuid, channelId, role, accept)
.then((response) => {
console.log("response:", response);
});
```
**v2**
The import path for `ChannelMemberRole` is changed. (Ref. [Import path changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { ChannelMemberRole } from "@neynar/nodejs-sdk/build/api";
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const channelId = "neynar";
const role = ChannelMemberRole.Member;
const accept = true;
client
.respondChannelInvite({ signerUuid, channelId, role, accept })
.then((response) => {
console.log("response:", response);
});
```
#### `fetchFollowersForAChannel`
**v1**
```typescript Typescript theme={"system"}
const id = "founders";
const viewerFid = 3;
const limit = 50;
client.fetchFollowersForAChannel(id, { limit, viewerFid }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const id = "founders";
const viewerFid = 3;
const limit = 50;
client.fetchFollowersForAChannel({ id, viewerFid, limit }).then((response) => {
console.log("response:", response);
});
```
#### `fetchRelevantFollowersForAChannel`
**v1**
```typescript Typescript theme={"system"}
const id = "why";
const viewerFid = 3;
client.fetchRelevantFollowersForAChannel(id, viewerFid).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const id = "why";
const viewerFid = 3;
client.fetchRelevantFollowersForAChannel({ id, viewerFid }).then((response) => {
console.log("response:", response);
});
```
#### `fetchUserChannels`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
const limit = 5;
client.fetchUserChannels(fid, { limit }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fid = 3;
const limit = 5;
client.fetchUserChannels({ fid, limit }).then((response) => {
console.log("response:", response);
});
```
#### `fetchUserChannelMemberships`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
const limit = 10;
client.fetchUserChannelMemberships(fid, { limit }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fid = 3;
const limit = 10;
client.fetchUserChannelMemberships({ fid, limit }).then((response) => {
console.log("response:", response);
});
```
#### `followChannel`
**v1**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const channelId = "neynar";
client.followChannel(signerUuid, channelId).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const channelId = "neynar";
client.followChannel({ signerUuid, channelId }).then((response) => {
console.log("response:", response);
});
```
#### `unfollowChannel`
**v1**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const channelId = "neynar";
client.unfollowChannel(signerUuid, channelId).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const channelId = "neynar";
client.unfollowChannel({ signerUuid, channelId }).then((response) => {
console.log("response:", response);
});
```
#### `fetchTrendingChannels`
**v1**
```typescript Typescript theme={"system"}
import { TimeWindow } from "@neynar/nodejs-sdk";
const timeWindow = TimeWindow.SEVEN_DAYS;
const limit = 20;
client.fetchTrendingChannels(timeWindow, { limit }).then((response) => {
console.log("response:", response);
});
```
**v2**
1. `TimeWindow` is renamed to `FetchTrendingChannelsTimeWindowEnum` (Ref. [Renamed enums](#renamed-enums))
2. `FetchTrendingChannelsTimeWindowEnum` import is changed (Ref. [Import Path Changes](#import-path-changes))
3. Enums key is changed from `SEVEN_DAYS` to `_7d` (Ref. [Enum Key Changes](#enum-key-changes))
```typescript Typescript theme={"system"}
import { FetchTrendingChannelsTimeWindowEnum } from "@neynar/nodejs-sdk/build/api";
const timeWindow = FetchTrendingChannelsTimeWindowEnum._7d;
const limit = 20;
client.fetchTrendingChannels({ timeWindow, limit }).then((response) => {
console.log("response:", response);
});
```
#### `fetchUsersActiveChannels`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
const limit = 10;
client.fetchUsersActiveChannels(fid, { limit }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fid = 3;
const limit = 10;
client.fetchUsersActiveChannels({ fid, limit }).then((response) => {
console.log("response:", response);
});
```
### Follows
#### `fetchUserFollowersV2`
**v1**
```typescript Typescript theme={"system"}
import { FollowSortType } from "@neynar/nodejs-sdk";
const fid = 3;
const viewerFid = 23;
const sortType = FollowSortType.DescChron;
const limit = 10;
client
.fetchUserFollowersV2(fid, { limit, viewerFid, sortType })
.then((response) => {
console.log("response:", response);
});
```
**v2**
1. `fetchUserFollowersV2` is now renamed to `fetchUserFollowers` (Ref. [Methods Updated to v2 API](#methods-updated-to-v2-api))
2. `FollowSortType` import is changed (Ref. [Import Path Changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { FollowSortType } from "@neynar/nodejs-sdk/build/api";
const fid = 3;
const viewerFid = 23;
const sortType = FollowSortType.DescChron;
const limit = 10;
client
.fetchUserFollowers({ fid, viewerFid, sortType, limit })
.then((response) => {
console.log("response:", response);
});
```
#### `fetchRelevantFollowers`
**v1**
```typescript Typescript theme={"system"}
const targetFid = 3;
const viewerFid = 19960;
client.fetchRelevantFollowers(targetFid, viewerFid).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const targetFid = 3;
const viewerFid = 19960;
client.fetchRelevantFollowers({ targetFid, viewerFid }).then((response) => {
console.log("response:", response);
});
```
#### `fetchUserFollowingV2`
**v1**
```typescript Typescript theme={"system"}
import { FollowSortType } from "@neynar/nodejs-sdk";
const fid = 3;
const viewerFid = 23;
const sortType = FollowSortType.DescChron;
const limit = 10;
client
.fetchUserFollowingV2(fid, { limit, viewerFid, sortType })
.then((response) => {
console.log("response:", response);
});
```
**v2**
1. `fetchUserFollowingV2` is now renamed to `fetchUserFollowing` (Ref. [Methods Updated to v2 API](#methods-updated-to-v2-api))
2. `FollowSortType` import is changed (Ref. [Import Path Changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { FollowSortType } from "@neynar/nodejs-sdk/build/api";
const fid = 3;
const viewerFid = 23;
const sortType = FollowSortType.DescChron;
const limit = 10;
client
.fetchUserFollowing({ fid, viewerFid, sortType, limit })
.then((response) => {
console.log("response:", response);
});
```
#### `fetchFollowSuggestions`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
const viewerFid = 19950;
const limit = 5;
client.fetchFollowSuggestions(fid, { limit, viewerFid }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fid = 3;
const viewerFid = 19950;
const limit = 5;
client.fetchFollowSuggestions({ fid, viewerFid, limit }).then((response) => {
console.log("response:", response);
});
```
### Storage
#### `lookupUserStorageAllocations`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
client.lookupUserStorageAllocations(fid).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fid = 3;
client.lookupUserStorageAllocations({ fid }).then((response) => {
console.log("response:", response);
});
```
#### `lookupUserStorageUsage`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
client.lookupUserStorageUsage(3).then((response) => {
console.log("User Storage Usage:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fid = 3;
client.lookupUserStorageUsage({ fid }).then((response) => {
console.log("response:", response);
});
```
#### `buyStorage`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
const units = 1;
const idem = "some_random_unique_key";
client.buyStorage(fid, { units, idem }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fid = 3;
const units = 1;
const idem = "some_random_unique_key";
client.buyStorage({ fid, units, idem }).then((response) => {
console.log("response:", response);
});
```
### Frame
#### `postFrameAction`
**v1**
```typescript Typescript theme={"system"}
const signerUuid = "signerUuid";
const castHash = "castHash";
const action = {
button: {
title: "Button Title",
index: 1,
},
frames_url: "frames Url",
post_url: "Post Url",
};
client.postFrameAction(signerUuid, castHash, action).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const signerUuid = "signerUuid";
const castHash = "castHash";
const action = {
button: {
title: "Button Title",
index: 1,
},
frames_url: "frames Url",
post_url: "Post Url",
};
client.postFrameAction({ signerUuid, castHash, action }).then((response) => {
console.log("response:", response);
});
```
#### `validateFrameAction`
**v1**
```typescript Typescript theme={"system"}
const messageBytesInHex = "messageBytesInHex";
const castReactionContext = false;
const followContext = true;
const signerContext = true;
const channelFollowContext = true;
client
.validateFrameAction(messageBytesInHex, {
castReactionContext,
followContext,
signerContext,
channelFollowContext,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const messageBytesInHex = "messageBytesInHex";
const castReactionContext = false;
const followContext = true;
const signerContext = true;
const channelFollowContext = true;
client
.validateFrameAction({
messageBytesInHex,
castReactionContext,
followContext,
signerContext,
channelFollowContext,
})
.then((response) => {
console.log("response:", response);
});
```
#### `fetchValidateFrameAnalytics`
**v1**
```typescript Typescript theme={"system"}
import {
ValidateFrameAnalyticsType,
ValidateFrameAggregateWindow,
} from "@neynar/nodejs-sdk";
const frameUrl = "https://shorturl.at/bDRY9";
const analyticsType = ValidateFrameAnalyticsType.InteractionsPerCast;
const start = "2024-04-06T06:44:56.811Z";
const stop = "2024-04-08T06:44:56.811Z";
const aggregateWindow = ValidateFrameAggregateWindow.TWELVE_HOURS;
client
.fetchValidateFrameAnalytics(frameUrl, analyticsType, start, stop, {
aggregateWindow,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
1. Import for `ValidateFrameAnalyticsType` and `ValidateFrameAggregateWindow` is changed (Ref. [Import Path Changes](#import-path-changes))
2. Enums key is changed from `TWELVE_HOURS` to `_12h` (Ref. [Enum Key Changes](#enum-key-changes))
```typescript Typescript theme={"system"}
import {
ValidateFrameAnalyticsType,
ValidateFrameAggregateWindow,
} from "@neynar/nodejs-sdk/build/api";
const frameUrl = "https://shorturl.at/bDRY9";
const analyticsType = ValidateFrameAnalyticsType.InteractionsPerCast;
const start = "2024-04-06T06:44:56.811Z";
const stop = "2024-04-08T06:44:56.811Z";
const aggregateWindow = ValidateFrameAggregateWindow._12h;
client
.fetchValidateFrameAnalytics({
frameUrl,
analyticsType,
start,
stop,
aggregateWindow,
})
.then((response) => {
console.log("response:", response);
});
```
#### `lookupNeynarFrame`
**v1**
```typescript Typescript theme={"system"}
import { FrameType } from "@neynar/nodejs-sdk";
const type = FrameType.Uuid;
const uuid = "your-frame-uuid";
client.lookupNeynarFrame(uuid, { type }).then((response) => {
console.log("response:", response);
});
```
**v2**
Import for `FrameType` is changed (Ref. [Import Path Changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { FrameType } from "@neynar/nodejs-sdk/build/api";
const type = FrameType.Uuid;
const uuid = "your-frame-uuid";
client.lookupNeynarFrame({ type, uuid }).then((response) => {
console.log("response:", response);
});
```
#### `deleteNeynarFrame`
**v1**
```typescript Typescript theme={"system"}
const uuid = "your-frame-uuid";
client.deleteNeynarFrame(uuid).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const uuid = "your-frame-uuid";
client.deleteNeynarFrame({ uuid }).then((response) => {
console.log("response:", response);
});
```
#### `fetchFrameMetaTagsFromUrl`
**v1**
```typescript Typescript theme={"system"}
const url = "https://frames.neynar.com/f/862277df/ff7be6a4";
client.fetchFrameMetaTagsFromUrl(url).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const url = "https://frames.neynar.com/f/862277df/ff7be6a4";
client.fetchFrameMetaTagsFromUrl({ url }).then((response) => {
console.log("response:", response);
});
```
#### `postFrameActionDeveloperManaged`
**v1**
```typescript Typescript theme={"system"}
const action = // Example action
const signature_packet = // Example signature packet
const castHash = "castHash";
client
.postFrameDeveloperManagedAction(action, signature_packet, {
castHash: castHash,
})
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const action = // Example action
const signature_packet = // Example signature packet
const castHash = "castHash";
client.postFrameActionDeveloperManaged({castHash, action, signaturePacket}).then(response => {
console.log('response:', response);
});
```
#### fname
#### `isFnameAvailable`
**v1**
```typescript Typescript theme={"system"}
const fname = "shreyas-chorge";
client.isFnameAvailable(fname).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fname = "shreyas-chorge";
client.isFnameAvailable({ fname }).then((response) => {
console.log("response:", response);
});
```
### Webhook
#### `lookupWebhook`
**v1**
```typescript Typescript theme={"system"}
const webhookId = "yourWebhookId";
client.lookupWebhook(webhookId).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const webhookId = "yourWebhookId";
client.lookupWebhook({ webhookId }).then((response) => {
console.log("response:", response);
});
```
#### `publishWebhook`
**v1**
```typescript Typescript theme={"system"}
const name = "Cast created Webhook";
const url = "https://example.com/webhook";
const subscription = {
"cast.created": {
author_fids: [3, 196, 194],
mentioned_fids: [196],
},
"user.created": {},
};
client.publishWebhook(name, url, { subscription }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const name = "Cast created Webhook";
const url = "https://example.com/webhook";
const subscription = {
"cast.created": {
author_fids: [3, 196, 194],
mentioned_fids: [196],
},
"user.created": {},
};
client.publishWebhook({ name, url, subscription }).then((response) => {
console.log("response:", response);
});
```
#### `updateWebhookActiveStatus`
**v1**
```typescript typescript theme={"system"}
const webhookId = "yourWebhookId";
const active = false;
client.updateWebhookActiveStatus(webhookId, active).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const webhookId = "yourWebhookId";
const active = false;
client.updateWebhookActiveStatus({ webhookId, active }).then((response) => {
console.log("response:", response);
});
```
#### `updateWebhook`
**v1**
```typescript Typescript theme={"system"}
const webhookId = "existingWebhookId";
const name = "UpdatedWebhookName";
const url = "https://example.com/new-webhook-url";
const subscription = {
"cast.created": {
author_fids: [2, 4, 6],
mentioned_fids: [194],
},
"user.created": {},
};
client
.updateWebhook(webhookId, name, url, { subscription })
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const webhookId = "existingWebhookId";
const name = "UpdatedWebhookName";
const url = "https://example.com/new-webhook-url";
const subscription = {
"cast.created": {
author_fids: [2, 4, 6],
mentioned_fids: [194],
},
"user.created": {},
};
client
.updateWebhook({ name, url, subscription, webhookId })
.then((response) => {
console.log("response:", response);
});
```
#### `deleteWebhook`
**v1**
```typescript Typescript theme={"system"}
const webhookId = "yourWebhookId";
client.deleteWebhook(webhookId).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const webhookId = "yourWebhookId";
client.deleteWebhook({ webhookId }).then((response) => {
console.log("response:", response);
});
```
### Action
#### `publishFarcasterAction`
**v1**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const baseUrl = "https://appb.example.com";
const action = {
type: "sendMessage",
payload: {
message: "Hello from App A!",
},
};
client.publishFarcasterAction(signerUuid, baseUrl, action).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const baseUrl = "https://appb.example.com";
const action = {
type: "sendMessage",
payload: {
message: "Hello from App A!",
},
};
client
.publishFarcasterAction({ signerUuid, baseUrl, action })
.then((response) => {
console.log("response:", response);
});
```
### Mute
#### `fetchMuteList`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
const limit = 10;
client.fetchMuteList(fid, { limit }).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fid = 3;
const limit = 10;
client.fetchMuteList({ fid, limit }).then((response) => {
console.log("response:", response);
});
```
#### `publishMute`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
const mutedFid = 19960;
client.publishMute(fid, mutedFid).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fid = 3;
const mutedFid = 19960;
client.publishMute({ fid, mutedFid }).then((response) => {
console.log("response:", response);
});
```
#### `deleteMute`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
const mutedFid = 19960;
client.deleteMute(fid, mutedFid).then((response) => {
console.log("Mute Response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fid = 3;
const mutedFid = 19960;
client.deleteMute({ fid, mutedFid }).then((response) => {
console.log("response:", response);
});
```
### Block
#### `publishBlock`
**v1**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const blockedFid = 19960;
client.publishBlock(signerUuid, blockedFid).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const blockedFid = 19960;
client.publishBlock({ signerUuid, blockedFid }).then((response) => {
console.log("response:", response);
});
```
#### `deleteBlock`
**v1**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const blockedFid = 19960;
client.deleteBlock(signerUuid, blockedFid).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const signerUuid = "19d0c5fd-9b33-4a48-a0e2-bc7b0555baec";
const blockedFid = 19960;
client.deleteBlock({ signerUuid, blockedFid }).then((response) => {
console.log("response:", response);
});
```
### Ban
#### `publishBans`
**v1**
```typescript Typescript theme={"system"}
const fids = [3, 19960];
client.publishBan(fids).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fids = [3, 19960];
client.publishBans({ fids }).then((response) => {
console.log("response:", response);
});
```
#### `deleteBans`
**v1**
```typescript Typescript theme={"system"}
const fids = [3, 19960];
client.deleteBans(fids).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fids = [3, 19960];
client.deleteBans({ fids }).then((response) => {
console.log("response:", response);
});
```
### Onchain
#### `fetchUserBalance`
**v1**
```typescript Typescript theme={"system"}
const fid = 3;
const networks = Networks.Base;
client.fetchUserBalance(fid, networks).then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const fid = 3;
const networks = Networks.Base;
client.fetchUserBalance({ fid, networks }).then((response) => {
console.log("response:", response);
});
```
#### `fetchSubscriptionsForFid`
**v1**
```typescript Typescript theme={"system"}
import { SubscriptionProvider } from "@neynar/nodejs-sdk";
const fid = 3;
const subscriptionProvider = SubscriptionProvider.FabricStp;
client.fetchSubscriptionsForFid(fid, subscriptionProvider).then((response) => {
console.log("response:", response);
});
```
**v2**
Import for `SubscriptionProvider` is changed (Ref. [Import Path Changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { SubscriptionProvider } from "@neynar/nodejs-sdk/build/api";
const fid = 3;
const subscriptionProvider = SubscriptionProvider.FabricStp;
client
.fetchSubscriptionsForFid({ fid, subscriptionProvider })
.then((response) => {
console.log("response:", response);
});
```
#### `fetchSubscribedToForFid`
**v1**
```typescript Typescript theme={"system"}
import { SubscriptionProvider } from "@neynar/nodejs-sdk";
const fid = 3;
const subscriptionProvider = SubscriptionProvider.FabricStp;
const viewerFid = 1231;
client
.fetchSubscribedToForFid(fid, subscriptionProvider, { viewerFid })
.then((response) => {
console.log("response:", response);
});
```
**v2**
Import for `SubscriptionProvider` is changed (Ref. [Import Path Changes](#import-path-changes))
```typescript Typescript theme={"system"}
import { SubscriptionProvider } from "@neynar/nodejs-sdk/build/api";
const fid = 3;
const subscriptionProvider = SubscriptionProvider.FabricStp;
const viewerFid = 1231;
client
.fetchSubscribedToForFid({ fid, subscriptionProvider, viewerFid })
.then((response) => {
console.log("response:", response);
});
```
#### `fetchSubscribersForFid`
**v1**
```typescript Typescript theme={"system"}
import { SubscriptionProvider } from "@neynar/nodejs-sdk";
const fid = 3;
const subscriptionProvider = SubscriptionProvider.FabricStp;
const viewerFid = 1231;
client
.fetchSubscribedToForFid(fid, subscriptionProvider, { viewerFid })
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
import { SubscriptionProvider } from "@neynar/nodejs-sdk/build/api";
const fid = 3;
const subscriptionProvider = SubscriptionProvider.FabricStp;
const viewerFid = 1231;
client
.fetchSubscribersForFid({ fid, subscriptionProvider, viewerFid })
.then((response) => {
console.log("response:", response);
});
```
#### `fetchSubscriptionCheck`
**v1**
```typescript Typescript theme={"system"}
const addresses = [
"0xedd3783e8c7c52b80cfbd026a63c207edc9cbee7",
"0x5a927ac639636e534b678e81768ca19e2c6280b7",
];
const contractAddress = "0x76ad4cb9ac51c09f4d9c2cadcea75c9fa9074e5b";
const chainId = "8453";
client
.fetchSubscriptionCheck(addresses, contractAddress, chainId)
.then((response) => {
console.log("response:", response);
});
```
**v2**
```typescript Typescript theme={"system"}
const addresses = [
"0xedd3783e8c7c52b80cfbd026a63c207edc9cbee7",
"0x5a927ac639636e534b678e81768ca19e2c6280b7",
];
const contractAddress = "0x76ad4cb9ac51c09f4d9c2cadcea75c9fa9074e5b";
const chainId = "8453";
client
.fetchSubscriptionCheck({ addresses, contractAddress, chainId })
.then((response) => {
console.log("response:", response);
});
```
This guide should assist in updating your existing code to SDK v2. If you encounter any issues or have further questions, please reach out to us. [Warpcast](https://warpcast.com/~/channel/neynar) [Slack](https://neynar.com/slack)
# null
Source: https://docs.neynar.com/reference/post-frame-action
post /v2/farcaster/frame/action/
To learn more about using this route on your own backend to handle frame interactions, [read our guide on Frame Interactions with the Neynar API](/docs/how-to-handle-frame-interactions-with-the-neynar-api)
The post frame action API can be used by clients to interact with a frame. You need to have an approved `signer_uuid` to post a frame action.
The POST request to the post\_url has a timeout of 5 seconds.
# null
Source: https://docs.neynar.com/reference/post-frame-action-developer-managed
post /v2/farcaster/frame/developer_managed/action/
# Mint NFT(s)
Source: https://docs.neynar.com/reference/post-nft-mint
post /v2/farcaster/nft/mint/
Mints an NFT to one or more recipients on a specified network and contract, using a configured server wallet. Contact us to set up your wallet configuration if you don't have one.
### Related tutorial: [Minting for Farcaster Users](/docs/mint-for-farcaster-users)
# null
Source: https://docs.neynar.com/reference/prompt-deployment
post /v2/studio/deployment/prompt
The Miniapp Studio API is an allowlisted API and not publicly available. [Contact the Neynar team](https://neynar.com/slack) for more information.
*This endpoint can take 5 to 10 minutes to return a response.* For incremental progress updates as the prompt is processed, use the [streaming endpoint](/reference/prompt-deployment-stream) instead.
# Prompt a deployment with streaming response
Source: https://docs.neynar.com/reference/prompt-deployment-stream
post /v2/studio/deployment/prompt/stream
Sends a prompt to a specific miniapp generator deployment and returns a streaming response using Server-Sent Events. The response is a continuous stream of Server-Sent Events, not a single JSON payload. Each event contains a JSON object with type, message, and other fields specific to the message type. Requires authentication via API key in the request header. Note: Studio CU is tracked based on LLM token usage, not per API call.
The Miniapp Studio API is an allowlisted API and not publicly available. [Contact the Neynar team](https://neynar.com/slack) for more information.
## Streaming Response Types
This endpoint uses Server-Sent Events (SSE) for streaming responses, which is why the OpenAPI specification only shows an empty 200 response. The actual streaming response returns the following message types (all exported by `@anthropic-ai/claude-code` except `ErrorMessage`):
* **SDKAssistantMessage**: Claude's responses with type `'assistant'`
* **SDKUserMessage**: User prompts with type `'user'`
* **SDKResultMessage**: Final results with type `'result'` and subtypes:
* `'success'`
* `'error_max_turns'`
* `'error_during_execution'`
* **SDKSystemMessage**: System messages with type `'system'` and subtype `'init'`
* **ErrorMessage**: Custom error type with:
* type: `'error'`
* message
* timestamp
## Node.js SDK
🔗 **SDK Method:** [promptDeploymentStream](/nodejs-sdk/studio-apis/promptDeploymentStream)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Ban FIDs from app
Source: https://docs.neynar.com/reference/publish-bans
post /v2/farcaster/ban/
Bans a list of FIDs from the app associated with your API key. Banned users, their casts and reactions will not appear in feeds.
### Related doc: [Mutes, Blocks, and Bans](/docs/mutes-blocks-and-bans)
## Node.js SDK
🔗 **SDK Method:** [publishBans](/nodejs-sdk/ban-apis/publishBans)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Block FID
Source: https://docs.neynar.com/reference/publish-block
post /v2/farcaster/block/
Adds a block for a given FID.
### Related doc: [Mutes, Blocks, and Bans](/docs/mutes-blocks-and-bans)
## Node.js SDK
🔗 **SDK Method:** [publishBlock](/nodejs-sdk/block-apis/publishBlock)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Post a cast
Source: https://docs.neynar.com/reference/publish-cast
post /v2/farcaster/cast/
Posts a cast or cast reply. Works with mentions and embeds.
(In order to post a cast `signer_uuid` must be approved)
### Easiest way to start is to clone our [repo](https://github.com/manan19/example-farcaster-app) that has sign in w/ Farcaster and writes. Ensure you're using a `signer_uuid` that was made with the same API key.
## Node.js SDK
🔗 **SDK Method:** [publishCast](/nodejs-sdk/cast-apis/publishCast)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# User actions across apps
Source: https://docs.neynar.com/reference/publish-farcaster-action
post /v2/farcaster/action/
Securely communicate and perform actions on behalf of users across different apps. It enables an app to send data or trigger actions in another app on behalf of a mutual user by signing messages using the user's Farcaster signer.
### Related tutorial: [Farcaster Actions Spec](/docs/farcaster-actions-spec)
## Node.js SDK
🔗 **SDK Method:** [publishFarcasterAction](/nodejs-sdk/action-apis/publishFarcasterAction)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Send notifications
Source: https://docs.neynar.com/reference/publish-frame-notifications
post /v2/farcaster/frame/notifications/
Send notifications to interactors of a mini app
### See guide on setting this up easily [Send Notifications to Mini App Users](/docs/send-notifications-to-mini-app-users)
## Node.js SDK
🔗 **SDK Method:** [publishFrameNotifications](/nodejs-sdk/frame-apis/publishFrameNotifications)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Submit signed message
Source: https://docs.neynar.com/reference/publish-message
post /v1/submitMessage
Submit a message to the Farcaster network.
# Publish message
Source: https://docs.neynar.com/reference/publish-message-to-farcaster
post /v2/farcaster/message/
Publish a message to farcaster. The message must be signed by a signer managed by the developer. Use the @farcaster/core library to construct and sign the message. Use the Message.toJSON method on the signed message and pass the JSON in the body of this POST request.
## Node.js SDK
🔗 **SDK Method:** [publishMessageToFarcaster](/nodejs-sdk/signer-apis/publishMessageToFarcaster)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Mute FID
Source: https://docs.neynar.com/reference/publish-mute
post /v2/farcaster/mute/
Adds a mute for a given FID. This is an allowlisted API, reach out if you want access.
### Related doc: [Mutes, Blocks, and Bans](/docs/mutes-blocks-and-bans)
## Node.js SDK
🔗 **SDK Method:** [publishMute](/nodejs-sdk/mute-apis/publishMute)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# null
Source: https://docs.neynar.com/reference/publish-neynar-frame
post /v2/farcaster/frame/
### Related doc: [Dynamic frame creation](/docs/how-to-create-frames-using-the-neynar-sdk)
# Post a reaction
Source: https://docs.neynar.com/reference/publish-reaction
post /v2/farcaster/reaction/
Post a reaction (like or recast) to a given cast
(In order to post a reaction `signer_uuid` must be approved)
### Related tutorial: [Like and recast](/docs/liking-and-recasting-with-neynar-sdk)
## Node.js SDK
🔗 **SDK Method:** [publishReaction](/nodejs-sdk/reaction-apis/publishReaction)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Add verification
Source: https://docs.neynar.com/reference/publish-verification
post /v2/farcaster/user/verification/
Adds verification for an eth address or contract for the user
(In order to add verification `signer_uuid` must be approved)
## Node.js SDK
🔗 **SDK Method:** [publishVerification](/nodejs-sdk/user-apis/publishVerification)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Create a webhook
Source: https://docs.neynar.com/reference/publish-webhook
post /v2/farcaster/webhook/
Create a webhook
### Related tutorial: [Programmatic webhooks](/docs/how-to-create-webhooks-on-the-go-using-the-sdk)
## Node.js SDK
🔗 **SDK Method:** [publishWebhook](/nodejs-sdk/webhook-apis/publishWebhook)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Quickstart
Source: https://docs.neynar.com/reference/quickstart
Getting started with the Neynar Farcaster API
# Introduction
Neynar provides APIs to easily interact with the [Farcaster](https://www.farcaster.xyz) decentralized social protocol. These APIs let you query user data, social graphs, and content (casts), enabling you to build decentralized social apps quickly.
## Core Concepts
Farcaster has a few key primitives you'll interact with via the Neynar API:
* **User** — Each user has a permanent **FID** (Farcaster ID) that identifies them. Profile details such as username, display name, bio, and linked accounts are stored on-chain and mapped to this FID.
* **Cast** — A unit of content posted to Farcaster, similar to a tweet on X. Each cast has a unique **hash** and may contain text, media, embeds, and metadata.
* **Social Graph** — The network of follower/following relationships between users.
* **Feeds** — Collections of casts, often based on a user’s social graph, channel membership, or other filters.
All data is open and decentralized, available on Farcaster hubs. Neynar abstracts away the complexity of querying these hubs.
## Base URL
All requests to the Neynar API must be made to:
```
https://api.neynar.com
```
HTTPS is required for all requests. HTTP requests will be rejected.
## Authentication
All API endpoints require an API key. Include the following header with every request:
Your Neynar API key as a string.
You can obtain your API key from the [Developer Portal](https://dev.neynar.com) after signing up.
Neynar APIs support payment per API request via [x402](https://www.x402.org/). API requests missing an API key header will see an x402 error to pay per request.
## Examples
```javascript theme={"system"}
fetch('https://api.neynar.com/v2/farcaster/user/bulk?fids=1', {
method: 'GET',
headers: {
'x-api-key': 'insert-your-api-key',
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data));
```
```bash theme={"system"}
curl -X GET "https://api.neynar.com/v2/farcaster/user/bulk?fids=1" \
-H "x-api-key: insert-your-api-key" \
-H "Content-Type: application/json"
```
```python theme={"system"}
import requests
api_key = "insert-your-api-key"
headers = {
"x-api-key": api_key,
"Content-Type": "application/json"
}
response = requests.get("https://api.neynar.com/v2/farcaster/user/bulk?fids=1", headers=headers)
data = response.json()
print(data)
```
# Register new account
Source: https://docs.neynar.com/reference/register-account
post /v2/farcaster/user/
Register account on farcaster. Optionally provide x-wallet-id header to use your own wallet.
**Note:** This API must be called within 10 minutes of the fetch FID API call (i.e., /v2/farcaster/user/fid). Otherwise, Neynar will assign this FID to another available user.
### Related documentation:
* [Create new Farcaster account](/docs/how-to-create-a-new-farcaster-account-with-neynar) - Complete tutorial
* [Managing Onchain Wallets](/docs/managing-onchain-wallets) - Wallet setup guide
* [Developer managed version](/reference/register-account-onchain) - For advanced users with developer\_managed signers
## Node.js SDK
🔗 **SDK Method:** [registerAccount](/nodejs-sdk/user-apis/registerAccount)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
## Understanding Wallet ID for Account Registration
This endpoint registers a new Farcaster account with a fetched FID. Registration includes onchain transactions for:
* Recording the FID ownership on Optimism
* Setting up initial storage allocation
* Configuring account metadata
### Wallet ID (REQUIRED)
The [`x-wallet-id` header](/docs/managing-onchain-wallets) is **required** for this endpoint. You must provide a wallet\_id to pay for the onchain registration transactions.
**Wallet Consistency Required:** You must use the **same `wallet_id`** that was used when fetching the FID via [`GET /v2/farcaster/user/fid`](/reference/get-fresh-account-fid). Using a different wallet will result in an error.
**Important Timing:** You must call this endpoint within **10 minutes** of fetching the FID via [`GET /v2/farcaster/user/fid`](/reference/get-fresh-account-fid). Otherwise, the FID may be assigned to another user.
**New to Wallet IDs?** See [Managing Onchain Wallets](/docs/managing-onchain-wallets) to create your app wallet in the developer portal and obtain your `x-wallet-id` value.
## Complete Registration Flow
### Step 1: Fetch FID
First, get a fresh FID from the shelf:
```javascript theme={"system"}
const fidResponse = await fetch('https://api.neynar.com/v2/farcaster/user/fid', {
headers: {
'x-api-key': 'YOUR_NEYNAR_API_KEY',
'x-wallet-id': 'your-wallet-id' // REQUIRED
}
});
const { fid } = await fidResponse.json();
console.log('Got FID:', fid);
```
### Step 2: Generate Signature (Client-Side)
The user must sign an EIP-712 message proving ownership of their custody address:
```javascript theme={"system"}
// This happens in the user's browser with their connected wallet
const signature = await userWallet.signTypedData({
domain: { /* ... */ },
types: { /* ... */ },
message: {
fid: fid,
to: custodyAddress,
// ... other fields
}
});
```
### Step 3: Register Account (This Endpoint)
Call this endpoint within 10 minutes of Step 1:
```javascript theme={"system"}
const registerResponse = await fetch('https://api.neynar.com/v2/farcaster/user', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_NEYNAR_API_KEY',
'x-wallet-id': 'your-wallet-id', // REQUIRED - same as Step 1
'Content-Type': 'application/json'
},
body: JSON.stringify({
fid: fid,
fname: 'username',
signature: signature,
requested_user_custody_address: custodyAddress,
// ... other required fields
})
});
const result = await registerResponse.json();
console.log('Account registered:', result);
```
## Code Examples
```javascript Node.js theme={"system"}
// Complete flow with wallet_id
const response = await fetch('https://api.neynar.com/v2/farcaster/user', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_NEYNAR_API_KEY',
'x-wallet-id': 'your-wallet-id', // REQUIRED
'Content-Type': 'application/json'
},
body: JSON.stringify({
fid: 12345,
fname: 'alice',
signature: '0x1234...', // EIP-712 signature from user
requested_user_custody_address: '0xabcd...',
timestamp: Math.floor(Date.now() / 1000),
deadline: Math.floor(Date.now() / 1000) + 3600
})
});
const result = await response.json();
console.log('Account registered:', result);
```
```bash cURL theme={"system"}
curl -X POST 'https://api.neynar.com/v2/farcaster/user' \
-H 'x-api-key: YOUR_NEYNAR_API_KEY' \
-H 'x-wallet-id: your-wallet-id' \
-H 'Content-Type: application/json' \
-d '{
"fid": 12345,
"fname": "alice",
"signature": "0x1234...",
"requested_user_custody_address": "0xabcd...",
"timestamp": 1703001234,
"deadline": 1703004834
}'
```
```python Python theme={"system"}
import requests
import time
headers = {
'x-api-key': 'YOUR_NEYNAR_API_KEY',
'x-wallet-id': 'your-wallet-id', # REQUIRED
'Content-Type': 'application/json'
}
payload = {
'fid': 12345,
'fname': 'alice',
'signature': '0x1234...',
'requested_user_custody_address': '0xabcd...',
'timestamp': int(time.time()),
'deadline': int(time.time()) + 3600
}
response = requests.post(
'https://api.neynar.com/v2/farcaster/user',
headers=headers,
json=payload
)
result = response.json()
print('Account registered:', result)
```
## What You're Paying For
When you register an account with a wallet\_id:
**Base Costs (Onchain transactions on Optimism):**
* FID registration in ID Registry
* Initial storage allocation (1 unit)
* Account configuration
**Services Included:**
* Transaction execution and monitoring
* Gas estimation and optimization
* Retry logic for failed transactions
* FID shelf management
## Error Handling
### Error: Missing Wallet ID
```json theme={"system"}
{
"code": "RequiredField",
"message": "x-wallet-id header is required"
}
```
**Solution:** Add the `x-wallet-id` header. See [Managing Onchain Wallets](/docs/managing-onchain-wallets) for setup instructions.
### Error: FID Expired
```json theme={"system"}
{
"code": "FidExpired",
"message": "The FID has expired. Please fetch a new FID and try again within 10 minutes."
}
```
**Solution:** Fetch a new FID and complete registration within 10 minutes.
### Error: Invalid Signature
```json theme={"system"}
{
"code": "InvalidSignature",
"message": "The provided signature is invalid or does not match the custody address."
}
```
**Solution:** Ensure the user properly signed the EIP-712 message with their custody address.
### Error: Insufficient Wallet Balance
```json theme={"system"}
{
"code": "InsufficientFunds",
"message": "Wallet does not have enough balance to complete this transaction."
}
```
**Solution:** Fund your wallet with more ETH on Optimism.
## Next Steps
Set username, bio, and profile picture
Enable your app to write on behalf of user
Fund your wallet for more operations
Full account creation guide with code examples
# Register Farcaster account onchain
Source: https://docs.neynar.com/reference/register-account-onchain
post /v2/farcaster/user/register/
Register a new farcaster account onchain. Optionally you can pass in signers to register a new account and create multiple signers in a single transaction. Requires x-wallet-id header.
### This API uses `developer_managed` signers
* If not using `developer_managed` signers, use [this API](/reference/register-account) to create new Farcaster accounts.
* If unsure whether you're using `developer_managed` signers, you're probably *not* using them and should use the other API.
## Node.js SDK
🔗 **SDK Method:** [registerAccountOnchain](/nodejs-sdk/onchain-apis/registerAccountOnchain)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Register New user
Source: https://docs.neynar.com/reference/register-new-user
Register new user on Farcaster protocol
Register a new user in two API calls:
1. Fetch the latest FID that a new user must be assigned
2. Generate signature for the user and call registration API
Read more about how to create new accounts: [Create a new Farcaster Account with Neynar](/docs/how-to-create-a-new-farcaster-account-with-neynar).
## Requirements
For this flow, you'll need:
* **App Wallet** - Required for all onchain operations. Create one in the [Developer Portal](https://dev.neynar.com) (self-service!)
* **API Key** - Get from [dev.neynar.com](https://dev.neynar.com)
Neynar abstracts away all onchain transactions required for account registration and storage.
**New to App Wallets?** See [Managing Onchain Wallets](/docs/managing-onchain-wallets) for step-by-step setup instructions.
# Register Signed Key
Source: https://docs.neynar.com/reference/register-signed-key
post /v2/farcaster/signer/signed_key/
Registers an app FID, deadline and a signature. Returns the signer status with an approval url.
### Easiest way to start with Neynar-managed signers
[Read more about how writes to Farcaster work with Neynar managed signers](/docs/write-to-farcaster-with-neynar-managed-signers)
## Node.js SDK
🔗 **SDK Method:** [registerSignedKey](/nodejs-sdk/signer-apis/registerSignedKey)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Register Signed Key
Source: https://docs.neynar.com/reference/register-signed-key-for-developer-managed-auth-address
post /v2/farcaster/auth_address/developer_managed/signed_key/
Allow apps to register an Ethereum addresses as authorized "auth addresses" for a user's Farcaster account, enabling seamless Sign-In With Farcaster (SIWF) across applications without repeated custody wallet authorizations.
## Node.js SDK
🔗 **SDK Method:** [registerSignedKeyForDeveloperManagedAuthAddress](/nodejs-sdk/auth-addres-apis/registerSignedKeyForDeveloperManagedAuthAddress)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Register Signed Key
Source: https://docs.neynar.com/reference/register-signed-key-for-developer-managed-signer
post /v2/farcaster/signer/developer_managed/signed_key/
Registers an signed key and returns the developer managed signer status with an approval url.
### Public key should be unique, do not reuse the public key on the same account or across accounts
## Node.js SDK
🔗 **SDK Method:** [registerSignedKeyForDeveloperManagedSigner](/nodejs-sdk/signer-apis/registerSignedKeyForDeveloperManagedSigner)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Remove user
Source: https://docs.neynar.com/reference/remove-channel-member
delete /v2/farcaster/channel/member/
Remove a user from a channel or a user's invite to a channel role
## Node.js SDK
🔗 **SDK Method:** [removeChannelMember](/nodejs-sdk/channel-apis/removeChannelMember)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Accept or reject an invite
Source: https://docs.neynar.com/reference/respond-channel-invite
put /v2/farcaster/channel/member/invite/
Accept or reject a channel invite
## Node.js SDK
🔗 **SDK Method:** [respondChannelInvite](/nodejs-sdk/channel-apis/respondChannelInvite)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Search for casts
Source: https://docs.neynar.com/reference/search-casts
get /v2/farcaster/cast/search/
Search for casts based on a query string, with optional AND filters
## Searching Casts
You can search for casts using [keywords and operators](#parameter-q) in the search query.
## Node.js SDK
🔗 **SDK Method:** [searchCasts](/nodejs-sdk/cast-apis/searchCasts)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Search by ID or name
Source: https://docs.neynar.com/reference/search-channels
get /v2/farcaster/channel/search/
Returns a list of channels based on ID or name
## Node.js SDK
🔗 **SDK Method:** [searchChannels](/nodejs-sdk/channel-apis/searchChannels)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Search mini apps
Source: https://docs.neynar.com/reference/search-frames
get /v2/farcaster/frame/search/
Search for mini apps based on a query string
## Node.js SDK
🔗 **SDK Method:** [searchFrames](/nodejs-sdk/frame-apis/searchFrames)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Search for Usernames
Source: https://docs.neynar.com/reference/search-user
get /v2/farcaster/user/search/
Search for Usernames
### See related guide: [Username search](/docs/implementing-username-search-suggestion-in-your-farcaster-app)
## Node.js SDK
🔗 **SDK Method:** [searchUser](/nodejs-sdk/user-apis/searchUser)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Send fungibles
Source: https://docs.neynar.com/reference/send-fungibles-to-users
post /v2/farcaster/fungible/send/
Send fungibles in bulk to several farcaster users. A funded wallet is to required use this API. React out to us on the Neynar channel on farcaster to get your wallet address.
### Related documentation:
* [Managing Onchain Wallets](/docs/managing-onchain-wallets) - Wallet setup guide
## Understanding Wallet ID for Fungible Transfers
This endpoint allows you to send fungible tokens (ERC-20, SPL tokens, etc.) in bulk to multiple Farcaster users. You can send tokens using their FID (Farcaster ID) instead of wallet addresses.
### Wallet ID (REQUIRED)
The [`x-wallet-id` header](/docs/managing-onchain-wallets) is **REQUIRED** for this endpoint. You must provide a funded wallet that will execute the token transfers on your behalf.
**New to Wallet IDs?** See [Managing Onchain Wallets](/docs/managing-onchain-wallets) to create your app wallet in the developer portal and obtain your `x-wallet-id` value.
## Code Examples
### Basic Token Transfer
```javascript Node.js theme={"system"}
const response = await fetch('https://api.neynar.com/v2/farcaster/fungible/send', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_NEYNAR_API_KEY',
'x-wallet-id': 'your-wallet-id', // REQUIRED
'Content-Type': 'application/json'
},
body: JSON.stringify({
token_address: '0x...', // ERC-20 contract address
network: 'base',
recipients: [
{ fid: 12345, amount: '1.5' },
{ fid: 67890, amount: '2.0' }
]
})
});
const result = await response.json();
console.log('Transfers:', result);
```
```bash cURL theme={"system"}
curl -X POST 'https://api.neynar.com/v2/farcaster/fungible/send' \
-H 'x-api-key: YOUR_NEYNAR_API_KEY' \
-H 'x-wallet-id: your-wallet-id' \
-H 'Content-Type: application/json' \
-d '{
"token_address": "0x...",
"network": "base",
"recipients": [
{"fid": 12345, "amount": "1.5"},
{"fid": 67890, "amount": "2.0"}
]
}'
```
```python Python theme={"system"}
import requests
headers = {
'x-api-key': 'YOUR_NEYNAR_API_KEY',
'x-wallet-id': 'your-wallet-id', # REQUIRED
'Content-Type': 'application/json'
}
payload = {
'token_address': '0x...',
'network': 'base',
'recipients': [
{'fid': 12345, 'amount': '1.5'},
{'fid': 67890, 'amount': '2.0'}
]
}
response = requests.post(
'https://api.neynar.com/v2/farcaster/fungible/send',
headers=headers,
json=payload
)
result = response.json()
print('Transfers:', result)
```
## Supported Networks
You can send fungibles on:
| Network | Token Standard | Native Token |
| ---------------- | ---------------- | ------------ |
| **Base** | ERC-20 | ETH |
| **Optimism** | ERC-20 | ETH |
| **Base Sepolia** | ERC-20 (testnet) | ETH |
| **Solana** | SPL | SOL |
## What You're Paying For
When you send fungibles with a wallet\_id:
* **Services Included:**
* FID to wallet address resolution
* Transaction execution and monitoring
* Gas estimation and optimization
* Retry logic for failed transactions
* Batch processing support
## Batch Transfers
Send to multiple recipients in a single API call:
```javascript Node.js theme={"system"}
const recipients = [
{ fid: 12345, amount: '1.0' },
{ fid: 23456, amount: '2.5' },
{ fid: 34567, amount: '0.5' },
{ fid: 45678, amount: '1.5' }
];
const response = await fetch('https://api.neynar.com/v2/farcaster/fungible/send', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_NEYNAR_API_KEY',
'x-wallet-id': 'your-wallet-id',
'Content-Type': 'application/json'
},
body: JSON.stringify({
token_address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
network: 'base',
recipients: recipients
})
});
const result = await response.json();
console.log(`Sent to ${result.transactions.length} recipients`);
```
**Batch Processing:** The API processes transfers in parallel for better efficiency. Each recipient gets their own transaction.
## Error Handling
### Error: Missing Wallet ID
```json theme={"system"}
{
"code": "RequiredField",
"message": "x-wallet-id header is required"
}
```
**Solution:** Add the `x-wallet-id` header. See [Managing Onchain Wallets](/docs/managing-onchain-wallets) for setup.
### Error: Invalid Wallet ID
```json theme={"system"}
{
"code": "InvalidWalletId",
"message": "The provided wallet_id is invalid or not found."
}
```
**Solution:** Verify your wallet\_id in the [Developer Portal](https://dev.neynar.com) or contact support.
### Error: Insufficient Wallet Balance
```json theme={"system"}
{
"code": "InsufficientFunds",
"message": "Wallet does not have enough balance to complete this transaction."
}
```
**Solution:** Fund your wallet with more tokens (and gas tokens) on the target network.
### Error: Insufficient Token Balance
```json theme={"system"}
{
"code": "InsufficientTokenBalance",
"message": "Wallet does not have enough of the specified token to complete transfers."
}
```
**Solution:** Ensure your wallet has enough of the token you're trying to send, plus gas tokens for fees.
## Use Cases
### Reward Community Members
```javascript theme={"system"}
// Reward top contributors with USDC
const topContributors = [
{ fid: 12345, amount: '100' }, // $100 USDC
{ fid: 23456, amount: '50' }, // $50 USDC
{ fid: 34567, amount: '25' } // $25 USDC
];
await sendFungibles({
token_address: USDC_ADDRESS,
network: 'base',
recipients: topContributors
});
```
### Airdrop Custom Tokens
```javascript theme={"system"}
// Airdrop your custom token to holders
const airdropList = users.map(user => ({
fid: user.fid,
amount: calculateAirdropAmount(user)
}));
await sendFungibles({
token_address: YOUR_TOKEN_ADDRESS,
network: 'base',
recipients: airdropList
});
```
### Pay for Services
```javascript theme={"system"}
// Pay creators for their work
await sendFungibles({
token_address: USDC_ADDRESS,
network: 'base',
recipients: [
{ fid: artistFid, amount: paymentAmount }
]
});
```
## Node.js SDK
🔗 **SDK Method:** [sendFungiblesToUsers](/nodejs-sdk/onchain-apis/sendFungiblesToUsers)
Use the Neynar Node.js SDK for typed responses and better developer experience:
```javascript theme={"system"}
import { NeynarAPIClient } from "@neynar/nodejs-sdk";
const client = new NeynarAPIClient({ apiKey: "YOUR_API_KEY" });
const result = await client.sendFungiblesToUsers({
walletId: "your-wallet-id",
tokenAddress: "0x...",
network: "base",
recipients: [
{ fid: 12345, amount: "1.0" }
]
});
```
## Best Practices
### Security
* ✅ **Validate recipients** - Verify FIDs before sending
* ✅ **Set reasonable limits** - Implement transfer caps
* ✅ **Monitor transactions** - Track all transfers
* ✅ **Handle errors gracefully** - Implement retry logic
### Operations
* ✅ **Fund wallet adequately** - Ensure sufficient token and gas balance
* ✅ **Batch when possible** - More efficient for multiple recipients
* ✅ **Test on testnet first** - Use Base Sepolia before mainnet
* ✅ **Monitor wallet balance** - Set up low balance alerts
### Cost Optimization
* ✅ **Batch transfers** - Reduce per-recipient costs
* ✅ **Choose right network** - Base often has lower gas costs than Optimism
* ✅ **Monitor gas prices** - Send during low-traffic periods when possible
* ✅ **Use efficient tokens** - Some ERC-20s are more gas-efficient than others
## Next Steps
Set up and fund your wallet for token transfers
Check token balances for Farcaster users
Mint NFTs to Farcaster users
Need help? Reach out to our team
# Unfollow a channel
Source: https://docs.neynar.com/reference/unfollow-channel
delete /v2/farcaster/channel/follow/
Unfollow a channel
## Node.js SDK
🔗 **SDK Method:** [unfollowChannel](/nodejs-sdk/channel-apis/unfollowChannel)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Unfollow user
Source: https://docs.neynar.com/reference/unfollow-user
delete /v2/farcaster/user/follow/
Unfollow a user
(In order to unfollow a user `signer_uuid` must be approved)
## Node.js SDK
🔗 **SDK Method:** [unfollowUser](/nodejs-sdk/user-apis/unfollowUser)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Update Follow / Unfollow
Source: https://docs.neynar.com/reference/update-follow-unfollow
Follow a new user or unfollow an existing user, for a given Farcaster user
Write follow / unfollow actions to the protocol
# null
Source: https://docs.neynar.com/reference/update-neynar-frame
put /v2/farcaster/frame/
### Related doc: [Dynamic frame creation](/docs/how-to-create-frames-using-the-neynar-sdk)
# Update user profile
Source: https://docs.neynar.com/reference/update-user
patch /v2/farcaster/user/
Update user profile
(In order to update user's profile `signer_uuid` must be approved)
## Node.js SDK
🔗 **SDK Method:** [updateUser](/nodejs-sdk/user-apis/updateUser)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Overview
Source: https://docs.neynar.com/reference/update-user-profile-1
Update profile information and connected addresses
Update profile details and connected addresses for a given user
# Update a webhook
Source: https://docs.neynar.com/reference/update-webhook
put /v2/farcaster/webhook/
Update a webhook
### Related tutorial: [Programmatic webhooks](/docs/how-to-create-webhooks-on-the-go-using-the-sdk)
## Node.js SDK
🔗 **SDK Method:** [updateWebhook](/nodejs-sdk/webhook-apis/updateWebhook)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# Update webhook status
Source: https://docs.neynar.com/reference/update-webhook-active-status
patch /v2/farcaster/webhook/
Update webhook active status
### Related tutorial: [Programmatic webhooks](/docs/how-to-create-webhooks-on-the-go-using-the-sdk)
## Node.js SDK
🔗 **SDK Method:** [updateWebhookActiveStatus](/nodejs-sdk/webhook-apis/updateWebhookActiveStatus)
Use this API endpoint with the Neynar Node.js SDK for typed responses and better developer experience.
# null
Source: https://docs.neynar.com/reference/validate-frame-action
post /v2/farcaster/frame/validate/
# Validate signed message
Source: https://docs.neynar.com/reference/validate-message
post /v1/validateMessage
Validate a message on the Farcaster network.
# Rate Limits
Source: https://docs.neynar.com/reference/what-are-the-rate-limits-on-neynar-apis
Rate limits are set per subscription plan
### Rate limits vs credits
Rate limits are based on *requests per min* and are fully independent of overall credits usage. Going over your credits will *not* automatically trigger rate limits.
## Rate limits are set per subscription plan
* Starter plan rate limits are 300 RPM or 5 RPS per API endpoint
* Growth plan rate limits are 600 RPM or 10 RPS per API endpoint
* Scale plan rate limits are 1200 RPM or 20 RPS per API endpoint
If you need higher limits, reach out and we can set up an enterprise plan for you.
## API-specific rate limits in RPM
This type of rate limit is triggered when you call the same API above its API-specific threshold on your plan. As an example - On the Growth plan, you can call two different APIs at 500 RPM each for a total RPM of 1000 without triggering your rate limits.
| API | Starter (RPM) | Growth ( RPM ) | Scale ( RPM ) | Enterprise |
| ------------------------------------------ | ------------- | --------------------------------- | -------------------------------- | ---------- |
| POST v2/farcaster/frame/validate | 5k | 10k | 20k | Custom |
| GET v2/farcaster/signer | 3k | 6k | 12k | Custom |
| GET v2/farcaster/signer/developer\_managed | 3k | 6k | 12k | Custom |
| GET v2/farcaster/cast/search | 60 | 120 | 240 | Custom |
| All others | 300 | 600 | 1200 | Custom |
## Global rate limits in RPM
This type of rate limit is triggered when you call any API above the global threshold on your plan.
`/validate`, `/signer` and `signer/developer_managed` APIs don't count towards global rate limits.
| API | Starter ( RPM ) | Growth ( RPM ) | Scale ( RPM ) | Enterprise |
| --------------- | ---------------------------------- | --------------------------------- | -------------------------------- | ---------- |
| Across all APIs | 500 | 1000 | 2000 | Custom |
## Cast creation rate limits
This type of rate limit depends on number of casts the account has made in a 24 hour period and its available storage.
| Casts per 24h | Available Storage Required |
| ------------- | --------------------------- |
| \< 1000 | No requirement |
| ≥ 1000 | 20% of past 24h cast volume |
## Mini app notification rate limits
See [here](/docs/send-notifications-to-mini-app-users#are-there-notification-rate-limits).
# Events
Source: https://docs.neynar.com/snapchain/datatypes/events
Events represent state changes in the Farcaster network, like new messages or contract events
Events represent state changes, like a new message or contract event.
Snapchain nodes emit events whenever they observe a state change. Clients can subscribe to a node using the [Events API](/snapchain/grpcapi/events) to get a live stream of changes.
Snapchain keeps events around for 3 days after which they are deleted to save space. To get older data, use the [GRPC](/snapchain/grpcapi/grpcapi) or [HTTP](/snapchain/httpapi/httpapi) APIs.
## HubEvent
| Field | Type | Label | Description |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ------------------------------------------------------------------ |
| type | [HubEventType](#HubEventType) | | The type of event |
| id | uint64 | | Unique identifier for the event that encodes block height ordering |
| block\_number | uint64 | | The block number when the event was created |
| shard\_index | uint32 | | The shard index where the event occurred |
| timestamp | uint64 | | The timestamp when the event was created |
| body | [MergeMessageBody](#mergemessagebody), [PruneMessageBody](#prunemessagebody), [RevokeMessageBody](#revokemessagebody), [MergeUserNameProofBody](#mergeusernameproofbody), [MergeOnChainEventBody](#mergeonchaineventbody), [MergeFailureBody](#mergefailurebody), [BlockConfirmedBody](#blockconfirmedbody) | oneOf | The event payload |
### Event ID Construction
Event IDs are constructed to ensure chronological ordering by block. The ID combines:
* The block height (when the event was created)
* A sequence number within that block
This design allows clients to determine which events occurred in the same block and their relative order, which is useful for processing events in chronological sequence.
## HubEventType
| Name | Number | Description |
| ----------------------------------------- | ------ | ----------- |
| HUB\_EVENT\_TYPE\_NONE | 0 | |
| HUB\_EVENT\_TYPE\_MERGE\_MESSAGE | 1 | |
| HUB\_EVENT\_TYPE\_PRUNE\_MESSAGE | 2 | |
| HUB\_EVENT\_TYPE\_REVOKE\_MESSAGE | 3 | |
| HUB\_EVENT\_TYPE\_MERGE\_USERNAME\_PROOF | 6 | |
| HUB\_EVENT\_TYPE\_MERGE\_ON\_CHAIN\_EVENT | 9 | |
| HUB\_EVENT\_TYPE\_MERGE\_FAILURE | 10 | |
| HUB\_EVENT\_TYPE\_BLOCK\_CONFIRMED | 11 | |
## MergeMessageBody
| Field | Type | Label | Description |
| ----------------- | ------------------- | -------- | ----------- |
| message | [Message](#Message) | | |
| deleted\_messages | [Message](#Message) | repeated | |
## MergeUserNameProofBody
| Field | Type | Label | Description |
| --------------------------------- | ------------------------------- | ----- | ----------- |
| username\_proof | [UserNameProof](#UserNameProof) | | |
| deleted\_username\_proof | [UserNameProof](#UserNameProof) | | |
| username\_proof\_message | [Message](#Message) | | |
| deleted\_username\_proof\_message | [Message](#Message) | | |
## PruneMessageBody
| Field | Type | Label | Description |
| ------- | ------------------- | ----- | ----------- |
| message | [Message](#Message) | | |
## RevokeMessageBody
| Field | Type | Label | Description |
| ------- | ------------------- | ----- | ----------- |
| message | [Message](#Message) | | |
## MergeOnChainEventBody
| Field | Type | Label | Description |
| ---------------- | ----------------------------- | ----- | ----------- |
| on\_chain\_event | [OnChainEvent](#OnChainEvent) | | |
## OnChainEvent
| Field | Type | Label | Description |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ------------------------------------ |
| type | [OnChainEventType](#OnChainEventType) | | The type of onchain event |
| chain\_id | uint32 | | The chain id for the event |
| block\_number | uint32 | | The block number for the event |
| block\_hash | bytes | | The block hash for the event |
| block\_timestamp | uint64 | | The block timestamp for the event |
| transaction\_hash | bytes | | The transaction hash for the event |
| log\_index | uint32 | | The log index for the event |
| fid | uint64 | | The fid the event is associated with |
| body | [SignerEventBody](#signereventbody), [SignerMigratedEventBody](#signermigratedeventbody), [IdRegisterEventBody](#idregistereventbody), [StorageRentEventBody](#storagerenteventbody), [TierPurchaseBody](#tierpurchasebody) | oneOf | |
| tx\_index | uint32 | | The tx index for the event |
## OnChainEventType
| Name | Number | Description |
| ----------------------------- | ------ | ----------- |
| EVENT\_TYPE\_NONE | 0 | |
| EVENT\_TYPE\_SIGNER | 1 | |
| EVENT\_TYPE\_SIGNER\_MIGRATED | 2 | |
| EVENT\_TYPE\_ID\_REGISTER | 3 | |
| EVENT\_TYPE\_STORAGE\_RENT | 4 | |
| EVENT\_TYPE\_TIER\_PURCHASE | 5 | |
## SignerEventBody
| Field | Type | Label | Description |
| -------------- | ----------------------------------- | ----- | -------------------------------------------------- |
| key | bytes | | The bytes of the public key for the signer |
| key\_type | uint32 | | The type of the key (currently only set to 1) |
| event\_type | [SignerEventType](#SignerEventType) | | The type of the signer event |
| metadata | bytes | | The metadata associated with the key |
| metadata\_type | uint32 | | The type of the metadata (currently only set to 1) |
## SignerEventType
| Name | Number | Description |
| --------------------------------- | ------ | ----------- |
| SIGNER\_EVENT\_TYPE\_NONE | 0 | |
| SIGNER\_EVENT\_TYPE\_ADD | 1 | |
| SIGNER\_EVENT\_TYPE\_REMOVE | 2 | |
| SIGNER\_EVENT\_TYPE\_ADMIN\_RESET | 3 | |
## SignerMigratedEventBody
| Field | Type | Label | Description |
| ---------- | ------ | ----- | -------------------------------------------------------- |
| migratedAt | uint32 | | The timestamp at which nodes were migrated to OP mainnet |
## IdRegisterEventBody
| Field | Type | Label | Description |
| ----------------- | ------------------------------------------- | ----- | ------------------------------------------------- |
| to | bytes | | The address the fid was registered/transferred to |
| event\_type | [IdRegisterEventType](#IdRegisterEventType) | | The type of the id register event |
| from | bytes | | The address the transfer originated from |
| recovery\_address | bytes | | The recovery address for the fid |
## IdRegisterEventType
| Name | Number | Description |
| ------------------------------------------- | ------ | ----------- |
| ID\_REGISTER\_EVENT\_TYPE\_NONE | 0 | |
| ID\_REGISTER\_EVENT\_TYPE\_REGISTER | 1 | |
| ID\_REGISTER\_EVENT\_TYPE\_TRANSFER | 2 | |
| ID\_REGISTER\_EVENT\_TYPE\_CHANGE\_RECOVERY | 3 | |
## StorageRentEventBody
| Field | Type | Label | Description |
| ------ | ------ | ----- | --------------------------------------------------------- |
| payer | bytes | | The address of the payer |
| units | uint32 | | The number of units of storage purchased |
| expiry | uint32 | | The timestamp at which these units of storage will expire |
## TierPurchaseBody
| Field | Type | Label | Description |
| --------- | ------ | ----- | ----------------------------------- |
| purchaser | bytes | | The address of the purchaser |
| tier | uint32 | | The tier number purchased |
| units | uint32 | | The number of units purchased |
| expiry | uint32 | | The timestamp at which tier expires |
## MergeFailureBody
| Field | Type | Label | Description |
| ------- | ------------------- | ----- | -------------------------------- |
| message | [Message](#Message) | | The message that failed to merge |
| error | string | | The reason for the merge failure |
## BlockConfirmedBody
| Field | Type | Label | Description |
| ------------- | ------ | ----- | ---------------------------------------- |
| block\_number | uint64 | | The block number that has been confirmed |
| block\_hash | bytes | | The hash of the confirmed block |
| shard\_index | uint32 | | The shard index of the confirmed block |
# Messages
Source: https://docs.neynar.com/snapchain/datatypes/messages
The fundamental data type in the Farcaster network - messages represent actions like casts, reactions, and verifications
A message is the fundamental data type in the Farcaster network.
When an account takes an action like casting a public message, changing its profile, or verifying an Ethereum account, it
generates a new message.
## 1. Message
The message is a protobuf that contains the data, its hash and a signature from the author.
| Field | Type | Label | Description |
| ----------------- | ----------------------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| data | [MessageData](#MessageData) | | Contents of the message. Alternatively, you can use the data\_bytes to serialize the `MessageData` |
| hash | bytes | | Hash digest of data |
| hash\_scheme | [HashScheme](#HashScheme) | | Hash scheme that produced the hash digest |
| signature | bytes | | Signature of the hash digest |
| signature\_scheme | [SignatureScheme](#SignatureScheme) | | Signature scheme that produced the signature |
| signer | bytes | | Public key or address of the key pair that produced the signature |
| data\_bytes | bytes | | Alternate to the "data" field. If you are constructing the [MessageData](#MessageData) in a programming language other than Typescript, you can use this field to serialize the `MessageData` and calculate the `hash` and `signature` on these bytes. Optional. |
### 1.1 MessageData
MessageData is a generic envelope that contains a type, fid, timestamp and network which must be present in all
Farcaster messages. It also contains a body whose type is determined by the MessageType.
| Field | Type | Label | Description |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ---------------------------------------------- |
| type | [MessageType](#MessageType) | | Type of Message contained in the body |
| fid | uint64 | | Farcaster ID of the user producing the message |
| timestamp | uint32 | | Farcaster epoch timestamp in seconds |
| network | [FarcasterNetwork](#FarcasterNetwork) | | Farcaster network the message is intended for |
| body | [CastAddBody](#CastAddBody), [CastRemoveBody](#CastRemoveBody), [ReactionBody](#ReactionBody), [VerificationAddAddressBody](#VerificationAddAddressBody), [VerificationRemoveBody](#VerificationRemoveBody), [UserDataBody](#UserDataBody), [LinkBody](#LinkBody), [UserNameProof](#UserNameProof), [FrameActionBody](#FrameActionBody), [LinkCompactStateBody](#LinkCompactStateBody) | oneOf | Properties specific to the MessageType |
### 1.2 HashScheme
Type of hashing scheme used to produce a digest of MessageData
| Name | Number | Description |
| -------------------- | ------ | -------------------------------------- |
| HASH\_SCHEME\_NONE | 0 | |
| HASH\_SCHEME\_BLAKE3 | 1 | Default scheme for hashing MessageData |
### 1.3 Signature Scheme
Type of signature scheme used to sign the Message hash
| Name | Number | Description |
| -------------------------- | ------ | ------------------------------------ |
| SIGNATURE\_SCHEME\_NONE | 0 | |
| SIGNATURE\_SCHEME\_ED25519 | 1 | Ed25519 signature (default) |
| SIGNATURE\_SCHEME\_EIP712 | 2 | ECDSA signature using EIP-712 scheme |
### 1.4 Message Type
Type of the MessageBody
| Name | Number | Description |
| ---------------------------------------------- | ------ | -------------------------------- |
| MESSAGE\_TYPE\_NONE | 0 | Invalid default value |
| MESSAGE\_TYPE\_CAST\_ADD | 1 | Add a new Cast |
| MESSAGE\_TYPE\_CAST\_REMOVE | 2 | Remove an existing Cast |
| MESSAGE\_TYPE\_REACTION\_ADD | 3 | Add a Reaction to a Cast |
| MESSAGE\_TYPE\_REACTION\_REMOVE | 4 | Remove a Reaction from a Cast |
| MESSAGE\_TYPE\_LINK\_ADD | 5 | Add a Link to a target |
| MESSAGE\_TYPE\_LINK\_REMOVE | 6 | Remove a Link from a target |
| MESSAGE\_TYPE\_VERIFICATION\_ADD\_ETH\_ADDRESS | 7 | Add a Verification of an Address |
| MESSAGE\_TYPE\_VERIFICATION\_REMOVE | 8 | Remove a Verification |
| MESSAGE\_TYPE\_USER\_DATA\_ADD | 11 | Add metadata about a user |
| MESSAGE\_TYPE\_USERNAME\_PROOF | 12 | Add or replace a username proof |
| MESSAGE\_TYPE\_FRAME\_ACTION | 13 | Frame action (not stored) |
| MESSAGE\_TYPE\_LINK\_COMPACT\_STATE | 14 | Compact state for links |
### 1.5 Farcaster Network
Farcaster network the message is intended for
| Name | Number | Description |
| --------------------------- | ------ | ---------------------- |
| FARCASTER\_NETWORK\_NONE | 0 | |
| FARCASTER\_NETWORK\_MAINNET | 1 | Public primary network |
| FARCASTER\_NETWORK\_TESTNET | 2 | Public test network |
| FARCASTER\_NETWORK\_DEVNET | 3 | Private test network |
## 2. UserData
A UserData message represents user metadata (e.g. a profile picture url) .
### 2.1 UserDataBody
Body of a UserData message
| Field | Type | Label | Description |
| ----- | ----------------------------- | ----- | --------------------- |
| type | [UserDataType](#UserDataType) | | Type of metadata |
| value | string | | Value of the metadata |
### 2.2 UserDataType
Type of UserData message
| Name | Number | Description |
| -------------------------------------- | ------ | ------------------------------------- |
| USER\_DATA\_TYPE\_NONE | 0 | Invalid default value |
| USER\_DATA\_TYPE\_PFP | 1 | Profile Picture for the user |
| USER\_DATA\_TYPE\_DISPLAY | 2 | Display Name for the user |
| USER\_DATA\_TYPE\_BIO | 3 | Bio for the user |
| USER\_DATA\_TYPE\_URL | 5 | URL of the user |
| USER\_DATA\_TYPE\_USERNAME | 6 | Preferred Farcaster Name for the user |
| USER\_DATA\_TYPE\_LOCATION | 7 | Location for the user |
| USER\_DATA\_TYPE\_TWITTER | 8 | Twitter username for the user |
| USER\_DATA\_TYPE\_GITHUB | 9 | GitHub username for the user |
| USER\_DATA\_TYPE\_BANNER | 10 | Banner image for the user |
| USER\_DATA\_PRIMARY\_ADDRESS\_ETHEREUM | 11 | Primary Ethereum address |
| USER\_DATA\_PRIMARY\_ADDRESS\_SOLANA | 12 | Primary Solana address |
See [FIP-196](https://github.com/farcasterxyz/protocol/discussions/196) for more information on Location.
See [FIP-19](https://github.com/farcasterxyz/protocol/discussions/199) for more information on Twitter/X and GitHub usernames.
## 3. Cast
A Cast message is a public post from a user.
### 3.1 CastAddBody
Adds a new Cast message.
| Field | Type | Label | Description |
| ------------------- | --------------------- | ------------ | ------------------------------------------- |
| embeds\_deprecated | string | repeated | URLs to be embedded in the cast |
| mentions | uint64 | repeated | Fids mentioned in the cast |
| parent\_cast\_id | [CastId](#CastId) | oneOf parent | Parent cast of the cast |
| parent\_url | string | oneOf parent | Parent URL of the cast |
| text | string | | Text of the cast |
| mentions\_positions | uint32 | repeated | Positions of the mentions in the text |
| embeds | [Embed](#Embed) | repeated | URLs or cast ids to be embedded in the cast |
| type | [CastType](#CastType) | | Type of cast (regular, long, etc.) |
#### Embed
| Field | Type | Label | Description |
| -------- | ----------------- | ----- | ----------- |
| url | string | | |
| cast\_id | [CastId](#CastId) | | |
### 3.2 CastRemoveBody
Removes an existing Cast message.
| Field | Type | Label | Description |
| ------------ | ----- | ----- | -------------------------- |
| target\_hash | bytes | | Hash of the cast to remove |
### 3.3 CastId
Identifier used to look up a Cast
| Field | Type | Label | Description |
| ----- | ------ | ----- | ------------------------------------ |
| fid | uint64 | | Fid of the user who created the cast |
| hash | bytes | | Hash of the cast |
## 4. Reaction
A Reaction message creates a relationship between an account and a cast. (e.g. like)
### 4.1 ReactionBody
Adds or removes a Reaction from a Cast
| Field | Type | Label | Description |
| ---------------- | ----------------------------- | ----- | ------------------------------ |
| type | [ReactionType](#ReactionType) | | Type of reaction |
| target\_cast\_id | [CastId](#CastId) | | CastId of the Cast to react to |
| target\_url | string | | URL to react to |
### 4.2 ReactionType
Type of Reaction
| Name | Number | Description |
| ---------------------- | ------ | ---------------------------------------- |
| REACTION\_TYPE\_NONE | 0 | Invalid default value |
| REACTION\_TYPE\_LIKE | 1 | Like the target cast |
| REACTION\_TYPE\_RECAST | 2 | Share target cast to the user's audience |
## 5. Link
A Link message creates a relationship between two users (e.g. follow)
### 5.1 LinkBody
Adds or removes a Link
| Field | Type | Label | Description |
| ---------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------- |
| type | string | | Type of link, \<= 8 characters |
| displayTimestamp | uint32 | optional | User-defined timestamp that preserves original timestamp when message.data.timestamp needs to be updated for compaction |
| target\_fid | uint64 | | The fid the link relates to |
## 6. Verification
A Verification message is a proof of ownership of something.
### 6.1 VerificationAddAddressBody
Adds a bi-directional signature proving that an fid has control over an address (supports multiple protocols).
| Field | Type | Label | Description |
| ------------------ | --------------------- | ----- | ---------------------------------------------------- |
| address | bytes | | Address being verified |
| claim\_signature | bytes | | Signature produced by the user's address |
| block\_hash | bytes | | Hash of the latest block when the claim was produced |
| verification\_type | uint32 | | Type of verification (0 = EOA, 1 = contract) |
| chain\_id | uint32 | | Chain ID of the verification |
| protocol | [Protocol](#Protocol) | | Protocol of the address (Ethereum, Solana, etc.) |
### 6.2 VerificationRemoveBody
Removes a Verification of any type
| Field | Type | Label | Description |
| ------- | ----- | ----- | ------------------------------------- |
| address | bytes | | Address of the Verification to remove |
## 7. Frame Action
Represents an action performed by a user on a frame. This message is not stored on the nodes and cannot be submitted,
only validated.
### 7.1 FrameActionBody
A user action on a frame
| Field | Type | Label | Description |
| --------------- | ----------------- | ----- | ----------------------------------------------------- |
| url | bytes | | The original url of the frame as embedded in the cast |
| button\_index | uint32 | | The button that was pressed (indexed from 1) |
| cast\_id | [CastId](#CastId) | | The cast id that hosted the frame |
| input\_text | bytes | | Any text the user input as part of the action |
| state | bytes | | Serialized state passed from frame to server |
| transaction\_id | bytes | | Transaction ID for transaction frames |
| address | bytes | | Address involved in the frame action |
## 8. Protocol
Type of protocol for addresses
| Name | Number | Description |
| ------------------ | ------ | ----------- |
| PROTOCOL\_ETHEREUM | 0 | Ethereum |
| PROTOCOL\_SOLANA | 1 | Solana |
## 9. CastType
Type of cast
| Name | Number | Description |
| ------------ | ------ | --------------------------- |
| CAST | 0 | Regular cast |
| LONG\_CAST | 1 | Extended length cast |
| TEN\_K\_CAST | 2 | Ten thousand character cast |
## 10. LinkCompactStateBody
Compact state representation for links
| Field | Type | Label | Description |
| ------------ | ------ | -------- | -------------------- |
| type | string | | Type of link |
| target\_fids | uint64 | repeated | Array of target fids |
# Getting Started
Source: https://docs.neynar.com/snapchain/getting-started
Set up and sync your own Snapchain node to access Farcaster data
If your goal is to get started as quickly as possible, consider a managed service like [Neynar](https://neynar.com/) instead of running your own node.
## Requirements
* 16 GB of RAM
* 4 CPU cores or vCPUs
* 2 TB of free storage
* A public IP address with ports 3381 - 3383 exposed
## Sync a node
The easiest way to run Snapchain is using [Docker](https://www.docker.com/get-started/). Once installed, run the following commands in a new folder
```bash theme={"system"}
curl -sSL https://raw.githubusercontent.com/farcasterxyz/snapchain/refs/heads/main/scripts/snapchain-bootstrap.sh | bash
```
Check the docker logs to ensure that the snapshot is being downloaded.
```bash theme={"system"}
./snapchain.sh logs
2025-04-16T02:40:11.265909Z INFO snapchain: Downloading snapshots
2025-04-16T02:40:11.266021Z INFO snapchain::storage::db::snapshot: Retrieving metadata from https://pub-d352dd8819104a778e20d08888c5a661.r2.dev/FARCASTER_NETWORK_MAINNET/1/latest.json
2025-04-16T02:40:11.637368Z INFO snapchain::storage::db::snapshot: Downloading zipped snapshot chunk chunk_0001.bin
2025-04-16T02:40:12.952768Z INFO snapchain::storage::db::snapshot: Downloading zipped snapshot chunk chunk_0002.bin
2025-04-16T02:40:15.305951Z INFO snapchain::storage::db::snapshot: Downloading zipped snapshot chunk chunk_0003.bin
2025-04-16T02:40:17.742041Z INFO snapchain::storage::db::snapshot: Downloading zipped snapshot chunk chunk_0004.bin
2025-04-16T02:40:20.720477Z INFO snapchain::storage::db::snapshot: Downloading zipped snapshot chunk chunk_0005.bin
....
```
Snapshots are about 200 GB in size and may take a few hours to sync and decompress. Once complete, you should see logs like this:
```
2025-04-16T08:20:01.749980Z INFO node{name=Block}:sync{height.tip=[0] 1875918 height.sync=[0] 1875919}: informalsystems_malachitebft_sync::handle: Requesting sync value from peer height.sync=[0] 1875920 peer=12D3KooWCc28TYrrXFivwUshyZ8R5HqPMgx4f7AP54iCDLYr7kFR
2025-04-16T08:20:01.752425Z INFO Actor{id="0.7"}: snapchain::consensus::read_validator: Processed decided block height=[0] 1875920 hash="d7a6e107fed6e1e90d4888f4aa2bfb922d9e6950d83c621db0500b15e1caccfe"
```
It will take a few hours for the node to sync. You can monitor it by running:
```bash theme={"system"}
curl localhost:3381/v1/info | jq
# if the nodes are in sync, the blockdelay values for shards should be in the single digits.
{
"dbStats": {
"numMessages": 656823920,
"numFidRegistrations": 1049519,
"approxSize": 324437755099
},
"numShards": 2,
"shardInfos": [
{
"shardId": 0,
"maxHeight": 1932723,
"numMessages": 0,
"numFidRegistrations": 0,
"approxSize": 1114006028,
"blockDelay": 5,
"mempoolSize": 0
},
{
"shardId": 2,
"maxHeight": 1945363,
"numMessages": 326936052,
"numFidRegistrations": 523905,
"approxSize": 161319650355,
"blockDelay": 6,
"mempoolSize": 4294967295
},
{
"shardId": 1,
"maxHeight": 1966901,
"numMessages": 329887868,
"numFidRegistrations": 525614,
"approxSize": 163118104744,
"blockDelay": 5,
"mempoolSize": 4294967295
}
]
}
```
## Query a node
Once the node is in sync you can start querying it for the latest messages from a user. You'll need the user's account id to get their messages. If you don't have this handy, you can do a lookup by username from the public Farcaster name server.
```bash theme={"system"}
# Query for the id associated with the @farcaster account
curl https://fnames.farcaster.xyz/transfers?name=farcaster
{
"transfers": [
{
"id": 1,
"timestamp": 1628882891,
"username": "farcaster",
"owner": "0x877...06ed",
"from": 0,
"to": 1,
"user_signature": "0xa6fdd...471b",
"server_signature": "0xb718...c41b"
}
]
}
```
The `@farcaster` user's account id is 1, and so you can fetch the latest messages by querying the node over its [HTTP API](/snapchain/httpapi/httpapi):
```bash theme={"system"}
curl http://localhost:3381/v1/castsByFid\?fid\=1 \
| jq ".messages[].data.castAddBody.text \
| select( . != null)"
```
# Blocks API
Source: https://docs.neynar.com/snapchain/grpcapi/blocks
gRPC API for retrieving blocks and shard chunks from the Snapchain
Used to retrieve blocks and shard chunks from the chain.
## API
| Method Name | Request Type | Response Type | Description |
| -------------- | ------------------ | ------------------- | -------------------------------------------- |
| GetBlocks | BlocksRequest | stream Block | Returns a stream of blocks for a given shard |
| GetShardChunks | ShardChunksRequest | ShardChunksResponse | Returns chunks of serialized block data |
## BlocksRequest
| Field | Type | Label | Description |
| -------------------- | ------ | -------- | -------------------------------------- |
| shard\_id | uint32 | | ID of the shard to get blocks from |
| start\_block\_number | uint64 | | Block number to start from (inclusive) |
| stop\_block\_number | uint64 | optional | Block number to stop at (inclusive) |
## ShardChunksRequest
| Field | Type | Label | Description |
| -------------------- | ------ | -------- | -------------------------------------- |
| shard\_id | uint32 | | ID of the shard to get chunks from |
| start\_block\_number | uint64 | | Block number to start from (inclusive) |
| stop\_block\_number | uint64 | optional | Block number to stop at (inclusive) |
## ShardChunksResponse
| Field | Type | Label | Description |
| ------------- | ---------- | -------- | --------------------- |
| shard\_chunks | ShardChunk | repeated | Array of shard chunks |
## Block
| Field | Type | Label | Description |
| -------- | ----------- | -------- | ------------------------------ |
| header | BlockHeader | | Header info for the block |
| messages | Message | repeated | Array of messages in the block |
# Casts API
Source: https://docs.neynar.com/snapchain/grpcapi/casts
gRPC API for retrieving casts and tombstones for deleted casts
Used to retrieve valid casts or tombstones for deleted casts
## API
| Method Name | Request Type | Response Type | Description |
| ----------------------- | -------------------- | ---------------- | -------------------------------------------------------------- |
| GetCast | CastId | Message | Returns a specific Cast |
| GetCastsByFid | FidRequest | MessagesResponse | Returns CastAdds for an Fid in reverse chron order |
| GetCastsByParent | CastsByParentRequest | MessagesResponse | Returns CastAdd replies to a given Cast in reverse chron order |
| GetCastsByMention | FidRequest | MessagesResponse | Returns CastAdds that mention an Fid in reverse chron order |
| GetAllCastMessagesByFid | FidTimestampRequest | MessagesResponse | Returns Casts for an Fid with optional timestamp filtering |
## CastsByParentRequest
| Field | Type | Label | Description |
| ---------------- | ----------------- | -------- | --------------------------------------------- |
| parent\_cast\_id | [CastId](#CastId) | | Parent cast ID to find replies for (optional) |
| parent\_url | string | | Parent URL to find replies for (optional) |
| page\_size | uint32 | optional | Number of results to return per page |
| page\_token | bytes | optional | Token for pagination |
| reverse | bool | optional | Whether to return results in reverse order |
## FidTimestampRequest
| Field | Type | Label | Description |
| ---------------- | ------ | -------- | ------------------------------------------ |
| fid | uint64 | | Farcaster ID |
| page\_size | uint32 | optional | Number of results to return per page |
| page\_token | bytes | optional | Token for pagination |
| reverse | bool | optional | Whether to return results in reverse order |
| start\_timestamp | uint64 | optional | Optional timestamp to start filtering from |
| stop\_timestamp | uint64 | optional | Optional timestamp to stop filtering at |
# Events API
Source: https://docs.neynar.com/snapchain/grpcapi/events
gRPC API for subscribing to real-time event updates from Snapchain nodes
Used to subscribe to real-time event updates from the Snapchain node
## API
| Method Name | Request Type | Response Type | Description |
| ----------- | ---------------- | --------------- | ---------------------------------- |
| Subscribe | SubscribeRequest | stream HubEvent | Streams new Events as they occur |
| GetEvent | EventRequest | HubEvent | Returns a single event by ID |
| GetEvents | EventsRequest | EventsResponse | Returns a paginated list of events |
## SubscribeRequest
| Field | Type | Label | Description |
| ------------ | ------------ | -------- | -------------------------------- |
| event\_types | HubEventType | repeated | Types of events to subscribe to |
| from\_id | uint64 | optional | Event ID to start streaming from |
| shard\_index | uint32 | optional | Shard index to subscribe to |
## EventRequest
| Field | Type | Label | Description |
| ------------ | ------ | ----- | ------------------------- |
| id | uint64 | | Event ID to retrieve |
| shard\_index | uint32 | | Shard index for the event |
## EventsRequest
| Field | Type | Label | Description |
| ------------ | ------ | -------- | ----------------------------------------- |
| start\_id | uint64 | | Starting event ID |
| shard\_index | uint32 | optional | Shard index to query |
| stop\_id | uint64 | optional | Stopping event ID |
| page\_size | uint32 | optional | Number of events to return per page |
| page\_token | bytes | optional | Page token for pagination |
| reverse | bool | optional | Whether to return events in reverse order |
## EventsResponse
| Field | Type | Label | Description |
| ----------------- | -------- | -------- | ------------------------------ |
| events | HubEvent | repeated | List of events |
| next\_page\_token | bytes | optional | Token for next page of results |
# Fids API
Source: https://docs.neynar.com/snapchain/grpcapi/fids
gRPC API for retrieving a list of all Farcaster IDs
Used to retrieve a list of all fids
## API
| Method Name | Request Type | Response Type | Description |
| ----------- | ------------ | ------------- | ------------------------------------ |
| GetFids | FidsRequest | FidsResponse | Returns a paginated list of all fids |
## FidsRequest
| Field | Type | Label | Description |
| ----------- | ------ | -------- | ----------- |
| page\_size | uint32 | optional | |
| page\_token | bytes | optional | |
| reverse | bool | optional | |
| shard\_id | uint32 | required | |
## Fids Response
| Field | Type | Label | Description |
| ----------------- | ------ | -------- | ------------- |
| fids | uint64 | repeated | Array of fids |
| next\_page\_token | bytes | optional | |
# gRPC API
Source: https://docs.neynar.com/snapchain/grpcapi/grpcapi
Overview of Snapchain's gRPC API for interacting with Farcaster nodes
Snapchain nodes serve a gRPC API on port 3383 by default.
## Using the API
We recommend using a library
like [hub-nodejs](https://github.com/farcasterxyz/hub-monorepo/tree/main/packages/hub-nodejs) to interact with the
gRPC APIs. Please refer
to its [documentation](https://github.com/farcasterxyz/hub-monorepo/tree/main/packages/hub-nodejs/docs) for how to use
it.
## Other languages
The protobufs for the gRPC API are available within
the [hub-monorepo](https://github.com/farcasterxyz/hub-monorepo/tree/main/protobufs). They can be used to generate
bindings for other clients built using other languages. Note that by default, nodes rely on the
javascript [ts-proto](https://www.npmjs.com/package/ts-proto) library's serialization byte order to verify messages
hashes. If you are using a different client, you may need to use the `data_bytes` field with the raw serialized bytes
when calling `SubmitMessage` in order for the message to be considered valid. Refer to
the [SubmitMessage HTTP API docs](/snapchain/httpapi/message#using-with-rust-go-or-other-programming-languages)
for more details.
# Links API
Source: https://docs.neynar.com/snapchain/grpcapi/links
gRPC API for retrieving links representing relationships between users
A Link represents a relationship between two users (e.g. follow)
## API
| Method Name | Request Type | Response Type | Description |
| ------------------------------- | -------------------- | ---------------- | -------------------------------------------------------------- |
| GetLink | LinkRequest | Message | Returns a specific Link |
| GetLinksByFid | LinksByFidRequest | MessagesResponse | Returns Links made by an fid in reverse chron order |
| GetLinksByTarget | LinksByTargetRequest | MessagesResponse | Returns LinkAdds for a given target in reverse chron order |
| GetLinkCompactStateMessageByFid | FidRequest | MessagesResponse | Returns compact state messages for Links by an fid |
| GetAllLinkMessagesByFid | FidTimestampRequest | MessagesResponse | Returns Links made by an fid with optional timestamp filtering |
## Link Request
| Field | Type | Label | Description |
| ----------- | ------ | ----- | ----------------------------------------------- |
| fid | uint64 | | Farcaster ID of the user who generated the Link |
| link\_type | string | | Type of the Link being requested |
| target\_fid | uint64 | | Fid of the target |
## LinksByFid Request
| Field | Type | Label | Description |
| ----------- | ------- | -------- | ----------------------------------------------- |
| fid | uint64 | | Farcaster ID of the user who generated the Link |
| link\_type | string | optional | Type of the Link being requested |
| page\_size | uint32 | optional | Number of results to return per page |
| page\_token | bytes | optional | Token for pagination |
| reverse | boolean | optional | Whether to return results in reverse order |
## LinksByTarget Request
| Field | Type | Label | Description |
| ----------- | ------- | -------- | ------------------------------------------ |
| target\_fid | uint64 | | Target Farcaster ID to find links for |
| link\_type | string | optional | Type of the Link being requested |
| page\_size | uint32 | optional | Number of results to return per page |
| page\_token | bytes | optional | Token for pagination |
| reverse | boolean | optional | Whether to return results in reverse order |
## FidTimestampRequest
| Field | Type | Label | Description |
| ---------------- | ------ | -------- | ------------------------------------------ |
| fid | uint64 | | Farcaster ID |
| page\_size | uint32 | optional | Number of results to return per page |
| page\_token | bytes | optional | Token for pagination |
| reverse | bool | optional | Whether to return results in reverse order |
| start\_timestamp | uint64 | optional | Optional timestamp to start filtering from |
| stop\_timestamp | uint64 | optional | Optional timestamp to stop filtering at |
# Message API
Source: https://docs.neynar.com/snapchain/grpcapi/message
gRPC API for validating and submitting messages to Snapchain nodes
Used to validate and send a message to the Snapchain node. Valid messages are accepted and gossiped to other nodes in the
network.
## API
| Method Name | Request Type | Response Type | Description |
| ------------------ | ------------------------- | -------------------------- | ------------------------------------------------------------- |
| SubmitMessage | Message | Message | Submits a Message to the node |
| SubmitBulkMessages | SubmitBulkMessagesRequest | SubmitBulkMessagesResponse | Submits several Messages to the node |
| ValidateMessage | Message | ValidationResponse | Validates a Message on the node without merging and gossiping |
## SubmitBulkMessagesRequest
| Field | Type | Label | Description |
| -------- | ------- | -------- | -------------------------------------------------------------------------------------- |
| messages | Message | repeated | An array of Messages to submit. All messages will submitted, even if earlier ones fail |
## SubmitBulkMessagesResponse
| Field | Type | Label | Description |
| -------- | ------------------- | -------- | --------------------------------------------------------------------------------------------- |
| messages | BulkMessageResponse | repeated | An array of BulkMessageResponse, one for each submitted message indicating success or failure |
## BulkMessageResponse
| Field | Type | Label | Description |
| -------------- | ------------ | ----- | ------------------------------------------------------------ |
| message | Message | oneOf | The message if it was submitted successfully |
| message\_error | MessageError | oneOf | Failure reason if the message was not submitted successfully |
## MessageError
| Field | Type | Label | Description |
| ------- | ------ | ----- | --------------------------------- |
| hash | bytes | | Message hash |
| errCode | string | | Failure error code |
| message | string | | Description of the failure reason |
## ValidationResponse
| Field | Type | Label | Description |
| ------- | ------- | ----- | --------------------------------------------- |
| valid | boolean | | Whether the message is valid or not |
| message | Message | | The message being validated (same as request) |
# Metadata API
Source: https://docs.neynar.com/snapchain/grpcapi/metadata
gRPC API for retrieving node metadata and synchronization information
These APIs are used to retrieve node metadata and for synchronization between nodes. Some methods are not meant for use by external applications.
## API
| Method Name | Request Type | Response Type | Description |
| ----------------------- | ----------------------- | ------------------------ | ----------------------------------------- |
| GetInfo | GetInfoRequest | GetInfoResponse | Returns metadata about the node's state |
| GetTrieMetadataByPrefix | TrieNodeMetadataRequest | TrieNodeMetadataResponse | Get trie metadata for a particular prefix |
## GetInfoRequest
Empty request, no parameters needed.
## GetInfoResponse
| Field | Type | Label | Description |
| ------------ | ------------------- | -------- | ---------------------------- |
| db\_stats | [DbStats](#DbStats) | | Database statistics |
| num\_shards | uint32 | | Number of shards in the node |
| shard\_infos | ShardInfo | repeated | Information about each shard |
## DbStats
| Field | Type | Label | Description |
| ----------------------- | ------ | ----- | ----------------------------------------- |
| num\_messages | uint64 | | Total number of messages in the node |
| num\_fid\_registrations | uint64 | | Number of FID registrations in the node |
| approx\_size | uint64 | | Approximate size of the database in bytes |
## ShardInfo
| Field | Type | Label | Description |
| ----------------------- | ------ | ----- | ---------------------------------------- |
| shard\_id | uint32 | | Shard identifier |
| max\_height | uint64 | | Maximum block height in the shard |
| num\_messages | uint64 | | Number of messages in the shard |
| num\_fid\_registrations | uint64 | | Number of FID registrations in the shard |
| approx\_size | uint64 | | Approximate size of the shard in bytes |
| block\_delay | uint64 | | Block delay in the shard |
| mempool\_size | uint64 | | Size of the mempool for this shard |
## TrieNodeMetadataRequest
| Field | Type | Label | Description |
| --------- | ------ | ----- | ---------------------------- |
| shard\_id | uint32 | | Shard ID to get metadata for |
| prefix | bytes | | Prefix to get metadata for |
## TrieNodeMetadataResponse
| Field | Type | Label | Description |
| ------------- | ------------------------ | -------- | ------------------------------------ |
| prefix | bytes | | Prefix of the trie node |
| num\_messages | uint64 | | Number of messages under this prefix |
| hash | string | | Hash of the trie node |
| children | TrieNodeMetadataResponse | repeated | Child nodes of this trie node |
# OnChainEvents API
Source: https://docs.neynar.com/snapchain/grpcapi/onchain
gRPC API for retrieving on-chain events like ID registry, keys, and storage rent
Used to retrieve on chain events (id registry, keys, storage rent)
## API
| Method Name | Request Type | Response Type | Description |
| ---------------------------------- | ------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------- |
| GetOnChainSigner | SignerRequest | OnChainEvent | Returns the onchain event for an active signer for an Fid |
| GetOnChainSignersByFid | FidRequest | OnChainEventResponse | Returns all active account keys (signers) add events for an Fid |
| GetIdRegistryOnChainEvent | FidRequest | OnChainEvent | Returns the most recent register/transfer on chain event for an fid |
| GetIdRegistryOnChainEventByAddress | IdRegistryEventByAddressRequest | OnChainEvent | Returns the registration/transfer event by address if it exists (allows looking up fid by address) |
| GetOnChainEvents | OnChainEventRequest | OnChainEventResponse | Returns all on chain events filtered by type for an Fid (includes inactive keys and expired rent events) |
| GetFidAddressType | FidAddressTypeRequest | FidAddressTypeResponse | Returns address type information for a given fid and address |
## Signer Request
| Field | Type | Label | Description |
| ------ | ------ | ----- | ------------------------------------------------- |
| fid | uint64 | | Farcaster ID of the user who generated the Signer |
| signer | bytes | | Public Key of the Signer |
## Fid Request
| Field | Type | Label | Description |
| ----------- | ------- | ----- | ------------------------------------------- |
| fid | uint64 | | Farcaster ID of the user |
| page\_size | uint32 | | (optional) Type of the Link being requested |
| page\_token | bytes | | (optional)Type of the Link being requested |
| reverse | boolean | | (optional) Ordering of the response |
## IdRegistryEventByAddressRequest
| Field | Type | Label | Description |
| ------- | ----- | ----- | ----------- |
| address | bytes | | |
## OnChainEventResponse
| Field | Type | Label | Description |
| ----------------- | ------------ | -------- | ----------- |
| events | OnChainEvent | repeated | |
| next\_page\_token | bytes | optional | |
## FidAddressTypeRequest
| Field | Type | Label | Description |
| ------- | ------ | ----- | ---------------- |
| fid | uint64 | | Farcaster ID |
| address | bytes | | Address to check |
## FidAddressTypeResponse
| Field | Type | Label | Description |
| ------------ | ---- | ----- | ---------------------------------------- |
| is\_custody | bool | | Whether the address is a custody address |
| is\_auth | bool | | Whether the address is an auth address |
| is\_verified | bool | | Whether the address is verified |
# Reactions API
Source: https://docs.neynar.com/snapchain/grpcapi/reactions
gRPC API for retrieving reactions (likes and recasts) on Farcaster
## API
| Method Name | Request Type | Response Type | Description |
| --------------------------- | ------------------------ | ---------------- | ------------------------------------------------------------------------------- |
| GetReaction | ReactionRequest | Message | Returns a specific Reaction |
| GetReactionsByFid | ReactionsByFidRequest | MessagesResponse | Returns Reactions made by an Fid in reverse chron order |
| GetReactionsByCast | ReactionsByTargetRequest | MessagesResponse | Returns ReactionAdds for a given Cast in reverse chron order (To be deprecated) |
| GetReactionsByTarget | ReactionsByTargetRequest | MessagesResponse | Returns ReactionAdds for a given target (cast or URL) in reverse chron order |
| GetAllReactionMessagesByFid | FidTimestampRequest | MessagesResponse | Returns Reactions made by an Fid with optional timestamp filtering |
## Reaction Request
Used to retrieve valid or revoked reactions
| Field | Type | Label | Description |
| ---------------- | ------------ | ----- | --------------------------------------------------------------------- |
| fid | uint64 | | Farcaster ID of the user who generated the Reaction |
| reaction\_type | ReactionType | | Type of the Reaction being requested |
| target\_cast\_id | CastId | | (optional) Identifier of the Cast whose reactions are being requested |
| target\_url | string | | (optional) Identifier of the Url whose reactions are being requested |
## ReactionsByFid Request
| Field | Type | Label | Description |
| -------------- | ------------ | -------- | --------------------------------------------------- |
| fid | uint64 | | Farcaster ID of the user who generated the Reaction |
| reaction\_type | ReactionType | optional | Type of the Reaction being requested |
| page\_size | uint32 | optional | Number of results to return per page |
| page\_token | bytes | optional | Token for pagination |
| reverse | boolean | optional | Whether to return results in reverse order |
## ReactionsByTargetRequest
| Field | Type | Label | Description |
| ---------------- | ------------ | -------- | ----------------------------------------------- |
| target\_cast\_id | CastId | | Target cast ID to find reactions for (optional) |
| target\_url | string | | Target URL to find reactions for (optional) |
| reaction\_type | ReactionType | optional | Type of reaction to filter by |
| page\_size | uint32 | optional | Number of results to return per page |
| page\_token | bytes | optional | Token for pagination |
| reverse | bool | optional | Whether to return results in reverse order |
## FidTimestampRequest
| Field | Type | Label | Description |
| ---------------- | ------ | -------- | ------------------------------------------ |
| fid | uint64 | | Farcaster ID |
| page\_size | uint32 | optional | Number of results to return per page |
| page\_token | bytes | optional | Token for pagination |
| reverse | bool | optional | Whether to return results in reverse order |
| start\_timestamp | uint64 | optional | Optional timestamp to start filtering from |
| stop\_timestamp | uint64 | optional | Optional timestamp to stop filtering at |
# Storage API
Source: https://docs.neynar.com/snapchain/grpcapi/storagelimits
gRPC API for retrieving FID storage limits on Farcaster
Get an FID's storage limits.
## API
| Method Name | Request Type | Response Type | Description |
| ---------------------------- | ------------ | --------------------- | -------------------------------------------------------- |
| GetCurrentStorageLimitsByFid | FidRequest | StorageLimitsResponse | Returns current storage limits for all stores for an Fid |
## StorageLimitsResponse
| Field | Type | Label | Description |
| ------------------- | ------------------ | -------- | ----------------------------- |
| limits | StorageLimit | repeated | Storage limits per store type |
| units | uint32 | | Number of units |
| unit\_details | StorageUnitDetails | repeated | Details about storage units |
| tier\_subscriptions | TierDetails | repeated | Tier subscription details |
## StorageLimit
| Field | Type | Label | Description |
| ----------------- | --------- | ----- | ------------------------------------------------------ |
| store\_type | StoreType | | The specific type being managed by the store |
| name | string | | Name of the store type |
| limit | uint64 | | The limit of the store type, scaled by the user's rent |
| used | uint64 | | Current usage of the store type |
| earliestTimestamp | uint64 | | Timestamp of earliest message |
| earliestHash | bytes | | Hash of earliest message |
## StorageUnitDetails
| Field | Type | Label | Description |
| ---------- | --------------- | ----- | ------------------------ |
| unit\_type | StorageUnitType | | Type of storage unit |
| unit\_size | uint32 | | Size of the storage unit |
## TierDetails
| Field | Type | Label | Description |
| ----------- | -------- | ----- | -------------------- |
| tier\_type | TierType | | Type of tier |
| expires\_at | uint64 | | Expiration timestamp |
# UserData API
Source: https://docs.neynar.com/snapchain/grpcapi/userdata
gRPC API for retrieving user metadata associated with Farcaster accounts
Used to retrieve the current metadata associated with a user
## API
| Method Name | Request Type | Response Type | Description |
| --------------------------- | ------------------- | ---------------- | -------------------------------------------------------- |
| GetUserData | UserDataRequest | Message | Returns a specific UserData for an Fid |
| GetUserDataByFid | FidRequest | MessagesResponse | Returns all UserData for an Fid |
| GetAllUserDataMessagesByFid | FidTimestampRequest | MessagesResponse | Returns all UserData for an Fid with timestamp filtering |
## UserData Request
| Field | Type | Label | Description |
| ---------------- | ------------ | ----- | --------------------------------------------------- |
| fid | uint64 | | Farcaster ID of the user who generated the UserData |
| user\_data\_type | UserDataType | | Type of UserData being requested |
## Messages Response
| Field | Type | Label | Description |
| ----------------- | ------- | -------- | ----------------------- |
| messages | Message | repeated | Farcaster Message array |
| next\_page\_token | bytes | optional | Token for pagination |
## FidTimestampRequest
| Field | Type | Label | Description |
| ---------------- | ------ | -------- | ------------------------------------------ |
| fid | uint64 | | Farcaster ID |
| page\_size | uint32 | optional | Number of results to return per page |
| page\_token | bytes | optional | Token for pagination |
| reverse | bool | optional | Whether to return results in reverse order |
| start\_timestamp | uint64 | optional | Optional timestamp to start filtering from |
| stop\_timestamp | uint64 | optional | Optional timestamp to stop filtering at |
# Username Proofs API
Source: https://docs.neynar.com/snapchain/grpcapi/usernameproof
gRPC API for retrieving proofs of username ownership on Farcaster
Used to retrieve proofs of username ownership.
## API
| Method Name | Request Type | Response Type | Description |
| ---------------------- | --------------------------------------------- | ------------------------------------------------- | ----------------------------------- |
| GetUsernameProof | [UsernameProofRequest](#UsernameProofRequest) | [UserNameProof](#UserNameProof) | Gets username proof by name |
| GetUserNameProofsByFid | [FidRequest](#FidRequest) | [UsernameProofsResponse](#UsernameProofsResponse) | Gets all username proofs for an FID |
## UsernameProofRequest
| Field | Type | Label | Description |
| ----- | ----- | ----- | ------------------------- |
| name | bytes | | Username to get proof for |
## UsernameProofsResponse
| Field | Type | Label | Description |
| ------ | ------------- | -------- | ------------------------ |
| proofs | UserNameProof | repeated | Array of username proofs |
## UserNameProof
| Field | Type | Label | Description |
| --------- | ------------ | ----- | -------------------------------------- |
| timestamp | uint64 | | Timestamp of the proof |
| name | bytes | | Username being proved |
| owner | bytes | | Owner address |
| signature | bytes | | Cryptographic signature |
| fid | uint64 | | Farcaster ID associated with the proof |
| type | UserNameType | | Type of username proof |
# Verifications API
Source: https://docs.neynar.com/snapchain/grpcapi/verification
gRPC API for retrieving address ownership proofs on Farcaster
Used to retrieve valid or revoked proof of ownership of an Ethereum Address.
## API
| Method Name | Request Type | Response Type | Description |
| ------------------------------- | ------------------- | ---------------- | ------------------------------------------------------------ |
| GetVerification | VerificationRequest | Message | Returns a VerificationAdd for an Ethereum Address |
| GetVerificationsByFid | FidRequest | MessagesResponse | Returns all VerificationAdds made by an Fid |
| GetAllVerificationMessagesByFid | FidTimestampRequest | MessagesResponse | Returns all Verifications made by an Fid with time filtering |
## Verification Request
| Field | Type | Label | Description |
| ------- | ------ | ----- | ------------------------------------------------------- |
| fid | uint64 | | Farcaster ID of the user who generated the Verification |
| address | bytes | | Ethereum Address being verified |
## FidTimestampRequest
| Field | Type | Label | Description |
| ---------------- | ------ | -------- | ------------------------------------------ |
| fid | uint64 | | Farcaster ID |
| page\_size | uint32 | optional | Number of results to return per page |
| page\_token | bytes | optional | Token for pagination |
| reverse | bool | optional | Whether to return results in reverse order |
| start\_timestamp | uint64 | optional | Optional timestamp to start filtering from |
| stop\_timestamp | uint64 | optional | Optional timestamp to stop filtering at |
# Migrating to Snapchain from Hubble
Source: https://docs.neynar.com/snapchain/guides/migrating-to-snapchain
Guide for migrating your application from Hubble to Snapchain
Snapchain is designed to be a drop-in replacement for [Hubble](https://github.com/farcasterxyz/hub-monorepo). To migrate, follow these steps:
1. Set up a Snapchain node ([instructions](/snapchain/guides/running-a-node)).
2. Update your app to use `hub-nodejs` with a version `>=0.13.0`
3. Update the connection url to talk the new snapchain host and port.
## Notable differences
1. Ports have changed. The HTTP port is `3381` and gRPC is `3383`.
2. `submitMessage` has a slightly different API and semantics, detailed below.
3. `HubEvent` ids no longer contain timestamps and calling `extractEventTimestamp` may return invalid data.
4. When calling `subscribe` or using shuttle, note that there are only 2 shards on snapchain and they are 1 indexed (shard 0 is chain metadata and does not have user data)
5. `hub-web` is not fully supported and may not work in some cases.
### submitMessage
Messages once submitted must be included in blocks, similar to blockchain transactions. The `submitMessage` has two main differences from Hubble:
1. `submitMessage` requests must all contain `dataBytes` for Snapchain. The `hub-nodejs` builders handle this in all versions `>=0.13.0`, but if you're not using those you will need to manually update this like so:
```typescript theme={"system"}
if (message.dataBytes === undefined) {
message.dataBytes = protobufs.MessageData.encode(message.data).finish();
message.data = undefined;
}
```
2. `submitMessage` is best-effort. It's possible, but rare, that `submitMessage` succeeds but the submitted message fails to get included in a block.
# Run Snapchain on AWS
Source: https://docs.neynar.com/snapchain/guides/running-a-node
Step-by-step guide to running a Snapchain node on AWS EC2
If your goal is to get started as quickly as possible, consider a managed service like [Neynar](https://neynar.com/) instead of running your own node.
This guide will get you set up with a Snapchain node on an AWS EC2 instance and will cost roughly \$100/month. You can run Snapchain on any server you like and costs may vary depending on provider.
## Launch a new instance
1. In AWS, go to EC2 > Instances > Launch Instances
2. Give it a name and select Ubuntu Server 22.04 LTS (HVM), SSD Volume Type and 64-bit (x86). Choose m5.xlarge instance type.
3. In Key pair (login), select Create a new key pair, then select RSA and .pem format, and save it
4. In Network settings, select Allow SSH traffic from Anywhere
5. In Configure storage, select 2 TB of gp3 storage.
6. Click Launch Instance on the right-hand side menu.
## Configure network firewall
1. Go to EC2 → Instances and find your instance
2. Click on the Instance ID
3. Click on Security > Security groups > (Security Group ID)
4. Click on Edit inbound rules and Edit outbound rules on the group page and add rules until they match the below.
**Inbound Rules:**
| Type | Protocol | Port Range | Source |
| ---------- | -------- | ---------- | --------- |
| SSH | TCP | 22 | 0.0.0.0/0 |
| Custom TCP | TCP | 3381-3383 | 0.0.0.0/0 |
**Outbound Rules:**
| Type | Protocol | Port Range | Destination |
| ----------- | -------- | ---------- | ----------- |
| All traffic | All | All | 0.0.0.0/0 |
## Connect to your instance
1. Find your *.pem* file from earlier and run `chmod 400 key.pem`
2. Go to EC2 → Instances, click on the Instance ID and copy the IPv4 Address
3. Connect with `ssh ubuntu@ -i key.pem`
## Start syncing a node
```bash theme={"system"}
# Install docker
curl -fsSL https://get.docker.com -o get-docker.sh
chmod +x get-docker.sh
./get-docker.sh
# Start Snapchain
mkdir snapchain && cd snapchain
curl -sSL https://raw.githubusercontent.com/farcasterxyz/snapchain/refs/heads/main/scripts/snapchain-bootstrap.sh | bash
```
Follow the remaining steps in [Getting Started](/snapchain/getting-started) to validate and query your node.
# Sync Snapchain to Postgres
Source: https://docs.neynar.com/snapchain/guides/syncing-to-db
Mirror Snapchain data to a Postgres database for convenient access
### Pre-requisites
* Read access to a Snapchain node
See [running a node](/snapchain/guides/running-a-node) for more information on how to set up a node.
While some applications can be written by directly querying Hubble, most serious applications need to access the data
in a more structured way.
[Shuttle](https://github.com/farcasterxyz/hub-monorepo/tree/main/packages/shuttle) is a package
that can be used to mirror Snapchain's data to a Postgres DB for convenient access to the underlying data.
## Quickstart
```bash theme={"system"}
git clone git@github.com:farcasterxyz/hub-monorepo.git
# Ensure you have node 21 installed, use nvm to install it
nvm install 21
# If necessary, build packages/core dependency
( cd packages/core && yarn install && yarn build; )
# If necessary, build packages/hub-nodejs dependency
( cd packages/hub-nodejs && yarn install && yarn build; )
# Do remainder within the packages/shuttle directory
cd packages/shuttle
yarn install && yarn build
# Start the db dependencies
docker compose up postgres redis
# To perform reconciliation/backfill, start the worker (can run multiple processes to speed this up)
POSTGRES_URL=postgres://shuttle:password@0.0.0.0:6541 REDIS_URL=0.0.0.0:16379 HUB_HOST=: HUB_SSL=false yarn start worker
# Kick off the backfill process (configure with MAX_FID=100 or BACKFILL_FIDS=1,2,3)
POSTGRES_URL=postgres://shuttle:password@0.0.0.0:6541 REDIS_URL=0.0.0.0:16379 HUB_HOST=: HUB_SSL=false yarn start backfill
# Start the app and sync messages from the event stream
POSTGRES_URL=postgres://shuttle:password@0.0.0.0:6541 REDIS_URL=0.0.0.0:16379 HUB_HOST=: HUB_SSL=false yarn start start
```
This package is fully re-used from Hubble because the Snapchain APIs are backwards compatible with Hubble.
Check out the [documentation](https://github.com/farcasterxyz/hub-monorepo/blob/main/packages/shuttle/README.md) for more information.
# Writing to Snapchain
Source: https://docs.neynar.com/snapchain/guides/writing-messages
Create a Farcaster account programmatically and publish your first message
Create your Farcaster account programmatically and publish your first message.
The example shows you how to:
* Make onchain transactions to create an account
* Rent a storage unit so you can publish messages
* Add signer key to sign messages
* Acquire an fname for your account
* Create, sign and publish messages
This example can be checked out as a fully functional
repository [here](https://github.com/farcasterxyz/hub-monorepo/tree/main/packages/hub-nodejs/examples/hello-world).
### Requirements
* Write access to a node (either your own, or a 3rd party one)
* An ETH wallet with about \~10\$ USD of ETH bridged to [Optimism](https://www.optimism.io/)
* An ETH RPC URL for OP Mainnet (e.g. via [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/) or [QuickNode](https://www.quicknode.com/)).
See [running a node](/snapchain/guides/running-a-node) for more information on how to set up a node.
### Custody address vs signer
In order to register an account and send messages, you need 2 pairs of keys:
* **Custody**: This is the ETH account which funds the initial id registration and storage. You need \~$10 USD in this account. You can use any ETH address as long as the $10 required is transferred via OP mainnet. The private key will be used to sign any signer requests and fname registrations. The person registering should always hold this private key.
* **Signer**: This is a keypair registered with the key registry that's used to sign messages a user publishes to the Farcaster network. If an app is publishing on behalf of a user, the app will hold the private key for this keypair.
## 1. Set up constants
```typescript theme={"system"}
import {
ID_GATEWAY_ADDRESS,
idGatewayABI,
KEY_GATEWAY_ADDRESS,
keyGatewayABI,
ID_REGISTRY_ADDRESS,
idRegistryABI,
FarcasterNetwork,
} from '@farcaster/hub-web';
import { zeroAddress } from 'viem';
import { optimism } from 'viem/chains';
import { generatePrivateKey, privateKeyToAccount, toAccount } from "viem/accounts";
/**
* Populate the following constants with your own values
*/
const CUSTODY_PRIVATE_KEY = ''; // A private key corresponding with any ETH address.
const OP_PROVIDER_URL = ''; // Alchemy or Infura url
const RECOVERY_ADDRESS = zeroAddress; // Optional, using the default value means the account will not be recoverable later if the mnemonic is lost
const SIGNER_PRIVATE_KEY: Hex = zeroAddress; // Optional, using the default means a new signer will be created each time
// Note: crackle is the Farcaster team's mainnet node, which is password protected to prevent abuse. Use a 3rd party node
// provider like https://neynar.com/ Or, run your own mainnet node and broadcast to it permissionlessly.
const HUB_URL = 'crackle.farcaster.xyz:3383'; // URL + Port of the node
const HUB_USERNAME = ''; // Username for auth, leave blank if not using TLS
const HUB_PASS = ''; // Password for auth, leave blank if not using TLS
const USE_SSL = false; // set to true if talking to a node that uses SSL (3rd party hosted nodes or nodes that require auth)
const FC_NETWORK = FarcasterNetwork.MAINNET; // Network of the node
const CHAIN = optimism;
const IdGateway = {
abi: idGatewayABI,
address: ID_GATEWAY_ADDRESS,
chain: CHAIN,
};
const IdContract = {
abi: idRegistryABI,
address: ID_REGISTRY_ADDRESS,
chain: CHAIN,
};
const KeyContract = {
abi: keyGatewayABI,
address: KEY_GATEWAY_ADDRESS,
chain: CHAIN,
};
```
## 2. Register and pay for storage
Create a function to register an FID and pay for storage. This function will check if the account already has an FID
and return early if so.
If you don't have a funded account you can use, note the address and private key pair that's logged. Transfer funds to the address and use the same private key as the `CUSTODY_PRIVATE_KEY` for the next run of the script.
```typescript theme={"system"}
const getOrRegisterFid = async (): Promise => {
const balance = await getBalance(walletClient, { address: account.address });
const existingFid = (await readContract(walletClient, {
...IdContract,
functionName: "idOf",
args: [account.address],
})) as bigint;
console.log(`Using address: ${account.address} with balance: ${balance}`);
if (balance === 0n && existingFid === 0n) {
throw new Error("No existing Fid and no funds to register an fid");
}
if (existingFid > 0n) {
return parseInt(existingFid.toString());
}
const price = await readContract(walletClient, {
...IdGateway,
functionName: "price",
});
if (balance < price) {
throw new Error(`Insufficient balance to rent storage, required: ${price}, balance: ${balance}`);
}
const { request: registerRequest } = await simulateContract(walletClient, {
...IdGateway,
functionName: "register",
args: [RECOVERY_ADDRESS],
value: price,
});
const registerTxHash = await writeContract(walletClient, registerRequest);
const registerTxReceipt = await waitForTransactionReceipt(walletClient, { hash: registerTxHash });
if (registerTxReceipt.logs[0]) {
// Now extract the FID from the logs
const registerLog = decodeEventLog({
abi: idRegistryABI,
data: registerTxReceipt.logs[0].data,
topics: registerTxReceipt.logs[0].topics,
});
const fid = parseInt(registerLog.args["id"]);
return fid;
} else {
throw new Error("Did not receive logs for registered fid");
}
};
const fid = await getOrRegisterFid();
```
## 3. Add a signer
Now, we will add a signer to the key registry. Every signer must have a signed metadata field from the fid of the app requesting it.
In our case, we will use our own fid. Note, this requires you to sign a message with the private key of the address
holding the fid. If this is not possible, register a separate fid for the app first and use that.
```typescript theme={"system"}
const getOrRegisterSigner = async (fid: number) => {
if (SIGNER_PRIVATE_KEY !== zeroAddress) {
// If a private key is provided, we assume the signer is already in the key registry
const privateKeyBytes = fromHex(SIGNER_PRIVATE_KEY, "bytes");
const publicKeyBytes = ed25519.getPublicKey(privateKeyBytes);
return privateKeyBytes;
}
const privateKey = ed25519.utils.randomPrivateKey();
const publicKey = toHex(ed25519.getPublicKey(privateKey));
// To add a key, we need to sign the metadata with the fid of the app we're adding the key on behalf of
// We'll use our own fid and custody address for simplicity. This can also be a separate App specific fid.
const localAccount = toAccount(account);
const eip712signer = new ViemLocalEip712Signer(localAccount);
const metadata = await eip712signer.getSignedKeyRequestMetadata({
requestFid: BigInt(fid),
key: fromHex(publicKey, "bytes"),
deadline: BigInt(Math.floor(Date.now() / 1000) + 60 * 60), // 1 hour from now
});
const metadataHex = toHex(metadata.unwrapOr(new Uint8Array()));
const { request: signerAddRequest } = await simulateContract(walletClient, {
...KeyContract,
functionName: "add",
args: [1, publicKey, 1, metadataHex], // keyType, publicKey, metadataType, metadata
});
const signerAddTxHash = await writeContract(walletClient, signerAddRequest);
await waitForTransactionReceipt(walletClient, { hash: signerAddTxHash });
await new Promise((resolve) => setTimeout(resolve, 30000));
return privateKey;
};
const signer = await getOrRegisterSigner(fid);
```
## 4. Register an fname
Now that the onchain actions are complete, let's register an fname using the farcaster offchain fname registry.
Registering an fname requires a signature from the custody address of the fid.
```typescript theme={"system"}
const registerFname = async (fid: number) => {
try {
// First check if this fid already has an fname
const response = await axios.get(`https://fnames.farcaster.xyz/transfers/current?fid=${fid}`);
const fname = response.data.transfer.username;
return fname;
} catch (e) {
// No username, ignore and continue with registering
}
const fname = `fid-${fid}`;
const timestamp = Math.floor(Date.now() / 1000);
const localAccount = toAccount(account);
const signer = new ViemLocalEip712Signer(localAccount as LocalAccount);
const userNameProofSignature = await signer.signUserNameProofClaim(
makeUserNameProofClaim({
name: fname,
timestamp: timestamp,
owner: account.address,
}),
);
try {
const response = await axios.post("https://fnames.farcaster.xyz/transfers", {
name: fname, // Name to register
from: 0, // Fid to transfer from (0 for a new registration)
to: fid, // Fid to transfer to (0 to unregister)
fid: fid, // Fid making the request (must match from or to)
owner: account.address, // Custody address of fid making the request
timestamp: timestamp, // Current timestamp in seconds
signature: bytesToHex(userNameProofSignature._unsafeUnwrap()), // EIP-712 signature signed by the current custody address of the fid
});
return fname;
} catch (e) {
// @ts-ignore
throw new Error(`Error registering fname: ${JSON.stringify(e.response.data)} (status: ${e.response.status})`);
}
};
const fname = await registerFname(fid);
```
Note that this only associates the name to our fid, we still need to set it as our username.
## 5. Write to Snapchain
Finally, we're now ready to submit messages. First, we shall set the fname as our username. And then post a
cast.
```typescript theme={"system"}
const submitMessage = async (resultPromise: HubAsyncResult) => {
const result = await resultPromise;
if (result.isErr()) {
throw new Error(`Error creating message: ${result.error}`);
}
const messageSubmitResult = await hubClient.submitMessage(result.value, metadata);
if (messageSubmitResult.isErr()) {
throw new Error(`Error submitting message to node: ${messageSubmitResult.error}`);
}
};
const signer = new NobleEd25519Signer(signerPrivateKey);
const dataOptions = {
fid: fid,
network: FC_NETWORK,
};
const userDataPfpBody = {
type: UserDataType.USERNAME,
value: fname,
};
await submitMessage(makeUserDataAdd(userDataPfpBody, dataOptions, signer));
await submitMessage(
makeCastAdd(
{
text: "Hello World!",
embeds: [],
embedsDeprecated: [],
mentions: [],
mentionsPositions: [],
type: CastType.CAST,
},
dataOptions,
signer,
)
);
```
Now, you can view your profile on any farcaster client. To see it on Warpcast, visit `https://warpcast.com/@`
# Casts API
Source: https://docs.neynar.com/snapchain/httpapi/casts
HTTP API for retrieving casts on Farcaster
## castById
Get a cast by its FID and Hash.
**Query Parameters**
| Parameter | Description | Example |
| --------- | ----------------------------- | ------------------------------------------------- |
| fid | The FID of the cast's creator | `fid=6833` |
| hash | The cast's hash | `hash=0xa48dd46161d8e57725f5e26e34ec19c13ff7f3b9` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/castById?fid=2&hash=0xd2b1ddc6c88e865a33cb1a565e0058d757042974
```
**Response**
```json theme={"system"}
{
"data": {
"type": "MESSAGE_TYPE_CAST_ADD",
"fid": 2,
"timestamp": 48994466,
"network": "FARCASTER_NETWORK_MAINNET",
"castAddBody": {
"embedsDeprecated": [],
"mentions": [],
"parentCastId": {
"fid": 226,
"hash": "0xa48dd46161d8e57725f5e26e34ec19c13ff7f3b9"
},
"text": "Cast Text",
"mentionsPositions": [],
"embeds": []
}
},
"hash": "0xd2b1ddc6c88e865a33cb1a565e0058d757042974",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "3msLXzxB4eEYe...dHrY1vkxcPAA==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x78ff9a...58c"
}
```
## castsByFid
Fetch all casts for authored by an FID.
**Query Parameters**
| Parameter | Description | Example |
| -------------- | ---------------------------------- | --------------------------- |
| fid | The FID of the cast's creator | `fid=6833` |
| pageSize | Optional page size (default: 1000) | `pageSize=100` |
| pageToken | Optional page token for pagination | `pageToken=DAEDAAAGlQ...` |
| reverse | Optional reverse order flag | `reverse=true` |
| startTimestamp | Optional start timestamp filter | `startTimestamp=1640995200` |
| stopTimestamp | Optional stop timestamp filter | `stopTimestamp=1640995200` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/castsByFid?fid=2
```
**Response**
```json theme={"system"}
{
"messages": [
{
"data": {
"type": "MESSAGE_TYPE_CAST_ADD",
"fid": 2,
"timestamp": 48994466,
"network": "FARCASTER_NETWORK_MAINNET",
"castAddBody": {
"text": "Cast Text",
"mentionsPositions": [],
"embeds": []
}
},
"hash": "0xd2b1ddc6c88e865a33cb1a565e0058d757042974",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "3msLXzxB4eEYeF0Le...dHrY1vkxcPAA==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x78ff9a768cf1...2eca647b6d62558c"
}
],
"nextPageToken": ""
}
```
## castsByParent
Fetch all casts by parent cast's FID and Hash OR by the parent's URL
**Query Parameters**
| Parameter | Description | Example |
| --------- | ---------------------------------- | ------------------------------------------------------------------------ |
| fid | The FID of the parent cast | `fid=6833` |
| hash | The parent cast's hash | `hash=0xa48dd46161d8e57725f5e26e34ec19c13ff7f3b9` |
| url | The URL of the parent cast | `url=chain://eip155:1/erc721:0x39d89b649ffa044383333d297e325d42d31329b2` |
| pageSize | Optional page size (default: 1000) | `pageSize=100` |
| pageToken | Optional page token for pagination | `pageToken=DAEDAAAGlQ...` |
| reverse | Optional reverse order flag | `reverse=true` |
You can use either `?fid=...&hash=...` OR `?url=...` to query this endpoint
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/castsByParent?fid=226&hash=0xa48dd46161d8e57725f5e26e34ec19c13ff7f3b9
```
**Response**
```json theme={"system"}
{
"messages": [
{
"data": {
"type": "MESSAGE_TYPE_CAST_ADD",
"fid": 226,
"timestamp": 48989255,
"network": "FARCASTER_NETWORK_MAINNET",
"castAddBody": {
"embedsDeprecated": [],
"mentions": [],
"parentCastId": {
"fid": 226,
"hash": "0xa48dd46161d8e57725f5e26e34ec19c13ff7f3b9"
},
"text": "Cast's Text",
"mentionsPositions": [],
"embeds": []
}
},
"hash": "0x0e501b359f88dcbcddac50a8f189260a9d02ad34",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "MjKnOQCTW42K8+A...tRbJfia2JJBg==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x6f1e8758...7f04a3b500ba"
}
],
"nextPageToken": ""
}
```
## castsByMention
Fetch all casts that mention an FID
**Query Parameters**
| Parameter | Description | Example |
| --------- | ----------------------------------- | ------------------------- |
| fid | The FID that is mentioned in a cast | `fid=6833` |
| pageSize | Optional page size (default: 1000) | `pageSize=100` |
| pageToken | Optional page token for pagination | `pageToken=DAEDAAAGlQ...` |
| reverse | Optional reverse order flag | `reverse=true` |
Use the `mentionsPositions` to extract the offset in the cast text where the FID was mentioned
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/castsByMention?fid=6833
```
**Response**
```json theme={"system"}
{
"messages": [
{
"data": {
"type": "MESSAGE_TYPE_CAST_ADD",
"fid": 2,
"timestamp": 62298143,
"network": "FARCASTER_NETWORK_MAINNET",
"castAddBody": {
"embedsDeprecated": [],
"mentions": [15, 6833],
"parentCastId": {
"fid": 2,
"hash": "0xd5540928cd3daf2758e501a61663427e41dcc09a"
},
"text": "cc and ",
"mentionsPositions": [3, 8],
"embeds": []
}
},
"hash": "0xc6d4607835197a8ee225e9218d41e38aafb12076",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "TOaWrSTmz+cyzPMFGvF...OeUznB0Ag==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x78ff9a768c...647b6d62558c"
}
],
"nextPageToken": ""
}
```
# Events API
Source: https://docs.neynar.com/snapchain/httpapi/events
HTTP API for retrieving events from the Snapchain hub
The events API returns events as they are merged into the Hub, which can be used to listen to Hub activity.
## eventById
Get an event by its Id
**Query Parameters**
| Parameter | Description | Example |
| ------------ | ----------------------------- | -------------------------- |
| event\_id | The Hub Id of the event | `event_id=350909155450880` |
| shard\_index | The shard index for the event | `shard_index=1` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/eventById?event_id=151622205440&shard_index=1
```
**Response**
```json theme={"system"}
{
"type": "HUB_EVENT_TYPE_BLOCK_CONFIRMED",
"id": 151622205440,
"blockConfirmedBody": {
"blockNumber": 9254285,
"shardIndex": 1,
"timestamp": 142732801,
"blockHash": "0x95659381b61ac3cd9fc06e61d9d9c256f8274aaab526cb23ce72856c2017f721",
"totalEvents": 10
},
"blockNumber": 9254285,
"shardIndex": 1
}
```
## events
Get a page of Hub events
**Query Parameters**
| Parameter | Description | Example |
| --------------- | -------------------------------------------------------------------------------------------- | ------------------------------- |
| from\_event\_id | An optional Hub Id to start getting events from. Set it to `0` to start from the first event | `from_event_id=350909155450880` |
| shard\_index | Optional shard index to query | `shard_index=1` |
| stop\_id | Optional stop event ID | `stop_id=350909170294785` |
| pageSize | Optional page size (default: 1000) | `pageSize=100` |
| pageToken | Optional page token for pagination | `pageToken=DAEDAAAGlQ...` |
| reverse | Optional reverse order flag | `reverse=true` |
Hubs prune events older than 3 days, so not all historical events can be fetched via this API
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/events?from_event_id=0
```
**Response**
```json theme={"system"}
{
"events": [
{
"type": "HUB_EVENT_TYPE_BLOCK_CONFIRMED",
"id": 151622205440,
"blockConfirmedBody": {
"blockNumber": 9254285,
"shardIndex": 1,
"timestamp": 142732801,
"blockHash": "0x95659381b61ac3cd9fc06e61d9d9c256f8274aaab526cb23ce72856c2017f721",
"totalEvents": 10
},
"blockNumber": 9254285,
"shardIndex": 1
},
{
"type": "HUB_EVENT_TYPE_MERGE_MESSAGE",
"id": 151622205441,
"mergeMessageBody": {
"message": {
"data": {
"type": "MESSAGE_TYPE_REACTION_ADD",
"fid": 310826,
"timestamp": 142732800,
"network": "FARCASTER_NETWORK_MAINNET",
"reactionBody": {
"type": "REACTION_TYPE_LIKE",
"targetCastId": {
"fid": 1026688,
"hash": "0xa1162b5d59281733daee1bcd3b810c5259f66ee1"
}
}
},
"hash": "0xfd4e55bb235fec5cad679182a2c926948d95b7cb",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "3N0jZHh46/gXa6uZS+jCbw/9eiOti3MyHNODn7cw5xqo7DBa45rixbzG2QNJtnDmF5XJb+q4GNv/eZF+19qQBw==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x217a69e523fbcc51643021d78f9a0fc98ac4e56c7418a2825f0870c81a5d18aa"
},
"deletedMessages": []
},
"blockNumber": 9254285,
"shardIndex": 1
}
]
}
```
# Fids API
Source: https://docs.neynar.com/snapchain/httpapi/fids
HTTP API for retrieving a list of all Farcaster IDs
## fids
Get a list of all the FIDs
**Query Parameters**
| Parameter | Description | Example |
| --------- | ---------------------------------- | ------------------------- |
| shard\_id | Required shard ID to query | `shard_id=1` |
| pageSize | Optional page size (default: 1000) | `pageSize=100` |
| pageToken | Optional page token for pagination | `pageToken=DAEDAAAGlQ...` |
| reverse | Optional reverse order flag | `reverse=true` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/fids?shard_id=1
```
**Response**
```json theme={"system"}
{
"fids": [1, 3, 5, 7, 11, 12, 15, 17, 18, 19, 21, 22, 23, 30, 31, 33, 36, 37, 41, 43, 47, 48, 52, 54, 55, 57, 58, 60, 62, 65, 67, 68, 70, 77, 79, 81, 85, 86, 87, 88, 89, 91, 92, 93, 94, 95, 96, 97, 99, 106],
"nextPageToken": "DAEDAAAGlQarXegAAACK"
}
```
# HTTP API
Source: https://docs.neynar.com/snapchain/httpapi/httpapi
Overview of Snapchain's HTTP API for interacting with Farcaster nodes
Snapchain nodes serve a HTTP API on port 3381 by default.
## Using the API
The API can be called from any programming language or browser by making a normal HTTP request.
**View the API responses in a browser**
Simply open the URL in a browser
```
http://127.0.0.1:3381/v1/castsByFid?fid=2
```
**Call the API using curl**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/castsByFid?fid=2
```
**Call the API via Javascript, using the axios library**
```javascript theme={"system"}
import axios from "axios";
const fid = 2;
const server = "http://127.0.0.1:3381";
try {
const response = await axios.get(`${server}/v1/castsByFid?fid=${fid}`);
console.log(`API Returned HTTP status ${response.status}`);
console.log(`First Cast's text is ${response.data.messages[0].data.castAddBody.text}`);
} catch (e) {
// Handle errors
console.log(e);
}
```
## Response encoding
Responses from the API are encoded as `application/json`, and can be parsed as normal JSON objects.
1. Hashes, ETH addresses, keys etc... are all encoded as hex strings starting with `0x`
2. Signatures and other binary fields are encoded in base64
3. Constants are encoded as their string types. For example, the `hashScheme` is encoded as `HASH_SCHEME_BLAKE3` which is equivalent to the `HASH_SCHEME_BLAKE3 = 1` from the protobuf schema.
## Timestamps
Messages contain timestamps which are seconds since the Farcaster Epoch, which began on Jan 1, 2021 00:00:00 UTC.
## Paging
Most endpoints support paging to get a large number of responses.
**Pagination Query Parameters**
| Parameter | Description | Example |
| --------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------- |
| pageSize | Maximum number of messages to return in a single response | `pageSize=100` |
| reverse | Reverse the sort order, returning latest messages first | `reverse=1` |
| pageToken | The page token returned by the previous query, to fetch the next page. If this parameters is empty, fetch the first page | `pageToken=AuzO1V0Dta...fStlynsGWT` |
The returned `nextPageToken` is empty if there are no more pages to return.
Pagination query parameters can be combined with other query parameters supported by the endpoint. For example, `/v1/casts?fid=2&pageSize=3`.
**Example**
Fetch all casts by FID `2`, fetching upto 3 casts per Page
```bash theme={"system"}
# Fetch first page
http://127.0.0.1:3381/v1/castsByFid?fid=2&pageSize=3
# Fetch next page. The pageToken is from the previous response(`response.nextPageToken`)
http://127.0.0.1:3381/v1/castsByFid?fid=2&pageSize=3&pageToken=AuzO1V0DtaItCwwa10X6YsfStlynsGWT
```
**Javascript Example**
```javascript theme={"system"}
import axios from "axios";
const fid = 2;
const server = "http://127.0.0.1:3381";
let nextPageToken = "";
do {
const response = await axios.get(`${server}/v1/castsByFid?fid=${fid}&pageSize=100&pageToken=${nextPageToken}`);
// Process response....
nextPageToken = response.data.nextPageToken;
} while (nextPageToken !== "")
```
## Timestamp Filtering
Some endpoints support filtering by timestamp, allowing you to retrieve results within a specific time range:
| Parameter | Description | Example |
| -------------- | ----------------------------------- | --------------------- |
| startTimestamp | Start of the time range (inclusive) | `startTimestamp=1000` |
| stopTimestamp | End of the time range (inclusive) | `stopTimestamp=2000` |
Example:
```bash theme={"system"}
# Get casts from FID 2 between timestamps 1000 and 2000
http://127.0.0.1:3381/v1/castsByFid?fid=2&startTimestamp=1000&stopTimestamp=2000
```
## Handling Errors
If there's an API error, the HTTP status code is set to `400` or `500` as appropriate. The response is a JSON object with `detail`, `errCode` and `metadata` fields set to identify and debug the errors.
**Example**
```bash theme={"system"}
$ curl "http://127.0.0.1:3381/v1/castById?fid=invalid"
{
"errCode": "bad_request.validation_failure",
"presentable": false,
"name": "HubError",
"code": 3,
"details": "fid must be an integer",
"metadata": {
"errcode": [
"bad_request.validation_failure",
],
},
}
```
## Limitations
The HTTP API currently does not support any of the Sync APIs that are available in the gRPC version. When nodes sync with each other, they will use the gRPC APIs instead of the HTTP APIs.
# Info API
Source: https://docs.neynar.com/snapchain/httpapi/info
HTTP API for getting Snapchain node information and statistics
## info
Get the Hub's info
**Query Parameters**
| Parameter | Description | Example |
| --------- | -------------------------- | ----------- |
| dbstats | Whether to return DB stats | `dbstats=1` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/info?dbstats=1
```
**Response**
```json theme={"system"}
{
"version": "1.5.5",
"isSyncing": false,
"nickname": "Farcaster Hub",
"rootHash": "fa349603a6c29d27041225261891bc9bc846bccb",
"dbStats": {
"numMessages": 4191203,
"numFidEvents": 20287,
"numFnameEvents": 20179
},
"peerId": "12D3KooWNr294AH1fviDQxRmQ4K79iFSGoRCWzGspVxPprJUKN47",
"hubOperatorFid": 6833
}
```
# Links API
Source: https://docs.neynar.com/snapchain/httpapi/links
HTTP API for retrieving links (follows) between Farcaster users
A Link represents a relationship between two users (e.g. follow)
The Links API will accept the following values for the `link_type` field.
| String | Description |
| ------ | ----------------------------- |
| follow | Follow from FID to Target FID |
## linkById
Get a link by its FID and target FID.
**Query Parameters**
| Parameter | Description | Example |
| ----------- | ----------------------------------- | ------------------ |
| fid | The FID of the link's originator | `fid=6833` |
| target\_fid | The FID of the target of the link | `target_fid=2` |
| link\_type | The type of link, as a string value | `link_type=follow` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/linkById?fid=6833&target_fid=2&link_type=follow
```
**Response**
```json theme={"system"}
{
"data": {
"type": "MESSAGE_TYPE_LINK_ADD",
"fid": 6833,
"timestamp": 61144470,
"network": "FARCASTER_NETWORK_MAINNET",
"linkBody": {
"type": "follow",
"targetFid": 2
}
},
"hash": "0x58c23eaf4f6e597bf3af44303a041afe9732971b",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "sMypYEMqSyY...nfCA==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x0852c07b56...06e999cdd"
}
```
## linksByFid
Get all links from a source FID
**Query Parameters**
| Parameter | Description | Example |
| ---------- | ----------------------------------- | ------------------------- |
| fid | The FID of the link's creator | `fid=6833` |
| link\_type | The type of link, as a string value | `link_type=follow` |
| pageSize | Optional page size (default: 1000) | `pageSize=100` |
| pageToken | Optional page token for pagination | `pageToken=DAEDAAAGlQ...` |
| reverse | Optional reverse order flag | `reverse=true` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/linksByFid?fid=6833
```
**Response**
```json theme={"system"}
{
"messages": [
{
"data": {
"type": "MESSAGE_TYPE_LINK_ADD",
"fid": 6833,
"timestamp": 61144470,
"network": "FARCASTER_NETWORK_MAINNET",
"linkBody": {
"type": "follow",
"targetFid": 83
}
},
"hash": "0x094e35891519c0e04791a6ba4d2eb63d17462f02",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "qYsfX08mS...McYq6IYMl+ECw==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x0852c0...a06e999cdd"
}
],
"nextPageToken": ""
}
```
## linksByTargetFid
Get all links to a target FID
**Query Parameters**
| Parameter | Description | Example |
| ----------- | ----------------------------------- | ------------------------- |
| target\_fid | The FID of the link's target | `target_fid=6833` |
| link\_type | The type of link, as a string value | `link_type=follow` |
| pageSize | Optional page size (default: 1000) | `pageSize=100` |
| pageToken | Optional page token for pagination | `pageToken=DAEDAAAGlQ...` |
| reverse | Optional reverse order flag | `reverse=true` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/linksByTargetFid?target_fid=6833
```
**Response**
```json theme={"system"}
{
"messages": [
{
"data": {
"type": "MESSAGE_TYPE_LINK_ADD",
"fid": 302,
"timestamp": 61144668,
"network": "FARCASTER_NETWORK_MAINNET",
"linkBody": {
"type": "follow",
"targetFid": 6833
}
},
"hash": "0x78c62531d96088f640ffe7e62088b49749efe286",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "frIZJGIizv...qQd9QJyCg==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x59a04...6860ddfab"
}
],
"nextPageToken": ""
}
```
# Message API
Source: https://docs.neynar.com/snapchain/httpapi/message
HTTP API for validating and submitting messages to Snapchain
The Message API lets you validate and submit signed Farcaster protocol messages to the Hub. Note that the message has to
be sent as the encoded bytestream of the protobuf (`Message.encode(msg).finish()` in typescript), as POST data to the
endpoint.
The encoding of the POST data has to be set to `application/octet-stream`. The endpoint returns the Message object as
JSON if it was successfully submitted or validated
## submitMessage
Submit a signed protobuf-serialized message to the Hub
**Query Parameters**
| Parameter | Description | Example |
| --------- | ----------------------------------- | ------- |
| | This endpoint accepts no parameters | |
**Example**
```bash theme={"system"}
curl -X POST "http://127.0.0.1:3381/v1/submitMessage" \
-H "Content-Type: application/octet-stream" \
--data-binary "@message.encoded.protobuf"
```
**Response**
```json theme={"system"}
{
"data": {
"type": "MESSAGE_TYPE_CAST_ADD",
"fid": 2,
"timestamp": 48994466,
"network": "FARCASTER_NETWORK_MAINNET",
"castAddBody": {
"embedsDeprecated": [],
"mentions": [],
"parentCastId": {
"fid": 226,
"hash": "0xa48dd46161d8e57725f5e26e34ec19c13ff7f3b9"
},
"text": "Cast Text",
"mentionsPositions": [],
"embeds": []
}
},
"hash": "0xd2b1ddc6c88e865a33cb1a565e0058d757042974",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "3msLXzxB4eEYe...dHrY1vkxcPAA==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x78ff9a...58c"
}
```
## submitBulkMessages
Submit several signed protobuf-serialized messages to the Hub at once. Each one will be submitted to the node sequentially.
**Query Parameters**
| Parameter | Description | Example |
| --------- | ----------------------------------- | ------- |
| | This endpoint accepts no parameters | |
**Example**
```bash theme={"system"}
curl -X POST "http://127.0.0.1:3381/v1/submitBulkMessages" \
-H "Content-Type: application/octet-stream" \
--data-binary "@SubmitBulkMessagesRequest.encoded.protobuf"
```
**Response**
```json theme={"system"}
[
{
"data": {
"type": "MESSAGE_TYPE_CAST_ADD",
"fid": 2,
"timestamp": 48994466,
"network": "FARCASTER_NETWORK_MAINNET",
"castAddBody": {
"embedsDeprecated": [],
"mentions": [],
"parentCastId": {
"fid": 226,
"hash": "0xa48dd46161d8e57725f5e26e34ec19c13ff7f3b9"
},
"text": "Cast Text",
"mentionsPositions": [],
"embeds": []
}
},
"hash": "0xd2b1ddc6c88e865a33cb1a565e0058d757042974",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "3msLXzxB4eEYe...dHrY1vkxcPAA==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x78ff9a...58c"
}
]
```
### Auth
If the rpc auth has been enabled on the server (using `--rpc-auth username:password`), you will need to also pass in the
username and password while calling `submitMessage` or `submitBulkMessages` using HTTP Basic Auth.
**Example**
```bash theme={"system"}
curl -X POST "http://127.0.0.1:3381/v1/submitMessage" \
-u "username:password" \
-H "Content-Type: application/octet-stream" \
--data-binary "@message.encoded.protobuf"
```
**JS Example**
```javascript theme={"system"}
import axios from "axios";
const url = `http://127.0.0.1:3381/v1/submitMessage`;
const postConfig = {
headers: { "Content-Type": "application/octet-stream" },
auth: { username: "username", password: "password" },
};
// Encode the message into a Buffer (of bytes)
const messageBytes = Buffer.from(Message.encode(castAdd).finish());
try {
const response = await axios.post(url, messageBytes, postConfig);
} catch (e) {
// handle errors...
}
```
## validateMessage
Validate a signed protobuf-serialized message with the Hub. This can be used to verify that the hub will consider the
message valid. Or to validate message that cannot be submitted (e.g. Frame actions)
The hub validates the following for all messages:
* The fid is registered
* The signer is active and registered to the fid
* The message hash is correct
* The signature is valid and corresponds to the signer
* Any other message specific validation
For FrameAction messages, note that the hub does not validate the castId is actually an existing cast. Nor
does it validate the frame url matches the embedded url in the cast. Make sure to check for this if it's
important for your application.
**Query Parameters**
| Parameter | Description | Example |
| --------- | ----------------------------------- | ------- |
| | This endpoint accepts no parameters | |
**Example**
```bash theme={"system"}
curl -X POST "http://127.0.0.1:3381/v1/validateMessage" \
-H "Content-Type: application/octet-stream" \
--data-binary "@message.encoded.protobuf"
```
**Response**
```json theme={"system"}
{
"valid": true,
"message": {
"data": {
"type": "MESSAGE_TYPE_FRAME_ACTION",
"fid": 2,
"timestamp": 48994466,
"network": "FARCASTER_NETWORK_MAINNET",
"frameActionBody": {
"url": "https://fcpolls.com/polls/1",
"buttonIndex": 2,
"inputText": "",
"castId": {
"fid": 226,
"hash": "0xa48dd46161d8e57725f5e26e34ec19c13ff7f3b9"
}
}
},
"hash": "0xd2b1ddc6c88e865a33cb1a565e0058d757042974",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "3msLXzxB4eEYe...dHrY1vkxcPAA==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x78ff9a...58c"
}
}
```
## Using with Rust, Go or other programming languages
Messages need to be signed with a Ed25519 account key belonging to the FID. If you are using a different programming
language
than Typescript, you can manually construct the `MessageData` object and serialize it to the `data_bytes` field of the
message. Then, use the `data_bytes` to compute the `hash` and `signature`. Please see
the [`rust-submitmessage` example](https://github.com/farcasterxyz/hub-monorepo/tree/main/packages/hub-web/examples) for
more details
```rust theme={"system"}
use ed25519_dalek::{SecretKey, Signer, SigningKey};
use hex::FromHex;
use reqwest::Client;
use message::{CastAddBody, FarcasterNetwork, MessageData};
use protobuf::Message;
#[tokio::main]
async fn main() {
let fid = 6833; // FID of the user submitting the message
let network = FarcasterNetwork::FARCASTER_NETWORK_MAINNET;
// Construct the cast add message
let mut cast_add = CastAddBody::new();
cast_add.set_text("Welcome to Rust!".to_string());
// Construct the cast add message data object
let mut msg_data = MessageData::new();
msg_data.set_field_type(message::MessageType::MESSAGE_TYPE_CAST_ADD);
msg_data.set_fid(fid);
msg_data.set_timestamp(
(std::time::SystemTime::now()
.duration_since(FARCASTER_EPOCH)
.unwrap()
.as_secs()) as u32,
);
msg_data.set_network(network);
msg_data.set_cast_add_body(cast_add);
let msg_data_bytes = msg_data.write_to_bytes().unwrap();
// Calculate the blake3 hash, truncated to 20 bytes
let hash = blake3::hash(&msg_data_bytes).as_bytes()[0..20].to_vec();
// Construct the actual message
let mut msg = message::Message::new();
msg.set_hash_scheme(message::HashScheme::HASH_SCHEME_BLAKE3);
msg.set_hash(hash);
// Sign the message. You need to use a signing key that corresponds to the FID you are adding.
// REPLACE THE PRIVATE KEY WITH YOUR OWN
let private_key = SigningKey::from_bytes(
&SecretKey::from_hex("0x...").expect("Please provide a valid private key"),
);
let signature = private_key.sign(&msg_data_bytes).to_bytes();
msg.set_signature_scheme(message::SignatureScheme::SIGNATURE_SCHEME_ED25519);
msg.set_signature(signature.to_vec());
msg.set_signer(private_key.verifying_key().to_bytes().to_vec());
// Serialize the message
msg.set_data_bytes(msg_data_bytes.to_vec());
let msg_bytes = msg.write_to_bytes().unwrap();
// Finally, submit the message to the network
// Create a reqwest Client
let client = Client::new();
// Define your endpoint URL
let url = "http://127.0.0.1:3381/v1/submitMessage";
// Make the POST request
let res = client
.post(url)
.header("Content-Type", "application/octet-stream")
.body(msg_bytes)
.send()
.await
.unwrap();
// Check if it's success
if res.status().is_success() {
println!("Successfully sent the message.");
} else {
println!("Failed to send the message. HTTP status: {}", res.status());
}
}
```
# OnChain API
Source: https://docs.neynar.com/snapchain/httpapi/onchain
HTTP API for retrieving on-chain events like signers, ID registry, and storage
## onChainSignersByFid
Get a list of account keys (signers) provided by an FID
**Query Parameters**
| Parameter | Description | Example |
| --------- | ---------------------------------- | --------------------------------------------------------------------------- |
| fid | The FID being requested | `fid=2` |
| signer | The optional key of signer | `signer=0x0852c07b5695ff94138b025e3f9b4788e06133f04e254f0ea0eb85a06e999cdd` |
| pageSize | Optional page size (default: 1000) | `pageSize=100` |
| pageToken | Optional page token for pagination | `pageToken=DAEDAAAGlQ...` |
| reverse | Optional reverse order flag | `reverse=true` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/onChainSignersByFid?fid=6833
```
**Response**
```json theme={"system"}
{
"events": [
{
"type": "EVENT_TYPE_SIGNER",
"chainId": 10,
"blockNumber": 108875854,
"blockHash": "0xceb1cdc21ee319b06f0455f1cedc0cd4669b471d283a5b2550b65aba0e0c1af0",
"blockTimestamp": 1693350485,
"transactionHash": "0x76e20cf2f7c3db4b78f00f6bb9a7b78b0acfb1eca4348c1f4b5819da66eb2bee",
"logIndex": 2,
"fid": 6833,
"signerEventBody": {
"key": "0x0852c07b5695ff94138b025e3f9b4788e06133f04e254f0ea0eb85a06e999cdd",
"keyType": 1,
"eventType": "SIGNER_EVENT_TYPE_ADD",
"metadata": "AAAAAAAAAAAA...AAAAAAAA",
"metadataType": 1
},
"txIndex": 0
}
]
}
```
## onChainEventsByFid
Get a list of account keys provided by an FID
**Query Parameters**
| Parameter | Description | Example |
| ----------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
| fid | The FID being requested | `fid=2` |
| event\_type | The string value of the event type being requested. This parameter is required | `event_type=EVENT_TYPE_SIGNER` OR `event_type=EVENT_TYPE_STORAGE_RENT` |
| pageSize | Optional page size (default: 1000) | `pageSize=100` |
| pageToken | Optional page token for pagination | `pageToken=DAEDAAAGlQ...` |
| reverse | Optional reverse order flag | `reverse=true` |
The onChainEventsByFid API will accept the following values for the `event_type` field.
| String |
| ----------------------------- |
| EVENT\_TYPE\_NONE |
| EVENT\_TYPE\_SIGNER |
| EVENT\_TYPE\_SIGNER\_MIGRATED |
| EVENT\_TYPE\_ID\_REGISTER |
| EVENT\_TYPE\_STORAGE\_RENT |
| EVENT\_TYPE\_TIER\_PURCHASE |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/onChainEventsByFid?fid=3&event_type=EVENT_TYPE_SIGNER
```
**Response**
```json theme={"system"}
{
"events": [
{
"type": "EVENT_TYPE_SIGNER",
"chainId": 10,
"blockNumber": 108875456,
"blockHash": "0x75fbbb8b2a4ede67ac350e1b0503c6a152c0091bd8e3ef4a6927d58e088eae28",
"blockTimestamp": 1693349689,
"transactionHash": "0x36ef79e6c460e6ae251908be13116ff0065960adb1ae032b4cc65a8352f28952",
"logIndex": 2,
"fid": 3,
"signerEventBody": {
"key": "0xc887f5bf385a4718eaee166481f1832198938cf33e98a82dc81a0b4b81ffe33d",
"keyType": 1,
"eventType": "SIGNER_EVENT_TYPE_ADD",
"metadata": "AAAAAAAAA...AAAAA",
"metadataType": 1
},
"txIndex": 0
}
]
}
```
## onChainIdRegistryEventByAddress
Get a list of on chain events for a given Address
**Query Parameters**
| Parameter | Description | Example |
| --------- | ------------------------------- | ---------------------------------------------------- |
| address | The ETH address being requested | `address=0x74232bf61e994655592747e20bdf6fa9b9476f79` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/onChainIdRegistryEventByAddress?address=0x74232bf61e994655592747e20bdf6fa9b9476f79
```
**Response**
```json theme={"system"}
{
"type": "EVENT_TYPE_ID_REGISTER",
"chainId": 10,
"blockNumber": 108874508,
"blockHash": "0x20d83804a26247ad8c26d672f2212b28268d145b8c1cefaa4126f7768f46682e",
"blockTimestamp": 1693347793,
"transactionHash": "0xf3481fc32227fbd982b5f30a87be32a2de1fc5736293cae7c3f169da48c3e764",
"logIndex": 7,
"fid": 3,
"idRegisterEventBody": {
"to": "0x74232bf61e994655592747e20bdf6fa9b9476f79",
"eventType": "ID_REGISTER_EVENT_TYPE_REGISTER",
"from": "0x",
"recoveryAddress": "0x00000000fcd5a8e45785c8a4b9a718c9348e4f18"
},
"txIndex": 0
}
```
## fidAddressType
Get the address type information for a given FID and address
**Query Parameters**
| Parameter | Description | Example |
| --------- | ------------------------ | ---------------------------------------------------- |
| fid | The FID being requested | `fid=2` |
| address | The ETH address to check | `address=0x91031dcfdea024b4d51e775486111d2b2a715871` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/fidAddressType?fid=2&address=0x91031dcfdea024b4d51e775486111d2b2a715871
```
**Response**
```json theme={"system"}
{
"is_custody": false,
"is_auth": false,
"is_verified": true
}
```
# Reactions API
Source: https://docs.neynar.com/snapchain/httpapi/reactions
HTTP API for retrieving reactions (likes and recasts) on Farcaster
The Reactions API will accept the following values for the `reaction_type` field.
| String | Description |
| ------ | ---------------------------------------- |
| Like | Like the target cast |
| Recast | Share target cast to the user's audience |
## reactionById
Get a reaction by its created FID and target Cast.
**Query Parameters**
| Parameter | Description | Example |
| -------------- | ----------------------------------------------- | -------------------------------------------------------- |
| fid | The FID of the reaction's creator | `fid=6833` |
| target\_fid | The FID of the cast's creator | `target_fid=2` |
| target\_hash | The cast's hash | `target_hash=0xa48dd46161d8e57725f5e26e34ec19c13ff7f3b9` |
| reaction\_type | The type of reaction, use string representation | `reaction_type=Like` OR `reaction_type=Recast` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/reactionById?fid=2&reaction_type=Like&target_fid=1795&target_hash=0x7363f449bfb0e7f01c5a1cc0054768ed5146abc0
```
**Response**
```json theme={"system"}
{
"data": {
"type": "MESSAGE_TYPE_REACTION_ADD",
"fid": 2,
"timestamp": 72752656,
"network": "FARCASTER_NETWORK_MAINNET",
"reactionBody": {
"type": "REACTION_TYPE_LIKE",
"targetCastId": {
"fid": 1795,
"hash": "0x7363f449bfb0e7f01c5a1cc0054768ed5146abc0"
}
}
},
"hash": "0x9fc9c51f6ea3acb84184efa88ba4f02e7d161766",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "F2OzKsn6Wj...gtyORbyCQ==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x78ff9a7...647b6d62558c"
}
```
## reactionsByFid
Get all reactions by an FID
**Query Parameters**
| Parameter | Description | Example |
| -------------- | ----------------------------------------------- | ---------------------------------------------- |
| fid | The FID of the reaction's creator | `fid=6833` |
| reaction\_type | The type of reaction, use string representation | `reaction_type=Like` OR `reaction_type=Recast` |
| pageSize | Optional page size (default: 1000) | `pageSize=100` |
| pageToken | Optional page token for pagination | `pageToken=DAEDAAAGlQ...` |
| reverse | Optional reverse order flag | `reverse=true` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/reactionsByFid?fid=2&reaction_type=Like
```
**Response**
```json theme={"system"}
{
"messages": [
{
"data": {
"type": "MESSAGE_TYPE_REACTION_ADD",
"fid": 2,
"timestamp": 72752656,
"network": "FARCASTER_NETWORK_MAINNET",
"reactionBody": {
"type": "REACTION_TYPE_LIKE",
"targetCastId": {
"fid": 1795,
"hash": "0x7363f449bfb0e7f01c5a1cc0054768ed5146abc0"
}
}
},
"hash": "0x9fc9c51f6ea3acb84184efa88ba4f02e7d161766",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "F2OzKsn6WjP8MTw...hqUbrAvp6mggtyORbyCQ==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x78ff9a768...62558c"
}
],
"nextPageToken": ""
}
```
## reactionsByCast
Get all reactions to a cast
**Query Parameters**
| Parameter | Description | Example |
| -------------- | ----------------------------------------------- | -------------------------------------------------------- |
| target\_fid | The FID of the cast's creator | `target_fid=6833` |
| target\_hash | The hash of the cast | `target_hash=0x7363f449bfb0e7f01c5a1cc0054768ed5146abc0` |
| reaction\_type | The type of reaction, use string representation | `reaction_type=Like` OR `reaction_type=Recast` |
| pageSize | Optional page size (default: 1000) | `pageSize=100` |
| pageToken | Optional page token for pagination | `pageToken=DAEDAAAGlQ...` |
| reverse | Optional reverse order flag | `reverse=true` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/reactionsByCast?target_fid=2&reaction_type=Like&target_hash=0x7363f449bfb0e7f01c5a1cc0054768ed5146abc0
```
**Response**
```json theme={"system"}
{
"messages": [
{
"data": {
"type": "MESSAGE_TYPE_REACTION_ADD",
"fid": 426,
"timestamp": 72750141,
"network": "FARCASTER_NETWORK_MAINNET",
"reactionBody": {
"type": "REACTION_TYPE_LIKE",
"targetCastId": {
"fid": 1795,
"hash": "0x7363f449bfb0e7f01c5a1cc0054768ed5146abc0"
}
}
},
"hash": "0x7662fba1be3166fc75acc0914a7b0e53468d5e7a",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "tmAUEYlt/+...R7IO3CA==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x13dd2...204e57bc2a"
}
],
"nextPageToken": ""
}
```
## reactionsByTarget
Get all reactions to cast's target URL
**Query Parameters**
| Parameter | Description | Example |
| -------------- | ----------------------------------------------- | ------------------------------------------------------------------------ |
| url | The URL of the parent cast | `url=chain://eip155:1/erc721:0x39d89b649ffa044383333d297e325d42d31329b2` |
| reaction\_type | The type of reaction, use string representation | `reaction_type=Like` OR `reaction_type=Recast` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/reactionsByTarget?url=chain://eip155:1/erc721:0x39d89b649ffa044383333d297e325d42d31329b2
```
**Response**
```json theme={"system"}
{
"messages": [
{
"data": {
"type": "MESSAGE_TYPE_REACTION_ADD",
"fid": 1134,
"timestamp": 79752856,
"network": "FARCASTER_NETWORK_MAINNET",
"reactionBody": {
"type": "REACTION_TYPE_LIKE",
"targetUrl": "chain://eip155:1/erc721:0x39d89b649ffa044383333d297e325d42d31329b2"
}
},
"hash": "0x94a0309cf11a07b95ace71c62837a8e61f17adfd",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "+f/+M...0Uqzd0Ag==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0xf6...3769198d4c"
}
],
"nextPageToken": ""
}
```
# Storage API
Source: https://docs.neynar.com/snapchain/httpapi/storagelimits
HTTP API for retrieving FID storage limits on Farcaster
## storageLimitsByFid
Get an FID's storage limits.
**Query Parameters**
| Parameter | Description | Example |
| --------- | ------------------------------ | ---------- |
| fid | The FID that's being requested | `fid=6833` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/storageLimitsByFid?fid=6833
```
**Response**
```json theme={"system"}
{
"limits": [
{
"storeType": "Casts",
"name": "CASTS",
"limit": 77000,
"used": 10510,
"earliestTimestamp": 0,
"earliestHash": []
},
{
"storeType": "Links",
"name": "LINKS",
"limit": 38500,
"used": 1742,
"earliestTimestamp": 0,
"earliestHash": []
},
{
"storeType": "Reactions",
"name": "REACTIONS",
"limit": 38500,
"used": 19578,
"earliestTimestamp": 0,
"earliestHash": []
},
{
"storeType": "UserData",
"name": "USER_DATA",
"limit": 800,
"used": 8,
"earliestTimestamp": 0,
"earliestHash": []
},
{
"storeType": "Verifications",
"name": "VERIFICATIONS",
"limit": 400,
"used": 7,
"earliestTimestamp": 0,
"earliestHash": []
},
{
"storeType": "UsernameProofs",
"name": "USERNAME_PROOFS",
"limit": 80,
"used": 1,
"earliestTimestamp": 0,
"earliestHash": []
}
],
"units": 515,
"unit_details": [
{
"unitType": "UnitTypeLegacy",
"unitSize": 15
},
{
"unitType": "UnitType2024",
"unitSize": 1
},
{
"unitType": "UnitType2025",
"unitSize": 0
}
],
"tier_subscriptions": [
{
"tier_type": "Pro",
"expires_at": 1781630485
}
]
}
```
# UserData API
Source: https://docs.neynar.com/snapchain/httpapi/userdata
HTTP API for retrieving user metadata on Farcaster
The UserData API will accept the following values for the `user_data_type` field.
| String | Numerical value | Description |
| -------------------------- | --------------- | ----------------------------- |
| USER\_DATA\_TYPE\_PFP | 1 | Profile Picture for the user |
| USER\_DATA\_TYPE\_DISPLAY | 2 | Display Name for the user |
| USER\_DATA\_TYPE\_BIO | 3 | Bio for the user |
| USER\_DATA\_TYPE\_URL | 5 | URL of the user |
| USER\_DATA\_TYPE\_USERNAME | 6 | Preferred Name for the user |
| USER\_DATA\_TYPE\_LOCATION | 7 | Location for the user |
| USER\_DATA\_TYPE\_TWITTER | 8 | Twitter username for the user |
| USER\_DATA\_TYPE\_GITHUB | 9 | GitHub username for the user |
See [FIP-196](https://github.com/farcasterxyz/protocol/discussions/196) for more information on Location.
See [FIP-19](https://github.com/farcasterxyz/protocol/discussions/199) for more information on Twitter/X and GitHub usernames.
## userDataByFid
Get UserData for a FID.
**Query Parameters**
| Parameter | Description | Example |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| fid | The FID that's being requested | `fid=6833` |
| user\_data\_type | The type of user data, either as a numerical value or type string. If this is omitted, all user data for the FID is returned | `user_data_type=1` OR `user_data_type=USER_DATA_TYPE_DISPLAY` |
| pageSize | Optional page size (default: 1000) | `pageSize=100` |
| pageToken | Optional page token for pagination | `pageToken=DAEDAAAGlQ...` |
| reverse | Optional reverse order flag | `reverse=true` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/userDataByFid?fid=6833&user_data_type=1
```
**Response**
```json theme={"system"}
{
"data": {
"type": "MESSAGE_TYPE_USER_DATA_ADD",
"fid": 6833,
"timestamp": 83433831,
"network": "FARCASTER_NETWORK_MAINNET",
"userDataBody": {
"type": "USER_DATA_TYPE_PFP",
"value": "https://i.imgur.com/HG54Hq6.png"
}
},
"hash": "0x327b8f47218c369ae01cc453cc23efc79f10181f",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "XITQZD7q...LdAlJ9Cg==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x0852...6e999cdd"
}
```
# Username Proofs API
Source: https://docs.neynar.com/snapchain/httpapi/usernameproof
HTTP API for retrieving username proofs on Farcaster
## userNameProofByName
Get a proof for a username by the Farcaster username
**Query Parameters**
| Parameter | Description | Example |
| --------- | ------------------------------------- | --------------------------------- |
| name | The Farcaster username or ENS address | `name=adityapk` OR `name=dwr.eth` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/userNameProofByName?name=adityapk
```
**Response**
```json theme={"system"}
{
"timestamp": 1670603245,
"name": "adityapk",
"owner": "Oi7uUaECifDm+larm+rzl3qQhcM=",
"signature": "fo5OhBP/ud...3IoJdhs=",
"fid": 6833,
"type": "USERNAME_TYPE_FNAME"
}
```
## userNameProofsByFid
Get a list of proofs provided by an FID
**Query Parameters**
| Parameter | Description | Example |
| --------- | ---------------------------------- | ------------------------- |
| fid | The FID being requested | `fid=2` |
| pageSize | Optional page size (default: 1000) | `pageSize=100` |
| pageToken | Optional page token for pagination | `pageToken=DAEDAAAGlQ...` |
| reverse | Optional reverse order flag | `reverse=true` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/userNameProofsByFid?fid=2
```
**Response**
```json theme={"system"}
{
"proofs": [
{
"timestamp": 1623910393,
"name": "v",
"owner": "0x4114e33eb831858649ea3702e1c9a2db3f626446",
"signature": "bANBae+Ub...kr3Bik4xs=",
"fid": 2,
"type": "USERNAME_TYPE_FNAME"
},
{
"timestamp": 1690329118,
"name": "varunsrin.eth",
"owner": "0x182327170fc284caaa5b1bc3e3878233f529d741",
"signature": "zCEszPt...zqxTiFqVBs=",
"fid": 2,
"type": "USERNAME_TYPE_ENS_L1"
}
]
}
```
# Verifications API
Source: https://docs.neynar.com/snapchain/httpapi/verification
HTTP API for retrieving address verifications on Farcaster
## verificationsByFid
Get a list of verifications provided by an FID
**Query Parameters**
| Parameter | Description | Example |
| --------- | ------------------------------------- | ---------------------------------------------------- |
| fid | The FID being requested | `fid=2` |
| address | The optional ETH address to filter by | `address=0x91031dcfdea024b4d51e775486111d2b2a715871` |
| pageSize | Optional page size (default: 1000) | `pageSize=100` |
| pageToken | Optional page token for pagination | `pageToken=DAEDAAAGlQ...` |
| reverse | Optional reverse order flag | `reverse=true` |
**Example**
```bash theme={"system"}
curl http://127.0.0.1:3381/v1/verificationsByFid?fid=2
```
**Response**
```json theme={"system"}
{
"messages": [
{
"data": {
"type": "MESSAGE_TYPE_VERIFICATION_ADD_ETH_ADDRESS",
"fid": 2,
"timestamp": 73244540,
"network": "FARCASTER_NETWORK_MAINNET",
"verificationAddEthAddressBody": {
"address": "0x91031dcfdea024b4d51e775486111d2b2a715871",
"ethSignature": "tyxj1...x1cYzhyxw=",
"blockHash": "0xd74860c4bbf574d5ad60f03a478a30f990e05ac723e138a5c860cdb3095f4296"
}
},
"hash": "0xa505331746ec8c5110a94bdb098cd964e43a8f2b",
"hashScheme": "HASH_SCHEME_BLAKE3",
"signature": "bln1zIZM.../4riB9IVBQ==",
"signatureScheme": "SIGNATURE_SCHEME_ED25519",
"signer": "0x78ff9...b6d62558c"
}
],
"nextPageToken": ""
}
```
# What is Snapchain?
Source: https://docs.neynar.com/snapchain/overview
Snapchain is the decentralized, peer-to-peer network that powers the Farcaster social network
Snapchain is the decentralized, peer-to-peer network that powers the [Farcaster social network](https://www.farcaster.xyz/).
1. Learn about Snapchain by reading the [whitepaper](/snapchain/whitepaper) or watching the [video overview](https://www.youtube.com/playlist?list=PL0eq1PLf6eUete1EtUvh7NTIhYCNcR46l).
2. Run a [Snapchain node](/snapchain/guides/running-a-node) to get real-time access to Farcaster data or to help decentralize the network.
# Contracts
Source: https://docs.neynar.com/snapchain/reference/contracts
Farcaster contracts documentation is available in the Farcaster reference section.
[Go to Contracts Documentation →](/farcaster/reference/contracts/index)
# Whitepaper
Source: https://docs.neynar.com/snapchain/whitepaper
Technical whitepaper explaining Snapchain's architecture, consensus mechanism, and design decisions
## Abstract
Snapchain is a blockchain-like network for storing and syncing Farcaster's social data. It has stronger and faster consistency guarantees than the current [deltagraph](https://www.varunsrinivasan.com/2024/04/28/the-goldilocks-consensus-problem) system which is finding it hard to keep all the nodes in sync in near real-time. The tradeoff we make for the consistency improvements is a new consensus step that introduces more complexity and failure modes which must be addressed.
## Problem
A decentralized social network is one where two users can find each other and communicate, even under adverse conditions. Users must be able to run a node and use it to communicate with each other. Each node must reach consensus about a user's state and stay in sync with other nodes. If Alice follows Bob at one node, it must make sure that she wasn't already following Bob and then update this relationship on every other node.
Users generate a lot of transactions and expect real-time delivery. Twitter, for example, has 200M daily users and sees 10k TPS and is likely to see 1TB - 10TB/day in state growth. Existing decentralized networks can't handle this kind of load with real-time delivery. It's not because it's impossible, but because they make tradeoffs to solve different user problems. Blockchains move money and are designed to prevent double spends, which makes sharding and pruning data difficult. Federated systems like email are shard-able but have weak decentralization and consistency, which makes apps harder to build. See Appendix D for more details.
Farcaster has used a CRDT-based system called a [deltagraph](https://www.varunsrinivasan.com/2024/04/28/the-goldilocks-consensus-problem) to decentralize social data. By defining every transaction as a CRDT operation, consensus is reached immediately without coordination at the local node. The changes are then gossiped out to peers which can lazily update their own state. The network served 100k users doing 500 TPS with 2GB/day state growth in early 2024.
As the network grew to thousands of nodes, some of them get out of sync due to gossip failures. Since CRDTs are unordered, a node could only detect gossip failures by syncing manually with every other node and comparing all transactions. This becomes slow and eventually infeasible as the number of nodes and valid messages cross some threshold (see Appendix C). The lack of ordering also meant that the network cannot enforce global rate limits, and they must be localized to each node. The side effect of this is that a transaction that passes the limits on one node might be rejected by another. Without strict ordering, it's hard to guarantee both real-time delivery and strong consistency.
## Specification
Snapchain introduces transaction ordering and blockchain-like semantics to Farcaster. A block production step is added which groups and orders user transactions. Syncing is much simpler since a node only needs to find and download missing blocks. Snapchain, like the deltagraph before it, relies on an external blockchain to handle account creation and fee collection.
Snapchain is different from most blockchains because its transactions are not turing complete, are account independent and pruned often. A transaction is a "post", "like" or other social operation which only affects a single account. This is important for scaling since it prevents the network from being used for non-social purposes and makes sharding by account easy. Older transactions are pruned to clear data from inactive users or negating transactions, such as when a user likes and unlikes something.
The initial release of snapchain should support a TPS of [> 9000](https://youtu.be/-p_SWPZ1_ew?si=peDiLXEZ1csgFGPc\&t=98) which would support 2 million daily users.
### 1. Accounts
Users create and manage accounts using an external blockchain. This incurs some fees during setup but is necessary for the strong security and consistency guarantees. Calling the registry contract onchain issues a unique account number or *farcaster id* to the wallet. Signed messages from this wallet are treated by Snapchain as authorized actions from the account. Accounts can be transferred between wallets at any time, though an address may only own one account at a time.
Accounts can acquire human-friendly ENS usernames by proving ownership with an onchain or offchain proof. All references to the account are made to the farcaster id, which in turn is mapped to the verified ENS username by clients. This lets users change their username without having to resign all data on the network. This system can also be extended to non-ENS name systems if desired.
Accounts can issue "app keys" onchain which are keys with a narrower set of permissions. They can post messages on behalf of the account but cannot change ownership of the account or modify other app keys. They are used like auth tokens to delegate permissions to clients safely. It may be possible to implement app keys on Snapchain in future, avoiding onchain fees for modifying them.
Account recovery is built into registry contract which lets the wallet nominate another address which also controls the farcaster id. This could be set to the user's primary wallet, an m-of-n social recovery multisig or institutional recovery wallet. User may also compose their own recovery systems by converting the wallet into a smart wallet which can implement custom recovery logic.
### 2. Transactions
A *blockchain transaction* is a Farcaster specific transactions that happens on an external blockchain. An example is when Alice makes a transaction to the registry contract to get her farcaster id and set up her app keys. Snapchain nodes listen to and store blockchain transactions in their history.
A *snapchain transaction* is a social action like making a new post. Alice says "Hello World" by making an *add-post* transaction, signing it with her app key and broadcasting it. Nodes verify that every transaction is correctly signed according to the specification. Common actions like deleting posts or following other users have their own transaction types. Snapchain transactions are self-authenticating and anyone can trace the authenticity from the message to the app key to the wallet to the farcaster id.
### 3. Account State
An account comes into existence when a blockchain transaction is made to create a new account in the registry. Its state is simply the set of blockchain and snapchain transactions that it generates. A deterministic state root can be computed by putting all the transaction ids into a merkle trie. Transactions made by one account cannot affect the state of another account. Enforcing this restriction makes Snapchain more scalable since account-level sharding becomes trivial to implement.
When a new transaction is accepted, it may be added to the state or it may replace a previous transaction in the state or it may delete a previous transaction entirely. In the example below, we see Alice's account state changing as she creates an account, adds a post and then deletes it.
*Formal definition: There exists a state (S) for an account (A) made up of transactions. S is a subset of all transactions made by a user (S ⊆ Ta). A merge function M accepts an S and t and returns a new state S' (M:S×t→S′). Each T is idempotent but not associative or commutative.*
### 4. Blocks
Snapchain and blockchain transactions are sequenced into blocks. A block must have a signature from the block producer, a link to the previous block and a global state root. The global state root is the root of the global state trie, whose leaves are the roots of each account state trie. If the state of any account changes, the global state root also changes.
Blocks are produced by a committee of block validators and tendermint is used to reach consensus. A leader is chosen to produce the block and at least two-thirds of other validators must sign off. Snapchain is byzantine tolerant and up to one-third of the network can be malicious without affecting block production. Validators are selected through a voting committee which is described in Appendix A.
Blocks are grouped into epochs that are K blocks in length. A special epoch block is published at the beginning which contains additional metadata used to re-configure chain parameters. These blocks must be preserved forever and cannot be pruned. One example of epoch metadata is the leader rotation schedule. Leaders must be rotated periodically or if they fail to produce a block. The schedule for the next K blocks is determined using a deterministic function and included in every epoch block.
Nodes get new blocks from their peers and update account states. After a week, non-epoch blocks can be pruned by nodes to free up disk space. Pruning permanently removes deleted posts and likes which is desirable feature for users. The week's delay ensures that nodes that go offline even for a few days can catch up by streaming blocks from their peers.
Nodes that go offline for long periods (or that start from scratch) must use snapshot sync instead. The protocol will publish daily snapshots of the global state to a file server as a public good. The snapshot is tamper-proof since modifying transactions will invalidate block signatures and omitting transactions will invalidate the global root. Nodes can download the state snapshot and then stream blocks from their peers to catch up.
### 5. State Rent
Decentralized networks can be flooded with transactions which consume disk space, bandwidth and compute. Blockchains control this by imposing a per-transaction fee, but this isn't great for a social network. If users have to worry about fees for each post, they will post less frequently which is bad for the network.
Snapchain gives users practically unlimited transactions if they pay a yearly fee. Users must rent a storage unit on the external blockchain after creating an account. Each unit gives them a rate limit (500 tx/hour) and a storage limit (10,000 txns) for their account state. Users can buy multiple units to increase these limits but in practice 99% of users rarely need more than one.
Usage feels "unlimited" because when storage limits are exceeded a user's oldest transaction is discarded instead of preventing the newer transaction from confirming. Each transaction type (post, like, follow) has its own set of limits and a newer post will push out the oldest post. This generally works well in a timeline based social network because older posts are rarely revisited and most users are comfortable with the ephemeral behavior. Those who want more permanence can pay for additional storage units or archive data elsewhere.
The benefits of this system are that users don't really have to think about storage and can just keep using the network. One downside is that a single storage unit must have separate, fixed limits for each type and users with different usage patterns may feel that they are wasting storage. Another downside is that while expiring the oldest message is a reasonable decision for posts, it may not be the right tradeoff for something like a follow. Apps may need to implement safeguards to protect users from blowing away certain historical data when limits are exceeded.
### 6. Sharding
Snapchain can be sharded into N segments using N+1 tendermint chains to improve scalability. Accounts are assigned to a chain using a deterministic function. In the example below, odd numbered accounts are assigned to one shard and even ones to the other. The 0th chain is used to unite all the shards so that they appear as a single chain. Our approach to sharding is inspired by [NEAR's Nightshade](https://pages.near.org/downloads/Nightshade.pdf).
A shard chain must have at least three validators and store all relevant account state. Validators may be automatically or manually rotated between shards through a validator schedule in the epoch block. Erasure coding is used to distribute account state from one shard across validators in other shards so that the data is still available even if all validators within a shard fail.
Block production is triggered when the previous block is finalized. Each shard chain bundles transactions into a block and computes a shard root, which is like the global root but limited to accounts in a shard. The 0th shard chain waits for the N shards to be produced and then performs another tendermint step bundling them into a single block and computes a global state root across the shard roots.
### 7. Sync
Nodes rely on gossip as the primary mechanism for p2p communication. When a block is produced, the block is gossiped out separately from the shards that compose it. Gossip failures are reasonably easy to recover from due to ordering. If a block is skipped, a sequence jump will be detected and the node is aware that they missed a block. All nodes will expose rpc endpoints which can be used to fetch older blocks.
Validators also rely on gossip to manage the mempool and for inter-validator communication when consensus is being reached on the state of a block. All the tendermint consensus steps happen via gossip messages.
### 8. Handling Failures
Validators can fail in a variety of ways and we must define how the network behaves in each scenario. Let's start with the honest malfunctions:
* Shard leader fails to produce a shard — after 1 second, consensus changes leadership according to the rotation. We can tolerate the failure of up to 1/3 of the validators.
* A shard is not produced in time for the block — block production continues. If they fail to produce a shard for an entire epoch, the chain is halted.
* A block is not produced — after 1 seconds, consensus changes leadership according to the rotation. We can tolerate the failure of up to 1/3 of the block leaders.
If nodes are behaving maliciously, there are more attack scenarios that are possible:
* Block leader excludes shards or halts production — mitigated by rotating leaders, but governance action is needed to evict them permanently and solve the issue.
* Shard leader excludes a user's transactions — mitigated by rotating shard leaders, but governance action is needed to evict them permanently and solve the issue.
* Shard validator majority excludes a user's transactions — if more than 2/3rd of a validators shards collude they can censor a user, and governance action is needed to resolve.
* Block validator majority excludes a shard — if more than 2/3rd of block validators collude they can censor a shard, and governance action is needed to resolve.
* Shard validator majority can reissue a shard before block finality — if more than 2/3rd of shard validators are malicious, they can reissue a shard for a block before it gets finalized.
* If > 2/3 majority of block validators and > 2/3 majority of one shard validators collude, they can reissue a block which would cause a network fork. Requires a refork and restart of the network.
### 9. Upgrade process
New node versions are released frequently, and nodes are expected to be kept up to date. There are two kinds of version upgrades:
**Non-consensus breaking upgrades**
These are the most common kind, usually containing bug fixes or performance improvements. They are backwards compatible and there is no need to coordinate with other nodes. Nodes can be upgraded at any time and will continue to work with older nodes. These changes are denoted with a patch version bump (e.g. 0.1.0 -> 0.1.1).
**Consensus breaking upgrades**
These are less common and usually contain breaking changes to the protocol. The changes are not backwards compatible, and a node may halt if it's not upgraded. These changes are denoted with a minor version bump (e.g. 0.1.0 -> 0.2.0).
Each block contains a version number for the protocol in its header. When a consensus breaking change is required, a new minor version is released with a PROTOCOL\_VERSION bump and a timestamp after which it will take effect. All nodes must upgrade to the latest minor version before this time. All blocks produced on or after this timestamp will have this newer version. Nodes will not accept blocks with an unexpected version number and old nodes will detect they are out of date and self terminate.
**Accidental breaking changes**
It's also possible a bug or non-deterministic behaviour causes a breaking changes. E.g. a consensus breaking change is made without a corresponding protocol version bump. In this case, the nodes will proceed as normal until they encounter a blocks with the breaking change. At which point, the merkle roots will not match and the nodes that are not upgraded will halt. If this happens to validators, then block production will halt until a new version is released with a fix.
## Frequently Asked Questions
### What exactly is hard about sync today?
A question that's come up a few times about Snapchain is some variant of "why is syncing hard today?"
1. **There is no source of truth to sync from** - Messages can be added or removed from any node at any point in history due to the eventually consistent nature of CRDTs. Changes are gossiped out when they happen, but this could fail for a variety of reasons. The only way for a node to catch up 100% is to 1) sync with every other node and compare every message and 2) prevent messages from entering the network until this is completed. There are 4000 nodes x 150 million messages today with 100s of messages changing every second making this impossible.
2. **Rate limits cause nodes to diverge** — rate limits are important to protect the network since we do not charge transaction fees. global rate limiting is impossible with crdts, so they are implemented per node. It is possible for a message to be temporarily rejected from a hub due to rate limits, but accepted by others.
3. **Pruning complicates things** — pruning means that when one message is received another, older message might be removed. this means that older state is constantly being modified by newer messages so its hard to be efficient about comparing message ids and hard to reason about why two nodes diverge.
4. **Unidirectional sync is slow**. A node can be "ahead" of another node for some accounts state and "behind" it for another account. In order for these nodes to get into sync, both of them must pull data from the other node (bidirectional sync) before any state change happens. In practice, this is challenging to implementing and we rely on unidirectional sync which means that only some state converges.
One class of solutions was "partial ordering" — the basic idea was that we would chain messages by having each message reference the previous one. The chains would either be per user or per app, instead of the total ordering that Snapchain proposes. The benefit of this approach is that we do not need a heavyweight consensus model since in the happy path each chain is typically only edited by one node at a time.
One way to think about this is that it reduces the sync space. Our nodes today must compare the total set of messages which is 150M items. If you can have a chain per user, that's down to 1M items. If you have a chain per app that's probably closer to just \~1000 unique items to compare per sync.
But there are still some unsolved problems:
1. **Pruning is not possible** — because there is a chain, we cannot easily prune older state because the tombstones are necessary for sync to function.
2. **Rate limits are still hard** — there's no way to reach consensus across users or apps, so the limits would still be local and diverge.
3. **Forking causes a lot of thrash** — a user or app can "fork" their chain by introducing a conflicting message at some point in history. This would invalidate all future messages, which causes a lot of sync thrash and is an easy way to DDOS the network.
4. **There is still no source of truth** — a node still has to sync with every other node to converge because we are using CRDTs. We have reduced the search space from 4000 nodes \* 150M items to 4000 nodes \* 1000 app chains. But nodes will still be slightly out of sync with each other, and the problem will return as we add more nodes or items.
5. **The migration path is messy** — since messages need to be chained to other messages, we have to update older messages to this new format. but the problem is that messages are signed, and unless the user comes online with their key the message cannot be upgraded. we cannot ensure that users return, so we must either deprecate older data after some cutoff or keep both sync models built into hubs for a really long time.
### Why not fork a blockchain instead of designing a new one?
An alternative to building snapchain would be to fork an existing blockchain to have similar properties. We would modify the VM so that the set of opcodes is limited to social actions and modify the transaction model to mirror snapchains rate-limit + pruning approach to metering usage. There are two challenges with this approach:
1. **Sharding** - given our tx volume and data size, we're going to need sharding soon. snapchains can be sharded by account easily because transactions are independent across accounts. blockchains have much more complicated sharding systems and we haven't found any that work in production yet. so there's a lot of implementation risk and unnecessary complexity.
2. **Pruning** - most chains we've looked at don't really have an easy way to bolt on pruning, or the ability to arbitrarily discard data from points in time cleanly. we would have to do a large refactor that touches most abstractions in the system.
Blockchains are doing a lot of work in both these areas and it is quite possible that in 2-3 years our POV on this has changed. But if we are making a decision today about the best solution for a 5 year time horizon, Snapchain seems like a better bet.
### Why was tendermint chosen as the consensus algorithm?
It has been used in production systems for many years, has fast finality and good liveness guarantees. There are also well written implementations in Go and even one in Rust.
### Will validators be able to censor users?
Censorship will be challenging with as few as ten globally distributed validators. There is no direct economic gain or loss caused by censorship. Users being censored can amplify their message via others and censorship is provable by observing transactions in the mempool. If all validators do collude, the voting committee described in Section A acts as a check and balance to change the validators set. If all the validators and voters collude, it may be possible to censor.
### Should we take a different approach that makes censorship even harder?
It is possible to design even more decentralized forms of governance and block production to make censorship less practical. The argument against this is that censorship is already reasonably impractical and most of these designs come with great cost to system complexity or user experience which makes the network less likely to be useful. It is also important to remember that Snapchain has been upgraded in the past as requirements have changed, and can be upgraded again in the future if necessary.