@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
@@ -25,6 +25,10 @@ import {
25
25
  TxParams,
26
26
  } from '../types';
27
27
  import { containsComputeUnitIxs } from '../util/computeUnits';
28
+ import { CachedBlockhashFetcher } from './blockhashFetcher/cachedBlockhashFetcher';
29
+ import { BaseBlockhashFetcher } from './blockhashFetcher/baseBlockhashFetcher';
30
+ import { BlockhashFetcher } from './blockhashFetcher/types';
31
+ import { isVersionedTransaction } from './utils';
28
32
 
29
33
  /**
30
34
  * Explanation for SIGNATURE_BLOCK_AND_EXPIRY:
@@ -37,6 +41,10 @@ const DEV_TRY_FORCE_TX_TIMEOUTS =
37
41
 
38
42
  export const COMPUTE_UNITS_DEFAULT = 200_000;
39
43
 
44
+ const BLOCKHASH_FETCH_RETRY_COUNT = 3;
45
+ const BLOCKHASH_FETCH_RETRY_SLEEP = 200;
46
+ const RECENT_BLOCKHASH_STALE_TIME_MS = 2_000; // Reuse blockhashes within this timeframe during bursts of tx contruction
47
+
40
48
  export type TxBuildingProps = {
41
49
  instructions: TransactionInstruction | TransactionInstruction[];
42
50
  txVersion: TransactionVersion;
@@ -50,6 +58,15 @@ export type TxBuildingProps = {
50
58
  wallet?: IWallet;
51
59
  };
52
60
 
61
+ export type TxHandlerConfig = {
62
+ blockhashCachingEnabled?: boolean;
63
+ blockhashCachingConfig?: {
64
+ retryCount: number;
65
+ retrySleepTimeMs: number;
66
+ staleCacheTimeMs: number;
67
+ };
68
+ };
69
+
53
70
  /**
54
71
  * This class is responsible for creating and signing transactions.
55
72
  */
@@ -65,6 +82,7 @@ export class TxHandler {
65
82
  private onSignedCb?: (txSigs: DriftClientMetricsEvents['txSigned']) => void;
66
83
 
67
84
  private blockhashCommitment: Commitment = 'finalized';
85
+ private blockHashFetcher: BlockhashFetcher;
68
86
 
69
87
  constructor(props: {
70
88
  connection: Connection;
@@ -75,11 +93,25 @@ export class TxHandler {
75
93
  onSignedCb?: (txSigs: DriftClientMetricsEvents['txSigned']) => void;
76
94
  preSignedCb?: () => void;
77
95
  };
96
+ config?: TxHandlerConfig;
78
97
  }) {
79
98
  this.connection = props.connection;
80
99
  this.wallet = props.wallet;
81
100
  this.confirmationOptions = props.confirmationOptions;
82
101
 
102
+ this.blockHashFetcher = props?.config?.blockhashCachingEnabled
103
+ ? new CachedBlockhashFetcher(
104
+ this.connection,
105
+ this.blockhashCommitment,
106
+ props?.config?.blockhashCachingConfig?.retryCount ??
107
+ BLOCKHASH_FETCH_RETRY_COUNT,
108
+ props?.config?.blockhashCachingConfig?.retrySleepTimeMs ??
109
+ BLOCKHASH_FETCH_RETRY_SLEEP,
110
+ props?.config?.blockhashCachingConfig?.staleCacheTimeMs ??
111
+ RECENT_BLOCKHASH_STALE_TIME_MS
112
+ )
113
+ : new BaseBlockhashFetcher(this.connection, this.blockhashCommitment);
114
+
83
115
  // #Optionals
84
116
  this.returnBlockHeightsWithSignedTxCallbackData =
85
117
  props.opts?.returnBlockHeightsWithSignedTxCallbackData ?? false;
@@ -113,8 +145,8 @@ export class TxHandler {
113
145
  *
114
146
  * @returns
115
147
  */
116
- public getLatestBlockhashForTransaction() {
117
- return this.connection.getLatestBlockhash(this.blockhashCommitment);
148
+ public async getLatestBlockhashForTransaction() {
149
+ return this.blockHashFetcher.getLatestBlockhash();
118
150
  }
119
151
 
120
152
  /**
@@ -157,8 +189,10 @@ export class TxHandler {
157
189
  return signedTx;
158
190
  }
159
191
 
160
- private isVersionedTransaction(tx: Transaction | VersionedTransaction) {
161
- return (tx as VersionedTransaction)?.message && true;
192
+ private isVersionedTransaction(
193
+ tx: Transaction | VersionedTransaction
194
+ ): boolean {
195
+ return isVersionedTransaction(tx);
162
196
  }
163
197
 
164
198
  private isLegacyTransaction(tx: Transaction | VersionedTransaction) {
@@ -32,7 +32,8 @@ export class TransactionParamProcessor {
32
32
  public static async getTxSimComputeUnits(
33
33
  tx: VersionedTransaction,
34
34
  connection: Connection,
35
- bufferMultiplier: number // Making this a mandatory param to force the user to remember that simulated CU's can be inaccurate and a buffer should be applied
35
+ bufferMultiplier: number, // Making this a mandatory param to force the user to remember that simulated CU's can be inaccurate and a buffer should be applied
36
+ lowerBoundCu?: number
36
37
  ): Promise<{ success: boolean; computeUnits: number }> {
37
38
  try {
38
39
  if (TEST_SIMS_ALWAYS_FAIL)
@@ -49,10 +50,18 @@ export class TransactionParamProcessor {
49
50
  const computeUnits = await this.getComputeUnitsFromSim(simTxResult);
50
51
 
51
52
  // Apply the buffer, but round down to the MAX_COMPUTE_UNITS, and round up to the nearest whole number
52
- const bufferedComputeUnits = Math.ceil(
53
+ let bufferedComputeUnits = Math.ceil(
53
54
  Math.min(computeUnits * bufferMultiplier, MAX_COMPUTE_UNITS)
54
55
  );
55
56
 
57
+ // If a lower bound CU is passed then enforce it
58
+ if (lowerBoundCu) {
59
+ bufferedComputeUnits = Math.max(
60
+ bufferedComputeUnits,
61
+ Math.min(lowerBoundCu, MAX_COMPUTE_UNITS)
62
+ );
63
+ }
64
+
56
65
  return {
57
66
  success: true,
58
67
  computeUnits: bufferedComputeUnits,
@@ -0,0 +1,11 @@
1
+ import { Transaction, VersionedTransaction } from '@solana/web3.js';
2
+
3
+ export const isVersionedTransaction = (
4
+ tx: Transaction | VersionedTransaction
5
+ ): boolean => {
6
+ const version = (tx as VersionedTransaction)?.version;
7
+ const isVersionedTx =
8
+ tx instanceof VersionedTransaction || version !== undefined;
9
+
10
+ return isVersionedTx;
11
+ };
package/src/types.ts CHANGED
@@ -1042,6 +1042,7 @@ export type ProcessingTxParams = {
1042
1042
  computeUnitsBufferMultiplier?: number;
1043
1043
  useSimulatedComputeUnitsForCUPriceCalculation?: boolean;
1044
1044
  getCUPriceFromComputeUnits?: (computeUnits: number) => number;
1045
+ lowerBoundCu?: number;
1045
1046
  };
1046
1047
 
1047
1048
  export type TxParams = BaseTxParams & ProcessingTxParams;
package/src/user.ts CHANGED
@@ -108,9 +108,12 @@ export class User {
108
108
  this.userAccountPublicKey = config.userAccountPublicKey;
109
109
  if (config.accountSubscription?.type === 'polling') {
110
110
  this.accountSubscriber = new PollingUserAccountSubscriber(
111
- config.driftClient.program,
111
+ config.driftClient.connection,
112
112
  config.userAccountPublicKey,
113
- config.accountSubscription.accountLoader
113
+ config.accountSubscription.accountLoader,
114
+ this.driftClient.program.account.user.coder.accounts.decodeUnchecked.bind(
115
+ this.driftClient.program.account.user.coder.accounts
116
+ )
114
117
  );
115
118
  } else if (config.accountSubscription?.type === 'custom') {
116
119
  this.accountSubscriber = config.accountSubscription.userAccountSubscriber;
@@ -0,0 +1,96 @@
1
+ import { expect } from 'chai';
2
+ import sinon from 'sinon';
3
+ import {
4
+ Connection,
5
+ Commitment,
6
+ BlockhashWithExpiryBlockHeight,
7
+ } from '@solana/web3.js';
8
+ import { CachedBlockhashFetcher } from '../../src/tx/blockhashFetcher/cachedBlockhashFetcher';
9
+
10
+ describe('CachedBlockhashFetcher', () => {
11
+ let connection: sinon.SinonStubbedInstance<Connection>;
12
+ let cachedBlockhashFetcher: CachedBlockhashFetcher;
13
+ const mockBlockhash: BlockhashWithExpiryBlockHeight = {
14
+ blockhash: 'mockedBlockhash',
15
+ lastValidBlockHeight: 1000,
16
+ };
17
+
18
+ beforeEach(() => {
19
+ connection = sinon.createStubInstance(Connection);
20
+ connection.getLatestBlockhash.resolves(mockBlockhash);
21
+
22
+ cachedBlockhashFetcher = new CachedBlockhashFetcher(
23
+ connection as unknown as Connection,
24
+ 'confirmed' as Commitment,
25
+ 3,
26
+ 100,
27
+ 1000
28
+ );
29
+ });
30
+
31
+ afterEach(() => {
32
+ sinon.restore();
33
+ });
34
+
35
+ it('should fetch and cache the latest blockhash', async () => {
36
+ const result = await cachedBlockhashFetcher.getLatestBlockhash();
37
+ expect(result).to.deep.equal(mockBlockhash);
38
+ expect(connection.getLatestBlockhash.calledOnce).to.be.true;
39
+ });
40
+
41
+ it('should use cached blockhash if not stale', async () => {
42
+ await cachedBlockhashFetcher.getLatestBlockhash();
43
+ await cachedBlockhashFetcher.getLatestBlockhash();
44
+ expect(connection.getLatestBlockhash.calledOnce).to.be.true;
45
+ });
46
+
47
+ it('should refresh blockhash if cache is stale', async () => {
48
+ const clock = sinon.useFakeTimers();
49
+
50
+ await cachedBlockhashFetcher.getLatestBlockhash();
51
+
52
+ // Advance time to make cache stale
53
+ clock.tick(1100);
54
+
55
+ await cachedBlockhashFetcher.getLatestBlockhash();
56
+ expect(connection.getLatestBlockhash.calledTwice).to.be.true;
57
+
58
+ clock.restore();
59
+ });
60
+
61
+ it('should retry on failure', async () => {
62
+ connection.getLatestBlockhash
63
+ .onFirstCall()
64
+ .rejects(new Error('Network error'))
65
+ .onSecondCall()
66
+ .rejects(new Error('Network error'))
67
+ .onThirdCall()
68
+ .resolves(mockBlockhash);
69
+
70
+ const result = await cachedBlockhashFetcher.getLatestBlockhash();
71
+ expect(result).to.deep.equal(mockBlockhash);
72
+ expect(connection.getLatestBlockhash.calledThrice).to.be.true;
73
+ });
74
+
75
+ it('should throw error after maximum retries', async () => {
76
+ connection.getLatestBlockhash.rejects(new Error('Network error'));
77
+
78
+ try {
79
+ await cachedBlockhashFetcher.getLatestBlockhash();
80
+ expect.fail('Should have thrown an error');
81
+ } catch (error) {
82
+ expect(error.message).to.equal(
83
+ 'Failed to fetch blockhash after maximum retries'
84
+ );
85
+ }
86
+ expect(connection.getLatestBlockhash.calledThrice).to.be.true;
87
+ });
88
+
89
+ it('should prevent concurrent requests for the same blockhash', async () => {
90
+ const promise1 = cachedBlockhashFetcher.getLatestBlockhash();
91
+ const promise2 = cachedBlockhashFetcher.getLatestBlockhash();
92
+
93
+ await Promise.all([promise1, promise2]);
94
+ expect(connection.getLatestBlockhash.calledOnce).to.be.true;
95
+ });
96
+ });