@drift-labs/sdk 2.40.0-beta.7 → 2.40.0-beta.8

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 (36) hide show
  1. package/VERSION +1 -1
  2. package/lib/accounts/types.d.ts +5 -1
  3. package/lib/accounts/webSocketAccountSubscriber.d.ts +6 -1
  4. package/lib/accounts/webSocketAccountSubscriber.js +28 -2
  5. package/lib/accounts/webSocketDriftClientAccountSubscriber.d.ts +2 -1
  6. package/lib/accounts/webSocketDriftClientAccountSubscriber.js +6 -5
  7. package/lib/accounts/webSocketProgramAccountSubscriber.d.ts +32 -0
  8. package/lib/accounts/webSocketProgramAccountSubscriber.js +99 -0
  9. package/lib/accounts/webSocketUserAccountSubscriber.d.ts +2 -1
  10. package/lib/accounts/webSocketUserAccountSubscriber.js +3 -2
  11. package/lib/accounts/webSocketUserStatsAccountSubsriber.d.ts +2 -1
  12. package/lib/accounts/webSocketUserStatsAccountSubsriber.js +3 -2
  13. package/lib/auctionSubscriber/auctionSubscriber.d.ts +3 -2
  14. package/lib/auctionSubscriber/auctionSubscriber.js +15 -7
  15. package/lib/auctionSubscriber/types.d.ts +1 -0
  16. package/lib/driftClient.js +3 -3
  17. package/lib/driftClientConfig.d.ts +1 -0
  18. package/lib/orderSubscriber/OrderSubscriber.d.ts +1 -3
  19. package/lib/orderSubscriber/OrderSubscriber.js +4 -3
  20. package/lib/orderSubscriber/WebsocketSubscription.d.ts +4 -2
  21. package/lib/orderSubscriber/WebsocketSubscription.js +13 -12
  22. package/lib/orderSubscriber/types.d.ts +1 -0
  23. package/package.json +1 -1
  24. package/src/accounts/types.ts +8 -1
  25. package/src/accounts/webSocketAccountSubscriber.ts +36 -2
  26. package/src/accounts/webSocketDriftClientAccountSubscriber.ts +15 -5
  27. package/src/accounts/webSocketProgramAccountSubscriber.ts +152 -0
  28. package/src/accounts/webSocketUserAccountSubscriber.ts +10 -2
  29. package/src/accounts/webSocketUserStatsAccountSubsriber.ts +10 -2
  30. package/src/auctionSubscriber/auctionSubscriber.ts +30 -21
  31. package/src/auctionSubscriber/types.ts +1 -0
  32. package/src/driftClient.ts +2 -1
  33. package/src/driftClientConfig.ts +1 -0
  34. package/src/orderSubscriber/OrderSubscriber.ts +12 -7
  35. package/src/orderSubscriber/WebsocketSubscription.ts +33 -23
  36. package/src/orderSubscriber/types.ts +1 -0
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.40.0-beta.7
1
+ 2.40.0-beta.8
@@ -3,7 +3,7 @@
3
3
  import { SpotMarketAccount, PerpMarketAccount, OracleSource, StateAccount, UserAccount, UserStatsAccount } from '../types';
4
4
  import StrictEventEmitter from 'strict-event-emitter-types';
5
5
  import { EventEmitter } from 'events';
6
- import { PublicKey } from '@solana/web3.js';
6
+ import { Context, PublicKey } from '@solana/web3.js';
7
7
  import { Account } from '@solana/spl-token';
8
8
  import { OracleInfo, OraclePriceData } from '..';
9
9
  export interface AccountSubscriber<T> {
@@ -13,6 +13,10 @@ export interface AccountSubscriber<T> {
13
13
  unsubscribe(): Promise<void>;
14
14
  setData(userAccount: T, slot?: number): void;
15
15
  }
16
+ export interface ProgramAccountSubscriber<T> {
17
+ subscribe(onChange: (accountId: PublicKey, data: T, context: Context) => void): Promise<void>;
18
+ unsubscribe(): Promise<void>;
19
+ }
16
20
  export declare class NotSubscribedError extends Error {
17
21
  name: string;
18
22
  }
@@ -1,4 +1,5 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
2
3
  import { DataAndSlot, BufferAndSlot, AccountSubscriber } from './types';
3
4
  import { Program } from '@coral-xyz/anchor';
4
5
  import { AccountInfo, Context, PublicKey } from '@solana/web3.js';
@@ -11,9 +12,13 @@ export declare class WebSocketAccountSubscriber<T> implements AccountSubscriber<
11
12
  decodeBufferFn: (buffer: Buffer) => T;
12
13
  onChange: (data: T) => void;
13
14
  listenerId?: number;
14
- constructor(accountName: string, program: Program, accountPublicKey: PublicKey, decodeBuffer?: (buffer: Buffer) => T);
15
+ resubTimeoutMs?: number;
16
+ timeoutId?: NodeJS.Timeout;
17
+ receivingData: boolean;
18
+ constructor(accountName: string, program: Program, accountPublicKey: PublicKey, decodeBuffer?: (buffer: Buffer) => T, resubTimeoutMs?: number);
15
19
  subscribe(onChange: (data: T) => void): Promise<void>;
16
20
  setData(data: T, slot?: number): void;
21
+ private setTimeout;
17
22
  fetch(): Promise<void>;
18
23
  handleRpcResponse(context: Context, accountInfo?: AccountInfo<Buffer>): void;
19
24
  decodeBuffer(buffer: Buffer): T;
@@ -3,11 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WebSocketAccountSubscriber = void 0;
4
4
  const utils_1 = require("./utils");
5
5
  class WebSocketAccountSubscriber {
6
- constructor(accountName, program, accountPublicKey, decodeBuffer) {
6
+ constructor(accountName, program, accountPublicKey, decodeBuffer, resubTimeoutMs) {
7
7
  this.accountName = accountName;
8
8
  this.program = program;
9
9
  this.accountPublicKey = accountPublicKey;
10
10
  this.decodeBufferFn = decodeBuffer;
11
+ this.resubTimeoutMs = resubTimeoutMs;
12
+ this.receivingData = false;
11
13
  }
12
14
  async subscribe(onChange) {
13
15
  if (this.listenerId) {
@@ -18,8 +20,19 @@ class WebSocketAccountSubscriber {
18
20
  await this.fetch();
19
21
  }
20
22
  this.listenerId = this.program.provider.connection.onAccountChange(this.accountPublicKey, (accountInfo, context) => {
21
- this.handleRpcResponse(context, accountInfo);
23
+ if (this.resubTimeoutMs) {
24
+ this.receivingData = true;
25
+ clearTimeout(this.timeoutId);
26
+ this.handleRpcResponse(context, accountInfo);
27
+ this.setTimeout();
28
+ }
29
+ else {
30
+ this.handleRpcResponse(context, accountInfo);
31
+ }
22
32
  }, this.program.provider.opts.commitment);
33
+ if (this.resubTimeoutMs) {
34
+ this.setTimeout();
35
+ }
23
36
  }
24
37
  setData(data, slot) {
25
38
  const newSlot = slot || 0;
@@ -31,6 +44,19 @@ class WebSocketAccountSubscriber {
31
44
  slot,
32
45
  };
33
46
  }
47
+ setTimeout() {
48
+ if (!this.onChange) {
49
+ throw new Error('onChange callback function must be set');
50
+ }
51
+ this.timeoutId = setTimeout(async () => {
52
+ if (this.receivingData) {
53
+ console.log(`No ws data from ${this.accountName} in ${this.resubTimeoutMs}ms, resubscribing`);
54
+ await this.unsubscribe();
55
+ this.receivingData = false;
56
+ await this.subscribe(this.onChange);
57
+ }
58
+ }, this.resubTimeoutMs);
59
+ }
34
60
  async fetch() {
35
61
  const rpcResponse = await this.program.provider.connection.getAccountInfoAndContext(this.accountPublicKey, this.program.provider.opts.commitment);
36
62
  this.handleRpcResponse(rpcResponse.context, rpcResponse === null || rpcResponse === void 0 ? void 0 : rpcResponse.value);
@@ -15,6 +15,7 @@ export declare class WebSocketDriftClientAccountSubscriber implements DriftClien
15
15
  spotMarketIndexes: number[];
16
16
  oracleInfos: OracleInfo[];
17
17
  oracleClientCache: OracleClientCache;
18
+ resubTimeoutMs?: number;
18
19
  shouldFindAllMarketsAndOracles: boolean;
19
20
  eventEmitter: StrictEventEmitter<EventEmitter, DriftClientAccountEvents>;
20
21
  stateAccountSubscriber?: AccountSubscriber<StateAccount>;
@@ -24,7 +25,7 @@ export declare class WebSocketDriftClientAccountSubscriber implements DriftClien
24
25
  private isSubscribing;
25
26
  private subscriptionPromise;
26
27
  private subscriptionPromiseResolver;
27
- constructor(program: Program, perpMarketIndexes: number[], spotMarketIndexes: number[], oracleInfos: OracleInfo[], shouldFindAllMarketsAndOracles: boolean);
28
+ constructor(program: Program, perpMarketIndexes: number[], spotMarketIndexes: number[], oracleInfos: OracleInfo[], shouldFindAllMarketsAndOracles: boolean, resubTimeoutMs?: number);
28
29
  subscribe(): Promise<boolean>;
29
30
  subscribeToPerpMarketAccounts(): Promise<boolean>;
30
31
  subscribeToPerpMarketAccount(marketIndex: number): Promise<boolean>;
@@ -10,7 +10,7 @@ const oracleClientCache_1 = require("../oracles/oracleClientCache");
10
10
  const quoteAssetOracleClient_1 = require("../oracles/quoteAssetOracleClient");
11
11
  const config_1 = require("../config");
12
12
  class WebSocketDriftClientAccountSubscriber {
13
- constructor(program, perpMarketIndexes, spotMarketIndexes, oracleInfos, shouldFindAllMarketsAndOracles) {
13
+ constructor(program, perpMarketIndexes, spotMarketIndexes, oracleInfos, shouldFindAllMarketsAndOracles, resubTimeoutMs) {
14
14
  this.oracleClientCache = new oracleClientCache_1.OracleClientCache();
15
15
  this.perpMarketAccountSubscribers = new Map();
16
16
  this.spotMarketAccountSubscribers = new Map();
@@ -23,6 +23,7 @@ class WebSocketDriftClientAccountSubscriber {
23
23
  this.spotMarketIndexes = spotMarketIndexes;
24
24
  this.oracleInfos = oracleInfos;
25
25
  this.shouldFindAllMarketsAndOracles = shouldFindAllMarketsAndOracles;
26
+ this.resubTimeoutMs = resubTimeoutMs;
26
27
  }
27
28
  async subscribe() {
28
29
  if (this.isSubscribed) {
@@ -43,7 +44,7 @@ class WebSocketDriftClientAccountSubscriber {
43
44
  }
44
45
  const statePublicKey = await (0, pda_1.getDriftStateAccountPublicKey)(this.program.programId);
45
46
  // create and activate main state account subscription
46
- this.stateAccountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('state', this.program, statePublicKey);
47
+ this.stateAccountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('state', this.program, statePublicKey, undefined, this.resubTimeoutMs);
47
48
  await this.stateAccountSubscriber.subscribe((data) => {
48
49
  this.eventEmitter.emit('stateAccountUpdate', data);
49
50
  this.eventEmitter.emit('update');
@@ -68,7 +69,7 @@ class WebSocketDriftClientAccountSubscriber {
68
69
  }
69
70
  async subscribeToPerpMarketAccount(marketIndex) {
70
71
  const perpMarketPublicKey = await (0, pda_1.getPerpMarketPublicKey)(this.program.programId, marketIndex);
71
- const accountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('perpMarket', this.program, perpMarketPublicKey);
72
+ const accountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('perpMarket', this.program, perpMarketPublicKey, undefined, this.resubTimeoutMs);
72
73
  await accountSubscriber.subscribe((data) => {
73
74
  this.eventEmitter.emit('perpMarketAccountUpdate', data);
74
75
  this.eventEmitter.emit('update');
@@ -84,7 +85,7 @@ class WebSocketDriftClientAccountSubscriber {
84
85
  }
85
86
  async subscribeToSpotMarketAccount(marketIndex) {
86
87
  const marketPublicKey = await (0, pda_1.getSpotMarketPublicKey)(this.program.programId, marketIndex);
87
- const accountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('spotMarket', this.program, marketPublicKey);
88
+ const accountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('spotMarket', this.program, marketPublicKey, undefined, this.resubTimeoutMs);
88
89
  await accountSubscriber.subscribe((data) => {
89
90
  this.eventEmitter.emit('spotMarketAccountUpdate', data);
90
91
  this.eventEmitter.emit('update');
@@ -104,7 +105,7 @@ class WebSocketDriftClientAccountSubscriber {
104
105
  const client = this.oracleClientCache.get(oracleInfo.source, this.program.provider.connection);
105
106
  const accountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('oracle', this.program, oracleInfo.publicKey, (buffer) => {
106
107
  return client.getOraclePriceDataFromBuffer(buffer);
107
- });
108
+ }, this.resubTimeoutMs);
108
109
  await accountSubscriber.subscribe((data) => {
109
110
  this.eventEmitter.emit('oraclePriceUpdate', oracleInfo.publicKey, data);
110
111
  this.eventEmitter.emit('update');
@@ -0,0 +1,32 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { DataAndSlot, BufferAndSlot, ProgramAccountSubscriber } from './types';
4
+ import { Program } from '@coral-xyz/anchor';
5
+ import { Commitment, Context, KeyedAccountInfo, MemcmpFilter, PublicKey } from '@solana/web3.js';
6
+ export declare class WebSocketProgramAccountSubscriber<T> implements ProgramAccountSubscriber<T> {
7
+ subscriptionName: string;
8
+ accountDiscriminator: string;
9
+ dataAndSlot?: DataAndSlot<T> & {
10
+ accountId: PublicKey;
11
+ };
12
+ bufferAndSlot?: BufferAndSlot;
13
+ program: Program;
14
+ decodeBuffer: (accountName: string, ix: Buffer) => T;
15
+ onChange: (accountId: PublicKey, data: T, context: Context) => void;
16
+ listenerId?: number;
17
+ resubTimeoutMs?: number;
18
+ timeoutId?: NodeJS.Timeout;
19
+ options: {
20
+ filters: MemcmpFilter[];
21
+ commitment?: Commitment;
22
+ };
23
+ receivingData: boolean;
24
+ constructor(subscriptionName: string, accountDiscriminator: string, program: Program, decodeBufferFn: (accountName: string, ix: Buffer) => T, options?: {
25
+ filters: MemcmpFilter[];
26
+ commitment?: Commitment;
27
+ }, resubTimeoutMs?: number);
28
+ subscribe(onChange: (accountId: PublicKey, data: T, context: Context) => void): Promise<void>;
29
+ private setTimeout;
30
+ handleRpcResponse(context: Context, keyedAccountInfo: KeyedAccountInfo): void;
31
+ unsubscribe(): Promise<void>;
32
+ }
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebSocketProgramAccountSubscriber = void 0;
4
+ class WebSocketProgramAccountSubscriber {
5
+ constructor(subscriptionName, accountDiscriminator, program, decodeBufferFn, options = {
6
+ filters: [],
7
+ }, resubTimeoutMs) {
8
+ this.receivingData = false;
9
+ this.subscriptionName = subscriptionName;
10
+ this.accountDiscriminator = accountDiscriminator;
11
+ this.program = program;
12
+ this.decodeBuffer = decodeBufferFn;
13
+ this.resubTimeoutMs = resubTimeoutMs;
14
+ this.options = options;
15
+ this.receivingData = false;
16
+ }
17
+ async subscribe(onChange) {
18
+ var _a;
19
+ if (this.listenerId) {
20
+ return;
21
+ }
22
+ this.onChange = onChange;
23
+ this.listenerId = this.program.provider.connection.onProgramAccountChange(this.program.programId, (keyedAccountInfo, context) => {
24
+ if (this.resubTimeoutMs) {
25
+ this.receivingData = true;
26
+ clearTimeout(this.timeoutId);
27
+ this.handleRpcResponse(context, keyedAccountInfo);
28
+ this.setTimeout();
29
+ }
30
+ else {
31
+ this.handleRpcResponse(context, keyedAccountInfo);
32
+ }
33
+ }, (_a = this.options.commitment) !== null && _a !== void 0 ? _a : this.program.provider.opts.commitment, this.options.filters);
34
+ if (this.resubTimeoutMs) {
35
+ this.setTimeout();
36
+ }
37
+ }
38
+ setTimeout() {
39
+ if (!this.onChange) {
40
+ throw new Error('onChange callback function must be set');
41
+ }
42
+ this.timeoutId = setTimeout(async () => {
43
+ if (this.receivingData) {
44
+ console.log(`No ws data from ${this.subscriptionName} in ${this.resubTimeoutMs}ms, resubscribing`);
45
+ await this.unsubscribe();
46
+ this.receivingData = false;
47
+ await this.subscribe(this.onChange);
48
+ }
49
+ }, this.resubTimeoutMs);
50
+ }
51
+ handleRpcResponse(context, keyedAccountInfo) {
52
+ const newSlot = context.slot;
53
+ let newBuffer = undefined;
54
+ if (keyedAccountInfo) {
55
+ newBuffer = keyedAccountInfo.accountInfo.data;
56
+ }
57
+ if (!this.bufferAndSlot) {
58
+ this.bufferAndSlot = {
59
+ buffer: newBuffer,
60
+ slot: newSlot,
61
+ };
62
+ if (newBuffer) {
63
+ const account = this.decodeBuffer(this.accountDiscriminator, newBuffer);
64
+ this.dataAndSlot = {
65
+ data: account,
66
+ slot: newSlot,
67
+ accountId: keyedAccountInfo.accountId,
68
+ };
69
+ this.onChange(keyedAccountInfo.accountId, account, context);
70
+ }
71
+ return;
72
+ }
73
+ if (newSlot <= this.bufferAndSlot.slot) {
74
+ return;
75
+ }
76
+ const oldBuffer = this.bufferAndSlot.buffer;
77
+ if (newBuffer && (!oldBuffer || !newBuffer.equals(oldBuffer))) {
78
+ this.bufferAndSlot = {
79
+ buffer: newBuffer,
80
+ slot: newSlot,
81
+ };
82
+ const account = this.decodeBuffer(this.accountDiscriminator, newBuffer);
83
+ this.dataAndSlot = {
84
+ data: account,
85
+ slot: newSlot,
86
+ accountId: keyedAccountInfo.accountId,
87
+ };
88
+ this.onChange(keyedAccountInfo.accountId, account, context);
89
+ }
90
+ }
91
+ unsubscribe() {
92
+ if (this.listenerId) {
93
+ const promise = this.program.provider.connection.removeAccountChangeListener(this.listenerId);
94
+ this.listenerId = undefined;
95
+ return promise;
96
+ }
97
+ }
98
+ }
99
+ exports.WebSocketProgramAccountSubscriber = WebSocketProgramAccountSubscriber;
@@ -7,11 +7,12 @@ import { PublicKey } from '@solana/web3.js';
7
7
  import { UserAccount } from '../types';
8
8
  export declare class WebSocketUserAccountSubscriber implements UserAccountSubscriber {
9
9
  isSubscribed: boolean;
10
+ reconnectTimeoutMs?: number;
10
11
  program: Program;
11
12
  eventEmitter: StrictEventEmitter<EventEmitter, UserAccountEvents>;
12
13
  userAccountPublicKey: PublicKey;
13
14
  userDataAccountSubscriber: AccountSubscriber<UserAccount>;
14
- constructor(program: Program, userAccountPublicKey: PublicKey);
15
+ constructor(program: Program, userAccountPublicKey: PublicKey, reconnectTimeoutMs?: number);
15
16
  subscribe(userAccount?: UserAccount): Promise<boolean>;
16
17
  fetch(): Promise<void>;
17
18
  unsubscribe(): Promise<void>;
@@ -5,17 +5,18 @@ const types_1 = require("./types");
5
5
  const events_1 = require("events");
6
6
  const webSocketAccountSubscriber_1 = require("./webSocketAccountSubscriber");
7
7
  class WebSocketUserAccountSubscriber {
8
- constructor(program, userAccountPublicKey) {
8
+ constructor(program, userAccountPublicKey, reconnectTimeoutMs) {
9
9
  this.isSubscribed = false;
10
10
  this.program = program;
11
11
  this.userAccountPublicKey = userAccountPublicKey;
12
12
  this.eventEmitter = new events_1.EventEmitter();
13
+ this.reconnectTimeoutMs = reconnectTimeoutMs;
13
14
  }
14
15
  async subscribe(userAccount) {
15
16
  if (this.isSubscribed) {
16
17
  return true;
17
18
  }
18
- this.userDataAccountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('user', this.program, this.userAccountPublicKey);
19
+ this.userDataAccountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('user', this.program, this.userAccountPublicKey, undefined, this.reconnectTimeoutMs);
19
20
  if (userAccount) {
20
21
  this.userDataAccountSubscriber.setData(userAccount);
21
22
  }
@@ -7,11 +7,12 @@ import { PublicKey } from '@solana/web3.js';
7
7
  import { UserStatsAccount } from '../types';
8
8
  export declare class WebSocketUserStatsAccountSubscriber implements UserStatsAccountSubscriber {
9
9
  isSubscribed: boolean;
10
+ reconnectTimeoutMs?: number;
10
11
  program: Program;
11
12
  eventEmitter: StrictEventEmitter<EventEmitter, UserStatsAccountEvents>;
12
13
  userStatsAccountPublicKey: PublicKey;
13
14
  userStatsAccountSubscriber: AccountSubscriber<UserStatsAccount>;
14
- constructor(program: Program, userStatsAccountPublicKey: PublicKey);
15
+ constructor(program: Program, userStatsAccountPublicKey: PublicKey, reconnectTimeoutMs?: number);
15
16
  subscribe(userStatsAccount?: UserStatsAccount): Promise<boolean>;
16
17
  fetch(): Promise<void>;
17
18
  unsubscribe(): Promise<void>;
@@ -5,17 +5,18 @@ const types_1 = require("./types");
5
5
  const events_1 = require("events");
6
6
  const webSocketAccountSubscriber_1 = require("./webSocketAccountSubscriber");
7
7
  class WebSocketUserStatsAccountSubscriber {
8
- constructor(program, userStatsAccountPublicKey) {
8
+ constructor(program, userStatsAccountPublicKey, reconnectTimeoutMs) {
9
9
  this.isSubscribed = false;
10
10
  this.program = program;
11
11
  this.userStatsAccountPublicKey = userStatsAccountPublicKey;
12
12
  this.eventEmitter = new events_1.EventEmitter();
13
+ this.reconnectTimeoutMs = reconnectTimeoutMs;
13
14
  }
14
15
  async subscribe(userStatsAccount) {
15
16
  if (this.isSubscribed) {
16
17
  return true;
17
18
  }
18
- this.userStatsAccountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('userStats', this.program, this.userStatsAccountPublicKey);
19
+ this.userStatsAccountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('userStats', this.program, this.userStatsAccountPublicKey, undefined, this.reconnectTimeoutMs);
19
20
  if (userStatsAccount) {
20
21
  this.userStatsAccountSubscriber.setData(userStatsAccount);
21
22
  }
@@ -5,9 +5,10 @@ import { EventEmitter } from 'events';
5
5
  export declare class AuctionSubscriber {
6
6
  private driftClient;
7
7
  private opts;
8
+ private resubTimeoutMs?;
8
9
  eventEmitter: StrictEventEmitter<EventEmitter, AuctionSubscriberEvents>;
9
- private websocketId;
10
- constructor({ driftClient, opts }: AuctionSubscriberConfig);
10
+ private subscriber;
11
+ constructor({ driftClient, opts, resubTimeoutMs }: AuctionSubscriberConfig);
11
12
  subscribe(): Promise<void>;
12
13
  unsubscribe(): Promise<void>;
13
14
  }
@@ -3,22 +3,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AuctionSubscriber = void 0;
4
4
  const memcmp_1 = require("../memcmp");
5
5
  const events_1 = require("events");
6
+ const webSocketProgramAccountSubscriber_1 = require("../accounts/webSocketProgramAccountSubscriber");
6
7
  class AuctionSubscriber {
7
- constructor({ driftClient, opts }) {
8
+ constructor({ driftClient, opts, resubTimeoutMs }) {
8
9
  this.driftClient = driftClient;
9
10
  this.opts = opts || this.driftClient.opts;
10
11
  this.eventEmitter = new events_1.EventEmitter();
12
+ this.resubTimeoutMs = resubTimeoutMs;
11
13
  }
12
14
  async subscribe() {
13
- this.websocketId = this.driftClient.connection.onProgramAccountChange(this.driftClient.program.programId, (keyAccountInfo, context) => {
14
- const userAccount = this.driftClient.program.account.user.coder.accounts.decode('User', keyAccountInfo.accountInfo.data);
15
- this.eventEmitter.emit('onAccountUpdate', userAccount, keyAccountInfo.accountId, context.slot);
16
- }, this.driftClient.opts.commitment, [(0, memcmp_1.getUserFilter)(), (0, memcmp_1.getUserWithAuctionFilter)()]);
15
+ if (!this.subscriber) {
16
+ this.subscriber = new webSocketProgramAccountSubscriber_1.WebSocketProgramAccountSubscriber('AuctionSubscriber', 'User', this.driftClient.program, this.driftClient.program.account.user.coder.accounts.decode.bind(this.driftClient.program.account.user.coder.accounts), {
17
+ filters: [(0, memcmp_1.getUserFilter)(), (0, memcmp_1.getUserWithAuctionFilter)()],
18
+ commitment: this.driftClient.opts.commitment,
19
+ }, this.resubTimeoutMs);
20
+ }
21
+ await this.subscriber.subscribe((accountId, data, context) => {
22
+ this.eventEmitter.emit('onAccountUpdate', data, accountId, context.slot);
23
+ });
17
24
  }
18
25
  async unsubscribe() {
19
- if (this.websocketId) {
20
- await this.driftClient.connection.removeProgramAccountChangeListener(this.websocketId);
26
+ if (!this.subscriber) {
27
+ return;
21
28
  }
29
+ this.subscriber.unsubscribe();
22
30
  }
23
31
  }
24
32
  exports.AuctionSubscriber = AuctionSubscriber;
@@ -4,6 +4,7 @@ import { ConfirmOptions, PublicKey } from '@solana/web3.js';
4
4
  export type AuctionSubscriberConfig = {
5
5
  driftClient: DriftClient;
6
6
  opts?: ConfirmOptions;
7
+ resubTimeoutMs?: number;
7
8
  };
8
9
  export interface AuctionSubscriberEvents {
9
10
  onAccountUpdate: (account: UserAccount, pubkey: PublicKey, slot: number) => void;
@@ -66,7 +66,7 @@ class DriftClient {
66
66
  this._isSubscribed = val;
67
67
  }
68
68
  constructor(config) {
69
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
69
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
70
70
  this.users = new Map();
71
71
  this._isSubscribed = false;
72
72
  this.perpMarketLastSlotCache = new Map();
@@ -139,11 +139,11 @@ class DriftClient {
139
139
  this.accountSubscriber = new pollingDriftClientAccountSubscriber_1.PollingDriftClientAccountSubscriber(this.program, config.accountSubscription.accountLoader, (_o = config.perpMarketIndexes) !== null && _o !== void 0 ? _o : [], (_p = config.spotMarketIndexes) !== null && _p !== void 0 ? _p : [], (_q = config.oracleInfos) !== null && _q !== void 0 ? _q : [], noMarketsAndOraclesSpecified);
140
140
  }
141
141
  else {
142
- this.accountSubscriber = new webSocketDriftClientAccountSubscriber_1.WebSocketDriftClientAccountSubscriber(this.program, (_r = config.perpMarketIndexes) !== null && _r !== void 0 ? _r : [], (_s = config.spotMarketIndexes) !== null && _s !== void 0 ? _s : [], (_t = config.oracleInfos) !== null && _t !== void 0 ? _t : [], noMarketsAndOraclesSpecified);
142
+ this.accountSubscriber = new webSocketDriftClientAccountSubscriber_1.WebSocketDriftClientAccountSubscriber(this.program, (_r = config.perpMarketIndexes) !== null && _r !== void 0 ? _r : [], (_s = config.spotMarketIndexes) !== null && _s !== void 0 ? _s : [], (_t = config.oracleInfos) !== null && _t !== void 0 ? _t : [], noMarketsAndOraclesSpecified, (_u = config.accountSubscription) === null || _u === void 0 ? void 0 : _u.resubTimeoutMs);
143
143
  }
144
144
  this.eventEmitter = this.accountSubscriber.eventEmitter;
145
145
  this.txSender =
146
- (_u = config.txSender) !== null && _u !== void 0 ? _u : new retryTxSender_1.RetryTxSender({
146
+ (_v = config.txSender) !== null && _v !== void 0 ? _v : new retryTxSender_1.RetryTxSender({
147
147
  connection: this.connection,
148
148
  wallet: this.wallet,
149
149
  opts: this.opts,
@@ -28,6 +28,7 @@ export type DriftClientConfig = {
28
28
  };
29
29
  export type DriftClientSubscriptionConfig = {
30
30
  type: 'websocket';
31
+ resubTimeoutMs?: number;
31
32
  } | {
32
33
  type: 'polling';
33
34
  accountLoader: BulkAccountLoader;
@@ -1,8 +1,6 @@
1
1
  /// <reference types="node" />
2
- /// <reference types="node" />
3
2
  import { DriftClient } from '../driftClient';
4
3
  import { UserAccount } from '../types';
5
- import { Buffer } from 'buffer';
6
4
  import { DLOB } from '../dlob/DLOB';
7
5
  import { OrderSubscriberConfig, OrderSubscriberEvents } from './types';
8
6
  import { PollingSubscription } from './PollingSubscription';
@@ -22,7 +20,7 @@ export declare class OrderSubscriber {
22
20
  constructor(config: OrderSubscriberConfig);
23
21
  subscribe(): Promise<void>;
24
22
  fetch(): Promise<void>;
25
- tryUpdateUserAccount(key: string, buffer: Buffer, slot: number): void;
23
+ tryUpdateUserAccount(key: string, userAccount: UserAccount, slot: number): void;
26
24
  getDLOB(slot: number): Promise<DLOB>;
27
25
  unsubscribe(): Promise<void>;
28
26
  }
@@ -22,6 +22,7 @@ class OrderSubscriber {
22
22
  this.subscription = new WebsocketSubscription_1.WebsocketSubscription({
23
23
  orderSubscriber: this,
24
24
  skipInitialLoad: config.subscriptionConfig.skipInitialLoad,
25
+ resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
25
26
  });
26
27
  }
27
28
  this.eventEmitter = new events_1.EventEmitter();
@@ -57,7 +58,8 @@ class OrderSubscriber {
57
58
  // @ts-ignore
58
59
  const buffer = buffer_1.Buffer.from(programAccount.account.data[0], programAccount.account.data[1]);
59
60
  programAccountSet.add(key);
60
- this.tryUpdateUserAccount(key, buffer, slot);
61
+ const userAccount = this.driftClient.program.account.user.coder.accounts.decode('User', buffer);
62
+ this.tryUpdateUserAccount(key, userAccount, slot);
61
63
  }
62
64
  for (const key of this.usersAccounts.keys()) {
63
65
  if (!programAccountSet.has(key)) {
@@ -73,10 +75,9 @@ class OrderSubscriber {
73
75
  this.fetchPromise = undefined;
74
76
  }
75
77
  }
76
- tryUpdateUserAccount(key, buffer, slot) {
78
+ tryUpdateUserAccount(key, userAccount, slot) {
77
79
  const slotAndUserAccount = this.usersAccounts.get(key);
78
80
  if (!slotAndUserAccount || slotAndUserAccount.slot < slot) {
79
- const userAccount = this.driftClient.program.account.user.coder.accounts.decode('User', buffer);
80
81
  const newOrders = userAccount.orders.filter((order) => {
81
82
  var _a;
82
83
  return order.slot.toNumber() > ((_a = slotAndUserAccount === null || slotAndUserAccount === void 0 ? void 0 : slotAndUserAccount.slot) !== null && _a !== void 0 ? _a : 0) &&
@@ -2,10 +2,12 @@ import { OrderSubscriber } from './OrderSubscriber';
2
2
  export declare class WebsocketSubscription {
3
3
  private orderSubscriber;
4
4
  private skipInitialLoad;
5
- private websocketId;
6
- constructor({ orderSubscriber, skipInitialLoad, }: {
5
+ private resubTimeoutMs?;
6
+ private subscriber;
7
+ constructor({ orderSubscriber, skipInitialLoad, resubTimeoutMs, }: {
7
8
  orderSubscriber: OrderSubscriber;
8
9
  skipInitialLoad?: boolean;
10
+ resubTimeoutMs?: number;
9
11
  });
10
12
  subscribe(): Promise<void>;
11
13
  unsubscribe(): Promise<void>;
@@ -2,29 +2,30 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WebsocketSubscription = void 0;
4
4
  const memcmp_1 = require("../memcmp");
5
+ const webSocketProgramAccountSubscriber_1 = require("../accounts/webSocketProgramAccountSubscriber");
5
6
  class WebsocketSubscription {
6
- constructor({ orderSubscriber, skipInitialLoad = false, }) {
7
+ constructor({ orderSubscriber, skipInitialLoad = false, resubTimeoutMs, }) {
7
8
  this.orderSubscriber = orderSubscriber;
8
9
  this.skipInitialLoad = skipInitialLoad;
10
+ this.resubTimeoutMs = resubTimeoutMs;
9
11
  }
10
12
  async subscribe() {
11
- if (this.websocketId) {
12
- return;
13
+ if (!this.subscriber) {
14
+ this.subscriber = new webSocketProgramAccountSubscriber_1.WebSocketProgramAccountSubscriber('OrderSubscriber', 'User', this.orderSubscriber.driftClient.program, this.orderSubscriber.driftClient.program.account.user.coder.accounts.decode.bind(this.orderSubscriber.driftClient.program.account.user.coder.accounts), {
15
+ filters: [(0, memcmp_1.getUserFilter)(), (0, memcmp_1.getNonIdleUserFilter)()],
16
+ commitment: this.orderSubscriber.driftClient.opts.commitment,
17
+ }, this.resubTimeoutMs);
13
18
  }
14
- this.websocketId =
15
- this.orderSubscriber.driftClient.connection.onProgramAccountChange(this.orderSubscriber.driftClient.program.programId, (keyAccountInfo, context) => {
16
- const userKey = keyAccountInfo.accountId.toBase58();
17
- this.orderSubscriber.tryUpdateUserAccount(userKey, keyAccountInfo.accountInfo.data, context.slot);
18
- }, this.orderSubscriber.driftClient.opts.commitment, [(0, memcmp_1.getUserFilter)(), (0, memcmp_1.getNonIdleUserFilter)()]);
19
+ await this.subscriber.subscribe((accountId, account, context) => {
20
+ const userKey = accountId.toBase58();
21
+ this.orderSubscriber.tryUpdateUserAccount(userKey, account, context.slot);
22
+ });
19
23
  if (!this.skipInitialLoad) {
20
24
  await this.orderSubscriber.fetch();
21
25
  }
22
26
  }
23
27
  async unsubscribe() {
24
- if (this.websocketId) {
25
- await this.orderSubscriber.driftClient.connection.removeProgramAccountChangeListener(this.websocketId);
26
- this.websocketId = undefined;
27
- }
28
+ this.subscriber.unsubscribe();
28
29
  }
29
30
  }
30
31
  exports.WebsocketSubscription = WebsocketSubscription;
@@ -9,6 +9,7 @@ export type OrderSubscriberConfig = {
9
9
  } | {
10
10
  type: 'websocket';
11
11
  skipInitialLoad?: boolean;
12
+ resubTimeoutMs?: number;
12
13
  };
13
14
  };
14
15
  export interface OrderSubscriberEvents {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.40.0-beta.7",
3
+ "version": "2.40.0-beta.8",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -8,7 +8,7 @@ import {
8
8
  } from '../types';
9
9
  import StrictEventEmitter from 'strict-event-emitter-types';
10
10
  import { EventEmitter } from 'events';
11
- import { PublicKey } from '@solana/web3.js';
11
+ import { Context, PublicKey } from '@solana/web3.js';
12
12
  import { Account } from '@solana/spl-token';
13
13
  import { OracleInfo, OraclePriceData } from '..';
14
14
 
@@ -21,6 +21,13 @@ export interface AccountSubscriber<T> {
21
21
  setData(userAccount: T, slot?: number): void;
22
22
  }
23
23
 
24
+ export interface ProgramAccountSubscriber<T> {
25
+ subscribe(
26
+ onChange: (accountId: PublicKey, data: T, context: Context) => void
27
+ ): Promise<void>;
28
+ unsubscribe(): Promise<void>;
29
+ }
30
+
24
31
  export class NotSubscribedError extends Error {
25
32
  name = 'NotSubscribedError';
26
33
  }
@@ -13,17 +13,24 @@ export class WebSocketAccountSubscriber<T> implements AccountSubscriber<T> {
13
13
  decodeBufferFn: (buffer: Buffer) => T;
14
14
  onChange: (data: T) => void;
15
15
  listenerId?: number;
16
+ resubTimeoutMs?: number;
17
+ timeoutId?: NodeJS.Timeout;
18
+
19
+ receivingData: boolean;
16
20
 
17
21
  public constructor(
18
22
  accountName: string,
19
23
  program: Program,
20
24
  accountPublicKey: PublicKey,
21
- decodeBuffer?: (buffer: Buffer) => T
25
+ decodeBuffer?: (buffer: Buffer) => T,
26
+ resubTimeoutMs?: number
22
27
  ) {
23
28
  this.accountName = accountName;
24
29
  this.program = program;
25
30
  this.accountPublicKey = accountPublicKey;
26
31
  this.decodeBufferFn = decodeBuffer;
32
+ this.resubTimeoutMs = resubTimeoutMs;
33
+ this.receivingData = false;
27
34
  }
28
35
 
29
36
  async subscribe(onChange: (data: T) => void): Promise<void> {
@@ -39,10 +46,21 @@ export class WebSocketAccountSubscriber<T> implements AccountSubscriber<T> {
39
46
  this.listenerId = this.program.provider.connection.onAccountChange(
40
47
  this.accountPublicKey,
41
48
  (accountInfo, context) => {
42
- this.handleRpcResponse(context, accountInfo);
49
+ if (this.resubTimeoutMs) {
50
+ this.receivingData = true;
51
+ clearTimeout(this.timeoutId);
52
+ this.handleRpcResponse(context, accountInfo);
53
+ this.setTimeout();
54
+ } else {
55
+ this.handleRpcResponse(context, accountInfo);
56
+ }
43
57
  },
44
58
  (this.program.provider as AnchorProvider).opts.commitment
45
59
  );
60
+
61
+ if (this.resubTimeoutMs) {
62
+ this.setTimeout();
63
+ }
46
64
  }
47
65
 
48
66
  setData(data: T, slot?: number): void {
@@ -57,6 +75,22 @@ export class WebSocketAccountSubscriber<T> implements AccountSubscriber<T> {
57
75
  };
58
76
  }
59
77
 
78
+ private setTimeout(): void {
79
+ if (!this.onChange) {
80
+ throw new Error('onChange callback function must be set');
81
+ }
82
+ this.timeoutId = setTimeout(async () => {
83
+ if (this.receivingData) {
84
+ console.log(
85
+ `No ws data from ${this.accountName} in ${this.resubTimeoutMs}ms, resubscribing`
86
+ );
87
+ await this.unsubscribe();
88
+ this.receivingData = false;
89
+ await this.subscribe(this.onChange);
90
+ }
91
+ }, this.resubTimeoutMs);
92
+ }
93
+
60
94
  async fetch(): Promise<void> {
61
95
  const rpcResponse =
62
96
  await this.program.provider.connection.getAccountInfoAndContext(
@@ -31,6 +31,7 @@ export class WebSocketDriftClientAccountSubscriber
31
31
  oracleInfos: OracleInfo[];
32
32
  oracleClientCache = new OracleClientCache();
33
33
 
34
+ resubTimeoutMs?: number;
34
35
  shouldFindAllMarketsAndOracles: boolean;
35
36
 
36
37
  eventEmitter: StrictEventEmitter<EventEmitter, DriftClientAccountEvents>;
@@ -54,7 +55,8 @@ export class WebSocketDriftClientAccountSubscriber
54
55
  perpMarketIndexes: number[],
55
56
  spotMarketIndexes: number[],
56
57
  oracleInfos: OracleInfo[],
57
- shouldFindAllMarketsAndOracles: boolean
58
+ shouldFindAllMarketsAndOracles: boolean,
59
+ resubTimeoutMs?: number
58
60
  ) {
59
61
  this.isSubscribed = false;
60
62
  this.program = program;
@@ -63,6 +65,7 @@ export class WebSocketDriftClientAccountSubscriber
63
65
  this.spotMarketIndexes = spotMarketIndexes;
64
66
  this.oracleInfos = oracleInfos;
65
67
  this.shouldFindAllMarketsAndOracles = shouldFindAllMarketsAndOracles;
68
+ this.resubTimeoutMs = resubTimeoutMs;
66
69
  }
67
70
 
68
71
  public async subscribe(): Promise<boolean> {
@@ -96,7 +99,9 @@ export class WebSocketDriftClientAccountSubscriber
96
99
  this.stateAccountSubscriber = new WebSocketAccountSubscriber(
97
100
  'state',
98
101
  this.program,
99
- statePublicKey
102
+ statePublicKey,
103
+ undefined,
104
+ this.resubTimeoutMs
100
105
  );
101
106
  await this.stateAccountSubscriber.subscribe((data: StateAccount) => {
102
107
  this.eventEmitter.emit('stateAccountUpdate', data);
@@ -136,7 +141,9 @@ export class WebSocketDriftClientAccountSubscriber
136
141
  const accountSubscriber = new WebSocketAccountSubscriber<PerpMarketAccount>(
137
142
  'perpMarket',
138
143
  this.program,
139
- perpMarketPublicKey
144
+ perpMarketPublicKey,
145
+ undefined,
146
+ this.resubTimeoutMs
140
147
  );
141
148
  await accountSubscriber.subscribe((data: PerpMarketAccount) => {
142
149
  this.eventEmitter.emit('perpMarketAccountUpdate', data);
@@ -161,7 +168,9 @@ export class WebSocketDriftClientAccountSubscriber
161
168
  const accountSubscriber = new WebSocketAccountSubscriber<SpotMarketAccount>(
162
169
  'spotMarket',
163
170
  this.program,
164
- marketPublicKey
171
+ marketPublicKey,
172
+ undefined,
173
+ this.resubTimeoutMs
165
174
  );
166
175
  await accountSubscriber.subscribe((data: SpotMarketAccount) => {
167
176
  this.eventEmitter.emit('spotMarketAccountUpdate', data);
@@ -192,7 +201,8 @@ export class WebSocketDriftClientAccountSubscriber
192
201
  oracleInfo.publicKey,
193
202
  (buffer: Buffer) => {
194
203
  return client.getOraclePriceDataFromBuffer(buffer);
195
- }
204
+ },
205
+ this.resubTimeoutMs
196
206
  );
197
207
 
198
208
  await accountSubscriber.subscribe((data: OraclePriceData) => {
@@ -0,0 +1,152 @@
1
+ import { DataAndSlot, BufferAndSlot, ProgramAccountSubscriber } from './types';
2
+ import { AnchorProvider, Program } from '@coral-xyz/anchor';
3
+ import {
4
+ Commitment,
5
+ Context,
6
+ KeyedAccountInfo,
7
+ MemcmpFilter,
8
+ PublicKey,
9
+ } from '@solana/web3.js';
10
+ import * as Buffer from 'buffer';
11
+
12
+ export class WebSocketProgramAccountSubscriber<T>
13
+ implements ProgramAccountSubscriber<T>
14
+ {
15
+ subscriptionName: string;
16
+ accountDiscriminator: string;
17
+ dataAndSlot?: DataAndSlot<T> & { accountId: PublicKey };
18
+ bufferAndSlot?: BufferAndSlot;
19
+ program: Program;
20
+ decodeBuffer: (accountName: string, ix: Buffer) => T;
21
+ onChange: (accountId: PublicKey, data: T, context: Context) => void;
22
+ listenerId?: number;
23
+ resubTimeoutMs?: number;
24
+ timeoutId?: NodeJS.Timeout;
25
+ options: { filters: MemcmpFilter[]; commitment?: Commitment };
26
+
27
+ receivingData = false;
28
+
29
+ public constructor(
30
+ subscriptionName: string,
31
+ accountDiscriminator: string,
32
+ program: Program,
33
+ decodeBufferFn: (accountName: string, ix: Buffer) => T,
34
+ options: { filters: MemcmpFilter[]; commitment?: Commitment } = {
35
+ filters: [],
36
+ },
37
+ resubTimeoutMs?: number
38
+ ) {
39
+ this.subscriptionName = subscriptionName;
40
+ this.accountDiscriminator = accountDiscriminator;
41
+ this.program = program;
42
+ this.decodeBuffer = decodeBufferFn;
43
+ this.resubTimeoutMs = resubTimeoutMs;
44
+ this.options = options;
45
+ this.receivingData = false;
46
+ }
47
+
48
+ async subscribe(
49
+ onChange: (accountId: PublicKey, data: T, context: Context) => void
50
+ ): Promise<void> {
51
+ if (this.listenerId) {
52
+ return;
53
+ }
54
+
55
+ this.onChange = onChange;
56
+
57
+ this.listenerId = this.program.provider.connection.onProgramAccountChange(
58
+ this.program.programId,
59
+ (keyedAccountInfo, context) => {
60
+ if (this.resubTimeoutMs) {
61
+ this.receivingData = true;
62
+ clearTimeout(this.timeoutId);
63
+ this.handleRpcResponse(context, keyedAccountInfo);
64
+ this.setTimeout();
65
+ } else {
66
+ this.handleRpcResponse(context, keyedAccountInfo);
67
+ }
68
+ },
69
+ this.options.commitment ??
70
+ (this.program.provider as AnchorProvider).opts.commitment,
71
+ this.options.filters
72
+ );
73
+
74
+ if (this.resubTimeoutMs) {
75
+ this.setTimeout();
76
+ }
77
+ }
78
+
79
+ private setTimeout(): void {
80
+ if (!this.onChange) {
81
+ throw new Error('onChange callback function must be set');
82
+ }
83
+ this.timeoutId = setTimeout(async () => {
84
+ if (this.receivingData) {
85
+ console.log(
86
+ `No ws data from ${this.subscriptionName} in ${this.resubTimeoutMs}ms, resubscribing`
87
+ );
88
+ await this.unsubscribe();
89
+ this.receivingData = false;
90
+ await this.subscribe(this.onChange);
91
+ }
92
+ }, this.resubTimeoutMs);
93
+ }
94
+
95
+ handleRpcResponse(
96
+ context: Context,
97
+ keyedAccountInfo: KeyedAccountInfo
98
+ ): void {
99
+ const newSlot = context.slot;
100
+ let newBuffer: Buffer | undefined = undefined;
101
+ if (keyedAccountInfo) {
102
+ newBuffer = keyedAccountInfo.accountInfo.data;
103
+ }
104
+
105
+ if (!this.bufferAndSlot) {
106
+ this.bufferAndSlot = {
107
+ buffer: newBuffer,
108
+ slot: newSlot,
109
+ };
110
+ if (newBuffer) {
111
+ const account = this.decodeBuffer(this.accountDiscriminator, newBuffer);
112
+ this.dataAndSlot = {
113
+ data: account,
114
+ slot: newSlot,
115
+ accountId: keyedAccountInfo.accountId,
116
+ };
117
+ this.onChange(keyedAccountInfo.accountId, account, context);
118
+ }
119
+ return;
120
+ }
121
+
122
+ if (newSlot <= this.bufferAndSlot.slot) {
123
+ return;
124
+ }
125
+
126
+ const oldBuffer = this.bufferAndSlot.buffer;
127
+ if (newBuffer && (!oldBuffer || !newBuffer.equals(oldBuffer))) {
128
+ this.bufferAndSlot = {
129
+ buffer: newBuffer,
130
+ slot: newSlot,
131
+ };
132
+ const account = this.decodeBuffer(this.accountDiscriminator, newBuffer);
133
+ this.dataAndSlot = {
134
+ data: account,
135
+ slot: newSlot,
136
+ accountId: keyedAccountInfo.accountId,
137
+ };
138
+ this.onChange(keyedAccountInfo.accountId, account, context);
139
+ }
140
+ }
141
+
142
+ unsubscribe(): Promise<void> {
143
+ if (this.listenerId) {
144
+ const promise =
145
+ this.program.provider.connection.removeAccountChangeListener(
146
+ this.listenerId
147
+ );
148
+ this.listenerId = undefined;
149
+ return promise;
150
+ }
151
+ }
152
+ }
@@ -14,17 +14,23 @@ import { UserAccount } from '../types';
14
14
 
15
15
  export class WebSocketUserAccountSubscriber implements UserAccountSubscriber {
16
16
  isSubscribed: boolean;
17
+ reconnectTimeoutMs?: number;
17
18
  program: Program;
18
19
  eventEmitter: StrictEventEmitter<EventEmitter, UserAccountEvents>;
19
20
  userAccountPublicKey: PublicKey;
20
21
 
21
22
  userDataAccountSubscriber: AccountSubscriber<UserAccount>;
22
23
 
23
- public constructor(program: Program, userAccountPublicKey: PublicKey) {
24
+ public constructor(
25
+ program: Program,
26
+ userAccountPublicKey: PublicKey,
27
+ reconnectTimeoutMs?: number
28
+ ) {
24
29
  this.isSubscribed = false;
25
30
  this.program = program;
26
31
  this.userAccountPublicKey = userAccountPublicKey;
27
32
  this.eventEmitter = new EventEmitter();
33
+ this.reconnectTimeoutMs = reconnectTimeoutMs;
28
34
  }
29
35
 
30
36
  async subscribe(userAccount?: UserAccount): Promise<boolean> {
@@ -35,7 +41,9 @@ export class WebSocketUserAccountSubscriber implements UserAccountSubscriber {
35
41
  this.userDataAccountSubscriber = new WebSocketAccountSubscriber(
36
42
  'user',
37
43
  this.program,
38
- this.userAccountPublicKey
44
+ this.userAccountPublicKey,
45
+ undefined,
46
+ this.reconnectTimeoutMs
39
47
  );
40
48
 
41
49
  if (userAccount) {
@@ -16,17 +16,23 @@ export class WebSocketUserStatsAccountSubscriber
16
16
  implements UserStatsAccountSubscriber
17
17
  {
18
18
  isSubscribed: boolean;
19
+ reconnectTimeoutMs?: number;
19
20
  program: Program;
20
21
  eventEmitter: StrictEventEmitter<EventEmitter, UserStatsAccountEvents>;
21
22
  userStatsAccountPublicKey: PublicKey;
22
23
 
23
24
  userStatsAccountSubscriber: AccountSubscriber<UserStatsAccount>;
24
25
 
25
- public constructor(program: Program, userStatsAccountPublicKey: PublicKey) {
26
+ public constructor(
27
+ program: Program,
28
+ userStatsAccountPublicKey: PublicKey,
29
+ reconnectTimeoutMs?: number
30
+ ) {
26
31
  this.isSubscribed = false;
27
32
  this.program = program;
28
33
  this.userStatsAccountPublicKey = userStatsAccountPublicKey;
29
34
  this.eventEmitter = new EventEmitter();
35
+ this.reconnectTimeoutMs = reconnectTimeoutMs;
30
36
  }
31
37
 
32
38
  async subscribe(userStatsAccount?: UserStatsAccount): Promise<boolean> {
@@ -37,7 +43,9 @@ export class WebSocketUserStatsAccountSubscriber
37
43
  this.userStatsAccountSubscriber = new WebSocketAccountSubscriber(
38
44
  'userStats',
39
45
  this.program,
40
- this.userStatsAccountPublicKey
46
+ this.userStatsAccountPublicKey,
47
+ undefined,
48
+ this.reconnectTimeoutMs
41
49
  );
42
50
 
43
51
  if (userStatsAccount) {
@@ -4,48 +4,57 @@ import { getUserFilter, getUserWithAuctionFilter } from '../memcmp';
4
4
  import StrictEventEmitter from 'strict-event-emitter-types';
5
5
  import { EventEmitter } from 'events';
6
6
  import { UserAccount } from '../types';
7
- import { ConfirmOptions } from '@solana/web3.js';
7
+ import { ConfirmOptions, Context, PublicKey } from '@solana/web3.js';
8
+ import { WebSocketProgramAccountSubscriber } from '../accounts/webSocketProgramAccountSubscriber';
8
9
 
9
10
  export class AuctionSubscriber {
10
11
  private driftClient: DriftClient;
11
12
  private opts: ConfirmOptions;
13
+ private resubTimeoutMs?: number;
12
14
 
13
15
  eventEmitter: StrictEventEmitter<EventEmitter, AuctionSubscriberEvents>;
16
+ private subscriber: WebSocketProgramAccountSubscriber<UserAccount>;
14
17
 
15
- private websocketId: number;
16
-
17
- constructor({ driftClient, opts }: AuctionSubscriberConfig) {
18
+ constructor({ driftClient, opts, resubTimeoutMs }: AuctionSubscriberConfig) {
18
19
  this.driftClient = driftClient;
19
20
  this.opts = opts || this.driftClient.opts;
20
21
  this.eventEmitter = new EventEmitter();
22
+ this.resubTimeoutMs = resubTimeoutMs;
21
23
  }
22
24
 
23
25
  public async subscribe() {
24
- this.websocketId = this.driftClient.connection.onProgramAccountChange(
25
- this.driftClient.program.programId,
26
- (keyAccountInfo, context) => {
27
- const userAccount =
28
- this.driftClient.program.account.user.coder.accounts.decode(
29
- 'User',
30
- keyAccountInfo.accountInfo.data
31
- ) as UserAccount;
26
+ if (!this.subscriber) {
27
+ this.subscriber = new WebSocketProgramAccountSubscriber<UserAccount>(
28
+ 'AuctionSubscriber',
29
+ 'User',
30
+ this.driftClient.program,
31
+ this.driftClient.program.account.user.coder.accounts.decode.bind(
32
+ this.driftClient.program.account.user.coder.accounts
33
+ ),
34
+ {
35
+ filters: [getUserFilter(), getUserWithAuctionFilter()],
36
+ commitment: this.driftClient.opts.commitment,
37
+ },
38
+ this.resubTimeoutMs
39
+ );
40
+ }
41
+
42
+ await this.subscriber.subscribe(
43
+ (accountId: PublicKey, data: UserAccount, context: Context) => {
32
44
  this.eventEmitter.emit(
33
45
  'onAccountUpdate',
34
- userAccount,
35
- keyAccountInfo.accountId,
46
+ data,
47
+ accountId,
36
48
  context.slot
37
49
  );
38
- },
39
- this.driftClient.opts.commitment,
40
- [getUserFilter(), getUserWithAuctionFilter()]
50
+ }
41
51
  );
42
52
  }
43
53
 
44
54
  public async unsubscribe() {
45
- if (this.websocketId) {
46
- await this.driftClient.connection.removeProgramAccountChangeListener(
47
- this.websocketId
48
- );
55
+ if (!this.subscriber) {
56
+ return;
49
57
  }
58
+ this.subscriber.unsubscribe();
50
59
  }
51
60
  }
@@ -5,6 +5,7 @@ import { ConfirmOptions, PublicKey } from '@solana/web3.js';
5
5
  export type AuctionSubscriberConfig = {
6
6
  driftClient: DriftClient;
7
7
  opts?: ConfirmOptions;
8
+ resubTimeoutMs?: number;
8
9
  };
9
10
 
10
11
  export interface AuctionSubscriberEvents {
@@ -278,7 +278,8 @@ export class DriftClient {
278
278
  config.perpMarketIndexes ?? [],
279
279
  config.spotMarketIndexes ?? [],
280
280
  config.oracleInfos ?? [],
281
- noMarketsAndOraclesSpecified
281
+ noMarketsAndOraclesSpecified,
282
+ config.accountSubscription?.resubTimeoutMs
282
283
  );
283
284
  }
284
285
  this.eventEmitter = this.accountSubscriber.eventEmitter;
@@ -36,6 +36,7 @@ export type DriftClientConfig = {
36
36
  export type DriftClientSubscriptionConfig =
37
37
  | {
38
38
  type: 'websocket';
39
+ resubTimeoutMs?: number;
39
40
  }
40
41
  | {
41
42
  type: 'polling';
@@ -30,6 +30,7 @@ export class OrderSubscriber {
30
30
  this.subscription = new WebsocketSubscription({
31
31
  orderSubscriber: this,
32
32
  skipInitialLoad: config.subscriptionConfig.skipInitialLoad,
33
+ resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
33
34
  });
34
35
  }
35
36
  this.eventEmitter = new EventEmitter();
@@ -86,7 +87,12 @@ export class OrderSubscriber {
86
87
  programAccount.account.data[1]
87
88
  );
88
89
  programAccountSet.add(key);
89
- this.tryUpdateUserAccount(key, buffer, slot);
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);
90
96
  }
91
97
 
92
98
  for (const key of this.usersAccounts.keys()) {
@@ -102,14 +108,13 @@ export class OrderSubscriber {
102
108
  }
103
109
  }
104
110
 
105
- tryUpdateUserAccount(key: string, buffer: Buffer, slot: number): void {
111
+ tryUpdateUserAccount(
112
+ key: string,
113
+ userAccount: UserAccount,
114
+ slot: number
115
+ ): void {
106
116
  const slotAndUserAccount = this.usersAccounts.get(key);
107
117
  if (!slotAndUserAccount || slotAndUserAccount.slot < slot) {
108
- const userAccount =
109
- this.driftClient.program.account.user.coder.accounts.decode(
110
- 'User',
111
- buffer
112
- ) as UserAccount;
113
118
  const newOrders = userAccount.orders.filter(
114
119
  (order) =>
115
120
  order.slot.toNumber() > (slotAndUserAccount?.slot ?? 0) &&
@@ -1,42 +1,57 @@
1
1
  import { OrderSubscriber } from './OrderSubscriber';
2
2
  import { getNonIdleUserFilter, getUserFilter } from '../memcmp';
3
+ import { WebSocketProgramAccountSubscriber } from '../accounts/webSocketProgramAccountSubscriber';
4
+ import { UserAccount } from '../types';
5
+ import { Context, PublicKey } from '@solana/web3.js';
3
6
 
4
7
  export class WebsocketSubscription {
5
8
  private orderSubscriber: OrderSubscriber;
6
9
  private skipInitialLoad: boolean;
10
+ private resubTimeoutMs?: number;
7
11
 
8
- private websocketId: number;
12
+ private subscriber: WebSocketProgramAccountSubscriber<UserAccount>;
9
13
 
10
14
  constructor({
11
15
  orderSubscriber,
12
16
  skipInitialLoad = false,
17
+ resubTimeoutMs,
13
18
  }: {
14
19
  orderSubscriber: OrderSubscriber;
15
20
  skipInitialLoad?: boolean;
21
+ resubTimeoutMs?: number;
16
22
  }) {
17
23
  this.orderSubscriber = orderSubscriber;
18
24
  this.skipInitialLoad = skipInitialLoad;
25
+ this.resubTimeoutMs = resubTimeoutMs;
19
26
  }
20
27
 
21
28
  public async subscribe(): Promise<void> {
22
- if (this.websocketId) {
23
- return;
24
- }
25
-
26
- this.websocketId =
27
- this.orderSubscriber.driftClient.connection.onProgramAccountChange(
28
- this.orderSubscriber.driftClient.program.programId,
29
- (keyAccountInfo, context) => {
30
- const userKey = keyAccountInfo.accountId.toBase58();
31
- this.orderSubscriber.tryUpdateUserAccount(
32
- userKey,
33
- keyAccountInfo.accountInfo.data,
34
- context.slot
35
- );
29
+ if (!this.subscriber) {
30
+ this.subscriber = new WebSocketProgramAccountSubscriber<UserAccount>(
31
+ 'OrderSubscriber',
32
+ 'User',
33
+ this.orderSubscriber.driftClient.program,
34
+ this.orderSubscriber.driftClient.program.account.user.coder.accounts.decode.bind(
35
+ this.orderSubscriber.driftClient.program.account.user.coder.accounts
36
+ ),
37
+ {
38
+ filters: [getUserFilter(), getNonIdleUserFilter()],
39
+ commitment: this.orderSubscriber.driftClient.opts.commitment,
36
40
  },
37
- this.orderSubscriber.driftClient.opts.commitment,
38
- [getUserFilter(), getNonIdleUserFilter()]
41
+ this.resubTimeoutMs
39
42
  );
43
+ }
44
+
45
+ await this.subscriber.subscribe(
46
+ (accountId: PublicKey, account: UserAccount, context: Context) => {
47
+ const userKey = accountId.toBase58();
48
+ this.orderSubscriber.tryUpdateUserAccount(
49
+ userKey,
50
+ account,
51
+ context.slot
52
+ );
53
+ }
54
+ );
40
55
 
41
56
  if (!this.skipInitialLoad) {
42
57
  await this.orderSubscriber.fetch();
@@ -44,11 +59,6 @@ export class WebsocketSubscription {
44
59
  }
45
60
 
46
61
  public async unsubscribe(): Promise<void> {
47
- if (this.websocketId) {
48
- await this.orderSubscriber.driftClient.connection.removeProgramAccountChangeListener(
49
- this.websocketId
50
- );
51
- this.websocketId = undefined;
52
- }
62
+ this.subscriber.unsubscribe();
53
63
  }
54
64
  }
@@ -12,6 +12,7 @@ export type OrderSubscriberConfig = {
12
12
  | {
13
13
  type: 'websocket';
14
14
  skipInitialLoad?: boolean;
15
+ resubTimeoutMs?: number;
15
16
  };
16
17
  };
17
18