@drift-labs/sdk 2.74.0-beta.1 → 2.74.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/VERSION +1 -1
  2. package/lib/adminClient.d.ts +12 -3
  3. package/lib/adminClient.js +60 -32
  4. package/lib/blockhashSubscriber/BlockhashSubscriber.d.ts +21 -0
  5. package/lib/blockhashSubscriber/BlockhashSubscriber.js +73 -0
  6. package/lib/blockhashSubscriber/index.d.ts +1 -0
  7. package/lib/blockhashSubscriber/index.js +17 -0
  8. package/lib/blockhashSubscriber/types.d.ts +7 -0
  9. package/lib/blockhashSubscriber/types.js +2 -0
  10. package/lib/dlob/orderBookLevels.js +47 -12
  11. package/lib/driftClient.d.ts +5 -0
  12. package/lib/driftClient.js +17 -0
  13. package/lib/events/parse.d.ts +1 -1
  14. package/lib/events/parse.js +12 -12
  15. package/lib/idl/drift.json +85 -0
  16. package/lib/index.d.ts +1 -0
  17. package/lib/index.js +1 -0
  18. package/lib/math/funding.js +0 -6
  19. package/lib/math/oracles.js +1 -1
  20. package/lib/math/tiers.js +1 -1
  21. package/lib/oracles/prelaunchOracleClient.js +1 -0
  22. package/lib/oracles/types.d.ts +1 -0
  23. package/lib/tx/baseTxSender.d.ts +1 -1
  24. package/lib/tx/baseTxSender.js +9 -2
  25. package/lib/tx/fastSingleTxSender.d.ts +1 -1
  26. package/lib/tx/fastSingleTxSender.js +11 -3
  27. package/lib/tx/types.d.ts +1 -1
  28. package/lib/types.d.ts +3 -0
  29. package/lib/types.js +1 -0
  30. package/lib/user.d.ts +6 -4
  31. package/lib/user.js +24 -21
  32. package/package.json +1 -1
  33. package/src/adminClient.ts +297 -87
  34. package/src/blockhashSubscriber/BlockhashSubscriber.ts +108 -0
  35. package/src/blockhashSubscriber/index.ts +1 -0
  36. package/src/blockhashSubscriber/types.ts +8 -0
  37. package/src/dlob/orderBookLevels.ts +51 -15
  38. package/src/driftClient.ts +37 -0
  39. package/src/events/parse.ts +26 -12
  40. package/src/idl/drift.json +85 -0
  41. package/src/index.ts +1 -0
  42. package/src/math/funding.ts +0 -4
  43. package/src/math/oracles.ts +1 -1
  44. package/src/math/tiers.ts +1 -1
  45. package/src/oracles/prelaunchOracleClient.ts +1 -0
  46. package/src/oracles/types.ts +1 -0
  47. package/src/tx/baseTxSender.ts +12 -4
  48. package/src/tx/fastSingleTxSender.ts +13 -5
  49. package/src/tx/types.ts +2 -1
  50. package/src/types.ts +1 -0
  51. package/src/user.ts +32 -30
  52. package/tests/amm/test.ts +3 -1
  53. package/tests/dlob/test.ts +57 -0
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  PublicKey,
3
3
  SYSVAR_RENT_PUBKEY,
4
+ TransactionInstruction,
4
5
  TransactionSignature,
5
6
  } from '@solana/web3.js';
6
7
  import {
@@ -31,7 +32,13 @@ import {
31
32
  import { squareRootBN } from './math/utils';
32
33
  import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
33
34
  import { DriftClient } from './driftClient';
34
- import { PEG_PRECISION } from './constants/numericConstants';
35
+ import {
36
+ PEG_PRECISION,
37
+ ZERO,
38
+ ONE,
39
+ BASE_PRECISION,
40
+ PRICE_PRECISION,
41
+ } from './constants/numericConstants';
35
42
  import { calculateTargetPriceTrade } from './math/trade';
36
43
  import { calculateAmmReservesAfterSwap, getSwapDirection } from './math/amm';
37
44
  import { PROGRAM_ID as PHOENIX_PROGRAM_ID } from '@ellipsis-labs/phoenix-sdk';
@@ -84,10 +91,84 @@ export class AdminClient extends DriftClient {
84
91
  maintenanceLiabilityWeight: number,
85
92
  imfFactor = 0,
86
93
  liquidatorFee = 0,
94
+ ifLiquidationFee = 0,
87
95
  activeStatus = true,
96
+ assetTier = AssetTier.COLLATERAL,
97
+ scaleInitialAssetWeightStart = ZERO,
98
+ withdrawGuardThreshold = ZERO,
99
+ orderTickSize = ONE,
100
+ orderStepSize = ONE,
101
+ ifTotalFactor = 0,
88
102
  name = DEFAULT_MARKET_NAME
89
103
  ): Promise<TransactionSignature> {
90
104
  const spotMarketIndex = this.getStateAccount().numberOfSpotMarkets;
105
+
106
+ const initializeIx = await this.getInitializeSpotMarketIx(
107
+ this.wallet.publicKey,
108
+ mint,
109
+ optimalUtilization,
110
+ optimalRate,
111
+ maxRate,
112
+ oracle,
113
+ oracleSource,
114
+ initialAssetWeight,
115
+ maintenanceAssetWeight,
116
+ initialLiabilityWeight,
117
+ maintenanceLiabilityWeight,
118
+ imfFactor,
119
+ liquidatorFee,
120
+ ifLiquidationFee,
121
+ activeStatus,
122
+ assetTier,
123
+ scaleInitialAssetWeightStart,
124
+ withdrawGuardThreshold,
125
+ orderTickSize,
126
+ orderStepSize,
127
+ ifTotalFactor,
128
+ name
129
+ );
130
+
131
+ const tx = await this.buildTransaction(initializeIx);
132
+
133
+ const { txSig } = await this.sendTransaction(tx, [], this.opts);
134
+
135
+ // const { txSig } = await this.sendTransaction(initializeTx, [], this.opts);
136
+
137
+ await this.accountSubscriber.addSpotMarket(spotMarketIndex);
138
+ await this.accountSubscriber.addOracle({
139
+ source: oracleSource,
140
+ publicKey: oracle,
141
+ });
142
+ await this.accountSubscriber.setSpotOracleMap();
143
+
144
+ return txSig;
145
+ }
146
+
147
+ public async getInitializeSpotMarketIx(
148
+ admin: PublicKey,
149
+ mint: PublicKey,
150
+ optimalUtilization: number,
151
+ optimalRate: number,
152
+ maxRate: number,
153
+ oracle: PublicKey,
154
+ oracleSource: OracleSource,
155
+ initialAssetWeight: number,
156
+ maintenanceAssetWeight: number,
157
+ initialLiabilityWeight: number,
158
+ maintenanceLiabilityWeight: number,
159
+ imfFactor = 0,
160
+ liquidatorFee = 0,
161
+ ifLiquidationFee = 0,
162
+ activeStatus = true,
163
+ assetTier = AssetTier.COLLATERAL,
164
+ scaleInitialAssetWeightStart = ZERO,
165
+ withdrawGuardThreshold = ZERO,
166
+ orderTickSize = ONE,
167
+ orderStepSize = ONE,
168
+ ifTotalFactor = 0,
169
+ name = DEFAULT_MARKET_NAME
170
+ ): Promise<TransactionInstruction> {
171
+ const spotMarketIndex = this.getStateAccount().numberOfSpotMarkets;
91
172
  const spotMarket = await getSpotMarketPublicKey(
92
173
  this.program.programId,
93
174
  spotMarketIndex
@@ -115,11 +196,18 @@ export class AdminClient extends DriftClient {
115
196
  maintenanceLiabilityWeight,
116
197
  imfFactor,
117
198
  liquidatorFee,
199
+ ifLiquidationFee,
118
200
  activeStatus,
201
+ assetTier,
202
+ scaleInitialAssetWeightStart,
203
+ withdrawGuardThreshold,
204
+ orderTickSize,
205
+ orderStepSize,
206
+ ifTotalFactor,
119
207
  nameBuffer,
120
208
  {
121
209
  accounts: {
122
- admin: this.wallet.publicKey,
210
+ admin,
123
211
  state: await this.getStatePublicKey(),
124
212
  spotMarket,
125
213
  spotMarketVault,
@@ -134,27 +222,34 @@ export class AdminClient extends DriftClient {
134
222
  }
135
223
  );
136
224
 
137
- const tx = await this.buildTransaction(initializeIx);
225
+ return initializeIx;
226
+ }
138
227
 
139
- const { txSig } = await this.sendTransaction(tx, [], this.opts);
228
+ public async initializeSerumFulfillmentConfig(
229
+ marketIndex: number,
230
+ serumMarket: PublicKey,
231
+ serumProgram: PublicKey
232
+ ): Promise<TransactionSignature> {
233
+ const initializeIx = await this.getInitializeSerumFulfillmentConfigIx(
234
+ this.wallet.publicKey,
235
+ marketIndex,
236
+ serumMarket,
237
+ serumProgram
238
+ );
140
239
 
141
- // const { txSig } = await this.sendTransaction(initializeTx, [], this.opts);
240
+ const tx = await this.buildTransaction(initializeIx);
142
241
 
143
- await this.accountSubscriber.addSpotMarket(spotMarketIndex);
144
- await this.accountSubscriber.addOracle({
145
- source: oracleSource,
146
- publicKey: oracle,
147
- });
148
- await this.accountSubscriber.setSpotOracleMap();
242
+ const { txSig } = await this.sendTransaction(tx, [], this.opts);
149
243
 
150
244
  return txSig;
151
245
  }
152
246
 
153
- public async initializeSerumFulfillmentConfig(
247
+ public async getInitializeSerumFulfillmentConfigIx(
248
+ admin: PublicKey,
154
249
  marketIndex: number,
155
250
  serumMarket: PublicKey,
156
251
  serumProgram: PublicKey
157
- ): Promise<TransactionSignature> {
252
+ ): Promise<TransactionInstruction> {
158
253
  const serumOpenOrders = getSerumOpenOrdersPublicKey(
159
254
  this.program.programId,
160
255
  serumMarket
@@ -165,25 +260,35 @@ export class AdminClient extends DriftClient {
165
260
  serumMarket
166
261
  );
167
262
 
168
- const initializeIx =
169
- await this.program.instruction.initializeSerumFulfillmentConfig(
170
- marketIndex,
171
- {
172
- accounts: {
173
- admin: this.wallet.publicKey,
174
- state: await this.getStatePublicKey(),
175
- baseSpotMarket: this.getSpotMarketAccount(marketIndex).pubkey,
176
- quoteSpotMarket: this.getQuoteSpotMarketAccount().pubkey,
177
- driftSigner: this.getSignerPublicKey(),
178
- serumProgram,
179
- serumMarket,
180
- serumOpenOrders,
181
- rent: SYSVAR_RENT_PUBKEY,
182
- systemProgram: anchor.web3.SystemProgram.programId,
183
- serumFulfillmentConfig,
184
- },
185
- }
186
- );
263
+ return await this.program.instruction.initializeSerumFulfillmentConfig(
264
+ marketIndex,
265
+ {
266
+ accounts: {
267
+ admin,
268
+ state: await this.getStatePublicKey(),
269
+ baseSpotMarket: this.getSpotMarketAccount(marketIndex).pubkey,
270
+ quoteSpotMarket: this.getQuoteSpotMarketAccount().pubkey,
271
+ driftSigner: this.getSignerPublicKey(),
272
+ serumProgram,
273
+ serumMarket,
274
+ serumOpenOrders,
275
+ rent: SYSVAR_RENT_PUBKEY,
276
+ systemProgram: anchor.web3.SystemProgram.programId,
277
+ serumFulfillmentConfig,
278
+ },
279
+ }
280
+ );
281
+ }
282
+
283
+ public async initializePhoenixFulfillmentConfig(
284
+ marketIndex: number,
285
+ phoenixMarket: PublicKey
286
+ ): Promise<TransactionSignature> {
287
+ const initializeIx = await this.getInitializePhoenixFulfillmentConfigIx(
288
+ this.wallet.publicKey,
289
+ marketIndex,
290
+ phoenixMarket
291
+ );
187
292
 
188
293
  const tx = await this.buildTransaction(initializeIx);
189
294
 
@@ -192,39 +297,33 @@ export class AdminClient extends DriftClient {
192
297
  return txSig;
193
298
  }
194
299
 
195
- public async initializePhoenixFulfillmentConfig(
300
+ public async getInitializePhoenixFulfillmentConfigIx(
301
+ admin: PublicKey,
196
302
  marketIndex: number,
197
303
  phoenixMarket: PublicKey
198
- ): Promise<TransactionSignature> {
304
+ ): Promise<TransactionInstruction> {
199
305
  const phoenixFulfillmentConfig = getPhoenixFulfillmentConfigPublicKey(
200
306
  this.program.programId,
201
307
  phoenixMarket
202
308
  );
203
309
 
204
- const initializeIx =
205
- await this.program.instruction.initializePhoenixFulfillmentConfig(
206
- marketIndex,
207
- {
208
- accounts: {
209
- admin: this.wallet.publicKey,
210
- state: await this.getStatePublicKey(),
211
- baseSpotMarket: this.getSpotMarketAccount(marketIndex).pubkey,
212
- quoteSpotMarket: this.getQuoteSpotMarketAccount().pubkey,
213
- driftSigner: this.getSignerPublicKey(),
214
- phoenixMarket: phoenixMarket,
215
- phoenixProgram: PHOENIX_PROGRAM_ID,
216
- rent: SYSVAR_RENT_PUBKEY,
217
- systemProgram: anchor.web3.SystemProgram.programId,
218
- phoenixFulfillmentConfig,
219
- },
220
- }
221
- );
222
-
223
- const tx = await this.buildTransaction(initializeIx);
224
-
225
- const { txSig } = await this.sendTransaction(tx, [], this.opts);
226
-
227
- return txSig;
310
+ return await this.program.instruction.initializePhoenixFulfillmentConfig(
311
+ marketIndex,
312
+ {
313
+ accounts: {
314
+ admin,
315
+ state: await this.getStatePublicKey(),
316
+ baseSpotMarket: this.getSpotMarketAccount(marketIndex).pubkey,
317
+ quoteSpotMarket: this.getQuoteSpotMarketAccount().pubkey,
318
+ driftSigner: this.getSignerPublicKey(),
319
+ phoenixMarket: phoenixMarket,
320
+ phoenixProgram: PHOENIX_PROGRAM_ID,
321
+ rent: SYSVAR_RENT_PUBKEY,
322
+ systemProgram: anchor.web3.SystemProgram.programId,
323
+ phoenixFulfillmentConfig,
324
+ },
325
+ }
326
+ );
228
327
  }
229
328
 
230
329
  public async initializePerpMarket(
@@ -238,40 +337,52 @@ export class AdminClient extends DriftClient {
238
337
  marginRatioInitial = 2000,
239
338
  marginRatioMaintenance = 500,
240
339
  liquidatorFee = 0,
340
+ ifLiquidatorFee = 10000,
341
+ imfFactor = 0,
241
342
  activeStatus = true,
343
+ baseSpread = 0,
344
+ maxSpread = 142500,
345
+ maxOpenInterest = ZERO,
346
+ maxRevenueWithdrawPerPeriod = ZERO,
347
+ quoteMaxInsurance = ZERO,
348
+ orderStepSize = BASE_PRECISION.divn(10000),
349
+ orderTickSize = PRICE_PRECISION.divn(100000),
350
+ minOrderSize = BASE_PRECISION.divn(10000),
351
+ concentrationCoefScale = ONE,
352
+ curveUpdateIntensity = 0,
353
+ ammJitIntensity = 0,
242
354
  name = DEFAULT_MARKET_NAME
243
355
  ): Promise<TransactionSignature> {
244
356
  const currentPerpMarketIndex = this.getStateAccount().numberOfMarkets;
245
- const perpMarketPublicKey = await getPerpMarketPublicKey(
246
- this.program.programId,
247
- currentPerpMarketIndex
248
- );
249
357
 
250
- const nameBuffer = encodeName(name);
251
- const initializeMarketIx =
252
- await this.program.instruction.initializePerpMarket(
253
- marketIndex,
254
- baseAssetReserve,
255
- quoteAssetReserve,
256
- periodicity,
257
- pegMultiplier,
258
- oracleSource,
259
- marginRatioInitial,
260
- marginRatioMaintenance,
261
- liquidatorFee,
262
- activeStatus,
263
- nameBuffer,
264
- {
265
- accounts: {
266
- state: await this.getStatePublicKey(),
267
- admin: this.wallet.publicKey,
268
- oracle: priceOracle,
269
- perpMarket: perpMarketPublicKey,
270
- rent: SYSVAR_RENT_PUBKEY,
271
- systemProgram: anchor.web3.SystemProgram.programId,
272
- },
273
- }
274
- );
358
+ const initializeMarketIx = await this.getInitializePerpMarketIx(
359
+ this.wallet.publicKey,
360
+ marketIndex,
361
+ priceOracle,
362
+ baseAssetReserve,
363
+ quoteAssetReserve,
364
+ periodicity,
365
+ pegMultiplier,
366
+ oracleSource,
367
+ marginRatioInitial,
368
+ marginRatioMaintenance,
369
+ liquidatorFee,
370
+ ifLiquidatorFee,
371
+ imfFactor,
372
+ activeStatus,
373
+ baseSpread,
374
+ maxSpread,
375
+ maxOpenInterest,
376
+ maxRevenueWithdrawPerPeriod,
377
+ quoteMaxInsurance,
378
+ orderStepSize,
379
+ orderTickSize,
380
+ minOrderSize,
381
+ concentrationCoefScale,
382
+ curveUpdateIntensity,
383
+ ammJitIntensity,
384
+ name
385
+ );
275
386
  const tx = await this.buildTransaction(initializeMarketIx);
276
387
 
277
388
  const { txSig } = await this.sendTransaction(tx, [], this.opts);
@@ -290,6 +401,79 @@ export class AdminClient extends DriftClient {
290
401
  return txSig;
291
402
  }
292
403
 
404
+ public async getInitializePerpMarketIx(
405
+ admin: PublicKey,
406
+ marketIndex: number,
407
+ priceOracle: PublicKey,
408
+ baseAssetReserve: BN,
409
+ quoteAssetReserve: BN,
410
+ periodicity: BN,
411
+ pegMultiplier: BN = PEG_PRECISION,
412
+ oracleSource: OracleSource = OracleSource.PYTH,
413
+ marginRatioInitial = 2000,
414
+ marginRatioMaintenance = 500,
415
+ liquidatorFee = 0,
416
+ ifLiquidatorFee = 10000,
417
+ imfFactor = 0,
418
+ activeStatus = true,
419
+ baseSpread = 0,
420
+ maxSpread = 142500,
421
+ maxOpenInterest = ZERO,
422
+ maxRevenueWithdrawPerPeriod = ZERO,
423
+ quoteMaxInsurance = ZERO,
424
+ orderStepSize = BASE_PRECISION.divn(10000),
425
+ orderTickSize = PRICE_PRECISION.divn(100000),
426
+ minOrderSize = BASE_PRECISION.divn(10000),
427
+ concentrationCoefScale = ONE,
428
+ curveUpdateIntensity = 0,
429
+ ammJitIntensity = 0,
430
+ name = DEFAULT_MARKET_NAME
431
+ ): Promise<TransactionInstruction> {
432
+ const currentPerpMarketIndex = this.getStateAccount().numberOfMarkets;
433
+ const perpMarketPublicKey = await getPerpMarketPublicKey(
434
+ this.program.programId,
435
+ currentPerpMarketIndex
436
+ );
437
+
438
+ const nameBuffer = encodeName(name);
439
+ return await this.program.instruction.initializePerpMarket(
440
+ marketIndex,
441
+ baseAssetReserve,
442
+ quoteAssetReserve,
443
+ periodicity,
444
+ pegMultiplier,
445
+ oracleSource,
446
+ marginRatioInitial,
447
+ marginRatioMaintenance,
448
+ liquidatorFee,
449
+ ifLiquidatorFee,
450
+ imfFactor,
451
+ activeStatus,
452
+ baseSpread,
453
+ maxSpread,
454
+ maxOpenInterest,
455
+ maxRevenueWithdrawPerPeriod,
456
+ quoteMaxInsurance,
457
+ orderStepSize,
458
+ orderTickSize,
459
+ minOrderSize,
460
+ concentrationCoefScale,
461
+ curveUpdateIntensity,
462
+ ammJitIntensity,
463
+ nameBuffer,
464
+ {
465
+ accounts: {
466
+ state: await this.getStatePublicKey(),
467
+ admin,
468
+ oracle: priceOracle,
469
+ perpMarket: perpMarketPublicKey,
470
+ rent: SYSVAR_RENT_PUBKEY,
471
+ systemProgram: anchor.web3.SystemProgram.programId,
472
+ },
473
+ }
474
+ );
475
+ }
476
+
293
477
  public async deleteInitializedPerpMarket(
294
478
  marketIndex: number
295
479
  ): Promise<TransactionSignature> {
@@ -2172,4 +2356,30 @@ export class AdminClient extends DriftClient {
2172
2356
 
2173
2357
  return txSig;
2174
2358
  }
2359
+
2360
+ public async deletePrelaunchOracle(
2361
+ perpMarketIndex: number
2362
+ ): Promise<TransactionSignature> {
2363
+ const deletePrelaunchOracleIx =
2364
+ await this.program.instruction.deletePrelaunchOracle(perpMarketIndex, {
2365
+ accounts: {
2366
+ admin: this.wallet.publicKey,
2367
+ state: await this.getStatePublicKey(),
2368
+ prelaunchOracle: await getPrelaunchOraclePublicKey(
2369
+ this.program.programId,
2370
+ perpMarketIndex
2371
+ ),
2372
+ perpMarket: await getPerpMarketPublicKey(
2373
+ this.program.programId,
2374
+ perpMarketIndex
2375
+ ),
2376
+ },
2377
+ });
2378
+
2379
+ const tx = await this.buildTransaction(deletePrelaunchOracleIx);
2380
+
2381
+ const { txSig } = await this.sendTransaction(tx, [], this.opts);
2382
+
2383
+ return txSig;
2384
+ }
2175
2385
  }
@@ -0,0 +1,108 @@
1
+ import {
2
+ BlockhashWithExpiryBlockHeight,
3
+ Commitment,
4
+ Connection,
5
+ Context,
6
+ } from '@solana/web3.js';
7
+ import { BlockhashSubscriberConfig } from './types';
8
+
9
+ export class BlockhashSubscriber {
10
+ private connection: Connection;
11
+ private isSubscribed = false;
12
+ private latestBlockHeight: number;
13
+ private latestBlockHeightContext: Context | undefined;
14
+ private blockhashes: Array<BlockhashWithExpiryBlockHeight> = [];
15
+ private updateBlockhashIntervalId: NodeJS.Timeout | undefined;
16
+ private commitment: Commitment;
17
+ private updateIntervalMs: number;
18
+
19
+ constructor(config: BlockhashSubscriberConfig) {
20
+ if (!config.connection && !config.rpcUrl) {
21
+ throw new Error(
22
+ 'BlockhashSubscriber requires one of connection or rpcUrl must be provided'
23
+ );
24
+ }
25
+ this.connection = config.connection || new Connection(config.rpcUrl!);
26
+ this.commitment = config.commitment ?? 'confirmed';
27
+ this.updateIntervalMs = config.updateIntervalMs ?? 1000;
28
+ }
29
+
30
+ getBlockhashCacheSize(): number {
31
+ return this.blockhashes.length;
32
+ }
33
+
34
+ getLatestBlockHeight(): number {
35
+ return this.latestBlockHeight;
36
+ }
37
+
38
+ getLatestBlockHeightContext(): Context | undefined {
39
+ return this.latestBlockHeightContext;
40
+ }
41
+
42
+ getLatestBlockhash(
43
+ offset?: number
44
+ ): BlockhashWithExpiryBlockHeight | undefined {
45
+ if (this.blockhashes.length === 0) {
46
+ return undefined;
47
+ }
48
+ const clampedOffset = Math.max(
49
+ 0,
50
+ Math.min(this.blockhashes.length - 1, offset ?? 0)
51
+ );
52
+
53
+ return this.blockhashes[this.blockhashes.length - 1 - clampedOffset];
54
+ }
55
+
56
+ pruneBlockhashes() {
57
+ if (this.latestBlockHeight) {
58
+ this.blockhashes = this.blockhashes.filter(
59
+ (blockhash) => blockhash.lastValidBlockHeight > this.latestBlockHeight!
60
+ );
61
+ }
62
+ }
63
+
64
+ async updateBlockhash() {
65
+ const [resp, lastConfirmedBlockHeight] = await Promise.all([
66
+ this.connection.getLatestBlockhashAndContext({
67
+ commitment: this.commitment,
68
+ }),
69
+ this.connection.getBlockHeight({ commitment: this.commitment }),
70
+ ]);
71
+ this.latestBlockHeight = lastConfirmedBlockHeight;
72
+ this.latestBlockHeightContext = resp.context;
73
+
74
+ // avoid caching duplicate blockhashes
75
+ if (this.blockhashes.length > 0) {
76
+ if (
77
+ resp.value.blockhash ===
78
+ this.blockhashes[this.blockhashes.length - 1].blockhash
79
+ ) {
80
+ return;
81
+ }
82
+ }
83
+
84
+ this.blockhashes.push(resp.value);
85
+ this.pruneBlockhashes();
86
+ }
87
+
88
+ async subscribe() {
89
+ if (this.isSubscribed) {
90
+ return;
91
+ }
92
+ this.isSubscribed = true;
93
+
94
+ await this.updateBlockhash();
95
+ this.updateBlockhashIntervalId = setInterval(
96
+ this.updateBlockhash.bind(this),
97
+ this.updateIntervalMs
98
+ );
99
+ }
100
+
101
+ unsubscribe() {
102
+ if (this.updateBlockhashIntervalId) {
103
+ clearInterval(this.updateBlockhashIntervalId);
104
+ this.updateBlockhashIntervalId = undefined;
105
+ }
106
+ this.isSubscribed = false;
107
+ }
108
+ }
@@ -0,0 +1 @@
1
+ export * from './BlockhashSubscriber';
@@ -0,0 +1,8 @@
1
+ import { Commitment, Connection } from '@solana/web3.js';
2
+
3
+ export type BlockhashSubscriberConfig = {
4
+ rpcUrl?: string;
5
+ connection?: Connection;
6
+ commitment?: Commitment;
7
+ updateIntervalMs?: number;
8
+ };
@@ -219,6 +219,9 @@ export function getVammL2Generator({
219
219
  );
220
220
 
221
221
  baseSwapped = bidAmm.baseAssetReserve.sub(afterSwapBaseReserves).abs();
222
+ if (baseSwapped.eq(ZERO)) {
223
+ return;
224
+ }
222
225
  if (remainingBaseLiquidity.lt(baseSwapped)) {
223
226
  baseSwapped = remainingBaseLiquidity;
224
227
  [afterSwapQuoteReserves, afterSwapBaseReserves] =
@@ -299,6 +302,9 @@ export function getVammL2Generator({
299
302
  );
300
303
 
301
304
  baseSwapped = askAmm.baseAssetReserve.sub(afterSwapBaseReserves).abs();
305
+ if (baseSwapped.eq(ZERO)) {
306
+ return;
307
+ }
302
308
  if (remainingBaseLiquidity.lt(baseSwapped)) {
303
309
  baseSwapped = remainingBaseLiquidity;
304
310
  [afterSwapQuoteReserves, afterSwapBaseReserves] =
@@ -423,6 +429,30 @@ function groupL2Levels(
423
429
  return groupedLevels;
424
430
  }
425
431
 
432
+ /**
433
+ * Method to merge bids or asks by price
434
+ */
435
+ const mergeByPrice = (bidsOrAsks: L2Level[]) => {
436
+ const merged = new Map<string, L2Level>();
437
+ for (const level of bidsOrAsks) {
438
+ const key = level.price.toString();
439
+ if (merged.has(key)) {
440
+ const existing = merged.get(key);
441
+ existing.size = existing.size.add(level.size);
442
+ for (const [source, size] of Object.entries(level.sources)) {
443
+ if (existing.sources[source]) {
444
+ existing.sources[source] = existing.sources[source].add(size);
445
+ } else {
446
+ existing.sources[source] = size;
447
+ }
448
+ }
449
+ } else {
450
+ merged.set(key, cloneL2Level(level));
451
+ }
452
+ }
453
+ return Array.from(merged.values());
454
+ };
455
+
426
456
  /**
427
457
  * The purpose of this function is uncross the L2 orderbook by modifying the bid/ask price at the top of the book
428
458
  * This will make the liquidity look worse but more intuitive (users familiar with clob get confused w temporarily
@@ -461,8 +491,8 @@ export function uncrossL2(
461
491
  return { bids, asks };
462
492
  }
463
493
 
464
- const newBids = [];
465
- const newAsks = [];
494
+ const newBids: L2Level[] = [];
495
+ const newAsks: L2Level[] = [];
466
496
 
467
497
  const updateLevels = (newPrice: BN, oldLevel: L2Level, levels: L2Level[]) => {
468
498
  if (levels.length > 0 && levels[levels.length - 1].price.eq(newPrice)) {
@@ -522,19 +552,19 @@ export function uncrossL2(
522
552
  continue;
523
553
  }
524
554
 
525
- if (nextBid.price.gte(nextAsk.price)) {
526
- if (userBids.has(nextBid.price.toString())) {
527
- newBids.push(nextBid);
528
- bidIndex++;
529
- continue;
530
- }
555
+ if (userBids.has(nextBid.price.toString())) {
556
+ newBids.push(nextBid);
557
+ bidIndex++;
558
+ continue;
559
+ }
531
560
 
532
- if (userAsks.has(nextAsk.price.toString())) {
533
- newAsks.push(nextAsk);
534
- askIndex++;
535
- continue;
536
- }
561
+ if (userAsks.has(nextAsk.price.toString())) {
562
+ newAsks.push(nextAsk);
563
+ askIndex++;
564
+ continue;
565
+ }
537
566
 
567
+ if (nextBid.price.gte(nextAsk.price)) {
538
568
  if (
539
569
  nextBid.price.gt(referencePrice) &&
540
570
  nextAsk.price.gt(referencePrice)
@@ -588,8 +618,14 @@ export function uncrossL2(
588
618
  }
589
619
  }
590
620
 
621
+ newBids.sort((a, b) => b.price.cmp(a.price));
622
+ newAsks.sort((a, b) => a.price.cmp(b.price));
623
+
624
+ const finalNewBids = mergeByPrice(newBids);
625
+ const finalNewAsks = mergeByPrice(newAsks);
626
+
591
627
  return {
592
- bids: newBids,
593
- asks: newAsks,
628
+ bids: finalNewBids,
629
+ asks: finalNewAsks,
594
630
  };
595
631
  }