Step-by-step developer guide to building a Solana portfolio tracker with Birdeye Data: wallet holdings and net worth in one call, a 90-point net worth chart, server-computed PnL with win rate, and lazy-loaded price sparklines under the Wallet API beta rate limit.
Table of Contents
- Direct Answer: Solana Portfolio Tracker: The Easy 4-Step Build Guide
- Step 1: Fetch Wallet Holdings with the Current Net Worth API
- Step 2: Plot the Net Worth Chart
- Step 3: Calculate Wallet PnL with the PnL Details API
- Step 4: Render Token Sparklines with the Historical Price API
- Utility: Monitor Your CU Budget Programmatically
- Putting It Together: Reference Architecture
-
FAQ
- Which Birdeye Data API returns all token holdings of a Solana wallet?
- Is the old Wallet PnL GET endpoint still supported?
- What is the rate limit for Birdeye Wallet APIs?
- Do I need to normalize token balances by decimals myself?
- How do I keep dust tokens out of the portfolio view?
- Does Wallet PnL work on EVM chains?
- Can I track multiple wallets at once?
A portfolio tracker is the feature users open first and refresh most, and it is also where most wallet apps quietly fall apart. They index transactions themselves, reconcile token accounts by hand, recompute cost basis on every load, and then discover that none of it survives contact with a wallet holding 200 tokens.
This guide builds the data layer for a Solana portfolio tracker differently, using Birdeye Data: a multi-chain market data layer covering real-time prices, liquidity, and trade activity across Solana and major EVM networks, plus wallet holdings and net worth history on Solana, with server-computed PnL extending to major EVM networks as well. The entire tracker runs on 4 endpoints.
The pipeline is built around one rule: load the three wallet calls in parallel, lazy-load everything else. Wallet endpoints are in beta with a hard cap of 5 requests per second and 75 requests per minute on every plan. Spending that budget on anything besides core data is how a Solana portfolio tracker ends up rate-limited with one user on screen.
Direct Answer: Solana Portfolio Tracker: The Easy 4-Step Build Guide
To build a Solana portfolio tracker with Birdeye Data, you need 4 endpoints covering holdings, history, performance, and per-token charts.
- Call
GET /wallet/v2/current-net-worthto pull every token in the wallet with balances, prices, and USD values, plus the authoritative total net worth, in a single response. - Call
GET /wallet/v2/net-worthto plot historical net worth, up to 90 data points at hourly or daily resolution with percent changes precomputed. - Call
POST /wallet/v2/pnl/detailsto get server-computed PnL: win rate, total invested, realized and unrealized profit, broken down per token for up to 100 tokens. - Call
GET /defi/history_priceto render mini price sparklines per token, lazy-loaded for visible rows only.
The pipeline starts with the net worth call because one request hydrates the entire top half of the UI: the headline total and the full holdings table come from the same response. Everything else builds on that.
All endpoints share the same base URL (https://public-api.birdeye.so), use X-API-KEY for auth, and select the network via the x-chain header. The three /wallet/v2/ endpoints in this guide are Solana-focused and available on every plan, but they sit in a beta group with its own rate limit: 5 requests per second and 75 requests per minute, regardless of package. history_price is a standard market data endpoint outside that group, which is exactly why Step 4 is architected differently.
Step 1: Fetch Wallet Holdings with the Current Net Worth API
Endpoint: GET /wallet/v2/current-net-worth
Chains: Solana
Docs: Wallet — Current Net Worth
One call here does the work of two. The response gives you total_value (the wallet's net worth in USD), a current_timestamp, and an items array where each token includes address, decimals, price, balance (raw units as a string), amount (human-readable, already normalized by 10^-decimals), name, symbol, logo_uri, and value (USD value of the holding). Everything your holdings table renders is in there, and the headline number above it comes free.
Key parameters:

\bash
curl --request GET \
--url 'https://public-api.birdeye.so/wallet/v2/net-worth?wallet=YOUR_WALLET&count=30&type=1d&direction=back&sort_type=asc' \
--header 'X-API-KEY: YOUR_API_KEY' \
--header 'x-chain: solana'
\\
The count ceiling of 90 maps cleanly to the standard tracker timeframes: count=7&type=1d for the week view, count=30 for the month, count=90 for the quarter, and count=24&type=1h for an intraday view. Each timeframe is a single API call, so switching tabs in the UI is one request, not a refetch of the whole portfolio.
The total and the chart tell users what they have. The next step tells them whether they are actually winning.
Step 3: Calculate Wallet PnL with the PnL Details API
Endpoint: POST /wallet/v2/pnl/details
Chains: Solana and major EVM networks (x-chain: ethereum, base, bsc, arbitrum, polygon, optimism, avalanche, and more)
Docs: Wallet – PnL Details
This is where Solana portfolio trackers either earn trust or lose it. Users forgive a slow chart; they do not forgive a wrong PnL number. Birdeye computes realized and unrealized PnL server-side from swap activity, so you consume structured results instead of reconstructing cost basis from raw transactions.
A companion endpoint, GET /wallet/v2/pnl/summary, returns the wallet-level PnL figure on its own (docs). In practice you rarely need it for this build, because the details endpoint already returns the same wallet-level summary alongside the per-token rows, which is the CU optimization at the heart of this step.
Deprecation warning: the older
GET /wallet/v2/pnl(PnL Per Token) endpoint is marked deprecated in the official OpenAPI spec, and it caps at 50 tokens per request versus 100 on the POST. If you have existing code calling the GET version, migrate toPOST /wallet/v2/pnl/detailsnow rather than after it breaks.
Key request body fields:

\bash
curl --request POST \
--url 'https://public-api.birdeye.so/wallet/v2/pnl/details' \
--header 'X-API-KEY: YOUR_API_KEY' \
--header 'x-chain: solana' \
--header 'content-type: application/json' \
--data '{
"wallet": "YOUR_WALLET",
"duration": "30d",
"limit": 100
}'
\\
The response is unusually dense. For each token you get counts (buys, sells, total trades), quantity (bought, sold, current holding, already normalized by decimals), cashflow_usd (total invested, total sold, current value), pnl (realized profit in USD and percent, unrealized, combined total, average profit per trade), and pricing (current price, average buy cost, average sell cost).
The production shortcut hiding in this response: the payload also includes a top-level summary object covering the whole wallet, with unique_tokens, trade counts including total_win, total_loss, and win_rate, plus aggregate cashflow_usd and pnl figures. One POST populates both the headline performance card and the per-token PnL table. Under a wallet budget of 75 requests per minute, getting two UI sections from one call is exactly the kind of consolidation you want.
One more thing worth wiring up: point your UI's 24h / 7d / 30d / 90d / All tabs directly at the duration field and users get timeframe-scoped performance with one request per tab switch.
Core data is now complete in three wallet calls. The last step adds the visual polish, and it deliberately runs on a different budget.
Step 4: Render Token Sparklines with the Historical Price API
Endpoint: GET /defi/history_price
Chains: All supported networks
Docs: Price – Historical
Sparklines are the small 24h or 7d line charts your Solana portfolio tracker shows next to each token row. Each one needs its own history_price call, which is why this step must be architected differently from the first three.
Key parameters:

\bash
curl --request GET \
--url 'https://public-api.birdeye.so/defi/history_price?address=So11111111111111111111111111111111111111112&address_type=token&type=1H&time_from=1780963200&time_to=1781049600' \
--header 'X-API-KEY: YOUR_API_KEY' \
--header 'x-chain: solana'
\\
The response is a clean array of {unixTime, value} points, which maps directly into any charting library without transformation. For a 24h sparkline, type=1H over a 24-hour window returns 24 points, plenty of resolution at sparkline size.
Why sparklines are deferred, with the actual rate limit math: two separate limits are in play. The /wallet/v2/ group is capped at 5 requests per second and 75 per minute on every plan, and your three core calls fit easily. history_price is not a wallet endpoint, so it never touches that cap; it counts against your account-level limit instead, which depends on your package (15 req/sec on Lite and Starter, 50 on Premium, 100 on Business). The catch is that the account limit is shared across all APIs. A wallet holding 40 tokens means 40 sparkline calls, and firing them all at once on a Lite plan saturates the account limit for nearly 3 seconds, starving every other request your app makes, wallet calls included.
The fix is to lazy-load: fetch sparklines only for rows currently visible in the viewport, throttle the queue to a fraction of your package's req/sec, and cache results, since a 24h sparkline does not need refreshing more than every few minutes.
With the pipeline running, you need one more thing: visibility into how much CU budget all four steps are actually consuming.
Utility: Monitor Your CU Budget Programmatically
Endpoint: GET /utils/v1/credits
Docs: Utils – Credits Usage
Solana portfolio trackers are refresh-heavy by nature, so wire credit monitoring in from day one. Poll this endpoint on a schedule and you will know a popular wallet page is burning through the monthly compute unit budget before your users find out the hard way.
\bash
curl --request GET \
--url 'https://public-api.birdeye.so/utils/v1/credits' \
--header 'X-API-KEY: YOUR_API_KEY'
\\
With no parameters it returns the current billing cycle: total usage broken down into REST api and WebSocket ws, remaining credits, and overage_usage/overage_cost. You can also pass time_from/time_to as unix timestamps to query up to one year back, though remaining and overage fields return null for windows outside the current cycle.
A practical pattern: poll hourly and alert if remaining credits drop below a safe threshold. If they do, automatically widen the sparkline refresh interval rather than letting the tracker run into a 429.
Here is how all four steps connect into a single loading sequence for your Solana portfolio tracker.
Putting It Together: Reference Architecture

The loading sequence in JavaScript:
\`javascript
const BASE = "https://public-api.birdeye.so";
const HEADERS = {
"X-API-KEY": process.env.BIRDEYE_API_KEY,
"x-chain": "solana",
};
async function loadPortfolio(wallet) {
// Core data: 3 wallet calls in parallel, well under 5 req/sec
const [networth, chart, pnl] = await Promise.all([
fetch(${BASE}/wallet/v2/current-net-worth?wallet=${wallet}&sort_type=desc&limit=100, { headers: HEADERS }),
fetch(${BASE}/wallet/v2/net-worth?wallet=${wallet}&count=30&type=1d&sort_type=asc, { headers: HEADERS }),
fetch(${BASE}/wallet/v2/pnl/details, {
method: "POST",
headers: { ...HEADERS, "Content-Type": "application/json" },
body: JSON.stringify({ wallet, duration: "30d", limit: 100 }),
}),
]).then(rs => Promise.all(rs.map(r => r.json())));
return {
totalValue: networth.data.total_value,
holdings: networth.data.items,
history: chart.data.history,
pnlSummary: pnl.data.summary, // win_rate, total_invested, total profit
pnlPerToken: pnl.data.tokens,
};
}
// Sparklines: lazy-loaded per visible row, throttled, cached
async function loadSparkline(tokenAddress, fromUnix, toUnix) {
const url = ${BASE}/defi/history_price?address=${tokenAddress} +
&address_type=token&type=1H&time_from=${fromUnix}&time_to=${toUnix};
const res = await fetch(url, { headers: HEADERS });
const json = await res.json();
return json.data.items; // [{unixTime, value}, ...]
}
`\
Render order matters for perceived speed: paint the total value and holdings table the moment Promise.all resolves, draw the net worth chart and PnL cards from the same batch, then stream sparklines in as each one arrives. Users see a complete Solana portfolio in one round trip and the decorative layer fills in behind it.
Code review checklist before you ship your Solana portfolio tracker:
- Core data loads through one
Promise.allof exactly three wallet calls, nothing more. -
limit=100is set oncurrent-net-worthandpagination.totalis checked for whale wallets. - PnL goes through
POST /wallet/v2/pnl/details; no code path references the deprecatedGET /wallet/v2/pnl. - Sparklines fetch only visible rows, throttled below the package req/sec, with a few minutes of caching.
- Credit usage is observable, so degradation is controlled instead of a surprise 429.
FAQ
Which Birdeye Data API returns all token holdings of a Solana wallet?
GET /wallet/v2/current-net-worth returns the full portfolio of a wallet: every token with its balance, current price, and USD value, plus the wallet's total net worth, in one call. This is the core data your Solana portfolio tracker needs. Results are paginated up to 100 tokens per page.
Is the old Wallet PnL GET endpoint still supported?
GET /wallet/v2/pnl is marked deprecated in Birdeye's OpenAPI spec. Use POST /wallet/v2/pnl/details instead: it supports up to 100 tokens per request versus 50 on the deprecated GET, adds a duration filter from 24h to all, and returns a wallet-level summary object alongside the per-token breakdown.
What is the rate limit for Birdeye Wallet APIs?
All Wallet APIs are currently in beta and limited to 5 requests per second and 75 requests per minute, on every package tier. Standard market data endpoints like /defi/history_price are not part of the wallet group and instead count against your account-level limit: 15 req/sec on Lite and Starter, 50 on Premium, 100 on Business.
Do I need to normalize token balances by decimals myself?
No for display values in your Solana portfolio tracker: the amount field in the holdings response and all quantity fields in PnL responses are already normalized by 10^-decimals. The raw balance field (a string in base units) is also provided if you need exact integer math.
How do I keep dust tokens out of the portfolio view?
Pass filter_value with a minimum USD threshold on GET /wallet/v2/current-net-worth. The endpoint also excludes low-liquidity tokens under $100 by default unless you opt in with flags=include_low_liquidity.
Does Wallet PnL work on EVM chains?
Yes. POST /wallet/v2/pnl/details supports EVM networks through the x-chain header, including ethereum, base, bsc, arbitrum, polygon, optimism, and avalanche. The holdings and net worth chart endpoints in this guide remain Solana only, so an EVM build would pair the PnL endpoint with a different holdings source.
Can I track multiple wallets at once?
Yes, on higher tiers. POST /wallet/v2/net-worth-summary/multiple returns net worth for up to 100 wallets in a single call, keeping batch jobs inside the 75 requests per minute wallet budget. Like most batch endpoints at Birdeye, it is available on Business and Enterprise packages only.
Explore the full endpoint catalog in the Birdeye Data API reference or compare data packages at birdeye.so/data-api.


Top comments (0)