Skip to Content
DevelopersDrift SDKSDK Internals

SDK Internals

The Drift SDK handles onchain interactions, account subscriptions, and transaction construction. Understanding its internals helps you optimize performance, debug issues, and use advanced features.

While the code examples below use the TypeScript SDK (@drift-labs/sdk), the architectural concepts , subscription patterns, account caching, transaction construction layers, and remaining accounts , apply equally to the Python (driftpy) and Rust SDKs.

Core architecture

The SDK has three main components:

DriftClient - Main interface for protocol interactions

  • Constructs transactions
  • Manages account subscriptions
  • Provides helper methods for orders, deposits, etc.
  • Caches market and state data

User - Represents a single user account

  • Subscribes to user account updates
  • Calculates positions, PnL, health
  • Provides convenience methods for account queries

AccountSubscriber - Handles real-time account updates

  • Polls or streams account data from RPC
  • Notifies clients when data changes
  • Caches account data for fast access

Account subscription patterns

The SDK supports multiple subscription strategies, each with different performance characteristics:

Polling subscription

How it works: Periodically calls connection.getAccountInfo() for each account

import { BulkAccountLoader } from "@drift-labs/sdk"; const accountLoader = new BulkAccountLoader(connection, "confirmed", 1000); const driftClient = new DriftClient({ connection, wallet, accountSubscription: { type: "polling", accountLoader, }, });

Pros:

  • Simple and reliable
  • Works with any RPC endpoint
  • Predictable resource usage

Cons:

  • Higher latency (poll interval delay)
  • More RPC calls
  • Not real-time

Best for: Development, low-frequency trading, simple bots

WebSocket subscription

How it works: Uses Solana’s onAccountChange WebSocket notifications

const driftClient = new DriftClient({ connection, wallet, accountSubscription: { type: "websocket", }, });

Pros:

  • Lower latency than polling
  • Fewer RPC calls
  • Real-time updates

Cons:

  • WebSocket can disconnect (needs reconnection handling)
  • Some RPC endpoints have connection limits
  • Slightly more complex error handling

Best for: Market makers, latency-sensitive bots, production trading

gRPC subscription (fastest)

How it works: Uses Yellowstone gRPC plugin for Solana validators

const driftClient = new DriftClient({ connection, wallet, accountSubscription: { type: "grpc", grpcConfigs: { endpoint: "https://grpc.mainnet.jito.wtf", token: "YOUR_GRPC_TOKEN", }, }, });

Pros:

  • Lowest latency (sub-second updates)
  • Most efficient bandwidth usage
  • Best for high-frequency trading

Cons:

  • Requires gRPC-enabled RPC (e.g., Jito, Triton)
  • More complex setup
  • May require authentication/payment

Best for: HFT bots, JIT market makers, competitive filling

BulkAccountLoader

For loading many accounts efficiently, the SDK provides BulkAccountLoader:

import { BulkAccountLoader } from "@drift-labs/sdk"; // Constructor: (connection, commitment, pollingFrequencyMs) const bulkAccountLoader = new BulkAccountLoader(connection, "confirmed", 1000); // Typically you don't call addAccount directly. Instead, pass the loader // to DriftClient and it registers the accounts it needs automatically. const driftClient = new DriftClient({ connection, wallet, accountSubscription: { type: "polling", accountLoader: bulkAccountLoader }, });

The loader batches multiple accounts into single getMultipleAccounts RPC calls for efficiency.

Transaction construction

The SDK builds transactions in several layers:

// 1. Get instruction const ix = await driftClient.getPlacePerpOrderIx(orderParams); // 2. Build transaction const tx = await driftClient.txSender.getVersionedTransaction( [ix], [], // lookup tables wallet.publicKey ); // 3. Send transaction const { txSig } = await driftClient.txSender.sendVersionedTransaction( tx, [], driftClient.opts );

Higher-level methods like placePerpOrder() do all three steps automatically.

Remaining accounts pattern

Many Drift instructions need dynamic account lists (oracles, markets, positions). The SDK’s getRemainingAccounts() method builds this list:

const remainingAccounts = driftClient.getRemainingAccounts({ userAccounts: [user.getUserAccount()], writableSpotMarketIndexes: [0], // USDC market }); // These accounts get passed to the instruction const ix = await driftClient.program.methods .placePerpOrder(params) .accounts({ user: userAccountPubkey, // ... other fixed accounts }) .remainingAccounts(remainingAccounts) .instruction();

This handles oracle accounts, market accounts, and cross-position accounts automatically.

Event subscriptions

The SDK can subscribe to onchain program events:

import { EventSubscriber, isVariant } from "@drift-labs/sdk"; const eventSubscriber = new EventSubscriber(connection, driftClient.program, { commitment: "confirmed", logProviderConfig: { type: "websocket" }, }); await eventSubscriber.subscribe(); // All events come through "newEvent", filter by eventType eventSubscriber.eventEmitter.on("newEvent", (event) => { if (event.eventType === "OrderActionRecord" && isVariant(event.action, "fill")) { console.log("Order filled:", event); console.log(" Market:", event.marketIndex); } });

Common event types:

  • OrderActionRecord (with action: fill, place, cancel, etc.) - Order lifecycle events
  • DepositRecord - Deposits and withdrawals
  • FundingPaymentRecord - Funding payments
  • LiquidationRecord - Liquidations

Caching and performance

// Market account caching: // First call after subscribe: data from subscription cache (no extra RPC) const market = driftClient.getPerpMarketAccount(0); // Subsequent calls: same cached data, updates via subscription const market2 = driftClient.getPerpMarketAccount(0); // Oracle price caching: const oracle = driftClient.getOracleDataForPerpMarket(0); // Price is cached from account subscription // User account caching: const user = driftClient.getUser(); const position = user.getPerpPosition(0); // No RPC call, data from subscription

This makes queries fast (microseconds vs milliseconds for RPC).

UserMap for multiple users

To track many user accounts efficiently:

import { UserMap } from "@drift-labs/sdk"; const userMap = new UserMap({ driftClient, subscriptionConfig: { type: "websocket", }, }); // Subscribe to a specific user await userMap.addUserAccount(userAccountPubkey); // Get user data const user = userMap.get(userAccountPubkey.toString()); const position = user.getPerpPosition(0);

UserMap handles subscription lifecycle for all users automatically.

Common patterns

import { ComputeBudgetProgram } from "@solana/web3.js"; // Initialize and subscribe const driftClient = new DriftClient({ connection, wallet, env: "mainnet-beta", }); await driftClient.subscribe(); const user = driftClient.getUser(); await user.subscribe(); // Transaction with priority fee const ix = await driftClient.getPlacePerpOrderIx(orderParams); const tx = await driftClient.txSender.getVersionedTransaction([ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50000 }), ix, ], [], wallet.publicKey); const { txSig } = await driftClient.txSender.sendVersionedTransaction( tx, [], driftClient.opts ); // Switch subaccounts await driftClient.switchActiveUser(1); // Switch to subaccount 1 const user1 = driftClient.getUser(); // Now returns subaccount 1

Error handling

Common error scenarios:

// Insufficient collateral try { await driftClient.placePerpOrder(params); } catch (e) { if (e.message.includes("InsufficientCollateral")) { console.log("Need to deposit more collateral"); } } // Account subscription errors driftClient.eventEmitter.on("error", (e) => { console.error("Subscription error:", e); // Reconnect logic here });

Performance tips

Use appropriate commitment:

  • processed - Fastest, some risk of reorgs
  • confirmed - Balanced (recommended)
  • finalized - Slowest, most secure
// Batch operations: place multiple orders in one transaction await driftClient.placeOrders([order1, order2, order3]); // Precompute values: convert once, reuse const size = driftClient.convertToPerpPrecision(1); const price = driftClient.convertToPricePrecision(100); // Use lookup tables (ALTs) to reduce transaction size const lookupTables = [/* your AddressLookupTableAccount objects */]; const tx = await driftClient.txSender.getVersionedTransaction( instructions, lookupTables, wallet.publicKey );
Last updated on