@drift-labs/sdk 2.26.0-beta.0 → 2.26.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/examples/phoenix.ts +40 -0
  2. package/lib/addresses/pda.d.ts +0 -1
  3. package/lib/adminClient.d.ts +0 -1
  4. package/lib/constants/numericConstants.d.ts +59 -61
  5. package/lib/constants/spotMarkets.d.ts +0 -1
  6. package/lib/dlob/DLOB.d.ts +38 -3
  7. package/lib/dlob/DLOB.js +69 -0
  8. package/lib/dlob/DLOBApiClient.js +1 -1
  9. package/lib/dlob/DLOBNode.d.ts +0 -1
  10. package/lib/dlob/DLOBSubscriber.d.ts +34 -0
  11. package/lib/dlob/DLOBSubscriber.js +100 -0
  12. package/lib/dlob/NodeList.d.ts +0 -1
  13. package/lib/dlob/orderBookLevels.d.ts +44 -0
  14. package/lib/dlob/orderBookLevels.js +137 -0
  15. package/lib/dlob/types.d.ts +2 -0
  16. package/lib/driftClient.d.ts +28 -11
  17. package/lib/driftClient.js +158 -55
  18. package/lib/driftClientConfig.d.ts +3 -0
  19. package/lib/factory/bigNum.d.ts +7 -8
  20. package/lib/idl/drift.json +1 -1
  21. package/lib/index.d.ts +3 -0
  22. package/lib/index.js +3 -0
  23. package/lib/math/amm.d.ts +1 -2
  24. package/lib/math/auction.d.ts +0 -1
  25. package/lib/math/conversion.d.ts +1 -2
  26. package/lib/math/funding.d.ts +0 -1
  27. package/lib/math/insurance.d.ts +0 -1
  28. package/lib/math/margin.d.ts +0 -1
  29. package/lib/math/market.d.ts +0 -1
  30. package/lib/math/oracles.d.ts +0 -1
  31. package/lib/math/orders.d.ts +0 -1
  32. package/lib/math/position.d.ts +0 -1
  33. package/lib/math/repeg.d.ts +0 -1
  34. package/lib/math/spotBalance.d.ts +0 -1
  35. package/lib/math/spotMarket.d.ts +0 -1
  36. package/lib/math/spotPosition.d.ts +0 -1
  37. package/lib/math/trade.d.ts +0 -1
  38. package/lib/math/utils.d.ts +0 -1
  39. package/lib/oracles/pythClient.d.ts +1 -2
  40. package/lib/oracles/types.d.ts +0 -1
  41. package/lib/orderParams.d.ts +0 -1
  42. package/lib/phoenix/phoenixFulfillmentConfigMap.d.ts +10 -0
  43. package/lib/phoenix/phoenixFulfillmentConfigMap.js +17 -0
  44. package/lib/phoenix/phoenixSubscriber.d.ts +34 -0
  45. package/lib/phoenix/phoenixSubscriber.js +79 -0
  46. package/lib/serum/serumSubscriber.d.ts +5 -2
  47. package/lib/serum/serumSubscriber.js +22 -0
  48. package/lib/tokenFaucet.d.ts +0 -1
  49. package/lib/types.d.ts +1 -1
  50. package/lib/user.d.ts +0 -1
  51. package/package.json +1 -1
  52. package/src/assert/assert.js +9 -0
  53. package/src/dlob/DLOB.ts +177 -17
  54. package/src/dlob/DLOBApiClient.ts +3 -1
  55. package/src/dlob/DLOBSubscriber.ts +141 -0
  56. package/src/dlob/orderBookLevels.ts +243 -0
  57. package/src/dlob/types.ts +2 -0
  58. package/src/driftClient.ts +231 -66
  59. package/src/driftClientConfig.ts +3 -0
  60. package/src/idl/drift.json +1 -1
  61. package/src/index.ts +3 -0
  62. package/src/phoenix/phoenixFulfillmentConfigMap.ts +26 -0
  63. package/src/phoenix/phoenixSubscriber.ts +156 -0
  64. package/src/serum/serumSubscriber.ts +27 -1
  65. package/src/token/index.js +38 -0
  66. package/src/types.ts +1 -0
  67. package/src/util/computeUnits.js +27 -0
  68. package/src/util/getTokenAddress.js +9 -0
  69. package/src/util/promiseTimeout.js +14 -0
  70. package/src/util/tps.js +27 -0
  71. package/tests/dlob/helpers.ts +3 -0
@@ -7,8 +7,18 @@ import {
7
7
  DLOBSubscriptionConfig,
8
8
  SlotSource,
9
9
  } from './types';
10
+ import { DriftClient } from '../driftClient';
11
+ import { isVariant, MarketType } from '../types';
12
+ import {
13
+ getVammL2Generator,
14
+ L2OrderBook,
15
+ L2OrderBookGenerator,
16
+ L3OrderBook,
17
+ } from './orderBookLevels';
18
+ import { calculateAskPrice, calculateBidPrice } from '../math/market';
10
19
 
11
20
  export class DLOBSubscriber {
21
+ driftClient: DriftClient;
12
22
  dlobSource: DLOBSource;
13
23
  slotSource: SlotSource;
14
24
  updateFrequency: number;
@@ -17,6 +27,7 @@ export class DLOBSubscriber {
17
27
  public eventEmitter: StrictEventEmitter<EventEmitter, DLOBSubscriberEvents>;
18
28
 
19
29
  constructor(config: DLOBSubscriptionConfig) {
30
+ this.driftClient = config.driftClient;
20
31
  this.dlobSource = config.dlobSource;
21
32
  this.slotSource = config.slotSource;
22
33
  this.updateFrequency = config.updateFrequency;
@@ -48,6 +59,136 @@ export class DLOBSubscriber {
48
59
  return this.dlob;
49
60
  }
50
61
 
62
+ /**
63
+ * Get the L2 order book for a given market.
64
+ *
65
+ * @param marketName e.g. "SOL-PERP" or "SOL". If not provided, marketIndex and marketType must be provided.
66
+ * @param marketIndex
67
+ * @param marketType
68
+ * @param depth Number of orders to include in the order book. Defaults to 10.
69
+ * @param includeVamm Whether to include the VAMM orders in the order book. Defaults to false. If true, creates vAMM generator {@link getVammL2Generator} and adds it to fallbackL2Generators.
70
+ * @param fallbackL2Generators L2 generators for fallback liquidity e.g. vAMM {@link getVammL2Generator}, openbook {@link SerumSubscriber}
71
+ */
72
+ public getL2({
73
+ marketName,
74
+ marketIndex,
75
+ marketType,
76
+ depth = 10,
77
+ includeVamm = false,
78
+ fallbackL2Generators = [],
79
+ }: {
80
+ marketName?: string;
81
+ marketIndex?: number;
82
+ marketType?: MarketType;
83
+ depth?: number;
84
+ includeVamm?: boolean;
85
+ fallbackL2Generators?: L2OrderBookGenerator[];
86
+ }): L2OrderBook {
87
+ if (marketName) {
88
+ const derivedMarketInfo =
89
+ this.driftClient.getMarketIndexAndType(marketName);
90
+ if (!derivedMarketInfo) {
91
+ throw new Error(`Market ${marketName} not found`);
92
+ }
93
+ marketIndex = derivedMarketInfo.marketIndex;
94
+ marketType = derivedMarketInfo.marketType;
95
+ } else {
96
+ if (marketIndex === undefined || marketType === undefined) {
97
+ throw new Error(
98
+ 'Either marketName or marketIndex and marketType must be provided'
99
+ );
100
+ }
101
+ }
102
+
103
+ let oraclePriceData;
104
+ let fallbackBid;
105
+ let fallbackAsk;
106
+ const isPerp = isVariant(marketType, 'perp');
107
+ if (isPerp) {
108
+ const perpMarketAccount =
109
+ this.driftClient.getPerpMarketAccount(marketIndex);
110
+ oraclePriceData = this.driftClient.getOraclePriceDataAndSlot(
111
+ perpMarketAccount.amm.oracle
112
+ );
113
+ fallbackBid = calculateBidPrice(perpMarketAccount, oraclePriceData);
114
+ fallbackAsk = calculateAskPrice(perpMarketAccount, oraclePriceData);
115
+ } else {
116
+ oraclePriceData =
117
+ this.driftClient.getOracleDataForSpotMarket(marketIndex);
118
+ }
119
+
120
+ if (isPerp && includeVamm) {
121
+ fallbackL2Generators = [
122
+ getVammL2Generator({
123
+ marketAccount: this.driftClient.getPerpMarketAccount(marketIndex),
124
+ oraclePriceData,
125
+ numOrders: depth,
126
+ }),
127
+ ];
128
+ }
129
+
130
+ return this.dlob.getL2({
131
+ marketIndex,
132
+ marketType,
133
+ depth,
134
+ oraclePriceData,
135
+ slot: this.slotSource.getSlot(),
136
+ fallbackBid,
137
+ fallbackAsk,
138
+ fallbackL2Generators: fallbackL2Generators,
139
+ });
140
+ }
141
+
142
+ /**
143
+ * Get the L3 order book for a given market.
144
+ *
145
+ * @param marketName e.g. "SOL-PERP" or "SOL". If not provided, marketIndex and marketType must be provided.
146
+ * @param marketIndex
147
+ * @param marketType
148
+ */
149
+ public getL3({
150
+ marketName,
151
+ marketIndex,
152
+ marketType,
153
+ }: {
154
+ marketName?: string;
155
+ marketIndex?: number;
156
+ marketType?: MarketType;
157
+ }): L3OrderBook {
158
+ if (marketName) {
159
+ const derivedMarketInfo =
160
+ this.driftClient.getMarketIndexAndType(marketName);
161
+ if (!derivedMarketInfo) {
162
+ throw new Error(`Market ${marketName} not found`);
163
+ }
164
+ marketIndex = derivedMarketInfo.marketIndex;
165
+ marketType = derivedMarketInfo.marketType;
166
+ } else {
167
+ if (marketIndex === undefined || marketType === undefined) {
168
+ throw new Error(
169
+ 'Either marketName or marketIndex and marketType must be provided'
170
+ );
171
+ }
172
+ }
173
+
174
+ let oraclePriceData;
175
+ const isPerp = isVariant(marketType, 'perp');
176
+ if (isPerp) {
177
+ oraclePriceData =
178
+ this.driftClient.getOracleDataForPerpMarket(marketIndex);
179
+ } else {
180
+ oraclePriceData =
181
+ this.driftClient.getOracleDataForSpotMarket(marketIndex);
182
+ }
183
+
184
+ return this.dlob.getL3({
185
+ marketIndex,
186
+ marketType,
187
+ oraclePriceData,
188
+ slot: this.slotSource.getSlot(),
189
+ });
190
+ }
191
+
51
192
  public async unsubscribe(): Promise<void> {
52
193
  if (this.intervalId) {
53
194
  clearInterval(this.intervalId);
@@ -0,0 +1,243 @@
1
+ import {
2
+ BASE_PRECISION,
3
+ BN,
4
+ calculateAmmReservesAfterSwap,
5
+ calculateMarketOpenBidAsk,
6
+ calculateQuoteAssetAmountSwapped,
7
+ calculateSpreadReserves,
8
+ calculateUpdatedAMM,
9
+ DLOBNode,
10
+ OraclePriceData,
11
+ PerpMarketAccount,
12
+ SwapDirection,
13
+ } from '..';
14
+ import { PublicKey } from '@solana/web3.js';
15
+
16
+ type liquiditySource = 'serum' | 'vamm' | 'dlob';
17
+
18
+ export type L2Level = {
19
+ price: BN;
20
+ size: BN;
21
+ sources: { [key in liquiditySource]?: BN };
22
+ };
23
+
24
+ export type L2OrderBook = {
25
+ asks: L2Level[];
26
+ bids: L2Level[];
27
+ };
28
+
29
+ export interface L2OrderBookGenerator {
30
+ getL2Asks(): Generator<L2Level>;
31
+ getL2Bids(): Generator<L2Level>;
32
+ }
33
+
34
+ export type L3Level = {
35
+ price: BN;
36
+ size: BN;
37
+ maker: PublicKey;
38
+ orderId: number;
39
+ };
40
+
41
+ export type L3OrderBook = {
42
+ asks: L3Level[];
43
+ bids: L3Level[];
44
+ };
45
+
46
+ /**
47
+ * Get an {@link Generator<L2Level>} generator from a {@link Generator<DLOBNode>}
48
+ * @param dlobNodes e.g. {@link DLOB#getMakerLimitAsks} or {@link DLOB#getMakerLimitBids}
49
+ * @param oraclePriceData
50
+ * @param slot
51
+ */
52
+ export function* getL2GeneratorFromDLOBNodes(
53
+ dlobNodes: Generator<DLOBNode>,
54
+ oraclePriceData: OraclePriceData,
55
+ slot: number
56
+ ): Generator<L2Level> {
57
+ for (const dlobNode of dlobNodes) {
58
+ const size = dlobNode.order.baseAssetAmount.sub(
59
+ dlobNode.order.baseAssetAmountFilled
60
+ ) as BN;
61
+ yield {
62
+ size,
63
+ price: dlobNode.getPrice(oraclePriceData, slot),
64
+ sources: {
65
+ dlob: size,
66
+ },
67
+ };
68
+ }
69
+ }
70
+
71
+ export function* mergeL2LevelGenerators(
72
+ l2LevelGenerators: Generator<L2Level>[],
73
+ compare: (a: L2Level, b: L2Level) => boolean
74
+ ): Generator<L2Level> {
75
+ const generators = l2LevelGenerators.map((generator) => {
76
+ return {
77
+ generator,
78
+ next: generator.next(),
79
+ };
80
+ });
81
+
82
+ let next;
83
+ do {
84
+ next = generators.reduce((best, next) => {
85
+ if (next.next.done) {
86
+ return best;
87
+ }
88
+
89
+ if (!best) {
90
+ return next;
91
+ }
92
+
93
+ if (compare(next.next.value, best.next.value)) {
94
+ return next;
95
+ } else {
96
+ return best;
97
+ }
98
+ }, undefined);
99
+
100
+ if (next) {
101
+ yield next.next.value;
102
+ next.next = next.generator.next();
103
+ }
104
+ } while (next !== undefined);
105
+ }
106
+
107
+ export function createL2Levels(
108
+ generator: Generator<L2Level>,
109
+ depth: number
110
+ ): L2Level[] {
111
+ const levels = [];
112
+ for (const level of generator) {
113
+ const price = level.price;
114
+ const size = level.size;
115
+ if (levels.length > 0 && levels[levels.length - 1].price.eq(price)) {
116
+ const currentLevel = levels[levels.length - 1];
117
+ currentLevel.size.add(size);
118
+ for (const [source, size] of Object.entries(level.sources)) {
119
+ if (currentLevel.sources[source]) {
120
+ currentLevel.sources[source] = currentLevel.sources[source].add(size);
121
+ } else {
122
+ currentLevel.sources[source] = size;
123
+ }
124
+ }
125
+ } else if (levels.length === depth) {
126
+ break;
127
+ } else {
128
+ levels.push(level);
129
+ }
130
+ }
131
+ return levels;
132
+ }
133
+
134
+ export function getVammL2Generator({
135
+ marketAccount,
136
+ oraclePriceData,
137
+ numOrders,
138
+ now,
139
+ }: {
140
+ marketAccount: PerpMarketAccount;
141
+ oraclePriceData: OraclePriceData;
142
+ numOrders: number;
143
+ now?: BN;
144
+ }): L2OrderBookGenerator {
145
+ const updatedAmm = calculateUpdatedAMM(marketAccount.amm, oraclePriceData);
146
+
147
+ const [openBids, openAsks] = calculateMarketOpenBidAsk(
148
+ updatedAmm.baseAssetReserve,
149
+ updatedAmm.minBaseAssetReserve,
150
+ updatedAmm.maxBaseAssetReserve,
151
+ updatedAmm.orderStepSize
152
+ );
153
+
154
+ now = now ?? new BN(Date.now() / 1000);
155
+ const [bidReserves, askReserves] = calculateSpreadReserves(
156
+ updatedAmm,
157
+ oraclePriceData,
158
+ now
159
+ );
160
+
161
+ let numBids = 0;
162
+ const baseSize = openBids.div(new BN(numOrders));
163
+ const bidAmm = {
164
+ baseAssetReserve: bidReserves.baseAssetReserve,
165
+ quoteAssetReserve: bidReserves.quoteAssetReserve,
166
+ sqrtK: updatedAmm.sqrtK,
167
+ pegMultiplier: updatedAmm.pegMultiplier,
168
+ };
169
+ const getL2Bids = function* () {
170
+ while (numBids < numOrders) {
171
+ const [afterSwapQuoteReserves, afterSwapBaseReserves] =
172
+ calculateAmmReservesAfterSwap(
173
+ bidAmm,
174
+ 'base',
175
+ baseSize,
176
+ SwapDirection.ADD
177
+ );
178
+
179
+ const quoteSwapped = calculateQuoteAssetAmountSwapped(
180
+ bidAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(),
181
+ bidAmm.pegMultiplier,
182
+ SwapDirection.ADD
183
+ );
184
+
185
+ const price = quoteSwapped.mul(BASE_PRECISION).div(baseSize);
186
+
187
+ bidAmm.baseAssetReserve = afterSwapBaseReserves;
188
+ bidAmm.quoteAssetReserve = afterSwapQuoteReserves;
189
+
190
+ yield {
191
+ price,
192
+ size: baseSize,
193
+ sources: { vamm: baseSize },
194
+ };
195
+
196
+ numBids++;
197
+ }
198
+ };
199
+
200
+ let numAsks = 0;
201
+ const askSize = openAsks.abs().div(new BN(numOrders));
202
+ const askAmm = {
203
+ baseAssetReserve: askReserves.baseAssetReserve,
204
+ quoteAssetReserve: askReserves.quoteAssetReserve,
205
+ sqrtK: updatedAmm.sqrtK,
206
+ pegMultiplier: updatedAmm.pegMultiplier,
207
+ };
208
+ const getL2Asks = function* () {
209
+ while (numAsks < numOrders) {
210
+ const [afterSwapQuoteReserves, afterSwapBaseReserves] =
211
+ calculateAmmReservesAfterSwap(
212
+ askAmm,
213
+ 'base',
214
+ askSize,
215
+ SwapDirection.REMOVE
216
+ );
217
+
218
+ const quoteSwapped = calculateQuoteAssetAmountSwapped(
219
+ askAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(),
220
+ askAmm.pegMultiplier,
221
+ SwapDirection.REMOVE
222
+ );
223
+
224
+ const price = quoteSwapped.mul(BASE_PRECISION).div(askSize);
225
+
226
+ askAmm.baseAssetReserve = afterSwapBaseReserves;
227
+ askAmm.quoteAssetReserve = afterSwapQuoteReserves;
228
+
229
+ yield {
230
+ price,
231
+ size: askSize,
232
+ sources: { vamm: baseSize },
233
+ };
234
+
235
+ numAsks++;
236
+ }
237
+ };
238
+
239
+ return {
240
+ getL2Bids,
241
+ getL2Asks,
242
+ };
243
+ }
package/src/dlob/types.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { DLOB } from './DLOB';
2
+ import { DriftClient } from '../driftClient';
2
3
 
3
4
  export type DLOBSubscriptionConfig = {
5
+ driftClient: DriftClient;
4
6
  dlobSource: DLOBSource;
5
7
  slotSource: SlotSource;
6
8
  updateFrequency: number;