Skip to main contentSkip to navigation
“Solana H1 2026 Report: Primed for the Third Leap” is out. Read the full report now.
Blog>Developer Guides>Solana Portfolio Tracker: The Easy 4-Step Build Guide

Solana Portfolio Tracker: The Easy 4-Step Build Guide

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.

Solana Portfolio Tracker: The Easy 4-Step Build Guide

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.

Four-step Solana portfolio tracker pipeline with Birdeye Data endpoints
  1. Call 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.
  2. Call GET /wallet/v2/net-worth to plot historical net worth, up to 90 data points at hourly or daily resolution with percent changes precomputed.
  3. Call 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.
  4. Call 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.

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:

ParameterRequiredNotes
walletYesThe wallet address
sort_typeYesdesc or asc (default desc)
sort_byNovalue (default)
filter_valueNoMinimum USD value threshold per token
flagsNoinclude_low_liquidity to include low-liquidity tokens
limitNo1–100 per page (default 20)
offsetNoPagination 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.

Step 2: Plot the Net Worth 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:

ParameterRequiredNotes
walletYesThe wallet address
sort_typeYesdesc or asc
countNoNumber of intervals, 1–90 (default 7)
typeNo1h (hourly) or 1d (daily), default 1d
directionNoback or forward relative to time (default back)
timeNoBase 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.

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 to POST /wallet/v2/pnl/details now rather than after it breaks.

Key request body fields:

FieldRequiredNotes
walletYesThe wallet address
token_addressesNoArray of token addresses to scope the breakdown
durationNoall, 90d, 30d, 7d, or 24h (default all)
sort_byNolast_trade (default)
sort_typeNodesc or asc (default desc)
limitNo1–100 (default 10), max 100 tokens per request
offsetNoMax 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.

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:

ParameterRequiredNotes
addressYesToken contract address
address_typeYestoken or pair
typeYes1m, 3m, 5m, 15m, 30m, 1H, 2H, 4H, 6H, 8H, 12H, 1D, 3D, 1W, 1M
time_fromYesStart time, unix seconds
time_toYesEnd 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.

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.

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

Solana portfolio tracker loading architecture with three parallel Birdeye wallet API calls

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:

  • Core data loads through one 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.
  • PnL goes through POST /wallet/v2/pnl/details; no code path references the deprecated GET /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.

Read next

Build a rug checker API with Birdeye Data: scan contract authority, mint and burn events, holder concentration, and real sellability across Solana and EVM.
Build a Solana new token sniper API with Birdeye Data: detect new token launches, gate every hit for rug risk, rank momentum, and watch positions over REST or WebSocket.
Step-by-step guide to building a Solana token screener with Birdeye Data: trending feed discovery, bulk scanning 5,000 tokens per call with 40+ filters, and CU-optimized watchlist refresh.
Don't miss out on what's next
Subscribe now and be the first to catch trends, tools, and exclusive updates.
© 2025, Wings Lab Pte. Ltd