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.
| Approach | Use 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 ↗
Method VelocityCore.buildDepositInstructionReference ↗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 ↗
Method VelocityCore.buildPlacePerpOrderInstructionReference ↗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 ↗
Method VelocityClient.getCancelOrdersIxReference ↗| Parameter | Type | Required |
|---|---|---|
marketType | MarketTypeOnly cancel orders of this market type. | Yes |
marketIndex | numberOnly cancel orders in this market index. | Yes |
direction | PositionDirectionOnly cancel orders on this side. | Yes |
subAccountId | numberSub-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
ComputeUnitPriceto land transactions faster during congestion - Compute budget — Use
setComputeUnitLimitto 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/placeScaleOrdersnow 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 withInsufficientCollateral