# Reading Data

> Canonical: https://docs.velocity.exchange/developers/ecosystem-builders/reading-data

This page covers every way to read Velocity state from an application: the subscribed `VelocityClient`/`User` caches, the stateless `VelocityCore` decoders, and the Data API REST endpoints.

## Account data via VelocityClient / User

Once `velocityClient` is subscribed (see [setup](/developers/ecosystem-builders.md#setup)), account data is available synchronously from the `User` wrapper for the active (or any) subaccount.

### Get the active account

```ts
const user = velocityClient.getUser();           // active subaccount
const otherUser = velocityClient.getUser(1);      // subaccount 1

const userAccount = user.getUserAccount();        // raw decoded UserAccount
userAccount.authority;
userAccount.subAccountId;
userAccount.name;                                 // number[]: decode with `decodeName()`
```

### Positions

```ts
import { decodeName } from "@velocity-exchange/sdk";

for (const position of userAccount.perpPositions) {
  if (position.baseAssetAmount.isZero()) continue;

  position.marketIndex;
  position.baseAssetAmount;         // BASE_PRECISION (1e9), signed: + long, - short
  position.quoteAssetAmount;        // QUOTE_PRECISION (1e6)
  position.quoteEntryAmount;
  position.quoteBreakEvenAmount;
  position.lastCumulativeFundingRate;
  position.openOrders;
  position.openBids;
  position.openAsks;
}

// Higher-level PnL/health accessors live on `User`, see PnL & Risk
const unrealizedPnl = user.getUnrealizedPNL(true, 0);   // withFunding=true, market 0
const health = user.getHealth();                        // 0-100
```

See [PnL & Risk](/developers/velocity-sdk/pnl-risk.md) for `getTotalCollateral`, `getMarginRequirement`, `getFreeCollateral`, and `getLeverage`.

### Open orders

```ts
import { isVariant } from "@velocity-exchange/sdk";

for (const order of userAccount.orders) {
  if (!isVariant(order.status, "open")) continue;

  order.orderId;
  order.marketIndex;
  order.direction;
  order.price;                    // PRICE_PRECISION (1e6), 0 for market orders
  order.triggerPrice;
  order.baseAssetAmount;
  order.baseAssetAmountFilled;
  order.oraclePriceOffset;        // BN (i64): always wrap in `new BN(...)` when constructing
  order.reduceOnly;
  order.postOnly;                 // boolean field, not a bitFlags bit
  order.bitFlags;                 // OrderBitFlag bitmask: SignedMessage / OracleTriggerMarket /
                                  // SafeTriggerOrder / NewTriggerReduceOnly / HasBuilder
}
```

### Spot balances and collateral

```ts
for (const position of userAccount.spotPositions) {
  if (position.scaledBalance.isZero()) continue;
  position.marketIndex;
  position.balanceType;           // { deposit: {} } | { borrow: {} }
  position.scaledBalance;         // SPOT_MARKET_BALANCE_PRECISION (1e9); multiply by the market's cumulative interest for token amount
}

const totalCollateral = user.getTotalCollateral();  // BN, QUOTE_PRECISION (1e6)
```

## Decoding accounts without a subscription: `VelocityCore`

For read-only tooling (indexers, one-off scripts, serverless functions) where a fully subscribed `VelocityClient` is overkill, `VelocityCore` decodes accounts directly from an RPC fetch or a raw buffer:

```ts
import { VelocityCore } from "@velocity-exchange/sdk";
import { Connection } from "@solana/web3.js";

const connection = new Connection("<RPC_URL>");

// Fetch + decode a User account in one call, no subscription required
const userAccount = await VelocityCore.fetchUserAccount(connection, userAccountPublicKey);

// Or decode a buffer already in hand (e.g. from a websocket account notification)
const decoded = VelocityCore.decodeUserAccount(rawAccountBuffer);

// PDA helpers (state, user, perp/spot market, vaults) are re-exported statically
const userPda = VelocityCore.pdas.getUserAccountPublicKeySync(programId, authority, 0);
```

For account types beyond `User`, build a coder directly against the bundled IDL:

```ts
import { VelocityCore } from "@velocity-exchange/sdk";

const coder = VelocityCore.coder(); // defaults to VelocityCore.defaultIdl()
const perpMarket = coder.accounts.decode("PerpMarket", rawAccountBuffer);
```

> **Warning:**
>
> **IDL account names are PascalCase.** Any string-keyed coder call must use `'PerpMarket'`, `'SpotMarket'`, `'User'`, `'UserStats'`, `'State'`, matching `accounts[].name` in `sdk/src/idl/velocity.json`. Passing a lowercase or camelCase name throws `Unknown account: <name>`.

> **Warning:**
>
> **`MarketStatus` discriminants and account sizes can shift across program versions.** The `MarketStatus` variants are `Initialized` (0), `Active` (1), `ReduceOnly` (2), `Settlement` (3), and `Delisted` (4). Always decode against the `sdk/src/idl/velocity.json` shipped with the SDK in use, never a hardcoded enum, a fixed offset, or a hardcoded account size carried over from an earlier integration. See [Account Model](/developers/concepts/account-model.md) for the full layout, or the [migration guide](/developers/migrate-from-drift/agent-guide.md#5-behavioral-divergences-stop-and-report) when porting a raw decoder from an earlier discriminant numbering.

> **Warning:**
>
> **Never validate an account by its exact data length.** Zero-copy account structs (`User`, `PerpMarket`, `SpotMarket`, `State`, `UserStats`, and others) can grow: when a struct runs out of reserved padding, the protocol grows the deployed accounts with the `extend_account` instruction, and the account then holds **more** bytes than an older client expects. Decode length-tolerantly:
>
> - Read exactly the `8 + size_of::<T>()` bytes the client knows and ignore the tail. The SDK already does this: Anchor's borsh coder and the `decodeUser` fast path both use start-relative offsets, so an extended account decodes unchanged.
> - Do not assert `data.length === EXPECTED_SIZE`, and do not derive a slice end from the buffer length.
> - **Do not filter `getProgramAccounts` by `dataSize`.** A hardcoded size silently matches zero accounts after an extension. Filter on the 8-byte discriminator with a `memcmp` instead, which is what every SDK map does.
> - On the Rust side, decode through `velocity-rs`'s `utils::deser_zero_copy` / `try_deser_zero_copy` or `AccountRef`, which trim to `8 + size_of::<T>()` before casting. Do not call Anchor's derived `T::try_deserialize` on a zero-copy type offchain: it casts the whole tail and panics on any size mismatch.
>
> Extension only ever appends. It never reorders, inserts, widens, or shrinks a field, so offsets already in use stay valid, and newly added bytes read as zero until code writes them. See [Account Model](/developers/concepts/account-model.md#account-sizes-can-grow).

## Market data

### Oracle and mark prices

```ts
const oraclePriceData = velocityClient.getMMOracleDataForPerpMarket(0); // MMOraclePriceData
const perpMarket = velocityClient.getPerpMarketAccount(0);

perpMarket.marketStats;    // mark/oracle TWAPs live here, not on the top-level PerpMarket
perpMarket.oracle;         // top-level now (moved off amm.* in the AMM decoupling)
perpMarket.oracleSource;
```

### Orderbook (DLOB)

For a live L2/L3 orderbook, subscribe to the DLOB directly with the SDK's `SlotSubscriber` / `OrderSubscriber` / `DLOBSubscriber` classes. See [DLOB](/developers/velocity-sdk/dlob.md) for the full setup sequence and [Orderbook + DLOB websocket](/developers/ecosystem-builders/orderbook-and-ws.md) for the hosted DLOB server's REST/websocket API, which avoids running a DLOB in-process.

## Data API (REST)

The Data API provides historical and aggregate data via REST endpoints, without running an indexer. Use it for dashboards, analytics, or any non-SDK integration.

> **Info:**
>
> Velocity's hosted Data API is live at `data.velocity.exchange`. The examples below use that host. See [Data API](/developers/data-api.md).

- Base URL: `https://data.velocity.exchange`
- [Data API playground](/developers/data-api.md)

### Market stats

Returns aggregate stats for all markets: volume, open interest, funding rate, oracle price, and market status.

```
GET https://data.velocity.exchange/stats/markets
```

Response shape:
```json
[
  {
    "marketIndex": 0,
    "symbol": "SOL-PERP",
    "marketType": "perp",
    "oraclePrice": 123.456,
    "volume24h": 50000000.0,
    "openInterest": 12000000.0,
    "fundingRate": 0.00012,
    "fundingRate24hAvg": 0.00010,
    "status": "active"
  }
]
```

### Funding rates

```
GET https://data.velocity.exchange/fundingRates?symbol=SOL-PERP
```

### Trades

```
GET https://data.velocity.exchange/trades?symbol=SOL-PERP&limit=100
```

Query parameters:
- `symbol`: Market symbol (required)
- `limit`: Max results (default varies, max typically 1000)
- `pageIndex`: For pagination

Note: Amounts are in protocol precision (base: 1e9, quote: 1e6). Divide accordingly for human-readable values. See the [Data API glossary](/developers/data-api/glossary.md) for the full column reference, and note that spot-fulfillment-related columns no longer apply (spot DLOB trading and external fulfillment are both removed on Velocity).

### Fetching data in code

  
```js
const marketsRes = await fetch("https://data.velocity.exchange/stats/markets");
const markets = await marketsRes.json();

for (const m of markets) {
  console.log(`${m.symbol}: price=${m.oraclePrice} OI=${m.openInterest} funding=${m.fundingRate}`);
}

const tradesRes = await fetch(
  "https://data.velocity.exchange/trades?symbol=SOL-PERP&limit=100"
);
const trades = await tradesRes.json();
```
  

## DLOB + Swift for live order flow

For real-time orderbook and order flow, Velocity uses separate services:

- **DLOB server** (`https://dlob.velocity.exchange`): Orderbook snapshots and streaming. See [Orderbook + DLOB websocket](/developers/ecosystem-builders/orderbook-and-ws.md).
- **Swift server** (`https://swift.velocity.exchange`): Signed message orders for fast execution. See [Swift](/developers/velocity-sdk/swift.md).
