Python client for the Drift DEX
DriftPy is the official Python client for the Drift Protocol, a decentralized exchange on the Solana blockchain. It enables users to programmatically trade, fetch data, and interact with the Drift DEX. The library is actively maintained with frequent updates, currently at version 0.8.89.
Common errors
-
IndexError: list index out of range
cause Attempting to access market or user data from `DriftClient` before `await drift_client.subscribe()` has been successfully called and completed.fixAdd `await drift_client.subscribe()` immediately after `DriftClient` instantiation to properly initialize data caches. Ensure this is awaited in an `async` context. -
AttributeError: transaction has not been signed correctly
cause This error typically occurs when trying to perform transactions (e.g., cancelling an order) on a delegated account where the signing keypair is not correctly configured or does not have the necessary permissions for the action.fixVerify that the `Keypair` used to initialize the `Wallet` and `DriftClient` corresponds to the actual authority or a correctly configured delegate with appropriate permissions. Double-check your `PRIVATE_KEY` and ensure it matches the expected account. -
quiknode 400 error
cause Using a QuickNode free plan RPC endpoint with an `AccountSubscriptionConfig` other than 'demo', or subscribing to more than one perp/spot market.fixSwitch to `AccountSubscriptionConfig('demo')` for free QuickNode plans. Limit `perp_market_indexes` and `spot_market_indexes` in `DriftClient` initialization to at most one index each. Consider using a paid RPC for higher limits and more flexibility.
Warnings
- breaking Failing to call `await drift_client.subscribe()` after `DriftClient` initialization will cause `IndexError: list index out of range` or similar data access errors when attempting to fetch market or user data.
- gotcha Users of QuickNode's free RPC plan must use `AccountSubscriptionConfig('demo')` and are limited to subscribing to only one perp market and one spot market at a time. Exceeding this limit or using other configurations will result in connection issues (e.g., 'quiknode 400 error').
- gotcha When running on Python 3.13, older versions of `cffi` (specifically below 2.0.0) might not be fully compatible, potentially leading to build or runtime issues, especially with free-threaded Python builds.
Install
-
pip install driftpy
Imports
- DriftClient
from driftpy.drift_client import DriftClient
- DriftUser
from driftpy.drift_user import DriftUser
- BASE_PRECISION, AMM_RESERVE_PRECISION
from driftpy.constants.numeric_constants import BASE_PRECISION, AMM_RESERVE_PRECISION
- AccountSubscriptionConfig
from driftpy.account_subscription_config import AccountSubscriptionConfig
- load_keypair
from driftpy.keypair import load_keypair
- AsyncClient
from solana.rpc.async_api import AsyncClient
- Keypair
from solana.keypair import Keypair
from solders.keypair import Keypair
- Wallet
from anchorpy import Wallet
Quickstart
import os
import asyncio
from dotenv import load_dotenv
from solders.keypair import Keypair
from solana.rpc.async_api import AsyncClient
from anchorpy import Wallet
from driftpy.drift_client import DriftClient
from driftpy.keypair import load_keypair
async def main():
load_dotenv() # Load .env file for environment variables
# It's safer to use os.environ.get with a default empty string for sensitive info
# and then handle the case where it's missing.
secret_key_str = os.environ.get("PRIVATE_KEY", "")
rpc_url = os.environ.get("RPC_URL", "https://api.devnet.solana.com")
if not secret_key_str:
print("Error: PRIVATE_KEY environment variable not set.")
return
if not rpc_url:
print("Error: RPC_URL environment variable not set.")
return
# Ensure the secret key is in the correct format (list of integers)
try:
secret_key_bytes = bytes(list(map(int, secret_key_str.strip('[]').split(','))))
keypair = Keypair.from_secret_key(secret_key_bytes)
except Exception as e:
print(f"Error loading keypair: {e}. Ensure PRIVATE_KEY is a comma-separated list of integers in your .env file.")
return
wallet = Wallet(keypair)
connection = AsyncClient(rpc_url)
# Initialize DriftClient for 'devnet'
# For mainnet, use 'mainnet'
drift_client = DriftClient(
connection,
wallet,
"devnet",
)
print("Subscribing to Drift Client...")
await drift_client.subscribe() # Crucial: Must subscribe before fetching data
print("Drift Client subscribed.")
# Optional: Register a default sub-account if not already done
# await drift_client.add_user(0)
# Example: Fetch user stats (after subscription)
user_stats = await drift_client.get_user_stats_account()
print(f"User Stats: {user_stats}")
await drift_client.unsubscribe()
await connection.close()
if __name__ == "__main__":
asyncio.run(main())