@drift-labs/sdk 2.31.1-beta.23 → 2.31.1-beta.24

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.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.31.1-beta.23
1
+ 2.31.1-beta.24
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.31.1-beta.22",
2
+ "version": "2.31.1-beta.23",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -2,6 +2,7 @@ import { BN } from '../';
2
2
  export declare function clampBN(x: BN, min: BN, max: BN): BN;
3
3
  export declare const squareRootBN: (n: BN) => BN;
4
4
  export declare const divCeil: (a: BN, b: BN) => BN;
5
+ export declare const sigNum: (x: BN) => BN;
5
6
  /**
6
7
  * calculates the time remaining until the next update based on a rounded, "on-the-hour" update schedule
7
8
  * this schedule is used for Perpetual Funding Rate and Revenue -> Insurance Updates
package/lib/math/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.timeRemainingUntilUpdate = exports.divCeil = exports.squareRootBN = exports.clampBN = void 0;
3
+ exports.timeRemainingUntilUpdate = exports.sigNum = exports.divCeil = exports.squareRootBN = exports.clampBN = void 0;
4
4
  const __1 = require("../");
5
5
  function clampBN(x, min, max) {
6
6
  return __1.BN.max(min, __1.BN.min(x, max));
@@ -34,6 +34,10 @@ const divCeil = (a, b) => {
34
34
  }
35
35
  };
36
36
  exports.divCeil = divCeil;
37
+ const sigNum = (x) => {
38
+ return x.isNeg() ? new __1.BN(-1) : new __1.BN(1);
39
+ };
40
+ exports.sigNum = sigNum;
37
41
  /**
38
42
  * calculates the time remaining until the next update based on a rounded, "on-the-hour" update schedule
39
43
  * this schedule is used for Perpetual Funding Rate and Revenue -> Insurance Updates
package/lib/user.d.ts CHANGED
@@ -42,6 +42,7 @@ export declare class User {
42
42
  * @returns userSpotPosition
43
43
  */
44
44
  getSpotPosition(marketIndex: number): SpotPosition | undefined;
45
+ getEmptySpotPosition(marketIndex: number): SpotPosition;
45
46
  /**
46
47
  * Returns the token amount for a given market. The spot market precision is based on the token mint decimals.
47
48
  * Positive if it is a deposit, negative if it is a borrow.
@@ -160,6 +161,12 @@ export declare class User {
160
161
  * @returns : Precision TEN_THOUSAND
161
162
  */
162
163
  getLeverage(): BN;
164
+ calculateLeverageFromComponents({ perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue, }: {
165
+ perpLiabilityValue: BN;
166
+ perpPnl: BN;
167
+ spotAssetValue: BN;
168
+ spotLiabilityValue: BN;
169
+ }): BN;
163
170
  getLeverageComponents(): {
164
171
  perpLiabilityValue: BN;
165
172
  perpPnl: BN;
@@ -252,6 +259,44 @@ export declare class User {
252
259
  * @returns tradeSizeAllowed : Precision QUOTE_PRECISION
253
260
  */
254
261
  getMaxTradeSizeUSDCForSpot(targetMarketIndex: number, direction: PositionDirection, currentQuoteAssetValue?: BN, currentSpotMarketNetValue?: BN): BN;
262
+ /**
263
+ * Calculates the max amount of token that can be swapped from inMarket to outMarket
264
+ * Assumes swap happens at oracle price
265
+ *
266
+ * @param inMarketIndex
267
+ * @param outMarketIndex
268
+ * @param calculateSwap function to similate in to out swa
269
+ * @param iterationLimit how long to run appromixation before erroring out
270
+ */
271
+ getMaxSwapAmount({ inMarketIndex, outMarketIndex, calculateSwap, iterationLimit, }: {
272
+ inMarketIndex: number;
273
+ outMarketIndex: number;
274
+ calculateSwap?: (inAmount: BN) => BN;
275
+ iterationLimit?: number;
276
+ }): {
277
+ inAmount: BN;
278
+ outAmount: BN;
279
+ leverage: BN;
280
+ };
281
+ cloneAndUpdateSpotPosition(position: SpotPosition, tokenAmount: BN, market: SpotMarketAccount): SpotPosition;
282
+ calculateSpotPositionFreeCollateralContribution(spotPosition: SpotPosition): BN;
283
+ calculateSpotPositionLeverageContribution(spotPosition: SpotPosition): {
284
+ totalAssetValue: BN;
285
+ totalLiabilityValue: BN;
286
+ };
287
+ /**
288
+ * Estimates what the user leverage will be after swap
289
+ * @param inMarketIndex
290
+ * @param outMarketIndex
291
+ * @param inAmount
292
+ * @param outAmount
293
+ */
294
+ accountLeverageAfterSwap({ inMarketIndex, outMarketIndex, inAmount, outAmount, }: {
295
+ inMarketIndex: number;
296
+ outMarketIndex: number;
297
+ inAmount: BN;
298
+ outAmount: BN;
299
+ }): BN;
255
300
  /**
256
301
  * Returns the leverage ratio for the account after adding (or subtracting) the given quote size to the given position
257
302
  * @param targetMarketIndex
package/lib/user.js CHANGED
@@ -80,6 +80,17 @@ class User {
80
80
  getSpotPosition(marketIndex) {
81
81
  return this.getUserAccount().spotPositions.find((position) => position.marketIndex === marketIndex);
82
82
  }
83
+ getEmptySpotPosition(marketIndex) {
84
+ return {
85
+ marketIndex,
86
+ scaledBalance: numericConstants_1.ZERO,
87
+ balanceType: _1.SpotBalanceType.DEPOSIT,
88
+ cumulativeDeposits: numericConstants_1.ZERO,
89
+ openAsks: numericConstants_1.ZERO,
90
+ openBids: numericConstants_1.ZERO,
91
+ openOrders: 0,
92
+ };
93
+ }
83
94
  /**
84
95
  * Returns the token amount for a given market. The spot market precision is based on the token mint decimals.
85
96
  * Positive if it is a deposit, negative if it is a borrow.
@@ -282,7 +293,7 @@ class User {
282
293
  * @returns : Precision QUOTE_PRECISION
283
294
  */
284
295
  getFreeCollateral() {
285
- const totalCollateral = this.getTotalCollateral();
296
+ const totalCollateral = this.getTotalCollateral('Initial', true);
286
297
  const initialMarginRequirement = this.getInitialMarginRequirement();
287
298
  const freeCollateral = totalCollateral.sub(initialMarginRequirement);
288
299
  return freeCollateral.gte(numericConstants_1.ZERO) ? freeCollateral : numericConstants_1.ZERO;
@@ -492,7 +503,8 @@ class User {
492
503
  return assetValue;
493
504
  }
494
505
  getSpotTokenAmount(marketIndex) {
495
- const spotPosition = this.getSpotPosition(marketIndex);
506
+ var _a;
507
+ const spotPosition = (_a = this.getSpotPosition(marketIndex)) !== null && _a !== void 0 ? _a : this.getEmptySpotPosition(marketIndex);
496
508
  return (0, spotBalance_1.getTokenAmount)(spotPosition.scaledBalance, this.driftClient.getSpotMarketAccount(marketIndex), spotPosition.balanceType);
497
509
  }
498
510
  getSpotPositionValue(marketIndex, marginCategory, includeOpenOrders, strict = false, now) {
@@ -671,8 +683,9 @@ class User {
671
683
  * @returns : Precision TEN_THOUSAND
672
684
  */
673
685
  getLeverage() {
674
- // get leverage components
675
- const { perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue } = this.getLeverageComponents();
686
+ return this.calculateLeverageFromComponents(this.getLeverageComponents());
687
+ }
688
+ calculateLeverageFromComponents({ perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue, }) {
676
689
  const totalLiabilityValue = perpLiabilityValue.add(spotLiabilityValue);
677
690
  const totalAssetValue = spotAssetValue.add(perpPnl);
678
691
  const netAssetValue = totalAssetValue.sub(spotLiabilityValue);
@@ -1089,6 +1102,216 @@ class User {
1089
1102
  }
1090
1103
  return tradeAmount;
1091
1104
  }
1105
+ /**
1106
+ * Calculates the max amount of token that can be swapped from inMarket to outMarket
1107
+ * Assumes swap happens at oracle price
1108
+ *
1109
+ * @param inMarketIndex
1110
+ * @param outMarketIndex
1111
+ * @param calculateSwap function to similate in to out swa
1112
+ * @param iterationLimit how long to run appromixation before erroring out
1113
+ */
1114
+ getMaxSwapAmount({ inMarketIndex, outMarketIndex, calculateSwap, iterationLimit = 1000, }) {
1115
+ const inMarket = this.driftClient.getSpotMarketAccount(inMarketIndex);
1116
+ const outMarket = this.driftClient.getSpotMarketAccount(outMarketIndex);
1117
+ const inOraclePrice = this.getOracleDataForSpotMarket(inMarketIndex).price;
1118
+ const outOraclePrice = this.getOracleDataForSpotMarket(outMarketIndex).price;
1119
+ const inPrecision = new _1.BN(10 ** inMarket.decimals);
1120
+ const outPrecision = new _1.BN(10 ** outMarket.decimals);
1121
+ const outSaferThanIn = inMarket.initialAssetWeight < outMarket.initialAssetWeight;
1122
+ const inSpotPosition = this.getSpotPosition(inMarketIndex) ||
1123
+ this.getEmptySpotPosition(inMarketIndex);
1124
+ const outSpotPosition = this.getSpotPosition(outMarketIndex) ||
1125
+ this.getEmptySpotPosition(outMarketIndex);
1126
+ const freeCollateral = this.getFreeCollateral();
1127
+ const inContributionInitial = this.calculateSpotPositionFreeCollateralContribution(inSpotPosition);
1128
+ const { totalAssetValue: inTotalAssetValueInitial, totalLiabilityValue: inTotalLiabilityValueInitial, } = this.calculateSpotPositionLeverageContribution(inSpotPosition);
1129
+ const outContributionInitial = this.calculateSpotPositionFreeCollateralContribution(outSpotPosition);
1130
+ const { totalAssetValue: outTotalAssetValueInitial, totalLiabilityValue: outTotalLiabilityValueInitial, } = this.calculateSpotPositionLeverageContribution(outSpotPosition);
1131
+ const initialContribution = inContributionInitial.add(outContributionInitial);
1132
+ const { perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue } = this.getLeverageComponents();
1133
+ if (!calculateSwap) {
1134
+ calculateSwap = (inSwap) => {
1135
+ return inSwap
1136
+ .mul(outPrecision)
1137
+ .mul(inOraclePrice)
1138
+ .div(outOraclePrice)
1139
+ .div(inPrecision);
1140
+ };
1141
+ }
1142
+ let inSwap = numericConstants_1.ZERO;
1143
+ let outSwap = numericConstants_1.ZERO;
1144
+ const inTokenAmount = this.getSpotTokenAmount(inMarketIndex);
1145
+ if (freeCollateral.lt(numericConstants_1.ONE)) {
1146
+ if (outSaferThanIn) {
1147
+ inSwap = inTokenAmount;
1148
+ outSwap = calculateSwap(inSwap);
1149
+ }
1150
+ }
1151
+ else {
1152
+ let minSwap = numericConstants_1.ZERO;
1153
+ let maxSwap = freeCollateral
1154
+ .mul(inPrecision)
1155
+ .mul(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION)
1156
+ .div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION.div(new _1.BN(100)))
1157
+ .div(inOraclePrice); // just assume user can go 100x
1158
+ inSwap = maxSwap.div(numericConstants_1.TWO);
1159
+ const error = _1.BN.min(numericConstants_1.QUOTE_PRECISION, freeCollateral.div(new _1.BN(100)));
1160
+ let i = 0;
1161
+ let freeCollateralAfter = freeCollateral;
1162
+ while (freeCollateralAfter.gt(error) || freeCollateralAfter.isNeg()) {
1163
+ outSwap = calculateSwap(inSwap);
1164
+ const inPositionAfter = this.cloneAndUpdateSpotPosition(inSpotPosition, inSwap.neg(), inMarket);
1165
+ const outPositionAfter = this.cloneAndUpdateSpotPosition(outSpotPosition, outSwap, outMarket);
1166
+ const inContributionAfter = this.calculateSpotPositionFreeCollateralContribution(inPositionAfter);
1167
+ const outContributionAfter = this.calculateSpotPositionFreeCollateralContribution(outPositionAfter);
1168
+ const contributionAfter = inContributionAfter.add(outContributionAfter);
1169
+ const contributionDelta = contributionAfter.sub(initialContribution);
1170
+ freeCollateralAfter = freeCollateral.add(contributionDelta);
1171
+ if (freeCollateralAfter.gt(error)) {
1172
+ minSwap = inSwap;
1173
+ inSwap = minSwap.add(maxSwap).div(numericConstants_1.TWO);
1174
+ }
1175
+ else if (freeCollateralAfter.isNeg()) {
1176
+ maxSwap = inSwap;
1177
+ inSwap = minSwap.add(maxSwap).div(numericConstants_1.TWO);
1178
+ }
1179
+ if (i++ > iterationLimit) {
1180
+ throw new Error('getMaxSwapAmount iteration limit reached');
1181
+ }
1182
+ }
1183
+ }
1184
+ const inPositionAfter = this.cloneAndUpdateSpotPosition(inSpotPosition, inSwap.neg(), inMarket);
1185
+ const outPositionAfter = this.cloneAndUpdateSpotPosition(outSpotPosition, outSwap, outMarket);
1186
+ const { totalAssetValue: inTotalAssetValueAfter, totalLiabilityValue: inTotalLiabilityValueAfter, } = this.calculateSpotPositionLeverageContribution(inPositionAfter);
1187
+ const { totalAssetValue: outTotalAssetValueAfter, totalLiabilityValue: outTotalLiabilityValueAfter, } = this.calculateSpotPositionLeverageContribution(outPositionAfter);
1188
+ const spotAssetValueDelta = inTotalAssetValueAfter
1189
+ .add(outTotalAssetValueAfter)
1190
+ .sub(inTotalAssetValueInitial)
1191
+ .sub(outTotalAssetValueInitial);
1192
+ const spotLiabilityValueDelta = inTotalLiabilityValueAfter
1193
+ .add(outTotalLiabilityValueAfter)
1194
+ .sub(inTotalLiabilityValueInitial)
1195
+ .sub(outTotalLiabilityValueInitial);
1196
+ const spotAssetValueAfter = spotAssetValue.add(spotAssetValueDelta);
1197
+ const spotLiabilityValueAfter = spotLiabilityValue.add(spotLiabilityValueDelta);
1198
+ const leverage = this.calculateLeverageFromComponents({
1199
+ perpLiabilityValue,
1200
+ perpPnl,
1201
+ spotAssetValue: spotAssetValueAfter,
1202
+ spotLiabilityValue: spotLiabilityValueAfter,
1203
+ });
1204
+ return { inAmount: inSwap, outAmount: outSwap, leverage };
1205
+ }
1206
+ cloneAndUpdateSpotPosition(position, tokenAmount, market) {
1207
+ const clonedPosition = Object.assign({}, position);
1208
+ if (tokenAmount.eq(numericConstants_1.ZERO)) {
1209
+ return clonedPosition;
1210
+ }
1211
+ const preTokenAmount = (0, _1.getSignedTokenAmount)((0, spotBalance_1.getTokenAmount)(position.scaledBalance, market, position.balanceType), position.balanceType);
1212
+ if ((0, _1.sigNum)(preTokenAmount).eq((0, _1.sigNum)(tokenAmount))) {
1213
+ const scaledBalanceDelta = (0, _1.getBalance)(tokenAmount.abs(), market, position.balanceType);
1214
+ clonedPosition.scaledBalance =
1215
+ clonedPosition.scaledBalance.add(scaledBalanceDelta);
1216
+ return clonedPosition;
1217
+ }
1218
+ const updateDirection = tokenAmount.isNeg()
1219
+ ? _1.SpotBalanceType.BORROW
1220
+ : _1.SpotBalanceType.DEPOSIT;
1221
+ if (tokenAmount.abs().gte(preTokenAmount.abs())) {
1222
+ clonedPosition.scaledBalance = (0, _1.getBalance)(tokenAmount.abs().sub(preTokenAmount.abs()), market, updateDirection);
1223
+ clonedPosition.balanceType = updateDirection;
1224
+ }
1225
+ else {
1226
+ const scaledBalanceDelta = (0, _1.getBalance)(tokenAmount.abs(), market, position.balanceType);
1227
+ clonedPosition.scaledBalance =
1228
+ clonedPosition.scaledBalance.sub(scaledBalanceDelta);
1229
+ }
1230
+ return clonedPosition;
1231
+ }
1232
+ calculateSpotPositionFreeCollateralContribution(spotPosition) {
1233
+ let freeCollateralContribution = numericConstants_1.ZERO;
1234
+ const now = new _1.BN(new Date().getTime() / 1000);
1235
+ const strict = true;
1236
+ const marginCategory = 'Initial';
1237
+ const spotMarketAccount = this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
1238
+ const oraclePriceData = this.getOracleDataForSpotMarket(spotPosition.marketIndex);
1239
+ const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = (0, spotPosition_1.getWorstCaseTokenAmounts)(spotPosition, spotMarketAccount, oraclePriceData);
1240
+ if (worstCaseTokenAmount.gt(numericConstants_1.ZERO)) {
1241
+ const baseAssetValue = this.getSpotAssetValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, marginCategory, strict, now);
1242
+ freeCollateralContribution =
1243
+ freeCollateralContribution.add(baseAssetValue);
1244
+ }
1245
+ else {
1246
+ const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, marginCategory, undefined, strict, now).abs();
1247
+ freeCollateralContribution =
1248
+ freeCollateralContribution.sub(baseLiabilityValue);
1249
+ }
1250
+ freeCollateralContribution.add(worstCaseQuoteTokenAmount);
1251
+ return freeCollateralContribution;
1252
+ }
1253
+ calculateSpotPositionLeverageContribution(spotPosition) {
1254
+ let totalAssetValue = numericConstants_1.ZERO;
1255
+ let totalLiabilityValue = numericConstants_1.ZERO;
1256
+ const now = new _1.BN(new Date().getTime() / 1000);
1257
+ const spotMarketAccount = this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
1258
+ const oraclePriceData = this.getOracleDataForSpotMarket(spotPosition.marketIndex);
1259
+ const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = (0, spotPosition_1.getWorstCaseTokenAmounts)(spotPosition, spotMarketAccount, oraclePriceData);
1260
+ if (worstCaseTokenAmount.gt(numericConstants_1.ZERO)) {
1261
+ totalAssetValue = this.getSpotAssetValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, undefined, false, now);
1262
+ }
1263
+ else {
1264
+ totalLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, undefined, undefined, false, now).abs();
1265
+ }
1266
+ if (worstCaseQuoteTokenAmount.gt(numericConstants_1.ZERO)) {
1267
+ totalAssetValue = totalAssetValue.add(worstCaseQuoteTokenAmount);
1268
+ }
1269
+ else {
1270
+ totalLiabilityValue = totalLiabilityValue.add(worstCaseQuoteTokenAmount.abs());
1271
+ }
1272
+ return {
1273
+ totalAssetValue,
1274
+ totalLiabilityValue,
1275
+ };
1276
+ }
1277
+ /**
1278
+ * Estimates what the user leverage will be after swap
1279
+ * @param inMarketIndex
1280
+ * @param outMarketIndex
1281
+ * @param inAmount
1282
+ * @param outAmount
1283
+ */
1284
+ accountLeverageAfterSwap({ inMarketIndex, outMarketIndex, inAmount, outAmount, }) {
1285
+ const inMarket = this.driftClient.getSpotMarketAccount(inMarketIndex);
1286
+ const outMarket = this.driftClient.getSpotMarketAccount(outMarketIndex);
1287
+ const inSpotPosition = this.getSpotPosition(inMarketIndex) ||
1288
+ this.getEmptySpotPosition(inMarketIndex);
1289
+ const outSpotPosition = this.getSpotPosition(outMarketIndex) ||
1290
+ this.getEmptySpotPosition(outMarketIndex);
1291
+ const { totalAssetValue: inTotalAssetValueInitial, totalLiabilityValue: inTotalLiabilityValueInitial, } = this.calculateSpotPositionLeverageContribution(inSpotPosition);
1292
+ const { totalAssetValue: outTotalAssetValueInitial, totalLiabilityValue: outTotalLiabilityValueInitial, } = this.calculateSpotPositionLeverageContribution(outSpotPosition);
1293
+ const { perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue } = this.getLeverageComponents();
1294
+ const inPositionAfter = this.cloneAndUpdateSpotPosition(inSpotPosition, inAmount.abs().neg(), inMarket);
1295
+ const outPositionAfter = this.cloneAndUpdateSpotPosition(outSpotPosition, outAmount.abs(), outMarket);
1296
+ const { totalAssetValue: inTotalAssetValueAfter, totalLiabilityValue: inTotalLiabilityValueAfter, } = this.calculateSpotPositionLeverageContribution(inPositionAfter);
1297
+ const { totalAssetValue: outTotalAssetValueAfter, totalLiabilityValue: outTotalLiabilityValueAfter, } = this.calculateSpotPositionLeverageContribution(outPositionAfter);
1298
+ const spotAssetValueDelta = inTotalAssetValueAfter
1299
+ .add(outTotalAssetValueAfter)
1300
+ .sub(inTotalAssetValueInitial)
1301
+ .sub(outTotalAssetValueInitial);
1302
+ const spotLiabilityValueDelta = inTotalLiabilityValueAfter
1303
+ .add(outTotalLiabilityValueAfter)
1304
+ .sub(inTotalLiabilityValueInitial)
1305
+ .sub(outTotalLiabilityValueInitial);
1306
+ const spotAssetValueAfter = spotAssetValue.add(spotAssetValueDelta);
1307
+ const spotLiabilityValueAfter = spotLiabilityValue.add(spotLiabilityValueDelta);
1308
+ return this.calculateLeverageFromComponents({
1309
+ perpLiabilityValue,
1310
+ perpPnl,
1311
+ spotAssetValue: spotAssetValueAfter,
1312
+ spotLiabilityValue: spotLiabilityValueAfter,
1313
+ });
1314
+ }
1092
1315
  // TODO - should this take the price impact of the trade into account for strict accuracy?
1093
1316
  /**
1094
1317
  * Returns the leverage ratio for the account after adding (or subtracting) the given quote size to the given position
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.31.1-beta.23",
3
+ "version": "2.31.1-beta.24",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.31.1-beta.23",
2
+ "version": "2.31.1-beta.24",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
package/src/math/utils.ts CHANGED
@@ -34,6 +34,10 @@ export const divCeil = (a: BN, b: BN): BN => {
34
34
  }
35
35
  };
36
36
 
37
+ export const sigNum = (x: BN): BN => {
38
+ return x.isNeg() ? new BN(-1) : new BN(1);
39
+ };
40
+
37
41
  /**
38
42
  * calculates the time remaining until the next update based on a rounded, "on-the-hour" update schedule
39
43
  * this schedule is used for Perpetual Funding Rate and Revenue -> Insurance Updates
package/src/user.ts CHANGED
@@ -28,6 +28,8 @@ import {
28
28
  OPEN_ORDER_MARGIN_REQUIREMENT,
29
29
  FIVE_MINUTE,
30
30
  BASE_PRECISION,
31
+ ONE,
32
+ TWO,
31
33
  } from './constants/numericConstants';
32
34
  import {
33
35
  UserAccountSubscriber,
@@ -50,6 +52,8 @@ import {
50
52
  calculateSpotMarketMarginRatio,
51
53
  getSignedTokenAmount,
52
54
  SpotBalanceType,
55
+ sigNum,
56
+ getBalance,
53
57
  } from '.';
54
58
  import {
55
59
  getTokenAmount,
@@ -165,6 +169,18 @@ export class User {
165
169
  );
166
170
  }
167
171
 
172
+ getEmptySpotPosition(marketIndex: number): SpotPosition {
173
+ return {
174
+ marketIndex,
175
+ scaledBalance: ZERO,
176
+ balanceType: SpotBalanceType.DEPOSIT,
177
+ cumulativeDeposits: ZERO,
178
+ openAsks: ZERO,
179
+ openBids: ZERO,
180
+ openOrders: 0,
181
+ };
182
+ }
183
+
168
184
  /**
169
185
  * Returns the token amount for a given market. The spot market precision is based on the token mint decimals.
170
186
  * Positive if it is a deposit, negative if it is a borrow.
@@ -442,7 +458,7 @@ export class User {
442
458
  * @returns : Precision QUOTE_PRECISION
443
459
  */
444
460
  public getFreeCollateral(): BN {
445
- const totalCollateral = this.getTotalCollateral();
461
+ const totalCollateral = this.getTotalCollateral('Initial', true);
446
462
  const initialMarginRequirement = this.getInitialMarginRequirement();
447
463
  const freeCollateral = totalCollateral.sub(initialMarginRequirement);
448
464
  return freeCollateral.gte(ZERO) ? freeCollateral : ZERO;
@@ -919,7 +935,9 @@ export class User {
919
935
  }
920
936
 
921
937
  public getSpotTokenAmount(marketIndex: number): BN {
922
- const spotPosition = this.getSpotPosition(marketIndex);
938
+ const spotPosition =
939
+ this.getSpotPosition(marketIndex) ??
940
+ this.getEmptySpotPosition(marketIndex);
923
941
  return getTokenAmount(
924
942
  spotPosition.scaledBalance,
925
943
  this.driftClient.getSpotMarketAccount(marketIndex),
@@ -1231,10 +1249,20 @@ export class User {
1231
1249
  * @returns : Precision TEN_THOUSAND
1232
1250
  */
1233
1251
  public getLeverage(): BN {
1234
- // get leverage components
1235
- const { perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue } =
1236
- this.getLeverageComponents();
1252
+ return this.calculateLeverageFromComponents(this.getLeverageComponents());
1253
+ }
1237
1254
 
1255
+ calculateLeverageFromComponents({
1256
+ perpLiabilityValue,
1257
+ perpPnl,
1258
+ spotAssetValue,
1259
+ spotLiabilityValue,
1260
+ }: {
1261
+ perpLiabilityValue: BN;
1262
+ perpPnl: BN;
1263
+ spotAssetValue: BN;
1264
+ spotLiabilityValue: BN;
1265
+ }): BN {
1238
1266
  const totalLiabilityValue = perpLiabilityValue.add(spotLiabilityValue);
1239
1267
  const totalAssetValue = spotAssetValue.add(perpPnl);
1240
1268
  const netAssetValue = totalAssetValue.sub(spotLiabilityValue);
@@ -1962,6 +1990,427 @@ export class User {
1962
1990
  return tradeAmount;
1963
1991
  }
1964
1992
 
1993
+ /**
1994
+ * Calculates the max amount of token that can be swapped from inMarket to outMarket
1995
+ * Assumes swap happens at oracle price
1996
+ *
1997
+ * @param inMarketIndex
1998
+ * @param outMarketIndex
1999
+ * @param calculateSwap function to similate in to out swa
2000
+ * @param iterationLimit how long to run appromixation before erroring out
2001
+ */
2002
+ public getMaxSwapAmount({
2003
+ inMarketIndex,
2004
+ outMarketIndex,
2005
+ calculateSwap,
2006
+ iterationLimit = 1000,
2007
+ }: {
2008
+ inMarketIndex: number;
2009
+ outMarketIndex: number;
2010
+ calculateSwap?: (inAmount: BN) => BN;
2011
+ iterationLimit?: number;
2012
+ }): { inAmount: BN; outAmount: BN; leverage: BN } {
2013
+ const inMarket = this.driftClient.getSpotMarketAccount(inMarketIndex);
2014
+ const outMarket = this.driftClient.getSpotMarketAccount(outMarketIndex);
2015
+
2016
+ const inOraclePrice = this.getOracleDataForSpotMarket(inMarketIndex).price;
2017
+ const outOraclePrice =
2018
+ this.getOracleDataForSpotMarket(outMarketIndex).price;
2019
+
2020
+ const inPrecision = new BN(10 ** inMarket.decimals);
2021
+ const outPrecision = new BN(10 ** outMarket.decimals);
2022
+
2023
+ const outSaferThanIn =
2024
+ inMarket.initialAssetWeight < outMarket.initialAssetWeight;
2025
+
2026
+ const inSpotPosition =
2027
+ this.getSpotPosition(inMarketIndex) ||
2028
+ this.getEmptySpotPosition(inMarketIndex);
2029
+ const outSpotPosition =
2030
+ this.getSpotPosition(outMarketIndex) ||
2031
+ this.getEmptySpotPosition(outMarketIndex);
2032
+
2033
+ const freeCollateral = this.getFreeCollateral();
2034
+
2035
+ const inContributionInitial =
2036
+ this.calculateSpotPositionFreeCollateralContribution(inSpotPosition);
2037
+ const {
2038
+ totalAssetValue: inTotalAssetValueInitial,
2039
+ totalLiabilityValue: inTotalLiabilityValueInitial,
2040
+ } = this.calculateSpotPositionLeverageContribution(inSpotPosition);
2041
+ const outContributionInitial =
2042
+ this.calculateSpotPositionFreeCollateralContribution(outSpotPosition);
2043
+ const {
2044
+ totalAssetValue: outTotalAssetValueInitial,
2045
+ totalLiabilityValue: outTotalLiabilityValueInitial,
2046
+ } = this.calculateSpotPositionLeverageContribution(outSpotPosition);
2047
+ const initialContribution = inContributionInitial.add(
2048
+ outContributionInitial
2049
+ );
2050
+
2051
+ const { perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue } =
2052
+ this.getLeverageComponents();
2053
+
2054
+ if (!calculateSwap) {
2055
+ calculateSwap = (inSwap: BN) => {
2056
+ return inSwap
2057
+ .mul(outPrecision)
2058
+ .mul(inOraclePrice)
2059
+ .div(outOraclePrice)
2060
+ .div(inPrecision);
2061
+ };
2062
+ }
2063
+
2064
+ let inSwap = ZERO;
2065
+ let outSwap = ZERO;
2066
+ const inTokenAmount = this.getSpotTokenAmount(inMarketIndex);
2067
+ if (freeCollateral.lt(ONE)) {
2068
+ if (outSaferThanIn) {
2069
+ inSwap = inTokenAmount;
2070
+ outSwap = calculateSwap(inSwap);
2071
+ }
2072
+ } else {
2073
+ let minSwap = ZERO;
2074
+ let maxSwap = freeCollateral
2075
+ .mul(inPrecision)
2076
+ .mul(SPOT_MARKET_WEIGHT_PRECISION)
2077
+ .div(SPOT_MARKET_WEIGHT_PRECISION.div(new BN(100)))
2078
+ .div(inOraclePrice); // just assume user can go 100x
2079
+ inSwap = maxSwap.div(TWO);
2080
+ const error = BN.min(QUOTE_PRECISION, freeCollateral.div(new BN(100)));
2081
+
2082
+ let i = 0;
2083
+ let freeCollateralAfter = freeCollateral;
2084
+ while (freeCollateralAfter.gt(error) || freeCollateralAfter.isNeg()) {
2085
+ outSwap = calculateSwap(inSwap);
2086
+
2087
+ const inPositionAfter = this.cloneAndUpdateSpotPosition(
2088
+ inSpotPosition,
2089
+ inSwap.neg(),
2090
+ inMarket
2091
+ );
2092
+ const outPositionAfter = this.cloneAndUpdateSpotPosition(
2093
+ outSpotPosition,
2094
+ outSwap,
2095
+ outMarket
2096
+ );
2097
+
2098
+ const inContributionAfter =
2099
+ this.calculateSpotPositionFreeCollateralContribution(inPositionAfter);
2100
+ const outContributionAfter =
2101
+ this.calculateSpotPositionFreeCollateralContribution(
2102
+ outPositionAfter
2103
+ );
2104
+
2105
+ const contributionAfter = inContributionAfter.add(outContributionAfter);
2106
+
2107
+ const contributionDelta = contributionAfter.sub(initialContribution);
2108
+
2109
+ freeCollateralAfter = freeCollateral.add(contributionDelta);
2110
+
2111
+ if (freeCollateralAfter.gt(error)) {
2112
+ minSwap = inSwap;
2113
+ inSwap = minSwap.add(maxSwap).div(TWO);
2114
+ } else if (freeCollateralAfter.isNeg()) {
2115
+ maxSwap = inSwap;
2116
+ inSwap = minSwap.add(maxSwap).div(TWO);
2117
+ }
2118
+
2119
+ if (i++ > iterationLimit) {
2120
+ throw new Error('getMaxSwapAmount iteration limit reached');
2121
+ }
2122
+ }
2123
+ }
2124
+
2125
+ const inPositionAfter = this.cloneAndUpdateSpotPosition(
2126
+ inSpotPosition,
2127
+ inSwap.neg(),
2128
+ inMarket
2129
+ );
2130
+ const outPositionAfter = this.cloneAndUpdateSpotPosition(
2131
+ outSpotPosition,
2132
+ outSwap,
2133
+ outMarket
2134
+ );
2135
+
2136
+ const {
2137
+ totalAssetValue: inTotalAssetValueAfter,
2138
+ totalLiabilityValue: inTotalLiabilityValueAfter,
2139
+ } = this.calculateSpotPositionLeverageContribution(inPositionAfter);
2140
+
2141
+ const {
2142
+ totalAssetValue: outTotalAssetValueAfter,
2143
+ totalLiabilityValue: outTotalLiabilityValueAfter,
2144
+ } = this.calculateSpotPositionLeverageContribution(outPositionAfter);
2145
+
2146
+ const spotAssetValueDelta = inTotalAssetValueAfter
2147
+ .add(outTotalAssetValueAfter)
2148
+ .sub(inTotalAssetValueInitial)
2149
+ .sub(outTotalAssetValueInitial);
2150
+ const spotLiabilityValueDelta = inTotalLiabilityValueAfter
2151
+ .add(outTotalLiabilityValueAfter)
2152
+ .sub(inTotalLiabilityValueInitial)
2153
+ .sub(outTotalLiabilityValueInitial);
2154
+
2155
+ const spotAssetValueAfter = spotAssetValue.add(spotAssetValueDelta);
2156
+ const spotLiabilityValueAfter = spotLiabilityValue.add(
2157
+ spotLiabilityValueDelta
2158
+ );
2159
+
2160
+ const leverage = this.calculateLeverageFromComponents({
2161
+ perpLiabilityValue,
2162
+ perpPnl,
2163
+ spotAssetValue: spotAssetValueAfter,
2164
+ spotLiabilityValue: spotLiabilityValueAfter,
2165
+ });
2166
+
2167
+ return { inAmount: inSwap, outAmount: outSwap, leverage };
2168
+ }
2169
+
2170
+ cloneAndUpdateSpotPosition(
2171
+ position: SpotPosition,
2172
+ tokenAmount: BN,
2173
+ market: SpotMarketAccount
2174
+ ): SpotPosition {
2175
+ const clonedPosition = Object.assign({}, position);
2176
+ if (tokenAmount.eq(ZERO)) {
2177
+ return clonedPosition;
2178
+ }
2179
+
2180
+ const preTokenAmount = getSignedTokenAmount(
2181
+ getTokenAmount(position.scaledBalance, market, position.balanceType),
2182
+ position.balanceType
2183
+ );
2184
+
2185
+ if (sigNum(preTokenAmount).eq(sigNum(tokenAmount))) {
2186
+ const scaledBalanceDelta = getBalance(
2187
+ tokenAmount.abs(),
2188
+ market,
2189
+ position.balanceType
2190
+ );
2191
+ clonedPosition.scaledBalance =
2192
+ clonedPosition.scaledBalance.add(scaledBalanceDelta);
2193
+ return clonedPosition;
2194
+ }
2195
+
2196
+ const updateDirection = tokenAmount.isNeg()
2197
+ ? SpotBalanceType.BORROW
2198
+ : SpotBalanceType.DEPOSIT;
2199
+
2200
+ if (tokenAmount.abs().gte(preTokenAmount.abs())) {
2201
+ clonedPosition.scaledBalance = getBalance(
2202
+ tokenAmount.abs().sub(preTokenAmount.abs()),
2203
+ market,
2204
+ updateDirection
2205
+ );
2206
+ clonedPosition.balanceType = updateDirection;
2207
+ } else {
2208
+ const scaledBalanceDelta = getBalance(
2209
+ tokenAmount.abs(),
2210
+ market,
2211
+ position.balanceType
2212
+ );
2213
+
2214
+ clonedPosition.scaledBalance =
2215
+ clonedPosition.scaledBalance.sub(scaledBalanceDelta);
2216
+ }
2217
+ return clonedPosition;
2218
+ }
2219
+
2220
+ calculateSpotPositionFreeCollateralContribution(
2221
+ spotPosition: SpotPosition
2222
+ ): BN {
2223
+ let freeCollateralContribution = ZERO;
2224
+ const now = new BN(new Date().getTime() / 1000);
2225
+ const strict = true;
2226
+ const marginCategory = 'Initial';
2227
+
2228
+ const spotMarketAccount: SpotMarketAccount =
2229
+ this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
2230
+
2231
+ const oraclePriceData = this.getOracleDataForSpotMarket(
2232
+ spotPosition.marketIndex
2233
+ );
2234
+
2235
+ const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] =
2236
+ getWorstCaseTokenAmounts(
2237
+ spotPosition,
2238
+ spotMarketAccount,
2239
+ oraclePriceData
2240
+ );
2241
+
2242
+ if (worstCaseTokenAmount.gt(ZERO)) {
2243
+ const baseAssetValue = this.getSpotAssetValue(
2244
+ worstCaseTokenAmount,
2245
+ oraclePriceData,
2246
+ spotMarketAccount,
2247
+ marginCategory,
2248
+ strict,
2249
+ now
2250
+ );
2251
+
2252
+ freeCollateralContribution =
2253
+ freeCollateralContribution.add(baseAssetValue);
2254
+ } else {
2255
+ const baseLiabilityValue = this.getSpotLiabilityValue(
2256
+ worstCaseTokenAmount,
2257
+ oraclePriceData,
2258
+ spotMarketAccount,
2259
+ marginCategory,
2260
+ undefined,
2261
+ strict,
2262
+ now
2263
+ ).abs();
2264
+
2265
+ freeCollateralContribution =
2266
+ freeCollateralContribution.sub(baseLiabilityValue);
2267
+ }
2268
+
2269
+ freeCollateralContribution.add(worstCaseQuoteTokenAmount);
2270
+
2271
+ return freeCollateralContribution;
2272
+ }
2273
+
2274
+ calculateSpotPositionLeverageContribution(spotPosition: SpotPosition): {
2275
+ totalAssetValue: BN;
2276
+ totalLiabilityValue: BN;
2277
+ } {
2278
+ let totalAssetValue = ZERO;
2279
+ let totalLiabilityValue = ZERO;
2280
+ const now = new BN(new Date().getTime() / 1000);
2281
+
2282
+ const spotMarketAccount: SpotMarketAccount =
2283
+ this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
2284
+
2285
+ const oraclePriceData = this.getOracleDataForSpotMarket(
2286
+ spotPosition.marketIndex
2287
+ );
2288
+
2289
+ const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] =
2290
+ getWorstCaseTokenAmounts(
2291
+ spotPosition,
2292
+ spotMarketAccount,
2293
+ oraclePriceData
2294
+ );
2295
+
2296
+ if (worstCaseTokenAmount.gt(ZERO)) {
2297
+ totalAssetValue = this.getSpotAssetValue(
2298
+ worstCaseTokenAmount,
2299
+ oraclePriceData,
2300
+ spotMarketAccount,
2301
+ undefined,
2302
+ false,
2303
+ now
2304
+ );
2305
+ } else {
2306
+ totalLiabilityValue = this.getSpotLiabilityValue(
2307
+ worstCaseTokenAmount,
2308
+ oraclePriceData,
2309
+ spotMarketAccount,
2310
+ undefined,
2311
+ undefined,
2312
+ false,
2313
+ now
2314
+ ).abs();
2315
+ }
2316
+
2317
+ if (worstCaseQuoteTokenAmount.gt(ZERO)) {
2318
+ totalAssetValue = totalAssetValue.add(worstCaseQuoteTokenAmount);
2319
+ } else {
2320
+ totalLiabilityValue = totalLiabilityValue.add(
2321
+ worstCaseQuoteTokenAmount.abs()
2322
+ );
2323
+ }
2324
+
2325
+ return {
2326
+ totalAssetValue,
2327
+ totalLiabilityValue,
2328
+ };
2329
+ }
2330
+
2331
+ /**
2332
+ * Estimates what the user leverage will be after swap
2333
+ * @param inMarketIndex
2334
+ * @param outMarketIndex
2335
+ * @param inAmount
2336
+ * @param outAmount
2337
+ */
2338
+ public accountLeverageAfterSwap({
2339
+ inMarketIndex,
2340
+ outMarketIndex,
2341
+ inAmount,
2342
+ outAmount,
2343
+ }: {
2344
+ inMarketIndex: number;
2345
+ outMarketIndex: number;
2346
+ inAmount: BN;
2347
+ outAmount: BN;
2348
+ }): BN {
2349
+ const inMarket = this.driftClient.getSpotMarketAccount(inMarketIndex);
2350
+ const outMarket = this.driftClient.getSpotMarketAccount(outMarketIndex);
2351
+
2352
+ const inSpotPosition =
2353
+ this.getSpotPosition(inMarketIndex) ||
2354
+ this.getEmptySpotPosition(inMarketIndex);
2355
+ const outSpotPosition =
2356
+ this.getSpotPosition(outMarketIndex) ||
2357
+ this.getEmptySpotPosition(outMarketIndex);
2358
+
2359
+ const {
2360
+ totalAssetValue: inTotalAssetValueInitial,
2361
+ totalLiabilityValue: inTotalLiabilityValueInitial,
2362
+ } = this.calculateSpotPositionLeverageContribution(inSpotPosition);
2363
+ const {
2364
+ totalAssetValue: outTotalAssetValueInitial,
2365
+ totalLiabilityValue: outTotalLiabilityValueInitial,
2366
+ } = this.calculateSpotPositionLeverageContribution(outSpotPosition);
2367
+
2368
+ const { perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue } =
2369
+ this.getLeverageComponents();
2370
+
2371
+ const inPositionAfter = this.cloneAndUpdateSpotPosition(
2372
+ inSpotPosition,
2373
+ inAmount.abs().neg(),
2374
+ inMarket
2375
+ );
2376
+ const outPositionAfter = this.cloneAndUpdateSpotPosition(
2377
+ outSpotPosition,
2378
+ outAmount.abs(),
2379
+ outMarket
2380
+ );
2381
+
2382
+ const {
2383
+ totalAssetValue: inTotalAssetValueAfter,
2384
+ totalLiabilityValue: inTotalLiabilityValueAfter,
2385
+ } = this.calculateSpotPositionLeverageContribution(inPositionAfter);
2386
+
2387
+ const {
2388
+ totalAssetValue: outTotalAssetValueAfter,
2389
+ totalLiabilityValue: outTotalLiabilityValueAfter,
2390
+ } = this.calculateSpotPositionLeverageContribution(outPositionAfter);
2391
+
2392
+ const spotAssetValueDelta = inTotalAssetValueAfter
2393
+ .add(outTotalAssetValueAfter)
2394
+ .sub(inTotalAssetValueInitial)
2395
+ .sub(outTotalAssetValueInitial);
2396
+ const spotLiabilityValueDelta = inTotalLiabilityValueAfter
2397
+ .add(outTotalLiabilityValueAfter)
2398
+ .sub(inTotalLiabilityValueInitial)
2399
+ .sub(outTotalLiabilityValueInitial);
2400
+
2401
+ const spotAssetValueAfter = spotAssetValue.add(spotAssetValueDelta);
2402
+ const spotLiabilityValueAfter = spotLiabilityValue.add(
2403
+ spotLiabilityValueDelta
2404
+ );
2405
+
2406
+ return this.calculateLeverageFromComponents({
2407
+ perpLiabilityValue,
2408
+ perpPnl,
2409
+ spotAssetValue: spotAssetValueAfter,
2410
+ spotLiabilityValue: spotLiabilityValueAfter,
2411
+ });
2412
+ }
2413
+
1965
2414
  // TODO - should this take the price impact of the trade into account for strict accuracy?
1966
2415
 
1967
2416
  /**