@velocity-exchange/vaults-sdk 0.0.1

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.
Files changed (65) hide show
  1. package/.env.example +3 -0
  2. package/README.md +152 -0
  3. package/cli/cli.ts +751 -0
  4. package/cli/commands/adminDeleteFeeUpdate.ts +73 -0
  5. package/cli/commands/adminInitFeeUpdate.ts +73 -0
  6. package/cli/commands/adminUpdateVaultClass.ts +49 -0
  7. package/cli/commands/applyProfitShare.ts +139 -0
  8. package/cli/commands/decodeLogs.ts +98 -0
  9. package/cli/commands/deposit.ts +98 -0
  10. package/cli/commands/deriveVaultAddress.ts +14 -0
  11. package/cli/commands/forceWithdraw.ts +56 -0
  12. package/cli/commands/forceWithdrawAll.ts +142 -0
  13. package/cli/commands/index.ts +28 -0
  14. package/cli/commands/initVault.ts +227 -0
  15. package/cli/commands/initVaultDepositor.ts +42 -0
  16. package/cli/commands/listDepositorsForVault.ts +32 -0
  17. package/cli/commands/managerApplyProfitShare.ts +32 -0
  18. package/cli/commands/managerBorrow.ts +77 -0
  19. package/cli/commands/managerCancelWithdraw.ts +30 -0
  20. package/cli/commands/managerDeposit.ts +45 -0
  21. package/cli/commands/managerRepay.ts +94 -0
  22. package/cli/commands/managerRequestWithdraw.ts +86 -0
  23. package/cli/commands/managerUpdateBorrow.ts +56 -0
  24. package/cli/commands/managerUpdateFees.ts +156 -0
  25. package/cli/commands/managerUpdateMarginTradingEnabled.ts +32 -0
  26. package/cli/commands/managerUpdatePoolId.ts +36 -0
  27. package/cli/commands/managerUpdateVault.ts +210 -0
  28. package/cli/commands/managerUpdateVaultDelegate.ts +43 -0
  29. package/cli/commands/managerUpdateVaultManager.ts +77 -0
  30. package/cli/commands/managerWithdraw.ts +30 -0
  31. package/cli/commands/requestWithdraw.ts +58 -0
  32. package/cli/commands/vaultDeposit.ts +42 -0
  33. package/cli/commands/vaultInvariantChecks.ts +407 -0
  34. package/cli/commands/vaultWithdraw.ts +42 -0
  35. package/cli/commands/viewVault.ts +50 -0
  36. package/cli/commands/viewVaultDepositor.ts +36 -0
  37. package/cli/commands/withdraw.ts +40 -0
  38. package/cli/ledgerWallet.test.ts +49 -0
  39. package/cli/ledgerWallet.ts +111 -0
  40. package/cli/utils.ts +389 -0
  41. package/package.json +48 -0
  42. package/src/accountSubscribers/index.ts +2 -0
  43. package/src/accountSubscribers/pollingVaultDepositorSubscriber.ts +69 -0
  44. package/src/accountSubscribers/pollingVaultSubscriber.ts +63 -0
  45. package/src/accountSubscribers/pollingVaultsProgramAccountSubscriber.ts +114 -0
  46. package/src/accounts/index.ts +2 -0
  47. package/src/accounts/vaultAccount.ts +255 -0
  48. package/src/accounts/vaultDepositorAccount.ts +77 -0
  49. package/src/accounts/vaultsProgramAccount.ts +38 -0
  50. package/src/addresses.ts +114 -0
  51. package/src/constants/index.ts +15 -0
  52. package/src/idl/drift_vaults.json +5698 -0
  53. package/src/index.ts +11 -0
  54. package/src/math/index.ts +2 -0
  55. package/src/math/vault.ts +71 -0
  56. package/src/math/vaultDepositor.ts +90 -0
  57. package/src/name.ts +18 -0
  58. package/src/parsers/index.ts +1 -0
  59. package/src/parsers/logParser.ts +28 -0
  60. package/src/types/drift_vaults.ts +6211 -0
  61. package/src/types/types.ts +336 -0
  62. package/src/utils.ts +74 -0
  63. package/src/vaultClient.ts +3666 -0
  64. package/tsconfig.json +24 -0
  65. package/velocity-exchange-vaults-sdk-0.0.1.tgz +0 -0
@@ -0,0 +1,3666 @@
1
+ import {
2
+ BN,
3
+ BigNum,
4
+ DriftClient,
5
+ getInsuranceFundStakeAccountPublicKey,
6
+ getUserAccountPublicKey,
7
+ getUserAccountPublicKeySync,
8
+ getUserStatsAccountPublicKey,
9
+ TEN,
10
+ UserMap,
11
+ unstakeSharesToAmount as depositSharesToVaultAmount,
12
+ ZERO,
13
+ getInsuranceFundVaultPublicKey,
14
+ OracleSource,
15
+ WRAPPED_SOL_MINT,
16
+ SpotMarketAccount,
17
+ UserAccount,
18
+ UserStatsAccount,
19
+ QUOTE_PRECISION_EXP,
20
+ } from '@velocity-exchange/sdk';
21
+ import { BorshAccountsCoder, Program, ProgramAccount } from '@coral-xyz/anchor';
22
+ import { DriftVaults } from './types/drift_vaults';
23
+ import {
24
+ getTokenizedVaultAddressSync,
25
+ getTokenizedVaultMintAddressSync,
26
+ getInsuranceFundTokenVaultAddressSync,
27
+ getTokenVaultAddressSync,
28
+ getVaultAddressSync,
29
+ getVaultDepositorAddressSync,
30
+ getVaultProtocolAddressSync,
31
+ getFeeUpdateAddressSync,
32
+ } from './addresses';
33
+ import {
34
+ AccountMeta,
35
+ AddressLookupTableAccount,
36
+ ComputeBudgetProgram,
37
+ PublicKey,
38
+ SystemProgram,
39
+ SYSVAR_RENT_PUBKEY,
40
+ TransactionInstruction,
41
+ TransactionSignature,
42
+ VersionedTransaction,
43
+ } from '@solana/web3.js';
44
+ import {
45
+ createAssociatedTokenAccountInstruction,
46
+ createCloseAccountInstruction,
47
+ createSyncNativeInstruction,
48
+ getAssociatedTokenAddressSync,
49
+ TOKEN_PROGRAM_ID,
50
+ } from '@solana/spl-token';
51
+ import {
52
+ FeeUpdate,
53
+ hasPendingFeeUpdate,
54
+ Vault,
55
+ VaultClass,
56
+ VaultDepositor,
57
+ VaultParams,
58
+ VaultProtocol,
59
+ VaultProtocolParams,
60
+ VaultWithProtocolParams,
61
+ WithdrawUnit,
62
+ } from './types/types';
63
+ import { bs58 } from '@coral-xyz/anchor/dist/cjs/utils/bytes';
64
+ import { UserMapConfig } from '@velocity-exchange/sdk';
65
+ import { calculateRealizedVaultDepositorEquity } from './math';
66
+ import { Metaplex } from '@metaplex-foundation/js';
67
+ import { getOrCreateATAInstruction } from './utils';
68
+ import { VAULT_ADMIN_KEY, VAULT_SHARES_PRECISION_EXP } from './constants';
69
+
70
+ type OracleFeedConfig = {
71
+ feed: PublicKey;
72
+ oracleSource: OracleSource;
73
+ pythLazerId?: number;
74
+ };
75
+
76
+ export type TxParams = {
77
+ cuLimit?: number;
78
+ cuPriceMicroLamports?: number;
79
+ simulateTransaction?: boolean;
80
+ lookupTables?: AddressLookupTableAccount[];
81
+ oracleFeedsToCrank?: {
82
+ feedsToCrank: OracleFeedConfig[];
83
+ pythLazerMsgHexGetter?: (feedIds: number[]) => Promise<string>;
84
+ };
85
+ noLut?: boolean;
86
+ };
87
+
88
+ export class VaultClient {
89
+ driftClient: DriftClient;
90
+ metaplex?: Metaplex;
91
+ program: Program<DriftVaults>;
92
+ cliMode: boolean;
93
+
94
+ /**
95
+ * Cache map of drift user accounts of vaults.
96
+ */
97
+ readonly vaultUsers: UserMap;
98
+
99
+ constructor({
100
+ driftClient,
101
+ program,
102
+ metaplex,
103
+ // @deprecated, no longer used
104
+ cliMode,
105
+ userMapConfig,
106
+ }: {
107
+ driftClient: DriftClient;
108
+ program: Program<DriftVaults>;
109
+ metaplex?: Metaplex;
110
+ // @deprecated, no longer used
111
+ cliMode?: boolean;
112
+ userMapConfig?: UserMapConfig;
113
+ }) {
114
+ this.driftClient = driftClient;
115
+ this.metaplex = metaplex;
116
+ this.program = program;
117
+ this.cliMode = !!cliMode;
118
+
119
+ if (!userMapConfig) {
120
+ this.vaultUsers = new UserMap({
121
+ driftClient: driftClient,
122
+ subscriptionConfig: {
123
+ type: 'polling',
124
+ frequency: 1000,
125
+ commitment: 'processed',
126
+ },
127
+ });
128
+ } else {
129
+ this.vaultUsers = new UserMap(userMapConfig);
130
+ }
131
+ }
132
+
133
+ private getRemainingAccountsForUser(
134
+ userAccounts: UserAccount[],
135
+ writableSpotMarketIndexes: number[],
136
+ vaultAccount: Vault,
137
+ _userStats: UserStatsAccount,
138
+ skipVaultProtocol = false,
139
+ _skipFuelOverflow = false,
140
+ skipFeeUpdate = false
141
+ ) {
142
+ const remainingAccounts = this.driftClient.getRemainingAccounts({
143
+ userAccounts,
144
+ writableSpotMarketIndexes,
145
+ });
146
+
147
+ const hasVaultProtocol = vaultAccount.vaultProtocol === true;
148
+
149
+ const hasFeeUpdate = hasPendingFeeUpdate(vaultAccount.feeUpdateStatus);
150
+
151
+ if (hasFeeUpdate && !skipFeeUpdate) {
152
+ const feeUpdate = getFeeUpdateAddressSync(
153
+ this.program.programId,
154
+ vaultAccount.pubkey
155
+ );
156
+ remainingAccounts.push({
157
+ pubkey: feeUpdate,
158
+ isSigner: false,
159
+ isWritable: true,
160
+ });
161
+ }
162
+
163
+ if (hasVaultProtocol && !skipVaultProtocol) {
164
+ const vaultProtocol = this.getVaultProtocolAddress(vaultAccount.pubkey);
165
+ remainingAccounts.push({
166
+ pubkey: vaultProtocol,
167
+ isSigner: false,
168
+ isWritable: true,
169
+ });
170
+ }
171
+
172
+ return remainingAccounts;
173
+ }
174
+
175
+ private async checkIfAccountExists(account: PublicKey): Promise<boolean> {
176
+ try {
177
+ const accountInfo = await this.driftClient.connection.getAccountInfo(
178
+ account
179
+ );
180
+ return accountInfo != null;
181
+ } catch (e) {
182
+ // Doesn't already exist
183
+ return false;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Unsubscribes from the vault users map. Call this to clean up any dangling promises.
189
+ */
190
+ public async unsubscribe() {
191
+ if (this.vaultUsers) {
192
+ await this.vaultUsers.unsubscribe();
193
+ }
194
+ }
195
+
196
+ public async getVault(vault: PublicKey): Promise<Vault> {
197
+ return await this.program.account.vault.fetch(vault);
198
+ }
199
+
200
+ public async getFeeUpdate(feeUpdate: PublicKey): Promise<FeeUpdate> {
201
+ return await this.program.account.feeUpdate.fetch(feeUpdate);
202
+ }
203
+
204
+ public async getVaultAndSlot(
205
+ vault: PublicKey
206
+ ): Promise<{ vault: Vault; slot: number }> {
207
+ const vaultAndSlot = await this.program.account.vault.fetchAndContext(
208
+ vault
209
+ );
210
+ return {
211
+ vault: vaultAndSlot.data as Vault,
212
+ slot: vaultAndSlot.context.slot,
213
+ };
214
+ }
215
+
216
+ public async getVaultDepositor(vaultDepositor: PublicKey): Promise<any> {
217
+ return await this.program.account.vaultDepositor.fetch(vaultDepositor);
218
+ }
219
+
220
+ public async getVaultDepositorAndSlot(
221
+ vaultDepositor: PublicKey
222
+ ): Promise<{ vaultDepositor: any; slot: number }> {
223
+ const vaultDepositorAndSlot =
224
+ await this.program.account.vaultDepositor.fetchAndContext(vaultDepositor);
225
+ return {
226
+ vaultDepositor: vaultDepositorAndSlot.data,
227
+ slot: vaultDepositorAndSlot.context.slot,
228
+ };
229
+ }
230
+
231
+ public getVaultProtocolAddress(vault: PublicKey): PublicKey {
232
+ return getVaultProtocolAddressSync(this.program.programId, vault);
233
+ }
234
+
235
+ public async getVaultProtocol(
236
+ vaultProtocol: PublicKey
237
+ ): Promise<VaultProtocol> {
238
+ return await this.program.account.vaultProtocol.fetch(vaultProtocol);
239
+ }
240
+
241
+ public async getVaultProtocolAndSlot(
242
+ vaultProtocol: PublicKey
243
+ ): Promise<{ vaultProtocol: VaultProtocol; slot: number }> {
244
+ const vaultProtocolAndSlot =
245
+ await this.program.account.vaultProtocol.fetchAndContext(vaultProtocol);
246
+ return {
247
+ vaultProtocol: vaultProtocolAndSlot.data as VaultProtocol,
248
+ slot: vaultProtocolAndSlot.context.slot,
249
+ };
250
+ }
251
+
252
+ public async getAllVaultDepositorsWithNoWithdrawRequest(
253
+ vault: PublicKey
254
+ ): Promise<ProgramAccount<VaultDepositor>[]> {
255
+ const filters = [
256
+ {
257
+ // discriminator = VaultDepositor
258
+ memcmp: {
259
+ offset: 0,
260
+ bytes: bs58.encode(
261
+ (
262
+ this.program.coder.accounts as BorshAccountsCoder
263
+ ).accountDiscriminator('vaultDepositor')
264
+ ),
265
+ },
266
+ },
267
+ {
268
+ // vault = vault
269
+ memcmp: {
270
+ offset: 8,
271
+ bytes: vault.toBase58(),
272
+ },
273
+ },
274
+ {
275
+ // last_withdraw_request.shares (u128) = 0
276
+ memcmp: {
277
+ offset: 112,
278
+ bytes: bs58.encode(new Uint8Array(16).fill(0)),
279
+ },
280
+ },
281
+ ];
282
+ // @ts-ignore
283
+ return (await this.program.account.vaultDepositor.all(
284
+ filters
285
+ )) as ProgramAccount<VaultDepositor>[];
286
+ }
287
+
288
+ public async getAllVaultDepositors(
289
+ vault?: PublicKey
290
+ ): Promise<ProgramAccount<VaultDepositor>[]> {
291
+ const filters = [
292
+ {
293
+ // discriminator = VaultDepositor
294
+ memcmp: {
295
+ offset: 0,
296
+ bytes: bs58.encode(
297
+ (
298
+ this.program.coder.accounts as BorshAccountsCoder
299
+ ).accountDiscriminator('vaultDepositor')
300
+ ),
301
+ },
302
+ },
303
+ ];
304
+ if (vault) {
305
+ filters.push({
306
+ // vault = vault
307
+ memcmp: {
308
+ offset: 8,
309
+ bytes: vault.toBase58(),
310
+ },
311
+ });
312
+ }
313
+ // @ts-ignore
314
+ return (await this.program.account.vaultDepositor.all(
315
+ filters
316
+ )) as ProgramAccount<VaultDepositor>[];
317
+ }
318
+
319
+ public async getAllVaultDepositorsForAuthority(
320
+ authority: PublicKey
321
+ ): Promise<ProgramAccount<VaultDepositor>[]> {
322
+ const filters = [
323
+ {
324
+ // discriminator = VaultDepositor
325
+ memcmp: {
326
+ offset: 0,
327
+ bytes: bs58.encode(
328
+ (
329
+ this.program.coder.accounts as BorshAccountsCoder
330
+ ).accountDiscriminator('vaultDepositor')
331
+ ),
332
+ },
333
+ },
334
+ ];
335
+ filters.push({
336
+ // authority = authority
337
+ memcmp: {
338
+ offset: 8 + 32 + 32,
339
+ bytes: authority.toBase58(),
340
+ },
341
+ });
342
+ // @ts-ignore
343
+ return (await this.program.account.vaultDepositor.all(
344
+ filters
345
+ )) as ProgramAccount<VaultDepositor>[];
346
+ }
347
+
348
+ public async getSubscribedVaultUser(vaultDriftUserAccountPubKey: PublicKey) {
349
+ return this.vaultUsers.mustGet(vaultDriftUserAccountPubKey.toBase58(), {
350
+ type: 'websocket',
351
+ });
352
+ }
353
+
354
+ /// useful for syncing state during tests.
355
+ public async syncVaultUsers() {
356
+ for (const user of this.vaultUsers.values()) {
357
+ await user.fetchAccounts();
358
+ }
359
+ }
360
+
361
+ /**
362
+ *
363
+ * @param vault pubkey
364
+ * @param factorUnrealizedPNL add unrealized pnl to net balance
365
+ * @returns vault equity, in USDC
366
+ */
367
+ public async calculateVaultEquity(params: {
368
+ address?: PublicKey;
369
+ vault?: Vault;
370
+ factorUnrealizedPNL?: boolean;
371
+ includeManagerBorrowedValue?: boolean;
372
+ }): Promise<BN> {
373
+ try {
374
+ // defaults to true if undefined
375
+ let factorUnrealizedPNL = true;
376
+ if (params.factorUnrealizedPNL !== undefined) {
377
+ factorUnrealizedPNL = params.factorUnrealizedPNL;
378
+ }
379
+
380
+ let includeManagerBorrowedValue = true;
381
+ if (params.includeManagerBorrowedValue !== undefined) {
382
+ includeManagerBorrowedValue = params.includeManagerBorrowedValue;
383
+ }
384
+
385
+ let vaultAccount: Vault;
386
+ if (params.address !== undefined) {
387
+ // @ts-ignore
388
+ vaultAccount = await this.program.account.vault.fetch(params.address);
389
+ } else if (params.vault !== undefined) {
390
+ vaultAccount = params.vault;
391
+ } else {
392
+ throw new Error('Must supply address or vault');
393
+ }
394
+
395
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
396
+
397
+ let netSpotValue = user.getNetSpotMarketValue();
398
+
399
+ if (factorUnrealizedPNL) {
400
+ const unrealizedPnl = user.getUnrealizedPNL(true, undefined, undefined);
401
+ netSpotValue = netSpotValue.add(unrealizedPnl);
402
+ }
403
+
404
+ if (includeManagerBorrowedValue) {
405
+ netSpotValue = netSpotValue.add(vaultAccount.managerBorrowedValue);
406
+ }
407
+
408
+ return netSpotValue;
409
+ } catch (err) {
410
+ console.error('VaultClient ~ err:', err);
411
+ return ZERO;
412
+ }
413
+ }
414
+
415
+ public async calculateVaultAllTimeNotionalPnl(params: {
416
+ address?: PublicKey;
417
+ vault?: Vault;
418
+ }): Promise<BN> {
419
+ try {
420
+ let vaultAccount: Vault;
421
+ if (params.address !== undefined) {
422
+ // @ts-ignore
423
+ vaultAccount = await this.program.account.vault.fetch(params.address);
424
+ } else if (params.vault !== undefined) {
425
+ vaultAccount = params.vault;
426
+ } else {
427
+ throw new Error('Must supply address or vault');
428
+ }
429
+
430
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
431
+ const allTimeTotalPnl = user.getTotalAllTimePnl();
432
+
433
+ return allTimeTotalPnl;
434
+ } catch (err) {
435
+ console.error('VaultClient ~ err:', err);
436
+ return ZERO;
437
+ }
438
+ }
439
+
440
+ /**
441
+ *
442
+ * @param vault pubkey
443
+ * @param factorUnrealizedPNL add unrealized pnl to existing equity
444
+ * @returns total vault equity, in spot deposit asset
445
+ */
446
+ public async calculateVaultEquityInDepositAsset(params: {
447
+ address?: PublicKey;
448
+ vault?: Vault;
449
+ factorUnrealizedPNL?: boolean;
450
+ }): Promise<BN> {
451
+ let vaultAccount: Vault;
452
+ if (params.address !== undefined) {
453
+ vaultAccount = await this.program.account.vault.fetch(params.address);
454
+ } else if (params.vault !== undefined) {
455
+ vaultAccount = params.vault;
456
+ } else {
457
+ throw new Error('Must supply address or vault');
458
+ }
459
+ const vaultEquity = await this.calculateVaultEquity({
460
+ vault: vaultAccount,
461
+ factorUnrealizedPNL: params.factorUnrealizedPNL,
462
+ });
463
+ const spotMarket = this.driftClient.getSpotMarketAccount(
464
+ vaultAccount.spotMarketIndex
465
+ );
466
+ const spotOracle = this.driftClient.getOracleDataForSpotMarket(
467
+ vaultAccount.spotMarketIndex
468
+ );
469
+ const spotPrecision = TEN.pow(new BN(spotMarket!.decimals));
470
+
471
+ return vaultEquity.mul(spotPrecision).div(spotOracle.price);
472
+ }
473
+
474
+ /**
475
+ * @param params
476
+ * @returns vault depositor equity, in spot market value (which is usually USDC)
477
+ */
478
+ public async calculateWithdrawableVaultDepositorEquity(params: {
479
+ vaultDepositorAddress?: PublicKey;
480
+ vaultDepositor?: VaultDepositor;
481
+ vaultAddress?: PublicKey;
482
+ vault?: Vault;
483
+ }): Promise<BN> {
484
+ let vaultAccount: Vault;
485
+ if (params.vaultAddress !== undefined) {
486
+ vaultAccount = await this.program.account.vault.fetch(
487
+ params.vaultAddress
488
+ );
489
+ } else if (params.vault !== undefined) {
490
+ vaultAccount = params.vault;
491
+ } else {
492
+ throw new Error('Must supply vaultAddress or vault');
493
+ }
494
+
495
+ let vaultDepositorAccount: VaultDepositor;
496
+ if (params.vaultDepositorAddress !== undefined) {
497
+ vaultDepositorAccount = await this.program.account.vaultDepositor.fetch(
498
+ params.vaultDepositorAddress
499
+ );
500
+ } else if (params.vaultDepositor !== undefined) {
501
+ vaultDepositorAccount = params.vaultDepositor;
502
+ } else {
503
+ throw new Error('Must supply vaultDepositorAddress or vaultDepositor');
504
+ }
505
+
506
+ const vaultEquity = await this.calculateVaultEquity({
507
+ vault: vaultAccount,
508
+ factorUnrealizedPNL: false,
509
+ });
510
+ return calculateRealizedVaultDepositorEquity(
511
+ vaultDepositorAccount,
512
+ vaultEquity,
513
+ vaultAccount
514
+ );
515
+ }
516
+
517
+ public async calculateWithdrawableVaultDepositorEquityInDepositAsset(params: {
518
+ vaultDepositorAddress?: PublicKey;
519
+ vaultDepositor?: VaultDepositor;
520
+ vaultAddress?: PublicKey;
521
+ vault?: Vault;
522
+ }): Promise<BN> {
523
+ let vaultAccount: Vault;
524
+ if (params.vaultAddress !== undefined) {
525
+ vaultAccount = await this.program.account.vault.fetch(
526
+ params.vaultAddress
527
+ );
528
+ } else if (params.vault !== undefined) {
529
+ vaultAccount = params.vault;
530
+ } else {
531
+ throw new Error('Must supply vaultAddress or vault');
532
+ }
533
+
534
+ let vaultDepositorAccount: VaultDepositor;
535
+ if (params.vaultDepositorAddress !== undefined) {
536
+ vaultDepositorAccount = await this.program.account.vaultDepositor.fetch(
537
+ params.vaultDepositorAddress
538
+ );
539
+ } else if (params.vaultDepositor !== undefined) {
540
+ vaultDepositorAccount = params.vaultDepositor;
541
+ } else {
542
+ throw new Error('Must supply vaultDepositorAddress or vaultDepositor');
543
+ }
544
+
545
+ let vaultProtocol: VaultProtocol | undefined = undefined;
546
+ if (vaultAccount.vaultProtocol) {
547
+ vaultProtocol = await this.program.account.vaultProtocol.fetch(
548
+ this.getVaultProtocolAddress(vaultAccount.pubkey)
549
+ );
550
+ }
551
+
552
+ const vaultEquity = await this.calculateVaultEquity({
553
+ vault: vaultAccount,
554
+ factorUnrealizedPNL: false,
555
+ });
556
+ const vdEquity = calculateRealizedVaultDepositorEquity(
557
+ vaultDepositorAccount,
558
+ vaultEquity,
559
+ vaultAccount,
560
+ vaultProtocol
561
+ );
562
+
563
+ const spotMarket = this.driftClient.getSpotMarketAccount(
564
+ vaultAccount.spotMarketIndex
565
+ );
566
+ const spotOracle = this.driftClient.getOracleDataForSpotMarket(
567
+ vaultAccount.spotMarketIndex
568
+ );
569
+ const spotPrecision = TEN.pow(new BN(spotMarket!.decimals));
570
+
571
+ return vdEquity.mul(spotPrecision).div(spotOracle.price);
572
+ }
573
+
574
+ public async calculateVaultProtocolEquity(params: {
575
+ vault: PublicKey;
576
+ }): Promise<BN> {
577
+ const vaultAccount = await this.program.account.vault.fetch(params.vault);
578
+ const vaultTotalEquity = await this.calculateVaultEquity({
579
+ vault: vaultAccount,
580
+ });
581
+ const vaultProtocol = this.getVaultProtocolAddress(params.vault);
582
+ const vpAccount = await this.program.account.vaultProtocol.fetch(
583
+ vaultProtocol
584
+ );
585
+ return depositSharesToVaultAmount(
586
+ vpAccount.protocolProfitAndFeeShares,
587
+ vaultAccount.totalShares,
588
+ vaultTotalEquity
589
+ );
590
+ }
591
+
592
+ public async initializeVault(
593
+ params: {
594
+ name: number[];
595
+ spotMarketIndex: number;
596
+ redeemPeriod: BN;
597
+ maxTokens: BN;
598
+ minDepositAmount: BN;
599
+ managementFee: BN;
600
+ profitShare: number;
601
+ hurdleRate: number;
602
+ permissioned: boolean;
603
+ vaultProtocol?: VaultProtocolParams;
604
+ manager?: PublicKey;
605
+ },
606
+ uiTxParams?: TxParams
607
+ ): Promise<TransactionSignature> {
608
+ const ix = await this.getInitializeVaultIx(params);
609
+ return await this.createAndSendTxn([ix], uiTxParams);
610
+ }
611
+
612
+ public async getInitializeVaultIx(params: {
613
+ name: number[];
614
+ spotMarketIndex: number;
615
+ redeemPeriod: BN;
616
+ maxTokens: BN;
617
+ minDepositAmount: BN;
618
+ managementFee: BN;
619
+ profitShare: number;
620
+ hurdleRate: number;
621
+ permissioned: boolean;
622
+ vaultProtocol?: VaultProtocolParams;
623
+ manager?: PublicKey;
624
+ }): Promise<TransactionInstruction> {
625
+ const { vaultProtocol: vaultProtocolParams, ...vaultParams } = params;
626
+ const vault = getVaultAddressSync(this.program.programId, params.name);
627
+ const tokenAccount = getTokenVaultAddressSync(
628
+ this.program.programId,
629
+ vault
630
+ );
631
+
632
+ const driftState = await this.driftClient.getStatePublicKey();
633
+ const spotMarket = this.driftClient.getSpotMarketAccount(
634
+ params.spotMarketIndex
635
+ );
636
+ if (!spotMarket) {
637
+ throw new Error(
638
+ `Spot market ${params.spotMarketIndex} not found on driftClient`
639
+ );
640
+ }
641
+
642
+ const userStatsKey = getUserStatsAccountPublicKey(
643
+ this.driftClient.program.programId,
644
+ vault
645
+ );
646
+ const userKey = getUserAccountPublicKeySync(
647
+ this.driftClient.program.programId,
648
+ vault
649
+ );
650
+
651
+ const accounts = {
652
+ driftSpotMarket: spotMarket.pubkey,
653
+ driftSpotMarketMint: spotMarket.mint,
654
+ driftUserStats: userStatsKey,
655
+ driftUser: userKey,
656
+ driftState,
657
+ vault,
658
+ tokenAccount,
659
+ driftProgram: this.driftClient.program.programId,
660
+ };
661
+
662
+ if (vaultProtocolParams) {
663
+ const _params: VaultWithProtocolParams = {
664
+ ...vaultParams,
665
+ vaultProtocol: vaultProtocolParams,
666
+ };
667
+
668
+ const uiAuthority = this.driftClient.wallet.publicKey;
669
+ const initializeVaultWithProtocolIx = await this.program.methods
670
+ .initializeVaultWithProtocol(_params)
671
+ .accounts({
672
+ ...accounts,
673
+ payer: params.manager ?? uiAuthority,
674
+ manager: params.manager ?? uiAuthority,
675
+ })
676
+ .instruction();
677
+ return initializeVaultWithProtocolIx;
678
+ } else {
679
+ const _params: VaultParams = vaultParams;
680
+
681
+ const uiAuthority = this.driftClient.wallet.publicKey;
682
+ const initializeVaultIx = await this.program.methods
683
+ .initializeVault(_params)
684
+ .accounts({
685
+ ...accounts,
686
+ payer: params.manager ?? uiAuthority,
687
+ manager: params.manager ?? uiAuthority,
688
+ })
689
+ .instruction();
690
+ return initializeVaultIx;
691
+ }
692
+ }
693
+
694
+ /**
695
+ * Updates the delegate address for a vault. The delegate address will be allowed to trade
696
+ * on behalf of the vault.
697
+ * @param vault vault address to update
698
+ * @param delegate delegate address to update to
699
+ * @returns
700
+ */
701
+ public async updateDelegate(
702
+ vault: PublicKey,
703
+ delegate: PublicKey,
704
+ uiTxParams?: TxParams
705
+ ): Promise<TransactionSignature> {
706
+ const vaultAccount = await this.program.account.vault.fetch(vault);
707
+ const updateDelegateIx = await this.getUpdateDelegateIx(
708
+ vault,
709
+ delegate,
710
+ vaultAccount.user,
711
+ vaultAccount.manager
712
+ );
713
+ return await this.createAndSendTxn([updateDelegateIx], uiTxParams);
714
+ }
715
+
716
+ public async getUpdateDelegateIx(
717
+ vault: PublicKey,
718
+ delegate: PublicKey,
719
+ vaultDriftUser: PublicKey,
720
+ vaultManager: PublicKey
721
+ ): Promise<TransactionInstruction> {
722
+ const accounts = {
723
+ vault: vault,
724
+ driftUser: vaultDriftUser,
725
+ driftProgram: this.driftClient.program.programId,
726
+ };
727
+
728
+ return await this.program.methods
729
+ .updateDelegate(delegate)
730
+ .accounts({ ...accounts, manager: vaultManager })
731
+ .instruction();
732
+ }
733
+
734
+ /**
735
+ * Updates the vault margin trading status.
736
+ * @param vault vault address to update
737
+ * @param enabled whether to enable margin trading
738
+ * @returns
739
+ */
740
+ public async updateMarginTradingEnabled(
741
+ vault: PublicKey,
742
+ enabled: boolean,
743
+ uiTxParams?: TxParams
744
+ ): Promise<TransactionSignature> {
745
+ const updateMarginTradingEnabledIx =
746
+ await this.getUpdateMarginTradingEnabledIx(vault, enabled);
747
+ return await this.createAndSendTxn(
748
+ [updateMarginTradingEnabledIx],
749
+ uiTxParams
750
+ );
751
+ }
752
+
753
+ public async getUpdateMarginTradingEnabledIx(
754
+ vault: PublicKey,
755
+ enabled: boolean
756
+ ): Promise<TransactionInstruction> {
757
+ const vaultAccount = await this.program.account.vault.fetch(vault);
758
+ const accounts = {
759
+ vault: vault,
760
+ driftUser: vaultAccount.user,
761
+ driftProgram: this.driftClient.program.programId,
762
+ };
763
+
764
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
765
+
766
+ const remainingAccounts: AccountMeta[] = [];
767
+ try {
768
+ const userStatsKey = getUserStatsAccountPublicKey(
769
+ this.driftClient.program.programId,
770
+ vault
771
+ );
772
+ const driftProgram = this.driftClient.program as any;
773
+ const userStats = (await driftProgram.account.userStats.fetch(
774
+ userStatsKey
775
+ )) as UserStatsAccount;
776
+ remainingAccounts.push(
777
+ ...this.getRemainingAccountsForUser(
778
+ [user.getUserAccount()],
779
+ [],
780
+ vaultAccount,
781
+ userStats
782
+ )
783
+ );
784
+ } catch (err) {
785
+ console.error('failed to get remaining accounts', err);
786
+ // do nothing
787
+ }
788
+
789
+ return await this.program.methods
790
+ .updateMarginTradingEnabled(enabled)
791
+ .accounts({ ...accounts, manager: vaultAccount.manager })
792
+ .remainingAccounts(remainingAccounts)
793
+ .instruction();
794
+ }
795
+
796
+ /**
797
+ * Updates the vault's pool id (for isolated pools).
798
+ * @param vault vault address to update
799
+ * @param poolId pool id to update to
800
+ * @returns
801
+ */
802
+ public async updateUserPoolId(
803
+ vault: PublicKey,
804
+ poolId: number,
805
+ uiTxParams?: TxParams
806
+ ): Promise<TransactionSignature> {
807
+ const vaultAccount = await this.program.account.vault.fetch(vault);
808
+ const updatePoolIdIx = await this.getUpdatePoolIdIx(
809
+ vault,
810
+ poolId,
811
+ vaultAccount
812
+ );
813
+ return await this.createAndSendTxn([updatePoolIdIx], uiTxParams);
814
+ }
815
+
816
+ /**
817
+ * Gets the instruction to update the pool id for a vault.
818
+ * @param vault vault address to update
819
+ * @param vaultAccount vault account data (optional, will be fetched if not provided)
820
+ * @param poolId pool id to update to
821
+ * @returns instruction to update pool id
822
+ */
823
+ public async getUpdatePoolIdIx(
824
+ vault: PublicKey,
825
+ poolId: number,
826
+ vaultAccount?: any
827
+ ): Promise<TransactionInstruction> {
828
+ if (!vaultAccount) {
829
+ vaultAccount = await this.program.account.vault.fetch(vault);
830
+ }
831
+
832
+ const accounts = {
833
+ vault: vault,
834
+ driftUser: vaultAccount.user,
835
+ driftProgram: this.driftClient.program.programId,
836
+ };
837
+
838
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
839
+
840
+ const remainingAccounts: AccountMeta[] = [];
841
+ try {
842
+ const userStatsKey = getUserStatsAccountPublicKey(
843
+ this.driftClient.program.programId,
844
+ vault
845
+ );
846
+ const driftProgram = this.driftClient.program as any;
847
+ const userStats = (await driftProgram.account.userStats.fetch(
848
+ userStatsKey
849
+ )) as UserStatsAccount;
850
+ remainingAccounts.push(
851
+ ...this.getRemainingAccountsForUser(
852
+ [user.getUserAccount()],
853
+ [],
854
+ vaultAccount,
855
+ userStats
856
+ )
857
+ );
858
+ } catch (err) {
859
+ console.error('failed to get remaining accounts', err);
860
+ // do nothing
861
+ }
862
+
863
+ return await this.program.methods
864
+ .updateUserPoolId(poolId)
865
+ .accounts({ ...accounts, manager: vaultAccount.manager })
866
+ .remainingAccounts(remainingAccounts)
867
+ .instruction();
868
+ }
869
+
870
+ private async handleWSolMovement(
871
+ amount: BN,
872
+ driftSpotMarket: SpotMarketAccount,
873
+ userTokenAccount: PublicKey
874
+ ) {
875
+ const isSolDeposit = driftSpotMarket.mint.equals(WRAPPED_SOL_MINT);
876
+ const preIxs: TransactionInstruction[] = [];
877
+ const postIxs: TransactionInstruction[] = [];
878
+
879
+ if (isSolDeposit) {
880
+ const { ixs: createWSolAccountIxs, pubkey } =
881
+ await this.driftClient.getWrappedSolAccountCreationIxs(amount, true);
882
+
883
+ userTokenAccount = pubkey;
884
+
885
+ preIxs.push(...createWSolAccountIxs);
886
+ postIxs.push(
887
+ createCloseAccountInstruction(
888
+ userTokenAccount,
889
+ this.driftClient.wallet.publicKey,
890
+ this.driftClient.wallet.publicKey,
891
+ []
892
+ )
893
+ );
894
+ }
895
+
896
+ return { userTokenAccount, preIxs, postIxs };
897
+ }
898
+
899
+ /**
900
+ *
901
+ * @param vault vault address to deposit to
902
+ * @param amount amount to deposit
903
+ * @returns
904
+ */
905
+ public async managerDeposit(
906
+ vault: PublicKey,
907
+ amount: BN,
908
+ uiTxParams?: TxParams,
909
+ managerTokenAccount?: PublicKey
910
+ ): Promise<TransactionSignature> {
911
+ const managerDepositIxs = await this.getManagerDepositIx(
912
+ vault,
913
+ amount,
914
+ managerTokenAccount
915
+ );
916
+ return await this.createAndSendTxn(managerDepositIxs, uiTxParams);
917
+ }
918
+
919
+ /**
920
+ *
921
+ * @param vault vault address to deposit to
922
+ * @param amount amount to deposit
923
+ * @returns
924
+ */
925
+ public async getManagerDepositIx(
926
+ vault: PublicKey,
927
+ amount: BN,
928
+ managerTokenAccount?: PublicKey
929
+ ): Promise<Array<TransactionInstruction>> {
930
+ const vaultAccount = await this.program.account.vault.fetch(vault);
931
+ const driftSpotMarket = this.driftClient.getSpotMarketAccount(
932
+ vaultAccount.spotMarketIndex
933
+ );
934
+ if (!driftSpotMarket) {
935
+ throw new Error(
936
+ `Spot market ${vaultAccount.spotMarketIndex} not found on driftClient`
937
+ );
938
+ }
939
+
940
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
941
+ const userStatsKey = getUserStatsAccountPublicKey(
942
+ this.driftClient.program.programId,
943
+ vault
944
+ );
945
+ const userStats = (await (
946
+ this.driftClient.program as any
947
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
948
+ const remainingAccounts = this.getRemainingAccountsForUser(
949
+ [user.getUserAccount()],
950
+ [vaultAccount.spotMarketIndex],
951
+ vaultAccount,
952
+ userStats,
953
+ false,
954
+ true,
955
+ true
956
+ );
957
+
958
+ const accounts = {
959
+ vault,
960
+ vaultTokenAccount: vaultAccount.tokenAccount,
961
+ driftUser: await getUserAccountPublicKey(
962
+ this.driftClient.program.programId,
963
+ vault
964
+ ),
965
+ driftUserStats: getUserStatsAccountPublicKey(
966
+ this.driftClient.program.programId,
967
+ vault
968
+ ),
969
+ driftProgram: this.driftClient.program.programId,
970
+ driftState: await this.driftClient.getStatePublicKey(),
971
+ driftSpotMarketVault: driftSpotMarket.vault,
972
+ userTokenAccount:
973
+ managerTokenAccount ??
974
+ getAssociatedTokenAddressSync(
975
+ driftSpotMarket.mint,
976
+ vaultAccount.manager,
977
+ true
978
+ ),
979
+ tokenProgram: TOKEN_PROGRAM_ID,
980
+ };
981
+
982
+ const { userTokenAccount, preIxs, postIxs } = await this.handleWSolMovement(
983
+ amount,
984
+ driftSpotMarket,
985
+ accounts.userTokenAccount
986
+ );
987
+
988
+ const managerDepositIx = await this.program.methods
989
+ .managerDeposit(amount)
990
+ .accounts({
991
+ ...accounts,
992
+ userTokenAccount,
993
+ manager: vaultAccount.manager,
994
+ })
995
+ .remainingAccounts(remainingAccounts)
996
+ .instruction();
997
+ return [...preIxs, managerDepositIx, ...postIxs];
998
+ }
999
+
1000
+ public async managerRequestWithdraw(
1001
+ vault: PublicKey,
1002
+ amount: BN,
1003
+ withdrawUnit: WithdrawUnit,
1004
+ uiTxParams?: TxParams
1005
+ ): Promise<TransactionSignature> {
1006
+ const requestWithdrawIx = await this.getManagerRequestWithdrawIx(
1007
+ vault,
1008
+ amount,
1009
+ withdrawUnit
1010
+ );
1011
+ return await this.createAndSendTxn([requestWithdrawIx], uiTxParams);
1012
+ }
1013
+
1014
+ public async getManagerRequestWithdrawIx(
1015
+ vault: PublicKey,
1016
+ amount: BN,
1017
+ withdrawUnit: WithdrawUnit
1018
+ ): Promise<TransactionInstruction> {
1019
+ this.program.idl.types;
1020
+ // @ts-ignore
1021
+ const vaultAccount = (await this.program.account.vault.fetch(
1022
+ vault
1023
+ )) as Vault;
1024
+
1025
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
1026
+ const userStatsKey = getUserStatsAccountPublicKey(
1027
+ this.driftClient.program.programId,
1028
+ vault
1029
+ );
1030
+ const userStats = (await (
1031
+ this.driftClient.program as any
1032
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
1033
+ const remainingAccounts = this.getRemainingAccountsForUser(
1034
+ [user.getUserAccount()],
1035
+ [vaultAccount.spotMarketIndex],
1036
+ vaultAccount,
1037
+ userStats,
1038
+ false,
1039
+ true,
1040
+ true
1041
+ );
1042
+
1043
+ const accounts = {
1044
+ vault,
1045
+ driftUser: vaultAccount.user,
1046
+ driftUserStats: userStatsKey,
1047
+ };
1048
+
1049
+ return this.program.instruction.managerRequestWithdraw(
1050
+ // @ts-ignore
1051
+ amount,
1052
+ withdrawUnit,
1053
+ {
1054
+ accounts: {
1055
+ manager: vaultAccount.manager,
1056
+ ...accounts,
1057
+ },
1058
+ remainingAccounts,
1059
+ }
1060
+ );
1061
+ }
1062
+
1063
+ public async managerCancelWithdrawRequest(
1064
+ vault: PublicKey,
1065
+ uiTxParams?: TxParams
1066
+ ): Promise<TransactionSignature> {
1067
+ const ix = await this.getManagerCancelWithdrawRequestIx(vault);
1068
+ return await this.createAndSendTxn([ix], uiTxParams);
1069
+ }
1070
+
1071
+ public async getManagerCancelWithdrawRequestIx(
1072
+ vault: PublicKey
1073
+ ): Promise<TransactionInstruction> {
1074
+ const vaultAccount = await this.program.account.vault.fetch(vault);
1075
+
1076
+ const userStatsKey = getUserStatsAccountPublicKey(
1077
+ this.driftClient.program.programId,
1078
+ vault
1079
+ );
1080
+
1081
+ const accounts = {
1082
+ manager: vaultAccount.manager,
1083
+ vault,
1084
+ driftUser: vaultAccount.user,
1085
+ driftUserStats: userStatsKey,
1086
+ };
1087
+
1088
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
1089
+ const userStats = (await (
1090
+ this.driftClient.program as any
1091
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
1092
+ const remainingAccounts = this.getRemainingAccountsForUser(
1093
+ [user.getUserAccount()],
1094
+ [],
1095
+ vaultAccount,
1096
+ userStats,
1097
+ false,
1098
+ true,
1099
+ true
1100
+ );
1101
+
1102
+ return this.program.instruction.mangerCancelWithdrawRequest({
1103
+ accounts,
1104
+ remainingAccounts,
1105
+ });
1106
+ }
1107
+
1108
+ public async managerWithdraw(
1109
+ vault: PublicKey,
1110
+ uiTxParams?: TxParams
1111
+ ): Promise<TransactionSignature> {
1112
+ const ixs = await this.getManagerWithdrawIx(vault);
1113
+ return this.createAndSendTxn(ixs, uiTxParams);
1114
+ }
1115
+
1116
+ public async getManagerWithdrawIx(
1117
+ vault: PublicKey
1118
+ ): Promise<TransactionInstruction[]> {
1119
+ const vaultAccount = await this.program.account.vault.fetch(vault);
1120
+
1121
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
1122
+ const userStatsKey = getUserStatsAccountPublicKey(
1123
+ this.driftClient.program.programId,
1124
+ vault
1125
+ );
1126
+ const userStats = (await (
1127
+ this.driftClient.program as any
1128
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
1129
+ const remainingAccounts = this.getRemainingAccountsForUser(
1130
+ [user.getUserAccount()],
1131
+ [vaultAccount.spotMarketIndex],
1132
+ vaultAccount,
1133
+ userStats,
1134
+ false,
1135
+ false,
1136
+ false
1137
+ );
1138
+
1139
+ const spotMarket = this.driftClient.getSpotMarketAccount(
1140
+ vaultAccount.spotMarketIndex
1141
+ );
1142
+ if (!spotMarket) {
1143
+ throw new Error(
1144
+ `Spot market ${vaultAccount.spotMarketIndex} not found on driftClient`
1145
+ );
1146
+ }
1147
+
1148
+ const isSolMarket = spotMarket.mint.equals(WRAPPED_SOL_MINT);
1149
+ let userAta = getAssociatedTokenAddressSync(
1150
+ spotMarket.mint,
1151
+ vaultAccount.manager,
1152
+ true
1153
+ );
1154
+
1155
+ const preIxs: TransactionInstruction[] = [];
1156
+ const postIxs: TransactionInstruction[] = [];
1157
+
1158
+ if (isSolMarket) {
1159
+ const { ixs, pubkey } =
1160
+ await this.driftClient.getWrappedSolAccountCreationIxs(ZERO, false);
1161
+
1162
+ userAta = pubkey;
1163
+ preIxs.push(...ixs);
1164
+ postIxs.push(createSyncNativeInstruction(userAta));
1165
+ postIxs.push(
1166
+ createCloseAccountInstruction(
1167
+ userAta,
1168
+ vaultAccount.manager,
1169
+ vaultAccount.manager,
1170
+ []
1171
+ )
1172
+ );
1173
+ } else {
1174
+ const userAtaExists = await this.driftClient.connection.getAccountInfo(
1175
+ userAta
1176
+ );
1177
+ if (userAtaExists === null) {
1178
+ preIxs.push(
1179
+ createAssociatedTokenAccountInstruction(
1180
+ vaultAccount.manager,
1181
+ userAta,
1182
+ vaultAccount.manager,
1183
+ spotMarket.mint
1184
+ )
1185
+ );
1186
+ }
1187
+ }
1188
+
1189
+ const withdrawIx = await this.program.instruction.managerWithdraw({
1190
+ accounts: {
1191
+ vault,
1192
+ manager: vaultAccount.manager,
1193
+ vaultTokenAccount: vaultAccount.tokenAccount,
1194
+ driftUser: await getUserAccountPublicKey(
1195
+ this.driftClient.program.programId,
1196
+ vault
1197
+ ),
1198
+ driftProgram: this.driftClient.program.programId,
1199
+ driftUserStats: getUserStatsAccountPublicKey(
1200
+ this.driftClient.program.programId,
1201
+ vault
1202
+ ),
1203
+ driftState: await this.driftClient.getStatePublicKey(),
1204
+ driftSpotMarketVault: spotMarket.vault,
1205
+ userTokenAccount: userAta,
1206
+ driftSigner: this.driftClient.getStateAccount().signer,
1207
+ tokenProgram: TOKEN_PROGRAM_ID,
1208
+ },
1209
+ remainingAccounts,
1210
+ });
1211
+
1212
+ return [...preIxs, withdrawIx, ...postIxs];
1213
+ }
1214
+
1215
+ public async managerBorrow(
1216
+ vault: PublicKey,
1217
+ borrowSpotMarketIndex: number,
1218
+ borrowAmount: BN,
1219
+ managerTokenAccount?: PublicKey,
1220
+ txParams?: TxParams
1221
+ ): Promise<TransactionSignature> {
1222
+ const ixs = await this.getManagerBorrowIx(
1223
+ vault,
1224
+ borrowSpotMarketIndex,
1225
+ borrowAmount,
1226
+ managerTokenAccount
1227
+ );
1228
+ return await this.createAndSendTxn(ixs, txParams);
1229
+ }
1230
+
1231
+ public async getManagerBorrowIx(
1232
+ vault: PublicKey,
1233
+ borrowSpotMarketIndex: number,
1234
+ borrowAmount: BN,
1235
+ managerTokenAccount?: PublicKey
1236
+ ): Promise<TransactionInstruction[]> {
1237
+ const vaultAccount = await this.program.account.vault.fetch(vault);
1238
+
1239
+ const spotMarket = this.driftClient.getSpotMarketAccount(
1240
+ borrowSpotMarketIndex
1241
+ );
1242
+ if (!spotMarket) {
1243
+ throw new Error(
1244
+ `Spot market ${borrowSpotMarketIndex} not found on driftClient`
1245
+ );
1246
+ }
1247
+
1248
+ if (!managerTokenAccount) {
1249
+ managerTokenAccount = getAssociatedTokenAddressSync(
1250
+ spotMarket.mint,
1251
+ this.driftClient.wallet.publicKey,
1252
+ true
1253
+ );
1254
+ }
1255
+
1256
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
1257
+ const userStatsKey = getUserStatsAccountPublicKey(
1258
+ this.driftClient.program.programId,
1259
+ vault
1260
+ );
1261
+ const userStats = (await (
1262
+ this.driftClient.program as any
1263
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
1264
+ const remainingAccounts = this.getRemainingAccountsForUser(
1265
+ [user.getUserAccount()],
1266
+ [borrowSpotMarketIndex, vaultAccount.spotMarketIndex],
1267
+ vaultAccount,
1268
+ userStats,
1269
+ false,
1270
+ false,
1271
+ false
1272
+ );
1273
+
1274
+ const preIxs = [];
1275
+ const postIxs = [];
1276
+
1277
+ const managerTokenAccountExists =
1278
+ await this.driftClient.connection.getAccountInfo(managerTokenAccount);
1279
+ if (managerTokenAccountExists === null) {
1280
+ preIxs.push(
1281
+ createAssociatedTokenAccountInstruction(
1282
+ vaultAccount.manager,
1283
+ managerTokenAccount,
1284
+ vaultAccount.manager,
1285
+ spotMarket.mint
1286
+ )
1287
+ );
1288
+ }
1289
+
1290
+ const vaultBorrowTokenAccount = getAssociatedTokenAddressSync(
1291
+ spotMarket.mint,
1292
+ vault,
1293
+ true
1294
+ );
1295
+ const vaultBorrowTokenAccountExists =
1296
+ await this.driftClient.connection.getAccountInfo(vaultBorrowTokenAccount);
1297
+ if (vaultBorrowTokenAccountExists === null) {
1298
+ preIxs.push(
1299
+ createAssociatedTokenAccountInstruction(
1300
+ this.driftClient.wallet.publicKey,
1301
+ vaultBorrowTokenAccount,
1302
+ vault,
1303
+ spotMarket.mint
1304
+ )
1305
+ );
1306
+ }
1307
+
1308
+ if (spotMarket.mint.equals(WRAPPED_SOL_MINT)) {
1309
+ postIxs.push(
1310
+ createCloseAccountInstruction(
1311
+ managerTokenAccount,
1312
+ vaultAccount.manager,
1313
+ vaultAccount.manager,
1314
+ []
1315
+ )
1316
+ );
1317
+ }
1318
+
1319
+ return [
1320
+ ...preIxs,
1321
+ await this.program.methods
1322
+ .managerBorrow(borrowSpotMarketIndex, borrowAmount)
1323
+ .accounts({
1324
+ vault,
1325
+ vaultTokenAccount: vaultBorrowTokenAccount,
1326
+ manager: vaultAccount.manager,
1327
+ driftUserStats: userStatsKey,
1328
+ driftUser: vaultAccount.user,
1329
+ driftState: await this.driftClient.getStatePublicKey(),
1330
+ driftSpotMarketVault: spotMarket.vault,
1331
+ driftSigner: this.driftClient.getStateAccount().signer,
1332
+ userTokenAccount: managerTokenAccount,
1333
+ })
1334
+ .remainingAccounts(remainingAccounts)
1335
+ .instruction(),
1336
+ ...postIxs,
1337
+ ];
1338
+ }
1339
+
1340
+ public async managerRepay(
1341
+ vault: PublicKey,
1342
+ repaySpotMarketIndex: number,
1343
+ repayAmount: BN,
1344
+ repayValue: BN | null,
1345
+ managerTokenAccount?: PublicKey,
1346
+ uiTxParams?: TxParams
1347
+ ): Promise<TransactionSignature> {
1348
+ const ixs = await this.getManagerRepayIxs(
1349
+ vault,
1350
+ repaySpotMarketIndex,
1351
+ repayAmount,
1352
+ repayValue,
1353
+ managerTokenAccount
1354
+ );
1355
+ return this.createAndSendTxn(ixs, uiTxParams);
1356
+ }
1357
+
1358
+ /**
1359
+ * Get the instructions for the manager repay transaction
1360
+ * @param vault - The vault to repay
1361
+ * @param repaySpotMarketIndex - The spot market index to repay
1362
+ * @param repayAmount - The amount to repay
1363
+ * @param repayValue - The value of the repay
1364
+ * @param managerTokenAccount - The manager token account to use, if depositing SOL, leave undefined to automatically wrap the SOL
1365
+ * @returns The instructions for the manager repay transaction
1366
+ */
1367
+ public async getManagerRepayIxs(
1368
+ vault: PublicKey,
1369
+ repaySpotMarketIndex: number,
1370
+ repayAmount: BN,
1371
+ repayValue: BN | null,
1372
+ managerTokenAccount?: PublicKey
1373
+ ): Promise<TransactionInstruction[]> {
1374
+ const vaultAccount = await this.program.account.vault.fetch(vault);
1375
+ const spotMarket =
1376
+ this.driftClient.getSpotMarketAccount(repaySpotMarketIndex);
1377
+ if (!spotMarket) {
1378
+ throw new Error(
1379
+ `Spot market ${repaySpotMarketIndex} not found on driftClient`
1380
+ );
1381
+ }
1382
+ const isSolMarket = spotMarket.mint.equals(WRAPPED_SOL_MINT);
1383
+
1384
+ const preIxs: TransactionInstruction[] = [];
1385
+ const postIxs: TransactionInstruction[] = [];
1386
+ let createdWsolAccount = false;
1387
+
1388
+ if (!managerTokenAccount) {
1389
+ if (isSolMarket) {
1390
+ // create wSOL
1391
+ const { ixs, pubkey } =
1392
+ await this.driftClient.getWrappedSolAccountCreationIxs(
1393
+ repayAmount,
1394
+ true
1395
+ );
1396
+ preIxs.push(...ixs);
1397
+ managerTokenAccount = pubkey;
1398
+ createdWsolAccount = true;
1399
+ } else {
1400
+ managerTokenAccount = getAssociatedTokenAddressSync(
1401
+ spotMarket.mint,
1402
+ vaultAccount.manager,
1403
+ true
1404
+ );
1405
+ }
1406
+ }
1407
+
1408
+ const vaultRepayTokenAccount = getAssociatedTokenAddressSync(
1409
+ spotMarket.mint,
1410
+ vault,
1411
+ true
1412
+ );
1413
+ const vaultRepayTokenAccountExists =
1414
+ await this.driftClient.connection.getAccountInfo(vaultRepayTokenAccount);
1415
+ if (vaultRepayTokenAccountExists === null) {
1416
+ preIxs.push(
1417
+ createAssociatedTokenAccountInstruction(
1418
+ this.driftClient.wallet.publicKey,
1419
+ vaultRepayTokenAccount,
1420
+ vault,
1421
+ spotMarket.mint
1422
+ )
1423
+ );
1424
+ }
1425
+
1426
+ if (createdWsolAccount) {
1427
+ postIxs.push(
1428
+ createCloseAccountInstruction(
1429
+ managerTokenAccount,
1430
+ vaultAccount.manager,
1431
+ vaultAccount.manager,
1432
+ []
1433
+ )
1434
+ );
1435
+ }
1436
+
1437
+ const userStatsKey = getUserStatsAccountPublicKey(
1438
+ this.driftClient.program.programId,
1439
+ vault
1440
+ );
1441
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
1442
+ const userStats = (await (
1443
+ this.driftClient.program as any
1444
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
1445
+ const remainingAccounts = this.getRemainingAccountsForUser(
1446
+ [user.getUserAccount()],
1447
+ [repaySpotMarketIndex, vaultAccount.spotMarketIndex],
1448
+ vaultAccount,
1449
+ userStats,
1450
+ false,
1451
+ false,
1452
+ false
1453
+ );
1454
+
1455
+ return [
1456
+ ...preIxs,
1457
+ await this.program.methods
1458
+ .managerRepay(repaySpotMarketIndex, repayAmount, repayValue)
1459
+ .accounts({
1460
+ vault,
1461
+ vaultTokenAccount: vaultRepayTokenAccount,
1462
+ manager: vaultAccount.manager,
1463
+ driftUserStats: userStatsKey,
1464
+ driftUser: vaultAccount.user,
1465
+ driftState: await this.driftClient.getStatePublicKey(),
1466
+ driftSpotMarketVault: spotMarket.vault,
1467
+ driftSigner: this.driftClient.getStateAccount().signer,
1468
+ userTokenAccount: managerTokenAccount,
1469
+ })
1470
+ .remainingAccounts(remainingAccounts)
1471
+ .instruction(),
1472
+ ...postIxs,
1473
+ ];
1474
+ }
1475
+
1476
+ public async managerUpdateBorrow(
1477
+ vault: PublicKey,
1478
+ newBorrowValue: BN,
1479
+ txParams?: TxParams
1480
+ ): Promise<TransactionSignature> {
1481
+ const ix = await this.getManagerUpdateBorrowIx(vault, newBorrowValue);
1482
+ return await this.createAndSendTxn([ix], txParams);
1483
+ }
1484
+
1485
+ public async getManagerUpdateBorrowIx(
1486
+ vault: PublicKey,
1487
+ newBorrowValue: BN
1488
+ ): Promise<TransactionInstruction> {
1489
+ const vaultAccount = await this.program.account.vault.fetch(vault);
1490
+
1491
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
1492
+ const userStatsKey = getUserStatsAccountPublicKey(
1493
+ this.driftClient.program.programId,
1494
+ vault
1495
+ );
1496
+ const userStats = (await (
1497
+ this.driftClient.program as any
1498
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
1499
+ const remainingAccounts = this.getRemainingAccountsForUser(
1500
+ [user.getUserAccount()],
1501
+ [],
1502
+ vaultAccount,
1503
+ userStats,
1504
+ false,
1505
+ false,
1506
+ false
1507
+ );
1508
+
1509
+ return this.program.instruction.managerUpdateBorrow(newBorrowValue, {
1510
+ accounts: {
1511
+ vault,
1512
+ manager: vaultAccount.manager,
1513
+ driftUserStats: userStatsKey,
1514
+ driftUser: vaultAccount.user,
1515
+ },
1516
+ remainingAccounts,
1517
+ });
1518
+ }
1519
+
1520
+ public async managerUpdateVault(
1521
+ vault: PublicKey,
1522
+ params: {
1523
+ redeemPeriod: BN | null;
1524
+ maxTokens: BN | null;
1525
+ managementFee: BN | null;
1526
+ minDepositAmount: BN | null;
1527
+ profitShare: number | null;
1528
+ hurdleRate: number | null;
1529
+ permissioned: boolean | null;
1530
+ },
1531
+ uiTxParams?: TxParams
1532
+ ): Promise<TransactionSignature> {
1533
+ const ix = await this.getManagerUpdateVaultIx(vault, params);
1534
+ return this.createAndSendTxn([ix], uiTxParams);
1535
+ }
1536
+
1537
+ public async getManagerUpdateVaultIx(
1538
+ vault: PublicKey,
1539
+ params: {
1540
+ redeemPeriod: BN | null;
1541
+ maxTokens: BN | null;
1542
+ managementFee: BN | null;
1543
+ minDepositAmount: BN | null;
1544
+ profitShare: number | null;
1545
+ hurdleRate: number | null;
1546
+ permissioned: boolean | null;
1547
+ }
1548
+ ): Promise<TransactionInstruction> {
1549
+ const vaultAccount = await this.program.account.vault.fetch(vault);
1550
+ return this.program.instruction.updateVault(params, {
1551
+ accounts: {
1552
+ vault,
1553
+ manager: vaultAccount.manager,
1554
+ },
1555
+ });
1556
+ }
1557
+
1558
+ public async managerUpdateVaultManager(
1559
+ vault: PublicKey,
1560
+ manager: PublicKey,
1561
+ uiTxParams?: TxParams
1562
+ ): Promise<TransactionSignature> {
1563
+ const ix = await this.getManagerUpdateVaultManagerIx(vault, manager);
1564
+ return this.createAndSendTxn([ix], uiTxParams);
1565
+ }
1566
+
1567
+ public async getManagerUpdateVaultManagerIx(
1568
+ vault: PublicKey,
1569
+ manager: PublicKey
1570
+ ): Promise<TransactionInstruction> {
1571
+ const vaultAccount = await this.program.account.vault.fetch(vault);
1572
+ return this.program.instruction.updateVaultManager(manager, {
1573
+ accounts: {
1574
+ vault,
1575
+ manager: vaultAccount.manager,
1576
+ },
1577
+ });
1578
+ }
1579
+
1580
+ public async applyProfitShare(
1581
+ vault: PublicKey,
1582
+ vaultDepositor: PublicKey,
1583
+ uiTxParams?: TxParams
1584
+ ): Promise<TransactionSignature> {
1585
+ const ix = await this.getApplyProfitShareIx(vault, vaultDepositor);
1586
+ return this.createAndSendTxn([ix], uiTxParams);
1587
+ }
1588
+
1589
+ public async getApplyProfitShareIx(
1590
+ vault: PublicKey,
1591
+ vaultDepositor: PublicKey
1592
+ ): Promise<TransactionInstruction> {
1593
+ const vaultAccount = await this.program.account.vault.fetch(vault);
1594
+
1595
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
1596
+
1597
+ const spotMarket = this.driftClient.getSpotMarketAccount(
1598
+ vaultAccount.spotMarketIndex
1599
+ );
1600
+ if (!spotMarket) {
1601
+ throw new Error(
1602
+ `Spot market ${vaultAccount.spotMarketIndex} not found on driftClient`
1603
+ );
1604
+ }
1605
+
1606
+ const userStatsKey = getUserStatsAccountPublicKey(
1607
+ this.driftClient.program.programId,
1608
+ vault
1609
+ );
1610
+ const userStats = (await (
1611
+ this.driftClient.program as any
1612
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
1613
+ const remainingAccounts = this.getRemainingAccountsForUser(
1614
+ [user.getUserAccount()],
1615
+ [vaultAccount.spotMarketIndex],
1616
+ vaultAccount,
1617
+ userStats,
1618
+ false,
1619
+ false,
1620
+ false
1621
+ );
1622
+
1623
+ const accounts = {
1624
+ vault,
1625
+ vaultDepositor,
1626
+ manager: vaultAccount.manager,
1627
+ driftUserStats: getUserStatsAccountPublicKey(
1628
+ this.driftClient.program.programId,
1629
+ vault
1630
+ ),
1631
+ driftUser: await getUserAccountPublicKey(
1632
+ this.driftClient.program.programId,
1633
+ vault
1634
+ ),
1635
+ driftState: await this.driftClient.getStatePublicKey(),
1636
+ driftSigner: this.driftClient.getStateAccount().signer,
1637
+ driftProgram: this.driftClient.program.programId,
1638
+ };
1639
+
1640
+ return this.program.instruction.applyProfitShare({
1641
+ accounts: {
1642
+ ...accounts,
1643
+ },
1644
+ remainingAccounts,
1645
+ });
1646
+ }
1647
+
1648
+ public async getApplyRebaseTokenizedDepositorIx(
1649
+ vault: PublicKey,
1650
+ tokenizedVaultDepositor: PublicKey
1651
+ ): Promise<TransactionInstruction> {
1652
+ const vaultAccount = await this.program.account.vault.fetch(vault);
1653
+
1654
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
1655
+
1656
+ const spotMarket = this.driftClient.getSpotMarketAccount(
1657
+ vaultAccount.spotMarketIndex
1658
+ );
1659
+ if (!spotMarket) {
1660
+ throw new Error(
1661
+ `Spot market ${vaultAccount.spotMarketIndex} not found on driftClient`
1662
+ );
1663
+ }
1664
+
1665
+ const userStatsKey = getUserStatsAccountPublicKey(
1666
+ this.driftClient.program.programId,
1667
+ vault
1668
+ );
1669
+ const userStats = (await (
1670
+ this.driftClient.program as any
1671
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
1672
+ const remainingAccounts = this.getRemainingAccountsForUser(
1673
+ [user.getUserAccount()],
1674
+ [vaultAccount.spotMarketIndex],
1675
+ vaultAccount,
1676
+ userStats,
1677
+ false,
1678
+ true,
1679
+ true
1680
+ );
1681
+
1682
+ const accounts = {
1683
+ vault,
1684
+ tokenizedVaultDepositor,
1685
+ driftUser: await getUserAccountPublicKey(
1686
+ this.driftClient.program.programId,
1687
+ vault
1688
+ ),
1689
+ driftState: await this.driftClient.getStatePublicKey(),
1690
+ driftSigner: this.driftClient.getStateAccount().signer,
1691
+ driftProgram: this.driftClient.program.programId,
1692
+ };
1693
+
1694
+ return this.program.instruction.applyRebaseTokenizedDepositor({
1695
+ accounts: {
1696
+ ...accounts,
1697
+ },
1698
+ remainingAccounts,
1699
+ });
1700
+ }
1701
+
1702
+ public async applyRebase(
1703
+ vault: PublicKey,
1704
+ vaultDepositor: PublicKey
1705
+ ): Promise<TransactionSignature> {
1706
+ return await this.createAndSendTxn([
1707
+ await this.getApplyRebaseIx(vault, vaultDepositor),
1708
+ ]);
1709
+ }
1710
+
1711
+ public async getApplyRebaseIx(
1712
+ vault: PublicKey,
1713
+ vaultDepositor: PublicKey
1714
+ ): Promise<TransactionInstruction> {
1715
+ const vaultAccount = await this.program.account.vault.fetch(vault);
1716
+
1717
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
1718
+
1719
+ const spotMarket = this.driftClient.getSpotMarketAccount(
1720
+ vaultAccount.spotMarketIndex
1721
+ );
1722
+ if (!spotMarket) {
1723
+ throw new Error(
1724
+ `Spot market ${vaultAccount.spotMarketIndex} not found on driftClient`
1725
+ );
1726
+ }
1727
+
1728
+ const userStatsKey = getUserStatsAccountPublicKey(
1729
+ this.driftClient.program.programId,
1730
+ vault
1731
+ );
1732
+ const userStats = (await (
1733
+ this.driftClient.program as any
1734
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
1735
+ const remainingAccounts = this.getRemainingAccountsForUser(
1736
+ [user.getUserAccount()],
1737
+ [vaultAccount.spotMarketIndex],
1738
+ vaultAccount,
1739
+ userStats,
1740
+ false,
1741
+ true,
1742
+ true
1743
+ );
1744
+
1745
+ const accounts = {
1746
+ vault,
1747
+ vaultDepositor,
1748
+ driftUser: await getUserAccountPublicKey(
1749
+ this.driftClient.program.programId,
1750
+ vault
1751
+ ),
1752
+ driftState: await this.driftClient.getStatePublicKey(),
1753
+ driftSigner: this.driftClient.getStateAccount().signer,
1754
+ driftProgram: this.driftClient.program.programId,
1755
+ };
1756
+
1757
+ return this.program.instruction.applyRebase({
1758
+ accounts: {
1759
+ ...accounts,
1760
+ },
1761
+ remainingAccounts,
1762
+ });
1763
+ }
1764
+
1765
+ public async applyRebaseTokenizedDepositor(
1766
+ vault: PublicKey,
1767
+ tokenizedVaultDepositor: PublicKey
1768
+ ): Promise<TransactionSignature> {
1769
+ return await this.createAndSendTxn([
1770
+ await this.getApplyRebaseTokenizedDepositorIx(
1771
+ vault,
1772
+ tokenizedVaultDepositor
1773
+ ),
1774
+ ]);
1775
+ }
1776
+
1777
+ private createInitVaultDepositorIx(
1778
+ vault: PublicKey,
1779
+ authority?: PublicKey,
1780
+ payer?: PublicKey
1781
+ ) {
1782
+ const vaultDepositor = getVaultDepositorAddressSync(
1783
+ this.program.programId,
1784
+ vault,
1785
+ authority || this.driftClient.wallet.publicKey
1786
+ );
1787
+
1788
+ const accounts = {
1789
+ vaultDepositor,
1790
+ vault,
1791
+ authority: authority || this.driftClient.wallet.publicKey,
1792
+ };
1793
+
1794
+ const initIx = this.program.instruction.initializeVaultDepositor({
1795
+ accounts: {
1796
+ ...accounts,
1797
+ payer: payer || authority || this.driftClient.wallet.publicKey,
1798
+ rent: SYSVAR_RENT_PUBKEY,
1799
+ systemProgram: SystemProgram.programId,
1800
+ },
1801
+ });
1802
+
1803
+ return initIx;
1804
+ }
1805
+
1806
+ /**
1807
+ * Initializes the vault depositor account. This account is used to deposit funds into a vault.
1808
+ * @param vault the vault address to deposit into
1809
+ * @param authority the authority allowed to make deposits into the vault
1810
+ * @returns
1811
+ */
1812
+ public async initializeVaultDepositor(
1813
+ vault: PublicKey,
1814
+ authority?: PublicKey,
1815
+ payer?: PublicKey,
1816
+ uiTxParams?: TxParams
1817
+ ): Promise<TransactionSignature> {
1818
+ const initIx = this.createInitVaultDepositorIx(vault, authority, payer);
1819
+ return await this.createAndSendTxn([initIx], uiTxParams);
1820
+ }
1821
+
1822
+ public async initializeTokenizedVaultDepositor(
1823
+ params: {
1824
+ vault: PublicKey;
1825
+ tokenName: string;
1826
+ tokenSymbol: string;
1827
+ tokenUri: string;
1828
+ decimals?: number;
1829
+ sharesBase?: number;
1830
+ },
1831
+ uiTxParams?: TxParams
1832
+ ): Promise<TransactionSignature> {
1833
+ if (!this.metaplex) {
1834
+ throw new Error(
1835
+ 'Metaplex instance is required when constructing VaultClient to initialize a tokenized vault depositor'
1836
+ );
1837
+ }
1838
+
1839
+ let spotMarketDecimals = 6;
1840
+ let sharesBase = 0;
1841
+ if (params.decimals === undefined || params.sharesBase === undefined) {
1842
+ const vault = await this.program.account.vault.fetch(params.vault);
1843
+ const spotMarketAccount = this.driftClient.getSpotMarketAccount(
1844
+ vault.spotMarketIndex
1845
+ );
1846
+ if (!spotMarketAccount) {
1847
+ throw new Error(
1848
+ `DriftClient failed to load vault's spot market (marketIndex: ${vault.spotMarketIndex})`
1849
+ );
1850
+ }
1851
+ spotMarketDecimals = spotMarketAccount.decimals;
1852
+ sharesBase = vault.sharesBase;
1853
+ }
1854
+
1855
+ const mintAddress = getTokenizedVaultMintAddressSync(
1856
+ this.program.programId,
1857
+ params.vault,
1858
+ sharesBase
1859
+ );
1860
+
1861
+ const vaultAccount = await this.program.account.vault.fetch(params.vault);
1862
+
1863
+ const accounts = {
1864
+ vault: params.vault,
1865
+ vaultDepositor: getTokenizedVaultAddressSync(
1866
+ this.program.programId,
1867
+ params.vault,
1868
+ sharesBase
1869
+ ),
1870
+ mintAccount: mintAddress,
1871
+ metadataAccount: this.metaplex.nfts().pdas().metadata({
1872
+ mint: mintAddress,
1873
+ }),
1874
+ tokenMetadataProgram: this.metaplex.programs().getTokenMetadata().address,
1875
+ payer: vaultAccount.manager,
1876
+ };
1877
+
1878
+ const vaultTokenAta = getAssociatedTokenAddressSync(
1879
+ mintAddress,
1880
+ params.vault,
1881
+ true
1882
+ );
1883
+ const createAtaIx = createAssociatedTokenAccountInstruction(
1884
+ vaultAccount.manager,
1885
+ vaultTokenAta,
1886
+ params.vault,
1887
+ mintAddress
1888
+ );
1889
+
1890
+ return await this.createAndSendTxn(
1891
+ [
1892
+ await this.program.methods
1893
+ .initializeTokenizedVaultDepositor({
1894
+ ...params,
1895
+ decimals: params.decimals ?? spotMarketDecimals,
1896
+ })
1897
+ .accounts(accounts)
1898
+ .instruction(),
1899
+ createAtaIx,
1900
+ ],
1901
+ uiTxParams
1902
+ );
1903
+ }
1904
+
1905
+ public async createTokenizeSharesIx(
1906
+ vaultDepositor: PublicKey,
1907
+ amount: BN,
1908
+ unit: WithdrawUnit,
1909
+ mint?: PublicKey
1910
+ ): Promise<TransactionInstruction[]> {
1911
+ const vaultDepositorAccount =
1912
+ await this.program.account.vaultDepositor.fetch(vaultDepositor);
1913
+ const vaultAccount = await this.program.account.vault.fetch(
1914
+ vaultDepositorAccount.vault
1915
+ );
1916
+
1917
+ mint =
1918
+ mint ??
1919
+ getTokenizedVaultMintAddressSync(
1920
+ this.program.programId,
1921
+ vaultDepositorAccount.vault,
1922
+ vaultAccount.sharesBase
1923
+ );
1924
+
1925
+ const userAta = getAssociatedTokenAddressSync(
1926
+ mint,
1927
+ this.driftClient.wallet.publicKey,
1928
+ true
1929
+ );
1930
+
1931
+ const ixs: TransactionInstruction[] = [];
1932
+
1933
+ const userAtaExists = await this.driftClient.connection.getAccountInfo(
1934
+ userAta
1935
+ );
1936
+ if (userAtaExists === null) {
1937
+ ixs.push(
1938
+ createAssociatedTokenAccountInstruction(
1939
+ this.driftClient.wallet.publicKey,
1940
+ userAta,
1941
+ this.driftClient.wallet.publicKey,
1942
+ mint
1943
+ )
1944
+ );
1945
+ }
1946
+
1947
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
1948
+ const userStatsKey = getUserStatsAccountPublicKey(
1949
+ this.driftClient.program.programId,
1950
+ vaultDepositorAccount.vault
1951
+ );
1952
+ const userStats = (await (
1953
+ this.driftClient.program as any
1954
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
1955
+ const remainingAccounts = this.getRemainingAccountsForUser(
1956
+ [user.getUserAccount()],
1957
+ [vaultAccount.spotMarketIndex],
1958
+ vaultAccount,
1959
+ userStats,
1960
+ false,
1961
+ true,
1962
+ true
1963
+ );
1964
+
1965
+ ixs.push(
1966
+ await this.program.methods
1967
+ // anchor idl bug: https://github.com/coral-xyz/anchor/issues/2914
1968
+ // @ts-ignore args tuple vs anchor 0.32 IDL recursion limit
1969
+ .tokenizeShares(amount, unit)
1970
+ // `mint` is auto-resolved from IDL seeds, but anchor 1.0's IDL
1971
+ // generator can't encode `vault.shares_base.to_string().as_bytes()`
1972
+ // and emits broken seeds. Pass it explicitly to override.
1973
+ .accountsPartial({
1974
+ authority: this.driftClient.wallet.publicKey,
1975
+ vault: vaultDepositorAccount.vault,
1976
+ tokenizedVaultDepositor: getTokenizedVaultAddressSync(
1977
+ this.program.programId,
1978
+ vaultDepositorAccount.vault,
1979
+ vaultAccount.sharesBase
1980
+ ),
1981
+ mint,
1982
+ userTokenAccount: userAta,
1983
+ driftUser: vaultAccount.user,
1984
+ })
1985
+ .remainingAccounts(remainingAccounts)
1986
+ .instruction()
1987
+ );
1988
+
1989
+ return ixs;
1990
+ }
1991
+
1992
+ public async tokenizeShares(
1993
+ vaultDepositor: PublicKey,
1994
+ amount: BN,
1995
+ unit: WithdrawUnit,
1996
+ mint?: PublicKey,
1997
+ txParams?: TxParams
1998
+ ): Promise<TransactionSignature> {
1999
+ const ixs = await this.createTokenizeSharesIx(
2000
+ vaultDepositor,
2001
+ amount,
2002
+ unit,
2003
+ mint
2004
+ );
2005
+ return await this.createAndSendTxn(ixs, txParams);
2006
+ }
2007
+
2008
+ public async createTransferVaultDepositorSharesIx(
2009
+ fromVaultDepositor: PublicKey,
2010
+ toVaultDepositor: PublicKey,
2011
+ amount: BN,
2012
+ withdrawUnit: WithdrawUnit
2013
+ ): Promise<TransactionInstruction[]> {
2014
+ const vaultDepositorAccount =
2015
+ await this.program.account.vaultDepositor.fetch(fromVaultDepositor);
2016
+ const vaultAccount = await this.program.account.vault.fetch(
2017
+ vaultDepositorAccount.vault
2018
+ );
2019
+
2020
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
2021
+ const userStatsKey = getUserStatsAccountPublicKey(
2022
+ this.driftClient.program.programId,
2023
+ vaultDepositorAccount.vault
2024
+ );
2025
+ const userStats = (await (
2026
+ this.driftClient.program as any
2027
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
2028
+ const remainingAccounts = this.getRemainingAccountsForUser(
2029
+ [user.getUserAccount()],
2030
+ [vaultAccount.spotMarketIndex],
2031
+ vaultAccount,
2032
+ userStats,
2033
+ false,
2034
+ true,
2035
+ true
2036
+ );
2037
+
2038
+ const ixs: TransactionInstruction[] = [];
2039
+
2040
+ ixs.push(
2041
+ await this.program.methods
2042
+ // @ts-ignore args tuple vs anchor 0.32 IDL recursion limit
2043
+ .transferVaultDepositorShares(amount, withdrawUnit)
2044
+ .accounts({
2045
+ vault: vaultDepositorAccount.vault,
2046
+ authority: this.driftClient.wallet.publicKey,
2047
+ toVaultDepositor,
2048
+ driftUser: vaultAccount.user,
2049
+ })
2050
+ .remainingAccounts(remainingAccounts)
2051
+ .instruction()
2052
+ );
2053
+
2054
+ return ixs;
2055
+ }
2056
+
2057
+ public async transferVaultDepositorShares(
2058
+ fromVaultDepositor: PublicKey,
2059
+ toVaultDepositor: PublicKey,
2060
+ amount: BN,
2061
+ withdrawUnit: WithdrawUnit,
2062
+ txParams?: TxParams
2063
+ ): Promise<TransactionSignature> {
2064
+ const ixs = await this.createTransferVaultDepositorSharesIx(
2065
+ fromVaultDepositor,
2066
+ toVaultDepositor,
2067
+ amount,
2068
+ withdrawUnit
2069
+ );
2070
+ return await this.createAndSendTxn(ixs, txParams);
2071
+ }
2072
+
2073
+ public async createRedeemTokensIx(
2074
+ vaultDepositor: PublicKey,
2075
+ tokensToBurn: BN,
2076
+ sharesBase?: number
2077
+ ): Promise<TransactionInstruction> {
2078
+ const vaultDepositorAccount =
2079
+ await this.program.account.vaultDepositor.fetch(vaultDepositor);
2080
+ const vaultAccount = await this.program.account.vault.fetch(
2081
+ vaultDepositorAccount.vault
2082
+ );
2083
+
2084
+ const mint = getTokenizedVaultMintAddressSync(
2085
+ this.program.programId,
2086
+ vaultDepositorAccount.vault,
2087
+ sharesBase ?? vaultAccount.sharesBase
2088
+ );
2089
+
2090
+ const userAta = getAssociatedTokenAddressSync(
2091
+ mint,
2092
+ this.driftClient.wallet.publicKey,
2093
+ true
2094
+ );
2095
+
2096
+ const vaultTokenAta = getAssociatedTokenAddressSync(
2097
+ mint,
2098
+ vaultDepositorAccount.vault,
2099
+ true
2100
+ );
2101
+
2102
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
2103
+ const userStatsKey = getUserStatsAccountPublicKey(
2104
+ this.driftClient.program.programId,
2105
+ vaultDepositorAccount.vault
2106
+ );
2107
+ const userStats = (await (
2108
+ this.driftClient.program as any
2109
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
2110
+ const remainingAccounts = this.getRemainingAccountsForUser(
2111
+ [user.getUserAccount()],
2112
+ [vaultAccount.spotMarketIndex],
2113
+ vaultAccount,
2114
+ userStats,
2115
+ false,
2116
+ true,
2117
+ true
2118
+ );
2119
+
2120
+ return await this.program.methods
2121
+ .redeemTokens(tokensToBurn)
2122
+ .accounts({
2123
+ authority: this.driftClient.wallet.publicKey,
2124
+ vault: vaultDepositorAccount.vault,
2125
+ tokenizedVaultDepositor: getTokenizedVaultAddressSync(
2126
+ this.program.programId,
2127
+ vaultDepositorAccount.vault,
2128
+ sharesBase ?? vaultAccount.sharesBase
2129
+ ),
2130
+ mint,
2131
+ userTokenAccount: userAta,
2132
+ vaultTokenAccount: vaultTokenAta,
2133
+ driftUser: vaultAccount.user,
2134
+ })
2135
+ .remainingAccounts(remainingAccounts)
2136
+ .instruction();
2137
+ }
2138
+
2139
+ /**
2140
+ * Redeems tokens from the vault.
2141
+ * @param vaultDepositor
2142
+ * @param tokensToBurn
2143
+ * @param mint optionally provide a mint, or infer the mint from the current vault share base
2144
+ * @param txParams
2145
+ * @returns
2146
+ */
2147
+ public async redeemTokens(
2148
+ vaultDepositor: PublicKey,
2149
+ tokensToBurn: BN,
2150
+ sharesBase?: number,
2151
+ txParams?: TxParams
2152
+ ): Promise<TransactionSignature> {
2153
+ const ix = await this.createRedeemTokensIx(
2154
+ vaultDepositor,
2155
+ tokensToBurn,
2156
+ sharesBase
2157
+ );
2158
+ return await this.createAndSendTxn([ix], txParams);
2159
+ }
2160
+
2161
+ public async prepDepositTx(
2162
+ vaultDepositor: PublicKey,
2163
+ amount: BN,
2164
+ initVaultDepositor?: {
2165
+ authority: PublicKey;
2166
+ vault: PublicKey;
2167
+ },
2168
+ depositTokenAccount?: PublicKey
2169
+ ) {
2170
+ let vaultPubKey: PublicKey;
2171
+ if (initVaultDepositor) {
2172
+ vaultPubKey = initVaultDepositor.vault;
2173
+ } else {
2174
+ const vaultDepositorAccount =
2175
+ await this.program.account.vaultDepositor.fetch(vaultDepositor);
2176
+ vaultPubKey = vaultDepositorAccount.vault;
2177
+ }
2178
+
2179
+ const vaultAccount = await this.program.account.vault.fetch(vaultPubKey);
2180
+
2181
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
2182
+ const userStatsKey = getUserStatsAccountPublicKey(
2183
+ this.driftClient.program.programId,
2184
+ vaultPubKey
2185
+ );
2186
+ const userStats = (await (
2187
+ this.driftClient.program as any
2188
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
2189
+ const remainingAccounts = this.getRemainingAccountsForUser(
2190
+ [user.getUserAccount()],
2191
+ [vaultAccount.spotMarketIndex],
2192
+ vaultAccount,
2193
+ userStats,
2194
+ false,
2195
+ false,
2196
+ false
2197
+ );
2198
+
2199
+ const driftStateKey = await this.driftClient.getStatePublicKey();
2200
+
2201
+ const spotMarket = this.driftClient.getSpotMarketAccount(
2202
+ vaultAccount.spotMarketIndex
2203
+ );
2204
+ if (!spotMarket) {
2205
+ throw new Error(
2206
+ `Spot market ${vaultAccount.spotMarketIndex} not found on driftClient`
2207
+ );
2208
+ }
2209
+
2210
+ const nonWSolUserTokenAccount =
2211
+ depositTokenAccount ??
2212
+ getAssociatedTokenAddressSync(
2213
+ spotMarket.mint,
2214
+ this.driftClient.wallet.publicKey,
2215
+ true
2216
+ );
2217
+
2218
+ const { userTokenAccount, preIxs, postIxs } = await this.handleWSolMovement(
2219
+ amount,
2220
+ spotMarket,
2221
+ nonWSolUserTokenAccount
2222
+ );
2223
+
2224
+ const accounts = {
2225
+ vault: vaultPubKey,
2226
+ vaultDepositor,
2227
+ vaultTokenAccount: vaultAccount.tokenAccount,
2228
+ driftUserStats: userStatsKey,
2229
+ driftUser: vaultAccount.user,
2230
+ driftState: driftStateKey,
2231
+ driftSpotMarketVault: spotMarket.vault,
2232
+ userTokenAccount: userTokenAccount,
2233
+ driftProgram: this.driftClient.program.programId,
2234
+ tokenProgram: TOKEN_PROGRAM_ID,
2235
+ };
2236
+
2237
+ return {
2238
+ vaultAccount,
2239
+ accounts,
2240
+ remainingAccounts,
2241
+ preIxs,
2242
+ postIxs,
2243
+ };
2244
+ }
2245
+
2246
+ /**
2247
+ * Creates a transaction to deposit funds into the specified vault.
2248
+ * Uses the associated token account of the vault depositor authority and spot market mint,
2249
+ * and assumes it exists before calling this function.
2250
+ * @param vaultDepositor
2251
+ * @param amount
2252
+ * @param initVaultDepositor If true, will initialize the vault depositor account
2253
+ * @returns transaction
2254
+ */
2255
+ public async createDepositTx(
2256
+ vaultDepositor: PublicKey,
2257
+ amount: BN,
2258
+ initVaultDepositor?: {
2259
+ authority: PublicKey;
2260
+ vault: PublicKey;
2261
+ },
2262
+ txParams?: TxParams,
2263
+ userTokenAccount?: PublicKey
2264
+ ): Promise<VersionedTransaction> {
2265
+ const { vaultAccount, accounts, remainingAccounts, preIxs, postIxs } =
2266
+ await this.prepDepositTx(
2267
+ vaultDepositor,
2268
+ amount,
2269
+ initVaultDepositor,
2270
+ userTokenAccount
2271
+ );
2272
+
2273
+ const ixs: TransactionInstruction[] = [];
2274
+
2275
+ if (initVaultDepositor) {
2276
+ ixs.push(
2277
+ this.createInitVaultDepositorIx(
2278
+ vaultAccount.pubkey,
2279
+ initVaultDepositor.authority
2280
+ )
2281
+ );
2282
+ }
2283
+
2284
+ const depositIx = await this.program.methods
2285
+ .deposit(amount)
2286
+ .accounts({
2287
+ authority: this.driftClient.wallet.publicKey,
2288
+ ...accounts,
2289
+ })
2290
+ .remainingAccounts(remainingAccounts)
2291
+ .instruction();
2292
+ ixs.push(...preIxs);
2293
+ ixs.push(depositIx);
2294
+ ixs.push(...postIxs);
2295
+
2296
+ if (txParams?.noLut ? txParams.noLut : false) {
2297
+ return await this.createTxnNoLut(ixs, txParams);
2298
+ } else {
2299
+ return await this.createTxn(ixs, txParams);
2300
+ }
2301
+ }
2302
+
2303
+ /**
2304
+ * Depositor funds into the specified vault.
2305
+ * @param vaultDepositor
2306
+ * @param amount
2307
+ * @param initVaultDepositor If true, will initialize the vault depositor account
2308
+ * @param txParams
2309
+ * @returns
2310
+ */
2311
+ public async deposit(
2312
+ vaultDepositor: PublicKey,
2313
+ amount: BN,
2314
+ initVaultDepositor?: {
2315
+ authority: PublicKey;
2316
+ vault: PublicKey;
2317
+ },
2318
+ txParams?: TxParams,
2319
+ userTokenAccount?: PublicKey
2320
+ ): Promise<TransactionSignature> {
2321
+ const depositTxn = await this.createDepositTx(
2322
+ vaultDepositor,
2323
+ amount,
2324
+ initVaultDepositor,
2325
+ txParams,
2326
+ userTokenAccount
2327
+ );
2328
+
2329
+ return this.sendTxn(depositTxn, txParams?.simulateTransaction);
2330
+ }
2331
+
2332
+ public async requestWithdraw(
2333
+ vaultDepositor: PublicKey,
2334
+ amount: BN,
2335
+ withdrawUnit: WithdrawUnit,
2336
+ txParams?: TxParams
2337
+ ): Promise<TransactionSignature> {
2338
+ const ixs = await this.getRequestWithdrawIx(
2339
+ vaultDepositor,
2340
+ amount,
2341
+ withdrawUnit,
2342
+ txParams?.oracleFeedsToCrank
2343
+ );
2344
+ return await this.createAndSendTxn(ixs, txParams);
2345
+ }
2346
+
2347
+ public async getRequestWithdrawIx(
2348
+ vaultDepositor: PublicKey,
2349
+ amount: BN,
2350
+ withdrawUnit: WithdrawUnit,
2351
+ oracleFeedsToCrank?: TxParams['oracleFeedsToCrank']
2352
+ ): Promise<TransactionInstruction[]> {
2353
+ const vaultDepositorAccount =
2354
+ await this.program.account.vaultDepositor.fetch(vaultDepositor);
2355
+ const vaultAccount = await this.program.account.vault.fetch(
2356
+ vaultDepositorAccount.vault
2357
+ );
2358
+
2359
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
2360
+ const userStatsKey = getUserStatsAccountPublicKey(
2361
+ this.driftClient.program.programId,
2362
+ vaultDepositorAccount.vault
2363
+ );
2364
+ const userStats = (await (
2365
+ this.driftClient.program as any
2366
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
2367
+ const remainingAccounts = this.getRemainingAccountsForUser(
2368
+ [user.getUserAccount()],
2369
+ [vaultAccount.spotMarketIndex],
2370
+ vaultAccount,
2371
+ userStats,
2372
+ false,
2373
+ false,
2374
+ false
2375
+ );
2376
+
2377
+ const accounts = {
2378
+ vault: vaultDepositorAccount.vault,
2379
+ vaultDepositor,
2380
+ driftUser: vaultAccount.user,
2381
+ driftUserStats: userStatsKey,
2382
+ };
2383
+
2384
+ const oracleFeedsToCrankIxs = await this.getOracleFeedsToCrankIxs(
2385
+ oracleFeedsToCrank
2386
+ );
2387
+
2388
+ const requestWithdrawIx = this.program.instruction.requestWithdraw(
2389
+ // @ts-ignore
2390
+ amount,
2391
+ withdrawUnit,
2392
+ {
2393
+ accounts: {
2394
+ authority: this.driftClient.wallet.publicKey,
2395
+ ...accounts,
2396
+ },
2397
+ remainingAccounts,
2398
+ }
2399
+ );
2400
+
2401
+ return [...oracleFeedsToCrankIxs, requestWithdrawIx];
2402
+ }
2403
+
2404
+ public async withdraw(
2405
+ vaultDepositor: PublicKey,
2406
+ txParams?: TxParams
2407
+ ): Promise<TransactionSignature> {
2408
+ const ixs = await this.getWithdrawIx(
2409
+ vaultDepositor,
2410
+ txParams?.oracleFeedsToCrank
2411
+ );
2412
+ return await this.createAndSendTxn(ixs, {
2413
+ cuLimit: 850_000, // overestimating to be safe
2414
+ ...txParams,
2415
+ });
2416
+ }
2417
+
2418
+ public async getWithdrawIx(
2419
+ vaultDepositor: PublicKey,
2420
+ oracleFeedsToCrank?: TxParams['oracleFeedsToCrank']
2421
+ ): Promise<TransactionInstruction[]> {
2422
+ const vaultDepositorAccount =
2423
+ await this.program.account.vaultDepositor.fetch(vaultDepositor);
2424
+ const vaultAccount = await this.program.account.vault.fetch(
2425
+ vaultDepositorAccount.vault
2426
+ );
2427
+
2428
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
2429
+ const userStatsKey = getUserStatsAccountPublicKey(
2430
+ this.driftClient.program.programId,
2431
+ vaultDepositorAccount.vault
2432
+ );
2433
+ const userStats = (await (
2434
+ this.driftClient.program as any
2435
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
2436
+ const remainingAccounts = this.getRemainingAccountsForUser(
2437
+ [user.getUserAccount()],
2438
+ [vaultAccount.spotMarketIndex],
2439
+ vaultAccount,
2440
+ userStats,
2441
+ false,
2442
+ false,
2443
+ false
2444
+ );
2445
+
2446
+ const driftStateKey = await this.driftClient.getStatePublicKey();
2447
+
2448
+ const spotMarket = this.driftClient.getSpotMarketAccount(
2449
+ vaultAccount.spotMarketIndex
2450
+ );
2451
+ if (!spotMarket) {
2452
+ throw new Error(
2453
+ `Spot market ${vaultAccount.spotMarketIndex} not found on driftClient`
2454
+ );
2455
+ }
2456
+
2457
+ const isSolMarket = spotMarket.mint.equals(WRAPPED_SOL_MINT);
2458
+
2459
+ // let createAtaIx: TransactionInstruction | undefined = undefined;
2460
+ let userAta = getAssociatedTokenAddressSync(
2461
+ spotMarket.mint,
2462
+ this.driftClient.wallet.publicKey,
2463
+ true
2464
+ );
2465
+
2466
+ const preIxs: TransactionInstruction[] = [];
2467
+ const postIxs: TransactionInstruction[] = [];
2468
+
2469
+ if (isSolMarket) {
2470
+ const { ixs, pubkey } =
2471
+ await this.driftClient.getWrappedSolAccountCreationIxs(ZERO, false);
2472
+
2473
+ userAta = pubkey;
2474
+ preIxs.push(...ixs);
2475
+ postIxs.push(createSyncNativeInstruction(userAta));
2476
+ postIxs.push(
2477
+ createCloseAccountInstruction(
2478
+ userAta,
2479
+ this.driftClient.wallet.publicKey,
2480
+ this.driftClient.wallet.publicKey,
2481
+ []
2482
+ )
2483
+ );
2484
+ } else {
2485
+ const userAtaExists = await this.driftClient.connection.getAccountInfo(
2486
+ userAta
2487
+ );
2488
+ if (userAtaExists === null) {
2489
+ preIxs.push(
2490
+ createAssociatedTokenAccountInstruction(
2491
+ this.driftClient.wallet.publicKey,
2492
+ userAta,
2493
+ this.driftClient.wallet.publicKey,
2494
+ spotMarket.mint
2495
+ )
2496
+ );
2497
+ }
2498
+ }
2499
+
2500
+ const accounts = {
2501
+ vault: vaultDepositorAccount.vault,
2502
+ vaultDepositor,
2503
+ vaultTokenAccount: vaultAccount.tokenAccount,
2504
+ driftUserStats: userStatsKey,
2505
+ driftUser: vaultAccount.user,
2506
+ driftState: driftStateKey,
2507
+ driftSpotMarketVault: spotMarket.vault,
2508
+ driftSigner: this.driftClient.getStateAccount().signer,
2509
+ userTokenAccount: userAta,
2510
+ driftProgram: this.driftClient.program.programId,
2511
+ tokenProgram: TOKEN_PROGRAM_ID,
2512
+ };
2513
+
2514
+ const oracleFeedsToCrankIxs = await this.getOracleFeedsToCrankIxs(
2515
+ oracleFeedsToCrank
2516
+ );
2517
+
2518
+ const ixs = [
2519
+ ...oracleFeedsToCrankIxs,
2520
+ ...preIxs,
2521
+ await this.program.methods
2522
+ .withdraw()
2523
+ .accounts({
2524
+ authority: this.driftClient.wallet.publicKey,
2525
+ ...accounts,
2526
+ })
2527
+ .remainingAccounts(remainingAccounts)
2528
+ .instruction(),
2529
+ ...postIxs,
2530
+ ];
2531
+
2532
+ return ixs;
2533
+ }
2534
+
2535
+ public async forceWithdraw(
2536
+ vaultDepositor: PublicKey,
2537
+ txParams?: TxParams
2538
+ ): Promise<TransactionSignature> {
2539
+ const ix = await this.getForceWithdrawIx(vaultDepositor);
2540
+ return await this.createAndSendTxn(ix, txParams);
2541
+ }
2542
+
2543
+ public async getForceWithdrawIx(
2544
+ vaultDepositor: PublicKey
2545
+ ): Promise<TransactionInstruction[]> {
2546
+ const vaultDepositorAccount =
2547
+ await this.program.account.vaultDepositor.fetch(vaultDepositor);
2548
+ const vaultAccount = await this.program.account.vault.fetch(
2549
+ vaultDepositorAccount.vault
2550
+ );
2551
+
2552
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
2553
+ const userStatsKey = getUserStatsAccountPublicKey(
2554
+ this.driftClient.program.programId,
2555
+ vaultDepositorAccount.vault
2556
+ );
2557
+ const userStats = (await (
2558
+ this.driftClient.program as any
2559
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
2560
+ const remainingAccounts = this.getRemainingAccountsForUser(
2561
+ [user.getUserAccount()],
2562
+ [vaultAccount.spotMarketIndex],
2563
+ vaultAccount,
2564
+ userStats,
2565
+ false,
2566
+ false,
2567
+ false
2568
+ );
2569
+ if (vaultAccount.vaultProtocol) {
2570
+ const vaultProtocol = this.getVaultProtocolAddress(
2571
+ vaultDepositorAccount.vault
2572
+ );
2573
+ remainingAccounts.push({
2574
+ pubkey: vaultProtocol,
2575
+ isSigner: false,
2576
+ isWritable: true,
2577
+ });
2578
+ }
2579
+
2580
+ const driftStateKey = await this.driftClient.getStatePublicKey();
2581
+
2582
+ const spotMarket = this.driftClient.getSpotMarketAccount(
2583
+ vaultAccount.spotMarketIndex
2584
+ );
2585
+ if (!spotMarket) {
2586
+ throw new Error(
2587
+ `Spot market ${vaultAccount.spotMarketIndex} not found on driftClient`
2588
+ );
2589
+ }
2590
+
2591
+ const [userTokenAccount, createAtaIx] = await getOrCreateATAInstruction(
2592
+ spotMarket.mint,
2593
+ vaultDepositorAccount.authority,
2594
+ this.driftClient.connection,
2595
+ true,
2596
+ this.driftClient.wallet.publicKey
2597
+ );
2598
+
2599
+ if (createAtaIx) {
2600
+ console.log(
2601
+ `Creating ATA for ${vaultDepositorAccount.authority.toBase58()} to ${userTokenAccount.toBase58()}`
2602
+ );
2603
+ }
2604
+
2605
+ const accounts = {
2606
+ manager: vaultAccount.manager,
2607
+ vault: vaultDepositorAccount.vault,
2608
+ vaultDepositor,
2609
+ vaultTokenAccount: vaultAccount.tokenAccount,
2610
+ driftUserStats: userStatsKey,
2611
+ driftUser: vaultAccount.user,
2612
+ driftState: driftStateKey,
2613
+ driftSpotMarketVault: spotMarket.vault,
2614
+ driftSigner: this.driftClient.getStateAccount().signer,
2615
+ userTokenAccount,
2616
+ driftProgram: this.driftClient.program.programId,
2617
+ tokenProgram: TOKEN_PROGRAM_ID,
2618
+ };
2619
+
2620
+ const ixs: TransactionInstruction[] = [];
2621
+
2622
+ if (createAtaIx) {
2623
+ ixs.push(createAtaIx);
2624
+ }
2625
+
2626
+ ixs.push(
2627
+ await this.program.methods
2628
+ .forceWithdraw()
2629
+ .accounts(accounts)
2630
+ .remainingAccounts(remainingAccounts)
2631
+ .instruction()
2632
+ );
2633
+
2634
+ return ixs;
2635
+ }
2636
+
2637
+ public async cancelRequestWithdraw(
2638
+ vaultDepositor: PublicKey,
2639
+ txParams?: TxParams
2640
+ ): Promise<TransactionSignature> {
2641
+ const ixs = await this.getCancelRequestWithdrawIx(
2642
+ vaultDepositor,
2643
+ txParams?.oracleFeedsToCrank
2644
+ );
2645
+ return await this.createAndSendTxn(ixs, txParams);
2646
+ }
2647
+
2648
+ public async getCancelRequestWithdrawIx(
2649
+ vaultDepositor: PublicKey,
2650
+ oracleFeedsToCrank: TxParams['oracleFeedsToCrank']
2651
+ ): Promise<TransactionInstruction[]> {
2652
+ const vaultDepositorAccount =
2653
+ await this.program.account.vaultDepositor.fetch(vaultDepositor);
2654
+ const vaultAccount = await this.program.account.vault.fetch(
2655
+ vaultDepositorAccount.vault
2656
+ );
2657
+
2658
+ const userStatsKey = getUserStatsAccountPublicKey(
2659
+ this.driftClient.program.programId,
2660
+ vaultDepositorAccount.vault
2661
+ );
2662
+
2663
+ const accounts = {
2664
+ vault: vaultDepositorAccount.vault,
2665
+ vaultDepositor,
2666
+ driftUserStats: userStatsKey,
2667
+ driftUser: vaultAccount.user,
2668
+ };
2669
+
2670
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
2671
+ const userStats = (await (
2672
+ this.driftClient.program as any
2673
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
2674
+ const remainingAccounts = this.getRemainingAccountsForUser(
2675
+ [user.getUserAccount()],
2676
+ [vaultAccount.spotMarketIndex],
2677
+ vaultAccount,
2678
+ userStats,
2679
+ false,
2680
+ false,
2681
+ false
2682
+ );
2683
+
2684
+ if (this.cliMode) {
2685
+ return [
2686
+ await this.program.methods
2687
+ .cancelRequestWithdraw()
2688
+ .accounts(accounts)
2689
+ .remainingAccounts(remainingAccounts)
2690
+ .instruction(),
2691
+ ];
2692
+ } else {
2693
+ const oracleFeedsToCrankIxs = await this.getOracleFeedsToCrankIxs(
2694
+ oracleFeedsToCrank
2695
+ );
2696
+
2697
+ const cancelRequestWithdrawIx =
2698
+ this.program.instruction.cancelRequestWithdraw({
2699
+ accounts: {
2700
+ authority: this.driftClient.wallet.publicKey,
2701
+ ...accounts,
2702
+ },
2703
+ remainingAccounts,
2704
+ });
2705
+
2706
+ return [...oracleFeedsToCrankIxs, cancelRequestWithdrawIx];
2707
+ }
2708
+ }
2709
+
2710
+ /**
2711
+ * Liquidates (become delegate for) a vault.
2712
+ * @param
2713
+ * @param
2714
+ * @returns
2715
+ */
2716
+ public async liquidate(
2717
+ vaultDepositor: PublicKey,
2718
+ txParams?: TxParams
2719
+ ): Promise<TransactionSignature> {
2720
+ const ix = await this.getLiquidateIx(vaultDepositor);
2721
+ return await this.createAndSendTxn([ix], txParams);
2722
+ }
2723
+
2724
+ public async getLiquidateIx(
2725
+ vaultDepositor: PublicKey
2726
+ ): Promise<TransactionInstruction> {
2727
+ if (!this.driftClient.wallet.publicKey.equals(VAULT_ADMIN_KEY)) {
2728
+ throw new Error('Only vault admin can liquidate');
2729
+ }
2730
+ const vaultDepositorAccount =
2731
+ await this.program.account.vaultDepositor.fetch(vaultDepositor);
2732
+ const vault = vaultDepositorAccount.vault;
2733
+
2734
+ const vaultAccount = await this.program.account.vault.fetch(vault);
2735
+
2736
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
2737
+ const userStatsKey = getUserStatsAccountPublicKey(
2738
+ this.driftClient.program.programId,
2739
+ vault
2740
+ );
2741
+ const userStats = (await (
2742
+ this.driftClient.program as any
2743
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
2744
+ const remainingAccounts = this.getRemainingAccountsForUser(
2745
+ [user.getUserAccount()],
2746
+ [vaultAccount.spotMarketIndex],
2747
+ vaultAccount,
2748
+ userStats,
2749
+ false,
2750
+ true,
2751
+ true
2752
+ );
2753
+
2754
+ const driftStateKey = await this.driftClient.getStatePublicKey();
2755
+
2756
+ const accounts = {
2757
+ vault,
2758
+ vaultDepositor,
2759
+ vaultTokenAccount: vaultAccount.tokenAccount,
2760
+ driftUserStats: userStatsKey,
2761
+ driftUser: vaultAccount.user,
2762
+ driftState: driftStateKey,
2763
+ driftProgram: this.driftClient.program.programId,
2764
+ authority: vaultDepositorAccount.authority,
2765
+ };
2766
+
2767
+ if (this.cliMode) {
2768
+ return await this.program.methods
2769
+ .liquidate()
2770
+ .accounts(accounts)
2771
+ .remainingAccounts(remainingAccounts)
2772
+ .instruction();
2773
+ } else {
2774
+ return this.program.instruction.liquidate({
2775
+ accounts: {
2776
+ ...accounts,
2777
+ admin: this.driftClient.wallet.publicKey,
2778
+ },
2779
+ remainingAccounts,
2780
+ });
2781
+ }
2782
+ }
2783
+
2784
+ public async createTxn(
2785
+ vaultIxs: TransactionInstruction[],
2786
+ txParams?: TxParams
2787
+ ): Promise<VersionedTransaction> {
2788
+ const ixs = [
2789
+ ComputeBudgetProgram.setComputeUnitLimit({
2790
+ units: txParams?.cuLimit ?? 400_000,
2791
+ }),
2792
+ ComputeBudgetProgram.setComputeUnitPrice({
2793
+ microLamports:
2794
+ txParams?.cuPriceMicroLamports === undefined
2795
+ ? 50_000
2796
+ : txParams.cuPriceMicroLamports,
2797
+ }),
2798
+ ...vaultIxs,
2799
+ ];
2800
+
2801
+ return (await this.driftClient.txHandler.buildTransaction({
2802
+ connection: this.driftClient.connection,
2803
+ instructions: ixs,
2804
+ lookupTables: txParams?.lookupTables ?? [],
2805
+ preFlightCommitment: 'confirmed',
2806
+ forceVersionedTransaction: true,
2807
+ txVersion: 0,
2808
+ fetchAllMarketLookupTableAccounts:
2809
+ this.driftClient.fetchAllLookupTableAccounts.bind(this.driftClient),
2810
+ })) as VersionedTransaction;
2811
+ }
2812
+
2813
+ public async createTxnNoLut(
2814
+ vaultIxs: TransactionInstruction[],
2815
+ txParams?: TxParams
2816
+ ): Promise<VersionedTransaction> {
2817
+ const ixs = [
2818
+ ComputeBudgetProgram.setComputeUnitLimit({
2819
+ units: txParams?.cuLimit ?? 400_000,
2820
+ }),
2821
+ ComputeBudgetProgram.setComputeUnitPrice({
2822
+ microLamports:
2823
+ txParams?.cuPriceMicroLamports === undefined
2824
+ ? 50_000
2825
+ : txParams.cuPriceMicroLamports,
2826
+ }),
2827
+ ...vaultIxs,
2828
+ ];
2829
+
2830
+ const recentBlockhash =
2831
+ await this.driftClient.connection.getLatestBlockhash();
2832
+
2833
+ return this.driftClient.txHandler.generateVersionedTransaction(
2834
+ recentBlockhash,
2835
+ ixs,
2836
+ [],
2837
+ this.driftClient.wallet
2838
+ );
2839
+ }
2840
+
2841
+ public async sendTxn(
2842
+ transaction: VersionedTransaction,
2843
+ simulateTransaction?: boolean
2844
+ ): Promise<TransactionSignature> {
2845
+ let txSig = bs58.encode(transaction.signatures[0]);
2846
+ if (simulateTransaction) {
2847
+ try {
2848
+ const resp = await this.driftClient.connection.simulateTransaction(
2849
+ transaction,
2850
+ {
2851
+ sigVerify: false,
2852
+ commitment: this.driftClient.connection.commitment,
2853
+ }
2854
+ );
2855
+ console.log(`Simulated transaction:\n${JSON.stringify(resp, null, 2)}`);
2856
+ } catch (e) {
2857
+ const err = e as Error;
2858
+ console.error(
2859
+ `Error simulating transaction: ${err.message}\n:${err.stack ?? ''}`
2860
+ );
2861
+ }
2862
+ } else {
2863
+ const resp = await this.driftClient.sendTransaction(
2864
+ transaction,
2865
+ [],
2866
+ this.driftClient.opts
2867
+ );
2868
+ if (resp.txSig !== txSig) {
2869
+ txSig = resp.txSig;
2870
+ }
2871
+ }
2872
+
2873
+ return txSig!;
2874
+ }
2875
+
2876
+ /**
2877
+ * Used for UI wallet adapters compatibility
2878
+ */
2879
+ public async createAndSendTxn(
2880
+ vaultIxs: TransactionInstruction[],
2881
+ txParams?: TxParams
2882
+ ): Promise<TransactionSignature> {
2883
+ let tx: VersionedTransaction;
2884
+ if (txParams?.noLut ? txParams.noLut : false) {
2885
+ tx = await this.createTxnNoLut(vaultIxs, txParams);
2886
+ // @ts-ignore
2887
+ tx.sign([this.driftClient.wallet.payer]);
2888
+ } else {
2889
+ tx = await this.createTxn(vaultIxs, txParams);
2890
+ }
2891
+ const txSig = await this.sendTxn(tx, txParams?.simulateTransaction);
2892
+
2893
+ return txSig;
2894
+ }
2895
+
2896
+ /**
2897
+ * Initializes an insurance fund stake for the vault.
2898
+ * @param vault vault address to update
2899
+ * @param spotMarketIndex spot market index of the insurance fund stake
2900
+ * @returns
2901
+ */
2902
+ public async initializeInsuranceFundStake(
2903
+ vault: PublicKey,
2904
+ spotMarketIndex: number,
2905
+ txParams?: TxParams
2906
+ ): Promise<TransactionSignature> {
2907
+ const ixs = await this.getInitializeInsuranceFundStakeIx(
2908
+ vault,
2909
+ spotMarketIndex
2910
+ );
2911
+ return await this.createAndSendTxn([ixs], txParams);
2912
+ }
2913
+
2914
+ public async getInitializeInsuranceFundStakeIx(
2915
+ vault: PublicKey,
2916
+ spotMarketIndex: number
2917
+ ): Promise<TransactionInstruction> {
2918
+ const vaultAccount = await this.program.account.vault.fetch(vault);
2919
+
2920
+ const _ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey(
2921
+ this.driftClient.program.programId,
2922
+ vault,
2923
+ spotMarketIndex
2924
+ );
2925
+
2926
+ const spotMarket = this.driftClient.getSpotMarketAccount(spotMarketIndex);
2927
+ if (!spotMarket) {
2928
+ throw new Error(
2929
+ `Spot market ${spotMarketIndex} not found on driftClient`
2930
+ );
2931
+ }
2932
+
2933
+ const _ifVaultTokenAccount = getInsuranceFundTokenVaultAddressSync(
2934
+ this.program.programId,
2935
+ vault,
2936
+ spotMarketIndex
2937
+ );
2938
+
2939
+ return await this.program.methods
2940
+ .initializeInsuranceFundStake(spotMarketIndex)
2941
+ .accounts({
2942
+ vault: vault,
2943
+ driftSpotMarketMint: spotMarket.mint,
2944
+ driftUserStats: vaultAccount.userStats,
2945
+ driftState: await this.driftClient.getStatePublicKey(),
2946
+ })
2947
+ .instruction();
2948
+ }
2949
+
2950
+ /**
2951
+ * Adds an amount to an insurance fund stake for the vault.
2952
+ * @param vault vault address to update
2953
+ * @param spotMarketIndex spot market index of the insurance fund stake
2954
+ * @param amount amount to add to the insurance fund stake, in spotMarketIndex precision
2955
+ * @returns
2956
+ */
2957
+ public async addToInsuranceFundStake(
2958
+ vault: PublicKey,
2959
+ spotMarketIndex: number,
2960
+ amount: BN,
2961
+ managerTokenAccount?: PublicKey,
2962
+ txParams?: TxParams
2963
+ ): Promise<TransactionSignature> {
2964
+ const ixs = await this.getAddToInsuranceFundStakeIx(
2965
+ vault,
2966
+ spotMarketIndex,
2967
+ amount,
2968
+ managerTokenAccount
2969
+ );
2970
+ return await this.createAndSendTxn([ixs], txParams);
2971
+ }
2972
+
2973
+ public async getAddToInsuranceFundStakeIx(
2974
+ vault: PublicKey,
2975
+ spotMarketIndex: number,
2976
+ amount: BN,
2977
+ managerTokenAccount?: PublicKey
2978
+ ): Promise<TransactionInstruction> {
2979
+ const vaultAccount = await this.program.account.vault.fetch(vault);
2980
+
2981
+ if (!vaultAccount.manager.equals(this.driftClient.wallet.publicKey)) {
2982
+ throw new Error(
2983
+ `Only the manager of the vault can add to the insurance fund stake.`
2984
+ );
2985
+ }
2986
+
2987
+ const _ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey(
2988
+ this.driftClient.program.programId,
2989
+ vault,
2990
+ spotMarketIndex
2991
+ );
2992
+ const _ifVaultPublicKey = await getInsuranceFundVaultPublicKey(
2993
+ this.driftClient.program.programId,
2994
+ spotMarketIndex
2995
+ );
2996
+
2997
+ const spotMarket = this.driftClient.getSpotMarketAccount(spotMarketIndex);
2998
+ if (!spotMarket) {
2999
+ throw new Error(
3000
+ `Spot market ${spotMarketIndex} not found on driftClient`
3001
+ );
3002
+ }
3003
+
3004
+ if (!managerTokenAccount) {
3005
+ managerTokenAccount = getAssociatedTokenAddressSync(
3006
+ spotMarket.mint,
3007
+ vaultAccount.manager,
3008
+ true
3009
+ );
3010
+ }
3011
+
3012
+ const _ifVaultTokenAccount = getInsuranceFundTokenVaultAddressSync(
3013
+ this.program.programId,
3014
+ vault,
3015
+ spotMarketIndex
3016
+ );
3017
+
3018
+ return await this.program.methods
3019
+ .addInsuranceFundStake(spotMarketIndex, amount)
3020
+ .accounts({
3021
+ vault: vault,
3022
+ managerTokenAccount,
3023
+ driftUserStats: vaultAccount.userStats,
3024
+ driftState: await this.driftClient.getStatePublicKey(),
3025
+ driftSigner: this.driftClient.getStateAccount().signer,
3026
+ })
3027
+ .instruction();
3028
+ }
3029
+
3030
+ public async requestRemoveInsuranceFundStake(
3031
+ vault: PublicKey,
3032
+ spotMarketIndex: number,
3033
+ amount: BN,
3034
+ txParams?: TxParams
3035
+ ): Promise<TransactionSignature> {
3036
+ const ix = await this.getRequestRemoveInsuranceFundStakeIx(
3037
+ vault,
3038
+ spotMarketIndex,
3039
+ amount
3040
+ );
3041
+ return await this.createAndSendTxn([ix], txParams);
3042
+ }
3043
+
3044
+ public async getRequestRemoveInsuranceFundStakeIx(
3045
+ vault: PublicKey,
3046
+ spotMarketIndex: number,
3047
+ amount: BN
3048
+ ): Promise<TransactionInstruction> {
3049
+ const vaultAccount = await this.program.account.vault.fetch(vault);
3050
+ const _ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey(
3051
+ this.driftClient.program.programId,
3052
+ vault,
3053
+ spotMarketIndex
3054
+ );
3055
+ const _ifVaultPublicKey = await getInsuranceFundVaultPublicKey(
3056
+ this.driftClient.program.programId,
3057
+ spotMarketIndex
3058
+ );
3059
+
3060
+ const spotMarket = this.driftClient.getSpotMarketAccount(spotMarketIndex);
3061
+ if (!spotMarket) {
3062
+ throw new Error(
3063
+ `Spot market ${spotMarketIndex} not found on driftClient`
3064
+ );
3065
+ }
3066
+
3067
+ return await this.program.methods
3068
+ .requestRemoveInsuranceFundStake(spotMarketIndex, amount)
3069
+ .accounts({
3070
+ vault,
3071
+ manager: vaultAccount.manager,
3072
+ driftUserStats: vaultAccount.userStats,
3073
+ })
3074
+ .instruction();
3075
+ }
3076
+
3077
+ public async cancelRequestRemoveInsuranceFundStake(
3078
+ vault: PublicKey,
3079
+ spotMarketIndex: number,
3080
+ txParams?: TxParams
3081
+ ): Promise<TransactionSignature> {
3082
+ const ix = await this.getCancelRequestRemoveInsuranceFundStakeIx(
3083
+ vault,
3084
+ spotMarketIndex
3085
+ );
3086
+ return await this.createAndSendTxn([ix], txParams);
3087
+ }
3088
+
3089
+ public async getCancelRequestRemoveInsuranceFundStakeIx(
3090
+ vault: PublicKey,
3091
+ spotMarketIndex: number
3092
+ ): Promise<TransactionInstruction> {
3093
+ const vaultAccount = await this.program.account.vault.fetch(vault);
3094
+ const _ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey(
3095
+ this.driftClient.program.programId,
3096
+ vault,
3097
+ spotMarketIndex
3098
+ );
3099
+ const _ifVaultPublicKey = await getInsuranceFundVaultPublicKey(
3100
+ this.driftClient.program.programId,
3101
+ spotMarketIndex
3102
+ );
3103
+ const spotMarket = this.driftClient.getSpotMarketAccount(spotMarketIndex);
3104
+ if (!spotMarket) {
3105
+ throw new Error(
3106
+ `Spot market ${spotMarketIndex} not found on driftClient`
3107
+ );
3108
+ }
3109
+
3110
+ return await this.program.methods
3111
+ .cancelRequestRemoveInsuranceFundStake(spotMarketIndex)
3112
+ .accounts({
3113
+ vault: vault,
3114
+ manager: vaultAccount.manager,
3115
+ driftUserStats: vaultAccount.userStats,
3116
+ })
3117
+ .instruction();
3118
+ }
3119
+
3120
+ public async removeInsuranceFundStake(
3121
+ vault: PublicKey,
3122
+ spotMarketIndex: number,
3123
+ managerTokenAccount?: PublicKey,
3124
+ txParams?: TxParams
3125
+ ): Promise<TransactionSignature> {
3126
+ const ixs = await this.getRemoveInsuranceFundStakeIx(
3127
+ vault,
3128
+ spotMarketIndex,
3129
+ managerTokenAccount
3130
+ );
3131
+ return await this.createAndSendTxn([ixs], txParams);
3132
+ }
3133
+
3134
+ public async getRemoveInsuranceFundStakeIx(
3135
+ vault: PublicKey,
3136
+ spotMarketIndex: number,
3137
+ managerTokenAccount?: PublicKey
3138
+ ): Promise<TransactionInstruction> {
3139
+ const vaultAccount = await this.program.account.vault.fetch(vault);
3140
+ const _ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey(
3141
+ this.driftClient.program.programId,
3142
+ vault,
3143
+ spotMarketIndex
3144
+ );
3145
+ const _ifVaultPublicKey = await getInsuranceFundVaultPublicKey(
3146
+ this.driftClient.program.programId,
3147
+ spotMarketIndex
3148
+ );
3149
+ const spotMarket = this.driftClient.getSpotMarketAccount(spotMarketIndex);
3150
+ if (!spotMarket) {
3151
+ throw new Error(
3152
+ `Spot market ${spotMarketIndex} not found on driftClient`
3153
+ );
3154
+ }
3155
+
3156
+ if (!managerTokenAccount) {
3157
+ managerTokenAccount = getAssociatedTokenAddressSync(
3158
+ spotMarket.mint,
3159
+ vaultAccount.manager,
3160
+ true
3161
+ );
3162
+ }
3163
+
3164
+ const _ifVaultTokenAccount = getInsuranceFundTokenVaultAddressSync(
3165
+ this.program.programId,
3166
+ vault,
3167
+ spotMarketIndex
3168
+ );
3169
+
3170
+ return await this.program.methods
3171
+ .removeInsuranceFundStake(spotMarketIndex)
3172
+ .accounts({
3173
+ vault: vault,
3174
+ managerTokenAccount,
3175
+ driftState: await this.driftClient.getStatePublicKey(),
3176
+ driftUserStats: vaultAccount.userStats,
3177
+ driftSigner: this.driftClient.getStateAccount().signer,
3178
+ })
3179
+ .instruction();
3180
+ }
3181
+
3182
+ public async protocolRequestWithdraw(
3183
+ vault: PublicKey,
3184
+ amount: BN,
3185
+ withdrawUnit: WithdrawUnit,
3186
+ txParams?: TxParams
3187
+ ): Promise<TransactionSignature> {
3188
+ const ix = await this.getProtocolRequestWithdrawIx(
3189
+ vault,
3190
+ amount,
3191
+ withdrawUnit
3192
+ );
3193
+ return await this.createAndSendTxn([ix], txParams);
3194
+ }
3195
+
3196
+ public async getProtocolRequestWithdrawIx(
3197
+ vault: PublicKey,
3198
+ amount: BN,
3199
+ withdrawUnit: WithdrawUnit
3200
+ ): Promise<TransactionInstruction> {
3201
+ // @ts-ignore
3202
+ const vaultAccount = (await this.program.account.vault.fetch(
3203
+ vault
3204
+ )) as Vault;
3205
+ const vp = this.getVaultProtocolAddress(vault);
3206
+ const vpAccount = (await this.program.account.vaultProtocol.fetch(
3207
+ vp
3208
+ )) as VaultProtocol;
3209
+
3210
+ if (!this.driftClient.wallet.publicKey.equals(vpAccount.protocol)) {
3211
+ throw new Error(`Only the protocol of the vault can request a withdraw.`);
3212
+ }
3213
+
3214
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
3215
+ const userStatsKey = getUserStatsAccountPublicKey(
3216
+ this.driftClient.program.programId,
3217
+ vault
3218
+ );
3219
+ const userStats = (await (
3220
+ this.driftClient.program as any
3221
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
3222
+ const remainingAccounts = this.getRemainingAccountsForUser(
3223
+ [user.getUserAccount()],
3224
+ [],
3225
+ vaultAccount,
3226
+ userStats,
3227
+ false,
3228
+ true,
3229
+ true
3230
+ );
3231
+
3232
+ const accounts = {
3233
+ vault,
3234
+ driftUserStats: userStatsKey,
3235
+ driftUser: vaultAccount.user,
3236
+ };
3237
+
3238
+ if (this.cliMode) {
3239
+ return await this.program.methods
3240
+ // @ts-ignore, 0.29.0 anchor issues..
3241
+ .managerRequestWithdraw(amount, withdrawUnit)
3242
+ .accounts(accounts)
3243
+ .remainingAccounts(remainingAccounts)
3244
+ .instruction();
3245
+ } else {
3246
+ const requestWithdrawIx = this.program.instruction.managerRequestWithdraw(
3247
+ // @ts-ignore
3248
+ amount,
3249
+ withdrawUnit,
3250
+ {
3251
+ accounts: {
3252
+ manager: vaultAccount.manager,
3253
+ ...accounts,
3254
+ },
3255
+ remainingAccounts,
3256
+ }
3257
+ );
3258
+
3259
+ return requestWithdrawIx;
3260
+ }
3261
+ }
3262
+
3263
+ public async protocolCancelWithdrawRequest(
3264
+ vault: PublicKey,
3265
+ txParams?: TxParams
3266
+ ): Promise<TransactionSignature> {
3267
+ const ixs = await this.getProtocolCancelWithdrawRequestIx(vault);
3268
+ return await this.createAndSendTxn(ixs, txParams);
3269
+ }
3270
+
3271
+ public async getProtocolCancelWithdrawRequestIx(
3272
+ vault: PublicKey
3273
+ ): Promise<TransactionInstruction[]> {
3274
+ const vaultAccount = await this.program.account.vault.fetch(vault);
3275
+
3276
+ const userStatsKey = getUserStatsAccountPublicKey(
3277
+ this.driftClient.program.programId,
3278
+ vault
3279
+ );
3280
+
3281
+ const accounts = {
3282
+ manager: vaultAccount.manager,
3283
+ vault,
3284
+ driftUserStats: userStatsKey,
3285
+ driftUser: vaultAccount.user,
3286
+ };
3287
+
3288
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
3289
+ const userStats = (await (
3290
+ this.driftClient.program as any
3291
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
3292
+ const remainingAccounts = this.getRemainingAccountsForUser(
3293
+ [user.getUserAccount()],
3294
+ [],
3295
+ vaultAccount,
3296
+ userStats,
3297
+ false,
3298
+ true,
3299
+ true
3300
+ );
3301
+
3302
+ if (this.cliMode) {
3303
+ return [
3304
+ await this.program.methods
3305
+ .mangerCancelWithdrawRequest()
3306
+ .accounts(accounts)
3307
+ .remainingAccounts(remainingAccounts)
3308
+ .instruction(),
3309
+ ];
3310
+ } else {
3311
+ const cancelRequestWithdrawIx =
3312
+ this.program.instruction.mangerCancelWithdrawRequest({
3313
+ accounts: {
3314
+ ...accounts,
3315
+ manager: vaultAccount.manager,
3316
+ },
3317
+ remainingAccounts,
3318
+ });
3319
+
3320
+ return [cancelRequestWithdrawIx];
3321
+ }
3322
+ }
3323
+
3324
+ public async protocolWithdraw(
3325
+ vault: PublicKey,
3326
+ txParams?: TxParams
3327
+ ): Promise<TransactionSignature> {
3328
+ const ixs = await this.getProtocolWithdrawIx(vault);
3329
+ return await this.createAndSendTxn(ixs, txParams);
3330
+ }
3331
+
3332
+ public async getProtocolWithdrawIx(
3333
+ vault: PublicKey
3334
+ ): Promise<TransactionInstruction[]> {
3335
+ const vaultAccount = await this.program.account.vault.fetch(vault);
3336
+
3337
+ if (!this.driftClient.wallet.publicKey.equals(vaultAccount.manager)) {
3338
+ throw new Error(`Only the manager of the vault can request a withdraw.`);
3339
+ }
3340
+
3341
+ const user = await this.getSubscribedVaultUser(vaultAccount.user);
3342
+ const userStatsKey = getUserStatsAccountPublicKey(
3343
+ this.driftClient.program.programId,
3344
+ vault
3345
+ );
3346
+ const userStats = (await (
3347
+ this.driftClient.program as any
3348
+ ).account.userStats.fetch(userStatsKey)) as UserStatsAccount;
3349
+ const remainingAccounts = this.getRemainingAccountsForUser(
3350
+ [user.getUserAccount()],
3351
+ [],
3352
+ vaultAccount,
3353
+ userStats,
3354
+ false,
3355
+ true,
3356
+ true
3357
+ );
3358
+
3359
+ const spotMarket = this.driftClient.getSpotMarketAccount(
3360
+ vaultAccount.spotMarketIndex
3361
+ );
3362
+ if (!spotMarket) {
3363
+ throw new Error(
3364
+ `Spot market ${vaultAccount.spotMarketIndex} not found on driftClient`
3365
+ );
3366
+ }
3367
+
3368
+ const ix = this.program.instruction.managerWithdraw({
3369
+ accounts: {
3370
+ vault,
3371
+ manager: vaultAccount.manager,
3372
+ vaultTokenAccount: vaultAccount.tokenAccount,
3373
+ driftUser: await getUserAccountPublicKey(
3374
+ this.driftClient.program.programId,
3375
+ vault
3376
+ ),
3377
+ driftProgram: this.driftClient.program.programId,
3378
+ driftUserStats: userStatsKey,
3379
+ driftState: await this.driftClient.getStatePublicKey(),
3380
+ driftSpotMarketVault: spotMarket.vault,
3381
+ userTokenAccount: getAssociatedTokenAddressSync(
3382
+ spotMarket.mint,
3383
+ this.driftClient.wallet.publicKey
3384
+ ),
3385
+ driftSigner: this.driftClient.getStateAccount().signer,
3386
+ tokenProgram: TOKEN_PROGRAM_ID,
3387
+ },
3388
+ remainingAccounts,
3389
+ });
3390
+ return [ix];
3391
+ }
3392
+
3393
+ private async getPythLazerOracleCrankIxs(
3394
+ oracleFeedsToCrank: OracleFeedConfig[] = [],
3395
+ pythLazerMsgHexGetter?: (feedIds: number[]) => Promise<string>
3396
+ ) {
3397
+ try {
3398
+ const isPythLazerOracle = (oracleSource: OracleSource) => {
3399
+ const pythLazerStr = JSON.stringify(OracleSource.PYTH_LAZER);
3400
+ const pythLazer1kStr = JSON.stringify(OracleSource.PYTH_LAZER_1K);
3401
+ const pythLazer1mStr = JSON.stringify(OracleSource.PYTH_LAZER_1M);
3402
+ const pythLazerStableCoinStr = JSON.stringify(
3403
+ OracleSource.PYTH_LAZER_STABLE_COIN
3404
+ );
3405
+ const targetOracleSourceStr = JSON.stringify(oracleSource);
3406
+ return (
3407
+ targetOracleSourceStr === pythLazerStr ||
3408
+ targetOracleSourceStr === pythLazer1kStr ||
3409
+ targetOracleSourceStr === pythLazer1mStr ||
3410
+ targetOracleSourceStr === pythLazerStableCoinStr
3411
+ );
3412
+ };
3413
+
3414
+ const pythLazerOracles = oracleFeedsToCrank.filter((config) =>
3415
+ isPythLazerOracle(config.oracleSource)
3416
+ );
3417
+
3418
+ if (pythLazerOracles.length === 0) {
3419
+ return [];
3420
+ }
3421
+
3422
+ if (!pythLazerMsgHexGetter) {
3423
+ console.error(
3424
+ 'pythLazerMsgHexGetter is required to crank pyth lazer oracles'
3425
+ );
3426
+ return [];
3427
+ }
3428
+
3429
+ const pythLazerFeedIds = pythLazerOracles
3430
+ .map((config) => config.pythLazerId)
3431
+ .filter(Boolean) as number[];
3432
+ const pythLazerMsgHex = await pythLazerMsgHexGetter(pythLazerFeedIds);
3433
+
3434
+ const oracleUpdateIxs =
3435
+ await this.driftClient.getPostPythLazerOracleUpdateIxs(
3436
+ pythLazerFeedIds,
3437
+ pythLazerMsgHex,
3438
+ undefined,
3439
+ 3
3440
+ );
3441
+
3442
+ return oracleUpdateIxs;
3443
+ } catch (err) {
3444
+ console.error('Error cranking pyth lazer oracles', err);
3445
+ return [];
3446
+ }
3447
+ }
3448
+
3449
+ public async getOracleFeedsToCrankIxs(
3450
+ oracleFeedsToCrank: TxParams['oracleFeedsToCrank']
3451
+ ) {
3452
+ if (!oracleFeedsToCrank?.feedsToCrank) {
3453
+ return [];
3454
+ }
3455
+
3456
+ return await this.getPythLazerOracleCrankIxs(
3457
+ oracleFeedsToCrank.feedsToCrank,
3458
+ oracleFeedsToCrank.pythLazerMsgHexGetter
3459
+ );
3460
+ }
3461
+
3462
+ public async updateVaultProtocol(
3463
+ vault: PublicKey,
3464
+ params: {
3465
+ protocolFee: BN | null;
3466
+ protocolProfitShare: number | null;
3467
+ },
3468
+ txParams?: TxParams
3469
+ ): Promise<TransactionSignature> {
3470
+ const ix = await this.getUpdateVaultProtocolIx(vault, params);
3471
+ return await this.createAndSendTxn([ix], txParams);
3472
+ }
3473
+
3474
+ public async getUpdateVaultProtocolIx(
3475
+ vault: PublicKey,
3476
+ params: {
3477
+ protocolFee: BN | null;
3478
+ protocolProfitShare: number | null;
3479
+ }
3480
+ ): Promise<TransactionInstruction> {
3481
+ return this.program.methods
3482
+ .updateVaultProtocol(params)
3483
+ .accounts({
3484
+ vault,
3485
+ vaultProtocol: this.getVaultProtocolAddress(vault),
3486
+ })
3487
+ .instruction();
3488
+ }
3489
+
3490
+ public async adminInitFeeUpdate(
3491
+ vault: PublicKey,
3492
+ uiTxParams?: TxParams
3493
+ ): Promise<TransactionSignature> {
3494
+ const ix = await this.getAdminInitFeeUpdateIx(vault);
3495
+ return await this.createAndSendTxn([ix], uiTxParams);
3496
+ }
3497
+
3498
+ public async getAdminInitFeeUpdateIx(
3499
+ vault: PublicKey
3500
+ ): Promise<TransactionInstruction> {
3501
+ const feeUpdate = getFeeUpdateAddressSync(this.program.programId, vault);
3502
+
3503
+ return this.program.instruction.adminInitFeeUpdate({
3504
+ accounts: {
3505
+ vault,
3506
+ admin: this.driftClient.wallet.publicKey,
3507
+ feeUpdate,
3508
+ systemProgram: SystemProgram.programId,
3509
+ },
3510
+ });
3511
+ }
3512
+
3513
+ public async adminDeleteFeeUpdate(
3514
+ vault: PublicKey,
3515
+ uiTxParams?: TxParams
3516
+ ): Promise<TransactionSignature> {
3517
+ const ix = await this.getAdminDeleteFeeUpdateIx(vault);
3518
+ return await this.createAndSendTxn([ix], uiTxParams);
3519
+ }
3520
+
3521
+ public async getAdminDeleteFeeUpdateIx(
3522
+ vault: PublicKey
3523
+ ): Promise<TransactionInstruction> {
3524
+ const feeUpdate = getFeeUpdateAddressSync(this.program.programId, vault);
3525
+
3526
+ return this.program.instruction.adminDeleteFeeUpdate({
3527
+ accounts: {
3528
+ vault,
3529
+ admin: this.driftClient.wallet.publicKey,
3530
+ feeUpdate,
3531
+ },
3532
+ });
3533
+ }
3534
+
3535
+ public async adminUpdateVaultClass(
3536
+ vault: PublicKey,
3537
+ newVaultClass: VaultClass,
3538
+ uiTxParams?: TxParams
3539
+ ): Promise<TransactionSignature> {
3540
+ const ix = await this.getAdminUpdateVaultClassIx(vault, newVaultClass);
3541
+ return await this.createAndSendTxn([ix], uiTxParams);
3542
+ }
3543
+
3544
+ public async getAdminUpdateVaultClassIx(
3545
+ vault: PublicKey,
3546
+ newVaultClass: VaultClass
3547
+ ): Promise<TransactionInstruction> {
3548
+ return this.program.methods
3549
+ .adminUpdateVaultClass(newVaultClass as any)
3550
+ .accounts({
3551
+ vault,
3552
+ admin: this.driftClient.wallet.publicKey,
3553
+ })
3554
+ .instruction();
3555
+ }
3556
+
3557
+ public async managerUpdateFees(
3558
+ vault: PublicKey,
3559
+ params: {
3560
+ timelockDuration: BN;
3561
+ newManagementFee: BN | null;
3562
+ newProfitShare: number | null;
3563
+ newHurdleRate: number | null;
3564
+ },
3565
+ uiTxParams?: TxParams
3566
+ ): Promise<TransactionSignature> {
3567
+ const feeUpdate = getFeeUpdateAddressSync(this.program.programId, vault);
3568
+ const ixs: TransactionInstruction[] = [];
3569
+ if (!(await this.checkIfAccountExists(feeUpdate))) {
3570
+ throw new Error(
3571
+ 'Fee update account does not exist, it must be created by an admin first'
3572
+ );
3573
+ }
3574
+ ixs.push(await this.getManagerUpdateFeesIx(vault, params));
3575
+ return await this.createAndSendTxn(ixs, uiTxParams);
3576
+ }
3577
+
3578
+ public async getManagerUpdateFeesIx(
3579
+ vault: PublicKey,
3580
+ params: {
3581
+ timelockDuration: BN;
3582
+ newManagementFee: BN | null;
3583
+ newProfitShare: number | null;
3584
+ newHurdleRate: number | null;
3585
+ }
3586
+ ): Promise<TransactionInstruction> {
3587
+ const vaultAccount = await this.program.account.vault.fetch(vault);
3588
+ const feeUpdate = getFeeUpdateAddressSync(this.program.programId, vault);
3589
+
3590
+ return this.program.instruction.managerUpdateFees(params, {
3591
+ accounts: {
3592
+ vault,
3593
+ manager: vaultAccount.manager,
3594
+ feeUpdate,
3595
+ },
3596
+ });
3597
+ }
3598
+
3599
+ public async managerCancelFeeUpdate(
3600
+ vault: PublicKey,
3601
+ uiTxParams?: TxParams
3602
+ ): Promise<TransactionSignature> {
3603
+ const ix = await this.getManagerCancelFeeUpdateIx(vault);
3604
+ return await this.createAndSendTxn([ix], uiTxParams);
3605
+ }
3606
+
3607
+ public async getManagerCancelFeeUpdateIx(
3608
+ vault: PublicKey
3609
+ ): Promise<TransactionInstruction> {
3610
+ const vaultAccount = await this.program.account.vault.fetch(vault);
3611
+ const feeUpdate = getFeeUpdateAddressSync(this.program.programId, vault);
3612
+
3613
+ return this.program.instruction.managerCancelFeeUpdate({
3614
+ accounts: {
3615
+ vault,
3616
+ manager: vaultAccount.manager,
3617
+ feeUpdate,
3618
+ },
3619
+ });
3620
+ }
3621
+
3622
+ /**
3623
+ * Calculate the vault share price (base value per share)
3624
+ * @param params vault address or vault account, and precision exponent
3625
+ * @returns BigNum representing the base value per share
3626
+ */
3627
+ public async calcVaultSharePrice(params: {
3628
+ address?: PublicKey;
3629
+ vault?: Vault;
3630
+ }): Promise<BigNum> {
3631
+ let spotPrecisionExp = QUOTE_PRECISION_EXP;
3632
+
3633
+ try {
3634
+ let vaultAccount: Vault;
3635
+ if (params.address !== undefined) {
3636
+ vaultAccount = await this.program.account.vault.fetch(params.address);
3637
+ } else if (params.vault !== undefined) {
3638
+ vaultAccount = params.vault;
3639
+ } else {
3640
+ throw new Error('Must supply address or vault');
3641
+ }
3642
+
3643
+ const spotMarket = this.driftClient.getSpotMarketAccount(
3644
+ vaultAccount.spotMarketIndex
3645
+ );
3646
+ spotPrecisionExp = new BN(spotMarket!.decimals);
3647
+
3648
+ const totalAccountValue = await this.calculateVaultEquityInDepositAsset({
3649
+ vault: vaultAccount,
3650
+ factorUnrealizedPNL: true,
3651
+ });
3652
+
3653
+ const vaultTotalShares = vaultAccount.totalShares.toString();
3654
+ const totalAccountValueStr = totalAccountValue.toString();
3655
+
3656
+ return vaultTotalShares === '0'
3657
+ ? BigNum.from(0, spotPrecisionExp)
3658
+ : new BigNum(totalAccountValueStr, spotPrecisionExp)
3659
+ .shift(VAULT_SHARES_PRECISION_EXP)
3660
+ .div(new BigNum(vaultTotalShares, VAULT_SHARES_PRECISION_EXP));
3661
+ } catch (err) {
3662
+ console.error('VaultClient calcVaultSharePrice error:', err);
3663
+ return BigNum.from(0, spotPrecisionExp);
3664
+ }
3665
+ }
3666
+ }