@drift-labs/sdk 2.49.0-beta.0 → 2.49.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 (53) hide show
  1. package/VERSION +1 -1
  2. package/lib/accounts/{mockUserAccountSubscriber.d.ts → basicUserAccountSubscriber.d.ts} +2 -2
  3. package/lib/accounts/{mockUserAccountSubscriber.js → basicUserAccountSubscriber.js} +9 -6
  4. package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.d.ts +29 -0
  5. package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.js +110 -0
  6. package/lib/accounts/types.d.ts +14 -1
  7. package/lib/accounts/webSocketInsuranceFundStakeAccountSubscriber.d.ts +23 -0
  8. package/lib/accounts/webSocketInsuranceFundStakeAccountSubscriber.js +65 -0
  9. package/lib/dlob/DLOB.d.ts +6 -2
  10. package/lib/dlob/DLOB.js +37 -12
  11. package/lib/driftClient.d.ts +66 -66
  12. package/lib/driftClient.js +208 -194
  13. package/lib/events/eventSubscriber.js +2 -1
  14. package/lib/events/sort.d.ts +2 -2
  15. package/lib/events/sort.js +6 -23
  16. package/lib/examples/loadDlob.js +10 -5
  17. package/lib/index.d.ts +3 -1
  18. package/lib/index.js +3 -1
  19. package/lib/math/auction.js +6 -1
  20. package/lib/orderSubscriber/OrderSubscriber.js +4 -0
  21. package/lib/orderSubscriber/WebsocketSubscription.d.ts +1 -1
  22. package/lib/orderSubscriber/WebsocketSubscription.js +8 -6
  23. package/lib/types.d.ts +0 -2
  24. package/lib/userMap/PollingSubscription.d.ts +15 -0
  25. package/lib/userMap/PollingSubscription.js +26 -0
  26. package/lib/userMap/WebsocketSubscription.d.ts +19 -0
  27. package/lib/userMap/WebsocketSubscription.js +40 -0
  28. package/lib/userMap/userMap.d.ts +15 -18
  29. package/lib/userMap/userMap.js +62 -31
  30. package/lib/userMap/userMapConfig.d.ts +20 -0
  31. package/lib/userMap/userMapConfig.js +2 -0
  32. package/package.json +1 -1
  33. package/src/accounts/{mockUserAccountSubscriber.ts → basicUserAccountSubscriber.ts} +8 -6
  34. package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +185 -0
  35. package/src/accounts/types.ts +21 -0
  36. package/src/accounts/webSocketInsuranceFundStakeAccountSubscriber.ts +127 -0
  37. package/src/dlob/DLOB.ts +55 -15
  38. package/src/driftClient.ts +429 -272
  39. package/src/events/eventSubscriber.ts +2 -1
  40. package/src/events/sort.ts +7 -29
  41. package/src/examples/loadDlob.ts +11 -6
  42. package/src/index.ts +3 -1
  43. package/src/math/auction.ts +8 -1
  44. package/src/orderSubscriber/OrderSubscriber.ts +4 -0
  45. package/src/orderSubscriber/WebsocketSubscription.ts +19 -16
  46. package/src/types.ts +0 -2
  47. package/src/userMap/PollingSubscription.ts +46 -0
  48. package/src/userMap/WebsocketSubscription.ts +74 -0
  49. package/src/userMap/userMap.ts +88 -60
  50. package/src/userMap/userMapConfig.ts +31 -0
  51. package/tests/amm/test.ts +6 -3
  52. package/tests/dlob/helpers.ts +2 -6
  53. package/tests/dlob/test.ts +194 -0
@@ -0,0 +1,185 @@
1
+ import {
2
+ DataAndSlot,
3
+ NotSubscribedError,
4
+ InsuranceFundStakeAccountEvents,
5
+ InsuranceFundStakeAccountSubscriber,
6
+ } from './types';
7
+ import { Program } from '@coral-xyz/anchor';
8
+ import StrictEventEmitter from 'strict-event-emitter-types';
9
+ import { EventEmitter } from 'events';
10
+ import { PublicKey } from '@solana/web3.js';
11
+ import { BulkAccountLoader } from './bulkAccountLoader';
12
+ import { InsuranceFundStake } from '../types';
13
+
14
+ export class PollingInsuranceFundStakeAccountSubscriber
15
+ implements InsuranceFundStakeAccountSubscriber
16
+ {
17
+ isSubscribed: boolean;
18
+ program: Program;
19
+ eventEmitter: StrictEventEmitter<
20
+ EventEmitter,
21
+ InsuranceFundStakeAccountEvents
22
+ >;
23
+ insuranceFundStakeAccountPublicKey: PublicKey;
24
+
25
+ accountLoader: BulkAccountLoader;
26
+ callbackId?: string;
27
+ errorCallbackId?: string;
28
+
29
+ insuranceFundStakeAccountAndSlot?: DataAndSlot<InsuranceFundStake>;
30
+
31
+ public constructor(
32
+ program: Program,
33
+ publicKey: PublicKey,
34
+ accountLoader: BulkAccountLoader
35
+ ) {
36
+ this.isSubscribed = false;
37
+ this.program = program;
38
+ this.insuranceFundStakeAccountPublicKey = publicKey;
39
+ this.accountLoader = accountLoader;
40
+ this.eventEmitter = new EventEmitter();
41
+ }
42
+
43
+ async subscribe(insuranceFundStake?: InsuranceFundStake): Promise<boolean> {
44
+ if (this.isSubscribed) {
45
+ return true;
46
+ }
47
+
48
+ if (insuranceFundStake) {
49
+ this.insuranceFundStakeAccountAndSlot = {
50
+ data: insuranceFundStake,
51
+ slot: undefined,
52
+ };
53
+ }
54
+
55
+ await this.addToAccountLoader();
56
+
57
+ if (this.doesAccountExist()) {
58
+ this.eventEmitter.emit('update');
59
+ }
60
+
61
+ this.isSubscribed = true;
62
+ return true;
63
+ }
64
+
65
+ async addToAccountLoader(): Promise<void> {
66
+ if (this.callbackId) {
67
+ return;
68
+ }
69
+
70
+ this.callbackId = await this.accountLoader.addAccount(
71
+ this.insuranceFundStakeAccountPublicKey,
72
+ (buffer, slot: number) => {
73
+ if (!buffer) {
74
+ return;
75
+ }
76
+
77
+ if (
78
+ this.insuranceFundStakeAccountAndSlot &&
79
+ this.insuranceFundStakeAccountAndSlot.slot > slot
80
+ ) {
81
+ return;
82
+ }
83
+
84
+ const account = this.program.account.user.coder.accounts.decode(
85
+ 'InsuranceFundStake',
86
+ buffer
87
+ );
88
+ this.insuranceFundStakeAccountAndSlot = { data: account, slot };
89
+ this.eventEmitter.emit('insuranceFundStakeAccountUpdate', account);
90
+ this.eventEmitter.emit('update');
91
+ }
92
+ );
93
+
94
+ this.errorCallbackId = this.accountLoader.addErrorCallbacks((error) => {
95
+ this.eventEmitter.emit('error', error);
96
+ });
97
+ }
98
+
99
+ async fetchIfUnloaded(): Promise<void> {
100
+ if (this.insuranceFundStakeAccountAndSlot === undefined) {
101
+ await this.fetch();
102
+ }
103
+ }
104
+
105
+ async fetch(): Promise<void> {
106
+ try {
107
+ const dataAndContext =
108
+ await this.program.account.insuranceFundStake.fetchAndContext(
109
+ this.insuranceFundStakeAccountPublicKey,
110
+ this.accountLoader.commitment
111
+ );
112
+ if (
113
+ dataAndContext.context.slot >
114
+ (this.insuranceFundStakeAccountAndSlot?.slot ?? 0)
115
+ ) {
116
+ this.insuranceFundStakeAccountAndSlot = {
117
+ data: dataAndContext.data as InsuranceFundStake,
118
+ slot: dataAndContext.context.slot,
119
+ };
120
+ }
121
+ } catch (e) {
122
+ console.log(
123
+ `PollingInsuranceFundStakeAccountSubscriber.fetch() InsuranceFundStake does not exist: ${e.message}`
124
+ );
125
+ }
126
+ }
127
+
128
+ doesAccountExist(): boolean {
129
+ return this.insuranceFundStakeAccountAndSlot !== undefined;
130
+ }
131
+
132
+ async unsubscribe(): Promise<void> {
133
+ if (!this.isSubscribed) {
134
+ return;
135
+ }
136
+
137
+ this.accountLoader.removeAccount(
138
+ this.insuranceFundStakeAccountPublicKey,
139
+ this.callbackId
140
+ );
141
+ this.callbackId = undefined;
142
+
143
+ this.accountLoader.removeErrorCallbacks(this.errorCallbackId);
144
+ this.errorCallbackId = undefined;
145
+
146
+ this.isSubscribed = false;
147
+ }
148
+
149
+ assertIsSubscribed(): void {
150
+ if (!this.isSubscribed) {
151
+ throw new NotSubscribedError(
152
+ 'You must call `subscribe` before using this function'
153
+ );
154
+ }
155
+ }
156
+
157
+ public getInsuranceFundStakeAccountAndSlot(): DataAndSlot<InsuranceFundStake> {
158
+ this.assertIsSubscribed();
159
+ return this.insuranceFundStakeAccountAndSlot;
160
+ }
161
+
162
+ didSubscriptionSucceed(): boolean {
163
+ return !!this.insuranceFundStakeAccountAndSlot;
164
+ }
165
+
166
+ public updateData(
167
+ insuranceFundStake: InsuranceFundStake,
168
+ slot: number
169
+ ): void {
170
+ if (
171
+ !this.insuranceFundStakeAccountAndSlot ||
172
+ this.insuranceFundStakeAccountAndSlot.slot < slot
173
+ ) {
174
+ this.insuranceFundStakeAccountAndSlot = {
175
+ data: insuranceFundStake,
176
+ slot,
177
+ };
178
+ this.eventEmitter.emit(
179
+ 'insuranceFundStakeAccountUpdate',
180
+ insuranceFundStake
181
+ );
182
+ this.eventEmitter.emit('update');
183
+ }
184
+ }
185
+ }
@@ -5,6 +5,7 @@ import {
5
5
  StateAccount,
6
6
  UserAccount,
7
7
  UserStatsAccount,
8
+ InsuranceFundStake,
8
9
  } from '../types';
9
10
  import StrictEventEmitter from 'strict-event-emitter-types';
10
11
  import { EventEmitter } from 'events';
@@ -105,6 +106,26 @@ export interface TokenAccountSubscriber {
105
106
  getTokenAccountAndSlot(): DataAndSlot<Account>;
106
107
  }
107
108
 
109
+ export interface InsuranceFundStakeAccountSubscriber {
110
+ eventEmitter: StrictEventEmitter<
111
+ EventEmitter,
112
+ InsuranceFundStakeAccountEvents
113
+ >;
114
+ isSubscribed: boolean;
115
+
116
+ subscribe(): Promise<boolean>;
117
+ fetch(): Promise<void>;
118
+ unsubscribe(): Promise<void>;
119
+
120
+ getInsuranceFundStakeAccountAndSlot(): DataAndSlot<InsuranceFundStake>;
121
+ }
122
+
123
+ export interface InsuranceFundStakeAccountEvents {
124
+ insuranceFundStakeAccountUpdate: (payload: InsuranceFundStake) => void;
125
+ update: void;
126
+ error: (e: Error) => void;
127
+ }
128
+
108
129
  export interface OracleEvents {
109
130
  oracleUpdate: (payload: OraclePriceData) => void;
110
131
  update: void;
@@ -0,0 +1,127 @@
1
+ import {
2
+ DataAndSlot,
3
+ AccountSubscriber,
4
+ NotSubscribedError,
5
+ InsuranceFundStakeAccountEvents,
6
+ InsuranceFundStakeAccountSubscriber,
7
+ } from './types';
8
+ import { Program } from '@coral-xyz/anchor';
9
+ import StrictEventEmitter from 'strict-event-emitter-types';
10
+ import { EventEmitter } from 'events';
11
+ import { Commitment, PublicKey } from '@solana/web3.js';
12
+ import { WebSocketAccountSubscriber } from './webSocketAccountSubscriber';
13
+ import { InsuranceFundStake } from '../types';
14
+
15
+ export class WebSocketInsuranceFundStakeAccountSubscriber
16
+ implements InsuranceFundStakeAccountSubscriber
17
+ {
18
+ isSubscribed: boolean;
19
+ reconnectTimeoutMs?: number;
20
+ commitment?: Commitment;
21
+ program: Program;
22
+ eventEmitter: StrictEventEmitter<
23
+ EventEmitter,
24
+ InsuranceFundStakeAccountEvents
25
+ >;
26
+ insuranceFundStakeAccountPublicKey: PublicKey;
27
+
28
+ insuranceFundStakeDataAccountSubscriber: AccountSubscriber<InsuranceFundStake>;
29
+
30
+ public constructor(
31
+ program: Program,
32
+ insuranceFundStakeAccountPublicKey: PublicKey,
33
+ reconnectTimeoutMs?: number,
34
+ commitment?: Commitment
35
+ ) {
36
+ this.isSubscribed = false;
37
+ this.program = program;
38
+ this.insuranceFundStakeAccountPublicKey =
39
+ insuranceFundStakeAccountPublicKey;
40
+ this.eventEmitter = new EventEmitter();
41
+ this.reconnectTimeoutMs = reconnectTimeoutMs;
42
+ this.commitment = commitment;
43
+ }
44
+
45
+ async subscribe(
46
+ insuranceFundStakeAccount?: InsuranceFundStake
47
+ ): Promise<boolean> {
48
+ if (this.isSubscribed) {
49
+ return true;
50
+ }
51
+
52
+ this.insuranceFundStakeDataAccountSubscriber =
53
+ new WebSocketAccountSubscriber(
54
+ 'insuranceFundStake',
55
+ this.program,
56
+ this.insuranceFundStakeAccountPublicKey,
57
+ undefined,
58
+ this.reconnectTimeoutMs,
59
+ this.commitment
60
+ );
61
+
62
+ if (insuranceFundStakeAccount) {
63
+ this.insuranceFundStakeDataAccountSubscriber.setData(
64
+ insuranceFundStakeAccount
65
+ );
66
+ }
67
+
68
+ await this.insuranceFundStakeDataAccountSubscriber.subscribe(
69
+ (data: InsuranceFundStake) => {
70
+ this.eventEmitter.emit('insuranceFundStakeAccountUpdate', data);
71
+ this.eventEmitter.emit('update');
72
+ }
73
+ );
74
+
75
+ this.eventEmitter.emit('update');
76
+ this.isSubscribed = true;
77
+ return true;
78
+ }
79
+
80
+ async fetch(): Promise<void> {
81
+ await Promise.all([this.insuranceFundStakeDataAccountSubscriber.fetch()]);
82
+ }
83
+
84
+ async unsubscribe(): Promise<void> {
85
+ if (!this.isSubscribed) {
86
+ return;
87
+ }
88
+
89
+ await Promise.all([
90
+ this.insuranceFundStakeDataAccountSubscriber.unsubscribe(),
91
+ ]);
92
+
93
+ this.isSubscribed = false;
94
+ }
95
+
96
+ assertIsSubscribed(): void {
97
+ if (!this.isSubscribed) {
98
+ throw new NotSubscribedError(
99
+ 'You must call `subscribe` before using this function'
100
+ );
101
+ }
102
+ }
103
+
104
+ public getInsuranceFundStakeAccountAndSlot(): DataAndSlot<InsuranceFundStake> {
105
+ this.assertIsSubscribed();
106
+ return this.insuranceFundStakeDataAccountSubscriber.dataAndSlot;
107
+ }
108
+
109
+ public updateData(
110
+ insuranceFundStake: InsuranceFundStake,
111
+ slot: number
112
+ ): void {
113
+ const currentDataSlot =
114
+ this.insuranceFundStakeDataAccountSubscriber.dataAndSlot?.slot || 0;
115
+ if (currentDataSlot <= slot) {
116
+ this.insuranceFundStakeDataAccountSubscriber.setData(
117
+ insuranceFundStake,
118
+ slot
119
+ );
120
+ this.eventEmitter.emit(
121
+ 'insuranceFundStakeAccountUpdate',
122
+ insuranceFundStake
123
+ );
124
+ this.eventEmitter.emit('update');
125
+ }
126
+ }
127
+ }
package/src/dlob/DLOB.ts CHANGED
@@ -504,6 +504,9 @@ export class DLOB {
504
504
  ? stateAccount.minPerpAuctionDuration
505
505
  : 0;
506
506
 
507
+ const { makerRebateNumerator, makerRebateDenominator } =
508
+ this.getMakerRebate(marketType, stateAccount, marketAccount);
509
+
507
510
  const restingLimitOrderNodesToFill: Array<NodeToFill> =
508
511
  this.findRestingLimitOrderNodesToFill(
509
512
  marketIndex,
@@ -512,6 +515,8 @@ export class DLOB {
512
515
  oraclePriceData,
513
516
  isAmmPaused,
514
517
  minAuctionDuration,
518
+ makerRebateNumerator,
519
+ makerRebateDenominator,
515
520
  fallbackAsk,
516
521
  fallbackBid
517
522
  );
@@ -549,6 +554,34 @@ export class DLOB {
549
554
  ).concat(expiredNodesToFill);
550
555
  }
551
556
 
557
+ getMakerRebate(
558
+ marketType: MarketType,
559
+ stateAccount: StateAccount,
560
+ marketAccount: PerpMarketAccount | SpotMarketAccount
561
+ ): { makerRebateNumerator: number; makerRebateDenominator: number } {
562
+ let makerRebateNumerator: number;
563
+ let makerRebateDenominator: number;
564
+ if (isVariant(marketType, 'perp')) {
565
+ makerRebateNumerator =
566
+ stateAccount.perpFeeStructure.feeTiers[0].makerRebateNumerator;
567
+ makerRebateDenominator =
568
+ stateAccount.perpFeeStructure.feeTiers[0].makerRebateDenominator;
569
+ } else {
570
+ makerRebateNumerator =
571
+ stateAccount.spotFeeStructure.feeTiers[0].makerRebateNumerator;
572
+ makerRebateDenominator =
573
+ stateAccount.spotFeeStructure.feeTiers[0].makerRebateDenominator;
574
+ }
575
+
576
+ // @ts-ignore
577
+ const feeAdjustment = marketAccount.feeAdjustment || 0;
578
+ if (feeAdjustment !== 0) {
579
+ makerRebateNumerator += (makerRebateNumerator * feeAdjustment) / 100;
580
+ }
581
+
582
+ return { makerRebateNumerator, makerRebateDenominator };
583
+ }
584
+
552
585
  mergeNodesToFill(
553
586
  restingLimitOrderNodesToFill: NodeToFill[],
554
587
  takingOrderNodesToFill: NodeToFill[]
@@ -590,6 +623,8 @@ export class DLOB {
590
623
  oraclePriceData: OraclePriceData,
591
624
  isAmmPaused: boolean,
592
625
  minAuctionDuration: number,
626
+ makerRebateNumerator: number,
627
+ makerRebateDenominator: number,
593
628
  fallbackAsk: BN | undefined,
594
629
  fallbackBid: BN | undefined
595
630
  ): NodeToFill[] {
@@ -613,14 +648,18 @@ export class DLOB {
613
648
  marketType,
614
649
  oraclePriceData
615
650
  );
651
+
652
+ const fallbackBidWithBuffer = fallbackBid.sub(
653
+ fallbackBid.muln(makerRebateNumerator).divn(makerRebateDenominator)
654
+ );
655
+
616
656
  const asksCrossingFallback = this.findNodesCrossingFallbackLiquidity(
617
657
  marketType,
618
658
  slot,
619
659
  oraclePriceData,
620
660
  askGenerator,
621
- fallbackBid,
622
- (askPrice, fallbackPrice) => {
623
- return askPrice.lte(fallbackPrice);
661
+ (askPrice) => {
662
+ return askPrice.lte(fallbackBidWithBuffer);
624
663
  },
625
664
  minAuctionDuration
626
665
  );
@@ -637,14 +676,18 @@ export class DLOB {
637
676
  marketType,
638
677
  oraclePriceData
639
678
  );
679
+
680
+ const fallbackAskWithBuffer = fallbackAsk.add(
681
+ fallbackAsk.muln(makerRebateNumerator).divn(makerRebateDenominator)
682
+ );
683
+
640
684
  const bidsCrossingFallback = this.findNodesCrossingFallbackLiquidity(
641
685
  marketType,
642
686
  slot,
643
687
  oraclePriceData,
644
688
  bidGenerator,
645
- fallbackAsk,
646
- (bidPrice, fallbackPrice) => {
647
- return bidPrice.gte(fallbackPrice);
689
+ (bidPrice) => {
690
+ return bidPrice.gte(fallbackAskWithBuffer);
648
691
  },
649
692
  minAuctionDuration
650
693
  );
@@ -713,9 +756,8 @@ export class DLOB {
713
756
  slot,
714
757
  oraclePriceData,
715
758
  takingOrderGenerator,
716
- fallbackBid,
717
- (takerPrice, fallbackPrice) => {
718
- return takerPrice === undefined || takerPrice.lte(fallbackPrice);
759
+ (takerPrice) => {
760
+ return takerPrice === undefined || takerPrice.lte(fallbackBid);
719
761
  },
720
762
  minAuctionDuration
721
763
  );
@@ -771,9 +813,8 @@ export class DLOB {
771
813
  slot,
772
814
  oraclePriceData,
773
815
  takingOrderGenerator,
774
- fallbackAsk,
775
- (takerPrice, fallbackPrice) => {
776
- return takerPrice === undefined || takerPrice.gte(fallbackPrice);
816
+ (takerPrice) => {
817
+ return takerPrice === undefined || takerPrice.gte(fallbackAsk);
777
818
  },
778
819
  minAuctionDuration
779
820
  );
@@ -875,8 +916,7 @@ export class DLOB {
875
916
  slot: number,
876
917
  oraclePriceData: OraclePriceData,
877
918
  nodeGenerator: Generator<DLOBNode>,
878
- fallbackPrice: BN,
879
- doesCross: (nodePrice: BN | undefined, fallbackPrice: BN) => boolean,
919
+ doesCross: (nodePrice: BN | undefined) => boolean,
880
920
  minAuctionDuration: number
881
921
  ): NodeToFill[] {
882
922
  const nodesToFill = new Array<NodeToFill>();
@@ -893,7 +933,7 @@ export class DLOB {
893
933
  const nodePrice = getLimitPrice(node.order, oraclePriceData, slot);
894
934
 
895
935
  // order crosses if there is no limit price or it crosses fallback price
896
- const crosses = doesCross(nodePrice, fallbackPrice);
936
+ const crosses = doesCross(nodePrice);
897
937
 
898
938
  // fallback is available if auction is complete or it's a spot order
899
939
  const fallbackAvailable =