@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
@@ -129,6 +129,7 @@ import { numberToSafeBN } from './math/utils';
129
129
  import { TransactionParamProcessor } from './tx/txParamProcessor';
130
130
  import { isOracleValid } from './math/oracles';
131
131
  import { TxHandler } from './tx/txHandler';
132
+ import { isVersionedTransaction } from './tx/utils';
132
133
 
133
134
  type RemainingAccountParams = {
134
135
  userAccounts: UserAccount[];
@@ -227,6 +228,7 @@ export class DriftClient {
227
228
  onSignedCb: this.handleSignedTransaction.bind(this),
228
229
  preSignedCb: this.handlePreSignedTransaction.bind(this),
229
230
  },
231
+ config: config.txHandlerConfig,
230
232
  });
231
233
 
232
234
  if (config.includeDelegates && config.subAccountIds) {
@@ -1800,25 +1802,14 @@ export class DriftClient {
1800
1802
  });
1801
1803
  }
1802
1804
 
1803
- /**
1804
- * Deposit funds into the given spot market
1805
- *
1806
- * @param amount to deposit
1807
- * @param marketIndex spot market index to deposit into
1808
- * @param associatedTokenAccount can be the wallet public key if using native sol
1809
- * @param subAccountId subaccountId to deposit
1810
- * @param reduceOnly if true, deposit must not increase account risk
1811
- */
1812
- public async deposit(
1805
+ public async createDepositTxn(
1813
1806
  amount: BN,
1814
1807
  marketIndex: number,
1815
1808
  associatedTokenAccount: PublicKey,
1816
1809
  subAccountId?: number,
1817
1810
  reduceOnly = false,
1818
1811
  txParams?: TxParams
1819
- ): Promise<TransactionSignature> {
1820
- const additionalSigners: Array<Signer> = [];
1821
-
1812
+ ): Promise<ReturnType<typeof this.buildTransaction>> {
1822
1813
  const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
1823
1814
 
1824
1815
  const isSolMarket = spotMarketAccount.mint.equals(WRAPPED_SOL_MINT);
@@ -1868,11 +1859,36 @@ export class DriftClient {
1868
1859
 
1869
1860
  const tx = await this.buildTransaction(instructions, txParams);
1870
1861
 
1871
- const { txSig, slot } = await this.sendTransaction(
1872
- tx,
1873
- additionalSigners,
1874
- this.opts
1862
+ return tx;
1863
+ }
1864
+
1865
+ /**
1866
+ * Deposit funds into the given spot market
1867
+ *
1868
+ * @param amount to deposit
1869
+ * @param marketIndex spot market index to deposit into
1870
+ * @param associatedTokenAccount can be the wallet public key if using native sol
1871
+ * @param subAccountId subaccountId to deposit
1872
+ * @param reduceOnly if true, deposit must not increase account risk
1873
+ */
1874
+ public async deposit(
1875
+ amount: BN,
1876
+ marketIndex: number,
1877
+ associatedTokenAccount: PublicKey,
1878
+ subAccountId?: number,
1879
+ reduceOnly = false,
1880
+ txParams?: TxParams
1881
+ ): Promise<TransactionSignature> {
1882
+ const tx = await this.createDepositTxn(
1883
+ amount,
1884
+ marketIndex,
1885
+ associatedTokenAccount,
1886
+ subAccountId,
1887
+ reduceOnly,
1888
+ txParams
1875
1889
  );
1890
+
1891
+ const { txSig, slot } = await this.sendTransaction(tx, [], this.opts);
1876
1892
  this.spotMarketLastSlotCache.set(marketIndex, slot);
1877
1893
  return txSig;
1878
1894
  }
@@ -2005,20 +2021,7 @@ export class DriftClient {
2005
2021
  );
2006
2022
  }
2007
2023
 
2008
- /**
2009
- * Creates the User account for a user, and deposits some initial collateral
2010
- * @param amount
2011
- * @param userTokenAccount
2012
- * @param marketIndex
2013
- * @param subAccountId
2014
- * @param name
2015
- * @param fromSubAccountId
2016
- * @param referrerInfo
2017
- * @param donateAmount
2018
- * @param txParams
2019
- * @returns
2020
- */
2021
- public async initializeUserAccountAndDepositCollateral(
2024
+ public async createInitializeUserAccountAndDepositCollateral(
2022
2025
  amount: BN,
2023
2026
  userTokenAccount: PublicKey,
2024
2027
  marketIndex = 0,
@@ -2029,7 +2032,7 @@ export class DriftClient {
2029
2032
  donateAmount?: BN,
2030
2033
  txParams?: TxParams,
2031
2034
  customMaxMarginRatio?: number
2032
- ): Promise<[TransactionSignature, PublicKey]> {
2035
+ ): Promise<[Transaction | VersionedTransaction, PublicKey]> {
2033
2036
  const ixs = [];
2034
2037
 
2035
2038
  const [userAccountPublicKey, initializeUserAccountIx] =
@@ -2039,8 +2042,6 @@ export class DriftClient {
2039
2042
  referrerInfo
2040
2043
  );
2041
2044
 
2042
- const additionalSigners: Array<Signer> = [];
2043
-
2044
2045
  const spotMarket = this.getSpotMarketAccount(marketIndex);
2045
2046
 
2046
2047
  const isSolMarket = spotMarket.mint.equals(WRAPPED_SOL_MINT);
@@ -2134,6 +2135,49 @@ export class DriftClient {
2134
2135
 
2135
2136
  const tx = await this.buildTransaction(ixs, txParams);
2136
2137
 
2138
+ return [tx, userAccountPublicKey];
2139
+ }
2140
+
2141
+ /**
2142
+ * Creates the User account for a user, and deposits some initial collateral
2143
+ * @param amount
2144
+ * @param userTokenAccount
2145
+ * @param marketIndex
2146
+ * @param subAccountId
2147
+ * @param name
2148
+ * @param fromSubAccountId
2149
+ * @param referrerInfo
2150
+ * @param donateAmount
2151
+ * @param txParams
2152
+ * @returns
2153
+ */
2154
+ public async initializeUserAccountAndDepositCollateral(
2155
+ amount: BN,
2156
+ userTokenAccount: PublicKey,
2157
+ marketIndex = 0,
2158
+ subAccountId = 0,
2159
+ name?: string,
2160
+ fromSubAccountId?: number,
2161
+ referrerInfo?: ReferrerInfo,
2162
+ donateAmount?: BN,
2163
+ txParams?: TxParams,
2164
+ customMaxMarginRatio?: number
2165
+ ): Promise<[TransactionSignature, PublicKey]> {
2166
+ const [tx, userAccountPublicKey] =
2167
+ await this.createInitializeUserAccountAndDepositCollateral(
2168
+ amount,
2169
+ userTokenAccount,
2170
+ marketIndex,
2171
+ subAccountId,
2172
+ name,
2173
+ fromSubAccountId,
2174
+ referrerInfo,
2175
+ donateAmount,
2176
+ txParams,
2177
+ customMaxMarginRatio
2178
+ );
2179
+ const additionalSigners: Array<Signer> = [];
2180
+
2137
2181
  const { txSig, slot } = await this.sendTransaction(
2138
2182
  tx,
2139
2183
  additionalSigners,
@@ -2770,7 +2814,7 @@ export class DriftClient {
2770
2814
  }
2771
2815
 
2772
2816
  public async sendSignedTx(
2773
- tx: Transaction,
2817
+ tx: Transaction | VersionedTransaction,
2774
2818
  opts?: ConfirmOptions
2775
2819
  ): Promise<TransactionSignature> {
2776
2820
  const { txSig } = await this.sendTransaction(
@@ -4118,6 +4162,10 @@ export class DriftClient {
4118
4162
  const outMarket = this.getSpotMarketAccount(outMarketIndex);
4119
4163
  const inMarket = this.getSpotMarketAccount(inMarketIndex);
4120
4164
 
4165
+ const isExactOut = swapMode === 'ExactOut' || quote.swapMode === 'ExactOut';
4166
+ const amountIn = new BN(quote.inAmount);
4167
+ const exactOutBufferedAmountIn = amountIn.muln(1001).divn(1000); // Add 10bp buffer
4168
+
4121
4169
  if (!quote) {
4122
4170
  const fetchedQuote = await jupiterClient.getQuote({
4123
4171
  inputMint: inMarket.mint,
@@ -4198,7 +4246,7 @@ export class DriftClient {
4198
4246
  const { beginSwapIx, endSwapIx } = await this.getSwapIx({
4199
4247
  outMarketIndex,
4200
4248
  inMarketIndex,
4201
- amountIn: new BN(quote.inAmount),
4249
+ amountIn: isExactOut ? exactOutBufferedAmountIn : amountIn,
4202
4250
  inTokenAccount: inAssociatedTokenAccount,
4203
4251
  outTokenAccount: outAssociatedTokenAccount,
4204
4252
  reduceOnly,
@@ -4664,7 +4712,8 @@ export class DriftClient {
4664
4712
  await TransactionParamProcessor.getTxSimComputeUnits(
4665
4713
  placeAndTakeTxToSim,
4666
4714
  this.connection,
4667
- txParams.computeUnitsBufferMultiplier ?? 1.2
4715
+ txParams.computeUnitsBufferMultiplier ?? 1.2,
4716
+ txParams.lowerBoundCu
4668
4717
  );
4669
4718
 
4670
4719
  if (shouldExitIfSimulationFails && !simulationResult.success) {
@@ -4784,6 +4833,10 @@ export class DriftClient {
4784
4833
  exitEarlyIfSimFails
4785
4834
  );
4786
4835
 
4836
+ if (!txsToSign) {
4837
+ return null;
4838
+ }
4839
+
4787
4840
  const signedTxs = (
4788
4841
  await this.txHandler.getSignedTransactionMap(
4789
4842
  txsToSign,
@@ -6854,11 +6907,7 @@ export class DriftClient {
6854
6907
  private isVersionedTransaction(
6855
6908
  tx: Transaction | VersionedTransaction
6856
6909
  ): boolean {
6857
- const version = (tx as VersionedTransaction)?.version;
6858
- const isVersionedTx =
6859
- tx instanceof VersionedTransaction || version !== undefined;
6860
-
6861
- return isVersionedTx;
6910
+ return isVersionedTransaction(tx);
6862
6911
  }
6863
6912
 
6864
6913
  sendTransaction(
@@ -10,7 +10,7 @@ import { OracleInfo } from './oracles/types';
10
10
  import { BulkAccountLoader } from './accounts/bulkAccountLoader';
11
11
  import { DriftEnv } from './config';
12
12
  import { TxSender } from './tx/types';
13
- import { TxHandler } from './tx/txHandler';
13
+ import { TxHandler, TxHandlerConfig } from './tx/txHandler';
14
14
 
15
15
  export type DriftClientConfig = {
16
16
  connection: Connection;
@@ -35,6 +35,7 @@ export type DriftClientConfig = {
35
35
  txVersion?: TransactionVersion; // which tx version to use
36
36
  txParams?: TxParams; // default tx params to use
37
37
  enableMetricsEvents?: boolean;
38
+ txHandlerConfig?: TxHandlerConfig;
38
39
  };
39
40
 
40
41
  export type DriftClientSubscriptionConfig =
@@ -45,6 +45,7 @@ export class EventSubscriber {
45
45
 
46
46
  if (this.options.logProviderConfig.type === 'websocket') {
47
47
  this.logProvider = new WebSocketLogProvider(
48
+ // @ts-ignore
48
49
  this.connection,
49
50
  this.address,
50
51
  this.options.commitment,
@@ -52,6 +53,7 @@ export class EventSubscriber {
52
53
  );
53
54
  } else {
54
55
  this.logProvider = new PollingLogProvider(
56
+ // @ts-ignore
55
57
  this.connection,
56
58
  this.address,
57
59
  options.commitment,
@@ -101,6 +103,7 @@ export class EventSubscriber {
101
103
  this.logProvider.eventEmitter.removeAllListeners('reconnect');
102
104
  this.unsubscribe().then(() => {
103
105
  this.logProvider = new PollingLogProvider(
106
+ // @ts-ignore
104
107
  this.connection,
105
108
  this.address,
106
109
  this.options.commitment,
@@ -148,6 +151,7 @@ export class EventSubscriber {
148
151
  }
149
152
 
150
153
  const wrappedEvents = this.parseEventsFromLogs(txSig, slot, logs);
154
+
151
155
  for (const wrappedEvent of wrappedEvents) {
152
156
  this.eventListMap.get(wrappedEvent.eventType).insert(wrappedEvent);
153
157
  }
@@ -188,6 +192,7 @@ export class EventSubscriber {
188
192
  const untilTx: TransactionSignature = this.options.untilTx;
189
193
  while (txFetched < this.options.maxTx) {
190
194
  const response = await fetchLogs(
195
+ // @ts-ignore
191
196
  this.connection,
192
197
  this.address,
193
198
  this.options.commitment === 'finalized' ? 'finalized' : 'confirmed',
@@ -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/src/testClient.ts CHANGED
@@ -10,8 +10,6 @@ export class TestClient extends AdminClient {
10
10
  throw new Error('Test client must be polling');
11
11
  }
12
12
  super(config);
13
- // @ts-ignore
14
- this.txHandler.blockhashCommitment = 'recent';
15
13
  }
16
14
 
17
15
  async sendTransaction(
@@ -30,6 +28,7 @@ export class TestClient extends AdminClient {
30
28
  let lastFetchedSlot = (
31
29
  this.accountSubscriber as PollingDriftClientAccountSubscriber
32
30
  ).accountLoader.mostRecentSlot;
31
+ await this.fetchAccounts();
33
32
  while (lastFetchedSlot < slot) {
34
33
  await this.fetchAccounts();
35
34
  lastFetchedSlot = (
@@ -2,10 +2,10 @@ import * as anchor from '@coral-xyz/anchor';
2
2
  import { AnchorProvider, Idl, Program } from '@coral-xyz/anchor';
3
3
  import {
4
4
  TOKEN_PROGRAM_ID,
5
- getAccount,
6
5
  Account,
7
6
  createAssociatedTokenAccountInstruction,
8
7
  getAssociatedTokenAddress,
8
+ getAccount,
9
9
  } from '@solana/spl-token';
10
10
  import {
11
11
  ConfirmOptions,
@@ -19,8 +19,10 @@ import {
19
19
  import { BN } from '.';
20
20
  import tokenFaucet from './idl/token_faucet.json';
21
21
  import { IWallet } from './types';
22
+ import { BankrunContextWrapper } from './bankrun/bankrunConnection';
22
23
 
23
24
  export class TokenFaucet {
25
+ context?: BankrunContextWrapper;
24
26
  connection: Connection;
25
27
  wallet: IWallet;
26
28
  public program: Program;
@@ -33,13 +35,20 @@ export class TokenFaucet {
33
35
  wallet: IWallet,
34
36
  programId: PublicKey,
35
37
  mint: PublicKey,
36
- opts?: ConfirmOptions
38
+ opts?: ConfirmOptions,
39
+ context?: BankrunContextWrapper
37
40
  ) {
38
41
  this.connection = connection;
42
+ this.context = context;
39
43
  this.wallet = wallet;
40
44
  this.opts = opts || AnchorProvider.defaultOptions();
41
45
  // @ts-ignore
42
- const provider = new AnchorProvider(connection, wallet, this.opts);
46
+ const provider = new AnchorProvider(
47
+ context ? context.connection.toConnection() : this.connection,
48
+ // @ts-ignore
49
+ wallet,
50
+ this.opts
51
+ );
43
52
  this.provider = provider;
44
53
  this.program = new Program(tokenFaucet as Idl, programId, provider);
45
54
  this.mint = mint;
@@ -76,7 +85,7 @@ export class TokenFaucet {
76
85
  public async initialize(): Promise<TransactionSignature> {
77
86
  const [faucetConfigPublicKey] =
78
87
  await this.getFaucetConfigPublicKeyAndNonce();
79
- return await this.program.rpc.initialize({
88
+ const ix = this.program.instruction.initialize({
80
89
  accounts: {
81
90
  faucetConfig: faucetConfigPublicKey,
82
91
  admin: this.wallet.publicKey,
@@ -86,6 +95,9 @@ export class TokenFaucet {
86
95
  tokenProgram: TOKEN_PROGRAM_ID,
87
96
  },
88
97
  });
98
+ const tx = new Transaction().add(ix);
99
+ const txSig = await this.context.sendTransaction(tx);
100
+ return txSig;
89
101
  }
90
102
 
91
103
  public async fetchState(): Promise<any> {
@@ -114,12 +126,29 @@ export class TokenFaucet {
114
126
 
115
127
  const tx = new Transaction().add(mintIx);
116
128
 
117
- const txSig = await this.program.provider.sendAndConfirm(tx, [], this.opts);
118
-
119
- return txSig;
129
+ if (this.context) {
130
+ return await this.context.sendTransaction(tx);
131
+ } else {
132
+ return await this.program.provider.sendAndConfirm(tx, [], this.opts);
133
+ }
120
134
  }
121
135
 
122
136
  public async transferMintAuthority(): Promise<TransactionSignature> {
137
+ if (this.context) {
138
+ const ix = this.program.instruction.transferMintAuthority({
139
+ accounts: {
140
+ faucetConfig: await this.getFaucetConfigPublicKey(),
141
+ mintAccount: this.mint,
142
+ mintAuthority: await this.getMintAuthority(),
143
+ tokenProgram: TOKEN_PROGRAM_ID,
144
+ admin: this.wallet.publicKey,
145
+ },
146
+ });
147
+ const tx = new Transaction().add(ix);
148
+ const txSig = await this.context.sendTransaction(tx);
149
+ return txSig;
150
+ }
151
+
123
152
  return await this.program.rpc.transferMintAuthority({
124
153
  accounts: {
125
154
  faucetConfig: await this.getFaucetConfigPublicKey(),
@@ -146,9 +175,8 @@ export class TokenFaucet {
146
175
  let associatedTokenAccountExists = false;
147
176
 
148
177
  try {
149
- const assosciatedTokenAccount = await this.connection.getAccountInfo(
150
- associatedTokenPublicKey
151
- );
178
+ const assosciatedTokenAccount =
179
+ await this.context.connection.getAccountInfo(associatedTokenPublicKey);
152
180
 
153
181
  associatedTokenAccountExists = !!assosciatedTokenAccount;
154
182
  } catch (e) {
@@ -162,7 +190,13 @@ export class TokenFaucet {
162
190
 
163
191
  tx.add(mintToTx);
164
192
 
165
- const txSig = await this.program.provider.sendAndConfirm(tx, [], this.opts);
193
+ let txSig;
194
+ if (this.context) {
195
+ txSig = await this.context.sendTransaction(tx);
196
+ } else {
197
+ txSig = await this.program.provider.sendAndConfirm(tx, [], this.opts);
198
+ }
199
+
166
200
  return [associatedTokenPublicKey, txSig];
167
201
  }
168
202
 
@@ -200,6 +234,9 @@ export class TokenFaucet {
200
234
  userPubKey: PublicKey;
201
235
  }): Promise<Account> {
202
236
  const associatedKey = await this.getAssosciatedMockUSDMintAddress(props);
237
+ if (this.context) {
238
+ return await this.context.connection.getTokenAccount(associatedKey);
239
+ }
203
240
  return await getAccount(this.connection, associatedKey);
204
241
  }
205
242
 
@@ -215,7 +252,7 @@ export class TokenFaucet {
215
252
  props.callback(await this.getTokenAccountInfo(props));
216
253
 
217
254
  // Couldn't find a way to do it using anchor framework subscription, someone on serum discord recommended this way
218
- this.connection.onAccountChange(
255
+ this.context.connection.onAccountChange(
219
256
  tokenAccountKey,
220
257
  async (
221
258
  _accountInfo /* accountInfo is a buffer which we don't know how to deserialize */
@@ -0,0 +1,19 @@
1
+ import {
2
+ BlockhashWithExpiryBlockHeight,
3
+ Commitment,
4
+ Connection,
5
+ } from '@solana/web3.js';
6
+ import { BlockhashFetcher } from './types';
7
+
8
+ export class BaseBlockhashFetcher implements BlockhashFetcher {
9
+ constructor(
10
+ private connection: Connection,
11
+ private blockhashCommitment: Commitment
12
+ ) {}
13
+
14
+ public async getLatestBlockhash(): Promise<
15
+ BlockhashWithExpiryBlockHeight | undefined
16
+ > {
17
+ return this.connection.getLatestBlockhash(this.blockhashCommitment);
18
+ }
19
+ }
@@ -0,0 +1,90 @@
1
+ import {
2
+ BlockhashWithExpiryBlockHeight,
3
+ Commitment,
4
+ Connection,
5
+ } from '@solana/web3.js';
6
+ import { BlockhashFetcher } from './types';
7
+
8
+ /**
9
+ * Fetches the latest blockhash and caches it for a configurable amount of time.
10
+ *
11
+ * - Prevents RPC spam by reusing cached values
12
+ * - Retries on failure with exponential backoff
13
+ * - Prevents concurrent requests for the same blockhash
14
+ */
15
+ export class CachedBlockhashFetcher implements BlockhashFetcher {
16
+ private recentBlockhashCache: {
17
+ value: BlockhashWithExpiryBlockHeight | undefined;
18
+ lastUpdated: number;
19
+ } = { value: undefined, lastUpdated: 0 };
20
+
21
+ private blockhashFetchingPromise: Promise<void> | null = null;
22
+
23
+ constructor(
24
+ private connection: Connection,
25
+ private blockhashCommitment: Commitment,
26
+ private retryCount: number,
27
+ private retrySleepTimeMs: number,
28
+ private staleCacheTimeMs: number
29
+ ) {}
30
+
31
+ private async fetchBlockhashWithRetry(): Promise<BlockhashWithExpiryBlockHeight> {
32
+ for (let i = 0; i < this.retryCount; i++) {
33
+ try {
34
+ return await this.connection.getLatestBlockhash(
35
+ this.blockhashCommitment
36
+ );
37
+ } catch (err) {
38
+ if (i === this.retryCount - 1) {
39
+ throw new Error('Failed to fetch blockhash after maximum retries');
40
+ }
41
+ await this.sleep(this.retrySleepTimeMs * 2 ** i);
42
+ }
43
+ }
44
+ throw new Error('Failed to fetch blockhash after maximum retries');
45
+ }
46
+
47
+ private sleep(ms: number): Promise<void> {
48
+ return new Promise((resolve) => setTimeout(resolve, ms));
49
+ }
50
+
51
+ private async updateBlockhashCache(): Promise<void> {
52
+ const result = await this.fetchBlockhashWithRetry();
53
+ this.recentBlockhashCache = {
54
+ value: result,
55
+ lastUpdated: Date.now(),
56
+ };
57
+ }
58
+
59
+ public async getLatestBlockhash(): Promise<
60
+ BlockhashWithExpiryBlockHeight | undefined
61
+ > {
62
+ if (this.isCacheStale()) {
63
+ await this.refreshBlockhash();
64
+ }
65
+ return this.recentBlockhashCache.value;
66
+ }
67
+
68
+ private isCacheStale(): boolean {
69
+ const lastUpdateTime = this.recentBlockhashCache.lastUpdated;
70
+ return (
71
+ !lastUpdateTime || Date.now() > lastUpdateTime + this.staleCacheTimeMs
72
+ );
73
+ }
74
+
75
+ /**
76
+ * Refresh the blockhash cache, await a pending refresh if it exists
77
+ */
78
+ private async refreshBlockhash(): Promise<void> {
79
+ if (!this.blockhashFetchingPromise) {
80
+ this.blockhashFetchingPromise = this.updateBlockhashCache();
81
+ try {
82
+ await this.blockhashFetchingPromise;
83
+ } finally {
84
+ this.blockhashFetchingPromise = null;
85
+ }
86
+ } else {
87
+ await this.blockhashFetchingPromise;
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,5 @@
1
+ import { BlockhashWithExpiryBlockHeight } from '@solana/web3.js';
2
+
3
+ export interface BlockhashFetcher {
4
+ getLatestBlockhash(): Promise<BlockhashWithExpiryBlockHeight | undefined>;
5
+ }