# Sending Actions

> Canonical: https://docs.velocity.exchange/developers/ecosystem-builders/sending-actions

This page covers every way to send transactions from an application: the high-level `VelocityClient` methods, and the stateless `VelocityCore` instruction builders for full control without a subscribed client.

## VelocityClient methods

Once subscribed (see [setup](/developers/ecosystem-builders.md#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

```ts
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

```ts
// null args = cancel all orders (any market, any direction)
await velocityClient.cancelOrders(null, null, null);
```

### Settle PnL

```ts
const user = velocityClient.getUser();
await velocityClient.settlePNL(user.userAccountPublicKey, user.getUserAccount(), 0);
```

### Deposit and withdraw

```ts
await velocityClient.deposit(
  depositAmount,  // BN, spot market's mint precision (e.g. 1e6 for USDT/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 only the instructions are needed), `VelocityCore` provides pure instruction builders that take explicit account pubkeys instead of reading from a subscription cache.

| Approach | Use when |
|---|---|
| `VelocityClient` methods (above) | A subscribed client already exists: the shortest path for most apps |
| `VelocityCore.build*Instruction` (below) | A full subscription is unwanted, several instructions have to go in one transaction, or the integration is lightweight or serverless |

### Get an Anchor `Program<Velocity>`

`VelocityCore` builders take an Anchor `program: VelocityProgram` argument rather than reading it from a client instance:

```ts
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 required accounts

```ts
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],
  }
);
```

> **Info:**
>
> `getRemainingAccounts` takes two arguments (a `RemainingAccountsContext` of caller-owned resolver hooks and caches, 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

  
```js
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,
});
```
  

### Build a place-perp-order instruction

  
```js
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,
});
```
  

### Compose and send the transaction

`VelocityCore` builders return raw `TransactionInstruction`s, so sending them is the caller's job: Anchor's `sendAndConfirm`, `@solana/web3.js` directly, or a separately constructed `VelocityClient.txSender`.

```js
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, closing the window with no resting orders while prices are updated. With a subscribed client, build the individual instructions and compose them:

  
```js
import { ComputeBudgetProgram } from "@solana/web3.js";
import { getLimitOrderParams, PositionDirection, PostOnlyParams } 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: PostOnlyParams.MUST_POST_ONLY,
});

const askParams = getLimitOrderParams({
  marketIndex: 0,
  direction: PositionDirection.SHORT,
  baseAssetAmount: velocityClient.convertToPerpPrecision(1),
  price: velocityClient.convertToPricePrecision(125),
  postOnly: PostOnlyParams.MUST_POST_ONLY,
});

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,
  ],
  []
);

const signature = await velocityClient.txSender.sendVersionedTransaction(
  tx,
  [],
  velocityClient.opts
);
console.log("Cancel + place transaction:", signature);
```
  

## Transaction performance

For batched or frequent transaction sending:

- **Priority fees**: Set `ComputeUnitPrice` to land transactions faster during congestion
- **Compute budget**: Use `setComputeUnitLimit` to 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` and `placeScaleOrders` run the initial-margin check once per risk scope touched by the batch rather than as a single end-of-batch check. A batch that previously succeeded may now revert with `InsufficientCollateral`.
