@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.
- package/VERSION +1 -1
- package/lib/browser/marginCalculation.d.ts +79 -0
- package/lib/browser/marginCalculation.js +196 -0
- package/lib/browser/math/margin.d.ts +1 -1
- package/lib/browser/math/margin.js +8 -2
- package/lib/browser/math/spotPosition.d.ts +1 -1
- package/lib/browser/math/spotPosition.js +3 -2
- package/lib/browser/types.d.ts +10 -0
- package/lib/browser/types.js +7 -1
- package/lib/browser/user.d.ts +56 -16
- package/lib/browser/user.js +414 -46
- package/lib/node/marginCalculation.d.ts +80 -0
- package/lib/node/marginCalculation.d.ts.map +1 -0
- package/lib/node/marginCalculation.js +196 -0
- package/lib/node/math/margin.d.ts +1 -1
- package/lib/node/math/margin.d.ts.map +1 -1
- package/lib/node/math/margin.js +8 -2
- package/lib/node/math/spotPosition.d.ts +1 -1
- package/lib/node/math/spotPosition.d.ts.map +1 -1
- package/lib/node/math/spotPosition.js +3 -2
- package/lib/node/types.d.ts +10 -0
- package/lib/node/types.d.ts.map +1 -1
- package/lib/node/types.js +7 -1
- package/lib/node/user.d.ts +56 -16
- package/lib/node/user.d.ts.map +1 -1
- package/lib/node/user.js +414 -46
- package/package.json +2 -1
- package/src/marginCalculation.ts +287 -0
- package/src/math/margin.ts +15 -2
- package/src/math/spotPosition.ts +6 -2
- package/src/types.ts +12 -0
- package/src/user.ts +737 -87
- package/tests/dlob/helpers.ts +19 -0
- package/tests/user/getMarginCalculation.ts +361 -0
- package/tests/user/helpers.ts +96 -2
- package/tests/user/liquidations.ts +129 -0
|
@@ -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
|
+
}
|
package/src/math/margin.ts
CHANGED
|
@@ -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,
|
package/src/math/spotPosition.ts
CHANGED
|
@@ -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 (
|
|
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
|
+
};
|