@drift-labs/sdk 2.85.0-beta.10 → 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/driftClient.d.ts +2 -0
- package/lib/driftClient.js +37 -30
- package/lib/driftClientConfig.d.ts +2 -1
- 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/utils.d.ts +2 -0
- package/lib/tx/utils.js +10 -0
- package/package.json +2 -1
- package/src/driftClient.ts +79 -39
- package/src/driftClientConfig.ts +2 -1
- 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/utils.ts +11 -0
- package/tests/tx/cachedBlockhashFetcher.test.ts +96 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.85.0-beta.
|
|
1
|
+
2.85.0-beta.11
|
package/bun.lockb
CHANGED
|
Binary file
|
package/lib/driftClient.d.ts
CHANGED
|
@@ -221,6 +221,7 @@ export declare class DriftClient {
|
|
|
221
221
|
*/
|
|
222
222
|
getAssociatedTokenAccount(marketIndex: number, useNative?: boolean): Promise<PublicKey>;
|
|
223
223
|
createAssociatedTokenAccountIdempotentInstruction(account: PublicKey, payer: PublicKey, owner: PublicKey, mint: PublicKey): TransactionInstruction;
|
|
224
|
+
createDepositTxn(amount: BN, marketIndex: number, associatedTokenAccount: PublicKey, subAccountId?: number, reduceOnly?: boolean, txParams?: TxParams): Promise<ReturnType<typeof this.buildTransaction>>;
|
|
224
225
|
/**
|
|
225
226
|
* Deposit funds into the given spot market
|
|
226
227
|
*
|
|
@@ -240,6 +241,7 @@ export declare class DriftClient {
|
|
|
240
241
|
pubkey: PublicKey;
|
|
241
242
|
}>;
|
|
242
243
|
getAssociatedTokenAccountCreationIx(tokenMintAddress: PublicKey, associatedTokenAddress: PublicKey): anchor.web3.TransactionInstruction;
|
|
244
|
+
createInitializeUserAccountAndDepositCollateral(amount: BN, userTokenAccount: PublicKey, marketIndex?: number, subAccountId?: number, name?: string, fromSubAccountId?: number, referrerInfo?: ReferrerInfo, donateAmount?: BN, txParams?: TxParams, customMaxMarginRatio?: number): Promise<[Transaction | VersionedTransaction, PublicKey]>;
|
|
243
245
|
/**
|
|
244
246
|
* Creates the User account for a user, and deposits some initial collateral
|
|
245
247
|
* @param amount
|
package/lib/driftClient.js
CHANGED
|
@@ -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
|
-
|
|
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);
|
|
@@ -3636,9 +3645,7 @@ class DriftClient {
|
|
|
3636
3645
|
}
|
|
3637
3646
|
}
|
|
3638
3647
|
isVersionedTransaction(tx) {
|
|
3639
|
-
|
|
3640
|
-
const isVersionedTx = tx instanceof web3_js_1.VersionedTransaction || version !== undefined;
|
|
3641
|
-
return isVersionedTx;
|
|
3648
|
+
return (0, utils_2.isVersionedTransaction)(tx);
|
|
3642
3649
|
}
|
|
3643
3650
|
sendTransaction(tx, additionalSigners, opts, preSigned) {
|
|
3644
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';
|
|
@@ -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;
|
package/lib/tx/txHandler.d.ts
CHANGED
|
@@ -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;
|
package/lib/tx/txHandler.js
CHANGED
|
@@ -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
|
-
(
|
|
34
|
-
this.onSignedCb = (
|
|
35
|
-
this.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.
|
|
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 (
|
|
92
|
+
return (0, utils_1.isVersionedTransaction)(tx);
|
|
84
93
|
}
|
|
85
94
|
isLegacyTransaction(tx) {
|
|
86
95
|
return !this.isVersionedTransaction(tx);
|
package/lib/tx/utils.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isVersionedTransaction = void 0;
|
|
4
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
5
|
+
const isVersionedTransaction = (tx) => {
|
|
6
|
+
const version = tx === null || tx === void 0 ? void 0 : tx.version;
|
|
7
|
+
const isVersionedTx = tx instanceof web3_js_1.VersionedTransaction || version !== undefined;
|
|
8
|
+
return isVersionedTx;
|
|
9
|
+
};
|
|
10
|
+
exports.isVersionedTransaction = isVersionedTransaction;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drift-labs/sdk",
|
|
3
|
-
"version": "2.85.0-beta.
|
|
3
|
+
"version": "2.85.0-beta.11",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"types": "lib/index.d.ts",
|
|
6
6
|
"author": "crispheaney",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"mocha": "^10.0.0",
|
|
66
66
|
"object-sizeof": "^2.6.3",
|
|
67
67
|
"prettier": "3.0.1",
|
|
68
|
+
"sinon": "^18.0.0",
|
|
68
69
|
"ts-node": "^10.8.0",
|
|
69
70
|
"typescript": "^4.9.5"
|
|
70
71
|
},
|
package/src/driftClient.ts
CHANGED
|
@@ -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<
|
|
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
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
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<[
|
|
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,
|
|
@@ -6863,11 +6907,7 @@ export class DriftClient {
|
|
|
6863
6907
|
private isVersionedTransaction(
|
|
6864
6908
|
tx: Transaction | VersionedTransaction
|
|
6865
6909
|
): boolean {
|
|
6866
|
-
|
|
6867
|
-
const isVersionedTx =
|
|
6868
|
-
tx instanceof VersionedTransaction || version !== undefined;
|
|
6869
|
-
|
|
6870
|
-
return isVersionedTx;
|
|
6910
|
+
return isVersionedTransaction(tx);
|
|
6871
6911
|
}
|
|
6872
6912
|
|
|
6873
6913
|
sendTransaction(
|
package/src/driftClientConfig.ts
CHANGED
|
@@ -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 =
|
|
@@ -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
|
+
}
|
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) {
|
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
|
+
};
|
|
@@ -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
|
+
});
|