Velocity ProtocolDevelopers
Velocity SDK

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'
EndpointsGET /swap/v1/quote, then POST /swap/v1/swapGET /swap/v2/build only
Round trips per swapTwo, plus deserializing the returned transactionOne, the quote carries the route instructions
userPublicKey on getQuoteOptionalRequired, v2 builds for a named taker
autoSlippageSupportedRejected, getQuote throws
swapMode'ExactIn' or 'ExactOut''ExactIn' only, anything else throws
onlyDirectRoutesSupportedRejected, 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:

ParameterDescriptionOptionalDefault
swapClientUnifiedSwapClient (preferred), or a raw JupiterClient/TitanClient for the deprecated legacy pathNo
inMarketIndexVelocity spot market index for the input tokenNo
outMarketIndexVelocity spot market index for the output tokenNo
amountAmount to swap as a BN, spot market precisionNo
slippageBpsMaximum allowed slippage in basis pointsYes50
swapMode'ExactIn' or 'ExactOut'Yes'ExactIn'
onlyDirectRoutesIf true, restricts to direct token pairs only (no multi-hop)Yesfalse
reduceOnlySwapReduceOnly: constrain the in/out token balance to reduce-only at swap endYes
quotePre-fetched quote (from getQuote()) to skip an extra round-tripYes
txParamsCompute-unit/priority-fee overridesYes

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.