Build "For You" Feeds for your Client, using Neynar and OpenRank

Create your own For You Feed for your client.

Developers can utilize Personalized Graphs and For You Feed Endpoints to seamlessly integrate with Neynar and set up a feed promptly. The current For You feed utilizes personalized network APIs to identify relevant casts based on your network's interactions with them. Here's a simple guide demonstrating how you can utilize the For You Feed API alongside Neynar's API to create a Personalized feed for your clients.

This guide expects you to have already have signed up for a Neynar account and have the basics set up ready to be able to consume Neynars' APIs. If you haven't, you can go through the getting started guide here.

Step 1: Getting the FID

When generating a personalized For You feed, we require the FID (Feed ID) for the user. In this example, we're using a static number. However, this step entirely depends on the client developer's preference, whether they opt for authentication or any other method to obtain the FID.

const USER_FID = 2025; // This could be static or also based on auth.

In this step, we simply take the base URL and append various parameters to create the fetching URL for the feed. By the end of this step, we will have an array containing all the casts relevant to your user, based on their personalized graph. We will use that array to fetch data for casts from Neynar.

const openRankBaseURL = 'https://graph.cast.k3l.io/casts/personalized/popular'
const recommendedCastHashesParams = 'agg=sumsquare&weights=L1C10R5Y1&k=1&offset=0&limit=25&graph_limit=100&lite=true'
const recommendedCastHashesUrl = `${openRankBaseURL}/${USER_FID}?${recommendedCastHashesParams}`;
const recommendedCastHashesResponses = await fetch(recommendedCastHashesUrl, {
  headers: {
    'Content-Type': 'application/json',
  },
});
const recommendedCastHashesArray = await recommendedCastHashesResponses.json().then(response => response.result);
console.log(recommendedCastHashesArray); // logs information about the cast hashes in ranked in order of recommendation

Step 3: Using Neynar to retrieve casts:

In the previous step, we successfully retrieved all the cast hashes in the order of their recommendation. Now, in this step, we transform the response from the previous step and connect to the Neynar API to obtain the cast information for each of the hashes. Neynar offers a convenient API endpoint where you can pass in an array of cast hashes and receive details for each cast in the response.

// Step 3: Integrating with Neynar to fetch the contents for these casts Hashes
// https://docs.neynar.com/reference/casts
const transformedCastHashesArray = recommendedCastHashesArray.map(element => element.cast_hash).join(',');
const neynarBaseURL = 'https://api.neynar.com/v2/farcaster/casts'
const neynarCastsURL = `${neynarBaseURL}?casts=${transformedCastHashesArray}`
const feedResponse = await fetch(neynarCastsURL, {
    headers: {
      'Content-Type': 'application/json',
      api_key: process.env.NEYNAR_API_KEY
    },
});
const feed = await feedResponse.json().then(response => response.result.casts);
console.log(feed) // logs the feed which is an array in order of all casts

Enabling Pagination

Building a client means you need to enable infinite scrolling and to enable that we need pagination.

The URL already has two parameters - offset: no of casts to skip from the entire list & limit: no of casts in each fetch - which can be used to generate the paginated feed.

For most part the code remains the same, we have just enclosed it within a paginatedFeed aync function which has two parameters, first the page and second the userFID which we can pass when calling the function. The logic on how to update the page parameter sits outside the async function.

Other that that we have only added two more variables in Step 2 - castsPerPage which essentially is the limit parameter and the offset parameter which we generate based on the page number. We pass these two variable to the recommendedCastHashParams string to which controls the parameters of the url.

Finally you have a feed that is paginated!

let page = 0;
const userFID = 2025;
const castsInPage = await paginatedFeed(page, userFID);
async function paginatedFeed(page, userFID) {

    // Step 1: Getting The Users FID
    const USER_FID = userFID;


    // Step 2: Getting the ranked casts hashes for creating the Personalized For You Feed
    const castsPerPage = 25; // Making casts per page as a configurable option
    const offset = page * castsPerPage; // Adding offset based on page.

    const openRankBaseURL = 'https://graph.cast.k3l.io/casts/personalized/popular'
    const recommendedCastHashesParams = `agg=sumsquare&weights=L1C10R5Y1&k=1&offset=${offset}&limit=${castsPerPage}&graph_limit=100&lite=true`
    const recommendedCastHashesUrl = `${openRankBaseURL}/${USER_FID}?${recommendedCastHashesParams}`;
    const recommendedCastHashesResponse = await fetch(recommendedCastHashesUrl, {
    headers: {
        'Content-Type': 'application/json',
    },
    });
    const recommendedCastHashesArray = await recommendedCastHashesResponse.json().then(response => response.result);


    // Step 3: Integrating with Neynar to fetch the contents for these casts Hashes
    // https://docs.neynar.com/reference/casts
    const transformedCastHashesArray = recommendedCastHashesArray.map(element => element.cast_hash).join(',');
    const neynarBaseURL = 'https://api.neynar.com/v2/farcaster/casts'
    const neynarCastsURL = `${neynarBaseURL}?casts=${transformedCastHashesArray}`
    const feedResponse = await fetch(neynarCastsURL, {
        headers: {
        'Content-Type': 'application/json',
        api_key: process.env.NEYNAR_API_KEY
        },
    });
    return await feedResponse.json().then(response => response.result.casts);
}

Configuring the feed

In the Step 2 above, If you notice in recommendedCastHashesParams, there are a few parameters we pass as defaults, however each of them can be configured to generate a different feed. Lets look at each of these parameters in detail.

const recommendedCastHashesParams = `agg=sumsquare&weights=L1C10R5Y1&k=1&offset=${offset}&limit=${castsPerPage}&graph_limit=100&lite=true`
ParameterDescriptionOptionsDefault

agg

Deciding which aggregation function to use to. Essentially decides how are the weights amplified.

sumsquare rms sum

sumsquare

weights

Linear combination of how each of the 4 actions (Like, Casts, Recast and Replies)

L{weight}C{weight}R{weight}Y{weight}

L1C10R5Y1 (i.e. Like has weight 1, Cast has weight 10, Recast has weight 10 and replies have weight 1)

k

How wide the your personalized network to spread when trying to use it to assign weights for the casts.

1 to 5

1

To understand this, we should first know why we need these weights. When generating feeds, it is actually a list of casts sorted or ranked based on the score associated with each cast. This score is calculated based on how the input FID (the user's personalized network) interacts with these casts.

A simple example of this works:

Let's there is a cast C1 and C2. The input FID is F1 and F1 has neighbors F11, F12, F13 and F14 with personalized scores S11, S12, S13 and S14 respectively. Some random profile Fx is the author of the cast C1 and neighbor F14 is the author of the cast C2.

F11 has liked C1, replied to C1 and recasted C1. F12 has replied to C1 and recasted C1. F13 has also replied to C1 and liked C2. F14 has liked C2.

If the API is called with agg=sumsquare, weights='L1C10R5Y1'

The score of Cast C1 for FID F1 will be:

[(S11 * L1 * TD(ts)) + (S11 * Y1 * TD(ts)) + (S11 * R5 * TD(ts))]^2 +
[(S12 * Y1 * TD(ts)) + (S12 * R5 * TD(ts))]^2 +
[(S13 * Y1 * TD(ts)) + (S13 * L1 * TD(ts))]^2 

The score of Cast C2 for FID F1 will be:

[(S14 * C10 * TD(ts)) + (S14 * L1 * TD(ts))]^2 +
[(S13 * L1 * TD(ts))]^2

Here L1 = weight of Like is 1, Y1 = weight of reply 1, R5 = weight of recast is 5, C10 = weight of cast is 10

Whats TD? We apply an hourly time decay value of TD(action) = (1-(1/(365*24)) ^ ActionTimestamp

Similarly, for an input FID, the score is calculated for each cast in the network. This score is then used to rank or sort the casts, which is how the feed is generated. By changing the aggregation parameter agg, we can control how the final score is calculated and adjusting the weights allows us to determine the importance given to each of the various actions.

Last updated

Logo

Copyright 2024