@drift-labs/sdk 2.52.0-beta.0 → 2.52.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/VERSION +1 -1
- package/lib/accounts/basicUserAccountSubscriber.d.ts +4 -0
- package/lib/accounts/basicUserAccountSubscriber.js +4 -0
- package/lib/accounts/bulkAccountLoader.js +7 -1
- package/lib/accounts/oneShotUserAccountSubscriber.d.ts +17 -0
- package/lib/accounts/oneShotUserAccountSubscriber.js +48 -0
- package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.js +1 -0
- package/lib/accounts/webSocketAccountSubscriber.d.ts +1 -1
- package/lib/accounts/webSocketAccountSubscriber.js +7 -4
- package/lib/accounts/webSocketProgramAccountSubscriber.d.ts +1 -1
- package/lib/accounts/webSocketProgramAccountSubscriber.js +7 -4
- package/lib/adminClient.d.ts +1 -0
- package/lib/adminClient.js +9 -0
- package/lib/constants/numericConstants.d.ts +3 -0
- package/lib/constants/numericConstants.js +4 -1
- package/lib/dlob/orderBookLevels.d.ts +22 -0
- package/lib/dlob/orderBookLevels.js +115 -1
- package/lib/driftClient.d.ts +1 -0
- package/lib/driftClient.js +14 -5
- package/lib/events/webSocketLogProvider.js +3 -3
- package/lib/factory/bigNum.d.ts +1 -1
- package/lib/factory/bigNum.js +5 -2
- package/lib/idl/drift.json +13 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/math/amm.d.ts +5 -1
- package/lib/math/amm.js +62 -13
- package/lib/phoenix/phoenixSubscriber.js +2 -2
- package/lib/slot/SlotSubscriber.d.ts +11 -3
- package/lib/slot/SlotSubscriber.js +40 -4
- package/lib/types.d.ts +1 -1
- package/lib/userMap/userMap.d.ts +3 -0
- package/lib/userMap/userMap.js +10 -1
- package/lib/userStats.d.ts +1 -0
- package/lib/userStats.js +3 -0
- package/package.json +1 -1
- package/src/accounts/basicUserAccountSubscriber.ts +4 -0
- package/src/accounts/bulkAccountLoader.ts +10 -3
- package/src/accounts/oneShotUserAccountSubscriber.ts +64 -0
- package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +1 -0
- package/src/accounts/webSocketAccountSubscriber.ts +7 -4
- package/src/accounts/webSocketProgramAccountSubscriber.ts +7 -4
- package/src/adminClient.ts +13 -0
- package/src/constants/numericConstants.ts +4 -0
- package/src/dlob/orderBookLevels.ts +136 -0
- package/src/driftClient.ts +21 -10
- package/src/events/webSocketLogProvider.ts +3 -3
- package/src/factory/bigNum.ts +11 -2
- package/src/idl/drift.json +13 -1
- package/src/index.ts +1 -0
- package/src/math/amm.ts +159 -25
- package/src/phoenix/phoenixSubscriber.ts +2 -2
- package/src/slot/SlotSubscriber.ts +52 -5
- package/src/types.ts +1 -1
- package/src/userMap/userMap.ts +17 -3
- package/src/userStats.ts +8 -0
- package/tests/amm/test.ts +219 -11
- package/tests/bn/test.ts +27 -0
- package/tests/dlob/helpers.ts +1 -1
- package/tests/dlob/test.ts +372 -2
package/lib/math/amm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.calculateMaxBaseAssetAmountFillable = exports.calculateQuoteAssetAmountSwapped = exports.calculateMaxBaseAssetAmountToTrade = exports.calculateTerminalPrice = exports.getSwapDirection = exports.calculateSwapOutput = exports.calculateSpreadReserves = exports.calculateSpread = exports.calculateSpreadBN = exports.calculateVolSpreadBN = exports.calculateMaxSpread = exports.calculateEffectiveLeverage = exports.calculateInventoryScale = exports.calculateMarketOpenBidAsk = exports.calculateAmmReservesAfterSwap = exports.calculatePrice = exports.calculateBidAskPrice = exports.calculateUpdatedAMMSpreadReserves = exports.calculateUpdatedAMM = exports.calculateNewAmm = exports.calculateOptimalPegAndBudget = exports.calculatePegFromTargetPrice = void 0;
|
|
3
|
+
exports.calculateMaxBaseAssetAmountFillable = exports.calculateQuoteAssetAmountSwapped = exports.calculateMaxBaseAssetAmountToTrade = exports.calculateTerminalPrice = exports.getSwapDirection = exports.calculateSwapOutput = exports.calculateSpreadReserves = exports.calculateSpread = exports.calculateSpreadBN = exports.calculateVolSpreadBN = exports.calculateMaxSpread = exports.calculateEffectiveLeverage = exports.calculateReferencePriceOffset = exports.calculateInventoryScale = exports.calculateInventoryLiquidityRatio = exports.calculateMarketOpenBidAsk = exports.calculateAmmReservesAfterSwap = exports.calculatePrice = exports.calculateBidAskPrice = exports.calculateUpdatedAMMSpreadReserves = exports.calculateUpdatedAMM = exports.calculateNewAmm = exports.calculateOptimalPegAndBudget = exports.calculatePegFromTargetPrice = void 0;
|
|
4
4
|
const anchor_1 = require("@coral-xyz/anchor");
|
|
5
5
|
const numericConstants_1 = require("../constants/numericConstants");
|
|
6
6
|
const types_1 = require("../types");
|
|
@@ -201,11 +201,7 @@ function calculateMarketOpenBidAsk(baseAssetReserve, minBaseAssetReserve, maxBas
|
|
|
201
201
|
return [openBids, openAsks];
|
|
202
202
|
}
|
|
203
203
|
exports.calculateMarketOpenBidAsk = calculateMarketOpenBidAsk;
|
|
204
|
-
function
|
|
205
|
-
if (baseAssetAmountWithAmm.eq(numericConstants_1.ZERO)) {
|
|
206
|
-
return 1;
|
|
207
|
-
}
|
|
208
|
-
const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = numericConstants_1.BID_ASK_SPREAD_PRECISION.mul(new anchor_1.BN(10));
|
|
204
|
+
function calculateInventoryLiquidityRatio(baseAssetAmountWithAmm, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve) {
|
|
209
205
|
// inventory skew
|
|
210
206
|
const [openBids, openAsks] = calculateMarketOpenBidAsk(baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve);
|
|
211
207
|
const minSideLiquidity = anchor_1.BN.min(openBids.abs(), openAsks.abs());
|
|
@@ -213,6 +209,15 @@ function calculateInventoryScale(baseAssetAmountWithAmm, baseAssetReserve, minBa
|
|
|
213
209
|
.mul(numericConstants_1.PERCENTAGE_PRECISION)
|
|
214
210
|
.div(anchor_1.BN.max(minSideLiquidity, numericConstants_1.ONE))
|
|
215
211
|
.abs(), numericConstants_1.PERCENTAGE_PRECISION);
|
|
212
|
+
return inventoryScaleBN;
|
|
213
|
+
}
|
|
214
|
+
exports.calculateInventoryLiquidityRatio = calculateInventoryLiquidityRatio;
|
|
215
|
+
function calculateInventoryScale(baseAssetAmountWithAmm, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve, directionalSpread, maxSpread) {
|
|
216
|
+
if (baseAssetAmountWithAmm.eq(numericConstants_1.ZERO)) {
|
|
217
|
+
return 1;
|
|
218
|
+
}
|
|
219
|
+
const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = numericConstants_1.BID_ASK_SPREAD_PRECISION.mul(new anchor_1.BN(10));
|
|
220
|
+
const inventoryScaleBN = calculateInventoryLiquidityRatio(baseAssetAmountWithAmm, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve);
|
|
216
221
|
const inventoryScaleMaxBN = anchor_1.BN.max(MAX_BID_ASK_INVENTORY_SKEW_FACTOR, new anchor_1.BN(maxSpread)
|
|
217
222
|
.mul(numericConstants_1.BID_ASK_SPREAD_PRECISION)
|
|
218
223
|
.div(new anchor_1.BN(Math.max(directionalSpread, 1))));
|
|
@@ -220,6 +225,36 @@ function calculateInventoryScale(baseAssetAmountWithAmm, baseAssetReserve, minBa
|
|
|
220
225
|
return inventoryScaleCapped;
|
|
221
226
|
}
|
|
222
227
|
exports.calculateInventoryScale = calculateInventoryScale;
|
|
228
|
+
function calculateReferencePriceOffset(reservePrice, last24hAvgFundingRate, liquidityFraction, oracleTwapFast, markTwapFast, oracleTwapSlow, markTwapSlow, maxOffsetPct) {
|
|
229
|
+
if (last24hAvgFundingRate.eq(numericConstants_1.ZERO)) {
|
|
230
|
+
return numericConstants_1.ZERO;
|
|
231
|
+
}
|
|
232
|
+
const maxOffsetInPrice = new anchor_1.BN(maxOffsetPct)
|
|
233
|
+
.mul(reservePrice)
|
|
234
|
+
.div(numericConstants_1.PERCENTAGE_PRECISION);
|
|
235
|
+
// Calculate quote denominated market premium
|
|
236
|
+
const markPremiumMinute = (0, __1.clampBN)(markTwapFast.sub(oracleTwapFast), maxOffsetInPrice.mul(new anchor_1.BN(-1)), maxOffsetInPrice);
|
|
237
|
+
const markPremiumHour = (0, __1.clampBN)(markTwapSlow.sub(oracleTwapSlow), maxOffsetInPrice.mul(new anchor_1.BN(-1)), maxOffsetInPrice);
|
|
238
|
+
// Convert last24hAvgFundingRate to quote denominated premium
|
|
239
|
+
const markPremiumDay = (0, __1.clampBN)(last24hAvgFundingRate.div(numericConstants_1.FUNDING_RATE_BUFFER_PRECISION).mul(new anchor_1.BN(24)), maxOffsetInPrice.mul(new anchor_1.BN(-1)), maxOffsetInPrice);
|
|
240
|
+
// Take average clamped premium as the price-based offset
|
|
241
|
+
const markPremiumAvg = markPremiumMinute
|
|
242
|
+
.add(markPremiumHour)
|
|
243
|
+
.add(markPremiumDay)
|
|
244
|
+
.div(new anchor_1.BN(3));
|
|
245
|
+
const markPremiumAvgPct = markPremiumAvg
|
|
246
|
+
.mul(numericConstants_1.PRICE_PRECISION)
|
|
247
|
+
.div(reservePrice);
|
|
248
|
+
const inventoryPct = (0, __1.clampBN)(liquidityFraction.mul(new anchor_1.BN(maxOffsetPct)).div(numericConstants_1.PERCENTAGE_PRECISION), maxOffsetInPrice.mul(new anchor_1.BN(-1)), maxOffsetInPrice);
|
|
249
|
+
// Only apply when inventory is consistent with recent and 24h market premium
|
|
250
|
+
let offsetPct = markPremiumAvgPct.add(inventoryPct);
|
|
251
|
+
if (!(0, __1.sigNum)(inventoryPct).eq((0, __1.sigNum)(markPremiumAvgPct))) {
|
|
252
|
+
offsetPct = numericConstants_1.ZERO;
|
|
253
|
+
}
|
|
254
|
+
const clampedOffsetPct = (0, __1.clampBN)(offsetPct, new anchor_1.BN(-maxOffsetPct), new anchor_1.BN(maxOffsetPct));
|
|
255
|
+
return clampedOffsetPct;
|
|
256
|
+
}
|
|
257
|
+
exports.calculateReferencePriceOffset = calculateReferencePriceOffset;
|
|
223
258
|
function calculateEffectiveLeverage(baseSpread, quoteAssetReserve, terminalQuoteAssetReserve, pegMultiplier, netBaseAssetAmount, reservePrice, totalFeeMinusDistributions) {
|
|
224
259
|
// vAMM skew
|
|
225
260
|
const netBaseAssetValue = quoteAssetReserve
|
|
@@ -283,6 +318,8 @@ function calculateSpreadBN(baseSpread, lastOracleReservePriceSpreadPct, lastOrac
|
|
|
283
318
|
halfRevenueRetreatAmount: 0,
|
|
284
319
|
longSpreadwRevRetreat: 0,
|
|
285
320
|
shortSpreadwRevRetreat: 0,
|
|
321
|
+
longSpreadwOffsetShrink: 0,
|
|
322
|
+
shortSpreadwOffsetShrink: 0,
|
|
286
323
|
totalSpread: 0,
|
|
287
324
|
longSpread: 0,
|
|
288
325
|
shortSpread: 0,
|
|
@@ -380,11 +417,13 @@ function calculateSpreadBN(baseSpread, lastOracleReservePriceSpreadPct, lastOrac
|
|
|
380
417
|
return [longSpread, shortSpread];
|
|
381
418
|
}
|
|
382
419
|
exports.calculateSpreadBN = calculateSpreadBN;
|
|
383
|
-
function calculateSpread(amm, oraclePriceData, now) {
|
|
420
|
+
function calculateSpread(amm, oraclePriceData, now, reservePrice) {
|
|
384
421
|
if (amm.baseSpread == 0 || amm.curveUpdateIntensity == 0) {
|
|
385
422
|
return [amm.baseSpread / 2, amm.baseSpread / 2];
|
|
386
423
|
}
|
|
387
|
-
|
|
424
|
+
if (!reservePrice) {
|
|
425
|
+
reservePrice = calculatePrice(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
|
|
426
|
+
}
|
|
388
427
|
const targetPrice = (oraclePriceData === null || oraclePriceData === void 0 ? void 0 : oraclePriceData.price) || reservePrice;
|
|
389
428
|
const confInterval = oraclePriceData.confidence || numericConstants_1.ZERO;
|
|
390
429
|
const targetMarkSpreadPct = reservePrice
|
|
@@ -410,10 +449,15 @@ function calculateSpreadReserves(amm, oraclePriceData, now) {
|
|
|
410
449
|
quoteAssetReserve: amm.quoteAssetReserve,
|
|
411
450
|
};
|
|
412
451
|
}
|
|
413
|
-
|
|
452
|
+
let spreadFraction = new anchor_1.BN(spread / 2);
|
|
453
|
+
// make non-zero
|
|
454
|
+
if (spreadFraction.eq(numericConstants_1.ZERO)) {
|
|
455
|
+
spreadFraction = spread >= 0 ? new anchor_1.BN(1) : new anchor_1.BN(-1);
|
|
456
|
+
}
|
|
414
457
|
const quoteAssetReserveDelta = amm.quoteAssetReserve.div(numericConstants_1.BID_ASK_SPREAD_PRECISION.div(spreadFraction));
|
|
415
458
|
let quoteAssetReserve;
|
|
416
|
-
if ((0, types_1.isVariant)(direction, 'long'))
|
|
459
|
+
if ((spread >= 0 && (0, types_1.isVariant)(direction, 'long')) ||
|
|
460
|
+
(spread <= 0 && (0, types_1.isVariant)(direction, 'short'))) {
|
|
417
461
|
quoteAssetReserve = amm.quoteAssetReserve.add(quoteAssetReserveDelta);
|
|
418
462
|
}
|
|
419
463
|
else {
|
|
@@ -425,9 +469,14 @@ function calculateSpreadReserves(amm, oraclePriceData, now) {
|
|
|
425
469
|
quoteAssetReserve,
|
|
426
470
|
};
|
|
427
471
|
}
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
const
|
|
472
|
+
const reservePrice = calculatePrice(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
|
|
473
|
+
// always allow 10 bps of price offset, up to a fifth of the market's max_spread
|
|
474
|
+
const maxOffset = Math.max(amm.maxSpread / 5, numericConstants_1.PERCENTAGE_PRECISION.toNumber() / 1000);
|
|
475
|
+
const liquidityFraction = calculateInventoryLiquidityRatio(amm.baseAssetAmountWithAmm, amm.baseAssetReserve, amm.minBaseAssetReserve, amm.maxBaseAssetReserve);
|
|
476
|
+
const referencePriceOffset = calculateReferencePriceOffset(reservePrice, amm.last24HAvgFundingRate, liquidityFraction, amm.historicalOracleData.lastOraclePriceTwap5Min, amm.lastMarkPriceTwap5Min, amm.historicalOracleData.lastOraclePriceTwap, amm.lastMarkPriceTwap, maxOffset);
|
|
477
|
+
const [longSpread, shortSpread] = calculateSpread(amm, oraclePriceData, now, reservePrice);
|
|
478
|
+
const askReserves = calculateSpreadReserve(longSpread + referencePriceOffset.toNumber(), types_1.PositionDirection.LONG, amm);
|
|
479
|
+
const bidReserves = calculateSpreadReserve(shortSpread + referencePriceOffset.toNumber(), types_1.PositionDirection.SHORT, amm);
|
|
431
480
|
return [bidReserves, askReserves];
|
|
432
481
|
}
|
|
433
482
|
exports.calculateSpreadReserves = calculateSpreadReserves;
|
|
@@ -104,9 +104,9 @@ class PhoenixSubscriber {
|
|
|
104
104
|
const ladder = (0, phoenix_sdk_1.getMarketUiLadder)(this.market, this.lastSlot, this.lastUnixTimestamp, 20);
|
|
105
105
|
for (let i = 0; i < ladder[side].length; i++) {
|
|
106
106
|
const { price, quantity } = ladder[side][i];
|
|
107
|
-
const size = new anchor_1.BN(
|
|
107
|
+
const size = new anchor_1.BN(quantity).mul(new anchor_1.BN(basePrecision));
|
|
108
108
|
yield {
|
|
109
|
-
price: new anchor_1.BN(
|
|
109
|
+
price: new anchor_1.BN(price).mul(new anchor_1.BN(pricePrecision)),
|
|
110
110
|
size,
|
|
111
111
|
sources: {
|
|
112
112
|
phoenix: size,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
2
3
|
import { Connection } from '@solana/web3.js';
|
|
3
4
|
import { EventEmitter } from 'events';
|
|
4
5
|
import StrictEventEmitter from 'strict-event-emitter-types/types/src';
|
|
5
|
-
type SlotSubscriberConfig = {
|
|
6
|
+
type SlotSubscriberConfig = {
|
|
7
|
+
resubTimeoutMs?: number;
|
|
8
|
+
};
|
|
6
9
|
export interface SlotSubscriberEvents {
|
|
7
10
|
newSlot: (newSlot: number) => void;
|
|
8
11
|
}
|
|
@@ -11,9 +14,14 @@ export declare class SlotSubscriber {
|
|
|
11
14
|
currentSlot: number;
|
|
12
15
|
subscriptionId: number;
|
|
13
16
|
eventEmitter: StrictEventEmitter<EventEmitter, SlotSubscriberEvents>;
|
|
14
|
-
|
|
17
|
+
timeoutId?: NodeJS.Timeout;
|
|
18
|
+
resubTimeoutMs?: number;
|
|
19
|
+
isUnsubscribing: boolean;
|
|
20
|
+
receivingData: boolean;
|
|
21
|
+
constructor(connection: Connection, config?: SlotSubscriberConfig);
|
|
15
22
|
subscribe(): Promise<void>;
|
|
23
|
+
private setTimeout;
|
|
16
24
|
getSlot(): number;
|
|
17
|
-
unsubscribe(): Promise<void>;
|
|
25
|
+
unsubscribe(onResub?: boolean): Promise<void>;
|
|
18
26
|
}
|
|
19
27
|
export {};
|
|
@@ -3,28 +3,64 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SlotSubscriber = void 0;
|
|
4
4
|
const events_1 = require("events");
|
|
5
5
|
class SlotSubscriber {
|
|
6
|
-
constructor(connection,
|
|
6
|
+
constructor(connection, config) {
|
|
7
7
|
this.connection = connection;
|
|
8
|
+
this.isUnsubscribing = false;
|
|
9
|
+
this.receivingData = false;
|
|
8
10
|
this.eventEmitter = new events_1.EventEmitter();
|
|
11
|
+
this.resubTimeoutMs = config === null || config === void 0 ? void 0 : config.resubTimeoutMs;
|
|
9
12
|
}
|
|
10
13
|
async subscribe() {
|
|
11
|
-
if (this.subscriptionId) {
|
|
14
|
+
if (this.subscriptionId != null) {
|
|
12
15
|
return;
|
|
13
16
|
}
|
|
14
17
|
this.currentSlot = await this.connection.getSlot('confirmed');
|
|
15
18
|
this.subscriptionId = this.connection.onSlotChange((slotInfo) => {
|
|
16
19
|
if (!this.currentSlot || this.currentSlot < slotInfo.slot) {
|
|
20
|
+
if (this.resubTimeoutMs && !this.isUnsubscribing) {
|
|
21
|
+
this.receivingData = true;
|
|
22
|
+
clearTimeout(this.timeoutId);
|
|
23
|
+
this.setTimeout();
|
|
24
|
+
}
|
|
17
25
|
this.currentSlot = slotInfo.slot;
|
|
18
26
|
this.eventEmitter.emit('newSlot', slotInfo.slot);
|
|
19
27
|
}
|
|
20
28
|
});
|
|
29
|
+
if (this.resubTimeoutMs) {
|
|
30
|
+
this.setTimeout();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
setTimeout() {
|
|
34
|
+
this.timeoutId = setTimeout(async () => {
|
|
35
|
+
if (this.isUnsubscribing) {
|
|
36
|
+
// If we are in the process of unsubscribing, do not attempt to resubscribe
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (this.receivingData) {
|
|
40
|
+
console.log(`No new slot in ${this.resubTimeoutMs}ms, slot subscriber resubscribing`);
|
|
41
|
+
await this.unsubscribe(true);
|
|
42
|
+
this.receivingData = false;
|
|
43
|
+
await this.subscribe();
|
|
44
|
+
}
|
|
45
|
+
}, this.resubTimeoutMs);
|
|
21
46
|
}
|
|
22
47
|
getSlot() {
|
|
23
48
|
return this.currentSlot;
|
|
24
49
|
}
|
|
25
|
-
async unsubscribe() {
|
|
26
|
-
if (
|
|
50
|
+
async unsubscribe(onResub = false) {
|
|
51
|
+
if (!onResub) {
|
|
52
|
+
this.resubTimeoutMs = undefined;
|
|
53
|
+
}
|
|
54
|
+
this.isUnsubscribing = true;
|
|
55
|
+
clearTimeout(this.timeoutId);
|
|
56
|
+
this.timeoutId = undefined;
|
|
57
|
+
if (this.subscriptionId != null) {
|
|
27
58
|
await this.connection.removeSlotChangeListener(this.subscriptionId);
|
|
59
|
+
this.subscriptionId = undefined;
|
|
60
|
+
this.isUnsubscribing = false;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.isUnsubscribing = false;
|
|
28
64
|
}
|
|
29
65
|
}
|
|
30
66
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -767,7 +767,7 @@ export type AMM = {
|
|
|
767
767
|
pegMultiplier: BN;
|
|
768
768
|
cumulativeFundingRateLong: BN;
|
|
769
769
|
cumulativeFundingRateShort: BN;
|
|
770
|
-
|
|
770
|
+
last24HAvgFundingRate: BN;
|
|
771
771
|
lastFundingRateShort: BN;
|
|
772
772
|
lastFundingRateLong: BN;
|
|
773
773
|
totalLiquidationFee: BN;
|
package/lib/userMap/userMap.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export declare class UserMap implements UserMapInterface {
|
|
|
22
22
|
private subscription;
|
|
23
23
|
private stateAccountUpdateCallback;
|
|
24
24
|
private decode;
|
|
25
|
+
private mostRecentSlot;
|
|
25
26
|
private syncPromise?;
|
|
26
27
|
private syncPromiseResolver;
|
|
27
28
|
/**
|
|
@@ -68,4 +69,6 @@ export declare class UserMap implements UserMapInterface {
|
|
|
68
69
|
sync(): Promise<void>;
|
|
69
70
|
unsubscribe(): Promise<void>;
|
|
70
71
|
updateUserAccount(key: string, userAccount: UserAccount, slot: number): Promise<void>;
|
|
72
|
+
updateLatestSlot(slot: number): void;
|
|
73
|
+
getSlot(): number;
|
|
71
74
|
}
|
package/lib/userMap/userMap.js
CHANGED
|
@@ -21,6 +21,7 @@ class UserMap {
|
|
|
21
21
|
this.lastNumberOfSubAccounts = state.numberOfSubAccounts;
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
|
+
this.mostRecentSlot = 0;
|
|
24
25
|
this.driftClient = config.driftClient;
|
|
25
26
|
if (config.connection) {
|
|
26
27
|
this.connection = config.connection;
|
|
@@ -73,7 +74,7 @@ class UserMap {
|
|
|
73
74
|
userAccountPublicKey,
|
|
74
75
|
accountSubscription: {
|
|
75
76
|
type: 'custom',
|
|
76
|
-
userAccountSubscriber: new __1.
|
|
77
|
+
userAccountSubscriber: new __1.OneShotUserAccountSubscriber(this.driftClient.program, userAccountPublicKey, userAccount, slot, this.commitment),
|
|
77
78
|
},
|
|
78
79
|
});
|
|
79
80
|
await user.subscribe(userAccount);
|
|
@@ -218,6 +219,7 @@ class UserMap {
|
|
|
218
219
|
await this.connection._rpcRequest('getProgramAccounts', rpcRequestArgs);
|
|
219
220
|
const rpcResponseAndContext = rpcJSONResponse.result;
|
|
220
221
|
const slot = rpcResponseAndContext.context.slot;
|
|
222
|
+
this.updateLatestSlot(slot);
|
|
221
223
|
const programAccountBufferMap = new Map();
|
|
222
224
|
for (const programAccount of rpcResponseAndContext.value) {
|
|
223
225
|
programAccountBufferMap.set(programAccount.pubkey.toString(),
|
|
@@ -263,6 +265,7 @@ class UserMap {
|
|
|
263
265
|
}
|
|
264
266
|
}
|
|
265
267
|
async updateUserAccount(key, userAccount, slot) {
|
|
268
|
+
this.updateLatestSlot(slot);
|
|
266
269
|
if (!this.userMap.has(key)) {
|
|
267
270
|
this.addPubkey(new web3_js_1.PublicKey(key), userAccount, slot);
|
|
268
271
|
}
|
|
@@ -271,5 +274,11 @@ class UserMap {
|
|
|
271
274
|
user.accountSubscriber.updateData(userAccount, slot);
|
|
272
275
|
}
|
|
273
276
|
}
|
|
277
|
+
updateLatestSlot(slot) {
|
|
278
|
+
this.mostRecentSlot = Math.max(slot, this.mostRecentSlot);
|
|
279
|
+
}
|
|
280
|
+
getSlot() {
|
|
281
|
+
return this.mostRecentSlot;
|
|
282
|
+
}
|
|
274
283
|
}
|
|
275
284
|
exports.UserMap = UserMap;
|
package/lib/userStats.d.ts
CHANGED
package/lib/userStats.js
CHANGED
package/package.json
CHANGED
|
@@ -4,6 +4,10 @@ import StrictEventEmitter from 'strict-event-emitter-types';
|
|
|
4
4
|
import { EventEmitter } from 'events';
|
|
5
5
|
import { UserAccount } from '../types';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Basic implementation of UserAccountSubscriber. It will only take in UserAccount
|
|
9
|
+
* data during initialization and will not fetch or subscribe to updates.
|
|
10
|
+
*/
|
|
7
11
|
export class BasicUserAccountSubscriber implements UserAccountSubscriber {
|
|
8
12
|
isSubscribed: boolean;
|
|
9
13
|
eventEmitter: StrictEventEmitter<EventEmitter, UserAccountEvents>;
|
|
@@ -78,6 +78,7 @@ export class BulkAccountLoader {
|
|
|
78
78
|
if (existingAccountToLoad) {
|
|
79
79
|
existingAccountToLoad.callbacks.delete(callbackId);
|
|
80
80
|
if (existingAccountToLoad.callbacks.size === 0) {
|
|
81
|
+
this.bufferAndSlotMap.delete(publicKey.toString());
|
|
81
82
|
this.accountsToLoad.delete(existingAccountToLoad.publicKey.toString());
|
|
82
83
|
}
|
|
83
84
|
}
|
|
@@ -153,9 +154,11 @@ export class BulkAccountLoader {
|
|
|
153
154
|
const requests = new Array<{ methodName: string; args: any }>();
|
|
154
155
|
for (const accountsToLoadChunk of accountsToLoadChunks) {
|
|
155
156
|
const args = [
|
|
156
|
-
accountsToLoadChunk
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
accountsToLoadChunk
|
|
158
|
+
.filter((accountToLoad) => accountToLoad.callbacks.size > 0)
|
|
159
|
+
.map((accountToLoad) => {
|
|
160
|
+
return accountToLoad.publicKey.toBase58();
|
|
161
|
+
}),
|
|
159
162
|
{ commitment: this.commitment },
|
|
160
163
|
];
|
|
161
164
|
|
|
@@ -190,6 +193,10 @@ export class BulkAccountLoader {
|
|
|
190
193
|
|
|
191
194
|
const accountsToLoad = accountsToLoadChunks[i];
|
|
192
195
|
accountsToLoad.forEach((accountToLoad, j) => {
|
|
196
|
+
if (accountToLoad.callbacks.size === 0) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
193
200
|
const key = accountToLoad.publicKey.toBase58();
|
|
194
201
|
const oldRPCResponse = this.bufferAndSlotMap.get(key);
|
|
195
202
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Commitment, PublicKey } from '@solana/web3.js';
|
|
2
|
+
import { UserAccount } from '../types';
|
|
3
|
+
import { BasicUserAccountSubscriber } from './basicUserAccountSubscriber';
|
|
4
|
+
import { Program } from '@coral-xyz/anchor';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Simple implementation of UserAccountSubscriber. It will fetch the UserAccount
|
|
8
|
+
* date on subscribe (or call to fetch) if no account data is provided on init.
|
|
9
|
+
* Expect to use only 1 RPC call unless you call fetch repeatedly.
|
|
10
|
+
*/
|
|
11
|
+
export class OneShotUserAccountSubscriber extends BasicUserAccountSubscriber {
|
|
12
|
+
program: Program;
|
|
13
|
+
commitment: Commitment;
|
|
14
|
+
|
|
15
|
+
public constructor(
|
|
16
|
+
program: Program,
|
|
17
|
+
userAccountPublicKey: PublicKey,
|
|
18
|
+
data?: UserAccount,
|
|
19
|
+
slot?: number,
|
|
20
|
+
commitment?: Commitment
|
|
21
|
+
) {
|
|
22
|
+
super(userAccountPublicKey, data, slot);
|
|
23
|
+
this.program = program;
|
|
24
|
+
this.commitment = commitment ?? 'confirmed';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async subscribe(userAccount?: UserAccount): Promise<boolean> {
|
|
28
|
+
if (userAccount) {
|
|
29
|
+
this.user = { data: userAccount, slot: this.user.slot };
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await this.fetchIfUnloaded();
|
|
34
|
+
if (this.doesAccountExist()) {
|
|
35
|
+
this.eventEmitter.emit('update');
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async fetchIfUnloaded(): Promise<void> {
|
|
41
|
+
if (this.user.data === undefined) {
|
|
42
|
+
await this.fetch();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async fetch(): Promise<void> {
|
|
47
|
+
try {
|
|
48
|
+
const dataAndContext = await this.program.account.user.fetchAndContext(
|
|
49
|
+
this.userAccountPublicKey,
|
|
50
|
+
this.commitment
|
|
51
|
+
);
|
|
52
|
+
if (dataAndContext.context.slot > (this.user?.slot ?? 0)) {
|
|
53
|
+
this.user = {
|
|
54
|
+
data: dataAndContext.data as UserAccount,
|
|
55
|
+
slot: dataAndContext.context.slot,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.error(
|
|
60
|
+
`OneShotUserAccountSubscriber.fetch() UserAccount does not exist: ${e.message}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -40,7 +40,7 @@ export class WebSocketAccountSubscriber<T> implements AccountSubscriber<T> {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
async subscribe(onChange: (data: T) => void): Promise<void> {
|
|
43
|
-
if (this.listenerId || this.isUnsubscribing) {
|
|
43
|
+
if (this.listenerId != null || this.isUnsubscribing) {
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -95,7 +95,7 @@ export class WebSocketAccountSubscriber<T> implements AccountSubscriber<T> {
|
|
|
95
95
|
console.log(
|
|
96
96
|
`No ws data from ${this.accountName} in ${this.resubTimeoutMs}ms, resubscribing`
|
|
97
97
|
);
|
|
98
|
-
await this.unsubscribe();
|
|
98
|
+
await this.unsubscribe(true);
|
|
99
99
|
this.receivingData = false;
|
|
100
100
|
await this.subscribe(this.onChange);
|
|
101
101
|
}
|
|
@@ -164,12 +164,15 @@ export class WebSocketAccountSubscriber<T> implements AccountSubscriber<T> {
|
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
unsubscribe(): Promise<void> {
|
|
167
|
+
unsubscribe(onResub = false): Promise<void> {
|
|
168
|
+
if (!onResub) {
|
|
169
|
+
this.resubTimeoutMs = undefined;
|
|
170
|
+
}
|
|
168
171
|
this.isUnsubscribing = true;
|
|
169
172
|
clearTimeout(this.timeoutId);
|
|
170
173
|
this.timeoutId = undefined;
|
|
171
174
|
|
|
172
|
-
if (this.listenerId) {
|
|
175
|
+
if (this.listenerId != null) {
|
|
173
176
|
const promise = this.program.provider.connection
|
|
174
177
|
.removeAccountChangeListener(this.listenerId)
|
|
175
178
|
.then(() => {
|
|
@@ -49,7 +49,7 @@ export class WebSocketProgramAccountSubscriber<T>
|
|
|
49
49
|
async subscribe(
|
|
50
50
|
onChange: (accountId: PublicKey, data: T, context: Context) => void
|
|
51
51
|
): Promise<void> {
|
|
52
|
-
if (this.listenerId || this.isUnsubscribing) {
|
|
52
|
+
if (this.listenerId != null || this.isUnsubscribing) {
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -91,7 +91,7 @@ export class WebSocketProgramAccountSubscriber<T>
|
|
|
91
91
|
console.log(
|
|
92
92
|
`No ws data from ${this.subscriptionName} in ${this.resubTimeoutMs}ms, resubscribing`
|
|
93
93
|
);
|
|
94
|
-
await this.unsubscribe();
|
|
94
|
+
await this.unsubscribe(true);
|
|
95
95
|
this.receivingData = false;
|
|
96
96
|
await this.subscribe(this.onChange);
|
|
97
97
|
}
|
|
@@ -145,12 +145,15 @@ export class WebSocketProgramAccountSubscriber<T>
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
unsubscribe(): Promise<void> {
|
|
148
|
+
unsubscribe(onResub = false): Promise<void> {
|
|
149
|
+
if (!onResub) {
|
|
150
|
+
this.resubTimeoutMs = undefined;
|
|
151
|
+
}
|
|
149
152
|
this.isUnsubscribing = true;
|
|
150
153
|
clearTimeout(this.timeoutId);
|
|
151
154
|
this.timeoutId = undefined;
|
|
152
155
|
|
|
153
|
-
if (this.listenerId) {
|
|
156
|
+
if (this.listenerId != null) {
|
|
154
157
|
const promise = this.program.provider.connection
|
|
155
158
|
.removeAccountChangeListener(this.listenerId)
|
|
156
159
|
.then(() => {
|
package/src/adminClient.ts
CHANGED
|
@@ -1181,6 +1181,19 @@ export class AdminClient extends DriftClient {
|
|
|
1181
1181
|
});
|
|
1182
1182
|
}
|
|
1183
1183
|
|
|
1184
|
+
public async updatePhoenixFulfillmentConfigStatus(
|
|
1185
|
+
phoenixFulfillmentConfig: PublicKey,
|
|
1186
|
+
status: SpotFulfillmentConfigStatus
|
|
1187
|
+
): Promise<TransactionSignature> {
|
|
1188
|
+
return await this.program.rpc.phoenixFulfillmentConfigStatus(status, {
|
|
1189
|
+
accounts: {
|
|
1190
|
+
admin: this.wallet.publicKey,
|
|
1191
|
+
state: await this.getStatePublicKey(),
|
|
1192
|
+
phoenixFulfillmentConfig,
|
|
1193
|
+
},
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1184
1197
|
public async updateSpotMarketExpiry(
|
|
1185
1198
|
spotMarketIndex: number,
|
|
1186
1199
|
expiryTs: BN
|
|
@@ -99,3 +99,7 @@ export const OPEN_ORDER_MARGIN_REQUIREMENT = QUOTE_PRECISION.div(new BN(100));
|
|
|
99
99
|
export const DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT = new BN(
|
|
100
100
|
-25
|
|
101
101
|
).mul(QUOTE_PRECISION);
|
|
102
|
+
|
|
103
|
+
export const ACCOUNT_AGE_DELETION_CUTOFF_SECONDS = 60 * 60 * 24 * 13; // 13 days
|
|
104
|
+
export const IDLE_TIME_SLOTS = 9000;
|
|
105
|
+
export const SLOT_TIME_ESTIMATE_MS = 400;
|