Terms of Use
By using any API provided by Drift Labs, you agree to the Terms of Use. If you do not agree to the foregoing, then do not use any such API.
Introduction
Drift Protocol is an open-sourced, decentralised exchange built on the Solana blockchain, enabling transparent and non-custodial trading on cryptocurrencies.
There are language bindings in Typescript and Python! You can view code examples in the dark area to the right, and you can switch the programming language of the examples with the tabs in the top right.
This API documentation page is open sourced and available here was created with Slate. Feel free to submit questions/comments in Issues or suggest changes as a PR.
Program Addresses
Enviroment | Program ID | User Interface |
---|---|---|
mainnet-beta | dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH | app |
devnet | dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH | app |
Authentication
To access and interact with a blockchain, such as Solana, you need a keypair, which consists of a public key and a private key. The private key should be kept secure and not shared with anyone else.
To generate a new keypair, you can use the Solana Tool Suite by running the following command on the command line:
solana-keygen new --outfile ~/.config/solana/my-keypair.json
This command will create a new keypair and store it in a JSON file located at ~/.config/solana/my-keypair.json.
To allow SDK code to use this keypair for authentication, you need to set the ANCHOR_WALLET environment variable to the path of the JSON file that contains the keypair. You can do this by running the following command:
export ANCHOR_WALLET=~/.config/solana/my-keypair.json
This command sets the ANCHOR_WALLET environment variable to the path of the JSON file that contains your keypair, so that SDK code can access it when needed.
Client
Typescript
Install @drift-labs/sdk from npm using yarn:
yarn add @drift-labs/sdk
auto-generated documentation here: [https://drift-labs.github.io/protocol-v2/sdk/]
Python
Install driftpy from PyPI using pip:
pip install driftpy
auto-generated documentation here: [https://drift-labs.github.io/driftpy/]
HTTP
Use the self-hosted HTTP gateway
Connection
import {Connection} from "@solana/web3.js";
// the default RPC for devnet is `https://api.devnet.solana.com`
const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed');
from solana.rpc.async_api import AsyncClient
url = 'https://api.mainnet-beta.solana.com' # replace w/ any rpc
connection = AsyncClient(url)
drift-gateway https://api.mainnet-beta.solana.com --port 8080
The connection object is used to send transactions to the Solana blockchain. It is used by the DriftClient to send transactions to the blockchain.
Wallet
import {Wallet, loadKeypair} from "@drift-labs/sdk";
const keyPairFile = `${process.env.HOME}/.config/solana/my-keypair.json`;
const wallet = new Wallet(loadKeypair(keyPairFile));
import os
from anchorpy import Wallet
from driftpy.keypair import load_keypair
keypair_file = os.path.expanduser('~/.config/solana/my-keypair.json')
keypair = load_keypair(keypair_file)
wallet = Wallet(kp)
# use `DRIFT_GATEWAY_KEY` environment variable to configure the gateway wallet
# either path to .json keypair or base58 seed string
export DRIFT_GATEWAY_KEY="</path/to/my-keypair.json|<KEYPAIR_BASE58_SEED>"
The wallet used to sign solana transactions. The wallet can be created from a private key or from a keypair file.
Make sure this wallet has some SOL first. SOL is used to pay for transactions and is required as rent for account intializations.
Client Initialization
import {Connection} from "@solana/web3.js";
import {Wallet, loadKeypair, DriftClient} from "@drift-labs/sdk";
const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed');
const keyPairFile = `${process.env.HOME}/.config/solana/my-keypair.json`;
const wallet = new Wallet(loadKeypair(keyPairFile))
const driftClient = new DriftClient({
connection,
wallet,
env: 'mainnet-beta',
});
await driftClient.subscribe();
from anchorpy import Wallet
from driftpy.drift_client import DriftClient
from solana.rpc.async_api import AsyncClient
# set connection and wallet
# ...
drift_client = DriftClient(connection, wallet, "mainnet")
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
connection | Connection object specifying solana rpc url | No | |
wallet | The wallet used to sign transactions sent to solana blockchain | No | |
programId | Drift program id | Yes | dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH |
env | devnet or mainnet-beta . Used to automatically derive market accounts to subscribe to if they're not explicitly set |
Yes | |
perpMarketIndexes | Which perp markets accounts to subscribe to. | Yes | Derived based on env |
spotMarketIndexes | Which spot markets accounts to subscribe to. | Yes | Derived based on env |
oracleInfos | Which oracles accounts to subscribe to. | Yes | Derived based on env |
accountSubscription | Whether to use websocket or polling to subscribe to on-chain accounts e.g. markets, users, oracle. | Yes | Websockets |
opts | Transaction confirmation status options | Yes | {preflightCommitment: "processed", commitment: "processed"} |
activeSubAccountId | Which sub account to use initially | Yes | 0 |
subAccountIds | All the sub account ids to subscribe to. If this and authoritySubAccountMap are empty, subscribes to all sub account ids. | Yes | [] |
authority | Which user account authority you're signing for. Only set if you're signing for delegated account. | Yes | wallet.publicKey |
authoritySubAccountMap | Map of authority to sub account ids to subscribe to. Only necessary if using multiple delegate accounts. If this and subAccountIds are empty, subscribes to all sub account ids. | Yes | {} |
includeDelegates | Whether or not to subscribe to delegates when subAccountIds and authoritySubAccountMap are empty | Yes | false |
userStats | Whether or not to listen subscribe to user stats account. | Yes | false |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
connection | AsyncClient connection to the Solana cluster | No | |
wallet | Wallet object, can be Keypair or Wallet type | No | |
env | Drift environment, 'devnet' or 'mainnet' | Yes | "mainnet" |
program_id | Drift program identifier | Yes | DRIFT_PROGRAM_ID |
opts | Options for transaction confirmation status | Yes | DEFAULT_TX_OPTIONS |
authority | Public key of the user account authority | Yes | None |
account_subscription | Configuration for account subscriptions (websockets/polling) | Yes | AccountSubscriptionConfig.default() |
perp_market_indexes | List of perpetual market indexes to interact with | Yes | None |
spot_market_indexes | List of spot market indexes to interact with | Yes | None |
oracle_infos | List of OracleInfo objects for market data | Yes | None |
tx_params | Additional parameters for transactions | Yes | None |
tx_version | Version of the transaction | Yes | None |
tx_sender | Object handling the sending of transactions | Yes | None |
active_sub_account_id | ID of the initially active sub-account | Yes | None |
sub_account_ids | List of sub-account IDs to subscribe to | Yes | None |
market_lookup_table | Public key for the market lookup table | Yes | None |
User Initialization
const [txSig, userPublickKey] = await driftClient.initializeUserAccount(
0,
"toly"
);
tx_sig = await drift_client.initialize_user(sub_account_id=0, name="toly")
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
subAccountId | The sub account id for the new user account. | Yes | 0 |
name | Display name for the user account | Yes | Main Account |
referrerInfo | The address of the referrer and referrer stats accounts | Yes |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
sub_account_id | The sub account id for the new user account. | Yes | 0 |
name | Display name for the user account | Yes | None |
referrer_info | The address of the referrer and referrer stats accounts | Yes | None |
Sub account ids are monotonic. The first user account created will have sub account id 0, the second will have sub account id 1, etc.
The next sub account id can be found by calling driftClient.getNextSubAccountId()
in TypeScript.
Updating User
Users accounts can update names, set custom max intial margin ratio, enable margin trading, and add a delegate account.
const subaccountId = 0;
// set max 1x intiial leverage
await driftClient.updateUserCustomMarginRatio([
{
'marginRatio': MARGIN_PRECISION,
'subAccountId': subaccountId
}
]);
// enable spot margin trading
await driftClient.updateUserMarginTradingEnabled(
true,
subaccountId
);
// add a delegate this user account
await driftClient.updateUserDelegate(
new PublicKey('satoshi'),
subaccountId
);
Switching Sub Accounts
driftClient.switchActiveUser(
1,
);
drift_client.switch_active_user(sub_account_id=1)
<<<<<<< HEAD
TypeScript
=======
shell
curl http://localhost:8080/v2/resource?subAccountId=1
upstream/main | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | subAccountId | The sub account to switch to | No | 0 | | authority | The authority of the sub account you're signing for. Only needed for delegate accounts | Yes | Current authority |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
sub_account_id | Identifier for the sub-account to be activated | No |
Deleting User Account
If an account contains no assets or liabilites, a user account can be deleted to reclaim rent.
driftClient.deleteUser(
1,
);
Depositing
const marketIndex = 0; // USDC
const amount = driftClient.convertToSpotPrecision(marketIndex, 100); // $100
const associatedTokenAccount = await driftClient.getAssociatedTokenAccount(marketIndex);
await driftClient.deposit(
amount,
marketIndex,
associatedTokenAccount,
);
spot_market_index = 0 # USDC
amount = drift_client.convert_to_spot_precision(100, spot_market_index) # $100
tx_sig = await drift_client.deposit(amount, spot_market_index)
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
amount | The amount to deposit in spot market's token mint precision | No | |
marketIndex | The spot market index you're depositing into | No | |
associatedTokenAccount | The public key of the token account you're depositing from. For sol, it can be the wallet's public key | No | |
subAccountId | The sub account you're depositing to | Yes | active sub account |
reduceOnly | Whether the deposit should only reduce borrow | Yes | false |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
amount | The amount to deposit in the spot market's token mint precision | No | |
spot_market_index | The index of the spot market where the deposit is made | No | |
user_token_account | The public key of the token account from which you are depositing | Yes | None (will derive ATA) |
sub_account_id | The sub account to which the deposit is being made | Yes | Active sub-account |
reduce_only | Whether the deposit should only reduce borrow | Yes | false |
user_initialized | Indicates if the user is already initialized (used internally, typically) | Yes | true |
Withdrawing
const marketIndex = 0;
const amount = driftClient.convertToSpotPrecision(marketIndex, 100);
const associatedTokenAccount = await driftClient.getAssociatedTokenAccount(marketIndex);
await driftClient.withdraw(
amount,
marketIndex,
associatedTokenAccount,
);
market_index = 0 # USDC
amount = drift_client.convert_to_spot_precision(100, market_index) # $100
# derive user ata to withdraw to
# ...
tx_sig = await drift_client.withdraw(amount, spot_market_index, ata_to_withdraw_to)
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
amount | The amount to withdraw in spot market's token mint precision | No | |
marketIndex | The spot market index you're withdrawing from | No | |
associatedTokenAccount | The public key of the token account you're withdrawing to. For sol, it can be the wallet's public key | No | |
reduceOnly | Whether the withdraw should only decrease a deposit and block a new borrow | Yes | false |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
amount | The amount to withdraw in the spot market's token mint precision | No | |
market_index | The index of the spot market from which the withdrawal is made | No | |
user_token_account | The public key of the token account to which you are withdrawing | No | |
reduce_only | Whether the withdrawal should only decrease a deposit, blocking new borrows | Yes | false |
sub_account_id | The sub account from which the withdrawal is made | Yes | None |
Withdrawing can lead to a borrow if the user has no deposits in the market and the user has enough margin to cover it.
Transferring Deposits
const marketIndex = 0;
const amount = driftClient.convertToSpotPrecision(marketIndex, 100);
const fromSubAccountId = 0;
const toSubAccountId = 1;
await driftClient.transferDeposit(
amount,
marketIndex,
fromSubAccountId,
toSubAccountId,
);
market_index = 0
<<<<<<< HEAD
amount = drift_client.convert_to_spot_precision(100, market_index)
=======
amount = drift_client.convert_to_spot_precision(market_index, 100)
>>>>>>> upstream/main
from_sub_account_id = 0
to_sub_account_id = 0
await drift_client.transfer_deposit(
amount,
market_index,
from_sub_account_id,
to_sub_account_id,
)
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
amount | The amount to transfer in spot market's token mint precision | No | |
marketIndex | The spot market index you're transferring deposits in | No | |
fromSubAccountId | The sub account you're withdrawing from | No | |
toSubAccountId | The sub account you're depositing too | No |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
amount | The amount to transfer in the spot market's token mint precision | No | |
market_index | The spot market index you're transferring deposits in | No | |
from_sub_account_id | The sub account from which the funds are withdrawn | No | |
to_sub_account_id | The sub account to which the funds are deposited | No |
Order Types
TypeScript
MARKET, LIMIT, ORACLE orders all support auction parameters.
Type | Description |
---|---|
MARKET | Market order. |
LIMIT | Limit order. |
TRIGGER_MARKET | Stop / Take-profit market order. |
TRIGGER_LIMIT | Stop / Take-profit limit order. |
ORACLE | Market order using oracle offset for auction parameters. |
Python
Market(), Limit(), Oracle() orders all support auction parameters.
Type | Description |
---|---|
Market() | Market order. |
Limit() | Limit order. |
TriggerMarket() | Stop / Take-profit market order. |
TriggerLimit() | Stop / Take-profit limit order. |
Oracle() | Market order using oracle offset for auction parameters. |
Order Params
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
orderType | The type of order e.g. market, limit | No | |
marketIndex | The market to place order in | No | |
direction | The direction of order e.g. long (bid) or short (ask) | No | |
baseAssetAmount | The amount of base asset to buy or sell | No | |
marketType | The type of market order is for e.g. PERP or SPOT | Yes | Depends on method |
price | The limit price for order | Yes | 0 |
userOrderId | Unique order id specified by user | Yes | 0 |
reduceOnly | If the order can only reduce positions | Yes | false |
postOnly | If the order can only be a maker | PostOnlyParam | None |
triggerPrice | at what price order is triggered. only applicable for triggerMarket and triggerLimit orders | Yes | |
triggerCondition | whether order is triggered above or below triggerPrice. only applicable for triggerMarket and triggerLimit orders | Yes | |
oraclePriceOffset | priceOffset for oracle derived limit price. only applicable for limit and oracle orders | Yes | |
auctionDuration | how many slots the auction lasts. only applicable for market and oracle orders | Yes | |
auctionStartPrice | the price the auction starts at | Yes | |
auctionEndPrice | the price the auction ends at | Yes | |
maxTs | the max timestamp (on-chain unix timestamp) before the order expires | Yes |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
order_type | Type of order | No | |
market_index | Index of the market to place order in | No | |
direction | Direction of the order | No | |
base_asset_amount | Amount of base asset to buy or sell | No | |
market_type | Type of market | Yes | Depends on method |
price | Limit price for the order | Yes | 0 |
user_order_id | Unique order ID specified by user | Yes | 0 |
reduce_only | Whether the order is only to reduce positions | Yes | false |
post_only | If the order should only be a maker | Yes | PostOnlyParams.NONE() |
immediate_or_cancel | Whether the order is immediate or cancel | Yes | false |
max_ts | Max timestamp for the order expiry | Yes | None |
trigger_price | Trigger price for trigger orders | Yes | None |
trigger_condition | Condition for triggering the order | Yes | OrderTriggerCondition.Above() |
oracle_price_offset | Offset for oracle-derived limit price | Yes | None |
auction_duration | Duration of the auction in slots | Yes | None |
auction_start_price | Starting price of the auction | Yes | None |
auction_end_price | Ending price of the auction | Yes | None |
Post Only Params
Drift orderbook is not a strict clob that enforces price-time priority. This is to maximize the parallelization of placing orders to take advantage of the solana runtime. To force an order to always be a maker, users most set the post only params. If a user order is set to post only, drift will check that an order does not cross the vamm spread, similar to how a traditional clob would check that an order doesn't cross the book's best bid/ask. If the post only is not used, a limit order can end up being a taker or maker.
Parameter | Description |
---|---|
None | Does not enforce being maker |
MustPostOnly | Tx fails if order crosses the vamm |
TryPostOnly | Order is skipped (not placed) and tx succeeds if order crosses the vamm |
Slide | Order price is modified to be one tick below/above the vamm ask/bid |
Placing Perp Order
// market buy for 100 SOL-PERP @ $21.20->$21.30 over 60 slots (~30 seconds)
// after 60 slots, market buy 100 SOL-PERP @ $21.35 until maxTs
const orderParams = {
orderType: OrderType.MARKET,
marketIndex: 0,
direction: PositionDirection.LONG,
baseAssetAmount: driftClient.convertToPerpPrecision(100),
auctionStartPrice: driftClient.convertToPricePrecision(21.20),
auctionEndPrice: driftClient.convertToPricePrecision(21.30),
price: driftClient.convertToPricePrecision(21.35),
auctionDuration: 60,
maxTs: now + 100,
}
await driftClient.placePerpOrder(orderParams);
// bid for 100 SOL-PERP @ $21.23
const orderParams = {
orderType: OrderType.LIMIT,
marketIndex: 0,
direction: PositionDirection.LONG,
baseAssetAmount: driftClient.convertToPerpPrecision(100),
price: driftClient.convertToPricePrecision(21.23),
}
await driftClient.placePerpOrder(orderParams);
// ask for 100 SOL-PERP @ ${OraclePrice} + .05
const orderParams = {
orderType: OrderType.LIMIT,
marketIndex: 0,
direction: PositionDirection.SHORT,
baseAssetAmount: driftClient.convertToPerpPrecision(100),
oraclePriceOffset: driftClient.convertToPricePrecision(.05).toNumber(),
}
await driftClient.placePerpOrder(orderParams);
from driftpy.types import *
from driftpy.constants.numeric_constants import BASE_PRECISION, PRICE_PRECISION
market_index = 0
# place order to long 1 SOL-PERP @ $21.88 (post only)
order_params = OrderParams(
order_type=OrderType.Limit(),
base_asset_amount=drift_client.convert_to_perp_precision(1),
market_index=market_index,
direction=PositionDirection.Long(),
price=drift_client.convert_to_price_precision(21.88),
post_only=PostOnlyParams.TryPostOnly(),
)
await drift_client.place_perp_order(order_params)
curl -X POST -H 'content-type: application/json' \
-d '{
"orders": [{
"marketIndex": 0,
"marketType": "perp",
"amount": 1.23,
"price": 80.0,
"postOnly": true,
"orderType": "limit",
"immediateOrCancel": false,
"reduceOnly": false
}]
}' \
localhost:8080/v2/orders
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
orderParams | The order params | No |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
order_params | The order params | No |
The order type is set to PERP / Perp() by default.
Placing Spot Order
const orderParams = {
orderType: OrderType.LIMIT,
marketIndex: 1,
direction: PositionDirection.LONG,
baseAssetAmount: driftClient.convertToSpotPrecision(1, 100),
price: driftClient.convertToPricePrecision(100),
}
await driftClient.placeSpotOrder(orderParams);
market_index = 1
order_params = OrderParams(
order_type=OrderType.Limit(),
base_asset_amount=drift_client.convert_to_spot_precision(100, market_index),
market_index=market_index,
direction=PositionDirection.Long(),
price=drift_client.convert_to_price_precision(100),
)
await driftClient.place_spot_order(order_params);
curl -X POST -H 'content-type: application/json' \
-d '{
"orders": [{
"marketIndex": 0,
"marketType": "spot",
"amount": -1.23,
"price": 80.0,
"postOnly": true,
"orderType": "limit",
"immediateOrCancel": false,
"reduceOnly": false
}]
}' \
localhost:8080/v2/orders
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
orderParams | The order params | No |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
order_params | The order params | No |
The order type is set to SPOT / Spot() by default.
Placing Multiple Orders
const placeOrderParams = [
{
orderType: OrderType.LIMIT,
marketType: MarketType.PERP,
marketIndex: 0,
direction: PositionDirection.LONG,
baseAssetAmount: driftClient.convertToPerpPrecision(100),
price: driftClient.convertToPricePrecision(21.23),
},
{
orderType: OrderType.LIMIT,
marketType: MarketType.PERP,
marketIndex: 0,
direction: PositionDirection.SHORT,
baseAssetAmount: driftClient.convertToPerpPrecision(100),
oraclePriceOffset: driftClient.convertToPricePrecision(.05).toNumber(),
}
];
await driftClient.placeOrders(placeOrderParams);
place_order_params = [
OrderParams(
order_type=OrderType.Limit(),
base_asset_amount=drift_client.convert_to_perp_precision(100),
market_index=0,
direction=PositionDirection.Long(),
market_type=MarketType.Perp(),
price=drift_client.convert_to_price_precision(21.23),
),
OrderParams(
order_type=OrderType.Limit(),
base_asset_amount=drift_client.convert_to_perp_precision(100),
market_index=0,
direction=PositionDirection.Short(),
market_type=MarketType.Perp(),
oracle_price_offset=drift_client.convert_to_price_precision(.05),
)
]
await drift_client.place_orders(place_order_params);
curl -X POST -H 'content-type: application/json' \
-d '{
"orders": [{
"marketIndex": 0,
"marketType": "spot",
"amount": 1.23,
"price": 80.0,
"postOnly": true,
"orderType": "limit",
"immediateOrCancel": false,
"reduceOnly": false
},
{
"marketIndex": 2,
"marketType": "perp",
"amount": 0.1,
"price": 10000.0,
"orderType": "market",
}]
}' \
localhost:8080/v2/orders
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
placeOrderParams | Parameters for place order instructions |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
place_order_params | Parameters for place order instructions |
Placing multiple orders in one tx can be cheaper than placing them in separate tx.
Placing Oracle Market Orders
const oraclePrice = driftClient.getOracleDataForPerpMarket(18).price;
const auctionStartPrice = oraclePrice.neg().divn(1000); // start auction 10bps below oracle
const auctionEndPrice = oraclePrice.divn(1000); // end auction 10bps above oracle
const oraclePriceOffset = oraclePrice.divn(500); // limit price after auction 20bps above oracle
const auctionDuration = 30; // 30 slots
const orderParams = {
orderType: OrderType.ORACLE,
baseAssetAmount: driftClient.convertToPerpPrecision(10),
direction: PositionDirection.LONG,
marketIndex: 18,
auctionStartPrice: auctionStartPrice,
auctionEndPrice: auctionEndPrice,
oraclePriceOffset: oraclePriceOffset,
auctionDuration: auctionDuration,
};
await driftClient.placePerpOrder(orderParams)
oracle_price = drift_client.get_oracle_price_data_for_perp_market(18).price
auction_start_price = -oracle_price // 1000 # start auction 10bps below oracle
auction_end_price = oracle_price // 1000 # end auction 10bps above oracle
oracle_price_offset = oracle_price // 500 # limit price after auction 20bps above oracle
auction_duration = 30 # 30 slots
order_params = OrderParams(
order_type=OrderType.Oracle(),
base_asset_amount=drift_client.convert_to_perp_precision(10),
market_index=18,
direction=PositionDirection.Long(),
auction_start_price=auction_start_price,
auction_end_price=auction_end_price,
oracle_price_offset=oracle_price_offset,
auction_duration=auction_duration
)
await drift_client.place_perp_order(order_params)
Oracle market orders enable a user to define their auction params as an offset (or relative to) the oracle price.
Canceling Order
const orderId = 1;
await driftClient.cancelOrder(orderId);
order_dd = 1;
await drift_client.cancel_order(order_id);
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
orderId | The order being canceled | No |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
order_id | The order being canceled | No | |
sub_account_id | Sub account to cancel order under | Yes | None |
curl -X DELETE -H 'content-type: application/json' \
-d '{ "ids": [1] }' \
localhost:8080/v2/orders
Canceling Order By User Order Id
const userOrderId = 1;
await driftClient.cancelOrderByUserOrderId(userOrderId);
const user_order_id = 1;
await drift_client.cancel_order_by_user_order_id(user_order_id);
curl -X DELETE -H 'content-type: application/json' \
-d '{ "userIds": [1] }' \
localhost:8080/v2/orders
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
userOrderId | Unique order id specified by user when order was placed | No |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
user_order_id | Unique order id specified by user when order was placed | No | |
sub_account_id | Sub account to cancel orders under | Yes | None |
Cancel Orders
const marketType = MarketType.PERP;
const marketIndex = 0;
const direction = PositionDirection.LONG;
await driftClient.cancelOrders(marketType, marketIndex, direction);
market_type = MarketType.Perp()
market_index = 0
direction = PositionDirection.Long()
await drift_client.cancel_orders(market_type, market_index, direction) # cancel bids in perp market 0
await drift_client.cancel_orders() # cancels all orders
curl -X DELETE -H 'content-type: application/json' \
-d '{ "marketIndex": 1, "marketType": "spot" }' \
localhost:8080/v2/orders
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
marketType | The market type of orders to cancel. Must be set if marketIndex set | Yes | |
marketIndex | The market index of orders to cancel. Must be set if marketType set | Yes | |
direction | The direction of orders to cancel. | Yes |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
market_type | The market type of orders to cancel | Yes | None |
market_index | The market index of orders to cancel | Yes | None |
direction | The direction of orders to cancel | Yes | None |
sub_account_id | The sub account from which to cancel orders | Yes | None |
To cancel all orders, do not set any parameters.
Cancel and Place Orders
const cancelOrderParams = {
marketType: MarketType.PERP,
marketIndex: 0,
};
const placeOrderParams = [
{
orderType: OrderType.LIMIT,
marketIndex: 0,
direction: PositionDirection.LONG,
baseAssetAmount: driftClient.convertToPerpPrecision(100),
price: driftClient.convertToPricePrecision(21.23),
},
{
orderType: OrderType.LIMIT,
marketIndex: 0,
direction: PositionDirection.SHORT,
baseAssetAmount: driftClient.convertToPerpPrecision(100),
oraclePriceOffset: driftClient.convertToPricePrecision(.05).toNumber(),
}
];
await driftClient.cancelAndPlaceOrders(cancelOrderParams, placeOrderParams);
canel_order_params = (MarketType.Perp(), 0, None) # cancel all orders in perp market 0
place_order_params = [
OrderParams(
order_type=OrderType.Limit(),
base_asset_amount=drift_client.convert_to_perp_precision(100),
market_index=0,
direction=PositionDirection.Long(),
market_type=MarketType.Perp(),
price=drift_client.convert_to_price_precision(21.23),
),
OrderParams(
order_type=OrderType.Limit(),
base_asset_amount=drift_client.convert_to_perp_precision(100),
market_index=0,
direction=PositionDirection.Short(),
market_type=MarketType.Perp(),
oracle_price_offset=drift_client.convert_to_price_precision(.05),
)
]
await drift_client.cancel_and_place_orders(canel_order_params, place_order_params);
curl -X POST -H 'content-type: application/json' \
-d '{
"cancel": {
"marketIndex": 0,
"marketType": "perp"
},
"place": {
"orders": [{
"marketIndex": 0,
"marketType": "perp",
"amount": -1.23,
"price": 80.0,
"postOnly": true,
"orderType": "limit",
"immediateOrCancel": false,
"reduceOnly": false
}]
}
}' \
localhost:8080/v2/orders/cancelAndPlace
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
cancelOrderParams | Parameters for cancel orders instruction | ||
placeOrderParams | Parameters for place order instructions |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
cancel_params | Tuple with optional MarketType, market index, and PositionDirection for canceling orders | Yes | None |
place_order_params | List of OrderParams for placing new orders | No | |
sub_account_id | The sub account to use | Yes | None |
To cancel all orders, do not set any parameters.
Modify Order Params
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
orderId | The order id of order to modify | No | |
baseAssetAmount | The amount of base asset to buy or sell | Yes | |
direction | The direction of order e.g. long (bid) or short (ask) | Yes | |
limitPrice | The limit price for order | Yes | |
reduceOnly | If the order can only reduce positions | Yes | |
postOnly | If the order can only be a maker | Yes | |
triggerPrice | at what price order is triggered. only applicable for triggerMarket and triggerLimit orders | Yes | |
triggerCondition | whether order is triggered above or below triggerPrice. only applicable for triggerMarket and triggerLimit orders | Yes | |
oraclePriceOffset | priceOffset for oracle derived limit price. only applicable for limit and oracle orders | Yes | |
auctionDuration | how many slots the auction lasts. only applicable for market and oracle orders | Yes | |
auctionStartPrice | the price the auction starts at | Yes | |
auctionEndPrice | the price the auction ends at | Yes | |
maxTs | the max timestamp before the order expires | Yes |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
direction | The direction of the order | Yes | None |
base_asset_amount | Amount of base asset to buy or sell | Yes | None |
price | Limit price for the order | Yes | None |
reduce_only | If the order should only reduce positions | Yes | None |
post_only | If the order should only be a maker order | Yes | None |
immediate_or_cancel | Whether the order is immediate or cancel | Yes | None |
max_ts | Max timestamp for order expiry | Yes | None |
trigger_price | Trigger price for trigger orders | Yes | None |
trigger_condition | Condition for triggering the order | Yes | None |
oracle_price_offset | Offset for oracle-derived limit price | Yes | None |
auction_duration | Duration of the auction in slots | Yes | None |
auction_start_price | Starting price of the auction | Yes | None |
auction_end_price | Ending price of the auction | Yes | None |
policy | Policy for modifying the order | Yes | None |
Modifying Order
const updateParams = {
orderId: 1,
newBaseAssetAmount: driftClient.convertToPerpPrecision(200),
}
await driftClient.modifyOrder(orderParams);
order_id = 1
modify_order_params = ModifyOrderParams(
base_asset_amount=drift_client.convert_to_perp_precision(1),
price=drift_client.convert_to_price_precision(20),
)
await drift_client.modify_order(order_id, modify_order_params);
curl -X PATCH -H 'content-type: application/json' \
-d '{
"orders": [{
"orderId": 32,
"amount": 1.05,
"price": 61.0
}]
}' \
localhost:8080/v2/orders
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
orderId | The order id of order to modify | No | |
modifyOrderParams | The modify order params | Yes |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
order_id | The order ID of the order to modify | No | |
modify_order_params | The parameters for modifying the order | No | |
sub_account_id | The sub account to use | Yes | None |
Modify cancels and places a new order
For typescript, the orderId and modifyOrderParams are merged into a single object and some properties are prefixed with new
e.g. newBaseAssetAmount
Modifying Order By User Order Id
const updateParams = {
userOrderId: 1,
newBaseAssetAmount: driftClient.convertToPerpPrecision(200),
}
await driftClient.modifyOrderByUserOrderId(orderParams);
user_order_id = 1
modify_order_params = ModifyOrderParams(
base_asset_amount=drift_client.convert_to_perp_precision(1),
price=drift_client.convert_to_price_precision(20),
)
await drift_client.modify_order_by_user_id(user_order_id, modify_order_params);
curl -X PATCH -H 'content-type: application/json' \
-d '{
"orders": [{
"userOrderId": 69,
"amount": 1.05,
"price": 61.0
}]
}' \
localhost:8080/v2/orders
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
userOrderId | The user order id of order to modify | No | |
modifyOrderParams | The modify order params | Yes |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
user_order_id | The user order ID of the order to modify | No | |
modify_order_params | The parameters for modifying the order | No | |
sub_account_id | The sub-account ID associated with the order | Yes | None |
Modify cancels and places a new order
For typescript, the userOrderId and modifyOrderParams are merged into a single object and some properties are prefixed with new
e.g. newBaseAssetAmount
Settle Perp PNL
const marketIndex = 0;
const user = driftClient.getUser();
await driftClient.settlePNL(
user.userAccountPublicKey,
user.getUserAccount(),
marketIndex
);
market_index = 0
user = drift_client.get_user()
await drift_client.settle_pnl(
user.user_public_key,
user.get_user_account(),
market_index
)
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
settleeUserAccountPublicKey | User address you're settling pnl for | No | |
settleeUserAccount | User account data you're settling pnl for | No | |
marketIndex | Market index for the perp market | No |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
settlee_user_account_public_key | Public key of the user account to settle PNL for | No | |
settlee_user_account | User account data for PNL settlement | No | |
market_index | Index of the perpetual market for PNL settlement | No |
Get Spot Market Account
const marketIndex = 1;
const spotMarketAccount = driftClient.getSpotMarketAccount(marketIndex);
market_index = 0;
spot_market_account = drift_client.get_spot_market_account(market_index);
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
marketIndex | The market index for the spot market | No |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
market_index | The market index for the spot market | No |
Get Perp Market Account
const marketIndex = 0;
const perpMarketAccount = driftClient.getPerpMarketAccount(marketIndex);
market_index = 0;
perp_market_account = drift_client.get_perp_market_account(market_index);
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
marketIndex | The market index for the perp market | No |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
market_index | The market index for the perp market | No |
User
Get User
const user = driftClient.getUser();
user = drift_client.get_user();
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
subAccountId | The sub account id of user to get | Yes | active sub account |
authority | The authority of user to get. Only necessary if using multiple delegate accounts | Yes | current authority |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
sub_account_id | The sub account id of user to get | Yes | active sub account |
Getting Deposit/Borrow Amounts
const marketIndex = 0;
const tokenAmount = user.getTokenAmount(
marketIndex,
);
const isDeposit = tokenAmount.gte(new BN(0));
const isBorrow = tokenAmount.lt(new BN(0));
market_index = 0
token_amount = user.get_token_amount(marketIndex)
is_deposit = token_amount > 0
is_borrow = token_amount < 0
curl -X GET \
-H 'content-type: application/json' \
-d '{"marketIndex":0,"marketType":"spot"}' \
localhost:8080/v2/positions
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
marketIndex | Market index for the spot market | No |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
market_index | Market index for the spot market | No |
If token amount is greater than 0, it is a deposit. If less than zero, it is a borrow.
Get Perp Position
const marketIndex = 0;
const baseAssetAmount = user.getPerpPosition(
marketIndex,
)?.baseAssetAmount;
const isLong = baseAssetAmount.gte(new BN(0));
const isShort = baseAssetAmount.lt(new BN(0));
market_index = 0
perp_position = user.get_perp_position(market_index)
base_asset_amount = perp_position.base_asset_amount if perp_position is not None else 0
is_long = base_asset_amount > 0
is_short = base_asset_amount < 0
curl -X GET \
-H 'content-type: application/json' \
-d '{"marketIndex":0,"marketType":"perp"}' \
localhost:8080/v2/positions
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
marketIndex | Market index for the perp market | No |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
market_index | Market index for the perp market | No |
If base amount is greater than 0, it is a long. If less than zero, it is a short.
Get Order
const orderId = 1;
const order = user.getOrder(
orderId,
);
order_id = 1
order = user.get_order(order_id)
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
orderId | Order id for the order you're getting | No |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
order_id | Order id for the order you're getting | No |
Get Order By User Order Id
const userOrderId = 1;
const order = user.getOrderByUserOrderId(
userOrderId,
);
user_order_id = 1
order = user.get_order_by_user_order_id(user_order_id)
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
userOrderId | User order id for the order you're getting | No |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
user_order_id | User order id for the order you're getting | No |
Get Open Orders
const orders = user.getOpenOrders();
orders = user.get_open_orders()
curl localhost:8080/v2/orders
Get Unrealized Perp Pnl
const pnl = user.getUnrealizedPNL();
pnl = user.get_unrealized_pnl()
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
withFunding | Whether to include unsettled funding payments | Yes | false |
marketIndex | Index of a specific market for PNL calculation | Yes | |
withWeightMarginCategory | To include margin category weighting in PNL calculation | Yes | |
strict | Whether the calculation should be strict | Yes | false |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
with_funding | Whether to include unsettled funding payments | Yes | false |
market_index | Index of a specific market for PNL calculation | Yes | |
with_weight_margin_category | To include margin category weighting in PNL calculation | Yes | |
strict | Whether the calculation should be strict | Yes | false |
Get Unrealized Funding Pnl
const pnl = user.getUnrealizedFundingPNL();
pnl = user.get_unrealized_funding_pnl()
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
marketIndex | Whether to only return pnl for specific market | Yes |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
market_index | Whether to only return pnl for specific market | Yes |
Get Total Collateral
const totalCollateral = user.getTotalCollateral();
total_collateral = user.get_total_collateral()
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
marginCategory | Initial or Maintenance | Yes | Initial |
strict | Whether the calculation should be strict | Yes | false |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
margin_category | Initial or Maintenance | Yes | Initial |
strict | Whether the calculation should be strict | Yes | false |
Asset weights vary based on whether you're checking the initial or maintenance margin requirement. Initial is used for initial leverage extension, maintenance for determining liquidations.
Get Margin Requirement
const marginRequirement = user.getMarginRequirement();
margin_requirement = user.get_margin_requirement()
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
marginCategory | The type of margin (Initial or Maintenance) | No | |
liquidationBuffer | Buffer value for liquidation calculation | Yes | |
strict | Whether the calculation should be strict | Yes | false |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
margin_category | Type of margin, either Initial or Maintenance | Yes | MarginCategory.INITIAL |
liquidation_buffer | Additional buffer value for liquidation calculation | Yes | 0 |
strict | Whether the calculation should be strict | Yes | False |
Liability weights (for borrows) and margin ratios (for perp positions) vary based on whether you're checking the initial or maintenance margin requirement. Initial is used for initial leverage extension, maintenance for determining liquidations.
Get Free Collateral
const freeCollateral = user.getFreeCollateral();
free_collateral = user.get_free_collateral()
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
marginCategory | Initial or Maintenance | Yes | Initial |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
marginCategory | Type of margin, either Initial or Maintenance | Yes | MarginCategory.INITIAL |
Free collateral is the difference between your total collateral and your margin requirement.
Get Leverage
const leverage = user.getLeverage();
leverage = user.get_leverage()
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
includeOpenOrders | Whether to factor in open orders in position size | Yes | true |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
include_open_orders | Whether to factor in open orders in position size | Yes | True |
Leverage is the total liability value (borrows plus total perp position) divided by net asset value (total assets plus total liabilities)
Events
Event Subscription
import {EventSubscriber} from "@drift-labs/sdk";
const options = {
eventTypes: [
'DepositRecord',
'FundingPaymentRecord',
'LiquidationRecord',
'OrderRecord',
'OrderActionRecord',
'FundingRateRecord',
'NewUserRecord',
'SettlePnlRecord',
'LPRecord',
'InsuranceFundRecord',
'SpotInterestRecord',
'InsuranceFundStakeRecord',
'CurveRecord',
],
maxTx: 4096,
maxEventsPerType: 4096,
orderBy: 'blockchain',
orderDir: 'asc',
commitment: 'confirmed',
logProviderConfig: {
type: 'websocket',
},
}
const eventSubscriber = new EventSubscriber(connection, driftClient.program, options);
await eventSubscriber.subscribe();
eventSubscriber.eventEmitter.on('newEvent', (event) => {
console.log(event);
});
from driftpy.events.event_subscriber import EventSubscriber
from driftpy.events.types import WrappedEvent, EventSubscriptionOptions, WebsocketLogProviderConfig
options = EventSubscriptionOptions(
event_types = (
'DepositRecord',
'FundingPaymentRecord',
'LiquidationRecord',
'OrderRecord',
'OrderActionRecord',
'FundingRateRecord',
'NewUserRecord',
'SettlePnlRecord',
'LPRecord',
'InsuranceFundRecord',
'SpotInterestRecord',
'InsuranceFundStakeRecord',
'CurveRecord',
),
max_tx= 4096,
max_events_per_type=4096,
order_by="blockchain",
order_dir="asc",
commitment="processed",
log_provider_config=WebsocketLogProviderConfig()
)
event_subscriber = EventSubscriber(connection, drift_client.program, options)
event_subscriber.subscribe()
event_subscriber.event_emitter.new_event += lambda event: print(event)
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
connection | Connection object specifying solana rpc url | No | |
program | Anchor program object used to deserialize events from transaction logs | No | |
options.eventTypes | Which events types to trigger event callbacks for | Yes | All events |
options.maxTx | Max number of transactions to keep in memory | Yes | 4096 |
options.maxEventsPerType | Max number of events per event type to keep in memory | Yes | 4096 |
options.orderBy | Whether to sort the tx in memory by the order they occurred on chain ('blockchain') or received by client ('client') | Yes | 'blockchain' |
options.orderDir | Whether to sort the tx in memory to be most recent ('desc') or oldest ('asc') | Yes | 'asc' |
options.commitment | What transaction commitment to wait for | Yes | 'confirmed' |
options.logProviderConfig | Whether to use websocket or polling to listen for tx logs | Yes | {type: "websocket"} |
options.address | Which address to listen to events for. Defaults to drift program. | Yes | dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH |
Python
Parameter | Description | Optional | Default |
---|---|---|---|
connection | AsyncClient object specifying Solana RPC URL | No | |
program | Anchor program object used to deserialize events from transaction logs | No | |
options | Configuration options for event subscription | Yes | EventSubscriptionOptions.default() |
- address | Address to listen to events for, defaults to Drift program | Yes | DRIFT_PROGRAM_ID |
- event_types | Types of events to trigger event callbacks for | Yes | DEFAULT_EVENT_TYPES |
- max_events_per_type | Maximum number of events per event type to keep in memory | Yes | 4096 |
- order_by | Sort order of transactions, by 'blockchain' order or 'client' received order | Yes | "blockchain" |
- order_dir | Sorting direction, either most recent ('desc') or oldest ('asc') | Yes | "asc" |
- commitment | Transaction commitment level to wait for | Yes | "confirmed" |
- max_tx | Maximum number of transactions to keep in memory | Yes | 4096 |
- log_provider_config | Configuration for log provider, either websocket or polling | Yes | WebsocketLogProviderConfig() |
- until_tx | Signature to listen until, used for log provider | Yes | None |
Protocol events are recorded in transactions logs. To listen for events, one must subscribe to the drift program's transaction logs.
Event Types
Event Type | Description |
---|---|
DepositRecord | A record of a user depositing or withdrawing funds from protocol |
FundingPaymentRecord | A record of a user paying/receiving funding payments |
LiquidationRecord | A record of a user being liquidated |
OrderRecord | A record of a user placing an order, including all of its parameters |
OrderActionRecord | A record of a user action on an order, including place, cancel and fill |
FundingRateRecord | A record of the funding rate changing |
NewUserRecord | A record of a new user |
SettlePnlRecord | A record of a user settling their pnl |
LPRecord | A record of a user adding or removing passive perp liquidity |
InsuranceFundRecord | A record of the insurance fund changing |
SpotInterestRecord | A record of the spot interest changing |
InsuranceFundStakeRecord | A record of a user staking or unstaking from the insurance fund |
CurveRecord | A record of the amm curve updating |
Listening to Perp Market Fills
const marketIndex = 0;
const isPerpMarketFill = (event) => {
if (event.eventType !== 'OrderActionRecord') {
return false;
}
if (event.marketIndex !== marketIndex) {
return false;
}
if (!isVariant(event.marketType, 'perp')) {
return false;
}
if (!isVariant(event.action, 'fill')) {
return false;
}
return true;
};
const fillCallback = (event) => {
console.log(event);
}
eventSubscriber.eventEmitter.on('newEvent', (event) => {
if (isPerpMarketFill(event)) {
fillCallback(event);
}
});
market_index = 0
def fill_callback(event: WrappedEvent):
if event.event_type != "OrderActionRecord":
return
if event.data.market_index != market_index:
return
if not is_variant(event.data.market_type, "Perp"):
return
if not is_variant(event.data.action, "Fill"):
return
print(event)
event_subscriber.event_emitter.new_event += fill_callback
Listening to User Fills
options = EventSubscriptionOptions(
address=drift_client.get_user_account_public_key(),
)
event_subscriber = EventSubscriber(connection, drift_client.program, options)
event_subscriber.subscribe()
def fill_callback(event: WrappedEvent):
if event.event_type != "OrderActionRecord":
return
if not is_variant(event.data.action, "Fill"):
return
is_taker = event.data.taker == drift_client.get_user_account_public_key()
is_maker = event.data.taker == drift_client.get_user_account_public_key()
if not is_taker and not is_maker:
return
print(event)
event_subscriber.event_emitter.new_event += fill_callback
Getting Events Received By Type
const eventType = 'OrderActionRecord';
const events = eventSubscriber.getEventsReceived(eventType);
event_type = 'OrderActionRecord'
events = event_subscriber.get_events_array(event_type)
This returns all the events that the event subscriber currently has stored in memory.
Getting Events By Transaction
const txSig = '3dq5PtQ3VnNTkQRrHhQ1nRACWZaFVvSBKs1RLXM8WvCqLHTzTuVGc7XER5awoLFLTdJ4kqZiNmo7e8b3pXaEGaoo';
const events = eventSubscriber.getEventsByTx(txSig);
tx_sig = '3dq5PtQ3VnNTkQRrHhQ1nRACWZaFVvSBKs1RLXM8WvCqLHTzTuVGc7XER5awoLFLTdJ4kqZiNmo7e8b3pXaEGaoo'
events = event_subscriber.get_events_by_tx(tx_sig)
This returns the events that the event subscriber currently has stored in memory for a given transaction.
Margin System
Drift offers a cross-collateral margining system, allowing users to utilize multiple assets as trading collateral.
The margining system tracks each user's total collateral, the weighted sum of the user's deposits and perp pnl, as well as their margin requirement, the weighted value out the user's outstanding borrow and perp positions.
Total collateral is calculated as:
\[\sum_{i=1}^n d_i \cdot p_i \cdot w_i^a\ +\ \sum_{j=1}^n pnl_j \cdot qp_j \cdot w_j^{pnl} \]
Where
- \(d_i\) is the deposit amount for spot market \(i\)
- \(p_i\) is the price for spot market \(i\)
- \(w_i^a\) is the asset weight for spot market \(i\)
- \(pnl_j\) is the pnl for perp market \(j\)
- \(qp_j\) is the quote asset price for perp market \(j\)
- \(w_j^{pnl}\) is the pnl weight for perp market \(j\)
Margin requirement is calculated as:
\[ \sum_{i=1}^n b_i \cdot p_i \cdot w_i^l\ +\ \sum_{j=1}^n ba_j \cdot o_j \cdot qp_j \cdot m_j \]
Where
- \(b_i\) is the borrow amount for spot market \(i\)
- \(p_i\) is the price for spot market \(i\)
- \(w_i^l\) is the liability weight for spot market \(i\)
- \(ba_j\) is the base amount for perp market \(j\)
- \(o_j\) is the price for perp/prediction market \(j\)
- \(qp_j\) is the quote asset price for perp market \(j\)
- \(m_j\) is the margin ratio for perp market \(j\)
The weights and margin ratios depend on whether you're calculating the initial or maintenance values.
The initial maintenance check governs leverage extension. To open a new perp position or borrow, a user's initial total collateral must be greater than their initial margin requirement.
The maintenance check governs when a user's position must be liquidated. If a user's maintenance total collateral drops below their maintenance margin requirement, a user's position can be liquidated to reduce their risk.
The prices used for deposits, borrows and perp quote assets differ between the initial and maintenance checks. The maintenance check uses
the current oracle price. The initial check uses the min(oracle_price, oracle_twap)
for deposits and positive perp pnl and max(oracle_price, oracle_twap)
for borrows, negative perp pnl and perp base amount.
For prediction markets, the price used in the margin system depends on the direction of the position. For short positions (betting no), the margin system uses 1 - oracle price. For long position (betting yes), it uses the oracle price. This is the account for the user's worst case loss. E.g. if a user shorts at $.01, their worst case loss is $.99.
Numerical Precisions
To maintain numerical precision, the on-chain program stores all values as integers.
Getting a Current Perp Position
const perpMarketIndex = 0; // SOL-PERP
const perpPosition = driftClient.getPerpPosition(perpMarketIndex);
console.log(convertToNumber(perpPosition.baseAssetAmount, BASE_PRECISION));
perp_market_index = 0 # SOL-PERP
perp_position = drift_client.get_perp_position(market_index)
print(convert_to_number(perp_position.base_asset_amount, BASE_PRECISION))
This prints the size of the current perp position in perp market index 0 (SOL-PERP)
Getting a Current Spot Position
const spotMarketIndex = 0; // USDC
const spotConfig = SpotMarkets['mainnet-beta'][spotMarketIndex];
const spotMarket = driftClient.getSpotMarketAccount(spotMarketIndex);
const spotPosition = driftClient.getSpotPosition(spotMarketIndex);
const tokenAmount = getTokenAmount(spotPosition.scaledBalance, spotMarket, spotPosition.balanceType);
console.log(convertToNumber(tokenAmount, spotConfig.precision));
spot_market_index = 0 # USDC
spot_market = drift_client.get_spot_market_account(spot_market_index)
spot_position = drift_client.get_spot_position(spot_market_index)
token_amount = get_token_amount(spot_psoition.scaled_balance, spot_market, spot_position.balance_type)
print(convert_to_number(token_amount, (10 ** spot_market.decimals)))
This prints the current spot position in spot market index 0 (USDC). This value is the same as the value shown on the UI, it includes any accumulated interest.
Common precision values
Value | Precision | Constant |
---|---|---|
perp base asset amount | 1e9 | BASE_PRECISION |
perp quote asset amount | 1e6 | QUOTE_PRECISION |
price | 1e6 | PRICE_PRECISION |
funding rate | 1e9 | FUNDING_RATE_PRECISION |
spot token amount | derived from token mint's decimals (USDC is 1e6, SOL is 1e9) | SpotMarketConfig.precision |
spot token balance | 1e9 | SPOT_MARKET_BALANCE_PRECISION |
margin ratio | 1e4 | MARGIN_PRECISION |
asset/liability weight | 1e4 | SPOT_WEIGHT_PRECISION |
Examples: Bots
Typescript
Perp Markets
- perp filler
- floating maker
- jit maker
- funding rate update
- pnl settler
Spot Markets
- spot filler
- insurance fund revenue settler
Python
Examples: Atomic Place&Take
Place And Take Orders
Place And Take orders allow you to place an order with an atomic fill in the same transaction. There are a few benefits to using them:
- They are the fastest way to get into a position.
- They allow the use of a "fill or kill" style flag to ensure that the transaction fails unless the order is filled.
- These are the easiest way to integrate Drift orders into other products - you don't need to wait to see if the order is filled.
The one caveat to these orders is that there is an extra upfront cost in that you need to know ahead of time which onchain maker order you want to be filled against. Drift has some off-chain infrastructure which provides an API to help you do this.
/**
* Making a Place And Take with atomic "fill-or-kill" flag to ensure the transaction only succeeds if the entire order is filled
*
**/
import {
decodeUser,
PublicKey,
MarketType,
BASE_PRECISION,
PlaceAndTakeOrderSuccessCondition,
UserAccount,
isVariant,
PositionDirection,
DriftClient,
getUserStatsAccountPublicKey,
BN,
OrderType,
} from '@drift-labs/sdk';
// Method to fetch the current top makers taking advantage of Drift's off-chain infra which can provide these. You may eventually want to keep track of the state of on-chain makers yourself.
async function getTopMakersForPlaceAndTake({
marketIndex,
marketType,
side,
}: {
marketIndex: number;
marketType: MarketType;
side: 'bid' | 'ask';
}): Promise<
{
userAccountPubKey: PublicKey;
userAccount: UserAccount;
}[]
> {
const dlobServerUrl = `https://dlob.drift.trade/`;
const marketTypeStr = isVariant(marketType, 'perp') ? 'perp' : 'spot';
const limit = 4; // NOTE: This parameter controls the number of top makers that will be returned. It is suggested not to use more than 4, in our current testing the size of the transaction will larger than the current limits if you pass more than 4 makers in.
const queryParams = `marketIndex=${marketIndex}&marketType=${marketTypeStr}&side=${side}&limit=${limit}&includeAccounts=true`;
const result = await new Promise<
{
userAccountPubKey: string;
accountBase64: string;
}[]
>((res) => {
fetch(`${dlobServerUrl}/topMakers?${queryParams}`)
.then(async (response) => {
if (!response.ok) {
// Handle failure
return;
}
res(await response.json());
})
.catch((err) => {
// Handle failure
});
});
return result.map(
(value: { userAccountPubKey: string; accountBase64: string }) => {
return {
userAccountPubKey: new PublicKey(value.userAccountPubKey),
userAccount: decodeUser(Buffer.from(value.accountBase64, 'base64')),
};
}
);
return [];
}
// Fetch and process the top makers result into params for a place and take order
async function getMakerInfoForPlaceAndTake(
orderDirection: PositionDirection,
orderMarketIndex: number,
orderMarketType: MarketType,
driftClient: DriftClient
) {
const topMakers = await getTopMakersForPlaceAndTake({
marketIndex: orderMarketIndex,
marketType: orderMarketType,
side: isVariant(orderDirection, 'long') ? 'ask' : 'bid', // If we're going LONG, we want the makers on the ASK side, and vice-versa
});
const makerAccountKeys = topMakers.map((maker) => maker.userAccountPubKey);
const makerStatsAccountKeys = topMakers.map((makerAccount) =>
getUserStatsAccountPublicKey(
driftClient.program.programId,
makerAccount.userAccount.authority
)
);
const makerAccounts = topMakers.map((maker) => maker.userAccount);
return makerAccountKeys.map((makerUserAccountKey, index) => {
return {
maker: makerUserAccountKey,
makerUserAccount: makerAccounts[index],
makerStats: makerStatsAccountKeys[index],
};
});
}
// Create a PlaceAndTake Instruction. DriftClient also has a `placeAndTakePerpOrder` If you want DriftClient to create+sign+send the transaction if you don't want to manually handle the instruction.
async function makePlaceAndTakePerpOrderIx(
orderDirection: PositionDirection,
orderMarketIndex: number,
orderSizeBase: BN,
successCondition: PlaceAndTakeOrderSuccessCondition,
driftClient: DriftClient
) {
const makerInfos = await getMakerInfoForPlaceAndTake(
orderDirection,
orderMarketIndex,
MarketType.PERP,
driftClient
);
const placeAndTakeIx = await driftClient.getPlaceAndTakePerpOrderIx(
{
direction: orderDirection,
baseAssetAmount: orderSizeBase,
marketIndex: orderMarketIndex,
marketType: MarketType.PERP,
orderType: OrderType.MARKET,
},
makerInfos,
undefined,
successCondition
);
return placeAndTakeIx;
}
// Example: Make a LONG order for 1 SOL-PERP which will "fill-or-kill" if the entire order isn't filled atomically.
const driftClient = DRIFT_CLIENT; // Assume you have a Drift Client set up and ready to go, see https://drift-labs.github.io/v2-teacher/#client-initialization
const sizeInSol = 1;
await makePlaceAndTakePerpOrderIx(
PositionDirection.LONG,
0, // SOL-PERP's market index is 0
new BN(sizeInSol).mul(BASE_PRECISION),
PlaceAndTakeOrderSuccessCondition.FullFill, // This ensures the order is completely filled, or the tx fails.
driftClient,
)
Orderbook (Blockchain)
The drift orderbook is a collection of all open orders on the Drift protocol. There is no single source of truth for the orderbook. The most up to date view of the orderbook is one where you track user orders and maintain the orderbook structure yourself. This section shows how to do that with the various SDKs.
Dlob Source
This is the main source of orders for maintaing the orderbook.
Dlob Source - UserMap
UserMap
stores a complete map of all user accounts (idle users are commonly filtered ou).
import {Connection} from "@solana/web3.js";
import {DriftClient, UserMap, Wallet, loadKeypair} from "@drift-labs/sdk";
const connection = new Connection("https://api.mainnet-beta.solana.com", 'confirmed');
const keyPairFile = `${process.env.HOME}/.config/solana/my-keypair.json`;
const wallet = new Wallet(loadKeypair(keyPairFile))
const driftClient = new DriftClient({
connection,
wallet,
env: 'mainnet-beta',
});
await driftClient.subscribe();
// polling keep users updated with periodic calls to getProgramAccounts
// websocket keep users updated via programSubscribe
const subscriptionConfig:
| {
type: 'polling';
frequency: number;
commitment?: Commitment;
} | {
type: 'websocket';
resubTimeoutMs?: number;
commitment?: Commitment;
} = {
type: 'websocket',
resubTimeoutMs: 30_000,
commitment: stateCommitment,
};
const userMap = new UserMap({
driftClient,
connection,
subscriptionConfig,
skipInitialLoad: false, // skips initial load of user accounts
includeIdle: false, // filters out idle users
});
await userMap.subscribe();
from solana.rpc.async_api import AsyncClient
from solders.keypair import Keypair
from anchorpy import Wallet
from driftpy.drift_client import DriftClient
from driftpy.keypair import load_keypair
from driftpy.user_map.user_map import UserMap
from driftpy.user_map.user_map_config import UserMapConfig, WebsocketConfig
connection = AsyncClient("https://api.mainnet-beta.solana.com") # switch for your own rpc
keypair_file = "" # path to keypair file
wallet = Wallet(load_keypair(keypair_file))
drift_client = DriftClient(
connection,
wallet,
"mainnet"
)
await drift_client.subscribe()
user_map_config = UserMapConfig(drift_client, WebsocketConfig())
user_map = UserMap(user_map_config)
await user_map.subscribe()
Dlob Source - OrderSubscriber
OrderSubscriber
is a more efficient version of UserMap
, only tracking user accounts that have orders.
import {Connection} from "@solana/web3.js";
import {DriftClient, OrderSubscriber, Wallet, loadKeypair} from "@drift-labs/sdk";
const connection = new Connection("https://api.mainnet-beta.solana.com", 'confirmed');
const keyPairFile = `${process.env.HOME}/.config/solana/my-keypair.json`;
const wallet = new Wallet(loadKeypair(keyPairFile))
const driftClient = new DriftClient({
connection,
wallet,
env: 'mainnet-beta',
});
await driftClient.subscribe();
const subscriptionConfig:
| {
type: 'polling',
frequency: ORDERBOOK_UPDATE_INTERVAL,
commitment: stateCommitment,
}
| {
type: 'websocket',
commitment: stateCommitment,
resyncIntervalMs: WS_FALLBACK_FETCH_INTERVAL,
} = {
type: 'websocket',
commitment: stateCommitment,
resyncIntervalMs: WS_FALLBACK_FETCH_INTERVAL, // periodically resyncs the orders in case of missed websocket messages
};
const orderSubscriber = new OrderSubscriber({
driftClient,
subscriptionConfig,
});
await orderSubscriber.subscribe();
Orderbook Subscription
With a DlobSource
you can then subscribe to the orderbook.
import {DLOBSubscriber} from "@drift-labs/sdk";
const dlobSubscriber = new DLOBSubscriber({
driftClient,
dlobSource: orderSubscriber, // or UserMap
slotSource: orderSubscriber, // or UserMap
updateFrequency: 1000,
});
await dlobSubscriber.subscribe();
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
driftClient | DriftClient object | No | |
dlobSource | Where to build the orderbook from. Can subscribe to user accounts on-chain using UserMap or request DLOB from api using DLOBApiClient | No | |
slotSource | Where to get slot from | No | |
updateFrequency | How often to rebuild the orderbook from the dlobSource in milliseconds | No |
from driftpy.dlob.dlob_subscriber import DLOBSubscriber
from driftpy.dlob.client_types import DLOBClientConfig
from driftpy.slot.slot_subscriber import SlotSubscriber
slot_subscriber = SlotSubscriber(drift_client)
config = DLOBClientConfig(drift_client, user_map, slot_subscriber, 1)
dlob_subscriber = DLOBSubscriber(config = config)
await dlob_subscriber.subscribe()
Python
Parameter | Description | Optional | Default |
---|---|---|---|
drift_client | DriftClient object | No | |
dlob_source | Where to build the orderbook from. Can subscribe to user accounts on-chain using UserMap. | No | |
slot_source | Where to get slot from | No | |
update_frequency | How often to rebuild the orderbook from the dlob_source in milliseconds | No |
Get L2 Orderbook
const l2 = dlobSubscriber.getL2({
marketName: 'SOL-PERP',
depth: 50,
});
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
marketName | The market name of the orderbook to get. If not set, marketIndex and marketType must be set | Yes | |
marketIndex | The market index of the orderbook to get. If not set, marketName must be set | Yes | |
marketType | The market type of the orderbook to get. If not set, marketName must be set | Yes | |
depth | The depth of the orderbook to get | Yes | 10 |
includeVamm | Whether to include vAMM | Yes | false |
fallbackL2Generators | L2OrderbookGenerators for fallback liquidity e.g. vAmm, openbook, phoenix. Unnecessary if includeVamm is true | Yes |
l2 = dlob_subscriber.get_l2_orderbook_sync("SOL-PERP")
Python
Parameter | Description | Optional | Default |
---|---|---|---|
market_name | The market name of the orderbook to get. If not set, market_index and market_type must be set | Yes | |
market_index | The market index of the orderbook to get. If not set, market_name must be set | Yes | |
market_type | The market type of the orderbook to get. If not set, market_name must be set | Yes | |
depth | The depth of the orderbook to get | Yes | 10 |
include_vamm | Whether to include vAMM | Yes | false |
num_vamm_orders | Number of orders to include from the vAMM. If not provided, depth is used. | Yes | |
fallback_l2_generators | L2OrderbookGenerators for fallback liquidity e.g. vAmm, openbook, phoenix. Unnecessary if includeVamm is true | Yes |
The L2 orderbook is an aggregate of drift dlob orders and, optionally, fallback liquidity.
Get L3 Orderbook
const l3 = dlobSubscriber.getL3({
marketName: 'SOL-PERP',
});
TypeScript
Parameter | Description | Optional | Default |
---|---|---|---|
marketName | The market name of the orderbook to get. If not set, marketIndex and marketType must be set | Yes | |
marketIndex | The market index of the orderbook to get. If not set, marketName must be set | Yes | |
marketType | The market type of the orderbook to get. If not set, marketName must be set | Yes |
l3 = dlob_subscriber.get_l3_orderbook_sync("SOL-PERP")
Python
Parameter | Description | Optional | Default |
---|---|---|---|
market_name | The market name of the orderbook to get. If not set, market_index and market_type must be set | Yes | |
market_index | The market index of the orderbook to get. If not set, market_name must be set | Yes | |
market_type | The market type of the orderbook to get. If not set, market_name must be set | Yes |
The L3 orderbook contains every maker order on drift dlob, including the address for the user that placed the order.
Orderbook/Trades (DLOB Server)
Drift runs a dlob-server
to reduce the RPC load on UI users and traders. You can access this server (or run your own!) instead of maintaing an order book from the blockchain.
mainnet-beta: https://dlob.drift.trade/ devnet: https://master.dlob.drift.trade/
All endpoints follow the same query parameter scheme to specify a market:
Parameter | Description | Optional | Default |
---|---|---|---|
marketName | The market name of the orderbook to get. If not set, marketIndex and marketType must be set | Yes | |
marketIndex | The market index of the orderbook to get. If not set, marketName must be set | Yes | |
marketType | The market type of the orderbook to get. If not set, marketName must be set | Yes |
GET /l2
GET /l3
Returns an L2 (aggregate price levels) or L3 (individual orders) orderbook for the specificed market.
Parameter | Description | Optional | Default | L2 only |
---|---|---|---|---|
depth | Number of records to return per side | Yes | all orders | Yes |
includeVamm | true to include vAMM liquidity in the response |
Yes | false |
Yes |
includeOracle | true to include oracle data with the response |
Yes | false |
No |
Example: https://dlob.drift.trade/l2?marketName=JTO-PERP&depth=10&includeOracle=true&includeVamm=true
Example: https://dlob.drift.trade/l3?marketName=JTO-PERP&includeOracle=true
GET /topMakers
Returns the top makers (currently returns an exhaustive list) for a given market (useful for place_and_take
orders).
Parameter | Description | Optional | Default |
---|---|---|---|
side | Side to return makers for (bid or ask ) |
No | |
limit | Limit number of makers to return | Yes | all |
includeUserStats | true to include full UserStats |
Yes | false |
Example: https://dlob.drift.trade/topMakers?marketName=JTO-PERP&side=bid&limit=5
Websocket
The mainnet-beta websocket endpoint is: wss://dlob.drift.trade/ws
Drift currently offers websocket streaming for two data streams: orderbooks and trades. The data streams are constructed from blockhain data. More data sources and user specific feeds will be added as this functionality is expanded.
mainnet-beta: wss://dlob.drift.trade/ws
devnet: wss://master.dlob.drift.trade/ws
Websocket - Subscribing
Clients must send subscribe messages over the websocket connection in order to start receiving messages.
Orderbook
Each market currently requires its own subscribe message. The two examples below show subscribe messages for a perp and spot market:
Perp markets
{
"type": "subscribe",
"marketType": "perp",
"channel": "orderbook",
"market": "SOL-PERP"
}
Response
Field | Description |
---|---|
channel | Identifies the type of data being streamed. orderbook_perp_0 indicates data for perpetual market 0 order book (SOL-PERP). |
data | Contains the actual order book data in JSON format. This field is a JSON string that needs to be parsed. |
bids | A list of bid orders in the order book, each representing an offer to buy. |
asks | A list of ask orders in the order book, each representing an offer to sell. |
price | The price at which the bid or ask is made. |
size | The size of the bid or ask, indicating the quantity of the asset the buyer or seller wishes to transact. |
sources | Indicates the origin or source of the bid or ask, such as vamm (Virtual Automated Market Maker) or dlob (Decentralized Limit Order Book). |
marketName | Name of the market, e.g., SOL-PERP . |
marketType | Type of the market, e.g., perp for perpetual. |
marketIndex | Index of the market, used to identify specific markets within a market type. |
slot | Solana slot. |
oracle | The reported price from the oracle for this market. |
oracleData | Contains detailed information from the oracle, including price, slot, confidence, etc. |
oracleData.price | The price reported by the oracle. |
oracleData.slot | The slot number associated with the oracle data. |
oracleData.confidence | The confidence interval for the oracle price. |
oracleData.hasSufficientNumberOfDataPoints | Indicates whether the oracle has sufficient data points for reliability. |
oracleData.twap | The time-weighted average price as reported by the oracle. |
oracleData.twapConfidence | The confidence interval for the time-weighted average price. |
marketSlot | Slot number associated with the market data. |
Spot markets
{
"type": "subscribe",
"marketType": "spot",
"channel": "orderbook",
"market": "SOL"
}
Response
Field | Description |
---|---|
channel | Identifies the type of data being streamed. i.e orderbook_spot_1 indicates data for the spot market order book (SOL/USDC). |
data | Contains the actual order book data in JSON format. This field is a JSON string that needs to be parsed. |
bids | A list of bid orders in the order book, each representing an offer to buy. |
asks | A list of ask orders in the order book, each representing an offer to sell. |
price | The price at which the bid or ask is made. |
size | The size of the bid or ask, indicating the quantity of the asset the buyer or seller wishes to transact. |
sources | Indicates the origin or source of the bid or ask, such as phoenix or serum , representing different liquidity providers. |
slot | Solana slot. |
marketName | Name of the market, e.g., SOL . |
marketType | Type of the market, e.g., spot . |
marketIndex | Index of the market, used to identify specific markets within a market type. |
oracle | The reported price from the oracle for this market. |
oracleData | Contains detailed information from the oracle, including price, slot, confidence, etc. |
oracleData.price | The price reported by the oracle. |
oracleData.slot | The slot number associated with the oracle data. |
oracleData.confidence | The confidence interval for the oracle price. |
oracleData.hasSufficientNumberOfDataPoints | Indicates whether the oracle has sufficient data points for reliability. |
oracleData.twap | The time-weighted average price as reported by the oracle. |
oracleData.twapConfidence | The confidence interval for the time-weighted average price. |
import WebSocket from 'ws';
const ws = new WebSocket('wss://dlob.drift.trade/ws');
ws.on('open', async () => {
console.log('Connected to the server');
// Subscribe to orderbook data
ws.send(JSON.stringify({ type: 'subscribe', marketType: 'perp', channel: 'orderbook', market: 'SOL-PERP' }));
ws.send(JSON.stringify({ type: 'subscribe', marketType: 'spot', channel: 'orderbook', market: 'SOL' }));
// Subscribe to trades data
ws.send(JSON.stringify({ type: 'subscribe', marketType: 'perp', channel: 'trades', market: 'SOL-PERP' }));
});
ws.on('message', (data: WebSocket.Data) => {
try {
const message = JSON.parse(data.toString());
console.log(`Received data from channel: ${JSON.stringify(message.channel)}`);
// book and trades data is in message.data
} catch (e) {
console.error('Invalid message:', data);
}
});
import json
import websocket
import ssl
def on_message(ws, message):
try:
message = json.loads(message)
print(f"Received data from channel: {json.dumps(message['channel'])}")
# book and trades data is in message['data']
except ValueError:
print(f"Invalid message: {message}")
def on_error(ws, error):
print(f"Error: {error}")
def on_close(ws, close_status_code, close_msg):
print("### Closed ###")
def on_open(ws):
print("Connected to the server")
# Subscribe to orderbook data
ws.send(json.dumps({'type': 'subscribe', 'marketType': 'perp', 'channel': 'orderbook', 'market': 'SOL-PERP'}))
ws.send(json.dumps({'type': 'subscribe', 'marketType': 'spot', 'channel': 'orderbook', 'market': 'SOL'}))
# Subscribe to trades data
ws.send(json.dumps({'type': 'subscribe', 'marketType': 'perp', 'channel': 'trades', 'market': 'SOL-PERP'}))
if __name__ == "__main__":
websocket.enableTrace(True)
ws = websocket.WebSocketApp("wss://dlob.drift.trade/ws",
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close)
ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
Update frequency
Currently, orderbook websockets send messages every 1000ms. Connections that contain frequent updates are coming soon.
Trades
Trades feed subscribe messages take a similar form to orderbook data, just change the channel.
Perp markets
{
"type": "subscribe",
"marketType": "perp",
"channel": "trades",
"market": "SOL-PERP"
}
Response
Field | Description |
---|---|
channel | Identifies the type of data being streamed. trades_perp_0 indicates data for trades in perp market 0. (SOL-PERP) |
ts | Timestamp of the trade. |
marketIndex | Index of the market where the trade occurred. |
marketType | Type of the market, here it's perp for perpetual. |
filler | The address or identifier of the filler (taker) in the trade. |
takerFee | Fee paid by the taker in the trade. |
makerFee | Fee paid or received by the maker in the trade. |
quoteAssetAmountSurplus | Surplus amount in quote asset. |
baseAssetAmountFilled | The amount of the base asset that was filled in this trade. |
quoteAssetAmountFilled | The amount of the quote asset that was filled in this trade. |
takerOrderId | Order ID of the taker's order, if available. |
takerOrderBaseAssetAmount | Base asset amount specified in the taker's order. |
takerOrderCumulativeBaseAssetAmountFilled | Cumulative base asset amount filled in the taker's order. |
takerOrderCumulativeQuoteAssetAmountFilled | Cumulative quote asset amount filled in the taker's order. |
maker | The address or identifier of the maker in the trade. |
makerOrderId | Order ID of the maker's order. |
makerOrderDirection | Direction of the maker's order (e.g., 'short' or 'long'). |
makerOrderBaseAssetAmount | Base asset amount specified in the maker's order. |
makerOrderCumulativeBaseAssetAmountFilled | Cumulative base asset amount filled in the maker's order. |
makerOrderCumulativeQuoteAssetAmountFilled | Cumulative quote asset amount filled in the maker's order. |
oraclePrice | The oracle price at the time of the trade. |
txSig | Transaction signature. |
slot | Slot number in which the trade occurred. |
action | fill. |
actionExplanation | Explanation of the action (e.g., 'orderFilledWithAmm' indicating order filled with Automated Market Maker). |
referrerReward | Reward amount for the referrer, if applicable. |
Spot markets
{
"type": "subscribe",
"marketType": "spot",
"channel": "trades",
"market": "SOL"
}
Response
Field | Description |
---|---|
channel | Identifies the type of data being streamed. trades_spot_1 indicates data for trades in spot market 1 (SOL/USDC). |
ts | Timestamp of the trade. |
marketIndex | Index of the market where the trade occurred. |
marketType | Type of the market, here it's spot . |
filler | The address or identifier of the filler (taker) in the trade. |
takerFee | Fee paid by the taker in the trade. |
makerFee | Fee paid or received by the maker in the trade. |
quoteAssetAmountSurplus | Surplus amount in quote asset. |
baseAssetAmountFilled | The amount of the base asset that was filled in this trade. |
quoteAssetAmountFilled | The amount of the quote asset that was filled in this trade. |
taker | The address or identifier of the taker in the trade. |
takerOrderId | Order ID of the taker's order, if available. |
takerOrderDirection | Direction of the taker's order (e.g., 'long'). |
takerOrderBaseAssetAmount | Base asset amount specified in the taker's order. |
takerOrderCumulativeBaseAssetAmountFilled | Cumulative base asset amount filled in the taker's order. |
takerOrderCumulativeQuoteAssetAmountFilled | Cumulative quote asset amount filled in the taker's order. |
maker | The address or identifier of the maker in the trade. |
makerOrderId | Order ID of the maker's order. |
makerOrderDirection | Direction of the maker's order (e.g., 'short'). |
makerOrderBaseAssetAmount | Base asset amount specified in the maker's order. |
makerOrderCumulativeBaseAssetAmountFilled | Cumulative base asset amount filled in the maker's order. |
makerOrderCumulativeQuoteAssetAmountFilled | Cumulative quote asset amount filled in the maker's order. |
oraclePrice | The oracle price at the time of the trade. |
txSig | Transaction signature. |
slot | Slot number in which the trade occurred. |
action | Type of action that occurred (e.g., 'fill'). |
actionExplanation | Explanation of the action (e.g., 'orderFilledWithMatch' indicating order filled with a matching order). |
referrerReward | Reward amount for the referrer, if applicable. |
Websocket - Unsubscribing
To unsubscribe to a channel, send a subscribe-like message, with the message type set to unsubscribe
. Unsubscribe requests to channels not previously subscribed to will have no impact.
{
"type": "unsubscribe",
"marketType": "perp",
"channel": "orderbook",
"market": "SOL-PERP"
}
Websocket - Liveness measure
To alleviate backpressure on websocket servers, drift websockets stop sending messages to clients that have more than 50 messages unproccessed in their buffer, until the buffer is cleared and the messages are processed. We recommend listening for heartbeat messages from the server to determine if your client is still receiving messages.
Heartbeat messages are sent every 5 seconds and take the following form:
{"channel": "heartbeat"}
Ping/pong
No ping messages are sent from the websocket server. Unsolicited ping messages are allowed and will receive pongs back.
Data API
The Drift Data API provides public access to various APIs that Drift uses, offering information about markets, contracts, and tokenomics. This API allows developers and users to retrieve data related to the Drift protocol.
mainnet-beta: https://data.api.drift.trade/ devnet: https://master-data.drift.trade/
Rate Limiting
To ensure fair usage and maintain system stability, the Drift Data API implements rate limiting. Users are restricted to a certain number of requests per minute. The exact limit may vary depending on the endpoint and overall system load. If you exceed the rate limit, you'll receive a 429 (Too Many Requests) response. It's recommended to implement exponential backoff in your applications to handle rate limiting gracefully.
Caching
The Drift Data API utilizes caching mechanisms to improve performance and reduce server load. Responses may be cached for a short period, typically ranging from a few seconds to a few minutes, depending on the endpoint and the nature of the data. While this ensures quick response times, it also means that there might be a slight delay in reflecting the most recent updates. Time-sensitive operations should account for this potential delay.
GET /contracts
Returns the contract information for each market. Contract information contains funding rate and open interest (oi).
Example: https://data.api.drift.trade/contracts
GET /fundingRates
Returns the last 30 days of funding rates by marketName or marketIndex.
import requests
def get_funding_rates(market_symbol='SOL-PERP'):
url = f'https://data.api.drift.trade/fundingRates'
params = {'marketName': market_symbol}
response = requests.get(url, params=params)
return response.json()['fundingRates']
# Example usage, print the funding rates for SOL-PERP
market_symbol = 'SOL-PERP'
rates = get_funding_rates(market_symbol)
print(f"Funding Rates for {market_symbol}:")
for rate in rates:
funding_rate = float(rate['fundingRate']) / 1e9
# ... any logic here, for example...
print(f"Slot: {rate['slot']}, Funding Rate: {funding_rate:.9f}")
interface FundingRate {
slot: number;
fundingRate: string;
// Other fields... (view the response section for the full list)
}
async function getFundingRates(marketSymbol: string = 'SOL-PERP'): Promise<FundingRate[]> {
const url = `https://data.api.drift.trade/fundingRates?marketName=${marketSymbol}`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data.fundingRates;
} catch (error) {
console.error('Error fetching funding rates:', error);
return [];
}
}
async function main() {
const marketSymbol = 'SOL-PERP';
const rates = await getFundingRates(marketSymbol);
console.log(`Funding Rates for ${marketSymbol}:`);
rates.forEach(rate => {
const fundingRate = parseFloat(rate.fundingRate) / 1e9;
// ... any logic here, for example...
console.log(`Slot: ${rate.slot}, Funding Rate: ${fundingRate.toFixed(9)}`);
});
}
main().catch(error => console.error('An error occurred:', error));
Parameter | Description | Optional | Values |
---|---|---|---|
marketName or marketIndex | The market name or index for the perp market | NO |
Example: https://data.api.drift.trade/fundingRates?marketName=SOL-PERP
Response
The response is a json object with a fundingRates
array. Each funding rate entry contains the following fields:
Field | Type | Description |
---|---|---|
txSig |
string | Transaction signature |
slot |
integer | Slot number |
ts |
string | Timestamp |
recordId |
string | Record identifier |
marketIndex |
integer | Market index |
fundingRate |
string | Funding rate (divide by 1e9 for actual rate) |
cumulativeFundingRateLong |
string | Cumulative funding rate for long positions |
cumulativeFundingRateShort |
string | Cumulative funding rate for short positions |
oraclePriceTwap |
string | Oracle price time-weighted average price |
markPriceTwap |
string | Mark price time-weighted average price |
fundingRateLong |
string | Funding rate for long positions |
fundingRateShort |
string | Funding rate for short positions |
periodRevenue |
string | Revenue for the period |
baseAssetAmountWithAmm |
string | Base asset amount with AMM |
baseAssetAmountWithUnsettledLp |
string | Base asset amount with unsettled LP |
GET /DRIFT/
Returns the tokenomics information about the Drift token.
Parameter | Description | Optional | Values |
---|---|---|---|
q | Metrics related to the drift tokenomics | NO | circulating-supply ,locked-supply ,total-supply |
Example: https://data.api.drift.trade/DRIFT?q=circulating-supply
Historical Data
Snapshots are collected by parsing on-chain transaction logs. For convience the below are parsed logs collected, stored as a CSV, and stored off-chain (~99% of records). Records are updated once per day.
Please share any transaction signatures or time ranges you believe might be missing in Drift Protocol Discord.
URL Prefix
mainnet-beta: https://drift-historical-data-v2.s3.eu-west-1.amazonaws.com/program/dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH/
URL Suffix
Schema
recordType | url suffix |
---|---|
trades | user/${accountKey}/tradeRecords/${year}/${year}${month}${day} |
market-trades | market/${marketSymbol}/tradeRecords/${year}/${year}${month}${day} |
swaps | user/${accountKey}/swapRecords/${year}/${year}${month}${day} |
funding-rates | market/${marketSymbol}/fundingRateRecords/${year}/${year}${month}${day} |
funding-payments | user/${accountKey}/fundingPaymentRecords/${year}/${year}${month}${day} |
deposits | user/${accountKey}/depositRecords/${year}/${year}${month}${day} |
liquidations | user/${accountKey}/liquidationRecords/${year}/${year}${month}${day} |
settle-pnl | user/${accountKey}/settlePnlRecords/${year}/${year}${month}${day} |
lp | user/${accountKey}/lpRecord/${year}/${year}${month}${day} |
insurance-fund | market/${marketSymbol}/insuranceFundRecords/${year}/${year}${month}${day} |
insurance-fund-stake | authority/${authorityAccountKey}/insuranceFundStakeRecords/${year}/${year}${month}${day} |
candle-history | candle-history/{year}/{marketKey}/{candleResolution}.csv |
Variables
variable | description | example |
---|---|---|
accountKey | user sub account public key (not authority) | |
authority | authority public key | |
marketSymbol | market name. E.g. SOL-PERP for Solana PERP or SOL for Solana SPOT |
SOL-PERP |
year | 2023 | |
month | 4 | |
day | utc time | 25 |
marketKey | The key for the market. Format: {marketType}_{marketIndex} | perp_0 |
candleResolution | Candle Resolution. See "Available Candle Resolutions" below | M |
Available Candle Resolutions:
resolution | description |
---|---|
1 | 1 minute |
15 | 15 minute |
60 | 1 hr |
240 | 4 hr |
D | 1 day |
W | 1 week |
Example: Trades for market
import requests
URL_PREFIX = 'https://drift-historical-data-v2.s3.eu-west-1.amazonaws.com/program/dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH'
market_symbol = 'SOL-PERP'
year = '2024'
month = '01'
day = '01'
# Method 1: using pandas (this is the easiest and fastest way)
import pandas as pd
df = pd.read_csv(f'{URL_PREFIX}/market/{market_symbol}/tradeRecords/{year}/{year}{month}{day}')
print(df)
# Method 2: using csv reader
import csv
from io import StringIO
response = requests.get(f'{URL_PREFIX}/market/{market_symbol}/tradeRecords/{year}/{year}{month}{day}')
response.raise_for_status()
csv_data = StringIO(response.text)
reader = csv.reader(csv_data)
for row in reader:
print(row)
import { parse } from 'csv-parse/sync';
const URL_PREFIX = 'https://drift-historical-data-v2.s3.eu-west-1.amazonaws.com/program/dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH';
async function fetchAndParseCSV(marketSymbol: string, year: string, month: string, day: string): Promise<void> {
const url = `${URL_PREFIX}/market/${marketSymbol}/tradeRecords/${year}/${year}${month}${day}`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const csvText = await response.text();
const records = parse(csvText, {
skip_empty_lines: true
});
records.forEach((row: string[]) => {
console.log(row);
});
} catch (error) {
console.error('Error fetching or parsing CSV:', error);
}
}
async function main() {
const marketSymbol = 'SOL-PERP';
const year = '2024';
const month = '01';
const day = '01';
await fetchAndParseCSV(marketSymbol, year, month, day);
}
main().catch(error => console.error('An error occurred:', error));
Get historical trades on SOL-PERP
for a given date (ex. 2024-01-01):
https://drift-historical-data-v2.s3.eu-west-1.amazonaws.com/program/dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH/market/SOL-PERP/tradeRecords/2024/20240101
Example: Trades for date range
import requests
import csv
from io import StringIO
from datetime import date, timedelta
import pandas as pd
URL_PREFIX = 'https://drift-historical-data-v2.s3.eu-west-1.amazonaws.com/program/dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH'
# Method 1: using pandas
def get_trades_for_range_pandas(account_key, start_date, end_date):
all_trades = []
current_date = start_date
while current_date <= end_date:
year = current_date.year
month = current_date.month
day = current_date.day
url = f"{URL_PREFIX}/user/{account_key}/tradeRecords/{year}/{year}{month:02}{day:02}"
try:
df = pd.read_csv(url)
all_trades.append(df)
except requests.exceptions.RequestException as e:
print(f"Error fetching data for {current_date}: {e}")
except pd.errors.EmptyDataError:
print(f"No data available for {current_date}")
current_date += timedelta(days=1)
if all_trades:
return pd.concat(all_trades, ignore_index=True)
else:
return pd.DataFrame()
# Method 2: using csv reader
def get_trades_for_range_csv(account_key, start_date, end_date):
all_trades = []
current_date = start_date
while current_date <= end_date:
year = current_date.year
month = current_date.month
day = current_date.day
url = f"{URL_PREFIX}/user/{account_key}/tradeRecords/{year}/{year}{month:02}{day:02}"
response = requests.get(url)
response.raise_for_status()
csv_data = StringIO(response.text)
reader = csv.reader(csv_data)
for row in reader:
all_trades.append(row)
current_date += timedelta(days=1)
return all_trades
# Example usage
account_key = "<Some Account Key>"
start_date = date(2024, 1, 24)
end_date = date(2024, 1, 26)
trades = get_trades_for_range(account_key, start_date, end_date)
import { parse } from 'csv-parse/sync';
const URL_PREFIX = 'https://drift-historical-data-v2.s3.eu-west-1.amazonaws.com/program/dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH';
interface Trade {
// Define the structure of a trade record here
// For example:
// date: string;
// amount: number;
// price: number;
[key: string]: any;
}
async function getTradesForRange(accountKey: string, startDate: Date, endDate: Date): Promise<Trade[]> {
const allTrades: Trade[] = [];
let currentDate = new Date(startDate);
while (currentDate <= endDate) {
const year = currentDate.getFullYear();
const month = (currentDate.getMonth() + 1).toString().padStart(2, '0');
const day = currentDate.getDate().toString().padStart(2, '0');
const url = `${URL_PREFIX}/user/${accountKey}/tradeRecords/${year}/${year}${month}${day}`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const csvText = await response.text();
const records = parse(csvText, {
skip_empty_lines: true,
columns: true
});
allTrades.push(...records);
} catch (error) {
console.error(`Error fetching or parsing CSV for ${year}-${month}-${day}:`, error);
}
currentDate.setDate(currentDate.getDate() + 1);
}
return allTrades;
}
async function main() {
const accountKey = "<Some Account Key>";
const startDate = new Date(2024, 0, 24); // Note: month is 0-indexed in JavaScript
const endDate = new Date(2024, 0, 26);
try {
const trades = await getTradesForRange(accountKey, startDate, endDate);
console.log(`Total trades fetched: ${trades.length}`);
console.log('First few trades:', trades.slice(0, 5));
} catch (error) {
console.error('An error occurred:', error);
}
}
main();
Note: To speed this up, you could download the data in parallel (Promise.all or asyncio).
We can write a script to download all the data, one by one, for a given account in a given date range.
Records Columns
Below are definitions of the columns in each record type.
trades
variable | description | example |
---|---|---|
accountKey | user sub account public key (not authority) |
funding-rates
note: 'rate' is in quote per base, to allow for async settlement
variable | description | example |
---|---|---|
fundingRate | the quote asset amount (precision=1e6) per base asset amount (precision=1e9) |
to convert to the rates seen on the ui, use the following formula: (funding_rate / BASE_PRECISION) / (oracle_twap / QUOTE_PRECISION) * 100
import requests
outcsv = requests.get('https://drift-historical-data-v2.s3.eu-west-1.amazonaws.com/program/dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH/user/FrEFAwxdrzHxgc7S4cuFfsfLmcg8pfbxnkCQW83euyCS/tradeRecords/2023/20230201')'
curl https://drift-historical-data-v2.s3.eu-west-1.amazonaws.com/program/dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH/user/FrEFAwxdrzHxgc7S4cuFfsfLmcg8pfbxnkCQW83euyCS/tradeRecords/2023/20230201 --output out.csv.gz
Errors
Drift Protocol uses has following error codes:
code | name | msg |
---|---|---|
6000 | InvalidSpotMarketAuthority | Invalid Spot Market Authority |
6001 | InvalidInsuranceFundAuthority | Clearing house not insurance fund authority |
6002 | InsufficientDeposit | Insufficient deposit |
6003 | InsufficientCollateral | Insufficient collateral |
6004 | SufficientCollateral | Sufficient collateral |
6005 | MaxNumberOfPositions | Max number of positions taken |
6006 | AdminControlsPricesDisabled | Admin Controls Prices Disabled |
6007 | MarketDelisted | Market Delisted |
6008 | MarketIndexAlreadyInitialized | Market Index Already Initialized |
6009 | UserAccountAndUserPositionsAccountMismatch | User Account And User Positions Account Mismatch |
6010 | UserHasNoPositionInMarket | User Has No Position In Market |
6011 | InvalidInitialPeg | Invalid Initial Peg |
6012 | InvalidRepegRedundant | AMM repeg already configured with amt given |
6013 | InvalidRepegDirection | AMM repeg incorrect repeg direction |
6014 | InvalidRepegProfitability | AMM repeg out of bounds pnl |
6015 | SlippageOutsideLimit | Slippage Outside Limit Price |
6016 | OrderSizeTooSmall | Order Size Too Small |
6017 | InvalidUpdateK | Price change too large when updating K |
6018 | AdminWithdrawTooLarge | Admin tried to withdraw amount larger than fees collected |
6019 | MathError | Math Error |
6020 | BnConversionError | Conversion to u128/u64 failed with an overflow or underflow |
6021 | ClockUnavailable | Clock unavailable |
6022 | UnableToLoadOracle | Unable To Load Oracles |
6023 | PriceBandsBreached | Price Bands Breached |
6024 | ExchangePaused | Exchange is paused |
6025 | InvalidWhitelistToken | Invalid whitelist token |
6026 | WhitelistTokenNotFound | Whitelist token not found |
6027 | InvalidDiscountToken | Invalid discount token |
6028 | DiscountTokenNotFound | Discount token not found |
6029 | ReferrerNotFound | Referrer not found |
6030 | ReferrerStatsNotFound | ReferrerNotFound |
6031 | ReferrerMustBeWritable | ReferrerMustBeWritable |
6032 | ReferrerStatsMustBeWritable | ReferrerMustBeWritable |
6033 | ReferrerAndReferrerStatsAuthorityUnequal | ReferrerAndReferrerStatsAuthorityUnequal |
6034 | InvalidReferrer | InvalidReferrer |
6035 | InvalidOracle | InvalidOracle |
6036 | OracleNotFound | OracleNotFound |
6037 | LiquidationsBlockedByOracle | Liquidations Blocked By Oracle |
6038 | MaxDeposit | Can not deposit more than max deposit |
6039 | CantDeleteUserWithCollateral | Can not delete user that still has collateral |
6040 | InvalidFundingProfitability | AMM funding out of bounds pnl |
6041 | CastingFailure | Casting Failure |
6042 | InvalidOrder | InvalidOrder |
6043 | InvalidOrderMaxTs | InvalidOrderMaxTs |
6044 | InvalidOrderMarketType | InvalidOrderMarketType |
6045 | InvalidOrderForInitialMarginReq | InvalidOrderForInitialMarginReq |
6046 | InvalidOrderNotRiskReducing | InvalidOrderNotRiskReducing |
6047 | InvalidOrderSizeTooSmall | InvalidOrderSizeTooSmall |
6048 | InvalidOrderNotStepSizeMultiple | InvalidOrderNotStepSizeMultiple |
6049 | InvalidOrderBaseQuoteAsset | InvalidOrderBaseQuoteAsset |
6050 | InvalidOrderIOC | InvalidOrderIOC |
6051 | InvalidOrderPostOnly | InvalidOrderPostOnly |
6052 | InvalidOrderIOCPostOnly | InvalidOrderIOCPostOnly |
6053 | InvalidOrderTrigger | InvalidOrderTrigger |
6054 | InvalidOrderAuction | InvalidOrderAuction |
6055 | InvalidOrderOracleOffset | InvalidOrderOracleOffset |
6056 | InvalidOrderMinOrderSize | InvalidOrderMinOrderSize |
6057 | PlacePostOnlyLimitFailure | Failed to Place Post-Only Limit Order |
6058 | UserHasNoOrder | User has no order |
6059 | OrderAmountTooSmall | Order Amount Too Small |
6060 | MaxNumberOfOrders | Max number of orders taken |
6061 | OrderDoesNotExist | Order does not exist |
6062 | OrderNotOpen | Order not open |
6063 | FillOrderDidNotUpdateState | FillOrderDidNotUpdateState |
6064 | ReduceOnlyOrderIncreasedRisk | Reduce only order increased risk |
6065 | UnableToLoadAccountLoader | Unable to load AccountLoader |
6066 | TradeSizeTooLarge | Trade Size Too Large |
6067 | UserCantReferThemselves | User cant refer themselves |
6068 | DidNotReceiveExpectedReferrer | Did not receive expected referrer |
6069 | CouldNotDeserializeReferrer | Could not deserialize referrer |
6070 | CouldNotDeserializeReferrerStats | Could not deserialize referrer stats |
6071 | UserOrderIdAlreadyInUse | User Order Id Already In Use |
6072 | NoPositionsLiquidatable | No positions liquidatable |
6073 | InvalidMarginRatio | Invalid Margin Ratio |
6074 | CantCancelPostOnlyOrder | Cant Cancel Post Only Order |
6075 | InvalidOracleOffset | InvalidOracleOffset |
6076 | CantExpireOrders | CantExpireOrders |
6077 | CouldNotLoadMarketData | CouldNotLoadMarketData |
6078 | PerpMarketNotFound | PerpMarketNotFound |
6079 | InvalidMarketAccount | InvalidMarketAccount |
6080 | UnableToLoadPerpMarketAccount | UnableToLoadMarketAccount |
6081 | MarketWrongMutability | MarketWrongMutability |
6082 | UnableToCastUnixTime | UnableToCastUnixTime |
6083 | CouldNotFindSpotPosition | CouldNotFindSpotPosition |
6084 | NoSpotPositionAvailable | NoSpotPositionAvailable |
6085 | InvalidSpotMarketInitialization | InvalidSpotMarketInitialization |
6086 | CouldNotLoadSpotMarketData | CouldNotLoadSpotMarketData |
6087 | SpotMarketNotFound | SpotMarketNotFound |
6088 | InvalidSpotMarketAccount | InvalidSpotMarketAccount |
6089 | UnableToLoadSpotMarketAccount | UnableToLoadSpotMarketAccount |
6090 | SpotMarketWrongMutability | SpotMarketWrongMutability |
6091 | SpotMarketInterestNotUpToDate | SpotInterestNotUpToDate |
6092 | SpotMarketInsufficientDeposits | SpotMarketInsufficientDeposits |
6093 | UserMustSettleTheirOwnPositiveUnsettledPNL | UserMustSettleTheirOwnPositiveUnsettledPNL |
6094 | CantUpdatePoolBalanceType | CantUpdatePoolBalanceType |
6095 | InsufficientCollateralForSettlingPNL | InsufficientCollateralForSettlingPNL |
6096 | AMMNotUpdatedInSameSlot | AMMNotUpdatedInSameSlot |
6097 | AuctionNotComplete | AuctionNotComplete |
6098 | MakerNotFound | MakerNotFound |
6099 | MakerStatsNotFound | MakerNotFound |
6100 | MakerMustBeWritable | MakerMustBeWritable |
6101 | MakerStatsMustBeWritable | MakerMustBeWritable |
6102 | MakerOrderNotFound | MakerOrderNotFound |
6103 | CouldNotDeserializeMaker | CouldNotDeserializeMaker |
6104 | CouldNotDeserializeMakerStats | CouldNotDeserializeMaker |
6105 | AuctionPriceDoesNotSatisfyMaker | AuctionPriceDoesNotSatisfyMaker |
6106 | MakerCantFulfillOwnOrder | MakerCantFulfillOwnOrder |
6107 | MakerOrderMustBePostOnly | MakerOrderMustBePostOnly |
6108 | CantMatchTwoPostOnlys | CantMatchTwoPostOnlys |
6109 | OrderBreachesOraclePriceLimits | OrderBreachesOraclePriceLimits |
6110 | OrderMustBeTriggeredFirst | OrderMustBeTriggeredFirst |
6111 | OrderNotTriggerable | OrderNotTriggerable |
6112 | OrderDidNotSatisfyTriggerCondition | OrderDidNotSatisfyTriggerCondition |
6113 | PositionAlreadyBeingLiquidated | PositionAlreadyBeingLiquidated |
6114 | PositionDoesntHaveOpenPositionOrOrders | PositionDoesntHaveOpenPositionOrOrders |
6115 | AllOrdersAreAlreadyLiquidations | AllOrdersAreAlreadyLiquidations |
6116 | CantCancelLiquidationOrder | CantCancelLiquidationOrder |
6117 | UserIsBeingLiquidated | UserIsBeingLiquidated |
6118 | LiquidationsOngoing | LiquidationsOngoing |
6119 | WrongSpotBalanceType | WrongSpotBalanceType |
6120 | UserCantLiquidateThemself | UserCantLiquidateThemself |
6121 | InvalidPerpPositionToLiquidate | InvalidPerpPositionToLiquidate |
6122 | InvalidBaseAssetAmountForLiquidatePerp | InvalidBaseAssetAmountForLiquidatePerp |
6123 | InvalidPositionLastFundingRate | InvalidPositionLastFundingRate |
6124 | InvalidPositionDelta | InvalidPositionDelta |
6125 | UserBankrupt | UserBankrupt |
6126 | UserNotBankrupt | UserNotBankrupt |
6127 | UserHasInvalidBorrow | UserHasInvalidBorrow |
6128 | DailyWithdrawLimit | DailyWithdrawLimit |
6129 | DefaultError | DefaultError |
6130 | InsufficientLPTokens | Insufficient LP tokens |
6131 | CantLPWithPerpPosition | Cant LP with a market position |
6132 | UnableToBurnLPTokens | Unable to burn LP tokens |
6133 | TryingToRemoveLiquidityTooFast | Trying to remove liqudity too fast after adding it |
6134 | InvalidSpotMarketVault | Invalid Spot Market Vault |
6135 | InvalidSpotMarketState | Invalid Spot Market State |
6136 | InvalidSerumProgram | InvalidSerumProgram |
6137 | InvalidSerumMarket | InvalidSerumMarket |
6138 | InvalidSerumBids | InvalidSerumBids |
6139 | InvalidSerumAsks | InvalidSerumAsks |
6140 | InvalidSerumOpenOrders | InvalidSerumOpenOrders |
6141 | FailedSerumCPI | FailedSerumCPI |
6142 | FailedToFillOnExternalMarket | FailedToFillOnExternalMarket |
6143 | InvalidFulfillmentConfig | InvalidFulfillmentConfig |
6144 | InvalidFeeStructure | InvalidFeeStructure |
6145 | InsufficientIFShares | Insufficient IF shares |
6146 | MarketActionPaused | the Market has paused this action |
6147 | MarketPlaceOrderPaused | the Market status doesnt allow placing orders |
6148 | MarketFillOrderPaused | the Market status doesnt allow filling orders |
6149 | MarketWithdrawPaused | the Market status doesnt allow withdraws |
6150 | ProtectedAssetTierViolation | Action violates the Protected Asset Tier rules |
6151 | IsolatedAssetTierViolation | Action violates the Isolated Asset Tier rules |
6152 | UserCantBeDeleted | User Cant Be Deleted |
6153 | ReduceOnlyWithdrawIncreasedRisk | Reduce Only Withdraw Increased Risk |
6154 | MaxOpenInterest | Max Open Interest |
6155 | CantResolvePerpBankruptcy | Cant Resolve Perp Bankruptcy |
6156 | LiquidationDoesntSatisfyLimitPrice | Liquidation Doesnt Satisfy Limit Price |
6157 | MarginTradingDisabled | Margin Trading Disabled |
6158 | InvalidMarketStatusToSettlePnl | Invalid Market Status to Settle Perp Pnl |
6159 | PerpMarketNotInSettlement | PerpMarketNotInSettlement |
6160 | PerpMarketNotInReduceOnly | PerpMarketNotInReduceOnly |
6161 | PerpMarketSettlementBufferNotReached | PerpMarketSettlementBufferNotReached |
6162 | PerpMarketSettlementUserHasOpenOrders | PerpMarketSettlementUserHasOpenOrders |
6163 | PerpMarketSettlementUserHasActiveLP | PerpMarketSettlementUserHasActiveLP |
6164 | UnableToSettleExpiredUserPosition | UnableToSettleExpiredUserPosition |
6165 | UnequalMarketIndexForSpotTransfer | UnequalMarketIndexForSpotTransfer |
6166 | InvalidPerpPositionDetected | InvalidPerpPositionDetected |
6167 | InvalidSpotPositionDetected | InvalidSpotPositionDetected |
6168 | InvalidAmmDetected | InvalidAmmDetected |
6169 | InvalidAmmForFillDetected | InvalidAmmForFillDetected |
6170 | InvalidAmmLimitPriceOverride | InvalidAmmLimitPriceOverride |
6171 | InvalidOrderFillPrice | InvalidOrderFillPrice |
6172 | SpotMarketBalanceInvariantViolated | SpotMarketBalanceInvariantViolated |
6173 | SpotMarketVaultInvariantViolated | SpotMarketVaultInvariantViolated |
6174 | InvalidPDA | InvalidPDA |
6175 | InvalidPDASigner | InvalidPDASigner |
6176 | RevenueSettingsCannotSettleToIF | RevenueSettingsCannotSettleToIF |
6177 | NoRevenueToSettleToIF | NoRevenueToSettleToIF |
6178 | NoAmmPerpPnlDeficit | NoAmmPerpPnlDeficit |
6179 | SufficientPerpPnlPool | SufficientPerpPnlPool |
6180 | InsufficientPerpPnlPool | InsufficientPerpPnlPool |
6181 | PerpPnlDeficitBelowThreshold | PerpPnlDeficitBelowThreshold |
6182 | MaxRevenueWithdrawPerPeriodReached | MaxRevenueWithdrawPerPeriodReached |
6183 | MaxIFWithdrawReached | InvalidSpotPositionDetected |
6184 | NoIFWithdrawAvailable | NoIFWithdrawAvailable |
6185 | InvalidIFUnstake | InvalidIFUnstake |
6186 | InvalidIFUnstakeSize | InvalidIFUnstakeSize |
6187 | InvalidIFUnstakeCancel | InvalidIFUnstakeCancel |
6188 | InvalidIFForNewStakes | InvalidIFForNewStakes |
6189 | InvalidIFRebase | InvalidIFRebase |
6190 | InvalidInsuranceUnstakeSize | InvalidInsuranceUnstakeSize |
6191 | InvalidOrderLimitPrice | InvalidOrderLimitPrice |
6192 | InvalidIFDetected | InvalidIFDetected |
6193 | InvalidAmmMaxSpreadDetected | InvalidAmmMaxSpreadDetected |
6194 | InvalidConcentrationCoef | InvalidConcentrationCoef |
6195 | InvalidSrmVault | InvalidSrmVault |
6196 | InvalidVaultOwner | InvalidVaultOwner |
6197 | InvalidMarketStatusForFills | InvalidMarketStatusForFills |
6198 | IFWithdrawRequestInProgress | IFWithdrawRequestInProgress |
6199 | NoIFWithdrawRequestInProgress | NoIFWithdrawRequestInProgress |
6200 | IFWithdrawRequestTooSmall | IFWithdrawRequestTooSmall |
6201 | IncorrectSpotMarketAccountPassed | IncorrectSpotMarketAccountPassed |
6202 | BlockchainClockInconsistency | BlockchainClockInconsistency |
6203 | InvalidIFSharesDetected | InvalidIFSharesDetected |
6204 | NewLPSizeTooSmall | NewLPSizeTooSmall |
6205 | MarketStatusInvalidForNewLP | MarketStatusInvalidForNewLP |
6206 | InvalidMarkTwapUpdateDetected | InvalidMarkTwapUpdateDetected |
6207 | MarketSettlementAttemptOnActiveMarket | MarketSettlementAttemptOnActiveMarket |
6208 | MarketSettlementRequiresSettledLP | MarketSettlementRequiresSettledLP |
6209 | MarketSettlementAttemptTooEarly | MarketSettlementAttemptTooEarly |
6210 | MarketSettlementTargetPriceInvalid | MarketSettlementTargetPriceInvalid |
6211 | UnsupportedSpotMarket | UnsupportedSpotMarket |
6212 | SpotOrdersDisabled | SpotOrdersDisabled |
6213 | MarketBeingInitialized | Market Being Initialized |
6214 | InvalidUserSubAccountId | Invalid Sub Account Id |
6215 | InvalidTriggerOrderCondition | Invalid Trigger Order Condition |
6216 | InvalidSpotPosition | Invalid Spot Position |
6217 | CantTransferBetweenSameUserAccount | Cant transfer between same user account |
6218 | InvalidPerpPosition | Invalid Perp Position |
6219 | UnableToGetLimitPrice | Unable To Get Limit Price |
6220 | InvalidLiquidation | Invalid Liquidation |
6221 | SpotFulfillmentConfigDisabled | Spot Fulfullment Config Disabled |
6222 | InvalidMaker | Invalid Maker |
6223 | FailedUnwrap | Failed Unwrap |
6224 | MaxNumberOfUsers | Max Number Of Users |
6225 | InvalidOracleForSettlePnl | InvalidOracleForSettlePnl |
6226 | MarginOrdersOpen | MarginOrdersOpen |
6227 | TierViolationLiquidatingPerpPnl | TierViolationLiquidatingPerpPnl |
6228 | CouldNotLoadUserData | CouldNotLoadUserData |
6229 | UserWrongMutability | UserWrongMutability |
6230 | InvalidUserAccount | InvalidUserAccount |
6231 | CouldNotLoadUserStatsData | CouldNotLoadUserData |
6232 | UserStatsWrongMutability | UserWrongMutability |
6233 | InvalidUserStatsAccount | InvalidUserAccount |
6234 | UserNotFound | UserNotFound |
6235 | UnableToLoadUserAccount | UnableToLoadUserAccount |
6236 | UserStatsNotFound | UserStatsNotFound |
6237 | UnableToLoadUserStatsAccount | UnableToLoadUserStatsAccount |
6238 | UserNotInactive | User Not Inactive |
6239 | RevertFill | RevertFill |
6240 | InvalidMarketAccountforDeletion | Invalid MarketAccount for Deletion |
6241 | InvalidSpotFulfillmentParams | Invalid Spot Fulfillment Params |
6242 | FailedToGetMint | Failed to Get Mint |
6243 | FailedPhoenixCPI | FailedPhoenixCPI |
6244 | FailedToDeserializePhoenixMarket | FailedToDeserializePhoenixMarket |
6245 | InvalidPricePrecision | InvalidPricePrecision |
6246 | InvalidPhoenixProgram | InvalidPhoenixProgram |
6247 | InvalidPhoenixMarket | InvalidPhoenixMarket |
6248 | InvalidSwap | InvalidSwap |
6249 | SwapLimitPriceBreached | SwapLimitPriceBreached |
6250 | SpotMarketReduceOnly | SpotMarketReduceOnly |
6251 | FundingWasNotUpdated | FundingWasNotUpdated |
6252 | ImpossibleFill | ImpossibleFill |
6253 | CantUpdatePerpBidAskTwap | CantUpdatePerpBidAskTwap |
6254 | UserReduceOnly | UserReduceOnly |
6255 | InvalidMarginCalculation | InvalidMarginCalculation |
6256 | CantPayUserInitFee | CantPayUserInitFee |
6257 | CantReclaimRent | CantReclaimRent |
6258 | InsuranceFundOperationPaused | InsuranceFundOperationPaused |
6259 | NoUnsettledPnl | NoUnsettledPnl |
6260 | PnlPoolCantSettleUser | PnlPoolCantSettleUser |
6261 | OracleNonPositive | OracleInvalid |
6262 | OracleTooVolatile | OracleTooVolatile |
6263 | OracleTooUncertain | OracleTooUncertain |
6264 | OracleStaleForMargin | OracleStaleForMargin |
6265 | OracleInsufficientDataPoints | OracleInsufficientDataPoints |
6266 | OracleStaleForAMM | OracleStaleForAMM |
6267 | UnableToParsePullOracleMessage | Unable to parse pull oracle message |
6268 | MaxBorrows | Can not borow more than max borrows |
6269 | OracleUpdatesNotMonotonic | Updates must be monotonically increasing |
6270 | OraclePriceFeedMessageMismatch | Trying to update price feed with the wrong feed id |
6271 | OracleUnsupportedMessageType | The message in the update must be a PriceFeedMessage |
6272 | OracleDeserializeMessageFailed | Could not deserialize the message in the update |
6273 | OracleWrongGuardianSetOwner | Wrong guardian set owner in update price atomic |
6274 | OracleWrongWriteAuthority | Oracle post update atomic price feed account must be drift program |
6275 | OracleWrongVaaOwner | Oracle vaa owner must be wormhole program |
6276 | OracleTooManyPriceAccountUpdates | Multi updates must have 2 or fewer accounts passed in remaining accounts |
6277 | OracleMismatchedVaaAndPriceUpdates | Don't have the same remaining accounts number and merkle price updates left |
6278 | OracleBadRemainingAccountPublicKey | Remaining account passed is not a valid pda |
6279 | FailedOpenbookV2CPI | FailedOpenbookV2CPI |
6280 | InvalidOpenbookV2Program | InvalidOpenbookV2Program |
6281 | InvalidOpenbookV2Market | InvalidOpenbookV2Market |
6282 | NonZeroTransferFee | Non zero transfer fee |
6283 | LiquidationOrderFailedToFill | Liquidation order failed to fill |
6284 | InvalidPredictionMarketOrder | Invalid prediction market order |
6285 | InvalidVerificationIxIndex | Ed25519 Ix must be before place and make swift order ix |
6286 | SigVerificationFailed | Swift message verificaiton failed |
6287 | MismatchedSwiftOrderParamsMarketIndex | Market index mismatched b/w taker and maker swift order params |
6288 | InvalidSwiftOrderParam | Swift only available for market/oracle perp orders |
6289 | PlaceAndTakeOrderSuccessConditionFailed | Place and take order success condition failed |
6290 | InvalidHighLeverageModeConfig | Invalid High Leverage Mode Config |
6291 | InvalidRFQUserAccount | Invalid RFQ User Account |
6292 | RFQUserAccountWrongMutability | RFQUserAccount should be mutable |
6293 | RFQUserAccountFull | RFQUserAccount has too many active RFQs |
6294 | RFQOrderNotFilled | RFQ order not filled as expected |
6295 | InvalidRFQOrder | RFQ orders must be jit makers |
6296 | InvalidRFQMatch | RFQ matches must be valid |
Market Indexes/Names
All spot and perp markets contain a market index and name field on chain. You can also find the perp and spot market indexes and names in the typescript sdk constants.
Prediction Markets
Prediction Markets are Perp Markets with the contract_type
set as Prediction
.
Prediction Markets function the same as Perp Markets with the following additional constraints:
- Prices must be between 0 and 1
- Funding is paused
- Margin ratios are set to 1 so that the position is fully collateralized
- The margin system uses
1 - oracle price
to value short positions - They use prelaunch oracles (the oracle price is the mark twap)