@drift-labs/sdk 0.1.18-orders.1 → 0.1.18-orders.5

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 (94) hide show
  1. package/README.md +2 -1
  2. package/lib/accounts/bulkAccountLoader.d.ts +31 -0
  3. package/lib/accounts/bulkAccountLoader.js +175 -0
  4. package/lib/accounts/bulkUserSubscription.d.ts +7 -0
  5. package/lib/accounts/bulkUserSubscription.js +28 -0
  6. package/lib/accounts/pollingClearingHouseAccountSubscriber.d.ts +49 -0
  7. package/lib/accounts/pollingClearingHouseAccountSubscriber.js +228 -0
  8. package/lib/accounts/pollingTokenAccountSubscriber.d.ts +25 -0
  9. package/lib/accounts/pollingTokenAccountSubscriber.js +79 -0
  10. package/lib/accounts/pollingUserAccountSubscriber.d.ts +32 -0
  11. package/lib/accounts/pollingUserAccountSubscriber.js +136 -0
  12. package/lib/accounts/types.d.ts +34 -25
  13. package/lib/accounts/utils.d.ts +1 -0
  14. package/lib/accounts/utils.js +7 -0
  15. package/lib/accounts/webSocketAccountSubscriber.d.ts +6 -2
  16. package/lib/accounts/webSocketAccountSubscriber.js +43 -12
  17. package/lib/accounts/{defaultClearingHouseAccountSubscriber.d.ts → webSocketClearingHouseAccountSubscriber.d.ts} +6 -4
  18. package/lib/accounts/{defaultClearingHouseAccountSubscriber.js → webSocketClearingHouseAccountSubscriber.js} +5 -4
  19. package/lib/accounts/{defaultUserAccountSubscriber.d.ts → webSocketUserAccountSubscriber.d.ts} +3 -1
  20. package/lib/accounts/{defaultUserAccountSubscriber.js → webSocketUserAccountSubscriber.js} +10 -7
  21. package/lib/addresses.d.ts +2 -2
  22. package/lib/addresses.js +4 -4
  23. package/lib/admin.d.ts +3 -0
  24. package/lib/admin.js +61 -13
  25. package/lib/clearingHouse.d.ts +20 -4
  26. package/lib/clearingHouse.js +90 -10
  27. package/lib/clearingHouseUser.d.ts +12 -4
  28. package/lib/clearingHouseUser.js +35 -25
  29. package/lib/config.js +1 -1
  30. package/lib/constants/markets.d.ts +2 -1
  31. package/lib/constants/markets.js +35 -14
  32. package/lib/constants/numericConstants.d.ts +3 -1
  33. package/lib/constants/numericConstants.js +15 -17
  34. package/lib/examples/makeTradeExample.js +1 -1
  35. package/lib/factory/clearingHouse.d.ts +25 -0
  36. package/lib/factory/clearingHouse.js +64 -0
  37. package/lib/factory/clearingHouseUser.d.ts +19 -0
  38. package/lib/factory/clearingHouseUser.js +34 -0
  39. package/lib/idl/clearing_house.json +366 -17
  40. package/lib/index.d.ts +8 -3
  41. package/lib/index.js +9 -3
  42. package/lib/math/conversion.d.ts +1 -1
  43. package/lib/math/funding.js +4 -1
  44. package/lib/math/insuranceFund.d.ts +2 -1
  45. package/lib/math/insuranceFund.js +3 -6
  46. package/lib/math/position.d.ts +2 -1
  47. package/lib/math/position.js +2 -5
  48. package/lib/math/utils.d.ts +2 -1
  49. package/lib/math/utils.js +3 -3
  50. package/lib/mockUSDCFaucet.d.ts +2 -1
  51. package/lib/orderParams.d.ts +3 -3
  52. package/lib/orderParams.js +28 -8
  53. package/lib/orders.d.ts +3 -2
  54. package/lib/orders.js +8 -8
  55. package/lib/token/index.d.ts +3 -0
  56. package/lib/token/index.js +38 -0
  57. package/lib/types.d.ts +20 -8
  58. package/lib/types.js +2 -2
  59. package/package.json +11 -3
  60. package/src/accounts/bulkAccountLoader.ts +215 -0
  61. package/src/accounts/bulkUserSubscription.ts +28 -0
  62. package/src/accounts/pollingClearingHouseAccountSubscriber.ts +326 -0
  63. package/src/accounts/pollingTokenAccountSubscriber.ts +99 -0
  64. package/src/accounts/pollingUserAccountSubscriber.ts +194 -0
  65. package/src/accounts/types.ts +40 -30
  66. package/src/accounts/utils.ts +3 -0
  67. package/src/accounts/webSocketAccountSubscriber.ts +67 -17
  68. package/src/accounts/{defaultClearingHouseAccountSubscriber.ts → webSocketClearingHouseAccountSubscriber.ts} +9 -6
  69. package/src/accounts/{defaultUserAccountSubscriber.ts → webSocketUserAccountSubscriber.ts} +10 -5
  70. package/src/addresses.ts +6 -4
  71. package/src/admin.ts +73 -20
  72. package/src/clearingHouse.ts +125 -25
  73. package/src/clearingHouseUser.ts +28 -8
  74. package/src/config.ts +1 -1
  75. package/src/constants/markets.ts +25 -1
  76. package/src/constants/numericConstants.ts +2 -1
  77. package/src/examples/makeTradeExample.ts +4 -1
  78. package/src/factory/clearingHouse.ts +125 -0
  79. package/src/factory/clearingHouseUser.ts +73 -0
  80. package/src/idl/clearing_house.json +366 -17
  81. package/src/index.ts +8 -3
  82. package/src/math/conversion.ts +1 -1
  83. package/src/math/funding.ts +5 -1
  84. package/src/math/insuranceFund.ts +1 -1
  85. package/src/math/position.ts +1 -1
  86. package/src/math/utils.ts +1 -1
  87. package/src/mockUSDCFaucet.ts +1 -1
  88. package/src/orderParams.ts +30 -7
  89. package/src/orders.ts +13 -7
  90. package/src/token/index.ts +37 -0
  91. package/src/types.ts +17 -6
  92. package/lib/accounts/defaultHistoryAccountSubscriber.d.ts +0 -28
  93. package/lib/accounts/defaultHistoryAccountSubscriber.js +0 -110
  94. package/src/accounts/defaultHistoryAccountSubscriber.ts +0 -176
@@ -0,0 +1,215 @@
1
+ import { Commitment, Connection, PublicKey } from '@solana/web3.js';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import { AccountData } from './types';
4
+
5
+ type AccountToLoad = {
6
+ publicKey: PublicKey;
7
+ callbacks: Map<string, (buffer: Buffer) => void>;
8
+ };
9
+
10
+ const GET_MULTIPLE_ACCOUNTS_CHUNK_SIZE = 99;
11
+
12
+ export class BulkAccountLoader {
13
+ connection: Connection;
14
+ commitment: Commitment;
15
+ pollingFrequency: number;
16
+ accountsToLoad = new Map<string, AccountToLoad>();
17
+ accountData = new Map<string, AccountData>();
18
+ errorCallbacks = new Map<string, (e) => void>();
19
+ intervalId?: NodeJS.Timer;
20
+ // to handle clients spamming load
21
+ loadPromise?: Promise<void>;
22
+ loadPromiseResolver: () => void;
23
+
24
+ public constructor(
25
+ connection: Connection,
26
+ commitment: Commitment,
27
+ pollingFrequency: number
28
+ ) {
29
+ this.connection = connection;
30
+ this.commitment = commitment;
31
+ this.pollingFrequency = pollingFrequency;
32
+ }
33
+
34
+ public addAccount(
35
+ publicKey: PublicKey,
36
+ callback: (buffer: Buffer) => void
37
+ ): string {
38
+ const existingSize = this.accountsToLoad.size;
39
+
40
+ const callbackId = uuidv4();
41
+ const existingAccountToLoad = this.accountsToLoad.get(publicKey.toString());
42
+ if (existingAccountToLoad) {
43
+ existingAccountToLoad.callbacks.set(callbackId, callback);
44
+ } else {
45
+ const callbacks = new Map<string, (buffer: Buffer) => void>();
46
+ callbacks.set(callbackId, callback);
47
+ const newAccountToLoad = {
48
+ publicKey,
49
+ callbacks,
50
+ };
51
+ this.accountsToLoad.set(publicKey.toString(), newAccountToLoad);
52
+ }
53
+
54
+ if (existingSize === 0) {
55
+ this.startPolling();
56
+ }
57
+
58
+ // if a new account needs to be polled, remove the cached loadPromise in case client calls load immediately after
59
+ this.loadPromise = undefined;
60
+
61
+ return callbackId;
62
+ }
63
+
64
+ public removeAccount(publicKey: PublicKey, callbackId: string): void {
65
+ const existingAccountToLoad = this.accountsToLoad.get(publicKey.toString());
66
+ if (existingAccountToLoad) {
67
+ existingAccountToLoad.callbacks.delete(callbackId);
68
+ if (existingAccountToLoad.callbacks.size === 0) {
69
+ this.accountsToLoad.delete(existingAccountToLoad.publicKey.toString());
70
+ }
71
+ }
72
+
73
+ if (this.accountsToLoad.size === 0) {
74
+ this.stopPolling();
75
+ }
76
+ }
77
+
78
+ public addErrorCallbacks(callback: (error: Error) => void): string {
79
+ const callbackId = uuidv4();
80
+ this.errorCallbacks.set(callbackId, callback);
81
+ return callbackId;
82
+ }
83
+
84
+ public removeErrorCallbacks(callbackId: string): void {
85
+ this.errorCallbacks.delete(callbackId);
86
+ }
87
+
88
+ chunks<T>(array: readonly T[], size: number): T[][] {
89
+ return new Array(Math.ceil(array.length / size))
90
+ .fill(null)
91
+ .map((_, index) => index * size)
92
+ .map((begin) => array.slice(begin, begin + size));
93
+ }
94
+
95
+ public async load(): Promise<void> {
96
+ if (this.loadPromise) {
97
+ return this.loadPromise;
98
+ }
99
+ this.loadPromise = new Promise((resolver) => {
100
+ this.loadPromiseResolver = resolver;
101
+ });
102
+
103
+ try {
104
+ const chunks = this.chunks(
105
+ Array.from(this.accountsToLoad.values()),
106
+ GET_MULTIPLE_ACCOUNTS_CHUNK_SIZE
107
+ );
108
+
109
+ await Promise.all(
110
+ chunks.map((chunk) => {
111
+ return this.loadChunk(chunk);
112
+ })
113
+ );
114
+ } catch (e) {
115
+ console.error(`Error in bulkAccountLoader.load()`);
116
+ console.error(e);
117
+ for (const [_, callback] of this.errorCallbacks) {
118
+ callback(e);
119
+ }
120
+ }
121
+
122
+ this.loadPromiseResolver();
123
+ this.loadPromise = undefined;
124
+ }
125
+
126
+ async loadChunk(accountsToLoad: AccountToLoad[]): Promise<void> {
127
+ if (accountsToLoad.length === 0) {
128
+ return;
129
+ }
130
+
131
+ const args = [
132
+ accountsToLoad.map((accountToLoad) => {
133
+ return accountToLoad.publicKey.toBase58();
134
+ }),
135
+ { commitment: this.commitment },
136
+ ];
137
+
138
+ let rpcResponse;
139
+ try {
140
+ // @ts-ignore
141
+ rpcResponse = await this.connection._rpcRequest(
142
+ 'getMultipleAccounts',
143
+ args
144
+ );
145
+ } catch (e) {
146
+ for (const [_, callback] of this.errorCallbacks) {
147
+ callback(e);
148
+ }
149
+ return;
150
+ }
151
+
152
+ const newSlot = rpcResponse.result.context.slot;
153
+
154
+ for (const i in accountsToLoad) {
155
+ const accountToLoad = accountsToLoad[i];
156
+ const key = accountToLoad.publicKey.toString();
157
+ const oldRPCResponse = this.accountData.get(key);
158
+
159
+ let newBuffer: Buffer | undefined = undefined;
160
+ if (rpcResponse.result.value[i]) {
161
+ const raw: string = rpcResponse.result.value[i].data[0];
162
+ const dataType = rpcResponse.result.value[i].data[1];
163
+ newBuffer = Buffer.from(raw, dataType);
164
+ }
165
+
166
+ if (!oldRPCResponse) {
167
+ this.accountData.set(key, {
168
+ slot: newSlot,
169
+ buffer: newBuffer,
170
+ });
171
+ this.handleAccountCallbacks(accountToLoad, newBuffer);
172
+ continue;
173
+ }
174
+
175
+ if (newSlot <= oldRPCResponse.slot) {
176
+ continue;
177
+ }
178
+
179
+ const oldBuffer = oldRPCResponse.buffer;
180
+ if (newBuffer && (!oldBuffer || !newBuffer.equals(oldBuffer))) {
181
+ this.accountData.set(key, {
182
+ slot: newSlot,
183
+ buffer: newBuffer,
184
+ });
185
+ this.handleAccountCallbacks(accountToLoad, newBuffer);
186
+ }
187
+ }
188
+ }
189
+
190
+ handleAccountCallbacks(accountToLoad: AccountToLoad, buffer: Buffer): void {
191
+ for (const [_, callback] of accountToLoad.callbacks) {
192
+ callback(buffer);
193
+ }
194
+ }
195
+
196
+ public getAccountData(publicKey: PublicKey): Buffer | undefined {
197
+ const accountData = this.accountData.get(publicKey.toString());
198
+ return accountData?.buffer;
199
+ }
200
+
201
+ public startPolling(): void {
202
+ if (this.intervalId) {
203
+ return;
204
+ }
205
+
206
+ this.intervalId = setInterval(this.load.bind(this), this.pollingFrequency);
207
+ }
208
+
209
+ public stopPolling(): void {
210
+ if (this.intervalId) {
211
+ clearInterval(this.intervalId);
212
+ this.intervalId = undefined;
213
+ }
214
+ }
215
+ }
@@ -0,0 +1,28 @@
1
+ import { ClearingHouseUser } from '../clearingHouseUser';
2
+ import { BulkAccountLoader } from './bulkAccountLoader';
3
+ import { PollingUserAccountSubscriber } from './pollingUserAccountSubscriber';
4
+
5
+ /**
6
+ * @param users
7
+ * @param accountLoader
8
+ */
9
+ export async function bulkPollingUserSubscribe(
10
+ users: ClearingHouseUser[],
11
+ accountLoader: BulkAccountLoader
12
+ ): Promise<void> {
13
+ await Promise.all(
14
+ users.map((user) => {
15
+ return (
16
+ user.accountSubscriber as PollingUserAccountSubscriber
17
+ ).addToAccountLoader();
18
+ })
19
+ );
20
+
21
+ await accountLoader.load();
22
+
23
+ await Promise.all(
24
+ users.map(async (user) => {
25
+ return user.subscribe();
26
+ })
27
+ );
28
+ }
@@ -0,0 +1,326 @@
1
+ import {
2
+ AccountToPoll,
3
+ ClearingHouseAccountEvents,
4
+ ClearingHouseAccountSubscriber,
5
+ ClearingHouseAccountTypes,
6
+ NotSubscribedError,
7
+ } from './types';
8
+ import { Program } from '@project-serum/anchor';
9
+ import StrictEventEmitter from 'strict-event-emitter-types';
10
+ import { EventEmitter } from 'events';
11
+ import {
12
+ DepositHistoryAccount,
13
+ ExtendedCurveHistoryAccount,
14
+ FundingPaymentHistoryAccount,
15
+ FundingRateHistoryAccount,
16
+ LiquidationHistoryAccount,
17
+ MarketsAccount,
18
+ OrderHistoryAccount,
19
+ OrderStateAccount,
20
+ StateAccount,
21
+ TradeHistoryAccount,
22
+ } from '../types';
23
+ import { getClearingHouseStateAccountPublicKey } from '../addresses';
24
+ import { BulkAccountLoader } from './bulkAccountLoader';
25
+ import { capitalize } from './utils';
26
+ import { ClearingHouseConfigType } from '../factory/clearingHouse';
27
+
28
+ export class PollingClearingHouseAccountSubscriber
29
+ implements ClearingHouseAccountSubscriber
30
+ {
31
+ isSubscribed: boolean;
32
+ program: Program;
33
+ eventEmitter: StrictEventEmitter<EventEmitter, ClearingHouseAccountEvents>;
34
+
35
+ accountLoader: BulkAccountLoader;
36
+ accountsToPoll = new Map<string, AccountToPoll>();
37
+ errorCallbackId?: string;
38
+
39
+ state?: StateAccount;
40
+ markets?: MarketsAccount;
41
+ orderState?: OrderStateAccount;
42
+ tradeHistory?: TradeHistoryAccount;
43
+ depositHistory?: DepositHistoryAccount;
44
+ fundingPaymentHistory?: FundingPaymentHistoryAccount;
45
+ fundingRateHistory?: FundingRateHistoryAccount;
46
+ liquidationHistory?: LiquidationHistoryAccount;
47
+ extendedCurveHistory: ExtendedCurveHistoryAccount;
48
+ orderHistory?: OrderHistoryAccount;
49
+
50
+ optionalExtraSubscriptions: ClearingHouseAccountTypes[] = [];
51
+
52
+ type: ClearingHouseConfigType = 'polling';
53
+
54
+ private isSubscribing = false;
55
+ private subscriptionPromise: Promise<boolean>;
56
+ private subscriptionPromiseResolver: (val: boolean) => void;
57
+
58
+ public constructor(program: Program, accountLoader: BulkAccountLoader) {
59
+ this.isSubscribed = false;
60
+ this.program = program;
61
+ this.eventEmitter = new EventEmitter();
62
+ this.accountLoader = accountLoader;
63
+ }
64
+
65
+ public async subscribe(
66
+ optionalSubscriptions?: ClearingHouseAccountTypes[]
67
+ ): Promise<boolean> {
68
+ if (this.isSubscribed) {
69
+ return true;
70
+ }
71
+
72
+ if (this.isSubscribing) {
73
+ return await this.subscriptionPromise;
74
+ }
75
+
76
+ this.optionalExtraSubscriptions = optionalSubscriptions;
77
+
78
+ this.isSubscribing = true;
79
+
80
+ this.subscriptionPromise = new Promise((res) => {
81
+ this.subscriptionPromiseResolver = res;
82
+ });
83
+
84
+ await this.updateAccountsToPoll();
85
+ await this.addToAccountLoader();
86
+ await this.fetch();
87
+ this.eventEmitter.emit('update');
88
+
89
+ this.isSubscribing = false;
90
+ this.isSubscribed = true;
91
+ this.subscriptionPromiseResolver(true);
92
+
93
+ return true;
94
+ }
95
+
96
+ async updateAccountsToPoll(): Promise<void> {
97
+ if (this.accountsToPoll.size > 0) {
98
+ return;
99
+ }
100
+
101
+ const statePublicKey = await getClearingHouseStateAccountPublicKey(
102
+ this.program.programId
103
+ );
104
+
105
+ const state = (await this.program.account.state.fetch(
106
+ statePublicKey
107
+ )) as StateAccount;
108
+
109
+ this.accountsToPoll.set(statePublicKey.toString(), {
110
+ key: 'state',
111
+ publicKey: statePublicKey,
112
+ eventType: 'stateAccountUpdate',
113
+ });
114
+
115
+ this.accountsToPoll.set(state.markets.toString(), {
116
+ key: 'markets',
117
+ publicKey: state.markets,
118
+ eventType: 'marketsAccountUpdate',
119
+ });
120
+
121
+ this.accountsToPoll.set(state.orderState.toString(), {
122
+ key: 'orderState',
123
+ publicKey: state.orderState,
124
+ eventType: 'orderStateAccountUpdate',
125
+ });
126
+
127
+ if (this.optionalExtraSubscriptions?.includes('tradeHistoryAccount')) {
128
+ this.accountsToPoll.set(state.tradeHistory.toString(), {
129
+ key: 'tradeHistory',
130
+ publicKey: state.tradeHistory,
131
+ eventType: 'tradeHistoryAccountUpdate',
132
+ });
133
+ }
134
+
135
+ if (this.optionalExtraSubscriptions?.includes('depositHistoryAccount')) {
136
+ this.accountsToPoll.set(state.depositHistory.toString(), {
137
+ key: 'depositHistory',
138
+ publicKey: state.depositHistory,
139
+ eventType: 'depositHistoryAccountUpdate',
140
+ });
141
+ }
142
+
143
+ if (
144
+ this.optionalExtraSubscriptions?.includes('fundingPaymentHistoryAccount')
145
+ ) {
146
+ this.accountsToPoll.set(state.fundingPaymentHistory.toString(), {
147
+ key: 'fundingPaymentHistory',
148
+ publicKey: state.fundingPaymentHistory,
149
+ eventType: 'fundingPaymentHistoryAccountUpdate',
150
+ });
151
+ }
152
+
153
+ if (
154
+ this.optionalExtraSubscriptions?.includes('fundingRateHistoryAccount')
155
+ ) {
156
+ this.accountsToPoll.set(state.fundingRateHistory.toString(), {
157
+ key: 'fundingRateHistory',
158
+ publicKey: state.fundingRateHistory,
159
+ eventType: 'fundingRateHistoryAccountUpdate',
160
+ });
161
+ }
162
+
163
+ if (this.optionalExtraSubscriptions?.includes('curveHistoryAccount')) {
164
+ this.accountsToPoll.set(state.extendedCurveHistory.toString(), {
165
+ key: 'extendedCurveHistory',
166
+ publicKey: state.extendedCurveHistory,
167
+ eventType: 'curveHistoryAccountUpdate',
168
+ });
169
+ }
170
+
171
+ if (
172
+ this.optionalExtraSubscriptions?.includes('liquidationHistoryAccount')
173
+ ) {
174
+ this.accountsToPoll.set(state.liquidationHistory.toString(), {
175
+ key: 'liquidationHistory',
176
+ publicKey: state.liquidationHistory,
177
+ eventType: 'liquidationHistoryAccountUpdate',
178
+ });
179
+ }
180
+
181
+ if (this.optionalExtraSubscriptions?.includes('orderHistoryAccount')) {
182
+ const orderState = (await this.program.account.orderState.fetch(
183
+ state.orderState
184
+ )) as OrderStateAccount;
185
+
186
+ this.accountsToPoll.set(orderState.orderHistory.toString(), {
187
+ key: 'orderHistory',
188
+ publicKey: orderState.orderHistory,
189
+ eventType: 'orderHistoryAccountUpdate',
190
+ });
191
+ }
192
+ }
193
+
194
+ async addToAccountLoader(): Promise<void> {
195
+ for (const [_, accountToPoll] of this.accountsToPoll) {
196
+ accountToPoll.callbackId = this.accountLoader.addAccount(
197
+ accountToPoll.publicKey,
198
+ (buffer) => {
199
+ const account = this.program.account[
200
+ accountToPoll.key
201
+ ].coder.accounts.decode(capitalize(accountToPoll.key), buffer);
202
+ this[accountToPoll.key] = account;
203
+ // @ts-ignore
204
+ this.eventEmitter.emit(accountToPoll.eventType, account);
205
+ this.eventEmitter.emit('update');
206
+ }
207
+ );
208
+ }
209
+
210
+ this.errorCallbackId = this.accountLoader.addErrorCallbacks((error) => {
211
+ this.eventEmitter.emit('error', error);
212
+ });
213
+ }
214
+
215
+ public async fetch(): Promise<void> {
216
+ await this.accountLoader.load();
217
+ for (const [_, accountToPoll] of this.accountsToPoll) {
218
+ const buffer = this.accountLoader.getAccountData(accountToPoll.publicKey);
219
+ if (buffer) {
220
+ this[accountToPoll.key] = this.program.account[
221
+ accountToPoll.key
222
+ ].coder.accounts.decode(capitalize(accountToPoll.key), buffer);
223
+ }
224
+ }
225
+ }
226
+
227
+ public async unsubscribe(): Promise<void> {
228
+ if (!this.isSubscribed) {
229
+ return;
230
+ }
231
+
232
+ for (const [_, accountToPoll] of this.accountsToPoll) {
233
+ this.accountLoader.removeAccount(
234
+ accountToPoll.publicKey,
235
+ accountToPoll.callbackId
236
+ );
237
+ }
238
+
239
+ this.accountLoader.removeErrorCallbacks(this.errorCallbackId);
240
+ this.errorCallbackId = undefined;
241
+
242
+ this.accountsToPoll.clear();
243
+ this.isSubscribed = false;
244
+ }
245
+
246
+ assertIsSubscribed(): void {
247
+ if (!this.isSubscribed) {
248
+ throw new NotSubscribedError(
249
+ 'You must call `subscribe` before using this function'
250
+ );
251
+ }
252
+ }
253
+
254
+ assertOptionalIsSubscribed(
255
+ optionalSubscription: ClearingHouseAccountTypes
256
+ ): void {
257
+ if (!this.isSubscribed) {
258
+ throw new NotSubscribedError(
259
+ 'You must call `subscribe` before using this function'
260
+ );
261
+ }
262
+
263
+ if (!this.optionalExtraSubscriptions.includes(optionalSubscription)) {
264
+ throw new NotSubscribedError(
265
+ `You need to subscribe to the optional Clearing House account "${optionalSubscription}" to use this method`
266
+ );
267
+ }
268
+ }
269
+
270
+ public getStateAccount(): StateAccount {
271
+ this.assertIsSubscribed();
272
+ return this.state;
273
+ }
274
+
275
+ public getMarketsAccount(): MarketsAccount {
276
+ this.assertIsSubscribed();
277
+ return this.markets;
278
+ }
279
+
280
+ public getOrderStateAccount(): OrderStateAccount {
281
+ this.assertIsSubscribed();
282
+ return this.orderState;
283
+ }
284
+
285
+ public getTradeHistoryAccount(): TradeHistoryAccount {
286
+ this.assertIsSubscribed();
287
+ this.assertOptionalIsSubscribed('tradeHistoryAccount');
288
+ return this.tradeHistory;
289
+ }
290
+
291
+ public getDepositHistoryAccount(): DepositHistoryAccount {
292
+ this.assertIsSubscribed();
293
+ this.assertOptionalIsSubscribed('depositHistoryAccount');
294
+ return this.depositHistory;
295
+ }
296
+
297
+ public getFundingPaymentHistoryAccount(): FundingPaymentHistoryAccount {
298
+ this.assertIsSubscribed();
299
+ this.assertOptionalIsSubscribed('fundingPaymentHistoryAccount');
300
+ return this.fundingPaymentHistory;
301
+ }
302
+
303
+ public getFundingRateHistoryAccount(): FundingRateHistoryAccount {
304
+ this.assertIsSubscribed();
305
+ this.assertOptionalIsSubscribed('fundingRateHistoryAccount');
306
+ return this.fundingRateHistory;
307
+ }
308
+
309
+ public getCurveHistoryAccount(): ExtendedCurveHistoryAccount {
310
+ this.assertIsSubscribed();
311
+ this.assertOptionalIsSubscribed('curveHistoryAccount');
312
+ return this.extendedCurveHistory;
313
+ }
314
+
315
+ public getLiquidationHistoryAccount(): LiquidationHistoryAccount {
316
+ this.assertIsSubscribed();
317
+ this.assertOptionalIsSubscribed('liquidationHistoryAccount');
318
+ return this.liquidationHistory;
319
+ }
320
+
321
+ public getOrderHistoryAccount(): OrderHistoryAccount {
322
+ this.assertIsSubscribed();
323
+ this.assertOptionalIsSubscribed('orderHistoryAccount');
324
+ return this.orderHistory;
325
+ }
326
+ }
@@ -0,0 +1,99 @@
1
+ import {
2
+ NotSubscribedError,
3
+ TokenAccountEvents,
4
+ TokenAccountSubscriber,
5
+ } from './types';
6
+ import { Program } from '@project-serum/anchor';
7
+ import StrictEventEmitter from 'strict-event-emitter-types';
8
+ import { EventEmitter } from 'events';
9
+ import { PublicKey } from '@solana/web3.js';
10
+ import { BulkAccountLoader } from './bulkAccountLoader';
11
+ import { AccountInfo } from '@solana/spl-token';
12
+ import { parseTokenAccount } from '../token';
13
+
14
+ export class PollingTokenAccountSubscriber implements TokenAccountSubscriber {
15
+ isSubscribed: boolean;
16
+ program: Program;
17
+ eventEmitter: StrictEventEmitter<EventEmitter, TokenAccountEvents>;
18
+ publicKey: PublicKey;
19
+
20
+ accountLoader: BulkAccountLoader;
21
+ callbackId?: string;
22
+ errorCallbackId?: string;
23
+
24
+ tokenAccount?: AccountInfo;
25
+
26
+ public constructor(publicKey: PublicKey, accountLoader: BulkAccountLoader) {
27
+ this.isSubscribed = false;
28
+ this.publicKey = publicKey;
29
+ this.accountLoader = accountLoader;
30
+ this.eventEmitter = new EventEmitter();
31
+ }
32
+
33
+ async subscribe(): Promise<boolean> {
34
+ if (this.isSubscribed) {
35
+ return true;
36
+ }
37
+
38
+ this.addToAccountLoader();
39
+ await this.fetch();
40
+ this.eventEmitter.emit('update');
41
+
42
+ this.isSubscribed = true;
43
+ return true;
44
+ }
45
+
46
+ addToAccountLoader(): void {
47
+ if (this.callbackId) {
48
+ return;
49
+ }
50
+
51
+ this.callbackId = this.accountLoader.addAccount(
52
+ this.publicKey,
53
+ (buffer) => {
54
+ const tokenAccount = parseTokenAccount(buffer);
55
+ this.tokenAccount = tokenAccount;
56
+ // @ts-ignore
57
+ this.eventEmitter.emit('tokenAccountUpdate', tokenAccount);
58
+ this.eventEmitter.emit('update');
59
+ }
60
+ );
61
+
62
+ this.errorCallbackId = this.accountLoader.addErrorCallbacks((error) => {
63
+ this.eventEmitter.emit('error', error);
64
+ });
65
+ }
66
+
67
+ async fetch(): Promise<void> {
68
+ await this.accountLoader.load();
69
+ const buffer = this.accountLoader.getAccountData(this.publicKey);
70
+ this.tokenAccount = parseTokenAccount(buffer);
71
+ }
72
+
73
+ async unsubscribe(): Promise<void> {
74
+ if (!this.isSubscribed) {
75
+ return;
76
+ }
77
+
78
+ this.accountLoader.removeAccount(this.publicKey, this.callbackId);
79
+ this.callbackId = undefined;
80
+
81
+ this.accountLoader.removeErrorCallbacks(this.errorCallbackId);
82
+ this.errorCallbackId = undefined;
83
+
84
+ this.isSubscribed = false;
85
+ }
86
+
87
+ assertIsSubscribed(): void {
88
+ if (!this.isSubscribed) {
89
+ throw new NotSubscribedError(
90
+ 'You must call `subscribe` before using this function'
91
+ );
92
+ }
93
+ }
94
+
95
+ public getTokenAccount(): AccountInfo {
96
+ this.assertIsSubscribed();
97
+ return this.tokenAccount;
98
+ }
99
+ }