@drift-labs/sdk 2.85.0-beta.4 → 2.85.0-beta.5

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.
@@ -0,0 +1,466 @@
1
+ import {
2
+ TransactionConfirmationStatus,
3
+ AccountInfo,
4
+ Keypair,
5
+ PublicKey,
6
+ Transaction,
7
+ RpcResponseAndContext,
8
+ Commitment,
9
+ TransactionSignature,
10
+ SignatureStatusConfig,
11
+ SignatureStatus,
12
+ GetVersionedTransactionConfig,
13
+ GetTransactionConfig,
14
+ VersionedTransaction,
15
+ SimulateTransactionConfig,
16
+ SimulatedTransactionResponse,
17
+ TransactionReturnData,
18
+ TransactionError,
19
+ SignatureResultCallback,
20
+ ClientSubscriptionId,
21
+ Connection as SolanaConnection,
22
+ SystemProgram,
23
+ Blockhash,
24
+ LogsFilter,
25
+ LogsCallback,
26
+ AccountChangeCallback,
27
+ LAMPORTS_PER_SOL,
28
+ } from '@solana/web3.js';
29
+ import {
30
+ ProgramTestContext,
31
+ BanksClient,
32
+ BanksTransactionResultWithMeta,
33
+ Clock,
34
+ } from 'solana-bankrun';
35
+ import { BankrunProvider } from 'anchor-bankrun';
36
+ import bs58 from 'bs58';
37
+ import { BN, Wallet } from '@coral-xyz/anchor';
38
+ import { Account, TOKEN_PROGRAM_ID, unpackAccount } from '@solana/spl-token';
39
+
40
+ export type Connection = SolanaConnection | BankrunConnection;
41
+
42
+ type BankrunTransactionMetaNormalized = {
43
+ logMessages: string[];
44
+ err: TransactionError;
45
+ };
46
+
47
+ type BankrunTransactionRespose = {
48
+ slot: number;
49
+ meta: BankrunTransactionMetaNormalized;
50
+ };
51
+
52
+ export class BankrunContextWrapper {
53
+ public readonly connection: BankrunConnection;
54
+ public readonly context: ProgramTestContext;
55
+ public readonly provider: BankrunProvider;
56
+ public readonly commitment: Commitment = 'confirmed';
57
+
58
+ constructor(context: ProgramTestContext) {
59
+ this.context = context;
60
+ this.provider = new BankrunProvider(context);
61
+ this.connection = new BankrunConnection(
62
+ this.context.banksClient,
63
+ this.context
64
+ );
65
+ }
66
+
67
+ async sendTransaction(
68
+ tx: Transaction,
69
+ additionalSigners?: Keypair[]
70
+ ): Promise<TransactionSignature> {
71
+ tx.recentBlockhash = (await this.getLatestBlockhash()).toString();
72
+ tx.feePayer = this.context.payer.publicKey;
73
+ if (!additionalSigners) {
74
+ additionalSigners = [];
75
+ }
76
+ tx.sign(this.context.payer, ...additionalSigners);
77
+ return await this.connection.sendTransaction(tx);
78
+ }
79
+
80
+ async getMinimumBalanceForRentExemption(_: number): Promise<number> {
81
+ return 10 * LAMPORTS_PER_SOL;
82
+ }
83
+
84
+ async fundKeypair(
85
+ keypair: Keypair | Wallet,
86
+ lamports: number | bigint
87
+ ): Promise<TransactionSignature> {
88
+ const ixs = [
89
+ SystemProgram.transfer({
90
+ fromPubkey: this.context.payer.publicKey,
91
+ toPubkey: keypair.publicKey,
92
+ lamports,
93
+ }),
94
+ ];
95
+ const tx = new Transaction().add(...ixs);
96
+ return await this.sendTransaction(tx);
97
+ }
98
+
99
+ async getLatestBlockhash(): Promise<Blockhash> {
100
+ const blockhash = await this.connection.getLatestBlockhash('finalized');
101
+
102
+ return blockhash.blockhash;
103
+ }
104
+
105
+ printTxLogs(signature: string): void {
106
+ this.connection.printTxLogs(signature);
107
+ }
108
+
109
+ async moveTimeForward(increment: number): Promise<void> {
110
+ const currentClock = await this.context.banksClient.getClock();
111
+ const newUnixTimestamp = currentClock.unixTimestamp + BigInt(increment);
112
+ const newClock = new Clock(
113
+ currentClock.slot,
114
+ currentClock.epochStartTimestamp,
115
+ currentClock.epoch,
116
+ currentClock.leaderScheduleEpoch,
117
+ newUnixTimestamp
118
+ );
119
+ await this.context.setClock(newClock);
120
+ }
121
+ }
122
+
123
+ export class BankrunConnection {
124
+ private readonly _banksClient: BanksClient;
125
+ private readonly context: ProgramTestContext;
126
+ private transactionToMeta: Map<
127
+ TransactionSignature,
128
+ BanksTransactionResultWithMeta
129
+ > = new Map();
130
+ private clock: Clock;
131
+
132
+ private nextClientSubscriptionId = 0;
133
+ private onLogCallbacks = new Map<number, LogsCallback>();
134
+ private onAccountChangeCallbacks = new Map<
135
+ number,
136
+ [PublicKey, AccountChangeCallback]
137
+ >();
138
+
139
+ constructor(banksClient: BanksClient, context: ProgramTestContext) {
140
+ this._banksClient = banksClient;
141
+ this.context = context;
142
+ }
143
+
144
+ getSlot(): Promise<bigint> {
145
+ return this._banksClient.getSlot();
146
+ }
147
+
148
+ toConnection(): SolanaConnection {
149
+ return this as unknown as SolanaConnection;
150
+ }
151
+
152
+ async getTokenAccount(publicKey: PublicKey): Promise<Account> {
153
+ const info = await this.getAccountInfo(publicKey);
154
+ return unpackAccount(publicKey, info, TOKEN_PROGRAM_ID);
155
+ }
156
+
157
+ async getMultipleAccountsInfo(
158
+ publicKeys: PublicKey[],
159
+ _commitmentOrConfig?: Commitment
160
+ ): Promise<AccountInfo<Buffer>[]> {
161
+ const accountInfos = [];
162
+
163
+ for (const publicKey of publicKeys) {
164
+ const accountInfo = await this.getAccountInfo(publicKey);
165
+ accountInfos.push(accountInfo);
166
+ }
167
+
168
+ return accountInfos;
169
+ }
170
+
171
+ async getAccountInfo(
172
+ publicKey: PublicKey
173
+ ): Promise<null | AccountInfo<Buffer>> {
174
+ const parsedAccountInfo = await this.getParsedAccountInfo(publicKey);
175
+ return parsedAccountInfo ? parsedAccountInfo.value : null;
176
+ }
177
+
178
+ async getAccountInfoAndContext(
179
+ publicKey: PublicKey,
180
+ _commitment?: Commitment
181
+ ): Promise<RpcResponseAndContext<null | AccountInfo<Buffer>>> {
182
+ return await this.getParsedAccountInfo(publicKey);
183
+ }
184
+ async sendRawTransaction(
185
+ rawTransaction: Buffer | Uint8Array | Array<number>,
186
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
187
+ _options?: any
188
+ ): Promise<TransactionSignature> {
189
+ const tx = Transaction.from(rawTransaction);
190
+ const signature = await this.sendTransaction(tx);
191
+ return signature;
192
+ }
193
+
194
+ async sendTransaction(tx: Transaction): Promise<TransactionSignature> {
195
+ const banksTransactionMeta = await this._banksClient.tryProcessTransaction(
196
+ tx
197
+ );
198
+ if (banksTransactionMeta.result) {
199
+ throw new Error(banksTransactionMeta.result);
200
+ }
201
+ const signature = bs58.encode(tx.signatures[0].signature);
202
+ this.transactionToMeta.set(signature, banksTransactionMeta);
203
+ let finalizedCount = 0;
204
+ while (finalizedCount < 10) {
205
+ const signatureStatus = (await this.getSignatureStatus(signature)).value
206
+ .confirmationStatus;
207
+ if (signatureStatus.toString() == '"finalized"') {
208
+ finalizedCount += 1;
209
+ }
210
+ }
211
+
212
+ // update the clock slot/timestamp
213
+ // sometimes race condition causes failures so we retry
214
+ try {
215
+ await this.updateSlotAndClock();
216
+ } catch (e) {
217
+ await this.updateSlotAndClock();
218
+ }
219
+
220
+ if (this.onLogCallbacks.size > 0) {
221
+ const transaction = await this.getTransaction(signature);
222
+
223
+ const context = { slot: transaction.slot };
224
+ const logs = {
225
+ logs: transaction.meta.logMessages,
226
+ err: transaction.meta.err,
227
+ signature,
228
+ };
229
+ for (const logCallback of this.onLogCallbacks.values()) {
230
+ logCallback(logs, context);
231
+ }
232
+ }
233
+
234
+ for (const [
235
+ publicKey,
236
+ callback,
237
+ ] of this.onAccountChangeCallbacks.values()) {
238
+ const accountInfo = await this.getParsedAccountInfo(publicKey);
239
+ callback(accountInfo.value, accountInfo.context);
240
+ }
241
+
242
+ return signature;
243
+ }
244
+
245
+ private async updateSlotAndClock() {
246
+ const currentSlot = await this.getSlot();
247
+ const nextSlot = currentSlot + BigInt(1);
248
+ this.context.warpToSlot(nextSlot);
249
+ const currentClock = await this._banksClient.getClock();
250
+ const newClock = new Clock(
251
+ nextSlot,
252
+ currentClock.epochStartTimestamp,
253
+ currentClock.epoch,
254
+ currentClock.leaderScheduleEpoch,
255
+ currentClock.unixTimestamp + BigInt(1)
256
+ );
257
+ this.context.setClock(newClock);
258
+ this.clock = newClock;
259
+ }
260
+
261
+ getTime(): number {
262
+ return Number(this.clock.unixTimestamp);
263
+ }
264
+
265
+ async getParsedAccountInfo(
266
+ publicKey: PublicKey
267
+ ): Promise<RpcResponseAndContext<AccountInfo<Buffer>>> {
268
+ const accountInfoBytes = await this._banksClient.getAccount(publicKey);
269
+ if (accountInfoBytes === null) {
270
+ return {
271
+ context: { slot: Number(await this._banksClient.getSlot()) },
272
+ value: null,
273
+ };
274
+ }
275
+ accountInfoBytes.data = Buffer.from(accountInfoBytes.data);
276
+ const accountInfoBuffer = accountInfoBytes as AccountInfo<Buffer>;
277
+ return {
278
+ context: { slot: Number(await this._banksClient.getSlot()) },
279
+ value: accountInfoBuffer,
280
+ };
281
+ }
282
+
283
+ async getLatestBlockhash(commitment?: Commitment): Promise<
284
+ Readonly<{
285
+ blockhash: string;
286
+ lastValidBlockHeight: number;
287
+ }>
288
+ > {
289
+ const blockhashAndBlockheight = await this._banksClient.getLatestBlockhash(
290
+ commitment
291
+ );
292
+ return {
293
+ blockhash: blockhashAndBlockheight[0],
294
+ lastValidBlockHeight: Number(blockhashAndBlockheight[1]),
295
+ };
296
+ }
297
+
298
+ async getSignatureStatus(
299
+ signature: string,
300
+ _config?: SignatureStatusConfig
301
+ ): Promise<RpcResponseAndContext<null | SignatureStatus>> {
302
+ const transactionStatus = await this._banksClient.getTransactionStatus(
303
+ signature
304
+ );
305
+ if (transactionStatus === null) {
306
+ return {
307
+ context: { slot: Number(await this._banksClient.getSlot()) },
308
+ value: null,
309
+ };
310
+ }
311
+ return {
312
+ context: { slot: Number(await this._banksClient.getSlot()) },
313
+ value: {
314
+ slot: Number(transactionStatus.slot),
315
+ confirmations: Number(transactionStatus.confirmations),
316
+ err: transactionStatus.err,
317
+ confirmationStatus:
318
+ transactionStatus.confirmationStatus as TransactionConfirmationStatus,
319
+ },
320
+ };
321
+ }
322
+
323
+ /**
324
+ * There's really no direct equivalent to getTransaction exposed by SolanaProgramTest, so we do the best that we can here - it's a little hacky.
325
+ */
326
+ async getTransaction(
327
+ signature: string,
328
+ _rawConfig?: GetTransactionConfig | GetVersionedTransactionConfig
329
+ ): Promise<BankrunTransactionRespose | null> {
330
+ const txMeta = this.transactionToMeta.get(
331
+ signature as TransactionSignature
332
+ );
333
+ if (txMeta === undefined) {
334
+ return null;
335
+ }
336
+ const transactionStatus = await this._banksClient.getTransactionStatus(
337
+ signature
338
+ );
339
+ const meta: BankrunTransactionMetaNormalized = {
340
+ logMessages: txMeta.meta.logMessages,
341
+ err: txMeta.result,
342
+ };
343
+ return {
344
+ slot: Number(transactionStatus.slot),
345
+ meta,
346
+ };
347
+ }
348
+
349
+ findComputeUnitConsumption(signature: string): bigint {
350
+ const txMeta = this.transactionToMeta.get(
351
+ signature as TransactionSignature
352
+ );
353
+ if (txMeta === undefined) {
354
+ throw new Error('Transaction not found');
355
+ }
356
+ return txMeta.meta.computeUnitsConsumed;
357
+ }
358
+
359
+ printTxLogs(signature: string): void {
360
+ const txMeta = this.transactionToMeta.get(
361
+ signature as TransactionSignature
362
+ );
363
+ if (txMeta === undefined) {
364
+ throw new Error('Transaction not found');
365
+ }
366
+ console.log(txMeta.meta.logMessages);
367
+ }
368
+
369
+ async simulateTransaction(
370
+ transaction: Transaction | VersionedTransaction,
371
+ _config?: SimulateTransactionConfig
372
+ ): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
373
+ const simulationResult = await this._banksClient.simulateTransaction(
374
+ transaction
375
+ );
376
+ const returnDataProgramId =
377
+ simulationResult.meta?.returnData?.programId.toBase58();
378
+ const returnDataNormalized = Buffer.from(
379
+ simulationResult.meta?.returnData?.data
380
+ ).toString('base64');
381
+ const returnData: TransactionReturnData = {
382
+ programId: returnDataProgramId,
383
+ data: [returnDataNormalized, 'base64'],
384
+ };
385
+ return {
386
+ context: { slot: Number(await this._banksClient.getSlot()) },
387
+ value: {
388
+ err: simulationResult.result,
389
+ logs: simulationResult.meta.logMessages,
390
+ accounts: undefined,
391
+ unitsConsumed: Number(simulationResult.meta.computeUnitsConsumed),
392
+ returnData,
393
+ },
394
+ };
395
+ }
396
+
397
+ onSignature(
398
+ signature: string,
399
+ callback: SignatureResultCallback,
400
+ commitment?: Commitment
401
+ ): ClientSubscriptionId {
402
+ const txMeta = this.transactionToMeta.get(
403
+ signature as TransactionSignature
404
+ );
405
+ this._banksClient.getSlot(commitment).then((slot) => {
406
+ if (txMeta) {
407
+ callback({ err: txMeta.result }, { slot: Number(slot) });
408
+ }
409
+ });
410
+ return 0;
411
+ }
412
+
413
+ async removeSignatureListener(_clientSubscriptionId: number): Promise<void> {
414
+ // Nothing actually has to happen here! Pretty cool, huh?
415
+ // This function signature only exists to match the web3js interface
416
+ }
417
+
418
+ onLogs(
419
+ filter: LogsFilter,
420
+ callback: LogsCallback,
421
+ _commitment?: Commitment
422
+ ): ClientSubscriptionId {
423
+ const subscriptId = this.nextClientSubscriptionId;
424
+
425
+ this.onLogCallbacks.set(subscriptId, callback);
426
+
427
+ this.nextClientSubscriptionId += 1;
428
+
429
+ return subscriptId;
430
+ }
431
+
432
+ async removeOnLogsListener(
433
+ clientSubscriptionId: ClientSubscriptionId
434
+ ): Promise<void> {
435
+ this.onLogCallbacks.delete(clientSubscriptionId);
436
+ }
437
+
438
+ onAccountChange(
439
+ publicKey: PublicKey,
440
+ callback: AccountChangeCallback,
441
+ // @ts-ignore
442
+ _commitment?: Commitment
443
+ ): ClientSubscriptionId {
444
+ const subscriptId = this.nextClientSubscriptionId;
445
+
446
+ this.onAccountChangeCallbacks.set(subscriptId, [publicKey, callback]);
447
+
448
+ this.nextClientSubscriptionId += 1;
449
+
450
+ return subscriptId;
451
+ }
452
+
453
+ async removeAccountChangeListener(
454
+ clientSubscriptionId: ClientSubscriptionId
455
+ ): Promise<void> {
456
+ this.onAccountChangeCallbacks.delete(clientSubscriptionId);
457
+ }
458
+
459
+ async getMinimumBalanceForRentExemption(_: number): Promise<number> {
460
+ return 10 * LAMPORTS_PER_SOL;
461
+ }
462
+ }
463
+
464
+ export function asBN(value: number | bigint): BN {
465
+ return new BN(Number(value));
466
+ }
@@ -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',
@@ -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": "padding1",
6202
- "type": {
6203
- "array": [
6204
- "u8",
6205
- 2
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
- 48
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 = (