Skip to main content
GET
Wallet cost-basis PnL summary

Authorizations

X-API-Key
string
header
required

Preferred for swaps-api endpoints (/swaps/*, /stats/*, /trending, /pool-events).

Path Parameters

wallet
string
required

Wallet address (base58, 32–44 characters).

Query Parameters

pnl_mode
enum<string>
default:adjusted

PnL accounting mode. strict (FIFO) and adjusted (weighted-avg cost) are realized-PnL figures; raw is net cash-flow (Σ sells − Σ buys), not realized PnL. Unknown values fall back to adjusted.

Available options:
raw,
strict,
adjusted

Response

PnL summary

Cost-basis PnL summary for a wallet (Phase 8 engine). All *_sol fields are SOL. unrealized_sol is read-time spot Σ(price−avg_cost)*balance; null-priced mints contribute 0. Wallets with no positions return zeros and an empty positions array.

wallet
string
pnl_mode
string
realized_sol
number
unrealized_sol
number

Read-time spot unrealized PnL (null-priced mints contribute 0).

cost_basis_sol
number
open_positions
integer

Count of positions with balance > 0 and not a quote mint.

closed_positions
integer

Count of positions with balance == 0.

realized_usd
number | null
unrealized_usd
number | null
oracle
object | null

SOL/USD rate snapshot used for USD conversion, derived from on-chain SOL/stablecoin swaps. null when the rate is unavailable or stale.

positions
object[]

Compact per-position rows.

positions_total
integer

How many positions the wallet has in the ledger, open and closed (one per mint). positions[] is capped at 200, so this is the size of the full list, which is paged at /v2/pnl/wallets/{wallet}/positions. Absent from the degraded answer returned when the ledger read times out.

positions_truncated
boolean

true when positions[] holds only the 200 most material positions of positions_total; the rest are at /v2/pnl/wallets/{wallet}/positions. Always true for a wallet with more than 2,000 positions. It says nothing about the aggregate figures: see unrealized_basis.

unrealized_basis
enum<string>

Whether unrealized_sol was summed over every position. full: it was; only the inline positions[] list is capped. top_200: only the 200 most material positions were priced (a wallet with more than 2,000 positions). realized_sol, cost_basis_sol and the open and closed counts are still exact, and no unrealized_floor_sol is published. This is not unrealized_price_basis, which says how many of the counted positions had a live price.

Available options:
full,
top_200
unrealized_price_basis
enum<string>

How much of the open book unrealized_sol covers. spot = every counted position had a live spot price; partial = some were excluded; none = nothing was priced, so unrealized_sol is 0 for lack of data, NOT because the wallet is flat. Distinct from unrealized_basis, which answers whether the whole position LIST was looked at.

Available options:
spot,
partial,
none
priced_position_count
integer

Open positions that had a live spot price and are therefore included in unrealized_sol.

unpriced_position_count
integer

Open positions with NO live spot price inside spot_price_max_age_secs. They are EXCLUDED from unrealized_sol — not valued at zero and not assumed break-even, both of which would be wrong in a direction the caller cannot detect.

unpriced_cost_basis_sol
number

Total cost basis, in SOL, of the excluded positions — the magnitude of what unrealized_sol does not cover.

unpriced_cost_basis_usd
number | null

unpriced_cost_basis_sol in USD via Dexploit's SOL/USD rate. null when the rate is unavailable.

spot_price_max_age_secs
integer

Maximum age of a trade for it to count as a live spot price (seconds).

coverage_position_set
enum<string>

WHICH set the coverage counts describe. onchain_held = every counted position was confirmed still held on chain. replay_unreconciled = on-chain truth was unavailable, so the counts come from the raw replay/ledger set and include bags the wallet may already have exited; treat them as an UPPER bound on the open book. onchain_held_basis_drift = held on chain, but at least one position's on-chain balance is above the quantity the trade ledger accounts for, so its cost basis understates it and no floor is published.

Available options:
onchain_held,
replay_unreconciled,
onchain_held_basis_drift
unrealized_floor_sol
number | null

Lower bound on the wallet's true unrealized PnL in SOL: unrealized_sol with every unpriced bag valued at zero. Non-null only when coverage_position_set is onchain_held AND the figure covers every open position. Otherwise it is null, because no bound holds: with replay_unreconciled the priced side can contain phantom gains; with onchain_held_basis_drift the cost basis understates the bag, so the figure would sit above the true minimum; and for a wallet with more than 2,000 positions (open and closed) the summary prices only 200 of them (unrealized_basis: "top_200"), so the untouched tail could each fall to its full cost basis, even though coverage_position_set reads onchain_held. The degraded answer for a wallet whose ledger read times out carries no coverage fields at all.

unrealized_floor_usd
number | null

unrealized_floor_sol in USD via Dexploit's SOL/USD rate. null when there is no floor or the rate is unavailable.