Velocity ProtocolDevelopers
Velocity SDK

Setup

This page goes from an empty project to a subscribed VelocityClient. Examples use placeholders like <RPC_URL> and <KEYPAIR_PATH>.

Install

bun add @velocity-exchange/sdk

Wallet and authentication

To interact with Solana you need a keypair: a public key and a private key. The private key signs transactions and should be kept secure. Generate one with the Solana CLI, then point ANCHOR_WALLET at the file so SDK code can find it:

solana-keygen new --outfile ~/.config/solana/my-keypair.json
export ANCHOR_WALLET=~/.config/solana/my-keypair.json

Load it with loadKeypair, and wrap it in the SDK's Wallet. The wallet needs some SOL: it pays transaction fees and the rent for any account the SDK initializes for that authority.

import { Wallet, loadKeypair } from "@velocity-exchange/sdk";

const keyPairFile = `${process.env.HOME}/.config/solana/my-keypair.json`;
const wallet = new Wallet(loadKeypair(keyPairFile));

Create a Velocity client

At a minimum the client takes a Solana connection, a wallet, and the env. Call subscribe() to start receiving account updates, and unsubscribe() on shutdown so websocket handles and polling intervals are released. Nothing that reads cached state returns useful data before subscribe() resolves.

import { Connection } from "@solana/web3.js";
import { VelocityClient, Wallet, loadKeypair } from "@velocity-exchange/sdk";

const connection = new Connection("<RPC_URL>", "confirmed");
const wallet = new Wallet(loadKeypair("<KEYPAIR_PATH>"));

const velocityClient = new VelocityClient({
  connection,
  wallet,
  env: "mainnet-beta",
});

await velocityClient.subscribe();

// ... place orders, read positions ...

await velocityClient.unsubscribe();

Client configuration

ParameterDescriptionOptionalDefault
connectionSolana RPC connectionNo
walletWallet used to sign transactionsNo
envdevnet or mainnet-beta, used to derive market accountsYesmainnet-beta
perpMarketIndexesPerp market accounts to subscribe toYesDerived from env
spotMarketIndexesSpot market accounts to subscribe toYesDerived from env
oracleInfosOracle accounts to subscribe toYesDerived from env
accountSubscriptionWebsocket, polling, or gRPC subscription modeYesWebsocket
activeSubAccountIdWhich subaccount to use initiallyYes0
subAccountIdsAll subaccount IDs to subscribe toYes[]
authorityAuthority the wallet signs for, only set for delegated accountsYeswallet.publicKey
txSenderTransaction sender used to broadcast and confirmYesRetryTxSender
txHandlerBuilder and signer used for every transactionYesA TxHandler on this connection and wallet
txParamsCompute-unit limit and priority feeYescomputeUnits: 600000, computeUnitsPrice: 0

Delegated accounts. Signing on behalf of a delegated account requires setting subAccountIds, activeSubAccountId, and authority explicitly. Omit any of the three and the client subscribes to the wrong accounts. See Users for what a delegate can and cannot do.

See Transactions for the four available tx senders, blockhash caching, and the compute-unit and priority-fee options.

Account subscriptions

For most bots the default websocket subscription is the easiest way to keep markets and users up to date. For read-only workflows, or for tighter control over RPC load, switch to polling with a BulkAccountLoader. Its constructor takes (connection, commitment, pollingFrequencyMs); a frequency of 0 polls as fast as the loader is driven.

import { Connection } from "@solana/web3.js";
import { BulkAccountLoader, VelocityClient } from "@velocity-exchange/sdk";

const accountLoader = new BulkAccountLoader(connection, "confirmed", 1000);

const velocityClient = new VelocityClient({
  connection,
  wallet,
  env: "mainnet-beta",
  accountSubscription: {
    type: "polling",
    accountLoader,
  },
  // Optional: explicitly list markets and oracles to load.
  // perpMarketIndexes: [0, 1],
  // spotMarketIndexes: [0],
  // oracleInfos: [{ publicKey: ORACLE_PUBKEY, source: ORACLE_SOURCE }],
});

SDK Internals compares polling, websocket, and gRPC, and covers what BulkAccountLoader batches.

Multiple subaccounts

Velocity supports multiple subaccounts per wallet, each with its own position and order state. That allows separate strategies, say a market-making bot and a hedging bot, under one authority without their risk or PnL mixing. Subscribe to an extra subaccount after initialization with addUser(), guarded by hasUser() so a repeat call is a no-op.

if (!velocityClient.hasUser(1)) {
  await velocityClient.addUser(1);
}

See Users for switching the active subaccount, delegates, and per-subaccount margin settings.

Program addresses

NetworkProgram ID
Velocity (mainnet and devnet)vELoC1audYbSYVRXn1vPaV8Axoa9oU6BYmNGZZBDZ1P
Velocity VaultsvAuLTsyrvSfZRuRB3XgvkPwNGgYSs9YRYymVebLKoxR

Velocity uses the same program ID on devnet and mainnet-beta. It is an entirely new deployment, so user accounts must be re-initialized and balances start fresh; no prior onchain state carries over. Rather than pasting the address, import it:

import { VELOCITY_PROGRAM_ID } from "@velocity-exchange/sdk";

// The Velocity program's public key on mainnet-beta and devnet.
// Use this when deriving PDAs or referencing the program directly.
console.log(VELOCITY_PROGRAM_ID.toBase58());
// vELoC1audYbSYVRXn1vPaV8Axoa9oU6BYmNGZZBDZ1P

Quote mint

The protocol's quote asset mint is environment-specific and available from the SDK's config presets. On mainnet-beta the quote asset is USDT (Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB). On devnet, spot market index 0 is dUSDT (GqmEqYsy8EyvofDpmtFxK8zhYrgWgNokAtYoduQdL7v6), a placeholder quote token at 1e6 precision rather than real USDT.

import { getConfig, initialize } from "@velocity-exchange/sdk";

initialize({ env: "devnet" });
console.log(getConfig().QUOTE_MINT_ADDRESS.toBase58());
// devnet: GqmEqYsy8EyvofDpmtFxK8zhYrgWgNokAtYoduQdL7v6 (dUSDT)
// mainnet-beta: Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB (USDT)

An integration ported from a different quote asset should check the migration guide.

Minting devnet dUSDT

On devnet, mint test collateral from the SDK's TokenFaucet before depositing.

import { PublicKey } from "@solana/web3.js";
import { BN, TokenFaucet } from "@velocity-exchange/sdk";

// <FAUCET_PROGRAM_ID> is the devnet token-faucet program's own program ID,
// a separate deployment from the Velocity program. It is not a fixed
// documented constant: read it from the devnet environment or deploy config.
const tokenFaucet = new TokenFaucet(
  connection,
  wallet,
  new PublicKey("<FAUCET_PROGRAM_ID>"),
  new PublicKey("GqmEqYsy8EyvofDpmtFxK8zhYrgWgNokAtYoduQdL7v6") // dUSDT mint (devnet)
);

const [associatedTokenAccount] = await tokenFaucet.createAssociatedTokenAccountAndMintTo(
  wallet.publicKey,
  new BN(1_000_000_000) // 1,000 dUSDT at 1e6 precision
);