Portfolio Data
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
farcasterProfile
endpoint to get connected 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 farcasterProfile
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 farcasterProfile Query
query GetFarcasterProfile($username: String) {
farcasterProfile(username: $username) {
username
fid
metadata {
displayName
description
imageUrl
warpcast
}
custodyAddress
connectedAddresses
}
}
Example Response
{
"data": {
"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"
},
"custodyAddress": "0x4ce34af3378a00c640125e4dbf4c9e64dff4c93b",
"connectedAddresses": [
"0x2211d1d0020daea8039e46cf1367962070d77da9",
"0x6adea326faea1b688af33df59e18f7a819bcaa4f",
"0x6e0d9c6dd8a08509bb625caa35dc61a991406f62",
"0x849151d7d0bf1f34b70d5cad5149d28cc2308bf1",
"0x85c0ba9e1456bc755a6ce69e1a85ccaa1faa9e41",
"0x8e86e5331d3a020909c9e42ea9051675555f5e49",
"0xe73f9c181b571cac2bf3173634d04a9921b7ffcf"
]
}
}
}
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 (tokenBalances) Query
query TokenBalances($addresses: [Address!]!, $first: Int) {
portfolioV2(addresses: $addresses) {
tokenBalances {
totalBalanceUSD
byToken(first: $first) {
totalCount
edges {
node {
symbol
tokenAddress
balance
balanceUSD
price
imgUrlV2
name
network {
name
}
}
}
}
}
}
}
Example Response
{
"data": {
"portfolioV2": {
"tokenBalances": {
"totalBalanceUSD": 292328.8286857288,
"byToken": {
"totalCount": 1223,
"edges": [
{
"node": {
"symbol": "ETH",
"tokenAddress": "0x0000000000000000000000000000000000000000",
"balance": 17.286417826281596,
"balanceUSD": 45317.035788310255,
"price": 2621.54,
"imgUrlV2": "https://storage.googleapis.com/zapper-fi-assets/tokens/base/0x0000000000000000000000000000000000000000.png",
"name": "Ethereum",
"network": {
"name": "Base"
}
}
},
{
"node": {
"symbol": "SYNDOG",
"tokenAddress": "0x3d1d651761d535df881740ab50ba4bd8a2ec2c00",
"balance": 30000000,
"balanceUSD": 38615.1,
"price": 0.00128717,
"imgUrlV2": "https://storage.googleapis.com/zapper-fi-assets/tokens/base/0x3d1d651761d535df881740ab50ba4bd8a2ec2c00.png",
"name": "Synthesizer Dog",
"network": {
"name": "Base"
}
}
},
{
"node": {
"symbol": "BKIT",
"tokenAddress": "0x262a9f4e84efa2816d87a68606bb4c1ea3874bf1",
"balance": 28980487535.238518,
"balanceUSD": 28654.60195290476,
"price": 9.88755e-7,
"imgUrlV2": "https://storage.googleapis.com/zapper-fi-assets/tokens/base/0x262a9f4e84efa2816d87a68606bb4c1ea3874bf1.png",
"name": "Bangkit",
"network": {
"name": "Base"
}
}
},
{
"node": {
"symbol": "ERA",
"tokenAddress": "0xb82d1d9dd2ca8cfbdecdfbfef0aaebb07eb29312",
"balance": 50408655.54694379,
"balanceUSD": 13756.164212247695,
"price": 0.0002728929002963999,
"imgUrlV2": "https://storage.googleapis.com/zapper-fi-assets/tokens/base/0xb82d1d9dd2ca8cfbdecdfbfef0aaebb07eb29312.png",
"name": "BASE ERA",
"network": {
"name": "Base"
}
}
},
{
"node": {
"symbol": "BNKR",
"tokenAddress": "0x22af33fe49fd1fa80c7149773dde5890d3c76f3b",
"balance": 55169518.28804873,
"balanceUSD": 10414.901662417838,
"price": 0.00018878,
"imgUrlV2": "https://storage.googleapis.com/zapper-fi-assets/tokens/base/0x22af33fe49fd1fa80c7149773dde5890d3c76f3b.png",
"name": "BankrCoin",
"network": {
"name": "Base"
}
}
}
]
}
}
}
}
}
To provide users with real-time portfolio data, use the computation endpoints to fetch and compute the latest balances for given address(s):
computeAppBalances
- Get current app positions, balances, and values.
Next, use balanceJobStatus
to return a jobID
. This checks computation status. Once a jobID
returns completed
, balances for that address(s) from portfolio
will be up-to-date.
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;