@drift-labs/sdk 2.96.0-beta.2 → 2.96.0-beta.20

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 (110) hide show
  1. package/README.md +1 -0
  2. package/VERSION +1 -1
  3. package/bun.lockb +0 -0
  4. package/lib/accounts/pollingDriftClientAccountSubscriber.d.ts +5 -3
  5. package/lib/accounts/pollingDriftClientAccountSubscriber.js +24 -1
  6. package/lib/accounts/types.d.ts +5 -8
  7. package/lib/accounts/types.js +7 -1
  8. package/lib/accounts/utils.d.ts +7 -0
  9. package/lib/accounts/utils.js +33 -1
  10. package/lib/accounts/webSocketAccountSubscriber.d.ts +1 -1
  11. package/lib/accounts/webSocketDriftClientAccountSubscriber.d.ts +8 -7
  12. package/lib/accounts/webSocketDriftClientAccountSubscriber.js +24 -1
  13. package/lib/accounts/webSocketProgramAccountSubscriber.d.ts +1 -1
  14. package/lib/config.d.ts +5 -1
  15. package/lib/config.js +9 -1
  16. package/lib/constants/perpMarkets.js +21 -0
  17. package/lib/constants/spotMarkets.js +12 -1
  18. package/lib/driftClient.d.ts +44 -9
  19. package/lib/driftClient.js +181 -61
  20. package/lib/driftClientConfig.d.ts +2 -6
  21. package/lib/events/eventSubscriber.js +9 -8
  22. package/lib/events/types.js +1 -5
  23. package/lib/idl/drift.json +169 -1
  24. package/lib/index.d.ts +1 -0
  25. package/lib/index.js +1 -0
  26. package/lib/math/margin.d.ts +16 -1
  27. package/lib/math/margin.js +67 -1
  28. package/lib/orderParams.js +8 -8
  29. package/lib/orderSubscriber/OrderSubscriber.d.ts +1 -2
  30. package/lib/orderSubscriber/OrderSubscriber.js +4 -19
  31. package/lib/orderSubscriber/types.d.ts +0 -9
  32. package/lib/tokenFaucet.js +2 -1
  33. package/lib/tx/baseTxSender.js +2 -2
  34. package/lib/tx/fastSingleTxSender.js +2 -2
  35. package/lib/tx/forwardOnlyTxSender.js +2 -2
  36. package/lib/tx/retryTxSender.js +2 -2
  37. package/lib/tx/txHandler.js +10 -7
  38. package/lib/tx/whileValidTxSender.d.ts +2 -4
  39. package/lib/tx/whileValidTxSender.js +16 -17
  40. package/lib/types.d.ts +21 -1
  41. package/lib/types.js +6 -1
  42. package/lib/user.d.ts +4 -1
  43. package/lib/user.js +13 -13
  44. package/lib/userConfig.d.ts +1 -6
  45. package/lib/userMap/userMap.js +0 -14
  46. package/lib/userMap/userMapConfig.d.ts +0 -7
  47. package/lib/userStatsConfig.d.ts +0 -6
  48. package/lib/util/TransactionConfirmationManager.d.ts +14 -0
  49. package/lib/util/TransactionConfirmationManager.js +96 -0
  50. package/package.json +4 -5
  51. package/src/accounts/pollingDriftClientAccountSubscriber.ts +41 -5
  52. package/src/accounts/types.ts +6 -9
  53. package/src/accounts/utils.ts +42 -0
  54. package/src/accounts/webSocketAccountSubscriber.ts +1 -1
  55. package/src/accounts/webSocketDriftClientAccountSubscriber.ts +43 -8
  56. package/src/accounts/webSocketProgramAccountSubscriber.ts +1 -1
  57. package/src/config.ts +15 -1
  58. package/src/constants/perpMarkets.ts +22 -0
  59. package/src/constants/spotMarkets.ts +14 -1
  60. package/src/driftClient.ts +423 -91
  61. package/src/driftClientConfig.ts +2 -7
  62. package/src/events/eventSubscriber.ts +18 -11
  63. package/src/events/types.ts +1 -5
  64. package/src/idl/drift.json +169 -1
  65. package/src/index.ts +1 -0
  66. package/src/math/margin.ts +137 -1
  67. package/src/orderParams.ts +20 -12
  68. package/src/orderSubscriber/OrderSubscriber.ts +1 -15
  69. package/src/orderSubscriber/types.ts +0 -10
  70. package/src/tokenFaucet.ts +2 -2
  71. package/src/tx/baseTxSender.ts +2 -2
  72. package/src/tx/fastSingleTxSender.ts +2 -2
  73. package/src/tx/forwardOnlyTxSender.ts +2 -2
  74. package/src/tx/retryTxSender.ts +2 -2
  75. package/src/tx/txHandler.ts +8 -2
  76. package/src/tx/whileValidTxSender.ts +23 -26
  77. package/src/types.ts +30 -1
  78. package/src/user.ts +35 -13
  79. package/src/userConfig.ts +1 -7
  80. package/src/userMap/userMap.ts +1 -17
  81. package/src/userMap/userMapConfig.ts +0 -8
  82. package/src/userStatsConfig.ts +0 -7
  83. package/src/util/TransactionConfirmationManager.ts +155 -0
  84. package/tests/ci/idl.ts +12 -3
  85. package/tests/ci/verifyConstants.ts +13 -0
  86. package/tests/tx/TransactionConfirmationManager.test.ts +286 -0
  87. package/lib/accounts/grpcAccountSubscriber.d.ts +0 -16
  88. package/lib/accounts/grpcAccountSubscriber.js +0 -155
  89. package/lib/accounts/grpcDriftClientAccountSubscriber.d.ts +0 -13
  90. package/lib/accounts/grpcDriftClientAccountSubscriber.js +0 -96
  91. package/lib/accounts/grpcInsuranceFundStakeAccountSubscriber.d.ts +0 -10
  92. package/lib/accounts/grpcInsuranceFundStakeAccountSubscriber.js +0 -30
  93. package/lib/accounts/grpcProgramAccountSubscriber.d.ts +0 -19
  94. package/lib/accounts/grpcProgramAccountSubscriber.js +0 -161
  95. package/lib/accounts/grpcUserAccountSubscriber.d.ts +0 -10
  96. package/lib/accounts/grpcUserAccountSubscriber.js +0 -28
  97. package/lib/accounts/grpcUserStatsAccountSubscriber.d.ts +0 -10
  98. package/lib/accounts/grpcUserStatsAccountSubscriber.js +0 -28
  99. package/lib/orderSubscriber/grpcSubscription.d.ts +0 -25
  100. package/lib/orderSubscriber/grpcSubscription.js +0 -68
  101. package/lib/userMap/grpcSubscription.d.ts +0 -26
  102. package/lib/userMap/grpcSubscription.js +0 -42
  103. package/src/accounts/grpcAccountSubscriber.ts +0 -158
  104. package/src/accounts/grpcDriftClientAccountSubscriber.ts +0 -196
  105. package/src/accounts/grpcInsuranceFundStakeAccountSubscriber.ts +0 -62
  106. package/src/accounts/grpcProgramAccountSubscriber.ts +0 -181
  107. package/src/accounts/grpcUserAccountSubscriber.ts +0 -48
  108. package/src/accounts/grpcUserStatsAccountSubscriber.ts +0 -51
  109. package/src/orderSubscriber/grpcSubscription.ts +0 -126
  110. package/src/userMap/grpcSubscription.ts +0 -83
@@ -29,6 +29,7 @@ import { CachedBlockhashFetcher } from './blockhashFetcher/cachedBlockhashFetche
29
29
  import { BaseBlockhashFetcher } from './blockhashFetcher/baseBlockhashFetcher';
30
30
  import { BlockhashFetcher } from './blockhashFetcher/types';
31
31
  import { isVersionedTransaction } from './utils';
32
+ import { DEFAULT_CONFIRMATION_OPTS } from '../config';
32
33
 
33
34
  /**
34
35
  * Explanation for SIGNATURE_BLOCK_AND_EXPIRY:
@@ -81,7 +82,8 @@ export class TxHandler {
81
82
  private preSignedCb?: () => void;
82
83
  private onSignedCb?: (txSigs: DriftClientMetricsEvents['txSigned']) => void;
83
84
 
84
- private blockhashCommitment: Commitment = 'finalized';
85
+ private blockhashCommitment: Commitment =
86
+ DEFAULT_CONFIRMATION_OPTS.commitment;
85
87
  private blockHashFetcher: BlockhashFetcher;
86
88
 
87
89
  constructor(props: {
@@ -98,6 +100,11 @@ export class TxHandler {
98
100
  this.connection = props.connection;
99
101
  this.wallet = props.wallet;
100
102
  this.confirmationOptions = props.confirmationOptions;
103
+ this.blockhashCommitment =
104
+ props.confirmationOptions?.preflightCommitment ??
105
+ props?.connection?.commitment ??
106
+ this.blockhashCommitment ??
107
+ 'confirmed';
101
108
 
102
109
  this.blockHashFetcher = props?.config?.blockhashCachingEnabled
103
110
  ? new CachedBlockhashFetcher(
@@ -536,7 +543,6 @@ export class TxHandler {
536
543
  }
537
544
  } else {
538
545
  const marketLookupTable = await fetchMarketLookupTableAccount();
539
-
540
546
  lookupTables = lookupTables
541
547
  ? [...lookupTables, marketLookupTable]
542
548
  : [marketLookupTable];
@@ -1,22 +1,20 @@
1
1
  import { TxSigAndSlot } from './types';
2
2
  import {
3
- Commitment,
4
3
  ConfirmOptions,
5
4
  Connection,
5
+ SendTransactionError,
6
6
  Signer,
7
7
  Transaction,
8
8
  VersionedTransaction,
9
9
  } from '@solana/web3.js';
10
- import { AnchorProvider } from '@coral-xyz/anchor';
11
10
  import { BaseTxSender } from './baseTxSender';
12
11
  import bs58 from 'bs58';
13
12
  import { TxHandler } from './txHandler';
14
13
  import { IWallet } from '../types';
14
+ import { DEFAULT_CONFIRMATION_OPTS } from '../config';
15
15
 
16
16
  const DEFAULT_RETRY = 2000;
17
17
 
18
- const VALID_BLOCK_HEIGHT_OFFSET = -150; // This is a bit of weirdness but the lastValidBlockHeight value returned from connection.getLatestBlockhash is always 300 blocks ahead of the current block, even though the transaction actually expires after 150 blocks. This accounts for that so that we can at least accuractely estimate the transaction expiry.
19
-
20
18
  type ResolveReference = {
21
19
  resolve?: () => void;
22
20
  };
@@ -33,7 +31,6 @@ export class WhileValidTxSender extends BaseTxSender {
33
31
  string,
34
32
  { blockhash: string; lastValidBlockHeight: number }
35
33
  >();
36
- blockhashCommitment: Commitment;
37
34
 
38
35
  useBlockHeightOffset = true;
39
36
 
@@ -62,11 +59,10 @@ export class WhileValidTxSender extends BaseTxSender {
62
59
  public constructor({
63
60
  connection,
64
61
  wallet,
65
- opts = { ...AnchorProvider.defaultOptions(), maxRetries: 0 },
62
+ opts = { ...DEFAULT_CONFIRMATION_OPTS, maxRetries: 0 },
66
63
  retrySleep = DEFAULT_RETRY,
67
64
  additionalConnections = new Array<Connection>(),
68
65
  additionalTxSenderCallbacks = [],
69
- blockhashCommitment = 'finalized',
70
66
  txHandler,
71
67
  trackTxLandRate,
72
68
  txLandRateLookbackWindowMinutes,
@@ -78,7 +74,6 @@ export class WhileValidTxSender extends BaseTxSender {
78
74
  retrySleep?: number;
79
75
  additionalConnections?;
80
76
  additionalTxSenderCallbacks?: ((base58EncodedTx: string) => void)[];
81
- blockhashCommitment?: Commitment;
82
77
  txHandler?: TxHandler;
83
78
  trackTxLandRate?: boolean;
84
79
  txLandRateLookbackWindowMinutes?: number;
@@ -96,7 +91,6 @@ export class WhileValidTxSender extends BaseTxSender {
96
91
  landRateToFeeFunc,
97
92
  });
98
93
  this.retrySleep = retrySleep;
99
- this.blockhashCommitment = blockhashCommitment;
100
94
 
101
95
  this.checkAndSetUseBlockHeightOffset();
102
96
  }
@@ -139,7 +133,7 @@ export class WhileValidTxSender extends BaseTxSender {
139
133
 
140
134
  // handle subclass-specific side effects
141
135
  const txSig = bs58.encode(
142
- signedTx.signatures[0]?.signature || signedTx.signatures[0]
136
+ signedTx?.signature || signedTx.signatures[0]?.signature
143
137
  );
144
138
  this.untilValid.set(txSig, latestBlockhash);
145
139
 
@@ -193,6 +187,11 @@ export class WhileValidTxSender extends BaseTxSender {
193
187
  const txSig = bs58.encode(signedTx.signatures[0]);
194
188
  this.untilValid.set(txSig, latestBlockhash);
195
189
 
190
+ console.debug(
191
+ `preflight_commitment`,
192
+ `sending_tx_with_preflight_commitment::${opts?.preflightCommitment}`
193
+ );
194
+
196
195
  return this.sendRawTransaction(signedTx.serialize(), opts);
197
196
  }
198
197
 
@@ -202,6 +201,10 @@ export class WhileValidTxSender extends BaseTxSender {
202
201
  ): Promise<TxSigAndSlot> {
203
202
  const startTime = this.getTimestamp();
204
203
 
204
+ console.debug(
205
+ `preflight_commitment`,
206
+ `sending_tx_with_preflight_commitment::${opts?.preflightCommitment}`
207
+ );
205
208
  const txid = await this.connection.sendRawTransaction(rawTransaction, opts);
206
209
  this.txSigCache?.set(txid, false);
207
210
  this.sendToAdditionalConnections(rawTransaction, opts);
@@ -234,27 +237,21 @@ export class WhileValidTxSender extends BaseTxSender {
234
237
 
235
238
  let slot: number;
236
239
  try {
237
- const { blockhash, lastValidBlockHeight } = this.untilValid.get(txid);
238
-
239
- const result = await this.connection.confirmTransaction(
240
- {
241
- signature: txid,
242
- blockhash,
243
- lastValidBlockHeight: this.useBlockHeightOffset
244
- ? lastValidBlockHeight + VALID_BLOCK_HEIGHT_OFFSET
245
- : lastValidBlockHeight,
246
- },
247
- opts?.commitment
248
- );
249
-
250
- if (!result) {
251
- throw new Error(`Couldn't get signature status for txid: ${txid}`);
252
- }
240
+ const result = await this.confirmTransaction(txid, opts.commitment);
253
241
 
254
242
  this.txSigCache?.set(txid, true);
255
243
 
256
244
  await this.checkConfirmationResultForError(txid, result.value);
257
245
 
246
+ if (result?.value?.err) {
247
+ // Fallback error handling if there's a problem reporting the error in checkConfirmationResultForError
248
+ throw new SendTransactionError({
249
+ action: 'send',
250
+ signature: txid,
251
+ transactionMessage: `Transaction Failed`,
252
+ });
253
+ }
254
+
258
255
  slot = result.context.slot;
259
256
  // eslint-disable-next-line no-useless-catch
260
257
  } catch (e) {
package/src/types.ts CHANGED
@@ -1,4 +1,9 @@
1
- import { PublicKey, Transaction, VersionedTransaction } from '@solana/web3.js';
1
+ import {
2
+ Keypair,
3
+ PublicKey,
4
+ Transaction,
5
+ VersionedTransaction,
6
+ } from '@solana/web3.js';
2
7
  import { BN, ZERO } from '.';
3
8
 
4
9
  // Utility type which lets you denote record with values of type A mapped to a record with the same keys but values of type B
@@ -1051,6 +1056,23 @@ export const DefaultOrderParams: OrderParams = {
1051
1056
  auctionEndPrice: null,
1052
1057
  };
1053
1058
 
1059
+ export type SwiftServerMessage = {
1060
+ slot: BN;
1061
+ swiftOrderSignature: Uint8Array;
1062
+ };
1063
+
1064
+ export type SwiftOrderParamsMessage = {
1065
+ swiftOrderParams: OptionalOrderParams;
1066
+ expectedOrderId: number;
1067
+ takeProfitOrderParams: SwiftTriggerOrderParams | null;
1068
+ stopLossOrderParams: SwiftTriggerOrderParams | null;
1069
+ };
1070
+
1071
+ export type SwiftTriggerOrderParams = {
1072
+ triggerPrice: BN;
1073
+ baseAssetAmount: BN;
1074
+ };
1075
+
1054
1076
  export type MakerInfo = {
1055
1077
  maker: PublicKey;
1056
1078
  makerStats: PublicKey;
@@ -1070,6 +1092,11 @@ export type ReferrerInfo = {
1070
1092
  referrerStats: PublicKey;
1071
1093
  };
1072
1094
 
1095
+ export enum PlaceAndTakeOrderSuccessCondition {
1096
+ PartialFill = 1,
1097
+ FullFill = 2,
1098
+ }
1099
+
1073
1100
  type ExactType<T> = Pick<T, keyof T>;
1074
1101
 
1075
1102
  export type BaseTxParams = ExactType<{
@@ -1097,6 +1124,7 @@ export interface IWallet {
1097
1124
  signTransaction(tx: Transaction): Promise<Transaction>;
1098
1125
  signAllTransactions(txs: Transaction[]): Promise<Transaction[]>;
1099
1126
  publicKey: PublicKey;
1127
+ payer?: Keypair;
1100
1128
  }
1101
1129
  export interface IVersionedWallet {
1102
1130
  signVersionedTransaction(
@@ -1106,6 +1134,7 @@ export interface IVersionedWallet {
1106
1134
  txs: VersionedTransaction[]
1107
1135
  ): Promise<VersionedTransaction[]>;
1108
1136
  publicKey: PublicKey;
1137
+ payer?: Keypair;
1109
1138
  }
1110
1139
 
1111
1140
  export type FeeStructure = {
package/src/user.ts CHANGED
@@ -78,6 +78,8 @@ import {
78
78
  import { calculateMarketOpenBidAsk } from './math/amm';
79
79
  import {
80
80
  calculateBaseAssetValueWithOracle,
81
+ calculateCollateralDepositRequiredForTrade,
82
+ calculateMarginUSDCRequiredForTrade,
81
83
  calculateWorstCaseBaseAssetAmount,
82
84
  } from './math/margin';
83
85
  import { OraclePriceData } from './oracles/types';
@@ -98,7 +100,6 @@ import {
98
100
  calculatePerpFuelBonus,
99
101
  calculateInsuranceFuelBonus,
100
102
  } from './math/fuel';
101
- import { grpcUserAccountSubscriber } from './accounts/grpcUserAccountSubscriber';
102
103
 
103
104
  export class User {
104
105
  driftClient: DriftClient;
@@ -129,16 +130,6 @@ export class User {
129
130
  );
130
131
  } else if (config.accountSubscription?.type === 'custom') {
131
132
  this.accountSubscriber = config.accountSubscription.userAccountSubscriber;
132
- } else if (config.accountSubscription?.type === 'grpc') {
133
- this.accountSubscriber = new grpcUserAccountSubscriber(
134
- config.accountSubscription.configs,
135
- config.driftClient.program,
136
- config.userAccountPublicKey,
137
- {
138
- resubTimeoutMs: config.accountSubscription?.resubTimeoutMs,
139
- logResubMessages: config.accountSubscription?.logResubMessages,
140
- }
141
- );
142
133
  } else {
143
134
  this.accountSubscriber = new WebSocketUserAccountSubscriber(
144
135
  config.driftClient.program,
@@ -2285,6 +2276,7 @@ export class User {
2285
2276
  * @param estimatedEntryPrice
2286
2277
  * @param marginCategory // allow Initial to be passed in if we are trying to calculate price for DLP de-risking
2287
2278
  * @param includeOpenOrders
2279
+ * @param offsetCollateral // allows calculating the liquidation price after this offset collateral is added to the user's account (e.g. : what will the liquidation price be for this position AFTER I deposit $x worth of collateral)
2288
2280
  * @returns Precision : PRICE_PRECISION
2289
2281
  */
2290
2282
  public liquidationPrice(
@@ -2292,7 +2284,8 @@ export class User {
2292
2284
  positionBaseSizeChange: BN = ZERO,
2293
2285
  estimatedEntryPrice: BN = ZERO,
2294
2286
  marginCategory: MarginCategory = 'Maintenance',
2295
- includeOpenOrders = false
2287
+ includeOpenOrders = false,
2288
+ offsetCollateral = ZERO
2296
2289
  ): BN {
2297
2290
  const totalCollateral = this.getTotalCollateral(marginCategory);
2298
2291
  const marginRequirement = this.getMarginRequirement(
@@ -2301,7 +2294,10 @@ export class User {
2301
2294
  false,
2302
2295
  includeOpenOrders
2303
2296
  );
2304
- let freeCollateral = BN.max(ZERO, totalCollateral.sub(marginRequirement));
2297
+ let freeCollateral = BN.max(
2298
+ ZERO,
2299
+ totalCollateral.sub(marginRequirement)
2300
+ ).add(offsetCollateral);
2305
2301
 
2306
2302
  const oracle =
2307
2303
  this.driftClient.getPerpMarketAccount(marketIndex).amm.oracle;
@@ -2604,6 +2600,32 @@ export class User {
2604
2600
  );
2605
2601
  }
2606
2602
 
2603
+ public getMarginUSDCRequiredForTrade(
2604
+ targetMarketIndex: number,
2605
+ baseSize: BN
2606
+ ): BN {
2607
+ return calculateMarginUSDCRequiredForTrade(
2608
+ this.driftClient,
2609
+ targetMarketIndex,
2610
+ baseSize,
2611
+ this.getUserAccount().maxMarginRatio
2612
+ );
2613
+ }
2614
+
2615
+ public getCollateralDepositRequiredForTrade(
2616
+ targetMarketIndex: number,
2617
+ baseSize: BN,
2618
+ collateralIndex: number
2619
+ ): BN {
2620
+ return calculateCollateralDepositRequiredForTrade(
2621
+ this.driftClient,
2622
+ targetMarketIndex,
2623
+ baseSize,
2624
+ collateralIndex,
2625
+ this.getUserAccount().maxMarginRatio
2626
+ );
2627
+ }
2628
+
2607
2629
  /**
2608
2630
  * Get the maximum trade size for a given market, taking into account the user's current leverage, positions, collateral, etc.
2609
2631
  *
package/src/userConfig.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { DriftClient } from './driftClient';
2
2
  import { Commitment, PublicKey } from '@solana/web3.js';
3
3
  import { BulkAccountLoader } from './accounts/bulkAccountLoader';
4
- import { GrpcConfigs, UserAccountSubscriber } from './accounts/types';
4
+ import { UserAccountSubscriber } from './accounts/types';
5
5
 
6
6
  export type UserConfig = {
7
7
  accountSubscription?: UserSubscriptionConfig;
@@ -23,10 +23,4 @@ export type UserSubscriptionConfig =
23
23
  | {
24
24
  type: 'custom';
25
25
  userAccountSubscriber: UserAccountSubscriber;
26
- }
27
- | {
28
- type: 'grpc';
29
- resubTimeoutMs?: number;
30
- logResubMessages?: boolean;
31
- configs: GrpcConfigs;
32
26
  };
@@ -36,7 +36,6 @@ import {
36
36
  import { WebsocketSubscription } from './WebsocketSubscription';
37
37
  import { PollingSubscription } from './PollingSubscription';
38
38
  import { decodeUser } from '../decode/user';
39
- import { grpcSubscription } from './grpcSubscription';
40
39
 
41
40
  const MAX_USER_ACCOUNT_SIZE_BYTES = 4376;
42
41
 
@@ -76,10 +75,7 @@ export class UserMap implements UserMapInterface {
76
75
  private includeIdle: boolean;
77
76
  private disableSyncOnTotalAccountsChange: boolean;
78
77
  private lastNumberOfSubAccounts: BN;
79
- private subscription:
80
- | PollingSubscription
81
- | WebsocketSubscription
82
- | grpcSubscription;
78
+ private subscription: PollingSubscription | WebsocketSubscription;
83
79
  private stateAccountUpdateCallback = async (state: StateAccount) => {
84
80
  if (!state.numberOfSubAccounts.eq(this.lastNumberOfSubAccounts)) {
85
81
  await this.sync();
@@ -126,18 +122,6 @@ export class UserMap implements UserMapInterface {
126
122
  frequency: config.subscriptionConfig.frequency,
127
123
  skipInitialLoad: config.skipInitialLoad,
128
124
  });
129
- } else if (config.subscriptionConfig.type === 'grpc') {
130
- this.subscription = new grpcSubscription({
131
- configs: config.subscriptionConfig.configs,
132
- userMap: this,
133
- commitment: this.commitment,
134
- resubOpts: {
135
- resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
136
- logResubMessages: config.subscriptionConfig.logResubMessages,
137
- },
138
- skipInitialLoad: config.skipInitialLoad,
139
- decodeFn,
140
- });
141
125
  } else {
142
126
  this.subscription = new WebsocketSubscription({
143
127
  userMap: this,
@@ -1,6 +1,5 @@
1
1
  import { Commitment, Connection } from '@solana/web3.js';
2
2
  import { DriftClient } from '../driftClient';
3
- import { GrpcConfigs } from '../accounts/types';
4
3
 
5
4
  // passed into UserMap.getUniqueAuthorities to filter users
6
5
  export type UserAccountFilterCriteria = {
@@ -28,13 +27,6 @@ export type UserMapConfig = {
28
27
  frequency: number;
29
28
  commitment?: Commitment;
30
29
  }
31
- | {
32
- type: 'grpc';
33
- configs: GrpcConfigs;
34
- resubTimeoutMs?: number;
35
- logResubMessages?: boolean;
36
- commitment?: Commitment;
37
- }
38
30
  | {
39
31
  type: 'websocket';
40
32
  resubTimeoutMs?: number;
@@ -1,7 +1,6 @@
1
1
  import { DriftClient } from './driftClient';
2
2
  import { Commitment, PublicKey } from '@solana/web3.js';
3
3
  import { BulkAccountLoader } from './accounts/bulkAccountLoader';
4
- import { GrpcConfigs } from './accounts/types';
5
4
 
6
5
  export type UserStatsConfig = {
7
6
  accountSubscription?: UserStatsSubscriptionConfig;
@@ -22,10 +21,4 @@ export type UserStatsSubscriptionConfig =
22
21
  }
23
22
  | {
24
23
  type: 'custom';
25
- }
26
- | {
27
- type: 'grpc';
28
- resubTimeoutMs?: number;
29
- logResubMessages?: boolean;
30
- configs: GrpcConfigs;
31
24
  };
@@ -0,0 +1,155 @@
1
+ import {
2
+ Connection,
3
+ SignatureStatus,
4
+ TransactionConfirmationStatus,
5
+ } from '@solana/web3.js';
6
+ import { DEFAULT_CONFIRMATION_OPTS } from '../config';
7
+
8
+ const confirmationStatusValues: Record<TransactionConfirmationStatus, number> =
9
+ {
10
+ processed: 0,
11
+ confirmed: 1,
12
+ finalized: 2,
13
+ };
14
+
15
+ interface TransactionConfirmationRequest {
16
+ txSig: string;
17
+ desiredConfirmationStatus: TransactionConfirmationStatus;
18
+ timeout: number;
19
+ pollInterval: number;
20
+ searchTransactionHistory: boolean;
21
+ startTime: number;
22
+ resolve: (status: SignatureStatus) => void;
23
+ reject: (error: Error) => void;
24
+ }
25
+
26
+ /**
27
+ * Class to await for transaction confirmations in an optimised manner. It tracks a shared list of all pending transactions and fetches them in bulk in a shared RPC request whenever they have an "overlapping" polling interval. E.g. tx1 with an interval of 200ms and tx2 with an interval of 300ms (if sent at the same time) will be fetched together at at 600ms, 1200ms, 1800ms, etc.
28
+ */
29
+ export class TransactionConfirmationManager {
30
+ private connection: Connection;
31
+ private pendingConfirmations: Map<string, TransactionConfirmationRequest> =
32
+ new Map();
33
+ private intervalId: NodeJS.Timeout | null = null;
34
+
35
+ constructor(connection: Connection) {
36
+ this.connection = connection;
37
+ }
38
+
39
+ async confirmTransaction(
40
+ txSig: string,
41
+ desiredConfirmationStatus = DEFAULT_CONFIRMATION_OPTS.commitment as TransactionConfirmationStatus,
42
+ timeout = 30000,
43
+ pollInterval = 1000,
44
+ searchTransactionHistory = false
45
+ ): Promise<SignatureStatus> {
46
+ // Interval must be > 400ms and a multiple of 100ms
47
+ if (pollInterval < 400 || pollInterval % 100 !== 0) {
48
+ throw new Error(
49
+ 'Transaction confirmation polling interval must be at least 400ms and a multiple of 100ms'
50
+ );
51
+ }
52
+
53
+ return new Promise((resolve, reject) => {
54
+ this.pendingConfirmations.set(txSig, {
55
+ txSig,
56
+ desiredConfirmationStatus,
57
+ timeout,
58
+ pollInterval,
59
+ searchTransactionHistory,
60
+ startTime: Date.now(),
61
+ resolve,
62
+ reject,
63
+ });
64
+
65
+ if (!this.intervalId) {
66
+ this.startConfirmationLoop();
67
+ }
68
+ });
69
+ }
70
+
71
+ private startConfirmationLoop() {
72
+ this.intervalId = setInterval(() => this.checkPendingConfirmations(), 100);
73
+ }
74
+
75
+ private async checkPendingConfirmations() {
76
+ const now = Date.now();
77
+ const transactionsToCheck: TransactionConfirmationRequest[] = [];
78
+
79
+ for (const [txSig, request] of this.pendingConfirmations.entries()) {
80
+ if (now - request.startTime >= request.timeout) {
81
+ request.reject(
82
+ new Error(
83
+ `Transaction confirmation timeout after ${request.timeout}ms`
84
+ )
85
+ );
86
+ this.pendingConfirmations.delete(txSig);
87
+ } else if ((now - request.startTime) % request.pollInterval < 100) {
88
+ transactionsToCheck.push(request);
89
+ }
90
+ }
91
+
92
+ if (transactionsToCheck.length > 0) {
93
+ await this.checkTransactionStatuses(transactionsToCheck);
94
+ }
95
+
96
+ if (this.pendingConfirmations.size === 0 && this.intervalId) {
97
+ clearInterval(this.intervalId);
98
+ this.intervalId = null;
99
+ }
100
+ }
101
+
102
+ private async checkTransactionStatuses(
103
+ requests: TransactionConfirmationRequest[]
104
+ ) {
105
+ const txSigs = requests.map((request) => request.txSig);
106
+ const { value: statuses } = await this.connection.getSignatureStatuses(
107
+ txSigs,
108
+ {
109
+ searchTransactionHistory: requests.some(
110
+ (req) => req.searchTransactionHistory
111
+ ),
112
+ }
113
+ );
114
+
115
+ if (!statuses || statuses.length !== txSigs.length) {
116
+ throw new Error('Failed to get signature statuses');
117
+ }
118
+
119
+ for (let i = 0; i < statuses.length; i++) {
120
+ const status = statuses[i];
121
+ const request = requests[i];
122
+
123
+ if (status === null) {
124
+ continue;
125
+ }
126
+
127
+ if (status.err) {
128
+ request.reject(
129
+ new Error(`Transaction failed: ${JSON.stringify(status.err)}`)
130
+ );
131
+ this.pendingConfirmations.delete(request.txSig);
132
+ continue;
133
+ }
134
+
135
+ if (
136
+ confirmationStatusValues[status.confirmationStatus] === undefined ||
137
+ confirmationStatusValues[request.desiredConfirmationStatus] ===
138
+ undefined
139
+ ) {
140
+ throw new Error(
141
+ `Invalid confirmation status when awaiting confirmation: ${status.confirmationStatus}`
142
+ );
143
+ }
144
+
145
+ if (
146
+ status.confirmationStatus &&
147
+ confirmationStatusValues[status.confirmationStatus] >=
148
+ confirmationStatusValues[request.desiredConfirmationStatus]
149
+ ) {
150
+ request.resolve(status);
151
+ this.pendingConfirmations.delete(request.txSig);
152
+ }
153
+ }
154
+ }
155
+ }
package/tests/ci/idl.ts CHANGED
@@ -51,7 +51,9 @@ describe('Verify IDL', function () {
51
51
  );
52
52
 
53
53
  if (onChainIdl === null) {
54
- throw new Error(`onChainIdl for ${mainnetDriftClient.program.programId.toBase58()} null`);
54
+ throw new Error(
55
+ `onChainIdl for ${mainnetDriftClient.program.programId.toBase58()} null`
56
+ );
55
57
  }
56
58
 
57
59
  // anchor idl init seems to strip the metadata
@@ -65,13 +67,20 @@ describe('Verify IDL', function () {
65
67
  const encodedSdkIdl = JSON.stringify(sdkIdl);
66
68
 
67
69
  try {
68
- assert(encodedSdkIdl === encodedMainnetIdl, 'on-chain IDL does not match SDK IDL');
70
+ assert(
71
+ encodedSdkIdl === encodedMainnetIdl,
72
+ 'on-chain IDL does not match SDK IDL'
73
+ );
69
74
  } catch (error) {
70
75
  const diff = {};
71
76
  for (const key of IDL_KEYS_TO_CHECK) {
72
77
  const onChainItems = onChainIdl[key];
73
78
  const sdkItems = sdkIdl[key];
74
- for (let i = 0; i < Math.max(onChainItems.length, sdkItems.length); i++) {
79
+ for (
80
+ let i = 0;
81
+ i < Math.max(onChainItems.length, sdkItems.length);
82
+ i++
83
+ ) {
75
84
  let onChainItem = null;
76
85
  let sdkItem = null;
77
86
  if (i < onChainItems.length) {
@@ -6,6 +6,7 @@ import {
6
6
  MainnetPerpMarkets,
7
7
  BulkAccountLoader,
8
8
  getVariant,
9
+ isOneOfVariant,
9
10
  } from '../../src';
10
11
  import { Connection, Keypair } from '@solana/web3.js';
11
12
  import { Wallet } from '@coral-xyz/anchor';
@@ -128,6 +129,12 @@ describe('Verify Constants', function () {
128
129
  market.marketIndex
129
130
  } oracle ${market.oracle.toBase58()}`
130
131
  );
132
+
133
+ if (isOneOfVariant(market.oracleSource, ['pythPull', 'pyth1KPull', 'pyth1MPull', 'pythStableCoinPull'])) {
134
+ if (!correspondingConfigMarket.pythFeedId) {
135
+ assert(false, `spot market ${market.marketIndex} missing feed id`);
136
+ }
137
+ }
131
138
  }
132
139
 
133
140
  const perpMarkets = mainnetDriftClient.getPerpMarketAccounts();
@@ -177,6 +184,12 @@ describe('Verify Constants', function () {
177
184
  market.marketIndex
178
185
  } oracle ${market.amm.oracle.toBase58()}`
179
186
  );
187
+
188
+ if (isOneOfVariant(market.amm.oracleSource, ['pythPull', 'pyth1KPull', 'pyth1MPull', 'pythStableCoinPull'])) {
189
+ if (!correspondingConfigMarket.pythFeedId) {
190
+ assert(false, `perp market ${market.marketIndex} missing feed id`);
191
+ }
192
+ }
180
193
  }
181
194
  });
182
195