@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/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);
|
|
@@ -2267,6 +2276,9 @@ class DriftClient {
|
|
|
2267
2276
|
async getJupiterSwapIxV6({ jupiterClient, outMarketIndex, inMarketIndex, outAssociatedTokenAccount, inAssociatedTokenAccount, amount, slippageBps, swapMode, onlyDirectRoutes, quote, reduceOnly, userAccountPublicKey, }) {
|
|
2268
2277
|
const outMarket = this.getSpotMarketAccount(outMarketIndex);
|
|
2269
2278
|
const inMarket = this.getSpotMarketAccount(inMarketIndex);
|
|
2279
|
+
const isExactOut = swapMode === 'ExactOut' || quote.swapMode === 'ExactOut';
|
|
2280
|
+
const amountIn = new anchor_1.BN(quote.inAmount);
|
|
2281
|
+
const exactOutBufferedAmountIn = amountIn.muln(1001).divn(1000); // Add 10bp buffer
|
|
2270
2282
|
if (!quote) {
|
|
2271
2283
|
const fetchedQuote = await jupiterClient.getQuote({
|
|
2272
2284
|
inputMint: inMarket.mint,
|
|
@@ -2312,7 +2324,7 @@ class DriftClient {
|
|
|
2312
2324
|
const { beginSwapIx, endSwapIx } = await this.getSwapIx({
|
|
2313
2325
|
outMarketIndex,
|
|
2314
2326
|
inMarketIndex,
|
|
2315
|
-
amountIn:
|
|
2327
|
+
amountIn: isExactOut ? exactOutBufferedAmountIn : amountIn,
|
|
2316
2328
|
inTokenAccount: inAssociatedTokenAccount,
|
|
2317
2329
|
outTokenAccount: outAssociatedTokenAccount,
|
|
2318
2330
|
reduceOnly,
|
|
@@ -2544,7 +2556,7 @@ class DriftClient {
|
|
|
2544
2556
|
};
|
|
2545
2557
|
if (shouldUseSimulationComputeUnits || shouldExitIfSimulationFails) {
|
|
2546
2558
|
const placeAndTakeTxToSim = (await this.buildTransaction(placeAndTakeIxs, txParams, undefined, undefined, true, recentBlockHash));
|
|
2547
|
-
const simulationResult = await txParamProcessor_1.TransactionParamProcessor.getTxSimComputeUnits(placeAndTakeTxToSim, this.connection, (_a = txParams.computeUnitsBufferMultiplier) !== null && _a !== void 0 ? _a : 1.2);
|
|
2559
|
+
const simulationResult = await txParamProcessor_1.TransactionParamProcessor.getTxSimComputeUnits(placeAndTakeTxToSim, this.connection, (_a = txParams.computeUnitsBufferMultiplier) !== null && _a !== void 0 ? _a : 1.2, txParams.lowerBoundCu);
|
|
2548
2560
|
if (shouldExitIfSimulationFails && !simulationResult.success) {
|
|
2549
2561
|
earlyExitFailedPlaceAndTakeSim = true;
|
|
2550
2562
|
return;
|
|
@@ -2586,6 +2598,9 @@ class DriftClient {
|
|
|
2586
2598
|
}
|
|
2587
2599
|
async placeAndTakePerpWithAdditionalOrders(orderParams, makerInfo, referrerInfo, bracketOrdersParams = new Array(), txParams, subAccountId, cancelExistingOrders, settlePnl, exitEarlyIfSimFails) {
|
|
2588
2600
|
const txsToSign = await this.preparePlaceAndTakePerpOrderWithAdditionalOrders(orderParams, makerInfo, referrerInfo, bracketOrdersParams, txParams, subAccountId, cancelExistingOrders, settlePnl, exitEarlyIfSimFails);
|
|
2601
|
+
if (!txsToSign) {
|
|
2602
|
+
return null;
|
|
2603
|
+
}
|
|
2589
2604
|
const signedTxs = (await this.txHandler.getSignedTransactionMap(txsToSign, this.provider.wallet)).signedTxMap;
|
|
2590
2605
|
const { txSig, slot } = await this.sendTransaction(signedTxs.placeAndTakeTx, [], this.opts, true);
|
|
2591
2606
|
this.perpMarketLastSlotCache.set(orderParams.marketIndex, slot);
|
|
@@ -3630,9 +3645,7 @@ class DriftClient {
|
|
|
3630
3645
|
}
|
|
3631
3646
|
}
|
|
3632
3647
|
isVersionedTransaction(tx) {
|
|
3633
|
-
|
|
3634
|
-
const isVersionedTx = tx instanceof web3_js_1.VersionedTransaction || version !== undefined;
|
|
3635
|
-
return isVersionedTx;
|
|
3648
|
+
return (0, utils_2.isVersionedTransaction)(tx);
|
|
3636
3649
|
}
|
|
3637
3650
|
sendTransaction(tx, additionalSigners, opts, preSigned) {
|
|
3638
3651
|
const isVersionedTx = this.isVersionedTransaction(tx);
|
|
@@ -4,7 +4,7 @@ import { OracleInfo } from './oracles/types';
|
|
|
4
4
|
import { BulkAccountLoader } from './accounts/bulkAccountLoader';
|
|
5
5
|
import { DriftEnv } from './config';
|
|
6
6
|
import { TxSender } from './tx/types';
|
|
7
|
-
import { TxHandler } from './tx/txHandler';
|
|
7
|
+
import { TxHandler, TxHandlerConfig } from './tx/txHandler';
|
|
8
8
|
export type DriftClientConfig = {
|
|
9
9
|
connection: Connection;
|
|
10
10
|
wallet: IWallet;
|
|
@@ -28,6 +28,7 @@ export type DriftClientConfig = {
|
|
|
28
28
|
txVersion?: TransactionVersion;
|
|
29
29
|
txParams?: TxParams;
|
|
30
30
|
enableMetricsEvents?: boolean;
|
|
31
|
+
txHandlerConfig?: TxHandlerConfig;
|
|
31
32
|
};
|
|
32
33
|
export type DriftClientSubscriptionConfig = {
|
|
33
34
|
type: 'websocket';
|
|
@@ -24,10 +24,14 @@ class EventSubscriber {
|
|
|
24
24
|
this.eventListMap = new Map();
|
|
25
25
|
this.eventEmitter = new events_1.EventEmitter();
|
|
26
26
|
if (this.options.logProviderConfig.type === 'websocket') {
|
|
27
|
-
this.logProvider = new webSocketLogProvider_1.WebSocketLogProvider(
|
|
27
|
+
this.logProvider = new webSocketLogProvider_1.WebSocketLogProvider(
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
this.connection, this.address, this.options.commitment, this.options.logProviderConfig.resubTimeoutMs);
|
|
28
30
|
}
|
|
29
31
|
else {
|
|
30
|
-
this.logProvider = new pollingLogProvider_1.PollingLogProvider(
|
|
32
|
+
this.logProvider = new pollingLogProvider_1.PollingLogProvider(
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
this.connection, this.address, options.commitment, this.options.logProviderConfig.frequency, this.options.logProviderConfig.batchSize);
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
populateInitialEventListMap() {
|
|
@@ -52,7 +56,9 @@ class EventSubscriber {
|
|
|
52
56
|
console.log('Failing over to polling');
|
|
53
57
|
this.logProvider.eventEmitter.removeAllListeners('reconnect');
|
|
54
58
|
this.unsubscribe().then(() => {
|
|
55
|
-
this.logProvider = new pollingLogProvider_1.PollingLogProvider(
|
|
59
|
+
this.logProvider = new pollingLogProvider_1.PollingLogProvider(
|
|
60
|
+
// @ts-ignore
|
|
61
|
+
this.connection, this.address, this.options.commitment, logProviderConfig.fallbackFrequency, logProviderConfig.fallbackBatchSize);
|
|
56
62
|
this.logProvider.subscribe((txSig, slot, logs, mostRecentBlockTime) => {
|
|
57
63
|
this.handleTxLogs(txSig, slot, logs, mostRecentBlockTime);
|
|
58
64
|
}, true);
|
|
@@ -108,7 +114,9 @@ class EventSubscriber {
|
|
|
108
114
|
let beforeTx = undefined;
|
|
109
115
|
const untilTx = this.options.untilTx;
|
|
110
116
|
while (txFetched < this.options.maxTx) {
|
|
111
|
-
const response = await (0, fetchLogs_1.fetchLogs)(
|
|
117
|
+
const response = await (0, fetchLogs_1.fetchLogs)(
|
|
118
|
+
// @ts-ignore
|
|
119
|
+
this.connection, this.address, this.options.commitment === 'finalized' ? 'finalized' : 'confirmed', beforeTx, untilTx);
|
|
112
120
|
if (response === undefined) {
|
|
113
121
|
break;
|
|
114
122
|
}
|
package/lib/idl/drift.json
CHANGED
|
@@ -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": "
|
|
6202
|
-
"
|
|
6203
|
-
"
|
|
6204
|
-
|
|
6205
|
-
|
|
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
|
-
|
|
6263
|
+
47
|
|
6249
6264
|
]
|
|
6250
6265
|
}
|
|
6251
6266
|
}
|
|
@@ -12009,6 +12024,11 @@
|
|
|
12009
12024
|
"code": 6267,
|
|
12010
12025
|
"name": "UnableToParsePullOracleMessage",
|
|
12011
12026
|
"msg": "Unable to parse pull oracle message"
|
|
12027
|
+
},
|
|
12028
|
+
{
|
|
12029
|
+
"code": 6268,
|
|
12030
|
+
"name": "MaxBorrows",
|
|
12031
|
+
"msg": "Can not borow more than max borrows"
|
|
12012
12032
|
}
|
|
12013
12033
|
]
|
|
12014
12034
|
}
|
package/lib/testClient.js
CHANGED
|
@@ -8,12 +8,11 @@ class TestClient extends adminClient_1.AdminClient {
|
|
|
8
8
|
throw new Error('Test client must be polling');
|
|
9
9
|
}
|
|
10
10
|
super(config);
|
|
11
|
-
// @ts-ignore
|
|
12
|
-
this.txHandler.blockhashCommitment = 'recent';
|
|
13
11
|
}
|
|
14
12
|
async sendTransaction(tx, additionalSigners, opts, preSigned) {
|
|
15
13
|
const { txSig, slot } = await super.sendTransaction(tx, additionalSigners, opts, preSigned);
|
|
16
14
|
let lastFetchedSlot = this.accountSubscriber.accountLoader.mostRecentSlot;
|
|
15
|
+
await this.fetchAccounts();
|
|
17
16
|
while (lastFetchedSlot < slot) {
|
|
18
17
|
await this.fetchAccounts();
|
|
19
18
|
lastFetchedSlot = this.accountSubscriber.accountLoader.mostRecentSlot;
|
package/lib/tokenFaucet.d.ts
CHANGED
|
@@ -5,14 +5,16 @@ import { Account } from '@solana/spl-token';
|
|
|
5
5
|
import { ConfirmOptions, Connection, PublicKey, TransactionInstruction, TransactionSignature } from '@solana/web3.js';
|
|
6
6
|
import { BN } from '.';
|
|
7
7
|
import { IWallet } from './types';
|
|
8
|
+
import { BankrunContextWrapper } from './bankrun/bankrunConnection';
|
|
8
9
|
export declare class TokenFaucet {
|
|
10
|
+
context?: BankrunContextWrapper;
|
|
9
11
|
connection: Connection;
|
|
10
12
|
wallet: IWallet;
|
|
11
13
|
program: Program;
|
|
12
14
|
provider: AnchorProvider;
|
|
13
15
|
mint: PublicKey;
|
|
14
16
|
opts?: ConfirmOptions;
|
|
15
|
-
constructor(connection: Connection, wallet: IWallet, programId: PublicKey, mint: PublicKey, opts?: ConfirmOptions);
|
|
17
|
+
constructor(connection: Connection, wallet: IWallet, programId: PublicKey, mint: PublicKey, opts?: ConfirmOptions, context?: BankrunContextWrapper);
|
|
16
18
|
getFaucetConfigPublicKeyAndNonce(): Promise<[
|
|
17
19
|
PublicKey,
|
|
18
20
|
number
|
package/lib/tokenFaucet.js
CHANGED
|
@@ -33,12 +33,15 @@ const spl_token_1 = require("@solana/spl-token");
|
|
|
33
33
|
const web3_js_1 = require("@solana/web3.js");
|
|
34
34
|
const token_faucet_json_1 = __importDefault(require("./idl/token_faucet.json"));
|
|
35
35
|
class TokenFaucet {
|
|
36
|
-
constructor(connection, wallet, programId, mint, opts) {
|
|
36
|
+
constructor(connection, wallet, programId, mint, opts, context) {
|
|
37
37
|
this.connection = connection;
|
|
38
|
+
this.context = context;
|
|
38
39
|
this.wallet = wallet;
|
|
39
40
|
this.opts = opts || anchor_1.AnchorProvider.defaultOptions();
|
|
40
41
|
// @ts-ignore
|
|
41
|
-
const provider = new anchor_1.AnchorProvider(connection
|
|
42
|
+
const provider = new anchor_1.AnchorProvider(context ? context.connection.toConnection() : this.connection,
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
wallet, this.opts);
|
|
42
45
|
this.provider = provider;
|
|
43
46
|
this.program = new anchor_1.Program(token_faucet_json_1.default, programId, provider);
|
|
44
47
|
this.mint = mint;
|
|
@@ -60,7 +63,7 @@ class TokenFaucet {
|
|
|
60
63
|
}
|
|
61
64
|
async initialize() {
|
|
62
65
|
const [faucetConfigPublicKey] = await this.getFaucetConfigPublicKeyAndNonce();
|
|
63
|
-
|
|
66
|
+
const ix = this.program.instruction.initialize({
|
|
64
67
|
accounts: {
|
|
65
68
|
faucetConfig: faucetConfigPublicKey,
|
|
66
69
|
admin: this.wallet.publicKey,
|
|
@@ -70,6 +73,9 @@ class TokenFaucet {
|
|
|
70
73
|
tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
|
|
71
74
|
},
|
|
72
75
|
});
|
|
76
|
+
const tx = new web3_js_1.Transaction().add(ix);
|
|
77
|
+
const txSig = await this.context.sendTransaction(tx);
|
|
78
|
+
return txSig;
|
|
73
79
|
}
|
|
74
80
|
async fetchState() {
|
|
75
81
|
return await this.program.account.faucetConfig.fetch(await this.getFaucetConfigPublicKey());
|
|
@@ -88,10 +94,28 @@ class TokenFaucet {
|
|
|
88
94
|
async mintToUser(userTokenAccount, amount) {
|
|
89
95
|
const mintIx = await this.mintToUserIx(userTokenAccount, amount);
|
|
90
96
|
const tx = new web3_js_1.Transaction().add(mintIx);
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
if (this.context) {
|
|
98
|
+
return await this.context.sendTransaction(tx);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
return await this.program.provider.sendAndConfirm(tx, [], this.opts);
|
|
102
|
+
}
|
|
93
103
|
}
|
|
94
104
|
async transferMintAuthority() {
|
|
105
|
+
if (this.context) {
|
|
106
|
+
const ix = this.program.instruction.transferMintAuthority({
|
|
107
|
+
accounts: {
|
|
108
|
+
faucetConfig: await this.getFaucetConfigPublicKey(),
|
|
109
|
+
mintAccount: this.mint,
|
|
110
|
+
mintAuthority: await this.getMintAuthority(),
|
|
111
|
+
tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
|
|
112
|
+
admin: this.wallet.publicKey,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
const tx = new web3_js_1.Transaction().add(ix);
|
|
116
|
+
const txSig = await this.context.sendTransaction(tx);
|
|
117
|
+
return txSig;
|
|
118
|
+
}
|
|
95
119
|
return await this.program.rpc.transferMintAuthority({
|
|
96
120
|
accounts: {
|
|
97
121
|
faucetConfig: await this.getFaucetConfigPublicKey(),
|
|
@@ -107,7 +131,7 @@ class TokenFaucet {
|
|
|
107
131
|
const [associatedTokenPublicKey, createAssociatedAccountIx, mintToTx] = await this.createAssociatedTokenAccountAndMintToInstructions(userPublicKey, amount);
|
|
108
132
|
let associatedTokenAccountExists = false;
|
|
109
133
|
try {
|
|
110
|
-
const assosciatedTokenAccount = await this.connection.getAccountInfo(associatedTokenPublicKey);
|
|
134
|
+
const assosciatedTokenAccount = await this.context.connection.getAccountInfo(associatedTokenPublicKey);
|
|
111
135
|
associatedTokenAccountExists = !!assosciatedTokenAccount;
|
|
112
136
|
}
|
|
113
137
|
catch (e) {
|
|
@@ -118,7 +142,13 @@ class TokenFaucet {
|
|
|
118
142
|
if (!skipAccountCreation)
|
|
119
143
|
tx.add(createAssociatedAccountIx);
|
|
120
144
|
tx.add(mintToTx);
|
|
121
|
-
|
|
145
|
+
let txSig;
|
|
146
|
+
if (this.context) {
|
|
147
|
+
txSig = await this.context.sendTransaction(tx);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
txSig = await this.program.provider.sendAndConfirm(tx, [], this.opts);
|
|
151
|
+
}
|
|
122
152
|
return [associatedTokenPublicKey, txSig];
|
|
123
153
|
}
|
|
124
154
|
async createAssociatedTokenAccountAndMintToInstructions(userPublicKey, amount) {
|
|
@@ -134,6 +164,9 @@ class TokenFaucet {
|
|
|
134
164
|
}
|
|
135
165
|
async getTokenAccountInfo(props) {
|
|
136
166
|
const associatedKey = await this.getAssosciatedMockUSDMintAddress(props);
|
|
167
|
+
if (this.context) {
|
|
168
|
+
return await this.context.connection.getTokenAccount(associatedKey);
|
|
169
|
+
}
|
|
137
170
|
return await (0, spl_token_1.getAccount)(this.connection, associatedKey);
|
|
138
171
|
}
|
|
139
172
|
async subscribeToTokenAccount(props) {
|
|
@@ -141,7 +174,7 @@ class TokenFaucet {
|
|
|
141
174
|
const tokenAccountKey = await this.getAssosciatedMockUSDMintAddress(props);
|
|
142
175
|
props.callback(await this.getTokenAccountInfo(props));
|
|
143
176
|
// Couldn't find a way to do it using anchor framework subscription, someone on serum discord recommended this way
|
|
144
|
-
this.connection.onAccountChange(tokenAccountKey, async (_accountInfo /* accountInfo is a buffer which we don't know how to deserialize */) => {
|
|
177
|
+
this.context.connection.onAccountChange(tokenAccountKey, async (_accountInfo /* accountInfo is a buffer which we don't know how to deserialize */) => {
|
|
145
178
|
props.callback(await this.getTokenAccountInfo(props));
|
|
146
179
|
});
|
|
147
180
|
return true;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BlockhashWithExpiryBlockHeight, Commitment, Connection } from '@solana/web3.js';
|
|
2
|
+
import { BlockhashFetcher } from './types';
|
|
3
|
+
export declare class BaseBlockhashFetcher implements BlockhashFetcher {
|
|
4
|
+
private connection;
|
|
5
|
+
private blockhashCommitment;
|
|
6
|
+
constructor(connection: Connection, blockhashCommitment: Commitment);
|
|
7
|
+
getLatestBlockhash(): Promise<BlockhashWithExpiryBlockHeight | undefined>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BaseBlockhashFetcher = void 0;
|
|
4
|
+
class BaseBlockhashFetcher {
|
|
5
|
+
constructor(connection, blockhashCommitment) {
|
|
6
|
+
this.connection = connection;
|
|
7
|
+
this.blockhashCommitment = blockhashCommitment;
|
|
8
|
+
}
|
|
9
|
+
async getLatestBlockhash() {
|
|
10
|
+
return this.connection.getLatestBlockhash(this.blockhashCommitment);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.BaseBlockhashFetcher = BaseBlockhashFetcher;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { BlockhashWithExpiryBlockHeight, Commitment, Connection } from '@solana/web3.js';
|
|
2
|
+
import { BlockhashFetcher } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Fetches the latest blockhash and caches it for a configurable amount of time.
|
|
5
|
+
*
|
|
6
|
+
* - Prevents RPC spam by reusing cached values
|
|
7
|
+
* - Retries on failure with exponential backoff
|
|
8
|
+
* - Prevents concurrent requests for the same blockhash
|
|
9
|
+
*/
|
|
10
|
+
export declare class CachedBlockhashFetcher implements BlockhashFetcher {
|
|
11
|
+
private connection;
|
|
12
|
+
private blockhashCommitment;
|
|
13
|
+
private retryCount;
|
|
14
|
+
private retrySleepTimeMs;
|
|
15
|
+
private staleCacheTimeMs;
|
|
16
|
+
private recentBlockhashCache;
|
|
17
|
+
private blockhashFetchingPromise;
|
|
18
|
+
constructor(connection: Connection, blockhashCommitment: Commitment, retryCount: number, retrySleepTimeMs: number, staleCacheTimeMs: number);
|
|
19
|
+
private fetchBlockhashWithRetry;
|
|
20
|
+
private sleep;
|
|
21
|
+
private updateBlockhashCache;
|
|
22
|
+
getLatestBlockhash(): Promise<BlockhashWithExpiryBlockHeight | undefined>;
|
|
23
|
+
private isCacheStale;
|
|
24
|
+
/**
|
|
25
|
+
* Refresh the blockhash cache, await a pending refresh if it exists
|
|
26
|
+
*/
|
|
27
|
+
private refreshBlockhash;
|
|
28
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CachedBlockhashFetcher = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Fetches the latest blockhash and caches it for a configurable amount of time.
|
|
6
|
+
*
|
|
7
|
+
* - Prevents RPC spam by reusing cached values
|
|
8
|
+
* - Retries on failure with exponential backoff
|
|
9
|
+
* - Prevents concurrent requests for the same blockhash
|
|
10
|
+
*/
|
|
11
|
+
class CachedBlockhashFetcher {
|
|
12
|
+
constructor(connection, blockhashCommitment, retryCount, retrySleepTimeMs, staleCacheTimeMs) {
|
|
13
|
+
this.connection = connection;
|
|
14
|
+
this.blockhashCommitment = blockhashCommitment;
|
|
15
|
+
this.retryCount = retryCount;
|
|
16
|
+
this.retrySleepTimeMs = retrySleepTimeMs;
|
|
17
|
+
this.staleCacheTimeMs = staleCacheTimeMs;
|
|
18
|
+
this.recentBlockhashCache = { value: undefined, lastUpdated: 0 };
|
|
19
|
+
this.blockhashFetchingPromise = null;
|
|
20
|
+
}
|
|
21
|
+
async fetchBlockhashWithRetry() {
|
|
22
|
+
for (let i = 0; i < this.retryCount; i++) {
|
|
23
|
+
try {
|
|
24
|
+
return await this.connection.getLatestBlockhash(this.blockhashCommitment);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
if (i === this.retryCount - 1) {
|
|
28
|
+
throw new Error('Failed to fetch blockhash after maximum retries');
|
|
29
|
+
}
|
|
30
|
+
await this.sleep(this.retrySleepTimeMs * 2 ** i);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
throw new Error('Failed to fetch blockhash after maximum retries');
|
|
34
|
+
}
|
|
35
|
+
sleep(ms) {
|
|
36
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
37
|
+
}
|
|
38
|
+
async updateBlockhashCache() {
|
|
39
|
+
const result = await this.fetchBlockhashWithRetry();
|
|
40
|
+
this.recentBlockhashCache = {
|
|
41
|
+
value: result,
|
|
42
|
+
lastUpdated: Date.now(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async getLatestBlockhash() {
|
|
46
|
+
if (this.isCacheStale()) {
|
|
47
|
+
await this.refreshBlockhash();
|
|
48
|
+
}
|
|
49
|
+
return this.recentBlockhashCache.value;
|
|
50
|
+
}
|
|
51
|
+
isCacheStale() {
|
|
52
|
+
const lastUpdateTime = this.recentBlockhashCache.lastUpdated;
|
|
53
|
+
return (!lastUpdateTime || Date.now() > lastUpdateTime + this.staleCacheTimeMs);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Refresh the blockhash cache, await a pending refresh if it exists
|
|
57
|
+
*/
|
|
58
|
+
async refreshBlockhash() {
|
|
59
|
+
if (!this.blockhashFetchingPromise) {
|
|
60
|
+
this.blockhashFetchingPromise = this.updateBlockhashCache();
|
|
61
|
+
try {
|
|
62
|
+
await this.blockhashFetchingPromise;
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
this.blockhashFetchingPromise = null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
await this.blockhashFetchingPromise;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.CachedBlockhashFetcher = CachedBlockhashFetcher;
|
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);
|