@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.
Files changed (100) hide show
  1. package/VERSION +1 -1
  2. package/examples/phoenix.ts +11 -4
  3. package/lib/accounts/basicUserAccountSubscriber.d.ts +4 -0
  4. package/lib/accounts/basicUserAccountSubscriber.js +4 -0
  5. package/lib/accounts/bulkAccountLoader.js +7 -1
  6. package/lib/accounts/oneShotUserAccountSubscriber.d.ts +17 -0
  7. package/lib/accounts/oneShotUserAccountSubscriber.js +48 -0
  8. package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.js +1 -0
  9. package/lib/accounts/webSocketAccountSubscriber.d.ts +1 -1
  10. package/lib/accounts/webSocketAccountSubscriber.js +7 -4
  11. package/lib/accounts/webSocketProgramAccountSubscriber.d.ts +1 -1
  12. package/lib/accounts/webSocketProgramAccountSubscriber.js +7 -4
  13. package/lib/adminClient.d.ts +1 -0
  14. package/lib/adminClient.js +9 -0
  15. package/lib/constants/numericConstants.d.ts +3 -0
  16. package/lib/constants/numericConstants.js +4 -1
  17. package/lib/dlob/orderBookLevels.d.ts +22 -0
  18. package/lib/dlob/orderBookLevels.js +115 -1
  19. package/lib/driftClient.d.ts +3 -0
  20. package/lib/driftClient.js +31 -5
  21. package/lib/events/webSocketLogProvider.js +3 -3
  22. package/lib/factory/bigNum.d.ts +1 -1
  23. package/lib/factory/bigNum.js +5 -2
  24. package/lib/idl/drift.json +52 -1
  25. package/lib/index.d.ts +2 -1
  26. package/lib/index.js +2 -1
  27. package/lib/math/amm.d.ts +5 -1
  28. package/lib/math/amm.js +62 -13
  29. package/lib/math/state.js +3 -0
  30. package/lib/orderSubscriber/OrderSubscriber.js +1 -0
  31. package/lib/orderSubscriber/WebsocketSubscription.d.ts +4 -1
  32. package/lib/orderSubscriber/WebsocketSubscription.js +25 -1
  33. package/lib/orderSubscriber/types.d.ts +1 -0
  34. package/lib/phoenix/phoenixSubscriber.js +10 -6
  35. package/lib/priorityFee/averageOverSlotsStrategy.d.ts +12 -0
  36. package/lib/priorityFee/averageOverSlotsStrategy.js +28 -0
  37. package/lib/priorityFee/averageStrategy.d.ts +7 -0
  38. package/lib/priorityFee/averageStrategy.js +11 -0
  39. package/lib/priorityFee/ewmaStrategy.d.ts +13 -0
  40. package/lib/priorityFee/ewmaStrategy.js +33 -0
  41. package/lib/priorityFee/index.d.ts +7 -0
  42. package/lib/priorityFee/index.js +23 -0
  43. package/lib/priorityFee/maxOverSlotsStrategy.d.ts +12 -0
  44. package/lib/priorityFee/maxOverSlotsStrategy.js +29 -0
  45. package/lib/priorityFee/maxStrategy.d.ts +7 -0
  46. package/lib/priorityFee/maxStrategy.js +9 -0
  47. package/lib/priorityFee/priorityFeeSubscriber.d.ts +15 -4
  48. package/lib/priorityFee/priorityFeeSubscriber.js +38 -14
  49. package/lib/priorityFee/types.d.ts +6 -0
  50. package/lib/priorityFee/types.js +2 -0
  51. package/lib/slot/SlotSubscriber.d.ts +11 -3
  52. package/lib/slot/SlotSubscriber.js +40 -4
  53. package/lib/types.d.ts +4 -1
  54. package/lib/types.js +1 -0
  55. package/lib/user.d.ts +1 -1
  56. package/lib/user.js +15 -8
  57. package/lib/userMap/userMap.d.ts +3 -0
  58. package/lib/userMap/userMap.js +10 -1
  59. package/lib/userStats.d.ts +1 -0
  60. package/lib/userStats.js +3 -0
  61. package/package.json +1 -1
  62. package/src/accounts/basicUserAccountSubscriber.ts +4 -0
  63. package/src/accounts/bulkAccountLoader.ts +10 -3
  64. package/src/accounts/oneShotUserAccountSubscriber.ts +64 -0
  65. package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +1 -0
  66. package/src/accounts/webSocketAccountSubscriber.ts +7 -4
  67. package/src/accounts/webSocketProgramAccountSubscriber.ts +7 -4
  68. package/src/adminClient.ts +13 -0
  69. package/src/constants/numericConstants.ts +4 -0
  70. package/src/dlob/orderBookLevels.ts +136 -0
  71. package/src/driftClient.ts +50 -6
  72. package/src/events/webSocketLogProvider.ts +3 -3
  73. package/src/factory/bigNum.ts +11 -2
  74. package/src/idl/drift.json +52 -1
  75. package/src/index.ts +2 -1
  76. package/src/math/amm.ts +159 -25
  77. package/src/math/state.ts +3 -0
  78. package/src/orderSubscriber/OrderSubscriber.ts +1 -0
  79. package/src/orderSubscriber/WebsocketSubscription.ts +28 -0
  80. package/src/orderSubscriber/types.ts +1 -0
  81. package/src/phoenix/phoenixSubscriber.ts +14 -8
  82. package/src/priorityFee/averageOverSlotsStrategy.ts +30 -0
  83. package/src/priorityFee/averageStrategy.ts +11 -0
  84. package/src/priorityFee/ewmaStrategy.ts +40 -0
  85. package/src/priorityFee/index.ts +7 -0
  86. package/src/priorityFee/maxOverSlotsStrategy.ts +31 -0
  87. package/src/priorityFee/maxStrategy.ts +7 -0
  88. package/src/priorityFee/priorityFeeSubscriber.ts +46 -19
  89. package/src/priorityFee/types.ts +5 -0
  90. package/src/slot/SlotSubscriber.ts +52 -5
  91. package/src/types.ts +2 -1
  92. package/src/user.ts +25 -8
  93. package/src/userMap/userMap.ts +17 -3
  94. package/src/userStats.ts +8 -0
  95. package/tests/amm/test.ts +219 -11
  96. package/tests/bn/test.ts +27 -0
  97. package/tests/dlob/helpers.ts +1 -1
  98. package/tests/dlob/test.ts +372 -2
  99. package/tests/tx/priorityFeeStrategy.ts +97 -0
  100. 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
  }
@@ -14,6 +14,7 @@ export type OrderSubscriberConfig = {
14
14
  type: 'websocket';
15
15
  skipInitialLoad?: boolean;
16
16
  resubTimeoutMs?: number;
17
+ resyncIntervalMs?: number;
17
18
  commitment?: Commitment;
18
19
  };
19
20
  fastDecode?: boolean;
@@ -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 basePrecision = Math.pow(
167
- 10,
168
- this.market.data.header.baseParams.decimals
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 ladder = getMarketUiLadder(
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 { price, quantity } = ladder[side][i];
181
- const size = new BN(Math.floor(quantity * basePrecision));
186
+ const { priceInTicks, sizeInBaseLots } = ladder[side][i];
187
+ const size = sizeInBaseLots.mul(basePrecision);
182
188
  yield {
183
- price: new BN(Math.floor(price * pricePrecision)),
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,7 @@
1
+ export * from './averageOverSlotsStrategy';
2
+ export * from './averageStrategy';
3
+ export * from './ewmaStrategy';
4
+ export * from './maxOverSlotsStrategy';
5
+ export * from './maxStrategy';
6
+ export * from './priorityFeeSubscriber';
7
+ export * from './types';
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ import { PriorityFeeStrategy } from './types';
2
+
3
+ export class MaxStrategy implements PriorityFeeStrategy {
4
+ calculate(samples: { slot: number; prioritizationFee: number }[]): number {
5
+ return Math.max(...samples.map((result) => result.prioritizationFee));
6
+ }
7
+ }
@@ -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
- slotsToCheck: number;
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
- // avg of last {slotsToCheck} slots
13
- avgPriorityFee = 0;
14
- // max of last {slotsToCheck} slots
15
- maxPriorityFee = 0;
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
- this.slotsToCheck = slotsToCheck;
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
- const descResults: { slot: number; prioritizationFee: number }[] =
51
- rpcJSONResponse?.result
52
- ?.sort((a, b) => b.slot - a.slot)
53
- ?.slice(0, this.slotsToCheck) ?? [];
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
- this.avgPriorityFee =
61
- descResults.reduce((a, b) => {
62
- return a + b.prioritizationFee;
63
- }, 0) / descResults.length;
64
- this.maxPriorityFee = Math.max(
65
- ...descResults.map((result) => result.prioritizationFee)
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> {
@@ -0,0 +1,5 @@
1
+ export interface PriorityFeeStrategy {
2
+ // calculate the priority fee for a given set of samples.
3
+ // expect samples to be sorted in descending order (by slot)
4
+ calculate(samples: { slot: number; prioritizationFee: number }[]): number;
5
+ }
@@ -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 = {}; // for future customization
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
- _config?: SlotSubscriberConfig
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 (this.subscriptionId) {
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
- last24hAvgFundingRate: BN;
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(marketIndex)[0] ||
1403
- this.getEmptyPosition(marketIndex);
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
- const feeTier = this.getUserFeeTier(MarketType.PERP);
3035
- return quoteAmount
3036
- .mul(new BN(feeTier.feeNumerator))
3037
- .div(new BN(feeTier.feeDenominator));
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
  /**
@@ -13,7 +13,7 @@ import {
13
13
  LPRecord,
14
14
  StateAccount,
15
15
  DLOB,
16
- BasicUserAccountSubscriber,
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 BasicUserAccountSubscriber(
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
  }