@drift-labs/sdk 2.149.0-beta.0 → 2.149.0-beta.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.
@@ -0,0 +1,287 @@
1
+ import { BN } from '@coral-xyz/anchor';
2
+ import { MARGIN_PRECISION, ZERO } from './constants/numericConstants';
3
+ import { getVariant, isVariant, MarketType } from './types';
4
+
5
+ export type MarginCategory = 'Initial' | 'Maintenance' | 'Fill';
6
+
7
+ export type MarginCalculationMode =
8
+ | { type: 'Standard' }
9
+ | { type: 'Liquidation' };
10
+
11
+ export class MarketIdentifier {
12
+ marketType: MarketType;
13
+ marketIndex: number;
14
+
15
+ private constructor(marketType: MarketType, marketIndex: number) {
16
+ this.marketType = marketType;
17
+ this.marketIndex = marketIndex;
18
+ }
19
+
20
+ static spot(marketIndex: number): MarketIdentifier {
21
+ return new MarketIdentifier(MarketType.SPOT, marketIndex);
22
+ }
23
+
24
+ static perp(marketIndex: number): MarketIdentifier {
25
+ return new MarketIdentifier(MarketType.PERP, marketIndex);
26
+ }
27
+
28
+ equals(other: MarketIdentifier | undefined): boolean {
29
+ return (
30
+ !!other &&
31
+ isVariant(this.marketType, getVariant(other.marketType)) &&
32
+ this.marketIndex === other.marketIndex
33
+ );
34
+ }
35
+ }
36
+
37
+ export class MarginContext {
38
+ marginType: MarginCategory;
39
+ mode: MarginCalculationMode;
40
+ strict: boolean;
41
+ ignoreInvalidDepositOracles: boolean;
42
+ isolatedMarginBuffers: Map<number, BN>;
43
+ crossMarginBuffer: BN;
44
+
45
+ private constructor(marginType: MarginCategory) {
46
+ this.marginType = marginType;
47
+ this.mode = { type: 'Standard' };
48
+ this.strict = false;
49
+ this.ignoreInvalidDepositOracles = false;
50
+ this.isolatedMarginBuffers = new Map();
51
+ }
52
+
53
+ static standard(marginType: MarginCategory): MarginContext {
54
+ return new MarginContext(marginType);
55
+ }
56
+
57
+ static liquidation(
58
+ crossMarginBuffer: BN,
59
+ isolatedMarginBuffers: Map<number, BN>
60
+ ): MarginContext {
61
+ const ctx = new MarginContext('Maintenance');
62
+ ctx.mode = { type: 'Liquidation' };
63
+ ctx.crossMarginBuffer = crossMarginBuffer;
64
+ ctx.isolatedMarginBuffers = isolatedMarginBuffers;
65
+ return ctx;
66
+ }
67
+
68
+ strictMode(strict: boolean): this {
69
+ this.strict = strict;
70
+ return this;
71
+ }
72
+
73
+ ignoreInvalidDeposits(ignore: boolean): this {
74
+ this.ignoreInvalidDepositOracles = ignore;
75
+ return this;
76
+ }
77
+
78
+ setCrossMarginBuffer(crossMarginBuffer: BN): this {
79
+ this.crossMarginBuffer = crossMarginBuffer;
80
+ return this;
81
+ }
82
+ setIsolatedMarginBuffers(isolatedMarginBuffers: Map<number, BN>): this {
83
+ this.isolatedMarginBuffers = isolatedMarginBuffers;
84
+ return this;
85
+ }
86
+ setIsolatedMarginBuffer(marketIndex: number, isolatedMarginBuffer: BN): this {
87
+ this.isolatedMarginBuffers.set(marketIndex, isolatedMarginBuffer);
88
+ return this;
89
+ }
90
+ }
91
+
92
+ export class IsolatedMarginCalculation {
93
+ marginRequirement: BN;
94
+ totalCollateral: BN; // deposit + pnl
95
+ totalCollateralBuffer: BN;
96
+ marginRequirementPlusBuffer: BN;
97
+
98
+ constructor() {
99
+ this.marginRequirement = ZERO;
100
+ this.totalCollateral = ZERO;
101
+ this.totalCollateralBuffer = ZERO;
102
+ this.marginRequirementPlusBuffer = ZERO;
103
+ }
104
+
105
+ getTotalCollateralPlusBuffer(): BN {
106
+ return this.totalCollateral.add(this.totalCollateralBuffer);
107
+ }
108
+
109
+ meetsMarginRequirement(): boolean {
110
+ return this.totalCollateral.gte(this.marginRequirement);
111
+ }
112
+
113
+ meetsMarginRequirementWithBuffer(): boolean {
114
+ return this.getTotalCollateralPlusBuffer().gte(
115
+ this.marginRequirementPlusBuffer
116
+ );
117
+ }
118
+
119
+ marginShortage(): BN {
120
+ const shortage = this.marginRequirementPlusBuffer.sub(
121
+ this.getTotalCollateralPlusBuffer()
122
+ );
123
+ return shortage.isNeg() ? ZERO : shortage;
124
+ }
125
+ }
126
+
127
+ export class MarginCalculation {
128
+ context: MarginContext;
129
+ totalCollateral: BN;
130
+ totalCollateralBuffer: BN;
131
+ marginRequirement: BN;
132
+ marginRequirementPlusBuffer: BN;
133
+ isolatedMarginCalculations: Map<number, IsolatedMarginCalculation>;
134
+ allDepositOraclesValid: boolean;
135
+ allLiabilityOraclesValid: boolean;
136
+ withPerpIsolatedLiability: boolean;
137
+ withSpotIsolatedLiability: boolean;
138
+ totalPerpLiabilityValue: BN;
139
+ trackedMarketMarginRequirement: BN;
140
+ fuelDeposits: number;
141
+ fuelBorrows: number;
142
+ fuelPositions: number;
143
+
144
+ constructor(context: MarginContext) {
145
+ this.context = context;
146
+ this.totalCollateral = ZERO;
147
+ this.totalCollateralBuffer = ZERO;
148
+ this.marginRequirement = ZERO;
149
+ this.marginRequirementPlusBuffer = ZERO;
150
+ this.isolatedMarginCalculations = new Map();
151
+ this.allDepositOraclesValid = true;
152
+ this.allLiabilityOraclesValid = true;
153
+ this.withPerpIsolatedLiability = false;
154
+ this.withSpotIsolatedLiability = false;
155
+ this.totalPerpLiabilityValue = ZERO;
156
+ this.trackedMarketMarginRequirement = ZERO;
157
+ this.fuelDeposits = 0;
158
+ this.fuelBorrows = 0;
159
+ this.fuelPositions = 0;
160
+ }
161
+
162
+ addCrossMarginTotalCollateral(delta: BN): void {
163
+ const crossMarginBuffer = this.context.crossMarginBuffer;
164
+ this.totalCollateral = this.totalCollateral.add(delta);
165
+ if (crossMarginBuffer.gt(ZERO) && delta.isNeg()) {
166
+ this.totalCollateralBuffer = this.totalCollateralBuffer.add(
167
+ delta.mul(crossMarginBuffer).div(MARGIN_PRECISION)
168
+ );
169
+ }
170
+ }
171
+
172
+ addCrossMarginRequirement(marginRequirement: BN, liabilityValue: BN): void {
173
+ const crossMarginBuffer = this.context.crossMarginBuffer;
174
+ this.marginRequirement = this.marginRequirement.add(marginRequirement);
175
+ if (crossMarginBuffer.gt(ZERO)) {
176
+ this.marginRequirementPlusBuffer = this.marginRequirementPlusBuffer.add(
177
+ marginRequirement.add(
178
+ liabilityValue.mul(crossMarginBuffer).div(MARGIN_PRECISION)
179
+ )
180
+ );
181
+ }
182
+ }
183
+
184
+ addIsolatedMarginCalculation(
185
+ marketIndex: number,
186
+ depositValue: BN,
187
+ pnl: BN,
188
+ liabilityValue: BN,
189
+ marginRequirement: BN
190
+ ): void {
191
+ const totalCollateral = depositValue.add(pnl);
192
+ const isolatedMarginBuffer =
193
+ this.context.isolatedMarginBuffers.get(marketIndex) ?? ZERO;
194
+
195
+ const totalCollateralBuffer =
196
+ isolatedMarginBuffer.gt(ZERO) && pnl.isNeg()
197
+ ? pnl.mul(isolatedMarginBuffer).div(MARGIN_PRECISION)
198
+ : ZERO;
199
+
200
+ const marginRequirementPlusBuffer = isolatedMarginBuffer.gt(ZERO)
201
+ ? marginRequirement.add(
202
+ liabilityValue.mul(isolatedMarginBuffer).div(MARGIN_PRECISION)
203
+ )
204
+ : marginRequirement;
205
+
206
+ const iso = new IsolatedMarginCalculation();
207
+ iso.marginRequirement = marginRequirement;
208
+ iso.totalCollateral = totalCollateral;
209
+ iso.totalCollateralBuffer = totalCollateralBuffer;
210
+ iso.marginRequirementPlusBuffer = marginRequirementPlusBuffer;
211
+ this.isolatedMarginCalculations.set(marketIndex, iso);
212
+ }
213
+
214
+ addPerpLiabilityValue(perpLiabilityValue: BN): void {
215
+ this.totalPerpLiabilityValue =
216
+ this.totalPerpLiabilityValue.add(perpLiabilityValue);
217
+ }
218
+
219
+ updateAllDepositOraclesValid(valid: boolean): void {
220
+ this.allDepositOraclesValid = this.allDepositOraclesValid && valid;
221
+ }
222
+
223
+ updateAllLiabilityOraclesValid(valid: boolean): void {
224
+ this.allLiabilityOraclesValid = this.allLiabilityOraclesValid && valid;
225
+ }
226
+
227
+ updateWithSpotIsolatedLiability(isolated: boolean): void {
228
+ this.withSpotIsolatedLiability = this.withSpotIsolatedLiability || isolated;
229
+ }
230
+
231
+ updateWithPerpIsolatedLiability(isolated: boolean): void {
232
+ this.withPerpIsolatedLiability = this.withPerpIsolatedLiability || isolated;
233
+ }
234
+
235
+ getCrossTotalCollateralPlusBuffer(): BN {
236
+ return this.totalCollateral.add(this.totalCollateralBuffer);
237
+ }
238
+
239
+ meetsCrossMarginRequirement(): boolean {
240
+ return this.totalCollateral.gte(this.marginRequirement);
241
+ }
242
+
243
+ meetsCrossMarginRequirementWithBuffer(): boolean {
244
+ return this.getCrossTotalCollateralPlusBuffer().gte(
245
+ this.marginRequirementPlusBuffer
246
+ );
247
+ }
248
+
249
+ meetsMarginRequirement(): boolean {
250
+ if (!this.meetsCrossMarginRequirement()) return false;
251
+ for (const [, iso] of this.isolatedMarginCalculations) {
252
+ if (!iso.meetsMarginRequirement()) return false;
253
+ }
254
+ return true;
255
+ }
256
+
257
+ meetsMarginRequirementWithBuffer(): boolean {
258
+ if (!this.meetsCrossMarginRequirementWithBuffer()) return false;
259
+ for (const [, iso] of this.isolatedMarginCalculations) {
260
+ if (!iso.meetsMarginRequirementWithBuffer()) return false;
261
+ }
262
+ return true;
263
+ }
264
+
265
+ getCrossFreeCollateral(): BN {
266
+ const free = this.totalCollateral.sub(this.marginRequirement);
267
+ return free.isNeg() ? ZERO : free;
268
+ }
269
+
270
+ getIsolatedFreeCollateral(marketIndex: number): BN {
271
+ const iso = this.isolatedMarginCalculations.get(marketIndex);
272
+ if (!iso)
273
+ throw new Error('InvalidMarginCalculation: missing isolated calc');
274
+ const free = iso.totalCollateral.sub(iso.marginRequirement);
275
+ return free.isNeg() ? ZERO : free;
276
+ }
277
+
278
+ getIsolatedMarginCalculation(
279
+ marketIndex: number
280
+ ): IsolatedMarginCalculation | undefined {
281
+ return this.isolatedMarginCalculations.get(marketIndex);
282
+ }
283
+
284
+ hasIsolatedMarginCalculation(marketIndex: number): boolean {
285
+ return this.isolatedMarginCalculations.has(marketIndex);
286
+ }
287
+ }
@@ -165,12 +165,25 @@ export function calculateWorstCaseBaseAssetAmount(
165
165
  export function calculateWorstCasePerpLiabilityValue(
166
166
  perpPosition: PerpPosition,
167
167
  perpMarket: PerpMarketAccount,
168
- oraclePrice: BN
168
+ oraclePrice: BN,
169
+ includeOpenOrders = true
169
170
  ): { worstCaseBaseAssetAmount: BN; worstCaseLiabilityValue: BN } {
171
+ const isPredictionMarket = isVariant(perpMarket.contractType, 'prediction');
172
+
173
+ if (!includeOpenOrders) {
174
+ return {
175
+ worstCaseBaseAssetAmount: perpPosition.baseAssetAmount,
176
+ worstCaseLiabilityValue: calculatePerpLiabilityValue(
177
+ perpPosition.baseAssetAmount,
178
+ oraclePrice,
179
+ isPredictionMarket
180
+ ),
181
+ };
182
+ }
183
+
170
184
  const allBids = perpPosition.baseAssetAmount.add(perpPosition.openBids);
171
185
  const allAsks = perpPosition.baseAssetAmount.add(perpPosition.openAsks);
172
186
 
173
- const isPredictionMarket = isVariant(perpMarket.contractType, 'prediction');
174
187
  const allBidsLiabilityValue = calculatePerpLiabilityValue(
175
188
  allBids,
176
189
  oraclePrice,
@@ -33,7 +33,8 @@ export function getWorstCaseTokenAmounts(
33
33
  spotMarketAccount: SpotMarketAccount,
34
34
  strictOraclePrice: StrictOraclePrice,
35
35
  marginCategory: MarginCategory,
36
- customMarginRatio?: number
36
+ customMarginRatio?: number,
37
+ includeOpenOrders: boolean = true
37
38
  ): OrderFillSimulation {
38
39
  const tokenAmount = getSignedTokenAmount(
39
40
  getTokenAmount(
@@ -50,7 +51,10 @@ export function getWorstCaseTokenAmounts(
50
51
  strictOraclePrice
51
52
  );
52
53
 
53
- if (spotPosition.openBids.eq(ZERO) && spotPosition.openAsks.eq(ZERO)) {
54
+ if (
55
+ (spotPosition.openBids.eq(ZERO) && spotPosition.openAsks.eq(ZERO)) ||
56
+ !includeOpenOrders
57
+ ) {
54
58
  const { weight, weightedTokenValue } = calculateWeightedTokenValue(
55
59
  tokenAmount,
56
60
  tokenValue,
package/src/types.ts CHANGED
@@ -1141,6 +1141,12 @@ export type PerpPosition = {
1141
1141
  positionFlag: number;
1142
1142
  };
1143
1143
 
1144
+ export class PositionFlag {
1145
+ static readonly IsolatedPosition = 1;
1146
+ static readonly BeingLiquidated = 2;
1147
+ static readonly Bankruptcy = 4;
1148
+ }
1149
+
1144
1150
  export type UserStatsAccount = {
1145
1151
  numberOfSubAccounts: number;
1146
1152
  numberOfSubAccountsCreated: number;
@@ -1898,3 +1904,9 @@ export type CacheInfo = {
1898
1904
  export type AmmCache = {
1899
1905
  cache: CacheInfo[];
1900
1906
  };
1907
+
1908
+ export type AccountLiquidatableStatus = {
1909
+ canBeLiquidated: boolean;
1910
+ marginRequirement: BN;
1911
+ totalCollateral: BN;
1912
+ };