@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.
- package/VERSION +1 -1
- package/bun.lockb +0 -0
- package/lib/accounts/bulkAccountLoader.d.ts +3 -3
- package/lib/accounts/pollingDriftClientAccountSubscriber.js +10 -2
- package/lib/accounts/pollingUserAccountSubscriber.d.ts +4 -3
- package/lib/accounts/pollingUserAccountSubscriber.js +7 -6
- package/lib/accounts/testBulkAccountLoader.d.ts +4 -0
- package/lib/accounts/testBulkAccountLoader.js +45 -0
- package/lib/bankrun/bankrunConnection.d.ts +71 -0
- package/lib/bankrun/bankrunConnection.js +285 -0
- package/lib/blockhashSubscriber/BlockhashSubscriber.js +22 -15
- package/lib/constants/perpMarkets.js +10 -0
- package/lib/constants/spotMarkets.js +10 -0
- package/lib/driftClient.d.ts +3 -1
- package/lib/driftClient.js +45 -32
- package/lib/driftClientConfig.d.ts +2 -1
- package/lib/events/eventSubscriber.js +12 -4
- package/lib/idl/drift.json +28 -8
- package/lib/testClient.js +1 -2
- package/lib/tokenFaucet.d.ts +3 -1
- package/lib/tokenFaucet.js +41 -8
- package/lib/tx/blockhashFetcher/baseBlockhashFetcher.d.ts +8 -0
- package/lib/tx/blockhashFetcher/baseBlockhashFetcher.js +13 -0
- package/lib/tx/blockhashFetcher/cachedBlockhashFetcher.d.ts +28 -0
- package/lib/tx/blockhashFetcher/cachedBlockhashFetcher.js +73 -0
- package/lib/tx/blockhashFetcher/types.d.ts +4 -0
- package/lib/tx/blockhashFetcher/types.js +2 -0
- package/lib/tx/txHandler.d.ts +10 -0
- package/lib/tx/txHandler.js +16 -7
- package/lib/tx/txParamProcessor.d.ts +2 -1
- package/lib/tx/txParamProcessor.js +7 -3
- package/lib/tx/utils.d.ts +2 -0
- package/lib/tx/utils.js +10 -0
- package/lib/types.d.ts +1 -0
- package/lib/user.js +1 -1
- package/package.json +4 -1
- package/src/accounts/bulkAccountLoader.ts +3 -2
- package/src/accounts/pollingDriftClientAccountSubscriber.ts +16 -3
- package/src/accounts/pollingUserAccountSubscriber.ts +13 -12
- package/src/accounts/testBulkAccountLoader.ts +53 -0
- package/src/bankrun/bankrunConnection.ts +466 -0
- package/src/blockhashSubscriber/BlockhashSubscriber.ts +24 -19
- package/src/constants/perpMarkets.ts +10 -0
- package/src/constants/spotMarkets.ts +10 -0
- package/src/driftClient.ts +91 -42
- package/src/driftClientConfig.ts +2 -1
- package/src/events/eventSubscriber.ts +5 -0
- package/src/idl/drift.json +28 -8
- package/src/testClient.ts +1 -2
- package/src/tokenFaucet.ts +49 -12
- package/src/tx/blockhashFetcher/baseBlockhashFetcher.ts +19 -0
- package/src/tx/blockhashFetcher/cachedBlockhashFetcher.ts +90 -0
- package/src/tx/blockhashFetcher/types.ts +5 -0
- package/src/tx/txHandler.ts +38 -4
- package/src/tx/txParamProcessor.ts +11 -2
- package/src/tx/utils.ts +11 -0
- package/src/types.ts +1 -0
- package/src/user.ts +5 -2
- package/tests/tx/cachedBlockhashFetcher.test.ts +96 -0
package/src/tx/txHandler.ts
CHANGED
|
@@ -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.
|
|
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(
|
|
161
|
-
|
|
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
|
-
|
|
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,
|
package/src/tx/utils.ts
ADDED
|
@@ -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.
|
|
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
|
+
});
|