@drift-labs/sdk 2.48.0-beta.9 → 2.49.0-beta.0

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 (45) hide show
  1. package/VERSION +1 -1
  2. package/lib/accounts/bulkAccountLoader.js +1 -1
  3. package/lib/accounts/pollingUserAccountSubscriber.js +11 -6
  4. package/lib/accounts/pollingUserStatsAccountSubscriber.js +11 -6
  5. package/lib/accounts/webSocketAccountSubscriber.js +1 -1
  6. package/lib/accounts/webSocketDriftClientAccountSubscriber.d.ts +3 -2
  7. package/lib/accounts/webSocketDriftClientAccountSubscriber.js +6 -5
  8. package/lib/accounts/webSocketProgramAccountSubscriber.js +1 -1
  9. package/lib/accounts/webSocketUserAccountSubscriber.js +1 -1
  10. package/lib/constants/spotMarkets.js +10 -0
  11. package/lib/driftClient.js +3 -3
  12. package/lib/events/eventSubscriber.js +23 -2
  13. package/lib/events/pollingLogProvider.d.ts +1 -1
  14. package/lib/events/pollingLogProvider.js +1 -1
  15. package/lib/events/types.d.ts +9 -2
  16. package/lib/events/webSocketLogProvider.d.ts +15 -3
  17. package/lib/events/webSocketLogProvider.js +70 -7
  18. package/lib/idl/drift.json +1 -1
  19. package/lib/orderSubscriber/OrderSubscriber.d.ts +5 -1
  20. package/lib/orderSubscriber/OrderSubscriber.js +30 -7
  21. package/lib/orderSubscriber/WebsocketSubscription.d.ts +4 -1
  22. package/lib/orderSubscriber/WebsocketSubscription.js +4 -3
  23. package/lib/orderSubscriber/types.d.ts +3 -1
  24. package/lib/slot/SlotSubscriber.js +4 -2
  25. package/lib/user.js +6 -2
  26. package/package.json +1 -1
  27. package/src/accounts/bulkAccountLoader.ts +1 -1
  28. package/src/accounts/pollingUserAccountSubscriber.ts +15 -9
  29. package/src/accounts/pollingUserStatsAccountSubscriber.ts +16 -9
  30. package/src/accounts/webSocketAccountSubscriber.ts +1 -1
  31. package/src/accounts/webSocketDriftClientAccountSubscriber.ts +13 -6
  32. package/src/accounts/webSocketProgramAccountSubscriber.ts +1 -1
  33. package/src/accounts/webSocketUserAccountSubscriber.ts +1 -1
  34. package/src/constants/spotMarkets.ts +10 -0
  35. package/src/driftClient.ts +2 -1
  36. package/src/events/eventSubscriber.ts +46 -2
  37. package/src/events/pollingLogProvider.ts +2 -2
  38. package/src/events/types.ts +11 -2
  39. package/src/events/webSocketLogProvider.ts +82 -8
  40. package/src/idl/drift.json +1 -1
  41. package/src/orderSubscriber/OrderSubscriber.ts +52 -15
  42. package/src/orderSubscriber/WebsocketSubscription.ts +7 -2
  43. package/src/orderSubscriber/types.ts +3 -1
  44. package/src/slot/SlotSubscriber.ts +4 -2
  45. package/src/user.ts +5 -2
package/lib/user.js CHANGED
@@ -22,7 +22,7 @@ class User {
22
22
  this._isSubscribed = val;
23
23
  }
24
24
  constructor(config) {
25
- var _a, _b;
25
+ var _a, _b, _c, _d;
26
26
  this._isSubscribed = false;
27
27
  this.driftClient = config.driftClient;
28
28
  this.userAccountPublicKey = config.userAccountPublicKey;
@@ -33,7 +33,7 @@ class User {
33
33
  this.accountSubscriber = config.accountSubscription.userAccountSubscriber;
34
34
  }
35
35
  else {
36
- this.accountSubscriber = new webSocketUserAccountSubscriber_1.WebSocketUserAccountSubscriber(config.driftClient.program, config.userAccountPublicKey, config.accountSubscription.resubTimeoutMs, config.accountSubscription.commitment);
36
+ this.accountSubscriber = new webSocketUserAccountSubscriber_1.WebSocketUserAccountSubscriber(config.driftClient.program, config.userAccountPublicKey, (_c = config.accountSubscription) === null || _c === void 0 ? void 0 : _c.resubTimeoutMs, (_d = config.accountSubscription) === null || _d === void 0 ? void 0 : _d.commitment);
37
37
  }
38
38
  this.eventEmitter = this.accountSubscriber.eventEmitter;
39
39
  }
@@ -1670,6 +1670,7 @@ class User {
1670
1670
  // eslint-disable-next-line prefer-const
1671
1671
  let { borrowLimit, withdrawLimit } = (0, spotBalance_1.calculateWithdrawLimit)(spotMarket, nowTs);
1672
1672
  const freeCollateral = this.getFreeCollateral();
1673
+ const initialMarginRequirement = this.getInitialMarginRequirement();
1673
1674
  const oracleData = this.getOracleDataForSpotMarket(marketIndex);
1674
1675
  const precisionIncrease = numericConstants_1.TEN.pow(new _1.BN(spotMarket.decimals - 6));
1675
1676
  const { canBypass, depositAmount: userDepositAmount } = this.canBypassWithdrawLimits(marketIndex);
@@ -1681,6 +1682,9 @@ class User {
1681
1682
  if (assetWeight.eq(numericConstants_1.ZERO)) {
1682
1683
  amountWithdrawable = userDepositAmount;
1683
1684
  }
1685
+ else if (initialMarginRequirement.eq(numericConstants_1.ZERO)) {
1686
+ amountWithdrawable = userDepositAmount;
1687
+ }
1684
1688
  else {
1685
1689
  amountWithdrawable = (0, _1.divCeil)((0, _1.divCeil)(freeCollateral.mul(numericConstants_1.MARGIN_PRECISION), assetWeight).mul(numericConstants_1.PRICE_PRECISION), oracleData.price).mul(precisionIncrease);
1686
1690
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.48.0-beta.9",
3
+ "version": "2.49.0-beta.0",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -193,7 +193,7 @@ export class BulkAccountLoader {
193
193
  const key = accountToLoad.publicKey.toBase58();
194
194
  const oldRPCResponse = this.bufferAndSlotMap.get(key);
195
195
 
196
- if (oldRPCResponse && newSlot <= oldRPCResponse.slot) {
196
+ if (oldRPCResponse && newSlot < oldRPCResponse.slot) {
197
197
  return;
198
198
  }
199
199
 
@@ -93,15 +93,21 @@ export class PollingUserAccountSubscriber implements UserAccountSubscriber {
93
93
  }
94
94
 
95
95
  async fetch(): Promise<void> {
96
- const dataAndContext = await this.program.account.user.fetchAndContext(
97
- this.userAccountPublicKey,
98
- this.accountLoader.commitment
99
- );
100
- if (dataAndContext.context.slot > (this.user?.slot ?? 0)) {
101
- this.user = {
102
- data: dataAndContext.data as UserAccount,
103
- slot: dataAndContext.context.slot,
104
- };
96
+ try {
97
+ const dataAndContext = await this.program.account.user.fetchAndContext(
98
+ this.userAccountPublicKey,
99
+ this.accountLoader.commitment
100
+ );
101
+ if (dataAndContext.context.slot > (this.user?.slot ?? 0)) {
102
+ this.user = {
103
+ data: dataAndContext.data as UserAccount,
104
+ slot: dataAndContext.context.slot,
105
+ };
106
+ }
107
+ } catch (e) {
108
+ console.log(
109
+ `PollingUserAccountSubscriber.fetch() UserAccount does not exist: ${e.message}`
110
+ );
105
111
  }
106
112
  }
107
113
 
@@ -97,15 +97,22 @@ export class PollingUserStatsAccountSubscriber
97
97
  }
98
98
 
99
99
  async fetch(): Promise<void> {
100
- const dataAndContext = await this.program.account.userStats.fetchAndContext(
101
- this.userStatsAccountPublicKey,
102
- this.accountLoader.commitment
103
- );
104
- if (dataAndContext.context.slot > (this.userStats?.slot ?? 0)) {
105
- this.userStats = {
106
- data: dataAndContext.data as UserStatsAccount,
107
- slot: dataAndContext.context.slot,
108
- };
100
+ try {
101
+ const dataAndContext =
102
+ await this.program.account.userStats.fetchAndContext(
103
+ this.userStatsAccountPublicKey,
104
+ this.accountLoader.commitment
105
+ );
106
+ if (dataAndContext.context.slot > (this.userStats?.slot ?? 0)) {
107
+ this.userStats = {
108
+ data: dataAndContext.data as UserStatsAccount,
109
+ slot: dataAndContext.context.slot,
110
+ };
111
+ }
112
+ } catch (e) {
113
+ console.log(
114
+ `PollingUserStatsAccountSubscriber.fetch() UserStatsAccount does not exist: ${e.message}`
115
+ );
109
116
  }
110
117
  }
111
118
 
@@ -134,7 +134,7 @@ export class WebSocketAccountSubscriber<T> implements AccountSubscriber<T> {
134
134
  return;
135
135
  }
136
136
 
137
- if (newSlot <= this.bufferAndSlot.slot) {
137
+ if (newSlot < this.bufferAndSlot.slot) {
138
138
  return;
139
139
  }
140
140
 
@@ -14,7 +14,7 @@ import {
14
14
  getPerpMarketPublicKey,
15
15
  } from '../addresses/pda';
16
16
  import { WebSocketAccountSubscriber } from './webSocketAccountSubscriber';
17
- import { PublicKey } from '@solana/web3.js';
17
+ import { Commitment, PublicKey } from '@solana/web3.js';
18
18
  import { OracleInfo, OraclePriceData } from '../oracles/types';
19
19
  import { OracleClientCache } from '../oracles/oracleClientCache';
20
20
  import * as Buffer from 'buffer';
@@ -26,6 +26,7 @@ export class WebSocketDriftClientAccountSubscriber
26
26
  {
27
27
  isSubscribed: boolean;
28
28
  program: Program;
29
+ commitment?: Commitment;
29
30
  perpMarketIndexes: number[];
30
31
  spotMarketIndexes: number[];
31
32
  oracleInfos: OracleInfo[];
@@ -56,7 +57,8 @@ export class WebSocketDriftClientAccountSubscriber
56
57
  spotMarketIndexes: number[],
57
58
  oracleInfos: OracleInfo[],
58
59
  shouldFindAllMarketsAndOracles: boolean,
59
- resubTimeoutMs?: number
60
+ resubTimeoutMs?: number,
61
+ commitment?: Commitment
60
62
  ) {
61
63
  this.isSubscribed = false;
62
64
  this.program = program;
@@ -66,6 +68,7 @@ export class WebSocketDriftClientAccountSubscriber
66
68
  this.oracleInfos = oracleInfos;
67
69
  this.shouldFindAllMarketsAndOracles = shouldFindAllMarketsAndOracles;
68
70
  this.resubTimeoutMs = resubTimeoutMs;
71
+ this.commitment = commitment;
69
72
  }
70
73
 
71
74
  public async subscribe(): Promise<boolean> {
@@ -101,7 +104,8 @@ export class WebSocketDriftClientAccountSubscriber
101
104
  this.program,
102
105
  statePublicKey,
103
106
  undefined,
104
- this.resubTimeoutMs
107
+ this.resubTimeoutMs,
108
+ this.commitment
105
109
  );
106
110
  await this.stateAccountSubscriber.subscribe((data: StateAccount) => {
107
111
  this.eventEmitter.emit('stateAccountUpdate', data);
@@ -143,7 +147,8 @@ export class WebSocketDriftClientAccountSubscriber
143
147
  this.program,
144
148
  perpMarketPublicKey,
145
149
  undefined,
146
- this.resubTimeoutMs
150
+ this.resubTimeoutMs,
151
+ this.commitment
147
152
  );
148
153
  await accountSubscriber.subscribe((data: PerpMarketAccount) => {
149
154
  this.eventEmitter.emit('perpMarketAccountUpdate', data);
@@ -170,7 +175,8 @@ export class WebSocketDriftClientAccountSubscriber
170
175
  this.program,
171
176
  marketPublicKey,
172
177
  undefined,
173
- this.resubTimeoutMs
178
+ this.resubTimeoutMs,
179
+ this.commitment
174
180
  );
175
181
  await accountSubscriber.subscribe((data: SpotMarketAccount) => {
176
182
  this.eventEmitter.emit('spotMarketAccountUpdate', data);
@@ -202,7 +208,8 @@ export class WebSocketDriftClientAccountSubscriber
202
208
  (buffer: Buffer) => {
203
209
  return client.getOraclePriceDataFromBuffer(buffer);
204
210
  },
205
- this.resubTimeoutMs
211
+ this.resubTimeoutMs,
212
+ this.commitment
206
213
  );
207
214
 
208
215
  await accountSubscriber.subscribe((data: OraclePriceData) => {
@@ -125,7 +125,7 @@ export class WebSocketProgramAccountSubscriber<T>
125
125
  return;
126
126
  }
127
127
 
128
- if (newSlot <= this.bufferAndSlot.slot) {
128
+ if (newSlot < this.bufferAndSlot.slot) {
129
129
  return;
130
130
  }
131
131
 
@@ -94,7 +94,7 @@ export class WebSocketUserAccountSubscriber implements UserAccountSubscriber {
94
94
  public updateData(userAccount: UserAccount, slot: number) {
95
95
  const currentDataSlot =
96
96
  this.userDataAccountSubscriber.dataAndSlot?.slot || 0;
97
- if (currentDataSlot < slot) {
97
+ if (currentDataSlot <= slot) {
98
98
  this.userDataAccountSubscriber.setData(userAccount, slot);
99
99
  this.eventEmitter.emit('userAccountUpdate', userAccount);
100
100
  this.eventEmitter.emit('update');
@@ -150,6 +150,16 @@ export const MainnetSpotMarkets: SpotMarketConfig[] = [
150
150
  '2sTMN9A1D1qeZLF95XQgJCUPiKe5DiV52jLfZGqMP46m'
151
151
  ),
152
152
  },
153
+ {
154
+ symbol: 'bSOL',
155
+ marketIndex: 8,
156
+ oracle: new PublicKey('AFrYBhb5wKQtxRS9UA9YRS4V3dwFm7SqmS6DHKq6YVgo'),
157
+ oracleSource: OracleSource.PYTH,
158
+ mint: new PublicKey('bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1'),
159
+ precision: new BN(10).pow(NINE),
160
+ precisionExp: NINE,
161
+ serumMarket: new PublicKey('ARjaHVxGCQfTvvKjLd7U7srvk6orthZSE6uqWchCczZc'),
162
+ },
153
163
  ];
154
164
 
155
165
  export const SpotMarkets: { [key in DriftEnv]: SpotMarketConfig[] } = {
@@ -283,7 +283,8 @@ export class DriftClient {
283
283
  config.spotMarketIndexes ?? [],
284
284
  config.oracleInfos ?? [],
285
285
  noMarketsAndOraclesSpecified,
286
- config.accountSubscription?.resubTimeoutMs
286
+ config.accountSubscription?.resubTimeoutMs,
287
+ config.accountSubscription?.commitment
287
288
  );
288
289
  }
289
290
  this.eventEmitter = this.accountSubscriber.eventEmitter;
@@ -8,6 +8,7 @@ import {
8
8
  EventMap,
9
9
  LogProvider,
10
10
  EventSubscriberEvents,
11
+ WebSocketLogProviderConfig,
11
12
  } from './types';
12
13
  import { TxEventCache } from './txEventCache';
13
14
  import { EventList } from './eventList';
@@ -56,7 +57,8 @@ export class EventSubscriber {
56
57
  this.logProvider = new WebSocketLogProvider(
57
58
  this.connection,
58
59
  this.address,
59
- this.options.commitment
60
+ this.options.commitment,
61
+ this.options.logProviderConfig.resubTimeoutMs
60
62
  );
61
63
  } else {
62
64
  this.logProvider = new PollingLogProvider(
@@ -75,6 +77,48 @@ export class EventSubscriber {
75
77
  return true;
76
78
  }
77
79
 
80
+ if (this.options.logProviderConfig.type === 'websocket') {
81
+ if (this.options.logProviderConfig.resubTimeoutMs) {
82
+ if (
83
+ this.options.logProviderConfig.maxReconnectAttempts &&
84
+ this.options.logProviderConfig.maxReconnectAttempts > 0
85
+ ) {
86
+ const logProviderConfig = this.options
87
+ .logProviderConfig as WebSocketLogProviderConfig;
88
+ this.logProvider.eventEmitter.on(
89
+ 'reconnect',
90
+ (reconnectAttempts) => {
91
+ if (
92
+ reconnectAttempts > logProviderConfig.maxReconnectAttempts
93
+ ) {
94
+ console.log('Failing over to polling');
95
+ this.logProvider.eventEmitter.removeAllListeners('reconnect');
96
+ this.unsubscribe().then(() => {
97
+ this.logProvider = new PollingLogProvider(
98
+ this.connection,
99
+ this.address,
100
+ this.options.commitment,
101
+ logProviderConfig.fallbackFrequency,
102
+ logProviderConfig.fallbackBatchSize
103
+ );
104
+ this.logProvider.subscribe(
105
+ (txSig, slot, logs, mostRecentBlockTime) => {
106
+ this.handleTxLogs(
107
+ txSig,
108
+ slot,
109
+ logs,
110
+ mostRecentBlockTime
111
+ );
112
+ },
113
+ true
114
+ );
115
+ });
116
+ }
117
+ }
118
+ );
119
+ }
120
+ }
121
+ }
78
122
  this.logProvider.subscribe((txSig, slot, logs, mostRecentBlockTime) => {
79
123
  this.handleTxLogs(txSig, slot, logs, mostRecentBlockTime);
80
124
  }, true);
@@ -158,7 +202,7 @@ export class EventSubscriber {
158
202
  }
159
203
 
160
204
  public async unsubscribe(): Promise<boolean> {
161
- return await this.logProvider.unsubscribe();
205
+ return await this.logProvider.unsubscribe(true);
162
206
  }
163
207
 
164
208
  private parseEventsFromLogs(
@@ -25,10 +25,10 @@ export class PollingLogProvider implements LogProvider {
25
25
  this.finality = commitment === 'finalized' ? 'finalized' : 'confirmed';
26
26
  }
27
27
 
28
- public subscribe(
28
+ public async subscribe(
29
29
  callback: logProviderCallback,
30
30
  skipHistory?: boolean
31
- ): boolean {
31
+ ): Promise<boolean> {
32
32
  if (this.intervalId) {
33
33
  return true;
34
34
  }
@@ -15,6 +15,7 @@ import {
15
15
  CurveRecord,
16
16
  SwapRecord,
17
17
  } from '../index';
18
+ import { EventEmitter } from 'events';
18
19
 
19
20
  export type EventSubscriptionOptions = {
20
21
  address?: PublicKey;
@@ -126,12 +127,20 @@ export type logProviderCallback = (
126
127
 
127
128
  export interface LogProvider {
128
129
  isSubscribed(): boolean;
129
- subscribe(callback: logProviderCallback, skipHistory?: boolean): boolean;
130
- unsubscribe(): Promise<boolean>;
130
+ subscribe(
131
+ callback: logProviderCallback,
132
+ skipHistory?: boolean
133
+ ): Promise<boolean>;
134
+ unsubscribe(external?: boolean): Promise<boolean>;
135
+ eventEmitter?: EventEmitter;
131
136
  }
132
137
 
133
138
  export type WebSocketLogProviderConfig = {
134
139
  type: 'websocket';
140
+ resubTimeoutMs?: number;
141
+ maxReconnectAttempts?: number;
142
+ fallbackFrequency?: number;
143
+ fallbackBatchSize?: number;
135
144
  };
136
145
 
137
146
  export type PollingLogProviderConfig = {
@@ -1,38 +1,112 @@
1
1
  import { LogProvider, logProviderCallback } from './types';
2
2
  import { Commitment, Connection, PublicKey } from '@solana/web3.js';
3
+ import { EventEmitter } from 'events';
3
4
 
4
5
  export class WebSocketLogProvider implements LogProvider {
5
6
  private subscriptionId: number;
7
+ private isUnsubscribing = false;
8
+ private externalUnsubscribe = false;
9
+ private receivingData = false;
10
+ private timeoutId?: NodeJS.Timeout;
11
+ private reconnectAttempts = 0;
12
+ eventEmitter?: EventEmitter;
13
+ private callback?: logProviderCallback;
6
14
  public constructor(
7
15
  private connection: Connection,
8
16
  private address: PublicKey,
9
- private commitment: Commitment
10
- ) {}
17
+ private commitment: Commitment,
18
+ private resubTimeoutMs?: number
19
+ ) {
20
+ if (this.resubTimeoutMs) {
21
+ this.eventEmitter = new EventEmitter();
22
+ }
23
+ }
11
24
 
12
- public subscribe(callback: logProviderCallback): boolean {
25
+ public async subscribe(callback: logProviderCallback): Promise<boolean> {
13
26
  if (this.subscriptionId) {
14
27
  return true;
15
28
  }
16
29
 
30
+ this.callback = callback;
31
+ try {
32
+ this.setSubscription(callback);
33
+ } catch (error) {
34
+ // Sometimes ws connection isn't ready, give it a few secs
35
+ setTimeout(() => this.setSubscription(callback), 2000);
36
+ }
37
+
38
+ if (this.resubTimeoutMs) {
39
+ this.setTimeout();
40
+ }
41
+
42
+ return true;
43
+ }
44
+
45
+ public setSubscription(callback: logProviderCallback): void {
17
46
  this.subscriptionId = this.connection.onLogs(
18
47
  this.address,
19
48
  (logs, ctx) => {
49
+ if (this.resubTimeoutMs && !this.isUnsubscribing) {
50
+ this.receivingData = true;
51
+ clearTimeout(this.timeoutId);
52
+ this.setTimeout();
53
+ if (this.reconnectAttempts > 0) {
54
+ console.log('Resetting reconnect attempts to 0');
55
+ }
56
+ this.reconnectAttempts = 0;
57
+ }
20
58
  callback(logs.signature, ctx.slot, logs.logs, undefined);
21
59
  },
22
60
  this.commitment
23
61
  );
24
- return true;
25
62
  }
26
63
 
27
64
  public isSubscribed(): boolean {
28
65
  return this.subscriptionId !== undefined;
29
66
  }
30
67
 
31
- public async unsubscribe(): Promise<boolean> {
68
+ public async unsubscribe(external = false): Promise<boolean> {
69
+ this.isUnsubscribing = true;
70
+ this.externalUnsubscribe = external;
71
+ clearTimeout(this.timeoutId);
72
+ this.timeoutId = undefined;
73
+
32
74
  if (this.subscriptionId !== undefined) {
33
- await this.connection.removeOnLogsListener(this.subscriptionId);
34
- this.subscriptionId = undefined;
75
+ try {
76
+ await this.connection.removeOnLogsListener(this.subscriptionId);
77
+ this.subscriptionId = undefined;
78
+ this.isUnsubscribing = false;
79
+ return true;
80
+ } catch (err) {
81
+ console.log('Error unsubscribing from logs: ', err);
82
+ this.isUnsubscribing = false;
83
+ return false;
84
+ }
85
+ } else {
86
+ this.isUnsubscribing = false;
87
+ return true;
35
88
  }
36
- return true;
89
+ }
90
+
91
+ private setTimeout(): void {
92
+ this.timeoutId = setTimeout(async () => {
93
+ if (this.isUnsubscribing || this.externalUnsubscribe) {
94
+ // If we are in the process of unsubscribing, do not attempt to resubscribe
95
+ return;
96
+ }
97
+
98
+ if (this.receivingData) {
99
+ console.log(
100
+ `No log data in ${this.resubTimeoutMs}ms, resubscribing on attempt ${
101
+ this.reconnectAttempts + 1
102
+ }`
103
+ );
104
+ await this.unsubscribe();
105
+ this.receivingData = false;
106
+ this.reconnectAttempts++;
107
+ this.eventEmitter.emit('reconnect', this.reconnectAttempts);
108
+ this.subscribe(this.callback);
109
+ }
110
+ }, this.resubTimeoutMs);
37
111
  }
38
112
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.47.0",
2
+ "version": "2.48.0",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -1,7 +1,7 @@
1
1
  import { DriftClient } from '../driftClient';
2
2
  import { UserAccount } from '../types';
3
3
  import { getUserFilter, getUserWithOrderFilter } from '../memcmp';
4
- import { PublicKey, RpcResponseAndContext } from '@solana/web3.js';
4
+ import { Commitment, PublicKey, RpcResponseAndContext } from '@solana/web3.js';
5
5
  import { Buffer } from 'buffer';
6
6
  import { DLOB } from '../dlob/DLOB';
7
7
  import { OrderSubscriberConfig, OrderSubscriberEvents } from './types';
@@ -9,18 +9,23 @@ import { PollingSubscription } from './PollingSubscription';
9
9
  import { WebsocketSubscription } from './WebsocketSubscription';
10
10
  import StrictEventEmitter from 'strict-event-emitter-types';
11
11
  import { EventEmitter } from 'events';
12
+ import { BN } from '../index';
12
13
 
13
14
  export class OrderSubscriber {
14
15
  driftClient: DriftClient;
15
16
  usersAccounts = new Map<string, { slot: number; userAccount: UserAccount }>();
16
17
  subscription: PollingSubscription | WebsocketSubscription;
18
+ commitment: Commitment;
17
19
  eventEmitter: StrictEventEmitter<EventEmitter, OrderSubscriberEvents>;
18
20
 
19
21
  fetchPromise?: Promise<void>;
20
22
  fetchPromiseResolver: () => void;
21
23
 
24
+ mostRecentSlot: number;
25
+
22
26
  constructor(config: OrderSubscriberConfig) {
23
27
  this.driftClient = config.driftClient;
28
+ this.commitment = config.subscriptionConfig.commitment || 'processed';
24
29
  if (config.subscriptionConfig.type === 'polling') {
25
30
  this.subscription = new PollingSubscription({
26
31
  orderSubscriber: this,
@@ -29,6 +34,7 @@ export class OrderSubscriber {
29
34
  } else {
30
35
  this.subscription = new WebsocketSubscription({
31
36
  orderSubscriber: this,
37
+ commitment: this.commitment,
32
38
  skipInitialLoad: config.subscriptionConfig.skipInitialLoad,
33
39
  resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
34
40
  });
@@ -53,7 +59,7 @@ export class OrderSubscriber {
53
59
  const rpcRequestArgs = [
54
60
  this.driftClient.program.programId.toBase58(),
55
61
  {
56
- commitment: this.driftClient.opts.commitment,
62
+ commitment: this.commitment,
57
63
  filters: [getUserFilter(), getUserWithOrderFilter()],
58
64
  encoding: 'base64',
59
65
  withContext: true,
@@ -81,18 +87,13 @@ export class OrderSubscriber {
81
87
  const programAccountSet = new Set<string>();
82
88
  for (const programAccount of rpcResponseAndContext.value) {
83
89
  const key = programAccount.pubkey.toString();
84
- // @ts-ignore
85
- const buffer = Buffer.from(
86
- programAccount.account.data[0],
87
- programAccount.account.data[1]
88
- );
89
90
  programAccountSet.add(key);
90
- const userAccount =
91
- this.driftClient.program.account.user.coder.accounts.decode(
92
- 'User',
93
- buffer
94
- ) as UserAccount;
95
- this.tryUpdateUserAccount(key, userAccount, slot);
91
+ this.tryUpdateUserAccount(
92
+ key,
93
+ 'raw',
94
+ programAccount.account.data,
95
+ slot
96
+ );
96
97
  }
97
98
 
98
99
  for (const key of this.usersAccounts.keys()) {
@@ -110,11 +111,43 @@ export class OrderSubscriber {
110
111
 
111
112
  tryUpdateUserAccount(
112
113
  key: string,
113
- userAccount: UserAccount,
114
+ dataType: 'raw' | 'decoded',
115
+ data: string[] | UserAccount,
114
116
  slot: number
115
117
  ): void {
118
+ if (!this.mostRecentSlot || slot > this.mostRecentSlot) {
119
+ this.mostRecentSlot = slot;
120
+ }
121
+
116
122
  const slotAndUserAccount = this.usersAccounts.get(key);
117
- if (!slotAndUserAccount || slotAndUserAccount.slot < slot) {
123
+ if (!slotAndUserAccount || slotAndUserAccount.slot <= slot) {
124
+ let userAccount: UserAccount;
125
+ // Polling leads to a lot of redundant decoding, so we only decode if data is from a fresh slot
126
+ if (dataType === 'raw') {
127
+ // @ts-ignore
128
+ const buffer = Buffer.from(data[0], data[1]);
129
+
130
+ const newLastActiveSlot = new BN(
131
+ buffer.subarray(4328, 4328 + 8),
132
+ undefined,
133
+ 'le'
134
+ );
135
+ if (
136
+ slotAndUserAccount &&
137
+ slotAndUserAccount.userAccount.lastActiveSlot.gt(newLastActiveSlot)
138
+ ) {
139
+ return;
140
+ }
141
+
142
+ userAccount =
143
+ this.driftClient.program.account.user.coder.accounts.decodeUnchecked(
144
+ 'User',
145
+ buffer
146
+ ) as UserAccount;
147
+ } else {
148
+ userAccount = data as UserAccount;
149
+ }
150
+
118
151
  const newOrders = userAccount.orders.filter(
119
152
  (order) =>
120
153
  order.slot.toNumber() > (slotAndUserAccount?.slot ?? 0) &&
@@ -148,6 +181,10 @@ export class OrderSubscriber {
148
181
  return dlob;
149
182
  }
150
183
 
184
+ public getSlot(): number {
185
+ return this.mostRecentSlot ?? 0;
186
+ }
187
+
151
188
  public async unsubscribe(): Promise<void> {
152
189
  await this.subscription.unsubscribe();
153
190
  }
@@ -2,10 +2,11 @@ import { OrderSubscriber } from './OrderSubscriber';
2
2
  import { getNonIdleUserFilter, getUserFilter } from '../memcmp';
3
3
  import { WebSocketProgramAccountSubscriber } from '../accounts/webSocketProgramAccountSubscriber';
4
4
  import { UserAccount } from '../types';
5
- import { Context, PublicKey } from '@solana/web3.js';
5
+ import { Commitment, Context, PublicKey } from '@solana/web3.js';
6
6
 
7
7
  export class WebsocketSubscription {
8
8
  private orderSubscriber: OrderSubscriber;
9
+ private commitment: Commitment;
9
10
  private skipInitialLoad: boolean;
10
11
  private resubTimeoutMs?: number;
11
12
 
@@ -13,14 +14,17 @@ export class WebsocketSubscription {
13
14
 
14
15
  constructor({
15
16
  orderSubscriber,
17
+ commitment,
16
18
  skipInitialLoad = false,
17
19
  resubTimeoutMs,
18
20
  }: {
19
21
  orderSubscriber: OrderSubscriber;
22
+ commitment: Commitment;
20
23
  skipInitialLoad?: boolean;
21
24
  resubTimeoutMs?: number;
22
25
  }) {
23
26
  this.orderSubscriber = orderSubscriber;
27
+ this.commitment = commitment;
24
28
  this.skipInitialLoad = skipInitialLoad;
25
29
  this.resubTimeoutMs = resubTimeoutMs;
26
30
  }
@@ -36,7 +40,7 @@ export class WebsocketSubscription {
36
40
  ),
37
41
  {
38
42
  filters: [getUserFilter(), getNonIdleUserFilter()],
39
- commitment: this.orderSubscriber.driftClient.opts.commitment,
43
+ commitment: this.commitment,
40
44
  },
41
45
  this.resubTimeoutMs
42
46
  );
@@ -47,6 +51,7 @@ export class WebsocketSubscription {
47
51
  const userKey = accountId.toBase58();
48
52
  this.orderSubscriber.tryUpdateUserAccount(
49
53
  userKey,
54
+ 'decoded',
50
55
  account,
51
56
  context.slot
52
57
  );