Swaps (Jupiter / Titan)
A spot swap can route through Jupiter or Titan directly from a Velocity account. The SDK fetches a quote from the chosen provider, builds the swap transaction, and wraps it in begin_swap and end_swap so the input and output tokens move through the account's Velocity spot balances inside one atomically-checked transaction.
This is a collateral swap between two spot balances inside a Velocity account, unrelated to spot order placement. Velocity removed spot DLOB trading entirely, so place_spot_order and its siblings no longer exist, but swapping the tokens backing those spot balances through an aggregator is a separate path that remains supported.
Choosing a swap client
UnifiedSwapClient is the interface to build against. It wraps either Jupiter or Titan behind one API, so switching providers is a constructor change rather than a call-site change.
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)
});The standalone JupiterClient remains available. It and TitanClient implement the same SwapProvider interface as UnifiedSwapClient, so a raw JupiterClient still works wherever swapClient is accepted. That path exists for backward compatibility; new integrations should use 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 });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",
});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 the requested amount spent as the input, which inverts the trade. The SDK throws instead of passing those through. Any of the three requires a client constructed 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 the route before committing to it. UnifiedSwapClient.getQuote() normalizes the request across both providers. Titan requires userPublicKey, and so does Jupiter's v2 API; Jupiter v1, the default, ignores it.
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);Executing the swap
VelocityClient.swap() takes a swapClient, resolves the route (using a quote passed in, or fetching one), and sends the transaction so the tokens move in and out of the account's 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);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.