Setup
The examples below use placeholders like <RPC_URL> and <KEYPAIR_PATH>.
Program Addresses
| Network | Program ID |
|---|---|
| Drift (mainnet & devnet) | dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH |
| Drift Vaults | vAuLTsyrvSfZRuRB3XgvkPwNGgYSs9YRYymVebLKoxR |
You can also import the Drift program ID directly from the SDK:
import { DRIFT_PROGRAM_ID } from "@drift-labs/sdk";
// The Drift program's public key on mainnet-beta and devnet.
// Use this when deriving PDAs or referencing the program directly.
console.log(DRIFT_PROGRAM_ID.toBase58());
// dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UHVariable DRIFT_PROGRAM_IDReference ↗
Variable DRIFT_PROGRAM_IDReference ↗| Property | Type | Required |
|---|---|---|
toString | () => string | Yes |
charAt | (pos: number) => string | Yes |
charCodeAt | (index: number) => number | Yes |
concat | (...strings: string[]) => string | Yes |
indexOf | (searchString: string, position?: number | undefined) => number | Yes |
lastIndexOf | (searchString: string, position?: number | undefined) => number | Yes |
localeCompare | { (that: string): number; (that: string, locales?: string | string[] | undefined, options?: CollatorOptions | undefined): number; (that: string, locales?: LocalesArgument, options?: CollatorOptions | undefined): number; } | Yes |
match | { (regexp: string | RegExp): RegExpMatchArray | null; (matcher: { [Symbol.match](string: string): RegExpMatchArray | null; }): RegExpMatchArray | null; } | Yes |
replace | { (searchValue: string | RegExp, replaceValue: string): string; (searchValue: string | RegExp, replacer: (substring: string, ...args: any[]) => string): string; (searchValue: { ...; }, replaceValue: string): string; (searchValue: { ...; }, replacer: (substring: string, ...args: any[]) => string): string; } | Yes |
search | { (regexp: string | RegExp): number; (searcher: { [Symbol.search](string: string): number; }): number; } | Yes |
slice | (start?: number | undefined, end?: number | undefined) => string | Yes |
split | { (separator: string | RegExp, limit?: number | undefined): string[]; (splitter: { [Symbol.split](string: string, limit?: number | undefined): string[]; }, limit?: number | undefined): string[]; } | Yes |
substring | (start: number, end?: number | undefined) => string | Yes |
toLowerCase | () => string | Yes |
toLocaleLowerCase | { (locales?: string | string[] | undefined): string; (locales?: LocalesArgument): string; } | Yes |
toUpperCase | () => string | Yes |
toLocaleUpperCase | { (locales?: string | string[] | undefined): string; (locales?: LocalesArgument): string; } | Yes |
trim | () => string | Yes |
length | number | Yes |
substr | (from: number, length?: number | undefined) => string | Yes |
valueOf | () => string | Yes |
codePointAt | (pos: number) => number | undefined | Yes |
includes | (searchString: string, position?: number | undefined) => boolean | Yes |
endsWith | (searchString: string, endPosition?: number | undefined) => boolean | Yes |
normalize | { (form: "NFC" | "NFD" | "NFKC" | "NFKD"): string; (form?: string | undefined): string; } | Yes |
repeat | (count: number) => string | Yes |
startsWith | (searchString: string, position?: number | undefined) => boolean | Yes |
anchor | (name: string) => string | Yes |
big | () => string | Yes |
blink | () => string | Yes |
bold | () => string | Yes |
fixed | () => string | Yes |
fontcolor | (color: string) => string | Yes |
fontsize | { (size: number): string; (size: string): string; } | Yes |
italics | () => string | Yes |
link | (url: string) => string | Yes |
small | () => string | Yes |
strike | () => string | Yes |
sub | () => string | Yes |
sup | () => string | Yes |
padStart | (maxLength: number, fillString?: string | undefined) => string | Yes |
padEnd | (maxLength: number, fillString?: string | undefined) => string | Yes |
trimEnd | () => string | Yes |
trimStart | () => string | Yes |
trimLeft | () => string | Yes |
trimRight | () => string | Yes |
matchAll | (regexp: RegExp) => RegExpStringIterator<RegExpExecArray> | Yes |
replaceAll | { (searchValue: string | RegExp, replaceValue: string): string; (searchValue: string | RegExp, replacer: (substring: string, ...args: any[]) => string): string; } | Yes |
at | (index: number) => string | undefined | Yes |
isWellFormed | () => boolean | Yes |
toWellFormed | () => string | Yes |
__@iterator@2718 | () => StringIterator<string> | Yes |
Wallet / Authentication
To interact with Solana you need a keypair, which consists of a public key and a private key. The private key is used to sign transactions and should be kept secure.
Generate a new keypair using the Solana CLI :
solana-keygen new --outfile ~/.config/solana/my-keypair.jsonTo allow SDK code to use this keypair, set the ANCHOR_WALLET environment variable to the path of the keypair file:
export ANCHOR_WALLET=~/.config/solana/my-keypair.jsonThen load the keypair in your code:
import { Wallet, loadKeypair } from "@drift-labs/sdk";
const keyPairFile = `${process.env.HOME}/.config/solana/my-keypair.json`;
const wallet = new Wallet(loadKeypair(keyPairFile));Example Wallet / AuthenticationReference ↗
Example Wallet / AuthenticationReference ↗Wallet / Authentication.Make sure the wallet has some SOL, as it is used to pay for transaction fees and rent for account initializations.
Install
npm i @drift-labs/sdkCreate a Drift Client
At a minimum you provide a Solana connection, a wallet, and the env. Then call subscribe() to start receiving account updates.
import { Connection } from "@solana/web3.js";
import { DriftClient, Wallet, loadKeypair } from "@drift-labs/sdk";
const connection = new Connection("<RPC_URL>", "confirmed");
const wallet = new Wallet(loadKeypair("<KEYPAIR_PATH>"));
const driftClient = new DriftClient({
connection,
wallet,
env: "mainnet-beta",
});
await driftClient.subscribe();await driftClient.subscribe();Method DriftClient.subscribeReference ↗
Method DriftClient.subscribeReference ↗| Returns |
|---|
Promise<boolean> |
await driftClient.unsubscribe();Method DriftClient.unsubscribeReference ↗
Method DriftClient.unsubscribeReference ↗| Returns |
|---|
Promise<void> |
Key DriftClient parameters:
| Parameter | Description | Optional | Default |
|---|---|---|---|
connection | Solana RPC connection | No | |
wallet | Wallet used to sign transactions | No | |
env | devnet or mainnet-beta, used to derive market accounts | Yes | |
perpMarketIndexes | Perp market accounts to subscribe to | Yes | Derived from env |
spotMarketIndexes | Spot market accounts to subscribe to | Yes | Derived from env |
oracleInfos | Oracle accounts to subscribe to | Yes | Derived from env |
accountSubscription | WebSocket or polling subscription mode | Yes | WebSocket |
activeSubAccountId | Which subaccount to use initially | Yes | 0 |
subAccountIds | All subaccount IDs to subscribe to | Yes | [] |
authority | Authority you’re signing for, only set for delegated accounts | Yes | wallet.publicKey |
Delegated accounts: When signing on behalf of a delegated account, you must explicitly set
subAccountIds,activeSubAccountId, andauthority. Omitting any of these will cause the client to subscribe to the wrong accounts.
Account Subscriptions (WebSocket vs Polling)
For most bots, websocket subscriptions are the easiest way to keep markets and users up to date. For read-only workflows or when you need tighter control over RPC load, you can switch to polling with a BulkAccountLoader.
import { BulkAccountLoader } from "@drift-labs/sdk";Class BulkAccountLoaderReference ↗
Class BulkAccountLoaderReference ↗| Property | Type | Required |
|---|---|---|
connection | Connection | Yes |
commitment | Commitment | Yes |
pollingFrequency | number | Yes |
accountsToLoad | Map<string, AccountToLoad> | Yes |
bufferAndSlotMap | Map<string, BufferAndSlot> | Yes |
errorCallbacks | Map<string, (e: any) => void> | Yes |
intervalId | Timeout | No |
loadPromise | Promise<void> | No |
loadPromiseResolver | () => void | Yes |
lastTimeLoadingPromiseCleared | number | Yes |
mostRecentSlot | number | Yes |
addAccount | (publicKey: PublicKey, callback: (buffer: Buffer, slot: number) => void) => Promise<string> | Yes |
removeAccount | (publicKey: PublicKey, callbackId: string) => void | Yes |
addErrorCallbacks | (callback: (error: Error) => void) => string | Yes |
removeErrorCallbacks | (callbackId: string) => void | Yes |
chunks | <T>(array: readonly T[], size: number) => T[][] | Yes |
load | () => Promise<void> | Yes |
loadChunk | (accountsToLoadChunks: AccountToLoad[][]) => Promise<void> | Yes |
handleAccountCallbacks | (accountToLoad: AccountToLoad, buffer: Buffer, slot: number) => void | Yes |
getBufferAndSlot | (publicKey: PublicKey) => BufferAndSlot | undefined | Yes |
getSlot | () => number | Yes |
startPolling | () => void | Yes |
stopPolling | () => void | Yes |
log | (msg: string) => void | Yes |
updatePollingFrequency | (pollingFrequency: number) => void | Yes |
import { BulkAccountLoader } from "@drift-labs/sdk";
const accountLoader = new BulkAccountLoader(connection, "confirmed", 0);
const driftClient = new DriftClient({
connection,
wallet,
env: "mainnet-beta",
accountSubscription: {
type: "polling",
accountLoader,
},
// Optional: explicitly list markets/oracles to load.
// perpMarketIndexes: [0, 1],
// spotMarketIndexes: [0],
// oracleInfos: [{ publicKey: ORACLE_PUBKEY, source: ORACLE_SOURCE }],
});Example Polling subscriptionReference ↗
Example Polling subscriptionReference ↗Polling subscription.Multiple Subaccounts
Drift supports multiple subaccounts per wallet, each with its own isolated position and order state. This lets you run separate strategies (e.g., a market-making bot and a hedging bot) under the same authority without cross-contaminating risk or PnL. Use addUser() to subscribe to additional subaccounts after initialization.
if (!driftClient.hasUser(1)) {
await driftClient.addUser(1);
}Method DriftClient.hasUserReference ↗
Method DriftClient.hasUserReference ↗| Parameter | Type | Required |
|---|---|---|
subAccountId | number | No |
authority | PublicKey | No |
| Returns |
|---|
boolean |
await driftClient.addUser(1);Method DriftClient.addUserReference ↗
Method DriftClient.addUserReference ↗| Parameter | Type | Required |
|---|---|---|
subAccountId | number | Yes |
authority | PublicKey | No |
userAccount | UserAccount | No |
| Returns |
|---|
Promise<boolean> |
High Leverage Mode
High Leverage Mode allows eligible accounts to trade with increased maximum leverage (up to 50x on supported markets) compared to the standard 10x cap. It is an opt-in feature that requires the account to meet specific criteria set by the protocol. You can fetch the current high leverage configuration to check limits and eligibility conditions.
import { getHighLeverageModeConfigPublicKey } from "@drift-labs/sdk";
const pda = getHighLeverageModeConfigPublicKey(driftClient.program.programId);
const config = await driftClient.program.account.highLeverageModeConfig.fetch(pda);Function getHighLeverageModeConfigPublicKeyReference ↗
Function getHighLeverageModeConfigPublicKeyReference ↗| Parameter | Type | Required |
|---|---|---|
programId | PublicKey | Yes |
| Returns |
|---|
PublicKey |