@drift-labs/sdk 0.2.0-temp.2 → 2.0.1

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