Skip to Content

Users

How it works

A user account is the onchain account that holds your positions, orders, and collateral on Velocity. Each wallet can create multiple user accounts (called subaccounts), identified by a numeric ID (0, 1, 2, etc.). All subaccounts under the same wallet share cross-margin, meaning collateral and risk are calculated across all of them together.

User accounts store your perp positions (long/short), spot balances (deposits/borrows), open orders, and leverage settings. When you interact with Velocity (place orders, deposit, trade), you’re modifying data in your user account. The account is a Solana PDA (Program Derived Address) owned by the Velocity program.

Subaccounts are useful for separating strategies, isolating risk between different trading styles, or delegating specific accounts to bots while keeping others manual. You can switch between subaccounts using the SDK, and each one maintains its own positions and orders while sharing the wallet’s overall collateral pool.

SDK Usage

Most write actions in Velocity are done through VelocityClient. For account-level reads (positions, orders, health), you’ll typically use a User.

Initialize a User Account

Create a new Velocity user subaccount (PDA) for your wallet and return the transaction signature + user account address.

// Assumes you already constructed and subscribed `velocityClient`. const [txSig, userAccountPublicKey] = await velocityClient.initializeUserAccount(0, "my-account"); console.log(txSig, userAccountPublicKey.toBase58());
Method VelocityClient.initializeUserAccountReference ↗
ParameterTypeRequired
subAccountId
number
Sub-account id to create; defaults to `0`.
No
name
string
Display name (max 32 bytes); see `getInitializeUserAccountIxs` for the default.
No
referrerInfo
ReferrerInfo
Referrer's `referrer`/`referrerStats` public keys, if attributing this account to a referrer.
No
txParams
TxParams
Optional compute-unit/priority-fee overrides for the transaction.
No
Returns
Promise<[string, PublicKey]>

Get the Next Subaccount ID

Return the next available subaccount ID for this client instance.

const nextId = await velocityClient.getNextSubAccountId(); console.log(nextId);
Method VelocityClient.getNextSubAccountIdReference ↗
Returns
Promise<number>

Get a User and Subscribe

Import the User class and subscribe to account updates for a specific user instance.

import { User } from "@velocity-exchange/sdk"; const user = velocityClient.getUser(); await user.subscribe();
Class UserReference ↗
PropertyTypeRequired
velocityClient
VelocityClient
Yes
userAccountPublicKey
PublicKey
Yes
accountSubscriber
UserAccountSubscriber
Yes
_isSubscribed
boolean
Yes
eventEmitter
StrictEventEmitter<EventEmitter, UserAccountEvents>
Yes
isSubscribed
boolean
True only when both `subscribe()` has completed and the underlying `accountSubscriber` itself reports subscribed.
Yes
subscribe
(userAccount?: UserAccount | undefined) => Promise<boolean>
Subscribes to this `User` account (websocket/polling/gRPC/custom per `UserConfig.accountSubscription`) and awaits the initial account fetch. Must resolve before any `get*`/margin/PnL accessor is called — those throw `NotSubscribedError` until this has completed.
Yes
fetchAccounts
() => Promise<void>
Forces the account subscriber to re-fetch the `User` account from RPC (bypassing any push/poll cadence).
Yes
unsubscribe
() => Promise<void>
Removes all event listeners and tears down the account subscription.
Yes
getUserAccount
() => UserAccount | undefined
Returns the cached user account. - **Throws** `NotSubscribedError` if the subscriber has not been subscribed yet — reading the account before `subscribe()` resolves is a programming error, not a missing-account condition. - Returns `undefined` when subscribed but no account was found on chain. Because `subscribe()` awaits the initial fetch, an `undefined` here means the account does not exist (or has not yet been observed), not that data is "still loading".
Yes
getUserAccountOrThrow
() => UserAccount
Like `getUserAccount` but throws instead of returning `undefined` when the account was not found. Use at call sites that structurally require the account to exist. (Still propagates `NotSubscribedError` when called before subscribing.) Delegates to `getUserAccount` (rather than the subscriber directly) so callers that override `getUserAccount` see the override here too.
Yes
forceGetUserAccount
() => Promise<UserAccount | undefined>
Bypasses the cached subscriber state and force-fetches the `User` account directly from the RPC (via `fetchAccounts`), then returns the freshly cached value. Useful right after sending a transaction, when the websocket/polling subscriber may not yet have observed the update.
Yes
getUserAccountAndSlot
() => DataAndSlot<UserAccount> | undefined
Returns the cached user account together with the slot at which it was last observed. Same `undefined`/`NotSubscribedError` contract as `getUserAccount`.
Yes
getUserAccountAndSlotOrThrow
() => DataAndSlot<UserAccount>
Like `getUserAccountAndSlot` but throws instead of returning `undefined` when the account was not found. Use at call sites that structurally require the account to exist. (Still propagates `NotSubscribedError` when called before subscribing.)
Yes
getPerpPositionForUserAccount
(userAccount: UserAccount, marketIndex: number) => PerpPosition | undefined
Finds the perp position for `marketIndex` on an explicit `userAccount` snapshot rather than the cached account. Only matches "active" positions (see `getActivePerpPositionsForUserAccount`) — a market the user has never touched (or has fully closed and settled) returns `undefined` even though the on-chain array always has a fixed-size slot for every market.
Yes
getPerpPosition
(marketIndex: number) => PerpPosition | undefined
Gets the user's current position for a given perp market. If the user has no position returns undefined
Yes
getPerpPositionOrEmpty
(marketIndex: number) => PerpPosition
Like `getPerpPosition`, but returns a zeroed-out placeholder position (see `getEmptyPosition`) instead of `undefined` when the user has no active position in `marketIndex`. Convenient for math helpers that need a `PerpPosition` shape unconditionally (e.g. buying-power/leverage calcs).
Yes
getPerpPositionOrThrow
(marketIndex: number) => PerpPosition
Like `getPerpPosition`, but throws instead of returning `undefined` when the user has no active position in `marketIndex`.
Yes
getPerpPositionAndSlot
(marketIndex: number) => DataAndSlot<PerpPosition | undefined>
Like `getPerpPosition`, but also returns the slot at which the underlying `UserAccount` was observed.
Yes
getSpotPositionForUserAccount
(userAccount: UserAccount, marketIndex: number) => SpotPosition | undefined
Finds the spot position for `marketIndex` on an explicit `userAccount` snapshot. Unlike `getPerpPositionForUserAccount`, this does not filter to "active" positions first — it returns whatever fixed-size slot entry exists for that market index, even if the position is empty/available.
Yes
getSpotPosition
(marketIndex: number) => SpotPosition | undefined
Gets the user's current position for a given spot market. If the user has no position returns undefined
Yes
getSpotPositionAndSlot
(marketIndex: number) => DataAndSlot<SpotPosition | undefined>
Like `getSpotPosition`, but also returns the slot at which the underlying `UserAccount` was observed.
Yes
getEmptySpotPosition
(marketIndex: number) => SpotPosition
Returns a zeroed-out (no deposit/borrow) placeholder `SpotPosition` for `marketIndex`.
Yes
getTokenAmount
(marketIndex: number) => BN
Returns the token amount for a given market. The spot market precision is based on the token mint decimals. Positive if it is a deposit, negative if it is a borrow.
Yes
getEmptyPosition
(marketIndex: number) => PerpPosition
Returns a zeroed-out placeholder `PerpPosition` for `marketIndex` (no size, no orders, cross margin).
Yes
isPositionEmpty
(position: PerpPosition) => boolean
Returns true if `position` has no size and no open orders (a market slot that can be treated as unused).
Yes
getIsolatePerpPositionTokenAmount
(perpMarketIndex: number) => BN
Returns the isolated-margin quote deposit backing a given perp position, i.e. `PerpPosition.isolatedPositionScaledBalance` converted to a token amount. This is the collateral segregated to that single isolated position, separate from the user's cross-margin free collateral.
Yes
getTotalIsolatedPositionDeposits
() => BN
Returns the total USD value of deposits across all isolated perp positions.
Yes
getClonedPosition
(position: PerpPosition) => PerpPosition
Returns a shallow copy of `position`. Mutating the clone does not affect the cached account.
Yes
getOrderForUserAccount
(userAccount: UserAccount, orderId: number) => Order | undefined
Finds an order by its program-assigned `orderId` on an explicit `userAccount` snapshot.
Yes
getOrder
(orderId: number) => Order | undefined
Finds an order in the cached `UserAccount` by its program-assigned `orderId`.
Yes
getOrderAndSlot
(orderId: number) => DataAndSlot<Order | undefined>
Like `getOrder`, but also returns the slot at which the underlying `UserAccount` was observed.
Yes
getOrderByUserIdForUserAccount
(userAccount: UserAccount, userOrderId: number) => Order | undefined
Finds an order by its caller-assigned `userOrderId` (a client-chosen tag, distinct from the program-assigned `orderId`) on an explicit `userAccount` snapshot.
Yes
getOrderByUserOrderId
(userOrderId: number) => Order | undefined
Finds an order in the cached `UserAccount` by its caller-assigned `userOrderId` (a client-chosen tag, distinct from the program-assigned `orderId`).
Yes
getOrderByUserOrderIdAndSlot
(userOrderId: number) => DataAndSlot<Order | undefined>
Like `getOrderByUserOrderId`, but also returns the slot at which the underlying `UserAccount` was observed.
Yes
getOpenOrdersForUserAccount
(userAccount?: UserAccount | undefined) => Order[] | undefined
Filters an explicit `userAccount` snapshot's orders down to those with `OrderStatus.Open`.
Yes
getOpenOrders
() => Order[]
Returns all of the user's orders with `OrderStatus.Open`. Empty array (not `undefined`) if there are none or no account is loaded.
Yes
getOpenOrdersAndSlot
() => DataAndSlot<Order[]>
Like `getOpenOrders`, but also returns the slot at which the underlying `UserAccount` was observed.
Yes
getUserAccountPublicKey
() => PublicKey
Returns this `User`'s account address (does not require the account to be subscribed or to exist on chain).
Yes
exists
() => Promise<boolean>
Checks directly via RPC (bypassing the subscriber cache) whether the `User` account exists on chain.
Yes
getPerpBidAsks
(marketIndex: number) => [BN, BN]
Returns the position's total resting open-order bid/ask size in a perp market.
Yes
getPerpBuyingPower
(marketIndex: number, collateralBuffer?: any, maxMarginRatio?: number | undefined, positionType?: "isolated" | "cross" | undefined) => BN
calculates Buying Power = free collateral / initial margin ratio For `positionType: 'isolated'`, the buying power is capped by the lesser of (a) the user's cross free collateral and (b) the free quote asset value in the perp's quote spot market — mirroring that an isolated position can only draw down as much quote collateral as is actually available to isolate into it.
Yes
resolveMaxMarginRatio
any
Yes
getPerpBuyingPowerFromFreeCollateralAndBaseAssetAmount
(marketIndex: number, freeCollateral: BN, baseAssetAmount: BN, perpMarketMaxMarginRatio?: number | undefined) => BN
Converts a free-collateral amount directly into buying power for a perp market, given the (hypothetical) resulting base position size — used internally so the margin ratio (which can vary with position size via the IMF factor) reflects the post-trade size rather than the current size.
Yes
getFreeCollateral
(marginCategory?: MarginCategory | undefined, perpMarketIndex?: number | undefined) => BN
calculates Free Collateral = Total collateral - margin requirement When `perpMarketIndex` is provided, returns the free collateral scoped to that market's isolated margin bucket (the isolated quote deposit plus its unrealized PnL, minus its own margin requirement) rather than the user's cross-margin free collateral. If the user has no isolated position open in that market, returns `ZERO` rather than throwing.
Yes
getMarginRequirement
{ (marginCategory: MarginCategory, liquidationBuffer?: any, strict?: boolean | undefined, includeOpenOrders?: boolean | undefined): BN; (marginCategory: MarginCategory, liquidationBuffer?: any, strict?: boolean | undefined, includeOpenOrders?: boolean | undefined, perpMarketIndex?: number | undefined): BN; }
Calculates the margin requirement based on the specified parameters. When `perpMarketIndex` is passed, returns the isolated margin requirement for that market's isolated position only (`ZERO` if none exists) rather than the cross-margin requirement. `liquidationBuffer`, when non-zero, selects the buffered variant (`marginRequirementPlusBuffer` / `MarginContext.liquidation`), which pads the requirement to build in the state account's `liquidationMarginBufferRatio` — the same buffer keepers apply so a position doesn't get flagged for liquidation and immediately clear again.
Yes
getInitialMarginRequirement
(perpMarketIndex?: number | undefined) => BN
Initial margin requirement — the collateral needed to open/maintain a position at initial (as opposed to maintenance) margin ratios, using strict (TWAP-bounded) oracle pricing. This is what gates new orders and increases in leverage.
Yes
getMaintenanceMarginRequirement
(liquidationBuffer?: any, perpMarketIndex?: number | undefined) => BN
Maintenance margin requirement — the minimum collateral below which the position becomes eligible for liquidation. Uses non-strict oracle pricing and includes open orders' worst-case impact by default.
Yes
getActivePerpPositionsForUserAccount
(userAccount: UserAccount) => PerpPosition[]
Filters an explicit `userAccount` snapshot's fixed-size perp position array down to slots that are actually "active": nonzero base or quote amount, an outstanding open order count, or a nonzero isolated-margin quote deposit (a position can be flat but still isolated-funded).
Yes
getActivePerpPositions
() => PerpPosition[]
Returns the cached account's active perp positions. See `getActivePerpPositionsForUserAccount` for the activity criteria.
Yes
getActivePerpPositionsAndSlot
() => DataAndSlot<PerpPosition[]>
Like `getActivePerpPositions`, but also returns the slot at which the underlying `UserAccount` was observed.
Yes
getActiveSpotPositionsForUserAccount
(userAccount: UserAccount) => SpotPosition[]
Filters an explicit `userAccount` snapshot's spot positions to those that are not `isSpotPositionAvailable` (i.e. have a nonzero balance, orders, or cumulative deposits).
Yes
getActiveSpotPositions
() => SpotPosition[]
Returns the cached account's active spot positions. See `getActiveSpotPositionsForUserAccount` for the activity criteria.
Yes
getActiveSpotPositionsAndSlot
() => DataAndSlot<SpotPosition[]>
Like `getActiveSpotPositions`, but also returns the slot at which the underlying `UserAccount` was observed.
Yes
getUnrealizedPNL
(withFunding?: boolean | undefined, marketIndex?: number | undefined, withWeightMarginCategory?: MarginCategory | undefined, strict?: boolean | undefined, liquidationBuffer?: any) => BN
Calculates unrealized position price PnL, summed across all active perp positions (or a single one if `marketIndex` is given). When `withWeightMarginCategory` is supplied, the PnL is asset-weighted for margin purposes: profitable positions are scaled down by `calculateUnrealizedAssetWeight` (an unrealized gain is a less-trusted asset than settled collateral), and — for `'Initial'` margin specifically — the *per-position* weighted gain is additionally capped at `MAX_POSITIVE_UPNL_FOR_INITIAL_MARGIN` (**$100**, QUOTE_PRECISION), a safety guard against a single dangerously-configured or manipulated market inflating buying power. Losses are never capped, and a `liquidationBuffer` (if provided) further inflates negative PnL to mirror the on-chain liquidation-buffer treatment.
Yes
getUnrealizedFundingPNL
(marketIndex?: number | undefined) => BN
Calculates unrealized funding payment PnL — the funding accrued since each position's `lastCumulativeFundingRate` was last settled, not yet reflected in `quoteAssetAmount`.
Yes
getSpotMarketAssetAndLiabilityValue
(marketIndex?: number | undefined, marginCategory?: MarginCategory | undefined, liquidationBuffer?: any, includeOpenOrders?: boolean | undefined, strict?: boolean | undefined, now?: any) => { ...; }
Computes the combined weighted asset value and weighted liability value across the user's spot positions (worst-case, including open-order exposure by default), plus the net quote balance. This is the core spot side of the margin system that `getTotalCollateral`/`getMarginRequirement` build on.
Yes
getSpotMarketLiabilityValue
(marketIndex?: number | undefined, marginCategory?: MarginCategory | undefined, liquidationBuffer?: any, includeOpenOrders?: boolean | undefined, strict?: boolean | undefined, now?: any) => BN
Convenience wrapper around `getSpotMarketAssetAndLiabilityValue` returning only `totalLiabilityValue`. See that method for parameter semantics. Returns QUOTE_PRECISION (1e6).
Yes
getSpotLiabilityValue
(tokenAmount: BN, strictOraclePrice: StrictOraclePrice, spotMarketAccount: SpotMarketAccount, marginCategory?: MarginCategory | undefined, liquidationBuffer?: any) => BN
Thin wrapper around the `math/spotBalance` `getSpotLiabilityValue` helper that supplies the user's `maxMarginRatio`. Returns QUOTE_PRECISION (1e6), negative.
Yes
getSpotMarketAssetValue
(marketIndex?: number | undefined, marginCategory?: MarginCategory | undefined, includeOpenOrders?: boolean | undefined, strict?: boolean | undefined, now?: any) => BN
Convenience wrapper around `getSpotMarketAssetAndLiabilityValue` returning only `totalAssetValue`. See that method for parameter semantics. Returns QUOTE_PRECISION (1e6), non-negative.
Yes
getSpotAssetValue
(tokenAmount: BN, strictOraclePrice: StrictOraclePrice, spotMarketAccount: SpotMarketAccount, marginCategory?: MarginCategory | undefined) => BN
Thin wrapper around the `math/spotBalance` `getSpotAssetValue` helper that supplies the user's `maxMarginRatio`. Returns QUOTE_PRECISION (1e6), non-negative.
Yes
getSpotPositionValue
(marketIndex: number, marginCategory?: MarginCategory | undefined, includeOpenOrders?: boolean | undefined, strict?: boolean | undefined, now?: any) => BN
Net spot value (`totalAssetValue - totalLiabilityValue`) for a single spot market. See `getSpotMarketAssetAndLiabilityValue` for parameter semantics. Returns QUOTE_PRECISION (1e6), can be negative.
Yes
getNetSpotMarketValue
(withWeightMarginCategory?: MarginCategory | undefined) => BN
Net spot value (`totalAssetValue - totalLiabilityValue`) across all spot markets combined.
Yes
getTotalCollateral
(marginCategory?: MarginCategory | undefined, strict?: boolean | undefined, includeOpenOrders?: boolean | undefined, liquidationBuffer?: any, perpMarketIndex?: number | undefined) => BN
Calculates Total Collateral: net spot collateral value plus weighted unrealized perp PnL (see `getUnrealizedPNL`'s `$100`-per-position cap under `'Initial'` margin). This is the numerator side of the margin system; `getFreeCollateral`/`getMarginRequirement` are derived from it. When `perpMarketIndex` is provided, returns the isolated total collateral for that market's isolated position bucket instead of the cross-margin total — and **throws** if the user has no isolated margin calculation for that market (unlike `getFreeCollateral`, which swallows the same case and returns `ZERO`).
Yes
getLiquidationBuffer
() => Map<number | "cross", BN>
Builds the liquidation-buffer map to pass into margin calculations while a liquidation is in progress: `'cross'` is set to the state account's `liquidationMarginBufferRatio` if cross margin is being liquidated, and each isolated perp position currently flagged `BeingLiquidated` or `Bankruptcy` gets the same buffer under its market index. Positions not currently being liquidated are omitted (no buffer applied).
Yes
getHealth
(perpMarketIndex?: number | undefined) => number
Calculates a user's health score by comparing total collateral against the maintenance margin requirement: `100 * (1 - maintenanceMarginReq / totalCollateral)`, clamped to `[0, 100]` and rounded to the nearest integer. `100` means no maintenance requirement (or a requirement of zero with non-negative collateral); `0` means at or past the maintenance threshold (liquidatable) or that collateral is non-positive. Short-circuits to `0` if the relevant scope is already flagged as being liquidated: cross margin via `isCrossMarginBeingLiquidated` (when `perpMarketIndex` is omitted), or the specific isolated position via `isIsolatedPositionBeingLiquidated` (when `perpMarketIndex` is given).
Yes
calculateWeightedPerpPositionLiability
(perpPosition: PerpPosition, marginCategory?: MarginCategory | undefined, liquidationBuffer?: any, includeOpenOrders?: boolean | undefined, strict?: boolean | undefined) => BN
Computes a single perp position's margin-weighted liability value: worst-case (or current, if `includeOpenOrders` is false) base amount, valued at the oracle price (or `expiryPrice` if the market is in settlement, which also zeroes the margin ratio), scaled by the applicable margin ratio for `marginCategory`. Underlies `getPerpMarketLiabilityValue`, `getTotalPerpPositionLiability`, and the leverage/liquidation-price math.
Yes
getPerpMarketLiabilityValue
(marketIndex: number, marginCategory?: MarginCategory | undefined, liquidationBuffer?: any, includeOpenOrders?: boolean | undefined, strict?: boolean | undefined) => BN
Margin-weighted liability value of a single perp position. Thin wrapper around `calculateWeightedPerpPositionLiability` for the position in `marketIndex`; see that method for the worst-case/margin-ratio semantics.
Yes
getTotalPerpPositionLiability
(marginCategory?: MarginCategory | undefined, liquidationBuffer?: any, includeOpenOrders?: boolean | undefined, strict?: boolean | undefined) => BN
Sums `calculateWeightedPerpPositionLiability` across every active perp position — the perp side of the margin requirement (see `getMarginRequirement`).
Yes
getPerpPositionValue
(marketIndex: number, oraclePriceData: Pick<OraclePriceData, "price">, includeOpenOrders?: boolean | undefined) => BN
Values a perp position's base-asset notional at a caller-supplied oracle price rather than looking one up internally — useful for pricing against a simulated/custom price. Returns `ZERO` (via `getPerpPositionOrEmpty`) if the user has no position in `marketIndex`.
Yes
getPerpLiabilityValue
(marketIndex: number, oraclePriceData: OraclePriceData, includeOpenOrders?: boolean | undefined) => BN
Unweighted (no margin ratio applied) perp liability notional at a caller-supplied oracle price. Returns `ZERO` (via `getPerpPositionOrEmpty`) if the user has no position in `marketIndex`.
Yes
getPositionSide
(currentPosition: Pick<PerpPosition, "baseAssetAmount">) => PositionDirection | undefined
Returns `PositionDirection.LONG`/`SHORT` from the sign of `baseAssetAmount`, or `undefined` if the position is flat.
Yes
getPositionEstimatedExitPriceAndPnl
(position: PerpPosition, amountToClose?: any, useAMMClose?: boolean | undefined) => [BN, BN]
calculates average exit price (optionally for closing up to 100% of position)
Yes
getLeverage
(includeOpenOrders?: boolean | undefined, perpMarketIndex?: number | undefined) => BN
calculates current user leverage which is (total liability size) / (net asset value)
Yes
calculateLeverageFromComponents
({ perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue, }: { perpLiabilityValue: BN; perpPnl: BN; spotAssetValue: BN; spotLiabilityValue: BN; }) => BN
Combines the components from `getLeverageComponents` into a single leverage ratio: `(perpLiability + spotLiability) / (spotAsset + perpPnl - spotLiability)`. Returns TEN_THOUSAND (1e4) precision; `ZERO` if net asset value is zero.
Yes
getLeverageComponents
(includeOpenOrders?: boolean | undefined, marginCategory?: MarginCategory | undefined, perpMarketIndex?: number | undefined) => { perpLiabilityValue: BN; perpPnl: BN; spotAssetValue: BN; spotLiabilityValue: BN; }
Gathers the four raw components (`perpLiabilityValue`, `perpPnl`, `spotAssetValue`, `spotLiabilityValue`, all QUOTE_PRECISION/1e6) that `calculateLeverageFromComponents` combines into a leverage ratio. When `perpMarketIndex` is given, scopes to a single isolated position: `spotAssetValue` becomes that position's isolated quote deposit and `spotLiabilityValue` is `ZERO` (isolated positions carry no spot liability of their own). Otherwise sums across the whole account, and folds in `getTotalIsolatedPositionDeposits` as additional spot asset value when `marginCategory` is unweighted.
Yes
isDustDepositPosition
(spotMarketAccount: SpotMarketAccount) => boolean
Returns true if the user's deposit position in `spotMarketAccount` is non-empty but worth less than `DUST_POSITION_SIZE` (QUOTE_PRECISION) — i.e. too small to be economically worth withdrawing/settling. Only evaluates deposits (returns false for borrows or an empty position).
Yes
getSpotMarketAccountsWithDustPosition
() => SpotMarketAccount[]
Returns every spot market where the user holds a dust-sized deposit; see `isDustDepositPosition`.
Yes
getTotalLiabilityValue
(marginCategory?: MarginCategory | undefined) => BN
Sum of the user's total perp position liability (worst-case, open orders included) and total spot liability value (worst-case, open orders included).
Yes
getTotalAssetValue
(marginCategory?: MarginCategory | undefined) => BN
Sum of the user's total spot asset value and total unrealized perp PnL (with funding). When `marginCategory` is omitted (unweighted), also includes `getTotalIsolatedPositionDeposits`.
Yes
getNetUsdValue
() => BN
Unweighted net USD value of the account: net spot market value, plus unrealized (funding-inclusive) perp PnL, plus isolated position deposits.
Yes
getTotalAllTimePnl
() => BN
Calculates the all-time P&L of the user: current net USD value (`getNetUsdValue`), plus lifetime total withdraws, minus lifetime total deposits. Equivalent to "everything the account is worth now, plus everything ever taken out, minus everything ever put in".
Yes
getMaxLeverageForPerp
(perpMarketIndex: number, _marginCategory?: MarginCategory | undefined) => BN
calculates max allowable leverage exceeding hitting requirement category for large sizes where imf factor activates, result is a lower bound
Yes
getMaxLeverageForSpot
(spotMarketIndex: number, direction: PositionDirection) => BN
calculates max allowable leverage exceeding hitting requirement category
Yes
getMarginRatio
() => BN
calculates margin ratio: 1 / leverage
Yes
canBeLiquidated
() => AccountLiquidatableStatus & { isolatedPositions: Map<number, AccountLiquidatableStatus>; }
Yes
getLiquidationStatuses
(marginCalc?: MarginCalculation | undefined) => Map<number | "cross", AccountLiquidatableStatus>
New API: Returns liquidation status for cross and each isolated perp position. Map keys: - 'cross' for cross margin - marketIndex (number) for each isolated perp position Each `canBeLiquidated` compares maintenance total collateral against the maintenance margin requirement for that scope. If `marginCalc` is not supplied, one is computed under `'Maintenance'` with the account's current `getLiquidationBuffer()` applied — i.e. this defaults to the same buffered check the on-chain liquidation instructions use, not a bare maintenance-margin comparison.
Yes
isBeingLiquidated
() => boolean
Returns true if cross margin or any isolated perp position is currently flagged as being liquidated or bankrupt.
Yes
isCrossMarginBeingLiquidated
() => boolean
Returns true if the account-level `UserStatus` has `BEING_LIQUIDATED` or `BANKRUPT` set (cross margin, not per-isolated-position).
Yes
canCrossMarginBeLiquidated
(marginCalc?: MarginCalculation | undefined) => boolean
Returns true if cross margin is currently below maintenance requirement (no buffer).
Yes
hasIsolatedPositionBeingLiquidated
() => boolean
Returns true if any active perp position has `PositionFlag.BeingLiquidated` or `PositionFlag.Bankruptcy` set.
Yes
isIsolatedPositionBeingLiquidated
(perpMarketIndex: number) => boolean
Returns true if the specific perp position in `perpMarketIndex` has `PositionFlag.BeingLiquidated` or `PositionFlag.Bankruptcy` set. False (not throw) if the user has no position there.
Yes
getLiquidatableIsolatedPositions
(marginCalc?: MarginCalculation | undefined) => number[]
Returns true if any isolated perp position is currently below its maintenance requirement (no buffer).
Yes
canIsolatedPositionMarginBeLiquidated
(isolatedMarginCalculation: IsolatedMarginCalculation) => boolean
Returns true if `isolatedMarginCalculation`'s collateral is below its margin requirement (no buffer).
Yes
hasStatus
(status: UserStatus) => boolean
Returns true if the account's `UserStatus` bitmask has `status` set.
Yes
isBankrupt
() => boolean
Returns true if the account's `UserStatus` has `BANKRUPT` set (equity insufficient to cover liabilities; awaiting bankruptcy resolution).
Yes
needsToSettleFundingPayment
() => boolean
Checks if any user position cumulative funding differs from respective market cumulative funding
Yes
spotLiquidationPrice
(marketIndex: number, positionBaseSizeChange?: any) => BN
Calculate the liquidation price of a spot position — the oracle price at which maintenance free collateral would hit zero, extrapolating linearly from the current free collateral and the position's per-unit-price sensitivity (`calculateFreeCollateralDeltaForSpot`). If a perp market shares the same oracle as this spot market, that perp position's sensitivity is folded in too (scaled for any oracle-source unit difference), since a single price move affects both simultaneously.
Yes
liquidationPrice
(marketIndex: number, positionBaseSizeChange?: any, estimatedEntryPrice?: any, marginCategory?: MarginCategory | undefined, includeOpenOrders?: boolean | undefined, offsetCollateral?: any, marginType?: MarginType | undefined) => BN
Calculate the liquidation price of a perp position, with optional parameter to calculate the liquidation price after a trade. Like `spotLiquidationPrice`, this extrapolates linearly from current free collateral (`totalCollateral - marginRequirement`, plus `offsetCollateral`) and the position's price sensitivity; if a spot market shares the same oracle, its sensitivity is folded in too. When `marginType === 'Isolated'`, free collateral and the margin requirement are scoped to that market's isolated bucket instead of the cross-margin account (and the spot-oracle cross-contribution above is skipped).
Yes
calculateEntriesEffectOnFreeCollateral
(market: PerpMarketAccount, oraclePrice: BN, perpPosition: PerpPosition, positionBaseSizeChange: BN, estimatedEntryPrice: BN, includeOpenOrders: boolean, marginCategory?: MarginCategory | undefined) => BN
Helper for `liquidationPrice`: estimates the net change to free collateral from simultaneously (a) realizing PnL on `positionBaseSizeChange` entered at `estimatedEntryPrice` (assuming the worst/taker fee tier) versus the oracle price, and (b) the resulting change in margin requirement from the new position size. Only component (a) applies under `'Maintenance'` (matching `liquidationPrice`'s default); under other margin categories only the margin-requirement delta is applied.
Yes
calculateFreeCollateralDeltaForPerp
(market: PerpMarketAccount, perpPosition: PerpPosition, positionBaseSizeChange: BN, oraclePrice: BN, marginCategory?: MarginCategory | undefined, includeOpenOrders?: boolean | undefined) => any
Helper for `liquidationPrice`: the derivative of free collateral with respect to the perp market's oracle price, for the proposed post-trade position (`positionBaseSizeChange` applied to the current, or worst-case if `includeOpenOrders`, base amount). Used as the linear-extrapolation slope to solve for the price at which free collateral hits zero.
Yes
calculateFreeCollateralDeltaForSpot
(market: SpotMarketAccount, signedTokenAmount: BN, marginCategory?: MarginCategory | undefined) => BN
Helper for `spotLiquidationPrice`/`liquidationPrice`: the derivative of free collateral with respect to the spot market's oracle price, for a position of `signedTokenAmount` (positive = deposit, negative = borrow).
Yes
liquidationPriceAfterClose
(positionMarketIndex: number, closeQuoteAmount: BN, estimatedEntryPrice?: any) => BN
Calculates the estimated liquidation price for a position after closing a quote amount of the position.
Yes
getMarginUSDCRequiredForTrade
(targetMarketIndex: number, baseSize: BN, estEntryPrice?: any, perpMarketMaxMarginRatio?: number | undefined) => BN
Calculates the margin required to open a trade of `baseSize` in `targetMarketIndex`, scalar only — does not account for trade direction or existing positions/whether the trade is actually risk-increasing.
Yes
getCollateralDepositRequiredForTrade
(targetMarketIndex: number, baseSize: BN, collateralIndex: number, perpMarketMaxMarginRatio?: number | undefined) => BN
Converts `getMarginUSDCRequiredForTrade`'s USDC margin requirement into how much of `collateralIndex`'s token a user would need to deposit to cover it, accounting for that collateral's scaled initial asset weight (a lower-weighted asset requires proportionally more deposited).
Yes
getMaxTradeSizeUSDCForPerp
(targetMarketIndex: number, tradeSide: PositionDirection, maxMarginRatio?: number | undefined, positionType?: "isolated" | "cross" | undefined) => { tradeSize: BN; oppositeSideTradeSize: BN; }
Separates the max trade size into two parts: - tradeSize: The maximum trade size for target direction - oppositeSideTradeSize: the trade size for closing the opposite direction
Yes
getMaxTradeSizeUSDCForSpot
(targetMarketIndex: number, direction: PositionDirection, currentQuoteAssetValue?: any, currentSpotMarketNetValue?: any) => BN
Get the maximum trade size for a given market, taking into account the user's current leverage, positions, collateral, etc.
Yes
getMaxSwapAmount
({ inMarketIndex, outMarketIndex, calculateSwap, iterationLimit, }: { inMarketIndex: number; outMarketIndex: number; calculateSwap?: (inAmount: BN) => BN; iterationLimit?: number; }) => { inAmount: BN; outAmount: BN; leverage: BN; }
Calculates the max amount of token that can be swapped from inMarket to outMarket Assumes swap happens at oracle price
Yes
cloneAndUpdateSpotPosition
(position: SpotPosition, tokenAmount: BN, market: SpotMarketAccount) => SpotPosition
Returns a cloned `SpotPosition` with `tokenAmount` (signed, positive = deposit / negative = borrow) applied on top of the existing balance — used to simulate the post-trade/post-swap position without mutating the cached account.
Yes
calculateSpotPositionFreeCollateralContribution
(spotPosition: SpotPosition, strictOraclePrice: StrictOraclePrice) => BN
Worst-case free-collateral contribution (under `'Initial'` margin) of a single spot position. Returns QUOTE_PRECISION (1e6).
Yes
calculateSpotPositionLeverageContribution
(spotPosition: SpotPosition, strictOraclePrice: StrictOraclePrice) => { totalAssetValue: BN; totalLiabilityValue: BN; }
Worst-case (under `'Initial'` margin) asset/liability value split of a single spot position, for use in leverage calculations. Both fields QUOTE_PRECISION (1e6), non-negative.
Yes
accountLeverageAfterSwap
({ inMarketIndex, outMarketIndex, inAmount, outAmount, }: { inMarketIndex: number; outMarketIndex: number; inAmount: BN; outAmount: BN; }) => BN
Estimates what the user leverage will be after swap
Yes
accountLeverageRatioAfterTrade
(targetMarketIndex: number, targetMarketType: MarketType, tradeQuoteAmount: BN, tradeSide: PositionDirection, includeOpenOrders?: boolean | undefined) => BN
Returns the leverage ratio for the account after adding (or subtracting) the given quote size to the given position
Yes
getUserFeeTier
(marketType: MarketType, now?: any) => FeeTier
Looks up the user's fee tier from the state account's fee structure. For perp markets, the tier is selected by the user's rolling 30-day volume (`getUser30dRollingVolumeEstimate`, QUOTE_PRECISION) against fixed breakpoints — $2M, $10M, $20M, $80M, $200M — picking the lowest-index tier whose breakpoint the user's volume is still under (tier 5, the lowest fees, if volume meets or exceeds the top breakpoint). Spot markets always use tier 0 (no volume-based discount).
Yes
calculatePerpTakerFee
(quoteAmount: BN, marketIndex?: number | undefined, isReferee?: boolean | undefined, builderInfo?: Pick<OrderParams, "builderIdx" | "builderFeeTenthBps"> | undefined) => BN
Calculates how much perp fee will be taken for a given sized trade. When `marketIndex` is provided, delegates to `VelocityClient.getMarketFees` for that specific market's taker-fee multiplier (which itself applies the market's `feeAdjustment`, the referee discount, and — when `builderInfo` is passed — the builder fee). Otherwise uses the volume-based fee tier from `getUserFeeTier(MarketType.PERP)`; if the user is a referee (determined from `UserStats.referrerStatus`'s `IsReferred` flag unless `isReferee` is explicitly passed), the tier's `refereeFeeNumerator`/`refereeFeeDenominator` proportion is subtracted from the fee as a discount, and — when `builderInfo` carries a builder code — the builder fee (`quoteAmount * builderFeeTenthBps / 100_000`) is added on top, mirroring the program's `builder_fee` (`math/fees.rs`).
Yes
getWithdrawalLimit
(marketIndex: number, reduceOnly?: boolean | undefined) => BN
Calculates a user's max withdrawal amounts for a spot market. If reduceOnly is true, it will return the max withdrawal amount without opening a liability for the user. Combines three caps: the market-wide withdraw/borrow guard (`calculateWithdrawLimit`, a rolling-window rate limit on the spot market), the user's own deposit balance, and how much their free collateral supports withdrawing/borrowing. If `canBypassWithdrawLimits` returns `canBypass: true` (see that method), the market-wide withdraw limit floor is raised to the user's full deposit amount — letting a small, healthy, always-net-positive depositor withdraw in full even if the market-wide guard would otherwise throttle them.
Yes
canBypassWithdrawLimits
(marketIndex: number) => { canBypass: boolean; netDeposits: BN; depositAmount: BN; maxDepositAmount: BN; }
Determines whether the user can bypass the spot market's rolling withdraw-guard limit for `marketIndex`. `canBypass` is true only when **all** of the following hold: - The user currently holds a deposit (not a borrow) in the market. - Their lifetime net deposits (`totalDeposits - totalWithdraws`) are non-negative — they have never net-withdrawn more than they net-deposited. - Their `cumulativeDeposits` for the position has never gone negative (no history of having borrowed and repaid in this market). - Their current deposit amount is below `maxDepositAmount`, i.e. 10% of the spot market's `withdrawGuardThreshold`. This lets a small, well-behaved depositor withdraw their own funds in full even while the market-wide withdraw guard is actively throttling larger movements. Used by `getWithdrawalLimit`.
Yes
canMakeIdle
(slot: BN) => boolean
Determines whether the user can be marked idle (excluded from userMap subscriptions by default, and skipped by most keeper crank passes) as of `slot`. Requires: not already idle; inactive for the required window since `lastActiveSlot` (1 hour / 9,000 slots if equity is under $1,000, otherwise 1 week / 1,512,000 slots); not currently being liquidated; and no open perp positions, borrows, spot open orders, or open orders of any kind.
Yes
canBeDeleted
(userStatsAccount?: UserStatsAccount | undefined, now?: any) => { canDelete: boolean; reason?: string; }
Determines whether this `User` (sub)account can be deleted (checked before sending a delete-user instruction, to give a friendlier error than an on-chain revert). Returns `canDelete: false` with a `reason` string if any of the following hold: it's a referrer's sub-account 0 (referrers cannot delete their primary account); the account is bankrupt or being liquidated; it has any non-empty perp/spot position or open order; or (when the state account charges an initialize-user fee) the account is a "fresh" account — younger than `ACCOUNT_AGE_DELETION_CUTOFF_SECONDS`, measured from its earliest recorded filler/maker/taker volume timestamp — that is not currently idle.
Yes
getSafestTiers
() => { perpTier: number; spotTier: number; }
Returns the numerically-lowest (i.e. safest) contract/asset tier across the user's active positions — perp tiers from active perp positions, spot tiers only from spot **borrows** (deposits are skipped, since asset tier only restricts borrowing exposure). Defaults to `4` (the second-riskiest tier index) when the user has no positions of that kind — this is a permissive default intended for callers doing tier-safety comparisons (see `perpTierIsAsSafeAs` in `math/tiers`), not a claim that "no position" is itself a risky tier.
Yes
getPerpPositionHealth
({ marginCategory, perpPosition, oraclePriceData, quoteOraclePriceData, includeOpenOrders, }: { marginCategory: MarginCategory; perpPosition: PerpPosition; oraclePriceData?: OraclePriceData; quoteOraclePriceData?: OraclePriceData; includeOpenOrders?: boolean; }) => HealthComponent
Breaks down a single perp position's contribution to the margin system as a `HealthComponent`: worst-case base size, its unweighted liability value, the applicable margin ratio (`weight`), and the resulting weighted margin requirement (`weightedValue`, which includes the position's open-order margin add-on). Used to build up `getHealthComponents`' `perpPositions` array (e.g. for UI breakdowns of "what's consuming my margin").
Yes
getHealthComponents
({ marginCategory, }: { marginCategory: MarginCategory; }) => HealthComponents
Builds a full breakdown of every component feeding into the user's margin calculation, for UI/diagnostic display: `deposits` and `borrows` (one `HealthComponent` per non-quote spot market with a nonzero worst-case position, plus a synthetic entry for the net quote balance), `perpPositions` (via `getPerpPositionHealth`, one per active perp position), and `perpPnl` (each position's weighted unrealized PnL — see `getUnrealizedPNL` for the `'Initial'`-margin $100 cap that also applies here).
Yes
getTotalPerpPositionValueExcludingMarket
any
Get the total position value, excluding any position coming from the given target market
Yes
getMMOracleDataForPerpMarket
any
Yes
getOracleDataForPerpMarket
any
Yes
getOracleDataForSpotMarket
any
Yes
getActivePositions
() => { activePerpPositions: number[]; activeSpotPositions: number[]; }
Get the active perp and spot positions of the user.
Yes
getMarginCalculation
(marginCategory?: MarginCategory | undefined, opts?: { strict?: boolean; includeOpenOrders?: boolean; liquidationBufferMap?: Map<number | "cross", BN>; } | undefined) => MarginCalculation
Compute the full margin calculation for the user's account. Prioritize using this function instead of calling getMarginRequirement or getTotalCollateral multiple times. Consumers can use this to avoid duplicating work across separate calls. Mirrors the on-chain margin accumulation in `math/margin.rs`, splitting contributions into cross-margin and per-market isolated buckets (`MarginCalculation.isolatedMarginCalculations`, keyed by perp market index — see `isPerpPositionIsolated`) and tracking whether the account holds any isolated-tier liability (`withPerpIsolatedLiability` / `withSpotIsolatedLiability`, consumed by `validateAnyIsolatedTierRequirements`). A perp position's isolated quote-deposit collateral only counts toward that position's own isolated bucket, never the cross-margin total. Also enforces pool-id consistency: every spot/perp position's market must match the user's `poolId`, **except** a pool-1 user is allowed to hold a quote-asset deposit (not borrow) even though the quote spot market itself belongs to pool 0 — throws `InvalidPoolId: ...` otherwise.
Yes
isPerpPositionIsolated
(perpPosition: PerpPosition) => boolean
Returns true if `perpPosition` was opened/is held under isolated margin (`PositionFlag.IsolatedPosition` set) — segregated to its own margin bucket (see `getMarginCalculation`) rather than sharing cross-margin collateral with the rest of the account.
Yes
validateAnyIsolatedTierRequirements
(calculation: MarginCalculation) => { valid: boolean; reason?: string; }
Pre-flight check for `IsolatedAssetTierViolation`: mirrors `validate_any_isolated_tier_requirements` in `math/margin.rs`. A user holding an isolated-tier perp or spot liability may not simultaneously carry other liabilities (besides a single usdc borrow, for a perp isolated liability), unless they are reduce-only. Specifically, if `calculation.withPerpIsolatedLiability` is set (an isolated-*contract-tier* perp liability exists) and the user is not `UserStatus.REDUCE_ONLY`: more than one perp liability is invalid; margin trading enabled is invalid; and any spot liability other than a single USDC borrow is invalid. If `calculation.withSpotIsolatedLiability` is set (an isolated-*asset-tier* spot liability exists) and not reduce-only: any perp liability, or more than the one isolated-tier spot liability, is invalid.
Yes

Get active subaccount user

Get the User object for the currently active subaccount.

const user = velocityClient.getUser();
Method VelocityClient.getUserReference ↗
ParameterTypeRequired
subAccountId
number
Sub-account id; defaults to `this.activeSubAccountId`.
No
authority
PublicKey
Authority owning the sub-account; defaults to `this.authority`.
No
Returns
User

Get a specific subaccount user

Get the User object for a specific subaccount ID.

const user = velocityClient.getUser(1);
Method VelocityClient.getUserReference ↗
ParameterTypeRequired
subAccountId
number
Sub-account id; defaults to `this.activeSubAccountId`.
No
authority
PublicKey
Authority owning the sub-account; defaults to `this.authority`.
No
Returns
User

Read account via User

Read raw onchain UserAccount data from a User instance.

const account = velocityClient.getUser().getUserAccount(); console.log(account);
Method User.getUserAccountReference ↗
Returns
UserAccount | undefined

Read account via VelocityClient

Read raw onchain UserAccount data directly from VelocityClient for the active subaccount.

const account = velocityClient.getUserAccount(); console.log(account);
Method VelocityClient.getUserAccountReference ↗
ParameterTypeRequired
subAccountId
number
Sub-account id; defaults to `this.activeSubAccountId`.
No
authority
PublicKey
Authority owning the sub-account; defaults to `this.authority`.
No
Returns
UserAccount | undefined

Refresh user accounts

Force-refresh subscribed user-related accounts from RPC.

const user = velocityClient.getUser(0); await user.fetchAccounts();
Method User.fetchAccountsReference ↗
Returns
Promise<void>

Derived Addresses (User Account)

User accounts are Program Derived Addresses (PDAs) - deterministic addresses generated from seeds (the program ID, wallet authority, and subaccount ID). PDAs are a Solana concept that lets you calculate an account’s address without making an RPC call.

This is useful when you need to reference a user account address before fetching it, such as when constructing transactions or querying multiple accounts in parallel.

For a deeper understanding of PDAs and program structure, see Program Structure.

Derive the user account PDA for a given subaccount without an RPC fetch.

const userAccountPublicKey = await velocityClient.getUserAccountPublicKey(0); console.log(userAccountPublicKey.toBase58());
Method VelocityClient.getUserAccountPublicKeyReference ↗
ParameterTypeRequired
subAccountId
number
Sub-account id; defaults to `this.activeSubAccountId`.
No
authority
PublicKey
Authority owning the sub-account; defaults to `this.authority`.
No
Returns
Promise<PublicKey>

Query User State (Orders / Positions)

Get token amount (deposit vs borrow)

Return your token balance for a spot market as a signed value (+ deposit, - borrow).

import { BN } from "@velocity-exchange/sdk"; const user = velocityClient.getUser(); const tokenAmount = user.getTokenAmount(0); const isDeposit = tokenAmount.gte(new BN(0)); const isBorrow = tokenAmount.lt(new BN(0)); console.log({ tokenAmount: tokenAmount.toString(), isDeposit, isBorrow });
Method User.getTokenAmountReference ↗
ParameterTypeRequired
marketIndex
number
Yes
Returns
BN

Get a perp position

Get your perp position record for a market index (or undefined if no position exists).

const position = velocityClient.getUser().getPerpPosition(0); console.log(position?.baseAssetAmount.toString());
Method User.getPerpPositionReference ↗
ParameterTypeRequired
marketIndex
number
Yes
Returns
PerpPosition | undefined

Get an order by order id

Look up a specific order using its internal Velocity orderId.

const order = velocityClient.getUser().getOrder(1); console.log(order);
Method User.getOrderReference ↗
ParameterTypeRequired
orderId
number
Yes
Returns
Order | undefined

Get an order by user order id

Look up a specific order using your custom userOrderId.

const order = velocityClient.getUser().getOrderByUserOrderId(1); console.log(order);
Method User.getOrderByUserOrderIdReference ↗
ParameterTypeRequired
userOrderId
number
Yes
Returns
Order | undefined

Get all open orders

Return all currently open (unfilled/uncancelled) orders for this user.

const orders = velocityClient.getUser().getOpenOrders(); console.log(orders.length);
Method User.getOpenOrdersReference ↗
Returns
Order[]

Active Subaccount

The active subaccount is the default subaccount that VelocityClient methods operate on when you don’t explicitly specify a subaccount ID. By default, the active subaccount is 0.

Many SDK methods use the active subaccount implicitly:

  • velocityClient.getUser() - returns the User for the active subaccount
  • velocityClient.getUserAccount() - returns the account data for the active subaccount
  • velocityClient.placePerpOrder(orderParams) - places an order on the active subaccount
  • velocityClient.getSpotPosition(marketIndex) - gets position from the active subaccount
  • velocityClient.getUser().getPerpPosition(marketIndex) - gets position from the active subaccount

You can change which subaccount is active using switchActiveUser(), or you can explicitly pass a subaccount ID to methods that support it (like getUser(subAccountId)).

Switch Active Subaccount

Change the active subaccount context used by default in VelocityClient methods.

// Switch to subaccount 1. await velocityClient.switchActiveUser(1);
Method VelocityClient.switchActiveUserReference ↗
ParameterTypeRequired
subAccountId
number
Sub-account id to make active.
Yes
authority
PublicKey
Authority to switch to; defaults to the current `this.authority` (no change).
No
Returns
Promise<void>

Update Delegate

A delegate is another wallet that can trade on behalf of your user account without being able to withdraw funds. This is useful for allowing trading bots to manage positions while keeping withdrawal authority secure.

Learn more: Delegated Accounts

Set or update the delegate authority allowed to trade on a subaccount.

import { PublicKey } from "@solana/web3.js"; await velocityClient.updateUserDelegate(new PublicKey("<DELEGATE_PUBKEY>"), 0);
Method VelocityClient.updateUserDelegateReference ↗
ParameterTypeRequired
delegate
PublicKey
New delegate public key; pass `PublicKey.default` to remove the delegate.
Yes
subAccountId
number
Sub-account id to update; defaults to `0`.
No
Returns
Promise<string>

A delegate can sign most trading instructions (place/cancel orders, etc.) on a subaccount’s behalf, but withdraw always requires the true owning authority’s own signature — delegates can never withdraw funds out of the protocol.

Delegate-initiated internal transfers

A delegate can move a subaccount’s collateral to another of the owner’s subaccounts via transferDepositByDelegate, but only once the owner has opted in. This opt-in is a single flag on the owner’s UserStats account (delegatePermissions) that gates delegate transfers across all of that owner’s subaccounts — it does not affect direct deposits/withdrawals or trading, and it is off by default.

Step 1 — the owner opts in (signed by the owner’s own wallet, not the delegate):

// Run with a VelocityClient whose wallet is the account owner (authority). await velocityClient.updateUserAllowDelegateTransfer(true);
Method VelocityClient.updateUserAllowDelegateTransferReference ↗
ParameterTypeRequired
allowDelegateTransfer
boolean
New value for the flag.
Yes
Returns
Promise<string>

Step 2 — the delegate transfers a deposit between the owner’s subaccounts:

// Run with a VelocityClient constructed with `authority: <OWNER_PUBKEY>` and a // delegate wallet, per the "Delegated accounts" note in Setup. const marketIndex = 0; // quote asset market const amount = velocityClient.convertToSpotPrecision(marketIndex, 100); await velocityClient.transferDepositByDelegate( amount, marketIndex, 0, // fromSubAccountId (owner's subaccount) 1 // toSubAccountId (owner's subaccount) );
Method VelocityClient.transferDepositByDelegateReference ↗
ParameterTypeRequired
amount
any
Amount to transfer, in the spot market's own token precision.
Yes
marketIndex
number
Spot market index of the balance to transfer.
Yes
fromSubAccountId
number
Sub-account id to debit.
Yes
toSubAccountId
number
Sub-account id to credit.
Yes
txParams
TxParams
Optional compute-unit/priority-fee overrides for the transaction.
No
Returns
Promise<string>

If the owner has not called updateUserAllowDelegateTransfer(true), the delegate’s transferDepositByDelegate call is rejected on-chain regardless of which subaccount the delegate is authorized on.

Update Margin Settings

Margin trading settings control how your account can use leverage and borrow against collateral. You can enable/disable margin trading or set custom margin ratios to control risk.

Learn more: Margin

Enable or disable margin trading permissions for one or more subaccounts.

await velocityClient.updateUserMarginTradingEnabled([ { marginTradingEnabled: true, subAccountId: 0 }, ]);
Method VelocityClient.updateUserMarginTradingEnabledReference ↗
ParameterTypeRequired
updates
{ marginTradingEnabled: boolean; subAccountId: number; }[]
List of `{ marginTradingEnabled, subAccountId }` updates to apply.
Yes
Returns
Promise<string>

Set a custom margin ratio cap for one or more subaccounts.

// marginRatio is a number scaled by 10000 (MARGIN_PRECISION) // e.g. 10000 = 1x max leverage, 5000 = 2x, 2000 = 5x await velocityClient.updateUserCustomMarginRatio([ { marginRatio: 5000, subAccountId: 0 }, // 2x max leverage ]);
Method VelocityClient.updateUserCustomMarginRatioReference ↗
ParameterTypeRequired
updates
{ marginRatio: number; subAccountId: number; }[]
List of `{ marginRatio, subAccountId }` updates to apply. `marginRatio` is in `MARGIN_PRECISION` (1e4 = 100%), e.g. `2000` = 20% margin ratio = 5x max leverage.
Yes
txParams
TxParams
Optional compute-unit/priority-fee overrides for the transaction.
No
Returns
Promise<string>

Delete a User Account

If a subaccount has no assets/liabilities, it can be deleted to reclaim rent.

Delete an empty subaccount and close its user account.

await velocityClient.deleteUser(1);
Method VelocityClient.deleteUserReference ↗
ParameterTypeRequired
subAccountId
number
Sub-account id to delete; defaults to `0`.
No
txParams
TxParams
Optional compute-unit/priority-fee overrides for the transaction.
No
Returns
Promise<string>
Last updated on