@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,595 @@
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, market);
29
+ this.orderLists.get('perp').set(marketIndex, {
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, market);
51
+ this.orderLists.get('spot').set(marketIndex, {
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
+ var _a;
107
+ if ((0, __1.isVariant)(order.status, 'init')) {
108
+ return;
109
+ }
110
+ const marketType = (0, __1.getVariant)(order.marketType);
111
+ if ((0, __1.isVariant)(order.status, 'open')) {
112
+ this.openOrders
113
+ .get(marketType)
114
+ .add((0, NodeList_1.getOrderSignature)(order.orderId, userAccount));
115
+ }
116
+ (_a = this.getListForOrder(order)) === null || _a === void 0 ? void 0 : _a.insert(order, marketType, this.marketIndexToAccount.get(marketType).get(order.marketIndex), userAccount);
117
+ if (onInsert) {
118
+ onInsert();
119
+ }
120
+ }
121
+ trigger(order, userAccount, onTrigger) {
122
+ var _a;
123
+ if ((0, __1.isVariant)(order.status, 'init')) {
124
+ return;
125
+ }
126
+ const marketType = (0, __1.getVariant)(order.marketType);
127
+ const triggerList = this.orderLists.get(marketType).get(order.marketIndex)
128
+ .trigger[(0, __1.isVariant)(order.triggerCondition, 'above') ? 'above' : 'below'];
129
+ triggerList.remove(order, userAccount);
130
+ (_a = this.getListForOrder(order)) === null || _a === void 0 ? void 0 : _a.insert(order, marketType, this.marketIndexToAccount.get(marketType).get(order.marketIndex), userAccount);
131
+ if (onTrigger) {
132
+ onTrigger();
133
+ }
134
+ }
135
+ getListForOrder(order) {
136
+ const isInactiveTriggerOrder = (0, __1.isOneOfVariant)(order.orderType, ['triggerMarket', 'triggerLimit']) &&
137
+ !order.triggered;
138
+ let type;
139
+ if (isInactiveTriggerOrder) {
140
+ type = 'trigger';
141
+ }
142
+ else if ((0, __1.isOneOfVariant)(order.orderType, ['market', 'triggerMarket'])) {
143
+ type = 'market';
144
+ }
145
+ else if (!order.oraclePriceOffset.eq(__1.ZERO)) {
146
+ type = 'floatingLimit';
147
+ }
148
+ else {
149
+ type = 'limit';
150
+ }
151
+ let subType;
152
+ if (isInactiveTriggerOrder) {
153
+ subType = (0, __1.isVariant)(order.triggerCondition, 'above') ? 'above' : 'below';
154
+ }
155
+ else {
156
+ subType = (0, __1.isVariant)(order.direction, 'long') ? 'bid' : 'ask';
157
+ }
158
+ const marketType = (0, __1.getVariant)(order.marketType);
159
+ if (!this.orderLists.has(marketType)) {
160
+ return undefined;
161
+ }
162
+ return this.orderLists.get(marketType).get(order.marketIndex)[type][subType];
163
+ }
164
+ findNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData) {
165
+ // Find all the crossing nodes
166
+ const crossingNodesToFill = this.findCrossingNodesToFill(marketIndex, slot, marketType, oraclePriceData);
167
+ const vAMMCrossingNodesToFill = this.findvAMMCrossingNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData);
168
+ // get expired market nodes
169
+ const marketNodesToFill = this.findExpiredMarketNodesToFill(marketIndex, slot, marketType);
170
+ return crossingNodesToFill.concat(vAMMCrossingNodesToFill, marketNodesToFill);
171
+ }
172
+ findCrossingNodesToFill(marketIndex, slot, marketType, oraclePriceData) {
173
+ const nodesToFill = new Array();
174
+ const askGenerator = this.getAsks(marketIndex, undefined, // dont include vask
175
+ slot, marketType, oraclePriceData);
176
+ const bidGenerator = this.getBids(marketIndex, undefined, // dont include vbid
177
+ slot, marketType, oraclePriceData);
178
+ let nextAsk = askGenerator.next();
179
+ let nextBid = bidGenerator.next();
180
+ // First try to find orders that cross
181
+ while (!nextAsk.done && !nextBid.done) {
182
+ const { crossingNodes, exhaustedSide } = this.findCrossingOrders(nextAsk.value, nextBid.value, oraclePriceData, slot);
183
+ if (exhaustedSide === 'bid') {
184
+ nextBid = bidGenerator.next();
185
+ }
186
+ else if (exhaustedSide === 'ask') {
187
+ nextAsk = askGenerator.next();
188
+ }
189
+ else if (exhaustedSide === 'both') {
190
+ nextBid = bidGenerator.next();
191
+ nextAsk = askGenerator.next();
192
+ }
193
+ else if (exhaustedSide === 'nocross') {
194
+ break;
195
+ }
196
+ else {
197
+ console.error(`invalid exhaustedSide: ${exhaustedSide}`);
198
+ break;
199
+ }
200
+ for (const crossingNode of crossingNodes) {
201
+ nodesToFill.push(crossingNode);
202
+ }
203
+ }
204
+ return nodesToFill;
205
+ }
206
+ findvAMMCrossingNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData) {
207
+ const nodesToFill = new Array();
208
+ const askGenerator = this.getAsks(marketIndex, undefined, // dont include vask
209
+ slot, marketType, oraclePriceData);
210
+ const bidGenerator = this.getBids(marketIndex, undefined, // dont include vbid
211
+ slot, marketType, oraclePriceData);
212
+ let nextAsk = askGenerator.next();
213
+ let nextBid = bidGenerator.next();
214
+ // check for asks that cross vBid
215
+ while (!nextAsk.done) {
216
+ const askNode = nextAsk.value;
217
+ const askPrice = askNode.getPrice(oraclePriceData, slot);
218
+ if (askPrice.lte(vBid) && (0, __1.isAuctionComplete)(askNode.order, slot)) {
219
+ nodesToFill.push({
220
+ node: askNode,
221
+ makerNode: undefined, // filled by vAMM
222
+ });
223
+ }
224
+ else {
225
+ break;
226
+ }
227
+ nextAsk = askGenerator.next();
228
+ }
229
+ // check for bids that cross vAsk
230
+ while (!nextBid.done) {
231
+ const bidNode = nextBid.value;
232
+ const bidPrice = bidNode.getPrice(oraclePriceData, slot);
233
+ if (bidPrice.gte(vAsk) && (0, __1.isAuctionComplete)(bidNode.order, slot)) {
234
+ nodesToFill.push({
235
+ node: bidNode,
236
+ makerNode: undefined, // filled by vAMM
237
+ });
238
+ }
239
+ else {
240
+ break;
241
+ }
242
+ nextBid = bidGenerator.next();
243
+ }
244
+ return nodesToFill;
245
+ }
246
+ findExpiredMarketNodesToFill(marketIndex, slot, marketType) {
247
+ const nodesToFill = new Array();
248
+ // Then see if there are orders to fill against vamm
249
+ for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
250
+ if ((0, __1.isOrderExpired)(marketBid.order, slot)) {
251
+ nodesToFill.push({
252
+ node: marketBid,
253
+ });
254
+ }
255
+ }
256
+ for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
257
+ if ((0, __1.isOrderExpired)(marketAsk.order, slot)) {
258
+ nodesToFill.push({
259
+ node: marketAsk,
260
+ });
261
+ }
262
+ }
263
+ return nodesToFill;
264
+ }
265
+ findJitAuctionNodesToFill(marketIndex, slot, marketType) {
266
+ const nodesToFill = new Array();
267
+ // Then see if there are orders still in JIT auction
268
+ for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
269
+ if (!(0, __1.isAuctionComplete)(marketBid.order, slot)) {
270
+ nodesToFill.push({
271
+ node: marketBid,
272
+ });
273
+ }
274
+ }
275
+ for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
276
+ if (!(0, __1.isAuctionComplete)(marketAsk.order, slot)) {
277
+ nodesToFill.push({
278
+ node: marketAsk,
279
+ });
280
+ }
281
+ }
282
+ return nodesToFill;
283
+ }
284
+ getMarketBids(marketIndex, marketType) {
285
+ const marketTypeStr = (0, __1.getVariant)(marketType);
286
+ return this.orderLists
287
+ .get(marketTypeStr)
288
+ .get(marketIndex)
289
+ .market.bid.getGenerator();
290
+ }
291
+ getMarketAsks(marketIndex, marketType) {
292
+ const marketTypeStr = (0, __1.getVariant)(marketType);
293
+ return this.orderLists
294
+ .get(marketTypeStr)
295
+ .get(marketIndex)
296
+ .market.ask.getGenerator();
297
+ }
298
+ *getAsks(marketIndex, vAsk, slot, marketType, oraclePriceData) {
299
+ if ((0, __1.isVariant)(marketType, 'spot') && !oraclePriceData) {
300
+ throw new Error('Must provide OraclePriceData to get spot asks');
301
+ }
302
+ const marketTypeStr = (0, __1.getVariant)(marketType);
303
+ const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
304
+ const generatorList = [
305
+ nodeLists.limit.ask.getGenerator(),
306
+ nodeLists.floatingLimit.ask.getGenerator(),
307
+ nodeLists.market.ask.getGenerator(),
308
+ ];
309
+ if (marketTypeStr === 'perp' && vAsk) {
310
+ generatorList.push((0, NodeList_1.getVammNodeGenerator)(vAsk));
311
+ }
312
+ const askGenerators = generatorList.map((generator) => {
313
+ return {
314
+ next: generator.next(),
315
+ generator,
316
+ };
317
+ });
318
+ let asksExhausted = false;
319
+ while (!asksExhausted) {
320
+ const bestGenerator = askGenerators.reduce((bestGenerator, currentGenerator) => {
321
+ if (currentGenerator.next.done) {
322
+ return bestGenerator;
323
+ }
324
+ if (bestGenerator.next.done) {
325
+ return currentGenerator;
326
+ }
327
+ const bestAskPrice = bestGenerator.next.value.getPrice(oraclePriceData, slot);
328
+ const currentAskPrice = currentGenerator.next.value.getPrice(oraclePriceData, slot);
329
+ return bestAskPrice.lt(currentAskPrice)
330
+ ? bestGenerator
331
+ : currentGenerator;
332
+ });
333
+ if (!bestGenerator.next.done) {
334
+ // skip this node if it's already completely filled
335
+ if (bestGenerator.next.value.isBaseFilled()) {
336
+ bestGenerator.next = bestGenerator.generator.next();
337
+ continue;
338
+ }
339
+ yield bestGenerator.next.value;
340
+ bestGenerator.next = bestGenerator.generator.next();
341
+ }
342
+ else {
343
+ asksExhausted = true;
344
+ }
345
+ }
346
+ }
347
+ *getBids(marketIndex, vBid, slot, marketType, oraclePriceData) {
348
+ if ((0, __1.isVariant)(marketType, 'spot') && !oraclePriceData) {
349
+ throw new Error('Must provide OraclePriceData to get spot bids');
350
+ }
351
+ const marketTypeStr = (0, __1.getVariant)(marketType);
352
+ const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
353
+ const generatorList = [
354
+ nodeLists.limit.bid.getGenerator(),
355
+ nodeLists.floatingLimit.bid.getGenerator(),
356
+ nodeLists.market.bid.getGenerator(),
357
+ ];
358
+ if (marketTypeStr === 'perp' && vBid) {
359
+ generatorList.push((0, NodeList_1.getVammNodeGenerator)(vBid));
360
+ }
361
+ const bidGenerators = generatorList.map((generator) => {
362
+ return {
363
+ next: generator.next(),
364
+ generator,
365
+ };
366
+ });
367
+ let bidsExhausted = false; // there will always be the vBid
368
+ while (!bidsExhausted) {
369
+ const bestGenerator = bidGenerators.reduce((bestGenerator, currentGenerator) => {
370
+ if (currentGenerator.next.done) {
371
+ return bestGenerator;
372
+ }
373
+ if (bestGenerator.next.done) {
374
+ return currentGenerator;
375
+ }
376
+ const bestBidPrice = bestGenerator.next.value.getPrice(oraclePriceData, slot);
377
+ const currentBidPrice = currentGenerator.next.value.getPrice(oraclePriceData, slot);
378
+ return bestBidPrice.gt(currentBidPrice)
379
+ ? bestGenerator
380
+ : currentGenerator;
381
+ });
382
+ if (!bestGenerator.next.done) {
383
+ // skip this node if it's already completely filled
384
+ if (bestGenerator.next.value.isBaseFilled()) {
385
+ bestGenerator.next = bestGenerator.generator.next();
386
+ continue;
387
+ }
388
+ yield bestGenerator.next.value;
389
+ bestGenerator.next = bestGenerator.generator.next();
390
+ }
391
+ else {
392
+ bidsExhausted = true;
393
+ }
394
+ }
395
+ }
396
+ findCrossingOrders(askNode, bidNode, oraclePriceData, slot) {
397
+ const bidPrice = bidNode.getPrice(oraclePriceData, slot);
398
+ const askPrice = askNode.getPrice(oraclePriceData, slot);
399
+ // orders don't cross - we're done walkin gup the book
400
+ if (bidPrice.lt(askPrice)) {
401
+ return {
402
+ crossingNodes: [],
403
+ exhaustedSide: 'nocross',
404
+ };
405
+ }
406
+ const bidOrder = bidNode.order;
407
+ const askOrder = askNode.order;
408
+ // Can't match two maker orders or if maker and taker are the same
409
+ const makerIsTaker = bidNode.userAccount.equals(askNode.userAccount);
410
+ if (makerIsTaker || (bidOrder.postOnly && askOrder.postOnly)) {
411
+ // don't have a principle way to pick which one to exhaust,
412
+ // exhaust each one 50% of the time so we can try each one against other orders
413
+ const exhaustedSide = Math.random() < 0.5 ? 'bid' : 'ask';
414
+ return {
415
+ crossingNodes: [],
416
+ exhaustedSide,
417
+ };
418
+ }
419
+ const bidBaseRemaining = bidOrder.baseAssetAmount.sub(bidOrder.baseAssetAmountFilled);
420
+ const askBaseRemaining = askOrder.baseAssetAmount.sub(askOrder.baseAssetAmountFilled);
421
+ let exhaustedSide;
422
+ if (bidBaseRemaining.eq(askBaseRemaining)) {
423
+ exhaustedSide = 'both';
424
+ }
425
+ else if (bidBaseRemaining.gt(askBaseRemaining)) {
426
+ exhaustedSide = 'ask';
427
+ }
428
+ else {
429
+ exhaustedSide = 'bid';
430
+ }
431
+ // update the orders on DLOB as if they were fill - so we don't try to match them on the next iteration
432
+ // NOTE: if something goes wrong during the actual fill (transaction fails, i.e. due to orders already being filled)
433
+ // then we risk having a mismatch between this local DLOB and the actual DLOB state on the blockchain. This isn't
434
+ // a problem in the current implementation because we construct a new DLOB from the blockchain state every time, rather
435
+ // than updating the existing DLOB based on events.
436
+ if (exhaustedSide === 'ask') {
437
+ // bid partially filled
438
+ const newBidOrder = { ...bidOrder };
439
+ newBidOrder.baseAssetAmountFilled =
440
+ bidOrder.baseAssetAmountFilled.add(askBaseRemaining);
441
+ this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
442
+ // ask completely filled
443
+ const newAskOrder = { ...askOrder };
444
+ newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
445
+ this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
446
+ }
447
+ else if (exhaustedSide === 'bid') {
448
+ // ask partially filled
449
+ const newAskOrder = { ...askOrder };
450
+ newAskOrder.baseAssetAmountFilled =
451
+ askOrder.baseAssetAmountFilled.add(bidBaseRemaining);
452
+ this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
453
+ // bid completely filled
454
+ const newBidOrder = { ...bidOrder };
455
+ newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
456
+ this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
457
+ }
458
+ else {
459
+ // both completely filled
460
+ const newBidOrder = { ...bidOrder };
461
+ newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
462
+ this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
463
+ const newAskOrder = { ...askOrder };
464
+ newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
465
+ this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
466
+ }
467
+ // Bid is maker
468
+ if (bidOrder.postOnly) {
469
+ return {
470
+ crossingNodes: [
471
+ {
472
+ node: askNode,
473
+ makerNode: bidNode,
474
+ },
475
+ ],
476
+ exhaustedSide,
477
+ };
478
+ }
479
+ // Ask is maker
480
+ if (askOrder.postOnly) {
481
+ return {
482
+ crossingNodes: [
483
+ {
484
+ node: bidNode,
485
+ makerNode: askNode,
486
+ },
487
+ ],
488
+ exhaustedSide,
489
+ };
490
+ }
491
+ // Both are takers
492
+ // older order is maker
493
+ const [olderNode, newerNode] = askOrder.ts.lt(bidOrder.ts)
494
+ ? [askNode, bidNode]
495
+ : [bidNode, askNode];
496
+ return {
497
+ crossingNodes: [
498
+ {
499
+ node: newerNode,
500
+ makerNode: olderNode,
501
+ },
502
+ ],
503
+ exhaustedSide,
504
+ };
505
+ }
506
+ getBestAsk(marketIndex, vAsk, slot, marketType, oraclePriceData) {
507
+ return this.getAsks(marketIndex, vAsk, slot, marketType, oraclePriceData)
508
+ .next()
509
+ .value.getPrice(oraclePriceData, slot);
510
+ }
511
+ getBestBid(marketIndex, vBid, slot, marketType, oraclePriceData) {
512
+ return this.getBids(marketIndex, vBid, slot, marketType, oraclePriceData)
513
+ .next()
514
+ .value.getPrice(oraclePriceData, slot);
515
+ }
516
+ findNodesToTrigger(marketIndex, slot, oraclePrice, marketType) {
517
+ const nodesToTrigger = [];
518
+ const marketTypeStr = (0, __1.getVariant)(marketType);
519
+ for (const node of this.orderLists
520
+ .get(marketTypeStr)
521
+ .get(marketIndex)
522
+ .trigger.above.getGenerator()) {
523
+ if (oraclePrice.gt(node.order.triggerPrice)) {
524
+ if ((0, __1.isAuctionComplete)(node.order, slot)) {
525
+ nodesToTrigger.push({
526
+ node: node,
527
+ });
528
+ }
529
+ }
530
+ else {
531
+ break;
532
+ }
533
+ }
534
+ for (const node of this.orderLists
535
+ .get(marketTypeStr)
536
+ .get(marketIndex)
537
+ .trigger.below.getGenerator()) {
538
+ if (oraclePrice.lt(node.order.triggerPrice)) {
539
+ if ((0, __1.isAuctionComplete)(node.order, slot)) {
540
+ nodesToTrigger.push({
541
+ node: node,
542
+ });
543
+ }
544
+ }
545
+ else {
546
+ break;
547
+ }
548
+ }
549
+ return nodesToTrigger;
550
+ }
551
+ printTopOfOrderLists(sdkConfig, clearingHouse, slotSubscriber, marketIndex, marketType) {
552
+ if ((0, __1.isVariant)(marketType, 'perp')) {
553
+ const market = clearingHouse.getPerpMarketAccount(marketIndex);
554
+ const slot = slotSubscriber.getSlot();
555
+ const oraclePriceData = clearingHouse.getOracleDataForMarket(marketIndex);
556
+ const vAsk = (0, __1.calculateAskPrice)(market, oraclePriceData);
557
+ const vBid = (0, __1.calculateBidPrice)(market, oraclePriceData);
558
+ const bestAsk = this.getBestAsk(marketIndex, vAsk, slot, marketType, oraclePriceData);
559
+ const bestBid = this.getBestBid(marketIndex, vBid, slot, marketType, oraclePriceData);
560
+ const mid = bestAsk.add(bestBid).div(new __1.BN(2));
561
+ const bidSpread = ((0, __1.convertToNumber)(bestBid, __1.PRICE_PRECISION) /
562
+ (0, __1.convertToNumber)(oraclePriceData.price, __1.PRICE_PRECISION) -
563
+ 1) *
564
+ 100.0;
565
+ const askSpread = ((0, __1.convertToNumber)(bestAsk, __1.PRICE_PRECISION) /
566
+ (0, __1.convertToNumber)(oraclePriceData.price, __1.PRICE_PRECISION) -
567
+ 1) *
568
+ 100.0;
569
+ console.log(`Market ${sdkConfig.MARKETS[marketIndex].symbol} Orders`);
570
+ console.log(` Ask`, (0, __1.convertToNumber)(bestAsk, __1.PRICE_PRECISION).toFixed(3), `(${askSpread.toFixed(4)}%)`);
571
+ console.log(` Mid`, (0, __1.convertToNumber)(mid, __1.PRICE_PRECISION).toFixed(3));
572
+ console.log(` Bid`, (0, __1.convertToNumber)(bestBid, __1.PRICE_PRECISION).toFixed(3), `(${bidSpread.toFixed(4)}%)`);
573
+ }
574
+ else if ((0, __1.isVariant)(marketType, 'spot')) {
575
+ const slot = slotSubscriber.getSlot();
576
+ const oraclePriceData = clearingHouse.getOracleDataForMarket(marketIndex);
577
+ const bestAsk = this.getBestAsk(marketIndex, undefined, slot, marketType, oraclePriceData);
578
+ const bestBid = this.getBestBid(marketIndex, undefined, slot, marketType, oraclePriceData);
579
+ const mid = bestAsk.add(bestBid).div(new __1.BN(2));
580
+ const bidSpread = ((0, __1.convertToNumber)(bestBid, __1.PRICE_PRECISION) /
581
+ (0, __1.convertToNumber)(oraclePriceData.price, __1.PRICE_PRECISION) -
582
+ 1) *
583
+ 100.0;
584
+ const askSpread = ((0, __1.convertToNumber)(bestAsk, __1.PRICE_PRECISION) /
585
+ (0, __1.convertToNumber)(oraclePriceData.price, __1.PRICE_PRECISION) -
586
+ 1) *
587
+ 100.0;
588
+ console.log(`Market ${sdkConfig.MARKETS[marketIndex].symbol} Orders`);
589
+ console.log(` Ask`, (0, __1.convertToNumber)(bestAsk, __1.PRICE_PRECISION).toFixed(3), `(${askSpread.toFixed(4)}%)`);
590
+ console.log(` Mid`, (0, __1.convertToNumber)(mid, __1.PRICE_PRECISION).toFixed(3));
591
+ console.log(` Bid`, (0, __1.convertToNumber)(bestBid, __1.PRICE_PRECISION).toFixed(3), `(${bidSpread.toFixed(4)}%)`);
592
+ }
593
+ }
594
+ }
595
+ exports.DLOB = DLOB;
@@ -0,0 +1,54 @@
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
+ isBaseFilled(): boolean;
9
+ haveFilled: boolean;
10
+ userAccount: PublicKey | undefined;
11
+ market: SpotMarketAccount | PerpMarketAccount;
12
+ }
13
+ export declare abstract class OrderNode implements DLOBNode {
14
+ order: Order;
15
+ market: SpotMarketAccount | PerpMarketAccount;
16
+ userAccount: PublicKey;
17
+ sortValue: BN;
18
+ haveFilled: boolean;
19
+ haveTrigger: boolean;
20
+ constructor(order: Order, market: SpotMarketAccount | PerpMarketAccount, userAccount: PublicKey);
21
+ abstract getSortValue(order: Order): BN;
22
+ getLabel(): string;
23
+ getPrice(oraclePriceData: OraclePriceData, slot: number): BN;
24
+ isBaseFilled(): boolean;
25
+ isVammNode(): boolean;
26
+ }
27
+ export declare class LimitOrderNode extends OrderNode {
28
+ next?: LimitOrderNode;
29
+ previous?: LimitOrderNode;
30
+ getSortValue(order: Order): BN;
31
+ }
32
+ export declare class FloatingLimitOrderNode extends OrderNode {
33
+ next?: FloatingLimitOrderNode;
34
+ previous?: FloatingLimitOrderNode;
35
+ getSortValue(order: Order): BN;
36
+ }
37
+ export declare class MarketOrderNode extends OrderNode {
38
+ next?: MarketOrderNode;
39
+ previous?: MarketOrderNode;
40
+ getSortValue(order: Order): BN;
41
+ }
42
+ export declare class TriggerOrderNode extends OrderNode {
43
+ next?: TriggerOrderNode;
44
+ previous?: TriggerOrderNode;
45
+ getSortValue(order: Order): BN;
46
+ }
47
+ export declare type DLOBNodeMap = {
48
+ limit: LimitOrderNode;
49
+ floatingLimit: FloatingLimitOrderNode;
50
+ market: MarketOrderNode;
51
+ trigger: TriggerOrderNode;
52
+ };
53
+ export declare type DLOBNodeType = 'limit' | 'floatingLimit' | 'market' | ('trigger' & keyof DLOBNodeMap);
54
+ export declare function createNode<T extends DLOBNodeType>(nodeType: T, order: Order, market: SpotMarketAccount | PerpMarketAccount, userAccount: PublicKey): DLOBNodeMap[T];