Skip to Content

Sending Actions

This page covers every way to send transactions in your app — from high-level VelocityClient methods, to the stateless VelocityCore instruction builders for full control without a subscribed client.

VelocityClient methods

Once subscribed (see setup), VelocityClient exposes high-level methods for the operations app builders use most. Each method handles transaction construction, compute budget, and sending.

Place a perp order

import { PositionDirection, OrderType, getMarketOrderParams, getLimitOrderParams } from "@velocity-exchange/sdk"; await velocityClient.placePerpOrder( getMarketOrderParams({ marketIndex: 0, direction: PositionDirection.LONG, baseAssetAmount: velocityClient.convertToPerpPrecision(1), // 1 SOL }) ); // Or a limit order: await velocityClient.placePerpOrder( getLimitOrderParams({ marketIndex: 0, direction: PositionDirection.LONG, baseAssetAmount: velocityClient.convertToPerpPrecision(1), price: velocityClient.convertToPricePrecision(120), }) );

Cancel orders

// null args = cancel all orders (any market, any direction) await velocityClient.cancelOrders(null, null, null);

Settle PnL

const user = velocityClient.getUser(); await velocityClient.settlePNL(user.userAccountPublicKey, user.getUserAccount(), 0);

Deposit and withdraw

await velocityClient.deposit( depositAmount, // BN, spot market's mint precision (e.g. 1e6 for USDC/dUSDT) 0, // spotMarketIndex userTokenAccount ); await velocityClient.withdraw( withdrawAmount, 0, userTokenAccount );

VelocityCore: stateless instruction builders

For advanced use cases — indexers, keeper bots without a full subscribed client, or any context where you only need to construct instructions — VelocityCore provides pure instruction builders that take explicit account pubkeys instead of reading from a subscription cache.

ApproachUse when
VelocityClient methods (above)You already have a subscribed client — simplest path for most apps
VelocityCore.build*Instruction (below)You don’t want a full subscription, need multiple instructions in one transaction, or are building a lightweight/serverless integration

Get an Anchor Program<Velocity>

VelocityCore builders take an Anchor program: VelocityProgram argument rather than reading it from a client instance:

import { VelocityCore } from "@velocity-exchange/sdk"; import { Program, AnchorProvider } from "@coral-xyz/anchor"; const provider = new AnchorProvider(connection, wallet, {}); const program = new Program(VelocityCore.defaultIdl(), provider);

Derive the accounts you need

const state = await VelocityCore.pdas.getVelocityStateAccountPublicKey(program.programId); const userPda = VelocityCore.pdas.getUserAccountPublicKeySync(program.programId, authority, 0); const userStatsPda = VelocityCore.pdas.getUserStatsAccountPublicKey(program.programId, authority); const userAccount = await VelocityCore.fetchUserAccount(connection, userPda); // getRemainingAccounts(ctx, params): ctx supplies resolver hooks + caller-owned caches, // params describes which markets/positions this call needs represented. const remainingAccounts = VelocityCore.remainingAccounts.getRemainingAccounts( { getPerpMarketAccount: (marketIndex) => perpMarketAccountsByIndex[marketIndex], getSpotMarketAccount: (marketIndex) => spotMarketAccountsByIndex[marketIndex], getUserAccountAndSlot: () => undefined, // no subscription cache in the stateless path activeSubAccountId: 0, authority, perpMarketLastSlotCache: new Map(), spotMarketLastSlotCache: new Map(), mustIncludePerpMarketIndexes: new Set(), mustIncludeSpotMarketIndexes: new Set(), }, { userAccounts: userAccount ? [userAccount] : [], writablePerpMarketIndexes: [0], } );

getRemainingAccounts takes two arguments — a RemainingAccountsContext of resolver hooks/caches you own, and a RemainingAccountParams describing the markets/positions for this call — not a single combined object. It derives the same oracle/market account set VelocityClient’s internal builder does, without needing a live subscription.

Build a deposit instruction

import { VelocityCore } from "@velocity-exchange/sdk"; const depositIx = await VelocityCore.buildDepositInstruction({ program, marketIndex: 0, amount: depositAmountBN, reduceOnly: false, state, spotMarket, spotMarketVault, user: userPda, userStats: userStatsPda, userTokenAccount, authority: wallet.publicKey, tokenProgram: TOKEN_PROGRAM_ID, remainingAccounts, });
Method VelocityCore.buildDepositInstructionReference ↗
TypeScript docs unavailable for buildDepositInstruction.

Build a place-perp-order instruction

import { VelocityCore } from "@velocity-exchange/sdk"; import { getMarketOrderParams, PositionDirection } from "@velocity-exchange/sdk"; const orderParams = getMarketOrderParams({ marketIndex: 0, direction: PositionDirection.LONG, baseAssetAmount: new BN(1_000_000_000), // 1 SOL, BASE_PRECISION (1e9) }); const placeOrderIx = await VelocityCore.buildPlacePerpOrderInstruction({ program, orderParams, state, user: userPda, userStats: userStatsPda, authority: wallet.publicKey, remainingAccounts, });
Method VelocityCore.buildPlacePerpOrderInstructionReference ↗
TypeScript docs unavailable for buildPlacePerpOrderInstruction.

Compose and send the transaction

VelocityCore builders return raw TransactionInstructions — bring your own transaction sender (Anchor’s sendAndConfirm, @solana/web3.js directly, or a VelocityClient.txSender you construct separately):

import { ComputeBudgetProgram, VersionedTransaction, TransactionMessage } from "@solana/web3.js"; const { blockhash } = await connection.getLatestBlockhash(); const message = new TransactionMessage({ payerKey: wallet.publicKey, recentBlockhash: blockhash, instructions: [ ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }), ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50_000 }), placeOrderIx, ], }).compileToV0Message(); const tx = new VersionedTransaction(message); tx.sign([wallet.payer]); const signature = await connection.sendTransaction(tx); console.log("Transaction:", signature);

Atomic cancel-and-place (order replacement) via VelocityClient

A common pattern for trading UIs is atomically canceling existing orders and placing new ones in a single transaction, avoiding the window where you have no resting orders while updating prices. With a subscribed client, build the individual instructions and compose them yourself:

import { ComputeBudgetProgram } from "@solana/web3.js"; import { getLimitOrderParams, PositionDirection } from "@velocity-exchange/sdk"; const cancelIx = await velocityClient.getCancelOrdersIx( null, // marketType (null = all) null, // marketIndex (null = all) null // direction (null = both) ); const bidParams = getLimitOrderParams({ marketIndex: 0, direction: PositionDirection.LONG, baseAssetAmount: velocityClient.convertToPerpPrecision(1), price: velocityClient.convertToPricePrecision(120), postOnly: true, }); const askParams = getLimitOrderParams({ marketIndex: 0, direction: PositionDirection.SHORT, baseAssetAmount: velocityClient.convertToPerpPrecision(1), price: velocityClient.convertToPricePrecision(125), postOnly: true, }); const bidIx = await velocityClient.getPlacePerpOrderIx(bidParams); const askIx = await velocityClient.getPlacePerpOrderIx(askParams); const tx = await velocityClient.txSender.getVersionedTransaction( [ ComputeBudgetProgram.setComputeUnitLimit({ units: 600000 }), ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50000 }), cancelIx, bidIx, askIx, ], [], velocityClient.wallet.publicKey ); const signature = await velocityClient.txSender.sendVersionedTransaction( tx, [], velocityClient.opts ); console.log("Cancel + place transaction:", signature);
Method VelocityClient.getCancelOrdersIxReference ↗
ParameterTypeRequired
marketType
MarketType
Only cancel orders of this market type.
Yes
marketIndex
number
Only cancel orders in this market index.
Yes
direction
PositionDirection
Only cancel orders on this side.
Yes
subAccountId
number
Sub-account to cancel orders for; defaults to the active sub-account.
No
Returns
Promise<TransactionInstruction>

Transaction performance

If you’re batching or sending frequent transactions, consider:

  • Priority fees — Set ComputeUnitPrice to land transactions faster during congestion
  • Compute budget — Use setComputeUnitLimit to allocate enough CU for multi-instruction transactions
  • Address Lookup Tables (ALTs) — Reduce transaction size when referencing many accounts
  • Retry logic — Transactions can be dropped; implement retries with backoff for production systems
  • Bulk order margin enforcement: placeOrders/placeScaleOrders now run the initial-margin check once per risk scope touched by the batch (cross-margin, plus each isolated market with a risk-increasing order) rather than a single end-of-batch check — a batch that previously succeeded may now revert with InsufficientCollateral
Last updated on