@drift-labs/sdk 0.2.0-master.25 → 0.2.0-master.27

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 (124) hide show
  1. package/lib/accounts/pollingClearingHouseAccountSubscriber.d.ts +14 -13
  2. package/lib/accounts/pollingClearingHouseAccountSubscriber.js +30 -27
  3. package/lib/accounts/types.d.ts +9 -9
  4. package/lib/accounts/webSocketClearingHouseAccountSubscriber.d.ts +15 -14
  5. package/lib/accounts/webSocketClearingHouseAccountSubscriber.js +38 -34
  6. package/lib/addresses/pda.d.ts +7 -6
  7. package/lib/addresses/pda.js +31 -27
  8. package/lib/admin.d.ts +13 -7
  9. package/lib/admin.js +111 -44
  10. package/lib/clearingHouse.d.ts +71 -42
  11. package/lib/clearingHouse.js +767 -278
  12. package/lib/clearingHouseConfig.d.ts +2 -2
  13. package/lib/clearingHouseUser.d.ts +24 -22
  14. package/lib/clearingHouseUser.js +273 -177
  15. package/lib/config.d.ts +7 -7
  16. package/lib/config.js +21 -21
  17. package/lib/constants/numericConstants.d.ts +12 -12
  18. package/lib/constants/numericConstants.js +13 -13
  19. package/lib/constants/{markets.d.ts → perpMarkets.d.ts} +5 -5
  20. package/lib/constants/{markets.js → perpMarkets.js} +4 -4
  21. package/lib/constants/{banks.d.ts → spotMarkets.d.ts} +6 -6
  22. package/lib/constants/{banks.js → spotMarkets.js} +16 -16
  23. package/lib/dlob/DLOB.d.ts +73 -0
  24. package/lib/dlob/DLOB.js +557 -0
  25. package/lib/dlob/DLOBNode.d.ts +52 -0
  26. package/lib/dlob/DLOBNode.js +82 -0
  27. package/lib/dlob/NodeList.d.ts +26 -0
  28. package/lib/dlob/NodeList.js +138 -0
  29. package/lib/events/types.d.ts +2 -1
  30. package/lib/events/types.js +1 -0
  31. package/lib/examples/makeTradeExample.js +7 -7
  32. package/lib/idl/clearing_house.json +1152 -503
  33. package/lib/index.d.ts +10 -3
  34. package/lib/index.js +10 -3
  35. package/lib/math/amm.d.ts +2 -2
  36. package/lib/math/amm.js +1 -1
  37. package/lib/math/funding.d.ts +6 -6
  38. package/lib/math/funding.js +2 -1
  39. package/lib/math/margin.d.ts +4 -4
  40. package/lib/math/margin.js +18 -11
  41. package/lib/math/market.d.ts +11 -10
  42. package/lib/math/market.js +30 -7
  43. package/lib/math/oracles.d.ts +2 -1
  44. package/lib/math/oracles.js +11 -1
  45. package/lib/math/orders.d.ts +6 -6
  46. package/lib/math/orders.js +31 -16
  47. package/lib/math/position.d.ts +13 -13
  48. package/lib/math/position.js +19 -19
  49. package/lib/math/spotBalance.d.ts +22 -0
  50. package/lib/math/spotBalance.js +193 -0
  51. package/lib/math/spotMarket.d.ts +4 -0
  52. package/lib/math/spotMarket.js +8 -0
  53. package/lib/math/spotPosition.d.ts +6 -0
  54. package/lib/math/spotPosition.js +23 -0
  55. package/lib/math/state.js +2 -2
  56. package/lib/math/trade.d.ts +4 -4
  57. package/lib/orderParams.d.ts +4 -4
  58. package/lib/orderParams.js +12 -4
  59. package/lib/serum/serumSubscriber.d.ts +23 -0
  60. package/lib/serum/serumSubscriber.js +41 -0
  61. package/lib/serum/types.d.ts +11 -0
  62. package/lib/serum/types.js +2 -0
  63. package/lib/tx/retryTxSender.d.ts +1 -1
  64. package/lib/tx/retryTxSender.js +4 -2
  65. package/lib/tx/types.d.ts +1 -1
  66. package/lib/types.d.ts +148 -57
  67. package/lib/types.js +39 -11
  68. package/lib/userMap/userMap.d.ts +25 -0
  69. package/lib/userMap/userMap.js +73 -0
  70. package/lib/userMap/userStatsMap.d.ts +19 -0
  71. package/lib/userMap/userStatsMap.js +68 -0
  72. package/package.json +6 -3
  73. package/src/accounts/pollingClearingHouseAccountSubscriber.ts +42 -38
  74. package/src/accounts/types.ts +12 -9
  75. package/src/accounts/webSocketClearingHouseAccountSubscriber.ts +65 -52
  76. package/src/addresses/pda.ts +49 -44
  77. package/src/admin.ts +190 -55
  78. package/src/clearingHouse.ts +1092 -365
  79. package/src/clearingHouseConfig.ts +2 -2
  80. package/src/clearingHouseUser.ts +518 -255
  81. package/src/config.ts +30 -30
  82. package/src/constants/numericConstants.ts +17 -15
  83. package/src/constants/{markets.ts → perpMarkets.ts} +5 -5
  84. package/src/constants/{banks.ts → spotMarkets.ts} +19 -19
  85. package/src/dlob/DLOB.ts +884 -0
  86. package/src/dlob/DLOBNode.ts +163 -0
  87. package/src/dlob/NodeList.ts +185 -0
  88. package/src/events/types.ts +3 -0
  89. package/src/examples/makeTradeExample.js +152 -75
  90. package/src/examples/makeTradeExample.ts +10 -8
  91. package/src/idl/clearing_house.json +1152 -503
  92. package/src/index.ts +10 -3
  93. package/src/math/amm.ts +6 -3
  94. package/src/math/funding.ts +7 -7
  95. package/src/math/margin.ts +34 -23
  96. package/src/math/market.ts +72 -20
  97. package/src/math/oracles.ts +18 -1
  98. package/src/math/orders.ts +33 -25
  99. package/src/math/position.ts +31 -31
  100. package/src/math/spotBalance.ts +316 -0
  101. package/src/math/spotMarket.ts +9 -0
  102. package/src/math/spotPosition.ts +47 -0
  103. package/src/math/state.ts +2 -2
  104. package/src/math/trade.ts +4 -4
  105. package/src/orderParams.ts +16 -8
  106. package/src/serum/serumSubscriber.ts +80 -0
  107. package/src/serum/types.ts +13 -0
  108. package/src/tx/retryTxSender.ts +5 -2
  109. package/src/tx/types.ts +2 -1
  110. package/src/types.ts +135 -56
  111. package/src/userMap/userMap.ts +100 -0
  112. package/src/userMap/userStatsMap.ts +110 -0
  113. package/tests/bn/test.ts +2 -3
  114. package/tests/dlob/helpers.ts +322 -0
  115. package/tests/dlob/test.ts +2865 -0
  116. package/lib/math/bankBalance.d.ts +0 -15
  117. package/lib/math/bankBalance.js +0 -150
  118. package/src/constants/numericConstants.js +0 -41
  119. package/src/math/bankBalance.ts +0 -258
  120. package/src/math/oracles.js +0 -26
  121. package/src/math/state.js +0 -15
  122. package/src/orderParams.js +0 -20
  123. package/src/slot/SlotSubscriber.js +0 -39
  124. package/src/tokenFaucet.js +0 -189
@@ -0,0 +1,557 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DLOB = void 0;
4
+ const NodeList_1 = require("./NodeList");
5
+ const __1 = require("..");
6
+ class DLOB {
7
+ /**
8
+ *
9
+ * @param perpMarkets The perp markets to maintain a DLOB for
10
+ * @param spotMarkets The spot markets to maintain a DLOB for
11
+ * @param silent set to true to prevent logging on inserts and removals
12
+ */
13
+ constructor(perpMarkets, spotMarkets, silent) {
14
+ this.openOrders = new Map();
15
+ this.orderLists = new Map();
16
+ this.marketIndexToAccount = new Map();
17
+ this.silent = false;
18
+ this.initialized = false;
19
+ this.silent = silent;
20
+ this.openOrders.set('perp', new Set());
21
+ this.openOrders.set('spot', new Set());
22
+ this.orderLists.set('perp', new Map());
23
+ this.orderLists.set('spot', new Map());
24
+ this.marketIndexToAccount.set('perp', new Map());
25
+ this.marketIndexToAccount.set('spot', new Map());
26
+ for (const market of perpMarkets) {
27
+ const marketIndex = market.marketIndex;
28
+ this.marketIndexToAccount.get('perp').set(marketIndex.toNumber(), market);
29
+ this.orderLists.get('perp').set(marketIndex.toNumber(), {
30
+ limit: {
31
+ ask: new NodeList_1.NodeList('limit', 'asc'),
32
+ bid: new NodeList_1.NodeList('limit', 'desc'),
33
+ },
34
+ floatingLimit: {
35
+ ask: new NodeList_1.NodeList('floatingLimit', 'asc'),
36
+ bid: new NodeList_1.NodeList('floatingLimit', 'desc'),
37
+ },
38
+ market: {
39
+ ask: new NodeList_1.NodeList('market', 'asc'),
40
+ bid: new NodeList_1.NodeList('market', 'asc'), // always sort ascending for market orders
41
+ },
42
+ trigger: {
43
+ above: new NodeList_1.NodeList('trigger', 'asc'),
44
+ below: new NodeList_1.NodeList('trigger', 'desc'),
45
+ },
46
+ });
47
+ }
48
+ for (const market of spotMarkets) {
49
+ const marketIndex = market.marketIndex;
50
+ this.marketIndexToAccount.get('spot').set(marketIndex.toNumber(), market);
51
+ this.orderLists.get('spot').set(marketIndex.toNumber(), {
52
+ limit: {
53
+ ask: new NodeList_1.NodeList('limit', 'asc'),
54
+ bid: new NodeList_1.NodeList('limit', 'desc'),
55
+ },
56
+ floatingLimit: {
57
+ ask: new NodeList_1.NodeList('floatingLimit', 'asc'),
58
+ bid: new NodeList_1.NodeList('floatingLimit', 'desc'),
59
+ },
60
+ market: {
61
+ ask: new NodeList_1.NodeList('market', 'asc'),
62
+ bid: new NodeList_1.NodeList('market', 'asc'), // always sort ascending for market orders
63
+ },
64
+ trigger: {
65
+ above: new NodeList_1.NodeList('trigger', 'asc'),
66
+ below: new NodeList_1.NodeList('trigger', 'desc'),
67
+ },
68
+ });
69
+ }
70
+ }
71
+ /**
72
+ * initializes a new DLOB instance
73
+ *
74
+ * @param clearingHouse The ClearingHouse instance to use for price data
75
+ * @returns a promise that resolves when the DLOB is initialized
76
+ */
77
+ async init(clearingHouse, userMap) {
78
+ if (this.initialized) {
79
+ return false;
80
+ }
81
+ if (userMap) {
82
+ // initialize the dlob with the user map (prevents hitting getProgramAccounts)
83
+ for (const user of userMap.values()) {
84
+ const userAccount = user.getUserAccount();
85
+ const userAccountPubkey = user.getUserAccountPublicKey();
86
+ for (const order of userAccount.orders) {
87
+ this.insertOrder(order, userAccountPubkey);
88
+ }
89
+ }
90
+ }
91
+ else {
92
+ const programAccounts = await clearingHouse.program.account.user.all();
93
+ for (const programAccount of programAccounts) {
94
+ // @ts-ignore
95
+ const userAccount = programAccount.account;
96
+ const userAccountPublicKey = programAccount.publicKey;
97
+ for (const order of userAccount.orders) {
98
+ this.insertOrder(order, userAccountPublicKey);
99
+ }
100
+ }
101
+ }
102
+ this.initialized = true;
103
+ return true;
104
+ }
105
+ insertOrder(order, userAccount, onInsert) {
106
+ if ((0, __1.isVariant)(order.status, 'init')) {
107
+ return;
108
+ }
109
+ const marketType = (0, __1.getVariant)(order.marketType);
110
+ if ((0, __1.isVariant)(order.status, 'open')) {
111
+ this.openOrders
112
+ .get(marketType)
113
+ .add((0, NodeList_1.getOrderSignature)(order.orderId, userAccount));
114
+ }
115
+ this.getListForOrder(order).insert(order, marketType, this.marketIndexToAccount
116
+ .get(marketType)
117
+ .get(order.marketIndex.toNumber()), userAccount);
118
+ if (onInsert) {
119
+ onInsert();
120
+ }
121
+ }
122
+ trigger(order, userAccount, onTrigger) {
123
+ if ((0, __1.isVariant)(order.status, 'init')) {
124
+ return;
125
+ }
126
+ const marketType = (0, __1.getVariant)(order.marketType);
127
+ const triggerList = this.orderLists
128
+ .get(marketType)
129
+ .get(order.marketIndex.toNumber()).trigger[(0, __1.isVariant)(order.triggerCondition, 'above') ? 'above' : 'below'];
130
+ triggerList.remove(order, userAccount);
131
+ this.getListForOrder(order).insert(order, marketType, this.marketIndexToAccount
132
+ .get(marketType)
133
+ .get(order.marketIndex.toNumber()), userAccount);
134
+ if (onTrigger) {
135
+ onTrigger();
136
+ }
137
+ }
138
+ getListForOrder(order) {
139
+ const isInactiveTriggerOrder = (0, __1.isOneOfVariant)(order.orderType, ['triggerMarket', 'triggerLimit']) &&
140
+ !order.triggered;
141
+ let type;
142
+ if (isInactiveTriggerOrder) {
143
+ type = 'trigger';
144
+ }
145
+ else if ((0, __1.isOneOfVariant)(order.orderType, ['market', 'triggerMarket'])) {
146
+ type = 'market';
147
+ }
148
+ else if (order.oraclePriceOffset.gt(__1.ZERO)) {
149
+ type = 'floatingLimit';
150
+ }
151
+ else {
152
+ type = 'limit';
153
+ }
154
+ let subType;
155
+ if (isInactiveTriggerOrder) {
156
+ subType = (0, __1.isVariant)(order.triggerCondition, 'above') ? 'above' : 'below';
157
+ }
158
+ else {
159
+ subType = (0, __1.isVariant)(order.direction, 'long') ? 'bid' : 'ask';
160
+ }
161
+ const marketType = (0, __1.getVariant)(order.marketType);
162
+ return this.orderLists.get(marketType).get(order.marketIndex.toNumber())[type][subType];
163
+ }
164
+ findNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData) {
165
+ // Find all the crossing nodes
166
+ const crossingNodesToFill = this.findCrossingNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData);
167
+ // get expired market nodes
168
+ const marketNodesToFill = this.findMarketNodesToFill(marketIndex, slot, marketType);
169
+ return crossingNodesToFill.concat(marketNodesToFill);
170
+ }
171
+ findCrossingNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData) {
172
+ const nodesToFill = new Array();
173
+ const askGenerator = this.getAsks(marketIndex, vAsk, slot, marketType, oraclePriceData);
174
+ const bidGenerator = this.getBids(marketIndex, vBid, slot, marketType, oraclePriceData);
175
+ let nextAsk = askGenerator.next();
176
+ let nextBid = bidGenerator.next();
177
+ // First try to find orders that cross
178
+ while (!nextAsk.done && !nextBid.done) {
179
+ const { crossingNodes, exhaustedSide } = this.findCrossingOrders(nextAsk.value, nextBid.value, oraclePriceData, slot);
180
+ if (exhaustedSide === 'bid') {
181
+ nextBid = bidGenerator.next();
182
+ }
183
+ else if (exhaustedSide === 'ask') {
184
+ nextAsk = askGenerator.next();
185
+ }
186
+ else if (exhaustedSide === 'both') {
187
+ nextBid = bidGenerator.next();
188
+ nextAsk = askGenerator.next();
189
+ }
190
+ else {
191
+ console.error(`invalid exhaustedSide: ${exhaustedSide}`);
192
+ break;
193
+ }
194
+ const takerIsMaker = (crossingNodes === null || crossingNodes === void 0 ? void 0 : crossingNodes.makerNode) !== undefined &&
195
+ crossingNodes.node.userAccount.equals(crossingNodes.makerNode.userAccount);
196
+ // Verify that each side is different user
197
+ if (crossingNodes && !takerIsMaker) {
198
+ nodesToFill.push(crossingNodes);
199
+ }
200
+ }
201
+ return nodesToFill;
202
+ }
203
+ findMarketNodesToFill(marketIndex, slot, marketType) {
204
+ const nodesToFill = new Array();
205
+ // Then see if there are orders to fill against vamm
206
+ for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
207
+ if ((0, __1.isAuctionComplete)(marketBid.order, slot) ||
208
+ (0, __1.isOrderExpired)(marketBid.order, slot)) {
209
+ nodesToFill.push({
210
+ node: marketBid,
211
+ });
212
+ }
213
+ }
214
+ for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
215
+ if ((0, __1.isAuctionComplete)(marketAsk.order, slot) ||
216
+ (0, __1.isOrderExpired)(marketAsk.order, slot)) {
217
+ nodesToFill.push({
218
+ node: marketAsk,
219
+ });
220
+ }
221
+ }
222
+ return nodesToFill;
223
+ }
224
+ findJitAuctionNodesToFill(marketIndex, slot, marketType) {
225
+ const nodesToFill = new Array();
226
+ // Then see if there are orders still in JIT auction
227
+ for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
228
+ if (!(0, __1.isAuctionComplete)(marketBid.order, slot)) {
229
+ nodesToFill.push({
230
+ node: marketBid,
231
+ });
232
+ }
233
+ }
234
+ for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
235
+ if (!(0, __1.isAuctionComplete)(marketAsk.order, slot)) {
236
+ nodesToFill.push({
237
+ node: marketAsk,
238
+ });
239
+ }
240
+ }
241
+ return nodesToFill;
242
+ }
243
+ getMarketBids(marketIndex, marketType) {
244
+ const marketTypeStr = (0, __1.getVariant)(marketType);
245
+ return this.orderLists
246
+ .get(marketTypeStr)
247
+ .get(marketIndex.toNumber())
248
+ .market.bid.getGenerator();
249
+ }
250
+ getMarketAsks(marketIndex, marketType) {
251
+ const marketTypeStr = (0, __1.getVariant)(marketType);
252
+ return this.orderLists
253
+ .get(marketTypeStr)
254
+ .get(marketIndex.toNumber())
255
+ .market.ask.getGenerator();
256
+ }
257
+ *getAsks(marketIndex, vAsk, slot, marketType, oraclePriceData) {
258
+ if ((0, __1.isVariant)(marketType, 'spot') && !oraclePriceData) {
259
+ throw new Error('Must provide OraclePriceData to get spot asks');
260
+ }
261
+ const marketTypeStr = (0, __1.getVariant)(marketType);
262
+ const nodeLists = this.orderLists
263
+ .get(marketTypeStr)
264
+ .get(marketIndex.toNumber());
265
+ const generatorList = [
266
+ nodeLists.limit.ask.getGenerator(),
267
+ nodeLists.floatingLimit.ask.getGenerator(),
268
+ nodeLists.market.ask.getGenerator(),
269
+ ];
270
+ if (marketTypeStr === 'perp') {
271
+ generatorList.push((0, NodeList_1.getVammNodeGenerator)(vAsk));
272
+ }
273
+ const askGenerators = generatorList.map((generator) => {
274
+ return {
275
+ next: generator.next(),
276
+ generator,
277
+ };
278
+ });
279
+ let asksExhausted = false;
280
+ while (!asksExhausted) {
281
+ const bestGenerator = askGenerators.reduce((bestGenerator, currentGenerator) => {
282
+ if (currentGenerator.next.done) {
283
+ return bestGenerator;
284
+ }
285
+ if (bestGenerator.next.done) {
286
+ return currentGenerator;
287
+ }
288
+ const bestAskPrice = bestGenerator.next.value.getPrice(oraclePriceData, slot);
289
+ const currentAskPrice = currentGenerator.next.value.getPrice(oraclePriceData, slot);
290
+ return bestAskPrice.lt(currentAskPrice)
291
+ ? bestGenerator
292
+ : currentGenerator;
293
+ });
294
+ if (!bestGenerator.next.done) {
295
+ yield bestGenerator.next.value;
296
+ bestGenerator.next = bestGenerator.generator.next();
297
+ }
298
+ else {
299
+ asksExhausted = true;
300
+ }
301
+ }
302
+ }
303
+ *getBids(marketIndex, vBid, slot, marketType, oraclePriceData) {
304
+ if ((0, __1.isVariant)(marketType, 'spot') && !oraclePriceData) {
305
+ throw new Error('Must provide OraclePriceData to get spot bids');
306
+ }
307
+ const marketTypeStr = (0, __1.getVariant)(marketType);
308
+ const nodeLists = this.orderLists
309
+ .get(marketTypeStr)
310
+ .get(marketIndex.toNumber());
311
+ const generatorList = [
312
+ nodeLists.limit.bid.getGenerator(),
313
+ nodeLists.floatingLimit.bid.getGenerator(),
314
+ nodeLists.market.bid.getGenerator(),
315
+ ];
316
+ if (marketTypeStr === 'perp') {
317
+ generatorList.push((0, NodeList_1.getVammNodeGenerator)(vBid));
318
+ }
319
+ const bidGenerators = generatorList.map((generator) => {
320
+ return {
321
+ next: generator.next(),
322
+ generator,
323
+ };
324
+ });
325
+ let bidsExhausted = false; // there will always be the vBid
326
+ while (!bidsExhausted) {
327
+ const bestGenerator = bidGenerators.reduce((bestGenerator, currentGenerator) => {
328
+ if (currentGenerator.next.done) {
329
+ return bestGenerator;
330
+ }
331
+ if (bestGenerator.next.done) {
332
+ return currentGenerator;
333
+ }
334
+ const bestBidPrice = bestGenerator.next.value.getPrice(oraclePriceData, slot);
335
+ const currentBidPrice = currentGenerator.next.value.getPrice(oraclePriceData, slot);
336
+ return bestBidPrice.gt(currentBidPrice)
337
+ ? bestGenerator
338
+ : currentGenerator;
339
+ });
340
+ if (!bestGenerator.next.done) {
341
+ yield bestGenerator.next.value;
342
+ bestGenerator.next = bestGenerator.generator.next();
343
+ }
344
+ else {
345
+ bidsExhausted = true;
346
+ }
347
+ }
348
+ }
349
+ findCrossingOrders(askNode, bidNode, oraclePriceData, slot) {
350
+ const bidPrice = bidNode.getPrice(oraclePriceData, slot);
351
+ const askPrice = askNode.getPrice(oraclePriceData, slot);
352
+ const bidOrder = bidNode.order;
353
+ const askOrder = askNode.order;
354
+ const containsMarketOrder = (askOrder && (0, __1.isVariant)(askOrder.orderType, 'market')) ||
355
+ (bidOrder && (0, __1.isVariant)(bidOrder.orderType, 'market'));
356
+ if (!containsMarketOrder || bidPrice.lt(askPrice)) {
357
+ // no market orders, and no crossing orders - return
358
+ if (askNode.isVammNode() && !bidNode.isVammNode()) {
359
+ return {
360
+ exhaustedSide: 'bid',
361
+ };
362
+ }
363
+ else if (!askNode.isVammNode() && bidNode.isVammNode()) {
364
+ return {
365
+ exhaustedSide: 'ask',
366
+ };
367
+ }
368
+ else {
369
+ return {
370
+ exhaustedSide: 'both',
371
+ };
372
+ }
373
+ }
374
+ // User bid crosses the vamm ask
375
+ if (askNode.isVammNode()) {
376
+ if (!(0, __1.isAuctionComplete)(bidOrder, slot)) {
377
+ return {
378
+ exhaustedSide: 'bid',
379
+ };
380
+ }
381
+ return {
382
+ crossingNodes: {
383
+ node: bidNode,
384
+ },
385
+ exhaustedSide: 'bid',
386
+ };
387
+ }
388
+ // User ask crosses the vamm bid
389
+ if (bidNode.isVammNode()) {
390
+ if (!(0, __1.isAuctionComplete)(askOrder, slot)) {
391
+ return {
392
+ exhaustedSide: 'ask',
393
+ };
394
+ }
395
+ return {
396
+ crossingNodes: {
397
+ node: askNode,
398
+ },
399
+ exhaustedSide: 'ask',
400
+ };
401
+ }
402
+ const bidBaseRemaining = bidOrder.baseAssetAmount.sub(bidOrder.baseAssetAmountFilled);
403
+ const askBaseRemaining = askOrder.baseAssetAmount.sub(askOrder.baseAssetAmountFilled);
404
+ let exhaustedSide;
405
+ if (bidBaseRemaining.eq(askBaseRemaining)) {
406
+ exhaustedSide = 'both';
407
+ }
408
+ else if (bidBaseRemaining.gt(askBaseRemaining)) {
409
+ exhaustedSide = 'ask';
410
+ }
411
+ else {
412
+ exhaustedSide = 'bid';
413
+ }
414
+ // Can't match two maker orders
415
+ if (bidOrder.postOnly && askOrder.postOnly) {
416
+ return {
417
+ exhaustedSide,
418
+ };
419
+ }
420
+ // update the orders as if they fill - so we don't try to match them on the next iteration
421
+ if (exhaustedSide === 'ask') {
422
+ bidNode.order.baseAssetAmountFilled =
423
+ bidOrder.baseAssetAmountFilled.add(askBaseRemaining);
424
+ askNode.order.baseAssetAmountFilled = askOrder.baseAssetAmount;
425
+ }
426
+ else if (exhaustedSide === 'bid') {
427
+ askNode.order.baseAssetAmountFilled =
428
+ askOrder.baseAssetAmountFilled.add(bidBaseRemaining);
429
+ bidNode.order.baseAssetAmountFilled = bidOrder.baseAssetAmount;
430
+ }
431
+ else {
432
+ askNode.order.baseAssetAmountFilled = askOrder.baseAssetAmount;
433
+ bidNode.order.baseAssetAmountFilled = bidOrder.baseAssetAmount;
434
+ }
435
+ // Bid is maker
436
+ if (bidOrder.postOnly) {
437
+ return {
438
+ crossingNodes: {
439
+ node: askNode,
440
+ makerNode: bidNode,
441
+ },
442
+ exhaustedSide,
443
+ };
444
+ }
445
+ // Ask is maker
446
+ if (askOrder.postOnly) {
447
+ return {
448
+ crossingNodes: {
449
+ node: bidNode,
450
+ makerNode: askNode,
451
+ },
452
+ exhaustedSide,
453
+ };
454
+ }
455
+ // Both are takers
456
+ // older order is maker
457
+ const [olderNode, newerNode] = askOrder.ts.lt(bidOrder.ts)
458
+ ? [askNode, bidNode]
459
+ : [bidNode, askNode];
460
+ return {
461
+ crossingNodes: {
462
+ node: newerNode,
463
+ makerNode: olderNode,
464
+ },
465
+ exhaustedSide,
466
+ };
467
+ }
468
+ getBestAsk(marketIndex, vAsk, slot, marketType, oraclePriceData) {
469
+ return this.getAsks(marketIndex, vAsk, slot, marketType, oraclePriceData)
470
+ .next()
471
+ .value.getPrice(oraclePriceData, slot);
472
+ }
473
+ getBestBid(marketIndex, vBid, slot, marketType, oraclePriceData) {
474
+ return this.getBids(marketIndex, vBid, slot, marketType, oraclePriceData)
475
+ .next()
476
+ .value.getPrice(oraclePriceData, slot);
477
+ }
478
+ findNodesToTrigger(marketIndex, slot, oraclePrice, marketType) {
479
+ const nodesToTrigger = [];
480
+ const marketTypeStr = (0, __1.getVariant)(marketType);
481
+ for (const node of this.orderLists
482
+ .get(marketTypeStr)
483
+ .get(marketIndex.toNumber())
484
+ .trigger.above.getGenerator()) {
485
+ if (oraclePrice.gt(node.order.triggerPrice)) {
486
+ if ((0, __1.isAuctionComplete)(node.order, slot)) {
487
+ nodesToTrigger.push({
488
+ node: node,
489
+ });
490
+ }
491
+ }
492
+ else {
493
+ break;
494
+ }
495
+ }
496
+ for (const node of this.orderLists
497
+ .get(marketTypeStr)
498
+ .get(marketIndex.toNumber())
499
+ .trigger.below.getGenerator()) {
500
+ if (oraclePrice.lt(node.order.triggerPrice)) {
501
+ if ((0, __1.isAuctionComplete)(node.order, slot)) {
502
+ nodesToTrigger.push({
503
+ node: node,
504
+ });
505
+ }
506
+ }
507
+ else {
508
+ break;
509
+ }
510
+ }
511
+ return nodesToTrigger;
512
+ }
513
+ printTopOfOrderLists(sdkConfig, clearingHouse, slotSubscriber, marketIndex, marketType) {
514
+ if ((0, __1.isVariant)(marketType, 'perp')) {
515
+ const market = clearingHouse.getPerpMarketAccount(marketIndex);
516
+ const slot = slotSubscriber.getSlot();
517
+ const oraclePriceData = clearingHouse.getOracleDataForMarket(marketIndex);
518
+ const vAsk = (0, __1.calculateAskPrice)(market, oraclePriceData);
519
+ const vBid = (0, __1.calculateBidPrice)(market, oraclePriceData);
520
+ const bestAsk = this.getBestAsk(marketIndex, vAsk, slot, marketType, oraclePriceData);
521
+ const bestBid = this.getBestBid(marketIndex, vBid, slot, marketType, oraclePriceData);
522
+ const mid = bestAsk.add(bestBid).div(new __1.BN(2));
523
+ const bidSpread = ((0, __1.convertToNumber)(bestBid, __1.MARK_PRICE_PRECISION) /
524
+ (0, __1.convertToNumber)(oraclePriceData.price, __1.MARK_PRICE_PRECISION) -
525
+ 1) *
526
+ 100.0;
527
+ const askSpread = ((0, __1.convertToNumber)(bestAsk, __1.MARK_PRICE_PRECISION) /
528
+ (0, __1.convertToNumber)(oraclePriceData.price, __1.MARK_PRICE_PRECISION) -
529
+ 1) *
530
+ 100.0;
531
+ console.log(`Market ${sdkConfig.MARKETS[marketIndex.toNumber()].symbol} Orders`);
532
+ console.log(` Ask`, (0, __1.convertToNumber)(bestAsk, __1.MARK_PRICE_PRECISION).toFixed(3), `(${askSpread.toFixed(4)}%)`);
533
+ console.log(` Mid`, (0, __1.convertToNumber)(mid, __1.MARK_PRICE_PRECISION).toFixed(3));
534
+ console.log(` Bid`, (0, __1.convertToNumber)(bestBid, __1.MARK_PRICE_PRECISION).toFixed(3), `(${bidSpread.toFixed(4)}%)`);
535
+ }
536
+ else if ((0, __1.isVariant)(marketType, 'spot')) {
537
+ const slot = slotSubscriber.getSlot();
538
+ const oraclePriceData = clearingHouse.getOracleDataForMarket(marketIndex);
539
+ const bestAsk = this.getBestAsk(marketIndex, undefined, slot, marketType, oraclePriceData);
540
+ const bestBid = this.getBestBid(marketIndex, undefined, slot, marketType, oraclePriceData);
541
+ const mid = bestAsk.add(bestBid).div(new __1.BN(2));
542
+ const bidSpread = ((0, __1.convertToNumber)(bestBid, __1.MARK_PRICE_PRECISION) /
543
+ (0, __1.convertToNumber)(oraclePriceData.price, __1.MARK_PRICE_PRECISION) -
544
+ 1) *
545
+ 100.0;
546
+ const askSpread = ((0, __1.convertToNumber)(bestAsk, __1.MARK_PRICE_PRECISION) /
547
+ (0, __1.convertToNumber)(oraclePriceData.price, __1.MARK_PRICE_PRECISION) -
548
+ 1) *
549
+ 100.0;
550
+ console.log(`Market ${sdkConfig.MARKETS[marketIndex.toNumber()].symbol} Orders`);
551
+ console.log(` Ask`, (0, __1.convertToNumber)(bestAsk, __1.MARK_PRICE_PRECISION).toFixed(3), `(${askSpread.toFixed(4)}%)`);
552
+ console.log(` Mid`, (0, __1.convertToNumber)(mid, __1.MARK_PRICE_PRECISION).toFixed(3));
553
+ console.log(` Bid`, (0, __1.convertToNumber)(bestBid, __1.MARK_PRICE_PRECISION).toFixed(3), `(${bidSpread.toFixed(4)}%)`);
554
+ }
555
+ }
556
+ }
557
+ exports.DLOB = DLOB;
@@ -0,0 +1,52 @@
1
+ /// <reference types="bn.js" />
2
+ import { BN, SpotMarketAccount, PerpMarketAccount, OraclePriceData, Order } from '..';
3
+ import { PublicKey } from '@solana/web3.js';
4
+ export interface DLOBNode {
5
+ getPrice(oraclePriceData: OraclePriceData, slot: number): BN;
6
+ isVammNode(): boolean;
7
+ order: Order | undefined;
8
+ haveFilled: boolean;
9
+ userAccount: PublicKey | undefined;
10
+ market: SpotMarketAccount | PerpMarketAccount;
11
+ }
12
+ export declare abstract class OrderNode implements DLOBNode {
13
+ order: Order;
14
+ market: SpotMarketAccount | PerpMarketAccount;
15
+ userAccount: PublicKey;
16
+ sortValue: BN;
17
+ haveFilled: boolean;
18
+ haveTrigger: boolean;
19
+ constructor(order: Order, market: SpotMarketAccount | PerpMarketAccount, userAccount: PublicKey);
20
+ abstract getSortValue(order: Order): BN;
21
+ getLabel(): string;
22
+ getPrice(oraclePriceData: OraclePriceData, slot: number): BN;
23
+ isVammNode(): boolean;
24
+ }
25
+ export declare class LimitOrderNode extends OrderNode {
26
+ next?: LimitOrderNode;
27
+ previous?: LimitOrderNode;
28
+ getSortValue(order: Order): BN;
29
+ }
30
+ export declare class FloatingLimitOrderNode extends OrderNode {
31
+ next?: FloatingLimitOrderNode;
32
+ previous?: FloatingLimitOrderNode;
33
+ getSortValue(order: Order): BN;
34
+ }
35
+ export declare class MarketOrderNode extends OrderNode {
36
+ next?: MarketOrderNode;
37
+ previous?: MarketOrderNode;
38
+ getSortValue(order: Order): BN;
39
+ }
40
+ export declare class TriggerOrderNode extends OrderNode {
41
+ next?: TriggerOrderNode;
42
+ previous?: TriggerOrderNode;
43
+ getSortValue(order: Order): BN;
44
+ }
45
+ export declare type DLOBNodeMap = {
46
+ limit: LimitOrderNode;
47
+ floatingLimit: FloatingLimitOrderNode;
48
+ market: MarketOrderNode;
49
+ trigger: TriggerOrderNode;
50
+ };
51
+ export declare type DLOBNodeType = 'limit' | 'floatingLimit' | 'market' | ('trigger' & keyof DLOBNodeMap);
52
+ export declare function createNode<T extends DLOBNodeType>(nodeType: T, order: Order, market: SpotMarketAccount | PerpMarketAccount, userAccount: PublicKey): DLOBNodeMap[T];
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createNode = exports.TriggerOrderNode = exports.MarketOrderNode = exports.FloatingLimitOrderNode = exports.LimitOrderNode = exports.OrderNode = void 0;
4
+ const __1 = require("..");
5
+ const NodeList_1 = require("./NodeList");
6
+ class OrderNode {
7
+ constructor(order, market, userAccount) {
8
+ this.haveFilled = false;
9
+ this.haveTrigger = false;
10
+ this.order = order;
11
+ this.market = market;
12
+ this.userAccount = userAccount;
13
+ this.sortValue = this.getSortValue(order);
14
+ }
15
+ getLabel() {
16
+ let msg = `Order ${(0, NodeList_1.getOrderSignature)(this.order.orderId, this.userAccount)}`;
17
+ msg += ` ${(0, __1.isVariant)(this.order.direction, 'long') ? 'LONG' : 'SHORT'} `;
18
+ msg += `${(0, __1.convertToNumber)(this.order.baseAssetAmount, __1.AMM_RESERVE_PRECISION).toFixed(3)}`;
19
+ if (this.order.price.gt(__1.ZERO)) {
20
+ msg += ` @ ${(0, __1.convertToNumber)(this.order.price, __1.MARK_PRICE_PRECISION).toFixed(3)}`;
21
+ }
22
+ if (this.order.triggerPrice.gt(__1.ZERO)) {
23
+ msg += ` ${(0, __1.isVariant)(this.order.triggerCondition, 'below') ? 'BELOW' : 'ABOVE'}`;
24
+ msg += ` ${(0, __1.convertToNumber)(this.order.triggerPrice, __1.MARK_PRICE_PRECISION).toFixed(3)}`;
25
+ }
26
+ return msg;
27
+ }
28
+ getPrice(oraclePriceData, slot) {
29
+ if ((0, __1.isOneOfVariant)(this.order.marketType, ['spot'])) {
30
+ return (0, __1.getLimitPrice)(this.order, oraclePriceData, slot);
31
+ }
32
+ else if ((0, __1.isOneOfVariant)(this.order.marketType, ['perp'])) {
33
+ return (0, __1.getLimitPrice)(this.order, oraclePriceData, slot, this.market);
34
+ }
35
+ else {
36
+ console.error(`Unknown market type: ${this.order.marketType}`);
37
+ }
38
+ }
39
+ isVammNode() {
40
+ return false;
41
+ }
42
+ }
43
+ exports.OrderNode = OrderNode;
44
+ class LimitOrderNode extends OrderNode {
45
+ getSortValue(order) {
46
+ return order.price;
47
+ }
48
+ }
49
+ exports.LimitOrderNode = LimitOrderNode;
50
+ class FloatingLimitOrderNode extends OrderNode {
51
+ getSortValue(order) {
52
+ return order.oraclePriceOffset;
53
+ }
54
+ }
55
+ exports.FloatingLimitOrderNode = FloatingLimitOrderNode;
56
+ class MarketOrderNode extends OrderNode {
57
+ getSortValue(order) {
58
+ return order.slot;
59
+ }
60
+ }
61
+ exports.MarketOrderNode = MarketOrderNode;
62
+ class TriggerOrderNode extends OrderNode {
63
+ getSortValue(order) {
64
+ return order.triggerPrice;
65
+ }
66
+ }
67
+ exports.TriggerOrderNode = TriggerOrderNode;
68
+ function createNode(nodeType, order, market, userAccount) {
69
+ switch (nodeType) {
70
+ case 'floatingLimit':
71
+ return new FloatingLimitOrderNode(order, market, userAccount);
72
+ case 'limit':
73
+ return new LimitOrderNode(order, market, userAccount);
74
+ case 'market':
75
+ return new MarketOrderNode(order, market, userAccount);
76
+ case 'trigger':
77
+ return new TriggerOrderNode(order, market, userAccount);
78
+ default:
79
+ throw Error(`Unknown DLOBNode type ${nodeType}`);
80
+ }
81
+ }
82
+ exports.createNode = createNode;