Get a wallet's holdings and PnL on Robinhood Chain
Get a Robinhood Chain wallet's tokens with USD value, ETH balance, realized and unrealized PnL, win rate, gas spent and wallet tags in one call. Real example.
Call GET /v1/robinhood/wallet/{address}?window=30d (Starter plan, 5 credits). It returns holdings[] (token, balance, value_usd, price_confidence, per-token pnl), native_balance in ETH, and a pnl summary over the window (realized_usd, unrealized_usd, total_usd, win_rate_pct, trades, volume_usd, gas_spent_usd), plus account_type and profile.tags.
A wallet profile is everything a Robinhood Chain address holds and has earned: its tokens with their USD value, its ETH balance, its realized and unrealized PnL over a window, its win rate and gas spent, and what kind of account it is. Crius reads the balances on-chain at one block and computes PnL from every trade attributed to the wallet, so a portfolio app or a wallet-scoring tool gets the whole picture in one call.
Get the profile in one call
CHARGE's creator: 0.71 ETH, two fake USDG tokens, no trades
What does the wallet that launched Charge AI (0x3ac82d45ff9e4963cefbc3f74dbe293f34c927ca) hold, and has it traded in the last 30 days?
curl "https://api.crius.sh/v1/robinhood/wallet/0x3ac82d45ff9e4963cefbc3f74dbe293f34c927ca" \ -H "x-api-key: $CRIUS_API_KEY"const res = await fetch("https://api.crius.sh/v1/robinhood/wallet/0x3ac82d45ff9e4963cefbc3f74dbe293f34c927ca", { headers: { "x-api-key": process.env.CRIUS_API_KEY },});const { data, meta } = await res.json();import os, requests
r = requests.get( "https://api.crius.sh/v1/robinhood/wallet/0x3ac82d45ff9e4963cefbc3f74dbe293f34c927ca", headers={"x-api-key": os.environ["CRIUS_API_KEY"]},)body = r.json()print(body["data"])Real response 200 Captured Oct 28, 2026 · 84 lines (arrays trimmed)
{ "data": { "account_type": "smart_account", "address": "0x3ac82d45ff9e4963cefbc3f74dbe293f34c927ca", "delegate": "0x63c0c19a282a1b52b07dd5a65b58948a07dae32b", "holdings": [ { "balance": 25, "pnl": null, "price_confidence": "low", "token": { "address": "0xfaaf8ffa8a29ceb6c85aff8d3961f65ae1bfc775", "logo": null, "symbol": "USDG" }, "value_eth": 0.00000500064, "value_usd": 0.0117789 }, { "balance": 25, "pnl": null, "price_confidence": "low", "token": { "address": "0x06993ae1230d3755b54f25022028daa66772bc3d", "logo": null, "symbol": "USDG" }, "value_eth": 0.00000500061, "value_usd": 0.0117789 } ], "holdings_truncated": false, "holdings_value_usd": 0.0235578, "is_contract": false, "label": null, "native_balance": 0.71473787532832551, "native_value_usd": 1683.56, "pnl": { "avg_hold_time_s": null, "best_trade": null, "gas_scope": "all transactions sent by the wallet in the window", "gas_spent_usd": 0.198772, "realized_usd": 0, "tokens_traded": 0, "tokens_with_known_cost": 0, "total_usd": 0, "trades": 0, "unrealized_usd": 0, "volume_usd": 0, "win_rate_pct": null, "window": "30d", "worst_trade": null }, "profile": { "first_seen_block": 91351250, "first_seen_ts": 1792283372000, "fresh": false, "funded_by": { "address": "0xd782268f600403843484b70710daee6846fc0db8", "amount_eth": 0.008148386337868892, "block": 87601233, "label": "bridge:Relay solver", "ts": 1791903516300 }, "history": "complete", "tags": [ "dev", "smart_account" ], "tx_count": 14, "wallet_age_days": 10.8994 } }, "meta": { "balances_block": 100406864, "block": 100406864, "block_age_ms": 1301, "block_ts": 1793225084000, "chain": "robinhood", "finalized_block": 100397649, "lag_ms": 59, "safe_block": 100401329 }}What this tells you
account_type: "smart_account"withdelegate: "0x63c0c19a282a1b52b07dd5a65b58948a07dae32b": an EIP-7702 wallet whose code delegates to that contract.is_contractisfalseandprofile.tagsis["dev", "smart_account"]: it created a launchpad token.native_balance: 0.71473787532832551ETH, worthnative_value_usd: 1683.56. That’s nearly all of its value.- Both holdings say
symbol: "USDG", but neither is USDG. Real USDG is0x5fc5360d0400a0fd4f2af552add042d716f1d168; these are0xfaaf8ffa8a29ceb6c85aff8d3961f65ae1bfc775and0x06993ae1230d3755b54f25022028daa66772bc3d. 25 of each are worthvalue_usd: 0.0117789atprice_confidence: "low", andpnlisnull: they were sent to the wallet, not bought. Always identify tokens by address. pnl.window: "30d"(the default):trades: 0,tokens_traded: 0,total_usd: 0andwin_rate_pct: null. It still paidgas_spent_usd: 0.198772, becausegas_scopeis “all transactions sent by the wallet in the window”, not only trades.profile.funded_byis0xd782268f600403843484b70710daee6846fc0db8, labeledbridge:Relay solver, with 0.008148386337868892 ETH: the wallet’s first ETH came over the Relay bridge.profile.tx_count: 14.meta.balances_block: 100406864: the ETH and token balances were read on-chain at that block.
How PnL is calculated
Per token, using the USD value of each trade when it happened (average cost):
- Realized = USD received from sells − tokens sold × average buy price.
- Unrealized = balance × current price − cost of the balance at the average buy price.
- The
pnlsummary adds both over every token traded in thewindow(1d,7d,30dby default, orall).
An unrealized loss in practice, from the holder snapshot: 0xe683eb5c66b0c55c54ce45d46a3bf0a69d809c8f held 29,879,481 CHARGE that cost it cost_basis_usd: 515.975 and were worth value_usd: 180.252 at that moment: an unrealized PnL of −$335.724.
Tokens a wallet sold more of than it bought (some arrived by transfer) have no known cost: they’re left out of the totals and the win rate, and tokens_with_known_cost says how many were counted. The details and edge cases are in How PnL works.
What the fields mean
| Field | Meaning |
|---|---|
account_type, delegate | eoa, smart_account (EIP-7702, with the contract it delegates to) or contract. |
label | Known name of the address (an exchange, a bridge, an app wallet), or null. |
native_balance, native_value_usd | ETH balance and its USD value. |
holdings[] | Tokens with a balance, most valuable first (up to 200): token (address, symbol, logo), balance, value_usd, value_eth, price_confidence, pnl. |
holdings[].price_confidence | How far to trust value_usd: high, medium (price pool under $10k liquidity), low (thin pool) or none (no price). |
holdings_value_usd, holdings_truncated | Total value of the token holdings; true when the address touched too many tokens to price them all. |
pnl.realized_usd, unrealized_usd, total_usd | PnL over the window. |
pnl.win_rate_pct | Share of tokens with known cost that ended in profit. |
pnl.trades, tokens_traded, tokens_with_known_cost, volume_usd | Activity in the window. |
pnl.best_trade, worst_trade, avg_hold_time_s | The tokens with the highest and lowest PnL, and the average time from first to last trade per token. |
pnl.gas_spent_usd, gas_scope | Gas paid in the window, for every transaction the wallet sent. |
profile.tags | dev (created a launchpad token), fresh (under a day old), smart_account, contract, or the app for app wallets (fomo). |
profile.funded_by | The wallet's first funder: address, label, amount_eth, block, ts. |
profile.tx_count, history | Transactions sent, and whether the whole history is indexed. |
Use it in your app
-
Get a Starter key.
/wallet/{address}needs the Starter plan and costs 5 credits per call. Send it in thex-api-keyheader. -
Hide spam. Add
min_value_usd=1(or more) so airdropped lookalike tokens don’t clutter the portfolio, and match tokens bytoken.address, never by symbol. -
Pick the window your users expect.
window=7dfor a weekly card,allfor lifetime PnL. Showtokens_with_known_costnext to the win rate so readers know what it’s based on. -
Go deeper when needed. Each trade is in wallet trades, and who funded the wallet in connected wallets.
import os, requests
wallet = "0x3ac82d45ff9e4963cefbc3f74dbe293f34c927ca"r = requests.get( f"https://api.crius.sh/v1/robinhood/wallet/{wallet}", params={"window": "7d", "min_value_usd": 1}, headers={"x-api-key": os.environ["CRIUS_API_KEY"]},)d = r.json()["data"]p = d["pnl"]print(f"{d['account_type']} {d['profile']['tags']}: {d['native_balance']} ETH (${d['native_value_usd']})")print(f"{p['window']} PnL ${p['total_usd']} on {p['trades']} trades, win rate {p['win_rate_pct']}%")for h in d["holdings"]: print(h["token"]["symbol"], h["token"]["address"], h["balance"], f"${h['value_usd']}", h["price_confidence"])Frequently asked questions
How is wallet PnL calculated?
Per token, with average cost in USD at the time of each trade. Realized PnL is what the sells returned minus the cost of the tokens sold at the wallet's average buy price; unrealized PnL is the current value of what it still holds minus its cost. The pnl summary adds these up over the tokens traded in the window. Gas is reported separately in gas_spent_usd, not subtracted.
How do I get every token a wallet holds?
/wallet/{address} lists up to 200 holdings with a balance, most valuable first, each checked against the token's balanceOf. Add min_value_usd=1 to hide dust and spam tokens. The ETH balance is separate, in native_balance and native_value_usd.
What does `win_rate_pct` mean?
The share of tokens traded in the window that ended in profit (realized plus unrealized PnL above zero), counting only tokens whose cost is known (tokens_with_known_cost). It's per token, not per trade. It's null when the wallet traded nothing in the window.
Why does a holding have `pnl: null`?
The wallet never bought that token, or sold more than it bought: it arrived by transfer or airdrop, so there is no cost to measure against. That's typical of spam tokens sent to active wallets.
Which plan does the wallet profile need?
The Starter plan or higher, 5 credits per call. See Plans & limits.
Related
Get your free Crius API key
Every example on this page works with a free key: higher rate limits than the anonymous plan, holders, wallets and the WebSocket stream. No card needed.
- Robinhood Chain (4663) only, from our own archive nodes
- REST + WebSocket
- Pay on-chain or by card