@drift-labs/sdk 0.2.0-master.4 → 0.2.0-master.40

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 (234) hide show
  1. package/README.md +27 -27
  2. package/lib/accounts/bulkAccountLoader.d.ts +2 -0
  3. package/lib/accounts/bulkAccountLoader.js +36 -29
  4. package/lib/accounts/bulkUserStatsSubscription.d.ts +7 -0
  5. package/lib/accounts/bulkUserStatsSubscription.js +21 -0
  6. package/lib/accounts/bulkUserSubscription.d.ts +2 -2
  7. package/lib/accounts/bulkUserSubscription.js +0 -1
  8. package/lib/accounts/fetch.d.ts +2 -1
  9. package/lib/accounts/fetch.js +9 -1
  10. package/lib/accounts/{pollingClearingHouseAccountSubscriber.d.ts → pollingDriftClientAccountSubscriber.d.ts} +20 -25
  11. package/lib/accounts/{pollingClearingHouseAccountSubscriber.js → pollingDriftClientAccountSubscriber.js} +45 -49
  12. package/lib/accounts/{pollingOracleSubscriber.d.ts → pollingOracleAccountSubscriber.d.ts} +2 -2
  13. package/lib/accounts/{pollingOracleSubscriber.js → pollingOracleAccountSubscriber.js} +3 -3
  14. package/lib/accounts/pollingUserStatsAccountSubscriber.d.ts +27 -0
  15. package/lib/accounts/pollingUserStatsAccountSubscriber.js +113 -0
  16. package/lib/accounts/types.d.ts +26 -15
  17. package/lib/accounts/webSocketDriftClientAccountSubscriber.d.ts +49 -0
  18. package/lib/accounts/{webSocketClearingHouseAccountSubscriber.js → webSocketDriftClientAccountSubscriber.js} +47 -45
  19. package/lib/accounts/webSocketUserStatsAccountSubsriber.d.ts +20 -0
  20. package/lib/accounts/webSocketUserStatsAccountSubsriber.js +47 -0
  21. package/lib/addresses/marketAddresses.d.ts +1 -3
  22. package/lib/addresses/marketAddresses.js +1 -1
  23. package/lib/addresses/pda.d.ts +15 -9
  24. package/lib/addresses/pda.js +73 -35
  25. package/lib/adminClient.d.ts +65 -0
  26. package/lib/adminClient.js +637 -0
  27. package/lib/config.d.ts +9 -9
  28. package/lib/config.js +25 -21
  29. package/lib/constants/numericConstants.d.ts +30 -12
  30. package/lib/constants/numericConstants.js +41 -21
  31. package/lib/constants/perpMarkets.d.ts +18 -0
  32. package/lib/constants/{markets.js → perpMarkets.js} +7 -7
  33. package/lib/constants/spotMarkets.d.ts +19 -0
  34. package/lib/constants/spotMarkets.js +53 -0
  35. package/lib/dlob/DLOB.d.ts +82 -0
  36. package/lib/dlob/DLOB.js +696 -0
  37. package/lib/dlob/DLOBNode.d.ts +54 -0
  38. package/lib/dlob/DLOBNode.js +77 -0
  39. package/lib/dlob/NodeList.d.ts +27 -0
  40. package/lib/dlob/NodeList.js +144 -0
  41. package/lib/driftClient.d.ts +233 -0
  42. package/lib/driftClient.js +2096 -0
  43. package/lib/{clearingHouseConfig.d.ts → driftClientConfig.d.ts} +9 -9
  44. package/lib/{clearingHouseConfig.js → driftClientConfig.js} +0 -0
  45. package/lib/events/eventList.js +3 -0
  46. package/lib/events/eventSubscriber.d.ts +5 -2
  47. package/lib/events/eventSubscriber.js +25 -11
  48. package/lib/events/fetchLogs.d.ts +13 -2
  49. package/lib/events/fetchLogs.js +40 -12
  50. package/lib/events/pollingLogProvider.d.ts +2 -1
  51. package/lib/events/pollingLogProvider.js +7 -3
  52. package/lib/events/sort.js +8 -11
  53. package/lib/events/types.d.ts +9 -3
  54. package/lib/events/types.js +6 -0
  55. package/lib/events/webSocketLogProvider.js +1 -1
  56. package/lib/examples/makeTradeExample.js +30 -18
  57. package/lib/factory/bigNum.d.ts +8 -4
  58. package/lib/factory/bigNum.js +109 -19
  59. package/lib/idl/drift.json +8250 -0
  60. package/lib/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  61. package/lib/index.d.ts +30 -13
  62. package/lib/index.js +30 -13
  63. package/lib/math/amm.d.ts +9 -6
  64. package/lib/math/amm.js +91 -38
  65. package/lib/math/conversion.js +1 -1
  66. package/lib/math/exchangeStatus.d.ts +4 -0
  67. package/lib/math/exchangeStatus.js +18 -0
  68. package/lib/math/funding.d.ts +6 -6
  69. package/lib/math/funding.js +23 -21
  70. package/lib/math/insurance.d.ts +4 -0
  71. package/lib/math/insurance.js +27 -0
  72. package/lib/math/margin.d.ts +11 -0
  73. package/lib/math/margin.js +82 -0
  74. package/lib/math/market.d.ts +14 -9
  75. package/lib/math/market.js +70 -10
  76. package/lib/math/oracles.d.ts +4 -0
  77. package/lib/math/oracles.js +36 -8
  78. package/lib/math/orders.d.ts +14 -6
  79. package/lib/math/orders.js +90 -17
  80. package/lib/math/position.d.ts +27 -13
  81. package/lib/math/position.js +92 -36
  82. package/lib/math/repeg.js +17 -8
  83. package/lib/math/spotBalance.d.ts +22 -0
  84. package/lib/math/spotBalance.js +192 -0
  85. package/lib/math/spotMarket.d.ts +4 -0
  86. package/lib/math/spotMarket.js +8 -0
  87. package/lib/math/spotPosition.d.ts +6 -0
  88. package/lib/math/spotPosition.js +23 -0
  89. package/lib/math/trade.d.ts +10 -10
  90. package/lib/math/trade.js +27 -31
  91. package/lib/oracles/pythClient.js +1 -1
  92. package/lib/oracles/quoteAssetOracleClient.js +1 -1
  93. package/lib/oracles/switchboardClient.js +1 -1
  94. package/lib/orderParams.d.ts +4 -4
  95. package/lib/orderParams.js +12 -4
  96. package/lib/serum/serumFulfillmentConfigMap.d.ts +10 -0
  97. package/lib/serum/serumFulfillmentConfigMap.js +17 -0
  98. package/lib/serum/serumSubscriber.d.ts +27 -0
  99. package/lib/serum/serumSubscriber.js +56 -0
  100. package/lib/serum/types.d.ts +11 -0
  101. package/lib/{clearingHouseUserConfig.js → serum/types.js} +0 -0
  102. package/lib/slot/SlotSubscriber.d.ts +7 -0
  103. package/lib/slot/SlotSubscriber.js +3 -0
  104. package/lib/{mockUSDCFaucet.d.ts → tokenFaucet.d.ts} +8 -5
  105. package/lib/{mockUSDCFaucet.js → tokenFaucet.js} +63 -51
  106. package/lib/tx/retryTxSender.d.ts +1 -1
  107. package/lib/tx/retryTxSender.js +13 -4
  108. package/lib/tx/types.d.ts +1 -1
  109. package/lib/tx/utils.js +1 -1
  110. package/lib/types.d.ts +589 -196
  111. package/lib/types.js +108 -17
  112. package/lib/user.d.ts +226 -0
  113. package/lib/user.js +949 -0
  114. package/lib/userConfig.d.ts +14 -0
  115. package/lib/userConfig.js +2 -0
  116. package/lib/userMap/userMap.d.ts +41 -0
  117. package/lib/userMap/userMap.js +85 -0
  118. package/lib/userMap/userStatsMap.d.ts +19 -0
  119. package/lib/userMap/userStatsMap.js +68 -0
  120. package/lib/userName.d.ts +1 -0
  121. package/lib/userName.js +3 -2
  122. package/lib/userStats.d.ts +18 -0
  123. package/lib/userStats.js +49 -0
  124. package/lib/userStatsConfig.d.ts +14 -0
  125. package/lib/userStatsConfig.js +2 -0
  126. package/lib/util/computeUnits.js +1 -1
  127. package/lib/util/getTokenAddress.d.ts +2 -0
  128. package/lib/util/getTokenAddress.js +9 -0
  129. package/package.json +10 -3
  130. package/src/accounts/bulkAccountLoader.ts +44 -34
  131. package/src/accounts/bulkUserStatsSubscription.ts +33 -0
  132. package/src/accounts/bulkUserSubscription.ts +2 -3
  133. package/src/accounts/fetch.ts +27 -2
  134. package/src/accounts/{pollingClearingHouseAccountSubscriber.ts → pollingDriftClientAccountSubscriber.ts} +65 -75
  135. package/src/accounts/{pollingOracleSubscriber.ts → pollingOracleAccountSubscriber.ts} +2 -2
  136. package/src/accounts/pollingUserStatsAccountSubscriber.ts +172 -0
  137. package/src/accounts/types.ts +35 -15
  138. package/src/accounts/{webSocketClearingHouseAccountSubscriber.ts → webSocketDriftClientAccountSubscriber.ts} +78 -73
  139. package/src/accounts/webSocketUserStatsAccountSubsriber.ts +80 -0
  140. package/src/addresses/marketAddresses.ts +3 -4
  141. package/src/addresses/pda.ts +105 -33
  142. package/src/adminClient.ts +1207 -0
  143. package/src/config.ts +37 -31
  144. package/src/constants/numericConstants.ts +58 -24
  145. package/src/constants/{markets.ts → perpMarkets.ts} +10 -10
  146. package/src/constants/spotMarkets.ts +73 -0
  147. package/src/dlob/DLOB.ts +1123 -0
  148. package/src/dlob/DLOBNode.ts +155 -0
  149. package/src/dlob/NodeList.ts +195 -0
  150. package/src/driftClient.ts +3564 -0
  151. package/src/{clearingHouseConfig.ts → driftClientConfig.ts} +9 -8
  152. package/src/events/eventList.ts +3 -0
  153. package/src/events/eventSubscriber.ts +36 -14
  154. package/src/events/fetchLogs.ts +55 -13
  155. package/src/events/pollingLogProvider.ts +11 -3
  156. package/src/events/sort.ts +11 -15
  157. package/src/events/types.ts +21 -2
  158. package/src/events/webSocketLogProvider.ts +1 -1
  159. package/src/examples/makeTradeExample.ts +44 -28
  160. package/src/factory/bigNum.ts +150 -22
  161. package/src/idl/drift.json +8250 -0
  162. package/src/idl/pyth.json +98 -2
  163. package/src/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  164. package/src/index.ts +30 -13
  165. package/src/math/amm.ts +161 -48
  166. package/src/math/conversion.ts +2 -2
  167. package/src/math/exchangeStatus.ts +31 -0
  168. package/src/math/funding.ts +41 -31
  169. package/src/math/insurance.ts +35 -0
  170. package/src/math/margin.ts +133 -0
  171. package/src/math/market.ts +143 -14
  172. package/src/math/oracles.ts +63 -9
  173. package/src/math/orders.ts +163 -26
  174. package/src/math/position.ts +136 -58
  175. package/src/math/repeg.ts +19 -9
  176. package/src/math/spotBalance.ts +319 -0
  177. package/src/math/spotMarket.ts +9 -0
  178. package/src/math/spotPosition.ts +47 -0
  179. package/src/math/trade.ts +33 -37
  180. package/src/oracles/pythClient.ts +2 -2
  181. package/src/oracles/quoteAssetOracleClient.ts +2 -2
  182. package/src/oracles/switchboardClient.ts +2 -2
  183. package/src/orderParams.ts +16 -8
  184. package/src/serum/serumFulfillmentConfigMap.ts +26 -0
  185. package/src/serum/serumSubscriber.ts +99 -0
  186. package/src/serum/types.ts +13 -0
  187. package/src/slot/SlotSubscriber.ts +11 -1
  188. package/src/{mockUSDCFaucet.ts → tokenFaucet.ts} +82 -70
  189. package/src/tx/retryTxSender.ts +16 -5
  190. package/src/tx/types.ts +2 -1
  191. package/src/tx/utils.ts +1 -1
  192. package/src/types.ts +572 -178
  193. package/src/user.ts +1582 -0
  194. package/src/{clearingHouseUserConfig.ts → userConfig.ts} +5 -5
  195. package/src/userMap/userMap.ts +124 -0
  196. package/src/userMap/userStatsMap.ts +108 -0
  197. package/src/userName.ts +2 -1
  198. package/src/userStats.ts +75 -0
  199. package/src/userStatsConfig.ts +18 -0
  200. package/src/util/computeUnits.ts +1 -1
  201. package/src/util/getTokenAddress.ts +18 -0
  202. package/tests/bn/test.ts +46 -11
  203. package/tests/dlob/helpers.ts +611 -0
  204. package/tests/dlob/test.ts +4588 -0
  205. package/lib/accounts/webSocketClearingHouseAccountSubscriber.d.ts +0 -49
  206. package/lib/admin.d.ts +0 -44
  207. package/lib/admin.js +0 -433
  208. package/lib/clearingHouse.d.ts +0 -133
  209. package/lib/clearingHouse.js +0 -931
  210. package/lib/clearingHouseUser.d.ts +0 -187
  211. package/lib/clearingHouseUser.js +0 -643
  212. package/lib/clearingHouseUserConfig.d.ts +0 -14
  213. package/lib/constants/banks.d.ts +0 -16
  214. package/lib/constants/banks.js +0 -34
  215. package/lib/constants/markets.d.ts +0 -19
  216. package/lib/idl/clearing_house.json +0 -3998
  217. package/lib/math/bankBalance.d.ts +0 -9
  218. package/lib/math/bankBalance.js +0 -75
  219. package/lib/math/state.d.ts +0 -8
  220. package/lib/math/state.js +0 -15
  221. package/lib/orders.d.ts +0 -8
  222. package/lib/orders.js +0 -134
  223. package/src/admin.ts +0 -722
  224. package/src/clearingHouse.ts +0 -1451
  225. package/src/clearingHouseUser.ts +0 -989
  226. package/src/constants/banks.ts +0 -43
  227. package/src/idl/clearing_house.json +0 -3998
  228. package/src/math/bankBalance.ts +0 -112
  229. package/src/math/state.ts +0 -14
  230. package/src/math/utils.js +0 -27
  231. package/src/math/utils.js.map +0 -1
  232. package/src/orders.ts +0 -244
  233. package/src/util/computeUnits.js +0 -17
  234. package/src/util/computeUnits.js.map +0 -1
@@ -0,0 +1,1123 @@
1
+ import { getOrderSignature, getVammNodeGenerator, NodeList } from './NodeList';
2
+ import {
3
+ MarketType,
4
+ BN,
5
+ calculateAskPrice,
6
+ calculateBidPrice,
7
+ DriftClient,
8
+ convertToNumber,
9
+ isAuctionComplete,
10
+ isOrderExpired,
11
+ isOneOfVariant,
12
+ isVariant,
13
+ getVariant,
14
+ Order,
15
+ PRICE_PRECISION,
16
+ SpotMarketAccount,
17
+ PerpMarketAccount,
18
+ OraclePriceData,
19
+ SlotSubscriber,
20
+ UserMapInterface,
21
+ MarketTypeStr,
22
+ StateAccount,
23
+ isMarketOrder,
24
+ isLimitOrder,
25
+ hasLimitPrice,
26
+ getOptionalLimitPrice,
27
+ } from '..';
28
+ import { PublicKey } from '@solana/web3.js';
29
+ import { DLOBNode, DLOBNodeType, TriggerOrderNode } from '..';
30
+ import { ammPaused, exchangePaused, fillPaused } from '../math/exchangeStatus';
31
+
32
+ export type MarketNodeLists = {
33
+ limit: {
34
+ ask: NodeList<'limit'>;
35
+ bid: NodeList<'limit'>;
36
+ };
37
+ floatingLimit: {
38
+ ask: NodeList<'floatingLimit'>;
39
+ bid: NodeList<'floatingLimit'>;
40
+ };
41
+ market: {
42
+ ask: NodeList<'market'>;
43
+ bid: NodeList<'market'>;
44
+ };
45
+ trigger: {
46
+ above: NodeList<'trigger'>;
47
+ below: NodeList<'trigger'>;
48
+ };
49
+ };
50
+
51
+ type OrderBookCallback = () => void;
52
+
53
+ export type NodeToFill = {
54
+ node: DLOBNode;
55
+ makerNode?: DLOBNode;
56
+ };
57
+
58
+ export type NodeToTrigger = {
59
+ node: TriggerOrderNode;
60
+ };
61
+
62
+ type Side = 'ask' | 'bid' | 'both' | 'nocross';
63
+
64
+ export class DLOB {
65
+ openOrders = new Map<MarketTypeStr, Set<string>>();
66
+ orderLists = new Map<MarketTypeStr, Map<number, MarketNodeLists>>();
67
+ stateAccount: StateAccount;
68
+ marketIndexToAccount = new Map<
69
+ MarketTypeStr,
70
+ Map<number, PerpMarketAccount | SpotMarketAccount>
71
+ >();
72
+
73
+ userMap: UserMapInterface;
74
+ silent = false;
75
+ initialized = false;
76
+
77
+ /**
78
+ *
79
+ * @param perpMarkets The perp markets to maintain a DLOB for
80
+ * @param spotMarkets The spot markets to maintain a DLOB for
81
+ * @param userMap map of all users
82
+ * @param silent set to true to prevent logging on inserts and removals
83
+ */
84
+ public constructor(
85
+ perpMarkets: PerpMarketAccount[],
86
+ spotMarkets: SpotMarketAccount[],
87
+ stateAccount: StateAccount,
88
+ userMap: UserMapInterface,
89
+ silent?: boolean
90
+ ) {
91
+ this.stateAccount = stateAccount;
92
+ this.userMap = userMap;
93
+ this.silent = silent;
94
+
95
+ this.openOrders.set('perp', new Set<string>());
96
+ this.openOrders.set('spot', new Set<string>());
97
+ this.orderLists.set('perp', new Map<number, MarketNodeLists>());
98
+ this.orderLists.set('spot', new Map<number, MarketNodeLists>());
99
+ this.marketIndexToAccount.set('perp', new Map<number, PerpMarketAccount>());
100
+ this.marketIndexToAccount.set('spot', new Map<number, SpotMarketAccount>());
101
+
102
+ for (const market of perpMarkets) {
103
+ const marketIndex = market.marketIndex;
104
+ this.marketIndexToAccount.get('perp').set(marketIndex, market);
105
+
106
+ this.orderLists.get('perp').set(marketIndex, {
107
+ limit: {
108
+ ask: new NodeList('limit', 'asc'),
109
+ bid: new NodeList('limit', 'desc'),
110
+ },
111
+ floatingLimit: {
112
+ ask: new NodeList('floatingLimit', 'asc'),
113
+ bid: new NodeList('floatingLimit', 'desc'),
114
+ },
115
+ market: {
116
+ ask: new NodeList('market', 'asc'),
117
+ bid: new NodeList('market', 'asc'), // always sort ascending for market orders
118
+ },
119
+ trigger: {
120
+ above: new NodeList('trigger', 'asc'),
121
+ below: new NodeList('trigger', 'desc'),
122
+ },
123
+ });
124
+ }
125
+ for (const market of spotMarkets) {
126
+ const marketIndex = market.marketIndex;
127
+ this.marketIndexToAccount.get('spot').set(marketIndex, market);
128
+
129
+ this.orderLists.get('spot').set(marketIndex, {
130
+ limit: {
131
+ ask: new NodeList('limit', 'asc'),
132
+ bid: new NodeList('limit', 'desc'),
133
+ },
134
+ floatingLimit: {
135
+ ask: new NodeList('floatingLimit', 'asc'),
136
+ bid: new NodeList('floatingLimit', 'desc'),
137
+ },
138
+ market: {
139
+ ask: new NodeList('market', 'asc'),
140
+ bid: new NodeList('market', 'asc'), // always sort ascending for market orders
141
+ },
142
+ trigger: {
143
+ above: new NodeList('trigger', 'asc'),
144
+ below: new NodeList('trigger', 'desc'),
145
+ },
146
+ });
147
+ }
148
+ }
149
+
150
+ public clear() {
151
+ for (const marketType of this.openOrders.keys()) {
152
+ this.openOrders.get(marketType).clear();
153
+ }
154
+ this.openOrders.clear();
155
+
156
+ for (const marketType of this.orderLists.keys()) {
157
+ for (const marketIndex of this.orderLists.get(marketType).keys()) {
158
+ const marketNodeLists = this.orderLists
159
+ .get(marketType)
160
+ .get(marketIndex);
161
+ for (const side of Object.keys(marketNodeLists)) {
162
+ for (const orderType of Object.keys(marketNodeLists[side])) {
163
+ marketNodeLists[side][orderType].clear();
164
+ }
165
+ }
166
+ }
167
+ }
168
+ this.orderLists.clear();
169
+
170
+ for (const marketType of this.marketIndexToAccount.keys()) {
171
+ this.marketIndexToAccount.get(marketType).clear();
172
+ }
173
+ this.marketIndexToAccount.clear();
174
+ }
175
+
176
+ /**
177
+ * initializes a new DLOB instance
178
+ *
179
+ * @returns a promise that resolves when the DLOB is initialized
180
+ */
181
+ public async init(): Promise<boolean> {
182
+ if (this.initialized) {
183
+ return false;
184
+ }
185
+
186
+ // initialize the dlob with the user map (prevents hitting getProgramAccounts)
187
+ for (const user of this.userMap.values()) {
188
+ const userAccount = user.getUserAccount();
189
+ const userAccountPubkey = user.getUserAccountPublicKey();
190
+
191
+ for (const order of userAccount.orders) {
192
+ this.insertOrder(order, userAccountPubkey);
193
+ }
194
+ }
195
+
196
+ this.initialized = true;
197
+ return true;
198
+ }
199
+
200
+ public insertOrder(
201
+ order: Order,
202
+ userAccount: PublicKey,
203
+ onInsert?: OrderBookCallback
204
+ ): void {
205
+ if (isVariant(order.status, 'init')) {
206
+ return;
207
+ }
208
+
209
+ const marketType = getVariant(order.marketType) as MarketTypeStr;
210
+
211
+ if (isVariant(order.status, 'open')) {
212
+ this.openOrders
213
+ .get(marketType)
214
+ .add(getOrderSignature(order.orderId, userAccount));
215
+ }
216
+ this.getListForOrder(order)?.insert(
217
+ order,
218
+ marketType,
219
+ this.marketIndexToAccount.get(marketType).get(order.marketIndex),
220
+ userAccount
221
+ );
222
+
223
+ if (onInsert) {
224
+ onInsert();
225
+ }
226
+ }
227
+
228
+ public trigger(
229
+ order: Order,
230
+ userAccount: PublicKey,
231
+ onTrigger?: OrderBookCallback
232
+ ): void {
233
+ if (isVariant(order.status, 'init')) {
234
+ return;
235
+ }
236
+
237
+ const marketType = getVariant(order.marketType) as MarketTypeStr;
238
+
239
+ const triggerList = this.orderLists.get(marketType).get(order.marketIndex)
240
+ .trigger[isVariant(order.triggerCondition, 'above') ? 'above' : 'below'];
241
+ triggerList.remove(order, userAccount);
242
+
243
+ this.getListForOrder(order)?.insert(
244
+ order,
245
+ marketType,
246
+ this.marketIndexToAccount.get(marketType).get(order.marketIndex),
247
+ userAccount
248
+ );
249
+ if (onTrigger) {
250
+ onTrigger();
251
+ }
252
+ }
253
+
254
+ public getListForOrder(order: Order): NodeList<any> | undefined {
255
+ const isInactiveTriggerOrder =
256
+ isOneOfVariant(order.orderType, ['triggerMarket', 'triggerLimit']) &&
257
+ !order.triggered;
258
+
259
+ let type: DLOBNodeType;
260
+ if (isInactiveTriggerOrder) {
261
+ type = 'trigger';
262
+ } else if (isOneOfVariant(order.orderType, ['market', 'triggerMarket'])) {
263
+ type = 'market';
264
+ } else if (order.oraclePriceOffset !== 0) {
265
+ type = 'floatingLimit';
266
+ } else {
267
+ type = 'limit';
268
+ }
269
+
270
+ let subType: string;
271
+ if (isInactiveTriggerOrder) {
272
+ subType = isVariant(order.triggerCondition, 'above') ? 'above' : 'below';
273
+ } else {
274
+ subType = isVariant(order.direction, 'long') ? 'bid' : 'ask';
275
+ }
276
+
277
+ const marketType = getVariant(order.marketType) as MarketTypeStr;
278
+
279
+ if (!this.orderLists.has(marketType)) {
280
+ return undefined;
281
+ }
282
+
283
+ return this.orderLists.get(marketType).get(order.marketIndex)[type][
284
+ subType
285
+ ];
286
+ }
287
+
288
+ public findNodesToFill(
289
+ marketIndex: number,
290
+ fallbackBid: BN | undefined,
291
+ fallbackAsk: BN | undefined,
292
+ slot: number,
293
+ ts: number,
294
+ marketType: MarketType,
295
+ oraclePriceData: OraclePriceData
296
+ ): NodeToFill[] {
297
+ const marketAccount = this.marketIndexToAccount
298
+ .get(getVariant(marketType) as MarketTypeStr)
299
+ .get(marketIndex);
300
+
301
+ if (fillPaused(this.stateAccount, marketAccount)) {
302
+ return [];
303
+ }
304
+
305
+ // Find all the crossing nodes
306
+ const crossingNodesToFill: Array<NodeToFill> = this.findCrossingNodesToFill(
307
+ marketIndex,
308
+ slot,
309
+ marketType,
310
+ oraclePriceData
311
+ );
312
+
313
+ let fallbackCrossingNodesToFill = new Array<NodeToFill>();
314
+ if (!ammPaused(this.stateAccount, marketAccount)) {
315
+ fallbackCrossingNodesToFill = this.findFallbackCrossingNodesToFill(
316
+ marketIndex,
317
+ fallbackBid,
318
+ fallbackAsk,
319
+ slot,
320
+ marketType,
321
+ oraclePriceData
322
+ );
323
+ }
324
+
325
+ // get expired market nodes
326
+ const expiredNodesToFill = this.findExpiredNodesToFill(
327
+ marketIndex,
328
+ ts,
329
+ marketType
330
+ );
331
+ return crossingNodesToFill.concat(
332
+ fallbackCrossingNodesToFill,
333
+ expiredNodesToFill
334
+ );
335
+ }
336
+
337
+ public findCrossingNodesToFill(
338
+ marketIndex: number,
339
+ slot: number,
340
+ marketType: MarketType,
341
+ oraclePriceData: OraclePriceData
342
+ ): NodeToFill[] {
343
+ const nodesToFill = new Array<NodeToFill>();
344
+
345
+ const askGenerator = this.getAsks(
346
+ marketIndex,
347
+ undefined, // dont include vask
348
+ slot,
349
+ marketType,
350
+ oraclePriceData
351
+ );
352
+ const bidGenerator = this.getBids(
353
+ marketIndex,
354
+ undefined, // dont include vbid
355
+ slot,
356
+ marketType,
357
+ oraclePriceData
358
+ );
359
+
360
+ let nextAsk = askGenerator.next();
361
+ let nextBid = bidGenerator.next();
362
+
363
+ // First try to find orders that cross
364
+ while (!nextAsk.done && !nextBid.done) {
365
+ const { crossingNodes, exhaustedSide } = this.findCrossingOrders(
366
+ nextAsk.value,
367
+ nextBid.value,
368
+ oraclePriceData,
369
+ slot
370
+ );
371
+
372
+ if (exhaustedSide === 'bid') {
373
+ nextBid = bidGenerator.next();
374
+ } else if (exhaustedSide === 'ask') {
375
+ nextAsk = askGenerator.next();
376
+ } else if (exhaustedSide === 'both') {
377
+ nextBid = bidGenerator.next();
378
+ nextAsk = askGenerator.next();
379
+ } else if (exhaustedSide === 'nocross') {
380
+ break;
381
+ } else {
382
+ console.error(`invalid exhaustedSide: ${exhaustedSide}`);
383
+ break;
384
+ }
385
+
386
+ for (const crossingNode of crossingNodes) {
387
+ nodesToFill.push(crossingNode);
388
+ }
389
+ }
390
+ return nodesToFill;
391
+ }
392
+
393
+ public findFallbackCrossingNodesToFill(
394
+ marketIndex: number,
395
+ fallbackBid: BN,
396
+ fallbackAsk: BN,
397
+ slot: number,
398
+ marketType: MarketType,
399
+ oraclePriceData: OraclePriceData
400
+ ): NodeToFill[] {
401
+ const nodesToFill = new Array<NodeToFill>();
402
+
403
+ const askGenerator = this.getAsks(
404
+ marketIndex,
405
+ undefined, // dont include vask
406
+ slot,
407
+ marketType,
408
+ oraclePriceData
409
+ );
410
+ const bidGenerator = this.getBids(
411
+ marketIndex,
412
+ undefined, // dont include vbid
413
+ slot,
414
+ marketType,
415
+ oraclePriceData
416
+ );
417
+
418
+ let nextAsk = askGenerator.next();
419
+ let nextBid = bidGenerator.next();
420
+
421
+ // check for asks that cross fallbackBid
422
+ while (!nextAsk.done && fallbackBid !== undefined) {
423
+ const askNode = nextAsk.value;
424
+
425
+ if (isVariant(marketType, 'spot') && askNode.order?.postOnly) {
426
+ nextAsk = askGenerator.next();
427
+ continue;
428
+ }
429
+
430
+ const askLimitPrice = getOptionalLimitPrice(
431
+ askNode.order,
432
+ oraclePriceData,
433
+ slot
434
+ );
435
+
436
+ // order crosses if there is no limit price or it crosses fallback price
437
+ const crosses =
438
+ askLimitPrice === undefined || askLimitPrice.lte(fallbackBid);
439
+
440
+ // fallback is available if auction is complete or it's a spot order
441
+ const fallbackAvailable =
442
+ isVariant(marketType, 'spot') || isAuctionComplete(askNode.order, slot);
443
+
444
+ if (crosses && fallbackAvailable) {
445
+ nodesToFill.push({
446
+ node: askNode,
447
+ makerNode: undefined, // filled by fallback
448
+ });
449
+ }
450
+
451
+ nextAsk = askGenerator.next();
452
+ }
453
+
454
+ // check for bids that cross fallbackAsk
455
+ while (!nextBid.done && fallbackAsk !== undefined) {
456
+ const bidNode = nextBid.value;
457
+
458
+ if (isVariant(marketType, 'spot') && bidNode.order?.postOnly) {
459
+ nextBid = bidGenerator.next();
460
+ continue;
461
+ }
462
+
463
+ const bidLimitPrice = getOptionalLimitPrice(
464
+ bidNode.order,
465
+ oraclePriceData,
466
+ slot
467
+ );
468
+
469
+ // order crosses if there is no limit price or it crosses fallback price
470
+ const crosses =
471
+ bidLimitPrice === undefined || bidLimitPrice.gte(fallbackAsk);
472
+
473
+ // fallback is available if auction is complete or it's a spot order
474
+ const fallbackAvailable =
475
+ isVariant(marketType, 'spot') || isAuctionComplete(bidNode.order, slot);
476
+
477
+ if (crosses && fallbackAvailable) {
478
+ nodesToFill.push({
479
+ node: bidNode,
480
+ makerNode: undefined, // filled by fallback
481
+ });
482
+ }
483
+
484
+ nextBid = bidGenerator.next();
485
+ }
486
+
487
+ return nodesToFill;
488
+ }
489
+
490
+ public findExpiredNodesToFill(
491
+ marketIndex: number,
492
+ ts: number,
493
+ marketType: MarketType
494
+ ): NodeToFill[] {
495
+ const nodesToFill = new Array<NodeToFill>();
496
+
497
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
498
+ const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
499
+
500
+ // All bids/asks that can expire
501
+ const bidGenerators = [
502
+ nodeLists.limit.bid.getGenerator(),
503
+ nodeLists.floatingLimit.bid.getGenerator(),
504
+ nodeLists.market.bid.getGenerator(),
505
+ ];
506
+ const askGenerators = [
507
+ nodeLists.limit.ask.getGenerator(),
508
+ nodeLists.floatingLimit.ask.getGenerator(),
509
+ nodeLists.market.ask.getGenerator(),
510
+ ];
511
+
512
+ for (const bidGenerator of bidGenerators) {
513
+ for (const bid of bidGenerator) {
514
+ if (isOrderExpired(bid.order, ts)) {
515
+ nodesToFill.push({
516
+ node: bid,
517
+ });
518
+ }
519
+ }
520
+ }
521
+
522
+ for (const askGenerator of askGenerators) {
523
+ for (const ask of askGenerator) {
524
+ if (isOrderExpired(ask.order, ts)) {
525
+ nodesToFill.push({
526
+ node: ask,
527
+ });
528
+ }
529
+ }
530
+ }
531
+
532
+ return nodesToFill;
533
+ }
534
+
535
+ public findJitAuctionNodesToFill(
536
+ marketIndex: number,
537
+ slot: number,
538
+ marketType: MarketType
539
+ ): NodeToFill[] {
540
+ const nodesToFill = new Array<NodeToFill>();
541
+ // Then see if there are orders still in JIT auction
542
+ for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
543
+ if (!isAuctionComplete(marketBid.order, slot)) {
544
+ nodesToFill.push({
545
+ node: marketBid,
546
+ });
547
+ }
548
+ }
549
+
550
+ for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
551
+ if (!isAuctionComplete(marketAsk.order, slot)) {
552
+ nodesToFill.push({
553
+ node: marketAsk,
554
+ });
555
+ }
556
+ }
557
+ return nodesToFill;
558
+ }
559
+
560
+ public getMarketBids(
561
+ marketIndex: number,
562
+ marketType: MarketType
563
+ ): Generator<DLOBNode> {
564
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
565
+ return this.orderLists
566
+ .get(marketTypeStr)
567
+ .get(marketIndex)
568
+ .market.bid.getGenerator();
569
+ }
570
+
571
+ public getMarketAsks(
572
+ marketIndex: number,
573
+ marketType: MarketType
574
+ ): Generator<DLOBNode> {
575
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
576
+ return this.orderLists
577
+ .get(marketTypeStr)
578
+ .get(marketIndex)
579
+ .market.ask.getGenerator();
580
+ }
581
+
582
+ *getAsks(
583
+ marketIndex: number,
584
+ fallbackAsk: BN | undefined,
585
+ slot: number,
586
+ marketType: MarketType,
587
+ oraclePriceData: OraclePriceData
588
+ ): Generator<DLOBNode> {
589
+ if (isVariant(marketType, 'spot') && !oraclePriceData) {
590
+ throw new Error('Must provide OraclePriceData to get spot asks');
591
+ }
592
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
593
+ const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
594
+
595
+ const generatorList = [
596
+ nodeLists.limit.ask.getGenerator(),
597
+ nodeLists.floatingLimit.ask.getGenerator(),
598
+ nodeLists.market.ask.getGenerator(),
599
+ ];
600
+
601
+ if (marketTypeStr === 'perp' && fallbackAsk) {
602
+ generatorList.push(getVammNodeGenerator(fallbackAsk));
603
+ }
604
+ if (generatorList.length === 0) {
605
+ throw new Error('No ask generators found');
606
+ }
607
+
608
+ const askGenerators = generatorList.map((generator) => {
609
+ return {
610
+ next: generator.next(),
611
+ generator,
612
+ };
613
+ });
614
+
615
+ let asksExhausted = false;
616
+ while (!asksExhausted) {
617
+ const bestGenerator = askGenerators.reduce(
618
+ (bestGenerator, currentGenerator) => {
619
+ if (currentGenerator.next.done) {
620
+ return bestGenerator;
621
+ }
622
+
623
+ if (bestGenerator.next.done) {
624
+ return currentGenerator;
625
+ }
626
+
627
+ const bestAskPrice = bestGenerator.next.value.getPrice(
628
+ oraclePriceData,
629
+ slot
630
+ );
631
+ const currentAskPrice = currentGenerator.next.value.getPrice(
632
+ oraclePriceData,
633
+ slot
634
+ );
635
+
636
+ return bestAskPrice.lt(currentAskPrice)
637
+ ? bestGenerator
638
+ : currentGenerator;
639
+ }
640
+ );
641
+
642
+ if (!bestGenerator.next.done) {
643
+ // skip this node if it's already completely filled
644
+ if (bestGenerator.next.value.isBaseFilled()) {
645
+ bestGenerator.next = bestGenerator.generator.next();
646
+ continue;
647
+ }
648
+
649
+ // skip order if user is being liquidated/bankrupt
650
+ if (bestGenerator.next.value.userAccount !== undefined) {
651
+ const user = this.userMap.get(
652
+ bestGenerator.next.value.userAccount.toString()
653
+ );
654
+ if (
655
+ user?.getUserAccount().isBeingLiquidated ||
656
+ user?.getUserAccount().isBankrupt
657
+ ) {
658
+ bestGenerator.next = bestGenerator.generator.next();
659
+ continue;
660
+ }
661
+ }
662
+
663
+ yield bestGenerator.next.value;
664
+ bestGenerator.next = bestGenerator.generator.next();
665
+ } else {
666
+ asksExhausted = true;
667
+ }
668
+ }
669
+ }
670
+
671
+ *getBids(
672
+ marketIndex: number,
673
+ fallbackBid: BN | undefined,
674
+ slot: number,
675
+ marketType: MarketType,
676
+ oraclePriceData: OraclePriceData
677
+ ): Generator<DLOBNode> {
678
+ if (isVariant(marketType, 'spot') && !oraclePriceData) {
679
+ throw new Error('Must provide OraclePriceData to get spot bids');
680
+ }
681
+
682
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
683
+ const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
684
+
685
+ const generatorList = [
686
+ nodeLists.limit.bid.getGenerator(),
687
+ nodeLists.floatingLimit.bid.getGenerator(),
688
+ nodeLists.market.bid.getGenerator(),
689
+ ];
690
+ if (marketTypeStr === 'perp' && fallbackBid) {
691
+ generatorList.push(getVammNodeGenerator(fallbackBid));
692
+ }
693
+ if (generatorList.length === 0) {
694
+ throw new Error('No bid generators found');
695
+ }
696
+
697
+ const bidGenerators = generatorList.map((generator) => {
698
+ return {
699
+ next: generator.next(),
700
+ generator,
701
+ };
702
+ });
703
+
704
+ let bidsExhausted = false; // there will always be the fallbackBid
705
+ while (!bidsExhausted) {
706
+ const bestGenerator = bidGenerators.reduce(
707
+ (bestGenerator, currentGenerator) => {
708
+ if (currentGenerator.next.done) {
709
+ return bestGenerator;
710
+ }
711
+
712
+ if (bestGenerator.next.done) {
713
+ return currentGenerator;
714
+ }
715
+
716
+ const bestBidPrice = bestGenerator.next.value.getPrice(
717
+ oraclePriceData,
718
+ slot
719
+ );
720
+ const currentBidPrice = currentGenerator.next.value.getPrice(
721
+ oraclePriceData,
722
+ slot
723
+ );
724
+
725
+ return bestBidPrice.gt(currentBidPrice)
726
+ ? bestGenerator
727
+ : currentGenerator;
728
+ }
729
+ );
730
+
731
+ if (!bestGenerator.next.done) {
732
+ // skip this node if it's already completely filled
733
+ if (bestGenerator.next.value.isBaseFilled()) {
734
+ bestGenerator.next = bestGenerator.generator.next();
735
+ continue;
736
+ }
737
+
738
+ // skip order if user is being liquidated/bankrupt
739
+ if (bestGenerator.next.value.userAccount !== undefined) {
740
+ const user = this.userMap.get(
741
+ bestGenerator.next.value.userAccount.toString()
742
+ );
743
+ if (
744
+ user?.getUserAccount().isBeingLiquidated ||
745
+ user?.getUserAccount().isBankrupt
746
+ ) {
747
+ bestGenerator.next = bestGenerator.generator.next();
748
+ continue;
749
+ }
750
+ }
751
+
752
+ yield bestGenerator.next.value;
753
+ bestGenerator.next = bestGenerator.generator.next();
754
+ } else {
755
+ bidsExhausted = true;
756
+ }
757
+ }
758
+ }
759
+
760
+ findCrossingOrders(
761
+ askNode: DLOBNode,
762
+ bidNode: DLOBNode,
763
+ oraclePriceData: OraclePriceData,
764
+ slot: number
765
+ ): {
766
+ crossingNodes: NodeToFill[];
767
+ exhaustedSide: Side;
768
+ } {
769
+ const bidPrice = bidNode.getPrice(oraclePriceData, slot);
770
+ const askPrice = askNode.getPrice(oraclePriceData, slot);
771
+
772
+ // orders don't cross - we're done walkin gup the book
773
+ if (bidPrice.lt(askPrice)) {
774
+ return {
775
+ crossingNodes: [],
776
+ exhaustedSide: 'nocross',
777
+ };
778
+ }
779
+
780
+ const bidOrder = bidNode.order;
781
+ const askOrder = askNode.order;
782
+
783
+ const bidUserAuthority = this.userMap.getUserAuthority(
784
+ bidNode.userAccount.toString()
785
+ );
786
+ const askUserAuthority = this.userMap.getUserAuthority(
787
+ askNode.userAccount.toString()
788
+ );
789
+
790
+ // Can't match orders from the same authority
791
+ const sameAuthority = bidUserAuthority.equals(askUserAuthority);
792
+ if (sameAuthority || (bidOrder.postOnly && askOrder.postOnly)) {
793
+ // don't have a principle way to pick which one to exhaust,
794
+ // exhaust each one 50% of the time so we can try each one against other orders
795
+ const exhaustedSide = Math.random() < 0.5 ? 'bid' : 'ask';
796
+ return {
797
+ crossingNodes: [],
798
+ exhaustedSide,
799
+ };
800
+ }
801
+
802
+ const { takerNode, makerNode, makerSide } = this.determineMakerAndTaker(
803
+ askNode,
804
+ bidNode
805
+ );
806
+
807
+ // If order doesn't have limit price, cant be maker
808
+ if (!hasLimitPrice(makerNode.order, slot)) {
809
+ return {
810
+ crossingNodes: [],
811
+ exhaustedSide: makerSide,
812
+ };
813
+ }
814
+
815
+ const bidBaseRemaining = bidOrder.baseAssetAmount.sub(
816
+ bidOrder.baseAssetAmountFilled
817
+ );
818
+ const askBaseRemaining = askOrder.baseAssetAmount.sub(
819
+ askOrder.baseAssetAmountFilled
820
+ );
821
+
822
+ let exhaustedSide: Side;
823
+ if (bidBaseRemaining.eq(askBaseRemaining)) {
824
+ exhaustedSide = 'both';
825
+ } else if (bidBaseRemaining.gt(askBaseRemaining)) {
826
+ exhaustedSide = 'ask';
827
+ } else {
828
+ exhaustedSide = 'bid';
829
+ }
830
+
831
+ // update the orders on DLOB as if they were fill - so we don't try to match them on the next iteration
832
+ // NOTE: if something goes wrong during the actual fill (transaction fails, i.e. due to orders already being filled)
833
+ // then we risk having a mismatch between this local DLOB and the actual DLOB state on the blockchain. This isn't
834
+ // a problem in the current implementation because we construct a new DLOB from the blockchain state every time, rather
835
+ // than updating the existing DLOB based on events.
836
+ if (exhaustedSide === 'ask') {
837
+ // bid partially filled
838
+ const newBidOrder = { ...bidOrder };
839
+ newBidOrder.baseAssetAmountFilled =
840
+ bidOrder.baseAssetAmountFilled.add(askBaseRemaining);
841
+ this.getListForOrder(newBidOrder).update(
842
+ newBidOrder,
843
+ bidNode.userAccount
844
+ );
845
+
846
+ // ask completely filled
847
+ const newAskOrder = { ...askOrder };
848
+ newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
849
+ this.getListForOrder(newAskOrder).update(
850
+ newAskOrder,
851
+ askNode.userAccount
852
+ );
853
+ } else if (exhaustedSide === 'bid') {
854
+ // ask partially filled
855
+ const newAskOrder = { ...askOrder };
856
+ newAskOrder.baseAssetAmountFilled =
857
+ askOrder.baseAssetAmountFilled.add(bidBaseRemaining);
858
+ this.getListForOrder(newAskOrder).update(
859
+ newAskOrder,
860
+ askNode.userAccount
861
+ );
862
+
863
+ // bid completely filled
864
+ const newBidOrder = { ...bidOrder };
865
+ newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
866
+ this.getListForOrder(newBidOrder).update(
867
+ newBidOrder,
868
+ bidNode.userAccount
869
+ );
870
+ } else {
871
+ // both completely filled
872
+ const newBidOrder = { ...bidOrder };
873
+ newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
874
+ this.getListForOrder(newBidOrder).update(
875
+ newBidOrder,
876
+ bidNode.userAccount
877
+ );
878
+
879
+ const newAskOrder = { ...askOrder };
880
+ newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
881
+ this.getListForOrder(newAskOrder).update(
882
+ newAskOrder,
883
+ askNode.userAccount
884
+ );
885
+ }
886
+
887
+ return {
888
+ crossingNodes: [
889
+ {
890
+ node: takerNode,
891
+ makerNode: makerNode,
892
+ },
893
+ ],
894
+ exhaustedSide,
895
+ };
896
+ }
897
+
898
+ determineMakerAndTaker(
899
+ askNode: DLOBNode,
900
+ bidNode: DLOBNode
901
+ ): { takerNode: DLOBNode; makerNode: DLOBNode; makerSide: Side } {
902
+ if (bidNode.order.postOnly) {
903
+ return {
904
+ takerNode: askNode,
905
+ makerNode: bidNode,
906
+ makerSide: 'bid',
907
+ };
908
+ } else if (askNode.order.postOnly) {
909
+ return {
910
+ takerNode: bidNode,
911
+ makerNode: askNode,
912
+ makerSide: 'ask',
913
+ };
914
+ } else if (isMarketOrder(bidNode.order) && isLimitOrder(askNode.order)) {
915
+ return {
916
+ takerNode: bidNode,
917
+ makerNode: askNode,
918
+ makerSide: 'ask',
919
+ };
920
+ } else if (isMarketOrder(askNode.order) && isLimitOrder(bidNode.order)) {
921
+ return {
922
+ takerNode: askNode,
923
+ makerNode: bidNode,
924
+ makerSide: 'bid',
925
+ };
926
+ } else if (askNode.order.slot.lt(bidNode.order.slot)) {
927
+ return {
928
+ takerNode: bidNode,
929
+ makerNode: askNode,
930
+ makerSide: 'ask',
931
+ };
932
+ } else {
933
+ return {
934
+ takerNode: askNode,
935
+ makerNode: bidNode,
936
+ makerSide: 'bid',
937
+ };
938
+ }
939
+ }
940
+
941
+ public getBestAsk(
942
+ marketIndex: number,
943
+ fallbackAsk: BN | undefined,
944
+ slot: number,
945
+ marketType: MarketType,
946
+ oraclePriceData: OraclePriceData
947
+ ): BN {
948
+ return this.getAsks(
949
+ marketIndex,
950
+ fallbackAsk,
951
+ slot,
952
+ marketType,
953
+ oraclePriceData
954
+ )
955
+ .next()
956
+ .value.getPrice(oraclePriceData, slot);
957
+ }
958
+
959
+ public getBestBid(
960
+ marketIndex: number,
961
+ fallbackBid: BN | undefined,
962
+ slot: number,
963
+ marketType: MarketType,
964
+ oraclePriceData: OraclePriceData
965
+ ): BN {
966
+ return this.getBids(
967
+ marketIndex,
968
+ fallbackBid,
969
+ slot,
970
+ marketType,
971
+ oraclePriceData
972
+ )
973
+ .next()
974
+ .value.getPrice(oraclePriceData, slot);
975
+ }
976
+
977
+ public findNodesToTrigger(
978
+ marketIndex: number,
979
+ slot: number,
980
+ oraclePrice: BN,
981
+ marketType: MarketType
982
+ ): NodeToTrigger[] {
983
+ if (exchangePaused(this.stateAccount)) {
984
+ return [];
985
+ }
986
+
987
+ const nodesToTrigger = [];
988
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
989
+ for (const node of this.orderLists
990
+ .get(marketTypeStr)
991
+ .get(marketIndex)
992
+ .trigger.above.getGenerator()) {
993
+ if (oraclePrice.gt(node.order.triggerPrice)) {
994
+ if (isAuctionComplete(node.order, slot)) {
995
+ nodesToTrigger.push({
996
+ node: node,
997
+ });
998
+ }
999
+ } else {
1000
+ break;
1001
+ }
1002
+ }
1003
+
1004
+ for (const node of this.orderLists
1005
+ .get(marketTypeStr)
1006
+ .get(marketIndex)
1007
+ .trigger.below.getGenerator()) {
1008
+ if (oraclePrice.lt(node.order.triggerPrice)) {
1009
+ if (isAuctionComplete(node.order, slot)) {
1010
+ nodesToTrigger.push({
1011
+ node: node,
1012
+ });
1013
+ }
1014
+ } else {
1015
+ break;
1016
+ }
1017
+ }
1018
+
1019
+ return nodesToTrigger;
1020
+ }
1021
+
1022
+ public printTopOfOrderLists(
1023
+ sdkConfig: any,
1024
+ driftClient: DriftClient,
1025
+ slotSubscriber: SlotSubscriber,
1026
+ marketIndex: number,
1027
+ marketType: MarketType
1028
+ ) {
1029
+ if (isVariant(marketType, 'perp')) {
1030
+ const market = driftClient.getPerpMarketAccount(marketIndex);
1031
+
1032
+ const slot = slotSubscriber.getSlot();
1033
+ const oraclePriceData =
1034
+ driftClient.getOracleDataForPerpMarket(marketIndex);
1035
+ const fallbackAsk = calculateAskPrice(market, oraclePriceData);
1036
+ const fallbackBid = calculateBidPrice(market, oraclePriceData);
1037
+
1038
+ const bestAsk = this.getBestAsk(
1039
+ marketIndex,
1040
+ fallbackAsk,
1041
+ slot,
1042
+ marketType,
1043
+ oraclePriceData
1044
+ );
1045
+ const bestBid = this.getBestBid(
1046
+ marketIndex,
1047
+ fallbackBid,
1048
+ slot,
1049
+ marketType,
1050
+ oraclePriceData
1051
+ );
1052
+ const mid = bestAsk.add(bestBid).div(new BN(2));
1053
+
1054
+ const bidSpread =
1055
+ (convertToNumber(bestBid, PRICE_PRECISION) /
1056
+ convertToNumber(oraclePriceData.price, PRICE_PRECISION) -
1057
+ 1) *
1058
+ 100.0;
1059
+ const askSpread =
1060
+ (convertToNumber(bestAsk, PRICE_PRECISION) /
1061
+ convertToNumber(oraclePriceData.price, PRICE_PRECISION) -
1062
+ 1) *
1063
+ 100.0;
1064
+
1065
+ console.log(`Market ${sdkConfig.MARKETS[marketIndex].symbol} Orders`);
1066
+ console.log(
1067
+ ` Ask`,
1068
+ convertToNumber(bestAsk, PRICE_PRECISION).toFixed(3),
1069
+ `(${askSpread.toFixed(4)}%)`
1070
+ );
1071
+ console.log(` Mid`, convertToNumber(mid, PRICE_PRECISION).toFixed(3));
1072
+ console.log(
1073
+ ` Bid`,
1074
+ convertToNumber(bestBid, PRICE_PRECISION).toFixed(3),
1075
+ `(${bidSpread.toFixed(4)}%)`
1076
+ );
1077
+ } else if (isVariant(marketType, 'spot')) {
1078
+ const slot = slotSubscriber.getSlot();
1079
+ const oraclePriceData =
1080
+ driftClient.getOracleDataForPerpMarket(marketIndex);
1081
+
1082
+ const bestAsk = this.getBestAsk(
1083
+ marketIndex,
1084
+ undefined,
1085
+ slot,
1086
+ marketType,
1087
+ oraclePriceData
1088
+ );
1089
+ const bestBid = this.getBestBid(
1090
+ marketIndex,
1091
+ undefined,
1092
+ slot,
1093
+ marketType,
1094
+ oraclePriceData
1095
+ );
1096
+ const mid = bestAsk.add(bestBid).div(new BN(2));
1097
+
1098
+ const bidSpread =
1099
+ (convertToNumber(bestBid, PRICE_PRECISION) /
1100
+ convertToNumber(oraclePriceData.price, PRICE_PRECISION) -
1101
+ 1) *
1102
+ 100.0;
1103
+ const askSpread =
1104
+ (convertToNumber(bestAsk, PRICE_PRECISION) /
1105
+ convertToNumber(oraclePriceData.price, PRICE_PRECISION) -
1106
+ 1) *
1107
+ 100.0;
1108
+
1109
+ console.log(`Market ${sdkConfig.MARKETS[marketIndex].symbol} Orders`);
1110
+ console.log(
1111
+ ` Ask`,
1112
+ convertToNumber(bestAsk, PRICE_PRECISION).toFixed(3),
1113
+ `(${askSpread.toFixed(4)}%)`
1114
+ );
1115
+ console.log(` Mid`, convertToNumber(mid, PRICE_PRECISION).toFixed(3));
1116
+ console.log(
1117
+ ` Bid`,
1118
+ convertToNumber(bestBid, PRICE_PRECISION).toFixed(3),
1119
+ `(${bidSpread.toFixed(4)}%)`
1120
+ );
1121
+ }
1122
+ }
1123
+ }