@drift-labs/sdk 2.0.16 → 2.0.18

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.
package/src/dlob/DLOB.ts CHANGED
@@ -17,17 +17,18 @@ import {
17
17
  PerpMarketAccount,
18
18
  OraclePriceData,
19
19
  SlotSubscriber,
20
- UserMapInterface,
21
20
  MarketTypeStr,
22
21
  StateAccount,
23
22
  isMarketOrder,
24
23
  mustBeTriggered,
25
24
  isTriggered,
26
25
  getLimitPrice,
26
+ UserMap,
27
27
  } from '..';
28
28
  import { PublicKey } from '@solana/web3.js';
29
29
  import { DLOBNode, DLOBNodeType, TriggerOrderNode } from '..';
30
30
  import { ammPaused, exchangePaused, fillPaused } from '../math/exchangeStatus';
31
+ import { DLOBOrders } from './DLOBOrders';
31
32
 
32
33
  export type MarketNodeLists = {
33
34
  limit: {
@@ -62,87 +63,14 @@ export type NodeToTrigger = {
62
63
  export class DLOB {
63
64
  openOrders = new Map<MarketTypeStr, Set<string>>();
64
65
  orderLists = new Map<MarketTypeStr, Map<number, MarketNodeLists>>();
65
- stateAccount: StateAccount;
66
- marketIndexToAccount = new Map<
67
- MarketTypeStr,
68
- Map<number, PerpMarketAccount | SpotMarketAccount>
69
- >();
70
-
71
- userMap: UserMapInterface;
72
- silent = false;
73
- initialized = false;
74
66
 
75
- /**
76
- *
77
- * @param perpMarkets The perp markets to maintain a DLOB for
78
- * @param spotMarkets The spot markets to maintain a DLOB for
79
- * @param userMap map of all users
80
- * @param silent set to true to prevent logging on inserts and removals
81
- */
82
- public constructor(
83
- perpMarkets: PerpMarketAccount[],
84
- spotMarkets: SpotMarketAccount[],
85
- stateAccount: StateAccount,
86
- userMap: UserMapInterface,
87
- silent?: boolean
88
- ) {
89
- this.stateAccount = stateAccount;
90
- this.userMap = userMap;
91
- this.silent = silent;
67
+ initialized = false;
92
68
 
69
+ public constructor() {
93
70
  this.openOrders.set('perp', new Set<string>());
94
71
  this.openOrders.set('spot', new Set<string>());
95
72
  this.orderLists.set('perp', new Map<number, MarketNodeLists>());
96
73
  this.orderLists.set('spot', new Map<number, MarketNodeLists>());
97
- this.marketIndexToAccount.set('perp', new Map<number, PerpMarketAccount>());
98
- this.marketIndexToAccount.set('spot', new Map<number, SpotMarketAccount>());
99
-
100
- for (const market of perpMarkets) {
101
- const marketIndex = market.marketIndex;
102
- this.marketIndexToAccount.get('perp').set(marketIndex, market);
103
-
104
- this.orderLists.get('perp').set(marketIndex, {
105
- limit: {
106
- ask: new NodeList('limit', 'asc'),
107
- bid: new NodeList('limit', 'desc'),
108
- },
109
- floatingLimit: {
110
- ask: new NodeList('floatingLimit', 'asc'),
111
- bid: new NodeList('floatingLimit', 'desc'),
112
- },
113
- market: {
114
- ask: new NodeList('market', 'asc'),
115
- bid: new NodeList('market', 'asc'), // always sort ascending for market orders
116
- },
117
- trigger: {
118
- above: new NodeList('trigger', 'asc'),
119
- below: new NodeList('trigger', 'desc'),
120
- },
121
- });
122
- }
123
- for (const market of spotMarkets) {
124
- const marketIndex = market.marketIndex;
125
- this.marketIndexToAccount.get('spot').set(marketIndex, market);
126
-
127
- this.orderLists.get('spot').set(marketIndex, {
128
- limit: {
129
- ask: new NodeList('limit', 'asc'),
130
- bid: new NodeList('limit', 'desc'),
131
- },
132
- floatingLimit: {
133
- ask: new NodeList('floatingLimit', 'asc'),
134
- bid: new NodeList('floatingLimit', 'desc'),
135
- },
136
- market: {
137
- ask: new NodeList('market', 'asc'),
138
- bid: new NodeList('market', 'asc'), // always sort ascending for market orders
139
- },
140
- trigger: {
141
- above: new NodeList('trigger', 'asc'),
142
- below: new NodeList('trigger', 'desc'),
143
- },
144
- });
145
- }
146
74
  }
147
75
 
148
76
  public clear() {
@@ -164,11 +92,6 @@ export class DLOB {
164
92
  }
165
93
  }
166
94
  this.orderLists.clear();
167
-
168
- for (const marketType of this.marketIndexToAccount.keys()) {
169
- this.marketIndexToAccount.get(marketType).clear();
170
- }
171
- this.marketIndexToAccount.clear();
172
95
  }
173
96
 
174
97
  /**
@@ -176,13 +99,13 @@ export class DLOB {
176
99
  *
177
100
  * @returns a promise that resolves when the DLOB is initialized
178
101
  */
179
- public async init(): Promise<boolean> {
102
+ public async initFromUserMap(userMap: UserMap): Promise<boolean> {
180
103
  if (this.initialized) {
181
104
  return false;
182
105
  }
183
106
 
184
- // initialize the dlob with the user map (prevents hitting getProgramAccounts)
185
- for (const user of this.userMap.values()) {
107
+ // initialize the dlob with the user map
108
+ for (const user of userMap.values()) {
186
109
  const userAccount = user.getUserAccount();
187
110
  const userAccountPubkey = user.getUserAccountPublicKey();
188
111
 
@@ -195,6 +118,19 @@ export class DLOB {
195
118
  return true;
196
119
  }
197
120
 
121
+ public initFromOrders(dlobOrders: DLOBOrders): boolean {
122
+ if (this.initialized) {
123
+ return false;
124
+ }
125
+
126
+ for (const { user, order } of dlobOrders) {
127
+ this.insertOrder(order, user);
128
+ }
129
+
130
+ this.initialized = true;
131
+ return true;
132
+ }
133
+
198
134
  public insertOrder(
199
135
  order: Order,
200
136
  userAccount: PublicKey,
@@ -206,23 +142,43 @@ export class DLOB {
206
142
 
207
143
  const marketType = getVariant(order.marketType) as MarketTypeStr;
208
144
 
145
+ if (!this.orderLists.get(marketType).has(order.marketIndex)) {
146
+ this.addOrderList(marketType, order.marketIndex);
147
+ }
148
+
209
149
  if (isVariant(order.status, 'open')) {
210
150
  this.openOrders
211
151
  .get(marketType)
212
152
  .add(getOrderSignature(order.orderId, userAccount));
213
153
  }
214
- this.getListForOrder(order)?.insert(
215
- order,
216
- marketType,
217
- this.marketIndexToAccount.get(marketType).get(order.marketIndex),
218
- userAccount
219
- );
154
+ this.getListForOrder(order)?.insert(order, marketType, userAccount);
220
155
 
221
156
  if (onInsert) {
222
157
  onInsert();
223
158
  }
224
159
  }
225
160
 
161
+ addOrderList(marketType: MarketTypeStr, marketIndex: number): void {
162
+ this.orderLists.get(marketType).set(marketIndex, {
163
+ limit: {
164
+ ask: new NodeList('limit', 'asc'),
165
+ bid: new NodeList('limit', 'desc'),
166
+ },
167
+ floatingLimit: {
168
+ ask: new NodeList('floatingLimit', 'asc'),
169
+ bid: new NodeList('floatingLimit', 'desc'),
170
+ },
171
+ market: {
172
+ ask: new NodeList('market', 'asc'),
173
+ bid: new NodeList('market', 'asc'), // always sort ascending for market orders
174
+ },
175
+ trigger: {
176
+ above: new NodeList('trigger', 'asc'),
177
+ below: new NodeList('trigger', 'desc'),
178
+ },
179
+ });
180
+ }
181
+
226
182
  public trigger(
227
183
  order: Order,
228
184
  userAccount: PublicKey,
@@ -238,12 +194,7 @@ export class DLOB {
238
194
  .trigger[isVariant(order.triggerCondition, 'above') ? 'above' : 'below'];
239
195
  triggerList.remove(order, userAccount);
240
196
 
241
- this.getListForOrder(order)?.insert(
242
- order,
243
- marketType,
244
- this.marketIndexToAccount.get(marketType).get(order.marketIndex),
245
- userAccount
246
- );
197
+ this.getListForOrder(order)?.insert(order, marketType, userAccount);
247
198
  if (onTrigger) {
248
199
  onTrigger();
249
200
  }
@@ -289,17 +240,15 @@ export class DLOB {
289
240
  slot: number,
290
241
  ts: number,
291
242
  marketType: MarketType,
292
- oraclePriceData: OraclePriceData
243
+ oraclePriceData: OraclePriceData,
244
+ stateAccount: StateAccount,
245
+ marketAccount: PerpMarketAccount | SpotMarketAccount
293
246
  ): NodeToFill[] {
294
- const marketAccount = this.marketIndexToAccount
295
- .get(getVariant(marketType) as MarketTypeStr)
296
- .get(marketIndex);
297
-
298
- if (fillPaused(this.stateAccount, marketAccount)) {
247
+ if (fillPaused(stateAccount, marketAccount)) {
299
248
  return [];
300
249
  }
301
250
 
302
- const isAmmPaused = ammPaused(this.stateAccount, marketAccount);
251
+ const isAmmPaused = ammPaused(stateAccount, marketAccount);
303
252
 
304
253
  const marketOrderNodesToFill: Array<NodeToFill> =
305
254
  this.findMarketNodesToFill(
@@ -517,17 +466,9 @@ export class DLOB {
517
466
  );
518
467
 
519
468
  for (const makerNode of makerNodeGenerator) {
520
- const bidUserAuthority = this.userMap.getUserAuthority(
521
- makerNode.userAccount.toString()
522
- );
523
- const askUserAuthority = this.userMap.getUserAuthority(
524
- takerNode.userAccount.toString()
525
- );
526
-
527
- // Can't match orders from the same authority
528
- const sameAuthority = bidUserAuthority.equals(askUserAuthority);
529
-
530
- if (sameAuthority) {
469
+ // Can't match orders from the same user
470
+ const sameUser = takerNode.userAccount.equals(makerNode.userAccount);
471
+ if (sameUser) {
531
472
  continue;
532
473
  }
533
474
 
@@ -701,10 +642,12 @@ export class DLOB {
701
642
  marketType: MarketType
702
643
  ): Generator<DLOBNode> {
703
644
  const marketTypeStr = getVariant(marketType) as MarketTypeStr;
704
- const generator = this.orderLists
705
- .get(marketTypeStr)
706
- .get(marketIndex)
707
- .market.bid.getGenerator();
645
+ const orderLists = this.orderLists.get(marketTypeStr).get(marketIndex);
646
+ if (!orderLists) {
647
+ return;
648
+ }
649
+
650
+ const generator = orderLists.market.bid.getGenerator();
708
651
  for (const marketBidNode of generator) {
709
652
  if (marketBidNode.isBaseFilled()) {
710
653
  continue;
@@ -718,10 +661,12 @@ export class DLOB {
718
661
  marketType: MarketType
719
662
  ): Generator<DLOBNode> {
720
663
  const marketTypeStr = getVariant(marketType) as MarketTypeStr;
721
- const generator = this.orderLists
722
- .get(marketTypeStr)
723
- .get(marketIndex)
724
- .market.ask.getGenerator();
664
+ const orderLists = this.orderLists.get(marketTypeStr).get(marketIndex);
665
+ if (!orderLists) {
666
+ return;
667
+ }
668
+
669
+ const generator = orderLists.market.ask.getGenerator();
725
670
  for (const marketAskNode of generator) {
726
671
  if (marketAskNode.isBaseFilled()) {
727
672
  continue;
@@ -782,17 +727,6 @@ export class DLOB {
782
727
  continue;
783
728
  }
784
729
 
785
- // skip order if user is being liquidated/bankrupt
786
- if (bestGenerator.next.value.userAccount !== undefined) {
787
- const user = this.userMap.get(
788
- bestGenerator.next.value.userAccount.toString()
789
- );
790
- if (user?.isBeingLiquidated()) {
791
- bestGenerator.next = bestGenerator.generator.next();
792
- continue;
793
- }
794
- }
795
-
796
730
  yield bestGenerator.next.value;
797
731
  bestGenerator.next = bestGenerator.generator.next();
798
732
  } else {
@@ -813,6 +747,10 @@ export class DLOB {
813
747
  const marketTypeStr = getVariant(marketType) as MarketTypeStr;
814
748
  const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
815
749
 
750
+ if (!nodeLists) {
751
+ return;
752
+ }
753
+
816
754
  const generatorList = [
817
755
  nodeLists.limit.ask.getGenerator(),
818
756
  nodeLists.floatingLimit.ask.getGenerator(),
@@ -841,6 +779,10 @@ export class DLOB {
841
779
  const marketTypeStr = getVariant(marketType) as MarketTypeStr;
842
780
  const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
843
781
 
782
+ if (!nodeLists) {
783
+ return;
784
+ }
785
+
844
786
  const generatorList = [
845
787
  nodeLists.limit.bid.getGenerator(),
846
788
  nodeLists.floatingLimit.bid.getGenerator(),
@@ -949,16 +891,9 @@ export class DLOB {
949
891
  const bidOrder = bidNode.order;
950
892
  const askOrder = askNode.order;
951
893
 
952
- const bidUserAuthority = this.userMap.getUserAuthority(
953
- bidNode.userAccount.toString()
954
- );
955
- const askUserAuthority = this.userMap.getUserAuthority(
956
- askNode.userAccount.toString()
957
- );
958
-
959
- // Can't match orders from the same authority
960
- const sameAuthority = bidUserAuthority.equals(askUserAuthority);
961
- if (sameAuthority || (bidOrder.postOnly && askOrder.postOnly)) {
894
+ // Can't match orders from the same user
895
+ const sameUser = bidNode.userAccount.equals(askNode.userAccount);
896
+ if (sameUser || (bidOrder.postOnly && askOrder.postOnly)) {
962
897
  continue;
963
898
  }
964
899
 
@@ -1074,9 +1009,10 @@ export class DLOB {
1074
1009
  marketIndex: number,
1075
1010
  slot: number,
1076
1011
  oraclePrice: BN,
1077
- marketType: MarketType
1012
+ marketType: MarketType,
1013
+ stateAccount: StateAccount
1078
1014
  ): NodeToTrigger[] {
1079
- if (exchangePaused(this.stateAccount)) {
1015
+ if (exchangePaused(stateAccount)) {
1080
1016
  return [];
1081
1017
  }
1082
1018
 
@@ -1216,4 +1152,88 @@ export class DLOB {
1216
1152
  );
1217
1153
  }
1218
1154
  }
1155
+
1156
+ public getDLOBOrders(): DLOBOrders {
1157
+ let dlobOrders: DLOBOrders = [];
1158
+
1159
+ for (const [marketIndex, nodeLists] of this.orderLists.get('perp')) {
1160
+ dlobOrders = dlobOrders.concat(
1161
+ this.getOrdersForMarket(marketIndex, MarketType.PERP, nodeLists)
1162
+ );
1163
+ }
1164
+
1165
+ for (const [marketIndex, nodeLists] of this.orderLists.get('spot')) {
1166
+ dlobOrders = dlobOrders.concat(
1167
+ this.getOrdersForMarket(marketIndex, MarketType.SPOT, nodeLists)
1168
+ );
1169
+ }
1170
+
1171
+ return dlobOrders;
1172
+ }
1173
+
1174
+ getOrdersForMarket(
1175
+ marketIndex: number,
1176
+ marketType: MarketType,
1177
+ nodeLists: MarketNodeLists
1178
+ ): DLOBOrders {
1179
+ const orders: { user: PublicKey; order: Order }[] = [];
1180
+
1181
+ for (const node of nodeLists.limit.bid.getGenerator()) {
1182
+ orders.push({
1183
+ user: node.userAccount,
1184
+ order: node.order,
1185
+ });
1186
+ }
1187
+
1188
+ for (const node of nodeLists.limit.ask.getGenerator()) {
1189
+ orders.push({
1190
+ user: node.userAccount,
1191
+ order: node.order,
1192
+ });
1193
+ }
1194
+
1195
+ for (const node of nodeLists.market.bid.getGenerator()) {
1196
+ orders.push({
1197
+ user: node.userAccount,
1198
+ order: node.order,
1199
+ });
1200
+ }
1201
+
1202
+ for (const node of nodeLists.market.ask.getGenerator()) {
1203
+ orders.push({
1204
+ user: node.userAccount,
1205
+ order: node.order,
1206
+ });
1207
+ }
1208
+
1209
+ for (const node of nodeLists.floatingLimit.bid.getGenerator()) {
1210
+ orders.push({
1211
+ user: node.userAccount,
1212
+ order: node.order,
1213
+ });
1214
+ }
1215
+
1216
+ for (const node of nodeLists.floatingLimit.ask.getGenerator()) {
1217
+ orders.push({
1218
+ user: node.userAccount,
1219
+ order: node.order,
1220
+ });
1221
+ }
1222
+
1223
+ for (const node of nodeLists.trigger.below.getGenerator()) {
1224
+ orders.push({
1225
+ user: node.userAccount,
1226
+ order: node.order,
1227
+ });
1228
+ }
1229
+
1230
+ for (const node of nodeLists.trigger.above.getGenerator()) {
1231
+ orders.push({
1232
+ user: node.userAccount,
1233
+ order: node.order,
1234
+ });
1235
+ }
1236
+
1237
+ return orders;
1238
+ }
1219
1239
  }
@@ -4,8 +4,6 @@ import {
4
4
  convertToNumber,
5
5
  getLimitPrice,
6
6
  isVariant,
7
- SpotMarketAccount,
8
- PerpMarketAccount,
9
7
  PRICE_PRECISION,
10
8
  OraclePriceData,
11
9
  Order,
@@ -21,24 +19,17 @@ export interface DLOBNode {
21
19
  isBaseFilled(): boolean;
22
20
  haveFilled: boolean;
23
21
  userAccount: PublicKey | undefined;
24
- market: SpotMarketAccount | PerpMarketAccount;
25
22
  }
26
23
 
27
24
  export abstract class OrderNode implements DLOBNode {
28
25
  order: Order;
29
- market: SpotMarketAccount | PerpMarketAccount;
30
26
  userAccount: PublicKey;
31
27
  sortValue: BN;
32
28
  haveFilled = false;
33
29
  haveTrigger = false;
34
30
 
35
- constructor(
36
- order: Order,
37
- market: SpotMarketAccount | PerpMarketAccount,
38
- userAccount: PublicKey
39
- ) {
31
+ constructor(order: Order, userAccount: PublicKey) {
40
32
  this.order = order;
41
- this.market = market;
42
33
  this.userAccount = userAccount;
43
34
  this.sortValue = this.getSortValue(order);
44
35
  }
@@ -137,18 +128,17 @@ export type DLOBNodeType =
137
128
  export function createNode<T extends DLOBNodeType>(
138
129
  nodeType: T,
139
130
  order: Order,
140
- market: SpotMarketAccount | PerpMarketAccount,
141
131
  userAccount: PublicKey
142
132
  ): DLOBNodeMap[T] {
143
133
  switch (nodeType) {
144
134
  case 'floatingLimit':
145
- return new FloatingLimitOrderNode(order, market, userAccount);
135
+ return new FloatingLimitOrderNode(order, userAccount);
146
136
  case 'limit':
147
- return new LimitOrderNode(order, market, userAccount);
137
+ return new LimitOrderNode(order, userAccount);
148
138
  case 'market':
149
- return new MarketOrderNode(order, market, userAccount);
139
+ return new MarketOrderNode(order, userAccount);
150
140
  case 'trigger':
151
- return new TriggerOrderNode(order, market, userAccount);
141
+ return new TriggerOrderNode(order, userAccount);
152
142
  default:
153
143
  throw Error(`Unknown DLOBNode type ${nodeType}`);
154
144
  }
@@ -0,0 +1,49 @@
1
+ import { PublicKey } from '@solana/web3.js';
2
+ import { Idl } from '@project-serum/anchor';
3
+ import { IdlCoder } from '@project-serum/anchor/dist/cjs/coder/borsh/idl';
4
+ import dlobIDL from './dlobIdl.json';
5
+ import { Order } from '../types';
6
+
7
+ export type DLOBOrder = { user: PublicKey; order: Order };
8
+
9
+ export type DLOBOrders = DLOBOrder[];
10
+
11
+ export class DLOBOrdersCoder {
12
+ public constructor(private idl: Idl) {}
13
+
14
+ static create(): DLOBOrdersCoder {
15
+ return new DLOBOrdersCoder(dlobIDL as Idl);
16
+ }
17
+
18
+ public encode(dlobOrders: DLOBOrders): Buffer {
19
+ const layout = IdlCoder.fieldLayout(
20
+ {
21
+ type: {
22
+ vec: {
23
+ defined: 'DLOBOrder',
24
+ },
25
+ },
26
+ },
27
+ this.idl.types
28
+ );
29
+
30
+ const size = 150 * dlobOrders.length;
31
+ const buffer = Buffer.alloc(size);
32
+ const len = layout.encode(dlobOrders, buffer);
33
+ return buffer.slice(0, len);
34
+ }
35
+
36
+ public decode(buffer: Buffer): DLOBOrders {
37
+ const layout = IdlCoder.fieldLayout(
38
+ {
39
+ type: {
40
+ vec: {
41
+ defined: 'DLOBOrder',
42
+ },
43
+ },
44
+ },
45
+ this.idl.types
46
+ );
47
+ return layout.decode(buffer) as DLOBOrders;
48
+ }
49
+ }
@@ -1,11 +1,4 @@
1
- import {
2
- BN,
3
- isVariant,
4
- MarketTypeStr,
5
- Order,
6
- PerpMarketAccount,
7
- SpotMarketAccount,
8
- } from '..';
1
+ import { BN, isVariant, MarketTypeStr, Order } from '..';
9
2
  import { PublicKey } from '@solana/web3.js';
10
3
  import { createNode, DLOBNode, DLOBNodeMap } from './DLOBNode';
11
4
 
@@ -43,20 +36,13 @@ export class NodeList<NodeType extends keyof DLOBNodeMap>
43
36
  public insert(
44
37
  order: Order,
45
38
  marketType: MarketTypeStr,
46
- market: PerpMarketAccount | SpotMarketAccount,
47
39
  userAccount: PublicKey
48
40
  ): void {
49
41
  if (isVariant(order.status, 'init')) {
50
42
  return;
51
43
  }
52
44
 
53
- if (marketType === 'spot') {
54
- market = market as SpotMarketAccount;
55
- } else if (marketType === 'perp') {
56
- market = market as PerpMarketAccount;
57
- }
58
-
59
- const newNode = createNode(this.nodeType, order, market, userAccount);
45
+ const newNode = createNode(this.nodeType, order, userAccount);
60
46
 
61
47
  const orderSignature = getOrderSignature(order.orderId, userAccount);
62
48
  if (this.nodeMap.has(orderSignature)) {
@@ -187,7 +173,6 @@ export function* getVammNodeGenerator(
187
173
  getPrice: () => price,
188
174
  isVammNode: () => true,
189
175
  order: undefined,
190
- market: undefined,
191
176
  userAccount: undefined,
192
177
  isBaseFilled: () => false,
193
178
  haveFilled: false,