@drift-labs/sdk 2.142.0-beta.17 → 2.142.0-beta.19

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 CHANGED
@@ -1 +1 @@
1
- 2.142.0-beta.17
1
+ 2.142.0-beta.19
@@ -21,6 +21,7 @@ export declare class grpcMultiAccountSubscriber<T, U = undefined> {
21
21
  private onChangeMap;
22
22
  private dataMap;
23
23
  private accountPropsMap;
24
+ private bufferMap;
24
25
  private constructor();
25
26
  static create<T, U = undefined>(grpcConfigs: GrpcConfigs, accountName: string, program: Program, decodeBuffer?: (buffer: Buffer, pubkey?: string, accountProps?: U) => T, resubOpts?: ResubOpts, clientProp?: Client, onUnsubscribe?: () => Promise<void>, accountPropsMap?: Map<string, U | Array<U>>): Promise<grpcMultiAccountSubscriber<T, U>>;
26
27
  setAccountData(accountPubkey: string, data: T, slot?: number): void;
@@ -51,6 +51,7 @@ class grpcMultiAccountSubscriber {
51
51
  this.onChangeMap = new Map();
52
52
  this.dataMap = new Map();
53
53
  this.accountPropsMap = new Map();
54
+ this.bufferMap = new Map();
54
55
  this.client = client;
55
56
  this.commitmentLevel = commitmentLevel;
56
57
  this.accountName = accountName;
@@ -101,8 +102,23 @@ class grpcMultiAccountSubscriber {
101
102
  const accountId = chunk[i];
102
103
  const accountInfo = rpcResponse[i];
103
104
  if (accountInfo) {
104
- const perpMarket = this.program.coder.accounts.decode('PerpMarket', accountInfo.data);
105
- this.setAccountData(accountId, perpMarket, currentSlot);
105
+ const prev = this.bufferMap.get(accountId);
106
+ const newBuffer = accountInfo.data;
107
+ if (prev && currentSlot < prev.slot) {
108
+ continue;
109
+ }
110
+ if (prev &&
111
+ prev.buffer &&
112
+ newBuffer &&
113
+ newBuffer.equals(prev.buffer)) {
114
+ continue;
115
+ }
116
+ this.bufferMap.set(accountId, {
117
+ buffer: newBuffer,
118
+ slot: currentSlot,
119
+ });
120
+ const accountDecoded = this.program.coder.accounts.decode(this.capitalize(this.accountName), newBuffer);
121
+ this.setAccountData(accountId, accountDecoded, currentSlot);
106
122
  }
107
123
  }
108
124
  }));
@@ -150,7 +166,7 @@ class grpcMultiAccountSubscriber {
150
166
  transactionsStatus: {},
151
167
  };
152
168
  this.stream.on('data', (chunk) => {
153
- var _a;
169
+ var _a, _b;
154
170
  if (!chunk.account) {
155
171
  return;
156
172
  }
@@ -160,6 +176,17 @@ class grpcMultiAccountSubscriber {
160
176
  if (!accountPubkey || !this.subscribedAccounts.has(accountPubkey)) {
161
177
  return;
162
178
  }
179
+ // Touch resub timer on any incoming account update for subscribed keys
180
+ if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs) {
181
+ this.receivingData = true;
182
+ clearTimeout(this.timeoutId);
183
+ this.setTimeout();
184
+ }
185
+ // Skip processing if we already have data for this account at a newer slot
186
+ const existing = this.dataMap.get(accountPubkey);
187
+ if ((existing === null || existing === void 0 ? void 0 : existing.slot) !== undefined && existing.slot > slot) {
188
+ return;
189
+ }
163
190
  const accountInfo = {
164
191
  owner: new web3_js_1.PublicKey(chunk.account.account.owner),
165
192
  lamports: Number(chunk.account.account.lamports),
@@ -169,23 +196,26 @@ class grpcMultiAccountSubscriber {
169
196
  };
170
197
  const context = { slot };
171
198
  const buffer = accountInfo.data;
172
- const accountProps = (_a = this.accountPropsMap) === null || _a === void 0 ? void 0 : _a.get(accountPubkey);
199
+ // Check existing buffer for this account and skip if unchanged or slot regressed
200
+ const prevBuffer = this.bufferMap.get(accountPubkey);
201
+ if (prevBuffer && slot < prevBuffer.slot) {
202
+ return;
203
+ }
204
+ if (prevBuffer &&
205
+ prevBuffer.buffer &&
206
+ buffer &&
207
+ buffer.equals(prevBuffer.buffer)) {
208
+ return;
209
+ }
210
+ this.bufferMap.set(accountPubkey, { buffer, slot });
211
+ const accountProps = (_b = this.accountPropsMap) === null || _b === void 0 ? void 0 : _b.get(accountPubkey);
173
212
  const handleDataBuffer = (context, buffer, accountProps) => {
174
- var _a;
175
213
  const data = this.decodeBufferFn
176
214
  ? this.decodeBufferFn(buffer, accountPubkey, accountProps)
177
215
  : this.program.account[this.accountName].coder.accounts.decode(this.capitalize(this.accountName), buffer);
178
216
  const handler = this.onChangeMap.get(accountPubkey);
179
217
  if (handler) {
180
- if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs) {
181
- this.receivingData = true;
182
- clearTimeout(this.timeoutId);
183
- handler(data, context, buffer, accountProps);
184
- this.setTimeout();
185
- }
186
- else {
187
- handler(data, context, buffer, accountProps);
188
- }
218
+ handler(data, context, buffer, accountProps);
189
219
  }
190
220
  };
191
221
  if (Array.isArray(accountProps)) {
@@ -204,7 +234,6 @@ class grpcMultiAccountSubscriber {
204
234
  this.listenerId = 1;
205
235
  if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs) {
206
236
  this.receivingData = true;
207
- this.setTimeout();
208
237
  }
209
238
  resolve();
210
239
  }
@@ -254,6 +283,8 @@ class grpcMultiAccountSubscriber {
254
283
  const k = pk.toBase58();
255
284
  this.subscribedAccounts.delete(k);
256
285
  this.onChangeMap.delete(k);
286
+ this.dataMap.delete(k);
287
+ this.bufferMap.delete(k);
257
288
  }
258
289
  const request = {
259
290
  slots: {},
@@ -21,6 +21,7 @@ export declare class grpcMultiAccountSubscriber<T, U = undefined> {
21
21
  private onChangeMap;
22
22
  private dataMap;
23
23
  private accountPropsMap;
24
+ private bufferMap;
24
25
  private constructor();
25
26
  static create<T, U = undefined>(grpcConfigs: GrpcConfigs, accountName: string, program: Program, decodeBuffer?: (buffer: Buffer, pubkey?: string, accountProps?: U) => T, resubOpts?: ResubOpts, clientProp?: Client, onUnsubscribe?: () => Promise<void>, accountPropsMap?: Map<string, U | Array<U>>): Promise<grpcMultiAccountSubscriber<T, U>>;
26
27
  setAccountData(accountPubkey: string, data: T, slot?: number): void;
@@ -1 +1 @@
1
- {"version":3,"file":"grpcMultiAccountSubscriber.d.ts","sourceRoot":"","sources":["../../../src/accounts/grpcMultiAccountSubscriber.ts"],"names":[],"mappings":";;AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAc,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAIjE,OAAO,EACN,MAAM,EAMN,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAyB9D,qBAAa,0BAA0B,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS;IACvD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAwD;IACtE,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,cAAc,CAAC,CAIhB;IACP,OAAO,CAAC,SAAS,CAAC,CAAY;IAC9B,OAAO,CAAC,aAAa,CAAC,CAAsB;IAErC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,UAAS;IAC/B,OAAO,CAAC,SAAS,CAAC,CAAgC;IAClD,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,WAAW,CAGf;IAEJ,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,eAAe,CAAmC;IAE1D,OAAO;WAoBa,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,EAC1C,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,EAChB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC,KAAK,CAAC,EACvE,SAAS,CAAC,EAAE,SAAS,EACrB,UAAU,CAAC,EAAE,MAAM,EACnB,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EACnC,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GACzC,OAAO,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAwB5C,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IAInE,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS;IAIjE,iBAAiB,IAAI,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IAI1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkDtB,SAAS,CACd,QAAQ,EAAE,SAAS,EAAE,EACrB,QAAQ,EAAE,CACT,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,CAAC,KACX,IAAI,GACP,OAAO,CAAC,IAAI,CAAC;IAkHV,WAAW,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAiCjD,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAmCpD,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IA4ClC,OAAO,CAAC,UAAU;IAelB,OAAO,CAAC,UAAU;CAIlB"}
1
+ {"version":3,"file":"grpcMultiAccountSubscriber.d.ts","sourceRoot":"","sources":["../../../src/accounts/grpcMultiAccountSubscriber.ts"],"names":[],"mappings":";;AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAc,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAIjE,OAAO,EACN,MAAM,EAMN,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAiB,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAyB7E,qBAAa,0BAA0B,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS;IACvD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAwD;IACtE,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,cAAc,CAAC,CAIhB;IACP,OAAO,CAAC,SAAS,CAAC,CAAY;IAC9B,OAAO,CAAC,aAAa,CAAC,CAAsB;IAErC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,UAAS;IAC/B,OAAO,CAAC,SAAS,CAAC,CAAgC;IAClD,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,WAAW,CAGf;IAEJ,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,eAAe,CAAmC;IAC1D,OAAO,CAAC,SAAS,CAAoC;IAErD,OAAO;WAoBa,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,EAC1C,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,EAChB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC,KAAK,CAAC,EACvE,SAAS,CAAC,EAAE,SAAS,EACrB,UAAU,CAAC,EAAE,MAAM,EACnB,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EACnC,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GACzC,OAAO,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAwB5C,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IAInE,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS;IAIjE,iBAAiB,IAAI,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IAI1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoEtB,SAAS,CACd,QAAQ,EAAE,SAAS,EAAE,EACrB,QAAQ,EAAE,CACT,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,CAAC,KACX,IAAI,GACP,OAAO,CAAC,IAAI,CAAC;IAsIV,WAAW,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAiCjD,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAqCpD,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IA4ClC,OAAO,CAAC,UAAU;IAelB,OAAO,CAAC,UAAU;CAIlB"}
@@ -51,6 +51,7 @@ class grpcMultiAccountSubscriber {
51
51
  this.onChangeMap = new Map();
52
52
  this.dataMap = new Map();
53
53
  this.accountPropsMap = new Map();
54
+ this.bufferMap = new Map();
54
55
  this.client = client;
55
56
  this.commitmentLevel = commitmentLevel;
56
57
  this.accountName = accountName;
@@ -101,8 +102,23 @@ class grpcMultiAccountSubscriber {
101
102
  const accountId = chunk[i];
102
103
  const accountInfo = rpcResponse[i];
103
104
  if (accountInfo) {
104
- const perpMarket = this.program.coder.accounts.decode('PerpMarket', accountInfo.data);
105
- this.setAccountData(accountId, perpMarket, currentSlot);
105
+ const prev = this.bufferMap.get(accountId);
106
+ const newBuffer = accountInfo.data;
107
+ if (prev && currentSlot < prev.slot) {
108
+ continue;
109
+ }
110
+ if (prev &&
111
+ prev.buffer &&
112
+ newBuffer &&
113
+ newBuffer.equals(prev.buffer)) {
114
+ continue;
115
+ }
116
+ this.bufferMap.set(accountId, {
117
+ buffer: newBuffer,
118
+ slot: currentSlot,
119
+ });
120
+ const accountDecoded = this.program.coder.accounts.decode(this.capitalize(this.accountName), newBuffer);
121
+ this.setAccountData(accountId, accountDecoded, currentSlot);
106
122
  }
107
123
  }
108
124
  }));
@@ -150,7 +166,7 @@ class grpcMultiAccountSubscriber {
150
166
  transactionsStatus: {},
151
167
  };
152
168
  this.stream.on('data', (chunk) => {
153
- var _a;
169
+ var _a, _b;
154
170
  if (!chunk.account) {
155
171
  return;
156
172
  }
@@ -160,6 +176,17 @@ class grpcMultiAccountSubscriber {
160
176
  if (!accountPubkey || !this.subscribedAccounts.has(accountPubkey)) {
161
177
  return;
162
178
  }
179
+ // Touch resub timer on any incoming account update for subscribed keys
180
+ if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs) {
181
+ this.receivingData = true;
182
+ clearTimeout(this.timeoutId);
183
+ this.setTimeout();
184
+ }
185
+ // Skip processing if we already have data for this account at a newer slot
186
+ const existing = this.dataMap.get(accountPubkey);
187
+ if ((existing === null || existing === void 0 ? void 0 : existing.slot) !== undefined && existing.slot > slot) {
188
+ return;
189
+ }
163
190
  const accountInfo = {
164
191
  owner: new web3_js_1.PublicKey(chunk.account.account.owner),
165
192
  lamports: Number(chunk.account.account.lamports),
@@ -169,23 +196,26 @@ class grpcMultiAccountSubscriber {
169
196
  };
170
197
  const context = { slot };
171
198
  const buffer = accountInfo.data;
172
- const accountProps = (_a = this.accountPropsMap) === null || _a === void 0 ? void 0 : _a.get(accountPubkey);
199
+ // Check existing buffer for this account and skip if unchanged or slot regressed
200
+ const prevBuffer = this.bufferMap.get(accountPubkey);
201
+ if (prevBuffer && slot < prevBuffer.slot) {
202
+ return;
203
+ }
204
+ if (prevBuffer &&
205
+ prevBuffer.buffer &&
206
+ buffer &&
207
+ buffer.equals(prevBuffer.buffer)) {
208
+ return;
209
+ }
210
+ this.bufferMap.set(accountPubkey, { buffer, slot });
211
+ const accountProps = (_b = this.accountPropsMap) === null || _b === void 0 ? void 0 : _b.get(accountPubkey);
173
212
  const handleDataBuffer = (context, buffer, accountProps) => {
174
- var _a;
175
213
  const data = this.decodeBufferFn
176
214
  ? this.decodeBufferFn(buffer, accountPubkey, accountProps)
177
215
  : this.program.account[this.accountName].coder.accounts.decode(this.capitalize(this.accountName), buffer);
178
216
  const handler = this.onChangeMap.get(accountPubkey);
179
217
  if (handler) {
180
- if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs) {
181
- this.receivingData = true;
182
- clearTimeout(this.timeoutId);
183
- handler(data, context, buffer, accountProps);
184
- this.setTimeout();
185
- }
186
- else {
187
- handler(data, context, buffer, accountProps);
188
- }
218
+ handler(data, context, buffer, accountProps);
189
219
  }
190
220
  };
191
221
  if (Array.isArray(accountProps)) {
@@ -204,7 +234,6 @@ class grpcMultiAccountSubscriber {
204
234
  this.listenerId = 1;
205
235
  if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs) {
206
236
  this.receivingData = true;
207
- this.setTimeout();
208
237
  }
209
238
  resolve();
210
239
  }
@@ -254,6 +283,8 @@ class grpcMultiAccountSubscriber {
254
283
  const k = pk.toBase58();
255
284
  this.subscribedAccounts.delete(k);
256
285
  this.onChangeMap.delete(k);
286
+ this.dataMap.delete(k);
287
+ this.bufferMap.delete(k);
257
288
  }
258
289
  const request = {
259
290
  slots: {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.142.0-beta.17",
3
+ "version": "2.142.0-beta.19",
4
4
  "main": "lib/node/index.js",
5
5
  "types": "lib/node/index.d.ts",
6
6
  "module": "./lib/browser/index.js",
@@ -19,12 +19,14 @@ import {
19
19
  ProgramAccount,
20
20
  } from '@coral-xyz/anchor';
21
21
  import driftIDL from '../src/idl/drift.json';
22
+ import assert from 'assert';
22
23
 
23
24
  const GRPC_ENDPOINT = process.env.GRPC_ENDPOINT;
24
25
  const TOKEN = process.env.TOKEN;
26
+ const RPC_ENDPOINT = process.env.RPC_ENDPOINT;
25
27
 
26
28
  async function initializeGrpcDriftClientV2VersusV1() {
27
- const connection = new Connection('https://api.mainnet-beta.solana.com');
29
+ const connection = new Connection(RPC_ENDPOINT);
28
30
  const wallet = new Wallet(new Keypair());
29
31
  dotenv.config({ path: '../' });
30
32
 
@@ -34,49 +36,38 @@ async function initializeGrpcDriftClientV2VersusV1() {
34
36
  // @ts-ignore
35
37
  wallet,
36
38
  {
37
- commitment: 'confirmed',
39
+ commitment: 'processed',
38
40
  }
39
41
  );
40
42
 
41
43
  const program = new Program(driftIDL as Idl, programId, provider);
42
44
 
43
- const perpMarketIndexes = [4];
44
- const spotMarketIndexes = [32];
45
-
46
- const perpMarketProgramAccounts = (
47
- await program.account.perpMarket.all()
48
- ).filter((a) =>
49
- perpMarketIndexes.includes(a.account.marketIndex as number)
50
- ) as ProgramAccount<PerpMarketAccount>[];
51
- const spotMarketProgramAccounts = (
52
- await program.account.spotMarket.all()
53
- ).filter((a) =>
54
- spotMarketIndexes.includes(a.account.marketIndex as number)
55
- ) as ProgramAccount<SpotMarketAccount>[];
56
-
57
- // const perpMarketIndexes = perpMarketProgramAccounts.map(
58
- // (a) => a.account.marketIndex
59
- // );
60
- // const spotMarketIndexes = spotMarketProgramAccounts.map(
61
- // (a) => a.account.marketIndex
62
- // );
63
- // const oracleInfos = [
64
- // {
65
- // publicKey: new PublicKey('BERaNi6cpEresbq6HC1EQGaB1H1UjvEo4NGnmYSSJof4'),
66
- // source: OracleSource.PYTH_LAZER,
67
- // },
68
- // {
69
- // publicKey: new PublicKey('BERaNi6cpEresbq6HC1EQGaB1H1UjvEo4NGnmYSSJof4'),
70
- // source: OracleSource.PYTH_LAZER_1M,
71
- // },
72
- // ];
45
+ const allPerpMarketProgramAccounts =
46
+ (await program.account.perpMarket.all()) as ProgramAccount<PerpMarketAccount>[];
47
+ const perpMarketProgramAccounts = allPerpMarketProgramAccounts.filter((val) =>
48
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].includes(
49
+ val.account.marketIndex
50
+ )
51
+ );
52
+ const perpMarketIndexes = perpMarketProgramAccounts.map(
53
+ (val) => val.account.marketIndex
54
+ );
55
+
56
+ const allSpotMarketProgramAccounts =
57
+ (await program.account.spotMarket.all()) as ProgramAccount<SpotMarketAccount>[];
58
+ const spotMarketProgramAccounts = allSpotMarketProgramAccounts.filter((val) =>
59
+ [0, 1, 2, 3, 4, 5].includes(val.account.marketIndex)
60
+ );
61
+ const spotMarketIndexes = spotMarketProgramAccounts.map(
62
+ (val) => val.account.marketIndex
63
+ );
73
64
 
74
65
  const seen = new Set<string>();
75
66
  const oracleInfos: OracleInfo[] = [];
76
67
  for (const acct of perpMarketProgramAccounts) {
77
- const key = `${acct.account.amm.oracle.toBase58()}-${Object.keys(
78
- acct.account.amm.oracleSource ?? {}
79
- )?.[0]}`;
68
+ const key = `${acct.account.amm.oracle.toBase58()}-${
69
+ Object.keys(acct.account.amm.oracleSource)[0]
70
+ }`;
80
71
  if (!seen.has(key)) {
81
72
  seen.add(key);
82
73
  oracleInfos.push({
@@ -86,9 +77,9 @@ async function initializeGrpcDriftClientV2VersusV1() {
86
77
  }
87
78
  }
88
79
  for (const acct of spotMarketProgramAccounts) {
89
- const key = `${acct.account.oracle.toBase58()}-${Object.keys(
90
- acct.account.oracleSource ?? {}
91
- )?.[0]}`;
80
+ const key = `${acct.account.oracle.toBase58()}-${
81
+ Object.keys(acct.account.oracleSource)[0]
82
+ }`;
92
83
  if (!seen.has(key)) {
93
84
  seen.add(key);
94
85
  oracleInfos.push({
@@ -103,7 +94,7 @@ async function initializeGrpcDriftClientV2VersusV1() {
103
94
  grpcConfigs: {
104
95
  endpoint: GRPC_ENDPOINT,
105
96
  token: TOKEN,
106
- commitmentLevel: 'confirmed' as unknown as CommitmentLevel,
97
+ commitmentLevel: CommitmentLevel.PROCESSED,
107
98
  channelOptions: {
108
99
  'grpc.keepalive_time_ms': 10_000,
109
100
  'grpc.keepalive_timeout_ms': 1_000,
@@ -119,7 +110,6 @@ async function initializeGrpcDriftClientV2VersusV1() {
119
110
  accountSubscription: {
120
111
  ...baseAccountSubscription,
121
112
  driftClientAccountSubscriber: grpcDriftClientAccountSubscriberV2,
122
- logResubMessages: true,
123
113
  },
124
114
  perpMarketIndexes,
125
115
  spotMarketIndexes,
@@ -133,7 +123,6 @@ async function initializeGrpcDriftClientV2VersusV1() {
133
123
  accountSubscription: {
134
124
  ...baseAccountSubscription,
135
125
  driftClientAccountSubscriber: grpcDriftClientAccountSubscriber,
136
- // logResubMessages: true,
137
126
  },
138
127
  perpMarketIndexes,
139
128
  spotMarketIndexes,
@@ -144,28 +133,227 @@ async function initializeGrpcDriftClientV2VersusV1() {
144
133
  const clientV1 = new DriftClient(configV1);
145
134
 
146
135
  await Promise.all([clientV1.subscribe(), clientV2.subscribe()]);
147
-
148
- clientV2.eventEmitter.on('oraclePriceUpdate', (pubkey, source, data) => {
149
- const key = pubkey.toBase58();
150
- const src = Object.keys(source ?? {})[0];
151
- console.log(
152
- `v2 oracle update ${key} (${src}) price ${data.price.toString()}`
153
- );
154
- });
155
136
  const compare = () => {
156
- for (const idx of perpMarketIndexes) {
157
- const p1 = clientV1.getOracleDataForPerpMarket(idx).price;
158
- const p2 = clientV2.getOracleDataForPerpMarket(idx).price;
159
- console.log(
160
- `perp mkt ${idx} | v1 ${p1.toString()} | v2 ${p2.toString()}`
137
+ try {
138
+ // 1. Test getStateAccountAndSlot
139
+ const state1 = clientV1.accountSubscriber.getStateAccountAndSlot();
140
+ const state2 = clientV2.accountSubscriber.getStateAccountAndSlot();
141
+ assert.deepStrictEqual(
142
+ state1.data,
143
+ state2.data,
144
+ 'State accounts should match'
161
145
  );
162
- }
163
- for (const idx of spotMarketIndexes) {
164
- const s1 = clientV1.getOracleDataForSpotMarket(idx).price;
165
- const s2 = clientV2.getOracleDataForSpotMarket(idx).price;
166
- console.log(
167
- `spot mkt ${idx} | v1 ${s1.toString()} | v2 ${s2.toString()}`
146
+ if (
147
+ state1.slot !== undefined &&
148
+ state2.slot !== undefined &&
149
+ state2.slot < state1.slot
150
+ ) {
151
+ console.error(
152
+ `State account slot regression: v2 slot ${state2.slot} < v1 slot ${state1.slot}`
153
+ );
154
+ }
155
+
156
+ // 2. Test getMarketAccountsAndSlots (all perp markets) - sorted comparison
157
+ const allPerpMarkets1 = clientV1.accountSubscriber
158
+ .getMarketAccountsAndSlots()
159
+ .sort((a, b) => a.data.marketIndex - b.data.marketIndex);
160
+ const allPerpMarkets2 = clientV2.accountSubscriber
161
+ .getMarketAccountsAndSlots()
162
+ .sort((a, b) => a.data.marketIndex - b.data.marketIndex);
163
+ assert.strictEqual(
164
+ allPerpMarkets1.length,
165
+ allPerpMarkets2.length,
166
+ 'Number of perp markets should match'
168
167
  );
168
+
169
+ // Compare each perp market in the sorted arrays
170
+ for (let i = 0; i < allPerpMarkets1.length; i++) {
171
+ const market1 = allPerpMarkets1[i];
172
+ const market2 = allPerpMarkets2[i];
173
+ assert.strictEqual(
174
+ market1.data.marketIndex,
175
+ market2.data.marketIndex,
176
+ `Perp market at position ${i} should have same marketIndex`
177
+ );
178
+ // assert.deepStrictEqual(
179
+ // market1.data,
180
+ // market2.data,
181
+ // `Perp market ${market1.data.marketIndex} (from getMarketAccountsAndSlots) should match`
182
+ // );
183
+ }
184
+
185
+ // 3. Test getMarketAccountAndSlot for each perp market
186
+ for (const idx of perpMarketIndexes) {
187
+ const market1 = clientV1.accountSubscriber.getMarketAccountAndSlot(idx);
188
+ const market2 = clientV2.accountSubscriber.getMarketAccountAndSlot(idx);
189
+ // assert.deepStrictEqual(
190
+ // market1?.data,
191
+ // market2?.data,
192
+ // `Perp market ${idx} data should match`
193
+ // );
194
+ // assert.strictEqual(
195
+ // market1?.slot,
196
+ // market2?.slot,
197
+ // `Perp market ${idx} slot should match`
198
+ // );
199
+ if (
200
+ market1?.slot !== undefined &&
201
+ market2?.slot !== undefined &&
202
+ market2.slot < market1.slot
203
+ ) {
204
+ console.error(
205
+ `Perp market ${idx} slot regression: v2 slot ${market2.slot} < v1 slot ${market1.slot}`
206
+ );
207
+ } else if (
208
+ market1?.slot !== undefined &&
209
+ market2?.slot !== undefined &&
210
+ market2.slot > market1.slot
211
+ ) {
212
+ console.info(
213
+ `Perp market ${idx} slot is FASTER! v2: ${market2.slot}, v1: ${market1.slot}`
214
+ );
215
+ }
216
+ }
217
+
218
+ // 4. Test getSpotMarketAccountsAndSlots (all spot markets) - sorted comparison
219
+ const allSpotMarkets1 = clientV1.accountSubscriber
220
+ .getSpotMarketAccountsAndSlots()
221
+ .sort((a, b) => a.data.marketIndex - b.data.marketIndex);
222
+ const allSpotMarkets2 = clientV2.accountSubscriber
223
+ .getSpotMarketAccountsAndSlots()
224
+ .sort((a, b) => a.data.marketIndex - b.data.marketIndex);
225
+ assert.strictEqual(
226
+ allSpotMarkets1.length,
227
+ allSpotMarkets2.length,
228
+ 'Number of spot markets should match'
229
+ );
230
+
231
+ // Compare each spot market in the sorted arrays
232
+ for (let i = 0; i < allSpotMarkets1.length; i++) {
233
+ const market1 = allSpotMarkets1[i];
234
+ const market2 = allSpotMarkets2[i];
235
+ assert.strictEqual(
236
+ market1.data.marketIndex,
237
+ market2.data.marketIndex,
238
+ `Spot market at position ${i} should have same marketIndex`
239
+ );
240
+ // assert.deepStrictEqual(
241
+ // market1.data,
242
+ // market2.data,
243
+ // `Spot market ${market1.data.marketIndex} (from getSpotMarketAccountsAndSlots) should match`
244
+ // );
245
+ }
246
+
247
+ // 5. Test getSpotMarketAccountAndSlot for each spot market
248
+ for (const idx of spotMarketIndexes) {
249
+ const market1 =
250
+ clientV1.accountSubscriber.getSpotMarketAccountAndSlot(idx);
251
+ const market2 =
252
+ clientV2.accountSubscriber.getSpotMarketAccountAndSlot(idx);
253
+ // assert.deepStrictEqual(
254
+ // market1?.data,
255
+ // market2?.data,
256
+ // `Spot market ${idx} data should match`
257
+ // );
258
+ // assert.strictEqual(
259
+ // market1?.slot,
260
+ // market2?.slot,
261
+ // `Spot market ${idx} slot should match`
262
+ // );
263
+ if (
264
+ market1?.slot !== undefined &&
265
+ market2?.slot !== undefined &&
266
+ market2.slot < market1.slot
267
+ ) {
268
+ console.error(
269
+ `Spot market ${idx} slot regression: v2 slot ${market2.slot} < v1 slot ${market1.slot}`
270
+ );
271
+ } else if (
272
+ market1?.slot !== undefined &&
273
+ market2?.slot !== undefined &&
274
+ market2.slot > market1.slot
275
+ ) {
276
+ console.info(
277
+ `Spot market ${idx} slot is FASTER! v2: ${market2.slot}, v1: ${market1.slot}`
278
+ );
279
+ }
280
+ }
281
+
282
+ // 6. Test getOraclePriceDataAndSlotForPerpMarket
283
+ for (const idx of perpMarketIndexes) {
284
+ const oracle1 =
285
+ clientV1.accountSubscriber.getOraclePriceDataAndSlotForPerpMarket(
286
+ idx
287
+ );
288
+ const oracle2 =
289
+ clientV2.accountSubscriber.getOraclePriceDataAndSlotForPerpMarket(
290
+ idx
291
+ );
292
+ // assert.deepStrictEqual(
293
+ // oracle1?.data,
294
+ // oracle2?.data,
295
+ // `Perp market ${idx} oracle data should match`
296
+ // );
297
+ // Note: slots might differ slightly due to timing, so we can optionally skip this check or be lenient
298
+ // assert.strictEqual(oracle1?.slot, oracle2?.slot, `Perp market ${idx} oracle slot should match`);
299
+ if (
300
+ oracle1?.slot !== undefined &&
301
+ oracle2?.slot !== undefined &&
302
+ oracle2.slot < oracle1.slot
303
+ ) {
304
+ console.error(
305
+ `Perp market ${idx} oracle slot regression: v2 slot ${oracle2.slot} < v1 slot ${oracle1.slot}`
306
+ );
307
+ } else if (
308
+ oracle1?.slot !== undefined &&
309
+ oracle2?.slot !== undefined &&
310
+ oracle2.slot > oracle1.slot
311
+ ) {
312
+ console.info(
313
+ `Perp market ${idx} oracle slot is FASTER! v2: ${oracle2.slot}, v1: ${oracle1.slot}`
314
+ );
315
+ }
316
+ }
317
+
318
+ // 7. Test getOraclePriceDataAndSlotForSpotMarket
319
+ for (const idx of spotMarketIndexes) {
320
+ const oracle1 =
321
+ clientV1.accountSubscriber.getOraclePriceDataAndSlotForSpotMarket(
322
+ idx
323
+ );
324
+ const oracle2 =
325
+ clientV2.accountSubscriber.getOraclePriceDataAndSlotForSpotMarket(
326
+ idx
327
+ );
328
+ // assert.deepStrictEqual(
329
+ // oracle1?.data,
330
+ // oracle2?.data,
331
+ // `Spot market ${idx} oracle data should match`
332
+ // );
333
+ // Note: slots might differ slightly due to timing
334
+ // assert.strictEqual(oracle1?.slot, oracle2?.slot, `Spot market ${idx} oracle slot should match`);
335
+ if (
336
+ oracle1?.slot !== undefined &&
337
+ oracle2?.slot !== undefined &&
338
+ oracle2.slot < oracle1.slot
339
+ ) {
340
+ console.error(
341
+ `Spot market ${idx} oracle slot regression: v2 slot ${oracle2.slot} < v1 slot ${oracle1.slot}`
342
+ );
343
+ } else if (
344
+ oracle1?.slot !== undefined &&
345
+ oracle2?.slot !== undefined &&
346
+ oracle2.slot > oracle1.slot
347
+ ) {
348
+ console.info(
349
+ `Spot market ${idx} oracle slot is FASTER! v2: ${oracle2.slot}, v1: ${oracle1.slot}`
350
+ );
351
+ }
352
+ }
353
+
354
+ console.log('✓ All comparisons passed');
355
+ } catch (error) {
356
+ console.error('✗ Comparison failed:', error);
169
357
  }
170
358
  };
171
359
 
@@ -11,7 +11,7 @@ import {
11
11
  SubscribeUpdate,
12
12
  createClient,
13
13
  } from '../isomorphic/grpc';
14
- import { DataAndSlot, GrpcConfigs, ResubOpts } from './types';
14
+ import { BufferAndSlot, DataAndSlot, GrpcConfigs, ResubOpts } from './types';
15
15
 
16
16
  interface AccountInfoLike {
17
17
  owner: PublicKey;
@@ -63,6 +63,7 @@ export class grpcMultiAccountSubscriber<T, U = undefined> {
63
63
 
64
64
  private dataMap = new Map<string, DataAndSlot<T>>();
65
65
  private accountPropsMap = new Map<string, U | Array<U>>();
66
+ private bufferMap = new Map<string, BufferAndSlot>();
66
67
 
67
68
  private constructor(
68
69
  client: Client,
@@ -160,11 +161,29 @@ export class grpcMultiAccountSubscriber<T, U = undefined> {
160
161
  const accountId = chunk[i];
161
162
  const accountInfo = rpcResponse[i];
162
163
  if (accountInfo) {
163
- const perpMarket = this.program.coder.accounts.decode(
164
- 'PerpMarket',
165
- accountInfo.data
164
+ const prev = this.bufferMap.get(accountId);
165
+ const newBuffer = accountInfo.data as Buffer;
166
+ if (prev && currentSlot < prev.slot) {
167
+ continue;
168
+ }
169
+ if (
170
+ prev &&
171
+ prev.buffer &&
172
+ newBuffer &&
173
+ newBuffer.equals(prev.buffer)
174
+ ) {
175
+ continue;
176
+ }
177
+ this.bufferMap.set(accountId, {
178
+ buffer: newBuffer,
179
+ slot: currentSlot,
180
+ });
181
+
182
+ const accountDecoded = this.program.coder.accounts.decode(
183
+ this.capitalize(this.accountName),
184
+ newBuffer
166
185
  );
167
- this.setAccountData(accountId, perpMarket, currentSlot);
186
+ this.setAccountData(accountId, accountDecoded, currentSlot);
168
187
  }
169
188
  }
170
189
  })
@@ -238,6 +257,19 @@ export class grpcMultiAccountSubscriber<T, U = undefined> {
238
257
  if (!accountPubkey || !this.subscribedAccounts.has(accountPubkey)) {
239
258
  return;
240
259
  }
260
+
261
+ // Touch resub timer on any incoming account update for subscribed keys
262
+ if (this.resubOpts?.resubTimeoutMs) {
263
+ this.receivingData = true;
264
+ clearTimeout(this.timeoutId);
265
+ this.setTimeout();
266
+ }
267
+
268
+ // Skip processing if we already have data for this account at a newer slot
269
+ const existing = this.dataMap.get(accountPubkey);
270
+ if (existing?.slot !== undefined && existing.slot > slot) {
271
+ return;
272
+ }
241
273
  const accountInfo: AccountInfoLike = {
242
274
  owner: new PublicKey(chunk.account.account.owner),
243
275
  lamports: Number(chunk.account.account.lamports),
@@ -248,6 +280,21 @@ export class grpcMultiAccountSubscriber<T, U = undefined> {
248
280
 
249
281
  const context = { slot } as Context;
250
282
  const buffer = accountInfo.data;
283
+
284
+ // Check existing buffer for this account and skip if unchanged or slot regressed
285
+ const prevBuffer = this.bufferMap.get(accountPubkey);
286
+ if (prevBuffer && slot < prevBuffer.slot) {
287
+ return;
288
+ }
289
+ if (
290
+ prevBuffer &&
291
+ prevBuffer.buffer &&
292
+ buffer &&
293
+ buffer.equals(prevBuffer.buffer)
294
+ ) {
295
+ return;
296
+ }
297
+ this.bufferMap.set(accountPubkey, { buffer, slot });
251
298
  const accountProps = this.accountPropsMap?.get(accountPubkey);
252
299
 
253
300
  const handleDataBuffer = (
@@ -263,14 +310,7 @@ export class grpcMultiAccountSubscriber<T, U = undefined> {
263
310
  );
264
311
  const handler = this.onChangeMap.get(accountPubkey);
265
312
  if (handler) {
266
- if (this.resubOpts?.resubTimeoutMs) {
267
- this.receivingData = true;
268
- clearTimeout(this.timeoutId);
269
- handler(data, context, buffer, accountProps);
270
- this.setTimeout();
271
- } else {
272
- handler(data, context, buffer, accountProps);
273
- }
313
+ handler(data, context, buffer, accountProps);
274
314
  }
275
315
  };
276
316
 
@@ -289,7 +329,6 @@ export class grpcMultiAccountSubscriber<T, U = undefined> {
289
329
  this.listenerId = 1;
290
330
  if (this.resubOpts?.resubTimeoutMs) {
291
331
  this.receivingData = true;
292
- this.setTimeout();
293
332
  }
294
333
  resolve();
295
334
  } else {
@@ -340,6 +379,8 @@ export class grpcMultiAccountSubscriber<T, U = undefined> {
340
379
  const k = pk.toBase58();
341
380
  this.subscribedAccounts.delete(k);
342
381
  this.onChangeMap.delete(k);
382
+ this.dataMap.delete(k);
383
+ this.bufferMap.delete(k);
343
384
  }
344
385
  const request: SubscribeRequest = {
345
386
  slots: {},