@drift-labs/sdk 2.13.0-beta.0 → 2.13.0-beta.2
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/lib/constants/numericConstants.d.ts +2 -0
- package/lib/constants/numericConstants.js +3 -1
- package/lib/driftClient.d.ts +9 -0
- package/lib/driftClient.js +49 -0
- package/lib/idl/drift.json +1 -1
- package/lib/math/margin.d.ts +1 -1
- package/lib/math/margin.js +7 -7
- package/lib/math/market.d.ts +5 -0
- package/lib/math/market.js +18 -1
- package/lib/math/oracles.d.ts +2 -2
- package/lib/math/oracles.js +17 -8
- package/lib/math/orders.js +1 -1
- package/lib/math/spotBalance.d.ts +5 -0
- package/lib/math/spotBalance.js +30 -3
- package/lib/math/trade.js +8 -2
- package/lib/math/utils.js +3 -0
- package/lib/user.d.ts +15 -8
- package/lib/user.js +146 -88
- package/package.json +1 -1
- package/src/constants/numericConstants.ts +2 -0
- package/src/driftClient.ts +64 -0
- package/src/idl/drift.json +1 -1
- package/src/math/margin.ts +9 -7
- package/src/math/market.ts +51 -0
- package/src/math/oracles.ts +27 -16
- package/src/math/orders.ts +2 -2
- package/src/math/spotBalance.ts +51 -3
- package/src/math/trade.ts +10 -2
- package/src/math/utils.ts +5 -1
- package/src/user.ts +321 -153
- package/tests/amm/test.ts +3 -2
package/src/user.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
QUOTE_SPOT_MARKET_INDEX,
|
|
27
27
|
TEN,
|
|
28
28
|
OPEN_ORDER_MARGIN_REQUIREMENT,
|
|
29
|
+
FIVE_MINUTE,
|
|
29
30
|
} from './constants/numericConstants';
|
|
30
31
|
import {
|
|
31
32
|
UserAccountSubscriber,
|
|
@@ -40,11 +41,10 @@ import {
|
|
|
40
41
|
calculateUnrealizedAssetWeight,
|
|
41
42
|
calculateMarketMarginRatio,
|
|
42
43
|
PositionDirection,
|
|
43
|
-
calculateTradeSlippage,
|
|
44
44
|
BN,
|
|
45
45
|
SpotMarketAccount,
|
|
46
46
|
getTokenValue,
|
|
47
|
-
|
|
47
|
+
getStrictTokenValue,
|
|
48
48
|
} from '.';
|
|
49
49
|
import {
|
|
50
50
|
getTokenAmount,
|
|
@@ -66,6 +66,8 @@ import {
|
|
|
66
66
|
isSpotPositionAvailable,
|
|
67
67
|
} from './math/spotPosition';
|
|
68
68
|
|
|
69
|
+
import { calculateLiveOracleTwap } from './math/oracles';
|
|
70
|
+
|
|
69
71
|
export class User {
|
|
70
72
|
driftClient: DriftClient;
|
|
71
73
|
userAccountPublicKey: PublicKey;
|
|
@@ -363,13 +365,36 @@ export class User {
|
|
|
363
365
|
}
|
|
364
366
|
|
|
365
367
|
/**
|
|
366
|
-
* calculates Buying Power =
|
|
368
|
+
* calculates Buying Power = free collateral / initial margin ratio
|
|
367
369
|
* @returns : Precision QUOTE_PRECISION
|
|
368
370
|
*/
|
|
369
371
|
public getBuyingPower(marketIndex: number): BN {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
372
|
+
const perpPosition = this.getPerpPosition(marketIndex);
|
|
373
|
+
const worstCaseBaseAssetAmount = perpPosition
|
|
374
|
+
? calculateWorstCaseBaseAssetAmount(perpPosition)
|
|
375
|
+
: ZERO;
|
|
376
|
+
|
|
377
|
+
const freeCollateral = this.getFreeCollateral();
|
|
378
|
+
|
|
379
|
+
return this.getBuyingPowerFromFreeCollateralAndBaseAssetAmount(
|
|
380
|
+
marketIndex,
|
|
381
|
+
freeCollateral,
|
|
382
|
+
worstCaseBaseAssetAmount
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
getBuyingPowerFromFreeCollateralAndBaseAssetAmount(
|
|
387
|
+
marketIndex: number,
|
|
388
|
+
freeCollateral: BN,
|
|
389
|
+
baseAssetAmount: BN
|
|
390
|
+
): BN {
|
|
391
|
+
const marginRatio = calculateMarketMarginRatio(
|
|
392
|
+
this.driftClient.getPerpMarketAccount(marketIndex),
|
|
393
|
+
baseAssetAmount,
|
|
394
|
+
'Initial'
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
return freeCollateral.mul(MARGIN_PRECISION).div(new BN(marginRatio));
|
|
373
398
|
}
|
|
374
399
|
|
|
375
400
|
/**
|
|
@@ -388,7 +413,8 @@ export class User {
|
|
|
388
413
|
*/
|
|
389
414
|
public getMarginRequirement(
|
|
390
415
|
marginCategory: MarginCategory,
|
|
391
|
-
liquidationBuffer?: BN
|
|
416
|
+
liquidationBuffer?: BN,
|
|
417
|
+
strict = false
|
|
392
418
|
): BN {
|
|
393
419
|
return this.getTotalPerpPositionValue(
|
|
394
420
|
marginCategory,
|
|
@@ -399,7 +425,8 @@ export class User {
|
|
|
399
425
|
undefined,
|
|
400
426
|
marginCategory,
|
|
401
427
|
liquidationBuffer,
|
|
402
|
-
true
|
|
428
|
+
true,
|
|
429
|
+
strict
|
|
403
430
|
)
|
|
404
431
|
);
|
|
405
432
|
}
|
|
@@ -408,7 +435,7 @@ export class User {
|
|
|
408
435
|
* @returns The initial margin requirement in USDC. : QUOTE_PRECISION
|
|
409
436
|
*/
|
|
410
437
|
public getInitialMarginRequirement(): BN {
|
|
411
|
-
return this.getMarginRequirement('Initial');
|
|
438
|
+
return this.getMarginRequirement('Initial', undefined, true);
|
|
412
439
|
}
|
|
413
440
|
|
|
414
441
|
/**
|
|
@@ -500,8 +527,11 @@ export class User {
|
|
|
500
527
|
marketIndex?: number,
|
|
501
528
|
marginCategory?: MarginCategory,
|
|
502
529
|
liquidationBuffer?: BN,
|
|
503
|
-
includeOpenOrders?: boolean
|
|
530
|
+
includeOpenOrders?: boolean,
|
|
531
|
+
strict = false,
|
|
532
|
+
now?: BN
|
|
504
533
|
): BN {
|
|
534
|
+
now = now || new BN(new Date().getTime() / 1000);
|
|
505
535
|
return this.getUserAccount().spotPositions.reduce(
|
|
506
536
|
(totalLiabilityValue, spotPosition) => {
|
|
507
537
|
if (
|
|
@@ -557,7 +587,9 @@ export class User {
|
|
|
557
587
|
oraclePriceData,
|
|
558
588
|
spotMarketAccount,
|
|
559
589
|
marginCategory,
|
|
560
|
-
liquidationBuffer
|
|
590
|
+
liquidationBuffer,
|
|
591
|
+
strict,
|
|
592
|
+
now
|
|
561
593
|
);
|
|
562
594
|
return totalLiabilityValue.add(liabilityValue);
|
|
563
595
|
} else {
|
|
@@ -579,7 +611,9 @@ export class User {
|
|
|
579
611
|
oraclePriceData,
|
|
580
612
|
spotMarketAccount,
|
|
581
613
|
marginCategory,
|
|
582
|
-
liquidationBuffer
|
|
614
|
+
liquidationBuffer,
|
|
615
|
+
strict,
|
|
616
|
+
now
|
|
583
617
|
);
|
|
584
618
|
|
|
585
619
|
newTotalLiabilityValue =
|
|
@@ -619,13 +653,32 @@ export class User {
|
|
|
619
653
|
oraclePriceData: OraclePriceData,
|
|
620
654
|
spotMarketAccount: SpotMarketAccount,
|
|
621
655
|
marginCategory?: MarginCategory,
|
|
622
|
-
liquidationBuffer?: BN
|
|
656
|
+
liquidationBuffer?: BN,
|
|
657
|
+
strict = false,
|
|
658
|
+
now?: BN
|
|
623
659
|
): BN {
|
|
624
|
-
let liabilityValue =
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
660
|
+
let liabilityValue = null;
|
|
661
|
+
|
|
662
|
+
if (strict && spotMarketAccount.marketIndex != QUOTE_SPOT_MARKET_INDEX) {
|
|
663
|
+
const estOracleTwap = calculateLiveOracleTwap(
|
|
664
|
+
spotMarketAccount.historicalOracleData,
|
|
665
|
+
oraclePriceData,
|
|
666
|
+
now,
|
|
667
|
+
FIVE_MINUTE // 5MIN
|
|
668
|
+
);
|
|
669
|
+
liabilityValue = getStrictTokenValue(
|
|
670
|
+
tokenAmount,
|
|
671
|
+
spotMarketAccount.decimals,
|
|
672
|
+
oraclePriceData,
|
|
673
|
+
estOracleTwap
|
|
674
|
+
);
|
|
675
|
+
} else {
|
|
676
|
+
liabilityValue = getTokenValue(
|
|
677
|
+
tokenAmount,
|
|
678
|
+
spotMarketAccount.decimals,
|
|
679
|
+
oraclePriceData
|
|
680
|
+
);
|
|
681
|
+
}
|
|
629
682
|
|
|
630
683
|
if (marginCategory !== undefined) {
|
|
631
684
|
let weight = calculateLiabilityWeight(
|
|
@@ -653,8 +706,11 @@ export class User {
|
|
|
653
706
|
public getSpotMarketAssetValue(
|
|
654
707
|
marketIndex?: number,
|
|
655
708
|
marginCategory?: MarginCategory,
|
|
656
|
-
includeOpenOrders?: boolean
|
|
709
|
+
includeOpenOrders?: boolean,
|
|
710
|
+
strict = false,
|
|
711
|
+
now?: BN
|
|
657
712
|
): BN {
|
|
713
|
+
now = now || new BN(new Date().getTime() / 1000);
|
|
658
714
|
return this.getUserAccount().spotPositions.reduce(
|
|
659
715
|
(totalAssetValue, spotPosition) => {
|
|
660
716
|
if (
|
|
@@ -698,7 +754,9 @@ export class User {
|
|
|
698
754
|
tokenAmount,
|
|
699
755
|
oraclePriceData,
|
|
700
756
|
spotMarketAccount,
|
|
701
|
-
marginCategory
|
|
757
|
+
marginCategory,
|
|
758
|
+
strict,
|
|
759
|
+
now
|
|
702
760
|
);
|
|
703
761
|
return totalAssetValue.add(assetValue);
|
|
704
762
|
} else {
|
|
@@ -719,7 +777,9 @@ export class User {
|
|
|
719
777
|
worstCaseTokenAmount,
|
|
720
778
|
oraclePriceData,
|
|
721
779
|
spotMarketAccount,
|
|
722
|
-
marginCategory
|
|
780
|
+
marginCategory,
|
|
781
|
+
strict,
|
|
782
|
+
now
|
|
723
783
|
);
|
|
724
784
|
|
|
725
785
|
newTotalAssetValue = newTotalAssetValue.add(baseAssetValue);
|
|
@@ -741,13 +801,31 @@ export class User {
|
|
|
741
801
|
tokenAmount: BN,
|
|
742
802
|
oraclePriceData: OraclePriceData,
|
|
743
803
|
spotMarketAccount: SpotMarketAccount,
|
|
744
|
-
marginCategory?: MarginCategory
|
|
804
|
+
marginCategory?: MarginCategory,
|
|
805
|
+
strict = false,
|
|
806
|
+
now?: BN
|
|
745
807
|
): BN {
|
|
746
|
-
let assetValue =
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
808
|
+
let assetValue = null;
|
|
809
|
+
if (strict && spotMarketAccount.marketIndex != QUOTE_SPOT_MARKET_INDEX) {
|
|
810
|
+
const estOracleTwap = calculateLiveOracleTwap(
|
|
811
|
+
spotMarketAccount.historicalOracleData,
|
|
812
|
+
oraclePriceData,
|
|
813
|
+
now,
|
|
814
|
+
FIVE_MINUTE // 5MIN
|
|
815
|
+
);
|
|
816
|
+
assetValue = getStrictTokenValue(
|
|
817
|
+
tokenAmount,
|
|
818
|
+
spotMarketAccount.decimals,
|
|
819
|
+
oraclePriceData,
|
|
820
|
+
estOracleTwap
|
|
821
|
+
);
|
|
822
|
+
} else {
|
|
823
|
+
assetValue = getTokenValue(
|
|
824
|
+
tokenAmount,
|
|
825
|
+
spotMarketAccount.decimals,
|
|
826
|
+
oraclePriceData
|
|
827
|
+
);
|
|
828
|
+
}
|
|
751
829
|
|
|
752
830
|
if (marginCategory !== undefined) {
|
|
753
831
|
const weight = calculateAssetWeight(
|
|
@@ -805,17 +883,12 @@ export class User {
|
|
|
805
883
|
} else if (totalCollateral.lte(ZERO)) {
|
|
806
884
|
health = 0;
|
|
807
885
|
} else {
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
const maintenanceRatio =
|
|
815
|
-
(maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
|
|
816
|
-
marginRatio;
|
|
817
|
-
|
|
818
|
-
const healthP1 = Math.max(0, (marginRatio - maintenanceRatio) * 100) + 1;
|
|
886
|
+
const healthP1 =
|
|
887
|
+
Math.max(
|
|
888
|
+
0,
|
|
889
|
+
(1 - maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
|
|
890
|
+
100
|
|
891
|
+
) + 1;
|
|
819
892
|
|
|
820
893
|
health = Math.min(1, Math.log(healthP1) / Math.log(100)) * 100;
|
|
821
894
|
if (health > 1) {
|
|
@@ -928,7 +1001,8 @@ export class User {
|
|
|
928
1001
|
*/
|
|
929
1002
|
public getPerpPositionValue(
|
|
930
1003
|
marketIndex: number,
|
|
931
|
-
oraclePriceData: OraclePriceData
|
|
1004
|
+
oraclePriceData: OraclePriceData,
|
|
1005
|
+
includeOpenOrders = false
|
|
932
1006
|
): BN {
|
|
933
1007
|
const userPosition =
|
|
934
1008
|
this.getPerpPosition(marketIndex) || this.getEmptyPosition(marketIndex);
|
|
@@ -938,7 +1012,8 @@ export class User {
|
|
|
938
1012
|
return calculateBaseAssetValueWithOracle(
|
|
939
1013
|
market,
|
|
940
1014
|
userPosition,
|
|
941
|
-
oraclePriceData
|
|
1015
|
+
oraclePriceData,
|
|
1016
|
+
includeOpenOrders
|
|
942
1017
|
);
|
|
943
1018
|
}
|
|
944
1019
|
|
|
@@ -1017,19 +1092,32 @@ export class User {
|
|
|
1017
1092
|
}
|
|
1018
1093
|
|
|
1019
1094
|
/**
|
|
1020
|
-
* calculates current user leverage
|
|
1095
|
+
* calculates current user leverage which is (total liability size) / (net asset value)
|
|
1021
1096
|
* @returns : Precision TEN_THOUSAND
|
|
1022
1097
|
*/
|
|
1023
1098
|
public getLeverage(): BN {
|
|
1024
|
-
const
|
|
1099
|
+
const totalPerpLiability = this.getTotalPerpPositionValue(
|
|
1100
|
+
undefined,
|
|
1101
|
+
undefined,
|
|
1102
|
+
true
|
|
1103
|
+
);
|
|
1104
|
+
const totalSpotLiability = this.getSpotMarketLiabilityValue(
|
|
1105
|
+
undefined,
|
|
1106
|
+
undefined,
|
|
1107
|
+
undefined,
|
|
1108
|
+
true
|
|
1109
|
+
);
|
|
1110
|
+
|
|
1111
|
+
const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
|
|
1025
1112
|
|
|
1026
1113
|
const totalAssetValue = this.getTotalAssetValue();
|
|
1114
|
+
const netAssetValue = totalAssetValue.sub(totalSpotLiability);
|
|
1027
1115
|
|
|
1028
|
-
if (
|
|
1116
|
+
if (netAssetValue.eq(ZERO)) {
|
|
1029
1117
|
return ZERO;
|
|
1030
1118
|
}
|
|
1031
1119
|
|
|
1032
|
-
return totalLiabilityValue.mul(TEN_THOUSAND).div(
|
|
1120
|
+
return totalLiabilityValue.mul(TEN_THOUSAND).div(netAssetValue);
|
|
1033
1121
|
}
|
|
1034
1122
|
|
|
1035
1123
|
getTotalLiabilityValue(marginCategory?: MarginCategory): BN {
|
|
@@ -1060,12 +1148,27 @@ export class User {
|
|
|
1060
1148
|
): BN {
|
|
1061
1149
|
const market = this.driftClient.getPerpMarketAccount(marketIndex);
|
|
1062
1150
|
|
|
1151
|
+
const totalPerpLiability = this.getTotalPerpPositionValue(
|
|
1152
|
+
undefined,
|
|
1153
|
+
undefined,
|
|
1154
|
+
true
|
|
1155
|
+
);
|
|
1156
|
+
const totalSpotLiability = this.getSpotMarketLiabilityValue(
|
|
1157
|
+
undefined,
|
|
1158
|
+
undefined,
|
|
1159
|
+
undefined,
|
|
1160
|
+
true
|
|
1161
|
+
);
|
|
1162
|
+
|
|
1063
1163
|
const totalAssetValue = this.getTotalAssetValue();
|
|
1064
|
-
|
|
1164
|
+
|
|
1165
|
+
const netAssetValue = totalAssetValue.sub(totalSpotLiability);
|
|
1166
|
+
|
|
1167
|
+
if (netAssetValue.eq(ZERO)) {
|
|
1065
1168
|
return ZERO;
|
|
1066
1169
|
}
|
|
1067
1170
|
|
|
1068
|
-
const totalLiabilityValue =
|
|
1171
|
+
const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
|
|
1069
1172
|
|
|
1070
1173
|
const marginRatio = calculateMarketMarginRatio(
|
|
1071
1174
|
market,
|
|
@@ -1083,7 +1186,7 @@ export class User {
|
|
|
1083
1186
|
return totalLiabilityValue
|
|
1084
1187
|
.add(additionalLiabilities)
|
|
1085
1188
|
.mul(TEN_THOUSAND)
|
|
1086
|
-
.div(
|
|
1189
|
+
.div(netAssetValue);
|
|
1087
1190
|
}
|
|
1088
1191
|
|
|
1089
1192
|
/**
|
|
@@ -1091,15 +1194,28 @@ export class User {
|
|
|
1091
1194
|
* @returns : Precision TEN_THOUSAND
|
|
1092
1195
|
*/
|
|
1093
1196
|
public getMarginRatio(marginCategory?: MarginCategory): BN {
|
|
1094
|
-
const
|
|
1197
|
+
const totalPerpLiability = this.getTotalPerpPositionValue(
|
|
1198
|
+
undefined,
|
|
1199
|
+
undefined,
|
|
1200
|
+
true
|
|
1201
|
+
);
|
|
1202
|
+
const totalSpotLiability = this.getSpotMarketLiabilityValue(
|
|
1203
|
+
undefined,
|
|
1204
|
+
undefined,
|
|
1205
|
+
undefined,
|
|
1206
|
+
true
|
|
1207
|
+
);
|
|
1208
|
+
|
|
1209
|
+
const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
|
|
1095
1210
|
|
|
1096
1211
|
if (totalLiabilityValue.eq(ZERO)) {
|
|
1097
1212
|
return BN_MAX;
|
|
1098
1213
|
}
|
|
1099
1214
|
|
|
1100
1215
|
const totalAssetValue = this.getTotalAssetValue(marginCategory);
|
|
1216
|
+
const netAssetValue = totalAssetValue.sub(totalSpotLiability);
|
|
1101
1217
|
|
|
1102
|
-
return
|
|
1218
|
+
return netAssetValue.mul(TEN_THOUSAND).div(totalLiabilityValue);
|
|
1103
1219
|
}
|
|
1104
1220
|
|
|
1105
1221
|
public canBeLiquidated(): boolean {
|
|
@@ -1246,11 +1362,16 @@ export class User {
|
|
|
1246
1362
|
for 10x long, BTC down $400:
|
|
1247
1363
|
3. (10k - 4k) / (100k - 4k) = 6k/96k => .0625 */
|
|
1248
1364
|
|
|
1249
|
-
const totalCollateral = this.getTotalCollateral();
|
|
1365
|
+
const totalCollateral = this.getTotalCollateral('Maintenance');
|
|
1250
1366
|
|
|
1251
1367
|
// calculate the total position value ignoring any value from the target market of the trade
|
|
1252
1368
|
const totalPositionValueExcludingTargetMarket =
|
|
1253
|
-
this.getTotalPerpPositionValueExcludingMarket(
|
|
1369
|
+
this.getTotalPerpPositionValueExcludingMarket(
|
|
1370
|
+
perpPosition.marketIndex,
|
|
1371
|
+
undefined,
|
|
1372
|
+
undefined,
|
|
1373
|
+
true
|
|
1374
|
+
);
|
|
1254
1375
|
|
|
1255
1376
|
const currentPerpPosition =
|
|
1256
1377
|
this.getPerpPosition(perpPosition.marketIndex) ||
|
|
@@ -1267,13 +1388,13 @@ export class User {
|
|
|
1267
1388
|
marketIndex: perpPosition.marketIndex,
|
|
1268
1389
|
baseAssetAmount: proposedBaseAssetAmount,
|
|
1269
1390
|
remainderBaseAssetAmount: 0,
|
|
1270
|
-
quoteAssetAmount:
|
|
1391
|
+
quoteAssetAmount: currentPerpPosition.quoteAssetAmount,
|
|
1271
1392
|
lastCumulativeFundingRate: ZERO,
|
|
1272
1393
|
quoteBreakEvenAmount: new BN(0),
|
|
1273
1394
|
quoteEntryAmount: new BN(0),
|
|
1274
1395
|
openOrders: 0,
|
|
1275
|
-
openBids:
|
|
1276
|
-
openAsks:
|
|
1396
|
+
openBids: currentPerpPosition.openBids,
|
|
1397
|
+
openAsks: currentPerpPosition.openAsks,
|
|
1277
1398
|
settledPnl: ZERO,
|
|
1278
1399
|
lpShares: ZERO,
|
|
1279
1400
|
lastBaseAssetAmountPerLp: ZERO,
|
|
@@ -1289,44 +1410,36 @@ export class User {
|
|
|
1289
1410
|
const proposedPerpPositionValue = calculateBaseAssetValueWithOracle(
|
|
1290
1411
|
market,
|
|
1291
1412
|
proposedPerpPosition,
|
|
1292
|
-
this.getOracleDataForPerpMarket(market.marketIndex)
|
|
1413
|
+
this.getOracleDataForPerpMarket(market.marketIndex),
|
|
1414
|
+
true
|
|
1293
1415
|
);
|
|
1294
1416
|
|
|
1295
1417
|
// total position value after trade
|
|
1296
1418
|
const totalPositionValueAfterTrade =
|
|
1297
1419
|
totalPositionValueExcludingTargetMarket.add(proposedPerpPositionValue);
|
|
1298
1420
|
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
.div(MARGIN_PRECISION);
|
|
1322
|
-
totalMarginRequirement = totalMarginRequirement.add(
|
|
1323
|
-
marketMarginRequirement
|
|
1324
|
-
);
|
|
1325
|
-
}
|
|
1326
|
-
return totalMarginRequirement;
|
|
1327
|
-
},
|
|
1328
|
-
ZERO
|
|
1329
|
-
);
|
|
1421
|
+
const marginRequirementOfAll = this.getMaintenanceMarginRequirement();
|
|
1422
|
+
const positionValue = calculateBaseAssetValueWithOracle(
|
|
1423
|
+
market,
|
|
1424
|
+
currentPerpPosition,
|
|
1425
|
+
this.getOracleDataForPerpMarket(market.marketIndex),
|
|
1426
|
+
true
|
|
1427
|
+
);
|
|
1428
|
+
const marginRequirementOfTargetMarket = positionValue
|
|
1429
|
+
.mul(
|
|
1430
|
+
new BN(
|
|
1431
|
+
calculateMarketMarginRatio(
|
|
1432
|
+
market,
|
|
1433
|
+
calculateWorstCaseBaseAssetAmount(currentPerpPosition).abs(),
|
|
1434
|
+
'Maintenance'
|
|
1435
|
+
)
|
|
1436
|
+
)
|
|
1437
|
+
)
|
|
1438
|
+
.div(MARGIN_PRECISION);
|
|
1439
|
+
|
|
1440
|
+
const marginRequirementExcludingTargetMarket = marginRequirementOfAll.sub(
|
|
1441
|
+
marginRequirementOfTargetMarket
|
|
1442
|
+
);
|
|
1330
1443
|
|
|
1331
1444
|
const freeCollateralExcludingTargetMarket = totalCollateral.sub(
|
|
1332
1445
|
marginRequirementExcludingTargetMarket
|
|
@@ -1340,70 +1453,64 @@ export class User {
|
|
|
1340
1453
|
return new BN(-1);
|
|
1341
1454
|
}
|
|
1342
1455
|
|
|
1343
|
-
const
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
)
|
|
1353
|
-
)
|
|
1456
|
+
const proposedWorstCastBaseAssetAmount =
|
|
1457
|
+
calculateWorstCaseBaseAssetAmount(proposedPerpPosition);
|
|
1458
|
+
const marginRequirementTargetMarket = proposedPerpPositionValue
|
|
1459
|
+
.mul(
|
|
1460
|
+
new BN(
|
|
1461
|
+
calculateMarketMarginRatio(
|
|
1462
|
+
market,
|
|
1463
|
+
proposedWorstCastBaseAssetAmount.abs(),
|
|
1464
|
+
'Maintenance'
|
|
1354
1465
|
)
|
|
1355
|
-
|
|
1356
|
-
)
|
|
1466
|
+
)
|
|
1467
|
+
)
|
|
1468
|
+
.div(MARGIN_PRECISION);
|
|
1469
|
+
|
|
1470
|
+
const marginRequirementAfterTrade =
|
|
1471
|
+
marginRequirementExcludingTargetMarket.add(marginRequirementTargetMarket);
|
|
1357
1472
|
const freeCollateralAfterTrade = totalCollateral.sub(
|
|
1358
1473
|
marginRequirementAfterTrade
|
|
1359
1474
|
);
|
|
1360
1475
|
|
|
1361
|
-
const
|
|
1362
|
-
|
|
1363
|
-
|
|
1476
|
+
const marketMaxMaintLeverage = new BN(
|
|
1477
|
+
TEN_THOUSAND.mul(TEN_THOUSAND).toNumber() /
|
|
1478
|
+
calculateMarketMarginRatio(
|
|
1479
|
+
market,
|
|
1480
|
+
proposedWorstCastBaseAssetAmount.abs(),
|
|
1481
|
+
'Maintenance'
|
|
1482
|
+
)
|
|
1364
1483
|
);
|
|
1365
1484
|
|
|
1366
1485
|
let priceDelta;
|
|
1367
1486
|
if (proposedBaseAssetAmount.lt(ZERO)) {
|
|
1368
1487
|
priceDelta = freeCollateralAfterTrade
|
|
1369
|
-
.mul(
|
|
1370
|
-
.div(
|
|
1488
|
+
.mul(marketMaxMaintLeverage) // precision is TEN_THOUSAND
|
|
1489
|
+
.div(marketMaxMaintLeverage.add(TEN_THOUSAND))
|
|
1371
1490
|
.mul(PRICE_TO_QUOTE_PRECISION)
|
|
1372
1491
|
.mul(AMM_RESERVE_PRECISION)
|
|
1373
1492
|
.div(proposedBaseAssetAmount);
|
|
1374
1493
|
} else {
|
|
1375
1494
|
priceDelta = freeCollateralAfterTrade
|
|
1376
|
-
.mul(
|
|
1377
|
-
.div(
|
|
1495
|
+
.mul(marketMaxMaintLeverage) // precision is TEN_THOUSAND
|
|
1496
|
+
.div(marketMaxMaintLeverage.sub(TEN_THOUSAND))
|
|
1378
1497
|
.mul(PRICE_TO_QUOTE_PRECISION)
|
|
1379
1498
|
.mul(AMM_RESERVE_PRECISION)
|
|
1380
1499
|
.div(proposedBaseAssetAmount);
|
|
1381
1500
|
}
|
|
1382
1501
|
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
this.driftClient.getPerpMarketAccount(perpPosition.marketIndex),
|
|
1387
|
-
this.getOracleDataForPerpMarket(perpPosition.marketIndex)
|
|
1388
|
-
);
|
|
1389
|
-
} else {
|
|
1390
|
-
const direction = positionBaseSizeChange.gt(ZERO)
|
|
1391
|
-
? PositionDirection.LONG
|
|
1392
|
-
: PositionDirection.SHORT;
|
|
1393
|
-
markPriceAfterTrade = calculateTradeSlippage(
|
|
1394
|
-
direction,
|
|
1395
|
-
positionBaseSizeChange.abs(),
|
|
1396
|
-
this.driftClient.getPerpMarketAccount(perpPosition.marketIndex),
|
|
1397
|
-
'base',
|
|
1398
|
-
this.getOracleDataForPerpMarket(perpPosition.marketIndex)
|
|
1399
|
-
)[3]; // newPrice after swap
|
|
1400
|
-
}
|
|
1502
|
+
const currentPrice = this.getOracleDataForPerpMarket(
|
|
1503
|
+
perpPosition.marketIndex
|
|
1504
|
+
).price;
|
|
1401
1505
|
|
|
1402
|
-
if (
|
|
1506
|
+
if (
|
|
1507
|
+
priceDelta.gt(currentPrice) &&
|
|
1508
|
+
proposedPerpPosition.baseAssetAmount.gte(ZERO)
|
|
1509
|
+
) {
|
|
1403
1510
|
return new BN(-1);
|
|
1404
1511
|
}
|
|
1405
1512
|
|
|
1406
|
-
return
|
|
1513
|
+
return currentPrice.sub(priceDelta);
|
|
1407
1514
|
}
|
|
1408
1515
|
|
|
1409
1516
|
/**
|
|
@@ -1518,9 +1625,12 @@ export class User {
|
|
|
1518
1625
|
const freeCollateralAfterClose = totalCollateral.sub(
|
|
1519
1626
|
marginRequirementAfterClosing
|
|
1520
1627
|
);
|
|
1521
|
-
const buyingPowerAfterClose =
|
|
1522
|
-
|
|
1523
|
-
|
|
1628
|
+
const buyingPowerAfterClose =
|
|
1629
|
+
this.getBuyingPowerFromFreeCollateralAndBaseAssetAmount(
|
|
1630
|
+
targetMarketIndex,
|
|
1631
|
+
freeCollateralAfterClose,
|
|
1632
|
+
ZERO
|
|
1633
|
+
);
|
|
1524
1634
|
maxPositionSize = perpPositionValue.add(buyingPowerAfterClose);
|
|
1525
1635
|
}
|
|
1526
1636
|
} else {
|
|
@@ -1557,7 +1667,8 @@ export class User {
|
|
|
1557
1667
|
|
|
1558
1668
|
let currentPositionQuoteAmount = this.getPerpPositionValue(
|
|
1559
1669
|
targetMarketIndex,
|
|
1560
|
-
oracleData
|
|
1670
|
+
oracleData,
|
|
1671
|
+
includeOpenOrders
|
|
1561
1672
|
);
|
|
1562
1673
|
|
|
1563
1674
|
const currentSide =
|
|
@@ -1585,21 +1696,29 @@ export class User {
|
|
|
1585
1696
|
|
|
1586
1697
|
const totalAssetValue = this.getTotalAssetValue();
|
|
1587
1698
|
|
|
1588
|
-
const
|
|
1699
|
+
const totalPerpPositionLiability = currentPerpPositionAfterTrade
|
|
1589
1700
|
.add(totalPositionAfterTradeExcludingTargetMarket)
|
|
1590
1701
|
.abs();
|
|
1591
1702
|
|
|
1592
|
-
const
|
|
1593
|
-
|
|
1703
|
+
const totalSpotLiability = this.getSpotMarketLiabilityValue(
|
|
1704
|
+
undefined,
|
|
1705
|
+
undefined,
|
|
1706
|
+
undefined,
|
|
1707
|
+
includeOpenOrders
|
|
1594
1708
|
);
|
|
1595
1709
|
|
|
1596
|
-
|
|
1710
|
+
const totalLiabilitiesAfterTrade =
|
|
1711
|
+
totalPerpPositionLiability.add(totalSpotLiability);
|
|
1712
|
+
|
|
1713
|
+
const netAssetValue = totalAssetValue.sub(totalSpotLiability);
|
|
1714
|
+
|
|
1715
|
+
if (netAssetValue.eq(ZERO)) {
|
|
1597
1716
|
return ZERO;
|
|
1598
1717
|
}
|
|
1599
1718
|
|
|
1600
1719
|
const newLeverage = totalLiabilitiesAfterTrade
|
|
1601
1720
|
.mul(TEN_THOUSAND)
|
|
1602
|
-
.div(
|
|
1721
|
+
.div(netAssetValue);
|
|
1603
1722
|
|
|
1604
1723
|
return newLeverage;
|
|
1605
1724
|
}
|
|
@@ -1627,7 +1746,8 @@ export class User {
|
|
|
1627
1746
|
const nowTs = new BN(Math.floor(Date.now() / 1000));
|
|
1628
1747
|
const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
|
|
1629
1748
|
|
|
1630
|
-
|
|
1749
|
+
// eslint-disable-next-line prefer-const
|
|
1750
|
+
let { borrowLimit, withdrawLimit } = calculateWithdrawLimit(
|
|
1631
1751
|
spotMarket,
|
|
1632
1752
|
nowTs
|
|
1633
1753
|
);
|
|
@@ -1636,6 +1756,12 @@ export class User {
|
|
|
1636
1756
|
const oracleData = this.getOracleDataForSpotMarket(marketIndex);
|
|
1637
1757
|
const precisionIncrease = TEN.pow(new BN(spotMarket.decimals - 6));
|
|
1638
1758
|
|
|
1759
|
+
const { canBypass, depositAmount: userDepositAmount } =
|
|
1760
|
+
this.canBypassWithdrawLimits(marketIndex);
|
|
1761
|
+
if (canBypass) {
|
|
1762
|
+
withdrawLimit = BN.max(withdrawLimit, userDepositAmount);
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1639
1765
|
const amountWithdrawable = freeCollateral
|
|
1640
1766
|
.mul(MARGIN_PRECISION)
|
|
1641
1767
|
.div(new BN(spotMarket.initialAssetWeight))
|
|
@@ -1643,22 +1769,8 @@ export class User {
|
|
|
1643
1769
|
.div(oracleData.price)
|
|
1644
1770
|
.mul(precisionIncrease);
|
|
1645
1771
|
|
|
1646
|
-
const userSpotPosition = this.getUserAccount().spotPositions.find(
|
|
1647
|
-
(spotPosition) =>
|
|
1648
|
-
isVariant(spotPosition.balanceType, 'deposit') &&
|
|
1649
|
-
spotPosition.marketIndex == marketIndex
|
|
1650
|
-
);
|
|
1651
|
-
|
|
1652
|
-
const userSpotBalance = userSpotPosition
|
|
1653
|
-
? getTokenAmount(
|
|
1654
|
-
userSpotPosition.scaledBalance,
|
|
1655
|
-
this.driftClient.getSpotMarketAccount(marketIndex),
|
|
1656
|
-
SpotBalanceType.DEPOSIT
|
|
1657
|
-
)
|
|
1658
|
-
: ZERO;
|
|
1659
|
-
|
|
1660
1772
|
const maxWithdrawValue = BN.min(
|
|
1661
|
-
BN.min(amountWithdrawable,
|
|
1773
|
+
BN.min(amountWithdrawable, userDepositAmount),
|
|
1662
1774
|
withdrawLimit.abs()
|
|
1663
1775
|
);
|
|
1664
1776
|
|
|
@@ -1671,7 +1783,7 @@ export class User {
|
|
|
1671
1783
|
false
|
|
1672
1784
|
);
|
|
1673
1785
|
|
|
1674
|
-
const freeCollatAfterWithdraw =
|
|
1786
|
+
const freeCollatAfterWithdraw = userDepositAmount.gt(ZERO)
|
|
1675
1787
|
? freeCollateral.sub(weightedAssetValue)
|
|
1676
1788
|
: freeCollateral;
|
|
1677
1789
|
|
|
@@ -1691,6 +1803,61 @@ export class User {
|
|
|
1691
1803
|
}
|
|
1692
1804
|
}
|
|
1693
1805
|
|
|
1806
|
+
public canBypassWithdrawLimits(marketIndex: number): {
|
|
1807
|
+
canBypass: boolean;
|
|
1808
|
+
netDeposits: BN;
|
|
1809
|
+
depositAmount: BN;
|
|
1810
|
+
maxDepositAmount: BN;
|
|
1811
|
+
} {
|
|
1812
|
+
const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
|
|
1813
|
+
const maxDepositAmount = spotMarket.withdrawGuardThreshold.div(new BN(10));
|
|
1814
|
+
const position = this.getSpotPosition(marketIndex);
|
|
1815
|
+
|
|
1816
|
+
const netDeposits = this.getUserAccount().totalDeposits.sub(
|
|
1817
|
+
this.getUserAccount().totalWithdraws
|
|
1818
|
+
);
|
|
1819
|
+
|
|
1820
|
+
if (!position) {
|
|
1821
|
+
return {
|
|
1822
|
+
canBypass: false,
|
|
1823
|
+
maxDepositAmount,
|
|
1824
|
+
depositAmount: ZERO,
|
|
1825
|
+
netDeposits,
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
if (isVariant(position.balanceType, 'borrow')) {
|
|
1830
|
+
return {
|
|
1831
|
+
canBypass: false,
|
|
1832
|
+
maxDepositAmount,
|
|
1833
|
+
netDeposits,
|
|
1834
|
+
depositAmount: ZERO,
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
const depositAmount = getTokenAmount(
|
|
1839
|
+
position.scaledBalance,
|
|
1840
|
+
spotMarket,
|
|
1841
|
+
'deposit'
|
|
1842
|
+
);
|
|
1843
|
+
|
|
1844
|
+
if (netDeposits.lt(ZERO)) {
|
|
1845
|
+
return {
|
|
1846
|
+
canBypass: false,
|
|
1847
|
+
maxDepositAmount,
|
|
1848
|
+
depositAmount: ZERO,
|
|
1849
|
+
netDeposits,
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
return {
|
|
1854
|
+
canBypass: depositAmount.lt(maxDepositAmount),
|
|
1855
|
+
maxDepositAmount,
|
|
1856
|
+
netDeposits,
|
|
1857
|
+
depositAmount,
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1694
1861
|
/**
|
|
1695
1862
|
* Get the total position value, excluding any position coming from the given target market
|
|
1696
1863
|
* @param marketToIgnore
|
|
@@ -1712,7 +1879,8 @@ export class User {
|
|
|
1712
1879
|
if (currentPerpPosition) {
|
|
1713
1880
|
currentPerpPositionValueUSDC = this.getPerpPositionValue(
|
|
1714
1881
|
marketToIgnore,
|
|
1715
|
-
oracleData
|
|
1882
|
+
oracleData,
|
|
1883
|
+
includeOpenOrders
|
|
1716
1884
|
);
|
|
1717
1885
|
}
|
|
1718
1886
|
|