@drift-labs/sdk 2.37.1-beta.1 → 2.37.1-beta.10
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/adminClient.d.ts +1 -0
- package/lib/adminClient.js +10 -0
- package/lib/constants/perpMarkets.js +20 -0
- package/lib/idl/drift.json +73 -8
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/math/auction.js +1 -1
- package/lib/orderSubscriber/OrderSubscriber.d.ts +5 -1
- package/lib/orderSubscriber/OrderSubscriber.js +10 -0
- package/lib/orderSubscriber/types.d.ts +5 -0
- package/lib/tx/baseTxSender.d.ts +30 -0
- package/lib/tx/baseTxSender.js +176 -0
- package/lib/tx/fastSingleTxSender.d.ts +27 -0
- package/lib/tx/fastSingleTxSender.js +83 -0
- package/lib/tx/retryTxSender.d.ts +5 -14
- package/lib/tx/retryTxSender.js +7 -158
- package/lib/types.d.ts +18 -0
- package/lib/types.js +1 -0
- package/lib/user.d.ts +10 -1
- package/lib/user.js +259 -61
- package/package.json +2 -2
- package/src/adminClient.ts +18 -0
- package/src/constants/perpMarkets.ts +20 -0
- package/src/idl/drift.json +73 -8
- package/src/index.ts +1 -0
- package/src/marinade/types.ts +70 -70
- package/src/math/auction.ts +1 -1
- package/src/orderSubscriber/OrderSubscriber.ts +19 -2
- package/src/orderSubscriber/types.ts +11 -0
- package/src/tx/baseTxSender.ts +276 -0
- package/src/tx/fastSingleTxSender.ts +142 -0
- package/src/tx/retryTxSender.ts +9 -235
- package/src/types.ts +19 -0
- package/src/user.ts +441 -101
- package/tests/amm/test.ts +83 -39
- package/tests/dlob/helpers.ts +2 -0
- package/tests/dlob/test.ts +19 -17
package/lib/tx/retryTxSender.js
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.RetryTxSender = void 0;
|
|
7
|
-
const web3_js_1 = require("@solana/web3.js");
|
|
8
4
|
const anchor_1 = require("@coral-xyz/anchor");
|
|
9
|
-
const
|
|
10
|
-
const bs58_1 = __importDefault(require("bs58"));
|
|
5
|
+
const baseTxSender_1 = require("./baseTxSender");
|
|
11
6
|
const DEFAULT_TIMEOUT = 35000;
|
|
12
7
|
const DEFAULT_RETRY = 8000;
|
|
13
|
-
class RetryTxSender {
|
|
8
|
+
class RetryTxSender extends baseTxSender_1.BaseTxSender {
|
|
14
9
|
constructor({ connection, wallet, opts = anchor_1.AnchorProvider.defaultOptions(), timeout = DEFAULT_TIMEOUT, retrySleep = DEFAULT_RETRY, additionalConnections = new Array(), }) {
|
|
10
|
+
super({ connection, wallet, opts, timeout, additionalConnections });
|
|
15
11
|
this.timoutCount = 0;
|
|
16
12
|
this.connection = connection;
|
|
17
13
|
this.wallet = wallet;
|
|
@@ -20,66 +16,11 @@ class RetryTxSender {
|
|
|
20
16
|
this.retrySleep = retrySleep;
|
|
21
17
|
this.additionalConnections = additionalConnections;
|
|
22
18
|
}
|
|
23
|
-
async
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (opts === undefined) {
|
|
28
|
-
opts = this.opts;
|
|
29
|
-
}
|
|
30
|
-
const signedTx = preSigned
|
|
31
|
-
? tx
|
|
32
|
-
: await this.prepareTx(tx, additionalSigners, opts);
|
|
33
|
-
return this.sendRawTransaction(signedTx.serialize(), opts);
|
|
34
|
-
}
|
|
35
|
-
async prepareTx(tx, additionalSigners, opts) {
|
|
36
|
-
tx.feePayer = this.wallet.publicKey;
|
|
37
|
-
tx.recentBlockhash = (await this.connection.getRecentBlockhash(opts.preflightCommitment)).blockhash;
|
|
38
|
-
additionalSigners
|
|
39
|
-
.filter((s) => s !== undefined)
|
|
40
|
-
.forEach((kp) => {
|
|
41
|
-
tx.partialSign(kp);
|
|
19
|
+
async sleep(reference) {
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
reference.resolve = resolve;
|
|
22
|
+
setTimeout(resolve, this.retrySleep);
|
|
42
23
|
});
|
|
43
|
-
const signedTx = await this.wallet.signTransaction(tx);
|
|
44
|
-
return signedTx;
|
|
45
|
-
}
|
|
46
|
-
async getVersionedTransaction(ixs, lookupTableAccounts, additionalSigners, opts) {
|
|
47
|
-
if (additionalSigners === undefined) {
|
|
48
|
-
additionalSigners = [];
|
|
49
|
-
}
|
|
50
|
-
if (opts === undefined) {
|
|
51
|
-
opts = this.opts;
|
|
52
|
-
}
|
|
53
|
-
const message = new web3_js_1.TransactionMessage({
|
|
54
|
-
payerKey: this.wallet.publicKey,
|
|
55
|
-
recentBlockhash: (await this.connection.getRecentBlockhash(opts.preflightCommitment)).blockhash,
|
|
56
|
-
instructions: ixs,
|
|
57
|
-
}).compileToV0Message(lookupTableAccounts);
|
|
58
|
-
const tx = new web3_js_1.VersionedTransaction(message);
|
|
59
|
-
return tx;
|
|
60
|
-
}
|
|
61
|
-
async sendVersionedTransaction(tx, additionalSigners, opts, preSigned) {
|
|
62
|
-
let signedTx;
|
|
63
|
-
if (preSigned) {
|
|
64
|
-
signedTx = tx;
|
|
65
|
-
// @ts-ignore
|
|
66
|
-
}
|
|
67
|
-
else if (this.wallet.payer) {
|
|
68
|
-
// @ts-ignore
|
|
69
|
-
tx.sign((additionalSigners !== null && additionalSigners !== void 0 ? additionalSigners : []).concat(this.wallet.payer));
|
|
70
|
-
signedTx = tx;
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
additionalSigners === null || additionalSigners === void 0 ? void 0 : additionalSigners.filter((s) => s !== undefined).forEach((kp) => {
|
|
74
|
-
tx.sign([kp]);
|
|
75
|
-
});
|
|
76
|
-
// @ts-ignore
|
|
77
|
-
signedTx = await this.wallet.signTransaction(tx);
|
|
78
|
-
}
|
|
79
|
-
if (opts === undefined) {
|
|
80
|
-
opts = this.opts;
|
|
81
|
-
}
|
|
82
|
-
return this.sendRawTransaction(signedTx.serialize(), opts);
|
|
83
24
|
}
|
|
84
25
|
async sendRawTransaction(rawTransaction, opts) {
|
|
85
26
|
const startTime = this.getTimestamp();
|
|
@@ -130,97 +71,5 @@ class RetryTxSender {
|
|
|
130
71
|
}
|
|
131
72
|
return { txSig: txid, slot };
|
|
132
73
|
}
|
|
133
|
-
async confirmTransaction(signature, commitment) {
|
|
134
|
-
let decodedSignature;
|
|
135
|
-
try {
|
|
136
|
-
decodedSignature = bs58_1.default.decode(signature);
|
|
137
|
-
}
|
|
138
|
-
catch (err) {
|
|
139
|
-
throw new Error('signature must be base58 encoded: ' + signature);
|
|
140
|
-
}
|
|
141
|
-
(0, assert_1.default)(decodedSignature.length === 64, 'signature has invalid length');
|
|
142
|
-
const start = Date.now();
|
|
143
|
-
const subscriptionCommitment = commitment || this.opts.commitment;
|
|
144
|
-
const subscriptionIds = new Array();
|
|
145
|
-
const connections = [this.connection, ...this.additionalConnections];
|
|
146
|
-
let response = null;
|
|
147
|
-
const promises = connections.map((connection, i) => {
|
|
148
|
-
let subscriptionId;
|
|
149
|
-
const confirmPromise = new Promise((resolve, reject) => {
|
|
150
|
-
try {
|
|
151
|
-
subscriptionId = connection.onSignature(signature, (result, context) => {
|
|
152
|
-
subscriptionIds[i] = undefined;
|
|
153
|
-
response = {
|
|
154
|
-
context,
|
|
155
|
-
value: result,
|
|
156
|
-
};
|
|
157
|
-
resolve(null);
|
|
158
|
-
}, subscriptionCommitment);
|
|
159
|
-
}
|
|
160
|
-
catch (err) {
|
|
161
|
-
reject(err);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
subscriptionIds.push(subscriptionId);
|
|
165
|
-
return confirmPromise;
|
|
166
|
-
});
|
|
167
|
-
try {
|
|
168
|
-
await this.promiseTimeout(promises, this.timeout);
|
|
169
|
-
}
|
|
170
|
-
finally {
|
|
171
|
-
for (const [i, subscriptionId] of subscriptionIds.entries()) {
|
|
172
|
-
if (subscriptionId) {
|
|
173
|
-
connections[i].removeSignatureListener(subscriptionId);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (response === null) {
|
|
178
|
-
this.timoutCount += 1;
|
|
179
|
-
const duration = (Date.now() - start) / 1000;
|
|
180
|
-
throw new Error(`Transaction was not confirmed in ${duration.toFixed(2)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`);
|
|
181
|
-
}
|
|
182
|
-
return response;
|
|
183
|
-
}
|
|
184
|
-
getTimestamp() {
|
|
185
|
-
return new Date().getTime();
|
|
186
|
-
}
|
|
187
|
-
async sleep(reference) {
|
|
188
|
-
return new Promise((resolve) => {
|
|
189
|
-
reference.resolve = resolve;
|
|
190
|
-
setTimeout(resolve, this.retrySleep);
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
promiseTimeout(promises, timeoutMs) {
|
|
194
|
-
let timeoutId;
|
|
195
|
-
const timeoutPromise = new Promise((resolve) => {
|
|
196
|
-
timeoutId = setTimeout(() => resolve(null), timeoutMs);
|
|
197
|
-
});
|
|
198
|
-
return Promise.race([...promises, timeoutPromise]).then((result) => {
|
|
199
|
-
clearTimeout(timeoutId);
|
|
200
|
-
return result;
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
sendToAdditionalConnections(rawTx, opts) {
|
|
204
|
-
this.additionalConnections.map((connection) => {
|
|
205
|
-
connection.sendRawTransaction(rawTx, opts).catch((e) => {
|
|
206
|
-
console.error(
|
|
207
|
-
// @ts-ignore
|
|
208
|
-
`error sending tx to additional connection ${connection._rpcEndpoint}`);
|
|
209
|
-
console.error(e);
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
addAdditionalConnection(newConnection) {
|
|
214
|
-
const alreadyUsingConnection = this.additionalConnections.filter((connection) => {
|
|
215
|
-
// @ts-ignore
|
|
216
|
-
return connection._rpcEndpoint === newConnection.rpcEndpoint;
|
|
217
|
-
}).length > 0;
|
|
218
|
-
if (!alreadyUsingConnection) {
|
|
219
|
-
this.additionalConnections.push(newConnection);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
getTimeoutCount() {
|
|
223
|
-
return this.timoutCount;
|
|
224
|
-
}
|
|
225
74
|
}
|
|
226
75
|
exports.RetryTxSender = RetryTxSender;
|
package/lib/types.d.ts
CHANGED
|
@@ -50,6 +50,9 @@ export declare class UserStatus {
|
|
|
50
50
|
static readonly BANKRUPT: {
|
|
51
51
|
bankrupt: {};
|
|
52
52
|
};
|
|
53
|
+
static readonly REDUCE_ONLY: {
|
|
54
|
+
reduceOnly: {};
|
|
55
|
+
};
|
|
53
56
|
}
|
|
54
57
|
export declare class ContractType {
|
|
55
58
|
static readonly PERPETUAL: {
|
|
@@ -818,6 +821,7 @@ export type AMM = {
|
|
|
818
821
|
bidQuoteAssetReserve: BN;
|
|
819
822
|
askBaseAssetReserve: BN;
|
|
820
823
|
askQuoteAssetReserve: BN;
|
|
824
|
+
perLpBase: number;
|
|
821
825
|
};
|
|
822
826
|
export type PerpPosition = {
|
|
823
827
|
baseAssetAmount: BN;
|
|
@@ -834,6 +838,7 @@ export type PerpPosition = {
|
|
|
834
838
|
remainderBaseAssetAmount: number;
|
|
835
839
|
lastBaseAssetAmountPerLp: BN;
|
|
836
840
|
lastQuoteAssetAmountPerLp: BN;
|
|
841
|
+
perLpBase: number;
|
|
837
842
|
};
|
|
838
843
|
export type UserStatsAccount = {
|
|
839
844
|
numberOfSubAccounts: number;
|
|
@@ -1114,3 +1119,16 @@ export type PerpMarketExtendedInfo = {
|
|
|
1114
1119
|
pnlPoolValue: BN;
|
|
1115
1120
|
contractTier: ContractTier;
|
|
1116
1121
|
};
|
|
1122
|
+
export type HealthComponents = {
|
|
1123
|
+
deposits: HealthComponent[];
|
|
1124
|
+
borrows: HealthComponent[];
|
|
1125
|
+
perpPositions: HealthComponent[];
|
|
1126
|
+
perpPnl: HealthComponent[];
|
|
1127
|
+
};
|
|
1128
|
+
export type HealthComponent = {
|
|
1129
|
+
marketIndex: number;
|
|
1130
|
+
size: BN;
|
|
1131
|
+
value: BN;
|
|
1132
|
+
weight: BN;
|
|
1133
|
+
weightedValue: BN;
|
|
1134
|
+
};
|
package/lib/types.js
CHANGED
|
@@ -33,6 +33,7 @@ exports.UserStatus = UserStatus;
|
|
|
33
33
|
UserStatus.ACTIVE = { active: {} };
|
|
34
34
|
UserStatus.BEING_LIQUIDATED = { beingLiquidated: {} };
|
|
35
35
|
UserStatus.BANKRUPT = { bankrupt: {} };
|
|
36
|
+
UserStatus.REDUCE_ONLY = { reduceOnly: {} };
|
|
36
37
|
class ContractType {
|
|
37
38
|
}
|
|
38
39
|
exports.ContractType = ContractType;
|
package/lib/user.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { PublicKey } from '@solana/web3.js';
|
|
|
3
3
|
import { EventEmitter } from 'events';
|
|
4
4
|
import StrictEventEmitter from 'strict-event-emitter-types';
|
|
5
5
|
import { DriftClient } from './driftClient';
|
|
6
|
-
import { MarginCategory, Order, UserAccount, PerpPosition, SpotPosition, PerpMarketAccount } from './types';
|
|
6
|
+
import { MarginCategory, Order, UserAccount, PerpPosition, SpotPosition, PerpMarketAccount, HealthComponents } from './types';
|
|
7
7
|
import { UserAccountSubscriber, UserAccountEvents, DataAndSlot } from './accounts/types';
|
|
8
8
|
import { PositionDirection, BN, SpotMarketAccount, MarketType } from '.';
|
|
9
9
|
import { OraclePriceData } from './oracles/types';
|
|
@@ -153,6 +153,12 @@ export declare class User {
|
|
|
153
153
|
* @returns : number (value from [0, 100])
|
|
154
154
|
*/
|
|
155
155
|
getHealth(): number;
|
|
156
|
+
calculateWeightedPerpPositionValue(perpPosition: PerpPosition, marginCategory?: MarginCategory, liquidationBuffer?: BN, includeOpenOrders?: boolean, strict?: boolean): BN;
|
|
157
|
+
/**
|
|
158
|
+
* calculates position value of a single perp market in margin system
|
|
159
|
+
* @returns : Precision QUOTE_PRECISION
|
|
160
|
+
*/
|
|
161
|
+
getPerpMarketLiabilityValue(marketIndex: number, marginCategory?: MarginCategory, liquidationBuffer?: BN, includeOpenOrders?: boolean, strict?: boolean): BN;
|
|
156
162
|
/**
|
|
157
163
|
* calculates sum of position value across all positions in margin system
|
|
158
164
|
* @returns : Precision QUOTE_PRECISION
|
|
@@ -352,6 +358,9 @@ export declare class User {
|
|
|
352
358
|
perpTier: number;
|
|
353
359
|
spotTier: number;
|
|
354
360
|
};
|
|
361
|
+
getHealthComponents({ marginCategory, }: {
|
|
362
|
+
marginCategory: MarginCategory;
|
|
363
|
+
}): HealthComponents;
|
|
355
364
|
/**
|
|
356
365
|
* Get the total position value, excluding any position coming from the given target market
|
|
357
366
|
* @param marketToIgnore
|
package/lib/user.js
CHANGED
|
@@ -145,6 +145,7 @@ class User {
|
|
|
145
145
|
lpShares: numericConstants_1.ZERO,
|
|
146
146
|
lastBaseAssetAmountPerLp: numericConstants_1.ZERO,
|
|
147
147
|
lastQuoteAssetAmountPerLp: numericConstants_1.ZERO,
|
|
148
|
+
perLpBase: 0,
|
|
148
149
|
};
|
|
149
150
|
}
|
|
150
151
|
getClonedPosition(position) {
|
|
@@ -258,17 +259,54 @@ class User {
|
|
|
258
259
|
}
|
|
259
260
|
const position = this.getClonedPosition(originalPosition);
|
|
260
261
|
const market = this.driftClient.getPerpMarketAccount(position.marketIndex);
|
|
262
|
+
if (market.amm.perLpBase != position.perLpBase) {
|
|
263
|
+
// perLpBase = 1 => per 10 LP shares, perLpBase = -1 => per 0.1 LP shares
|
|
264
|
+
const expoDiff = market.amm.perLpBase - position.perLpBase;
|
|
265
|
+
const marketPerLpRebaseScalar = new _1.BN(10 ** Math.abs(expoDiff));
|
|
266
|
+
if (expoDiff > 0) {
|
|
267
|
+
position.lastBaseAssetAmountPerLp =
|
|
268
|
+
position.lastBaseAssetAmountPerLp.mul(marketPerLpRebaseScalar);
|
|
269
|
+
position.lastQuoteAssetAmountPerLp =
|
|
270
|
+
position.lastQuoteAssetAmountPerLp.mul(marketPerLpRebaseScalar);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
position.lastBaseAssetAmountPerLp =
|
|
274
|
+
position.lastBaseAssetAmountPerLp.div(marketPerLpRebaseScalar);
|
|
275
|
+
position.lastQuoteAssetAmountPerLp =
|
|
276
|
+
position.lastQuoteAssetAmountPerLp.div(marketPerLpRebaseScalar);
|
|
277
|
+
}
|
|
278
|
+
position.perLpBase = position.perLpBase + expoDiff;
|
|
279
|
+
}
|
|
261
280
|
const nShares = position.lpShares;
|
|
262
281
|
// incorp unsettled funding on pre settled position
|
|
263
282
|
const quoteFundingPnl = (0, _1.calculatePositionFundingPNL)(market, position);
|
|
283
|
+
let baseUnit = numericConstants_1.AMM_RESERVE_PRECISION;
|
|
284
|
+
if (market.amm.perLpBase == position.perLpBase) {
|
|
285
|
+
if (position.perLpBase >= 0 &&
|
|
286
|
+
position.perLpBase <= numericConstants_1.AMM_RESERVE_PRECISION_EXP.toNumber()) {
|
|
287
|
+
const marketPerLpRebase = new _1.BN(10 ** market.amm.perLpBase);
|
|
288
|
+
baseUnit = baseUnit.mul(marketPerLpRebase);
|
|
289
|
+
}
|
|
290
|
+
else if (position.perLpBase < 0 &&
|
|
291
|
+
position.perLpBase >= -numericConstants_1.AMM_RESERVE_PRECISION_EXP.toNumber()) {
|
|
292
|
+
const marketPerLpRebase = new _1.BN(10 ** Math.abs(market.amm.perLpBase));
|
|
293
|
+
baseUnit = baseUnit.div(marketPerLpRebase);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
throw 'cannot calc';
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
throw 'market.amm.perLpBase != position.perLpBase';
|
|
301
|
+
}
|
|
264
302
|
const deltaBaa = market.amm.baseAssetAmountPerLp
|
|
265
303
|
.sub(position.lastBaseAssetAmountPerLp)
|
|
266
304
|
.mul(nShares)
|
|
267
|
-
.div(
|
|
305
|
+
.div(baseUnit);
|
|
268
306
|
const deltaQaa = market.amm.quoteAssetAmountPerLp
|
|
269
307
|
.sub(position.lastQuoteAssetAmountPerLp)
|
|
270
308
|
.mul(nShares)
|
|
271
|
-
.div(
|
|
309
|
+
.div(baseUnit);
|
|
272
310
|
function sign(v) {
|
|
273
311
|
return v.isNeg() ? new _1.BN(-1) : new _1.BN(1);
|
|
274
312
|
}
|
|
@@ -464,7 +502,7 @@ class User {
|
|
|
464
502
|
}
|
|
465
503
|
positionUnrealizedPnl = positionUnrealizedPnl
|
|
466
504
|
.mul(quotePrice)
|
|
467
|
-
.div(
|
|
505
|
+
.div(numericConstants_1.PRICE_PRECISION);
|
|
468
506
|
if (withWeightMarginCategory !== undefined) {
|
|
469
507
|
if (positionUnrealizedPnl.gt(numericConstants_1.ZERO)) {
|
|
470
508
|
positionUnrealizedPnl = positionUnrealizedPnl
|
|
@@ -660,64 +698,81 @@ class User {
|
|
|
660
698
|
}
|
|
661
699
|
return health;
|
|
662
700
|
}
|
|
701
|
+
calculateWeightedPerpPositionValue(perpPosition, marginCategory, liquidationBuffer, includeOpenOrders, strict = false) {
|
|
702
|
+
const market = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
|
|
703
|
+
if (perpPosition.lpShares.gt(numericConstants_1.ZERO)) {
|
|
704
|
+
// is an lp, clone so we dont mutate the position
|
|
705
|
+
perpPosition = this.getPerpPositionWithLPSettle(market.marketIndex, this.getClonedPosition(perpPosition), !!marginCategory)[0];
|
|
706
|
+
}
|
|
707
|
+
let valuationPrice = this.getOracleDataForPerpMarket(market.marketIndex).price;
|
|
708
|
+
if ((0, types_1.isVariant)(market.status, 'settlement')) {
|
|
709
|
+
valuationPrice = market.expiryPrice;
|
|
710
|
+
}
|
|
711
|
+
const baseAssetAmount = includeOpenOrders
|
|
712
|
+
? (0, margin_1.calculateWorstCaseBaseAssetAmount)(perpPosition)
|
|
713
|
+
: perpPosition.baseAssetAmount;
|
|
714
|
+
let baseAssetValue = baseAssetAmount
|
|
715
|
+
.abs()
|
|
716
|
+
.mul(valuationPrice)
|
|
717
|
+
.div(numericConstants_1.BASE_PRECISION);
|
|
718
|
+
if (marginCategory) {
|
|
719
|
+
let marginRatio = new _1.BN((0, _1.calculateMarketMarginRatio)(market, baseAssetAmount.abs(), marginCategory));
|
|
720
|
+
if (marginCategory === 'Initial') {
|
|
721
|
+
marginRatio = _1.BN.max(marginRatio, new _1.BN(this.getUserAccount().maxMarginRatio));
|
|
722
|
+
}
|
|
723
|
+
if (liquidationBuffer !== undefined) {
|
|
724
|
+
marginRatio = marginRatio.add(liquidationBuffer);
|
|
725
|
+
}
|
|
726
|
+
if ((0, types_1.isVariant)(market.status, 'settlement')) {
|
|
727
|
+
marginRatio = numericConstants_1.ZERO;
|
|
728
|
+
}
|
|
729
|
+
const quoteSpotMarket = this.driftClient.getSpotMarketAccount(market.quoteSpotMarketIndex);
|
|
730
|
+
const quoteOraclePriceData = this.driftClient.getOraclePriceDataAndSlot(quoteSpotMarket.oracle).data;
|
|
731
|
+
let quotePrice;
|
|
732
|
+
if (strict) {
|
|
733
|
+
quotePrice = _1.BN.max(quoteOraclePriceData.price, quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min);
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
quotePrice = quoteOraclePriceData.price;
|
|
737
|
+
}
|
|
738
|
+
baseAssetValue = baseAssetValue
|
|
739
|
+
.mul(quotePrice)
|
|
740
|
+
.div(numericConstants_1.PRICE_PRECISION)
|
|
741
|
+
.mul(marginRatio)
|
|
742
|
+
.div(numericConstants_1.MARGIN_PRECISION);
|
|
743
|
+
if (includeOpenOrders) {
|
|
744
|
+
baseAssetValue = baseAssetValue.add(new _1.BN(perpPosition.openOrders).mul(numericConstants_1.OPEN_ORDER_MARGIN_REQUIREMENT));
|
|
745
|
+
if (perpPosition.lpShares.gt(numericConstants_1.ZERO)) {
|
|
746
|
+
baseAssetValue = baseAssetValue.add(_1.BN.max(numericConstants_1.QUOTE_PRECISION, valuationPrice
|
|
747
|
+
.mul(market.amm.orderStepSize)
|
|
748
|
+
.mul(numericConstants_1.QUOTE_PRECISION)
|
|
749
|
+
.div(numericConstants_1.AMM_RESERVE_PRECISION)
|
|
750
|
+
.div(numericConstants_1.PRICE_PRECISION)));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return baseAssetValue;
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* calculates position value of a single perp market in margin system
|
|
758
|
+
* @returns : Precision QUOTE_PRECISION
|
|
759
|
+
*/
|
|
760
|
+
getPerpMarketLiabilityValue(marketIndex, marginCategory, liquidationBuffer, includeOpenOrders, strict = false) {
|
|
761
|
+
const perpPosition = this.getPerpPosition(marketIndex);
|
|
762
|
+
if (!perpPosition) {
|
|
763
|
+
return numericConstants_1.ZERO;
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
return this.calculateWeightedPerpPositionValue(perpPosition, marginCategory, liquidationBuffer, includeOpenOrders, strict);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
663
769
|
/**
|
|
664
770
|
* calculates sum of position value across all positions in margin system
|
|
665
771
|
* @returns : Precision QUOTE_PRECISION
|
|
666
772
|
*/
|
|
667
773
|
getTotalPerpPositionValue(marginCategory, liquidationBuffer, includeOpenOrders, strict = false) {
|
|
668
774
|
return this.getActivePerpPositions().reduce((totalPerpValue, perpPosition) => {
|
|
669
|
-
const
|
|
670
|
-
if (perpPosition.lpShares.gt(numericConstants_1.ZERO)) {
|
|
671
|
-
// is an lp, clone so we dont mutate the position
|
|
672
|
-
perpPosition = this.getPerpPositionWithLPSettle(market.marketIndex, this.getClonedPosition(perpPosition), !!marginCategory)[0];
|
|
673
|
-
}
|
|
674
|
-
let valuationPrice = this.getOracleDataForPerpMarket(market.marketIndex).price;
|
|
675
|
-
if ((0, types_1.isVariant)(market.status, 'settlement')) {
|
|
676
|
-
valuationPrice = market.expiryPrice;
|
|
677
|
-
}
|
|
678
|
-
const baseAssetAmount = includeOpenOrders
|
|
679
|
-
? (0, margin_1.calculateWorstCaseBaseAssetAmount)(perpPosition)
|
|
680
|
-
: perpPosition.baseAssetAmount;
|
|
681
|
-
let baseAssetValue = baseAssetAmount
|
|
682
|
-
.abs()
|
|
683
|
-
.mul(valuationPrice)
|
|
684
|
-
.div(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO.mul(numericConstants_1.PRICE_PRECISION));
|
|
685
|
-
if (marginCategory) {
|
|
686
|
-
let marginRatio = new _1.BN((0, _1.calculateMarketMarginRatio)(market, baseAssetAmount.abs(), marginCategory));
|
|
687
|
-
if (marginCategory === 'Initial') {
|
|
688
|
-
marginRatio = _1.BN.max(marginRatio, new _1.BN(this.getUserAccount().maxMarginRatio));
|
|
689
|
-
}
|
|
690
|
-
if (liquidationBuffer !== undefined) {
|
|
691
|
-
marginRatio = marginRatio.add(liquidationBuffer);
|
|
692
|
-
}
|
|
693
|
-
if ((0, types_1.isVariant)(market.status, 'settlement')) {
|
|
694
|
-
marginRatio = numericConstants_1.ZERO;
|
|
695
|
-
}
|
|
696
|
-
const quoteSpotMarket = this.driftClient.getSpotMarketAccount(market.quoteSpotMarketIndex);
|
|
697
|
-
const quoteOraclePriceData = this.driftClient.getOraclePriceDataAndSlot(quoteSpotMarket.oracle).data;
|
|
698
|
-
let quotePrice;
|
|
699
|
-
if (strict) {
|
|
700
|
-
quotePrice = _1.BN.max(quoteOraclePriceData.price, quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min);
|
|
701
|
-
}
|
|
702
|
-
else {
|
|
703
|
-
quotePrice = quoteOraclePriceData.price;
|
|
704
|
-
}
|
|
705
|
-
baseAssetValue = baseAssetValue
|
|
706
|
-
.mul(quotePrice)
|
|
707
|
-
.div(numericConstants_1.PRICE_PRECISION)
|
|
708
|
-
.mul(marginRatio)
|
|
709
|
-
.div(numericConstants_1.MARGIN_PRECISION);
|
|
710
|
-
if (includeOpenOrders) {
|
|
711
|
-
baseAssetValue = baseAssetValue.add(new _1.BN(perpPosition.openOrders).mul(numericConstants_1.OPEN_ORDER_MARGIN_REQUIREMENT));
|
|
712
|
-
if (perpPosition.lpShares.gt(numericConstants_1.ZERO)) {
|
|
713
|
-
baseAssetValue = baseAssetValue.add(_1.BN.max(numericConstants_1.QUOTE_PRECISION, valuationPrice
|
|
714
|
-
.mul(market.amm.orderStepSize)
|
|
715
|
-
.mul(numericConstants_1.QUOTE_PRECISION)
|
|
716
|
-
.div(numericConstants_1.AMM_RESERVE_PRECISION)
|
|
717
|
-
.div(numericConstants_1.PRICE_PRECISION)));
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
}
|
|
775
|
+
const baseAssetValue = this.calculateWeightedPerpPositionValue(perpPosition, marginCategory, liquidationBuffer, includeOpenOrders, strict);
|
|
721
776
|
return totalPerpValue.add(baseAssetValue);
|
|
722
777
|
}, numericConstants_1.ZERO);
|
|
723
778
|
}
|
|
@@ -1589,12 +1644,15 @@ class User {
|
|
|
1589
1644
|
if (canBypass) {
|
|
1590
1645
|
withdrawLimit = _1.BN.max(withdrawLimit, userDepositAmount);
|
|
1591
1646
|
}
|
|
1592
|
-
const
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1647
|
+
const assetWeight = (0, spotBalance_1.calculateAssetWeight)(userDepositAmount, spotMarket, 'Initial');
|
|
1648
|
+
const amountWithdrawable = assetWeight.eq(numericConstants_1.ZERO)
|
|
1649
|
+
? userDepositAmount
|
|
1650
|
+
: freeCollateral
|
|
1651
|
+
.mul(numericConstants_1.MARGIN_PRECISION)
|
|
1652
|
+
.div(assetWeight)
|
|
1653
|
+
.mul(numericConstants_1.PRICE_PRECISION)
|
|
1654
|
+
.div(oracleData.price)
|
|
1655
|
+
.mul(precisionIncrease);
|
|
1598
1656
|
const maxWithdrawValue = _1.BN.min(_1.BN.min(amountWithdrawable, userDepositAmount), withdrawLimit.abs());
|
|
1599
1657
|
if (reduceOnly) {
|
|
1600
1658
|
return _1.BN.max(maxWithdrawValue, numericConstants_1.ZERO);
|
|
@@ -1702,6 +1760,146 @@ class User {
|
|
|
1702
1760
|
spotTier: safestSpotTier,
|
|
1703
1761
|
};
|
|
1704
1762
|
}
|
|
1763
|
+
getHealthComponents({ marginCategory, }) {
|
|
1764
|
+
const healthComponents = {
|
|
1765
|
+
deposits: [],
|
|
1766
|
+
borrows: [],
|
|
1767
|
+
perpPositions: [],
|
|
1768
|
+
perpPnl: [],
|
|
1769
|
+
};
|
|
1770
|
+
for (const perpPosition of this.getActivePerpPositions()) {
|
|
1771
|
+
const perpMarket = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
|
|
1772
|
+
const oraclePriceData = this.driftClient.getOraclePriceDataAndSlot(perpMarket.amm.oracle).data;
|
|
1773
|
+
const oraclePrice = oraclePriceData.price;
|
|
1774
|
+
const worstCaseBaseAmount = (0, margin_1.calculateWorstCaseBaseAssetAmount)(perpPosition);
|
|
1775
|
+
const marginRatio = new _1.BN((0, _1.calculateMarketMarginRatio)(perpMarket, worstCaseBaseAmount.abs(), marginCategory));
|
|
1776
|
+
const quoteSpotMarket = this.driftClient.getSpotMarketAccount(perpMarket.quoteSpotMarketIndex);
|
|
1777
|
+
const quoteOraclePriceData = this.driftClient.getOraclePriceDataAndSlot(quoteSpotMarket.oracle).data;
|
|
1778
|
+
const baseAssetValue = worstCaseBaseAmount
|
|
1779
|
+
.abs()
|
|
1780
|
+
.mul(oraclePrice)
|
|
1781
|
+
.div(numericConstants_1.BASE_PRECISION);
|
|
1782
|
+
let marginRequirement = baseAssetValue
|
|
1783
|
+
.mul(quoteOraclePriceData.price)
|
|
1784
|
+
.div(numericConstants_1.PRICE_PRECISION)
|
|
1785
|
+
.mul(marginRatio)
|
|
1786
|
+
.div(numericConstants_1.MARGIN_PRECISION);
|
|
1787
|
+
marginRequirement = marginRequirement.add(new _1.BN(perpPosition.openOrders).mul(numericConstants_1.OPEN_ORDER_MARGIN_REQUIREMENT));
|
|
1788
|
+
if (perpPosition.lpShares.gt(numericConstants_1.ZERO)) {
|
|
1789
|
+
marginRequirement = marginRequirement.add(_1.BN.max(numericConstants_1.QUOTE_PRECISION, oraclePrice
|
|
1790
|
+
.mul(perpMarket.amm.orderStepSize)
|
|
1791
|
+
.mul(numericConstants_1.QUOTE_PRECISION)
|
|
1792
|
+
.div(numericConstants_1.AMM_RESERVE_PRECISION)
|
|
1793
|
+
.div(numericConstants_1.PRICE_PRECISION)));
|
|
1794
|
+
}
|
|
1795
|
+
healthComponents.perpPositions.push({
|
|
1796
|
+
marketIndex: perpMarket.marketIndex,
|
|
1797
|
+
size: worstCaseBaseAmount,
|
|
1798
|
+
value: baseAssetValue,
|
|
1799
|
+
weight: marginRatio,
|
|
1800
|
+
weightedValue: marginRequirement,
|
|
1801
|
+
});
|
|
1802
|
+
const settledPerpPosition = this.getPerpPositionWithLPSettle(perpPosition.marketIndex, perpPosition)[0];
|
|
1803
|
+
const positionUnrealizedPnl = (0, _1.calculatePositionPNL)(perpMarket, settledPerpPosition, true, oraclePriceData);
|
|
1804
|
+
let pnlWeight;
|
|
1805
|
+
if (positionUnrealizedPnl.gt(numericConstants_1.ZERO)) {
|
|
1806
|
+
pnlWeight = (0, _1.calculateUnrealizedAssetWeight)(perpMarket, quoteSpotMarket, positionUnrealizedPnl, marginCategory, oraclePriceData);
|
|
1807
|
+
}
|
|
1808
|
+
else {
|
|
1809
|
+
pnlWeight = numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION;
|
|
1810
|
+
}
|
|
1811
|
+
const pnlValue = positionUnrealizedPnl
|
|
1812
|
+
.mul(quoteOraclePriceData.price)
|
|
1813
|
+
.div(numericConstants_1.PRICE_PRECISION);
|
|
1814
|
+
const wegithedPnlValue = pnlValue
|
|
1815
|
+
.mul(pnlWeight)
|
|
1816
|
+
.div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
|
|
1817
|
+
healthComponents.perpPnl.push({
|
|
1818
|
+
marketIndex: perpMarket.marketIndex,
|
|
1819
|
+
size: positionUnrealizedPnl,
|
|
1820
|
+
value: pnlValue,
|
|
1821
|
+
weight: pnlWeight,
|
|
1822
|
+
weightedValue: wegithedPnlValue,
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
let netQuoteValue = numericConstants_1.ZERO;
|
|
1826
|
+
for (const spotPosition of this.getActiveSpotPositions()) {
|
|
1827
|
+
const spotMarketAccount = this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
|
|
1828
|
+
const oraclePriceData = this.getOracleDataForSpotMarket(spotPosition.marketIndex);
|
|
1829
|
+
if (spotPosition.marketIndex === numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
|
|
1830
|
+
const tokenAmount = (0, _1.getSignedTokenAmount)((0, spotBalance_1.getTokenAmount)(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType), spotPosition.balanceType);
|
|
1831
|
+
netQuoteValue = netQuoteValue.add(tokenAmount);
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = (0, spotPosition_1.getWorstCaseTokenAmounts)(spotPosition, spotMarketAccount, oraclePriceData);
|
|
1835
|
+
netQuoteValue = netQuoteValue.add(worstCaseQuoteTokenAmount);
|
|
1836
|
+
const baseAssetValue = (0, _1.getTokenValue)(worstCaseTokenAmount.abs(), spotMarketAccount.decimals, oraclePriceData);
|
|
1837
|
+
const isLiability = (0, types_1.isVariant)(spotPosition.balanceType, 'borrow');
|
|
1838
|
+
let weight;
|
|
1839
|
+
if (isLiability) {
|
|
1840
|
+
weight = (0, spotBalance_1.calculateLiabilityWeight)(worstCaseTokenAmount.abs(), spotMarketAccount, marginCategory);
|
|
1841
|
+
}
|
|
1842
|
+
else {
|
|
1843
|
+
weight = (0, spotBalance_1.calculateAssetWeight)(worstCaseTokenAmount, spotMarketAccount, marginCategory);
|
|
1844
|
+
}
|
|
1845
|
+
const weightedValue = baseAssetValue
|
|
1846
|
+
.mul(weight)
|
|
1847
|
+
.div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
|
|
1848
|
+
if (isLiability) {
|
|
1849
|
+
healthComponents.borrows.push({
|
|
1850
|
+
marketIndex: spotMarketAccount.marketIndex,
|
|
1851
|
+
size: worstCaseTokenAmount,
|
|
1852
|
+
value: baseAssetValue,
|
|
1853
|
+
weight: weight,
|
|
1854
|
+
weightedValue: weightedValue,
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
else {
|
|
1858
|
+
healthComponents.deposits.push({
|
|
1859
|
+
marketIndex: spotMarketAccount.marketIndex,
|
|
1860
|
+
size: worstCaseTokenAmount,
|
|
1861
|
+
value: baseAssetValue,
|
|
1862
|
+
weight: weight,
|
|
1863
|
+
weightedValue: weightedValue,
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
if (!netQuoteValue.eq(numericConstants_1.ZERO)) {
|
|
1868
|
+
const spotMarketAccount = this.driftClient.getQuoteSpotMarketAccount();
|
|
1869
|
+
const oraclePriceData = this.getOracleDataForSpotMarket(numericConstants_1.QUOTE_SPOT_MARKET_INDEX);
|
|
1870
|
+
const baseAssetValue = (0, _1.getTokenValue)(netQuoteValue.abs(), spotMarketAccount.decimals, oraclePriceData);
|
|
1871
|
+
const isLiability = netQuoteValue.lt(numericConstants_1.ZERO);
|
|
1872
|
+
let weight;
|
|
1873
|
+
if (isLiability) {
|
|
1874
|
+
weight = (0, spotBalance_1.calculateLiabilityWeight)(netQuoteValue.abs(), spotMarketAccount, marginCategory);
|
|
1875
|
+
}
|
|
1876
|
+
else {
|
|
1877
|
+
weight = (0, spotBalance_1.calculateAssetWeight)(netQuoteValue, spotMarketAccount, marginCategory);
|
|
1878
|
+
}
|
|
1879
|
+
const weightedValue = baseAssetValue
|
|
1880
|
+
.mul(weight)
|
|
1881
|
+
.div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
|
|
1882
|
+
if (isLiability) {
|
|
1883
|
+
healthComponents.borrows.push({
|
|
1884
|
+
marketIndex: spotMarketAccount.marketIndex,
|
|
1885
|
+
size: netQuoteValue,
|
|
1886
|
+
value: baseAssetValue,
|
|
1887
|
+
weight: weight,
|
|
1888
|
+
weightedValue: weightedValue,
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
else {
|
|
1892
|
+
healthComponents.deposits.push({
|
|
1893
|
+
marketIndex: spotMarketAccount.marketIndex,
|
|
1894
|
+
size: netQuoteValue,
|
|
1895
|
+
value: baseAssetValue,
|
|
1896
|
+
weight: weight,
|
|
1897
|
+
weightedValue: weightedValue,
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
return healthComponents;
|
|
1902
|
+
}
|
|
1705
1903
|
/**
|
|
1706
1904
|
* Get the total position value, excluding any position coming from the given target market
|
|
1707
1905
|
* @param marketToIgnore
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drift-labs/sdk",
|
|
3
|
-
"version": "2.37.1-beta.
|
|
3
|
+
"version": "2.37.1-beta.10",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"types": "lib/index.d.ts",
|
|
6
6
|
"author": "crispheaney",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"eslint-plugin-prettier": "^3.4.0",
|
|
56
56
|
"lodash": "^4.17.21",
|
|
57
57
|
"mocha": "^10.0.0",
|
|
58
|
-
"prettier": "^
|
|
58
|
+
"prettier": "^3.0.1",
|
|
59
59
|
"ts-node": "^10.8.0",
|
|
60
60
|
"typescript": "^4.9.5"
|
|
61
61
|
},
|