@drift-labs/sdk 0.2.0-master.4 → 0.2.0-master.40

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 (234) hide show
  1. package/README.md +27 -27
  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/pollingUserStatsAccountSubscriber.d.ts +27 -0
  15. package/lib/accounts/pollingUserStatsAccountSubscriber.js +113 -0
  16. package/lib/accounts/types.d.ts +26 -15
  17. package/lib/accounts/webSocketDriftClientAccountSubscriber.d.ts +49 -0
  18. package/lib/accounts/{webSocketClearingHouseAccountSubscriber.js → webSocketDriftClientAccountSubscriber.js} +47 -45
  19. package/lib/accounts/webSocketUserStatsAccountSubsriber.d.ts +20 -0
  20. package/lib/accounts/webSocketUserStatsAccountSubsriber.js +47 -0
  21. package/lib/addresses/marketAddresses.d.ts +1 -3
  22. package/lib/addresses/marketAddresses.js +1 -1
  23. package/lib/addresses/pda.d.ts +15 -9
  24. package/lib/addresses/pda.js +73 -35
  25. package/lib/adminClient.d.ts +65 -0
  26. package/lib/adminClient.js +637 -0
  27. package/lib/config.d.ts +9 -9
  28. package/lib/config.js +25 -21
  29. package/lib/constants/numericConstants.d.ts +30 -12
  30. package/lib/constants/numericConstants.js +41 -21
  31. package/lib/constants/perpMarkets.d.ts +18 -0
  32. package/lib/constants/{markets.js → perpMarkets.js} +7 -7
  33. package/lib/constants/spotMarkets.d.ts +19 -0
  34. package/lib/constants/spotMarkets.js +53 -0
  35. package/lib/dlob/DLOB.d.ts +82 -0
  36. package/lib/dlob/DLOB.js +696 -0
  37. package/lib/dlob/DLOBNode.d.ts +54 -0
  38. package/lib/dlob/DLOBNode.js +77 -0
  39. package/lib/dlob/NodeList.d.ts +27 -0
  40. package/lib/dlob/NodeList.js +144 -0
  41. package/lib/driftClient.d.ts +233 -0
  42. package/lib/driftClient.js +2096 -0
  43. package/lib/{clearingHouseConfig.d.ts → driftClientConfig.d.ts} +9 -9
  44. package/lib/{clearingHouseConfig.js → driftClientConfig.js} +0 -0
  45. package/lib/events/eventList.js +3 -0
  46. package/lib/events/eventSubscriber.d.ts +5 -2
  47. package/lib/events/eventSubscriber.js +25 -11
  48. package/lib/events/fetchLogs.d.ts +13 -2
  49. package/lib/events/fetchLogs.js +40 -12
  50. package/lib/events/pollingLogProvider.d.ts +2 -1
  51. package/lib/events/pollingLogProvider.js +7 -3
  52. package/lib/events/sort.js +8 -11
  53. package/lib/events/types.d.ts +9 -3
  54. package/lib/events/types.js +6 -0
  55. package/lib/events/webSocketLogProvider.js +1 -1
  56. package/lib/examples/makeTradeExample.js +30 -18
  57. package/lib/factory/bigNum.d.ts +8 -4
  58. package/lib/factory/bigNum.js +109 -19
  59. package/lib/idl/drift.json +8250 -0
  60. package/lib/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  61. package/lib/index.d.ts +30 -13
  62. package/lib/index.js +30 -13
  63. package/lib/math/amm.d.ts +9 -6
  64. package/lib/math/amm.js +91 -38
  65. package/lib/math/conversion.js +1 -1
  66. package/lib/math/exchangeStatus.d.ts +4 -0
  67. package/lib/math/exchangeStatus.js +18 -0
  68. package/lib/math/funding.d.ts +6 -6
  69. package/lib/math/funding.js +23 -21
  70. package/lib/math/insurance.d.ts +4 -0
  71. package/lib/math/insurance.js +27 -0
  72. package/lib/math/margin.d.ts +11 -0
  73. package/lib/math/margin.js +82 -0
  74. package/lib/math/market.d.ts +14 -9
  75. package/lib/math/market.js +70 -10
  76. package/lib/math/oracles.d.ts +4 -0
  77. package/lib/math/oracles.js +36 -8
  78. package/lib/math/orders.d.ts +14 -6
  79. package/lib/math/orders.js +90 -17
  80. package/lib/math/position.d.ts +27 -13
  81. package/lib/math/position.js +92 -36
  82. package/lib/math/repeg.js +17 -8
  83. package/lib/math/spotBalance.d.ts +22 -0
  84. package/lib/math/spotBalance.js +192 -0
  85. package/lib/math/spotMarket.d.ts +4 -0
  86. package/lib/math/spotMarket.js +8 -0
  87. package/lib/math/spotPosition.d.ts +6 -0
  88. package/lib/math/spotPosition.js +23 -0
  89. package/lib/math/trade.d.ts +10 -10
  90. package/lib/math/trade.js +27 -31
  91. package/lib/oracles/pythClient.js +1 -1
  92. package/lib/oracles/quoteAssetOracleClient.js +1 -1
  93. package/lib/oracles/switchboardClient.js +1 -1
  94. package/lib/orderParams.d.ts +4 -4
  95. package/lib/orderParams.js +12 -4
  96. package/lib/serum/serumFulfillmentConfigMap.d.ts +10 -0
  97. package/lib/serum/serumFulfillmentConfigMap.js +17 -0
  98. package/lib/serum/serumSubscriber.d.ts +27 -0
  99. package/lib/serum/serumSubscriber.js +56 -0
  100. package/lib/serum/types.d.ts +11 -0
  101. package/lib/{clearingHouseUserConfig.js → serum/types.js} +0 -0
  102. package/lib/slot/SlotSubscriber.d.ts +7 -0
  103. package/lib/slot/SlotSubscriber.js +3 -0
  104. package/lib/{mockUSDCFaucet.d.ts → tokenFaucet.d.ts} +8 -5
  105. package/lib/{mockUSDCFaucet.js → tokenFaucet.js} +63 -51
  106. package/lib/tx/retryTxSender.d.ts +1 -1
  107. package/lib/tx/retryTxSender.js +13 -4
  108. package/lib/tx/types.d.ts +1 -1
  109. package/lib/tx/utils.js +1 -1
  110. package/lib/types.d.ts +589 -196
  111. package/lib/types.js +108 -17
  112. package/lib/user.d.ts +226 -0
  113. package/lib/user.js +949 -0
  114. package/lib/userConfig.d.ts +14 -0
  115. package/lib/userConfig.js +2 -0
  116. package/lib/userMap/userMap.d.ts +41 -0
  117. package/lib/userMap/userMap.js +85 -0
  118. package/lib/userMap/userStatsMap.d.ts +19 -0
  119. package/lib/userMap/userStatsMap.js +68 -0
  120. package/lib/userName.d.ts +1 -0
  121. package/lib/userName.js +3 -2
  122. package/lib/userStats.d.ts +18 -0
  123. package/lib/userStats.js +49 -0
  124. package/lib/userStatsConfig.d.ts +14 -0
  125. package/lib/userStatsConfig.js +2 -0
  126. package/lib/util/computeUnits.js +1 -1
  127. package/lib/util/getTokenAddress.d.ts +2 -0
  128. package/lib/util/getTokenAddress.js +9 -0
  129. package/package.json +10 -3
  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/pollingUserStatsAccountSubscriber.ts +172 -0
  137. package/src/accounts/types.ts +35 -15
  138. package/src/accounts/{webSocketClearingHouseAccountSubscriber.ts → webSocketDriftClientAccountSubscriber.ts} +78 -73
  139. package/src/accounts/webSocketUserStatsAccountSubsriber.ts +80 -0
  140. package/src/addresses/marketAddresses.ts +3 -4
  141. package/src/addresses/pda.ts +105 -33
  142. package/src/adminClient.ts +1207 -0
  143. package/src/config.ts +37 -31
  144. package/src/constants/numericConstants.ts +58 -24
  145. package/src/constants/{markets.ts → perpMarkets.ts} +10 -10
  146. package/src/constants/spotMarkets.ts +73 -0
  147. package/src/dlob/DLOB.ts +1123 -0
  148. package/src/dlob/DLOBNode.ts +155 -0
  149. package/src/dlob/NodeList.ts +195 -0
  150. package/src/driftClient.ts +3564 -0
  151. package/src/{clearingHouseConfig.ts → driftClientConfig.ts} +9 -8
  152. package/src/events/eventList.ts +3 -0
  153. package/src/events/eventSubscriber.ts +36 -14
  154. package/src/events/fetchLogs.ts +55 -13
  155. package/src/events/pollingLogProvider.ts +11 -3
  156. package/src/events/sort.ts +11 -15
  157. package/src/events/types.ts +21 -2
  158. package/src/events/webSocketLogProvider.ts +1 -1
  159. package/src/examples/makeTradeExample.ts +44 -28
  160. package/src/factory/bigNum.ts +150 -22
  161. package/src/idl/drift.json +8250 -0
  162. package/src/idl/pyth.json +98 -2
  163. package/src/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  164. package/src/index.ts +30 -13
  165. package/src/math/amm.ts +161 -48
  166. package/src/math/conversion.ts +2 -2
  167. package/src/math/exchangeStatus.ts +31 -0
  168. package/src/math/funding.ts +41 -31
  169. package/src/math/insurance.ts +35 -0
  170. package/src/math/margin.ts +133 -0
  171. package/src/math/market.ts +143 -14
  172. package/src/math/oracles.ts +63 -9
  173. package/src/math/orders.ts +163 -26
  174. package/src/math/position.ts +136 -58
  175. package/src/math/repeg.ts +19 -9
  176. package/src/math/spotBalance.ts +319 -0
  177. package/src/math/spotMarket.ts +9 -0
  178. package/src/math/spotPosition.ts +47 -0
  179. package/src/math/trade.ts +33 -37
  180. package/src/oracles/pythClient.ts +2 -2
  181. package/src/oracles/quoteAssetOracleClient.ts +2 -2
  182. package/src/oracles/switchboardClient.ts +2 -2
  183. package/src/orderParams.ts +16 -8
  184. package/src/serum/serumFulfillmentConfigMap.ts +26 -0
  185. package/src/serum/serumSubscriber.ts +99 -0
  186. package/src/serum/types.ts +13 -0
  187. package/src/slot/SlotSubscriber.ts +11 -1
  188. package/src/{mockUSDCFaucet.ts → tokenFaucet.ts} +82 -70
  189. package/src/tx/retryTxSender.ts +16 -5
  190. package/src/tx/types.ts +2 -1
  191. package/src/tx/utils.ts +1 -1
  192. package/src/types.ts +572 -178
  193. package/src/user.ts +1582 -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/computeUnits.ts +1 -1
  201. package/src/util/getTokenAddress.ts +18 -0
  202. package/tests/bn/test.ts +46 -11
  203. package/tests/dlob/helpers.ts +611 -0
  204. package/tests/dlob/test.ts +4588 -0
  205. package/lib/accounts/webSocketClearingHouseAccountSubscriber.d.ts +0 -49
  206. package/lib/admin.d.ts +0 -44
  207. package/lib/admin.js +0 -433
  208. package/lib/clearingHouse.d.ts +0 -133
  209. package/lib/clearingHouse.js +0 -931
  210. package/lib/clearingHouseUser.d.ts +0 -187
  211. package/lib/clearingHouseUser.js +0 -643
  212. package/lib/clearingHouseUserConfig.d.ts +0 -14
  213. package/lib/constants/banks.d.ts +0 -16
  214. package/lib/constants/banks.js +0 -34
  215. package/lib/constants/markets.d.ts +0 -19
  216. package/lib/idl/clearing_house.json +0 -3998
  217. package/lib/math/bankBalance.d.ts +0 -9
  218. package/lib/math/bankBalance.js +0 -75
  219. package/lib/math/state.d.ts +0 -8
  220. package/lib/math/state.js +0 -15
  221. package/lib/orders.d.ts +0 -8
  222. package/lib/orders.js +0 -134
  223. package/src/admin.ts +0 -722
  224. package/src/clearingHouse.ts +0 -1451
  225. package/src/clearingHouseUser.ts +0 -989
  226. package/src/constants/banks.ts +0 -43
  227. package/src/idl/clearing_house.json +0 -3998
  228. package/src/math/bankBalance.ts +0 -112
  229. package/src/math/state.ts +0 -14
  230. package/src/math/utils.js +0 -27
  231. package/src/math/utils.js.map +0 -1
  232. package/src/orders.ts +0 -244
  233. package/src/util/computeUnits.js +0 -17
  234. package/src/util/computeUnits.js.map +0 -1
package/src/user.ts ADDED
@@ -0,0 +1,1582 @@
1
+ import { PublicKey } from '@solana/web3.js';
2
+ import { EventEmitter } from 'events';
3
+ import StrictEventEmitter from 'strict-event-emitter-types';
4
+ import { DriftClient } from './driftClient';
5
+ import {
6
+ isVariant,
7
+ MarginCategory,
8
+ Order,
9
+ UserAccount,
10
+ PerpPosition,
11
+ SpotPosition,
12
+ } from './types';
13
+ import { calculateEntryPrice } from './math/position';
14
+ import {
15
+ PRICE_PRECISION,
16
+ AMM_TO_QUOTE_PRECISION_RATIO,
17
+ ZERO,
18
+ TEN_THOUSAND,
19
+ BN_MAX,
20
+ QUOTE_PRECISION,
21
+ AMM_RESERVE_PRECISION,
22
+ PRICE_TO_QUOTE_PRECISION,
23
+ MARGIN_PRECISION,
24
+ SPOT_MARKET_WEIGHT_PRECISION,
25
+ QUOTE_SPOT_MARKET_INDEX,
26
+ TEN,
27
+ } from './constants/numericConstants';
28
+ import {
29
+ UserAccountSubscriber,
30
+ UserAccountEvents,
31
+ DataAndSlot,
32
+ } from './accounts/types';
33
+ import {
34
+ calculateReservePrice,
35
+ calculateBaseAssetValue,
36
+ calculatePositionFundingPNL,
37
+ calculatePositionPNL,
38
+ calculateUnrealizedAssetWeight,
39
+ calculateMarketMarginRatio,
40
+ PositionDirection,
41
+ calculateTradeSlippage,
42
+ BN,
43
+ SpotMarketAccount,
44
+ getTokenValue,
45
+ SpotBalanceType,
46
+ } from '.';
47
+ import {
48
+ getTokenAmount,
49
+ calculateAssetWeight,
50
+ calculateLiabilityWeight,
51
+ calculateWithdrawLimit,
52
+ } from './math/spotBalance';
53
+ import { calculateMarketOpenBidAsk } from './math/amm';
54
+ import {
55
+ calculateBaseAssetValueWithOracle,
56
+ calculateWorstCaseBaseAssetAmount,
57
+ } from './math/margin';
58
+ import { OraclePriceData } from './oracles/types';
59
+ import { UserConfig } from './userConfig';
60
+ import { PollingUserAccountSubscriber } from './accounts/pollingUserAccountSubscriber';
61
+ import { WebSocketUserAccountSubscriber } from './accounts/webSocketUserAccountSubscriber';
62
+ import {
63
+ getWorstCaseTokenAmounts,
64
+ isSpotPositionAvailable,
65
+ } from './math/spotPosition';
66
+ export class User {
67
+ driftClient: DriftClient;
68
+ userAccountPublicKey: PublicKey;
69
+ accountSubscriber: UserAccountSubscriber;
70
+ _isSubscribed = false;
71
+ eventEmitter: StrictEventEmitter<EventEmitter, UserAccountEvents>;
72
+
73
+ public get isSubscribed() {
74
+ return this._isSubscribed && this.accountSubscriber.isSubscribed;
75
+ }
76
+
77
+ public set isSubscribed(val: boolean) {
78
+ this._isSubscribed = val;
79
+ }
80
+
81
+ public constructor(config: UserConfig) {
82
+ this.driftClient = config.driftClient;
83
+ this.userAccountPublicKey = config.userAccountPublicKey;
84
+ if (config.accountSubscription?.type === 'polling') {
85
+ this.accountSubscriber = new PollingUserAccountSubscriber(
86
+ config.driftClient.program,
87
+ config.userAccountPublicKey,
88
+ config.accountSubscription.accountLoader
89
+ );
90
+ } else {
91
+ this.accountSubscriber = new WebSocketUserAccountSubscriber(
92
+ config.driftClient.program,
93
+ config.userAccountPublicKey
94
+ );
95
+ }
96
+ this.eventEmitter = this.accountSubscriber.eventEmitter;
97
+ }
98
+
99
+ /**
100
+ * Subscribe to User state accounts
101
+ * @returns SusbcriptionSuccess result
102
+ */
103
+ public async subscribe(): Promise<boolean> {
104
+ this.isSubscribed = await this.accountSubscriber.subscribe();
105
+ return this.isSubscribed;
106
+ }
107
+
108
+ /**
109
+ * Forces the accountSubscriber to fetch account updates from rpc
110
+ */
111
+ public async fetchAccounts(): Promise<void> {
112
+ await this.accountSubscriber.fetch();
113
+ }
114
+
115
+ public async unsubscribe(): Promise<void> {
116
+ await this.accountSubscriber.unsubscribe();
117
+ this.isSubscribed = false;
118
+ }
119
+
120
+ public getUserAccount(): UserAccount {
121
+ return this.accountSubscriber.getUserAccountAndSlot().data;
122
+ }
123
+
124
+ public getUserAccountAndSlot(): DataAndSlot<UserAccount> | undefined {
125
+ return this.accountSubscriber.getUserAccountAndSlot();
126
+ }
127
+
128
+ /**
129
+ * Gets the user's current position for a given perp market. If the user has no position returns undefined
130
+ * @param marketIndex
131
+ * @returns userPerpPosition
132
+ */
133
+ public getPerpPosition(marketIndex: number): PerpPosition | undefined {
134
+ return this.getUserAccount().perpPositions.find(
135
+ (position) => position.marketIndex === marketIndex
136
+ );
137
+ }
138
+
139
+ /**
140
+ * Gets the user's current position for a given spot market. If the user has no position returns undefined
141
+ * @param marketIndex
142
+ * @returns userSpotPosition
143
+ */
144
+ public getSpotPosition(marketIndex: number): SpotPosition | undefined {
145
+ return this.getUserAccount().spotPositions.find(
146
+ (position) => position.marketIndex === marketIndex
147
+ );
148
+ }
149
+
150
+ public getEmptyPosition(marketIndex: number): PerpPosition {
151
+ return {
152
+ baseAssetAmount: ZERO,
153
+ remainderBaseAssetAmount: 0,
154
+ lastCumulativeFundingRate: ZERO,
155
+ marketIndex,
156
+ quoteAssetAmount: ZERO,
157
+ quoteEntryAmount: ZERO,
158
+ quoteBreakEvenAmount: ZERO,
159
+ openOrders: 0,
160
+ openBids: ZERO,
161
+ openAsks: ZERO,
162
+ settledPnl: ZERO,
163
+ lpShares: ZERO,
164
+ lastNetBaseAssetAmountPerLp: ZERO,
165
+ lastNetQuoteAssetAmountPerLp: ZERO,
166
+ };
167
+ }
168
+
169
+ public getClonedPosition(position: PerpPosition): PerpPosition {
170
+ const clonedPosition = Object.assign({}, position);
171
+ return clonedPosition;
172
+ }
173
+
174
+ /**
175
+ * @param orderId
176
+ * @returns Order
177
+ */
178
+ public getOrder(orderId: number): Order | undefined {
179
+ return this.getUserAccount().orders.find(
180
+ (order) => order.orderId === orderId
181
+ );
182
+ }
183
+
184
+ /**
185
+ * @param userOrderId
186
+ * @returns Order
187
+ */
188
+ public getOrderByUserOrderId(userOrderId: number): Order | undefined {
189
+ return this.getUserAccount().orders.find(
190
+ (order) => order.userOrderId === userOrderId
191
+ );
192
+ }
193
+
194
+ public getUserAccountPublicKey(): PublicKey {
195
+ return this.userAccountPublicKey;
196
+ }
197
+
198
+ public async exists(): Promise<boolean> {
199
+ const userAccountRPCResponse =
200
+ await this.driftClient.connection.getParsedAccountInfo(
201
+ this.userAccountPublicKey
202
+ );
203
+ return userAccountRPCResponse.value !== null;
204
+ }
205
+
206
+ /**
207
+ * calculates the total open bids/asks in a perp market (including lps)
208
+ * @returns : open bids
209
+ * @returns : open asks
210
+ */
211
+ public getPerpBidAsks(marketIndex: number): [BN, BN] {
212
+ const position = this.getPerpPosition(marketIndex);
213
+
214
+ const [lpOpenBids, lpOpenAsks] = this.getLPBidAsks(marketIndex);
215
+
216
+ const totalOpenBids = lpOpenBids.add(position.openBids);
217
+ const totalOpenAsks = lpOpenAsks.add(position.openAsks);
218
+
219
+ return [totalOpenBids, totalOpenAsks];
220
+ }
221
+
222
+ /**
223
+ * calculates the open bids and asks for an lp
224
+ * @returns : lp open bids
225
+ * @returns : lp open asks
226
+ */
227
+ public getLPBidAsks(marketIndex: number): [BN, BN] {
228
+ const position = this.getPerpPosition(marketIndex);
229
+ if (position === undefined || position.lpShares.eq(ZERO)) {
230
+ return [ZERO, ZERO];
231
+ }
232
+
233
+ const market = this.driftClient.getPerpMarketAccount(marketIndex);
234
+ const [marketOpenBids, marketOpenAsks] = calculateMarketOpenBidAsk(
235
+ market.amm.baseAssetReserve,
236
+ market.amm.minBaseAssetReserve,
237
+ market.amm.maxBaseAssetReserve
238
+ );
239
+
240
+ const lpOpenBids = marketOpenBids
241
+ .mul(position.lpShares)
242
+ .div(market.amm.sqrtK);
243
+ const lpOpenAsks = marketOpenAsks
244
+ .mul(position.lpShares)
245
+ .div(market.amm.sqrtK);
246
+
247
+ return [lpOpenBids, lpOpenAsks];
248
+ }
249
+
250
+ /**
251
+ * calculates the market position if the lp position was settled
252
+ * @returns : the settled userPosition
253
+ * @returns : the dust base asset amount (ie, < stepsize)
254
+ * @returns : pnl from settle
255
+ */
256
+ public getSettledLPPosition(marketIndex: number): [PerpPosition, BN, BN] {
257
+ const _position = this.getPerpPosition(marketIndex);
258
+ const position = this.getClonedPosition(_position);
259
+
260
+ if (position.lpShares.eq(ZERO)) {
261
+ return [position, ZERO, ZERO];
262
+ }
263
+
264
+ const market = this.driftClient.getPerpMarketAccount(position.marketIndex);
265
+ const nShares = position.lpShares;
266
+
267
+ const deltaBaa = market.amm.baseAssetAmountPerLp
268
+ .sub(position.lastNetBaseAssetAmountPerLp)
269
+ .mul(nShares)
270
+ .div(AMM_RESERVE_PRECISION);
271
+ const deltaQaa = market.amm.quoteAssetAmountPerLp
272
+ .sub(position.lastNetQuoteAssetAmountPerLp)
273
+ .mul(nShares)
274
+ .div(AMM_RESERVE_PRECISION);
275
+
276
+ function sign(v: BN) {
277
+ const sign = { true: new BN(1), false: new BN(-1) }[
278
+ v.gte(ZERO).toString()
279
+ ];
280
+ return sign;
281
+ }
282
+
283
+ function standardize(amount: BN, stepsize: BN) {
284
+ const remainder = amount.abs().mod(stepsize).mul(sign(amount));
285
+ const standardizedAmount = amount.sub(remainder);
286
+ return [standardizedAmount, remainder];
287
+ }
288
+
289
+ const [standardizedBaa, remainderBaa] = standardize(
290
+ deltaBaa,
291
+ market.amm.orderStepSize
292
+ );
293
+
294
+ position.remainderBaseAssetAmount += remainderBaa.toNumber();
295
+
296
+ if (
297
+ Math.abs(position.remainderBaseAssetAmount) >
298
+ market.amm.orderStepSize.toNumber()
299
+ ) {
300
+ const [newStandardizedBaa, newRemainderBaa] = standardize(
301
+ new BN(position.remainderBaseAssetAmount),
302
+ market.amm.orderStepSize
303
+ );
304
+ position.baseAssetAmount =
305
+ position.baseAssetAmount.add(newStandardizedBaa);
306
+ position.remainderBaseAssetAmount = newRemainderBaa.toNumber();
307
+ }
308
+
309
+ let updateType;
310
+ if (position.baseAssetAmount.eq(ZERO)) {
311
+ updateType = 'open';
312
+ } else if (sign(position.baseAssetAmount).eq(sign(deltaBaa))) {
313
+ updateType = 'increase';
314
+ } else if (position.baseAssetAmount.abs().gt(deltaBaa.abs())) {
315
+ updateType = 'reduce';
316
+ } else if (position.baseAssetAmount.abs().eq(deltaBaa.abs())) {
317
+ updateType = 'close';
318
+ } else {
319
+ updateType = 'flip';
320
+ }
321
+
322
+ let newQuoteEntry;
323
+ let pnl;
324
+ if (updateType == 'open' || updateType == 'increase') {
325
+ newQuoteEntry = position.quoteEntryAmount.add(deltaQaa);
326
+ pnl = 0;
327
+ } else if (updateType == 'reduce' || updateType == 'close') {
328
+ newQuoteEntry = position.quoteEntryAmount.sub(
329
+ position.quoteEntryAmount
330
+ .mul(deltaBaa.abs())
331
+ .div(position.baseAssetAmount.abs())
332
+ );
333
+ pnl = position.quoteEntryAmount.sub(newQuoteEntry).add(deltaQaa);
334
+ } else {
335
+ newQuoteEntry = deltaQaa.sub(
336
+ deltaQaa.mul(position.baseAssetAmount.abs()).div(deltaBaa.abs())
337
+ );
338
+ pnl = position.quoteEntryAmount.add(deltaQaa.sub(newQuoteEntry));
339
+ }
340
+ position.quoteEntryAmount = newQuoteEntry;
341
+ position.baseAssetAmount = position.baseAssetAmount.add(standardizedBaa);
342
+ position.quoteAssetAmount = position.quoteAssetAmount.add(deltaQaa);
343
+
344
+ if (position.baseAssetAmount.gt(ZERO)) {
345
+ position.lastCumulativeFundingRate = market.amm.cumulativeFundingRateLong;
346
+ } else if (position.baseAssetAmount.lt(ZERO)) {
347
+ position.lastCumulativeFundingRate =
348
+ market.amm.cumulativeFundingRateShort;
349
+ } else {
350
+ position.lastCumulativeFundingRate = ZERO;
351
+ }
352
+
353
+ return [position, remainderBaa, pnl];
354
+ }
355
+
356
+ /**
357
+ * calculates Buying Power = FC * MAX_LEVERAGE
358
+ * @returns : Precision QUOTE_PRECISION
359
+ */
360
+ public getBuyingPower(marketIndex: number): BN {
361
+ return this.getFreeCollateral()
362
+ .mul(this.getMaxLeverage(marketIndex, 'Initial'))
363
+ .div(TEN_THOUSAND);
364
+ }
365
+
366
+ /**
367
+ * calculates Free Collateral = Total collateral - initial margin requirement
368
+ * @returns : Precision QUOTE_PRECISION
369
+ */
370
+ public getFreeCollateral(): BN {
371
+ const totalCollateral = this.getTotalCollateral();
372
+ const initialMarginRequirement = this.getInitialMarginRequirement();
373
+ const freeCollateral = totalCollateral.sub(initialMarginRequirement);
374
+ return freeCollateral.gte(ZERO) ? freeCollateral : ZERO;
375
+ }
376
+
377
+ /**
378
+ * @returns The margin requirement of a certain type (Initial or Maintenance) in USDC. : QUOTE_PRECISION
379
+ */
380
+ public getMarginRequirement(
381
+ marginCategory: MarginCategory,
382
+ liquidationBuffer?: BN
383
+ ): BN {
384
+ return this.getTotalPerpPositionValue(
385
+ marginCategory,
386
+ liquidationBuffer,
387
+ true
388
+ ).add(
389
+ this.getSpotMarketLiabilityValue(
390
+ undefined,
391
+ marginCategory,
392
+ liquidationBuffer,
393
+ true
394
+ )
395
+ );
396
+ }
397
+
398
+ /**
399
+ * @returns The initial margin requirement in USDC. : QUOTE_PRECISION
400
+ */
401
+ public getInitialMarginRequirement(): BN {
402
+ return this.getMarginRequirement('Initial');
403
+ }
404
+
405
+ /**
406
+ * @returns The maintenance margin requirement in USDC. : QUOTE_PRECISION
407
+ */
408
+ public getMaintenanceMarginRequirement(liquidationBuffer?: BN): BN {
409
+ return this.getMarginRequirement('Maintenance', liquidationBuffer);
410
+ }
411
+
412
+ /**
413
+ * calculates unrealized position price pnl
414
+ * @returns : Precision QUOTE_PRECISION
415
+ */
416
+ public getUnrealizedPNL(
417
+ withFunding?: boolean,
418
+ marketIndex?: number,
419
+ withWeightMarginCategory?: MarginCategory
420
+ ): BN {
421
+ const quoteSpotMarket = this.driftClient.getQuoteSpotMarketAccount();
422
+ return this.getUserAccount()
423
+ .perpPositions.filter((pos) =>
424
+ marketIndex ? pos.marketIndex === marketIndex : true
425
+ )
426
+ .reduce((unrealizedPnl, perpPosition) => {
427
+ const market = this.driftClient.getPerpMarketAccount(
428
+ perpPosition.marketIndex
429
+ );
430
+ const oraclePriceData = this.getOracleDataForPerpMarket(
431
+ market.marketIndex
432
+ );
433
+
434
+ let positionUnrealizedPnl = calculatePositionPNL(
435
+ market,
436
+ perpPosition,
437
+ withFunding,
438
+ oraclePriceData
439
+ );
440
+
441
+ if (withWeightMarginCategory !== undefined) {
442
+ if (positionUnrealizedPnl.gt(ZERO)) {
443
+ positionUnrealizedPnl = positionUnrealizedPnl
444
+ .mul(
445
+ calculateUnrealizedAssetWeight(
446
+ market,
447
+ quoteSpotMarket,
448
+ positionUnrealizedPnl,
449
+ withWeightMarginCategory,
450
+ oraclePriceData
451
+ )
452
+ )
453
+ .div(new BN(SPOT_MARKET_WEIGHT_PRECISION));
454
+ }
455
+ }
456
+
457
+ return unrealizedPnl.add(positionUnrealizedPnl);
458
+ }, ZERO);
459
+ }
460
+
461
+ /**
462
+ * calculates unrealized funding payment pnl
463
+ * @returns : Precision QUOTE_PRECISION
464
+ */
465
+ public getUnrealizedFundingPNL(marketIndex?: number): BN {
466
+ return this.getUserAccount()
467
+ .perpPositions.filter((pos) =>
468
+ marketIndex ? pos.marketIndex === marketIndex : true
469
+ )
470
+ .reduce((pnl, perpPosition) => {
471
+ const market = this.driftClient.getPerpMarketAccount(
472
+ perpPosition.marketIndex
473
+ );
474
+ return pnl.add(calculatePositionFundingPNL(market, perpPosition));
475
+ }, ZERO);
476
+ }
477
+
478
+ public getSpotMarketLiabilityValue(
479
+ marketIndex?: number,
480
+ marginCategory?: MarginCategory,
481
+ liquidationBuffer?: BN,
482
+ includeOpenOrders?: boolean
483
+ ): BN {
484
+ return this.getUserAccount().spotPositions.reduce(
485
+ (totalLiabilityValue, spotPosition) => {
486
+ if (
487
+ isSpotPositionAvailable(spotPosition) ||
488
+ (marketIndex !== undefined &&
489
+ spotPosition.marketIndex !== marketIndex)
490
+ ) {
491
+ return totalLiabilityValue;
492
+ }
493
+
494
+ const spotMarketAccount: SpotMarketAccount =
495
+ this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
496
+
497
+ if (spotPosition.marketIndex === QUOTE_SPOT_MARKET_INDEX) {
498
+ if (isVariant(spotPosition.balanceType, 'borrow')) {
499
+ const tokenAmount = getTokenAmount(
500
+ spotPosition.scaledBalance,
501
+ spotMarketAccount,
502
+ spotPosition.balanceType
503
+ );
504
+
505
+ let weight = SPOT_MARKET_WEIGHT_PRECISION;
506
+ if (marginCategory === 'Initial') {
507
+ weight = BN.max(
508
+ weight,
509
+ new BN(this.getUserAccount().maxMarginRatio)
510
+ );
511
+ }
512
+
513
+ const weightedTokenValue = tokenAmount
514
+ .mul(weight)
515
+ .div(SPOT_MARKET_WEIGHT_PRECISION);
516
+
517
+ return totalLiabilityValue.add(weightedTokenValue);
518
+ } else {
519
+ return totalLiabilityValue;
520
+ }
521
+ }
522
+
523
+ const oraclePriceData = this.getOracleDataForSpotMarket(
524
+ spotPosition.marketIndex
525
+ );
526
+
527
+ if (!includeOpenOrders) {
528
+ if (isVariant(spotPosition.balanceType, 'borrow')) {
529
+ const tokenAmount = getTokenAmount(
530
+ spotPosition.scaledBalance,
531
+ spotMarketAccount,
532
+ spotPosition.balanceType
533
+ );
534
+ const liabilityValue = this.getSpotLiabilityValue(
535
+ tokenAmount,
536
+ oraclePriceData,
537
+ spotMarketAccount,
538
+ marginCategory,
539
+ liquidationBuffer
540
+ );
541
+ return totalLiabilityValue.add(liabilityValue);
542
+ } else {
543
+ return totalLiabilityValue;
544
+ }
545
+ }
546
+
547
+ const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] =
548
+ getWorstCaseTokenAmounts(
549
+ spotPosition,
550
+ spotMarketAccount,
551
+ this.getOracleDataForSpotMarket(spotPosition.marketIndex)
552
+ );
553
+
554
+ let newTotalLiabilityValue = totalLiabilityValue;
555
+ if (worstCaseTokenAmount.lt(ZERO)) {
556
+ const baseLiabilityValue = this.getSpotLiabilityValue(
557
+ worstCaseTokenAmount.abs(),
558
+ oraclePriceData,
559
+ spotMarketAccount,
560
+ marginCategory,
561
+ liquidationBuffer
562
+ );
563
+
564
+ newTotalLiabilityValue =
565
+ newTotalLiabilityValue.add(baseLiabilityValue);
566
+ }
567
+
568
+ if (worstCaseQuoteTokenAmount.lt(ZERO)) {
569
+ let weight = SPOT_MARKET_WEIGHT_PRECISION;
570
+ if (marginCategory === 'Initial') {
571
+ weight = BN.max(
572
+ weight,
573
+ new BN(this.getUserAccount().maxMarginRatio)
574
+ );
575
+ }
576
+
577
+ const weightedTokenValue = worstCaseQuoteTokenAmount
578
+ .abs()
579
+ .mul(weight)
580
+ .div(SPOT_MARKET_WEIGHT_PRECISION);
581
+
582
+ newTotalLiabilityValue =
583
+ newTotalLiabilityValue.add(weightedTokenValue);
584
+ }
585
+
586
+ return newTotalLiabilityValue;
587
+ },
588
+ ZERO
589
+ );
590
+ }
591
+
592
+ getSpotLiabilityValue(
593
+ tokenAmount: BN,
594
+ oraclePriceData: OraclePriceData,
595
+ spotMarketAccount: SpotMarketAccount,
596
+ marginCategory?: MarginCategory,
597
+ liquidationBuffer?: BN
598
+ ): BN {
599
+ let liabilityValue = getTokenValue(
600
+ tokenAmount,
601
+ spotMarketAccount.decimals,
602
+ oraclePriceData
603
+ );
604
+
605
+ if (marginCategory !== undefined) {
606
+ let weight = calculateLiabilityWeight(
607
+ tokenAmount,
608
+ spotMarketAccount,
609
+ marginCategory
610
+ );
611
+
612
+ if (marginCategory === 'Initial') {
613
+ weight = BN.max(weight, new BN(this.getUserAccount().maxMarginRatio));
614
+ }
615
+
616
+ if (liquidationBuffer !== undefined) {
617
+ weight = weight.add(liquidationBuffer);
618
+ }
619
+
620
+ liabilityValue = liabilityValue
621
+ .mul(weight)
622
+ .div(SPOT_MARKET_WEIGHT_PRECISION);
623
+ }
624
+
625
+ return liabilityValue;
626
+ }
627
+
628
+ public getSpotMarketAssetValue(
629
+ marketIndex?: number,
630
+ marginCategory?: MarginCategory,
631
+ includeOpenOrders?: boolean
632
+ ): BN {
633
+ return this.getUserAccount().spotPositions.reduce(
634
+ (totalAssetValue, spotPosition) => {
635
+ if (
636
+ isSpotPositionAvailable(spotPosition) ||
637
+ (marketIndex !== undefined &&
638
+ spotPosition.marketIndex !== marketIndex)
639
+ ) {
640
+ return totalAssetValue;
641
+ }
642
+
643
+ // Todo this needs to account for whether it's based on initial or maintenance requirements
644
+ const spotMarketAccount: SpotMarketAccount =
645
+ this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
646
+
647
+ if (spotPosition.marketIndex === QUOTE_SPOT_MARKET_INDEX) {
648
+ if (isVariant(spotPosition.balanceType, 'deposit')) {
649
+ const tokenAmount = getTokenAmount(
650
+ spotPosition.scaledBalance,
651
+ spotMarketAccount,
652
+ spotPosition.balanceType
653
+ );
654
+
655
+ return totalAssetValue.add(tokenAmount);
656
+ } else {
657
+ return totalAssetValue;
658
+ }
659
+ }
660
+
661
+ const oraclePriceData = this.getOracleDataForSpotMarket(
662
+ spotPosition.marketIndex
663
+ );
664
+
665
+ if (!includeOpenOrders) {
666
+ if (isVariant(spotPosition.balanceType, 'deposit')) {
667
+ const tokenAmount = getTokenAmount(
668
+ spotPosition.scaledBalance,
669
+ spotMarketAccount,
670
+ spotPosition.balanceType
671
+ );
672
+ const assetValue = this.getSpotAssetValue(
673
+ tokenAmount,
674
+ oraclePriceData,
675
+ spotMarketAccount,
676
+ marginCategory
677
+ );
678
+ return totalAssetValue.add(assetValue);
679
+ } else {
680
+ return totalAssetValue;
681
+ }
682
+ }
683
+
684
+ const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] =
685
+ getWorstCaseTokenAmounts(
686
+ spotPosition,
687
+ spotMarketAccount,
688
+ this.getOracleDataForSpotMarket(spotPosition.marketIndex)
689
+ );
690
+
691
+ let newTotalAssetValue = totalAssetValue;
692
+ if (worstCaseTokenAmount.gt(ZERO)) {
693
+ const baseAssetValue = this.getSpotAssetValue(
694
+ worstCaseTokenAmount,
695
+ oraclePriceData,
696
+ spotMarketAccount,
697
+ marginCategory
698
+ );
699
+
700
+ newTotalAssetValue = newTotalAssetValue.add(baseAssetValue);
701
+ }
702
+
703
+ if (worstCaseQuoteTokenAmount.gt(ZERO)) {
704
+ newTotalAssetValue = newTotalAssetValue.add(
705
+ worstCaseQuoteTokenAmount
706
+ );
707
+ }
708
+
709
+ return newTotalAssetValue;
710
+ },
711
+ ZERO
712
+ );
713
+ }
714
+
715
+ getSpotAssetValue(
716
+ tokenAmount: BN,
717
+ oraclePriceData: OraclePriceData,
718
+ spotMarketAccount: SpotMarketAccount,
719
+ marginCategory?: MarginCategory
720
+ ): BN {
721
+ let assetValue = getTokenValue(
722
+ tokenAmount,
723
+ spotMarketAccount.decimals,
724
+ oraclePriceData
725
+ );
726
+
727
+ if (marginCategory !== undefined) {
728
+ const weight = calculateAssetWeight(
729
+ tokenAmount,
730
+ spotMarketAccount,
731
+ marginCategory
732
+ );
733
+
734
+ assetValue = assetValue.mul(weight).div(SPOT_MARKET_WEIGHT_PRECISION);
735
+ }
736
+
737
+ return assetValue;
738
+ }
739
+
740
+ public getNetSpotMarketValue(withWeightMarginCategory?: MarginCategory): BN {
741
+ return this.getSpotMarketAssetValue(
742
+ undefined,
743
+ withWeightMarginCategory
744
+ ).sub(
745
+ this.getSpotMarketLiabilityValue(undefined, withWeightMarginCategory)
746
+ );
747
+ }
748
+
749
+ /**
750
+ * calculates TotalCollateral: collateral + unrealized pnl
751
+ * @returns : Precision QUOTE_PRECISION
752
+ */
753
+ public getTotalCollateral(marginCategory: MarginCategory = 'Initial'): BN {
754
+ return this.getSpotMarketAssetValue(undefined, marginCategory, true).add(
755
+ this.getUnrealizedPNL(true, undefined, marginCategory)
756
+ );
757
+ }
758
+
759
+ /**
760
+ * calculates sum of position value across all positions in margin system
761
+ * @returns : Precision QUOTE_PRECISION
762
+ */
763
+ getTotalPerpPositionValue(
764
+ marginCategory?: MarginCategory,
765
+ liquidationBuffer?: BN,
766
+ includeOpenOrders?: boolean
767
+ ): BN {
768
+ return this.getUserAccount().perpPositions.reduce(
769
+ (totalPerpValue, perpPosition) => {
770
+ const market = this.driftClient.getPerpMarketAccount(
771
+ perpPosition.marketIndex
772
+ );
773
+
774
+ if (perpPosition.lpShares.gt(ZERO)) {
775
+ // is an lp
776
+ // clone so we dont mutate the position
777
+ perpPosition = this.getClonedPosition(perpPosition);
778
+
779
+ // settle position
780
+ const [settledPosition, dustBaa, _] = this.getSettledLPPosition(
781
+ market.marketIndex
782
+ );
783
+ perpPosition.baseAssetAmount =
784
+ settledPosition.baseAssetAmount.add(dustBaa);
785
+ perpPosition.quoteAssetAmount = settledPosition.quoteAssetAmount;
786
+
787
+ const [totalOpenBids, totalOpenAsks] = this.getPerpBidAsks(
788
+ market.marketIndex
789
+ );
790
+
791
+ perpPosition.openAsks = totalOpenAsks;
792
+ perpPosition.openBids = totalOpenBids;
793
+ }
794
+
795
+ let valuationPrice = this.getOracleDataForPerpMarket(
796
+ market.marketIndex
797
+ ).price;
798
+
799
+ if (isVariant(market.status, 'settlement')) {
800
+ valuationPrice = market.expiryPrice;
801
+ }
802
+
803
+ const baseAssetAmount = includeOpenOrders
804
+ ? calculateWorstCaseBaseAssetAmount(perpPosition)
805
+ : perpPosition.baseAssetAmount;
806
+
807
+ let baseAssetValue = baseAssetAmount
808
+ .abs()
809
+ .mul(valuationPrice)
810
+ .div(AMM_TO_QUOTE_PRECISION_RATIO.mul(PRICE_PRECISION));
811
+
812
+ if (marginCategory) {
813
+ let marginRatio = new BN(
814
+ calculateMarketMarginRatio(
815
+ market,
816
+ baseAssetAmount.abs(),
817
+ marginCategory
818
+ )
819
+ );
820
+
821
+ if (marginCategory === 'Initial') {
822
+ marginRatio = BN.max(
823
+ marginRatio,
824
+ new BN(this.getUserAccount().maxMarginRatio)
825
+ );
826
+ }
827
+
828
+ if (liquidationBuffer !== undefined) {
829
+ marginRatio = marginRatio.add(liquidationBuffer);
830
+ }
831
+
832
+ if (isVariant(market.status, 'settlement')) {
833
+ marginRatio = ZERO;
834
+ }
835
+
836
+ baseAssetValue = baseAssetValue
837
+ .mul(marginRatio)
838
+ .div(MARGIN_PRECISION);
839
+ }
840
+
841
+ return totalPerpValue.add(baseAssetValue);
842
+ },
843
+ ZERO
844
+ );
845
+ }
846
+
847
+ /**
848
+ * calculates position value in margin system
849
+ * @returns : Precision QUOTE_PRECISION
850
+ */
851
+ public getPerpPositionValue(
852
+ marketIndex: number,
853
+ oraclePriceData: OraclePriceData
854
+ ): BN {
855
+ const userPosition =
856
+ this.getPerpPosition(marketIndex) || this.getEmptyPosition(marketIndex);
857
+ const market = this.driftClient.getPerpMarketAccount(
858
+ userPosition.marketIndex
859
+ );
860
+ return calculateBaseAssetValueWithOracle(
861
+ market,
862
+ userPosition,
863
+ oraclePriceData
864
+ );
865
+ }
866
+
867
+ public getPositionSide(
868
+ currentPosition: Pick<PerpPosition, 'baseAssetAmount'>
869
+ ): PositionDirection | undefined {
870
+ if (currentPosition.baseAssetAmount.gt(ZERO)) {
871
+ return PositionDirection.LONG;
872
+ } else if (currentPosition.baseAssetAmount.lt(ZERO)) {
873
+ return PositionDirection.SHORT;
874
+ } else {
875
+ return undefined;
876
+ }
877
+ }
878
+
879
+ /**
880
+ * calculates average exit price (optionally for closing up to 100% of position)
881
+ * @returns : Precision PRICE_PRECISION
882
+ */
883
+ public getPositionEstimatedExitPriceAndPnl(
884
+ position: PerpPosition,
885
+ amountToClose?: BN,
886
+ useAMMClose = false
887
+ ): [BN, BN] {
888
+ const market = this.driftClient.getPerpMarketAccount(position.marketIndex);
889
+
890
+ const entryPrice = calculateEntryPrice(position);
891
+
892
+ const oraclePriceData = this.getOracleDataForPerpMarket(
893
+ position.marketIndex
894
+ );
895
+
896
+ if (amountToClose) {
897
+ if (amountToClose.eq(ZERO)) {
898
+ return [calculateReservePrice(market, oraclePriceData), ZERO];
899
+ }
900
+ position = {
901
+ baseAssetAmount: amountToClose,
902
+ lastCumulativeFundingRate: position.lastCumulativeFundingRate,
903
+ marketIndex: position.marketIndex,
904
+ quoteAssetAmount: position.quoteAssetAmount,
905
+ } as PerpPosition;
906
+ }
907
+
908
+ let baseAssetValue: BN;
909
+
910
+ if (useAMMClose) {
911
+ baseAssetValue = calculateBaseAssetValue(
912
+ market,
913
+ position,
914
+ oraclePriceData
915
+ );
916
+ } else {
917
+ baseAssetValue = calculateBaseAssetValueWithOracle(
918
+ market,
919
+ position,
920
+ oraclePriceData
921
+ );
922
+ }
923
+ if (position.baseAssetAmount.eq(ZERO)) {
924
+ return [ZERO, ZERO];
925
+ }
926
+
927
+ const exitPrice = baseAssetValue
928
+ .mul(AMM_TO_QUOTE_PRECISION_RATIO)
929
+ .mul(PRICE_PRECISION)
930
+ .div(position.baseAssetAmount.abs());
931
+
932
+ const pnlPerBase = exitPrice.sub(entryPrice);
933
+ const pnl = pnlPerBase
934
+ .mul(position.baseAssetAmount)
935
+ .div(PRICE_PRECISION)
936
+ .div(AMM_TO_QUOTE_PRECISION_RATIO);
937
+
938
+ return [exitPrice, pnl];
939
+ }
940
+
941
+ /**
942
+ * calculates current user leverage across all positions
943
+ * @returns : Precision TEN_THOUSAND
944
+ */
945
+ public getLeverage(): BN {
946
+ const totalLiabilityValue = this.getTotalLiabilityValue();
947
+
948
+ const totalAssetValue = this.getTotalAssetValue();
949
+
950
+ if (totalAssetValue.eq(ZERO) && totalLiabilityValue.eq(ZERO)) {
951
+ return ZERO;
952
+ }
953
+
954
+ return totalLiabilityValue.mul(TEN_THOUSAND).div(totalAssetValue);
955
+ }
956
+
957
+ getTotalLiabilityValue(): BN {
958
+ return this.getTotalPerpPositionValue(undefined, undefined, true).add(
959
+ this.getSpotMarketLiabilityValue(undefined, undefined, undefined, true)
960
+ );
961
+ }
962
+
963
+ getTotalAssetValue(): BN {
964
+ return this.getSpotMarketAssetValue(undefined, undefined, true).add(
965
+ this.getUnrealizedPNL(true, undefined, undefined)
966
+ );
967
+ }
968
+
969
+ /**
970
+ * calculates max allowable leverage exceeding hitting requirement category
971
+ * @params category {Initial, Maintenance}
972
+ * @returns : Precision TEN_THOUSAND
973
+ */
974
+ public getMaxLeverage(
975
+ marketIndex: number,
976
+ category: MarginCategory = 'Initial'
977
+ ): BN {
978
+ const market = this.driftClient.getPerpMarketAccount(marketIndex);
979
+
980
+ const totalAssetValue = this.getTotalAssetValue();
981
+ if (totalAssetValue.eq(ZERO)) {
982
+ return ZERO;
983
+ }
984
+
985
+ const totalLiabilityValue = this.getTotalLiabilityValue();
986
+
987
+ const marginRatio = calculateMarketMarginRatio(
988
+ market,
989
+ // worstCaseBaseAssetAmount.abs(),
990
+ ZERO, // todo
991
+ category
992
+ );
993
+ const freeCollateral = this.getFreeCollateral();
994
+
995
+ // how much more liabilities can be opened w remaining free collateral
996
+ const additionalLiabilities = freeCollateral
997
+ .mul(MARGIN_PRECISION)
998
+ .div(new BN(marginRatio));
999
+
1000
+ return totalLiabilityValue
1001
+ .add(additionalLiabilities)
1002
+ .mul(TEN_THOUSAND)
1003
+ .div(totalAssetValue);
1004
+ }
1005
+
1006
+ /**
1007
+ * calculates margin ratio: total collateral / |total position value|
1008
+ * @returns : Precision TEN_THOUSAND
1009
+ */
1010
+ public getMarginRatio(): BN {
1011
+ const totalLiabilityValue = this.getTotalLiabilityValue();
1012
+
1013
+ if (totalLiabilityValue.eq(ZERO)) {
1014
+ return BN_MAX;
1015
+ }
1016
+
1017
+ const totalAssetValue = this.getTotalAssetValue();
1018
+
1019
+ return totalAssetValue.mul(TEN_THOUSAND).div(totalLiabilityValue);
1020
+ }
1021
+
1022
+ public canBeLiquidated(): boolean {
1023
+ const totalCollateral = this.getTotalCollateral();
1024
+
1025
+ // if user being liq'd, can continue to be liq'd until total collateral above the margin requirement plus buffer
1026
+ let liquidationBuffer = undefined;
1027
+ if (this.getUserAccount().isBeingLiquidated) {
1028
+ liquidationBuffer = new BN(
1029
+ this.driftClient.getStateAccount().liquidationMarginBufferRatio
1030
+ );
1031
+ }
1032
+ const maintenanceRequirement =
1033
+ this.getMaintenanceMarginRequirement(liquidationBuffer);
1034
+ return totalCollateral.lt(maintenanceRequirement);
1035
+ }
1036
+
1037
+ /**
1038
+ * Checks if any user position cumulative funding differs from respective market cumulative funding
1039
+ * @returns
1040
+ */
1041
+ public needsToSettleFundingPayment(): boolean {
1042
+ for (const userPosition of this.getUserAccount().perpPositions) {
1043
+ if (userPosition.baseAssetAmount.eq(ZERO)) {
1044
+ continue;
1045
+ }
1046
+
1047
+ const market = this.driftClient.getPerpMarketAccount(
1048
+ userPosition.marketIndex
1049
+ );
1050
+ if (
1051
+ market.amm.cumulativeFundingRateLong.eq(
1052
+ userPosition.lastCumulativeFundingRate
1053
+ ) ||
1054
+ market.amm.cumulativeFundingRateShort.eq(
1055
+ userPosition.lastCumulativeFundingRate
1056
+ )
1057
+ ) {
1058
+ continue;
1059
+ }
1060
+
1061
+ return true;
1062
+ }
1063
+ return false;
1064
+ }
1065
+
1066
+ /**
1067
+ * Calculate the liquidation price of a position, with optional parameter to calculate the liquidation price after a trade
1068
+ * @param PerpPosition
1069
+ * @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
1070
+ * @param partial
1071
+ * @returns Precision : PRICE_PRECISION
1072
+ */
1073
+ public liquidationPrice(
1074
+ perpPosition: Pick<PerpPosition, 'marketIndex'>,
1075
+ positionBaseSizeChange: BN = ZERO
1076
+ ): BN {
1077
+ // solves formula for example canBeLiquidated below
1078
+
1079
+ /* example: assume BTC price is $40k (examine 10% up/down)
1080
+
1081
+ if 10k deposit and levered 10x short BTC => BTC up $400 means:
1082
+ 1. higher base_asset_value (+$4k)
1083
+ 2. lower collateral (-$4k)
1084
+ 3. (10k - 4k)/(100k + 4k) => 6k/104k => .0576
1085
+
1086
+ for 10x long, BTC down $400:
1087
+ 3. (10k - 4k) / (100k - 4k) = 6k/96k => .0625 */
1088
+
1089
+ const totalCollateral = this.getTotalCollateral();
1090
+
1091
+ // calculate the total position value ignoring any value from the target market of the trade
1092
+ const totalPositionValueExcludingTargetMarket =
1093
+ this.getTotalPerpPositionValueExcludingMarket(perpPosition.marketIndex);
1094
+
1095
+ const currentPerpPosition =
1096
+ this.getPerpPosition(perpPosition.marketIndex) ||
1097
+ this.getEmptyPosition(perpPosition.marketIndex);
1098
+
1099
+ const currentPerpPositionBaseSize = currentPerpPosition.baseAssetAmount;
1100
+
1101
+ const proposedBaseAssetAmount = currentPerpPositionBaseSize.add(
1102
+ positionBaseSizeChange
1103
+ );
1104
+
1105
+ // calculate position for current market after trade
1106
+ const proposedPerpPosition: PerpPosition = {
1107
+ marketIndex: perpPosition.marketIndex,
1108
+ baseAssetAmount: proposedBaseAssetAmount,
1109
+ remainderBaseAssetAmount: 0,
1110
+ quoteAssetAmount: new BN(0),
1111
+ lastCumulativeFundingRate: ZERO,
1112
+ quoteBreakEvenAmount: new BN(0),
1113
+ quoteEntryAmount: new BN(0),
1114
+ openOrders: 0,
1115
+ openBids: new BN(0),
1116
+ openAsks: new BN(0),
1117
+ settledPnl: ZERO,
1118
+ lpShares: ZERO,
1119
+ lastNetBaseAssetAmountPerLp: ZERO,
1120
+ lastNetQuoteAssetAmountPerLp: ZERO,
1121
+ };
1122
+
1123
+ if (proposedBaseAssetAmount.eq(ZERO)) return new BN(-1);
1124
+
1125
+ const market = this.driftClient.getPerpMarketAccount(
1126
+ proposedPerpPosition.marketIndex
1127
+ );
1128
+
1129
+ const proposedPerpPositionValue = calculateBaseAssetValueWithOracle(
1130
+ market,
1131
+ proposedPerpPosition,
1132
+ this.getOracleDataForPerpMarket(market.marketIndex)
1133
+ );
1134
+
1135
+ // total position value after trade
1136
+ const totalPositionValueAfterTrade =
1137
+ totalPositionValueExcludingTargetMarket.add(proposedPerpPositionValue);
1138
+
1139
+ const marginRequirementExcludingTargetMarket =
1140
+ this.getUserAccount().perpPositions.reduce(
1141
+ (totalMarginRequirement, position) => {
1142
+ if (position.marketIndex !== perpPosition.marketIndex) {
1143
+ const market = this.driftClient.getPerpMarketAccount(
1144
+ position.marketIndex
1145
+ );
1146
+ const positionValue = calculateBaseAssetValueWithOracle(
1147
+ market,
1148
+ position,
1149
+ this.getOracleDataForPerpMarket(market.marketIndex)
1150
+ );
1151
+ const marketMarginRequirement = positionValue
1152
+ .mul(
1153
+ new BN(
1154
+ calculateMarketMarginRatio(
1155
+ market,
1156
+ position.baseAssetAmount.abs(),
1157
+ 'Maintenance'
1158
+ )
1159
+ )
1160
+ )
1161
+ .div(MARGIN_PRECISION);
1162
+ totalMarginRequirement = totalMarginRequirement.add(
1163
+ marketMarginRequirement
1164
+ );
1165
+ }
1166
+ return totalMarginRequirement;
1167
+ },
1168
+ ZERO
1169
+ );
1170
+
1171
+ const freeCollateralExcludingTargetMarket = totalCollateral.sub(
1172
+ marginRequirementExcludingTargetMarket
1173
+ );
1174
+
1175
+ // if the position value after the trade is less than free collateral, there is no liq price
1176
+ if (
1177
+ totalPositionValueAfterTrade.lte(freeCollateralExcludingTargetMarket) &&
1178
+ proposedPerpPosition.baseAssetAmount.abs().gt(ZERO)
1179
+ ) {
1180
+ return new BN(-1);
1181
+ }
1182
+
1183
+ const marginRequirementAfterTrade =
1184
+ marginRequirementExcludingTargetMarket.add(
1185
+ proposedPerpPositionValue
1186
+ .mul(
1187
+ new BN(
1188
+ calculateMarketMarginRatio(
1189
+ market,
1190
+ proposedPerpPosition.baseAssetAmount.abs(),
1191
+ 'Maintenance'
1192
+ )
1193
+ )
1194
+ )
1195
+ .div(MARGIN_PRECISION)
1196
+ );
1197
+ const freeCollateralAfterTrade = totalCollateral.sub(
1198
+ marginRequirementAfterTrade
1199
+ );
1200
+
1201
+ const marketMaxLeverage = this.getMaxLeverage(
1202
+ proposedPerpPosition.marketIndex,
1203
+ 'Maintenance'
1204
+ );
1205
+
1206
+ let priceDelta;
1207
+ if (proposedBaseAssetAmount.lt(ZERO)) {
1208
+ priceDelta = freeCollateralAfterTrade
1209
+ .mul(marketMaxLeverage) // precision is TEN_THOUSAND
1210
+ .div(marketMaxLeverage.add(TEN_THOUSAND))
1211
+ .mul(PRICE_TO_QUOTE_PRECISION)
1212
+ .mul(AMM_RESERVE_PRECISION)
1213
+ .div(proposedBaseAssetAmount);
1214
+ } else {
1215
+ priceDelta = freeCollateralAfterTrade
1216
+ .mul(marketMaxLeverage) // precision is TEN_THOUSAND
1217
+ .div(marketMaxLeverage.sub(TEN_THOUSAND))
1218
+ .mul(PRICE_TO_QUOTE_PRECISION)
1219
+ .mul(AMM_RESERVE_PRECISION)
1220
+ .div(proposedBaseAssetAmount);
1221
+ }
1222
+
1223
+ let markPriceAfterTrade;
1224
+ if (positionBaseSizeChange.eq(ZERO)) {
1225
+ markPriceAfterTrade = calculateReservePrice(
1226
+ this.driftClient.getPerpMarketAccount(perpPosition.marketIndex),
1227
+ this.getOracleDataForPerpMarket(perpPosition.marketIndex)
1228
+ );
1229
+ } else {
1230
+ const direction = positionBaseSizeChange.gt(ZERO)
1231
+ ? PositionDirection.LONG
1232
+ : PositionDirection.SHORT;
1233
+ markPriceAfterTrade = calculateTradeSlippage(
1234
+ direction,
1235
+ positionBaseSizeChange.abs(),
1236
+ this.driftClient.getPerpMarketAccount(perpPosition.marketIndex),
1237
+ 'base',
1238
+ this.getOracleDataForPerpMarket(perpPosition.marketIndex)
1239
+ )[3]; // newPrice after swap
1240
+ }
1241
+
1242
+ if (priceDelta.gt(markPriceAfterTrade)) {
1243
+ return new BN(-1);
1244
+ }
1245
+
1246
+ return markPriceAfterTrade.sub(priceDelta);
1247
+ }
1248
+
1249
+ /**
1250
+ * Calculates the estimated liquidation price for a position after closing a quote amount of the position.
1251
+ * @param positionMarketIndex
1252
+ * @param closeQuoteAmount
1253
+ * @returns : Precision PRICE_PRECISION
1254
+ */
1255
+ public liquidationPriceAfterClose(
1256
+ positionMarketIndex: number,
1257
+ closeQuoteAmount: BN
1258
+ ): BN {
1259
+ const currentPosition =
1260
+ this.getPerpPosition(positionMarketIndex) ||
1261
+ this.getEmptyPosition(positionMarketIndex);
1262
+
1263
+ const closeBaseAmount = currentPosition.baseAssetAmount
1264
+ .mul(closeQuoteAmount)
1265
+ .div(currentPosition.quoteAssetAmount.abs())
1266
+ .add(
1267
+ currentPosition.baseAssetAmount
1268
+ .mul(closeQuoteAmount)
1269
+ .mod(currentPosition.quoteAssetAmount.abs())
1270
+ )
1271
+ .neg();
1272
+
1273
+ return this.liquidationPrice(
1274
+ {
1275
+ marketIndex: positionMarketIndex,
1276
+ },
1277
+ closeBaseAmount
1278
+ );
1279
+ }
1280
+
1281
+ /**
1282
+ * Get the maximum trade size for a given market, taking into account the user's current leverage, positions, collateral, etc.
1283
+ *
1284
+ * To Calculate Max Quote Available:
1285
+ *
1286
+ * Case 1: SameSide
1287
+ * => Remaining quote to get to maxLeverage
1288
+ *
1289
+ * Case 2: NOT SameSide && currentLeverage <= maxLeverage
1290
+ * => Current opposite position x2 + remaining to get to maxLeverage
1291
+ *
1292
+ * Case 3: NOT SameSide && currentLeverage > maxLeverage && otherPositions - currentPosition > maxLeverage
1293
+ * => strictly reduce current position size
1294
+ *
1295
+ * Case 4: NOT SameSide && currentLeverage > maxLeverage && otherPositions - currentPosition < maxLeverage
1296
+ * => current position + remaining to get to maxLeverage
1297
+ *
1298
+ * @param targetMarketIndex
1299
+ * @param tradeSide
1300
+ * @returns tradeSizeAllowed : Precision QUOTE_PRECISION
1301
+ */
1302
+ public getMaxTradeSizeUSDC(
1303
+ targetMarketIndex: number,
1304
+ tradeSide: PositionDirection
1305
+ ): BN {
1306
+ const currentPosition =
1307
+ this.getPerpPosition(targetMarketIndex) ||
1308
+ this.getEmptyPosition(targetMarketIndex);
1309
+
1310
+ const targetSide = isVariant(tradeSide, 'short') ? 'short' : 'long';
1311
+
1312
+ const currentPositionSide = currentPosition?.baseAssetAmount.isNeg()
1313
+ ? 'short'
1314
+ : 'long';
1315
+
1316
+ const targetingSameSide = !currentPosition
1317
+ ? true
1318
+ : targetSide === currentPositionSide;
1319
+
1320
+ const oracleData = this.getOracleDataForPerpMarket(targetMarketIndex);
1321
+
1322
+ // add any position we have on the opposite side of the current trade, because we can "flip" the size of this position without taking any extra leverage.
1323
+ const oppositeSizeValueUSDC = targetingSameSide
1324
+ ? ZERO
1325
+ : this.getPerpPositionValue(targetMarketIndex, oracleData);
1326
+
1327
+ let maxPositionSize = this.getBuyingPower(targetMarketIndex);
1328
+ if (maxPositionSize.gte(ZERO)) {
1329
+ if (oppositeSizeValueUSDC.eq(ZERO)) {
1330
+ // case 1 : Regular trade where current total position less than max, and no opposite position to account for
1331
+ // do nothing
1332
+ } else {
1333
+ // case 2 : trade where current total position less than max, but need to account for flipping the current position over to the other side
1334
+ maxPositionSize = maxPositionSize.add(
1335
+ oppositeSizeValueUSDC.mul(new BN(2))
1336
+ );
1337
+ }
1338
+ } else {
1339
+ // current leverage is greater than max leverage - can only reduce position size
1340
+
1341
+ if (!targetingSameSide) {
1342
+ const market = this.driftClient.getPerpMarketAccount(targetMarketIndex);
1343
+ const perpPositionValue = this.getPerpPositionValue(
1344
+ targetMarketIndex,
1345
+ oracleData
1346
+ );
1347
+ const totalCollateral = this.getTotalCollateral();
1348
+ const marginRequirement = this.getInitialMarginRequirement();
1349
+ const marginFreedByClosing = perpPositionValue
1350
+ .mul(new BN(market.marginRatioInitial))
1351
+ .div(MARGIN_PRECISION);
1352
+ const marginRequirementAfterClosing =
1353
+ marginRequirement.sub(marginFreedByClosing);
1354
+
1355
+ if (marginRequirementAfterClosing.gt(totalCollateral)) {
1356
+ maxPositionSize = perpPositionValue;
1357
+ } else {
1358
+ const freeCollateralAfterClose = totalCollateral.sub(
1359
+ marginRequirementAfterClosing
1360
+ );
1361
+ const buyingPowerAfterClose = freeCollateralAfterClose
1362
+ .mul(this.getMaxLeverage(targetMarketIndex))
1363
+ .div(TEN_THOUSAND);
1364
+ maxPositionSize = perpPositionValue.add(buyingPowerAfterClose);
1365
+ }
1366
+ } else {
1367
+ // do nothing if targetting same side
1368
+ }
1369
+ }
1370
+
1371
+ // subtract oneMillionth of maxPositionSize
1372
+ // => to avoid rounding errors when taking max leverage
1373
+ const oneMilli = maxPositionSize.div(QUOTE_PRECISION);
1374
+ return maxPositionSize.sub(oneMilli);
1375
+ }
1376
+
1377
+ // TODO - should this take the price impact of the trade into account for strict accuracy?
1378
+
1379
+ /**
1380
+ * Returns the leverage ratio for the account after adding (or subtracting) the given quote size to the given position
1381
+ * @param targetMarketIndex
1382
+ * @param positionMarketIndex
1383
+ * @param tradeQuoteAmount
1384
+ * @returns leverageRatio : Precision TEN_THOUSAND
1385
+ */
1386
+ public accountLeverageRatioAfterTrade(
1387
+ targetMarketIndex: number,
1388
+ tradeQuoteAmount: BN,
1389
+ tradeSide: PositionDirection,
1390
+ includeOpenOrders = true
1391
+ ): BN {
1392
+ const currentPosition =
1393
+ this.getPerpPosition(targetMarketIndex) ||
1394
+ this.getEmptyPosition(targetMarketIndex);
1395
+
1396
+ const oracleData = this.getOracleDataForPerpMarket(targetMarketIndex);
1397
+
1398
+ let currentPositionQuoteAmount = this.getPerpPositionValue(
1399
+ targetMarketIndex,
1400
+ oracleData
1401
+ );
1402
+
1403
+ const currentSide =
1404
+ currentPosition && currentPosition.baseAssetAmount.isNeg()
1405
+ ? PositionDirection.SHORT
1406
+ : PositionDirection.LONG;
1407
+
1408
+ if (currentSide === PositionDirection.SHORT)
1409
+ currentPositionQuoteAmount = currentPositionQuoteAmount.neg();
1410
+
1411
+ if (tradeSide === PositionDirection.SHORT)
1412
+ tradeQuoteAmount = tradeQuoteAmount.neg();
1413
+
1414
+ const currentPerpPositionAfterTrade = currentPositionQuoteAmount
1415
+ .add(tradeQuoteAmount)
1416
+ .abs();
1417
+
1418
+ const totalPositionAfterTradeExcludingTargetMarket =
1419
+ this.getTotalPerpPositionValueExcludingMarket(
1420
+ targetMarketIndex,
1421
+ undefined,
1422
+ undefined,
1423
+ includeOpenOrders
1424
+ );
1425
+
1426
+ const totalAssetValue = this.getTotalAssetValue();
1427
+
1428
+ const totalPerpPositionValue = currentPerpPositionAfterTrade
1429
+ .add(totalPositionAfterTradeExcludingTargetMarket)
1430
+ .abs();
1431
+
1432
+ const totalLiabilitiesAfterTrade = totalPerpPositionValue.add(
1433
+ this.getSpotMarketLiabilityValue(undefined, undefined, undefined, false)
1434
+ );
1435
+
1436
+ if (totalAssetValue.eq(ZERO) && totalLiabilitiesAfterTrade.eq(ZERO)) {
1437
+ return ZERO;
1438
+ }
1439
+
1440
+ const newLeverage = totalLiabilitiesAfterTrade
1441
+ .mul(TEN_THOUSAND)
1442
+ .div(totalAssetValue);
1443
+
1444
+ return newLeverage;
1445
+ }
1446
+
1447
+ /**
1448
+ * Calculates how much fee will be taken for a given sized trade
1449
+ * @param quoteAmount
1450
+ * @returns feeForQuote : Precision QUOTE_PRECISION
1451
+ */
1452
+ public calculateFeeForQuoteAmount(quoteAmount: BN): BN {
1453
+ const feeTier =
1454
+ this.driftClient.getStateAccount().perpFeeStructure.feeTiers[0];
1455
+ return quoteAmount
1456
+ .mul(new BN(feeTier.feeNumerator))
1457
+ .div(new BN(feeTier.feeDenominator));
1458
+ }
1459
+
1460
+ /**
1461
+ * Calculates a user's max withdrawal amounts for a spot market. If reduceOnly is true,
1462
+ * it will return the max withdrawal amount without opening a liability for the user
1463
+ * @param marketIndex
1464
+ * @returns withdrawalLimit : Precision is the token precision for the chosen SpotMarket
1465
+ */
1466
+ public getWithdrawalLimit(marketIndex: number, reduceOnly?: boolean): BN {
1467
+ const nowTs = new BN(Math.floor(Date.now() / 1000));
1468
+ const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
1469
+
1470
+ const { borrowLimit, withdrawLimit } = calculateWithdrawLimit(
1471
+ spotMarket,
1472
+ nowTs
1473
+ );
1474
+
1475
+ const freeCollateral = this.getFreeCollateral();
1476
+ const oracleData = this.getOracleDataForSpotMarket(marketIndex);
1477
+ const precisionIncrease = TEN.pow(new BN(spotMarket.decimals - 6));
1478
+
1479
+ const amountWithdrawable = freeCollateral
1480
+ .mul(MARGIN_PRECISION)
1481
+ .div(new BN(spotMarket.initialAssetWeight))
1482
+ .mul(PRICE_PRECISION)
1483
+ .div(oracleData.price)
1484
+ .mul(precisionIncrease);
1485
+
1486
+ const userSpotPosition = this.getUserAccount().spotPositions.find(
1487
+ (spotPosition) =>
1488
+ isVariant(spotPosition.balanceType, 'deposit') &&
1489
+ spotPosition.marketIndex == marketIndex
1490
+ );
1491
+
1492
+ const userSpotBalance = userSpotPosition
1493
+ ? getTokenAmount(
1494
+ userSpotPosition.scaledBalance,
1495
+ this.driftClient.getSpotMarketAccount(marketIndex),
1496
+ SpotBalanceType.DEPOSIT
1497
+ )
1498
+ : ZERO;
1499
+
1500
+ const maxWithdrawValue = BN.min(
1501
+ BN.min(amountWithdrawable, userSpotBalance),
1502
+ withdrawLimit.abs()
1503
+ );
1504
+
1505
+ if (reduceOnly) {
1506
+ return BN.max(maxWithdrawValue, ZERO);
1507
+ } else {
1508
+ const weightedAssetValue = this.getSpotMarketAssetValue(
1509
+ marketIndex,
1510
+ 'Initial',
1511
+ false
1512
+ );
1513
+
1514
+ const freeCollatAfterWithdraw = userSpotBalance.gt(ZERO)
1515
+ ? freeCollateral.sub(weightedAssetValue)
1516
+ : freeCollateral;
1517
+
1518
+ const maxLiabilityAllowed = freeCollatAfterWithdraw
1519
+ .mul(MARGIN_PRECISION)
1520
+ .div(new BN(spotMarket.initialLiabilityWeight))
1521
+ .mul(PRICE_PRECISION)
1522
+ .div(oracleData.price)
1523
+ .mul(precisionIncrease);
1524
+
1525
+ const maxBorrowValue = BN.min(
1526
+ maxWithdrawValue.add(maxLiabilityAllowed),
1527
+ borrowLimit.abs()
1528
+ );
1529
+
1530
+ return BN.max(maxBorrowValue, ZERO);
1531
+ }
1532
+ }
1533
+
1534
+ /**
1535
+ * Get the total position value, excluding any position coming from the given target market
1536
+ * @param marketToIgnore
1537
+ * @returns positionValue : Precision QUOTE_PRECISION
1538
+ */
1539
+ private getTotalPerpPositionValueExcludingMarket(
1540
+ marketToIgnore: number,
1541
+ marginCategory?: MarginCategory,
1542
+ liquidationBuffer?: BN,
1543
+ includeOpenOrders?: boolean
1544
+ ): BN {
1545
+ const currentPerpPosition =
1546
+ this.getPerpPosition(marketToIgnore) ||
1547
+ this.getEmptyPosition(marketToIgnore);
1548
+
1549
+ const oracleData = this.getOracleDataForPerpMarket(marketToIgnore);
1550
+
1551
+ let currentPerpPositionValueUSDC = ZERO;
1552
+ if (currentPerpPosition) {
1553
+ currentPerpPositionValueUSDC = this.getPerpPositionValue(
1554
+ marketToIgnore,
1555
+ oracleData
1556
+ );
1557
+ }
1558
+
1559
+ return this.getTotalPerpPositionValue(
1560
+ marginCategory,
1561
+ liquidationBuffer,
1562
+ includeOpenOrders
1563
+ ).sub(currentPerpPositionValueUSDC);
1564
+ }
1565
+
1566
+ private getOracleDataForPerpMarket(marketIndex: number): OraclePriceData {
1567
+ const oracleKey =
1568
+ this.driftClient.getPerpMarketAccount(marketIndex).amm.oracle;
1569
+ const oracleData =
1570
+ this.driftClient.getOraclePriceDataAndSlot(oracleKey).data;
1571
+
1572
+ return oracleData;
1573
+ }
1574
+ private getOracleDataForSpotMarket(marketIndex: number): OraclePriceData {
1575
+ const oracleKey = this.driftClient.getSpotMarketAccount(marketIndex).oracle;
1576
+
1577
+ const oracleData =
1578
+ this.driftClient.getOraclePriceDataAndSlot(oracleKey).data;
1579
+
1580
+ return oracleData;
1581
+ }
1582
+ }