@drift-labs/sdk 2.48.0-beta.2 → 2.48.0-beta.21

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/bulkAccountLoader.js +1 -1
  3. package/lib/accounts/pollingUserAccountSubscriber.js +14 -7
  4. package/lib/accounts/pollingUserStatsAccountSubscriber.js +14 -7
  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/dlob/orderBookLevels.js +1 -1
  12. package/lib/driftClient.js +4 -4
  13. package/lib/events/eventSubscriber.js +23 -2
  14. package/lib/events/pollingLogProvider.d.ts +1 -1
  15. package/lib/events/pollingLogProvider.js +1 -1
  16. package/lib/events/types.d.ts +9 -2
  17. package/lib/events/webSocketLogProvider.d.ts +15 -3
  18. package/lib/events/webSocketLogProvider.js +66 -7
  19. package/lib/orderSubscriber/OrderSubscriber.d.ts +5 -1
  20. package/lib/orderSubscriber/OrderSubscriber.js +24 -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 +3 -3
  26. package/lib/userMap/userMap.d.ts +16 -4
  27. package/lib/userMap/userMap.js +83 -41
  28. package/lib/userMap/userStatsMap.d.ts +29 -8
  29. package/lib/userMap/userStatsMap.js +46 -41
  30. package/package.json +1 -1
  31. package/src/accounts/bulkAccountLoader.ts +1 -1
  32. package/src/accounts/pollingUserAccountSubscriber.ts +19 -11
  33. package/src/accounts/pollingUserStatsAccountSubscriber.ts +20 -12
  34. package/src/accounts/webSocketAccountSubscriber.ts +1 -1
  35. package/src/accounts/webSocketDriftClientAccountSubscriber.ts +13 -6
  36. package/src/accounts/webSocketProgramAccountSubscriber.ts +1 -1
  37. package/src/accounts/webSocketUserAccountSubscriber.ts +1 -1
  38. package/src/constants/spotMarkets.ts +10 -0
  39. package/src/dlob/orderBookLevels.ts +1 -1
  40. package/src/driftClient.ts +3 -2
  41. package/src/events/eventSubscriber.ts +46 -2
  42. package/src/events/pollingLogProvider.ts +2 -2
  43. package/src/events/types.ts +11 -2
  44. package/src/events/webSocketLogProvider.ts +78 -8
  45. package/src/orderSubscriber/OrderSubscriber.ts +39 -15
  46. package/src/orderSubscriber/WebsocketSubscription.ts +7 -2
  47. package/src/orderSubscriber/types.ts +3 -1
  48. package/src/slot/SlotSubscriber.ts +4 -2
  49. package/src/user.ts +3 -3
  50. package/src/userMap/userMap.ts +139 -66
  51. package/src/userMap/userStatsMap.ts +64 -69
  52. package/tests/amm/test.ts +3 -2
  53. package/tests/dlob/test.ts +8 -5
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.48.0-beta.2
1
+ 2.48.0-beta.21
@@ -139,7 +139,7 @@ class BulkAccountLoader {
139
139
  accountsToLoad.forEach((accountToLoad, j) => {
140
140
  const key = accountToLoad.publicKey.toBase58();
141
141
  const oldRPCResponse = this.bufferAndSlotMap.get(key);
142
- if (oldRPCResponse && newSlot <= oldRPCResponse.slot) {
142
+ if (oldRPCResponse && newSlot < oldRPCResponse.slot) {
143
143
  return;
144
144
  }
145
145
  let newBuffer = undefined;
@@ -53,12 +53,17 @@ class PollingUserAccountSubscriber {
53
53
  }
54
54
  async fetch() {
55
55
  var _a, _b;
56
- await this.accountLoader.load();
57
- const { buffer, slot } = this.accountLoader.getBufferAndSlot(this.userAccountPublicKey);
58
- const currentSlot = (_b = (_a = this.user) === null || _a === void 0 ? void 0 : _a.slot) !== null && _b !== void 0 ? _b : 0;
59
- if (buffer && slot > currentSlot) {
60
- const account = this.program.account.user.coder.accounts.decode('User', buffer);
61
- this.user = { data: account, slot };
56
+ try {
57
+ const dataAndContext = await this.program.account.user.fetchAndContext(this.userAccountPublicKey, this.accountLoader.commitment);
58
+ if (dataAndContext.context.slot > ((_b = (_a = this.user) === null || _a === void 0 ? void 0 : _a.slot) !== null && _b !== void 0 ? _b : 0)) {
59
+ this.user = {
60
+ data: dataAndContext.data,
61
+ slot: dataAndContext.context.slot,
62
+ };
63
+ }
64
+ }
65
+ catch (e) {
66
+ console.log(`PollingUserAccountSubscriber.fetch() UserAccount does not exist: ${e.message}`);
62
67
  }
63
68
  }
64
69
  doesAccountExist() {
@@ -80,7 +85,9 @@ class PollingUserAccountSubscriber {
80
85
  }
81
86
  }
82
87
  getUserAccountAndSlot() {
83
- this.assertIsSubscribed();
88
+ if (!this.doesAccountExist()) {
89
+ throw new types_1.NotSubscribedError('You must call `subscribe` or `fetch` before using this function');
90
+ }
84
91
  return this.user;
85
92
  }
86
93
  updateData(userAccount, slot) {
@@ -53,12 +53,17 @@ class PollingUserStatsAccountSubscriber {
53
53
  }
54
54
  async fetch() {
55
55
  var _a, _b;
56
- await this.accountLoader.load();
57
- const { buffer, slot } = this.accountLoader.getBufferAndSlot(this.userStatsAccountPublicKey);
58
- const currentSlot = (_b = (_a = this.userStats) === null || _a === void 0 ? void 0 : _a.slot) !== null && _b !== void 0 ? _b : 0;
59
- if (buffer && slot > currentSlot) {
60
- const account = this.program.account.userStats.coder.accounts.decodeUnchecked('UserStats', buffer);
61
- this.userStats = { data: account, slot };
56
+ try {
57
+ const dataAndContext = await this.program.account.userStats.fetchAndContext(this.userStatsAccountPublicKey, this.accountLoader.commitment);
58
+ if (dataAndContext.context.slot > ((_b = (_a = this.userStats) === null || _a === void 0 ? void 0 : _a.slot) !== null && _b !== void 0 ? _b : 0)) {
59
+ this.userStats = {
60
+ data: dataAndContext.data,
61
+ slot: dataAndContext.context.slot,
62
+ };
63
+ }
64
+ }
65
+ catch (e) {
66
+ console.log(`PollingUserStatsAccountSubscriber.fetch() UserStatsAccount does not exist: ${e.message}`);
62
67
  }
63
68
  }
64
69
  doesAccountExist() {
@@ -80,7 +85,9 @@ class PollingUserStatsAccountSubscriber {
80
85
  }
81
86
  }
82
87
  getUserStatsAccountAndSlot() {
83
- this.assertIsSubscribed();
88
+ if (!this.doesAccountExist()) {
89
+ throw new types_1.NotSubscribedError('You must call `subscribe` or `fetch` before using this function');
90
+ }
84
91
  return this.userStats;
85
92
  }
86
93
  }
@@ -89,7 +89,7 @@ class WebSocketAccountSubscriber {
89
89
  }
90
90
  return;
91
91
  }
92
- if (newSlot <= this.bufferAndSlot.slot) {
92
+ if (newSlot < this.bufferAndSlot.slot) {
93
93
  return;
94
94
  }
95
95
  const oldBuffer = this.bufferAndSlot.buffer;
@@ -5,12 +5,13 @@ import { SpotMarketAccount, PerpMarketAccount, StateAccount } from '../types';
5
5
  import { Program } from '@coral-xyz/anchor';
6
6
  import StrictEventEmitter from 'strict-event-emitter-types';
7
7
  import { EventEmitter } from 'events';
8
- import { PublicKey } from '@solana/web3.js';
8
+ import { Commitment, PublicKey } from '@solana/web3.js';
9
9
  import { OracleInfo, OraclePriceData } from '../oracles/types';
10
10
  import { OracleClientCache } from '../oracles/oracleClientCache';
11
11
  export declare class WebSocketDriftClientAccountSubscriber implements DriftClientAccountSubscriber {
12
12
  isSubscribed: boolean;
13
13
  program: Program;
14
+ commitment?: Commitment;
14
15
  perpMarketIndexes: number[];
15
16
  spotMarketIndexes: number[];
16
17
  oracleInfos: OracleInfo[];
@@ -25,7 +26,7 @@ export declare class WebSocketDriftClientAccountSubscriber implements DriftClien
25
26
  private isSubscribing;
26
27
  private subscriptionPromise;
27
28
  private subscriptionPromiseResolver;
28
- constructor(program: Program, perpMarketIndexes: number[], spotMarketIndexes: number[], oracleInfos: OracleInfo[], shouldFindAllMarketsAndOracles: boolean, resubTimeoutMs?: number);
29
+ constructor(program: Program, perpMarketIndexes: number[], spotMarketIndexes: number[], oracleInfos: OracleInfo[], shouldFindAllMarketsAndOracles: boolean, resubTimeoutMs?: number, commitment?: Commitment);
29
30
  subscribe(): Promise<boolean>;
30
31
  subscribeToPerpMarketAccounts(): Promise<boolean>;
31
32
  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, resubTimeoutMs) {
13
+ constructor(program, perpMarketIndexes, spotMarketIndexes, oracleInfos, shouldFindAllMarketsAndOracles, resubTimeoutMs, commitment) {
14
14
  this.oracleClientCache = new oracleClientCache_1.OracleClientCache();
15
15
  this.perpMarketAccountSubscribers = new Map();
16
16
  this.spotMarketAccountSubscribers = new Map();
@@ -24,6 +24,7 @@ class WebSocketDriftClientAccountSubscriber {
24
24
  this.oracleInfos = oracleInfos;
25
25
  this.shouldFindAllMarketsAndOracles = shouldFindAllMarketsAndOracles;
26
26
  this.resubTimeoutMs = resubTimeoutMs;
27
+ this.commitment = commitment;
27
28
  }
28
29
  async subscribe() {
29
30
  if (this.isSubscribed) {
@@ -44,7 +45,7 @@ class WebSocketDriftClientAccountSubscriber {
44
45
  }
45
46
  const statePublicKey = await (0, pda_1.getDriftStateAccountPublicKey)(this.program.programId);
46
47
  // create and activate main state account subscription
47
- this.stateAccountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('state', this.program, statePublicKey, undefined, this.resubTimeoutMs);
48
+ this.stateAccountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('state', this.program, statePublicKey, undefined, this.resubTimeoutMs, this.commitment);
48
49
  await this.stateAccountSubscriber.subscribe((data) => {
49
50
  this.eventEmitter.emit('stateAccountUpdate', data);
50
51
  this.eventEmitter.emit('update');
@@ -69,7 +70,7 @@ class WebSocketDriftClientAccountSubscriber {
69
70
  }
70
71
  async subscribeToPerpMarketAccount(marketIndex) {
71
72
  const perpMarketPublicKey = await (0, pda_1.getPerpMarketPublicKey)(this.program.programId, marketIndex);
72
- const accountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('perpMarket', this.program, perpMarketPublicKey, undefined, this.resubTimeoutMs);
73
+ const accountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('perpMarket', this.program, perpMarketPublicKey, undefined, this.resubTimeoutMs, this.commitment);
73
74
  await accountSubscriber.subscribe((data) => {
74
75
  this.eventEmitter.emit('perpMarketAccountUpdate', data);
75
76
  this.eventEmitter.emit('update');
@@ -85,7 +86,7 @@ class WebSocketDriftClientAccountSubscriber {
85
86
  }
86
87
  async subscribeToSpotMarketAccount(marketIndex) {
87
88
  const marketPublicKey = await (0, pda_1.getSpotMarketPublicKey)(this.program.programId, marketIndex);
88
- const accountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('spotMarket', this.program, marketPublicKey, undefined, this.resubTimeoutMs);
89
+ const accountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('spotMarket', this.program, marketPublicKey, undefined, this.resubTimeoutMs, this.commitment);
89
90
  await accountSubscriber.subscribe((data) => {
90
91
  this.eventEmitter.emit('spotMarketAccountUpdate', data);
91
92
  this.eventEmitter.emit('update');
@@ -105,7 +106,7 @@ class WebSocketDriftClientAccountSubscriber {
105
106
  const client = this.oracleClientCache.get(oracleInfo.source, this.program.provider.connection);
106
107
  const accountSubscriber = new webSocketAccountSubscriber_1.WebSocketAccountSubscriber('oracle', this.program, oracleInfo.publicKey, (buffer) => {
107
108
  return client.getOraclePriceDataFromBuffer(buffer);
108
- }, this.resubTimeoutMs);
109
+ }, this.resubTimeoutMs, this.commitment);
109
110
  await accountSubscriber.subscribe((data) => {
110
111
  this.eventEmitter.emit('oraclePriceUpdate', oracleInfo.publicKey, data);
111
112
  this.eventEmitter.emit('update');
@@ -75,7 +75,7 @@ class WebSocketProgramAccountSubscriber {
75
75
  }
76
76
  return;
77
77
  }
78
- if (newSlot <= this.bufferAndSlot.slot) {
78
+ if (newSlot < this.bufferAndSlot.slot) {
79
79
  return;
80
80
  }
81
81
  const oldBuffer = this.bufferAndSlot.buffer;
@@ -51,7 +51,7 @@ class WebSocketUserAccountSubscriber {
51
51
  updateData(userAccount, slot) {
52
52
  var _a;
53
53
  const currentDataSlot = ((_a = this.userDataAccountSubscriber.dataAndSlot) === null || _a === void 0 ? void 0 : _a.slot) || 0;
54
- if (currentDataSlot < slot) {
54
+ if (currentDataSlot <= slot) {
55
55
  this.userDataAccountSubscriber.setData(userAccount, slot);
56
56
  this.eventEmitter.emit('userAccountUpdate', userAccount);
57
57
  this.eventEmitter.emit('update');
@@ -120,6 +120,16 @@ exports.MainnetSpotMarkets = [
120
120
  serumMarket: new web3_js_1.PublicKey('4E17F3BxtNVqzVsirxguuqkpYLtFgCR6NfTpccPh82WE'),
121
121
  phoenixMarket: new web3_js_1.PublicKey('2sTMN9A1D1qeZLF95XQgJCUPiKe5DiV52jLfZGqMP46m'),
122
122
  },
123
+ {
124
+ symbol: 'bSOL',
125
+ marketIndex: 8,
126
+ oracle: new web3_js_1.PublicKey('AFrYBhb5wKQtxRS9UA9YRS4V3dwFm7SqmS6DHKq6YVgo'),
127
+ oracleSource: __1.OracleSource.PYTH,
128
+ mint: new web3_js_1.PublicKey('bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1'),
129
+ precision: new __1.BN(10).pow(numericConstants_1.NINE),
130
+ precisionExp: numericConstants_1.NINE,
131
+ serumMarket: new web3_js_1.PublicKey('ARjaHVxGCQfTvvKjLd7U7srvk6orthZSE6uqWchCczZc'),
132
+ },
123
133
  ];
124
134
  exports.SpotMarkets = {
125
135
  devnet: exports.DevnetSpotMarkets,
@@ -93,7 +93,7 @@ function getVammL2Generator({ marketAccount, oraclePriceData, numOrders, now, to
93
93
  }
94
94
  const updatedAmm = (0, __1.calculateUpdatedAMM)(marketAccount.amm, oraclePriceData);
95
95
  let [openBids, openAsks] = (0, __1.calculateMarketOpenBidAsk)(updatedAmm.baseAssetReserve, updatedAmm.minBaseAssetReserve, updatedAmm.maxBaseAssetReserve, updatedAmm.orderStepSize);
96
- const minOrderSize = marketAccount.amm.orderStepSize;
96
+ const minOrderSize = marketAccount.amm.minOrderSize;
97
97
  if (openBids.lt(minOrderSize.muln(2))) {
98
98
  openBids = __1.ZERO;
99
99
  }
@@ -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, _v, _w, _x, _y, _z;
69
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
70
70
  this.users = new Map();
71
71
  this._isSubscribed = false;
72
72
  this.perpMarketLastSlotCache = new Map();
@@ -143,11 +143,11 @@ class DriftClient {
143
143
  this.accountSubscriber = new pollingDriftClientAccountSubscriber_1.PollingDriftClientAccountSubscriber(this.program, config.accountSubscription.accountLoader, (_s = config.perpMarketIndexes) !== null && _s !== void 0 ? _s : [], (_t = config.spotMarketIndexes) !== null && _t !== void 0 ? _t : [], (_u = config.oracleInfos) !== null && _u !== void 0 ? _u : [], noMarketsAndOraclesSpecified);
144
144
  }
145
145
  else {
146
- this.accountSubscriber = new webSocketDriftClientAccountSubscriber_1.WebSocketDriftClientAccountSubscriber(this.program, (_v = config.perpMarketIndexes) !== null && _v !== void 0 ? _v : [], (_w = config.spotMarketIndexes) !== null && _w !== void 0 ? _w : [], (_x = config.oracleInfos) !== null && _x !== void 0 ? _x : [], noMarketsAndOraclesSpecified, (_y = config.accountSubscription) === null || _y === void 0 ? void 0 : _y.resubTimeoutMs);
146
+ this.accountSubscriber = new webSocketDriftClientAccountSubscriber_1.WebSocketDriftClientAccountSubscriber(this.program, (_v = config.perpMarketIndexes) !== null && _v !== void 0 ? _v : [], (_w = config.spotMarketIndexes) !== null && _w !== void 0 ? _w : [], (_x = config.oracleInfos) !== null && _x !== void 0 ? _x : [], noMarketsAndOraclesSpecified, (_y = config.accountSubscription) === null || _y === void 0 ? void 0 : _y.resubTimeoutMs, (_z = config.accountSubscription) === null || _z === void 0 ? void 0 : _z.commitment);
147
147
  }
148
148
  this.eventEmitter = this.accountSubscriber.eventEmitter;
149
149
  this.txSender =
150
- (_z = config.txSender) !== null && _z !== void 0 ? _z : new retryTxSender_1.RetryTxSender({
150
+ (_0 = config.txSender) !== null && _0 !== void 0 ? _0 : new retryTxSender_1.RetryTxSender({
151
151
  connection: this.connection,
152
152
  wallet: this.wallet,
153
153
  opts: this.opts,
@@ -1150,7 +1150,7 @@ class DriftClient {
1150
1150
  withdrawIxs.push((0, spl_token_1.createCloseAccountInstruction)(associatedTokenAddress, authority, authority, []));
1151
1151
  }
1152
1152
  const tx = await this.buildTransaction(withdrawIxs, {
1153
- computeUnits: 600000,
1153
+ computeUnits: 1400000,
1154
1154
  });
1155
1155
  const { txSig, slot } = await this.sendTransaction(tx, additionalSigners, this.opts);
1156
1156
  this.spotMarketLastSlotCache.set(marketIndex, slot);
@@ -27,7 +27,7 @@ class EventSubscriber {
27
27
  }
28
28
  this.eventEmitter = new events_1.EventEmitter();
29
29
  if (this.options.logProviderConfig.type === 'websocket') {
30
- this.logProvider = new webSocketLogProvider_1.WebSocketLogProvider(this.connection, this.address, this.options.commitment);
30
+ this.logProvider = new webSocketLogProvider_1.WebSocketLogProvider(this.connection, this.address, this.options.commitment, this.options.logProviderConfig.resubTimeoutMs);
31
31
  }
32
32
  else {
33
33
  this.logProvider = new pollingLogProvider_1.PollingLogProvider(this.connection, this.address, options.commitment, this.options.logProviderConfig.frequency, this.options.logProviderConfig.batchSize);
@@ -38,6 +38,27 @@ class EventSubscriber {
38
38
  if (this.logProvider.isSubscribed()) {
39
39
  return true;
40
40
  }
41
+ if (this.options.logProviderConfig.type === 'websocket') {
42
+ if (this.options.logProviderConfig.resubTimeoutMs) {
43
+ if (this.options.logProviderConfig.maxReconnectAttempts &&
44
+ this.options.logProviderConfig.maxReconnectAttempts > 0) {
45
+ const logProviderConfig = this.options
46
+ .logProviderConfig;
47
+ this.logProvider.eventEmitter.on('reconnect', (reconnectAttempts) => {
48
+ if (reconnectAttempts > logProviderConfig.maxReconnectAttempts) {
49
+ console.log('Failing over to polling');
50
+ this.logProvider.eventEmitter.removeAllListeners('reconnect');
51
+ this.unsubscribe().then(() => {
52
+ this.logProvider = new pollingLogProvider_1.PollingLogProvider(this.connection, this.address, this.options.commitment, logProviderConfig.fallbackFrequency, logProviderConfig.fallbackBatchSize);
53
+ this.logProvider.subscribe((txSig, slot, logs, mostRecentBlockTime) => {
54
+ this.handleTxLogs(txSig, slot, logs, mostRecentBlockTime);
55
+ }, true);
56
+ });
57
+ }
58
+ });
59
+ }
60
+ }
61
+ }
41
62
  this.logProvider.subscribe((txSig, slot, logs, mostRecentBlockTime) => {
42
63
  this.handleTxLogs(txSig, slot, logs, mostRecentBlockTime);
43
64
  }, true);
@@ -95,7 +116,7 @@ class EventSubscriber {
95
116
  }
96
117
  }
97
118
  async unsubscribe() {
98
- return await this.logProvider.unsubscribe();
119
+ return await this.logProvider.unsubscribe(true);
99
120
  }
100
121
  parseEventsFromLogs(txSig, slot, logs) {
101
122
  const records = [];
@@ -11,7 +11,7 @@ export declare class PollingLogProvider implements LogProvider {
11
11
  private mutex;
12
12
  private firstFetch;
13
13
  constructor(connection: Connection, address: PublicKey, commitment: Commitment, frequency?: number, batchSize?: number);
14
- subscribe(callback: logProviderCallback, skipHistory?: boolean): boolean;
14
+ subscribe(callback: logProviderCallback, skipHistory?: boolean): Promise<boolean>;
15
15
  isSubscribed(): boolean;
16
16
  unsubscribe(): Promise<boolean>;
17
17
  }
@@ -11,7 +11,7 @@ class PollingLogProvider {
11
11
  this.firstFetch = true;
12
12
  this.finality = commitment === 'finalized' ? 'finalized' : 'confirmed';
13
13
  }
14
- subscribe(callback, skipHistory) {
14
+ async subscribe(callback, skipHistory) {
15
15
  if (this.intervalId) {
16
16
  return true;
17
17
  }
@@ -1,5 +1,7 @@
1
+ /// <reference types="node" />
1
2
  import { Commitment, PublicKey, TransactionSignature } from '@solana/web3.js';
2
3
  import { DepositRecord, FundingPaymentRecord, FundingRateRecord, LiquidationRecord, NewUserRecord, OrderActionRecord, OrderRecord, SettlePnlRecord, LPRecord, InsuranceFundRecord, SpotInterestRecord, InsuranceFundStakeRecord, CurveRecord, SwapRecord } from '../index';
4
+ import { EventEmitter } from 'events';
3
5
  export type EventSubscriptionOptions = {
4
6
  address?: PublicKey;
5
7
  eventTypes?: EventType[];
@@ -48,11 +50,16 @@ export type SortFn = (currentRecord: EventMap[EventType], newRecord: EventMap[Ev
48
50
  export type logProviderCallback = (txSig: TransactionSignature, slot: number, logs: string[], mostRecentBlockTime: number | undefined) => void;
49
51
  export interface LogProvider {
50
52
  isSubscribed(): boolean;
51
- subscribe(callback: logProviderCallback, skipHistory?: boolean): boolean;
52
- unsubscribe(): Promise<boolean>;
53
+ subscribe(callback: logProviderCallback, skipHistory?: boolean): Promise<boolean>;
54
+ unsubscribe(external: boolean): Promise<boolean>;
55
+ eventEmitter?: EventEmitter;
53
56
  }
54
57
  export type WebSocketLogProviderConfig = {
55
58
  type: 'websocket';
59
+ resubTimeoutMs?: number;
60
+ maxReconnectAttempts?: number;
61
+ fallbackFrequency?: number;
62
+ fallbackBatchSize?: number;
56
63
  };
57
64
  export type PollingLogProviderConfig = {
58
65
  type: 'polling';
@@ -1,12 +1,24 @@
1
+ /// <reference types="node" />
1
2
  import { LogProvider, logProviderCallback } from './types';
2
3
  import { Commitment, Connection, PublicKey } from '@solana/web3.js';
4
+ import { EventEmitter } from 'events';
3
5
  export declare class WebSocketLogProvider implements LogProvider {
4
6
  private connection;
5
7
  private address;
6
8
  private commitment;
9
+ private resubTimeoutMs?;
7
10
  private subscriptionId;
8
- constructor(connection: Connection, address: PublicKey, commitment: Commitment);
9
- subscribe(callback: logProviderCallback): boolean;
11
+ private isUnsubscribing;
12
+ private externalUnsubscribe;
13
+ private receivingData;
14
+ private timeoutId?;
15
+ private reconnectAttempts;
16
+ eventEmitter?: EventEmitter;
17
+ private callback?;
18
+ constructor(connection: Connection, address: PublicKey, commitment: Commitment, resubTimeoutMs?: number);
19
+ subscribe(callback: logProviderCallback): Promise<boolean>;
20
+ setSubscription(callback: logProviderCallback): void;
10
21
  isSubscribed(): boolean;
11
- unsubscribe(): Promise<boolean>;
22
+ unsubscribe(external?: boolean): Promise<boolean>;
23
+ private setTimeout;
12
24
  }
@@ -1,30 +1,89 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WebSocketLogProvider = void 0;
4
+ const events_1 = require("events");
4
5
  class WebSocketLogProvider {
5
- constructor(connection, address, commitment) {
6
+ constructor(connection, address, commitment, resubTimeoutMs) {
6
7
  this.connection = connection;
7
8
  this.address = address;
8
9
  this.commitment = commitment;
10
+ this.resubTimeoutMs = resubTimeoutMs;
11
+ this.isUnsubscribing = false;
12
+ this.externalUnsubscribe = false;
13
+ this.receivingData = false;
14
+ this.reconnectAttempts = 0;
15
+ if (this.resubTimeoutMs) {
16
+ this.eventEmitter = new events_1.EventEmitter();
17
+ }
9
18
  }
10
- subscribe(callback) {
19
+ async subscribe(callback) {
11
20
  if (this.subscriptionId) {
12
21
  return true;
13
22
  }
23
+ this.callback = callback;
24
+ try {
25
+ this.setSubscription(callback);
26
+ }
27
+ catch (error) {
28
+ // Sometimes ws connection isn't ready, give it a few secs
29
+ setTimeout(() => this.setSubscription(callback), 2000);
30
+ }
31
+ if (this.resubTimeoutMs) {
32
+ this.setTimeout();
33
+ }
34
+ return true;
35
+ }
36
+ setSubscription(callback) {
14
37
  this.subscriptionId = this.connection.onLogs(this.address, (logs, ctx) => {
38
+ if (this.resubTimeoutMs && !this.isUnsubscribing) {
39
+ this.receivingData = true;
40
+ clearTimeout(this.timeoutId);
41
+ this.setTimeout();
42
+ }
15
43
  callback(logs.signature, ctx.slot, logs.logs, undefined);
16
44
  }, this.commitment);
17
- return true;
18
45
  }
19
46
  isSubscribed() {
20
47
  return this.subscriptionId !== undefined;
21
48
  }
22
- async unsubscribe() {
49
+ async unsubscribe(external = false) {
50
+ this.isUnsubscribing = true;
51
+ this.externalUnsubscribe = external;
52
+ clearTimeout(this.timeoutId);
53
+ this.timeoutId = undefined;
23
54
  if (this.subscriptionId !== undefined) {
24
- await this.connection.removeOnLogsListener(this.subscriptionId);
25
- this.subscriptionId = undefined;
55
+ try {
56
+ await this.connection.removeOnLogsListener(this.subscriptionId);
57
+ this.subscriptionId = undefined;
58
+ this.isUnsubscribing = false;
59
+ return true;
60
+ }
61
+ catch (err) {
62
+ console.log('Error unsubscribing from logs: ', err);
63
+ this.isUnsubscribing = false;
64
+ return false;
65
+ }
26
66
  }
27
- return true;
67
+ else {
68
+ this.isUnsubscribing = false;
69
+ return true;
70
+ }
71
+ }
72
+ setTimeout() {
73
+ this.timeoutId = setTimeout(async () => {
74
+ if (this.isUnsubscribing || this.externalUnsubscribe) {
75
+ // If we are in the process of unsubscribing, do not attempt to resubscribe
76
+ return;
77
+ }
78
+ if (this.receivingData) {
79
+ console.log(`No log data in ${this.resubTimeoutMs}ms, resubscribing on attempt ${this.reconnectAttempts + 1}`);
80
+ await this.unsubscribe();
81
+ this.receivingData = false;
82
+ this.reconnectAttempts++;
83
+ this.eventEmitter.emit('reconnect', this.reconnectAttempts);
84
+ this.subscribe(this.callback);
85
+ }
86
+ }, this.resubTimeoutMs);
28
87
  }
29
88
  }
30
89
  exports.WebSocketLogProvider = WebSocketLogProvider;
@@ -1,6 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import { DriftClient } from '../driftClient';
3
3
  import { UserAccount } from '../types';
4
+ import { Commitment } from '@solana/web3.js';
4
5
  import { DLOB } from '../dlob/DLOB';
5
6
  import { OrderSubscriberConfig, OrderSubscriberEvents } from './types';
6
7
  import { PollingSubscription } from './PollingSubscription';
@@ -14,13 +15,16 @@ export declare class OrderSubscriber {
14
15
  userAccount: UserAccount;
15
16
  }>;
16
17
  subscription: PollingSubscription | WebsocketSubscription;
18
+ commitment: Commitment;
17
19
  eventEmitter: StrictEventEmitter<EventEmitter, OrderSubscriberEvents>;
18
20
  fetchPromise?: Promise<void>;
19
21
  fetchPromiseResolver: () => void;
22
+ mostRecentSlot: number;
20
23
  constructor(config: OrderSubscriberConfig);
21
24
  subscribe(): Promise<void>;
22
25
  fetch(): Promise<void>;
23
- tryUpdateUserAccount(key: string, userAccount: UserAccount, slot: number): void;
26
+ tryUpdateUserAccount(key: string, dataType: 'raw' | 'decoded', data: string[] | UserAccount, slot: number): void;
24
27
  getDLOB(slot: number): Promise<DLOB>;
28
+ getSlot(): number;
25
29
  unsubscribe(): Promise<void>;
26
30
  }
@@ -12,6 +12,7 @@ class OrderSubscriber {
12
12
  constructor(config) {
13
13
  this.usersAccounts = new Map();
14
14
  this.driftClient = config.driftClient;
15
+ this.commitment = config.subscriptionConfig.commitment || 'processed';
15
16
  if (config.subscriptionConfig.type === 'polling') {
16
17
  this.subscription = new PollingSubscription_1.PollingSubscription({
17
18
  orderSubscriber: this,
@@ -21,6 +22,7 @@ class OrderSubscriber {
21
22
  else {
22
23
  this.subscription = new WebsocketSubscription_1.WebsocketSubscription({
23
24
  orderSubscriber: this,
25
+ commitment: this.commitment,
24
26
  skipInitialLoad: config.subscriptionConfig.skipInitialLoad,
25
27
  resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
26
28
  });
@@ -41,7 +43,7 @@ class OrderSubscriber {
41
43
  const rpcRequestArgs = [
42
44
  this.driftClient.program.programId.toBase58(),
43
45
  {
44
- commitment: this.driftClient.opts.commitment,
46
+ commitment: this.commitment,
45
47
  filters: [(0, memcmp_1.getUserFilter)(), (0, memcmp_1.getUserWithOrderFilter)()],
46
48
  encoding: 'base64',
47
49
  withContext: true,
@@ -55,11 +57,8 @@ class OrderSubscriber {
55
57
  const programAccountSet = new Set();
56
58
  for (const programAccount of rpcResponseAndContext.value) {
57
59
  const key = programAccount.pubkey.toString();
58
- // @ts-ignore
59
- const buffer = buffer_1.Buffer.from(programAccount.account.data[0], programAccount.account.data[1]);
60
60
  programAccountSet.add(key);
61
- const userAccount = this.driftClient.program.account.user.coder.accounts.decode('User', buffer);
62
- this.tryUpdateUserAccount(key, userAccount, slot);
61
+ this.tryUpdateUserAccount(key, 'raw', programAccount.account.data, slot);
63
62
  }
64
63
  for (const key of this.usersAccounts.keys()) {
65
64
  if (!programAccountSet.has(key)) {
@@ -75,9 +74,23 @@ class OrderSubscriber {
75
74
  this.fetchPromise = undefined;
76
75
  }
77
76
  }
78
- tryUpdateUserAccount(key, userAccount, slot) {
77
+ tryUpdateUserAccount(key, dataType, data, slot) {
78
+ if (!this.mostRecentSlot || slot > this.mostRecentSlot) {
79
+ this.mostRecentSlot = slot;
80
+ }
79
81
  const slotAndUserAccount = this.usersAccounts.get(key);
80
- if (!slotAndUserAccount || slotAndUserAccount.slot < slot) {
82
+ if (!slotAndUserAccount || slotAndUserAccount.slot <= slot) {
83
+ let userAccount;
84
+ // Polling leads to a lot of redundant decoding, so we only decode if data is from a fresh slot
85
+ if (dataType === 'raw') {
86
+ // @ts-ignore
87
+ const buffer = buffer_1.Buffer.from(data[0], data[1]);
88
+ userAccount =
89
+ this.driftClient.program.account.user.coder.accounts.decodeUnchecked('User', buffer);
90
+ }
91
+ else {
92
+ userAccount = data;
93
+ }
81
94
  const newOrders = userAccount.orders.filter((order) => {
82
95
  var _a;
83
96
  return order.slot.toNumber() > ((_a = slotAndUserAccount === null || slotAndUserAccount === void 0 ? void 0 : slotAndUserAccount.slot) !== null && _a !== void 0 ? _a : 0) &&
@@ -104,6 +117,10 @@ class OrderSubscriber {
104
117
  }
105
118
  return dlob;
106
119
  }
120
+ getSlot() {
121
+ var _a;
122
+ return (_a = this.mostRecentSlot) !== null && _a !== void 0 ? _a : 0;
123
+ }
107
124
  async unsubscribe() {
108
125
  await this.subscription.unsubscribe();
109
126
  }
@@ -1,11 +1,14 @@
1
1
  import { OrderSubscriber } from './OrderSubscriber';
2
+ import { Commitment } from '@solana/web3.js';
2
3
  export declare class WebsocketSubscription {
3
4
  private orderSubscriber;
5
+ private commitment;
4
6
  private skipInitialLoad;
5
7
  private resubTimeoutMs?;
6
8
  private subscriber;
7
- constructor({ orderSubscriber, skipInitialLoad, resubTimeoutMs, }: {
9
+ constructor({ orderSubscriber, commitment, skipInitialLoad, resubTimeoutMs, }: {
8
10
  orderSubscriber: OrderSubscriber;
11
+ commitment: Commitment;
9
12
  skipInitialLoad?: boolean;
10
13
  resubTimeoutMs?: number;
11
14
  });
@@ -4,8 +4,9 @@ exports.WebsocketSubscription = void 0;
4
4
  const memcmp_1 = require("../memcmp");
5
5
  const webSocketProgramAccountSubscriber_1 = require("../accounts/webSocketProgramAccountSubscriber");
6
6
  class WebsocketSubscription {
7
- constructor({ orderSubscriber, skipInitialLoad = false, resubTimeoutMs, }) {
7
+ constructor({ orderSubscriber, commitment, skipInitialLoad = false, resubTimeoutMs, }) {
8
8
  this.orderSubscriber = orderSubscriber;
9
+ this.commitment = commitment;
9
10
  this.skipInitialLoad = skipInitialLoad;
10
11
  this.resubTimeoutMs = resubTimeoutMs;
11
12
  }
@@ -13,12 +14,12 @@ class WebsocketSubscription {
13
14
  if (!this.subscriber) {
14
15
  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
16
  filters: [(0, memcmp_1.getUserFilter)(), (0, memcmp_1.getNonIdleUserFilter)()],
16
- commitment: this.orderSubscriber.driftClient.opts.commitment,
17
+ commitment: this.commitment,
17
18
  }, this.resubTimeoutMs);
18
19
  }
19
20
  await this.subscriber.subscribe((accountId, account, context) => {
20
21
  const userKey = accountId.toBase58();
21
- this.orderSubscriber.tryUpdateUserAccount(userKey, account, context.slot);
22
+ this.orderSubscriber.tryUpdateUserAccount(userKey, 'decoded', account, context.slot);
22
23
  });
23
24
  if (!this.skipInitialLoad) {
24
25
  await this.orderSubscriber.fetch();
@@ -1,4 +1,4 @@
1
- import { PublicKey } from '@solana/web3.js';
1
+ import { Commitment, PublicKey } from '@solana/web3.js';
2
2
  import { Order, UserAccount } from '../types';
3
3
  import { DriftClient } from '../driftClient';
4
4
  export type OrderSubscriberConfig = {
@@ -6,10 +6,12 @@ export type OrderSubscriberConfig = {
6
6
  subscriptionConfig: {
7
7
  type: 'polling';
8
8
  frequency: number;
9
+ commitment?: Commitment;
9
10
  } | {
10
11
  type: 'websocket';
11
12
  skipInitialLoad?: boolean;
12
13
  resubTimeoutMs?: number;
14
+ commitment?: Commitment;
13
15
  };
14
16
  };
15
17
  export interface OrderSubscriberEvents {