> ## 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 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](/streams/wallet-positions-stream) plus the valuation fields of REST [`/v1/wallet/positions`](/endpoints/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 a `delta` each 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 at `1000`; a trading screen keeps `100`.
* **Totals in every message.** `summary` carries 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 `snapshot` again (`"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 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 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 became `null` is sent as `null`.
* **`leave`**: the keys of the positions that closed.
* **`summary`**: `{ positions, realizedPnlUsd, totalValueUsd, unrealizedPnlUsd, totalPnlUsd, valued }` over the rows you hold. `valued` is the number of rows with a known value: the ones `totalValueUsd` adds up.

### 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

Every field of the [Wallet Positions Stream](/streams/wallet-positions-stream#the-row) 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 a `cursor`. 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](/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. 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. |

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

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

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

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


## AsyncAPI

````yaml asyncapi.json wallet-portfolio
id: wallet-portfolio
title: Wallet Portfolio Stream
description: >-
  The positions of a wallet on an EVM chain with their live value: USD value,
  unrealized PnL and share of the portfolio, repriced as the market moves. Rows
  are those of `wallet-positions` plus the valuation fields of REST
  `/v1/wallet/positions`; `summary` carries the wallet's totals in every
  message. Enabled on your key on request.
servers:
  - id: production
    protocol: wss
    host: ws.serialized.xyz
    bindings: []
    variables: []
address: wallet-portfolio
parameters: []
bindings: []
operations:
  - &ref_2
    id: subscribe-wallet-portfolio
    title: Subscribe Wallet Portfolio 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-portfolio
                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-151>
            channel:
              type: string
              const: wallet-portfolio
              x-parser-schema-id: <anonymous-schema-152>
            id:
              type: string
              description: >-
                Client-chosen subscription id, echoed on every frame of this
                subscription.
              x-parser-schema-id: <anonymous-schema-153>
            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-155>
                address:
                  type: string
                  description: The wallet address (hex, case-insensitive).
                  x-parser-schema-id: <anonymous-schema-156>
                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-157>
              required:
                - chain
                - address
              x-parser-schema-id: <anonymous-schema-154>
            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-158>
          required:
            - op
            - channel
            - id
            - params
          x-parser-schema-id: <anonymous-schema-150>
        title: Subscribe
        description: Client frame opening the subscription (additive, ack is explicit).
        example: |-
          {
            "op": "subscribe",
            "channel": "wallet-portfolio",
            "id": "pf-1",
            "params": {
              "chain": "evm:8453",
              "address": "0x9f1c3b6a2e5d4c7b8a9f0e1d2c3b4a5968778695",
              "updatePeriod": 1000
            }
          }
        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-portfolio
  - &ref_1
    id: receive-wallet-portfolio
    title: Wallet Portfolio 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-159>
        title: Ack
        description: 'Server acknowledgement: the subscription is live.'
        example: |-
          {
            "op": "subscribed",
            "id": "pf-1",
            "channel": "wallet-portfolio",
            "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-160>
        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": "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"
          }
        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-161>
        title: Delta
        description: >-
          Each change of the view, at most one per `updatePeriod`: 1 credit per
          message.
        example: |-
          {
            "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"
          }
        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-portfolio
securitySchemes: []

````

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