Velocity ProtocolDevelopers
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), account data is available synchronously from the User wrapper for the active (or any) subaccount.

Get the active account

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

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 for getTotalCollateral, getMarginRequirement, getFreeCollateral, and getLeverage.

Open orders

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

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:

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:

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

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

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

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 for the full layout, or the migration guide when porting a raw decoder from an earlier discriminant numbering.

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.

Market data

Oracle and mark prices

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 for the full setup sequence and Orderbook + DLOB websocket 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.

Velocity's hosted Data API is live at data.velocity.exchange. The examples below use that host. See Data API.

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:

[
  {
    "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 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

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.
  • Swift server (https://swift.velocity.exchange): Signed message orders for fast execution. See Swift.