Messages
{
"op": "subscribed",
"id": "wallet-1",
"channel": "wallet-positions",
"updatePeriod": 100
}{
"op": "event",
"id": "wallet-1",
"channel": "wallet-positions",
"data": {
"type": "snapshot",
"seq": 0,
"epoch": "mun6v2l5lbes",
"chain": "evm:8453",
"wallet": "0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695",
"asOf": 1790539760120,
"summary": {
"positions": 1,
"realizedPnlUsd": 41.3
},
"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"
],
"...": "..."
}
]
},
"asOf": 1790539767146,
"cursor": "v1.amun6v2l5lbes.2.mfq3c2y2",
"reason": "subscribe"
}{
"op": "event",
"id": "wallet-1",
"channel": "wallet-positions",
"data": {
"type": "delta",
"seq": 1,
"prev": 0,
"epoch": "mun6v2l5lbes",
"summary": {
"positions": 1,
"realizedPnlUsd": 41.3
},
"enter": [],
"update": [
{
"key": "evm:8453:0x4ed4e862860bed51a9570b96d89af5e1b0efefed",
"amount": "37415963.2",
"entryPriceUsd": 0.0000044,
"buys": 4,
"lastTradeAt": 1790539767000
}
],
"leave": []
},
"asOf": 1790539767301,
"cursor": "v1.amun6v2l5lbes.5.mfq3c4a1"
}{
"op": "subscribe",
"channel": "wallet-positions",
"id": "wallet-1",
"params": {
"chain": "evm:8453",
"address": "0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695"
}
}Wallets
Wallet Positions Stream
The open positions of a wallet on a chain, kept up to date: the full list on subscribe, then each position as a trade changes it. Rows carry the same fields as REST /v1/wallet/positions.
WS channel: wallet-positions
Enabled on your key on request: contact us.
The open positions of a wallet on a chain, kept up to date: the full list on subscribe, then each position as a trade changes it. Rows carry the same fields as REST /v1/wallet/positions: amount held, average entry and exit prices, realized PnL, bought and sold totals, token decimals and the wallet’s badges.
One subscription is one wallet on one chain. For the same rows with their live USD value and unrealized PnL, use the Wallet Portfolio Stream: same parameters, same messages. For the trades themselves, the Wallet Trades Stream.
positions is accepted as a channel name on subscribe; the ack and every event say wallet-positions.
Delivery model
- Snapshot, then deltas. Right after the ack you receive a
snapshot: every open position of the wallet on the chain. From then on you receive adeltaeach time a position changes. - At your pace. A change is pushed as soon as it happens. Changes that follow within your
updatePeriod(100 ms by default) are grouped into the next message. - A fresh copy every minute. Every 60 s, if the positions or the USD rate moved, you receive the complete
snapshotagain ("reason": "periodic"), free: replace your list with it, like any snapshot. - 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 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 }over the rows you hold, in every message.
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
The fields of REST/v1/wallet/positions, with the same names and the same null rules; token amounts are strings.
| Field | |
|---|---|
key | <chain>:<token address> |
chain, address, symbol, name, iconUrl | The token |
kind | position |
decimals | The token’s decimals (amount is already scaled); null when unknown |
amount | Tokens held |
entryPriceUsd, entryPriceLifetimeUsd, exitPriceUsd | Average entry of the current bag, average entry over the life of the position, average exit; null when there is no buy or no sell to average |
realizedPnlUsd | Realized PnL |
boughtTokens, soldTokens, boughtUsd, soldUsd, buys, sells | Cumulative totals |
firstTradeAt, lastTradeAt, holdingSince | Times in ms; holdingSince is when the current bag was opened, null when unknown |
buyFeesUsd, sellFeesUsd, totalFeesUsd | null on the stream; REST serves the fees paid on Solana |
labels | The wallet’s badges on the row: dev when the wallet is the token’s creator, proTrader and smartTrader when the wallet itself carries the badge (repeated on each of its rows); [] when none. A badge that changes arrives as an update |
boughtUsd, soldUsd, realized PnL) follows the rule of REST: on Solana, at the rate of each trade; on EVM chains, at the chain’s current native price. amount is the wallet’s on-chain balance on both families: a transfer in or out moves it like a trade does.
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 positions 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. A wallet that does not trade costs nothing, and the connection is free. A longer updatePeriod means fewer messages, and fewer credits.
GET /v1/usage/breakdown reports this channel under WS wallet-positions, 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-positions",
"id": "wallet-1",
"params": {
"chain": "evm:8453",
"address": "0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695"
}
}
{
"op": "subscribed",
"id": "wallet-1",
"channel": "wallet-positions",
"updatePeriod": 100
}
{
"op": "event",
"id": "wallet-1",
"channel": "wallet-positions",
"data": {
"type": "snapshot",
"seq": 0,
"epoch": "mun6v2l5lbes",
"chain": "evm:8453",
"wallet": "0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695",
"asOf": 1790539760120,
"summary": { "positions": 1, "realizedPnlUsd": 41.3 },
"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"]
}
]
},
"asOf": 1790539767146,
"cursor": "v1.amun6v2l5lbes.2.mfq3c2y2",
"reason": "subscribe"
}
{
"op": "event",
"id": "wallet-1",
"channel": "wallet-positions",
"data": {
"type": "delta",
"seq": 1,
"prev": 0,
"epoch": "mun6v2l5lbes",
"summary": { "positions": 1, "realizedPnlUsd": 41.3 },
"enter": [],
"update": [
{
"key": "evm:8453:0x4ed4e862860bed51a9570b96d89af5e1b0efefed",
"amount": "37415963.2",
"entryPriceUsd": 0.0000044,
"entryPriceLifetimeUsd": 0.0000042,
"boughtTokens": "79415963.2",
"boughtUsd": 335.37,
"buys": 4,
"lastTradeAt": 1790539767000
}
],
"leave": []
},
"asOf": 1790539767301,
"cursor": "v1.amun6v2l5lbes.5.mfq3c4a1"
}
Messages
{
"op": "subscribed",
"id": "wallet-1",
"channel": "wallet-positions",
"updatePeriod": 100
}{
"op": "event",
"id": "wallet-1",
"channel": "wallet-positions",
"data": {
"type": "snapshot",
"seq": 0,
"epoch": "mun6v2l5lbes",
"chain": "evm:8453",
"wallet": "0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695",
"asOf": 1790539760120,
"summary": {
"positions": 1,
"realizedPnlUsd": 41.3
},
"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"
],
"...": "..."
}
]
},
"asOf": 1790539767146,
"cursor": "v1.amun6v2l5lbes.2.mfq3c2y2",
"reason": "subscribe"
}{
"op": "event",
"id": "wallet-1",
"channel": "wallet-positions",
"data": {
"type": "delta",
"seq": 1,
"prev": 0,
"epoch": "mun6v2l5lbes",
"summary": {
"positions": 1,
"realizedPnlUsd": 41.3
},
"enter": [],
"update": [
{
"key": "evm:8453:0x4ed4e862860bed51a9570b96d89af5e1b0efefed",
"amount": "37415963.2",
"entryPriceUsd": 0.0000044,
"buys": 4,
"lastTradeAt": 1790539767000
}
],
"leave": []
},
"asOf": 1790539767301,
"cursor": "v1.amun6v2l5lbes.5.mfq3c4a1"
}{
"op": "subscribe",
"channel": "wallet-positions",
"id": "wallet-1",
"params": {
"chain": "evm:8453",
"address": "0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695"
}
}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).