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

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 +5 -2
  3. package/lib/adminClient.js +17 -4
  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 +89 -1
  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 +73 -1
  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 +89 -1
  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
@@ -27,6 +27,6 @@ export declare class FastSingleTxSender extends BaseTxSender {
27
27
  });
28
28
  startBlockhashRefreshLoop(): void;
29
29
  prepareTx(tx: Transaction, additionalSigners: Array<Signer>, _opts: ConfirmOptions): Promise<Transaction>;
30
- getVersionedTransaction(ixs: TransactionInstruction[], lookupTableAccounts: AddressLookupTableAccount[], additionalSigners?: Array<Signer>, opts?: ConfirmOptions): Promise<VersionedTransaction>;
30
+ getVersionedTransaction(ixs: TransactionInstruction[], lookupTableAccounts: AddressLookupTableAccount[], additionalSigners?: Array<Signer>, opts?: ConfirmOptions, blockhash?: string): Promise<VersionedTransaction>;
31
31
  sendRawTransaction(rawTransaction: Buffer | Uint8Array, opts: ConfirmOptions): Promise<TxSigAndSlot>;
32
32
  }
@@ -52,7 +52,7 @@ class FastSingleTxSender extends baseTxSender_1.BaseTxSender {
52
52
  const signedTx = await this.wallet.signTransaction(tx);
53
53
  return signedTx;
54
54
  }
55
- async getVersionedTransaction(ixs, lookupTableAccounts, additionalSigners, opts) {
55
+ async getVersionedTransaction(ixs, lookupTableAccounts, additionalSigners, opts, blockhash) {
56
56
  var _a;
57
57
  if (additionalSigners === undefined) {
58
58
  additionalSigners = [];
@@ -60,10 +60,18 @@ class FastSingleTxSender extends baseTxSender_1.BaseTxSender {
60
60
  if (opts === undefined) {
61
61
  opts = this.opts;
62
62
  }
63
+ let recentBlockhash = '';
64
+ if (blockhash) {
65
+ recentBlockhash = blockhash;
66
+ }
67
+ else {
68
+ recentBlockhash =
69
+ (_a = this.recentBlockhash) !== null && _a !== void 0 ? _a : (await this.connection.getLatestBlockhash(opts.preflightCommitment))
70
+ .blockhash;
71
+ }
63
72
  const message = new web3_js_1.TransactionMessage({
64
73
  payerKey: this.wallet.publicKey,
65
- recentBlockhash: (_a = this.recentBlockhash) !== null && _a !== void 0 ? _a : (await this.connection.getLatestBlockhash(opts.preflightCommitment))
66
- .blockhash,
74
+ recentBlockhash,
67
75
  instructions: ixs,
68
76
  }).compileToV0Message(lookupTableAccounts);
69
77
  const tx = new web3_js_1.VersionedTransaction(message);
package/lib/tx/types.d.ts CHANGED
@@ -17,7 +17,7 @@ export interface TxSender {
17
17
  wallet: IWallet;
18
18
  send(tx: Transaction, additionalSigners?: Array<Signer>, opts?: ConfirmOptions, preSigned?: boolean, extraConfirmationOptions?: ExtraConfirmationOptions): Promise<TxSigAndSlot>;
19
19
  sendVersionedTransaction(tx: VersionedTransaction, additionalSigners?: Array<Signer>, opts?: ConfirmOptions, preSigned?: boolean, extraConfirmationOptions?: ExtraConfirmationOptions): Promise<TxSigAndSlot>;
20
- getVersionedTransaction(ixs: TransactionInstruction[], lookupTableAccounts: AddressLookupTableAccount[], additionalSigners?: Array<Signer>, opts?: ConfirmOptions): Promise<VersionedTransaction>;
20
+ getVersionedTransaction(ixs: TransactionInstruction[], lookupTableAccounts: AddressLookupTableAccount[], additionalSigners?: Array<Signer>, opts?: ConfirmOptions, blockhash?: string): Promise<VersionedTransaction>;
21
21
  sendRawTransaction(rawTransaction: Buffer | Uint8Array, opts: ConfirmOptions): Promise<TxSigAndSlot>;
22
22
  simulateTransaction(tx: VersionedTransaction): Promise<boolean>;
23
23
  getTimeoutCount(): number;
package/lib/types.d.ts CHANGED
@@ -80,6 +80,9 @@ export declare class ContractTier {
80
80
  static readonly SPECULATIVE: {
81
81
  speculative: {};
82
82
  };
83
+ static readonly HIGHLY_SPECULATIVE: {
84
+ highlySpeculative: {};
85
+ };
83
86
  static readonly ISOLATED: {
84
87
  isolated: {};
85
88
  };
package/lib/types.js CHANGED
@@ -60,6 +60,7 @@ ContractTier.A = { a: {} };
60
60
  ContractTier.B = { b: {} };
61
61
  ContractTier.C = { c: {} };
62
62
  ContractTier.SPECULATIVE = { speculative: {} };
63
+ ContractTier.HIGHLY_SPECULATIVE = { highlySpeculative: {} };
63
64
  ContractTier.ISOLATED = { isolated: {} };
64
65
  class AssetTier {
65
66
  }
package/lib/user.d.ts CHANGED
@@ -246,13 +246,15 @@ export declare class User {
246
246
  /**
247
247
  * Calculate the liquidation price of a perp position, with optional parameter to calculate the liquidation price after a trade
248
248
  * @param marketIndex
249
- * @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
249
+ * @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^9
250
+ * @param estimatedEntryPrice
250
251
  * @param marginCategory // allow Initial to be passed in if we are trying to calculate price for DLP de-risking
252
+ * @param includeOpenOrders
251
253
  * @returns Precision : PRICE_PRECISION
252
254
  */
253
- liquidationPrice(marketIndex: number, positionBaseSizeChange?: BN, estimatedEntryPrice?: BN, marginCategory?: MarginCategory): BN;
254
- calculateEntriesEffectOnFreeCollateral(market: PerpMarketAccount, oraclePrice: BN, perpPosition: PerpPosition, positionBaseSizeChange: BN, estimatedEntryPrice: BN): BN;
255
- calculateFreeCollateralDeltaForPerp(market: PerpMarketAccount, perpPosition: PerpPosition, positionBaseSizeChange: BN, marginCategory?: MarginCategory): BN | undefined;
255
+ liquidationPrice(marketIndex: number, positionBaseSizeChange?: BN, estimatedEntryPrice?: BN, marginCategory?: MarginCategory, includeOpenOrders?: boolean): BN;
256
+ calculateEntriesEffectOnFreeCollateral(market: PerpMarketAccount, oraclePrice: BN, perpPosition: PerpPosition, positionBaseSizeChange: BN, estimatedEntryPrice: BN, includeOpenOrders: boolean): BN;
257
+ calculateFreeCollateralDeltaForPerp(market: PerpMarketAccount, perpPosition: PerpPosition, positionBaseSizeChange: BN, marginCategory?: MarginCategory, includeOpenOrders?: boolean): BN | undefined;
256
258
  calculateFreeCollateralDeltaForSpot(market: SpotMarketAccount, signedTokenAmount: BN, marginCategory?: MarginCategory): BN;
257
259
  /**
258
260
  * Calculates the estimated liquidation price for a position after closing a quote amount of the position.
package/lib/user.js CHANGED
@@ -1082,13 +1082,15 @@ class User {
1082
1082
  /**
1083
1083
  * Calculate the liquidation price of a perp position, with optional parameter to calculate the liquidation price after a trade
1084
1084
  * @param marketIndex
1085
- * @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^13
1085
+ * @param positionBaseSizeChange // change in position size to calculate liquidation price for : Precision 10^9
1086
+ * @param estimatedEntryPrice
1086
1087
  * @param marginCategory // allow Initial to be passed in if we are trying to calculate price for DLP de-risking
1088
+ * @param includeOpenOrders
1087
1089
  * @returns Precision : PRICE_PRECISION
1088
1090
  */
1089
- liquidationPrice(marketIndex, positionBaseSizeChange = numericConstants_1.ZERO, estimatedEntryPrice = numericConstants_1.ZERO, marginCategory = 'Maintenance') {
1091
+ liquidationPrice(marketIndex, positionBaseSizeChange = numericConstants_1.ZERO, estimatedEntryPrice = numericConstants_1.ZERO, marginCategory = 'Maintenance', includeOpenOrders = false) {
1090
1092
  const totalCollateral = this.getTotalCollateral(marginCategory);
1091
- const marginRequirement = this.getMarginRequirement(marginCategory, undefined, false);
1093
+ const marginRequirement = this.getMarginRequirement(marginCategory, undefined, false, includeOpenOrders);
1092
1094
  let freeCollateral = _1.BN.max(numericConstants_1.ZERO, totalCollateral.sub(marginRequirement));
1093
1095
  const oracle = this.driftClient.getPerpMarketAccount(marketIndex).amm.oracle;
1094
1096
  const oraclePrice = this.driftClient.getOracleDataForPerpMarket(marketIndex).price;
@@ -1096,9 +1098,9 @@ class User {
1096
1098
  const currentPerpPosition = this.getPerpPositionWithLPSettle(marketIndex, undefined, true)[0] ||
1097
1099
  this.getEmptyPosition(marketIndex);
1098
1100
  positionBaseSizeChange = (0, _1.standardizeBaseAssetAmount)(positionBaseSizeChange, market.amm.orderStepSize);
1099
- const freeCollateralChangeFromNewPosition = this.calculateEntriesEffectOnFreeCollateral(market, oraclePrice, currentPerpPosition, positionBaseSizeChange, estimatedEntryPrice);
1101
+ const freeCollateralChangeFromNewPosition = this.calculateEntriesEffectOnFreeCollateral(market, oraclePrice, currentPerpPosition, positionBaseSizeChange, estimatedEntryPrice, includeOpenOrders);
1100
1102
  freeCollateral = freeCollateral.add(freeCollateralChangeFromNewPosition);
1101
- let freeCollateralDelta = this.calculateFreeCollateralDeltaForPerp(market, currentPerpPosition, positionBaseSizeChange, marginCategory);
1103
+ let freeCollateralDelta = this.calculateFreeCollateralDeltaForPerp(market, currentPerpPosition, positionBaseSizeChange, marginCategory, includeOpenOrders);
1102
1104
  if (!freeCollateralDelta) {
1103
1105
  return new _1.BN(-1);
1104
1106
  }
@@ -1125,7 +1127,7 @@ class User {
1125
1127
  }
1126
1128
  return liqPrice;
1127
1129
  }
1128
- calculateEntriesEffectOnFreeCollateral(market, oraclePrice, perpPosition, positionBaseSizeChange, estimatedEntryPrice) {
1130
+ calculateEntriesEffectOnFreeCollateral(market, oraclePrice, perpPosition, positionBaseSizeChange, estimatedEntryPrice, includeOpenOrders) {
1129
1131
  let freeCollateralChange = numericConstants_1.ZERO;
1130
1132
  // update free collateral to account for change in pnl from new position
1131
1133
  if (!estimatedEntryPrice.eq(numericConstants_1.ZERO) && !positionBaseSizeChange.eq(numericConstants_1.ZERO)) {
@@ -1139,8 +1141,6 @@ class User {
1139
1141
  freeCollateralChange = costBasis.sub(newPositionValue);
1140
1142
  }
1141
1143
  else {
1142
- console.log('newPositionValue', newPositionValue.toString());
1143
- console.log('costBasis', costBasis.toString());
1144
1144
  freeCollateralChange = newPositionValue.sub(costBasis);
1145
1145
  }
1146
1146
  // assume worst fee tier
@@ -1150,30 +1150,33 @@ class User {
1150
1150
  .divn(takerFeeTier.feeDenominator);
1151
1151
  freeCollateralChange = freeCollateralChange.sub(takerFee);
1152
1152
  }
1153
- const worstCaseBaseAssetAmount = (0, margin_1.calculateWorstCaseBaseAssetAmount)(perpPosition);
1154
- const newWorstCaseBaseAssetAmount = worstCaseBaseAssetAmount.add(positionBaseSizeChange);
1155
- const newMarginRatio = (0, _1.calculateMarketMarginRatio)(market, newWorstCaseBaseAssetAmount.abs(), 'Maintenance');
1153
+ const baseAssetAmount = includeOpenOrders
1154
+ ? (0, margin_1.calculateWorstCaseBaseAssetAmount)(perpPosition)
1155
+ : perpPosition.baseAssetAmount;
1156
+ const newBaseAssetAmount = baseAssetAmount.add(positionBaseSizeChange);
1157
+ const newMarginRatio = (0, _1.calculateMarketMarginRatio)(market, newBaseAssetAmount.abs(), 'Maintenance');
1156
1158
  // update free collateral to account for new margin requirement from position change
1157
- freeCollateralChange = freeCollateralChange.sub(newWorstCaseBaseAssetAmount
1159
+ freeCollateralChange = freeCollateralChange.sub(newBaseAssetAmount
1158
1160
  .abs()
1159
- .sub(worstCaseBaseAssetAmount.abs())
1161
+ .sub(baseAssetAmount.abs())
1160
1162
  .mul(oraclePrice)
1161
1163
  .div(numericConstants_1.BASE_PRECISION)
1162
1164
  .mul(new _1.BN(newMarginRatio))
1163
1165
  .div(numericConstants_1.MARGIN_PRECISION));
1164
1166
  return freeCollateralChange;
1165
1167
  }
1166
- calculateFreeCollateralDeltaForPerp(market, perpPosition, positionBaseSizeChange, marginCategory = 'Maintenance') {
1167
- const currentBaseAssetAmount = perpPosition.baseAssetAmount;
1168
- const worstCaseBaseAssetAmount = (0, margin_1.calculateWorstCaseBaseAssetAmount)(perpPosition);
1169
- const orderBaseAssetAmount = worstCaseBaseAssetAmount.sub(currentBaseAssetAmount);
1170
- const proposedBaseAssetAmount = currentBaseAssetAmount.add(positionBaseSizeChange);
1171
- const proposedWorstCaseBaseAssetAmount = worstCaseBaseAssetAmount.add(positionBaseSizeChange);
1172
- const marginRatio = (0, _1.calculateMarketMarginRatio)(market, proposedWorstCaseBaseAssetAmount.abs(), marginCategory, this.getUserAccount().maxMarginRatio);
1168
+ calculateFreeCollateralDeltaForPerp(market, perpPosition, positionBaseSizeChange, marginCategory = 'Maintenance', includeOpenOrders = false) {
1169
+ const baseAssetAmount = includeOpenOrders
1170
+ ? (0, margin_1.calculateWorstCaseBaseAssetAmount)(perpPosition)
1171
+ : perpPosition.baseAssetAmount;
1172
+ // zero if include orders == false
1173
+ const orderBaseAssetAmount = baseAssetAmount.sub(perpPosition.baseAssetAmount);
1174
+ const proposedBaseAssetAmount = baseAssetAmount.add(positionBaseSizeChange);
1175
+ const marginRatio = (0, _1.calculateMarketMarginRatio)(market, proposedBaseAssetAmount.abs(), marginCategory, this.getUserAccount().maxMarginRatio);
1173
1176
  const marginRatioQuotePrecision = new _1.BN(marginRatio)
1174
1177
  .mul(numericConstants_1.QUOTE_PRECISION)
1175
1178
  .div(numericConstants_1.MARGIN_PRECISION);
1176
- if (proposedWorstCaseBaseAssetAmount.eq(numericConstants_1.ZERO)) {
1179
+ if (proposedBaseAssetAmount.eq(numericConstants_1.ZERO)) {
1177
1180
  return undefined;
1178
1181
  }
1179
1182
  let freeCollateralDelta = numericConstants_1.ZERO;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.74.0-beta.1",
3
+ "version": "2.74.0-beta.10",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -31,7 +31,13 @@ import {
31
31
  import { squareRootBN } from './math/utils';
32
32
  import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
33
33
  import { DriftClient } from './driftClient';
34
- import { PEG_PRECISION } from './constants/numericConstants';
34
+ import {
35
+ PEG_PRECISION,
36
+ ZERO,
37
+ ONE,
38
+ BASE_PRECISION,
39
+ PRICE_PRECISION,
40
+ } from './constants/numericConstants';
35
41
  import { calculateTargetPriceTrade } from './math/trade';
36
42
  import { calculateAmmReservesAfterSwap, getSwapDirection } from './math/amm';
37
43
  import { PROGRAM_ID as PHOENIX_PROGRAM_ID } from '@ellipsis-labs/phoenix-sdk';
@@ -84,7 +90,14 @@ export class AdminClient extends DriftClient {
84
90
  maintenanceLiabilityWeight: number,
85
91
  imfFactor = 0,
86
92
  liquidatorFee = 0,
93
+ ifLiquidationFee = 0,
87
94
  activeStatus = true,
95
+ assetTier = AssetTier.COLLATERAL,
96
+ scaleInitialAssetWeightStart = ZERO,
97
+ withdrawGuardThreshold = ZERO,
98
+ orderTickSize = ONE,
99
+ orderStepSize = ONE,
100
+ ifTotalFactor = 0,
88
101
  name = DEFAULT_MARKET_NAME
89
102
  ): Promise<TransactionSignature> {
90
103
  const spotMarketIndex = this.getStateAccount().numberOfSpotMarkets;
@@ -115,7 +128,14 @@ export class AdminClient extends DriftClient {
115
128
  maintenanceLiabilityWeight,
116
129
  imfFactor,
117
130
  liquidatorFee,
131
+ ifLiquidationFee,
118
132
  activeStatus,
133
+ assetTier,
134
+ scaleInitialAssetWeightStart,
135
+ withdrawGuardThreshold,
136
+ orderTickSize,
137
+ orderStepSize,
138
+ ifTotalFactor,
119
139
  nameBuffer,
120
140
  {
121
141
  accounts: {
@@ -238,7 +258,20 @@ export class AdminClient extends DriftClient {
238
258
  marginRatioInitial = 2000,
239
259
  marginRatioMaintenance = 500,
240
260
  liquidatorFee = 0,
261
+ ifLiquidatorFee = 10000,
262
+ imfFactor = 0,
241
263
  activeStatus = true,
264
+ baseSpread = 0,
265
+ maxSpread = 142500,
266
+ maxOpenInterest = ZERO,
267
+ maxRevenueWithdrawPerPeriod = ZERO,
268
+ quoteMaxInsurance = ZERO,
269
+ orderStepSize = BASE_PRECISION.divn(10000),
270
+ orderTickSize = PRICE_PRECISION.divn(100000),
271
+ minOrderSize = BASE_PRECISION.divn(10000),
272
+ concentrationCoefScale = ONE,
273
+ curveUpdateIntensity = 0,
274
+ ammJitIntensity = 0,
242
275
  name = DEFAULT_MARKET_NAME
243
276
  ): Promise<TransactionSignature> {
244
277
  const currentPerpMarketIndex = this.getStateAccount().numberOfMarkets;
@@ -259,7 +292,20 @@ export class AdminClient extends DriftClient {
259
292
  marginRatioInitial,
260
293
  marginRatioMaintenance,
261
294
  liquidatorFee,
295
+ ifLiquidatorFee,
296
+ imfFactor,
262
297
  activeStatus,
298
+ baseSpread,
299
+ maxSpread,
300
+ maxOpenInterest,
301
+ maxRevenueWithdrawPerPeriod,
302
+ quoteMaxInsurance,
303
+ orderStepSize,
304
+ orderTickSize,
305
+ minOrderSize,
306
+ concentrationCoefScale,
307
+ curveUpdateIntensity,
308
+ ammJitIntensity,
263
309
  nameBuffer,
264
310
  {
265
311
  accounts: {
@@ -2172,4 +2218,30 @@ export class AdminClient extends DriftClient {
2172
2218
 
2173
2219
  return txSig;
2174
2220
  }
2221
+
2222
+ public async deletePrelaunchOracle(
2223
+ perpMarketIndex: number
2224
+ ): Promise<TransactionSignature> {
2225
+ const deletePrelaunchOracleIx =
2226
+ await this.program.instruction.deletePrelaunchOracle(perpMarketIndex, {
2227
+ accounts: {
2228
+ admin: this.wallet.publicKey,
2229
+ state: await this.getStatePublicKey(),
2230
+ prelaunchOracle: await getPrelaunchOraclePublicKey(
2231
+ this.program.programId,
2232
+ perpMarketIndex
2233
+ ),
2234
+ perpMarket: await getPerpMarketPublicKey(
2235
+ this.program.programId,
2236
+ perpMarketIndex
2237
+ ),
2238
+ },
2239
+ });
2240
+
2241
+ const tx = await this.buildTransaction(deletePrelaunchOracleIx);
2242
+
2243
+ const { txSig } = await this.sendTransaction(tx, [], this.opts);
2244
+
2245
+ return txSig;
2246
+ }
2175
2247
  }
@@ -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
  }
@@ -1074,6 +1074,43 @@ export class DriftClient {
1074
1074
  return ix;
1075
1075
  }
1076
1076
 
1077
+ public async updateUserReduceOnly(
1078
+ updates: { reduceOnly: boolean; subAccountId: number }[]
1079
+ ): Promise<TransactionSignature> {
1080
+ const ixs = await Promise.all(
1081
+ updates.map(async ({ reduceOnly, subAccountId }) => {
1082
+ return await this.getUpdateUserReduceOnlyIx(reduceOnly, subAccountId);
1083
+ })
1084
+ );
1085
+
1086
+ const tx = await this.buildTransaction(ixs, this.txParams);
1087
+
1088
+ const { txSig } = await this.sendTransaction(tx, [], this.opts);
1089
+ return txSig;
1090
+ }
1091
+
1092
+ public async getUpdateUserReduceOnlyIx(
1093
+ reduceOnly: boolean,
1094
+ subAccountId: number
1095
+ ) {
1096
+ const ix = await this.program.instruction.updateUserReduceOnly(
1097
+ subAccountId,
1098
+ reduceOnly,
1099
+ {
1100
+ accounts: {
1101
+ user: getUserAccountPublicKeySync(
1102
+ this.program.programId,
1103
+ this.wallet.publicKey,
1104
+ subAccountId
1105
+ ),
1106
+ authority: this.wallet.publicKey,
1107
+ },
1108
+ }
1109
+ );
1110
+
1111
+ return ix;
1112
+ }
1113
+
1077
1114
  public async fetchAllUserAccounts(
1078
1115
  includeIdle = true
1079
1116
  ): Promise<ProgramAccount<UserAccount>[]> {