curl --request GET \
--url https://api.dexploit.dev/v2/pnl/wallets/{wallet} \
--header 'X-API-Key: <api-key>'import requests
url = "https://api.dexploit.dev/v2/pnl/wallets/{wallet}"
headers = {"X-API-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-API-Key': '<api-key>'}};
fetch('https://api.dexploit.dev/v2/pnl/wallets/{wallet}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.dexploit.dev/v2/pnl/wallets/{wallet}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.dexploit.dev/v2/pnl/wallets/{wallet}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.dexploit.dev/v2/pnl/wallets/{wallet}")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.dexploit.dev/v2/pnl/wallets/{wallet}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"wallet": "<string>",
"pnl_mode": "<string>",
"realized_sol": 123,
"unrealized_sol": 123,
"cost_basis_sol": 123,
"open_positions": 123,
"closed_positions": 123,
"realized_usd": 123,
"unrealized_usd": 123,
"oracle": {
"price": 123,
"confidence": 123,
"published_at": 123,
"source": "<string>"
},
"positions": [
{
"mint": "<string>",
"balance_atomic": 123,
"realized_sol": 123,
"unrealized_sol": 123,
"is_arbitrageur": true
}
],
"positions_total": 123,
"positions_truncated": true,
"unrealized_basis": "full",
"unrealized_price_basis": "spot",
"priced_position_count": 123,
"unpriced_position_count": 123,
"unpriced_cost_basis_sol": 123,
"unpriced_cost_basis_usd": 123,
"spot_price_max_age_secs": 123,
"coverage_position_set": "onchain_held",
"unrealized_floor_sol": 123,
"unrealized_floor_usd": 123
}{
"success": false,
"error": {
"code": "INVALID_PARAM",
"message": "use pair_address; see /api/v1/pairs?token_address=X to discover pools"
}
}{
"success": false,
"error": {
"code": "INVALID_PARAM",
"message": "use pair_address; see /api/v1/pairs?token_address=X to discover pools"
}
}{
"success": false,
"error": {
"code": "INVALID_PARAM",
"message": "use pair_address; see /api/v1/pairs?token_address=X to discover pools"
}
}Wallet cost-basis PnL summary
Phase 8 cost-basis PnL summary for a wallet: realized + read-time-spot unrealized SOL, open/closed position counts, USD via Dexploit’s SOL/USD rate, and a compact per-position list. Empty wallets return zeros and an empty positions array.
Cached 5s when any position traded in the last 5 minutes, otherwise 30s.
curl --request GET \
--url https://api.dexploit.dev/v2/pnl/wallets/{wallet} \
--header 'X-API-Key: <api-key>'import requests
url = "https://api.dexploit.dev/v2/pnl/wallets/{wallet}"
headers = {"X-API-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-API-Key': '<api-key>'}};
fetch('https://api.dexploit.dev/v2/pnl/wallets/{wallet}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.dexploit.dev/v2/pnl/wallets/{wallet}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.dexploit.dev/v2/pnl/wallets/{wallet}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.dexploit.dev/v2/pnl/wallets/{wallet}")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.dexploit.dev/v2/pnl/wallets/{wallet}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"wallet": "<string>",
"pnl_mode": "<string>",
"realized_sol": 123,
"unrealized_sol": 123,
"cost_basis_sol": 123,
"open_positions": 123,
"closed_positions": 123,
"realized_usd": 123,
"unrealized_usd": 123,
"oracle": {
"price": 123,
"confidence": 123,
"published_at": 123,
"source": "<string>"
},
"positions": [
{
"mint": "<string>",
"balance_atomic": 123,
"realized_sol": 123,
"unrealized_sol": 123,
"is_arbitrageur": true
}
],
"positions_total": 123,
"positions_truncated": true,
"unrealized_basis": "full",
"unrealized_price_basis": "spot",
"priced_position_count": 123,
"unpriced_position_count": 123,
"unpriced_cost_basis_sol": 123,
"unpriced_cost_basis_usd": 123,
"spot_price_max_age_secs": 123,
"coverage_position_set": "onchain_held",
"unrealized_floor_sol": 123,
"unrealized_floor_usd": 123
}{
"success": false,
"error": {
"code": "INVALID_PARAM",
"message": "use pair_address; see /api/v1/pairs?token_address=X to discover pools"
}
}{
"success": false,
"error": {
"code": "INVALID_PARAM",
"message": "use pair_address; see /api/v1/pairs?token_address=X to discover pools"
}
}{
"success": false,
"error": {
"code": "INVALID_PARAM",
"message": "use pair_address; see /api/v1/pairs?token_address=X to discover pools"
}
}Authorizations
Preferred for swaps-api endpoints (/swaps/*, /stats/*, /trending, /pool-events).
Path Parameters
Wallet address (base58, 32–44 characters).
Query Parameters
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.
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.
Read-time spot unrealized PnL (null-priced mints contribute 0).
Count of positions with balance > 0 and not a quote mint.
Count of positions with balance == 0.
SOL/USD rate snapshot used for USD conversion, derived from on-chain SOL/stablecoin swaps. null when the rate is unavailable or stale.
Show child attributes
Show child attributes
Compact per-position rows.
Show child attributes
Show child attributes
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.
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.
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.
full, top_200 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.
spot, partial, none Open positions that had a live spot price and are therefore included in unrealized_sol.
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.
Total cost basis, in SOL, of the excluded positions — the magnitude of what unrealized_sol does not cover.
unpriced_cost_basis_sol in USD via Dexploit's SOL/USD rate. null when the rate is unavailable.
Maximum age of a trade for it to count as a live spot price (seconds).
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.
onchain_held, replay_unreconciled, onchain_held_basis_drift 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_sol in USD via Dexploit's SOL/USD rate. null when there is no floor or the rate is unavailable.

