@drift-labs/sdk 2.85.0-beta.1 → 2.85.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 (59) hide show
  1. package/VERSION +1 -1
  2. package/bun.lockb +0 -0
  3. package/lib/accounts/bulkAccountLoader.d.ts +3 -3
  4. package/lib/accounts/pollingDriftClientAccountSubscriber.js +10 -2
  5. package/lib/accounts/pollingUserAccountSubscriber.d.ts +4 -3
  6. package/lib/accounts/pollingUserAccountSubscriber.js +7 -6
  7. package/lib/accounts/testBulkAccountLoader.d.ts +4 -0
  8. package/lib/accounts/testBulkAccountLoader.js +45 -0
  9. package/lib/bankrun/bankrunConnection.d.ts +71 -0
  10. package/lib/bankrun/bankrunConnection.js +285 -0
  11. package/lib/blockhashSubscriber/BlockhashSubscriber.js +22 -15
  12. package/lib/constants/perpMarkets.js +10 -0
  13. package/lib/constants/spotMarkets.js +10 -0
  14. package/lib/driftClient.d.ts +3 -1
  15. package/lib/driftClient.js +45 -32
  16. package/lib/driftClientConfig.d.ts +2 -1
  17. package/lib/events/eventSubscriber.js +12 -4
  18. package/lib/idl/drift.json +28 -8
  19. package/lib/testClient.js +1 -2
  20. package/lib/tokenFaucet.d.ts +3 -1
  21. package/lib/tokenFaucet.js +41 -8
  22. package/lib/tx/blockhashFetcher/baseBlockhashFetcher.d.ts +8 -0
  23. package/lib/tx/blockhashFetcher/baseBlockhashFetcher.js +13 -0
  24. package/lib/tx/blockhashFetcher/cachedBlockhashFetcher.d.ts +28 -0
  25. package/lib/tx/blockhashFetcher/cachedBlockhashFetcher.js +73 -0
  26. package/lib/tx/blockhashFetcher/types.d.ts +4 -0
  27. package/lib/tx/blockhashFetcher/types.js +2 -0
  28. package/lib/tx/txHandler.d.ts +10 -0
  29. package/lib/tx/txHandler.js +16 -7
  30. package/lib/tx/txParamProcessor.d.ts +2 -1
  31. package/lib/tx/txParamProcessor.js +7 -3
  32. package/lib/tx/utils.d.ts +2 -0
  33. package/lib/tx/utils.js +10 -0
  34. package/lib/types.d.ts +1 -0
  35. package/lib/user.js +1 -1
  36. package/package.json +4 -1
  37. package/src/accounts/bulkAccountLoader.ts +3 -2
  38. package/src/accounts/pollingDriftClientAccountSubscriber.ts +16 -3
  39. package/src/accounts/pollingUserAccountSubscriber.ts +13 -12
  40. package/src/accounts/testBulkAccountLoader.ts +53 -0
  41. package/src/bankrun/bankrunConnection.ts +466 -0
  42. package/src/blockhashSubscriber/BlockhashSubscriber.ts +24 -19
  43. package/src/constants/perpMarkets.ts +10 -0
  44. package/src/constants/spotMarkets.ts +10 -0
  45. package/src/driftClient.ts +91 -42
  46. package/src/driftClientConfig.ts +2 -1
  47. package/src/events/eventSubscriber.ts +5 -0
  48. package/src/idl/drift.json +28 -8
  49. package/src/testClient.ts +1 -2
  50. package/src/tokenFaucet.ts +49 -12
  51. package/src/tx/blockhashFetcher/baseBlockhashFetcher.ts +19 -0
  52. package/src/tx/blockhashFetcher/cachedBlockhashFetcher.ts +90 -0
  53. package/src/tx/blockhashFetcher/types.ts +5 -0
  54. package/src/tx/txHandler.ts +38 -4
  55. package/src/tx/txParamProcessor.ts +11 -2
  56. package/src/tx/utils.ts +11 -0
  57. package/src/types.ts +1 -0
  58. package/src/user.ts +5 -2
  59. package/tests/tx/cachedBlockhashFetcher.test.ts +96 -0
@@ -58,6 +58,7 @@ const utils_1 = require("./math/utils");
58
58
  const txParamProcessor_1 = require("./tx/txParamProcessor");
59
59
  const oracles_1 = require("./math/oracles");
60
60
  const txHandler_1 = require("./tx/txHandler");
61
+ const utils_2 = require("./tx/utils");
61
62
  /**
62
63
  * # DriftClient
63
64
  * This class is the main way to interact with Drift Protocol. It allows you to subscribe to the various accounts where the Market's state is stored, as well as: opening positions, liquidating, settling funding, depositing & withdrawing, and more.
@@ -106,6 +107,7 @@ class DriftClient {
106
107
  onSignedCb: this.handleSignedTransaction.bind(this),
107
108
  preSignedCb: this.handlePreSignedTransaction.bind(this),
108
109
  },
110
+ config: config.txHandlerConfig,
109
111
  });
110
112
  if (config.includeDelegates && config.subAccountIds) {
111
113
  throw new Error('Can only pass one of includeDelegates or subAccountIds. If you want to specify subaccount ids for multiple authorities, pass authoritySubaccountMap instead');
@@ -1061,17 +1063,7 @@ class DriftClient {
1061
1063
  data: Buffer.from([0x1]),
1062
1064
  });
1063
1065
  }
1064
- /**
1065
- * Deposit funds into the given spot market
1066
- *
1067
- * @param amount to deposit
1068
- * @param marketIndex spot market index to deposit into
1069
- * @param associatedTokenAccount can be the wallet public key if using native sol
1070
- * @param subAccountId subaccountId to deposit
1071
- * @param reduceOnly if true, deposit must not increase account risk
1072
- */
1073
- async deposit(amount, marketIndex, associatedTokenAccount, subAccountId, reduceOnly = false, txParams) {
1074
- const additionalSigners = [];
1066
+ async createDepositTxn(amount, marketIndex, associatedTokenAccount, subAccountId, reduceOnly = false, txParams) {
1075
1067
  const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
1076
1068
  const isSolMarket = spotMarketAccount.mint.equals(spotMarkets_1.WRAPPED_SOL_MINT);
1077
1069
  const signerAuthority = this.wallet.publicKey;
@@ -1090,7 +1082,20 @@ class DriftClient {
1090
1082
  }
1091
1083
  txParams = { ...(txParams !== null && txParams !== void 0 ? txParams : this.txParams), computeUnits: 600000 };
1092
1084
  const tx = await this.buildTransaction(instructions, txParams);
1093
- const { txSig, slot } = await this.sendTransaction(tx, additionalSigners, this.opts);
1085
+ return tx;
1086
+ }
1087
+ /**
1088
+ * Deposit funds into the given spot market
1089
+ *
1090
+ * @param amount to deposit
1091
+ * @param marketIndex spot market index to deposit into
1092
+ * @param associatedTokenAccount can be the wallet public key if using native sol
1093
+ * @param subAccountId subaccountId to deposit
1094
+ * @param reduceOnly if true, deposit must not increase account risk
1095
+ */
1096
+ async deposit(amount, marketIndex, associatedTokenAccount, subAccountId, reduceOnly = false, txParams) {
1097
+ const tx = await this.createDepositTxn(amount, marketIndex, associatedTokenAccount, subAccountId, reduceOnly, txParams);
1098
+ const { txSig, slot } = await this.sendTransaction(tx, [], this.opts);
1094
1099
  this.spotMarketLastSlotCache.set(marketIndex, slot);
1095
1100
  return txSig;
1096
1101
  }
@@ -1165,23 +1170,9 @@ class DriftClient {
1165
1170
  getAssociatedTokenAccountCreationIx(tokenMintAddress, associatedTokenAddress) {
1166
1171
  return (0, spl_token_1.createAssociatedTokenAccountInstruction)(this.wallet.publicKey, associatedTokenAddress, this.wallet.publicKey, tokenMintAddress);
1167
1172
  }
1168
- /**
1169
- * Creates the User account for a user, and deposits some initial collateral
1170
- * @param amount
1171
- * @param userTokenAccount
1172
- * @param marketIndex
1173
- * @param subAccountId
1174
- * @param name
1175
- * @param fromSubAccountId
1176
- * @param referrerInfo
1177
- * @param donateAmount
1178
- * @param txParams
1179
- * @returns
1180
- */
1181
- async initializeUserAccountAndDepositCollateral(amount, userTokenAccount, marketIndex = 0, subAccountId = 0, name, fromSubAccountId, referrerInfo, donateAmount, txParams, customMaxMarginRatio) {
1173
+ async createInitializeUserAccountAndDepositCollateral(amount, userTokenAccount, marketIndex = 0, subAccountId = 0, name, fromSubAccountId, referrerInfo, donateAmount, txParams, customMaxMarginRatio) {
1182
1174
  const ixs = [];
1183
1175
  const [userAccountPublicKey, initializeUserAccountIx] = await this.getInitializeUserInstructions(subAccountId, name, referrerInfo);
1184
- const additionalSigners = [];
1185
1176
  const spotMarket = this.getSpotMarketAccount(marketIndex);
1186
1177
  const isSolMarket = spotMarket.mint.equals(spotMarkets_1.WRAPPED_SOL_MINT);
1187
1178
  const authority = this.wallet.publicKey;
@@ -1226,6 +1217,24 @@ class DriftClient {
1226
1217
  ixs.push((0, spl_token_1.createCloseAccountInstruction)(wsolTokenAccount, authority, authority, []));
1227
1218
  }
1228
1219
  const tx = await this.buildTransaction(ixs, txParams);
1220
+ return [tx, userAccountPublicKey];
1221
+ }
1222
+ /**
1223
+ * Creates the User account for a user, and deposits some initial collateral
1224
+ * @param amount
1225
+ * @param userTokenAccount
1226
+ * @param marketIndex
1227
+ * @param subAccountId
1228
+ * @param name
1229
+ * @param fromSubAccountId
1230
+ * @param referrerInfo
1231
+ * @param donateAmount
1232
+ * @param txParams
1233
+ * @returns
1234
+ */
1235
+ async initializeUserAccountAndDepositCollateral(amount, userTokenAccount, marketIndex = 0, subAccountId = 0, name, fromSubAccountId, referrerInfo, donateAmount, txParams, customMaxMarginRatio) {
1236
+ const [tx, userAccountPublicKey] = await this.createInitializeUserAccountAndDepositCollateral(amount, userTokenAccount, marketIndex, subAccountId, name, fromSubAccountId, referrerInfo, donateAmount, txParams, customMaxMarginRatio);
1237
+ const additionalSigners = [];
1229
1238
  const { txSig, slot } = await this.sendTransaction(tx, additionalSigners, this.opts);
1230
1239
  this.spotMarketLastSlotCache.set(marketIndex, slot);
1231
1240
  await this.addUser(subAccountId);
@@ -2267,6 +2276,9 @@ class DriftClient {
2267
2276
  async getJupiterSwapIxV6({ jupiterClient, outMarketIndex, inMarketIndex, outAssociatedTokenAccount, inAssociatedTokenAccount, amount, slippageBps, swapMode, onlyDirectRoutes, quote, reduceOnly, userAccountPublicKey, }) {
2268
2277
  const outMarket = this.getSpotMarketAccount(outMarketIndex);
2269
2278
  const inMarket = this.getSpotMarketAccount(inMarketIndex);
2279
+ const isExactOut = swapMode === 'ExactOut' || quote.swapMode === 'ExactOut';
2280
+ const amountIn = new anchor_1.BN(quote.inAmount);
2281
+ const exactOutBufferedAmountIn = amountIn.muln(1001).divn(1000); // Add 10bp buffer
2270
2282
  if (!quote) {
2271
2283
  const fetchedQuote = await jupiterClient.getQuote({
2272
2284
  inputMint: inMarket.mint,
@@ -2312,7 +2324,7 @@ class DriftClient {
2312
2324
  const { beginSwapIx, endSwapIx } = await this.getSwapIx({
2313
2325
  outMarketIndex,
2314
2326
  inMarketIndex,
2315
- amountIn: new anchor_1.BN(quote.inAmount),
2327
+ amountIn: isExactOut ? exactOutBufferedAmountIn : amountIn,
2316
2328
  inTokenAccount: inAssociatedTokenAccount,
2317
2329
  outTokenAccount: outAssociatedTokenAccount,
2318
2330
  reduceOnly,
@@ -2544,7 +2556,7 @@ class DriftClient {
2544
2556
  };
2545
2557
  if (shouldUseSimulationComputeUnits || shouldExitIfSimulationFails) {
2546
2558
  const placeAndTakeTxToSim = (await this.buildTransaction(placeAndTakeIxs, txParams, undefined, undefined, true, recentBlockHash));
2547
- const simulationResult = await txParamProcessor_1.TransactionParamProcessor.getTxSimComputeUnits(placeAndTakeTxToSim, this.connection, (_a = txParams.computeUnitsBufferMultiplier) !== null && _a !== void 0 ? _a : 1.2);
2559
+ const simulationResult = await txParamProcessor_1.TransactionParamProcessor.getTxSimComputeUnits(placeAndTakeTxToSim, this.connection, (_a = txParams.computeUnitsBufferMultiplier) !== null && _a !== void 0 ? _a : 1.2, txParams.lowerBoundCu);
2548
2560
  if (shouldExitIfSimulationFails && !simulationResult.success) {
2549
2561
  earlyExitFailedPlaceAndTakeSim = true;
2550
2562
  return;
@@ -2586,6 +2598,9 @@ class DriftClient {
2586
2598
  }
2587
2599
  async placeAndTakePerpWithAdditionalOrders(orderParams, makerInfo, referrerInfo, bracketOrdersParams = new Array(), txParams, subAccountId, cancelExistingOrders, settlePnl, exitEarlyIfSimFails) {
2588
2600
  const txsToSign = await this.preparePlaceAndTakePerpOrderWithAdditionalOrders(orderParams, makerInfo, referrerInfo, bracketOrdersParams, txParams, subAccountId, cancelExistingOrders, settlePnl, exitEarlyIfSimFails);
2601
+ if (!txsToSign) {
2602
+ return null;
2603
+ }
2589
2604
  const signedTxs = (await this.txHandler.getSignedTransactionMap(txsToSign, this.provider.wallet)).signedTxMap;
2590
2605
  const { txSig, slot } = await this.sendTransaction(signedTxs.placeAndTakeTx, [], this.opts, true);
2591
2606
  this.perpMarketLastSlotCache.set(orderParams.marketIndex, slot);
@@ -3630,9 +3645,7 @@ class DriftClient {
3630
3645
  }
3631
3646
  }
3632
3647
  isVersionedTransaction(tx) {
3633
- const version = tx === null || tx === void 0 ? void 0 : tx.version;
3634
- const isVersionedTx = tx instanceof web3_js_1.VersionedTransaction || version !== undefined;
3635
- return isVersionedTx;
3648
+ return (0, utils_2.isVersionedTransaction)(tx);
3636
3649
  }
3637
3650
  sendTransaction(tx, additionalSigners, opts, preSigned) {
3638
3651
  const isVersionedTx = this.isVersionedTransaction(tx);
@@ -4,7 +4,7 @@ import { OracleInfo } from './oracles/types';
4
4
  import { BulkAccountLoader } from './accounts/bulkAccountLoader';
5
5
  import { DriftEnv } from './config';
6
6
  import { TxSender } from './tx/types';
7
- import { TxHandler } from './tx/txHandler';
7
+ import { TxHandler, TxHandlerConfig } from './tx/txHandler';
8
8
  export type DriftClientConfig = {
9
9
  connection: Connection;
10
10
  wallet: IWallet;
@@ -28,6 +28,7 @@ export type DriftClientConfig = {
28
28
  txVersion?: TransactionVersion;
29
29
  txParams?: TxParams;
30
30
  enableMetricsEvents?: boolean;
31
+ txHandlerConfig?: TxHandlerConfig;
31
32
  };
32
33
  export type DriftClientSubscriptionConfig = {
33
34
  type: 'websocket';
@@ -24,10 +24,14 @@ class EventSubscriber {
24
24
  this.eventListMap = new Map();
25
25
  this.eventEmitter = new events_1.EventEmitter();
26
26
  if (this.options.logProviderConfig.type === 'websocket') {
27
- this.logProvider = new webSocketLogProvider_1.WebSocketLogProvider(this.connection, this.address, this.options.commitment, this.options.logProviderConfig.resubTimeoutMs);
27
+ this.logProvider = new webSocketLogProvider_1.WebSocketLogProvider(
28
+ // @ts-ignore
29
+ this.connection, this.address, this.options.commitment, this.options.logProviderConfig.resubTimeoutMs);
28
30
  }
29
31
  else {
30
- this.logProvider = new pollingLogProvider_1.PollingLogProvider(this.connection, this.address, options.commitment, this.options.logProviderConfig.frequency, this.options.logProviderConfig.batchSize);
32
+ this.logProvider = new pollingLogProvider_1.PollingLogProvider(
33
+ // @ts-ignore
34
+ this.connection, this.address, options.commitment, this.options.logProviderConfig.frequency, this.options.logProviderConfig.batchSize);
31
35
  }
32
36
  }
33
37
  populateInitialEventListMap() {
@@ -52,7 +56,9 @@ class EventSubscriber {
52
56
  console.log('Failing over to polling');
53
57
  this.logProvider.eventEmitter.removeAllListeners('reconnect');
54
58
  this.unsubscribe().then(() => {
55
- this.logProvider = new pollingLogProvider_1.PollingLogProvider(this.connection, this.address, this.options.commitment, logProviderConfig.fallbackFrequency, logProviderConfig.fallbackBatchSize);
59
+ this.logProvider = new pollingLogProvider_1.PollingLogProvider(
60
+ // @ts-ignore
61
+ this.connection, this.address, this.options.commitment, logProviderConfig.fallbackFrequency, logProviderConfig.fallbackBatchSize);
56
62
  this.logProvider.subscribe((txSig, slot, logs, mostRecentBlockTime) => {
57
63
  this.handleTxLogs(txSig, slot, logs, mostRecentBlockTime);
58
64
  }, true);
@@ -108,7 +114,9 @@ class EventSubscriber {
108
114
  let beforeTx = undefined;
109
115
  const untilTx = this.options.untilTx;
110
116
  while (txFetched < this.options.maxTx) {
111
- const response = await (0, fetchLogs_1.fetchLogs)(this.connection, this.address, this.options.commitment === 'finalized' ? 'finalized' : 'confirmed', beforeTx, untilTx);
117
+ const response = await (0, fetchLogs_1.fetchLogs)(
118
+ // @ts-ignore
119
+ this.connection, this.address, this.options.commitment === 'finalized' ? 'finalized' : 'confirmed', beforeTx, untilTx);
112
120
  if (response === undefined) {
113
121
  break;
114
122
  }
@@ -3934,6 +3934,12 @@
3934
3934
  {
3935
3935
  "name": "maxBorrowRate",
3936
3936
  "type": "u32"
3937
+ },
3938
+ {
3939
+ "name": "minBorrowRate",
3940
+ "type": {
3941
+ "option": "u8"
3942
+ }
3937
3943
  }
3938
3944
  ]
3939
3945
  },
@@ -6198,13 +6204,13 @@
6198
6204
  "type": "i16"
6199
6205
  },
6200
6206
  {
6201
- "name": "padding1",
6202
- "type": {
6203
- "array": [
6204
- "u8",
6205
- 2
6206
- ]
6207
- }
6207
+ "name": "maxTokenBorrowsFraction",
6208
+ "docs": [
6209
+ "What fraction of max_token_deposits",
6210
+ "disabled when 0, 1 => 1/10000 => .01% of max_token_deposits",
6211
+ "precision: X/10000"
6212
+ ],
6213
+ "type": "u16"
6208
6214
  },
6209
6215
  {
6210
6216
  "name": "flashLoanAmount",
@@ -6240,12 +6246,21 @@
6240
6246
  ],
6241
6247
  "type": "u64"
6242
6248
  },
6249
+ {
6250
+ "name": "minBorrowRate",
6251
+ "docs": [
6252
+ "The min borrow rate for this market when the market regardless of utilization",
6253
+ "1 => 1/200 => .5%",
6254
+ "precision: X/200"
6255
+ ],
6256
+ "type": "u8"
6257
+ },
6243
6258
  {
6244
6259
  "name": "padding",
6245
6260
  "type": {
6246
6261
  "array": [
6247
6262
  "u8",
6248
- 48
6263
+ 47
6249
6264
  ]
6250
6265
  }
6251
6266
  }
@@ -12009,6 +12024,11 @@
12009
12024
  "code": 6267,
12010
12025
  "name": "UnableToParsePullOracleMessage",
12011
12026
  "msg": "Unable to parse pull oracle message"
12027
+ },
12028
+ {
12029
+ "code": 6268,
12030
+ "name": "MaxBorrows",
12031
+ "msg": "Can not borow more than max borrows"
12012
12032
  }
12013
12033
  ]
12014
12034
  }
package/lib/testClient.js CHANGED
@@ -8,12 +8,11 @@ class TestClient extends adminClient_1.AdminClient {
8
8
  throw new Error('Test client must be polling');
9
9
  }
10
10
  super(config);
11
- // @ts-ignore
12
- this.txHandler.blockhashCommitment = 'recent';
13
11
  }
14
12
  async sendTransaction(tx, additionalSigners, opts, preSigned) {
15
13
  const { txSig, slot } = await super.sendTransaction(tx, additionalSigners, opts, preSigned);
16
14
  let lastFetchedSlot = this.accountSubscriber.accountLoader.mostRecentSlot;
15
+ await this.fetchAccounts();
17
16
  while (lastFetchedSlot < slot) {
18
17
  await this.fetchAccounts();
19
18
  lastFetchedSlot = this.accountSubscriber.accountLoader.mostRecentSlot;
@@ -5,14 +5,16 @@ import { Account } from '@solana/spl-token';
5
5
  import { ConfirmOptions, Connection, PublicKey, TransactionInstruction, TransactionSignature } from '@solana/web3.js';
6
6
  import { BN } from '.';
7
7
  import { IWallet } from './types';
8
+ import { BankrunContextWrapper } from './bankrun/bankrunConnection';
8
9
  export declare class TokenFaucet {
10
+ context?: BankrunContextWrapper;
9
11
  connection: Connection;
10
12
  wallet: IWallet;
11
13
  program: Program;
12
14
  provider: AnchorProvider;
13
15
  mint: PublicKey;
14
16
  opts?: ConfirmOptions;
15
- constructor(connection: Connection, wallet: IWallet, programId: PublicKey, mint: PublicKey, opts?: ConfirmOptions);
17
+ constructor(connection: Connection, wallet: IWallet, programId: PublicKey, mint: PublicKey, opts?: ConfirmOptions, context?: BankrunContextWrapper);
16
18
  getFaucetConfigPublicKeyAndNonce(): Promise<[
17
19
  PublicKey,
18
20
  number
@@ -33,12 +33,15 @@ const spl_token_1 = require("@solana/spl-token");
33
33
  const web3_js_1 = require("@solana/web3.js");
34
34
  const token_faucet_json_1 = __importDefault(require("./idl/token_faucet.json"));
35
35
  class TokenFaucet {
36
- constructor(connection, wallet, programId, mint, opts) {
36
+ constructor(connection, wallet, programId, mint, opts, context) {
37
37
  this.connection = connection;
38
+ this.context = context;
38
39
  this.wallet = wallet;
39
40
  this.opts = opts || anchor_1.AnchorProvider.defaultOptions();
40
41
  // @ts-ignore
41
- const provider = new anchor_1.AnchorProvider(connection, wallet, this.opts);
42
+ const provider = new anchor_1.AnchorProvider(context ? context.connection.toConnection() : this.connection,
43
+ // @ts-ignore
44
+ wallet, this.opts);
42
45
  this.provider = provider;
43
46
  this.program = new anchor_1.Program(token_faucet_json_1.default, programId, provider);
44
47
  this.mint = mint;
@@ -60,7 +63,7 @@ class TokenFaucet {
60
63
  }
61
64
  async initialize() {
62
65
  const [faucetConfigPublicKey] = await this.getFaucetConfigPublicKeyAndNonce();
63
- return await this.program.rpc.initialize({
66
+ const ix = this.program.instruction.initialize({
64
67
  accounts: {
65
68
  faucetConfig: faucetConfigPublicKey,
66
69
  admin: this.wallet.publicKey,
@@ -70,6 +73,9 @@ class TokenFaucet {
70
73
  tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
71
74
  },
72
75
  });
76
+ const tx = new web3_js_1.Transaction().add(ix);
77
+ const txSig = await this.context.sendTransaction(tx);
78
+ return txSig;
73
79
  }
74
80
  async fetchState() {
75
81
  return await this.program.account.faucetConfig.fetch(await this.getFaucetConfigPublicKey());
@@ -88,10 +94,28 @@ class TokenFaucet {
88
94
  async mintToUser(userTokenAccount, amount) {
89
95
  const mintIx = await this.mintToUserIx(userTokenAccount, amount);
90
96
  const tx = new web3_js_1.Transaction().add(mintIx);
91
- const txSig = await this.program.provider.sendAndConfirm(tx, [], this.opts);
92
- return txSig;
97
+ if (this.context) {
98
+ return await this.context.sendTransaction(tx);
99
+ }
100
+ else {
101
+ return await this.program.provider.sendAndConfirm(tx, [], this.opts);
102
+ }
93
103
  }
94
104
  async transferMintAuthority() {
105
+ if (this.context) {
106
+ const ix = this.program.instruction.transferMintAuthority({
107
+ accounts: {
108
+ faucetConfig: await this.getFaucetConfigPublicKey(),
109
+ mintAccount: this.mint,
110
+ mintAuthority: await this.getMintAuthority(),
111
+ tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
112
+ admin: this.wallet.publicKey,
113
+ },
114
+ });
115
+ const tx = new web3_js_1.Transaction().add(ix);
116
+ const txSig = await this.context.sendTransaction(tx);
117
+ return txSig;
118
+ }
95
119
  return await this.program.rpc.transferMintAuthority({
96
120
  accounts: {
97
121
  faucetConfig: await this.getFaucetConfigPublicKey(),
@@ -107,7 +131,7 @@ class TokenFaucet {
107
131
  const [associatedTokenPublicKey, createAssociatedAccountIx, mintToTx] = await this.createAssociatedTokenAccountAndMintToInstructions(userPublicKey, amount);
108
132
  let associatedTokenAccountExists = false;
109
133
  try {
110
- const assosciatedTokenAccount = await this.connection.getAccountInfo(associatedTokenPublicKey);
134
+ const assosciatedTokenAccount = await this.context.connection.getAccountInfo(associatedTokenPublicKey);
111
135
  associatedTokenAccountExists = !!assosciatedTokenAccount;
112
136
  }
113
137
  catch (e) {
@@ -118,7 +142,13 @@ class TokenFaucet {
118
142
  if (!skipAccountCreation)
119
143
  tx.add(createAssociatedAccountIx);
120
144
  tx.add(mintToTx);
121
- const txSig = await this.program.provider.sendAndConfirm(tx, [], this.opts);
145
+ let txSig;
146
+ if (this.context) {
147
+ txSig = await this.context.sendTransaction(tx);
148
+ }
149
+ else {
150
+ txSig = await this.program.provider.sendAndConfirm(tx, [], this.opts);
151
+ }
122
152
  return [associatedTokenPublicKey, txSig];
123
153
  }
124
154
  async createAssociatedTokenAccountAndMintToInstructions(userPublicKey, amount) {
@@ -134,6 +164,9 @@ class TokenFaucet {
134
164
  }
135
165
  async getTokenAccountInfo(props) {
136
166
  const associatedKey = await this.getAssosciatedMockUSDMintAddress(props);
167
+ if (this.context) {
168
+ return await this.context.connection.getTokenAccount(associatedKey);
169
+ }
137
170
  return await (0, spl_token_1.getAccount)(this.connection, associatedKey);
138
171
  }
139
172
  async subscribeToTokenAccount(props) {
@@ -141,7 +174,7 @@ class TokenFaucet {
141
174
  const tokenAccountKey = await this.getAssosciatedMockUSDMintAddress(props);
142
175
  props.callback(await this.getTokenAccountInfo(props));
143
176
  // Couldn't find a way to do it using anchor framework subscription, someone on serum discord recommended this way
144
- this.connection.onAccountChange(tokenAccountKey, async (_accountInfo /* accountInfo is a buffer which we don't know how to deserialize */) => {
177
+ this.context.connection.onAccountChange(tokenAccountKey, async (_accountInfo /* accountInfo is a buffer which we don't know how to deserialize */) => {
145
178
  props.callback(await this.getTokenAccountInfo(props));
146
179
  });
147
180
  return true;
@@ -0,0 +1,8 @@
1
+ import { BlockhashWithExpiryBlockHeight, Commitment, Connection } from '@solana/web3.js';
2
+ import { BlockhashFetcher } from './types';
3
+ export declare class BaseBlockhashFetcher implements BlockhashFetcher {
4
+ private connection;
5
+ private blockhashCommitment;
6
+ constructor(connection: Connection, blockhashCommitment: Commitment);
7
+ getLatestBlockhash(): Promise<BlockhashWithExpiryBlockHeight | undefined>;
8
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseBlockhashFetcher = void 0;
4
+ class BaseBlockhashFetcher {
5
+ constructor(connection, blockhashCommitment) {
6
+ this.connection = connection;
7
+ this.blockhashCommitment = blockhashCommitment;
8
+ }
9
+ async getLatestBlockhash() {
10
+ return this.connection.getLatestBlockhash(this.blockhashCommitment);
11
+ }
12
+ }
13
+ exports.BaseBlockhashFetcher = BaseBlockhashFetcher;
@@ -0,0 +1,28 @@
1
+ import { BlockhashWithExpiryBlockHeight, Commitment, Connection } from '@solana/web3.js';
2
+ import { BlockhashFetcher } from './types';
3
+ /**
4
+ * Fetches the latest blockhash and caches it for a configurable amount of time.
5
+ *
6
+ * - Prevents RPC spam by reusing cached values
7
+ * - Retries on failure with exponential backoff
8
+ * - Prevents concurrent requests for the same blockhash
9
+ */
10
+ export declare class CachedBlockhashFetcher implements BlockhashFetcher {
11
+ private connection;
12
+ private blockhashCommitment;
13
+ private retryCount;
14
+ private retrySleepTimeMs;
15
+ private staleCacheTimeMs;
16
+ private recentBlockhashCache;
17
+ private blockhashFetchingPromise;
18
+ constructor(connection: Connection, blockhashCommitment: Commitment, retryCount: number, retrySleepTimeMs: number, staleCacheTimeMs: number);
19
+ private fetchBlockhashWithRetry;
20
+ private sleep;
21
+ private updateBlockhashCache;
22
+ getLatestBlockhash(): Promise<BlockhashWithExpiryBlockHeight | undefined>;
23
+ private isCacheStale;
24
+ /**
25
+ * Refresh the blockhash cache, await a pending refresh if it exists
26
+ */
27
+ private refreshBlockhash;
28
+ }
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CachedBlockhashFetcher = void 0;
4
+ /**
5
+ * Fetches the latest blockhash and caches it for a configurable amount of time.
6
+ *
7
+ * - Prevents RPC spam by reusing cached values
8
+ * - Retries on failure with exponential backoff
9
+ * - Prevents concurrent requests for the same blockhash
10
+ */
11
+ class CachedBlockhashFetcher {
12
+ constructor(connection, blockhashCommitment, retryCount, retrySleepTimeMs, staleCacheTimeMs) {
13
+ this.connection = connection;
14
+ this.blockhashCommitment = blockhashCommitment;
15
+ this.retryCount = retryCount;
16
+ this.retrySleepTimeMs = retrySleepTimeMs;
17
+ this.staleCacheTimeMs = staleCacheTimeMs;
18
+ this.recentBlockhashCache = { value: undefined, lastUpdated: 0 };
19
+ this.blockhashFetchingPromise = null;
20
+ }
21
+ async fetchBlockhashWithRetry() {
22
+ for (let i = 0; i < this.retryCount; i++) {
23
+ try {
24
+ return await this.connection.getLatestBlockhash(this.blockhashCommitment);
25
+ }
26
+ catch (err) {
27
+ if (i === this.retryCount - 1) {
28
+ throw new Error('Failed to fetch blockhash after maximum retries');
29
+ }
30
+ await this.sleep(this.retrySleepTimeMs * 2 ** i);
31
+ }
32
+ }
33
+ throw new Error('Failed to fetch blockhash after maximum retries');
34
+ }
35
+ sleep(ms) {
36
+ return new Promise((resolve) => setTimeout(resolve, ms));
37
+ }
38
+ async updateBlockhashCache() {
39
+ const result = await this.fetchBlockhashWithRetry();
40
+ this.recentBlockhashCache = {
41
+ value: result,
42
+ lastUpdated: Date.now(),
43
+ };
44
+ }
45
+ async getLatestBlockhash() {
46
+ if (this.isCacheStale()) {
47
+ await this.refreshBlockhash();
48
+ }
49
+ return this.recentBlockhashCache.value;
50
+ }
51
+ isCacheStale() {
52
+ const lastUpdateTime = this.recentBlockhashCache.lastUpdated;
53
+ return (!lastUpdateTime || Date.now() > lastUpdateTime + this.staleCacheTimeMs);
54
+ }
55
+ /**
56
+ * Refresh the blockhash cache, await a pending refresh if it exists
57
+ */
58
+ async refreshBlockhash() {
59
+ if (!this.blockhashFetchingPromise) {
60
+ this.blockhashFetchingPromise = this.updateBlockhashCache();
61
+ try {
62
+ await this.blockhashFetchingPromise;
63
+ }
64
+ finally {
65
+ this.blockhashFetchingPromise = null;
66
+ }
67
+ }
68
+ else {
69
+ await this.blockhashFetchingPromise;
70
+ }
71
+ }
72
+ }
73
+ exports.CachedBlockhashFetcher = CachedBlockhashFetcher;
@@ -0,0 +1,4 @@
1
+ import { BlockhashWithExpiryBlockHeight } from '@solana/web3.js';
2
+ export interface BlockhashFetcher {
3
+ getLatestBlockhash(): Promise<BlockhashWithExpiryBlockHeight | undefined>;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -13,6 +13,14 @@ export type TxBuildingProps = {
13
13
  recentBlockhash?: BlockhashWithExpiryBlockHeight;
14
14
  wallet?: IWallet;
15
15
  };
16
+ export type TxHandlerConfig = {
17
+ blockhashCachingEnabled?: boolean;
18
+ blockhashCachingConfig?: {
19
+ retryCount: number;
20
+ retrySleepTimeMs: number;
21
+ staleCacheTimeMs: number;
22
+ };
23
+ };
16
24
  /**
17
25
  * This class is responsible for creating and signing transactions.
18
26
  */
@@ -25,6 +33,7 @@ export declare class TxHandler {
25
33
  private preSignedCb?;
26
34
  private onSignedCb?;
27
35
  private blockhashCommitment;
36
+ private blockHashFetcher;
28
37
  constructor(props: {
29
38
  connection: Connection;
30
39
  wallet: IWallet;
@@ -34,6 +43,7 @@ export declare class TxHandler {
34
43
  onSignedCb?: (txSigs: DriftClientMetricsEvents['txSigned']) => void;
35
44
  preSignedCb?: () => void;
36
45
  };
46
+ config?: TxHandlerConfig;
37
47
  });
38
48
  private addHashAndExpiryToLookup;
39
49
  private getProps;
@@ -8,6 +8,9 @@ const web3_js_1 = require("@solana/web3.js");
8
8
  const txParamProcessor_1 = require("./txParamProcessor");
9
9
  const bs58_1 = __importDefault(require("bs58"));
10
10
  const computeUnits_1 = require("../util/computeUnits");
11
+ const cachedBlockhashFetcher_1 = require("./blockhashFetcher/cachedBlockhashFetcher");
12
+ const baseBlockhashFetcher_1 = require("./blockhashFetcher/baseBlockhashFetcher");
13
+ const utils_1 = require("./utils");
11
14
  /**
12
15
  * Explanation for SIGNATURE_BLOCK_AND_EXPIRY:
13
16
  *
@@ -15,12 +18,15 @@ const computeUnits_1 = require("../util/computeUnits");
15
18
  */
16
19
  const DEV_TRY_FORCE_TX_TIMEOUTS = process.env.DEV_TRY_FORCE_TX_TIMEOUTS === 'true' || false;
17
20
  exports.COMPUTE_UNITS_DEFAULT = 200000;
21
+ const BLOCKHASH_FETCH_RETRY_COUNT = 3;
22
+ const BLOCKHASH_FETCH_RETRY_SLEEP = 200;
23
+ const RECENT_BLOCKHASH_STALE_TIME_MS = 2000; // Reuse blockhashes within this timeframe during bursts of tx contruction
18
24
  /**
19
25
  * This class is responsible for creating and signing transactions.
20
26
  */
21
27
  class TxHandler {
22
28
  constructor(props) {
23
- var _a, _b, _c, _d;
29
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
24
30
  this.blockHashToLastValidBlockHeightLookup = {};
25
31
  this.returnBlockHeightsWithSignedTxCallbackData = false;
26
32
  this.blockhashCommitment = 'finalized';
@@ -28,11 +34,14 @@ class TxHandler {
28
34
  this.connection = props.connection;
29
35
  this.wallet = props.wallet;
30
36
  this.confirmationOptions = props.confirmationOptions;
37
+ this.blockHashFetcher = ((_a = props === null || props === void 0 ? void 0 : props.config) === null || _a === void 0 ? void 0 : _a.blockhashCachingEnabled)
38
+ ? new cachedBlockhashFetcher_1.CachedBlockhashFetcher(this.connection, this.blockhashCommitment, (_d = (_c = (_b = props === null || props === void 0 ? void 0 : props.config) === null || _b === void 0 ? void 0 : _b.blockhashCachingConfig) === null || _c === void 0 ? void 0 : _c.retryCount) !== null && _d !== void 0 ? _d : BLOCKHASH_FETCH_RETRY_COUNT, (_g = (_f = (_e = props === null || props === void 0 ? void 0 : props.config) === null || _e === void 0 ? void 0 : _e.blockhashCachingConfig) === null || _f === void 0 ? void 0 : _f.retrySleepTimeMs) !== null && _g !== void 0 ? _g : BLOCKHASH_FETCH_RETRY_SLEEP, (_k = (_j = (_h = props === null || props === void 0 ? void 0 : props.config) === null || _h === void 0 ? void 0 : _h.blockhashCachingConfig) === null || _j === void 0 ? void 0 : _j.staleCacheTimeMs) !== null && _k !== void 0 ? _k : RECENT_BLOCKHASH_STALE_TIME_MS)
39
+ : new baseBlockhashFetcher_1.BaseBlockhashFetcher(this.connection, this.blockhashCommitment);
31
40
  // #Optionals
32
41
  this.returnBlockHeightsWithSignedTxCallbackData =
33
- (_b = (_a = props.opts) === null || _a === void 0 ? void 0 : _a.returnBlockHeightsWithSignedTxCallbackData) !== null && _b !== void 0 ? _b : false;
34
- this.onSignedCb = (_c = props.opts) === null || _c === void 0 ? void 0 : _c.onSignedCb;
35
- this.preSignedCb = (_d = props.opts) === null || _d === void 0 ? void 0 : _d.preSignedCb;
42
+ (_m = (_l = props.opts) === null || _l === void 0 ? void 0 : _l.returnBlockHeightsWithSignedTxCallbackData) !== null && _m !== void 0 ? _m : false;
43
+ this.onSignedCb = (_o = props.opts) === null || _o === void 0 ? void 0 : _o.onSignedCb;
44
+ this.preSignedCb = (_p = props.opts) === null || _p === void 0 ? void 0 : _p.preSignedCb;
36
45
  }
37
46
  addHashAndExpiryToLookup(hashAndExpiry) {
38
47
  if (!this.returnBlockHeightsWithSignedTxCallbackData)
@@ -50,8 +59,8 @@ class TxHandler {
50
59
  *
51
60
  * @returns
52
61
  */
53
- getLatestBlockhashForTransaction() {
54
- return this.connection.getLatestBlockhash(this.blockhashCommitment);
62
+ async getLatestBlockhashForTransaction() {
63
+ return this.blockHashFetcher.getLatestBlockhash();
55
64
  }
56
65
  /**
57
66
  * Applies recent blockhash and signs a given transaction
@@ -80,7 +89,7 @@ class TxHandler {
80
89
  return signedTx;
81
90
  }
82
91
  isVersionedTransaction(tx) {
83
- return (tx === null || tx === void 0 ? void 0 : tx.message) && true;
92
+ return (0, utils_1.isVersionedTransaction)(tx);
84
93
  }
85
94
  isLegacyTransaction(tx) {
86
95
  return !this.isVersionedTransaction(tx);