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-100See 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 thedecodeUserfast 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
getProgramAccountsbydataSize. A hardcoded size silently matches zero accounts after an extension. Filter on the 8-byte discriminator with amemcmpinstead, which is what every SDK map does. - On the Rust side, decode through
velocity-rs'sutils::deser_zero_copy/try_deser_zero_copyorAccountRef, which trim to8 + size_of::<T>()before casting. Do not call Anchor's derivedT::try_deserializeon 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.
- Base URL:
https://data.velocity.exchange - Data API playground
Market stats
Returns aggregate stats for all markets: volume, open interest, funding rate, oracle price, and market status.
GET https://data.velocity.exchange/stats/marketsResponse 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-PERPTrades
GET https://data.velocity.exchange/trades?symbol=SOL-PERP&limit=100Query 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.