@drift-labs/vaults-sdk 0.6.30 → 0.6.32

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 (46) hide show
  1. package/cli/cli.ts +31 -2
  2. package/cli/commands/adminDeleteFeeUpdate.ts +73 -0
  3. package/cli/commands/adminInitFeeUpdate.ts +73 -0
  4. package/cli/commands/applyProfitShare.ts +5 -2
  5. package/cli/commands/decodeLogs.ts +3 -2
  6. package/cli/commands/deposit.ts +2 -2
  7. package/cli/commands/forceWithdrawAll.ts +1 -1
  8. package/cli/commands/index.ts +2 -0
  9. package/cli/commands/initVault.ts +35 -35
  10. package/cli/commands/managerApplyProfitShare.ts +1 -1
  11. package/cli/commands/managerCancelWithdraw.ts +1 -1
  12. package/cli/commands/managerDeposit.ts +1 -1
  13. package/cli/commands/managerRequestWithdraw.ts +2 -2
  14. package/cli/commands/managerUpdateFees.ts +121 -0
  15. package/cli/commands/managerUpdateMarginTradingEnabled.ts +1 -1
  16. package/cli/commands/managerUpdatePoolId.ts +1 -1
  17. package/cli/commands/managerUpdateVault.ts +13 -2
  18. package/cli/commands/managerUpdateVaultDelegate.ts +1 -1
  19. package/cli/commands/managerUpdateVaultManager.ts +1 -1
  20. package/cli/commands/managerWithdraw.ts +1 -1
  21. package/cli/commands/requestWithdraw.ts +1 -1
  22. package/cli/commands/viewVault.ts +12 -1
  23. package/cli/commands/withdraw.ts +1 -1
  24. package/cli/utils.ts +12 -3
  25. package/lib/addresses.d.ts +1 -0
  26. package/lib/addresses.d.ts.map +1 -1
  27. package/lib/addresses.js +7 -0
  28. package/lib/constants/index.d.ts +2 -0
  29. package/lib/constants/index.d.ts.map +1 -1
  30. package/lib/constants/index.js +3 -1
  31. package/lib/types/drift_vaults.d.ts +275 -1
  32. package/lib/types/drift_vaults.d.ts.map +1 -1
  33. package/lib/types/drift_vaults.js +275 -1
  34. package/lib/types/types.d.ts +33 -0
  35. package/lib/types/types.d.ts.map +1 -1
  36. package/lib/types/types.js +11 -1
  37. package/lib/vaultClient.d.ts +22 -1
  38. package/lib/vaultClient.d.ts.map +1 -1
  39. package/lib/vaultClient.js +115 -22
  40. package/package.json +1 -1
  41. package/src/addresses.ts +13 -0
  42. package/src/constants/index.ts +5 -0
  43. package/src/idl/drift_vaults.json +281 -1
  44. package/src/types/drift_vaults.ts +550 -2
  45. package/src/types/types.ts +33 -0
  46. package/src/vaultClient.ts +229 -22
package/cli/cli.ts CHANGED
@@ -28,6 +28,9 @@ import { Command, Option } from 'commander';
28
28
  import { viewVaultDepositor } from "./commands/viewVaultDepositor";
29
29
  import { managerUpdatePoolId } from "./commands/managerUpdatePoolId";
30
30
  import { managerApplyProfitShare } from "./commands/managerApplyProfitShare";
31
+ import { managerUpdateFees } from "./commands/managerUpdateFees";
32
+ import { adminInitFeeUpdate } from "./commands/adminInitFeeUpdate";
33
+ import { adminDeleteFeeUpdate } from "./commands/adminDeleteFeeUpdate";
31
34
 
32
35
  const program = new Command();
33
36
  program
@@ -94,8 +97,9 @@ program
94
97
  .option("-r, --redeem-period <number>", "The new redeem period (can only be lowered)")
95
98
  .option("-x, --max-tokens <number>", "The max tokens the vault can accept")
96
99
  .option("-a, --min-deposit-amount <number", "The minimum token amount allowed to deposit")
97
- .option("-m, --management-fee <percent>", "The new management fee (can only be lowered)")
98
- .option("-s, --profit-share <percent>", "The new profit share percentage (can only be lowered)")
100
+ .option("-m, --management-fee <percent>", "The new management fee (can only be lowered, use timelocked manager-update-fees to raise)")
101
+ .option("-s, --profit-share <percent>", "The new profit share percentage (can only be lowered, use timelocked manager-update-fees to raise)")
102
+ .option("-h, --hurdle-rate <percent>", "The new hurdle rate percentage (can only be raised, use timelocked manager-update-fees to lower)")
99
103
  .option("-p, --permissioned <boolean>", "Set the vault as permissioned (true) or open (false)")
100
104
  .addOption(new Option("--dump-transaction-message", "Dump the transaction message to the console").makeOptionMandatory(false))
101
105
  .action((opts) => managerUpdateVault(program, opts));
@@ -206,6 +210,31 @@ program
206
210
 
207
211
  .action((opts) => vaultInvariantChecks(program, opts));
208
212
 
213
+ program
214
+ .command("manager-update-fees")
215
+ .description("Update vault fees for a manager")
216
+ .addOption(new Option("--vault-address <address>", "Address of the vault to update").makeOptionMandatory(true))
217
+ .option("-t, --timelock-duration <number>", "The new timelock duration in seconds, must be at least the greater of 1 day, or the current redeem period (default: minimum duration)")
218
+ .option("-m, --management-fee <percent>", "The new management fee percentage")
219
+ .option("-s, --profit-share <percent>", "The new profit share percentage")
220
+ .option("-h, --hurdle-rate <percent>", "The new hurdle rate percentage")
221
+ .addOption(new Option("--dump-transaction-message", "Dump the transaction message to the console").makeOptionMandatory(false))
222
+ .action((opts) => managerUpdateFees(program, opts));
223
+
224
+ program
225
+ .command("admin-init-fee-update")
226
+ .description("Admin initialize a fee update for a vault")
227
+ .addOption(new Option("--vault-address <address>", "Address of the vault to initialize fee update for").makeOptionMandatory(true))
228
+ .addOption(new Option("--dump-transaction-message", "Dump the transaction message to the console").makeOptionMandatory(false))
229
+ .action((opts) => adminInitFeeUpdate(program, opts));
230
+
231
+ program
232
+ .command("admin-delete-fee-update")
233
+ .description("Admin delete a fee update for a vault")
234
+ .addOption(new Option("--vault-address <address>", "Address of the vault to delete fee update for").makeOptionMandatory(true))
235
+ .addOption(new Option("--dump-transaction-message", "Dump the transaction message to the console").makeOptionMandatory(false))
236
+ .action((opts) => adminDeleteFeeUpdate(program, opts));
237
+
209
238
  program.parseAsync().then(() => {
210
239
  process.exit(0);
211
240
  });
@@ -0,0 +1,73 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import {
3
+ OptionValues,
4
+ Command
5
+ } from "commander";
6
+ import { dumpTransactionMessage, getCommandContext } from "../utils";
7
+ import { VAULT_ADMIN_KEY } from "../../src";
8
+
9
+ export const adminDeleteFeeUpdate = async (program: Command, cmdOpts: OptionValues) => {
10
+ let vaultAddress: PublicKey;
11
+ try {
12
+ vaultAddress = new PublicKey(cmdOpts.vaultAddress as string);
13
+ } catch (err) {
14
+ console.error("Invalid vault address");
15
+ process.exit(1);
16
+ }
17
+
18
+ const {
19
+ driftVault,
20
+ driftClient,
21
+ } = await getCommandContext(program, true);
22
+
23
+ if (!driftClient.wallet.publicKey.equals(VAULT_ADMIN_KEY)) {
24
+ console.error("Only vault admin can delete fee update");
25
+ process.exit(1);
26
+ }
27
+
28
+ const vault = await driftVault.getVault(vaultAddress);
29
+
30
+ console.log(`Deleting fee update for vault:`);
31
+ console.log(` Vault: ${vault.pubkey.toBase58()}`);
32
+
33
+ const readline = require('readline').createInterface({
34
+ input: process.stdin,
35
+ output: process.stdout
36
+ });
37
+ console.log('');
38
+ const answer = await new Promise(resolve => {
39
+ readline.question('Are you sure you want to delete the fee update? (yes/no) ', (answer: string) => {
40
+ readline.close();
41
+ resolve(answer);
42
+ });
43
+ });
44
+ if ((answer as string).toLowerCase() !== 'yes') {
45
+ console.log('Fee update deletion canceled.');
46
+ process.exit(0);
47
+ }
48
+ console.log('Deleting fee update...');
49
+
50
+ let done = false;
51
+ while (!done) {
52
+ try {
53
+ if (cmdOpts.dumpTransactionMessage) {
54
+ const tx = await driftVault.getAdminDeleteFeeUpdateIx(vaultAddress);
55
+ console.log(dumpTransactionMessage(driftClient.wallet.publicKey, [tx]));
56
+ } else {
57
+ const tx = await driftVault.adminDeleteFeeUpdate(vaultAddress);
58
+ console.log(`Deleted fee update as admin: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
59
+ done = true;
60
+ }
61
+ break;
62
+ } catch (e) {
63
+ const err = e as Error;
64
+ if (err.message.includes('TransactionExpiredTimeoutError')) {
65
+ console.log(err.message);
66
+ console.log('Transaction timeout. Retrying...');
67
+ await new Promise(resolve => setTimeout(resolve, 5000));
68
+ } else {
69
+ throw err;
70
+ }
71
+ }
72
+ }
73
+ };
@@ -0,0 +1,73 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import {
3
+ OptionValues,
4
+ Command
5
+ } from "commander";
6
+ import { dumpTransactionMessage, getCommandContext } from "../utils";
7
+ import { VAULT_ADMIN_KEY } from "../../src";
8
+
9
+ export const adminInitFeeUpdate = async (program: Command, cmdOpts: OptionValues) => {
10
+ let vaultAddress: PublicKey;
11
+ try {
12
+ vaultAddress = new PublicKey(cmdOpts.vaultAddress as string);
13
+ } catch (err) {
14
+ console.error("Invalid vault address");
15
+ process.exit(1);
16
+ }
17
+
18
+ const {
19
+ driftVault,
20
+ driftClient,
21
+ } = await getCommandContext(program, true);
22
+
23
+ if (!driftClient.wallet.publicKey.equals(VAULT_ADMIN_KEY)) {
24
+ console.error("Only vault admin can initialize fee update");
25
+ process.exit(1);
26
+ }
27
+
28
+ const vault = await driftVault.getVault(vaultAddress);
29
+
30
+ console.log(`Initializing fee update for vault:`);
31
+ console.log(` Vault: ${vault.pubkey.toBase58()}`);
32
+
33
+ const readline = require('readline').createInterface({
34
+ input: process.stdin,
35
+ output: process.stdout
36
+ });
37
+ console.log('');
38
+ const answer = await new Promise(resolve => {
39
+ readline.question('Is the above information correct? (yes/no) ', (answer: string) => {
40
+ readline.close();
41
+ resolve(answer);
42
+ });
43
+ });
44
+ if ((answer as string).toLowerCase() !== 'yes') {
45
+ console.log('Fee update initialization canceled.');
46
+ process.exit(0);
47
+ }
48
+ console.log('Initializing fee update...');
49
+
50
+ let done = false;
51
+ while (!done) {
52
+ try {
53
+ if (cmdOpts.dumpTransactionMessage) {
54
+ const tx = await driftVault.getAdminInitFeeUpdateIx(vaultAddress);
55
+ console.log(dumpTransactionMessage(driftClient.wallet.publicKey, [tx]));
56
+ } else {
57
+ const tx = await driftVault.adminInitFeeUpdate(vaultAddress);
58
+ console.log(`Initialized fee update as admin: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
59
+ done = true;
60
+ }
61
+ break;
62
+ } catch (e) {
63
+ const err = e as Error;
64
+ if (err.message.includes('TransactionExpiredTimeoutError')) {
65
+ console.log(err.message);
66
+ console.log('Transaction timeout. Retrying...');
67
+ await new Promise(resolve => setTimeout(resolve, 5000));
68
+ } else {
69
+ throw err;
70
+ }
71
+ }
72
+ }
73
+ };
@@ -37,8 +37,11 @@ export const applyProfitShare = async (program: Command, cmdOpts: OptionValues)
37
37
  const thresholdBN = numberToSafeBN(thresholdNumber, spotMarketPrecision);
38
38
  let pendingProfitShareToRealize = ZERO;
39
39
  const vdWithPendingProfitShare = vdWithNoWithdrawRequests.filter((vd: ProgramAccount<VaultDepositor>) => {
40
+ if (vault.managementFee.gt(ZERO)) {
41
+ return true;
42
+ }
40
43
  const pendingProfitShares = calculateApplyProfitShare(vd.account, vaultEquitySpot, vault);
41
- const doRealize = pendingProfitShares.profitShareAmount.gt(thresholdBN);
44
+ const doRealize = pendingProfitShares.profitShareAmount.gte(thresholdBN);
42
45
  if (doRealize) {
43
46
  pendingProfitShareToRealize = pendingProfitShareToRealize.add(pendingProfitShares.profitShareAmount);
44
47
  return true;
@@ -83,7 +86,7 @@ export const applyProfitShare = async (program: Command, cmdOpts: OptionValues)
83
86
 
84
87
  try {
85
88
  const txid = await driftClient.connection.sendTransaction(tx);
86
- console.log(`Sent chunk: ${txid}`);
89
+ console.log(`Sent chunk: https://solana.fm/tx/${txid}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
87
90
  } catch (e) {
88
91
  console.error(`Error sending chunk: ${e}`);
89
92
  console.log((e as SendTransactionError).logs);
@@ -30,10 +30,13 @@ export const decodeLogs = async (program: Command, cmdOpts: OptionValues) => {
30
30
  maxSupportedTransactionVersion: 0,
31
31
  });
32
32
 
33
+ let i = 0;
33
34
  // @ts-ignore
34
35
  for (const event of driftVault.program._events._eventParser.parseLogs(
35
36
  tx!.meta!.logMessages
36
37
  )) {
38
+ console.log(`--------------- Event: ${i} ${event.name} -----------------`);
39
+ i++;
37
40
 
38
41
  /* eslint-disable no-case-declarations */
39
42
  switch (event.name) {
@@ -63,12 +66,10 @@ export const decodeLogs = async (program: Command, cmdOpts: OptionValues) => {
63
66
  console.log(` depositOraclePrice: ${data.depositOraclePrice?.toNumber()}`);
64
67
  break;
65
68
  default:
66
- console.log(event);
67
69
  }
68
70
 
69
71
  console.log("----------Raw Event-------------");
70
72
  console.log(event);
71
- console.log("--------------------------------");
72
73
  }
73
74
 
74
75
  };
@@ -57,7 +57,7 @@ export const deposit = async (program: Command, cmdOpts: OptionValues) => {
57
57
  authority: depositAuthority,
58
58
  vault: vaultAddress
59
59
  });
60
- console.log(`Deposited ${cmdOpts.amount} to vault: https://solscan.io/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
60
+ console.log(`Deposited ${cmdOpts.amount} to vault: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
61
61
  } else {
62
62
  // VaultDepositor exists
63
63
  const vaultAddress = vaultDepositorAccount.vault;
@@ -71,6 +71,6 @@ export const deposit = async (program: Command, cmdOpts: OptionValues) => {
71
71
 
72
72
  console.log(`depositing (existing VaultDepositor account): ${depositBN.toString()}`);
73
73
  const tx = await driftVault.deposit(vaultDepositorAddress, depositBN);
74
- console.log(`Deposited ${cmdOpts.amount} to vault: https://solscan.io/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
74
+ console.log(`Deposited ${cmdOpts.amount} to vault: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
75
75
  }
76
76
  };
@@ -97,7 +97,7 @@ export const forceWithdrawAll = async (program: Command, cmdOpts: OptionValues)
97
97
  console.log(`Sending chunk: ${bs58.encode(tx.signatures[0])}`);
98
98
  try {
99
99
  const txid = await driftClient.connection.sendTransaction(tx);
100
- console.log(`Sent chunk: https://solscan.io/tx/${txid}`);
100
+ console.log(`Sent chunk: https://solana.fm/tx/${txid}`);
101
101
  } catch (e) {
102
102
  console.error(`Error sending chunk: ${e}`);
103
103
  console.log((e as SendTransactionError).logs);
@@ -19,4 +19,6 @@ export * from './listDepositorsForVault';
19
19
  export * from './managerUpdateMarginTradingEnabled';
20
20
  export * from './decodeLogs';
21
21
  export * from './vaultInvariantChecks';
22
+ export * from './adminInitFeeUpdate';
23
+ export * from './adminDeleteFeeUpdate';
22
24
 
@@ -5,6 +5,7 @@ import {
5
5
  TEN,
6
6
  convertToNumber,
7
7
  decodeName,
8
+ getSignedMsgUserAccountPublicKey,
8
9
  } from "@drift-labs/sdk";
9
10
  import {
10
11
  OptionValues,
@@ -122,8 +123,8 @@ export const initVault = async (program: Command, cmdOpts: OptionValues) => {
122
123
 
123
124
  const vaultAddress = getVaultAddressSync(VAULT_PROGRAM_ID, vaultNameBytes);
124
125
 
125
- if (cmdOpts.dumpTransactionMessage) {
126
- const initIx = await driftVault.getInitializeVaultIx({
126
+ const ixs = [
127
+ await driftVault.getInitializeVaultIx({
127
128
  name: vaultNameBytes,
128
129
  spotMarketIndex,
129
130
  redeemPeriod: new BN(redeemPeriodSec),
@@ -134,42 +135,41 @@ export const initVault = async (program: Command, cmdOpts: OptionValues) => {
134
135
  permissioned,
135
136
  minDepositAmount: minDepositAmountBN,
136
137
  manager: cmdOpts.manager,
137
- });
138
- const updateDelegateIx = await driftVault.getUpdateDelegateIx(vaultAddress, delegate);
139
-
140
- console.log(`New vault address will be: ${vaultAddress.toBase58()}`);
141
- console.log(`Setting trading delegate to: ${delegate.toBase58()}`);
138
+ }),
139
+ await driftVault.getUpdateDelegateIx(vaultAddress, delegate)
140
+ ];
141
+
142
+ const signedOrdersAccountAddress = getSignedMsgUserAccountPublicKey(
143
+ driftClient.program.programId,
144
+ vaultAddress,
145
+ );
146
+
147
+ let swiftUsersAccountExists = false;
148
+ try {
149
+ const acc = await driftClient.connection.getAccountInfo(signedOrdersAccountAddress);
150
+ swiftUsersAccountExists = acc !== null;
151
+ } catch (_err) {
152
+ // Error getting account info is non-critical, default to false
153
+ }
142
154
 
143
- console.log('');
144
- console.log(`Base 58 encoded transaction:`);
145
- console.log(dumpTransactionMessage(cmdOpts.manager ? new PublicKey(cmdOpts.manager) : driftClient.wallet.publicKey, [initIx, updateDelegateIx]));
146
- } else {
147
- const initSignedOrdersAccIx = await driftClient.getInitializeSignedMsgUserOrdersAccountIx(
148
- vaultAddress,
149
- 8
155
+ if (!swiftUsersAccountExists) {
156
+ ixs.push(
157
+ await driftClient.getInitializeSignedMsgUserOrdersAccountIx(
158
+ vaultAddress,
159
+ 8
160
+ )[1]
150
161
  );
151
- const initIx = await driftVault.getInitializeVaultIx({
152
- name: vaultNameBytes,
153
- spotMarketIndex,
154
- redeemPeriod: new BN(redeemPeriodSec),
155
- maxTokens: maxTokensBN,
156
- managementFee: managementFeeBN,
157
- profitShare: profitShareBN.toNumber(),
158
- hurdleRate: 0,
159
- permissioned,
160
- minDepositAmount: minDepositAmountBN,
161
- manager: cmdOpts.manager,
162
- });
163
- const initTx = await driftVault.createAndSendTxn([
164
- initSignedOrdersAccIx[1],
165
- initIx,
166
- ]);
167
- console.log(`Initialized vault, tx: https://solscan.io/tx/${initTx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
162
+ }
168
163
 
169
- console.log(`\nNew vault address: ${vaultAddress}\n`);
164
+ console.log(`New vault address will be: ${vaultAddress.toBase58()}`);
165
+ console.log(`Setting trading delegate to: ${delegate.toBase58()}`);
166
+ console.log('');
170
167
 
171
- console.log(`Updating the drift account delegate to: ${delegate}...`);
172
- const updateDelegateTx = await driftVault.updateDelegate(vaultAddress, delegate);
173
- console.log(`update delegate tx: https://solscan.io/tx/${updateDelegateTx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
168
+ if (cmdOpts.dumpTransactionMessage) {
169
+ console.log(`Base 58 encoded transaction:`);
170
+ console.log(dumpTransactionMessage(cmdOpts.manager ? new PublicKey(cmdOpts.manager) : driftClient.wallet.publicKey, ixs));
171
+ } else {
172
+ const initTx = await driftVault.createAndSendTxn(ixs);
173
+ console.log(`Initialized vault, tx: https://solana.fm/tx/${initTx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
174
174
  }
175
175
  };
@@ -27,6 +27,6 @@ export const managerApplyProfitShare = async (program: Command, cmdOpts: OptionV
27
27
  console.log(dumpTransactionMessage(driftClient.wallet.publicKey, [tx]));
28
28
  } else {
29
29
  const tx = await driftVault.applyProfitShare(vaultAddress, vaultDepositorAddress);
30
- console.log(`Applied profit share: https://solscan.io/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
30
+ console.log(`Applied profit share: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
31
31
  }
32
32
  };
@@ -25,6 +25,6 @@ export const managerCancelWithdraw = async (program: Command, cmdOpts: OptionVal
25
25
  console.log(dumpTransactionMessage(driftClient.wallet.publicKey, [tx]));
26
26
  } else {
27
27
  const tx = await driftVault.managerCancelWithdrawRequest(vaultAddress);
28
- console.log(`Canceled withdraw as vault manager: https://solscan.io/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
28
+ console.log(`Canceled withdraw as vault manager: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
29
29
  }
30
30
  };
@@ -34,6 +34,6 @@ export const managerDeposit = async (program: Command, cmdOpts: OptionValues) =>
34
34
  console.log(dumpTransactionMessage(driftClient.wallet.publicKey, txs));
35
35
  } else {
36
36
  const tx = await driftVault.managerDeposit(vaultAddress, depositBN);
37
- console.log(`Deposited ${cmdOpts.amount} to vault as manager: https://solscan.io/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
37
+ console.log(`Deposited ${cmdOpts.amount} to vault as manager: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
38
38
  }
39
39
  };
@@ -32,7 +32,7 @@ export const managerRequestWithdraw = async (program: Command, cmdOpts: OptionVa
32
32
  console.log(dumpTransactionMessage(driftClient.wallet.publicKey, [tx]));
33
33
  } else {
34
34
  const tx = await driftVault.managerRequestWithdraw(vaultAddress, new BN(cmdOpts.shares), WithdrawUnit.SHARES);
35
- console.log(`Requested to withraw ${cmdOpts.shares} shares as vault manager: https://solscan.io/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
35
+ console.log(`Requested to withraw ${cmdOpts.shares} shares as vault manager: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
36
36
  }
37
37
  } else if (cmdOpts.amount && !cmdOpts.shares) {
38
38
  const vault = await driftVault.getVault(vaultAddress);
@@ -50,7 +50,7 @@ export const managerRequestWithdraw = async (program: Command, cmdOpts: OptionVa
50
50
  console.log(dumpTransactionMessage(driftClient.wallet.publicKey, [tx]));
51
51
  } else {
52
52
  const tx = await driftVault.managerRequestWithdraw(vaultAddress, amountBN, WithdrawUnit.TOKEN);
53
- console.log(`Requested to withdraw ${amount} ${decodeName(spotMarket.name)} as vault manager: https://solscan.io/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
53
+ console.log(`Requested to withdraw ${amount} ${decodeName(spotMarket.name)} as vault manager: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
54
54
  }
55
55
 
56
56
 
@@ -0,0 +1,121 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import {
3
+ OptionValues,
4
+ Command
5
+ } from "commander";
6
+ import { dumpTransactionMessage, getCommandContext } from "../utils";
7
+ import { BN, PERCENTAGE_PRECISION, convertToNumber } from "@drift-labs/sdk";
8
+
9
+ export const managerUpdateFees = async (program: Command, cmdOpts: OptionValues) => {
10
+ let vaultAddress: PublicKey;
11
+ try {
12
+ vaultAddress = new PublicKey(cmdOpts.vaultAddress as string);
13
+ } catch (err) {
14
+ console.error("Invalid vault address");
15
+ process.exit(1);
16
+ }
17
+
18
+ const {
19
+ driftVault,
20
+ driftClient,
21
+ } = await getCommandContext(program, true);
22
+
23
+ const vault = await driftVault.getVault(vaultAddress);
24
+
25
+ const timelockDuration = cmdOpts.timelockDuration;
26
+ let timelockDurationBN: BN | null = null;
27
+ // const minTimelockDurationBN = new BN(Math.max(1 * 24 * 60 * 60, vault.redeemPeriod.toNumber()));
28
+ const minTimelockDurationBN = new BN(Math.max(1 * 60 * 60, vault.redeemPeriod.toNumber()));
29
+ if (timelockDuration !== undefined && timelockDuration !== null) {
30
+ timelockDurationBN = new BN(parseInt(timelockDuration));
31
+ if (timelockDurationBN.lt(minTimelockDurationBN)) {
32
+ throw new Error(`Timelock duration must be at least ${minTimelockDurationBN.toNumber()} seconds`);
33
+ }
34
+ } else {
35
+ timelockDurationBN = minTimelockDurationBN;
36
+ }
37
+
38
+ let managementFee = cmdOpts.managementFee;
39
+ let managementFeeBN: BN | null = null;
40
+ if (managementFee !== undefined && managementFee !== null) {
41
+ managementFee = parseInt(managementFee);
42
+ managementFeeBN = new BN(managementFee).mul(PERCENTAGE_PRECISION).div(new BN(100));
43
+ }
44
+
45
+ let profitShare = cmdOpts.profitShare;
46
+ let profitShareNumber: number | null = null;
47
+ if (profitShare !== undefined && profitShare !== null) {
48
+ profitShare = parseInt(profitShare);
49
+ profitShareNumber = profitShare * PERCENTAGE_PRECISION.toNumber() / 100.0;
50
+ }
51
+
52
+ let hurdleRate = cmdOpts.hurdleRate;
53
+ let hurdleRateNumber: number | null = null;
54
+ if (hurdleRate !== undefined && hurdleRate !== null) {
55
+ hurdleRate = parseInt(hurdleRate);
56
+ hurdleRateNumber = hurdleRate * PERCENTAGE_PRECISION.toNumber() / 100.0;
57
+ }
58
+
59
+ console.log(`Updating fees, effective in ${timelockDurationBN?.toNumber()} seconds`);
60
+
61
+ const managementFeeBefore = convertToNumber(vault.managementFee, PERCENTAGE_PRECISION) * 100.0;
62
+ const managementFeeAfter = managementFeeBN ? `${convertToNumber(managementFeeBN, PERCENTAGE_PRECISION) * 100.0}%` : 'unchanged';
63
+ console.log(` ManagementFee: ${managementFeeBefore}% -> ${managementFeeAfter}`);
64
+
65
+ const profitShareBefore = vault.profitShare / PERCENTAGE_PRECISION.toNumber() * 100.0;
66
+ const profitShareAfter = profitShareNumber !== null ? `${profitShareNumber / PERCENTAGE_PRECISION.toNumber() * 100.0}%` : 'unchanged';
67
+ console.log(` ProfitShare: ${profitShareBefore}% -> ${profitShareAfter}`);
68
+
69
+ const hurdleRateBefore = vault.hurdleRate / PERCENTAGE_PRECISION.toNumber() * 100.0;
70
+ const hurdleRateAfter = hurdleRateNumber !== null ? `${hurdleRateNumber / PERCENTAGE_PRECISION.toNumber() * 100.0}%` : 'unchanged';
71
+ console.log(` HurdleRate: ${hurdleRateBefore}% -> ${hurdleRateAfter}`);
72
+
73
+ const readline = require('readline').createInterface({
74
+ input: process.stdin,
75
+ output: process.stdout
76
+ });
77
+ console.log('');
78
+ const answer = await new Promise(resolve => {
79
+ readline.question('Is the above information correct? (yes/no) ', (answer: string) => {
80
+ readline.close();
81
+ resolve(answer);
82
+ });
83
+ });
84
+ if ((answer as string).toLowerCase() !== 'yes') {
85
+ console.log('Fee update canceled.');
86
+ readline.close();
87
+ process.exit(0);
88
+ }
89
+ console.log('Updating fees...');
90
+
91
+ const newParams = {
92
+ timelockDuration: timelockDurationBN,
93
+ newManagementFee: managementFeeBN,
94
+ newProfitShare: profitShareNumber,
95
+ newHurdleRate: hurdleRateNumber,
96
+ };
97
+
98
+ let done = false;
99
+ while (!done) {
100
+ try {
101
+ if (cmdOpts.dumpTransactionMessage) {
102
+ const tx = await driftVault.getManagerUpdateFeesIx(vaultAddress, newParams);
103
+ console.log(dumpTransactionMessage(driftClient.wallet.publicKey, [tx]));
104
+ } else {
105
+ const tx = await driftVault.managerUpdateFees(vaultAddress, newParams);
106
+ console.log(`Updated vault fees as vault manager: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
107
+ done = true;
108
+ }
109
+ break;
110
+ } catch (e) {
111
+ const err = e as Error;
112
+ if (err.message.includes('TransactionExpiredTimeoutError')) {
113
+ console.log(err.message);
114
+ console.log('Transaction timeout. Retrying...');
115
+ await new Promise(resolve => setTimeout(resolve, 5000));
116
+ } else {
117
+ throw err;
118
+ }
119
+ }
120
+ }
121
+ };
@@ -27,6 +27,6 @@ export const managerUpdateMarginTradingEnabled = async (program: Command, cmdOpt
27
27
  console.log(dumpTransactionMessage(driftClient.wallet.publicKey, [tx]));
28
28
  } else {
29
29
  const tx = await driftVault.updateMarginTradingEnabled(vaultAddress, enabled);
30
- console.log(`Updated margin trading vault manager: https://solscan.io/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
30
+ console.log(`Updated margin trading vault manager: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
31
31
  }
32
32
  };
@@ -31,6 +31,6 @@ export const managerUpdatePoolId = async (program: Command, cmdOpts: OptionValue
31
31
  console.log(dumpTransactionMessage(driftClient.wallet.publicKey, [tx]));
32
32
  } else {
33
33
  const tx = await driftVault.updateUserPoolId(vaultAddress, poolId);
34
- console.log(`Updated pool id vault manager: https://solscan.io/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
34
+ console.log(`Updated pool id vault manager: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
35
35
  }
36
36
  };
@@ -57,6 +57,13 @@ export const managerUpdateVault = async (program: Command, cmdOpts: OptionValues
57
57
  profitShareNumber = profitShare * PERCENTAGE_PRECISION.toNumber() / 100.0;
58
58
  }
59
59
 
60
+ let hurdleRate = cmdOpts.hurdleRate;
61
+ let hurdleRateNumber: number | null = null;
62
+ if (hurdleRate !== undefined && hurdleRate !== null) {
63
+ hurdleRate = parseInt(hurdleRate);
64
+ hurdleRateNumber = hurdleRate * PERCENTAGE_PRECISION.toNumber() / 100.0;
65
+ }
66
+
60
67
  let minDepositAmount = cmdOpts.minDepositAmount;
61
68
  let minDepositAmountBN: BN | null = null;
62
69
  if (minDepositAmount !== undefined && minDepositAmount !== null) {
@@ -85,6 +92,10 @@ export const managerUpdateVault = async (program: Command, cmdOpts: OptionValues
85
92
  const profitShareAfter = profitShareNumber !== null ? `${profitShareNumber / PERCENTAGE_PRECISION.toNumber() * 100.0}%` : 'unchanged';
86
93
  console.log(` ProfitShare: ${profitShareBefore}% -> ${profitShareAfter}`);
87
94
 
95
+ const hurdleRateBefore = vault.hurdleRate / PERCENTAGE_PRECISION.toNumber() * 100.0;
96
+ const hurdleRateAfter = hurdleRateNumber !== null ? `${hurdleRateNumber / PERCENTAGE_PRECISION.toNumber() * 100.0}%` : 'unchanged';
97
+ console.log(` HurdleRate: ${hurdleRateBefore}% -> ${hurdleRateAfter}`);
98
+
88
99
  const permissioned: boolean | null = (cmdOpts.permissioned === null || cmdOpts.permissioned === undefined) ? null : JSON.parse(cmdOpts.permissioned);
89
100
  const permissionedBefore = vault.permissioned;
90
101
  const permissionedAfter = permissioned !== null ? permissioned : 'unchanged';
@@ -115,7 +126,7 @@ export const managerUpdateVault = async (program: Command, cmdOpts: OptionValues
115
126
  minDepositAmount: minDepositAmountBN,
116
127
  managementFee: managementFeeBN,
117
128
  profitShare: profitShareNumber,
118
- hurdleRate: null,
129
+ hurdleRate: hurdleRateNumber,
119
130
  permissioned,
120
131
  };
121
132
 
@@ -127,7 +138,7 @@ export const managerUpdateVault = async (program: Command, cmdOpts: OptionValues
127
138
  console.log(dumpTransactionMessage(driftClient.wallet.publicKey, [tx]));
128
139
  } else {
129
140
  const tx = await driftVault.managerUpdateVault(vaultAddress, newParams);
130
- console.log(`Updated vault params as vault manager: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
141
+ console.log(`Updated vault params as vault manager: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
131
142
  done = true;
132
143
  }
133
144
  break;
@@ -36,7 +36,7 @@ export const managerUpdateVaultDelegate = async (program: Command, cmdOpts: Opti
36
36
  console.log(dumpTransactionMessage(driftClient.wallet.publicKey, [tx]));
37
37
  } else {
38
38
  const tx = await driftVault.updateDelegate(vaultAddress, delegate);
39
- console.log(`Updated vault delegate to ${delegate.toBase58()}: https://solscan.io/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
39
+ console.log(`Updated vault delegate to ${delegate.toBase58()}: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
40
40
  }
41
41
 
42
42
  };
@@ -59,7 +59,7 @@ export const managerUpdateVaultManager = async (program: Command, cmdOpts: Optio
59
59
  console.log(dumpTransactionMessage(driftClient.wallet.publicKey, [tx]));
60
60
  } else {
61
61
  const tx = await driftVault.managerUpdateVaultManager(vaultAddress, manager);
62
- console.log(`Updated vault manager: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
62
+ console.log(`Updated vault manager: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
63
63
  done = true;
64
64
  }
65
65
  break;
@@ -25,6 +25,6 @@ export const managerWithdraw = async (program: Command, cmdOpts: OptionValues) =
25
25
  console.log(dumpTransactionMessage(driftClient.wallet.publicKey, [tx]));
26
26
  } else {
27
27
  const tx = await driftVault.managerWithdraw(vaultAddress);
28
- console.log(`Withrew as vault manager: https://solscan.io/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
28
+ console.log(`Withrew as vault manager: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
29
29
  }
30
30
  };
@@ -40,5 +40,5 @@ export const requestWithdraw = async (program: Command, cmdOpts: OptionValues) =
40
40
  const withdrawAmountBN = new BN(cmdOpts.amount);
41
41
 
42
42
  const tx = await driftVault.requestWithdraw(vaultDepositorAddress, withdrawAmountBN, WithdrawUnit.SHARES);
43
- console.log(`Requested to withdraw ${cmdOpts.amount} shares from the vault: https://solscan.io/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet" : ""}`);
43
+ console.log(`Requested to withdraw ${cmdOpts.amount} shares from the vault: https://solana.fm/tx/${tx}${driftClient.env === "devnet" ? "?cluster=devnet-solana" : ""}`);
44
44
  };