Messages
{
"op": "subscribed",
"id": "pf-1",
"channel": "wallet-portfolio",
"updatePeriod": 100
}{
"op": "event",
"id": "pf-1",
"channel": "wallet-portfolio",
"data": {
"type": "snapshot",
"seq": 0,
"epoch": "mun6v2l5lbes",
"chain": "evm:8453",
"wallet": "0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695",
"asOf": 1790539760120,
"summary": {
"positions": 1,
"realizedPnlUsd": 41.3,
"totalValueUsd": 101.05,
"unrealizedPnlUsd": 10.75,
"totalPnlUsd": 52.05,
"valued": 1
},
"rows": [
{
"key": "evm:8453:0x4ed4e862860bed51a9570b96d89af5e1b0efefed",
"chain": "evm:8453",
"address": "0x4ed4e862860bed51a9570b96d89af5e1b0efefed",
"symbol": "DEGEN",
"name": "Degen",
"kind": "position",
"decimals": 18,
"amount": "21500000",
"entryPriceUsd": 0.0000042,
"realizedPnlUsd": 41.3,
"buys": 3,
"sells": 2,
"labels": [
"proTrader"
],
"...": "...",
"amountUsd": 101.05,
"currentPriceUsd": 0.0000047,
"marketCapUsd": 173759,
"unrealizedPnlUsd": 10.75,
"totalPnlUsd": 52.05,
"share": 100
}
]
},
"asOf": 1790539767146,
"cursor": "v1.amun6v2l5lbes.2.mfq3c2y2",
"reason": "subscribe"
}{
"op": "event",
"id": "pf-1",
"channel": "wallet-portfolio",
"data": {
"type": "delta",
"seq": 1,
"prev": 0,
"epoch": "mun6v2l5lbes",
"summary": {
"positions": 1,
"realizedPnlUsd": 41.3,
"totalValueUsd": 105.35,
"unrealizedPnlUsd": 15.05,
"totalPnlUsd": 56.35,
"valued": 1
},
"enter": [],
"update": [
{
"key": "evm:8453:0x4ed4e862860bed51a9570b96d89af5e1b0efefed",
"amountUsd": 105.35,
"currentPriceUsd": 0.0000049,
"unrealizedPnlUsd": 15.05,
"totalPnlUsd": 56.35
}
],
"leave": []
},
"asOf": 1790539767301,
"cursor": "v1.amun6v2l5lbes.5.mfq3c4a1"
}{
"op": "subscribe",
"channel": "wallet-portfolio",
"id": "pf-1",
"params": {
"chain": "evm:8453",
"address": "0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695",
"updatePeriod": 1000
}
}Wallets
Wallet Portfolio Stream
The positions of a wallet on a chain with their live value: USD value, unrealized PnL and share of the portfolio, repriced as the market moves. The full list on subscribe, then each position as its value or its size changes.
WS channel: wallet-portfolio
Enabled on your key on request: contact us.
The positions of a wallet on a chain with their live value: USD value, unrealized PnL and share of the portfolio, repriced as the market moves. The full list on subscribe, then each position as its value or its size changes.
Rows are those of the Wallet Positions Stream plus the valuation fields of REST /v1/wallet/positions, and every message carries the totals of the wallet. One subscription is one wallet on one chain.
Delivery model
- Snapshot, then deltas. Right after the ack you receive a
snapshot: every open position of the wallet on the chain, valued. From then on you receive adeltaeach time a position changes: a trade of the wallet, or a price move of a token it holds. - At your pace. Changes within your
updatePeriod(100 ms by default) are grouped into one message. A portfolio shown as a single total reads well at1000; a trading screen keeps100. - Totals in every message.
summarycarries the value, the unrealized PnL and the realized PnL of the wallet, over the rows you hold: no need to add the rows yourself. - A fresh copy every minute. Every 60 s, if the portfolio or the USD rate moved, you receive the complete
snapshotagain ("reason": "periodic"), free: replace your list with it. - Always consistent. Messages are numbered (
seq,prev). Whenever the exact sequence cannot be delivered, you receive a freshsnapshotinstead.
Parameters
string
required
Public chain id,
evm:<id> or solana. Example: evm:8453string
required
The wallet address (EVM hex, case-insensitive; Solana base58). Example:
0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695integer
Milliseconds, 0 to 60000, default
100: at most one delta per period, carrying every change of the period. The ack carries the value applied (snapped up to 0, 100, 250, 500, 1000, 2000, 5000, 10000, 30000, 60000). See Choosing your pace.Messages
Every message is thedata of an event frame.
data.type | Fields | Sent |
|---|---|---|
snapshot | seq, epoch, chain, wallet, asOf, summary, rows | right after the ack, and whenever the list has to be replaced |
delta | seq, prev, epoch, summary, enter, update, leave | each time a position or its value changes |
enter: full rows of positions that open.update:{ "key": "<chain>:<token address>", … }with only the fields that changed. A field that becamenullis sent asnull.leave: the keys of the positions that closed.summary:{ positions, realizedPnlUsd, totalValueUsd, unrealizedPnlUsd, totalPnlUsd, valued }over the rows you hold.valuedis the number of rows with a known value: the onestotalValueUsdadds up.
Applying a delta
- Remove every key listed in
leave. - Apply every
update: the fields present replace those of the row. - Add every row of
enter.
key and sort them as you like. A snapshot replaces everything you hold. It carries up to 1,000 positions, the largest first; page through REST for more.
The row
Every field of the Wallet Positions Stream row (decimals and labels included), plus, after them:
| Field | |
|---|---|
amountUsd | What the position is worth in USD: the amount you would get by selling it in its main pool. null when the value is unknown, never 0 |
currentPriceUsd | Market price of the token; null when unknown |
marketCapUsd | The token’s current market cap, currentPriceUsd times its total supply, the same figure as /v1/token; null when the price or the supply is unknown, and on multi-chain major assets such as WBTC, whose market cap is read on /v1/token. It moves with the price |
unrealizedPnlUsd | Value minus cost of the tokens held; null when the value is unknown |
totalPnlUsd | Realized plus unrealized PnL |
share | Share of the wallet value held in this position, in percent. On snapshot rows only: between two snapshots, compute it from amountUsd and summary.totalValueUsd |
Resume
Events carry acursor. After a reconnect, send the same subscribe with "since": "<cursor>": you receive the deltas you missed, or a snapshot of the current portfolio followed by what comes next. Either way, applying what you receive is all there is to do. See Reconnect & resume in the Streams overview.
Billing
1 credit per message delivered. Thesnapshot of a subscription ("reason": "subscribe") costs 1, whatever its size; every delta costs 1, whatever it carries. Snapshots sent on our own after that first one ("reason": "periodic" every 60 s, "reason": "resync" after a hiccup) and deltas flagged "replay": true are free; the snapshot sent when you resume with since costs 1, like the one of a new subscription. The connection is free. A longer updatePeriod means fewer messages, and fewer credits: at 1000, a wallet holding active tokens costs at most 60 credits per minute.
GET /v1/usage/breakdown reports this channel under WS wallet-portfolio, where requests is the number of messages delivered.
Errors
| Code | Meaning | What to do |
|---|---|---|
INVALID_PARAM | A parameter was refused. details.param names it | Fix the subscription |
INVALID_CHAIN | Unknown chain id | Fix the subscription |
RATE_LIMITED | The subscription limit of your account is reached | Close a subscription, or ask us to raise the limit |
OVERLOADED with details.retryAfterMs | The server cannot follow one more wallet right now | Send the same subscribe again after details.retryAfterMs |
UPSTREAM_ERROR with details.retryAfterMs | The positions of the wallet are being loaded | Send the same subscribe again after details.retryAfterMs |
UPSTREAM_ERROR with details.resumes: true | Live data is temporarily unavailable | Nothing: the subscription resumes by itself |
Close codes
| Code | Meaning |
|---|---|
4401 | Auth - key missing, invalid, or revoked. Live connections are dropped the moment a key is revoked. |
4402 | Monthly credit quota exhausted (the WebSocket mirror of REST 402). |
1012 | Server restart - reconnect after the retryAfterMs of the JSON reason, then subscribe again with since. |
1013 | Server at capacity - reconnect after the retryAfterMs of the JSON reason. |
1008 | Policy - subscription limits, flooding, or a blocked socket. Fix the cause before reconnecting. |
1001 | Idle timeout - no ping within the keepalive window. |
{
"op": "subscribe",
"channel": "wallet-portfolio",
"id": "pf-1",
"params": {
"chain": "evm:8453",
"address": "0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695",
"updatePeriod": 1000
}
}
{
"op": "subscribed",
"id": "pf-1",
"channel": "wallet-portfolio",
"updatePeriod": 1000
}
{
"op": "event",
"id": "pf-1",
"channel": "wallet-portfolio",
"data": {
"type": "snapshot",
"seq": 0,
"epoch": "mun6v2l5lbes",
"chain": "evm:8453",
"wallet": "0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695",
"asOf": 1790539760120,
"summary": {
"positions": 1,
"realizedPnlUsd": 41.3,
"totalValueUsd": 101.05,
"unrealizedPnlUsd": 10.75,
"totalPnlUsd": 52.05,
"valued": 1
},
"rows": [
{
"key": "evm:8453:0x4ed4e862860bed51a9570b96d89af5e1b0efefed",
"chain": "evm:8453",
"address": "0x4ed4e862860bed51a9570b96d89af5e1b0efefed",
"symbol": "DEGEN",
"name": "Degen",
"kind": "position",
"iconUrl": "https://serialized.cloud/token/0190a2f4-1b2c-7d3e-8f4a-5b6c7d8e9f0a/icon",
"decimals": 18,
"amount": "21500000",
"entryPriceUsd": 0.0000042,
"entryPriceLifetimeUsd": 0.0000041,
"exitPriceUsd": 0.0000051,
"realizedPnlUsd": 41.3,
"boughtTokens": "63500000",
"soldTokens": "42000000",
"boughtUsd": 260.35,
"soldUsd": 214.2,
"buys": 3,
"sells": 2,
"firstTradeAt": 1790536100000,
"lastTradeAt": 1790539700000,
"holdingSince": 1790536100000,
"buyFeesUsd": null,
"sellFeesUsd": null,
"totalFeesUsd": null,
"labels": ["proTrader"],
"amountUsd": 101.05,
"currentPriceUsd": 0.0000047,
"marketCapUsd": 173759,
"unrealizedPnlUsd": 10.75,
"totalPnlUsd": 52.05,
"share": 100
}
]
},
"asOf": 1790539767146,
"cursor": "v1.amun6v2l5lbes.2.mfq3c2y2",
"reason": "subscribe"
}
{
"op": "event",
"id": "pf-1",
"channel": "wallet-portfolio",
"data": {
"type": "delta",
"seq": 1,
"prev": 0,
"epoch": "mun6v2l5lbes",
"summary": {
"positions": 1,
"realizedPnlUsd": 41.3,
"totalValueUsd": 105.35,
"unrealizedPnlUsd": 15.05,
"totalPnlUsd": 56.35,
"valued": 1
},
"enter": [],
"update": [
{
"key": "evm:8453:0x4ed4e862860bed51a9570b96d89af5e1b0efefed",
"amountUsd": 105.35,
"currentPriceUsd": 0.0000049,
"marketCapUsd": 4900,
"unrealizedPnlUsd": 15.05,
"totalPnlUsd": 56.35
}
],
"leave": []
},
"asOf": 1790539768301,
"cursor": "v1.amun6v2l5lbes.5.mfq3c4a1"
}
Messages
{
"op": "subscribed",
"id": "pf-1",
"channel": "wallet-portfolio",
"updatePeriod": 100
}{
"op": "event",
"id": "pf-1",
"channel": "wallet-portfolio",
"data": {
"type": "snapshot",
"seq": 0,
"epoch": "mun6v2l5lbes",
"chain": "evm:8453",
"wallet": "0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695",
"asOf": 1790539760120,
"summary": {
"positions": 1,
"realizedPnlUsd": 41.3,
"totalValueUsd": 101.05,
"unrealizedPnlUsd": 10.75,
"totalPnlUsd": 52.05,
"valued": 1
},
"rows": [
{
"key": "evm:8453:0x4ed4e862860bed51a9570b96d89af5e1b0efefed",
"chain": "evm:8453",
"address": "0x4ed4e862860bed51a9570b96d89af5e1b0efefed",
"symbol": "DEGEN",
"name": "Degen",
"kind": "position",
"decimals": 18,
"amount": "21500000",
"entryPriceUsd": 0.0000042,
"realizedPnlUsd": 41.3,
"buys": 3,
"sells": 2,
"labels": [
"proTrader"
],
"...": "...",
"amountUsd": 101.05,
"currentPriceUsd": 0.0000047,
"marketCapUsd": 173759,
"unrealizedPnlUsd": 10.75,
"totalPnlUsd": 52.05,
"share": 100
}
]
},
"asOf": 1790539767146,
"cursor": "v1.amun6v2l5lbes.2.mfq3c2y2",
"reason": "subscribe"
}{
"op": "event",
"id": "pf-1",
"channel": "wallet-portfolio",
"data": {
"type": "delta",
"seq": 1,
"prev": 0,
"epoch": "mun6v2l5lbes",
"summary": {
"positions": 1,
"realizedPnlUsd": 41.3,
"totalValueUsd": 105.35,
"unrealizedPnlUsd": 15.05,
"totalPnlUsd": 56.35,
"valued": 1
},
"enter": [],
"update": [
{
"key": "evm:8453:0x4ed4e862860bed51a9570b96d89af5e1b0efefed",
"amountUsd": 105.35,
"currentPriceUsd": 0.0000049,
"unrealizedPnlUsd": 15.05,
"totalPnlUsd": 56.35
}
],
"leave": []
},
"asOf": 1790539767301,
"cursor": "v1.amun6v2l5lbes.5.mfq3c4a1"
}{
"op": "subscribe",
"channel": "wallet-portfolio",
"id": "pf-1",
"params": {
"chain": "evm:8453",
"address": "0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695",
"updatePeriod": 1000
}
}Ack
type:object
Server acknowledgement: the subscription is live.
Snapshot
type:object
The complete view, in order: right after the ack ("reason": "subscribe", 1 credit), every 60 s if the view moved (periodic, free) and whenever the view has to be replaced (resync, free).
Delta
type:object
Each change of the view, at most one per updatePeriod: 1 credit per message.
Subscribe
type:object
Client frame opening the subscription (additive, ack is explicit).