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