@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/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,
|
|
@@ -2770,7 +2814,7 @@ export class DriftClient {
|
|
|
2770
2814
|
}
|
|
2771
2815
|
|
|
2772
2816
|
public async sendSignedTx(
|
|
2773
|
-
tx: Transaction,
|
|
2817
|
+
tx: Transaction | VersionedTransaction,
|
|
2774
2818
|
opts?: ConfirmOptions
|
|
2775
2819
|
): Promise<TransactionSignature> {
|
|
2776
2820
|
const { txSig } = await this.sendTransaction(
|
|
@@ -4118,6 +4162,10 @@ export class DriftClient {
|
|
|
4118
4162
|
const outMarket = this.getSpotMarketAccount(outMarketIndex);
|
|
4119
4163
|
const inMarket = this.getSpotMarketAccount(inMarketIndex);
|
|
4120
4164
|
|
|
4165
|
+
const isExactOut = swapMode === 'ExactOut' || quote.swapMode === 'ExactOut';
|
|
4166
|
+
const amountIn = new BN(quote.inAmount);
|
|
4167
|
+
const exactOutBufferedAmountIn = amountIn.muln(1001).divn(1000); // Add 10bp buffer
|
|
4168
|
+
|
|
4121
4169
|
if (!quote) {
|
|
4122
4170
|
const fetchedQuote = await jupiterClient.getQuote({
|
|
4123
4171
|
inputMint: inMarket.mint,
|
|
@@ -4198,7 +4246,7 @@ export class DriftClient {
|
|
|
4198
4246
|
const { beginSwapIx, endSwapIx } = await this.getSwapIx({
|
|
4199
4247
|
outMarketIndex,
|
|
4200
4248
|
inMarketIndex,
|
|
4201
|
-
amountIn:
|
|
4249
|
+
amountIn: isExactOut ? exactOutBufferedAmountIn : amountIn,
|
|
4202
4250
|
inTokenAccount: inAssociatedTokenAccount,
|
|
4203
4251
|
outTokenAccount: outAssociatedTokenAccount,
|
|
4204
4252
|
reduceOnly,
|
|
@@ -4664,7 +4712,8 @@ export class DriftClient {
|
|
|
4664
4712
|
await TransactionParamProcessor.getTxSimComputeUnits(
|
|
4665
4713
|
placeAndTakeTxToSim,
|
|
4666
4714
|
this.connection,
|
|
4667
|
-
txParams.computeUnitsBufferMultiplier ?? 1.2
|
|
4715
|
+
txParams.computeUnitsBufferMultiplier ?? 1.2,
|
|
4716
|
+
txParams.lowerBoundCu
|
|
4668
4717
|
);
|
|
4669
4718
|
|
|
4670
4719
|
if (shouldExitIfSimulationFails && !simulationResult.success) {
|
|
@@ -4784,6 +4833,10 @@ export class DriftClient {
|
|
|
4784
4833
|
exitEarlyIfSimFails
|
|
4785
4834
|
);
|
|
4786
4835
|
|
|
4836
|
+
if (!txsToSign) {
|
|
4837
|
+
return null;
|
|
4838
|
+
}
|
|
4839
|
+
|
|
4787
4840
|
const signedTxs = (
|
|
4788
4841
|
await this.txHandler.getSignedTransactionMap(
|
|
4789
4842
|
txsToSign,
|
|
@@ -6854,11 +6907,7 @@ export class DriftClient {
|
|
|
6854
6907
|
private isVersionedTransaction(
|
|
6855
6908
|
tx: Transaction | VersionedTransaction
|
|
6856
6909
|
): boolean {
|
|
6857
|
-
|
|
6858
|
-
const isVersionedTx =
|
|
6859
|
-
tx instanceof VersionedTransaction || version !== undefined;
|
|
6860
|
-
|
|
6861
|
-
return isVersionedTx;
|
|
6910
|
+
return isVersionedTransaction(tx);
|
|
6862
6911
|
}
|
|
6863
6912
|
|
|
6864
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 =
|
|
@@ -45,6 +45,7 @@ export class EventSubscriber {
|
|
|
45
45
|
|
|
46
46
|
if (this.options.logProviderConfig.type === 'websocket') {
|
|
47
47
|
this.logProvider = new WebSocketLogProvider(
|
|
48
|
+
// @ts-ignore
|
|
48
49
|
this.connection,
|
|
49
50
|
this.address,
|
|
50
51
|
this.options.commitment,
|
|
@@ -52,6 +53,7 @@ export class EventSubscriber {
|
|
|
52
53
|
);
|
|
53
54
|
} else {
|
|
54
55
|
this.logProvider = new PollingLogProvider(
|
|
56
|
+
// @ts-ignore
|
|
55
57
|
this.connection,
|
|
56
58
|
this.address,
|
|
57
59
|
options.commitment,
|
|
@@ -101,6 +103,7 @@ export class EventSubscriber {
|
|
|
101
103
|
this.logProvider.eventEmitter.removeAllListeners('reconnect');
|
|
102
104
|
this.unsubscribe().then(() => {
|
|
103
105
|
this.logProvider = new PollingLogProvider(
|
|
106
|
+
// @ts-ignore
|
|
104
107
|
this.connection,
|
|
105
108
|
this.address,
|
|
106
109
|
this.options.commitment,
|
|
@@ -148,6 +151,7 @@ export class EventSubscriber {
|
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
const wrappedEvents = this.parseEventsFromLogs(txSig, slot, logs);
|
|
154
|
+
|
|
151
155
|
for (const wrappedEvent of wrappedEvents) {
|
|
152
156
|
this.eventListMap.get(wrappedEvent.eventType).insert(wrappedEvent);
|
|
153
157
|
}
|
|
@@ -188,6 +192,7 @@ export class EventSubscriber {
|
|
|
188
192
|
const untilTx: TransactionSignature = this.options.untilTx;
|
|
189
193
|
while (txFetched < this.options.maxTx) {
|
|
190
194
|
const response = await fetchLogs(
|
|
195
|
+
// @ts-ignore
|
|
191
196
|
this.connection,
|
|
192
197
|
this.address,
|
|
193
198
|
this.options.commitment === 'finalized' ? 'finalized' : 'confirmed',
|
package/src/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/src/testClient.ts
CHANGED
|
@@ -10,8 +10,6 @@ export class TestClient extends AdminClient {
|
|
|
10
10
|
throw new Error('Test client must be polling');
|
|
11
11
|
}
|
|
12
12
|
super(config);
|
|
13
|
-
// @ts-ignore
|
|
14
|
-
this.txHandler.blockhashCommitment = 'recent';
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
async sendTransaction(
|
|
@@ -30,6 +28,7 @@ export class TestClient extends AdminClient {
|
|
|
30
28
|
let lastFetchedSlot = (
|
|
31
29
|
this.accountSubscriber as PollingDriftClientAccountSubscriber
|
|
32
30
|
).accountLoader.mostRecentSlot;
|
|
31
|
+
await this.fetchAccounts();
|
|
33
32
|
while (lastFetchedSlot < slot) {
|
|
34
33
|
await this.fetchAccounts();
|
|
35
34
|
lastFetchedSlot = (
|
package/src/tokenFaucet.ts
CHANGED
|
@@ -2,10 +2,10 @@ import * as anchor from '@coral-xyz/anchor';
|
|
|
2
2
|
import { AnchorProvider, Idl, Program } from '@coral-xyz/anchor';
|
|
3
3
|
import {
|
|
4
4
|
TOKEN_PROGRAM_ID,
|
|
5
|
-
getAccount,
|
|
6
5
|
Account,
|
|
7
6
|
createAssociatedTokenAccountInstruction,
|
|
8
7
|
getAssociatedTokenAddress,
|
|
8
|
+
getAccount,
|
|
9
9
|
} from '@solana/spl-token';
|
|
10
10
|
import {
|
|
11
11
|
ConfirmOptions,
|
|
@@ -19,8 +19,10 @@ import {
|
|
|
19
19
|
import { BN } from '.';
|
|
20
20
|
import tokenFaucet from './idl/token_faucet.json';
|
|
21
21
|
import { IWallet } from './types';
|
|
22
|
+
import { BankrunContextWrapper } from './bankrun/bankrunConnection';
|
|
22
23
|
|
|
23
24
|
export class TokenFaucet {
|
|
25
|
+
context?: BankrunContextWrapper;
|
|
24
26
|
connection: Connection;
|
|
25
27
|
wallet: IWallet;
|
|
26
28
|
public program: Program;
|
|
@@ -33,13 +35,20 @@ export class TokenFaucet {
|
|
|
33
35
|
wallet: IWallet,
|
|
34
36
|
programId: PublicKey,
|
|
35
37
|
mint: PublicKey,
|
|
36
|
-
opts?: ConfirmOptions
|
|
38
|
+
opts?: ConfirmOptions,
|
|
39
|
+
context?: BankrunContextWrapper
|
|
37
40
|
) {
|
|
38
41
|
this.connection = connection;
|
|
42
|
+
this.context = context;
|
|
39
43
|
this.wallet = wallet;
|
|
40
44
|
this.opts = opts || AnchorProvider.defaultOptions();
|
|
41
45
|
// @ts-ignore
|
|
42
|
-
const provider = new AnchorProvider(
|
|
46
|
+
const provider = new AnchorProvider(
|
|
47
|
+
context ? context.connection.toConnection() : this.connection,
|
|
48
|
+
// @ts-ignore
|
|
49
|
+
wallet,
|
|
50
|
+
this.opts
|
|
51
|
+
);
|
|
43
52
|
this.provider = provider;
|
|
44
53
|
this.program = new Program(tokenFaucet as Idl, programId, provider);
|
|
45
54
|
this.mint = mint;
|
|
@@ -76,7 +85,7 @@ export class TokenFaucet {
|
|
|
76
85
|
public async initialize(): Promise<TransactionSignature> {
|
|
77
86
|
const [faucetConfigPublicKey] =
|
|
78
87
|
await this.getFaucetConfigPublicKeyAndNonce();
|
|
79
|
-
|
|
88
|
+
const ix = this.program.instruction.initialize({
|
|
80
89
|
accounts: {
|
|
81
90
|
faucetConfig: faucetConfigPublicKey,
|
|
82
91
|
admin: this.wallet.publicKey,
|
|
@@ -86,6 +95,9 @@ export class TokenFaucet {
|
|
|
86
95
|
tokenProgram: TOKEN_PROGRAM_ID,
|
|
87
96
|
},
|
|
88
97
|
});
|
|
98
|
+
const tx = new Transaction().add(ix);
|
|
99
|
+
const txSig = await this.context.sendTransaction(tx);
|
|
100
|
+
return txSig;
|
|
89
101
|
}
|
|
90
102
|
|
|
91
103
|
public async fetchState(): Promise<any> {
|
|
@@ -114,12 +126,29 @@ export class TokenFaucet {
|
|
|
114
126
|
|
|
115
127
|
const tx = new Transaction().add(mintIx);
|
|
116
128
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
129
|
+
if (this.context) {
|
|
130
|
+
return await this.context.sendTransaction(tx);
|
|
131
|
+
} else {
|
|
132
|
+
return await this.program.provider.sendAndConfirm(tx, [], this.opts);
|
|
133
|
+
}
|
|
120
134
|
}
|
|
121
135
|
|
|
122
136
|
public async transferMintAuthority(): Promise<TransactionSignature> {
|
|
137
|
+
if (this.context) {
|
|
138
|
+
const ix = this.program.instruction.transferMintAuthority({
|
|
139
|
+
accounts: {
|
|
140
|
+
faucetConfig: await this.getFaucetConfigPublicKey(),
|
|
141
|
+
mintAccount: this.mint,
|
|
142
|
+
mintAuthority: await this.getMintAuthority(),
|
|
143
|
+
tokenProgram: TOKEN_PROGRAM_ID,
|
|
144
|
+
admin: this.wallet.publicKey,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
const tx = new Transaction().add(ix);
|
|
148
|
+
const txSig = await this.context.sendTransaction(tx);
|
|
149
|
+
return txSig;
|
|
150
|
+
}
|
|
151
|
+
|
|
123
152
|
return await this.program.rpc.transferMintAuthority({
|
|
124
153
|
accounts: {
|
|
125
154
|
faucetConfig: await this.getFaucetConfigPublicKey(),
|
|
@@ -146,9 +175,8 @@ export class TokenFaucet {
|
|
|
146
175
|
let associatedTokenAccountExists = false;
|
|
147
176
|
|
|
148
177
|
try {
|
|
149
|
-
const assosciatedTokenAccount =
|
|
150
|
-
associatedTokenPublicKey
|
|
151
|
-
);
|
|
178
|
+
const assosciatedTokenAccount =
|
|
179
|
+
await this.context.connection.getAccountInfo(associatedTokenPublicKey);
|
|
152
180
|
|
|
153
181
|
associatedTokenAccountExists = !!assosciatedTokenAccount;
|
|
154
182
|
} catch (e) {
|
|
@@ -162,7 +190,13 @@ export class TokenFaucet {
|
|
|
162
190
|
|
|
163
191
|
tx.add(mintToTx);
|
|
164
192
|
|
|
165
|
-
|
|
193
|
+
let txSig;
|
|
194
|
+
if (this.context) {
|
|
195
|
+
txSig = await this.context.sendTransaction(tx);
|
|
196
|
+
} else {
|
|
197
|
+
txSig = await this.program.provider.sendAndConfirm(tx, [], this.opts);
|
|
198
|
+
}
|
|
199
|
+
|
|
166
200
|
return [associatedTokenPublicKey, txSig];
|
|
167
201
|
}
|
|
168
202
|
|
|
@@ -200,6 +234,9 @@ export class TokenFaucet {
|
|
|
200
234
|
userPubKey: PublicKey;
|
|
201
235
|
}): Promise<Account> {
|
|
202
236
|
const associatedKey = await this.getAssosciatedMockUSDMintAddress(props);
|
|
237
|
+
if (this.context) {
|
|
238
|
+
return await this.context.connection.getTokenAccount(associatedKey);
|
|
239
|
+
}
|
|
203
240
|
return await getAccount(this.connection, associatedKey);
|
|
204
241
|
}
|
|
205
242
|
|
|
@@ -215,7 +252,7 @@ export class TokenFaucet {
|
|
|
215
252
|
props.callback(await this.getTokenAccountInfo(props));
|
|
216
253
|
|
|
217
254
|
// Couldn't find a way to do it using anchor framework subscription, someone on serum discord recommended this way
|
|
218
|
-
this.connection.onAccountChange(
|
|
255
|
+
this.context.connection.onAccountChange(
|
|
219
256
|
tokenAccountKey,
|
|
220
257
|
async (
|
|
221
258
|
_accountInfo /* accountInfo is a buffer which we don't know how to deserialize */
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BlockhashWithExpiryBlockHeight,
|
|
3
|
+
Commitment,
|
|
4
|
+
Connection,
|
|
5
|
+
} from '@solana/web3.js';
|
|
6
|
+
import { BlockhashFetcher } from './types';
|
|
7
|
+
|
|
8
|
+
export class BaseBlockhashFetcher implements BlockhashFetcher {
|
|
9
|
+
constructor(
|
|
10
|
+
private connection: Connection,
|
|
11
|
+
private blockhashCommitment: Commitment
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
public async getLatestBlockhash(): Promise<
|
|
15
|
+
BlockhashWithExpiryBlockHeight | undefined
|
|
16
|
+
> {
|
|
17
|
+
return this.connection.getLatestBlockhash(this.blockhashCommitment);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BlockhashWithExpiryBlockHeight,
|
|
3
|
+
Commitment,
|
|
4
|
+
Connection,
|
|
5
|
+
} from '@solana/web3.js';
|
|
6
|
+
import { BlockhashFetcher } from './types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Fetches the latest blockhash and caches it for a configurable amount of time.
|
|
10
|
+
*
|
|
11
|
+
* - Prevents RPC spam by reusing cached values
|
|
12
|
+
* - Retries on failure with exponential backoff
|
|
13
|
+
* - Prevents concurrent requests for the same blockhash
|
|
14
|
+
*/
|
|
15
|
+
export class CachedBlockhashFetcher implements BlockhashFetcher {
|
|
16
|
+
private recentBlockhashCache: {
|
|
17
|
+
value: BlockhashWithExpiryBlockHeight | undefined;
|
|
18
|
+
lastUpdated: number;
|
|
19
|
+
} = { value: undefined, lastUpdated: 0 };
|
|
20
|
+
|
|
21
|
+
private blockhashFetchingPromise: Promise<void> | null = null;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
private connection: Connection,
|
|
25
|
+
private blockhashCommitment: Commitment,
|
|
26
|
+
private retryCount: number,
|
|
27
|
+
private retrySleepTimeMs: number,
|
|
28
|
+
private staleCacheTimeMs: number
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
private async fetchBlockhashWithRetry(): Promise<BlockhashWithExpiryBlockHeight> {
|
|
32
|
+
for (let i = 0; i < this.retryCount; i++) {
|
|
33
|
+
try {
|
|
34
|
+
return await this.connection.getLatestBlockhash(
|
|
35
|
+
this.blockhashCommitment
|
|
36
|
+
);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
if (i === this.retryCount - 1) {
|
|
39
|
+
throw new Error('Failed to fetch blockhash after maximum retries');
|
|
40
|
+
}
|
|
41
|
+
await this.sleep(this.retrySleepTimeMs * 2 ** i);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
throw new Error('Failed to fetch blockhash after maximum retries');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private sleep(ms: number): Promise<void> {
|
|
48
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private async updateBlockhashCache(): Promise<void> {
|
|
52
|
+
const result = await this.fetchBlockhashWithRetry();
|
|
53
|
+
this.recentBlockhashCache = {
|
|
54
|
+
value: result,
|
|
55
|
+
lastUpdated: Date.now(),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public async getLatestBlockhash(): Promise<
|
|
60
|
+
BlockhashWithExpiryBlockHeight | undefined
|
|
61
|
+
> {
|
|
62
|
+
if (this.isCacheStale()) {
|
|
63
|
+
await this.refreshBlockhash();
|
|
64
|
+
}
|
|
65
|
+
return this.recentBlockhashCache.value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private isCacheStale(): boolean {
|
|
69
|
+
const lastUpdateTime = this.recentBlockhashCache.lastUpdated;
|
|
70
|
+
return (
|
|
71
|
+
!lastUpdateTime || Date.now() > lastUpdateTime + this.staleCacheTimeMs
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Refresh the blockhash cache, await a pending refresh if it exists
|
|
77
|
+
*/
|
|
78
|
+
private async refreshBlockhash(): Promise<void> {
|
|
79
|
+
if (!this.blockhashFetchingPromise) {
|
|
80
|
+
this.blockhashFetchingPromise = this.updateBlockhashCache();
|
|
81
|
+
try {
|
|
82
|
+
await this.blockhashFetchingPromise;
|
|
83
|
+
} finally {
|
|
84
|
+
this.blockhashFetchingPromise = null;
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
await this.blockhashFetchingPromise;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|