Saltar a contenido

Drift

Connector for Drift protocol.

almanak.connectors.drift

Drift Protocol Connector.

Provides perpetual futures trading on Drift (Solana's #1 perps DEX). Supports market orders for opening and closing perp positions.

Key classes: - DriftAdapter: Compiles PerpOpen/PerpClose intents to ActionBundles - DriftSDK: Low-level instruction building (PDA derivation, Borsh encoding) - DriftDataClient: REST client for Drift Data API (market info, funding rates) - DriftReceiptParser: Parses transaction receipts for fill data - DriftConfig: Adapter configuration

Lazy attribute access (VIB-4835): the strategy-side public surface is exposed via PEP 562 __getattr__ so importing almanak.connectors.drift.gateway.provider at gateway boot does not eagerly pull adapter / SDK / client modules. The pre-existing DRIFT_DATA_API_BASE_URL shim is preserved in the same __getattr__.

DriftAdapter

DriftAdapter(
    config: DriftConfig,
    token_resolver: TokenResolver | None = None,
)

Adapter for Drift protocol integration with the Intent system.

Converts PerpOpenIntent and PerpCloseIntent into ActionBundles containing serialized Solana VersionedTransactions.

Key features: - Market orders only (MVP scope) - Automatic account initialization if needed - Oracle and remaining accounts resolution via RPC - Sub-account 0 only (default)

compile_perp_open_intent

compile_perp_open_intent(
    intent: PerpOpenIntent, price_oracle: Any = None
) -> ActionBundle

Compile a PerpOpenIntent to an ActionBundle.

Parameters:

Name Type Description Default
intent PerpOpenIntent

Perp open intent with market, size, direction, etc.

required
price_oracle Any

Optional price oracle for USD conversions

None

Returns:

Type Description
ActionBundle

ActionBundle with serialized VersionedTransaction

compile_perp_close_intent

compile_perp_close_intent(
    intent: PerpCloseIntent, price_oracle: Any = None
) -> ActionBundle

Compile a PerpCloseIntent to an ActionBundle.

Parameters:

Name Type Description Default
intent PerpCloseIntent

Perp close intent with market, direction, optional size

required
price_oracle Any

Optional price oracle

None

Returns:

Type Description
ActionBundle

ActionBundle with serialized VersionedTransaction

DriftDataClient

DriftDataClient(
    base_url: str | None = None, timeout: int = 30
)

Client for the Drift public data API.

Provides read-only access to market data, funding rates, and oracle prices. No authentication required.

Example

client = DriftDataClient() markets = client.get_perp_markets() oracle_prices = client.get_oracle_prices()

get_perp_markets

get_perp_markets() -> list[DriftMarket]

Get all perpetual futures markets.

Returns:

Type Description
list[DriftMarket]

List of DriftMarket with market info and stats

get_oracle_prices

get_oracle_prices() -> dict[int, Decimal]

Get current oracle prices for all perp markets.

Returns:

Type Description
dict[int, Decimal]

Dict of market_index → oracle price in USD

get_oracle_price

get_oracle_price(market_index: int) -> Decimal | None

Get oracle price for a specific market.

Parameters:

Name Type Description Default
market_index int

Perp market index

required

Returns:

Type Description
Decimal | None

Oracle price in USD, or None if not available

get_funding_rates

get_funding_rates(market_index: int) -> list[FundingRate]

Get historical funding rates for a market.

Parameters:

Name Type Description Default
market_index int

Perp market index

required

Returns:

Type Description
list[FundingRate]

List of FundingRate data points

get_market_info

get_market_info(market_index: int) -> DriftMarket | None

Get detailed info for a specific perp market.

Parameters:

Name Type Description Default
market_index int

Perp market index

required

Returns:

Type Description
DriftMarket | None

DriftMarket or None if not found

DriftAccountNotFoundError

DriftAccountNotFoundError(
    message: str, account_type: str = "", address: str = ""
)

Bases: DriftError

Exception raised when a Drift account is not found on-chain.

Attributes:

Name Type Description
message

Error message

account_type

Type of account (e.g., "User", "PerpMarket")

address

The address that was looked up

DriftAPIError

DriftAPIError(
    message: str,
    status_code: int,
    endpoint: str | None = None,
    error_code: str | None = None,
)

Bases: DriftError

Exception raised for errors in the Drift Data API response.

Attributes:

Name Type Description
message

Error message

status_code

HTTP status code of the response

endpoint

The API endpoint that was called

error_code

Drift-specific error code

DriftConfigError

DriftConfigError(
    message: str, parameter: str | None = None
)

Bases: DriftError

Exception raised for configuration errors.

Attributes:

Name Type Description
message

Error message

parameter

Name of the configuration parameter that caused the error

DriftError

Bases: Exception

Base exception class for all Drift connector errors.

DriftMarketError

DriftMarketError(message: str, market: str = '')

Bases: DriftError

Exception raised for market-related errors.

Attributes:

Name Type Description
message

Error message

market

Market identifier

DriftValidationError

DriftValidationError(
    message: str,
    field: str | None = None,
    value: Any | None = None,
)

Bases: DriftError

Exception raised for validation errors.

Attributes:

Name Type Description
message

Error message

field

Name of the field that failed validation

value

The invalid value

DriftConfig dataclass

DriftConfig(
    wallet_address: str,
    rpc_url: str = "",
    sub_account_id: int = 0,
    data_api_base_url: str = "https://data.api.drift.trade",
    timeout: int = 30,
    gateway_client: GatewayClient | None = None,
)

Configuration for Drift adapter.

Attributes:

Name Type Description
wallet_address str

Solana public key (Base58)

rpc_url str

Solana RPC endpoint URL

sub_account_id int

Drift sub-account ID (default 0)

data_api_base_url str

Drift Data API base URL

timeout int

Request timeout in seconds

DriftMarket dataclass

DriftMarket(
    market_index: int,
    symbol: str = "",
    base_asset_symbol: str = "",
    oracle_price: Decimal = Decimal("0"),
    funding_rate: Decimal = Decimal("0"),
    funding_rate_24h: Decimal = Decimal("0"),
    open_interest: Decimal = Decimal("0"),
    volume_24h: Decimal = Decimal("0"),
    mark_price: Decimal = Decimal("0"),
)

A Drift perpetual futures market.

Attributes:

Name Type Description
market_index int

On-chain market index

symbol str

Market symbol (e.g., "SOL-PERP")

base_asset_symbol str

Base asset symbol (e.g., "SOL")

oracle_price Decimal

Current oracle price in USD

funding_rate Decimal

Current funding rate (hourly)

funding_rate_24h Decimal

24-hour average funding rate

open_interest Decimal

Total open interest in base units

volume_24h Decimal

24-hour volume in USD

mark_price Decimal

Current mark price

from_api_response classmethod

from_api_response(data: dict[str, Any]) -> DriftMarket

Create from Drift Data API market response.

DriftPerpPosition dataclass

DriftPerpPosition(
    market_index: int = 0,
    base_asset_amount: int = 0,
    quote_asset_amount: int = 0,
    last_cumulative_funding_rate: int = 0,
    open_orders: int = 0,
)

A user's perpetual position on Drift.

Parsed from on-chain User account data.

Attributes:

Name Type Description
market_index int

Perp market index

base_asset_amount int

Signed base amount (positive=long, negative=short)

quote_asset_amount int

Quote amount (accumulated PnL)

last_cumulative_funding_rate int

Last seen funding rate

open_orders int

Number of open orders for this market

is_active property

is_active: bool

Whether this position slot has an active position.

is_long property

is_long: bool

Whether the position is long.

DriftSpotPosition dataclass

DriftSpotPosition(
    market_index: int = 0,
    scaled_balance: int = 0,
    balance_type: int = 0,
    open_orders: int = 0,
)

A user's spot position on Drift.

Parsed from on-chain User account data.

Attributes:

Name Type Description
market_index int

Spot market index

scaled_balance int

Scaled balance (deposit or borrow)

balance_type int

0=Deposit, 1=Borrow

open_orders int

Number of open orders

is_active property

is_active: bool

Whether this position slot has an active balance.

DriftUserAccount dataclass

DriftUserAccount(
    authority: str = "",
    sub_account_id: int = 0,
    perp_positions: list[DriftPerpPosition] = list(),
    spot_positions: list[DriftSpotPosition] = list(),
    exists: bool = True,
)

Parsed Drift User account state.

Attributes:

Name Type Description
authority str

Wallet public key that owns this account

sub_account_id int

Sub-account identifier

perp_positions list[DriftPerpPosition]

List of perp position slots

spot_positions list[DriftSpotPosition]

List of spot position slots

exists bool

Whether the account exists on-chain

active_perp_market_indexes property

active_perp_market_indexes: list[int]

Get market indexes of active perp positions.

active_spot_market_indexes property

active_spot_market_indexes: list[int]

Get market indexes of active spot positions.

FundingRate dataclass

FundingRate(
    timestamp: int = 0,
    funding_rate: Decimal = Decimal("0"),
    market_index: int = 0,
)

Funding rate data point.

Attributes:

Name Type Description
timestamp int

Unix timestamp

funding_rate Decimal

Hourly funding rate as decimal

market_index int

Market index

from_api_response classmethod

from_api_response(data: dict[str, Any]) -> FundingRate

Create from Drift Data API response.

OrderParams dataclass

OrderParams(
    order_type: int = ORDER_TYPE_MARKET,
    market_type: int = MARKET_TYPE_PERP,
    direction: int = DIRECTION_LONG,
    user_order_id: int = 0,
    base_asset_amount: int = 0,
    price: int = 0,
    market_index: int = 0,
    reduce_only: bool = False,
    post_only: int = POST_ONLY_NONE,
    bit_flags: int = 0,
    max_ts: int | None = None,
    trigger_price: int | None = None,
    trigger_condition: int = TRIGGER_CONDITION_ABOVE,
    oracle_price_offset: int | None = None,
    auction_duration: int | None = None,
    auction_start_price: int | None = None,
    auction_end_price: int | None = None,
)

Parameters for a Drift perpetual order.

Maps to the on-chain OrderParams struct that gets Borsh-encoded into instruction data.

Attributes:

Name Type Description
order_type int

0=Market, 1=Limit, 2=TriggerMarket, 3=TriggerLimit, 4=Oracle

market_type int

0=Perp, 1=Spot

direction int

0=Long, 1=Short

user_order_id int

User-assigned order ID (u8, 0-255)

base_asset_amount int

Position size in base precision (1e9)

price int

Limit price in price precision (1e6), 0 for market orders

market_index int

Market index (u16)

reduce_only bool

Whether order can only reduce position

post_only int

PostOnlyParam enum (0=None, 1=MustPostOnly, 2=TryPostOnly, 3=Slide)

bit_flags int

u8 bit field — bit 0: ImmediateOrCancel, bit 1: UpdateHighLeverageMode

max_ts int | None

Optional max timestamp (unix seconds)

trigger_price int | None

Optional trigger price for stop/take-profit orders

trigger_condition int

0=Above, 1=Below

oracle_price_offset int | None

Optional offset from oracle price (i32, in 1e6)

auction_duration int | None

Optional auction duration in slots

auction_start_price int | None

Optional auction start price

auction_end_price int | None

Optional auction end price

DriftReceiptParser

Receipt parser for Drift Protocol transactions.

Uses balance-delta approach to extract execution information from Solana transaction receipts.

parse_receipt

parse_receipt(receipt: dict[str, Any]) -> dict[str, Any]

Parse a Drift transaction receipt.

Parameters:

Name Type Description Default
receipt dict[str, Any]

Solana transaction receipt dict with 'meta' containing preTokenBalances, postTokenBalances, and logMessages

required

Returns:

Type Description
dict[str, Any]

Parsed result with extracted data

extract_perp_fill

extract_perp_fill(
    receipt: dict[str, Any],
) -> dict[str, Any] | None

Extract perpetual fill data from a receipt.

Parses Drift program logs for fill events that contain order execution details.

Parameters:

Name Type Description Default
receipt dict[str, Any]

Transaction receipt

required

Returns:

Type Description
dict[str, Any] | None

Fill data dict or None

extract_position_id

extract_position_id(_receipt: dict[str, Any]) -> None

No-op stub — Drift position IDs require account-state queries.

extract_size_delta

extract_size_delta(_receipt: dict[str, Any]) -> None

No-op stub — Drift size delta requires account-state diff.

extract_collateral

extract_collateral(_receipt: dict[str, Any]) -> None

No-op stub — Drift collateral requires account-state diff.

extract_entry_price

extract_entry_price(_receipt: dict[str, Any]) -> None

No-op stub — Drift entry price is not surfaced in receipt logs.

extract_leverage

extract_leverage(_receipt: dict[str, Any]) -> None

No-op stub — Drift leverage is not surfaced in receipt logs.

extract_exit_price

extract_exit_price(_receipt: dict[str, Any]) -> None

No-op stub — Drift exit price requires log parsing (deferred).

extract_realized_pnl

extract_realized_pnl(_receipt: dict[str, Any]) -> None

No-op stub — Drift PnL requires account-state diff (deferred).

extract_fees_paid

extract_fees_paid(_receipt: dict[str, Any]) -> None

No-op stub — Drift fees require log/account-state parsing (deferred).

extract_collateral_returned

extract_collateral_returned(
    _receipt: dict[str, Any],
) -> None

No-op stub — Drift collateral returned requires account-state diff.

extract_protocol_fees

extract_protocol_fees(_receipt: dict[str, Any]) -> None

Placeholder for Drift protocol-fee extraction (VIB-3204).

Drift fees (taker fee, filler reward) are embedded in program log messages and account-state deltas that are non-trivial to parse. Full extraction is deferred to a follow-up ticket.

extract_funding_fee_usd

extract_funding_fee_usd(_receipt: dict[str, Any]) -> None

No-op stub for funding fee USD extraction (VIB-3520).

Drift accrues funding payments through vAMM account-state changes rather than discrete receipt events. Extracting the USD-denominated funding cost at close time requires querying the user's PerpPosition account before and after settlement, which is out of scope for a receipt parser. This stub suppresses the extraction warning that ResultEnricher emits when processing PERP_CLOSE intents.

DriftSDK

DriftSDK(
    wallet_address: str,
    rpc_url: str = "",
    timeout: int = 30,
    gateway_client: GatewayClient | None = None,
)

SDK for building Drift protocol instructions.

Provides methods to: - Derive PDAs for Drift accounts (state, user, markets) - Build Solana instructions for perp trading - Fetch and parse on-chain account data via RPC - Build remaining accounts lists for order placement

Example

sdk = DriftSDK(wallet_address="your-pubkey", rpc_url="https://...") ix = sdk.build_place_perp_order_ix( order_params=OrderParams(direction=DIRECTION_LONG, ...), remaining_accounts=[...], )

get_state_pda

get_state_pda() -> Pubkey

Derive the Drift state account PDA.

get_user_pda

get_user_pda(
    authority: Pubkey | None = None, sub_account_id: int = 0
) -> Pubkey

Derive a Drift User account PDA.

Parameters:

Name Type Description Default
authority Pubkey | None

Wallet pubkey (defaults to SDK's wallet)

None
sub_account_id int

Sub-account ID (default 0)

0

get_user_stats_pda

get_user_stats_pda(
    authority: Pubkey | None = None,
) -> Pubkey

Derive the User Stats account PDA.

get_perp_market_pda

get_perp_market_pda(market_index: int) -> Pubkey

Derive a Perp Market account PDA.

get_spot_market_pda

get_spot_market_pda(market_index: int) -> Pubkey

Derive a Spot Market account PDA.

fetch_user_account

fetch_user_account(
    sub_account_id: int = 0,
) -> DriftUserAccount

Fetch and parse a Drift User account from on-chain data.

Parameters:

Name Type Description Default
sub_account_id int

Sub-account ID to fetch

0

Returns:

Type Description
DriftUserAccount

DriftUserAccount with parsed positions, or exists=False if not found

fetch_market_oracle

fetch_market_oracle(market_index: int) -> Pubkey | None

Fetch the oracle pubkey from a perp market account.

Parameters:

Name Type Description Default
market_index int

Perp market index

required

Returns:

Type Description
Pubkey | None

Oracle Pubkey or None if not found

fetch_spot_market_oracle

fetch_spot_market_oracle(
    market_index: int,
) -> Pubkey | None

Fetch the oracle pubkey from a spot market account.

build_remaining_accounts

build_remaining_accounts(
    market_index: int, sub_account_id: int = 0
) -> list[AccountMeta]

Build the remaining accounts list for a place_perp_order instruction.

Drift requires remaining accounts to include all markets the user has positions in, plus their oracles. The order is: 1. Oracle accounts (readable) 2. Spot market accounts (readable) 3. Perp market accounts (readable)

Parameters:

Name Type Description Default
market_index int

The perp market being traded

required
sub_account_id int

Sub-account ID

0

Returns:

Type Description
list[AccountMeta]

List of AccountMeta for remaining accounts

build_place_perp_order_ix

build_place_perp_order_ix(
    order_params: OrderParams,
    remaining_accounts: list[AccountMeta],
    sub_account_id: int = 0,
) -> Instruction

Build a place_perp_order instruction.

Parameters:

Name Type Description Default
order_params OrderParams

Order parameters to encode

required
remaining_accounts list[AccountMeta]

Pre-built remaining accounts (oracles, markets)

required
sub_account_id int

Sub-account ID

0

Returns:

Type Description
Instruction

Solana Instruction ready for transaction

build_initialize_user_ix

build_initialize_user_ix(
    sub_account_id: int = 0,
) -> Instruction

Build an initialize_user instruction.

Creates a new Drift User account for the wallet. Must be called before the first order if no account exists.

build_initialize_user_stats_ix

build_initialize_user_stats_ix() -> Instruction

Build an initialize_user_stats instruction.

Must be called before initialize_user if no user stats exist.

build_deposit_ix

build_deposit_ix(
    amount: int,
    market_index: int = 0,
    sub_account_id: int = 0,
) -> Instruction

Build a deposit instruction.

Deposits collateral (typically USDC) into a Drift spot market.

Parameters:

Name Type Description Default
amount int

Amount in smallest units (e.g., USDC with 6 decimals)

required
market_index int

Spot market index (0 = USDC)

0
sub_account_id int

Sub-account ID

0

get_init_instructions

get_init_instructions(
    sub_account_id: int = 0,
) -> list[Instruction]

Get instructions to initialize user accounts if they don't exist.

Returns an empty list if accounts already exist.

get_drift_data_api_base_url

get_drift_data_api_base_url() -> str

Return the Drift data API base URL.

Phase 5b of the config-service migration: the legacy module-level constant DRIFT_DATA_API_BASE_URL = os.environ.get(...) froze the env value at import time, which was a real bug (a test that monkeypatched the env var after the constants module was first loaded saw no effect). This helper reads the typed config at call time instead, so monkeypatching DRIFT_DATA_API_BASE_URL between calls works as expected.

is_supported_collateral

is_supported_collateral(collateral_token: str) -> bool

Return True if collateral_token is a Drift spot-market symbol.

The check is case-insensitive. Raw 0x/base58 addresses are NOT recognised by this predicate — address-based collaterals are considered non-validatable here and should be treated as permissive by callers (the compiler's address-resolution path handles that).

Parameters:

Name Type Description Default
collateral_token str

Collateral token symbol (e.g. "USDC", "mSOL"). Case-insensitive.

required

Returns:

Type Description
bool

True if the symbol is a registered Drift spot-market collateral,

bool

False otherwise.

validate_drift_collateral

validate_drift_collateral(collateral_token: str) -> None

Validate that collateral_token is a legal Drift collateral symbol.

Compile-path validation. This is the entry point called by the intent compiler's PERP_OPEN path for Drift intents. It must be invoked BEFORE the Drift adapter is instantiated so that invalid configurations fail before any transaction is built.

Behaviour
  • Empty / whitespace-only strings raise :class:InvalidCollateralForMarketError.
  • 0x-prefixed or base58-looking raw addresses (anything that does not look like a short ticker symbol) are skipped with a debug log — the compiler's downstream resolution path handles address collateral.
  • A recognised symbol returns normally.
  • An unrecognised symbol raises :class:InvalidCollateralForMarketError whose allowed_collaterals lists every Drift spot-market symbol.

Parameters:

Name Type Description Default
collateral_token str

Collateral token symbol (e.g. "USDC", "SOL", "mSOL"). Case-insensitive.

required

Raises:

Type Description
InvalidCollateralForMarketError

When the supplied symbol is not one of Drift's registered spot-market collaterals.

__getattr__

__getattr__(name: str) -> Any

PEP 562 lazy attribute access.

Resolves DRIFT_DATA_API_BASE_URL to the runtime accessor (legacy shim from constants.py) and everything else via _LAZY.