Skip to main content

Farcaster Portfolio

Access complete onchain portfolio data for any Farcaster user by resolving their addresses through via their FID (Farcaster ID) or username, then using those addresses to fetch their portfolio data.

Overview

This process involves two steps:

  1. Query the accounts endpoint to resolve Farcaster addresses
  2. Use the resolved addresses to query the portfolio endpoint

Typical Usage

A code sample is provided at the end of this page, implementing the following suggested setup.

Step 1: Resolve Farcaster Addresses

First, query the accounts endpoint using either FIDs or Farcaster usernames to get associated addresses.

Example Variables

{
"fids": [99], // Query by FID
"farcasterUsernames": ["jessepollak"] // Or query by username
}

Example Accounts Query

query GetFarcasterAddresses($fids: [Float!], $farcasterUsernames: [String!]) {
accounts(fids: $fids, farcasterUsernames: $farcasterUsernames) {
farcasterProfile {
username
fid
metadata {
displayName
description
imageUrl
warpcast
}
connectedAddresses
custodyAddress
}
}
}

Example Response

{
"data": {
"accounts": [
{
"farcasterProfile": {
"username": "jessepollak",
"fid": 99,
"metadata": {
"displayName": "jesse.base.eth 🔵",
"description": "@base builder #001; onchain cities w/ OAK & city3",
"imageUrl": "https://imagedelivery.net/BXluQx4ige9GuW0Ia56BHw/1013b0f6-1bf4-4f4e-15fb-34be06fede00/original",
"warpcast": "https://warpcast.com/jessepollak"
},
"connectedAddresses": [
"0x6adea326faea1b688af33df59e18f7a819bcaa4f",
"0x8e86e5331d3a020909c9e42ea9051675555f5e49",
"0x2211d1d0020daea8039e46cf1367962070d77da9",
"0x6e0d9c6dd8a08509bb625caa35dc61a991406f62",
"0xe73f9c181b571cac2bf3173634d04a9921b7ffcf",
"0x849151d7d0bf1f34b70d5cad5149d28cc2308bf1"
],
"custodyAddress": "0x4ce34af3378a00c640125e4dbf4c9e64dff4c93b"
}
},
]
}
}

Step 2: Fetch Portfolio Data

Using the connectedAddresses and custodyAddress obtained from Step 1, query the portfolio endpoint to get a complete onchain portfolio. Access rich data across tokenBalances, appBalances, nftBalances, totals, and claimables.

Combine the connectedAddresses and custodyAddress into a single array for the portfolio query:

Example Portfolio Variables

{
"addresses": [
// Connected addresses
"0x6adea326faea1b688af33df59e18f7a819bcaa4f",
"0x8e86e5331d3a020909c9e42ea9051675555f5e49",
"0x2211d1d0020daea8039e46cf1367962070d77da9",
"0x6e0d9c6dd8a08509bb625caa35dc61a991406f62",
"0xe73f9c181b571cac2bf3173634d04a9921b7ffcf",
"0x849151d7d0bf1f34b70d5cad5149d28cc2308bf1",
// Custody address
"0x4ce34af3378a00c640125e4dbf4c9e64dff4c93b"
]
}

Example Portfolio Query

query GetFarcasterPortfolio($addresses: [Address!]!) {
portfolio(addresses: $addresses) {
tokenBalances {
token {
balanceUSD
baseToken {
symbol
network
imgUrl
}
}
}
appBalances {
appName
balanceUSD
network
products {
label
assets {
... on AppTokenPositionBalance {
balanceUSD
symbol
}
}
}
}
nftBalances {
network
balanceUSD
}
totals {
total
totalWithNFT
totalByNetwork {
network
total
}
}
}
}

Example Response

{
"data": {
"portfolio": {
"tokenBalances": [
{
"token": {
"balanceUSD": 225178.39,
"baseToken": {
"symbol": "BKIT",
"network": "BASE_MAINNET",
"imgUrl": "https://storage.googleapis.com/zapper-fi-assets/tokens/base/0x262a9f4e84efa2816d87a68606bb4c1ea3874bf1.png"
}
}
},
{
"token": {
"balanceUSD": 77913.30,
"baseToken": {
"symbol": "SYNDOG",
"network": "BASE_MAINNET",
"imgUrl": "https://storage.googleapis.com/zapper-fi-assets/tokens/base/0x3d1d651761d535df881740ab50ba4bd8a2ec2c00.png"
}
}
}
// Additional token balances omitted
],
"appBalances": [
{
"appName": "Aerodrome",
"balanceUSD": 27393.52,
"network": "BASE_MAINNET",
"products": [
{
"label": "Pools",
"assets": [
{
"balanceUSD": 27393.52,
"symbol": "vAMM-WETH/DEGEN"
}
]
}
]
}
// Additional app balances omitted
],
"nftBalances": [
{
"network": "ETHEREUM_MAINNET",
"balanceUSD": 165510.66
},
{
"network": "BASE_MAINNET",
"balanceUSD": 153905.562958338
},
// Additional NFT balances omitted
],
"totals": {
"total": 716151.24,
"totalWithNFT": 881661.90,
"totalByNetwork": [
{
"network": "ETHEREUM_MAINNET",
"total": 2089.95
},
{
"network": "ARBITRUM_MAINNET",
"total": 4.58
},
{
"network": "OPTIMISM_MAINNET",
"total": 2107.74
},
{
"network": "POLYGON_MAINNET",
"total": 7640.83
},
{
"network": "DEGEN_MAINNET",
"total": 2377.78
},
{
"network": "ZORA_MAINNET",
"total": 411.11
}
// Additional totals omitted
]
}
}
}
}

Key Fields

Farcaster Profile Fields

FieldDescriptionType
usernameFarcaster usernameString!
fidFarcaster IDInt!
connectedAddressesArray of associated addresses[String!]!
custodyAddressAddress that owns the farcaster account.[String!]!
metadataProfile metadataFarcasterMetadata!

Metadata Fields

FieldDescriptionType
displayNameUser's display nameString
descriptionUser's profile description/bioString
imageUrlURL of the user's profile imageString
warpcastUser's Warpcast profile URLString
note

All metadata fields are optional and may be null. Always handle cases where these fields might not be present.

Portfolio Fields

Inherits all fields from the standard portfolio query, including:

  • Token balances
  • App positions
  • NFT holdings
  • Portfolio totals
tip

Remember to handle cases where a Farcaster user might have multiple connected addresses. You should aggregate portfolio data across all addresses for a complete view.

note

The connectedAddresses array includes all addresses that the Farcaster user has verified ownership of through the Farcaster protocol.

Error Handling

Common scenarios to handle:

  • Invalid or non-existent Farcaster username/FID
  • Farcaster profile with no connected addresses

Best Practices

  1. Cache resolved Farcaster addresses to minimize API calls
  2. Use batch queries when fetching portfolios for multiple Farcaster users
  3. Implement proper error handling for invalid or non-existent Farcaster profiles
  4. Consider implementing pagination for users with large portfolios

Code Example (React)

Following is an example of displaying portfolio data for a farcaster FID, and a farcaster username.

import { ApolloClient, InMemoryCache, createHttpLink, gql, useQuery, ApolloProvider } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

// Types for the GraphQL responses
interface BaseToken {
name: string;
symbol: string;
}

interface Token {
balance: string;
balanceUSD: number;
baseToken: BaseToken;
}

interface TokenBalance {
address: string;
network: string;
token: Token;
}

interface PortfolioData {
portfolio: {
tokenBalances: TokenBalance[];
};
}

interface AccountsData {
accounts: {
address: string;
}[];
}

// Set up Apollo Client
const httpLink = createHttpLink({
uri: 'https://public.zapper.xyz/graphql',
});

const API_KEY = 'YOUR_API_KEY';
const encodedKey = btoa(API_KEY);

const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: `Basic ${encodedKey}`,
},
};
});

const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});

const AccountsQuery = gql`
query($fids: [Float!], $farcasterUsernames: [String!]) {
accounts(fids: $fids, farcasterUsernames: $farcasterUsernames) {
address
}
}
`;

const PortfolioQuery = gql`
query providerPorfolioQuery($addresses: [Address!]!, $networks: [Network!]!) {
portfolio(addresses: $addresses, networks: $networks) {
tokenBalances {
address
network
token {
balance
balanceUSD
baseToken {
name
symbol
}
}
}
}
}
`;

function Portfolio() {
// First, fetch the addresses
const { loading: addressesLoading, data: addressesData } = useQuery<AccountsData>(AccountsQuery, {
variables: {
fids: [177],
farcasterUsernames: ['jasper'],
},
});

// Then use the addresses to fetch portfolio data
const { loading: portfolioLoading, data: portfolioData } = useQuery<PortfolioData>(PortfolioQuery, {
variables: {
addresses: addressesData?.accounts.map((account) => account.address) || [],
networks: ['ETHEREUM_MAINNET'],
},
skip: !addressesData,
});

// Handle loading states
if (addressesLoading || portfolioLoading) return <div>Loading...</div>;

// Handle no data
if (!addressesData || !portfolioData) return <div>No data found</div>;

return (
<div className="p-4">
<h2 className="text-xl font-bold mb-4">Portfolio Data for {addressesData.accounts.length} Addresses</h2>

{portfolioData.portfolio.tokenBalances.map((balance, index) => (
<div key={`${balance.address}-${index}`} className="mb-4 p-4 border rounded shadow-sm">
<p className="font-semibold">Token: {balance.token.baseToken.name}</p>
<p>Symbol: {balance.token.baseToken.symbol}</p>
<p>Balance: {balance.token.balance}</p>
<p>Value (USD): ${balance.token.balanceUSD.toFixed(2)}</p>
<p>Network: {balance.network}</p>
<p className="text-sm text-gray-600">Address: {balance.address}</p>
</div>
))}
</div>
);
}

function App() {
return (
<ApolloProvider client={client}>
<Portfolio />
</ApolloProvider>
);
}

export default App;
Try in sandbox