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:
- Query the
accounts
endpoint to resolve Farcaster addresses - 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
Field | Description | Type |
---|---|---|
username | Farcaster username | String! |
fid | Farcaster ID | Int! |
connectedAddresses | Array of associated addresses | [String!]! |
custodyAddress | Address that owns the farcaster account. | [String!]! |
metadata | Profile metadata | FarcasterMetadata! |
Metadata Fields
Field | Description | Type |
---|---|---|
displayName | User's display name | String |
description | User's profile description/bio | String |
imageUrl | URL of the user's profile image | String |
warpcast | User's Warpcast profile URL | String |
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
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.
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
- Cache resolved Farcaster addresses to minimize API calls
- Use batch queries when fetching portfolios for multiple Farcaster users
- Implement proper error handling for invalid or non-existent Farcaster profiles
- 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;