@drift-labs/sdk 2.142.0-beta.0 → 2.142.0-beta.2
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/bun.lock +25 -10
- package/lib/browser/accounts/grpcAccountSubscriber.d.ts +2 -1
- package/lib/browser/accounts/grpcAccountSubscriber.js +4 -2
- package/lib/browser/accounts/grpcDriftClientAccountSubscriber.d.ts +1 -1
- package/lib/browser/accounts/grpcDriftClientAccountSubscriber.js +3 -3
- package/lib/browser/accounts/grpcDriftClientAccountSubscriberV2.d.ts +17 -0
- package/lib/browser/accounts/grpcDriftClientAccountSubscriberV2.js +161 -0
- package/lib/browser/accounts/grpcMultiAccountSubscriber.d.ts +30 -0
- package/lib/browser/accounts/grpcMultiAccountSubscriber.js +271 -0
- package/lib/browser/driftClient.js +11 -10
- package/lib/browser/driftClientConfig.d.ts +3 -0
- package/lib/browser/index.d.ts +1 -0
- package/lib/browser/index.js +1 -0
- package/lib/node/accounts/grpcAccountSubscriber.d.ts +2 -1
- package/lib/node/accounts/grpcAccountSubscriber.d.ts.map +1 -1
- package/lib/node/accounts/grpcAccountSubscriber.js +4 -2
- package/lib/node/accounts/grpcDriftClientAccountSubscriber.d.ts +1 -1
- package/lib/node/accounts/grpcDriftClientAccountSubscriber.js +3 -3
- package/lib/node/accounts/grpcDriftClientAccountSubscriberV2.d.ts +18 -0
- package/lib/node/accounts/grpcDriftClientAccountSubscriberV2.d.ts.map +1 -0
- package/lib/node/accounts/grpcDriftClientAccountSubscriberV2.js +161 -0
- package/lib/node/accounts/grpcMultiAccountSubscriber.d.ts +31 -0
- package/lib/node/accounts/grpcMultiAccountSubscriber.d.ts.map +1 -0
- package/lib/node/accounts/grpcMultiAccountSubscriber.js +271 -0
- package/lib/node/driftClient.d.ts.map +1 -1
- package/lib/node/driftClient.js +11 -10
- package/lib/node/driftClientConfig.d.ts +3 -0
- package/lib/node/driftClientConfig.d.ts.map +1 -1
- package/lib/node/index.d.ts +1 -0
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +1 -0
- package/lib/node/isomorphic/grpc.d.ts +5 -3
- package/lib/node/isomorphic/grpc.js +1 -3
- package/lib/node/isomorphic/grpc.node.d.ts +5 -3
- package/lib/node/isomorphic/grpc.node.d.ts.map +1 -1
- package/lib/node/isomorphic/grpc.node.js +1 -3
- package/package.json +3 -3
- package/scripts/client-test.ts +87 -0
- package/src/accounts/grpcAccountSubscriber.ts +9 -6
- package/src/accounts/grpcDriftClientAccountSubscriber.ts +1 -1
- package/src/accounts/grpcDriftClientAccountSubscriberV2.ts +294 -0
- package/src/accounts/grpcMultiAccountSubscriber.ts +328 -0
- package/src/driftClient.ts +5 -2
- package/src/driftClientConfig.ts +13 -0
- package/src/index.ts +1 -0
- package/src/isomorphic/grpc.node.ts +11 -7
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { WebSocketDriftClientAccountSubscriber } from './webSocketDriftClientAccountSubscriber';
|
|
2
|
+
import { OracleInfo, OraclePriceData } from '../oracles/types';
|
|
3
|
+
import { Program } from '@coral-xyz/anchor';
|
|
4
|
+
import { PublicKey } from '@solana/web3.js';
|
|
5
|
+
import { findAllMarketAndOracles } from '../config';
|
|
6
|
+
import {
|
|
7
|
+
getDriftStateAccountPublicKey,
|
|
8
|
+
getPerpMarketPublicKey,
|
|
9
|
+
getSpotMarketPublicKey,
|
|
10
|
+
} from '../addresses/pda';
|
|
11
|
+
import { DelistedMarketSetting, GrpcConfigs, ResubOpts } from './types';
|
|
12
|
+
import { grpcAccountSubscriber } from './grpcAccountSubscriber';
|
|
13
|
+
import { grpcMultiAccountSubscriber } from './grpcMultiAccountSubscriber';
|
|
14
|
+
import { PerpMarketAccount, SpotMarketAccount, StateAccount } from '../types';
|
|
15
|
+
import { getOracleId } from '../oracles/oracleId';
|
|
16
|
+
|
|
17
|
+
export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAccountSubscriber {
|
|
18
|
+
private grpcConfigs: GrpcConfigs;
|
|
19
|
+
private perpMarketsSubscriber?: grpcMultiAccountSubscriber<PerpMarketAccount>;
|
|
20
|
+
private spotMarketsSubscriber?: grpcMultiAccountSubscriber<SpotMarketAccount>;
|
|
21
|
+
private oracleMultiSubscriber?: grpcMultiAccountSubscriber<OraclePriceData>;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
grpcConfigs: GrpcConfigs,
|
|
25
|
+
program: Program,
|
|
26
|
+
perpMarketIndexes: number[],
|
|
27
|
+
spotMarketIndexes: number[],
|
|
28
|
+
oracleInfos: OracleInfo[],
|
|
29
|
+
shouldFindAllMarketsAndOracles: boolean,
|
|
30
|
+
delistedMarketSetting: DelistedMarketSetting,
|
|
31
|
+
resubOpts?: ResubOpts
|
|
32
|
+
) {
|
|
33
|
+
super(
|
|
34
|
+
program,
|
|
35
|
+
perpMarketIndexes,
|
|
36
|
+
spotMarketIndexes,
|
|
37
|
+
oracleInfos,
|
|
38
|
+
shouldFindAllMarketsAndOracles,
|
|
39
|
+
delistedMarketSetting,
|
|
40
|
+
resubOpts
|
|
41
|
+
);
|
|
42
|
+
this.grpcConfigs = grpcConfigs;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public async subscribe(): Promise<boolean> {
|
|
46
|
+
if (this.isSubscribed) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (this.isSubscribing) {
|
|
51
|
+
return await this.subscriptionPromise;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.isSubscribing = true;
|
|
55
|
+
|
|
56
|
+
this.subscriptionPromise = new Promise((res) => {
|
|
57
|
+
this.subscriptionPromiseResolver = res;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (this.shouldFindAllMarketsAndOracles) {
|
|
61
|
+
const {
|
|
62
|
+
perpMarketIndexes,
|
|
63
|
+
perpMarketAccounts,
|
|
64
|
+
spotMarketIndexes,
|
|
65
|
+
spotMarketAccounts,
|
|
66
|
+
oracleInfos,
|
|
67
|
+
} = await findAllMarketAndOracles(this.program);
|
|
68
|
+
this.perpMarketIndexes = perpMarketIndexes;
|
|
69
|
+
this.spotMarketIndexes = spotMarketIndexes;
|
|
70
|
+
this.oracleInfos = oracleInfos;
|
|
71
|
+
// front run and set the initial data here to save extra gma call in set initial data
|
|
72
|
+
this.initialPerpMarketAccountData = new Map(
|
|
73
|
+
perpMarketAccounts.map((market) => [market.marketIndex, market])
|
|
74
|
+
);
|
|
75
|
+
this.initialSpotMarketAccountData = new Map(
|
|
76
|
+
spotMarketAccounts.map((market) => [market.marketIndex, market])
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const statePublicKey = await getDriftStateAccountPublicKey(
|
|
81
|
+
this.program.programId
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// create and activate main state account subscription
|
|
85
|
+
this.stateAccountSubscriber =
|
|
86
|
+
await grpcAccountSubscriber.create<StateAccount>(
|
|
87
|
+
this.grpcConfigs,
|
|
88
|
+
'state',
|
|
89
|
+
this.program,
|
|
90
|
+
statePublicKey,
|
|
91
|
+
undefined,
|
|
92
|
+
undefined
|
|
93
|
+
);
|
|
94
|
+
await this.stateAccountSubscriber.subscribe((data: StateAccount) => {
|
|
95
|
+
this.eventEmitter.emit('stateAccountUpdate', data);
|
|
96
|
+
this.eventEmitter.emit('update');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// set initial data to avoid spamming getAccountInfo calls in webSocketAccountSubscriber
|
|
100
|
+
await this.setInitialData();
|
|
101
|
+
|
|
102
|
+
// subscribe to perp + spot markets (separate) and oracles
|
|
103
|
+
await Promise.all([
|
|
104
|
+
this.subscribeToPerpMarketAccounts(),
|
|
105
|
+
this.subscribeToSpotMarketAccounts(),
|
|
106
|
+
this.subscribeToOracles(),
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
this.eventEmitter.emit('update');
|
|
110
|
+
|
|
111
|
+
await this.handleDelistedMarkets();
|
|
112
|
+
|
|
113
|
+
await Promise.all([this.setPerpOracleMap(), this.setSpotOracleMap()]);
|
|
114
|
+
|
|
115
|
+
this.subscriptionPromiseResolver(true);
|
|
116
|
+
|
|
117
|
+
this.isSubscribing = false;
|
|
118
|
+
this.isSubscribed = true;
|
|
119
|
+
|
|
120
|
+
// delete initial data
|
|
121
|
+
this.removeInitialData();
|
|
122
|
+
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
override async subscribeToPerpMarketAccounts(): Promise<boolean> {
|
|
127
|
+
const perpMarketPubkeys = await Promise.all(
|
|
128
|
+
this.perpMarketIndexes.map((marketIndex) =>
|
|
129
|
+
getPerpMarketPublicKey(this.program.programId, marketIndex)
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
this.perpMarketsSubscriber =
|
|
134
|
+
await grpcMultiAccountSubscriber.create<PerpMarketAccount>(
|
|
135
|
+
this.grpcConfigs,
|
|
136
|
+
'perpMarket',
|
|
137
|
+
this.program,
|
|
138
|
+
undefined,
|
|
139
|
+
this.resubOpts,
|
|
140
|
+
undefined,
|
|
141
|
+
async () => {
|
|
142
|
+
try {
|
|
143
|
+
if (this.resubOpts?.logResubMessages) {
|
|
144
|
+
console.log(
|
|
145
|
+
'[grpcDriftClientAccountSubscriberV2] perp markets subscriber unsubscribed; resubscribing'
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
await this.subscribeToPerpMarketAccounts();
|
|
149
|
+
} catch (e) {
|
|
150
|
+
console.error('Perp markets resubscribe failed:', e);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
await this.perpMarketsSubscriber.subscribe(
|
|
155
|
+
perpMarketPubkeys,
|
|
156
|
+
(_accountId, data) => {
|
|
157
|
+
this.eventEmitter.emit(
|
|
158
|
+
'perpMarketAccountUpdate',
|
|
159
|
+
data as PerpMarketAccount
|
|
160
|
+
);
|
|
161
|
+
this.eventEmitter.emit('update');
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
override async subscribeToSpotMarketAccounts(): Promise<boolean> {
|
|
169
|
+
const spotMarketPubkeys = await Promise.all(
|
|
170
|
+
this.spotMarketIndexes.map((marketIndex) =>
|
|
171
|
+
getSpotMarketPublicKey(this.program.programId, marketIndex)
|
|
172
|
+
)
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
this.spotMarketsSubscriber =
|
|
176
|
+
await grpcMultiAccountSubscriber.create<SpotMarketAccount>(
|
|
177
|
+
this.grpcConfigs,
|
|
178
|
+
'spotMarket',
|
|
179
|
+
this.program,
|
|
180
|
+
undefined,
|
|
181
|
+
this.resubOpts,
|
|
182
|
+
undefined,
|
|
183
|
+
async () => {
|
|
184
|
+
try {
|
|
185
|
+
if (this.resubOpts?.logResubMessages) {
|
|
186
|
+
console.log(
|
|
187
|
+
'[grpcDriftClientAccountSubscriberV2] spot markets subscriber unsubscribed; resubscribing'
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
await this.subscribeToSpotMarketAccounts();
|
|
191
|
+
} catch (e) {
|
|
192
|
+
console.error('Spot markets resubscribe failed:', e);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
await this.spotMarketsSubscriber.subscribe(
|
|
197
|
+
spotMarketPubkeys,
|
|
198
|
+
(_accountId, data) => {
|
|
199
|
+
this.eventEmitter.emit(
|
|
200
|
+
'spotMarketAccountUpdate',
|
|
201
|
+
data as SpotMarketAccount
|
|
202
|
+
);
|
|
203
|
+
this.eventEmitter.emit('update');
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
override async subscribeToOracles(): Promise<boolean> {
|
|
211
|
+
// Build list of unique oracle pubkeys and a lookup for sources
|
|
212
|
+
const uniqueOraclePubkeys = new Map<string, OracleInfo>();
|
|
213
|
+
for (const info of this.oracleInfos) {
|
|
214
|
+
const id = getOracleId(info.publicKey, info.source);
|
|
215
|
+
if (
|
|
216
|
+
!uniqueOraclePubkeys.has(id) &&
|
|
217
|
+
!info.publicKey.equals((PublicKey as any).default)
|
|
218
|
+
) {
|
|
219
|
+
uniqueOraclePubkeys.set(id, info);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const oraclePubkeys = Array.from(uniqueOraclePubkeys.values()).map(
|
|
224
|
+
(i) => i.publicKey
|
|
225
|
+
);
|
|
226
|
+
const pubkeyToSource = new Map<string, OracleInfo['source']>(
|
|
227
|
+
Array.from(uniqueOraclePubkeys.values()).map((i) => [
|
|
228
|
+
i.publicKey.toBase58(),
|
|
229
|
+
i.source,
|
|
230
|
+
])
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
this.oracleMultiSubscriber =
|
|
234
|
+
await grpcMultiAccountSubscriber.create<OraclePriceData>(
|
|
235
|
+
this.grpcConfigs,
|
|
236
|
+
'oracle',
|
|
237
|
+
this.program,
|
|
238
|
+
(buffer: Buffer, pubkey?: string) => {
|
|
239
|
+
if (!pubkey) {
|
|
240
|
+
throw new Error('Oracle pubkey missing in decode');
|
|
241
|
+
}
|
|
242
|
+
const source = pubkeyToSource.get(pubkey);
|
|
243
|
+
const client = this.oracleClientCache.get(
|
|
244
|
+
source,
|
|
245
|
+
this.program.provider.connection,
|
|
246
|
+
this.program
|
|
247
|
+
);
|
|
248
|
+
return client.getOraclePriceDataFromBuffer(buffer);
|
|
249
|
+
},
|
|
250
|
+
this.resubOpts,
|
|
251
|
+
undefined,
|
|
252
|
+
async () => {
|
|
253
|
+
try {
|
|
254
|
+
if (this.resubOpts?.logResubMessages) {
|
|
255
|
+
console.log(
|
|
256
|
+
'[grpcDriftClientAccountSubscriberV2] oracle subscriber unsubscribed; resubscribing'
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
await this.subscribeToOracles();
|
|
260
|
+
} catch (e) {
|
|
261
|
+
console.error('Oracle resubscribe failed:', e);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
await this.oracleMultiSubscriber.subscribe(
|
|
267
|
+
oraclePubkeys,
|
|
268
|
+
(accountId, data) => {
|
|
269
|
+
const source = pubkeyToSource.get(accountId.toBase58());
|
|
270
|
+
this.eventEmitter.emit('oraclePriceUpdate', accountId, source, data);
|
|
271
|
+
this.eventEmitter.emit('update');
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async unsubscribeFromOracles(): Promise<void> {
|
|
279
|
+
if (this.oracleMultiSubscriber) {
|
|
280
|
+
await this.oracleMultiSubscriber.unsubscribe();
|
|
281
|
+
this.oracleMultiSubscriber = undefined;
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
await super.unsubscribeFromOracles();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
override async unsubscribe(): Promise<void> {
|
|
288
|
+
if (this.isSubscribed) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
await this.stateAccountSubscriber.unsubscribe();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { Program } from '@coral-xyz/anchor';
|
|
2
|
+
import { Context, PublicKey } from '@solana/web3.js';
|
|
3
|
+
import * as Buffer from 'buffer';
|
|
4
|
+
import bs58 from 'bs58';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Client,
|
|
8
|
+
ClientDuplexStream,
|
|
9
|
+
CommitmentLevel,
|
|
10
|
+
SubscribeRequest,
|
|
11
|
+
SubscribeUpdate,
|
|
12
|
+
createClient,
|
|
13
|
+
} from '../isomorphic/grpc';
|
|
14
|
+
import { GrpcConfigs, ResubOpts } from './types';
|
|
15
|
+
|
|
16
|
+
interface AccountInfoLike {
|
|
17
|
+
owner: PublicKey;
|
|
18
|
+
lamports: number;
|
|
19
|
+
data: Buffer;
|
|
20
|
+
executable: boolean;
|
|
21
|
+
rentEpoch: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class grpcMultiAccountSubscriber<T> {
|
|
25
|
+
private client: Client;
|
|
26
|
+
private stream: ClientDuplexStream<SubscribeRequest, SubscribeUpdate>;
|
|
27
|
+
private commitmentLevel: CommitmentLevel;
|
|
28
|
+
private program: Program;
|
|
29
|
+
private accountName: string;
|
|
30
|
+
private decodeBufferFn?: (buffer: Buffer, pubkey?: string) => T;
|
|
31
|
+
private resubOpts?: ResubOpts;
|
|
32
|
+
private onUnsubscribe?: () => Promise<void>;
|
|
33
|
+
|
|
34
|
+
public listenerId?: number;
|
|
35
|
+
public isUnsubscribing = false;
|
|
36
|
+
private timeoutId?: ReturnType<typeof setTimeout>;
|
|
37
|
+
private receivingData = false;
|
|
38
|
+
|
|
39
|
+
private subscribedAccounts = new Set<string>();
|
|
40
|
+
private onChangeMap = new Map<
|
|
41
|
+
string,
|
|
42
|
+
(data: T, context: Context, buffer: Buffer) => void
|
|
43
|
+
>();
|
|
44
|
+
|
|
45
|
+
private constructor(
|
|
46
|
+
client: Client,
|
|
47
|
+
commitmentLevel: CommitmentLevel,
|
|
48
|
+
accountName: string,
|
|
49
|
+
program: Program,
|
|
50
|
+
decodeBuffer?: (buffer: Buffer, pubkey?: string) => T,
|
|
51
|
+
resubOpts?: ResubOpts,
|
|
52
|
+
onUnsubscribe?: () => Promise<void>
|
|
53
|
+
) {
|
|
54
|
+
this.client = client;
|
|
55
|
+
this.commitmentLevel = commitmentLevel;
|
|
56
|
+
this.accountName = accountName;
|
|
57
|
+
this.program = program;
|
|
58
|
+
this.decodeBufferFn = decodeBuffer;
|
|
59
|
+
this.resubOpts = resubOpts;
|
|
60
|
+
this.onUnsubscribe = onUnsubscribe;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public static async create<U>(
|
|
64
|
+
grpcConfigs: GrpcConfigs,
|
|
65
|
+
accountName: string,
|
|
66
|
+
program: Program,
|
|
67
|
+
decodeBuffer?: (buffer: Buffer, pubkey?: string) => U,
|
|
68
|
+
resubOpts?: ResubOpts,
|
|
69
|
+
clientProp?: Client,
|
|
70
|
+
onUnsubscribe?: () => Promise<void>
|
|
71
|
+
): Promise<grpcMultiAccountSubscriber<U>> {
|
|
72
|
+
const client = clientProp
|
|
73
|
+
? clientProp
|
|
74
|
+
: await createClient(
|
|
75
|
+
grpcConfigs.endpoint,
|
|
76
|
+
grpcConfigs.token,
|
|
77
|
+
grpcConfigs.channelOptions ?? {}
|
|
78
|
+
);
|
|
79
|
+
const commitmentLevel =
|
|
80
|
+
// @ts-ignore :: isomorphic exported enum fails typescript but will work at runtime
|
|
81
|
+
grpcConfigs.commitmentLevel ?? CommitmentLevel.CONFIRMED;
|
|
82
|
+
|
|
83
|
+
return new grpcMultiAccountSubscriber(
|
|
84
|
+
client,
|
|
85
|
+
commitmentLevel,
|
|
86
|
+
accountName,
|
|
87
|
+
program,
|
|
88
|
+
decodeBuffer,
|
|
89
|
+
resubOpts,
|
|
90
|
+
onUnsubscribe
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async subscribe(
|
|
95
|
+
accounts: PublicKey[],
|
|
96
|
+
onChange: (
|
|
97
|
+
accountId: PublicKey,
|
|
98
|
+
data: T,
|
|
99
|
+
context: Context,
|
|
100
|
+
buffer: Buffer
|
|
101
|
+
) => void
|
|
102
|
+
): Promise<void> {
|
|
103
|
+
if (this.listenerId != null || this.isUnsubscribing) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Track accounts and single onChange for all
|
|
108
|
+
for (const pk of accounts) {
|
|
109
|
+
const key = pk.toBase58();
|
|
110
|
+
this.subscribedAccounts.add(key);
|
|
111
|
+
this.onChangeMap.set(key, (data, ctx, buffer) =>
|
|
112
|
+
onChange(new PublicKey(key), data, ctx, buffer)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.stream =
|
|
117
|
+
(await this.client.subscribe()) as unknown as typeof this.stream;
|
|
118
|
+
const request: SubscribeRequest = {
|
|
119
|
+
slots: {},
|
|
120
|
+
accounts: {
|
|
121
|
+
account: {
|
|
122
|
+
account: accounts.map((a) => a.toBase58()),
|
|
123
|
+
owner: [],
|
|
124
|
+
filters: [],
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
transactions: {},
|
|
128
|
+
blocks: {},
|
|
129
|
+
blocksMeta: {},
|
|
130
|
+
accountsDataSlice: [],
|
|
131
|
+
commitment: this.commitmentLevel,
|
|
132
|
+
entry: {},
|
|
133
|
+
transactionsStatus: {},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
this.stream.on('data', (chunk: SubscribeUpdate) => {
|
|
137
|
+
if (!chunk.account) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const slot = Number(chunk.account.slot);
|
|
141
|
+
const accountPubkeyBytes = chunk.account.account.pubkey;
|
|
142
|
+
const accountPubkey = bs58.encode(
|
|
143
|
+
accountPubkeyBytes as unknown as Uint8Array
|
|
144
|
+
);
|
|
145
|
+
if (!accountPubkey || !this.subscribedAccounts.has(accountPubkey)) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const accountInfo: AccountInfoLike = {
|
|
149
|
+
owner: new PublicKey(chunk.account.account.owner),
|
|
150
|
+
lamports: Number(chunk.account.account.lamports),
|
|
151
|
+
data: Buffer.Buffer.from(chunk.account.account.data),
|
|
152
|
+
executable: chunk.account.account.executable,
|
|
153
|
+
rentEpoch: Number(chunk.account.account.rentEpoch),
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const context = { slot } as Context;
|
|
157
|
+
const buffer = accountInfo.data;
|
|
158
|
+
const data = this.decodeBufferFn
|
|
159
|
+
? this.decodeBufferFn(buffer, accountPubkey)
|
|
160
|
+
: this.program.account[this.accountName].coder.accounts.decode(
|
|
161
|
+
this.capitalize(this.accountName),
|
|
162
|
+
buffer
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const handler = this.onChangeMap.get(accountPubkey);
|
|
166
|
+
if (handler) {
|
|
167
|
+
if (this.resubOpts?.resubTimeoutMs) {
|
|
168
|
+
this.receivingData = true;
|
|
169
|
+
clearTimeout(this.timeoutId);
|
|
170
|
+
handler(data, context, buffer);
|
|
171
|
+
this.setTimeout();
|
|
172
|
+
} else {
|
|
173
|
+
handler(data, context, buffer);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return new Promise<void>((resolve, reject) => {
|
|
179
|
+
this.stream.write(request, (err) => {
|
|
180
|
+
if (err === null || err === undefined) {
|
|
181
|
+
this.listenerId = 1;
|
|
182
|
+
if (this.resubOpts?.resubTimeoutMs) {
|
|
183
|
+
this.receivingData = true;
|
|
184
|
+
this.setTimeout();
|
|
185
|
+
}
|
|
186
|
+
resolve();
|
|
187
|
+
} else {
|
|
188
|
+
reject(err);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}).catch((reason) => {
|
|
192
|
+
console.error(reason);
|
|
193
|
+
throw reason;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async addAccounts(accounts: PublicKey[]): Promise<void> {
|
|
198
|
+
for (const pk of accounts) {
|
|
199
|
+
this.subscribedAccounts.add(pk.toBase58());
|
|
200
|
+
}
|
|
201
|
+
const request: SubscribeRequest = {
|
|
202
|
+
slots: {},
|
|
203
|
+
accounts: {
|
|
204
|
+
account: {
|
|
205
|
+
account: Array.from(this.subscribedAccounts.values()),
|
|
206
|
+
owner: [],
|
|
207
|
+
filters: [],
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
transactions: {},
|
|
211
|
+
blocks: {},
|
|
212
|
+
blocksMeta: {},
|
|
213
|
+
accountsDataSlice: [],
|
|
214
|
+
commitment: this.commitmentLevel,
|
|
215
|
+
entry: {},
|
|
216
|
+
transactionsStatus: {},
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
await new Promise<void>((resolve, reject) => {
|
|
220
|
+
this.stream.write(request, (err) => {
|
|
221
|
+
if (err === null || err === undefined) {
|
|
222
|
+
resolve();
|
|
223
|
+
} else {
|
|
224
|
+
reject(err);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async removeAccounts(accounts: PublicKey[]): Promise<void> {
|
|
231
|
+
for (const pk of accounts) {
|
|
232
|
+
const k = pk.toBase58();
|
|
233
|
+
this.subscribedAccounts.delete(k);
|
|
234
|
+
this.onChangeMap.delete(k);
|
|
235
|
+
}
|
|
236
|
+
const request: SubscribeRequest = {
|
|
237
|
+
slots: {},
|
|
238
|
+
accounts: {
|
|
239
|
+
account: {
|
|
240
|
+
account: Array.from(this.subscribedAccounts.values()),
|
|
241
|
+
owner: [],
|
|
242
|
+
filters: [],
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
transactions: {},
|
|
246
|
+
blocks: {},
|
|
247
|
+
blocksMeta: {},
|
|
248
|
+
accountsDataSlice: [],
|
|
249
|
+
commitment: this.commitmentLevel,
|
|
250
|
+
entry: {},
|
|
251
|
+
transactionsStatus: {},
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
await new Promise<void>((resolve, reject) => {
|
|
255
|
+
this.stream.write(request, (err) => {
|
|
256
|
+
if (err === null || err === undefined) {
|
|
257
|
+
resolve();
|
|
258
|
+
} else {
|
|
259
|
+
reject(err);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async unsubscribe(): Promise<void> {
|
|
266
|
+
this.isUnsubscribing = true;
|
|
267
|
+
clearTimeout(this.timeoutId);
|
|
268
|
+
this.timeoutId = undefined;
|
|
269
|
+
|
|
270
|
+
if (this.listenerId != null) {
|
|
271
|
+
const promise = new Promise<void>((resolve, reject) => {
|
|
272
|
+
const request: SubscribeRequest = {
|
|
273
|
+
slots: {},
|
|
274
|
+
accounts: {},
|
|
275
|
+
transactions: {},
|
|
276
|
+
blocks: {},
|
|
277
|
+
blocksMeta: {},
|
|
278
|
+
accountsDataSlice: [],
|
|
279
|
+
entry: {},
|
|
280
|
+
transactionsStatus: {},
|
|
281
|
+
};
|
|
282
|
+
this.stream.write(request, (err) => {
|
|
283
|
+
if (err === null || err === undefined) {
|
|
284
|
+
this.listenerId = undefined;
|
|
285
|
+
this.isUnsubscribing = false;
|
|
286
|
+
resolve();
|
|
287
|
+
} else {
|
|
288
|
+
reject(err);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}).catch((reason) => {
|
|
292
|
+
console.error(reason);
|
|
293
|
+
throw reason;
|
|
294
|
+
});
|
|
295
|
+
return promise;
|
|
296
|
+
} else {
|
|
297
|
+
this.isUnsubscribing = false;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (this.onUnsubscribe) {
|
|
301
|
+
try {
|
|
302
|
+
await this.onUnsubscribe();
|
|
303
|
+
} catch (e) {
|
|
304
|
+
console.error(e);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private setTimeout(): void {
|
|
310
|
+
this.timeoutId = setTimeout(
|
|
311
|
+
async () => {
|
|
312
|
+
if (this.isUnsubscribing) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (this.receivingData) {
|
|
316
|
+
await this.unsubscribe();
|
|
317
|
+
this.receivingData = false;
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
this.resubOpts?.resubTimeoutMs
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private capitalize(value: string): string {
|
|
325
|
+
if (!value) return value;
|
|
326
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
327
|
+
}
|
|
328
|
+
}
|
package/src/driftClient.ts
CHANGED
|
@@ -189,7 +189,7 @@ import {
|
|
|
189
189
|
} from './tx/utils';
|
|
190
190
|
import pythSolanaReceiverIdl from './idl/pyth_solana_receiver.json';
|
|
191
191
|
import { asV0Tx, PullFeed, AnchorUtils } from '@switchboard-xyz/on-demand';
|
|
192
|
-
import {
|
|
192
|
+
import { grpcDriftClientAccountSubscriber } from './accounts/grpcDriftClientAccountSubscriber';
|
|
193
193
|
import nacl from 'tweetnacl';
|
|
194
194
|
import { Slothash } from './slot/SlothashSubscriber';
|
|
195
195
|
import { getOracleId } from './oracles/oracleId';
|
|
@@ -434,7 +434,10 @@ export class DriftClient {
|
|
|
434
434
|
delistedMarketSetting
|
|
435
435
|
);
|
|
436
436
|
} else if (config.accountSubscription?.type === 'grpc') {
|
|
437
|
-
|
|
437
|
+
const accountSubscriberClass =
|
|
438
|
+
config.accountSubscription?.driftClientAccountSubscriber ??
|
|
439
|
+
grpcDriftClientAccountSubscriber;
|
|
440
|
+
this.accountSubscriber = new accountSubscriberClass(
|
|
438
441
|
config.accountSubscription.grpcConfigs,
|
|
439
442
|
this.program,
|
|
440
443
|
config.perpMarketIndexes ?? [],
|
package/src/driftClientConfig.ts
CHANGED
|
@@ -22,6 +22,8 @@ import { WebSocketAccountSubscriberV2 } from './accounts/webSocketAccountSubscri
|
|
|
22
22
|
import { WebSocketProgramAccountSubscriber } from './accounts/webSocketProgramAccountSubscriber';
|
|
23
23
|
import { WebSocketDriftClientAccountSubscriberV2 } from './accounts/webSocketDriftClientAccountSubscriberV2';
|
|
24
24
|
import { WebSocketDriftClientAccountSubscriber } from './accounts/webSocketDriftClientAccountSubscriber';
|
|
25
|
+
import { grpcDriftClientAccountSubscriberV2 } from './accounts/grpcDriftClientAccountSubscriberV2';
|
|
26
|
+
import { grpcDriftClientAccountSubscriber } from './accounts/grpcDriftClientAccountSubscriber';
|
|
25
27
|
|
|
26
28
|
export type DriftClientConfig = {
|
|
27
29
|
connection: Connection;
|
|
@@ -60,6 +62,17 @@ export type DriftClientSubscriptionConfig =
|
|
|
60
62
|
grpcConfigs: GrpcConfigs;
|
|
61
63
|
resubTimeoutMs?: number;
|
|
62
64
|
logResubMessages?: boolean;
|
|
65
|
+
driftClientAccountSubscriber?: new (
|
|
66
|
+
grpcConfigs: GrpcConfigs,
|
|
67
|
+
program: Program,
|
|
68
|
+
perpMarketIndexes: number[],
|
|
69
|
+
spotMarketIndexes: number[],
|
|
70
|
+
oracleInfos: OracleInfo[],
|
|
71
|
+
shouldFindAllMarketsAndOracles: boolean,
|
|
72
|
+
delistedMarketSetting: DelistedMarketSetting
|
|
73
|
+
) =>
|
|
74
|
+
| grpcDriftClientAccountSubscriberV2
|
|
75
|
+
| grpcDriftClientAccountSubscriber;
|
|
63
76
|
}
|
|
64
77
|
| {
|
|
65
78
|
type: 'websocket';
|
package/src/index.ts
CHANGED
|
@@ -54,6 +54,7 @@ export * from './events/parse';
|
|
|
54
54
|
export * from './events/pollingLogProvider';
|
|
55
55
|
export * from './jupiter/jupiterClient';
|
|
56
56
|
export * from './math/auction';
|
|
57
|
+
export * from './math/builder';
|
|
57
58
|
export * from './math/spotMarket';
|
|
58
59
|
export * from './math/conversion';
|
|
59
60
|
export * from './math/exchangeStatus';
|