diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index add4434..74fd01d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,9 +2,9 @@ name: Build on: push: - branches: [ '*' ] + branches: ["*"] pull_request: - branches: [ '*' ] + branches: ["*"] jobs: test: @@ -12,31 +12,31 @@ jobs: strategy: matrix: - ruby-version: [2.6, 2.7, '3.0', 3.1] + ruby-version: [3.2] steps: - - uses: actions/checkout@v2 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby-version }} - - - uses: actions/cache@v2 - with: - path: vendor/bundle - key: gems-${{ runner.os }}-${{ matrix.ruby-version }}-${{ hashFiles('**/Gemfile.lock') }} - restore-keys: | - gems-${{ runner.os }}-${{ matrix.ruby-version }}- - gems-${{ runner.os }}- - - # necessary to get ruby 2.3 to work nicely with bundler vendor/bundle cache - # can remove once ruby 2.3 is no longer supported - - run: gem update --system - - - run: bundle config set deployment 'true' - - name: bundle install - run: | - bundle config path vendor/bundle - bundle install --jobs 4 --retry 3 - - - run: bundle exec middleman build + - uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + + - uses: actions/cache@v2 + with: + path: vendor/bundle + key: gems-${{ runner.os }}-${{ matrix.ruby-version }}-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + gems-${{ runner.os }}-${{ matrix.ruby-version }}- + gems-${{ runner.os }}- + + # necessary to get ruby 2.3 to work nicely with bundler vendor/bundle cache + # can remove once ruby 2.3 is no longer supported + - run: gem update --system + + - run: bundle config set deployment 'true' + - name: bundle install + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + + - run: bundle exec middleman build diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3eefbec..0088dfa 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest env: - ruby-version: 2.6 + ruby-version: 3.2 steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/dev_deploy.yml b/.github/workflows/dev_deploy.yml index 85c7f8b..c11ca57 100644 --- a/.github/workflows/dev_deploy.yml +++ b/.github/workflows/dev_deploy.yml @@ -53,7 +53,7 @@ jobs: runs-on: ubuntu-latest env: - ruby-version: 2.6 + ruby-version: 3.2 steps: - uses: actions/checkout@v2 diff --git a/README.md b/README.md index 00871ab..cd60ea0 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Make sure latest ruby version is installed: brew update brew install ruby ``` +(mac users will need to make sure their [`PATH` is set properly](https://mac.install.guide/ruby/13.html) to not use the default system ruby) Update gem (no `sudo`, if it fails running it again seems to work...): ``` @@ -51,9 +52,7 @@ bundle config set force_ruby_platform true Build the docs ``` bundle install -``` - -# Build and run docs locally +`` After doing the above, you can work on docs locally via: ``` diff --git a/source/includes/_historicaldata.md b/source/includes/_historicaldata.md index 678406f..40230bd 100644 --- a/source/includes/_historicaldata.md +++ b/source/includes/_historicaldata.md @@ -31,12 +31,42 @@ mainnet-beta: `https://drift-historical-data.s3-v2.eu-west-1.amazonaws.com/prog | accountKey | user sub account public key (not authority) | | | marketSymbol | market name | SOL-PERP | | year | | 2023 | -| month | | 02 | -| day | utc time | 01 | +| month | | 4 | +| day | utc time | 25 | +| candleResolution | | 1M | ## Examples +Get historical trades on `SOL-PERP` for August 5, 2023: +``` +https://drift-historical-data.s3.eu-west-1.amazonaws.com/program/dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH/market/SOL-PERP/trades/2023/8/5 +``` + +## Records Columns + +Below are definitions of the columns in each record type. + +### trades + +| variable | description | example | +| --- | --- | --- | +| accountKey | user sub account public key (not authority) | | + +### market-trades + +### funding-rates + +### funding-payments + +### deposits + +### liquidations + +### candles + +### settle-pnl-records + ```python import requests diff --git a/source/includes/_orderbook_blockchain.md b/source/includes/_orderbook_blockchain.md new file mode 100644 index 0000000..1922fb4 --- /dev/null +++ b/source/includes/_orderbook_blockchain.md @@ -0,0 +1,241 @@ +# 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). + +```typescript +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"); + +const keyPairFile = '~/.config/solana/my-keypair.json'; +const wallet = new Wallet(loadKeypair(privateKeyFile)) + +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(); +``` + + +```python +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. + +```typescript +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"); + +const keyPairFile = '~/.config/solana/my-keypair.json'; +const wallet = new Wallet(loadKeypair(privateKeyFile)) + +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. + +```typescript +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 | | + + +```python +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 + +```typescript +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 | | + +```python +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 + +```typescript +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 | | + +```python +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. diff --git a/source/includes/_orderbook_dlobserver.md b/source/includes/_orderbook_dlobserver.md new file mode 100644 index 0000000..96ce719 --- /dev/null +++ b/source/includes/_orderbook_dlobserver.md @@ -0,0 +1,316 @@ +# Orderbook/Trades (DLOB Server) + +Drift runs a [`dlob-server`](https://github.com/drift-labs/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](#orderbook-blockchain). + + +mainnet-beta: [https://dlob.drift.trade/](https://dlob.drift.trade/) +devnet: [https://master.dlob.drift.trade/](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](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](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](https://dlob.drift.trade/topMakers?marketName=JTO-PERP&side=bid&limit=5) + +## Websocket + +The mainnet-beta websocket endpoint is: [wss://dlob.drift.trade/ws](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](wss://dlob.drift.trade/ws) + +devnet: [wss://master.dlob.drift.trade/ws](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. | + +```typescript + +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); + } +}); + +``` + +```python +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. + diff --git a/source/index.html.md b/source/index.html.md index e8fd0e2..8d7e526 100644 --- a/source/index.html.md +++ b/source/index.html.md @@ -11,6 +11,8 @@ toc_footers: - Documentation includes: - examples + - orderbook_blockchain + - orderbook_dlobserver - historicaldata - errors @@ -115,18 +117,16 @@ The connection object is used to send transactions to the Solana blockchain. It ```typescript import {Wallet, loadKeypair} from "@drift-labs/sdk"; -const keyPairFile = '~/.config/solana/my-keypair.json'; -const wallet = new Wallet(loadKeypair(privateKeyFile)); +const keyPairFile = `${process.env.HOME}/.config/solana/my-keypair.json`; +const wallet = new Wallet(loadKeypair(keyPairFile)); ``` ```python -from solana.keypair import Keypair -from anchorpy import Wallet import os -import json +from anchorpy import Wallet +from driftpy.keypair import load_keypair -key_pair_file = '~/.config/solana/my-keypair.json' -with open(os.path.expanduser(key_pair_file), 'r') as f: secret = json.load(f) -kp = Keypair.from_secret_key(bytes(secret)) +keypair_file = os.path.expanduser('~/.config/solana/my-keypair.json') +keypair = load_keypair(keypair_file) wallet = Wallet(kp) ``` @@ -143,7 +143,7 @@ import {Wallet, loadKeypair, DriftClient} from "@drift-labs/sdk"; const connection = new Connection('https://api.mainnet-beta.solana.com'); const keyPairFile = '~/.config/solana/my-keypair.json'; -const wallet = new Wallet(loadKeypair(privateKeyFile)) +const wallet = new Wallet(loadKeypair(keyPairFile)) const driftClient = new DriftClient({ connection, @@ -154,16 +154,13 @@ const driftClient = new DriftClient({ driftClient.subscribe(); ``` ```python - import driftpy - from anchorpy import Wallet, Provider - from driftpy.constants.config import configs - from driftpy.clearing_house import ClearingHouse + from anchorpy import Wallet + from driftpy.drift_client import DriftClient + from solana.rpc.async_api import AsyncClient # set connection and wallet # ... - provider = Provider(connection, wallet) - config = configs['mainnet'] # or devnet - drift_client = ClearingHouse.from_config(config, provider) + drift_client = DriftClient(connection, wallet, "mainnet") ``` | Parameter | Description | Optional | Default | @@ -201,8 +198,7 @@ const [txSig, userPublickKey] = await driftClient.initializeUser( ``` ```python -# todo: cannot init with name -tx_sig = await drift_client.intialize_user(0) +tx_sig = await drift_client.initialize_user(sub_account_id=0, name="toly") ``` | Parameter | Description | Optional | Default | @@ -246,6 +242,10 @@ driftClient.switchActiveUser( ); ``` +```python +drift_client.switch_active_user(sub_account_id=1) +``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | subAccountId | The sub account to switch to | No | 0 | @@ -275,15 +275,11 @@ driftClient.deposit( ``` ```python -from driftpy.accounts import get_spot_market_account -from spl.token.instructions import get_associated_token_address spot_market_index = 0 # USDC -spot_market = await get_spot_market_account(drift_client.program, spot_market_index) -user_token_account = get_associated_token_address(drift_client.authority, spot_market.mint) -amount = 100 * QUOTE_PRECISION # $100 +amount = drift_client.convert_to_spot_precision(spot_market_index, 100) # $100 -tx_sig = await drift_client.deposit(amount, spot_market_index, user_token_account) +tx_sig = await drift_client.deposit(amount, spot_market_index) ``` | Parameter | Description | Optional | Default | @@ -308,6 +304,14 @@ driftClient.withdraw( ); ``` +```python + +market_index = 0 # USDC +amount = drift_client.convert_to_spot_precision(market_index, 100) # $100 + +tx_sig = await drift_client.withdraw(amount, spot_market_index) +``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | amount | The amount to withdraw in spot market's token mint precision | No | | @@ -333,6 +337,20 @@ driftClient.transferDeposit( ); ``` +```python +market_index = 0 +amount = drift_client.convert_to_spot_precision(market_ndex, 100) +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, +) +``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | amount | The amount to transfer in spot market's token mint precision | No | | @@ -352,6 +370,42 @@ MARKET, LIMIT, ORACLE orders all support auction parameters. | TRIGGER_LIMIT | Stop / Take-profit limit order. | | ORACLE | Market order using oracle offset for auction parameters. | +## Order Params + +| 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 | | + +## 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 ```typescript @@ -396,50 +450,25 @@ await driftClient.placePerpOrder(orderParams); from driftpy.types import * from driftpy.constants.numeric_constants import BASE_PRECISION, PRICE_PRECISION -subaccount_id = 0 market_index = 0 # place order to long 1 SOL-PERP @ $21.88 (post only) -bid_params = OrderParams( +order_params = OrderParams( order_type=OrderType.LIMIT(), - market_type=MarketType.PERP(), direction=PositionDirection.LONG(), - user_order_id=0, - base_asset_amount=int(1 * BASE_PRECISION), - price=21.88 * PRICE_PRECISION, + base_asset_amount=drift_client.convert_to_perp_precision(1), + price=drift_client.convert_to_price_precision(21.88), market_index=market_index, - reduce_only=False, post_only=PostOnlyParams.TRY_POST_ONLY(), - immediate_or_cancel=False, - trigger_price=0, - trigger_condition=OrderTriggerCondition.ABOVE(), - oracle_price_offset=0, - auction_duration=None, - max_ts=None, - auction_start_price=None, - auction_end_price=None, ) -await drift_client.get_place_perp_order(bid_order_params, subaccount_id) +await drift_client.get_place_perp_order(order_params) ``` | 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 | PERP | -| 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 | Yes | false | -| 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 | | +| orderParams | The order params | No | | + +The order type is set to PERP by default. ## Placing Spot Order @@ -456,32 +485,35 @@ const orderParams = { await driftClient.placeSpotOrder(orderParams); ``` +```python + +market_index = 1 + +order_params = OrderParams( + order_type=OrderType.LIMIT(), + direction=PositionDirection.LONG(), + base_asset_amount=drift_client.convert_to_spot_precision(market_index, 100), + price=drift_client.convert_to_price_precision(100), + market_index=market_index, + ) + +await driftClient.place_spot_order(order_params); +``` + | 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 | SPOT | -| 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 | Yes | false | -| 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 | | +| orderParams | The order params | No | | -## Place Orders +The order type is set to SPOT by default. + +## Placing Multiple Orders ```typescript const placeOrderParams = [ { orderType: OrderType.LIMIT, + marketType: MarketType.PERP, marketIndex: 0, direction: PositionDirection.LONG, baseAssetAmount: driftClient.convertToPerpPrecision(100), @@ -489,6 +521,7 @@ const placeOrderParams = [ }, { orderType: OrderType.LIMIT, + marketType: MarketType.PERP, marketIndex: 0, direction: PositionDirection.SHORT, baseAssetAmount: driftClient.convertToPerpPrecision(100), @@ -499,12 +532,78 @@ const placeOrderParams = [ await driftClient.placeOrders(placeOrderParams); ``` +```python + +place_order_params = [ + OrderParams( + order_type=OrderType.LIMIT(), + market_type=MarketType.PERP(), + market_index=0, + direction=PositionDirection.LONG(), + base_asset_amount=drift_client.convert_to_perp_precision(100), + price=drift_client.convert_to_price_precision(21.23), + ), + OrderParams( + order_type=OrderType.LIMIT(), + market_type=MarketType.PERP(), + market_index=0, + direction=PositionDirection.SHORT(), + base_asset_amount=drift_client.convert_to_perp_precision(100), + oracle_price_offset=drift_client.convert_to_price_precision(.05), + ) +] + +await drift_client.place_orders(place_order_params); +``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | placeOrderParams | Parameters for place order instructions | | | Placing multiple orders in one tx can be cheaper than placing them in separate tx. +## Placing Oracle Market Orders + +```typescript +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) +``` + +```python +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( + OrderType.ORACLE(), + drift_client.convert_to_perp_precision(10), + 18, + 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 ```typescript @@ -513,6 +612,12 @@ const orderId = 1; await driftClient.cancelOrder(orderId); ``` +```python + +order_dd = 1; +await drift_client.cancel_order(order_id); +``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | orderId | The order being canceled | No | | @@ -525,6 +630,12 @@ const userOrderId = 1; await driftClient.cancelOrderByUserOrderId(userOrderId); ``` +```python + +const user_order_id = 1; +await drift_client.cancel_order_by_user_order_id(user_order_id); +``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | userOrderId | Unique order id specified by user when order was placed | No | | @@ -540,8 +651,12 @@ await driftClient.cancelOrders(marketType, marketIndex, direction); ``` ``` python -subaccount_id = 0 -await drift_client.cancel_orders(subaccount_id) # cancels all orders +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 ``` | Parameter | Description | Optional | Default | @@ -581,6 +696,31 @@ const placeOrderParams = [ await driftClient.cancelAndPlaceOrders(cancelOrderParams, placeOrderParams); ``` +```python + +canel_order_params = (MarketType.PERP(), 0, None) # cancel all orders in perp market 0 +place_order_params = [ + OrderParams( + order_type=OrderType.LIMIT(), + market_type=MarketType.PERP(), + market_index=0, + direction=PositionDirection.LONG(), + base_asset_amount=drift_client.convert_to_perp_precision(100), + price=drift_client.convert_to_price_precision(21.23), + ), + OrderParams( + order_type=OrderType.LIMIT(), + market_type=MarketType.PERP(), + market_index=0, + direction=PositionDirection.SHORT(), + base_asset_amount=drift_client.convert_to_perp_precision(100), + oracle_price_offset=drift_client.convert_to_price_precision(.05), + ) +] + +await drift_client.cancel_and_place_orders(canel_order_params, place_order_params); +``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | cancelOrderParams | Parameters for cancel orders instruction | | | @@ -588,6 +728,24 @@ await driftClient.cancelAndPlaceOrders(cancelOrderParams, placeOrderParams); To cancel all orders, do not set any parameters. +## Modify Order Params + +| 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 | | + ## Modifying Order ```typescript @@ -600,23 +758,26 @@ const updateParams = { await driftClient.modifyOrder(orderParams); ``` +```python + +order_id = 1 + +modfiy_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); +``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | orderId | The order id of order to modify | No | | -| newBaseAssetAmount | The amount of base asset to buy or sell | Yes | | -| newDirection | The direction of order e.g. long (bid) or short (ask) | Yes | | -| newLimitPrice | 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 | | -| newTriggerPrice | at what price order is triggered. only applicable for triggerMarket and triggerLimit orders | Yes | | -| newTriggerCondition | whether order is triggered above or below triggerPrice. only applicable for triggerMarket and triggerLimit orders | Yes | | -| newOraclePriceOffset | 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 timestampe before the order expires | Yes | | +| modifyOrderParams | The modify order params | Yes | | + +Modify cancels and places a new order -Modify order 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 @@ -627,26 +788,29 @@ const updateParams = { newBaseAssetAmount: driftClient.convertToPerpPrecision(200), } -await driftClient.modifyOrder(orderParams); +await driftClient.modifyOrderByUserOrderId(orderParams); +``` + +```python + +user_order_id = 1 + +modfiy_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); ``` | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | userOrderId | The user order id of order to modify | No | | -| newBaseAssetAmount | The amount of base asset to buy or sell | Yes | | -| newDirection | The direction of order e.g. long (bid) or short (ask) | Yes | | -| newLimitPrice | 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 | | -| newTriggerPrice | at what price order is triggered. only applicable for triggerMarket and triggerLimit orders | Yes | | -| newTriggerCondition | whether order is triggered above or below triggerPrice. only applicable for triggerMarket and triggerLimit orders | Yes | | -| newOraclePriceOffset | 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 timestampe before the order expires | Yes | | +| modifyOrderParams | The modify order params | Yes | | + +Modify cancels and places a new order -Modify order 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 @@ -660,6 +824,16 @@ await driftClient.settlePNL( ); ``` +```python +market_index = 0 +user = drift_client.get_user() +await drift_client.settle_pnl( + user.user_public_key, + user.get_user_account(), + market_index +) +``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | settleeUserAccountPublicKey | User address you're settling pnl for | No | | @@ -673,6 +847,12 @@ const marketIndex = 1; const spotMarketAccount = driftClient.getSpotMarketAccount(marketIndex); ``` +```python +market_index = 0; + +spot_market_account = drift_client.get_spot_market_account(market_index); +``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | marketIndex | The market index for the spot market | No | | @@ -684,6 +864,12 @@ const marketIndex = 0; const perpMarketAccount = driftClient.getPerpMarketAccount(marketIndex); ``` +```python +market_index = 0; + +perp_market_account = drift_client.get_perp_market_account(market_index); +``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | marketIndex | The market index for the perp market | No | | @@ -693,7 +879,11 @@ const perpMarketAccount = driftClient.getPerpMarketAccount(marketIndex); ## Get User ```typescript - const user = await driftClient.getUser(); + const user = driftClient.getUser(); +``` + +```python + user = drift_client.get_user(); ``` | Parameter | Description | Optional | Default | @@ -714,6 +904,15 @@ const isDeposit = tokenAmount.gte(new BN(0)); const isBorrow = tokenAmount.lt(new BN(0)); ``` +```python +market_index = 0 + +token_amount = user.get_token_amount(marketIndex) + +is_deposit = token_amount > 0 +is_borrow = token_amount < 0 +``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | marketIndex | Market index for the spot market | No | | @@ -732,6 +931,17 @@ const isLong = baseAssetAmount.gte(new BN(0)); const isShort = baseAssetAmount.lt(new BN(0)); ``` +```python +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 +``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | @@ -748,6 +958,12 @@ const order = user.getOrder( ); ``` +```python +order_id = 1 + +order = user.get_order(order_id) +``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | @@ -762,6 +978,11 @@ const order = user.getOrderByUserOrderId( ); ``` +```python +user_order_id = 1 + +order = user.get_order_by_user_order_id(user_order_id) +``` | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | @@ -769,13 +990,21 @@ const order = user.getOrderByUserOrderId( ## Get Open Orders ```typescript -const order = user.getOpenOrders(); +const orders = user.getOpenOrders(); +``` + +```python +orders = user.get_open_orders() ``` ## Get Unrealized Perp Pnl ```typescript - const pnl = await user.getUnrealizedPNL(); + const pnl = user.getUnrealizedPNL(); + ``` + + ```python + pnl = user.get_unrealized_pnl() ``` | Parameter | Description | Optional | Default | @@ -790,6 +1019,10 @@ const order = user.getOpenOrders(); const pnl = await user.getUnrealizedFundingPNL(); ``` + ```python + pnl = user.get_unrealized_funding_pnl() + ``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | marketIndex | Whether to only return pnl for specific market | Yes | | @@ -800,6 +1033,10 @@ const order = user.getOpenOrders(); const totalCollateral = await user.getTotalCollateral(); ``` + ```python + total_collateral = await user.get_total_collateral() + ``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | marginCategory | Initial or Maintenance | Yes | Initial | @@ -812,6 +1049,10 @@ Asset weights vary based on whether you're checking the initial or maintenance m const marginRequirement = await user.getMarginRequirement(); ``` + ```python + margin_requirement = await user.get_margin_requirement() + ``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | marginCategory | Initial or Maintenance | Yes | Initial | @@ -821,177 +1062,42 @@ Liability weights (for borrows) and margin ratios (for perp positions) vary base ## Get Free Collateral ```typescript - const freeCollateral = await user.getFreeCollateral(); + const freeCollateral = user.getFreeCollateral(); ``` -Free collateral is the difference between your total collateral and your initial margin requirement. - -## Get Leverage - - ```typescript - const leverage = await user.getLeverage(); + ```python + free_collateral = user.get_free_collateral() ``` -Leverage is the total liability value (borrows plus total perp position) divided by net asset value (total assets plus total liabilities) - - - -# Orderbook - -## Slot Subscription - -```typescript - import {Connection} from "@solana/web3.js"; - import {SlotSubscriber} from "@drift-labs/sdk"; - - const connection = new Connection("https://api.mainnet-beta.solana.com"); - const slotSubscriber = new SlotSubscriber(connection); - - await slotSubscriber.subscribe(); - const slot = slotSubscriber.getSlot(); - - slotSubscriber.eventEmitter.on('newSlot', async (slot) => { - console.log('new slot', slot); - }); -``` - -| Parameter | Description | Optional | Default | -| ----------- | ----------- | -------- | ------- | -| connection | Connection object specifying solana rpc url | No | | - -The slot subscriber subscribes to the latest slot and updates the slot value every time a new slot is received. The state of the orderbook is dependent on the slot value, so to build the orderbook you must keep track of the slot value. - -## User Subscription - -```typescript - 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"); - - const keyPairFile = '~/.config/solana/my-keypair.json'; - const wallet = new Wallet(loadKeypair(privateKeyFile)) - - const driftClient = new DriftClient({ - connection, - wallet, - env: 'mainnet-beta', - }); - - await driftClient.subscribe(); - - const includeIdleUsers = false; - const userMap = new UserMap(driftClient, {type: 'websocket'}, false); - await userMap.subscribe(); -``` - | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | -| driftClient | DriftClient object | No | | -| accountSubscription | Whether to use websocket or polling to subscribe to users | No | | -| includeIdle | Whether to include idle users. An idle user has had no orders, perp position or borrow for 7 days | Yes | | - -Orders are stored on user accounts. To reconstruct the orderbook, you must keep track of the user accounts that have orders. -The user map subscribes to user account updates. - -## Orderbook Subscription - -```typescript -import {DLOBSubscriber} from "@drift-labs/sdk"; - -// on-chain subscription to users -const userMap = new UserMap(driftClient, {type: 'websocket'}, false); -await userMap.subscribe(); - - const dlobSubscriber = new DLOBSubscriber({ - driftClient, - dlobSource: userMap, - slotSource: slotSubscriber, - updateFrequency: 1000, - }); - -await dlobSubscriber.subscribe(); -``` - -```typescript -import {DLOBApiClient, DLOBSubscriber} from "@drift-labs/sdk"; - - // Polling from api - const dlobApiClient = new DLOBApiClient({ - url: 'https://dlob.drift.trade/orders/idlWithSlot', - }); +| marginCategory | Initial or Maintenance | Yes | Initial | - const dlobSubscriber = new DLOBSubscriber({ - driftClient, - dlobSource: dlobApiClient, - slotSource: slotSubscriber, - updateFrequency: 1000, - }); +Free collateral is the difference between your total collateral and your margin requirement. - await dlobSubscriber.subscribe(); -``` - -| 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 | | +## Get Leverage -## Get L2 Orderbook + ```typescript + const leverage = user.getLeverage(); + ``` -```typescript -const l2 = dlobSubscriber.getL2({ - marketName: 'SOL-PERP', - depth: 50, -}); -``` + ```python + leverage = user.get_leverage() + ``` | 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 | | - -The L2 orderbook is an aggregate of drift dlob orders and, optionally, fallback liquidity. - -## Get L3 Orderbook - -```typescript -const l3 = dlobSubscriber.getL3({ - marketName: 'SOL-PERP', -}); -``` +| includeOpenOrders | Whether to factor in open orders in position size | Yes | true | -| 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 | | +Leverage is the total liability value (borrows plus total perp position) divided by net asset value (total assets plus total liabilities) -The L3 orderbook contains every maker order on drift dlob, including the address for the user that placed the order. # Events ## Event Subscription ```typescript -import {Connection} from "@solana/web3.js"; -import {Wallet, loadKeypair, DriftClient, EventSubscriber} from "@drift-labs/sdk"; - -const connection = new Connection('https://api.mainnet-beta.solana.com'); - -const keyPairFile = '~/.config/solana/my-keypair.json'; -const wallet = new Wallet(loadKeypair(privateKeyFile)) - -const driftClient = new DriftClient({ - connection, - wallet, - env: 'mainnet-beta', -}); +import {EventSubscriber} from "@drift-labs/sdk"; const options = { eventTypes: [ @@ -1027,6 +1133,40 @@ eventSubscriber.eventEmitter.on('newEvent', (event) => { }); ``` +```python +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) +``` + | Parameter | Description | Optional | Default | | ----------- | ----------- | -------- | ------- | | connection | Connection object specifying solana rpc url | No | | @@ -1097,6 +1237,56 @@ eventSubscriber.eventEmitter.on('newEvent', (event) => { ``` +```python +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 + +```python +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 ```typescript @@ -1105,14 +1295,20 @@ const eventType = 'OrderActionRecord'; const events = eventSubscriber.getEventsReceived(eventType); ``` +```python + +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 ```typescript -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.