@drift-labs/sdk 2.0.10 → 2.0.12
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/adminClient.d.ts +2 -1
- package/lib/adminClient.js +9 -0
- package/lib/config.js +1 -1
- package/lib/constants/spotMarkets.js +1 -1
- package/lib/events/pollingLogProvider.js +1 -1
- package/lib/idl/drift.json +40 -2
- package/lib/types.d.ts +8 -0
- package/lib/types.js +6 -1
- package/lib/user.d.ts +18 -4
- package/lib/user.js +93 -11
- package/package.json +1 -1
- package/src/adminClient.ts +14 -0
- package/src/assert/assert.js +9 -0
- package/src/config.ts +1 -1
- package/src/constants/spotMarkets.ts +1 -1
- package/src/events/eventList.js +77 -0
- package/src/events/pollingLogProvider.ts +2 -2
- package/src/examples/makeTradeExample.js +157 -0
- package/src/idl/drift.json +40 -2
- package/src/token/index.js +38 -0
- package/src/tx/types.js +2 -0
- package/src/tx/utils.js +17 -0
- package/src/types.ts +5 -0
- package/src/user.ts +133 -15
- package/src/util/computeUnits.js +27 -0
- package/src/util/getTokenAddress.js +9 -0
- package/src/util/promiseTimeout.js +14 -0
- package/src/util/tps.js +27 -0
package/lib/adminClient.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="bn.js" />
|
|
2
2
|
import { PublicKey, TransactionSignature } from '@solana/web3.js';
|
|
3
|
-
import { FeeStructure, OracleGuardRails, OracleSource, ExchangeStatus, MarketStatus, ContractTier, AssetTier } from './types';
|
|
3
|
+
import { FeeStructure, OracleGuardRails, OracleSource, ExchangeStatus, MarketStatus, ContractTier, AssetTier, SpotFulfillmentConfigStatus } from './types';
|
|
4
4
|
import { BN } from '@project-serum/anchor';
|
|
5
5
|
import { DriftClient } from './driftClient';
|
|
6
6
|
export declare class AdminClient extends DriftClient {
|
|
@@ -43,6 +43,7 @@ export declare class AdminClient extends DriftClient {
|
|
|
43
43
|
updatePerpMarketExpiry(perpMarketIndex: number, expiryTs: BN): Promise<TransactionSignature>;
|
|
44
44
|
updateSpotMarketOracle(spotMarketIndex: number, oracle: PublicKey, oracleSource: OracleSource): Promise<TransactionSignature>;
|
|
45
45
|
updateSpotMarketOrdersEnabled(spotMarketIndex: number, ordersEnabled: boolean): Promise<TransactionSignature>;
|
|
46
|
+
updateSerumFulfillmentConfigStatus(serumFulfillmentConfig: PublicKey, status: SpotFulfillmentConfigStatus): Promise<TransactionSignature>;
|
|
46
47
|
updateSpotMarketExpiry(spotMarketIndex: number, expiryTs: BN): Promise<TransactionSignature>;
|
|
47
48
|
updateWhitelistMint(whitelistMint?: PublicKey): Promise<TransactionSignature>;
|
|
48
49
|
updateDiscountMint(discountMint: PublicKey): Promise<TransactionSignature>;
|
package/lib/adminClient.js
CHANGED
|
@@ -467,6 +467,15 @@ class AdminClient extends driftClient_1.DriftClient {
|
|
|
467
467
|
},
|
|
468
468
|
});
|
|
469
469
|
}
|
|
470
|
+
async updateSerumFulfillmentConfigStatus(serumFulfillmentConfig, status) {
|
|
471
|
+
return await this.program.rpc.updateSerumFulfillmentConfigStatus(status, {
|
|
472
|
+
accounts: {
|
|
473
|
+
admin: this.wallet.publicKey,
|
|
474
|
+
state: await this.getStatePublicKey(),
|
|
475
|
+
serumFulfillmentConfig,
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
}
|
|
470
479
|
async updateSpotMarketExpiry(spotMarketIndex, expiryTs) {
|
|
471
480
|
return await this.program.rpc.updateSpotMarketExpiry(expiryTs, {
|
|
472
481
|
accounts: {
|
package/lib/config.js
CHANGED
|
@@ -19,7 +19,7 @@ exports.configs = {
|
|
|
19
19
|
PYTH_ORACLE_MAPPING_ADDRESS: 'AHtgzX45WTKfkPG53L6WYhGEXwQkN1BVknET3sVsLL8J',
|
|
20
20
|
DRIFT_PROGRAM_ID: 'dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH',
|
|
21
21
|
USDC_MINT_ADDRESS: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
|
22
|
-
SERUM_V3: '
|
|
22
|
+
SERUM_V3: 'srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX',
|
|
23
23
|
V2_ALPHA_TICKET_MINT_ADDRESS: 'Cmvhycb6LQvvzaShGw4iDHRLzeSSryioAsU98DSSkMNa',
|
|
24
24
|
PERP_MARKETS: perpMarkets_1.MainnetPerpMarkets,
|
|
25
25
|
SPOT_MARKETS: spotMarkets_1.MainnetSpotMarkets,
|
|
@@ -54,7 +54,7 @@ exports.MainnetSpotMarkets = [
|
|
|
54
54
|
mint: new web3_js_1.PublicKey(exports.WRAPPED_SOL_MINT),
|
|
55
55
|
precision: numericConstants_1.LAMPORTS_PRECISION,
|
|
56
56
|
precisionExp: numericConstants_1.LAMPORTS_EXP,
|
|
57
|
-
serumMarket: new web3_js_1.PublicKey('
|
|
57
|
+
serumMarket: new web3_js_1.PublicKey('8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'),
|
|
58
58
|
},
|
|
59
59
|
];
|
|
60
60
|
exports.SpotMarkets = {
|
|
@@ -23,10 +23,10 @@ class PollingLogProvider {
|
|
|
23
23
|
const response = await (0, fetchLogs_1.fetchLogs)(this.connection, this.programId, this.finality, undefined, this.mostRecentSeenTx,
|
|
24
24
|
// If skipping history, only fetch one log back, not the maximum amount available
|
|
25
25
|
skipHistory && this.firstFetch ? 1 : undefined);
|
|
26
|
-
this.firstFetch = false;
|
|
27
26
|
if (response === undefined) {
|
|
28
27
|
return;
|
|
29
28
|
}
|
|
29
|
+
this.firstFetch = false;
|
|
30
30
|
const { mostRecentTx, transactionLogs } = response;
|
|
31
31
|
for (const { txSig, slot, logs } of transactionLogs) {
|
|
32
32
|
callback(txSig, slot, logs, response.mostRecentBlockTime);
|
package/lib/idl/drift.json
CHANGED
|
@@ -2070,6 +2070,34 @@
|
|
|
2070
2070
|
}
|
|
2071
2071
|
]
|
|
2072
2072
|
},
|
|
2073
|
+
{
|
|
2074
|
+
"name": "updateSerumFulfillmentConfigStatus",
|
|
2075
|
+
"accounts": [
|
|
2076
|
+
{
|
|
2077
|
+
"name": "state",
|
|
2078
|
+
"isMut": false,
|
|
2079
|
+
"isSigner": false
|
|
2080
|
+
},
|
|
2081
|
+
{
|
|
2082
|
+
"name": "serumFulfillmentConfig",
|
|
2083
|
+
"isMut": true,
|
|
2084
|
+
"isSigner": false
|
|
2085
|
+
},
|
|
2086
|
+
{
|
|
2087
|
+
"name": "admin",
|
|
2088
|
+
"isMut": true,
|
|
2089
|
+
"isSigner": true
|
|
2090
|
+
}
|
|
2091
|
+
],
|
|
2092
|
+
"args": [
|
|
2093
|
+
{
|
|
2094
|
+
"name": "status",
|
|
2095
|
+
"type": {
|
|
2096
|
+
"defined": "SpotFulfillmentConfigStatus"
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
]
|
|
2100
|
+
},
|
|
2073
2101
|
{
|
|
2074
2102
|
"name": "updateSerumVault",
|
|
2075
2103
|
"accounts": [
|
|
@@ -4184,7 +4212,7 @@
|
|
|
4184
4212
|
{
|
|
4185
4213
|
"name": "status",
|
|
4186
4214
|
"type": {
|
|
4187
|
-
"defined": "
|
|
4215
|
+
"defined": "SpotFulfillmentConfigStatus"
|
|
4188
4216
|
}
|
|
4189
4217
|
},
|
|
4190
4218
|
{
|
|
@@ -6206,7 +6234,7 @@
|
|
|
6206
6234
|
}
|
|
6207
6235
|
},
|
|
6208
6236
|
{
|
|
6209
|
-
"name": "
|
|
6237
|
+
"name": "SpotFulfillmentConfigStatus",
|
|
6210
6238
|
"type": {
|
|
6211
6239
|
"kind": "enum",
|
|
6212
6240
|
"variants": [
|
|
@@ -8423,6 +8451,16 @@
|
|
|
8423
8451
|
"code": 6218,
|
|
8424
8452
|
"name": "InvalidPerpPosition",
|
|
8425
8453
|
"msg": "Invalid Perp Position"
|
|
8454
|
+
},
|
|
8455
|
+
{
|
|
8456
|
+
"code": 6219,
|
|
8457
|
+
"name": "InvalidLiquidation",
|
|
8458
|
+
"msg": "Invalid Liquidation"
|
|
8459
|
+
},
|
|
8460
|
+
{
|
|
8461
|
+
"code": 6220,
|
|
8462
|
+
"name": "SpotFulfillmentConfigDisabled",
|
|
8463
|
+
"msg": "Spot Fulfullment Config Disabled"
|
|
8426
8464
|
}
|
|
8427
8465
|
]
|
|
8428
8466
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -284,6 +284,14 @@ export declare class SettlePnlExplanation {
|
|
|
284
284
|
expiredPosition: {};
|
|
285
285
|
};
|
|
286
286
|
}
|
|
287
|
+
export declare class SpotFulfillmentConfigStatus {
|
|
288
|
+
static readonly ENABLED: {
|
|
289
|
+
enabled: {};
|
|
290
|
+
};
|
|
291
|
+
static readonly DISABLED: {
|
|
292
|
+
disabled: {};
|
|
293
|
+
};
|
|
294
|
+
}
|
|
287
295
|
export declare class StakeAction {
|
|
288
296
|
static readonly STAKE: {
|
|
289
297
|
stake: {};
|
package/lib/types.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DefaultOrderParams = exports.LiquidationType = exports.LPAction = exports.TradeSide = exports.getVariant = exports.isOneOfVariant = exports.isVariant = exports.StakeAction = exports.SettlePnlExplanation = exports.DepositExplanation = exports.SpotFulfillmentStatus = exports.SpotFulfillmentType = exports.OrderTriggerCondition = exports.OrderActionExplanation = exports.OrderAction = exports.OrderStatus = exports.MarketType = exports.OrderType = exports.OracleSource = exports.DepositDirection = exports.PositionDirection = exports.SpotBalanceType = exports.SwapDirection = exports.AssetTier = exports.ContractTier = exports.ContractType = exports.UserStatus = exports.MarketStatus = exports.ExchangeStatus = void 0;
|
|
3
|
+
exports.DefaultOrderParams = exports.LiquidationType = exports.LPAction = exports.TradeSide = exports.getVariant = exports.isOneOfVariant = exports.isVariant = exports.StakeAction = exports.SpotFulfillmentConfigStatus = exports.SettlePnlExplanation = exports.DepositExplanation = exports.SpotFulfillmentStatus = exports.SpotFulfillmentType = exports.OrderTriggerCondition = exports.OrderActionExplanation = exports.OrderAction = exports.OrderStatus = exports.MarketType = exports.OrderType = exports.OracleSource = exports.DepositDirection = exports.PositionDirection = exports.SpotBalanceType = exports.SwapDirection = exports.AssetTier = exports.ContractTier = exports.ContractType = exports.UserStatus = exports.MarketStatus = exports.ExchangeStatus = void 0;
|
|
4
4
|
const _1 = require(".");
|
|
5
5
|
// # Utility Types / Enums / Constants
|
|
6
6
|
class ExchangeStatus {
|
|
@@ -172,6 +172,11 @@ class SettlePnlExplanation {
|
|
|
172
172
|
exports.SettlePnlExplanation = SettlePnlExplanation;
|
|
173
173
|
SettlePnlExplanation.NONE = { none: {} };
|
|
174
174
|
SettlePnlExplanation.EXPIRED_POSITION = { expiredPosition: {} };
|
|
175
|
+
class SpotFulfillmentConfigStatus {
|
|
176
|
+
}
|
|
177
|
+
exports.SpotFulfillmentConfigStatus = SpotFulfillmentConfigStatus;
|
|
178
|
+
SpotFulfillmentConfigStatus.ENABLED = { enabled: {} };
|
|
179
|
+
SpotFulfillmentConfigStatus.DISABLED = { disabled: {} };
|
|
175
180
|
class StakeAction {
|
|
176
181
|
}
|
|
177
182
|
exports.StakeAction = StakeAction;
|
package/lib/user.d.ts
CHANGED
|
@@ -97,6 +97,7 @@ export declare class User {
|
|
|
97
97
|
* @returns The maintenance margin requirement in USDC. : QUOTE_PRECISION
|
|
98
98
|
*/
|
|
99
99
|
getMaintenanceMarginRequirement(liquidationBuffer?: BN): BN;
|
|
100
|
+
getActivePerpPositions(): PerpPosition[];
|
|
100
101
|
/**
|
|
101
102
|
* calculates unrealized position price pnl
|
|
102
103
|
* @returns : Precision QUOTE_PRECISION
|
|
@@ -117,6 +118,11 @@ export declare class User {
|
|
|
117
118
|
* @returns : Precision QUOTE_PRECISION
|
|
118
119
|
*/
|
|
119
120
|
getTotalCollateral(marginCategory?: MarginCategory): BN;
|
|
121
|
+
/**
|
|
122
|
+
* calculates User Health by comparing total collateral and maint. margin requirement
|
|
123
|
+
* @returns : number (value from [0, 100])
|
|
124
|
+
*/
|
|
125
|
+
getHealth(): number;
|
|
120
126
|
/**
|
|
121
127
|
* calculates sum of position value across all positions in margin system
|
|
122
128
|
* @returns : Precision QUOTE_PRECISION
|
|
@@ -138,8 +144,8 @@ export declare class User {
|
|
|
138
144
|
* @returns : Precision TEN_THOUSAND
|
|
139
145
|
*/
|
|
140
146
|
getLeverage(): BN;
|
|
141
|
-
getTotalLiabilityValue(): BN;
|
|
142
|
-
getTotalAssetValue(): BN;
|
|
147
|
+
getTotalLiabilityValue(marginCategory?: MarginCategory): BN;
|
|
148
|
+
getTotalAssetValue(marginCategory?: MarginCategory): BN;
|
|
143
149
|
/**
|
|
144
150
|
* calculates max allowable leverage exceeding hitting requirement category
|
|
145
151
|
* @params category {Initial, Maintenance}
|
|
@@ -150,7 +156,7 @@ export declare class User {
|
|
|
150
156
|
* calculates margin ratio: total collateral / |total position value|
|
|
151
157
|
* @returns : Precision TEN_THOUSAND
|
|
152
158
|
*/
|
|
153
|
-
getMarginRatio(): BN;
|
|
159
|
+
getMarginRatio(marginCategory?: MarginCategory): BN;
|
|
154
160
|
canBeLiquidated(): boolean;
|
|
155
161
|
isBeingLiquidated(): boolean;
|
|
156
162
|
isBankrupt(): boolean;
|
|
@@ -160,7 +166,15 @@ export declare class User {
|
|
|
160
166
|
*/
|
|
161
167
|
needsToSettleFundingPayment(): boolean;
|
|
162
168
|
/**
|
|
163
|
-
* Calculate the liquidation price of a position, with optional parameter to calculate the liquidation price after a trade
|
|
169
|
+
* Calculate the liquidation price of a perp position, with optional parameter to calculate the liquidation price after a trade
|
|
170
|
+
* @param PerpPosition
|
|
171
|
+
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
|
|
172
|
+
* @param partial
|
|
173
|
+
* @returns Precision : PRICE_PRECISION
|
|
174
|
+
*/
|
|
175
|
+
spotLiquidationPrice(spotPosition: Pick<SpotPosition, 'marketIndex'>): BN;
|
|
176
|
+
/**
|
|
177
|
+
* Calculate the liquidation price of a perp position, with optional parameter to calculate the liquidation price after a trade
|
|
164
178
|
* @param PerpPosition
|
|
165
179
|
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
|
|
166
180
|
* @param partial
|
package/lib/user.js
CHANGED
|
@@ -270,14 +270,20 @@ class User {
|
|
|
270
270
|
getMaintenanceMarginRequirement(liquidationBuffer) {
|
|
271
271
|
return this.getMarginRequirement('Maintenance', liquidationBuffer);
|
|
272
272
|
}
|
|
273
|
+
getActivePerpPositions() {
|
|
274
|
+
return this.getUserAccount().perpPositions.filter((pos) => !pos.baseAssetAmount.eq(numericConstants_1.ZERO) ||
|
|
275
|
+
!pos.quoteAssetAmount.eq(numericConstants_1.ZERO) ||
|
|
276
|
+
!(pos.openOrders == 0) ||
|
|
277
|
+
!pos.lpShares.eq(numericConstants_1.ZERO));
|
|
278
|
+
}
|
|
273
279
|
/**
|
|
274
280
|
* calculates unrealized position price pnl
|
|
275
281
|
* @returns : Precision QUOTE_PRECISION
|
|
276
282
|
*/
|
|
277
283
|
getUnrealizedPNL(withFunding, marketIndex, withWeightMarginCategory) {
|
|
278
284
|
const quoteSpotMarket = this.driftClient.getQuoteSpotMarketAccount();
|
|
279
|
-
return this.
|
|
280
|
-
.
|
|
285
|
+
return this.getActivePerpPositions()
|
|
286
|
+
.filter((pos) => (marketIndex ? pos.marketIndex === marketIndex : true))
|
|
281
287
|
.reduce((unrealizedPnl, perpPosition) => {
|
|
282
288
|
const market = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
|
|
283
289
|
const oraclePriceData = this.getOracleDataForPerpMarket(market.marketIndex);
|
|
@@ -436,12 +442,48 @@ class User {
|
|
|
436
442
|
getTotalCollateral(marginCategory = 'Initial') {
|
|
437
443
|
return this.getSpotMarketAssetValue(undefined, marginCategory, true).add(this.getUnrealizedPNL(true, undefined, marginCategory));
|
|
438
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* calculates User Health by comparing total collateral and maint. margin requirement
|
|
447
|
+
* @returns : number (value from [0, 100])
|
|
448
|
+
*/
|
|
449
|
+
getHealth() {
|
|
450
|
+
const userAccount = this.getUserAccount();
|
|
451
|
+
if ((0, types_1.isVariant)(userAccount.status, 'beingLiquidated') ||
|
|
452
|
+
(0, types_1.isVariant)(userAccount.status, 'bankrupt')) {
|
|
453
|
+
return 0;
|
|
454
|
+
}
|
|
455
|
+
const totalCollateral = this.getTotalCollateral('Maintenance');
|
|
456
|
+
const maintenanceMarginReq = this.getMaintenanceMarginRequirement();
|
|
457
|
+
let health;
|
|
458
|
+
if (maintenanceMarginReq.eq(numericConstants_1.ZERO) && totalCollateral.gte(numericConstants_1.ZERO)) {
|
|
459
|
+
health = 100;
|
|
460
|
+
}
|
|
461
|
+
else if (totalCollateral.lte(numericConstants_1.ZERO)) {
|
|
462
|
+
health = 0;
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
// const totalCollateral = this.getTotalCollateral('Initial');
|
|
466
|
+
// const maintenanceMarginReq = this.getMaintenanceMarginRequirement();
|
|
467
|
+
const marginRatio = this.getMarginRatio().toNumber() / numericConstants_1.MARGIN_PRECISION.toNumber();
|
|
468
|
+
const maintenanceRatio = (maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
|
|
469
|
+
marginRatio;
|
|
470
|
+
const healthP1 = Math.max(0, (marginRatio - maintenanceRatio) * 100) + 1;
|
|
471
|
+
health = Math.min(1, Math.log(healthP1) / Math.log(100)) * 100;
|
|
472
|
+
if (health > 1) {
|
|
473
|
+
health = Math.round(health);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
health = Math.round(health * 100) / 100;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return health;
|
|
480
|
+
}
|
|
439
481
|
/**
|
|
440
482
|
* calculates sum of position value across all positions in margin system
|
|
441
483
|
* @returns : Precision QUOTE_PRECISION
|
|
442
484
|
*/
|
|
443
485
|
getTotalPerpPositionValue(marginCategory, liquidationBuffer, includeOpenOrders) {
|
|
444
|
-
return this.
|
|
486
|
+
return this.getActivePerpPositions().reduce((totalPerpValue, perpPosition) => {
|
|
445
487
|
const market = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
|
|
446
488
|
if (perpPosition.lpShares.gt(numericConstants_1.ZERO)) {
|
|
447
489
|
// is an lp
|
|
@@ -557,11 +599,11 @@ class User {
|
|
|
557
599
|
}
|
|
558
600
|
return totalLiabilityValue.mul(numericConstants_1.TEN_THOUSAND).div(totalAssetValue);
|
|
559
601
|
}
|
|
560
|
-
getTotalLiabilityValue() {
|
|
561
|
-
return this.getTotalPerpPositionValue(
|
|
602
|
+
getTotalLiabilityValue(marginCategory) {
|
|
603
|
+
return this.getTotalPerpPositionValue(marginCategory, undefined, true).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, undefined, true));
|
|
562
604
|
}
|
|
563
|
-
getTotalAssetValue() {
|
|
564
|
-
return this.getSpotMarketAssetValue(undefined,
|
|
605
|
+
getTotalAssetValue(marginCategory) {
|
|
606
|
+
return this.getSpotMarketAssetValue(undefined, marginCategory, true).add(this.getUnrealizedPNL(true, undefined, marginCategory));
|
|
565
607
|
}
|
|
566
608
|
/**
|
|
567
609
|
* calculates max allowable leverage exceeding hitting requirement category
|
|
@@ -593,12 +635,12 @@ class User {
|
|
|
593
635
|
* calculates margin ratio: total collateral / |total position value|
|
|
594
636
|
* @returns : Precision TEN_THOUSAND
|
|
595
637
|
*/
|
|
596
|
-
getMarginRatio() {
|
|
597
|
-
const totalLiabilityValue = this.getTotalLiabilityValue();
|
|
638
|
+
getMarginRatio(marginCategory) {
|
|
639
|
+
const totalLiabilityValue = this.getTotalLiabilityValue(marginCategory);
|
|
598
640
|
if (totalLiabilityValue.eq(numericConstants_1.ZERO)) {
|
|
599
641
|
return numericConstants_1.BN_MAX;
|
|
600
642
|
}
|
|
601
|
-
const totalAssetValue = this.getTotalAssetValue();
|
|
643
|
+
const totalAssetValue = this.getTotalAssetValue(marginCategory);
|
|
602
644
|
return totalAssetValue.mul(numericConstants_1.TEN_THOUSAND).div(totalLiabilityValue);
|
|
603
645
|
}
|
|
604
646
|
canBeLiquidated() {
|
|
@@ -640,7 +682,47 @@ class User {
|
|
|
640
682
|
return false;
|
|
641
683
|
}
|
|
642
684
|
/**
|
|
643
|
-
* Calculate the liquidation price of a position, with optional parameter to calculate the liquidation price after a trade
|
|
685
|
+
* Calculate the liquidation price of a perp position, with optional parameter to calculate the liquidation price after a trade
|
|
686
|
+
* @param PerpPosition
|
|
687
|
+
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
|
|
688
|
+
* @param partial
|
|
689
|
+
* @returns Precision : PRICE_PRECISION
|
|
690
|
+
*/
|
|
691
|
+
spotLiquidationPrice(spotPosition) {
|
|
692
|
+
const currentSpotPosition = this.getSpotPosition(spotPosition.marketIndex);
|
|
693
|
+
const mtc = this.getTotalCollateral('Maintenance');
|
|
694
|
+
const mmr = this.getMaintenanceMarginRequirement();
|
|
695
|
+
const deltaValueToLiq = mtc.sub(mmr); // QUOTE_PRECISION
|
|
696
|
+
const currentSpotMarket = this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
|
|
697
|
+
const tokenAmount = (0, spotBalance_1.getTokenAmount)(currentSpotPosition.scaledBalance, currentSpotMarket, currentSpotPosition.balanceType);
|
|
698
|
+
const tokenAmountQP = tokenAmount
|
|
699
|
+
.mul(numericConstants_1.QUOTE_PRECISION)
|
|
700
|
+
.div(new _1.BN(10 ** currentSpotMarket.decimals));
|
|
701
|
+
if (tokenAmountQP.abs().eq(numericConstants_1.ZERO)) {
|
|
702
|
+
return new _1.BN(-1);
|
|
703
|
+
}
|
|
704
|
+
let liqPriceDelta;
|
|
705
|
+
if ((0, types_1.isVariant)(currentSpotPosition.balanceType, 'borrow')) {
|
|
706
|
+
liqPriceDelta = deltaValueToLiq
|
|
707
|
+
.mul(numericConstants_1.PRICE_PRECISION)
|
|
708
|
+
.mul(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION)
|
|
709
|
+
.div(tokenAmountQP)
|
|
710
|
+
.div(new _1.BN(currentSpotMarket.maintenanceLiabilityWeight));
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
liqPriceDelta = deltaValueToLiq
|
|
714
|
+
.mul(numericConstants_1.PRICE_PRECISION)
|
|
715
|
+
.mul(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION)
|
|
716
|
+
.div(tokenAmountQP)
|
|
717
|
+
.div(new _1.BN(currentSpotMarket.maintenanceAssetWeight))
|
|
718
|
+
.mul(new _1.BN(-1));
|
|
719
|
+
}
|
|
720
|
+
const currentPrice = this.driftClient.getOracleDataForSpotMarket(spotPosition.marketIndex).price;
|
|
721
|
+
const liqPrice = currentPrice.add(liqPriceDelta);
|
|
722
|
+
return liqPrice;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Calculate the liquidation price of a perp position, with optional parameter to calculate the liquidation price after a trade
|
|
644
726
|
* @param PerpPosition
|
|
645
727
|
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
|
|
646
728
|
* @param partial
|
package/package.json
CHANGED
package/src/adminClient.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
MarketStatus,
|
|
12
12
|
ContractTier,
|
|
13
13
|
AssetTier,
|
|
14
|
+
SpotFulfillmentConfigStatus,
|
|
14
15
|
} from './types';
|
|
15
16
|
import { DEFAULT_MARKET_NAME, encodeName } from './userName';
|
|
16
17
|
import { BN } from '@project-serum/anchor';
|
|
@@ -881,6 +882,19 @@ export class AdminClient extends DriftClient {
|
|
|
881
882
|
});
|
|
882
883
|
}
|
|
883
884
|
|
|
885
|
+
public async updateSerumFulfillmentConfigStatus(
|
|
886
|
+
serumFulfillmentConfig: PublicKey,
|
|
887
|
+
status: SpotFulfillmentConfigStatus
|
|
888
|
+
): Promise<TransactionSignature> {
|
|
889
|
+
return await this.program.rpc.updateSerumFulfillmentConfigStatus(status, {
|
|
890
|
+
accounts: {
|
|
891
|
+
admin: this.wallet.publicKey,
|
|
892
|
+
state: await this.getStatePublicKey(),
|
|
893
|
+
serumFulfillmentConfig,
|
|
894
|
+
},
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
884
898
|
public async updateSpotMarketExpiry(
|
|
885
899
|
spotMarketIndex: number,
|
|
886
900
|
expiryTs: BN
|
package/src/config.ts
CHANGED
|
@@ -42,7 +42,7 @@ export const configs: { [key in DriftEnv]: DriftConfig } = {
|
|
|
42
42
|
PYTH_ORACLE_MAPPING_ADDRESS: 'AHtgzX45WTKfkPG53L6WYhGEXwQkN1BVknET3sVsLL8J',
|
|
43
43
|
DRIFT_PROGRAM_ID: 'dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH',
|
|
44
44
|
USDC_MINT_ADDRESS: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
|
45
|
-
SERUM_V3: '
|
|
45
|
+
SERUM_V3: 'srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX',
|
|
46
46
|
V2_ALPHA_TICKET_MINT_ADDRESS:
|
|
47
47
|
'Cmvhycb6LQvvzaShGw4iDHRLzeSSryioAsU98DSSkMNa',
|
|
48
48
|
PERP_MARKETS: MainnetPerpMarkets,
|
|
@@ -73,7 +73,7 @@ export const MainnetSpotMarkets: SpotMarketConfig[] = [
|
|
|
73
73
|
mint: new PublicKey(WRAPPED_SOL_MINT),
|
|
74
74
|
precision: LAMPORTS_PRECISION,
|
|
75
75
|
precisionExp: LAMPORTS_EXP,
|
|
76
|
-
serumMarket: new PublicKey('
|
|
76
|
+
serumMarket: new PublicKey('8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'),
|
|
77
77
|
},
|
|
78
78
|
];
|
|
79
79
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EventList = void 0;
|
|
4
|
+
class Node {
|
|
5
|
+
constructor(event, next, prev) {
|
|
6
|
+
this.event = event;
|
|
7
|
+
this.next = next;
|
|
8
|
+
this.prev = prev;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
class EventList {
|
|
12
|
+
constructor(eventType, maxSize, sortFn, orderDirection) {
|
|
13
|
+
this.eventType = eventType;
|
|
14
|
+
this.maxSize = maxSize;
|
|
15
|
+
this.sortFn = sortFn;
|
|
16
|
+
this.orderDirection = orderDirection;
|
|
17
|
+
this.size = 0;
|
|
18
|
+
}
|
|
19
|
+
insert(event) {
|
|
20
|
+
this.size++;
|
|
21
|
+
const newNode = new Node(event);
|
|
22
|
+
if (this.head === undefined) {
|
|
23
|
+
this.head = this.tail = newNode;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (this.sortFn(this.head.event, newNode.event) ===
|
|
27
|
+
(this.orderDirection === 'asc' ? 'less than' : 'greater than')) {
|
|
28
|
+
this.head.prev = newNode;
|
|
29
|
+
newNode.next = this.head;
|
|
30
|
+
this.head = newNode;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
let currentNode = this.head;
|
|
34
|
+
while (currentNode.next !== undefined &&
|
|
35
|
+
this.sortFn(currentNode.next.event, newNode.event) !==
|
|
36
|
+
(this.orderDirection === 'asc' ? 'less than' : 'greater than')) {
|
|
37
|
+
currentNode = currentNode.next;
|
|
38
|
+
}
|
|
39
|
+
newNode.next = currentNode.next;
|
|
40
|
+
if (currentNode.next !== undefined) {
|
|
41
|
+
newNode.next.prev = newNode;
|
|
42
|
+
}
|
|
43
|
+
currentNode.next = newNode;
|
|
44
|
+
newNode.prev = currentNode;
|
|
45
|
+
}
|
|
46
|
+
if (this.size > this.maxSize) {
|
|
47
|
+
this.detach();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
detach() {
|
|
51
|
+
const node = this.tail;
|
|
52
|
+
if (node.prev !== undefined) {
|
|
53
|
+
node.prev.next = node.next;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
this.head = node.next;
|
|
57
|
+
}
|
|
58
|
+
if (node.next !== undefined) {
|
|
59
|
+
node.next.prev = node.prev;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
this.tail = node.prev;
|
|
63
|
+
}
|
|
64
|
+
this.size--;
|
|
65
|
+
}
|
|
66
|
+
toArray() {
|
|
67
|
+
return Array.from(this);
|
|
68
|
+
}
|
|
69
|
+
*[Symbol.iterator]() {
|
|
70
|
+
let node = this.head;
|
|
71
|
+
while (node) {
|
|
72
|
+
yield node.event;
|
|
73
|
+
node = node.next;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.EventList = EventList;
|
|
@@ -49,12 +49,12 @@ export class PollingLogProvider implements LogProvider {
|
|
|
49
49
|
skipHistory && this.firstFetch ? 1 : undefined
|
|
50
50
|
);
|
|
51
51
|
|
|
52
|
-
this.firstFetch = false;
|
|
53
|
-
|
|
54
52
|
if (response === undefined) {
|
|
55
53
|
return;
|
|
56
54
|
}
|
|
57
55
|
|
|
56
|
+
this.firstFetch = false;
|
|
57
|
+
|
|
58
58
|
const { mostRecentTx, transactionLogs } = response;
|
|
59
59
|
|
|
60
60
|
for (const { txSig, slot, logs } of transactionLogs) {
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
var __awaiter =
|
|
3
|
+
(this && this.__awaiter) ||
|
|
4
|
+
function (thisArg, _arguments, P, generator) {
|
|
5
|
+
function adopt(value) {
|
|
6
|
+
return value instanceof P
|
|
7
|
+
? value
|
|
8
|
+
: new P(function (resolve) {
|
|
9
|
+
resolve(value);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
13
|
+
function fulfilled(value) {
|
|
14
|
+
try {
|
|
15
|
+
step(generator.next(value));
|
|
16
|
+
} catch (e) {
|
|
17
|
+
reject(e);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function rejected(value) {
|
|
21
|
+
try {
|
|
22
|
+
step(generator['throw'](value));
|
|
23
|
+
} catch (e) {
|
|
24
|
+
reject(e);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function step(result) {
|
|
28
|
+
result.done
|
|
29
|
+
? resolve(result.value)
|
|
30
|
+
: adopt(result.value).then(fulfilled, rejected);
|
|
31
|
+
}
|
|
32
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
36
|
+
exports.getTokenAddress = void 0;
|
|
37
|
+
const anchor_1 = require('@project-serum/anchor');
|
|
38
|
+
const __1 = require('..');
|
|
39
|
+
const spl_token_1 = require('@solana/spl-token');
|
|
40
|
+
const web3_js_1 = require('@solana/web3.js');
|
|
41
|
+
const __2 = require('..');
|
|
42
|
+
const banks_1 = require('../constants/spotMarkets');
|
|
43
|
+
const getTokenAddress = (mintAddress, userPubKey) => {
|
|
44
|
+
return spl_token_1.Token.getAssociatedTokenAddress(
|
|
45
|
+
new web3_js_1.PublicKey(`ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`),
|
|
46
|
+
spl_token_1.TOKEN_PROGRAM_ID,
|
|
47
|
+
new web3_js_1.PublicKey(mintAddress),
|
|
48
|
+
new web3_js_1.PublicKey(userPubKey)
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
exports.getTokenAddress = getTokenAddress;
|
|
52
|
+
const main = () =>
|
|
53
|
+
__awaiter(void 0, void 0, void 0, function* () {
|
|
54
|
+
// Initialize Drift SDK
|
|
55
|
+
const sdkConfig = __2.initialize({ env: 'devnet' });
|
|
56
|
+
// Set up the Wallet and Provider
|
|
57
|
+
const privateKey = process.env.BOT_PRIVATE_KEY; // stored as an array string
|
|
58
|
+
const keypair = web3_js_1.Keypair.fromSecretKey(
|
|
59
|
+
Uint8Array.from(JSON.parse(privateKey))
|
|
60
|
+
);
|
|
61
|
+
const wallet = new __1.Wallet(keypair);
|
|
62
|
+
// Set up the Connection
|
|
63
|
+
const rpcAddress = process.env.RPC_ADDRESS; // can use: https://api.devnet.solana.com for devnet; https://api.mainnet-beta.solana.com for mainnet;
|
|
64
|
+
const connection = new web3_js_1.Connection(rpcAddress);
|
|
65
|
+
// Set up the Provider
|
|
66
|
+
const provider = new anchor_1.AnchorProvider(
|
|
67
|
+
connection,
|
|
68
|
+
wallet,
|
|
69
|
+
anchor_1.AnchorProvider.defaultOptions()
|
|
70
|
+
);
|
|
71
|
+
// Check SOL Balance
|
|
72
|
+
const lamportsBalance = yield connection.getBalance(wallet.publicKey);
|
|
73
|
+
console.log('SOL balance:', lamportsBalance / Math.pow(10, 9));
|
|
74
|
+
// Misc. other things to set up
|
|
75
|
+
const usdcTokenAddress = yield exports.getTokenAddress(
|
|
76
|
+
sdkConfig.USDC_MINT_ADDRESS,
|
|
77
|
+
wallet.publicKey.toString()
|
|
78
|
+
);
|
|
79
|
+
// Set up the Drift Clearing House
|
|
80
|
+
const clearingHousePublicKey = new web3_js_1.PublicKey(
|
|
81
|
+
sdkConfig.DRIFT_PROGRAM_ID
|
|
82
|
+
);
|
|
83
|
+
const clearingHouse = new __2.ClearingHouse({
|
|
84
|
+
connection,
|
|
85
|
+
wallet: provider.wallet,
|
|
86
|
+
programID: clearingHousePublicKey,
|
|
87
|
+
});
|
|
88
|
+
yield clearingHouse.subscribe();
|
|
89
|
+
// Set up Clearing House user client
|
|
90
|
+
const user = new __2.ClearingHouseUser({
|
|
91
|
+
clearingHouse,
|
|
92
|
+
userAccountPublicKey: yield clearingHouse.getUserAccountPublicKey(),
|
|
93
|
+
});
|
|
94
|
+
//// Check if clearing house account exists for the current wallet
|
|
95
|
+
const userAccountExists = yield user.exists();
|
|
96
|
+
if (!userAccountExists) {
|
|
97
|
+
//// Create a Clearing House account by Depositing some USDC ($10,000 in this case)
|
|
98
|
+
const depositAmount = new anchor_1.BN(10000).mul(__2.QUOTE_PRECISION);
|
|
99
|
+
yield clearingHouse.initializeUserAccountAndDepositCollateral(
|
|
100
|
+
depositAmount,
|
|
101
|
+
yield exports.getTokenAddress(
|
|
102
|
+
usdcTokenAddress.toString(),
|
|
103
|
+
wallet.publicKey.toString()
|
|
104
|
+
),
|
|
105
|
+
banks_1.SpotMarkets['devnet'][0].marketIndex
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
yield user.subscribe();
|
|
109
|
+
// Get current price
|
|
110
|
+
const solMarketInfo = sdkConfig.PERP_MARKETS.find(
|
|
111
|
+
(market) => market.baseAssetSymbol === 'SOL'
|
|
112
|
+
);
|
|
113
|
+
const currentMarketPrice = __2.calculateMarkPrice(
|
|
114
|
+
clearingHouse.getMarketAccount(solMarketInfo.marketIndex),
|
|
115
|
+
undefined
|
|
116
|
+
);
|
|
117
|
+
const formattedPrice = __2.convertToNumber(
|
|
118
|
+
currentMarketPrice,
|
|
119
|
+
__2.PRICE_PRECISION
|
|
120
|
+
);
|
|
121
|
+
console.log(`Current Market Price is $${formattedPrice}`);
|
|
122
|
+
// Estimate the slippage for a $5000 LONG trade
|
|
123
|
+
const solMarketAccount = clearingHouse.getMarketAccount(
|
|
124
|
+
solMarketInfo.marketIndex
|
|
125
|
+
);
|
|
126
|
+
const longAmount = new anchor_1.BN(5000).mul(__2.QUOTE_PRECISION);
|
|
127
|
+
const slippage = __2.convertToNumber(
|
|
128
|
+
__2.calculateTradeSlippage(
|
|
129
|
+
__2.PositionDirection.LONG,
|
|
130
|
+
longAmount,
|
|
131
|
+
solMarketAccount,
|
|
132
|
+
'quote',
|
|
133
|
+
undefined
|
|
134
|
+
)[0],
|
|
135
|
+
__2.PRICE_PRECISION
|
|
136
|
+
);
|
|
137
|
+
console.log(
|
|
138
|
+
`Slippage for a $5000 LONG on the SOL market would be $${slippage}`
|
|
139
|
+
);
|
|
140
|
+
// Make a $5000 LONG trade
|
|
141
|
+
yield clearingHouse.openPosition(
|
|
142
|
+
__2.PositionDirection.LONG,
|
|
143
|
+
longAmount,
|
|
144
|
+
solMarketInfo.marketIndex
|
|
145
|
+
);
|
|
146
|
+
console.log(`LONGED $5000 SOL`);
|
|
147
|
+
// Reduce the position by $2000
|
|
148
|
+
const reduceAmount = new anchor_1.BN(2000).mul(__2.QUOTE_PRECISION);
|
|
149
|
+
yield clearingHouse.openPosition(
|
|
150
|
+
__2.PositionDirection.SHORT,
|
|
151
|
+
reduceAmount,
|
|
152
|
+
solMarketInfo.marketIndex
|
|
153
|
+
);
|
|
154
|
+
// Close the rest of the position
|
|
155
|
+
yield clearingHouse.closePosition(solMarketInfo.marketIndex);
|
|
156
|
+
});
|
|
157
|
+
main();
|
package/src/idl/drift.json
CHANGED
|
@@ -2070,6 +2070,34 @@
|
|
|
2070
2070
|
}
|
|
2071
2071
|
]
|
|
2072
2072
|
},
|
|
2073
|
+
{
|
|
2074
|
+
"name": "updateSerumFulfillmentConfigStatus",
|
|
2075
|
+
"accounts": [
|
|
2076
|
+
{
|
|
2077
|
+
"name": "state",
|
|
2078
|
+
"isMut": false,
|
|
2079
|
+
"isSigner": false
|
|
2080
|
+
},
|
|
2081
|
+
{
|
|
2082
|
+
"name": "serumFulfillmentConfig",
|
|
2083
|
+
"isMut": true,
|
|
2084
|
+
"isSigner": false
|
|
2085
|
+
},
|
|
2086
|
+
{
|
|
2087
|
+
"name": "admin",
|
|
2088
|
+
"isMut": true,
|
|
2089
|
+
"isSigner": true
|
|
2090
|
+
}
|
|
2091
|
+
],
|
|
2092
|
+
"args": [
|
|
2093
|
+
{
|
|
2094
|
+
"name": "status",
|
|
2095
|
+
"type": {
|
|
2096
|
+
"defined": "SpotFulfillmentConfigStatus"
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
]
|
|
2100
|
+
},
|
|
2073
2101
|
{
|
|
2074
2102
|
"name": "updateSerumVault",
|
|
2075
2103
|
"accounts": [
|
|
@@ -4184,7 +4212,7 @@
|
|
|
4184
4212
|
{
|
|
4185
4213
|
"name": "status",
|
|
4186
4214
|
"type": {
|
|
4187
|
-
"defined": "
|
|
4215
|
+
"defined": "SpotFulfillmentConfigStatus"
|
|
4188
4216
|
}
|
|
4189
4217
|
},
|
|
4190
4218
|
{
|
|
@@ -6206,7 +6234,7 @@
|
|
|
6206
6234
|
}
|
|
6207
6235
|
},
|
|
6208
6236
|
{
|
|
6209
|
-
"name": "
|
|
6237
|
+
"name": "SpotFulfillmentConfigStatus",
|
|
6210
6238
|
"type": {
|
|
6211
6239
|
"kind": "enum",
|
|
6212
6240
|
"variants": [
|
|
@@ -8423,6 +8451,16 @@
|
|
|
8423
8451
|
"code": 6218,
|
|
8424
8452
|
"name": "InvalidPerpPosition",
|
|
8425
8453
|
"msg": "Invalid Perp Position"
|
|
8454
|
+
},
|
|
8455
|
+
{
|
|
8456
|
+
"code": 6219,
|
|
8457
|
+
"name": "InvalidLiquidation",
|
|
8458
|
+
"msg": "Invalid Liquidation"
|
|
8459
|
+
},
|
|
8460
|
+
{
|
|
8461
|
+
"code": 6220,
|
|
8462
|
+
"name": "SpotFulfillmentConfigDisabled",
|
|
8463
|
+
"msg": "Spot Fulfullment Config Disabled"
|
|
8426
8464
|
}
|
|
8427
8465
|
]
|
|
8428
8466
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseTokenAccount = void 0;
|
|
4
|
+
const spl_token_1 = require("@solana/spl-token");
|
|
5
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
6
|
+
function parseTokenAccount(data) {
|
|
7
|
+
const accountInfo = spl_token_1.AccountLayout.decode(data);
|
|
8
|
+
accountInfo.mint = new web3_js_1.PublicKey(accountInfo.mint);
|
|
9
|
+
accountInfo.owner = new web3_js_1.PublicKey(accountInfo.owner);
|
|
10
|
+
accountInfo.amount = spl_token_1.u64.fromBuffer(accountInfo.amount);
|
|
11
|
+
if (accountInfo.delegateOption === 0) {
|
|
12
|
+
accountInfo.delegate = null;
|
|
13
|
+
// eslint-disable-next-line new-cap
|
|
14
|
+
accountInfo.delegatedAmount = new spl_token_1.u64(0);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
accountInfo.delegate = new web3_js_1.PublicKey(accountInfo.delegate);
|
|
18
|
+
accountInfo.delegatedAmount = spl_token_1.u64.fromBuffer(accountInfo.delegatedAmount);
|
|
19
|
+
}
|
|
20
|
+
accountInfo.isInitialized = accountInfo.state !== 0;
|
|
21
|
+
accountInfo.isFrozen = accountInfo.state === 2;
|
|
22
|
+
if (accountInfo.isNativeOption === 1) {
|
|
23
|
+
accountInfo.rentExemptReserve = spl_token_1.u64.fromBuffer(accountInfo.isNative);
|
|
24
|
+
accountInfo.isNative = true;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
accountInfo.rentExemptReserve = null;
|
|
28
|
+
accountInfo.isNative = false;
|
|
29
|
+
}
|
|
30
|
+
if (accountInfo.closeAuthorityOption === 0) {
|
|
31
|
+
accountInfo.closeAuthority = null;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
accountInfo.closeAuthority = new web3_js_1.PublicKey(accountInfo.closeAuthority);
|
|
35
|
+
}
|
|
36
|
+
return accountInfo;
|
|
37
|
+
}
|
|
38
|
+
exports.parseTokenAccount = parseTokenAccount;
|
package/src/tx/types.js
ADDED
package/src/tx/utils.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.wrapInTx = void 0;
|
|
4
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
5
|
+
const COMPUTE_UNITS_DEFAULT = 200000;
|
|
6
|
+
function wrapInTx(instruction, computeUnits = 600000 // TODO, requires less code change
|
|
7
|
+
) {
|
|
8
|
+
const tx = new web3_js_1.Transaction();
|
|
9
|
+
if (computeUnits != COMPUTE_UNITS_DEFAULT) {
|
|
10
|
+
tx.add(web3_js_1.ComputeBudgetProgram.requestUnits({
|
|
11
|
+
units: computeUnits,
|
|
12
|
+
additionalFee: 0,
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
return tx.add(instruction);
|
|
16
|
+
}
|
|
17
|
+
exports.wrapInTx = wrapInTx;
|
package/src/types.ts
CHANGED
|
@@ -173,6 +173,11 @@ export class SettlePnlExplanation {
|
|
|
173
173
|
static readonly EXPIRED_POSITION = { expiredPosition: {} };
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
export class SpotFulfillmentConfigStatus {
|
|
177
|
+
static readonly ENABLED = { enabled: {} };
|
|
178
|
+
static readonly DISABLED = { disabled: {} };
|
|
179
|
+
}
|
|
180
|
+
|
|
176
181
|
export class StakeAction {
|
|
177
182
|
static readonly STAKE = { stake: {} };
|
|
178
183
|
static readonly UNSTAKE_REQUEST = { unstakeRequest: {} };
|
package/src/user.ts
CHANGED
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
getWorstCaseTokenAmounts,
|
|
65
65
|
isSpotPositionAvailable,
|
|
66
66
|
} from './math/spotPosition';
|
|
67
|
+
|
|
67
68
|
export class User {
|
|
68
69
|
driftClient: DriftClient;
|
|
69
70
|
userAccountPublicKey: PublicKey;
|
|
@@ -410,6 +411,16 @@ export class User {
|
|
|
410
411
|
return this.getMarginRequirement('Maintenance', liquidationBuffer);
|
|
411
412
|
}
|
|
412
413
|
|
|
414
|
+
public getActivePerpPositions(): PerpPosition[] {
|
|
415
|
+
return this.getUserAccount().perpPositions.filter(
|
|
416
|
+
(pos) =>
|
|
417
|
+
!pos.baseAssetAmount.eq(ZERO) ||
|
|
418
|
+
!pos.quoteAssetAmount.eq(ZERO) ||
|
|
419
|
+
!(pos.openOrders == 0) ||
|
|
420
|
+
!pos.lpShares.eq(ZERO)
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
413
424
|
/**
|
|
414
425
|
* calculates unrealized position price pnl
|
|
415
426
|
* @returns : Precision QUOTE_PRECISION
|
|
@@ -420,10 +431,8 @@ export class User {
|
|
|
420
431
|
withWeightMarginCategory?: MarginCategory
|
|
421
432
|
): BN {
|
|
422
433
|
const quoteSpotMarket = this.driftClient.getQuoteSpotMarketAccount();
|
|
423
|
-
return this.
|
|
424
|
-
.
|
|
425
|
-
marketIndex ? pos.marketIndex === marketIndex : true
|
|
426
|
-
)
|
|
434
|
+
return this.getActivePerpPositions()
|
|
435
|
+
.filter((pos) => (marketIndex ? pos.marketIndex === marketIndex : true))
|
|
427
436
|
.reduce((unrealizedPnl, perpPosition) => {
|
|
428
437
|
const market = this.driftClient.getPerpMarketAccount(
|
|
429
438
|
perpPosition.marketIndex
|
|
@@ -757,6 +766,53 @@ export class User {
|
|
|
757
766
|
);
|
|
758
767
|
}
|
|
759
768
|
|
|
769
|
+
/**
|
|
770
|
+
* calculates User Health by comparing total collateral and maint. margin requirement
|
|
771
|
+
* @returns : number (value from [0, 100])
|
|
772
|
+
*/
|
|
773
|
+
public getHealth(): number {
|
|
774
|
+
const userAccount = this.getUserAccount();
|
|
775
|
+
|
|
776
|
+
if (
|
|
777
|
+
isVariant(userAccount.status, 'beingLiquidated') ||
|
|
778
|
+
isVariant(userAccount.status, 'bankrupt')
|
|
779
|
+
) {
|
|
780
|
+
return 0;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const totalCollateral = this.getTotalCollateral('Maintenance');
|
|
784
|
+
const maintenanceMarginReq = this.getMaintenanceMarginRequirement();
|
|
785
|
+
|
|
786
|
+
let health: number;
|
|
787
|
+
|
|
788
|
+
if (maintenanceMarginReq.eq(ZERO) && totalCollateral.gte(ZERO)) {
|
|
789
|
+
health = 100;
|
|
790
|
+
} else if (totalCollateral.lte(ZERO)) {
|
|
791
|
+
health = 0;
|
|
792
|
+
} else {
|
|
793
|
+
// const totalCollateral = this.getTotalCollateral('Initial');
|
|
794
|
+
// const maintenanceMarginReq = this.getMaintenanceMarginRequirement();
|
|
795
|
+
|
|
796
|
+
const marginRatio =
|
|
797
|
+
this.getMarginRatio().toNumber() / MARGIN_PRECISION.toNumber();
|
|
798
|
+
|
|
799
|
+
const maintenanceRatio =
|
|
800
|
+
(maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
|
|
801
|
+
marginRatio;
|
|
802
|
+
|
|
803
|
+
const healthP1 = Math.max(0, (marginRatio - maintenanceRatio) * 100) + 1;
|
|
804
|
+
|
|
805
|
+
health = Math.min(1, Math.log(healthP1) / Math.log(100)) * 100;
|
|
806
|
+
if (health > 1) {
|
|
807
|
+
health = Math.round(health);
|
|
808
|
+
} else {
|
|
809
|
+
health = Math.round(health * 100) / 100;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return health;
|
|
814
|
+
}
|
|
815
|
+
|
|
760
816
|
/**
|
|
761
817
|
* calculates sum of position value across all positions in margin system
|
|
762
818
|
* @returns : Precision QUOTE_PRECISION
|
|
@@ -766,7 +822,7 @@ export class User {
|
|
|
766
822
|
liquidationBuffer?: BN,
|
|
767
823
|
includeOpenOrders?: boolean
|
|
768
824
|
): BN {
|
|
769
|
-
return this.
|
|
825
|
+
return this.getActivePerpPositions().reduce(
|
|
770
826
|
(totalPerpValue, perpPosition) => {
|
|
771
827
|
const market = this.driftClient.getPerpMarketAccount(
|
|
772
828
|
perpPosition.marketIndex
|
|
@@ -955,15 +1011,20 @@ export class User {
|
|
|
955
1011
|
return totalLiabilityValue.mul(TEN_THOUSAND).div(totalAssetValue);
|
|
956
1012
|
}
|
|
957
1013
|
|
|
958
|
-
getTotalLiabilityValue(): BN {
|
|
959
|
-
return this.getTotalPerpPositionValue(
|
|
960
|
-
this.getSpotMarketLiabilityValue(
|
|
1014
|
+
getTotalLiabilityValue(marginCategory?: MarginCategory): BN {
|
|
1015
|
+
return this.getTotalPerpPositionValue(marginCategory, undefined, true).add(
|
|
1016
|
+
this.getSpotMarketLiabilityValue(
|
|
1017
|
+
undefined,
|
|
1018
|
+
marginCategory,
|
|
1019
|
+
undefined,
|
|
1020
|
+
true
|
|
1021
|
+
)
|
|
961
1022
|
);
|
|
962
1023
|
}
|
|
963
1024
|
|
|
964
|
-
getTotalAssetValue(): BN {
|
|
965
|
-
return this.getSpotMarketAssetValue(undefined,
|
|
966
|
-
this.getUnrealizedPNL(true, undefined,
|
|
1025
|
+
getTotalAssetValue(marginCategory?: MarginCategory): BN {
|
|
1026
|
+
return this.getSpotMarketAssetValue(undefined, marginCategory, true).add(
|
|
1027
|
+
this.getUnrealizedPNL(true, undefined, marginCategory)
|
|
967
1028
|
);
|
|
968
1029
|
}
|
|
969
1030
|
|
|
@@ -1008,14 +1069,14 @@ export class User {
|
|
|
1008
1069
|
* calculates margin ratio: total collateral / |total position value|
|
|
1009
1070
|
* @returns : Precision TEN_THOUSAND
|
|
1010
1071
|
*/
|
|
1011
|
-
public getMarginRatio(): BN {
|
|
1012
|
-
const totalLiabilityValue = this.getTotalLiabilityValue();
|
|
1072
|
+
public getMarginRatio(marginCategory?: MarginCategory): BN {
|
|
1073
|
+
const totalLiabilityValue = this.getTotalLiabilityValue(marginCategory);
|
|
1013
1074
|
|
|
1014
1075
|
if (totalLiabilityValue.eq(ZERO)) {
|
|
1015
1076
|
return BN_MAX;
|
|
1016
1077
|
}
|
|
1017
1078
|
|
|
1018
|
-
const totalAssetValue = this.getTotalAssetValue();
|
|
1079
|
+
const totalAssetValue = this.getTotalAssetValue(marginCategory);
|
|
1019
1080
|
|
|
1020
1081
|
return totalAssetValue.mul(TEN_THOUSAND).div(totalLiabilityValue);
|
|
1021
1082
|
}
|
|
@@ -1081,7 +1142,64 @@ export class User {
|
|
|
1081
1142
|
}
|
|
1082
1143
|
|
|
1083
1144
|
/**
|
|
1084
|
-
* Calculate the liquidation price of a position, with optional parameter to calculate the liquidation price after a trade
|
|
1145
|
+
* Calculate the liquidation price of a perp position, with optional parameter to calculate the liquidation price after a trade
|
|
1146
|
+
* @param PerpPosition
|
|
1147
|
+
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
|
|
1148
|
+
* @param partial
|
|
1149
|
+
* @returns Precision : PRICE_PRECISION
|
|
1150
|
+
*/
|
|
1151
|
+
public spotLiquidationPrice(
|
|
1152
|
+
spotPosition: Pick<SpotPosition, 'marketIndex'>
|
|
1153
|
+
): BN {
|
|
1154
|
+
const currentSpotPosition = this.getSpotPosition(spotPosition.marketIndex);
|
|
1155
|
+
|
|
1156
|
+
const mtc = this.getTotalCollateral('Maintenance');
|
|
1157
|
+
const mmr = this.getMaintenanceMarginRequirement();
|
|
1158
|
+
|
|
1159
|
+
const deltaValueToLiq = mtc.sub(mmr); // QUOTE_PRECISION
|
|
1160
|
+
|
|
1161
|
+
const currentSpotMarket = this.driftClient.getSpotMarketAccount(
|
|
1162
|
+
spotPosition.marketIndex
|
|
1163
|
+
);
|
|
1164
|
+
const tokenAmount = getTokenAmount(
|
|
1165
|
+
currentSpotPosition.scaledBalance,
|
|
1166
|
+
currentSpotMarket,
|
|
1167
|
+
currentSpotPosition.balanceType
|
|
1168
|
+
);
|
|
1169
|
+
const tokenAmountQP = tokenAmount
|
|
1170
|
+
.mul(QUOTE_PRECISION)
|
|
1171
|
+
.div(new BN(10 ** currentSpotMarket.decimals));
|
|
1172
|
+
|
|
1173
|
+
if (tokenAmountQP.abs().eq(ZERO)) {
|
|
1174
|
+
return new BN(-1);
|
|
1175
|
+
}
|
|
1176
|
+
let liqPriceDelta: BN;
|
|
1177
|
+
if (isVariant(currentSpotPosition.balanceType, 'borrow')) {
|
|
1178
|
+
liqPriceDelta = deltaValueToLiq
|
|
1179
|
+
.mul(PRICE_PRECISION)
|
|
1180
|
+
.mul(SPOT_MARKET_WEIGHT_PRECISION)
|
|
1181
|
+
.div(tokenAmountQP)
|
|
1182
|
+
.div(new BN(currentSpotMarket.maintenanceLiabilityWeight));
|
|
1183
|
+
} else {
|
|
1184
|
+
liqPriceDelta = deltaValueToLiq
|
|
1185
|
+
.mul(PRICE_PRECISION)
|
|
1186
|
+
.mul(SPOT_MARKET_WEIGHT_PRECISION)
|
|
1187
|
+
.div(tokenAmountQP)
|
|
1188
|
+
.div(new BN(currentSpotMarket.maintenanceAssetWeight))
|
|
1189
|
+
.mul(new BN(-1));
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
const currentPrice = this.driftClient.getOracleDataForSpotMarket(
|
|
1193
|
+
spotPosition.marketIndex
|
|
1194
|
+
).price;
|
|
1195
|
+
|
|
1196
|
+
const liqPrice = currentPrice.add(liqPriceDelta);
|
|
1197
|
+
|
|
1198
|
+
return liqPrice;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* Calculate the liquidation price of a perp position, with optional parameter to calculate the liquidation price after a trade
|
|
1085
1203
|
* @param PerpPosition
|
|
1086
1204
|
* @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
|
|
1087
1205
|
* @param partial
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.findComputeUnitConsumption = void 0;
|
|
13
|
+
function findComputeUnitConsumption(programId, connection, txSignature, commitment = 'confirmed') {
|
|
14
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
15
|
+
const tx = yield connection.getTransaction(txSignature, { commitment });
|
|
16
|
+
const computeUnits = [];
|
|
17
|
+
const regex = new RegExp(`Program ${programId.toString()} consumed ([0-9]{0,6}) of ([0-9]{0,7}) compute units`);
|
|
18
|
+
tx.meta.logMessages.forEach((logMessage) => {
|
|
19
|
+
const match = logMessage.match(regex);
|
|
20
|
+
if (match && match[1]) {
|
|
21
|
+
computeUnits.push(match[1]);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return computeUnits;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
exports.findComputeUnitConsumption = findComputeUnitConsumption;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTokenAddress = void 0;
|
|
4
|
+
const spl_token_1 = require("@solana/spl-token");
|
|
5
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
6
|
+
const getTokenAddress = (mintAddress, userPubKey) => {
|
|
7
|
+
return spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, new web3_js_1.PublicKey(mintAddress), new web3_js_1.PublicKey(userPubKey));
|
|
8
|
+
};
|
|
9
|
+
exports.getTokenAddress = getTokenAddress;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.promiseTimeout = void 0;
|
|
4
|
+
function promiseTimeout(promise, timeoutMs) {
|
|
5
|
+
let timeoutId;
|
|
6
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
7
|
+
timeoutId = setTimeout(() => resolve(null), timeoutMs);
|
|
8
|
+
});
|
|
9
|
+
return Promise.race([promise, timeoutPromise]).then((result) => {
|
|
10
|
+
clearTimeout(timeoutId);
|
|
11
|
+
return result;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
exports.promiseTimeout = promiseTimeout;
|
package/src/util/tps.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.estimateTps = void 0;
|
|
13
|
+
function estimateTps(programId, connection, failed) {
|
|
14
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
15
|
+
let signatures = yield connection.getSignaturesForAddress(programId, undefined, 'finalized');
|
|
16
|
+
if (failed) {
|
|
17
|
+
signatures = signatures.filter((signature) => signature.err);
|
|
18
|
+
}
|
|
19
|
+
const numberOfSignatures = signatures.length;
|
|
20
|
+
if (numberOfSignatures === 0) {
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
return (numberOfSignatures /
|
|
24
|
+
(signatures[0].blockTime - signatures[numberOfSignatures - 1].blockTime));
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
exports.estimateTps = estimateTps;
|