@drift-labs/sdk 2.52.0-beta.0 → 2.52.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/VERSION +1 -1
- package/examples/phoenix.ts +11 -4
- 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 +3 -0
- package/lib/driftClient.js +31 -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 +52 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +2 -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 +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 +50 -6
- 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 +2 -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 +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/tests/tx/priorityFeeStrategy.ts +97 -0
- package/tests/user/helpers.ts +1 -0
|
@@ -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 this.lastAvgStrategyResult;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public get maxPriorityFee(): number {
|
|
53
|
+
return this.lastMaxStrategyResult;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public get customPriorityFee(): number {
|
|
57
|
+
if (!this.customStrategy) {
|
|
58
|
+
console.error('Custom strategy not set');
|
|
59
|
+
}
|
|
60
|
+
return 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> {
|
|
@@ -3,7 +3,9 @@ import { EventEmitter } from 'events';
|
|
|
3
3
|
import StrictEventEmitter from 'strict-event-emitter-types/types/src';
|
|
4
4
|
|
|
5
5
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
6
|
-
type SlotSubscriberConfig = {
|
|
6
|
+
type SlotSubscriberConfig = {
|
|
7
|
+
resubTimeoutMs?: number;
|
|
8
|
+
}; // for future customization
|
|
7
9
|
|
|
8
10
|
export interface SlotSubscriberEvents {
|
|
9
11
|
newSlot: (newSlot: number) => void;
|
|
@@ -14,15 +16,22 @@ export class SlotSubscriber {
|
|
|
14
16
|
subscriptionId: number;
|
|
15
17
|
eventEmitter: StrictEventEmitter<EventEmitter, SlotSubscriberEvents>;
|
|
16
18
|
|
|
19
|
+
// Reconnection
|
|
20
|
+
timeoutId?: NodeJS.Timeout;
|
|
21
|
+
resubTimeoutMs?: number;
|
|
22
|
+
isUnsubscribing = false;
|
|
23
|
+
receivingData = false;
|
|
24
|
+
|
|
17
25
|
public constructor(
|
|
18
26
|
private connection: Connection,
|
|
19
|
-
|
|
27
|
+
config?: SlotSubscriberConfig
|
|
20
28
|
) {
|
|
21
29
|
this.eventEmitter = new EventEmitter();
|
|
30
|
+
this.resubTimeoutMs = config?.resubTimeoutMs;
|
|
22
31
|
}
|
|
23
32
|
|
|
24
33
|
public async subscribe(): Promise<void> {
|
|
25
|
-
if (this.subscriptionId) {
|
|
34
|
+
if (this.subscriptionId != null) {
|
|
26
35
|
return;
|
|
27
36
|
}
|
|
28
37
|
|
|
@@ -30,19 +39,57 @@ export class SlotSubscriber {
|
|
|
30
39
|
|
|
31
40
|
this.subscriptionId = this.connection.onSlotChange((slotInfo) => {
|
|
32
41
|
if (!this.currentSlot || this.currentSlot < slotInfo.slot) {
|
|
42
|
+
if (this.resubTimeoutMs && !this.isUnsubscribing) {
|
|
43
|
+
this.receivingData = true;
|
|
44
|
+
clearTimeout(this.timeoutId);
|
|
45
|
+
this.setTimeout();
|
|
46
|
+
}
|
|
33
47
|
this.currentSlot = slotInfo.slot;
|
|
34
48
|
this.eventEmitter.emit('newSlot', slotInfo.slot);
|
|
35
49
|
}
|
|
36
50
|
});
|
|
51
|
+
|
|
52
|
+
if (this.resubTimeoutMs) {
|
|
53
|
+
this.setTimeout();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private setTimeout(): void {
|
|
58
|
+
this.timeoutId = setTimeout(async () => {
|
|
59
|
+
if (this.isUnsubscribing) {
|
|
60
|
+
// If we are in the process of unsubscribing, do not attempt to resubscribe
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this.receivingData) {
|
|
65
|
+
console.log(
|
|
66
|
+
`No new slot in ${this.resubTimeoutMs}ms, slot subscriber resubscribing`
|
|
67
|
+
);
|
|
68
|
+
await this.unsubscribe(true);
|
|
69
|
+
this.receivingData = false;
|
|
70
|
+
await this.subscribe();
|
|
71
|
+
}
|
|
72
|
+
}, this.resubTimeoutMs);
|
|
37
73
|
}
|
|
38
74
|
|
|
39
75
|
public getSlot(): number {
|
|
40
76
|
return this.currentSlot;
|
|
41
77
|
}
|
|
42
78
|
|
|
43
|
-
public async unsubscribe(): Promise<void> {
|
|
44
|
-
if (
|
|
79
|
+
public async unsubscribe(onResub = false): Promise<void> {
|
|
80
|
+
if (!onResub) {
|
|
81
|
+
this.resubTimeoutMs = undefined;
|
|
82
|
+
}
|
|
83
|
+
this.isUnsubscribing = true;
|
|
84
|
+
clearTimeout(this.timeoutId);
|
|
85
|
+
this.timeoutId = undefined;
|
|
86
|
+
|
|
87
|
+
if (this.subscriptionId != null) {
|
|
45
88
|
await this.connection.removeSlotChangeListener(this.subscriptionId);
|
|
89
|
+
this.subscriptionId = undefined;
|
|
90
|
+
this.isUnsubscribing = false;
|
|
91
|
+
} else {
|
|
92
|
+
this.isUnsubscribing = false;
|
|
46
93
|
}
|
|
47
94
|
}
|
|
48
95
|
}
|
package/src/types.ts
CHANGED
|
@@ -182,6 +182,7 @@ export class SpotFulfillmentStatus {
|
|
|
182
182
|
export class DepositExplanation {
|
|
183
183
|
static readonly NONE = { none: {} };
|
|
184
184
|
static readonly TRANSFER = { transfer: {} };
|
|
185
|
+
static readonly BORROW = { borrow: {} };
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
export class SettlePnlExplanation {
|
|
@@ -710,7 +711,7 @@ export type AMM = {
|
|
|
710
711
|
pegMultiplier: BN;
|
|
711
712
|
cumulativeFundingRateLong: BN;
|
|
712
713
|
cumulativeFundingRateShort: BN;
|
|
713
|
-
|
|
714
|
+
last24HAvgFundingRate: BN;
|
|
714
715
|
lastFundingRateShort: BN;
|
|
715
716
|
lastFundingRateLong: BN;
|
|
716
717
|
|
package/src/user.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
OPEN_ORDER_MARGIN_REQUIREMENT,
|
|
28
28
|
PRICE_PRECISION,
|
|
29
29
|
QUOTE_PRECISION,
|
|
30
|
+
QUOTE_PRECISION_EXP,
|
|
30
31
|
QUOTE_SPOT_MARKET_INDEX,
|
|
31
32
|
SPOT_MARKET_WEIGHT_PRECISION,
|
|
32
33
|
TEN,
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
UserAccountSubscriber,
|
|
41
42
|
} from './accounts/types';
|
|
42
43
|
import {
|
|
44
|
+
BigNum,
|
|
43
45
|
BN,
|
|
44
46
|
calculateBaseAssetValue,
|
|
45
47
|
calculateMarketMarginRatio,
|
|
@@ -1399,8 +1401,12 @@ export class User {
|
|
|
1399
1401
|
includeOpenOrders = false
|
|
1400
1402
|
): BN {
|
|
1401
1403
|
const userPosition =
|
|
1402
|
-
this.getPerpPositionWithLPSettle(
|
|
1403
|
-
|
|
1404
|
+
this.getPerpPositionWithLPSettle(
|
|
1405
|
+
marketIndex,
|
|
1406
|
+
undefined,
|
|
1407
|
+
false,
|
|
1408
|
+
true
|
|
1409
|
+
)[0] || this.getEmptyPosition(marketIndex);
|
|
1404
1410
|
const market = this.driftClient.getPerpMarketAccount(
|
|
1405
1411
|
userPosition.marketIndex
|
|
1406
1412
|
);
|
|
@@ -3013,7 +3019,7 @@ export class User {
|
|
|
3013
3019
|
let makerFee =
|
|
3014
3020
|
feeTier.makerRebateNumerator / feeTier.makerRebateDenominator;
|
|
3015
3021
|
|
|
3016
|
-
if (marketIndex && isVariant(marketType, 'perp')) {
|
|
3022
|
+
if (marketIndex !== undefined && isVariant(marketType, 'perp')) {
|
|
3017
3023
|
const marketAccount = this.driftClient.getPerpMarketAccount(marketIndex);
|
|
3018
3024
|
takerFee += (takerFee * marketAccount.feeAdjustment) / 100;
|
|
3019
3025
|
makerFee += (makerFee * marketAccount.feeAdjustment) / 100;
|
|
@@ -3030,11 +3036,22 @@ export class User {
|
|
|
3030
3036
|
* @param quoteAmount
|
|
3031
3037
|
* @returns feeForQuote : Precision QUOTE_PRECISION
|
|
3032
3038
|
*/
|
|
3033
|
-
public calculateFeeForQuoteAmount(quoteAmount: BN): BN {
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3039
|
+
public calculateFeeForQuoteAmount(quoteAmount: BN, marketIndex?: number): BN {
|
|
3040
|
+
if (marketIndex !== undefined) {
|
|
3041
|
+
const takerFeeMultiplier = this.getMarketFees(
|
|
3042
|
+
MarketType.PERP,
|
|
3043
|
+
marketIndex
|
|
3044
|
+
).takerFee;
|
|
3045
|
+
const feeAmountNum =
|
|
3046
|
+
BigNum.from(quoteAmount, QUOTE_PRECISION_EXP).toNum() *
|
|
3047
|
+
takerFeeMultiplier;
|
|
3048
|
+
return BigNum.fromPrint(feeAmountNum.toString(), QUOTE_PRECISION_EXP).val;
|
|
3049
|
+
} else {
|
|
3050
|
+
const feeTier = this.getUserFeeTier(MarketType.PERP);
|
|
3051
|
+
return quoteAmount
|
|
3052
|
+
.mul(new BN(feeTier.feeNumerator))
|
|
3053
|
+
.div(new BN(feeTier.feeDenominator));
|
|
3054
|
+
}
|
|
3038
3055
|
}
|
|
3039
3056
|
|
|
3040
3057
|
/**
|
package/src/userMap/userMap.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
LPRecord,
|
|
14
14
|
StateAccount,
|
|
15
15
|
DLOB,
|
|
16
|
-
|
|
16
|
+
OneShotUserAccountSubscriber,
|
|
17
17
|
BN,
|
|
18
18
|
} from '..';
|
|
19
19
|
|
|
@@ -60,6 +60,7 @@ export class UserMap implements UserMapInterface {
|
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
62
|
private decode;
|
|
63
|
+
private mostRecentSlot = 0;
|
|
63
64
|
|
|
64
65
|
private syncPromise?: Promise<void>;
|
|
65
66
|
private syncPromiseResolver: () => void;
|
|
@@ -132,10 +133,12 @@ export class UserMap implements UserMapInterface {
|
|
|
132
133
|
userAccountPublicKey,
|
|
133
134
|
accountSubscription: {
|
|
134
135
|
type: 'custom',
|
|
135
|
-
userAccountSubscriber: new
|
|
136
|
+
userAccountSubscriber: new OneShotUserAccountSubscriber(
|
|
137
|
+
this.driftClient.program,
|
|
136
138
|
userAccountPublicKey,
|
|
137
139
|
userAccount,
|
|
138
|
-
slot
|
|
140
|
+
slot,
|
|
141
|
+
this.commitment
|
|
139
142
|
),
|
|
140
143
|
},
|
|
141
144
|
});
|
|
@@ -310,6 +313,8 @@ export class UserMap implements UserMapInterface {
|
|
|
310
313
|
|
|
311
314
|
const slot = rpcResponseAndContext.context.slot;
|
|
312
315
|
|
|
316
|
+
this.updateLatestSlot(slot);
|
|
317
|
+
|
|
313
318
|
const programAccountBufferMap = new Map<string, Buffer>();
|
|
314
319
|
for (const programAccount of rpcResponseAndContext.value) {
|
|
315
320
|
programAccountBufferMap.set(
|
|
@@ -371,6 +376,7 @@ export class UserMap implements UserMapInterface {
|
|
|
371
376
|
userAccount: UserAccount,
|
|
372
377
|
slot: number
|
|
373
378
|
) {
|
|
379
|
+
this.updateLatestSlot(slot);
|
|
374
380
|
if (!this.userMap.has(key)) {
|
|
375
381
|
this.addPubkey(new PublicKey(key), userAccount, slot);
|
|
376
382
|
} else {
|
|
@@ -378,4 +384,12 @@ export class UserMap implements UserMapInterface {
|
|
|
378
384
|
user.accountSubscriber.updateData(userAccount, slot);
|
|
379
385
|
}
|
|
380
386
|
}
|
|
387
|
+
|
|
388
|
+
updateLatestSlot(slot: number): void {
|
|
389
|
+
this.mostRecentSlot = Math.max(slot, this.mostRecentSlot);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
public getSlot(): number {
|
|
393
|
+
return this.mostRecentSlot;
|
|
394
|
+
}
|
|
381
395
|
}
|
package/src/userStats.ts
CHANGED
|
@@ -82,4 +82,12 @@ export class UserStats {
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
+
|
|
86
|
+
public static getOldestActionTs(account: UserStatsAccount): number {
|
|
87
|
+
return Math.min(
|
|
88
|
+
account.lastFillerVolume30DTs.toNumber(),
|
|
89
|
+
account.lastMakerVolume30DTs.toNumber(),
|
|
90
|
+
account.lastTakerVolume30DTs.toNumber()
|
|
91
|
+
);
|
|
92
|
+
}
|
|
85
93
|
}
|