All articles
solanadeveloper-toolsapitradingrustexchangewebsocket

Backpack Exchange API: ED25519 auth, 70 endpoints, and the official Rust SDK

Backpack Exchange offers a full REST and WebSocket API backed by ED25519 keypair signing. The official SDK is Rust-only; community Python clients cover all 70 endpoints. Here is the complete developer picture: auth, market data, order management, capital, and real-time streams.

Share
devrels.xyz/a/119short link

Backpack Exchange is a Solana-native exchange built by the team behind the Anchor framework, the xNFT standard, and the Backpack wallet. Unlike most centralised exchanges, Backpack's API uses ED25519 keypair authentication — the same cryptographic primitive as Solana itself. There is no API secret string to leak; there is a private key you sign requests with and a public key the server verifies against. The official documentation is at docs.backpack.exchange and the official Rust client at github.com/backpack-exchange/bpx-api-client.

Authentication

All state-mutating requests require four headers. The signature covers the instruction string (method + path + body parameters, sorted) plus timestamp and window, signed with your ED25519 private key.

typescript
import { Keypair } from "@solana/web3.js";
import nacl from "tweetnacl";

const keypair = Keypair.generate(); // or load from file
const publicKey = Buffer.from(keypair.publicKey.toBytes()).toString("base64");

function signRequest(
  method: string,
  path: string,
  params: Record<string, string>,
  timestamp: number,
  window = 5000
): string {
  // Build the instruction string
  const sorted = Object.entries(params)
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([k, v]) => `${k}=${v}`)
    .join("&");

  const instruction = [`instruction=${method}${path}`, sorted, `timestamp=${timestamp}`, `window=${window}`]
    .filter(Boolean)
    .join("&");

  const sig = nacl.sign.detached(
    Buffer.from(instruction),
    keypair.secretKey
  );
  return Buffer.from(sig).toString("base64");
}

// Request headers
const headers = {
  "X-API-Key":   publicKey,
  "X-Signature": signRequest("GET", "/api/v1/capital", {}, Date.now()),
  "X-Timestamp": Date.now().toString(),
  "X-Window":    "5000",     // ms — replay attack window
};

The X-Window parameter sets how long a signed request remains valid (default 5000ms, maximum 60000ms). Requests arriving outside the window are rejected, preventing replay attacks. Public endpoints — market data, assets, tickers — require no headers.

REST endpoints

The REST base URL is https://api.backpack.exchange/. Endpoints split into two host prefixes: /api/v1/ for trading operations and /wapi/v1/ for capital management (deposits, withdrawals).

bash
# ── Market data (public, no auth) ──────────────────────────────────
GET /api/v1/assets           # all tradeable assets
GET /api/v1/markets          # all markets with specs
GET /api/v1/ticker?symbol=   # single ticker
GET /api/v1/tickers          # all tickers
GET /api/v1/depth?symbol=    # order book (default 1000 levels per side)
GET /api/v1/klines?symbol=&interval=  # OHLCV candles
GET /api/v1/fundingRates     # perpetual funding
GET /api/v1/markPrices       # mark + index prices

# ── Orders (authenticated) ──────────────────────────────────────────
GET    /api/v1/order?symbol=&orderId=    # single open order
POST   /api/v1/order                     # execute order
DELETE /api/v1/order                     # cancel order
GET    /api/v1/orders?symbol=            # all open orders
DELETE /api/v1/orders                    # cancel all open orders
POST   /api/v1/orders                    # batch order submit

# ── Capital (authenticated) ─────────────────────────────────────────
GET  /api/v1/capital                     # balances
GET  /wapi/v1/capital/deposits           # deposit history
GET  /wapi/v1/capital/deposit/address    # deposit address
GET  /wapi/v1/capital/withdrawals        # withdrawal history
POST /wapi/v1/capital/withdrawals        # request withdrawal

# ── Futures (authenticated) ─────────────────────────────────────────
GET /api/v1/futures/position             # open futures positions

# ── Borrow / Lend (authenticated) ───────────────────────────────────
GET /api/v1/borrow-lend/positions        # lending positions

# ── RFQ — Request for Quote (authenticated) ─────────────────────────
POST /api/v1/rfq               # create RFQ
GET  /api/v1/rfq/quote         # get quote
POST /api/v1/rfq/accept        # accept quote
POST /api/v1/rfq/cancel        # cancel RFQ
POST /api/v1/rfq/refresh       # refresh quote

# ── Vault (authenticated) ───────────────────────────────────────────
POST /api/v1/vault/mint        # mint vault tokens
POST /api/v1/vault/redeem      # redeem vault tokens

Order execution

Backpack processes all orders through a single linear command stream into one matching engine, regardless of how many API clients are connected. Order IDs are now randomly generated (changed June 2025 from timestamp-derived). Orders support stop-loss and take-profit fields natively.

typescript
// Execute a limit order
const payload = {
  symbol:     "SOL_USDC",
  side:       "Bid",             // "Bid" | "Ask"
  orderType:  "Limit",
  price:      "145.50",
  quantity:   "1.0",
  timeInForce: "GTC",            // GTC | IOC | FOK
  // Optional: stop-loss and take-profit
  stopLossPrice:  "140.00",
  takeProfitPrice: "155.00",
};

const res = await fetch("https://api.backpack.exchange/api/v1/order", {
  method: "POST",
  headers: { ...authHeaders, "Content-Type": "application/json" },
  body: JSON.stringify(payload),
});

WebSocket streams

Real-time data streams from wss://ws.backpack.exchange/. Subscribe to multiple channels in one connection.

typescript
const ws = new WebSocket("wss://ws.backpack.exchange/");

ws.onopen = () => {
  // Public streams — no auth needed
  ws.send(JSON.stringify({
    method: "SUBSCRIBE",
    params: [
      "ticker.SOL_USDC",        // real-time ticker
      "depth.SOL_USDC",         // order book updates
      "trade.SOL_USDC",         // trade feed
      "kline.SOL_USDC.1m",      // 1-minute candles
    ],
  }));

  // Private streams — signed subscription
  ws.send(JSON.stringify({
    method: "SUBSCRIBE",
    params: ["account.orderUpdate"],  // requires auth headers
    signature: [...],                  // ED25519 signed subscription
  }));
};

ws.onmessage = (msg) => {
  const data = JSON.parse(msg.data);
  // data.stream: "ticker.SOL_USDC" | "depth.SOL_USDC" | ...
};

The Rust SDK

The official client at github.com/backpack-exchange/bpx-api-client is Rust-only. It wraps all REST and WebSocket endpoints with typed request/response structs, handles ED25519 signing internally, and requires only your base64-encoded secret key to initialise.

rust
use bpx_api_client::{BACKPACK_API_BASE_URL, BpxClient};

#[tokio::main]
async fn main() {
    let client = BpxClient::init(
        BACKPACK_API_BASE_URL.to_string(),
        "your_base64_secret_key",
        None,  // optional extra headers
    ).expect("failed to init client");

    // Market data — no auth needed
    let markets = client.get_markets().await.unwrap();
    println!("markets: {}", markets.len());

    // Open orders for a symbol
    let orders = client.get_open_orders(Some("SOL_USDC")).await.unwrap();
    println!("open orders: {:?}", orders);

    // Account balances
    let balances = client.get_balances().await.unwrap();
    println!("balances: {:?}", balances);

    // Execute an order
    let order = client.execute_order(ExecuteOrderPayload {
        symbol: "SOL_USDC".into(),
        side: Side::Bid,
        order_type: OrderType::Limit,
        price: Some("145.50".into()),
        quantity: "1.0".into(),
        ..Default::default()
    }).await.unwrap();
}

// Build from source
// cd rust && just build
// cargo add bpx-api-client

Python SDK (community)

The community backpack-exchange-sdk on PyPI covers all 70 endpoints including REST and WebSocket and is the fastest path if you are not working in Rust.

bash
pip install backpack-exchange-sdk
python
from backpack_exchange_sdk.authenticated import AuthenticationClient

client = AuthenticationClient(
    public_key="your_base64_public_key",
    secret_key="your_base64_secret_key"
)

# Balances
balances = client.get_balances()

# Place order
order = client.execute_order(
    symbol="SOL_USDC",
    side="Bid",
    order_type="Limit",
    price="145.50",
    quantity="1.0",
    time_in_force="GTC"
)

# Cancel all open orders for a symbol
client.cancel_open_orders("SOL_USDC")

Rate limits

Limits are applied per sub-account: 2000 requests per minute across standard REST endpoints, 30 requests per minute for historical market data endpoints. WebSocket connections are not rate-limited by request count but are subject to connection-level limits.

bash
# Check current rate limit headers on any response:
# X-RateLimit-Limit:     2000
# X-RateLimit-Remaining: 1998
# X-RateLimit-Reset:     1719600000000

Keep reading

Get new articles in your inbox

Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.