> ## Documentation Index
> Fetch the complete documentation index at: https://docs.serialized.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# 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`](/endpoints/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](/streams/wallet-portfolio-stream): same parameters, same messages. For the trades themselves, the [Wallet Trades Stream](/streams/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 a `delta` each 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 `snapshot` again (`"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 fresh `snapshot` instead.

## Parameters

<ParamField body="chain" type="string" required>
  Public chain id, `evm:<id>` or `solana`. Example: `evm:8453`
</ParamField>

<ParamField body="address" type="string" required>
  The wallet address (EVM hex, case-insensitive; Solana base58). Example: `0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695`
</ParamField>

<ParamField body="updatePeriod" type="integer">
  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](/streams/overview#choosing-your-pace-updateperiod).
</ParamField>

## Messages

Every message is the `data` 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 became `null` is sent as `null`.
* **`leave`**: the keys of the positions that closed.
* **`summary`**: `{ positions, realizedPnlUsd }` over the rows you hold, in every message.

### Applying a delta

1. Remove every key listed in `leave`.
2. Apply every `update`: the fields present replace those of the row.
3. Add every row of `enter`.

Rows are not ranked: keep them by `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` |

Every USD figure drawn from the wallet's trades (entry and exit prices, `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 a `cursor`. 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](/streams/overview).

## Billing

**1 credit per message delivered.** The `snapshot` 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. |

<RequestExample>
  ```json Subscribe theme={null}
  {
    "op": "subscribe",
    "channel": "wallet-positions",
    "id": "wallet-1",
    "params": {
      "chain": "evm:8453",
      "address": "0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695"
    }
  }
  ```
</RequestExample>

<ResponseExample>
  ```json Ack theme={null}
  {
    "op": "subscribed",
    "id": "wallet-1",
    "channel": "wallet-positions",
    "updatePeriod": 100
  }
  ```

  ```json Snapshot theme={null}
  {
    "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"
  }
  ```

  ```json Delta (the wallet bought more) theme={null}
  {
    "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"
  }
  ```
</ResponseExample>


## AsyncAPI

````yaml asyncapi.json wallet-positions
id: wallet-positions
title: Wallet Positions Stream
description: >-
  The open positions of a wallet on an EVM chain, kept up to date: the full list
  on subscribe, then each position as a trade changes it. Rows carry the fields
  of REST `/v1/wallet/positions`; `summary` carries the totals in every message.
  `positions` is accepted as a channel name. Enabled on your key on request.
servers:
  - id: production
    protocol: wss
    host: ws.serialized.xyz
    bindings: []
    variables: []
address: wallet-positions
parameters: []
bindings: []
operations:
  - &ref_2
    id: subscribe-wallet-positions
    title: Subscribe Wallet Positions Stream
    type: send
    messages:
      - &ref_6
        id: subscribe
        payload:
          - name: Subscribe
            description: Client frame opening the subscription (additive, ack is explicit).
            type: object
            properties:
              - name: op
                type: string
                description: subscribe
                required: true
              - name: channel
                type: string
                description: wallet-positions
                required: true
              - name: id
                type: string
                description: >-
                  Client-chosen subscription id, echoed on every frame of this
                  subscription.
                required: true
              - name: params
                type: object
                required: true
                properties:
                  - name: chain
                    type: string
                    description: >-
                      Public chain id of an EVM chain, `evm:<id>`. Solana
                      wallets: read REST `/v1/wallet/positions`.
                    required: true
                  - name: address
                    type: string
                    description: The wallet address (hex, case-insensitive).
                    required: true
                  - name: updatePeriod
                    type: integer
                    description: >-
                      Milliseconds, 0 to 60000, default 100: how often, at most,
                      this subscription receives a message (snapped up to 0,
                      100, 250, 500, 1000, 2000, 5000, 10000, 30000, 60000; the
                      ack carries the value applied).
                    required: false
              - name: since
                type: string
                description: >-
                  Optional, next to `params`: the `cursor` of the last event you
                  received. You get the deltas you missed, or a snapshot of the
                  current view.
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          properties:
            op:
              type: string
              const: subscribe
              x-parser-schema-id: <anonymous-schema-139>
            channel:
              type: string
              const: wallet-positions
              x-parser-schema-id: <anonymous-schema-140>
            id:
              type: string
              description: >-
                Client-chosen subscription id, echoed on every frame of this
                subscription.
              x-parser-schema-id: <anonymous-schema-141>
            params:
              type: object
              properties:
                chain:
                  type: string
                  description: >-
                    Public chain id of an EVM chain, `evm:<id>`. Solana wallets:
                    read REST `/v1/wallet/positions`.
                  x-parser-schema-id: <anonymous-schema-143>
                address:
                  type: string
                  description: The wallet address (hex, case-insensitive).
                  x-parser-schema-id: <anonymous-schema-144>
                updatePeriod:
                  type: integer
                  minimum: 0
                  maximum: 60000
                  default: 100
                  description: >-
                    Milliseconds, 0 to 60000, default 100: how often, at most,
                    this subscription receives a message (snapped up to 0, 100,
                    250, 500, 1000, 2000, 5000, 10000, 30000, 60000; the ack
                    carries the value applied).
                  x-parser-schema-id: <anonymous-schema-145>
              required:
                - chain
                - address
              x-parser-schema-id: <anonymous-schema-142>
            since:
              type: string
              description: >-
                Optional, next to `params`: the `cursor` of the last event you
                received. You get the deltas you missed, or a snapshot of the
                current view.
              x-parser-schema-id: <anonymous-schema-146>
          required:
            - op
            - channel
            - id
            - params
          x-parser-schema-id: <anonymous-schema-138>
        title: Subscribe
        description: Client frame opening the subscription (additive, ack is explicit).
        example: |-
          {
            "op": "subscribe",
            "channel": "wallet-positions",
            "id": "wallet-1",
            "params": {
              "chain": "evm:8453",
              "address": "0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695"
            }
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: subscribe
          - id: x-parser-message-name
            value: subscribe
    bindings: []
    extensions: &ref_0
      - id: x-parser-unique-object-id
        value: wallet-positions
  - &ref_1
    id: receive-wallet-positions
    title: Wallet Positions Stream frames
    type: receive
    messages:
      - &ref_3
        id: ack
        payload:
          - name: Ack
            description: 'Server acknowledgement: the subscription is live.'
            type: object
            properties:
              - name: type
                type: string
                description: object
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          x-parser-schema-id: <anonymous-schema-147>
        title: Ack
        description: 'Server acknowledgement: the subscription is live.'
        example: |-
          {
            "op": "subscribed",
            "id": "wallet-1",
            "channel": "wallet-positions",
            "updatePeriod": 100
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: ack
          - id: x-parser-message-name
            value: ack
      - &ref_4
        id: snapshot
        payload:
          - name: Snapshot
            description: >-
              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).
            type: object
            properties:
              - name: type
                type: string
                description: object
                required: false
              - name: description
                type: string
                description: >-
                  `data.type` is `snapshot`; `data.rows` is the complete view in
                  order; `seq` and `epoch` number the messages. `cursor` resumes
                  the subscription.
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          description: >-
            `data.type` is `snapshot`; `data.rows` is the complete view in
            order; `seq` and `epoch` number the messages. `cursor` resumes the
            subscription.
          x-parser-schema-id: <anonymous-schema-148>
        title: Snapshot
        description: >-
          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).
        example: |-
          {
            "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"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: snapshot
          - id: x-parser-message-name
            value: snapshot
      - &ref_5
        id: delta
        payload:
          - name: Delta
            description: >-
              Each change of the view, at most one per `updatePeriod`: 1 credit
              per message.
            type: object
            properties:
              - name: type
                type: string
                description: object
                required: false
              - name: description
                type: string
                description: >-
                  `data.type` is `delta`: `enter` (full rows, with `rank` on
                  views), `update` (`key` plus the fields that changed), `leave`
                  (keys), and `order` on views when rows you hold changed place.
                  Apply: remove `leave`, apply `update`, insert `enter`, then
                  `order`.
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          description: >-
            `data.type` is `delta`: `enter` (full rows, with `rank` on views),
            `update` (`key` plus the fields that changed), `leave` (keys), and
            `order` on views when rows you hold changed place. Apply: remove
            `leave`, apply `update`, insert `enter`, then `order`.
          x-parser-schema-id: <anonymous-schema-149>
        title: Delta
        description: >-
          Each change of the view, at most one per `updatePeriod`: 1 credit per
          message.
        example: |-
          {
            "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"
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: delta
          - id: x-parser-message-name
            value: delta
    bindings: []
    extensions: *ref_0
sendOperations:
  - *ref_1
receiveOperations:
  - *ref_2
sendMessages:
  - *ref_3
  - *ref_4
  - *ref_5
receiveMessages:
  - *ref_6
extensions:
  - id: x-parser-unique-object-id
    value: wallet-positions
securitySchemes: []

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.