Swaps (Jupiter / Titan)
You can route spot swaps through Jupiter or Titan directly from your Velocity account. The SDK fetches a quote from the chosen provider, builds the swap transaction, and wraps it with begin_swap/end_swap so the input and output tokens flow through your Velocity spot balances in a single, atomically-checked transaction.
This is a collateral swap between two spot balances inside your Velocity account: unrelated to spot order placement. Velocity removed spot DLOB trading entirely (place_spot_order and friends no longer exist), but swapping the tokens backing your spot balances via an aggregator is a separate, still-supported code path.
Choosing a swap client
UnifiedSwapClient is the primary interface for swaps: it wraps either Jupiter or Titan behind one API, so you can switch providers without changing call sites:
import { Connection } from "@solana/web3.js";
import { UnifiedSwapClient } from "@velocity-exchange/sdk";
const connection = new Connection("<RPC_URL>", "confirmed");
// clientType: 'jupiter' | 'titan'
const swapClient = new UnifiedSwapClient({
clientType: "jupiter",
connection,
authToken: "<JUPITER_API_KEY_OR_TITAN_AUTH_TOKEN>", // Jupiter: API key (portal.jup.ag). Titan: auth token (not needed behind a proxy)
});Class UnifiedSwapClientReference ↗
Class UnifiedSwapClientReference ↗| Property | Type | Required |
|---|---|---|
client | any | Yes |
clientType | any | Yes |
getQuote | (params: SwapQuoteParams) => Promise<UnifiedQuoteResponse>Get a swap quote from the underlying client | Yes |
getSwap | (params: SwapTransactionParams) => Promise<SwapTransactionResult>Get a swap transaction from the underlying client | Yes |
getSwapInstructions | ({ inputMint, outputMint, amount, userPublicKey, slippageBps, swapMode, onlyDirectRoutes, quote, sizeConstraint, }: { inputMint: PublicKey; outputMint: PublicKey; amount: BN; userPublicKey: PublicKey; slippageBps?: number; swapMode?: SwapMode; onlyDirectRoutes?: boolean; quote?: UnifiedQuoteResponse; sizeConstraint?...Get swap instructions from the underlying client (Jupiter or Titan)
This is the core swap logic without any context preparation | Yes |
getClient | () => JupiterClient | TitanClientGet the underlying client instance | Yes |
getClientType | () => SwapClientTypeGet the client type | Yes |
isJupiter | () => booleanCheck if this is a Jupiter client | Yes |
isTitan | () => booleanCheck if this is a Titan client | Yes |
The standalone JupiterClient remains available. Both JupiterClient and TitanClient implement the same SwapProvider interface as UnifiedSwapClient, so a raw JupiterClient can be passed as swapClient for backward compatibility, but new integrations should prefer UnifiedSwapClient:
import { Connection } from "@solana/web3.js";
import { JupiterClient } from "@velocity-exchange/sdk";
const connection = new Connection("<RPC_URL>", "confirmed");
const jupiterClient = new JupiterClient({ connection });Class JupiterClientReference ↗
Class JupiterClientReference ↗| Property | Type | Required |
|---|---|---|
url | string | Yes |
connection | Connection | Yes |
lookupTableCahce | Map<string, AddressLookupTableAccount> | Yes |
apiKey | any | No |
getHeaders | anyGet the headers for API requests, including API key if configured | Yes |
getQuote | ({ inputMint, outputMint, amount, maxAccounts, slippageBps, swapMode, onlyDirectRoutes, excludeDexes, autoSlippage, maxAutoSlippageBps, usdEstimate, }: { inputMint: PublicKey; outputMint: PublicKey; amount: BN; maxAccounts?: number; slippageBps?: number; swapMode?: SwapMode; onlyDirectRoutes?: boolean; excludeDexes?...Get routes for a swap | Yes |
getSwap | ({ quote, userPublicKey, slippageBps, }: { quote: QuoteResponse; userPublicKey: PublicKey; slippageBps?: number; }) => Promise<VersionedTransaction>Get a swap transaction for quote | Yes |
getTransactionMessageAndLookupTables | ({ transaction, }: { transaction: VersionedTransaction; }) => Promise<{ transactionMessage: TransactionMessage; lookupTables: AddressLookupTableAccount[]; }>Get the transaction message and lookup tables for a transaction | Yes |
getLookupTable | (accountKey: PublicKey) => Promise<AddressLookupTableAccount | undefined> | Yes |
getJupiterInstructions | ({ transactionMessage, inputMint, outputMint, }: { transactionMessage: TransactionMessage; inputMint: PublicKey; outputMint: PublicKey; }) => TransactionInstruction[]Get the jupiter instructions from transaction by filtering out instructions to compute budget and associated token programs | Yes |
Jupiter Swap API v1 versus v2
JupiterClient takes an apiVersion of 'v1' or 'v2'. It defaults to 'v1'. UnifiedSwapClient forwards the same choice as jupiterApiVersion (ignored when clientType is 'titan').
'v1' (default) | 'v2' | |
|---|---|---|
| Endpoints | GET /swap/v1/quote, then POST /swap/v1/swap | GET /swap/v2/build only |
| Round trips per swap | Two, plus deserializing the returned transaction | One, the quote carries the route instructions |
userPublicKey on getQuote | Optional | Required, v2 builds for a named taker |
autoSlippage | Supported | Rejected, getQuote throws |
swapMode | 'ExactIn' or 'ExactOut' | 'ExactIn' only, anything else throws |
onlyDirectRoutes | Supported | Rejected, getQuote throws |
import { JupiterClient, UnifiedSwapClient } from "@velocity-exchange/sdk";
// Opt into v2 on the standalone client
const jupiterV2 = new JupiterClient({
connection,
apiKey: "<JUPITER_API_KEY>",
apiVersion: "v2",
});
// Or through the unified client
const swapClient = new UnifiedSwapClient({
clientType: "jupiter",
connection,
authToken: "<JUPITER_API_KEY>",
jupiterApiVersion: "v2",
});Example apiVersionReference ↗
Example apiVersionReference ↗apiVersion.The v2 rejections are deliberate. Jupiter’s /swap/v2/build answers 200 for autoSlippage, onlyDirectRoutes, and ExactOut while ignoring them: auto-slippage comes back with zero slippage tolerance (any adverse move reverts the swap), direct-only still returns multi-hop routes, and ExactOut comes back as ExactIn with your amount spent as the input, which inverts the trade. The SDK throws instead of passing those through. If you need any of the three, construct the client with apiVersion: 'v1'.
The Rust SDK (velocity-rs) is v2 only. Its Jupiter path has no v1 mode and no version toggle: jupiter_swap_query has no swap_mode, transaction_config, or only_direct_routes parameters, and it takes max_accounts: Option<usize> (defaulting to 50) as the remaining routing lever. Rust consumers get the v2 behavior described above whether or not they opt in.
Getting a quote
Preview the expected output and route before committing. UnifiedSwapClient.getQuote() normalizes the request across both providers (Titan additionally requires userPublicKey):
const quote = await swapClient.getQuote({
inputMint: usdtMint, // PublicKey
outputMint: solMint, // PublicKey
amount: velocityClient.convertToSpotPrecision(0, 10), // 10 USDT, BN
slippageBps: 50,
userPublicKey: velocityClient.wallet.publicKey, // required for Titan, and for Jupiter's v2 API; ignored by Jupiter v1 (the default)
});
console.log(quote);Method UnifiedSwapClient.getQuoteReference ↗
Method UnifiedSwapClient.getQuoteReference ↗| Parameter | Type | Required |
|---|---|---|
params | SwapQuoteParams | Yes |
| Returns |
|---|
Promise<UnifiedQuoteResponse> |
Executing the swap
VelocityClient.swap() accepts a swapClient (a UnifiedSwapClient, or a raw JupiterClient/TitanClient for the deprecated legacy path), fetches/uses the route, and sends the transaction so tokens move in and out of your Velocity spot balances:
// Assumes `velocityClient` is subscribed.
const txSig = await velocityClient.swap({
swapClient,
inMarketIndex: 0, // e.g. USDT (spot market index)
outMarketIndex: 1, // e.g. SOL (spot market index)
amount: velocityClient.convertToSpotPrecision(0, 10), // 10 USDT
slippageBps: 50, // 0.5% max slippage
onlyDirectRoutes: false, // allow multi-hop routes for better pricing
});
console.log(txSig);Method VelocityClient.swapReference ↗
Method VelocityClient.swapReference ↗| Parameter | Type | Required |
|---|---|---|
__0 | { swapClient?: UnifiedSwapClient | SwapClient; jupiterClient?: JupiterClient; outMarketIndex: number; inMarketIndex: number; outAssociatedTokenAccount?: PublicKey; ... 8 more ...; quote?: UnifiedQuoteResponse; }Swap client used to fetch routes/instructions (`UnifiedSwapClient` or a
`TitanClient`); dispatches to `getSwapIxV2` or `getTitanSwapIx` respectively. | Yes |
| Returns |
|---|
Promise<string> |
Parameters:
| Parameter | Description | Optional | Default |
|---|---|---|---|
swapClient | UnifiedSwapClient (preferred), or a raw JupiterClient/TitanClient for the deprecated legacy path | No | |
inMarketIndex | Velocity spot market index for the input token | No | |
outMarketIndex | Velocity spot market index for the output token | No | |
amount | Amount to swap as a BN, spot market precision | No | |
slippageBps | Maximum allowed slippage in basis points | Yes | 50 |
swapMode | 'ExactIn' or 'ExactOut' | Yes | 'ExactIn' |
onlyDirectRoutes | If true, restricts to direct token pairs only (no multi-hop) | Yes | false |
reduceOnly | SwapReduceOnly: constrain the in/out token balance to reduce-only at swap end | Yes | |
quote | Pre-fetched quote (from getQuote()) to skip an extra round-trip | Yes | |
txParams | Compute-unit/priority-fee overrides | Yes |
TitanClient itself is an internal implementation detail and is not exported from the SDK root: construct Titan-backed swaps through new UnifiedSwapClient({ clientType: "titan", ... }) rather than importing TitanClient directly.