# Orders

> Canonical: https://docs.velocity.exchange/developers/velocity-sdk/orders

## How it works

A taker order fills by price, not by source. It walks the **DLOB** (Decentralized Limit Order Book) from the best price level down, and at each level the AMM's quote, the resting order, and any **JIT** (Just-In-Time) maker quote compete on price. One order can therefore fill from more than one source.

A market order's auction bounds what price is acceptable while it runs, and the AMM comes last: it takes only the residual fill, once the maker list is exhausted and size still crosses the order's limit price. See [Orderbook & Matching](/developers/market-makers/orderbook-and-matching.md) for the full rules.

Orders are stored in the user account and have both an onchain order ID and an optional user-assigned ID for tracking. When an order fills, the position updates automatically, and fill events carry the execution details.

**Perp markets only.** Velocity's spot markets are collateral and borrow-lend only: order-book trading on spot markets was removed. `placeSpotOrder`, `placeAndTakeSpotOrder`, `placeAndMakeSpotOrder`, `fillSpotOrder`, and their `getXIx` builders all throw a client-side error without sending a transaction ("Spot DLOB trading is disabled; spot balances, deposits, and swaps remain available.").

The onchain `SpotDlobTradingDisabled` error (`6350` / `0x18ce`) is a separate thing. The spot order instructions are gone from the program entirely, so what raises it is a shared order instruction handed a `MarketType::Spot`. Everything on this page applies to perp orders.

## Order Types

| Type | Description |
|---|---|
| `MARKET` | Executes immediately. Opens a JIT auction (controlled by `auctionStartPrice`, `auctionEndPrice`, `auctionDuration`); JIT makers, resting DLOB orders, and the AMM compete on price at each level, and any size left after the auction can still fill against the DLOB or AMM up to the order's limit. |
| `LIMIT` | Rests on the DLOB at a fixed price until filled or canceled. Set `postOnly: PostOnlyParams.MUST_POST_ONLY` to guarantee maker status and avoid crossing the spread. |
| `ORACLE` | Like a market order, but auction prices and the resting limit price are expressed as **offsets from the oracle price** (not absolute prices). Useful for market makers who want tight spreads without hardcoding prices. |
| `TRIGGER_MARKET` | A stop/take-profit market order. Executes as a market order when the market's trigger price crosses `triggerPrice` in the specified `triggerCondition` direction. That price is not always the oracle price: see [Trigger Orders](#trigger-orders-stop--take-profit). |
| `TRIGGER_LIMIT` | A stop/take-profit limit order. Same trigger mechanism as `TRIGGER_MARKET`, but executes as a limit order at `price` once triggered. |

### Post-Only Params

When placing limit orders, `postOnly` controls maker and taker behavior:

| Value | Behavior |
|---|---|
| `PostOnlyParams.NONE` | Order can be maker or taker (default) |
| `PostOnlyParams.MUST_POST_ONLY` | Transaction fails if the order would cross the spread |
| `PostOnlyParams.TRY_POST_ONLY` | Order is silently skipped (not placed) if it would cross; tx succeeds |
| `PostOnlyParams.SLIDE` | Order price is adjusted one tick inside the spread to guarantee maker status |

### `oraclePriceOffset` is a `BN`

`OrderParams.oraclePriceOffset` (and `Order.oraclePriceOffset` on filled orders) is a signed **`BN`** in `PRICE_PRECISION` (1e6) units: it was widened from `number` to `BN` (i64 onchain). Always wrap raw numbers with `new BN(...)`; passing a plain JS number will fail type-checking (and, if bypassed, will not round-trip correctly through Borsh encoding).

```js
import { BN, PRICE_PRECISION } from "@velocity-exchange/sdk";

// +$0.30 above oracle
const oraclePriceOffset = new BN(0.3 * PRICE_PRECISION.toNumber());
// or, using client precision helpers:
const oraclePriceOffset2 = velocityClient.convertToPricePrecision(0.3); // already a BN
```

## SDK Usage

This page focuses on placing and canceling perp orders via `VelocityClient`. The examples use helper builders like `getMarketOrderParams(...)` to stay concise.

### Build Market Order Params

  
```js
import { MarketType, PositionDirection, getMarketOrderParams } from "@velocity-exchange/sdk";

const orderParams = getMarketOrderParams({
  marketIndex: 0,
  marketType: MarketType.PERP,
  direction: PositionDirection.LONG,
  baseAssetAmount: velocityClient.convertToPerpPrecision(1), // 1 SOL, BN at 1e9
});
```
  

### Build Order Params (Generic Helper)

`getOrderParams(...)` is the single helper that covers limit, market, oracle, and trigger orders.

  
```js
import { OrderType, PositionDirection, getOrderParams } from "@velocity-exchange/sdk";

const orderParams = getOrderParams({
  orderType: OrderType.LIMIT,
  marketIndex: 0,
  direction: PositionDirection.LONG,
  baseAssetAmount: velocityClient.convertToPerpPrecision(1), // 1 base unit, BN(1e9)
  price: velocityClient.convertToPricePrecision(21.23),      // $21.23, BN(21_230_000)
});
```
  

### Place a Perp Order

  
```js
import { PositionDirection, getMarketOrderParams } from "@velocity-exchange/sdk";

// Assumes `velocityClient` is subscribed.
const txSig = await velocityClient.placePerpOrder(
  getMarketOrderParams({
    marketIndex: 0,
    direction: PositionDirection.LONG,
    baseAssetAmount: velocityClient.convertToPerpPrecision(1),
  })
);
console.log(txSig);
```
  

### Place Multiple Orders

  
```js
import { MarketType, OrderType, PositionDirection } from "@velocity-exchange/sdk";

await velocityClient.placeOrders([
  {
    orderType: OrderType.LIMIT,
    marketType: MarketType.PERP,
    marketIndex: 0,
    direction: PositionDirection.LONG,
    baseAssetAmount: velocityClient.convertToPerpPrecision(1),
    price: velocityClient.convertToPricePrecision(21.23),
  },
  {
    orderType: OrderType.LIMIT,
    marketType: MarketType.PERP,
    marketIndex: 0,
    direction: PositionDirection.SHORT,
    baseAssetAmount: velocityClient.convertToPerpPrecision(1),
    oraclePriceOffset: velocityClient.convertToPricePrecision(0.05), // BN, not .toNumber()
  },
]);
```
  

> **Warning:**
>
> **Bulk margin enforcement.** `placeOrders` and `placeScaleOrders` run the initial-margin check once per risk scope the batch touches, not once at the end of the batch. A batch that slipped past a weaker or absent margin gate under the old check may now be rejected with `InsufficientCollateral` (error `6003` / `0x1773`).

### Scale Orders (Price Ladder)

`placeScaleOrders` places a ladder of resting limit orders in one instruction. It takes a total size and a price range; the program splits the size across `orderCount` orders at evenly spaced prices and places them all. Every order in the ladder is `OrderType.LIMIT` and rests until matched, nothing fills inside the instruction.

| Field | Type | Meaning |
|---|---|---|
| `marketType` | `MarketType` | Use `MarketType.PERP`. A `SPOT` ladder is rejected onchain with `SpotDlobTradingDisabled`. |
| `direction` | `PositionDirection` | Side of every order in the ladder. |
| `marketIndex` | `number` | Perp market index. |
| `totalBaseAssetAmount` | `BN` | Total size to distribute, in `BASE_PRECISION` (1e9). |
| `startPrice` | `BN` | First rung, in `PRICE_PRECISION` (1e6). |
| `endPrice` | `BN` | Last rung, in `PRICE_PRECISION` (1e6). |
| `orderCount` | `number` | Number of orders, minimum 2, maximum 32. |
| `sizeDistribution` | `SizeDistribution` | `FLAT`, `ASCENDING`, or `DESCENDING` (see below). |
| `reduceOnly` | `boolean` | Applied to every order in the ladder. |
| `postOnly` | `PostOnlyParams` | Applied to every order in the ladder. |
| `bitFlags` | `number` | Applied to the **first** order only; the rest are placed with `bitFlags: 0`. |
| `maxTs` | `BN \| null` | Expiry timestamp applied to every order, or `null` for none. |

Every field is required, there are no optional defaults.

**Price range rules.** `startPrice` and `endPrice` must differ, and the range has to run away from the market in the ladder's direction:

- `PositionDirection.LONG` requires `startPrice > endPrice` (buy the ladder down).
- `PositionDirection.SHORT` requires `startPrice < endPrice` (sell the ladder up).

Prices are spaced evenly: with `orderCount` = 2 the rungs are exactly `startPrice` and `endPrice`; with more, the step is `|startPrice - endPrice| / (orderCount - 1)` and the last rung is set to `endPrice` exactly so rounding cannot drift.

**Size distribution.** `ASCENDING` and `DESCENDING` are defined relative to the **start price**:

| Value | Sizes |
|---|---|
| `SizeDistribution.FLAT` | Equal size per order (rounded down to the market's `orderStepSize`), with the leftover added to the last order. |
| `SizeDistribution.ASCENDING` | Smallest order at `startPrice`, largest at `endPrice`. Sizes follow 1x, 1.5x, 2x, 2.5x, and so on. |
| `SizeDistribution.DESCENDING` | The same ladder reversed: largest order at `startPrice`, smallest at `endPrice`. |

`totalBaseAssetAmount` must be at least `orderStepSize * orderCount`, otherwise there is not enough size to give every rung a valid order. Each rung is rounded to the market's `orderStepSize` and the last one absorbs the rounding difference.

**Errors.** The program validates every parameter before it expands the ladder, and returns the specific error code, not a generic one: `InvalidOrderScaleOrderCount` (`6346` / `0x18ca`) for an `orderCount` outside 2 to 32, `InvalidOrderScalePriceRange` (`6347` / `0x18cb`) for equal prices or a range running the wrong way for the direction, and `OrderAmountTooSmall` (`6059` / `0x17ab`) when `totalBaseAssetAmount` is below `orderStepSize * orderCount`. Each carries the offending value in the transaction logs. The 32-order ceiling is also the account's total open-order capacity, so a ladder only fits if the subaccount has that many free order slots.

  
```js
import {
  MarketType,
  PositionDirection,
  PostOnlyParams,
  SizeDistribution,
} from "@velocity-exchange/sdk";

// 10 SOL of bids laddered from $150 down to $140, in 5 orders,
// with the largest order at the top of the range.
await velocityClient.placeScaleOrders({
  marketType: MarketType.PERP,
  direction: PositionDirection.LONG,
  marketIndex: 0,
  totalBaseAssetAmount: velocityClient.convertToPerpPrecision(10),
  startPrice: velocityClient.convertToPricePrecision(150),
  endPrice: velocityClient.convertToPricePrecision(140),
  orderCount: 5,
  sizeDistribution: SizeDistribution.DESCENDING,
  reduceOnly: false,
  postOnly: PostOnlyParams.MUST_POST_ONLY,
  bitFlags: 0,
  maxTs: null,
});
```
  

`preparePlaceScaleOrdersTx(params, txParams?, subAccountId?)` returns the unsigned transaction as `{ placeScaleOrdersTx }`, and `getPlaceScaleOrdersIx(params, subAccountId?)` returns the single instruction for batching it with others.

For the trader-facing description of scale orders, see [Order Types](/protocol/trading/order-types.md#scale-orders).

### Oracle / Auction-Style Orders

Oracle orders have prices that track the oracle feed with an offset. They go through a JIT auction before execution, with auction prices that gradually converge from the start offset to the end offset relative to oracle.

**Important:** For `OrderType.ORACLE`, `auctionStartPrice`, `auctionEndPrice`, and `oraclePriceOffset` are all **offsets** from the oracle price (in PRICE_PRECISION, 1e6), not absolute prices, and all three are `BN`.

  
```js
import { OrderType, PRICE_PRECISION, PositionDirection } from "@velocity-exchange/sdk";

const marketIndex = 18;

// Offsets are relative to oracle price (in PRICE_PRECISION)
// For a long: auction starts at a better (lower) price and ends at a worse (higher) price
const auctionStartPrice = PRICE_PRECISION.muln(-5).divn(10); // -$0.50 below oracle
const auctionEndPrice = PRICE_PRECISION.muln(5).divn(10);    // +$0.50 above oracle

const orderParams = {
  orderType: OrderType.ORACLE,
  baseAssetAmount: velocityClient.convertToPerpPrecision(10),
  direction: PositionDirection.LONG,
  marketIndex,
  auctionStartPrice,
  auctionEndPrice,
  oraclePriceOffset: velocityClient.convertToPricePrecision(0.30), // +$0.30 from oracle, BN
  auctionDuration: 30, // 400ms wall-clock units, 12s
};

await velocityClient.placePerpOrder(orderParams);
```
  

### Builder Codes

`OrderParams` accepts an optional `builderIdx` and `builderFeeTenthBps` pair to attribute an order to an approved builder and charge an extra builder fee. The fee is in tenths of a basis point, so `100` is 10 bps, or 0.1% of notional. Both fields are optional; omit them for ordinary orders.

`builderIdx` indexes into the placing user's `RevenueShareEscrow.approvedBuilders` list, so the builder must be approved first with `changeApprovedBuilder(...)`. Whatever value is passed, the program caps the fee actually charged at `MAX_BUILDER_FEE_TENTH_BPS`, which is 1000 tenth-bps, or 1% of notional. That is an onchain Rust constant rather than an SDK export, so treat it as a fixed value instead of something to import.

  
```js
import { OrderType, PositionDirection } from "@velocity-exchange/sdk";

const orderParams = {
  orderType: OrderType.LIMIT,
  marketIndex: 0,
  direction: PositionDirection.LONG,
  baseAssetAmount: velocityClient.convertToPerpPrecision(1),
  price: velocityClient.convertToPricePrecision(21.23),
  builderIdx: 0,           // index into this user's approved-builders list
  builderFeeTenthBps: 100, // 10 bps, 0.1% of notional
};

await velocityClient.placePerpOrder(orderParams);
```
  

If the taker is referred (their `UserStats.referrerStatus` has the `BuilderReferral` bit) or the order itself carries a builder code, fillers must attach the taker's `RevenueShareEscrow` when filling (see `getFillPerpOrderIx` below) or the fill is rejected with `UnableToLoadRevenueShareAccount`.

### Cancel Orders

Cancel a specific order by its onchain order ID.

  
```js
await velocityClient.cancelOrder(1);
```
  

Cancel multiple specific orders by their onchain order IDs in a single transaction.

  
```js
await velocityClient.cancelOrdersByIds([1, 2, 3]);
```
  

Cancel all orders matching the given market and direction filters. Omit a filter to match all on that field; omit all parameters to cancel every open order.

  
```js
import { MarketType, PositionDirection } from "@velocity-exchange/sdk";

// Cancel all long perp orders on market 0
await velocityClient.cancelOrders(MarketType.PERP, 0, PositionDirection.LONG);

// Cancel all orders across all markets
await velocityClient.cancelOrders();
```
  

`getCancelOrdersIx` (the instruction-builder variant, used when composing a transaction by hand) accepts `null` explicitly for any filter, in addition to `undefined`.

### Cancel and Place (Atomic)

Atomically cancels existing orders and places new ones in a single transaction. This is the preferred approach for market makers who need to replace quotes without risk of being filled on stale orders in the gap between a separate cancel and re-quote.

  
```js
import { MarketType, OrderType, PositionDirection } from "@velocity-exchange/sdk";

await velocityClient.cancelAndPlaceOrders(
  { marketType: MarketType.PERP, marketIndex: 0 },
  [
    {
      orderType: OrderType.LIMIT,
      marketType: MarketType.PERP,
      marketIndex: 0,
      direction: PositionDirection.LONG,
      baseAssetAmount: velocityClient.convertToPerpPrecision(1),
      price: velocityClient.convertToPricePrecision(21.23),
    },
  ]
);
```
  

> **Warning:**
>
> `marketType` is required on every order object passed to `cancelAndPlaceOrders`. It routes through the generic order-placement path, which throws `must set param.marketType` when the field is missing. `placePerpOrder` fills it in; this one does not.

### Modify Orders

  
```js
await velocityClient.modifyOrder({
  orderId: 1,
  newBaseAmount: velocityClient.convertToPerpPrecision(2),
});
```
  

  
```js
await velocityClient.modifyOrderByUserOrderId({
  userOrderId: 1,
  newBaseAmount: velocityClient.convertToPerpPrecision(2),
});
```
  

### Trigger Orders (Stop / Take-Profit)

  
```js
import { OrderTriggerCondition, OrderType, PositionDirection } from "@velocity-exchange/sdk";

const orderParams = {
  orderType: OrderType.TRIGGER_MARKET,
  marketIndex: 0,
  direction: PositionDirection.SHORT,
  baseAssetAmount: velocityClient.convertToPerpPrecision(1),
  triggerPrice: velocityClient.convertToPricePrecision(95),
  triggerCondition: OrderTriggerCondition.BELOW,
};

await velocityClient.placePerpOrder(orderParams);
```
  

#### The price a trigger is evaluated against

`triggerPrice` is not compared against the raw oracle price. `State.featureBitFlags` carries a `MEDIAN_TRIGGER_PRICE` bit; read it with `useMedianTriggerPrice(stateAccount)` and derive the price with `getTriggerPrice(market, oraclePrice, now, useMedian)` rather than comparing against the oracle directly.

With the bit clear, the trigger price is the absolute value of the oracle price. With the bit set, it is the median of three legs:

- **Last fill price**: `market.lastFillPrice`, used only while `marketStats.lastTradeTs` is within `TRIGGER_PRICE_LAST_FILL_MAX_AGE`, 5 minutes. With no fill yet, or a stale one, the oracle price stands in for this leg.
- **Funding basis**: `oracle + basis`. The basis is the last funding rate normalized by `marketStats.lastFundingOracleTwap`, scaled by 24, reduced by `FUNDING_RATE_OFFSET_PERCENTAGE`, applied to the oracle price, then decayed linearly to zero across one funding period as time passes since `lastFundingRateTs`. It is zero when `lastFundingOracleTwap` is zero.
- **5-minute basis**: `oracle + (marketStats.lastMarkPriceTwap5Min - marketStats.historicalOracleData.lastOraclePriceTwap5Min)`.

The median is then clamped to a band around the raw oracle price set by the market's contract tier: 20 bps on tiers A and B, 100 bps on tier C, 250 bps below that. The trigger price therefore never sits further than that from the oracle, whatever the three legs say.

### Instruction Builders (Advanced)

Higher-level methods like `placePerpOrder()` build, sign, and send a transaction in one call. Instruction (IX) builders return the raw `TransactionInstruction` objects, which makes it possible to:

- **Set a custom compute budget** with priority fees for faster inclusion
- **Batch multiple instructions** into a single transaction (e.g., cancel + place atomically)
- **Use Address Lookup Tables (ALTs)** to fit more accounts into a transaction
- **Compose with other programs** (e.g., add a memo or call another protocol in the same tx)

#### Complete Example: Batching IXs with Compute Budget

  
```js
import { ComputeBudgetProgram } from "@solana/web3.js";
import {
  MarketType,
  OrderType,
  PositionDirection,
  getOrderParams,
} from "@velocity-exchange/sdk";

// 1. Build individual instructions
const cancelIx = await velocityClient.getCancelOrdersIx(
  MarketType.PERP, // marketType (null to cancel all types)
  0,               // marketIndex (null to cancel across all markets)
  null             // direction (null to cancel both sides)
);

const placeIx = await velocityClient.getPlacePerpOrderIx(
  getOrderParams({
    orderType: OrderType.LIMIT,
    marketIndex: 0,
    direction: PositionDirection.LONG,
    baseAssetAmount: velocityClient.convertToPerpPrecision(1),
    price: velocityClient.convertToPricePrecision(21.0),
  })
);

// 2. Add compute budget instructions for priority fees
const computeUnitPrice = ComputeBudgetProgram.setComputeUnitPrice({
  microLamports: 50_000, // priority fee in micro-lamports per CU
});
const computeUnitLimit = ComputeBudgetProgram.setComputeUnitLimit({
  units: 400_000, // max compute units for the transaction
});

// 3. Build a versioned transaction with all instructions
const tx = await velocityClient.txSender.getVersionedTransaction(
  [computeUnitLimit, computeUnitPrice, cancelIx, placeIx],
  []                        // lookup table accounts (AddressLookupTableAccount[])
);

// 4. Send the transaction
const { txSig } = await velocityClient.txSender.sendVersionedTransaction(
  tx,
  [],
  velocityClient.opts
);
console.log("Batch tx:", txSig);
```
  

#### Individual IX Builders

**getPlacePerpOrderIx** builds an instruction to place a perp order.

  
```js
// Params:
//   orderParams: OptionalOrderParams - same params as placePerpOrder()
//   subAccountId?: number - defaults to active subaccount
const ix = await velocityClient.getPlacePerpOrderIx(
  getOrderParams({
    orderType: OrderType.LIMIT,
    marketIndex: 0,
    direction: PositionDirection.LONG,
    baseAssetAmount: velocityClient.convertToPerpPrecision(1),
    price: velocityClient.convertToPricePrecision(21.0),
  })
);
```
  

**getCancelOrdersIx** builds an instruction to cancel orders matching the given filters. Pass `null` for any filter to match all.

  
```js
import { MarketType, PositionDirection } from "@velocity-exchange/sdk";

// Params:
//   marketType: MarketType | null - filter by PERP or SPOT (null = all)
//   marketIndex: number | null - filter by market index (null = all)
//   direction: PositionDirection | null - filter by LONG or SHORT (null = both)
//   subAccountId?: number - defaults to active subaccount

// Cancel all perp orders on market 0
const ix = await velocityClient.getCancelOrdersIx(MarketType.PERP, 0, null);

// Cancel ALL orders across all markets
const ixAll = await velocityClient.getCancelOrdersIx(null, null, null);
```
  

**getFillPerpOrderIx** builds an instruction to fill another user's perp order (used by filler/keeper bots).

  
```js
// Params:
//   userAccountPublicKey: PublicKey - the taker's user account address
//   userAccount: UserAccount - the taker's deserialized user account
//   order: { marketIndex, orderId } - the order to fill
//   makerInfo?: MakerInfo | MakerInfo[] - optional maker(s) to match against
//   fillerSubAccountId?: number - filler's subaccount
//   isSignedMsg?: boolean
//   fillerAuthority?: PublicKey
//   hasBuilderFee?: boolean
//   takerEscrow?: RevenueShareEscrowAccount - the taker's decoded RevenueShareEscrow
//     (e.g. from a RevenueShareEscrowMap); REQUIRED when the taker's order carries a
//     builder code, otherwise the program rejects the fill with UnableToLoadRevenueShareAccount.
//   takerIsReferred?: boolean - set when the taker is referred (their UserStats.referrerStatus
//     has the BuilderReferral bit). This is the preferred way to satisfy the referred-taker
//     gate: it needs no account fetch. Passing a decoded takerEscrow for this case still
//     works, but is not required.

const takerPubkey = takerUser.userAccountPublicKey;
const takerAccount = takerUser.getUserAccount();
const order = takerAccount.orders[0]; // the order to fill

const ix = await velocityClient.getFillPerpOrderIx(
  takerPubkey,
  takerAccount,
  { marketIndex: order.marketIndex, orderId: order.orderId }
);
```
  

**getTriggerOrderIx** builds an instruction to trigger a conditional order (stop-loss or take-profit) that has met its trigger condition.

  
```js
// Params:
//   userAccountPublicKey: PublicKey - the user whose order to trigger
//   userAccount: UserAccount - the deserialized user account
//   order: Order - the full order object (must have trigger condition met)
//   fillerPublicKey?: PublicKey - optional, defaults to the caller's user account

const userPubkey = targetUser.userAccountPublicKey;
const userAccount = targetUser.getUserAccount();
const triggerOrder = userAccount.orders.find(
  (o) => o.orderType.triggerMarket !== undefined || o.orderType.triggerLimit !== undefined
);

const ix = await velocityClient.getTriggerOrderIx(
  userPubkey,
  userAccount,
  triggerOrder
);
```
  

**getRevertFillIx** builds an instruction to revert a fill (used by filler bots when a fill was invalid).

  
```js
// Params:
//   fillerPublicKey?: PublicKey - defaults to the caller's user account
const ix = await velocityClient.getRevertFillIx();
```
  

**getSettlePNLsIxs** builds instructions to settle PnL for one or more users across multiple markets. Returns an array of instructions (one per user per market).

  
```js
// Params:
//   users: Array of { settleeUserAccountPublicKey, settleeUserAccount }
//   marketIndexes: number[] - perp market indexes to settle

const user = velocityClient.getUser();
const ixs = await velocityClient.getSettlePNLsIxs(
  [
    {
      settleeUserAccountPublicKey: user.userAccountPublicKey,
      settleeUserAccount: user.getUserAccount(),
    },
  ],
  [0, 1] // settle PnL on perp markets 0 and 1
);
// ixs is an array of TransactionInstruction, one per user per market
```
  

**getProviderSwapIx** builds a swap instruction, routed through an external provider (Jupiter or Titan) but settled against the user's Velocity deposits/vault balances rather than their wallet's own token accounts. `swap(...)` wraps this and sends the transaction.

  
```js
import { BN } from "@velocity-exchange/sdk";

// Params:
//   swapProvider: SwapProvider - a UnifiedSwapClient, TitanClient, or JupiterClient
//   inMarketIndex: number - spot market index of the input token
//   outMarketIndex: number - spot market index of the output token
//   amount: BN - amount of the input token, in the token's own mint decimals
//     (not a fixed protocol precision)
//   slippageBps: number - max slippage in basis points
//   swapMode?: SwapMode - ExactIn (default) or ExactOut
//   reduceOnly?: SwapReduceOnly - require the position to reduce, not flip sign

const { ixs, lookupTables } = await velocityClient.getProviderSwapIx({
  swapProvider,
  inMarketIndex: 0,   // e.g. quote asset
  outMarketIndex: 1,  // e.g. SOL
  amount: new BN(10_000_000), // amount in the input token's own mint decimals
  slippageBps: 50,    // 0.5% max slippage
});
```
