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.
July 3, 2026
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.
To build a Solana portfolio tracker with Birdeye Data, you need 4 endpoints covering holdings, history, performance, and per-token charts.

GET /wallet/v2/current-net-worth to pull every token in the wallet with balances, prices, and USD values, plus the authoritative total net worth, in a single response.GET /wallet/v2/net-worth to plot historical net worth, up to 90 data points at hourly or daily resolution with percent changes precomputed.POST /wallet/v2/pnl/details to get server-computed PnL: win rate, total invested, realized and unrealized profit, broken down per token for up to 100 tokens.GET /defi/history_price to 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.
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:
| Parameter | Required | Notes |
|---|---|---|
wallet | Yes | The wallet address |
sort_type | Yes | desc or asc (default desc) |
sort_by | No | value (default) |
filter_value | No | Minimum USD value threshold per token |
flags | No | include_low_liquidity to include low-liquidity tokens |
limit | No | 1–100 per page (default 20) |
offset | No | Pagination cursor, max 10,000 |
curl --request GET \\
--url '<https://public-api.birdeye.so/wallet/v2/current-net-worth?wallet=YOUR_WALLET&sort_type=desc&limit=100>' \\
--header 'X-API-KEY: YOUR_API_KEY' \\
--header 'x-chain: solana'
Dust filtering is built in, so do not do it client-side. Two mechanisms handle it for you: by default the endpoint excludes low-liquidity tokens under $100, visible only if you explicitly pass flags=include_low_liquidity, and the filter_value parameter sets a minimum USD value per position, so filter_value=1 drops everything worth less than a dollar before the response reaches your app. Filtering at the API level means smaller payloads and no wasted render cycles on tokens nobody cares about.
One thing that trips people up: the default limit is 20, which is too small for whale wallets. Set limit=100 on the first call and check pagination.total in the response to decide whether you need additional pages.
Now you have the total and the table. The next step turns that static number into a chart.
Endpoint: GET /wallet/v2/net-worth
Chains: Solana
Docs: Wallet – Net Worth Chart
The net worth chart is what separates a Solana portfolio tracker from a balance screen. This endpoint returns a history array of data points, each with a timestamp, net_worth, net_worth_change, and net_worth_change_percent. That last field is a gift: the percent change between intervals is precomputed, so your chart tooltips need zero client-side math.
Key parameters:
| Parameter | Required | Notes |
|---|---|---|
wallet | Yes | The wallet address |
sort_type | Yes | desc or asc |
count | No | Number of intervals, 1–90 (default 7) |
type | No | 1h (hourly) or 1d (daily), default 1d |
direction | No | back or forward relative to time (default back) |
time | No | Base timestamp in ISO 8601 UTC (default now) |
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.
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 to POST /wallet/v2/pnl/details now rather than after it breaks.
Key request body fields:
| Field | Required | Notes |
|---|---|---|
wallet | Yes | The wallet address |
token_addresses | No | Array of token addresses to scope the breakdown |
duration | No | all, 90d, 30d, 7d, or 24h (default all) |
sort_by | No | last_trade (default) |
sort_type | No | desc or asc (default desc) |
limit | No | 1–100 (default 10), max 100 tokens per request |
offset | No | Max 10,000 |
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.
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:
| Parameter | Required | Notes |
|---|---|---|
address | Yes | Token contract address |
address_type | Yes | token or pair |
type | Yes | 1m, 3m, 5m, 15m, 30m, 1H, 2H, 4H, 6H, 8H, 12H, 1D, 3D, 1W, 1M |
time_from | Yes | Start time, unix seconds |
time_to | Yes | End time, unix seconds |
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.
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.
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.

The loading sequence in 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:
Promise.all of exactly three wallet calls, nothing more.limit=100 is set on current-net-worth and pagination.total is checked for whale wallets.POST /wallet/v2/pnl/details; no code path references the deprecated GET /wallet/v2/pnl.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.
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.
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.
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.
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.
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.
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.
Birdeye provides expansive data covering tokens, wallets, trades, and protocols across 300+ exchanges on 10 chains.
Whether you’re a solo tinkerer or a large team looking to scale, Birdeye offers plans that caters for your data needs and budget.
Dive into our docs and start querying data on 60+ APIs and 8 WebSocket types today!
Insights is a feature that allows users to analyze market trends in various aspects and dive deep into many industry sectors.
Find Gems is a feature that helps user identify potential Tokens at the current time.
Launch Explorer is a feature that enables users to access real-time data of tokens on popular launchpads like pump.fun, letsbonk.fun,...
New insight article by Birdeye reveals USDC's breakout growth in recent years
Data by Birdeye shows total trading volume of xStocks, PreStocks, and Ondo Global Markets on Solana peaked in March 2026
After the Drift Protocol's hack, Solana Foundation initiated programs such as STRIDE and SIRIN to tighten security for ecosystem teams
July 3, 2026
July 3, 2026
July 3, 2026