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