@drift-labs/sdk 0.2.0-master.3 → 0.2.0-master.30

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 (208) hide show
  1. package/README.md +13 -13
  2. package/lib/accounts/bulkUserStatsSubscription.d.ts +7 -0
  3. package/lib/accounts/bulkUserStatsSubscription.js +21 -0
  4. package/lib/accounts/bulkUserSubscription.js +0 -1
  5. package/lib/accounts/fetch.d.ts +2 -1
  6. package/lib/accounts/fetch.js +9 -1
  7. package/lib/accounts/pollingClearingHouseAccountSubscriber.d.ts +16 -16
  8. package/lib/accounts/pollingClearingHouseAccountSubscriber.js +31 -28
  9. package/lib/accounts/pollingUserStatsAccountSubscriber.d.ts +27 -0
  10. package/lib/accounts/pollingUserStatsAccountSubscriber.js +113 -0
  11. package/lib/accounts/types.d.ts +22 -11
  12. package/lib/accounts/webSocketClearingHouseAccountSubscriber.d.ts +17 -17
  13. package/lib/accounts/webSocketClearingHouseAccountSubscriber.js +37 -35
  14. package/lib/accounts/webSocketUserStatsAccountSubsriber.d.ts +20 -0
  15. package/lib/accounts/webSocketUserStatsAccountSubsriber.js +47 -0
  16. package/lib/addresses/marketAddresses.d.ts +1 -3
  17. package/lib/addresses/pda.d.ts +10 -4
  18. package/lib/addresses/pda.js +51 -14
  19. package/lib/admin.d.ts +34 -22
  20. package/lib/admin.js +182 -73
  21. package/lib/clearingHouse.d.ts +120 -42
  22. package/lib/clearingHouse.js +1505 -254
  23. package/lib/clearingHouseConfig.d.ts +4 -4
  24. package/lib/clearingHouseUser.d.ts +50 -38
  25. package/lib/clearingHouseUser.js +410 -190
  26. package/lib/clearingHouseUserStats.d.ts +18 -0
  27. package/lib/clearingHouseUserStats.js +49 -0
  28. package/lib/clearingHouseUserStatsConfig.d.ts +14 -0
  29. package/lib/clearingHouseUserStatsConfig.js +2 -0
  30. package/lib/config.d.ts +7 -9
  31. package/lib/config.js +21 -21
  32. package/lib/constants/numericConstants.d.ts +18 -12
  33. package/lib/constants/numericConstants.js +28 -21
  34. package/lib/constants/perpMarkets.d.ts +18 -0
  35. package/lib/constants/{markets.js → perpMarkets.js} +7 -7
  36. package/lib/constants/spotMarkets.d.ts +19 -0
  37. package/lib/constants/spotMarkets.js +53 -0
  38. package/lib/dlob/DLOB.d.ts +73 -0
  39. package/lib/dlob/DLOB.js +553 -0
  40. package/lib/dlob/DLOBNode.d.ts +52 -0
  41. package/lib/dlob/DLOBNode.js +82 -0
  42. package/lib/dlob/NodeList.d.ts +26 -0
  43. package/lib/dlob/NodeList.js +138 -0
  44. package/lib/events/eventList.js +3 -0
  45. package/lib/events/eventSubscriber.d.ts +4 -2
  46. package/lib/events/eventSubscriber.js +16 -9
  47. package/lib/events/fetchLogs.d.ts +10 -1
  48. package/lib/events/fetchLogs.js +27 -7
  49. package/lib/events/pollingLogProvider.d.ts +2 -1
  50. package/lib/events/pollingLogProvider.js +6 -2
  51. package/lib/events/sort.js +8 -11
  52. package/lib/events/types.d.ts +7 -2
  53. package/lib/events/types.js +5 -0
  54. package/lib/examples/makeTradeExample.js +20 -8
  55. package/lib/factory/bigNum.d.ts +1 -0
  56. package/lib/factory/bigNum.js +34 -10
  57. package/lib/idl/clearing_house.json +4313 -1433
  58. package/lib/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  59. package/lib/index.d.ts +21 -6
  60. package/lib/index.js +25 -6
  61. package/lib/math/amm.d.ts +8 -5
  62. package/lib/math/amm.js +68 -46
  63. package/lib/math/auction.js +4 -1
  64. package/lib/math/conversion.js +1 -1
  65. package/lib/math/funding.d.ts +6 -6
  66. package/lib/math/funding.js +11 -10
  67. package/lib/math/insurance.d.ts +4 -0
  68. package/lib/math/insurance.js +27 -0
  69. package/lib/math/margin.d.ts +11 -0
  70. package/lib/math/margin.js +77 -0
  71. package/lib/math/market.d.ts +14 -9
  72. package/lib/math/market.js +69 -7
  73. package/lib/math/oracles.d.ts +4 -0
  74. package/lib/math/oracles.js +36 -8
  75. package/lib/math/orders.d.ts +6 -2
  76. package/lib/math/orders.js +78 -3
  77. package/lib/math/position.d.ts +21 -13
  78. package/lib/math/position.js +76 -36
  79. package/lib/math/repeg.js +14 -5
  80. package/lib/math/spotBalance.d.ts +22 -0
  81. package/lib/math/spotBalance.js +193 -0
  82. package/lib/math/spotMarket.d.ts +4 -0
  83. package/lib/math/spotMarket.js +8 -0
  84. package/lib/math/spotPosition.d.ts +6 -0
  85. package/lib/math/spotPosition.js +23 -0
  86. package/lib/math/trade.d.ts +10 -10
  87. package/lib/math/trade.js +22 -29
  88. package/lib/oracles/pythClient.js +1 -1
  89. package/lib/oracles/quoteAssetOracleClient.js +1 -1
  90. package/lib/oracles/switchboardClient.js +1 -1
  91. package/lib/orderParams.d.ts +14 -5
  92. package/lib/orderParams.js +12 -92
  93. package/lib/serum/serumSubscriber.d.ts +23 -0
  94. package/lib/serum/serumSubscriber.js +41 -0
  95. package/lib/serum/types.d.ts +11 -0
  96. package/lib/serum/types.js +2 -0
  97. package/lib/slot/SlotSubscriber.d.ts +7 -0
  98. package/lib/slot/SlotSubscriber.js +3 -0
  99. package/lib/{mockUSDCFaucet.d.ts → tokenFaucet.d.ts} +8 -5
  100. package/lib/{mockUSDCFaucet.js → tokenFaucet.js} +63 -51
  101. package/lib/tx/retryTxSender.d.ts +1 -1
  102. package/lib/tx/retryTxSender.js +13 -4
  103. package/lib/tx/types.d.ts +1 -1
  104. package/lib/tx/utils.js +1 -1
  105. package/lib/types.d.ts +474 -123
  106. package/lib/types.js +99 -5
  107. package/lib/userMap/userMap.d.ts +25 -0
  108. package/lib/userMap/userMap.js +73 -0
  109. package/lib/userMap/userStatsMap.d.ts +19 -0
  110. package/lib/userMap/userStatsMap.js +68 -0
  111. package/lib/util/computeUnits.js +1 -1
  112. package/lib/util/getTokenAddress.d.ts +2 -0
  113. package/lib/util/getTokenAddress.js +9 -0
  114. package/package.json +9 -5
  115. package/src/accounts/bulkUserStatsSubscription.ts +33 -0
  116. package/src/accounts/bulkUserSubscription.ts +0 -1
  117. package/src/accounts/fetch.ts +27 -2
  118. package/src/accounts/pollingClearingHouseAccountSubscriber.ts +46 -42
  119. package/src/accounts/pollingUserStatsAccountSubscriber.ts +172 -0
  120. package/src/accounts/types.ts +31 -11
  121. package/src/accounts/webSocketClearingHouseAccountSubscriber.ts +64 -59
  122. package/src/accounts/webSocketUserStatsAccountSubsriber.ts +80 -0
  123. package/src/addresses/marketAddresses.ts +1 -2
  124. package/src/addresses/pda.ts +88 -14
  125. package/src/admin.ts +333 -128
  126. package/src/assert/assert.js +9 -0
  127. package/src/clearingHouse.ts +2464 -458
  128. package/src/clearingHouseConfig.ts +4 -3
  129. package/src/clearingHouseUser.ts +747 -291
  130. package/src/clearingHouseUserStats.ts +75 -0
  131. package/src/clearingHouseUserStatsConfig.ts +18 -0
  132. package/src/config.ts +30 -31
  133. package/src/constants/numericConstants.ts +41 -25
  134. package/src/constants/{markets.ts → perpMarkets.ts} +10 -10
  135. package/src/constants/spotMarkets.ts +72 -0
  136. package/src/dlob/DLOB.ts +868 -0
  137. package/src/dlob/DLOBNode.ts +162 -0
  138. package/src/dlob/NodeList.ts +185 -0
  139. package/src/events/eventList.js +77 -0
  140. package/src/events/eventList.ts +3 -0
  141. package/src/events/eventSubscriber.ts +20 -12
  142. package/src/events/fetchLogs.ts +35 -8
  143. package/src/events/pollingLogProvider.ts +10 -2
  144. package/src/events/sort.ts +11 -15
  145. package/src/events/types.ts +16 -1
  146. package/src/examples/makeTradeExample.js +157 -0
  147. package/src/examples/makeTradeExample.ts +32 -14
  148. package/src/factory/bigNum.ts +42 -13
  149. package/src/idl/clearing_house.json +4313 -1433
  150. package/src/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  151. package/src/index.ts +21 -6
  152. package/src/math/amm.ts +136 -66
  153. package/src/math/auction.ts +5 -1
  154. package/src/math/conversion.ts +2 -2
  155. package/src/math/funding.ts +20 -18
  156. package/src/math/insurance.ts +35 -0
  157. package/src/math/margin.ts +127 -0
  158. package/src/math/market.ts +138 -12
  159. package/src/math/oracles.ts +63 -9
  160. package/src/math/orders.ts +138 -4
  161. package/src/math/position.ts +119 -58
  162. package/src/math/repeg.ts +16 -6
  163. package/src/math/spotBalance.ts +316 -0
  164. package/src/math/spotMarket.ts +9 -0
  165. package/src/math/spotPosition.ts +47 -0
  166. package/src/math/trade.ts +43 -49
  167. package/src/oracles/pythClient.ts +2 -2
  168. package/src/oracles/quoteAssetOracleClient.ts +2 -2
  169. package/src/oracles/switchboardClient.ts +2 -2
  170. package/src/orderParams.ts +24 -137
  171. package/src/serum/serumSubscriber.ts +80 -0
  172. package/src/serum/types.ts +13 -0
  173. package/src/slot/SlotSubscriber.ts +11 -1
  174. package/src/token/index.js +38 -0
  175. package/src/{mockUSDCFaucet.ts → tokenFaucet.ts} +82 -70
  176. package/src/tx/retryTxSender.ts +16 -5
  177. package/src/tx/types.js +2 -0
  178. package/src/tx/types.ts +2 -1
  179. package/src/tx/utils.js +17 -0
  180. package/src/tx/utils.ts +1 -1
  181. package/src/types.ts +477 -125
  182. package/src/userMap/userMap.ts +100 -0
  183. package/src/userMap/userStatsMap.ts +110 -0
  184. package/src/util/computeUnits.js +21 -11
  185. package/src/util/computeUnits.ts +1 -1
  186. package/src/util/getTokenAddress.js +9 -0
  187. package/src/util/getTokenAddress.ts +18 -0
  188. package/src/util/promiseTimeout.js +14 -0
  189. package/src/util/tps.js +27 -0
  190. package/tests/bn/test.ts +12 -3
  191. package/tests/dlob/helpers.ts +374 -0
  192. package/tests/dlob/test.ts +2865 -0
  193. package/lib/constants/banks.d.ts +0 -16
  194. package/lib/constants/banks.js +0 -34
  195. package/lib/constants/markets.d.ts +0 -19
  196. package/lib/math/bankBalance.d.ts +0 -9
  197. package/lib/math/bankBalance.js +0 -75
  198. package/lib/math/state.d.ts +0 -8
  199. package/lib/math/state.js +0 -15
  200. package/lib/orders.d.ts +0 -8
  201. package/lib/orders.js +0 -134
  202. package/src/constants/banks.ts +0 -43
  203. package/src/math/bankBalance.ts +0 -112
  204. package/src/math/state.ts +0 -14
  205. package/src/math/utils.js +0 -27
  206. package/src/math/utils.js.map +0 -1
  207. package/src/orders.ts +0 -244
  208. package/src/util/computeUnits.js.map +0 -1
@@ -0,0 +1,868 @@
1
+ import { getOrderSignature, getVammNodeGenerator, NodeList } from './NodeList';
2
+ import {
3
+ MarketType,
4
+ BN,
5
+ calculateAskPrice,
6
+ calculateBidPrice,
7
+ ClearingHouse,
8
+ convertToNumber,
9
+ isAuctionComplete,
10
+ isOrderExpired,
11
+ isOneOfVariant,
12
+ isVariant,
13
+ getVariant,
14
+ Order,
15
+ ZERO,
16
+ PRICE_PRECISION,
17
+ SpotMarketAccount,
18
+ PerpMarketAccount,
19
+ OraclePriceData,
20
+ SlotSubscriber,
21
+ UserMap,
22
+ MarketTypeStr,
23
+ } from '..';
24
+ import { PublicKey } from '@solana/web3.js';
25
+ import { DLOBNode, DLOBNodeType, TriggerOrderNode } from '..';
26
+
27
+ export type MarketNodeLists = {
28
+ limit: {
29
+ ask: NodeList<'limit'>;
30
+ bid: NodeList<'limit'>;
31
+ };
32
+ floatingLimit: {
33
+ ask: NodeList<'floatingLimit'>;
34
+ bid: NodeList<'floatingLimit'>;
35
+ };
36
+ market: {
37
+ ask: NodeList<'market'>;
38
+ bid: NodeList<'market'>;
39
+ };
40
+ trigger: {
41
+ above: NodeList<'trigger'>;
42
+ below: NodeList<'trigger'>;
43
+ };
44
+ };
45
+
46
+ type OrderBookCallback = () => void;
47
+
48
+ export type NodeToFill = {
49
+ node: DLOBNode;
50
+ makerNode?: DLOBNode;
51
+ };
52
+
53
+ export type NodeToTrigger = {
54
+ node: TriggerOrderNode;
55
+ };
56
+
57
+ type Side = 'ask' | 'bid' | 'both';
58
+
59
+ export class DLOB {
60
+ openOrders = new Map<MarketTypeStr, Set<string>>();
61
+ orderLists = new Map<MarketTypeStr, Map<number, MarketNodeLists>>();
62
+ marketIndexToAccount = new Map<
63
+ MarketTypeStr,
64
+ Map<number, PerpMarketAccount | SpotMarketAccount>
65
+ >();
66
+
67
+ silent = false;
68
+ initialized = false;
69
+
70
+ /**
71
+ *
72
+ * @param perpMarkets The perp markets to maintain a DLOB for
73
+ * @param spotMarkets The spot markets to maintain a DLOB for
74
+ * @param silent set to true to prevent logging on inserts and removals
75
+ */
76
+ public constructor(
77
+ perpMarkets: PerpMarketAccount[],
78
+ spotMarkets: SpotMarketAccount[],
79
+ silent?: boolean
80
+ ) {
81
+ this.silent = silent;
82
+
83
+ this.openOrders.set('perp', new Set<string>());
84
+ this.openOrders.set('spot', new Set<string>());
85
+ this.orderLists.set('perp', new Map<number, MarketNodeLists>());
86
+ this.orderLists.set('spot', new Map<number, MarketNodeLists>());
87
+ this.marketIndexToAccount.set('perp', new Map<number, PerpMarketAccount>());
88
+ this.marketIndexToAccount.set('spot', new Map<number, SpotMarketAccount>());
89
+
90
+ for (const market of perpMarkets) {
91
+ const marketIndex = market.marketIndex;
92
+ this.marketIndexToAccount.get('perp').set(marketIndex, market);
93
+
94
+ this.orderLists.get('perp').set(marketIndex, {
95
+ limit: {
96
+ ask: new NodeList('limit', 'asc'),
97
+ bid: new NodeList('limit', 'desc'),
98
+ },
99
+ floatingLimit: {
100
+ ask: new NodeList('floatingLimit', 'asc'),
101
+ bid: new NodeList('floatingLimit', 'desc'),
102
+ },
103
+ market: {
104
+ ask: new NodeList('market', 'asc'),
105
+ bid: new NodeList('market', 'asc'), // always sort ascending for market orders
106
+ },
107
+ trigger: {
108
+ above: new NodeList('trigger', 'asc'),
109
+ below: new NodeList('trigger', 'desc'),
110
+ },
111
+ });
112
+ }
113
+ for (const market of spotMarkets) {
114
+ const marketIndex = market.marketIndex;
115
+ this.marketIndexToAccount.get('spot').set(marketIndex, market);
116
+
117
+ this.orderLists.get('spot').set(marketIndex, {
118
+ limit: {
119
+ ask: new NodeList('limit', 'asc'),
120
+ bid: new NodeList('limit', 'desc'),
121
+ },
122
+ floatingLimit: {
123
+ ask: new NodeList('floatingLimit', 'asc'),
124
+ bid: new NodeList('floatingLimit', 'desc'),
125
+ },
126
+ market: {
127
+ ask: new NodeList('market', 'asc'),
128
+ bid: new NodeList('market', 'asc'), // always sort ascending for market orders
129
+ },
130
+ trigger: {
131
+ above: new NodeList('trigger', 'asc'),
132
+ below: new NodeList('trigger', 'desc'),
133
+ },
134
+ });
135
+ }
136
+ }
137
+
138
+ /**
139
+ * initializes a new DLOB instance
140
+ *
141
+ * @param clearingHouse The ClearingHouse instance to use for price data
142
+ * @returns a promise that resolves when the DLOB is initialized
143
+ */
144
+ public async init(
145
+ clearingHouse: ClearingHouse,
146
+ userMap?: UserMap
147
+ ): Promise<boolean> {
148
+ if (this.initialized) {
149
+ return false;
150
+ }
151
+ if (userMap) {
152
+ // initialize the dlob with the user map (prevents hitting getProgramAccounts)
153
+ for (const user of userMap.values()) {
154
+ const userAccount = user.getUserAccount();
155
+ const userAccountPubkey = user.getUserAccountPublicKey();
156
+
157
+ for (const order of userAccount.orders) {
158
+ this.insertOrder(order, userAccountPubkey);
159
+ }
160
+ }
161
+ } else {
162
+ const programAccounts = await clearingHouse.program.account.user.all();
163
+ for (const programAccount of programAccounts) {
164
+ // @ts-ignore
165
+ const userAccount: UserAccount = programAccount.account;
166
+ const userAccountPublicKey = programAccount.publicKey;
167
+
168
+ for (const order of userAccount.orders) {
169
+ this.insertOrder(order, userAccountPublicKey);
170
+ }
171
+ }
172
+ }
173
+
174
+ this.initialized = true;
175
+ return true;
176
+ }
177
+
178
+ public insertOrder(
179
+ order: Order,
180
+ userAccount: PublicKey,
181
+ onInsert?: OrderBookCallback
182
+ ): void {
183
+ if (isVariant(order.status, 'init')) {
184
+ return;
185
+ }
186
+
187
+ const marketType = getVariant(order.marketType) as MarketTypeStr;
188
+
189
+ if (isVariant(order.status, 'open')) {
190
+ this.openOrders
191
+ .get(marketType)
192
+ .add(getOrderSignature(order.orderId, userAccount));
193
+ }
194
+ this.getListForOrder(order)?.insert(
195
+ order,
196
+ marketType,
197
+ this.marketIndexToAccount.get(marketType).get(order.marketIndex),
198
+ userAccount
199
+ );
200
+
201
+ if (onInsert) {
202
+ onInsert();
203
+ }
204
+ }
205
+
206
+ public trigger(
207
+ order: Order,
208
+ userAccount: PublicKey,
209
+ onTrigger?: OrderBookCallback
210
+ ): void {
211
+ if (isVariant(order.status, 'init')) {
212
+ return;
213
+ }
214
+
215
+ const marketType = getVariant(order.marketType) as MarketTypeStr;
216
+
217
+ const triggerList = this.orderLists.get(marketType).get(order.marketIndex)
218
+ .trigger[isVariant(order.triggerCondition, 'above') ? 'above' : 'below'];
219
+ triggerList.remove(order, userAccount);
220
+
221
+ this.getListForOrder(order)?.insert(
222
+ order,
223
+ marketType,
224
+ this.marketIndexToAccount.get(marketType).get(order.marketIndex),
225
+ userAccount
226
+ );
227
+ if (onTrigger) {
228
+ onTrigger();
229
+ }
230
+ }
231
+
232
+ public getListForOrder(order: Order): NodeList<any> | undefined {
233
+ const isInactiveTriggerOrder =
234
+ isOneOfVariant(order.orderType, ['triggerMarket', 'triggerLimit']) &&
235
+ !order.triggered;
236
+
237
+ let type: DLOBNodeType;
238
+ if (isInactiveTriggerOrder) {
239
+ type = 'trigger';
240
+ } else if (isOneOfVariant(order.orderType, ['market', 'triggerMarket'])) {
241
+ type = 'market';
242
+ } else if (order.oraclePriceOffset.gt(ZERO)) {
243
+ type = 'floatingLimit';
244
+ } else {
245
+ type = 'limit';
246
+ }
247
+
248
+ let subType: string;
249
+ if (isInactiveTriggerOrder) {
250
+ subType = isVariant(order.triggerCondition, 'above') ? 'above' : 'below';
251
+ } else {
252
+ subType = isVariant(order.direction, 'long') ? 'bid' : 'ask';
253
+ }
254
+
255
+ const marketType = getVariant(order.marketType) as MarketTypeStr;
256
+
257
+ if (!this.orderLists.has(marketType)) {
258
+ return undefined;
259
+ }
260
+
261
+ return this.orderLists.get(marketType).get(order.marketIndex)[type][
262
+ subType
263
+ ];
264
+ }
265
+
266
+ public findNodesToFill(
267
+ marketIndex: number,
268
+ vBid: BN | undefined,
269
+ vAsk: BN | undefined,
270
+ slot: number,
271
+ marketType: MarketType,
272
+ oraclePriceData: OraclePriceData
273
+ ): NodeToFill[] {
274
+ // Find all the crossing nodes
275
+ const crossingNodesToFill: Array<NodeToFill> = this.findCrossingNodesToFill(
276
+ marketIndex,
277
+ vBid,
278
+ vAsk,
279
+ slot,
280
+ marketType,
281
+ oraclePriceData
282
+ );
283
+
284
+ // get expired market nodes
285
+ const marketNodesToFill = this.findMarketNodesToFill(
286
+ marketIndex,
287
+ slot,
288
+ marketType
289
+ );
290
+ return crossingNodesToFill.concat(marketNodesToFill);
291
+ }
292
+
293
+ public findCrossingNodesToFill(
294
+ marketIndex: number,
295
+ vBid: BN | undefined,
296
+ vAsk: BN | undefined,
297
+ slot: number,
298
+ marketType: MarketType,
299
+ oraclePriceData: OraclePriceData
300
+ ): NodeToFill[] {
301
+ const nodesToFill = new Array<NodeToFill>();
302
+
303
+ const askGenerator = this.getAsks(
304
+ marketIndex,
305
+ vAsk,
306
+ slot,
307
+ marketType,
308
+ oraclePriceData
309
+ );
310
+ const bidGenerator = this.getBids(
311
+ marketIndex,
312
+ vBid,
313
+ slot,
314
+ marketType,
315
+ oraclePriceData
316
+ );
317
+
318
+ let nextAsk = askGenerator.next();
319
+ let nextBid = bidGenerator.next();
320
+
321
+ // First try to find orders that cross
322
+ while (!nextAsk.done && !nextBid.done) {
323
+ const { crossingNodes, exhaustedSide } = this.findCrossingOrders(
324
+ nextAsk.value,
325
+ nextBid.value,
326
+ oraclePriceData,
327
+ slot
328
+ );
329
+
330
+ if (exhaustedSide === 'bid') {
331
+ nextBid = bidGenerator.next();
332
+ } else if (exhaustedSide === 'ask') {
333
+ nextAsk = askGenerator.next();
334
+ } else if (exhaustedSide === 'both') {
335
+ nextBid = bidGenerator.next();
336
+ nextAsk = askGenerator.next();
337
+ } else {
338
+ console.error(`invalid exhaustedSide: ${exhaustedSide}`);
339
+ break;
340
+ }
341
+
342
+ const takerIsMaker =
343
+ crossingNodes?.makerNode !== undefined &&
344
+ crossingNodes.node.userAccount.equals(
345
+ crossingNodes.makerNode.userAccount
346
+ );
347
+
348
+ // Verify that each side is different user
349
+ if (crossingNodes && !takerIsMaker) {
350
+ nodesToFill.push(crossingNodes);
351
+ }
352
+ }
353
+ return nodesToFill;
354
+ }
355
+
356
+ public findMarketNodesToFill(
357
+ marketIndex: number,
358
+ slot: number,
359
+ marketType: MarketType
360
+ ): NodeToFill[] {
361
+ const nodesToFill = new Array<NodeToFill>();
362
+ // Then see if there are orders to fill against vamm
363
+ for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
364
+ if (
365
+ isAuctionComplete(marketBid.order, slot) ||
366
+ isOrderExpired(marketBid.order, slot)
367
+ ) {
368
+ nodesToFill.push({
369
+ node: marketBid,
370
+ });
371
+ }
372
+ }
373
+
374
+ for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
375
+ if (
376
+ isAuctionComplete(marketAsk.order, slot) ||
377
+ isOrderExpired(marketAsk.order, slot)
378
+ ) {
379
+ nodesToFill.push({
380
+ node: marketAsk,
381
+ });
382
+ }
383
+ }
384
+ return nodesToFill;
385
+ }
386
+
387
+ public findJitAuctionNodesToFill(
388
+ marketIndex: number,
389
+ slot: number,
390
+ marketType: MarketType
391
+ ): NodeToFill[] {
392
+ const nodesToFill = new Array<NodeToFill>();
393
+ // Then see if there are orders still in JIT auction
394
+ for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
395
+ if (!isAuctionComplete(marketBid.order, slot)) {
396
+ nodesToFill.push({
397
+ node: marketBid,
398
+ });
399
+ }
400
+ }
401
+
402
+ for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
403
+ if (!isAuctionComplete(marketAsk.order, slot)) {
404
+ nodesToFill.push({
405
+ node: marketAsk,
406
+ });
407
+ }
408
+ }
409
+ return nodesToFill;
410
+ }
411
+
412
+ public getMarketBids(
413
+ marketIndex: number,
414
+ marketType: MarketType
415
+ ): Generator<DLOBNode> {
416
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
417
+ return this.orderLists
418
+ .get(marketTypeStr)
419
+ .get(marketIndex)
420
+ .market.bid.getGenerator();
421
+ }
422
+
423
+ public getMarketAsks(
424
+ marketIndex: number,
425
+ marketType: MarketType
426
+ ): Generator<DLOBNode> {
427
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
428
+ return this.orderLists
429
+ .get(marketTypeStr)
430
+ .get(marketIndex)
431
+ .market.ask.getGenerator();
432
+ }
433
+
434
+ *getAsks(
435
+ marketIndex: number,
436
+ vAsk: BN | undefined,
437
+ slot: number,
438
+ marketType: MarketType,
439
+ oraclePriceData: OraclePriceData
440
+ ): Generator<DLOBNode> {
441
+ if (isVariant(marketType, 'spot') && !oraclePriceData) {
442
+ throw new Error('Must provide OraclePriceData to get spot asks');
443
+ }
444
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
445
+ const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
446
+
447
+ const generatorList = [
448
+ nodeLists.limit.ask.getGenerator(),
449
+ nodeLists.floatingLimit.ask.getGenerator(),
450
+ nodeLists.market.ask.getGenerator(),
451
+ ];
452
+
453
+ if (marketTypeStr === 'perp') {
454
+ generatorList.push(getVammNodeGenerator(vAsk));
455
+ }
456
+
457
+ const askGenerators = generatorList.map((generator) => {
458
+ return {
459
+ next: generator.next(),
460
+ generator,
461
+ };
462
+ });
463
+
464
+ let asksExhausted = false;
465
+ while (!asksExhausted) {
466
+ const bestGenerator = askGenerators.reduce(
467
+ (bestGenerator, currentGenerator) => {
468
+ if (currentGenerator.next.done) {
469
+ return bestGenerator;
470
+ }
471
+
472
+ if (bestGenerator.next.done) {
473
+ return currentGenerator;
474
+ }
475
+
476
+ const bestAskPrice = bestGenerator.next.value.getPrice(
477
+ oraclePriceData,
478
+ slot
479
+ );
480
+ const currentAskPrice = currentGenerator.next.value.getPrice(
481
+ oraclePriceData,
482
+ slot
483
+ );
484
+
485
+ return bestAskPrice.lt(currentAskPrice)
486
+ ? bestGenerator
487
+ : currentGenerator;
488
+ }
489
+ );
490
+
491
+ if (!bestGenerator.next.done) {
492
+ yield bestGenerator.next.value;
493
+ bestGenerator.next = bestGenerator.generator.next();
494
+ } else {
495
+ asksExhausted = true;
496
+ }
497
+ }
498
+ }
499
+
500
+ *getBids(
501
+ marketIndex: number,
502
+ vBid: BN | undefined,
503
+ slot: number,
504
+ marketType: MarketType,
505
+ oraclePriceData: OraclePriceData
506
+ ): Generator<DLOBNode> {
507
+ if (isVariant(marketType, 'spot') && !oraclePriceData) {
508
+ throw new Error('Must provide OraclePriceData to get spot bids');
509
+ }
510
+
511
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
512
+ const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
513
+
514
+ const generatorList = [
515
+ nodeLists.limit.bid.getGenerator(),
516
+ nodeLists.floatingLimit.bid.getGenerator(),
517
+ nodeLists.market.bid.getGenerator(),
518
+ ];
519
+ if (marketTypeStr === 'perp') {
520
+ generatorList.push(getVammNodeGenerator(vBid));
521
+ }
522
+ const bidGenerators = generatorList.map((generator) => {
523
+ return {
524
+ next: generator.next(),
525
+ generator,
526
+ };
527
+ });
528
+
529
+ let bidsExhausted = false; // there will always be the vBid
530
+ while (!bidsExhausted) {
531
+ const bestGenerator = bidGenerators.reduce(
532
+ (bestGenerator, currentGenerator) => {
533
+ if (currentGenerator.next.done) {
534
+ return bestGenerator;
535
+ }
536
+
537
+ if (bestGenerator.next.done) {
538
+ return currentGenerator;
539
+ }
540
+
541
+ const bestBidPrice = bestGenerator.next.value.getPrice(
542
+ oraclePriceData,
543
+ slot
544
+ );
545
+ const currentBidPrice = currentGenerator.next.value.getPrice(
546
+ oraclePriceData,
547
+ slot
548
+ );
549
+
550
+ return bestBidPrice.gt(currentBidPrice)
551
+ ? bestGenerator
552
+ : currentGenerator;
553
+ }
554
+ );
555
+
556
+ if (!bestGenerator.next.done) {
557
+ yield bestGenerator.next.value;
558
+ bestGenerator.next = bestGenerator.generator.next();
559
+ } else {
560
+ bidsExhausted = true;
561
+ }
562
+ }
563
+ }
564
+
565
+ findCrossingOrders(
566
+ askNode: DLOBNode,
567
+ bidNode: DLOBNode,
568
+ oraclePriceData: OraclePriceData,
569
+ slot: number
570
+ ): {
571
+ crossingNodes?: NodeToFill;
572
+ exhaustedSide?: Side;
573
+ } {
574
+ const bidPrice = bidNode.getPrice(oraclePriceData, slot);
575
+ const askPrice = askNode.getPrice(oraclePriceData, slot);
576
+
577
+ const bidOrder = bidNode.order;
578
+ const askOrder = askNode.order;
579
+
580
+ const containsMarketOrder =
581
+ (askOrder && isVariant(askOrder.orderType, 'market')) ||
582
+ (bidOrder && isVariant(bidOrder.orderType, 'market'));
583
+
584
+ if (!containsMarketOrder || bidPrice.lt(askPrice)) {
585
+ // no market orders, and no crossing orders - return
586
+ if (askNode.isVammNode() && !bidNode.isVammNode()) {
587
+ return {
588
+ exhaustedSide: 'bid',
589
+ };
590
+ } else if (!askNode.isVammNode() && bidNode.isVammNode()) {
591
+ return {
592
+ exhaustedSide: 'ask',
593
+ };
594
+ } else {
595
+ return {
596
+ exhaustedSide: 'both',
597
+ };
598
+ }
599
+ }
600
+
601
+ // User bid crosses the vamm ask
602
+ if (askNode.isVammNode()) {
603
+ if (!isAuctionComplete(bidOrder, slot)) {
604
+ return {
605
+ exhaustedSide: 'bid',
606
+ };
607
+ }
608
+ return {
609
+ crossingNodes: {
610
+ node: bidNode,
611
+ },
612
+ exhaustedSide: 'bid',
613
+ };
614
+ }
615
+
616
+ // User ask crosses the vamm bid
617
+ if (bidNode.isVammNode()) {
618
+ if (!isAuctionComplete(askOrder, slot)) {
619
+ return {
620
+ exhaustedSide: 'ask',
621
+ };
622
+ }
623
+ return {
624
+ crossingNodes: {
625
+ node: askNode,
626
+ },
627
+ exhaustedSide: 'ask',
628
+ };
629
+ }
630
+
631
+ const bidBaseRemaining = bidOrder.baseAssetAmount.sub(
632
+ bidOrder.baseAssetAmountFilled
633
+ );
634
+ const askBaseRemaining = askOrder.baseAssetAmount.sub(
635
+ askOrder.baseAssetAmountFilled
636
+ );
637
+
638
+ let exhaustedSide: Side;
639
+ if (bidBaseRemaining.eq(askBaseRemaining)) {
640
+ exhaustedSide = 'both';
641
+ } else if (bidBaseRemaining.gt(askBaseRemaining)) {
642
+ exhaustedSide = 'ask';
643
+ } else {
644
+ exhaustedSide = 'bid';
645
+ }
646
+
647
+ // Can't match two maker orders
648
+ if (bidOrder.postOnly && askOrder.postOnly) {
649
+ return {
650
+ exhaustedSide,
651
+ };
652
+ }
653
+
654
+ // update the orders as if they fill - so we don't try to match them on the next iteration
655
+ if (exhaustedSide === 'ask') {
656
+ bidNode.order.baseAssetAmountFilled =
657
+ bidOrder.baseAssetAmountFilled.add(askBaseRemaining);
658
+ askNode.order.baseAssetAmountFilled = askOrder.baseAssetAmount;
659
+ } else if (exhaustedSide === 'bid') {
660
+ askNode.order.baseAssetAmountFilled =
661
+ askOrder.baseAssetAmountFilled.add(bidBaseRemaining);
662
+ bidNode.order.baseAssetAmountFilled = bidOrder.baseAssetAmount;
663
+ } else {
664
+ askNode.order.baseAssetAmountFilled = askOrder.baseAssetAmount;
665
+ bidNode.order.baseAssetAmountFilled = bidOrder.baseAssetAmount;
666
+ }
667
+
668
+ // Bid is maker
669
+ if (bidOrder.postOnly) {
670
+ return {
671
+ crossingNodes: {
672
+ node: askNode,
673
+ makerNode: bidNode,
674
+ },
675
+ exhaustedSide,
676
+ };
677
+ }
678
+
679
+ // Ask is maker
680
+ if (askOrder.postOnly) {
681
+ return {
682
+ crossingNodes: {
683
+ node: bidNode,
684
+ makerNode: askNode,
685
+ },
686
+ exhaustedSide,
687
+ };
688
+ }
689
+
690
+ // Both are takers
691
+ // older order is maker
692
+ const [olderNode, newerNode] = askOrder.ts.lt(bidOrder.ts)
693
+ ? [askNode, bidNode]
694
+ : [bidNode, askNode];
695
+ return {
696
+ crossingNodes: {
697
+ node: newerNode,
698
+ makerNode: olderNode,
699
+ },
700
+ exhaustedSide,
701
+ };
702
+ }
703
+
704
+ public getBestAsk(
705
+ marketIndex: number,
706
+ vAsk: BN | undefined,
707
+ slot: number,
708
+ marketType: MarketType,
709
+ oraclePriceData: OraclePriceData
710
+ ): BN {
711
+ return this.getAsks(marketIndex, vAsk, slot, marketType, oraclePriceData)
712
+ .next()
713
+ .value.getPrice(oraclePriceData, slot);
714
+ }
715
+
716
+ public getBestBid(
717
+ marketIndex: number,
718
+ vBid: BN | undefined,
719
+ slot: number,
720
+ marketType: MarketType,
721
+ oraclePriceData: OraclePriceData
722
+ ): BN {
723
+ return this.getBids(marketIndex, vBid, slot, marketType, oraclePriceData)
724
+ .next()
725
+ .value.getPrice(oraclePriceData, slot);
726
+ }
727
+
728
+ public findNodesToTrigger(
729
+ marketIndex: number,
730
+ slot: number,
731
+ oraclePrice: BN,
732
+ marketType: MarketType
733
+ ): NodeToTrigger[] {
734
+ const nodesToTrigger = [];
735
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
736
+ for (const node of this.orderLists
737
+ .get(marketTypeStr)
738
+ .get(marketIndex)
739
+ .trigger.above.getGenerator()) {
740
+ if (oraclePrice.gt(node.order.triggerPrice)) {
741
+ if (isAuctionComplete(node.order, slot)) {
742
+ nodesToTrigger.push({
743
+ node: node,
744
+ });
745
+ }
746
+ } else {
747
+ break;
748
+ }
749
+ }
750
+
751
+ for (const node of this.orderLists
752
+ .get(marketTypeStr)
753
+ .get(marketIndex)
754
+ .trigger.below.getGenerator()) {
755
+ if (oraclePrice.lt(node.order.triggerPrice)) {
756
+ if (isAuctionComplete(node.order, slot)) {
757
+ nodesToTrigger.push({
758
+ node: node,
759
+ });
760
+ }
761
+ } else {
762
+ break;
763
+ }
764
+ }
765
+
766
+ return nodesToTrigger;
767
+ }
768
+
769
+ public printTopOfOrderLists(
770
+ sdkConfig: any,
771
+ clearingHouse: ClearingHouse,
772
+ slotSubscriber: SlotSubscriber,
773
+ marketIndex: number,
774
+ marketType: MarketType
775
+ ) {
776
+ if (isVariant(marketType, 'perp')) {
777
+ const market = clearingHouse.getPerpMarketAccount(marketIndex);
778
+
779
+ const slot = slotSubscriber.getSlot();
780
+ const oraclePriceData = clearingHouse.getOracleDataForMarket(marketIndex);
781
+ const vAsk = calculateAskPrice(market, oraclePriceData);
782
+ const vBid = calculateBidPrice(market, oraclePriceData);
783
+
784
+ const bestAsk = this.getBestAsk(
785
+ marketIndex,
786
+ vAsk,
787
+ slot,
788
+ marketType,
789
+ oraclePriceData
790
+ );
791
+ const bestBid = this.getBestBid(
792
+ marketIndex,
793
+ vBid,
794
+ slot,
795
+ marketType,
796
+ oraclePriceData
797
+ );
798
+ const mid = bestAsk.add(bestBid).div(new BN(2));
799
+
800
+ const bidSpread =
801
+ (convertToNumber(bestBid, PRICE_PRECISION) /
802
+ convertToNumber(oraclePriceData.price, PRICE_PRECISION) -
803
+ 1) *
804
+ 100.0;
805
+ const askSpread =
806
+ (convertToNumber(bestAsk, PRICE_PRECISION) /
807
+ convertToNumber(oraclePriceData.price, PRICE_PRECISION) -
808
+ 1) *
809
+ 100.0;
810
+
811
+ console.log(`Market ${sdkConfig.MARKETS[marketIndex].symbol} Orders`);
812
+ console.log(
813
+ ` Ask`,
814
+ convertToNumber(bestAsk, PRICE_PRECISION).toFixed(3),
815
+ `(${askSpread.toFixed(4)}%)`
816
+ );
817
+ console.log(` Mid`, convertToNumber(mid, PRICE_PRECISION).toFixed(3));
818
+ console.log(
819
+ ` Bid`,
820
+ convertToNumber(bestBid, PRICE_PRECISION).toFixed(3),
821
+ `(${bidSpread.toFixed(4)}%)`
822
+ );
823
+ } else if (isVariant(marketType, 'spot')) {
824
+ const slot = slotSubscriber.getSlot();
825
+ const oraclePriceData = clearingHouse.getOracleDataForMarket(marketIndex);
826
+
827
+ const bestAsk = this.getBestAsk(
828
+ marketIndex,
829
+ undefined,
830
+ slot,
831
+ marketType,
832
+ oraclePriceData
833
+ );
834
+ const bestBid = this.getBestBid(
835
+ marketIndex,
836
+ undefined,
837
+ slot,
838
+ marketType,
839
+ oraclePriceData
840
+ );
841
+ const mid = bestAsk.add(bestBid).div(new BN(2));
842
+
843
+ const bidSpread =
844
+ (convertToNumber(bestBid, PRICE_PRECISION) /
845
+ convertToNumber(oraclePriceData.price, PRICE_PRECISION) -
846
+ 1) *
847
+ 100.0;
848
+ const askSpread =
849
+ (convertToNumber(bestAsk, PRICE_PRECISION) /
850
+ convertToNumber(oraclePriceData.price, PRICE_PRECISION) -
851
+ 1) *
852
+ 100.0;
853
+
854
+ console.log(`Market ${sdkConfig.MARKETS[marketIndex].symbol} Orders`);
855
+ console.log(
856
+ ` Ask`,
857
+ convertToNumber(bestAsk, PRICE_PRECISION).toFixed(3),
858
+ `(${askSpread.toFixed(4)}%)`
859
+ );
860
+ console.log(` Mid`, convertToNumber(mid, PRICE_PRECISION).toFixed(3));
861
+ console.log(
862
+ ` Bid`,
863
+ convertToNumber(bestBid, PRICE_PRECISION).toFixed(3),
864
+ `(${bidSpread.toFixed(4)}%)`
865
+ );
866
+ }
867
+ }
868
+ }