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

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 +42 -26
  20. package/lib/admin.js +228 -81
  21. package/lib/clearingHouse.d.ts +130 -49
  22. package/lib/clearingHouse.js +1256 -367
  23. package/lib/clearingHouseConfig.d.ts +4 -4
  24. package/lib/clearingHouseUser.d.ts +51 -39
  25. package/lib/clearingHouseUser.js +408 -191
  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 +25 -12
  33. package/lib/constants/numericConstants.js +35 -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 +74 -0
  39. package/lib/dlob/DLOB.js +595 -0
  40. package/lib/dlob/DLOBNode.d.ts +54 -0
  41. package/lib/dlob/DLOBNode.js +85 -0
  42. package/lib/dlob/NodeList.d.ts +26 -0
  43. package/lib/dlob/NodeList.js +139 -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 +8 -2
  53. package/lib/events/types.js +6 -0
  54. package/lib/examples/makeTradeExample.js +20 -8
  55. package/lib/factory/bigNum.d.ts +2 -0
  56. package/lib/factory/bigNum.js +48 -10
  57. package/lib/idl/clearing_house.json +4889 -1529
  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 +9 -6
  62. package/lib/math/amm.js +91 -66
  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 +72 -10
  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 +192 -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 +32 -39
  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 +566 -138
  106. package/lib/types.js +129 -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 +12 -6
  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 +452 -147
  126. package/src/assert/assert.js +9 -0
  127. package/src/clearingHouse.ts +2066 -467
  128. package/src/clearingHouseConfig.ts +4 -3
  129. package/src/clearingHouseUser.ts +747 -294
  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 +48 -25
  134. package/src/constants/{markets.ts → perpMarkets.ts} +10 -10
  135. package/src/constants/spotMarkets.ts +73 -0
  136. package/src/dlob/DLOB.ts +953 -0
  137. package/src/dlob/DLOBNode.ts +167 -0
  138. package/src/dlob/NodeList.ts +189 -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 +19 -1
  146. package/src/examples/makeTradeExample.js +157 -0
  147. package/src/examples/makeTradeExample.ts +32 -14
  148. package/src/factory/bigNum.ts +65 -13
  149. package/src/idl/clearing_house.json +4889 -1529
  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 +171 -88
  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 +145 -14
  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 +319 -0
  164. package/src/math/spotMarket.ts +9 -0
  165. package/src/math/spotPosition.ts +47 -0
  166. package/src/math/trade.ts +53 -59
  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 +533 -140
  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 +24 -10
  191. package/tests/dlob/helpers.ts +397 -0
  192. package/tests/dlob/test.ts +3688 -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,953 @@
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' | 'nocross';
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.eq(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
+ slot,
278
+ marketType,
279
+ oraclePriceData
280
+ );
281
+
282
+ const vAMMCrossingNodesToFill: Array<NodeToFill> =
283
+ this.findvAMMCrossingNodesToFill(
284
+ marketIndex,
285
+ vBid,
286
+ vAsk,
287
+ slot,
288
+ marketType,
289
+ oraclePriceData
290
+ );
291
+
292
+ // get expired market nodes
293
+ const marketNodesToFill = this.findExpiredMarketNodesToFill(
294
+ marketIndex,
295
+ slot,
296
+ marketType
297
+ );
298
+ return crossingNodesToFill.concat(
299
+ vAMMCrossingNodesToFill,
300
+ marketNodesToFill
301
+ );
302
+ }
303
+
304
+ public findCrossingNodesToFill(
305
+ marketIndex: number,
306
+ slot: number,
307
+ marketType: MarketType,
308
+ oraclePriceData: OraclePriceData
309
+ ): NodeToFill[] {
310
+ const nodesToFill = new Array<NodeToFill>();
311
+
312
+ const askGenerator = this.getAsks(
313
+ marketIndex,
314
+ undefined, // dont include vask
315
+ slot,
316
+ marketType,
317
+ oraclePriceData
318
+ );
319
+ const bidGenerator = this.getBids(
320
+ marketIndex,
321
+ undefined, // dont include vbid
322
+ slot,
323
+ marketType,
324
+ oraclePriceData
325
+ );
326
+
327
+ let nextAsk = askGenerator.next();
328
+ let nextBid = bidGenerator.next();
329
+
330
+ // First try to find orders that cross
331
+ while (!nextAsk.done && !nextBid.done) {
332
+ const { crossingNodes, exhaustedSide } = this.findCrossingOrders(
333
+ nextAsk.value,
334
+ nextBid.value,
335
+ oraclePriceData,
336
+ slot
337
+ );
338
+
339
+ if (exhaustedSide === 'bid') {
340
+ nextBid = bidGenerator.next();
341
+ } else if (exhaustedSide === 'ask') {
342
+ nextAsk = askGenerator.next();
343
+ } else if (exhaustedSide === 'both') {
344
+ nextBid = bidGenerator.next();
345
+ nextAsk = askGenerator.next();
346
+ } else if (exhaustedSide === 'nocross') {
347
+ break;
348
+ } else {
349
+ console.error(`invalid exhaustedSide: ${exhaustedSide}`);
350
+ break;
351
+ }
352
+
353
+ for (const crossingNode of crossingNodes) {
354
+ nodesToFill.push(crossingNode);
355
+ }
356
+ }
357
+ return nodesToFill;
358
+ }
359
+
360
+ public findvAMMCrossingNodesToFill(
361
+ marketIndex: number,
362
+ vBid: BN,
363
+ vAsk: BN,
364
+ slot: number,
365
+ marketType: MarketType,
366
+ oraclePriceData: OraclePriceData
367
+ ): NodeToFill[] {
368
+ const nodesToFill = new Array<NodeToFill>();
369
+
370
+ const askGenerator = this.getAsks(
371
+ marketIndex,
372
+ undefined, // dont include vask
373
+ slot,
374
+ marketType,
375
+ oraclePriceData
376
+ );
377
+ const bidGenerator = this.getBids(
378
+ marketIndex,
379
+ undefined, // dont include vbid
380
+ slot,
381
+ marketType,
382
+ oraclePriceData
383
+ );
384
+
385
+ let nextAsk = askGenerator.next();
386
+ let nextBid = bidGenerator.next();
387
+
388
+ // check for asks that cross vBid
389
+ while (!nextAsk.done) {
390
+ const askNode = nextAsk.value;
391
+ const askPrice = askNode.getPrice(oraclePriceData, slot);
392
+
393
+ if (askPrice.lte(vBid) && isAuctionComplete(askNode.order, slot)) {
394
+ nodesToFill.push({
395
+ node: askNode,
396
+ makerNode: undefined, // filled by vAMM
397
+ });
398
+ } else {
399
+ break;
400
+ }
401
+
402
+ nextAsk = askGenerator.next();
403
+ }
404
+
405
+ // check for bids that cross vAsk
406
+ while (!nextBid.done) {
407
+ const bidNode = nextBid.value;
408
+ const bidPrice = bidNode.getPrice(oraclePriceData, slot);
409
+
410
+ if (bidPrice.gte(vAsk) && isAuctionComplete(bidNode.order, slot)) {
411
+ nodesToFill.push({
412
+ node: bidNode,
413
+ makerNode: undefined, // filled by vAMM
414
+ });
415
+ } else {
416
+ break;
417
+ }
418
+
419
+ nextBid = bidGenerator.next();
420
+ }
421
+
422
+ return nodesToFill;
423
+ }
424
+
425
+ public findExpiredMarketNodesToFill(
426
+ marketIndex: number,
427
+ slot: number,
428
+ marketType: MarketType
429
+ ): NodeToFill[] {
430
+ const nodesToFill = new Array<NodeToFill>();
431
+ // Then see if there are orders to fill against vamm
432
+ for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
433
+ if (isOrderExpired(marketBid.order, slot)) {
434
+ nodesToFill.push({
435
+ node: marketBid,
436
+ });
437
+ }
438
+ }
439
+
440
+ for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
441
+ if (isOrderExpired(marketAsk.order, slot)) {
442
+ nodesToFill.push({
443
+ node: marketAsk,
444
+ });
445
+ }
446
+ }
447
+ return nodesToFill;
448
+ }
449
+
450
+ public findJitAuctionNodesToFill(
451
+ marketIndex: number,
452
+ slot: number,
453
+ marketType: MarketType
454
+ ): NodeToFill[] {
455
+ const nodesToFill = new Array<NodeToFill>();
456
+ // Then see if there are orders still in JIT auction
457
+ for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
458
+ if (!isAuctionComplete(marketBid.order, slot)) {
459
+ nodesToFill.push({
460
+ node: marketBid,
461
+ });
462
+ }
463
+ }
464
+
465
+ for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
466
+ if (!isAuctionComplete(marketAsk.order, slot)) {
467
+ nodesToFill.push({
468
+ node: marketAsk,
469
+ });
470
+ }
471
+ }
472
+ return nodesToFill;
473
+ }
474
+
475
+ public getMarketBids(
476
+ marketIndex: number,
477
+ marketType: MarketType
478
+ ): Generator<DLOBNode> {
479
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
480
+ return this.orderLists
481
+ .get(marketTypeStr)
482
+ .get(marketIndex)
483
+ .market.bid.getGenerator();
484
+ }
485
+
486
+ public getMarketAsks(
487
+ marketIndex: number,
488
+ marketType: MarketType
489
+ ): Generator<DLOBNode> {
490
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
491
+ return this.orderLists
492
+ .get(marketTypeStr)
493
+ .get(marketIndex)
494
+ .market.ask.getGenerator();
495
+ }
496
+
497
+ *getAsks(
498
+ marketIndex: number,
499
+ vAsk: BN | undefined,
500
+ slot: number,
501
+ marketType: MarketType,
502
+ oraclePriceData: OraclePriceData
503
+ ): Generator<DLOBNode> {
504
+ if (isVariant(marketType, 'spot') && !oraclePriceData) {
505
+ throw new Error('Must provide OraclePriceData to get spot asks');
506
+ }
507
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
508
+ const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
509
+
510
+ const generatorList = [
511
+ nodeLists.limit.ask.getGenerator(),
512
+ nodeLists.floatingLimit.ask.getGenerator(),
513
+ nodeLists.market.ask.getGenerator(),
514
+ ];
515
+
516
+ if (marketTypeStr === 'perp' && vAsk) {
517
+ generatorList.push(getVammNodeGenerator(vAsk));
518
+ }
519
+
520
+ const askGenerators = generatorList.map((generator) => {
521
+ return {
522
+ next: generator.next(),
523
+ generator,
524
+ };
525
+ });
526
+
527
+ let asksExhausted = false;
528
+ while (!asksExhausted) {
529
+ const bestGenerator = askGenerators.reduce(
530
+ (bestGenerator, currentGenerator) => {
531
+ if (currentGenerator.next.done) {
532
+ return bestGenerator;
533
+ }
534
+
535
+ if (bestGenerator.next.done) {
536
+ return currentGenerator;
537
+ }
538
+
539
+ const bestAskPrice = bestGenerator.next.value.getPrice(
540
+ oraclePriceData,
541
+ slot
542
+ );
543
+ const currentAskPrice = currentGenerator.next.value.getPrice(
544
+ oraclePriceData,
545
+ slot
546
+ );
547
+
548
+ return bestAskPrice.lt(currentAskPrice)
549
+ ? bestGenerator
550
+ : currentGenerator;
551
+ }
552
+ );
553
+
554
+ if (!bestGenerator.next.done) {
555
+ // skip this node if it's already completely filled
556
+ if (bestGenerator.next.value.isBaseFilled()) {
557
+ bestGenerator.next = bestGenerator.generator.next();
558
+ continue;
559
+ }
560
+
561
+ yield bestGenerator.next.value;
562
+ bestGenerator.next = bestGenerator.generator.next();
563
+ } else {
564
+ asksExhausted = true;
565
+ }
566
+ }
567
+ }
568
+
569
+ *getBids(
570
+ marketIndex: number,
571
+ vBid: BN | undefined,
572
+ slot: number,
573
+ marketType: MarketType,
574
+ oraclePriceData: OraclePriceData
575
+ ): Generator<DLOBNode> {
576
+ if (isVariant(marketType, 'spot') && !oraclePriceData) {
577
+ throw new Error('Must provide OraclePriceData to get spot bids');
578
+ }
579
+
580
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
581
+ const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
582
+
583
+ const generatorList = [
584
+ nodeLists.limit.bid.getGenerator(),
585
+ nodeLists.floatingLimit.bid.getGenerator(),
586
+ nodeLists.market.bid.getGenerator(),
587
+ ];
588
+ if (marketTypeStr === 'perp' && vBid) {
589
+ generatorList.push(getVammNodeGenerator(vBid));
590
+ }
591
+ const bidGenerators = generatorList.map((generator) => {
592
+ return {
593
+ next: generator.next(),
594
+ generator,
595
+ };
596
+ });
597
+
598
+ let bidsExhausted = false; // there will always be the vBid
599
+ while (!bidsExhausted) {
600
+ const bestGenerator = bidGenerators.reduce(
601
+ (bestGenerator, currentGenerator) => {
602
+ if (currentGenerator.next.done) {
603
+ return bestGenerator;
604
+ }
605
+
606
+ if (bestGenerator.next.done) {
607
+ return currentGenerator;
608
+ }
609
+
610
+ const bestBidPrice = bestGenerator.next.value.getPrice(
611
+ oraclePriceData,
612
+ slot
613
+ );
614
+ const currentBidPrice = currentGenerator.next.value.getPrice(
615
+ oraclePriceData,
616
+ slot
617
+ );
618
+
619
+ return bestBidPrice.gt(currentBidPrice)
620
+ ? bestGenerator
621
+ : currentGenerator;
622
+ }
623
+ );
624
+
625
+ if (!bestGenerator.next.done) {
626
+ // skip this node if it's already completely filled
627
+ if (bestGenerator.next.value.isBaseFilled()) {
628
+ bestGenerator.next = bestGenerator.generator.next();
629
+ continue;
630
+ }
631
+
632
+ yield bestGenerator.next.value;
633
+ bestGenerator.next = bestGenerator.generator.next();
634
+ } else {
635
+ bidsExhausted = true;
636
+ }
637
+ }
638
+ }
639
+
640
+ findCrossingOrders(
641
+ askNode: DLOBNode,
642
+ bidNode: DLOBNode,
643
+ oraclePriceData: OraclePriceData,
644
+ slot: number
645
+ ): {
646
+ crossingNodes: NodeToFill[];
647
+ exhaustedSide: Side;
648
+ } {
649
+ const bidPrice = bidNode.getPrice(oraclePriceData, slot);
650
+ const askPrice = askNode.getPrice(oraclePriceData, slot);
651
+
652
+ // orders don't cross - we're done walkin gup the book
653
+ if (bidPrice.lt(askPrice)) {
654
+ return {
655
+ crossingNodes: [],
656
+ exhaustedSide: 'nocross',
657
+ };
658
+ }
659
+
660
+ const bidOrder = bidNode.order;
661
+ const askOrder = askNode.order;
662
+
663
+ // Can't match two maker orders or if maker and taker are the same
664
+ const makerIsTaker = bidNode.userAccount.equals(askNode.userAccount);
665
+ if (makerIsTaker || (bidOrder.postOnly && askOrder.postOnly)) {
666
+ // don't have a principle way to pick which one to exhaust,
667
+ // exhaust each one 50% of the time so we can try each one against other orders
668
+ const exhaustedSide = Math.random() < 0.5 ? 'bid' : 'ask';
669
+ return {
670
+ crossingNodes: [],
671
+ exhaustedSide,
672
+ };
673
+ }
674
+
675
+ const bidBaseRemaining = bidOrder.baseAssetAmount.sub(
676
+ bidOrder.baseAssetAmountFilled
677
+ );
678
+ const askBaseRemaining = askOrder.baseAssetAmount.sub(
679
+ askOrder.baseAssetAmountFilled
680
+ );
681
+
682
+ let exhaustedSide: Side;
683
+ if (bidBaseRemaining.eq(askBaseRemaining)) {
684
+ exhaustedSide = 'both';
685
+ } else if (bidBaseRemaining.gt(askBaseRemaining)) {
686
+ exhaustedSide = 'ask';
687
+ } else {
688
+ exhaustedSide = 'bid';
689
+ }
690
+
691
+ // update the orders on DLOB as if they were fill - so we don't try to match them on the next iteration
692
+ // NOTE: if something goes wrong during the actual fill (transaction fails, i.e. due to orders already being filled)
693
+ // then we risk having a mismatch between this local DLOB and the actual DLOB state on the blockchain. This isn't
694
+ // a problem in the current implementation because we construct a new DLOB from the blockchain state every time, rather
695
+ // than updating the existing DLOB based on events.
696
+ if (exhaustedSide === 'ask') {
697
+ // bid partially filled
698
+ const newBidOrder = { ...bidOrder };
699
+ newBidOrder.baseAssetAmountFilled =
700
+ bidOrder.baseAssetAmountFilled.add(askBaseRemaining);
701
+ this.getListForOrder(newBidOrder).update(
702
+ newBidOrder,
703
+ bidNode.userAccount
704
+ );
705
+
706
+ // ask completely filled
707
+ const newAskOrder = { ...askOrder };
708
+ newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
709
+ this.getListForOrder(newAskOrder).update(
710
+ newAskOrder,
711
+ askNode.userAccount
712
+ );
713
+ } else if (exhaustedSide === 'bid') {
714
+ // ask partially filled
715
+ const newAskOrder = { ...askOrder };
716
+ newAskOrder.baseAssetAmountFilled =
717
+ askOrder.baseAssetAmountFilled.add(bidBaseRemaining);
718
+ this.getListForOrder(newAskOrder).update(
719
+ newAskOrder,
720
+ askNode.userAccount
721
+ );
722
+
723
+ // bid completely filled
724
+ const newBidOrder = { ...bidOrder };
725
+ newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
726
+ this.getListForOrder(newBidOrder).update(
727
+ newBidOrder,
728
+ bidNode.userAccount
729
+ );
730
+ } else {
731
+ // both completely filled
732
+ const newBidOrder = { ...bidOrder };
733
+ newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
734
+ this.getListForOrder(newBidOrder).update(
735
+ newBidOrder,
736
+ bidNode.userAccount
737
+ );
738
+
739
+ const newAskOrder = { ...askOrder };
740
+ newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
741
+ this.getListForOrder(newAskOrder).update(
742
+ newAskOrder,
743
+ askNode.userAccount
744
+ );
745
+ }
746
+
747
+ // Bid is maker
748
+ if (bidOrder.postOnly) {
749
+ return {
750
+ crossingNodes: [
751
+ {
752
+ node: askNode,
753
+ makerNode: bidNode,
754
+ },
755
+ ],
756
+ exhaustedSide,
757
+ };
758
+ }
759
+
760
+ // Ask is maker
761
+ if (askOrder.postOnly) {
762
+ return {
763
+ crossingNodes: [
764
+ {
765
+ node: bidNode,
766
+ makerNode: askNode,
767
+ },
768
+ ],
769
+ exhaustedSide,
770
+ };
771
+ }
772
+
773
+ // Both are takers
774
+ // older order is maker
775
+ const [olderNode, newerNode] = askOrder.ts.lt(bidOrder.ts)
776
+ ? [askNode, bidNode]
777
+ : [bidNode, askNode];
778
+ return {
779
+ crossingNodes: [
780
+ {
781
+ node: newerNode,
782
+ makerNode: olderNode,
783
+ },
784
+ ],
785
+ exhaustedSide,
786
+ };
787
+ }
788
+
789
+ public getBestAsk(
790
+ marketIndex: number,
791
+ vAsk: BN | undefined,
792
+ slot: number,
793
+ marketType: MarketType,
794
+ oraclePriceData: OraclePriceData
795
+ ): BN {
796
+ return this.getAsks(marketIndex, vAsk, slot, marketType, oraclePriceData)
797
+ .next()
798
+ .value.getPrice(oraclePriceData, slot);
799
+ }
800
+
801
+ public getBestBid(
802
+ marketIndex: number,
803
+ vBid: BN | undefined,
804
+ slot: number,
805
+ marketType: MarketType,
806
+ oraclePriceData: OraclePriceData
807
+ ): BN {
808
+ return this.getBids(marketIndex, vBid, slot, marketType, oraclePriceData)
809
+ .next()
810
+ .value.getPrice(oraclePriceData, slot);
811
+ }
812
+
813
+ public findNodesToTrigger(
814
+ marketIndex: number,
815
+ slot: number,
816
+ oraclePrice: BN,
817
+ marketType: MarketType
818
+ ): NodeToTrigger[] {
819
+ const nodesToTrigger = [];
820
+ const marketTypeStr = getVariant(marketType) as MarketTypeStr;
821
+ for (const node of this.orderLists
822
+ .get(marketTypeStr)
823
+ .get(marketIndex)
824
+ .trigger.above.getGenerator()) {
825
+ if (oraclePrice.gt(node.order.triggerPrice)) {
826
+ if (isAuctionComplete(node.order, slot)) {
827
+ nodesToTrigger.push({
828
+ node: node,
829
+ });
830
+ }
831
+ } else {
832
+ break;
833
+ }
834
+ }
835
+
836
+ for (const node of this.orderLists
837
+ .get(marketTypeStr)
838
+ .get(marketIndex)
839
+ .trigger.below.getGenerator()) {
840
+ if (oraclePrice.lt(node.order.triggerPrice)) {
841
+ if (isAuctionComplete(node.order, slot)) {
842
+ nodesToTrigger.push({
843
+ node: node,
844
+ });
845
+ }
846
+ } else {
847
+ break;
848
+ }
849
+ }
850
+
851
+ return nodesToTrigger;
852
+ }
853
+
854
+ public printTopOfOrderLists(
855
+ sdkConfig: any,
856
+ clearingHouse: ClearingHouse,
857
+ slotSubscriber: SlotSubscriber,
858
+ marketIndex: number,
859
+ marketType: MarketType
860
+ ) {
861
+ if (isVariant(marketType, 'perp')) {
862
+ const market = clearingHouse.getPerpMarketAccount(marketIndex);
863
+
864
+ const slot = slotSubscriber.getSlot();
865
+ const oraclePriceData = clearingHouse.getOracleDataForMarket(marketIndex);
866
+ const vAsk = calculateAskPrice(market, oraclePriceData);
867
+ const vBid = calculateBidPrice(market, oraclePriceData);
868
+
869
+ const bestAsk = this.getBestAsk(
870
+ marketIndex,
871
+ vAsk,
872
+ slot,
873
+ marketType,
874
+ oraclePriceData
875
+ );
876
+ const bestBid = this.getBestBid(
877
+ marketIndex,
878
+ vBid,
879
+ slot,
880
+ marketType,
881
+ oraclePriceData
882
+ );
883
+ const mid = bestAsk.add(bestBid).div(new BN(2));
884
+
885
+ const bidSpread =
886
+ (convertToNumber(bestBid, PRICE_PRECISION) /
887
+ convertToNumber(oraclePriceData.price, PRICE_PRECISION) -
888
+ 1) *
889
+ 100.0;
890
+ const askSpread =
891
+ (convertToNumber(bestAsk, PRICE_PRECISION) /
892
+ convertToNumber(oraclePriceData.price, PRICE_PRECISION) -
893
+ 1) *
894
+ 100.0;
895
+
896
+ console.log(`Market ${sdkConfig.MARKETS[marketIndex].symbol} Orders`);
897
+ console.log(
898
+ ` Ask`,
899
+ convertToNumber(bestAsk, PRICE_PRECISION).toFixed(3),
900
+ `(${askSpread.toFixed(4)}%)`
901
+ );
902
+ console.log(` Mid`, convertToNumber(mid, PRICE_PRECISION).toFixed(3));
903
+ console.log(
904
+ ` Bid`,
905
+ convertToNumber(bestBid, PRICE_PRECISION).toFixed(3),
906
+ `(${bidSpread.toFixed(4)}%)`
907
+ );
908
+ } else if (isVariant(marketType, 'spot')) {
909
+ const slot = slotSubscriber.getSlot();
910
+ const oraclePriceData = clearingHouse.getOracleDataForMarket(marketIndex);
911
+
912
+ const bestAsk = this.getBestAsk(
913
+ marketIndex,
914
+ undefined,
915
+ slot,
916
+ marketType,
917
+ oraclePriceData
918
+ );
919
+ const bestBid = this.getBestBid(
920
+ marketIndex,
921
+ undefined,
922
+ slot,
923
+ marketType,
924
+ oraclePriceData
925
+ );
926
+ const mid = bestAsk.add(bestBid).div(new BN(2));
927
+
928
+ const bidSpread =
929
+ (convertToNumber(bestBid, PRICE_PRECISION) /
930
+ convertToNumber(oraclePriceData.price, PRICE_PRECISION) -
931
+ 1) *
932
+ 100.0;
933
+ const askSpread =
934
+ (convertToNumber(bestAsk, PRICE_PRECISION) /
935
+ convertToNumber(oraclePriceData.price, PRICE_PRECISION) -
936
+ 1) *
937
+ 100.0;
938
+
939
+ console.log(`Market ${sdkConfig.MARKETS[marketIndex].symbol} Orders`);
940
+ console.log(
941
+ ` Ask`,
942
+ convertToNumber(bestAsk, PRICE_PRECISION).toFixed(3),
943
+ `(${askSpread.toFixed(4)}%)`
944
+ );
945
+ console.log(` Mid`, convertToNumber(mid, PRICE_PRECISION).toFixed(3));
946
+ console.log(
947
+ ` Bid`,
948
+ convertToNumber(bestBid, PRICE_PRECISION).toFixed(3),
949
+ `(${bidSpread.toFixed(4)}%)`
950
+ );
951
+ }
952
+ }
953
+ }