@drift-labs/sdk 2.52.0-beta.1 → 2.52.0-beta.11
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/examples/phoenix.ts +11 -4
- 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/dlob/orderBookLevels.d.ts +22 -0
- package/lib/dlob/orderBookLevels.js +115 -1
- package/lib/driftClient.d.ts +3 -1
- package/lib/driftClient.js +30 -8
- 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 +52 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/math/amm.d.ts +5 -1
- package/lib/math/amm.js +62 -13
- package/lib/math/state.js +3 -0
- package/lib/orderSubscriber/OrderSubscriber.js +1 -0
- package/lib/orderSubscriber/WebsocketSubscription.d.ts +4 -1
- package/lib/orderSubscriber/WebsocketSubscription.js +25 -1
- package/lib/orderSubscriber/types.d.ts +1 -0
- package/lib/phoenix/phoenixSubscriber.js +10 -6
- package/lib/priorityFee/averageOverSlotsStrategy.d.ts +12 -0
- package/lib/priorityFee/averageOverSlotsStrategy.js +28 -0
- package/lib/priorityFee/averageStrategy.d.ts +7 -0
- package/lib/priorityFee/averageStrategy.js +11 -0
- package/lib/priorityFee/ewmaStrategy.d.ts +13 -0
- package/lib/priorityFee/ewmaStrategy.js +33 -0
- package/lib/priorityFee/index.d.ts +7 -0
- package/lib/priorityFee/index.js +23 -0
- package/lib/priorityFee/maxOverSlotsStrategy.d.ts +12 -0
- package/lib/priorityFee/maxOverSlotsStrategy.js +29 -0
- package/lib/priorityFee/maxStrategy.d.ts +7 -0
- package/lib/priorityFee/maxStrategy.js +9 -0
- package/lib/priorityFee/priorityFeeSubscriber.d.ts +15 -4
- package/lib/priorityFee/priorityFeeSubscriber.js +38 -14
- package/lib/priorityFee/types.d.ts +6 -0
- package/lib/priorityFee/types.js +2 -0
- package/lib/slot/SlotSubscriber.d.ts +11 -3
- package/lib/slot/SlotSubscriber.js +40 -4
- package/lib/types.d.ts +4 -1
- package/lib/types.js +1 -0
- package/lib/user.d.ts +1 -1
- package/lib/user.js +15 -8
- package/lib/userMap/userMap.d.ts +3 -0
- package/lib/userMap/userMap.js +9 -0
- package/package.json +1 -1
- package/src/accounts/webSocketAccountSubscriber.ts +7 -4
- package/src/accounts/webSocketProgramAccountSubscriber.ts +7 -4
- package/src/dlob/orderBookLevels.ts +136 -0
- package/src/driftClient.ts +46 -8
- package/src/events/webSocketLogProvider.ts +3 -3
- package/src/factory/bigNum.ts +11 -2
- package/src/idl/drift.json +52 -1
- package/src/index.ts +1 -1
- package/src/math/amm.ts +159 -25
- package/src/math/state.ts +3 -0
- package/src/orderSubscriber/OrderSubscriber.ts +1 -0
- package/src/orderSubscriber/WebsocketSubscription.ts +28 -0
- package/src/orderSubscriber/types.ts +1 -0
- package/src/phoenix/phoenixSubscriber.ts +14 -8
- package/src/priorityFee/averageOverSlotsStrategy.ts +30 -0
- package/src/priorityFee/averageStrategy.ts +11 -0
- package/src/priorityFee/ewmaStrategy.ts +40 -0
- package/src/priorityFee/index.ts +7 -0
- package/src/priorityFee/maxOverSlotsStrategy.ts +31 -0
- package/src/priorityFee/maxStrategy.ts +7 -0
- package/src/priorityFee/priorityFeeSubscriber.ts +46 -19
- package/src/priorityFee/types.ts +5 -0
- package/src/slot/SlotSubscriber.ts +52 -5
- package/src/types.ts +2 -1
- package/src/user.ts +25 -8
- package/src/userMap/userMap.ts +12 -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/tests/tx/priorityFeeStrategy.ts +97 -0
- package/tests/user/helpers.ts +1 -0
package/src/math/amm.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
PRICE_DIV_PEG,
|
|
13
13
|
PERCENTAGE_PRECISION,
|
|
14
14
|
DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT,
|
|
15
|
+
FUNDING_RATE_BUFFER_PRECISION,
|
|
15
16
|
TWO,
|
|
16
17
|
} from '../constants/numericConstants';
|
|
17
18
|
import {
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
isVariant,
|
|
23
24
|
} from '../types';
|
|
24
25
|
import { assert } from '../assert/assert';
|
|
25
|
-
import { squareRootBN, clampBN, standardizeBaseAssetAmount } from '..';
|
|
26
|
+
import { squareRootBN, sigNum, clampBN, standardizeBaseAssetAmount } from '..';
|
|
26
27
|
|
|
27
28
|
import { OraclePriceData } from '../oracles/types';
|
|
28
29
|
import {
|
|
@@ -352,21 +353,12 @@ export function calculateMarketOpenBidAsk(
|
|
|
352
353
|
return [openBids, openAsks];
|
|
353
354
|
}
|
|
354
355
|
|
|
355
|
-
export function
|
|
356
|
+
export function calculateInventoryLiquidityRatio(
|
|
356
357
|
baseAssetAmountWithAmm: BN,
|
|
357
358
|
baseAssetReserve: BN,
|
|
358
359
|
minBaseAssetReserve: BN,
|
|
359
|
-
maxBaseAssetReserve: BN
|
|
360
|
-
|
|
361
|
-
maxSpread: number
|
|
362
|
-
): number {
|
|
363
|
-
if (baseAssetAmountWithAmm.eq(ZERO)) {
|
|
364
|
-
return 1;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = BID_ASK_SPREAD_PRECISION.mul(
|
|
368
|
-
new BN(10)
|
|
369
|
-
);
|
|
360
|
+
maxBaseAssetReserve: BN
|
|
361
|
+
): BN {
|
|
370
362
|
// inventory skew
|
|
371
363
|
const [openBids, openAsks] = calculateMarketOpenBidAsk(
|
|
372
364
|
baseAssetReserve,
|
|
@@ -383,6 +375,31 @@ export function calculateInventoryScale(
|
|
|
383
375
|
.abs(),
|
|
384
376
|
PERCENTAGE_PRECISION
|
|
385
377
|
);
|
|
378
|
+
return inventoryScaleBN;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function calculateInventoryScale(
|
|
382
|
+
baseAssetAmountWithAmm: BN,
|
|
383
|
+
baseAssetReserve: BN,
|
|
384
|
+
minBaseAssetReserve: BN,
|
|
385
|
+
maxBaseAssetReserve: BN,
|
|
386
|
+
directionalSpread: number,
|
|
387
|
+
maxSpread: number
|
|
388
|
+
): number {
|
|
389
|
+
if (baseAssetAmountWithAmm.eq(ZERO)) {
|
|
390
|
+
return 1;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = BID_ASK_SPREAD_PRECISION.mul(
|
|
394
|
+
new BN(10)
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
const inventoryScaleBN = calculateInventoryLiquidityRatio(
|
|
398
|
+
baseAssetAmountWithAmm,
|
|
399
|
+
baseAssetReserve,
|
|
400
|
+
minBaseAssetReserve,
|
|
401
|
+
maxBaseAssetReserve
|
|
402
|
+
);
|
|
386
403
|
|
|
387
404
|
const inventoryScaleMaxBN = BN.max(
|
|
388
405
|
MAX_BID_ASK_INVENTORY_SKEW_FACTOR,
|
|
@@ -402,6 +419,76 @@ export function calculateInventoryScale(
|
|
|
402
419
|
return inventoryScaleCapped;
|
|
403
420
|
}
|
|
404
421
|
|
|
422
|
+
export function calculateReferencePriceOffset(
|
|
423
|
+
reservePrice: BN,
|
|
424
|
+
last24hAvgFundingRate: BN,
|
|
425
|
+
liquidityFraction: BN,
|
|
426
|
+
oracleTwapFast: BN,
|
|
427
|
+
markTwapFast: BN,
|
|
428
|
+
oracleTwapSlow: BN,
|
|
429
|
+
markTwapSlow: BN,
|
|
430
|
+
maxOffsetPct: number
|
|
431
|
+
): BN {
|
|
432
|
+
if (last24hAvgFundingRate.eq(ZERO)) {
|
|
433
|
+
return ZERO;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const maxOffsetInPrice = new BN(maxOffsetPct)
|
|
437
|
+
.mul(reservePrice)
|
|
438
|
+
.div(PERCENTAGE_PRECISION);
|
|
439
|
+
|
|
440
|
+
// Calculate quote denominated market premium
|
|
441
|
+
const markPremiumMinute = clampBN(
|
|
442
|
+
markTwapFast.sub(oracleTwapFast),
|
|
443
|
+
maxOffsetInPrice.mul(new BN(-1)),
|
|
444
|
+
maxOffsetInPrice
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
const markPremiumHour = clampBN(
|
|
448
|
+
markTwapSlow.sub(oracleTwapSlow),
|
|
449
|
+
maxOffsetInPrice.mul(new BN(-1)),
|
|
450
|
+
maxOffsetInPrice
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
// Convert last24hAvgFundingRate to quote denominated premium
|
|
454
|
+
const markPremiumDay = clampBN(
|
|
455
|
+
last24hAvgFundingRate.div(FUNDING_RATE_BUFFER_PRECISION).mul(new BN(24)),
|
|
456
|
+
maxOffsetInPrice.mul(new BN(-1)),
|
|
457
|
+
maxOffsetInPrice
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
// Take average clamped premium as the price-based offset
|
|
461
|
+
const markPremiumAvg = markPremiumMinute
|
|
462
|
+
.add(markPremiumHour)
|
|
463
|
+
.add(markPremiumDay)
|
|
464
|
+
.div(new BN(3));
|
|
465
|
+
|
|
466
|
+
const markPremiumAvgPct = markPremiumAvg
|
|
467
|
+
.mul(PRICE_PRECISION)
|
|
468
|
+
.div(reservePrice);
|
|
469
|
+
|
|
470
|
+
const inventoryPct = clampBN(
|
|
471
|
+
liquidityFraction.mul(new BN(maxOffsetPct)).div(PERCENTAGE_PRECISION),
|
|
472
|
+
maxOffsetInPrice.mul(new BN(-1)),
|
|
473
|
+
maxOffsetInPrice
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
// Only apply when inventory is consistent with recent and 24h market premium
|
|
477
|
+
let offsetPct = markPremiumAvgPct.add(inventoryPct);
|
|
478
|
+
|
|
479
|
+
if (!sigNum(inventoryPct).eq(sigNum(markPremiumAvgPct))) {
|
|
480
|
+
offsetPct = ZERO;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const clampedOffsetPct = clampBN(
|
|
484
|
+
offsetPct,
|
|
485
|
+
new BN(-maxOffsetPct),
|
|
486
|
+
new BN(maxOffsetPct)
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
return clampedOffsetPct;
|
|
490
|
+
}
|
|
491
|
+
|
|
405
492
|
export function calculateEffectiveLeverage(
|
|
406
493
|
baseSpread: number,
|
|
407
494
|
quoteAssetReserve: BN,
|
|
@@ -532,6 +619,8 @@ export function calculateSpreadBN(
|
|
|
532
619
|
halfRevenueRetreatAmount: 0,
|
|
533
620
|
longSpreadwRevRetreat: 0,
|
|
534
621
|
shortSpreadwRevRetreat: 0,
|
|
622
|
+
longSpreadwOffsetShrink: 0,
|
|
623
|
+
shortSpreadwOffsetShrink: 0,
|
|
535
624
|
totalSpread: 0,
|
|
536
625
|
longSpread: 0,
|
|
537
626
|
shortSpread: 0,
|
|
@@ -678,7 +767,6 @@ export function calculateSpreadBN(
|
|
|
678
767
|
spreadTerms.totalSpread = totalSpread;
|
|
679
768
|
spreadTerms.longSpread = longSpread;
|
|
680
769
|
spreadTerms.shortSpread = shortSpread;
|
|
681
|
-
|
|
682
770
|
if (returnTerms) {
|
|
683
771
|
return spreadTerms;
|
|
684
772
|
}
|
|
@@ -688,17 +776,20 @@ export function calculateSpreadBN(
|
|
|
688
776
|
export function calculateSpread(
|
|
689
777
|
amm: AMM,
|
|
690
778
|
oraclePriceData: OraclePriceData,
|
|
691
|
-
now?: BN
|
|
779
|
+
now?: BN,
|
|
780
|
+
reservePrice?: BN
|
|
692
781
|
): [number, number] {
|
|
693
782
|
if (amm.baseSpread == 0 || amm.curveUpdateIntensity == 0) {
|
|
694
783
|
return [amm.baseSpread / 2, amm.baseSpread / 2];
|
|
695
784
|
}
|
|
696
785
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
786
|
+
if (!reservePrice) {
|
|
787
|
+
reservePrice = calculatePrice(
|
|
788
|
+
amm.baseAssetReserve,
|
|
789
|
+
amm.quoteAssetReserve,
|
|
790
|
+
amm.pegMultiplier
|
|
791
|
+
);
|
|
792
|
+
}
|
|
702
793
|
|
|
703
794
|
const targetPrice = oraclePriceData?.price || reservePrice;
|
|
704
795
|
const confInterval = oraclePriceData.confidence || ZERO;
|
|
@@ -760,13 +851,22 @@ export function calculateSpreadReserves(
|
|
|
760
851
|
quoteAssetReserve: amm.quoteAssetReserve,
|
|
761
852
|
};
|
|
762
853
|
}
|
|
763
|
-
|
|
854
|
+
let spreadFraction = new BN(spread / 2);
|
|
855
|
+
|
|
856
|
+
// make non-zero
|
|
857
|
+
if (spreadFraction.eq(ZERO)) {
|
|
858
|
+
spreadFraction = spread >= 0 ? new BN(1) : new BN(-1);
|
|
859
|
+
}
|
|
860
|
+
|
|
764
861
|
const quoteAssetReserveDelta = amm.quoteAssetReserve.div(
|
|
765
862
|
BID_ASK_SPREAD_PRECISION.div(spreadFraction)
|
|
766
863
|
);
|
|
767
864
|
|
|
768
865
|
let quoteAssetReserve;
|
|
769
|
-
if (
|
|
866
|
+
if (
|
|
867
|
+
(spread >= 0 && isVariant(direction, 'long')) ||
|
|
868
|
+
(spread <= 0 && isVariant(direction, 'short'))
|
|
869
|
+
) {
|
|
770
870
|
quoteAssetReserve = amm.quoteAssetReserve.add(quoteAssetReserveDelta);
|
|
771
871
|
} else {
|
|
772
872
|
quoteAssetReserve = amm.quoteAssetReserve.sub(quoteAssetReserveDelta);
|
|
@@ -779,14 +879,48 @@ export function calculateSpreadReserves(
|
|
|
779
879
|
};
|
|
780
880
|
}
|
|
781
881
|
|
|
782
|
-
const
|
|
882
|
+
const reservePrice = calculatePrice(
|
|
883
|
+
amm.baseAssetReserve,
|
|
884
|
+
amm.quoteAssetReserve,
|
|
885
|
+
amm.pegMultiplier
|
|
886
|
+
);
|
|
887
|
+
|
|
888
|
+
// always allow 10 bps of price offset, up to a fifth of the market's max_spread
|
|
889
|
+
const maxOffset = Math.max(
|
|
890
|
+
amm.maxSpread / 5,
|
|
891
|
+
PERCENTAGE_PRECISION.toNumber() / 1000
|
|
892
|
+
);
|
|
893
|
+
const liquidityFraction = calculateInventoryLiquidityRatio(
|
|
894
|
+
amm.baseAssetAmountWithAmm,
|
|
895
|
+
amm.baseAssetReserve,
|
|
896
|
+
amm.minBaseAssetReserve,
|
|
897
|
+
amm.maxBaseAssetReserve
|
|
898
|
+
);
|
|
899
|
+
const referencePriceOffset = calculateReferencePriceOffset(
|
|
900
|
+
reservePrice,
|
|
901
|
+
amm.last24HAvgFundingRate,
|
|
902
|
+
liquidityFraction,
|
|
903
|
+
amm.historicalOracleData.lastOraclePriceTwap5Min,
|
|
904
|
+
amm.lastMarkPriceTwap5Min,
|
|
905
|
+
amm.historicalOracleData.lastOraclePriceTwap,
|
|
906
|
+
amm.lastMarkPriceTwap,
|
|
907
|
+
maxOffset
|
|
908
|
+
);
|
|
909
|
+
|
|
910
|
+
const [longSpread, shortSpread] = calculateSpread(
|
|
911
|
+
amm,
|
|
912
|
+
oraclePriceData,
|
|
913
|
+
now,
|
|
914
|
+
reservePrice
|
|
915
|
+
);
|
|
916
|
+
|
|
783
917
|
const askReserves = calculateSpreadReserve(
|
|
784
|
-
longSpread,
|
|
918
|
+
longSpread + referencePriceOffset.toNumber(),
|
|
785
919
|
PositionDirection.LONG,
|
|
786
920
|
amm
|
|
787
921
|
);
|
|
788
922
|
const bidReserves = calculateSpreadReserve(
|
|
789
|
-
shortSpread,
|
|
923
|
+
shortSpread + referencePriceOffset.toNumber(),
|
|
790
924
|
PositionDirection.SHORT,
|
|
791
925
|
amm
|
|
792
926
|
);
|
package/src/math/state.ts
CHANGED
|
@@ -22,5 +22,8 @@ export function calculateInitUserFee(stateAccount: StateAccount): BN {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function getMaxNumberOfSubAccounts(stateAccount: StateAccount): BN {
|
|
25
|
+
if (stateAccount.maxNumberOfSubAccounts <= 5) {
|
|
26
|
+
return new BN(stateAccount.maxNumberOfSubAccounts);
|
|
27
|
+
}
|
|
25
28
|
return new BN(stateAccount.maxNumberOfSubAccounts).muln(100);
|
|
26
29
|
}
|
|
@@ -39,6 +39,7 @@ export class OrderSubscriber {
|
|
|
39
39
|
commitment: this.commitment,
|
|
40
40
|
skipInitialLoad: config.subscriptionConfig.skipInitialLoad,
|
|
41
41
|
resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
|
|
42
|
+
resyncIntervalMs: config.subscriptionConfig.resyncIntervalMs,
|
|
42
43
|
});
|
|
43
44
|
}
|
|
44
45
|
if (config.fastDecode ?? true) {
|
|
@@ -9,24 +9,29 @@ export class WebsocketSubscription {
|
|
|
9
9
|
private commitment: Commitment;
|
|
10
10
|
private skipInitialLoad: boolean;
|
|
11
11
|
private resubTimeoutMs?: number;
|
|
12
|
+
private resyncIntervalMs?: number;
|
|
12
13
|
|
|
13
14
|
private subscriber?: WebSocketProgramAccountSubscriber<UserAccount>;
|
|
15
|
+
private resyncTimeoutId?: NodeJS.Timeout;
|
|
14
16
|
|
|
15
17
|
constructor({
|
|
16
18
|
orderSubscriber,
|
|
17
19
|
commitment,
|
|
18
20
|
skipInitialLoad = false,
|
|
19
21
|
resubTimeoutMs,
|
|
22
|
+
resyncIntervalMs,
|
|
20
23
|
}: {
|
|
21
24
|
orderSubscriber: OrderSubscriber;
|
|
22
25
|
commitment: Commitment;
|
|
23
26
|
skipInitialLoad?: boolean;
|
|
24
27
|
resubTimeoutMs?: number;
|
|
28
|
+
resyncIntervalMs?: number;
|
|
25
29
|
}) {
|
|
26
30
|
this.orderSubscriber = orderSubscriber;
|
|
27
31
|
this.commitment = commitment;
|
|
28
32
|
this.skipInitialLoad = skipInitialLoad;
|
|
29
33
|
this.resubTimeoutMs = resubTimeoutMs;
|
|
34
|
+
this.resyncIntervalMs = resyncIntervalMs;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
public async subscribe(): Promise<void> {
|
|
@@ -61,11 +66,34 @@ export class WebsocketSubscription {
|
|
|
61
66
|
if (!this.skipInitialLoad) {
|
|
62
67
|
await this.orderSubscriber.fetch();
|
|
63
68
|
}
|
|
69
|
+
|
|
70
|
+
if (this.resyncIntervalMs) {
|
|
71
|
+
const recursiveResync = () => {
|
|
72
|
+
this.resyncTimeoutId = setTimeout(() => {
|
|
73
|
+
this.orderSubscriber
|
|
74
|
+
.fetch()
|
|
75
|
+
.catch((e) => {
|
|
76
|
+
console.error('Failed to resync in OrderSubscriber');
|
|
77
|
+
console.log(e);
|
|
78
|
+
})
|
|
79
|
+
.finally(() => {
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
81
|
+
if (!this.resyncTimeoutId) return;
|
|
82
|
+
recursiveResync();
|
|
83
|
+
});
|
|
84
|
+
}, this.resyncIntervalMs);
|
|
85
|
+
};
|
|
86
|
+
recursiveResync();
|
|
87
|
+
}
|
|
64
88
|
}
|
|
65
89
|
|
|
66
90
|
public async unsubscribe(): Promise<void> {
|
|
67
91
|
if (!this.subscriber) return;
|
|
68
92
|
await this.subscriber.unsubscribe();
|
|
69
93
|
this.subscriber = undefined;
|
|
94
|
+
if (this.resyncTimeoutId !== undefined) {
|
|
95
|
+
clearTimeout(this.resyncTimeoutId);
|
|
96
|
+
this.resyncTimeoutId = undefined;
|
|
97
|
+
}
|
|
70
98
|
}
|
|
71
99
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
toNum,
|
|
7
7
|
getMarketUiLadder,
|
|
8
8
|
Market,
|
|
9
|
+
getMarketLadder,
|
|
9
10
|
} from '@ellipsis-labs/phoenix-sdk';
|
|
10
11
|
import { PRICE_PRECISION } from '../constants/numericConstants';
|
|
11
12
|
import { BN } from '@coral-xyz/anchor';
|
|
@@ -163,13 +164,18 @@ export class PhoenixSubscriber implements L2OrderBookGenerator {
|
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
*getL2Levels(side: 'bids' | 'asks'): Generator<L2Level> {
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
const tickSize = this.market.data.header
|
|
168
|
+
.tickSizeInQuoteAtomsPerBaseUnit as BN;
|
|
169
|
+
const baseLotsToRawBaseUnits = this.market.baseLotsToRawBaseUnits(1);
|
|
170
|
+
|
|
171
|
+
const basePrecision = new BN(
|
|
172
|
+
Math.pow(10, this.market.data.header.baseParams.decimals) *
|
|
173
|
+
baseLotsToRawBaseUnits
|
|
169
174
|
);
|
|
170
|
-
const pricePrecision = PRICE_PRECISION.toNumber();
|
|
171
175
|
|
|
172
|
-
const
|
|
176
|
+
const pricePrecision = PRICE_PRECISION.div(tickSize as BN);
|
|
177
|
+
|
|
178
|
+
const ladder = getMarketLadder(
|
|
173
179
|
this.market,
|
|
174
180
|
this.lastSlot,
|
|
175
181
|
this.lastUnixTimestamp,
|
|
@@ -177,10 +183,10 @@ export class PhoenixSubscriber implements L2OrderBookGenerator {
|
|
|
177
183
|
);
|
|
178
184
|
|
|
179
185
|
for (let i = 0; i < ladder[side].length; i++) {
|
|
180
|
-
const {
|
|
181
|
-
const size =
|
|
186
|
+
const { priceInTicks, sizeInBaseLots } = ladder[side][i];
|
|
187
|
+
const size = sizeInBaseLots.mul(basePrecision);
|
|
182
188
|
yield {
|
|
183
|
-
price: new BN(
|
|
189
|
+
price: priceInTicks.mul(new BN(pricePrecision)),
|
|
184
190
|
size,
|
|
185
191
|
sources: {
|
|
186
192
|
phoenix: size,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { PriorityFeeStrategy } from './types';
|
|
2
|
+
|
|
3
|
+
export class AverageOverSlotsStrategy implements PriorityFeeStrategy {
|
|
4
|
+
private lookbackSlots: number;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param lookbackSlots The number of slots to look back from the max slot in the sample
|
|
8
|
+
*/
|
|
9
|
+
constructor(lookbackSlots = 10) {
|
|
10
|
+
this.lookbackSlots = lookbackSlots;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
calculate(samples: { slot: number; prioritizationFee: number }[]): number {
|
|
14
|
+
if (samples.length === 0) {
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
const stopSlot = samples[0].slot - this.lookbackSlots;
|
|
18
|
+
let runningSumFees = 0;
|
|
19
|
+
let countFees = 0;
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < samples.length; i++) {
|
|
22
|
+
if (samples[i].slot <= stopSlot) {
|
|
23
|
+
return runningSumFees / countFees;
|
|
24
|
+
}
|
|
25
|
+
runningSumFees += samples[i].prioritizationFee;
|
|
26
|
+
countFees++;
|
|
27
|
+
}
|
|
28
|
+
return runningSumFees / countFees;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PriorityFeeStrategy } from './types';
|
|
2
|
+
|
|
3
|
+
export class AverageStrategy implements PriorityFeeStrategy {
|
|
4
|
+
calculate(samples: { slot: number; prioritizationFee: number }[]): number {
|
|
5
|
+
return (
|
|
6
|
+
samples.reduce((a, b) => {
|
|
7
|
+
return a + b.prioritizationFee;
|
|
8
|
+
}, 0) / samples.length
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { PriorityFeeStrategy } from './types';
|
|
2
|
+
|
|
3
|
+
class EwmaStrategy implements PriorityFeeStrategy {
|
|
4
|
+
private halfLife: number;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param halfLife The half life of the EWMA in slots. Default is 25 slots, approx 10 seconds.
|
|
8
|
+
*/
|
|
9
|
+
constructor(halfLife = 25) {
|
|
10
|
+
this.halfLife = halfLife;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// samples provided in desc slot order
|
|
14
|
+
calculate(samples: { slot: number; prioritizationFee: number }[]): number {
|
|
15
|
+
if (samples.length === 0) {
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
if (samples.length === 1) {
|
|
19
|
+
return samples[0].prioritizationFee;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let ewma = 0;
|
|
23
|
+
|
|
24
|
+
const samplesReversed = samples.slice().reverse();
|
|
25
|
+
for (let i = 0; i < samplesReversed.length; i++) {
|
|
26
|
+
if (i === 0) {
|
|
27
|
+
ewma = samplesReversed[i].prioritizationFee;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const gap = samplesReversed[i].slot - samplesReversed[i - 1].slot;
|
|
31
|
+
const alpha = 1 - Math.exp((Math.log(0.5) / this.halfLife) * gap);
|
|
32
|
+
|
|
33
|
+
ewma = alpha * samplesReversed[i].prioritizationFee + (1 - alpha) * ewma;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return ewma;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { EwmaStrategy };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { PriorityFeeStrategy } from './types';
|
|
2
|
+
|
|
3
|
+
export class MaxOverSlotsStrategy implements PriorityFeeStrategy {
|
|
4
|
+
private lookbackSlots: number;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param lookbackSlots The number of slots to look back from the max slot in the sample
|
|
8
|
+
*/
|
|
9
|
+
constructor(lookbackSlots = 10) {
|
|
10
|
+
this.lookbackSlots = lookbackSlots;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
calculate(samples: { slot: number; prioritizationFee: number }[]): number {
|
|
14
|
+
if (samples.length === 0) {
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
// Assuming samples are sorted in descending order of slot.
|
|
18
|
+
const stopSlot = samples[0].slot - this.lookbackSlots;
|
|
19
|
+
let currMaxFee = samples[0].prioritizationFee;
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < samples.length; i++) {
|
|
22
|
+
if (samples[i].slot <= stopSlot) {
|
|
23
|
+
return currMaxFee;
|
|
24
|
+
}
|
|
25
|
+
if (samples[i].prioritizationFee > currMaxFee) {
|
|
26
|
+
currMaxFee = samples[i].prioritizationFee;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return currMaxFee;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -1,35 +1,63 @@
|
|
|
1
1
|
import { Connection, PublicKey } from '@solana/web3.js';
|
|
2
|
+
import { PriorityFeeStrategy } from './types';
|
|
3
|
+
import { AverageOverSlotsStrategy } from './averageOverSlotsStrategy';
|
|
4
|
+
import { MaxOverSlotsStrategy } from './maxOverSlotsStrategy';
|
|
2
5
|
|
|
3
6
|
export class PriorityFeeSubscriber {
|
|
4
7
|
connection: Connection;
|
|
5
8
|
frequencyMs: number;
|
|
6
9
|
addresses: PublicKey[];
|
|
7
|
-
|
|
10
|
+
customStrategy?: PriorityFeeStrategy;
|
|
11
|
+
averageStrategy = new AverageOverSlotsStrategy();
|
|
12
|
+
maxStrategy = new MaxOverSlotsStrategy();
|
|
8
13
|
|
|
9
14
|
intervalId?: ReturnType<typeof setTimeout>;
|
|
10
15
|
|
|
11
16
|
latestPriorityFee = 0;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
lastStrategyResult = 0;
|
|
18
|
+
lastCustomStrategyResult = 0;
|
|
19
|
+
lastAvgStrategyResult = 0;
|
|
20
|
+
lastMaxStrategyResult = 0;
|
|
16
21
|
lastSlotSeen = 0;
|
|
17
22
|
|
|
18
23
|
public constructor({
|
|
19
24
|
connection,
|
|
20
25
|
frequencyMs,
|
|
21
26
|
addresses,
|
|
27
|
+
customStrategy,
|
|
22
28
|
slotsToCheck = 10,
|
|
23
29
|
}: {
|
|
24
30
|
connection: Connection;
|
|
25
31
|
frequencyMs: number;
|
|
26
32
|
addresses: PublicKey[];
|
|
33
|
+
customStrategy?: PriorityFeeStrategy;
|
|
27
34
|
slotsToCheck?: number;
|
|
28
35
|
}) {
|
|
29
36
|
this.connection = connection;
|
|
30
37
|
this.frequencyMs = frequencyMs;
|
|
31
38
|
this.addresses = addresses;
|
|
32
|
-
|
|
39
|
+
if (slotsToCheck) {
|
|
40
|
+
this.averageStrategy = new AverageOverSlotsStrategy(slotsToCheck);
|
|
41
|
+
this.maxStrategy = new MaxOverSlotsStrategy(slotsToCheck);
|
|
42
|
+
}
|
|
43
|
+
if (customStrategy) {
|
|
44
|
+
this.customStrategy = customStrategy;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public get avgPriorityFee(): number {
|
|
49
|
+
return Math.floor(this.lastAvgStrategyResult);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public get maxPriorityFee(): number {
|
|
53
|
+
return Math.floor(this.lastMaxStrategyResult);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public get customPriorityFee(): number {
|
|
57
|
+
if (!this.customStrategy) {
|
|
58
|
+
console.error('Custom strategy not set');
|
|
59
|
+
}
|
|
60
|
+
return Math.floor(this.lastCustomStrategyResult);
|
|
33
61
|
}
|
|
34
62
|
|
|
35
63
|
public async subscribe(): Promise<void> {
|
|
@@ -47,23 +75,22 @@ export class PriorityFeeSubscriber {
|
|
|
47
75
|
[this.addresses]
|
|
48
76
|
);
|
|
49
77
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (!descResults?.length) return;
|
|
78
|
+
// getRecentPrioritizationFees returns results unsorted
|
|
79
|
+
const results: { slot: number; prioritizationFee: number }[] =
|
|
80
|
+
rpcJSONResponse?.result;
|
|
81
|
+
if (!results.length) return;
|
|
82
|
+
const descResults = results.sort((a, b) => b.slot - a.slot);
|
|
56
83
|
|
|
57
84
|
const mostRecentResult = descResults[0];
|
|
58
85
|
this.latestPriorityFee = mostRecentResult.prioritizationFee;
|
|
59
86
|
this.lastSlotSeen = mostRecentResult.slot;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
87
|
+
|
|
88
|
+
this.lastAvgStrategyResult = this.averageStrategy.calculate(descResults);
|
|
89
|
+
this.lastMaxStrategyResult = this.maxStrategy.calculate(descResults);
|
|
90
|
+
if (this.customStrategy) {
|
|
91
|
+
this.lastCustomStrategyResult =
|
|
92
|
+
this.customStrategy.calculate(descResults);
|
|
93
|
+
}
|
|
67
94
|
}
|
|
68
95
|
|
|
69
96
|
public async unsubscribe(): Promise<void> {
|