@carrot-protocol/clend-vaults-rpc 0.0.2 → 0.0.3-pub2-dev-be4bd59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/rpc.js ADDED
@@ -0,0 +1,685 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ClendVaultsClient = void 0;
4
+ exports.buildEquityRemainingAccounts = buildEquityRemainingAccounts;
5
+ // rpc.ts
6
+ const anchor_1 = require("@coral-xyz/anchor");
7
+ const spl_token_1 = require("@solana/spl-token");
8
+ const decimal_js_1 = require("decimal.js");
9
+ const program_1 = require("./program");
10
+ const addresses_1 = require("./addresses");
11
+ const clend_rpc_1 = require("@carrot-protocol/clend-rpc");
12
+ const state_1 = require("./state");
13
+ const swapper_1 = require("./swapper");
14
+ const math_1 = require("./math");
15
+ class ClendVaultsClient {
16
+ constructor(provider, skipPreflight = false, swapperOverride) {
17
+ this.connection = provider.connection;
18
+ this.program = new program_1.ClendVaultsProgram(provider);
19
+ this.skipPreflight = skipPreflight;
20
+ this.clendClient = new clend_rpc_1.ClendClient(provider.connection, provider.wallet, skipPreflight, 0);
21
+ this.swapper =
22
+ swapperOverride || new swapper_1.ClendVaultsJupSwapper(this.connection);
23
+ }
24
+ address() {
25
+ return this.program.program.provider.publicKey;
26
+ }
27
+ async getVault(vault) {
28
+ const vaultAccountInfo = await this.connection.getAccountInfo(vault);
29
+ if (!vaultAccountInfo) {
30
+ throw new Error(`Vault account not found: ${vault.toString()}`);
31
+ }
32
+ return (0, state_1.parseVaultAccount)(vault, vaultAccountInfo, this.connection);
33
+ }
34
+ async getVaultEquity(vault) {
35
+ let vaultEquity = 0;
36
+ const clendAccountEquity = [];
37
+ // calculate equity for each clend account
38
+ for (const clendAccount of vault.clendAccounts) {
39
+ const clendAccountData = await this.clendClient.getClendAccount(clendAccount.address);
40
+ if (!clendAccountData) {
41
+ throw new Error(`Clend account not found: ${clendAccount.address.toString()}`);
42
+ }
43
+ const lendingAccountEquity = (0, math_1.calculateLendingAccountEquity)(clendAccountData.lendingAccount);
44
+ clendAccountEquity.push({
45
+ address: clendAccount.address,
46
+ equity: lendingAccountEquity,
47
+ });
48
+ vaultEquity += lendingAccountEquity;
49
+ }
50
+ // calculate equity for each reserve
51
+ const reserveEquity = [];
52
+ for (const asset of vault.assets) {
53
+ const mint = asset.mint.address;
54
+ const priceUi = await this.clendClient.getPythOraclePrice(mint);
55
+ const rEquity = priceUi * asset.reserve.amountUi;
56
+ reserveEquity.push({ address: asset.reserve.address, equity: rEquity });
57
+ vaultEquity += rEquity;
58
+ }
59
+ return {
60
+ address: vault.address,
61
+ vaultEquity,
62
+ clendAccountEquity,
63
+ reserveEquity,
64
+ };
65
+ }
66
+ async initVault(sharesMint, managementFeeBps) {
67
+ const { vault, ixns: initVaultIxns } = await this.program.initVault(sharesMint, this.address(), managementFeeBps);
68
+ const initializeTxSig = await this.send(initVaultIxns);
69
+ return { txSig: initializeTxSig, vault };
70
+ }
71
+ async addAsset(vault, assetMint, assetOracle, addAssetArgs) {
72
+ const ixns = await this.program.addAsset(vault, this.address(), assetMint, assetOracle, spl_token_1.TOKEN_PROGRAM_ID, addAssetArgs);
73
+ const txSig = await this.send(ixns);
74
+ return txSig;
75
+ }
76
+ async addClendAccount(vault, clendGroup) {
77
+ const clendAccount = anchor_1.web3.Keypair.generate();
78
+ const addClendAccountIxns = await this.program.addClendAccount(vault, this.address(), clendGroup, clendAccount);
79
+ const txSig = await this.send([...addClendAccountIxns], [clendAccount]);
80
+ return { txSig, clendAccount: clendAccount.publicKey };
81
+ }
82
+ async updateVaultManager(vault, newManager) {
83
+ return this.program.updateVaultManager(vault, this.address(), newManager);
84
+ }
85
+ async issue(vault, assetMint, amount) {
86
+ const ixns = await this.prepareIssueIxns(this.address(), vault, assetMint, amount);
87
+ const txSig = await this.send(ixns);
88
+ return txSig;
89
+ }
90
+ async prepareIssueIxns(user, vault, assetMint, amount) {
91
+ // get vault and asset data
92
+ const vaultData = await this.getVault(vault);
93
+ const assetData = vaultData.assets.find((a) => a.mint.address.equals(assetMint));
94
+ const remainingAccounts = await buildEquityRemainingAccounts(vaultData, this.clendClient);
95
+ const ixns = await this.program.issue(vault, vaultData.sharesMint, user, assetMint, assetData.oracle, amount, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.TOKEN_2022_PROGRAM_ID, remainingAccounts);
96
+ return ixns;
97
+ }
98
+ async redeem(vault, assetMint, sharesAmount) {
99
+ const ixns = await this.prepareRedeemIxns(this.address(), vault, assetMint, sharesAmount);
100
+ const txSig = await this.send(ixns);
101
+ return txSig;
102
+ }
103
+ async prepareRedeemIxns(user, vault, assetMint, amount) {
104
+ const vaultData = await this.getVault(vault);
105
+ const assetData = vaultData.assets.find((a) => a.mint.address.equals(assetMint));
106
+ const remainingAccounts = await buildEquityRemainingAccounts(vaultData, this.clendClient);
107
+ return this.program.redeem(vault, vaultData.sharesMint, user, assetMint, assetData.oracle, amount, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.TOKEN_2022_PROGRAM_ID, remainingAccounts);
108
+ }
109
+ async clendAccountDeposit(vault, clendAccount, assetMint, amount) {
110
+ // fetch clend account data
111
+ const clendAccountData = await this.clendClient.getClendAccount(clendAccount);
112
+ if (!clendAccountData) {
113
+ throw new Error(`Clend account not found: ${clendAccount.toString()}`);
114
+ }
115
+ // fetch vault data
116
+ const vaultData = await this.getVault(vault);
117
+ const assetData = vaultData.assets.find((a) => a.mint.address.equals(assetMint));
118
+ // remaining accounts for protocol call
119
+ const ra = (0, clend_rpc_1.getClendAccountRemainingAccounts)([]);
120
+ const ixns = await this.program.clendAccountDeposit(vault, this.address(), clendAccountData.group, clendAccount, assetMint, spl_token_1.TOKEN_PROGRAM_ID, amount, assetData.reserve.address, ra);
121
+ const txSig = await this.send(ixns);
122
+ return txSig;
123
+ }
124
+ async clendAccountWithdraw(vault, clendAccount, assetMint, amount) {
125
+ // fetch clend account data
126
+ const clendAccountData = await this.clendClient.getClendAccount(clendAccount);
127
+ if (!clendAccountData) {
128
+ throw new Error(`Clend account not found: ${clendAccount.toString()}`);
129
+ }
130
+ // fetch active banks
131
+ const clendAccountActiveBanks = (0, clend_rpc_1.getClendAccountActiveBanks)(clendAccountData);
132
+ const activeBankData = [];
133
+ for (const bank of clendAccountActiveBanks) {
134
+ const bankData = await this.clendClient.getBank(bank);
135
+ activeBankData.push(bankData);
136
+ }
137
+ const ra = (0, clend_rpc_1.getClendAccountRemainingAccounts)(activeBankData);
138
+ const ixns = await this.program.clendAccountWithdraw(vault, this.address(), clendAccountData.group, clendAccount, assetMint, spl_token_1.TOKEN_PROGRAM_ID, amount, ra);
139
+ const txSig = await this.send(ixns);
140
+ return txSig;
141
+ }
142
+ async clendAccountRepay(vault, clendAccount, assetMint, amount) {
143
+ const clendAccountData = await this.clendClient.getClendAccount(clendAccount);
144
+ if (!clendAccountData) {
145
+ throw new Error(`Clend account not found: ${clendAccount.toString()}`);
146
+ }
147
+ const ra = (0, clend_rpc_1.getClendAccountRemainingAccounts)([]);
148
+ const ixns = await this.program.clendAccountRepay(vault, this.address(), clendAccountData.group, clendAccount, assetMint, spl_token_1.TOKEN_PROGRAM_ID, amount, ra);
149
+ const txSig = await this.send(ixns);
150
+ return txSig;
151
+ }
152
+ async clendAccountBorrow(vault, clendAccount, assetMint, amount) {
153
+ const clendAccountData = await this.clendClient.getClendAccount(clendAccount);
154
+ if (!clendAccountData) {
155
+ throw new Error(`Clend account not found: ${clendAccount.toString()}`);
156
+ }
157
+ // fetch active banks
158
+ const clendAccountActiveBanks = (0, clend_rpc_1.getClendAccountActiveBanks)(clendAccountData);
159
+ // add target bank if not already in list
160
+ // this would be the case for a first time borrow
161
+ const targetBank = (0, clend_rpc_1.getBankPda)(clendAccountData.group, assetMint);
162
+ const alreadyInBankList = clendAccountActiveBanks.some((c) => c.equals(targetBank));
163
+ if (!alreadyInBankList) {
164
+ clendAccountActiveBanks.push(targetBank);
165
+ }
166
+ const activeBankData = [];
167
+ for (const bank of clendAccountActiveBanks) {
168
+ const bankData = await this.clendClient.getBank(bank);
169
+ activeBankData.push(bankData);
170
+ }
171
+ const ra = (0, clend_rpc_1.getClendAccountRemainingAccounts)(activeBankData);
172
+ const ixns = await this.program.clendAccountBorrow(vault, this.address(), clendAccountData.group, clendAccount, assetMint, spl_token_1.TOKEN_PROGRAM_ID, amount, ra);
173
+ const txSig = await this.send(ixns);
174
+ return txSig;
175
+ }
176
+ async clendAccountClaimEmissions(vault, clendGroup, clendAccount, bank, assetMint, emissionsMint) {
177
+ const emissionsVault = (0, clend_rpc_1.getBankLiquidityVaultPda)(bank);
178
+ const emissionsAuthority = (0, clend_rpc_1.getBankLiquidityVaultAuthorityPda)(bank);
179
+ // destination is an ATA owned by the vault PDA
180
+ const destinationAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(emissionsMint, vault, true, spl_token_1.TOKEN_PROGRAM_ID);
181
+ return this.program.clendAccountClaimEmissions(vault, this.address(), clendGroup, clendAccount, bank, assetMint, emissionsMint, destinationAccount, spl_token_1.TOKEN_PROGRAM_ID, emissionsVault, emissionsAuthority, clend_rpc_1.CLEND_PROGRAM_ID);
182
+ }
183
+ // adjust the leverage of the clend account
184
+ async clendAccountAdjustLeverage(clendAccount, collateralMint, debtMint, targetLeverage, slippageBps) {
185
+ const clendAccountState = await this.clendClient.getClendAccount(clendAccount);
186
+ if (!clendAccountState) {
187
+ throw new Error(`Clend account not found: ${clendAccount.toString()}`);
188
+ }
189
+ const adjustLeverageTxSig = await this.clendClient.adjustLeverage(clendAccountState.group, clendAccount, collateralMint, debtMint, targetLeverage, slippageBps, this.swapper);
190
+ return adjustLeverageTxSig;
191
+ }
192
+ // create instructions for redeeming shares
193
+ // however this will pull from a clend account balance
194
+ // the equity value of the shares desired for redemption
195
+ async prepareRedeemFromClendAccountIxns(user, vault, clendAccount, outputAssetMint, collateralMint, debtMint, totalSharesToRedeem, sharesToRedeemFromClendAccount, slippageBps) {
196
+ const vaultState = await this.getVault(vault);
197
+ const outputAssetState = vaultState.assets.find((a) => a.mint.address.equals(outputAssetMint));
198
+ if (!outputAssetState) {
199
+ throw new Error(`output asset mint ${outputAssetMint.toString()} not found in vault ${vault.toString()}`);
200
+ }
201
+ // get price of output asset
202
+ const priceUi = await this.clendClient.getPythOraclePrice(outputAssetState.mint.address);
203
+ const vaultEquity = await this.getVaultEquity(vaultState);
204
+ const sharesSupplyUi = (0, math_1.amountToUi)(vaultState.sharesSupply, vaultState.sharesDecimals);
205
+ const sharesToRedeemFromClendAccountUi = (0, math_1.amountToUi)(sharesToRedeemFromClendAccount, vaultState.sharesDecimals);
206
+ const vaultNav = (0, math_1.calculateVaultNav)(vaultEquity.vaultEquity, sharesSupplyUi);
207
+ // TODO: will need to add fees to this
208
+ const desiredOutputAmount = (0, math_1.convertSharesToAsset)(vaultNav, sharesToRedeemFromClendAccountUi, outputAssetState.mint.decimals, priceUi);
209
+ // check if clend account is listed in vault config
210
+ const clendAccountIsValid = vaultState.clendAccounts.some((c) => c.address.equals(clendAccount));
211
+ if (!clendAccountIsValid) {
212
+ throw new Error(`Clend account ${clendAccount.toString()} not found in vault ${vault.toString()} config`);
213
+ }
214
+ // fetch clend account state
215
+ const clendAccountState = await this.clendClient.getClendAccount(clendAccount);
216
+ if (!clendAccountState) {
217
+ throw new Error(`error fetching clend account state: ${clendAccount.toString()}`);
218
+ }
219
+ // we need to know how many ixns will go before the leverage ixns
220
+ // this is for the flash loan index positioning
221
+ let prependedIxCount = 0;
222
+ let cuIxCount = 0; // TODO: in the clend libs we already account for this
223
+ prependedIxCount += cuIxCount;
224
+ // get the redemption ixns
225
+ // these are run after the withdraw leverage ixns
226
+ const redeemIxns = await this.prepareRedeemIxns(user, vault, outputAssetMint, totalSharesToRedeem);
227
+ // calculate parameters for withdraw leverage
228
+ // based on output asset mint
229
+ let debtToRepay;
230
+ let collateralToWithdraw;
231
+ let debtBankData;
232
+ let collateralBankData;
233
+ switch (outputAssetMint.toString()) {
234
+ case collateralMint.toString():
235
+ // get params for withdraw leverage
236
+ const { debtToRepay: debtToRepayCollateral, collateralToWithdraw: collateralToWithdrawCollateral, debtBankData: debtBankDataCollateral, collateralBankData: collateralBankDataCollateral, } = await this.clendClient.getNetWithdrawLeverageCollateralParams(clendAccountState.group, clendAccount, collateralMint, debtMint, desiredOutputAmount, false);
237
+ debtToRepay = debtToRepayCollateral;
238
+ collateralToWithdraw = collateralToWithdrawCollateral;
239
+ debtBankData = debtBankDataCollateral;
240
+ collateralBankData = collateralBankDataCollateral;
241
+ break;
242
+ case debtMint.toString():
243
+ // get params for withdraw leverage
244
+ const { debtToRepay: debtToRepayDebt, collateralToWithdraw: collateralToWithdrawDebt, debtBankData: debtBankDataDebt, collateralBankData: collateralBankDataDebt, } = await this.clendClient.getNetWithdrawLeverageDebtParams(clendAccountState.group, clendAccount, collateralMint, debtMint, desiredOutputAmount, false);
245
+ debtToRepay = debtToRepayDebt;
246
+ collateralToWithdraw = collateralToWithdrawDebt;
247
+ debtBankData = debtBankDataDebt;
248
+ collateralBankData = collateralBankDataDebt;
249
+ break;
250
+ default:
251
+ throw new Error(`invalid output token mint: ${outputAssetMint.toString()}`);
252
+ }
253
+ // compose instructions for user to sign
254
+ let ixns = [];
255
+ let luts = [];
256
+ switch (outputAssetMint.toString()) {
257
+ case collateralMint.toString():
258
+ const { ixns: collateralIxns, luts: collateralLuts } = await this.getWithdrawLeverageCollateralIxns(vault, vaultState.manager, user, clendAccountState.group, clendAccount, collateralMint, debtMint, collateralBankData.mintDecimals, debtBankData.mintDecimals, false, collateralToWithdraw, desiredOutputAmount, debtToRepay, slippageBps, prependedIxCount, this.swapper);
259
+ ixns = collateralIxns;
260
+ luts = collateralLuts;
261
+ break;
262
+ case debtMint.toString():
263
+ const { ixns: debtIxns, luts: debtLuts } = await this.getWithdrawLeverageDebtIxns(vault, vaultState.manager, user, clendAccountState.group, clendAccount, collateralMint, debtMint, collateralBankData.mintDecimals, debtBankData.mintDecimals, false, debtToRepay, collateralToWithdraw, slippageBps, prependedIxCount, this.swapper);
264
+ ixns = debtIxns;
265
+ luts = debtLuts;
266
+ break;
267
+ default:
268
+ throw new Error(`invalid output token mint: ${outputAssetMint.toString()}`);
269
+ }
270
+ const allIxns = [...ixns, ...redeemIxns];
271
+ return { ixns: allIxns, luts };
272
+ }
273
+ async getWithdrawLeverageCollateralIxns(vault, vaultManager, user, clendGroup, clendAccount, collateralMint, debtMint, collateralDecimals, debtDecimals, withdrawAll, collateralToWithdraw, desiredNetCollateralToReceive, debtToRepay, slippageBps, additionalIxnCount, swapperOverride) {
274
+ // override swapper if provided
275
+ let activeSwapper = this.swapper;
276
+ if (swapperOverride) {
277
+ activeSwapper = swapperOverride;
278
+ }
279
+ const clendAccountData = await this.clendClient.getClendAccount(clendAccount);
280
+ if (!clendAccountData) {
281
+ throw new Error(`Clend account not found: ${clendAccount.toString()}`);
282
+ }
283
+ let activeBanks = (0, clend_rpc_1.getClendAccountActiveBanks)(clendAccountData);
284
+ const activeBankData = await this.clendClient.getBanks(activeBanks);
285
+ const remainingAccounts = (0, clend_rpc_1.getClendAccountRemainingAccounts)(activeBankData);
286
+ // this is for partial withdraws
287
+ // overriden if withdrawAll is true
288
+ let collateralToSwap = collateralToWithdraw.sub(desiredNetCollateralToReceive);
289
+ // again for partial withdraws
290
+ let swapQuote = await activeSwapper.getQuote({
291
+ payer: vault,
292
+ inputMint: collateralMint,
293
+ inputMintDecimals: collateralDecimals,
294
+ outputMint: debtMint,
295
+ outputMintDecimals: debtDecimals,
296
+ inputAmount: collateralToSwap,
297
+ slippageBps,
298
+ swapMode: "ExactIn",
299
+ });
300
+ if (withdrawAll) {
301
+ // Step 1: Get the current price via a nominal 1-token quote.
302
+ const oneToken = (0, math_1.uiToAmount)(1, collateralDecimals);
303
+ const nominalQuote = await activeSwapper.getQuote({
304
+ payer: vault,
305
+ inputMint: collateralMint,
306
+ inputMintDecimals: collateralDecimals,
307
+ outputMint: debtMint,
308
+ outputMintDecimals: debtDecimals,
309
+ inputAmount: oneToken,
310
+ slippageBps,
311
+ swapMode: "ExactIn",
312
+ });
313
+ const outAmountUi = (0, math_1.amountToUi)(nominalQuote.otherAmountThreshold, debtDecimals);
314
+ // Step 2: Estimate the ideal collateral needed to cover the debt.
315
+ const debtToRepayUi = new decimal_js_1.Decimal((0, math_1.amountToUi)(debtToRepay, debtDecimals).toString());
316
+ const collateralToSwapUi_ideal = debtToRepayUi.div(outAmountUi);
317
+ const collateralToSwap_ideal = (0, math_1.uiToAmount)(collateralToSwapUi_ideal.toNumber(), collateralDecimals);
318
+ collateralToSwap = collateralToSwap_ideal;
319
+ //collateralToSwap = adjustAmountForSlippage(
320
+ // collateralToSwap_ideal,
321
+ // slippageBps,
322
+ //);
323
+ // Step 4 (Final Quote): Get the real quote for the buffered amount.
324
+ swapQuote = await activeSwapper.getQuote({
325
+ payer: user,
326
+ inputMint: collateralMint,
327
+ inputMintDecimals: collateralDecimals,
328
+ outputMint: debtMint,
329
+ outputMintDecimals: debtDecimals,
330
+ inputAmount: collateralToSwap,
331
+ slippageBps,
332
+ swapMode: "ExactIn",
333
+ });
334
+ // check its sufficient
335
+ if (swapQuote.outAmount.lt(debtToRepay)) {
336
+ throw new Error(`Quote is insufficient to cover debt after slippage. Try increasing slippage tolerance.
337
+ ${swapQuote.otherAmountThreshold.toString()} < ${debtToRepay.toString()}`);
338
+ }
339
+ }
340
+ // 3. The final amount to repay is the minimum we are guaranteed to get from the swap.
341
+ const finalDebtToRepay = swapQuote.outAmount;
342
+ const swapIxns = await activeSwapper.getSwapIxns(swapQuote);
343
+ const collateralTokenProgram = await (0, clend_rpc_1.getTokenProgramForMintFromRpc)(this.connection, collateralMint);
344
+ const debtTokenProgram = await (0, clend_rpc_1.getTokenProgramForMintFromRpc)(this.connection, debtMint);
345
+ const withdrawIx = await this.program.clendAccountWithdraw(vault, vaultManager, clendGroup, clendAccount, collateralMint, collateralTokenProgram, collateralToWithdraw, remainingAccounts);
346
+ // if withdrawAll == true we need to make sure we withdraw emissions from both banks before withdrawing all the bank tokens
347
+ const emissionsIxns = [];
348
+ if (withdrawAll) {
349
+ // get bank data
350
+ const collateralBankData = activeBankData.find((b) => b.mint.equals(collateralMint));
351
+ if (!collateralBankData) {
352
+ throw new Error(`Collateral bank not found: ${collateralMint.toString()}`);
353
+ }
354
+ const debtBankData = activeBankData.find((b) => b.mint.equals(debtMint));
355
+ if (!debtBankData) {
356
+ throw new Error(`Debt bank not found: ${debtMint.toString()}`);
357
+ }
358
+ //// collateral bank emissions
359
+ //if (
360
+ // !collateralBankData.emissionsMint.equals(web3.PublicKey.default) &&
361
+ // (collateralBankData.flags === BankFlags.LendingEmissionsActive ||
362
+ // collateralBankData.flags ===
363
+ // BankFlags.LendingAndBorrowingEmissionsActive)
364
+ //) {
365
+ // const emissionsMint = collateralBankData.emissionsMint;
366
+ // const emissionsMintTokenProgram = await getTokenProgramForMintFromRpc(
367
+ // this.connection,
368
+ // emissionsMint,
369
+ // );
370
+ // const withdrawEmissionsIx = await this.program.clendAccountClaimEmissions(
371
+ // clendGroup,
372
+ // clendAccount,
373
+ // clendAccountData.authority,
374
+ // collateralBank,
375
+ // emissionsMint,
376
+ // emissionsMintTokenProgram,
377
+ // );
378
+ // emissionsIxns.push(...withdrawEmissionsIx);
379
+ //}
380
+ //// debt bank emissions
381
+ //if (
382
+ // !debtBankData.emissionsMint.equals(web3.PublicKey.default) &&
383
+ // (debtBankData.flags === BankFlags.BorrowEmissionsActive ||
384
+ // debtBankData.flags === BankFlags.LendingAndBorrowingEmissionsActive)
385
+ //) {
386
+ // const emissionsMint = debtBankData.emissionsMint;
387
+ // const emissionsMintTokenProgram = await getTokenProgramForMintFromRpc(
388
+ // this.connection,
389
+ // emissionsMint,
390
+ // );
391
+ // const withdrawEmissionsIx = await this.program.clendAccountWithdrawEmissions(
392
+ // clendGroup,
393
+ // clendAccount,
394
+ // clendAccountData.authority,
395
+ // debtBank,
396
+ // emissionsMint,
397
+ // emissionsMintTokenProgram,
398
+ // );
399
+ // emissionsIxns.push(...withdrawEmissionsIx);
400
+ //}
401
+ }
402
+ // Create repay instruction with the amount from the 'ExactIn' swap
403
+ const repayIx = await this.program.clendAccountRepay(vault, vaultManager, clendGroup, clendAccount, debtMint, debtTokenProgram, finalDebtToRepay, remainingAccounts);
404
+ const ixnsWithoutFlashLoan = [
405
+ ...emissionsIxns,
406
+ ...withdrawIx,
407
+ ...swapIxns.ixns,
408
+ ...repayIx,
409
+ ];
410
+ const cuIxns = 2;
411
+ const endIndex = new anchor_1.BN(cuIxns + ixnsWithoutFlashLoan.length + 1 + additionalIxnCount);
412
+ const remainingAccountsForFlashLoan = (0, clend_rpc_1.getClendAccountRemainingAccounts)(activeBankData);
413
+ const { beginFlashLoanIx, endFlashLoanIx } = await this.clendClient.instructions.createFlashLoanInstructions(clendAccount, user, endIndex, remainingAccountsForFlashLoan);
414
+ const instructions = [
415
+ beginFlashLoanIx,
416
+ ...ixnsWithoutFlashLoan,
417
+ endFlashLoanIx,
418
+ ];
419
+ return { ixns: instructions, luts: swapIxns.luts };
420
+ }
421
+ async getWithdrawLeverageDebtIxns(vault, vaultManager, user, clendGroup, clendAccount, collateralMint, debtMint, collateralDecimals, debtDecimals, withdrawAll, debtToRepay, collateralToWithdraw, slippageBps, additionalIxnCount, swapperOverride) {
422
+ // override swapper if provided
423
+ let activeSwapper = this.swapper;
424
+ if (swapperOverride) {
425
+ activeSwapper = swapperOverride;
426
+ }
427
+ const clendAccountData = await this.clendClient.getClendAccount(clendAccount);
428
+ if (!clendAccountData) {
429
+ throw new Error(`Clend account not found: ${clendAccount.toString()}`);
430
+ }
431
+ let activeBanks = (0, clend_rpc_1.getClendAccountActiveBanks)(clendAccountData);
432
+ const activeBankData = await this.clendClient.getBanks(activeBanks);
433
+ const remainingAccounts = (0, clend_rpc_1.getClendAccountRemainingAccounts)(activeBankData);
434
+ // Get a fresh 'ExactIn' quote for swapping the total collateral to withdraw
435
+ const swapQuote = await activeSwapper.getQuote({
436
+ payer: vault,
437
+ inputMint: collateralMint,
438
+ inputMintDecimals: collateralDecimals,
439
+ outputMint: debtMint,
440
+ outputMintDecimals: debtDecimals,
441
+ inputAmount: collateralToWithdraw,
442
+ slippageBps,
443
+ swapMode: "ExactIn",
444
+ });
445
+ // The minimum amount of debt token we are guaranteed to receive from the swap
446
+ const minDebtReceivedFromSwap = swapQuote.outAmount;
447
+ // Determine the final amount to repay. It's the lesser of what we need and what we're guaranteed to get.
448
+ const finalDebtToRepay = anchor_1.BN.min(debtToRepay, minDebtReceivedFromSwap);
449
+ // Get Swap Instructions from the swapper
450
+ const swapIxns = await activeSwapper.getSwapIxns(swapQuote);
451
+ // --- ATAs and Clend Instructions ---
452
+ const collateralTokenProgram = (0, clend_rpc_1.getTokenProgramForMint)(collateralMint);
453
+ const debtTokenProgram = (0, clend_rpc_1.getTokenProgramForMint)(debtMint);
454
+ // Create withdraw instruction: Withdraw the total collateral calculated by the params function
455
+ const withdrawIx = await this.program.clendAccountWithdraw(vault, vaultManager, clendGroup, clendAccount, collateralMint, collateralTokenProgram, collateralToWithdraw, remainingAccounts);
456
+ // if withdrawAll == true we need to make sure we withdraw emissions from both banks before withdrawing all the bank tokens
457
+ const emissionsIxns = [];
458
+ if (withdrawAll) {
459
+ // get bank data
460
+ const collateralBankData = activeBankData.find((b) => b.mint.equals(collateralMint));
461
+ if (!collateralBankData) {
462
+ throw new Error(`Collateral bank not found: ${collateralMint.toString()}`);
463
+ }
464
+ const debtBankData = activeBankData.find((b) => b.mint.equals(debtMint));
465
+ if (!debtBankData) {
466
+ throw new Error(`Debt bank not found: ${debtMint.toString()}`);
467
+ }
468
+ //// collateral bank emissions
469
+ //if (
470
+ // !collateralBankData.emissionsMint.equals(web3.PublicKey.default) &&
471
+ // (collateralBankData.flags === BankFlags.LendingEmissionsActive ||
472
+ // collateralBankData.flags ===
473
+ // BankFlags.LendingAndBorrowingEmissionsActive)
474
+ //) {
475
+ // const emissionsMint = collateralBankData.emissionsMint;
476
+ // const emissionsMintTokenProgram = await getTokenProgramForMintFromRpc(
477
+ // this.connection,
478
+ // emissionsMint,
479
+ // );
480
+ // const withdrawEmissionsIx = await this.instructions.withdrawEmissions(
481
+ // clendGroup,
482
+ // clendAccount,
483
+ // clendAccountData.authority,
484
+ // collateralBank,
485
+ // emissionsMint,
486
+ // emissionsMintTokenProgram,
487
+ // );
488
+ // emissionsIxns.push(...withdrawEmissionsIx);
489
+ //}
490
+ //// debt bank emissions
491
+ //if (
492
+ // !debtBankData.emissionsMint.equals(web3.PublicKey.default) &&
493
+ // (debtBankData.flags === BankFlags.BorrowEmissionsActive ||
494
+ // debtBankData.flags === BankFlags.LendingAndBorrowingEmissionsActive)
495
+ //) {
496
+ // const emissionsMint = debtBankData.emissionsMint;
497
+ // const emissionsMintTokenProgram = await getTokenProgramForMintFromRpc(
498
+ // this.connection,
499
+ // emissionsMint,
500
+ // );
501
+ // const withdrawEmissionsIx = await this.instructions.withdrawEmissions(
502
+ // clendGroup,
503
+ // clendAccount,
504
+ // clendAccountData.authority,
505
+ // debtBank,
506
+ // emissionsMint,
507
+ // emissionsMintTokenProgram,
508
+ // );
509
+ // emissionsIxns.push(...withdrawEmissionsIx);
510
+ //}
511
+ }
512
+ // Create repay instruction: Repay the debt portion using the guaranteed swap output
513
+ const repayIx = await this.program.clendAccountRepay(vault, vaultManager, clendGroup, clendAccount, debtMint, debtTokenProgram, finalDebtToRepay, // Use the slippage-protected amount from the final quote
514
+ remainingAccounts);
515
+ // Assemble instructions
516
+ const ixnsWithoutFlashLoan = [
517
+ ...emissionsIxns,
518
+ ...withdrawIx,
519
+ ...swapIxns.ixns,
520
+ ...repayIx,
521
+ ];
522
+ // Flash Loan Wrapping
523
+ const cuIxns = 2;
524
+ const endIndex = new anchor_1.BN(cuIxns + ixnsWithoutFlashLoan.length + 1 + additionalIxnCount);
525
+ const remainingAccountsForFlashLoan = (0, clend_rpc_1.getClendAccountRemainingAccounts)(activeBankData);
526
+ const { beginFlashLoanIx, endFlashLoanIx } = await this.clendClient.instructions.createFlashLoanInstructions(clendAccount, user, endIndex, remainingAccountsForFlashLoan);
527
+ const instructions = [
528
+ beginFlashLoanIx,
529
+ ...ixnsWithoutFlashLoan,
530
+ endFlashLoanIx,
531
+ ];
532
+ return { ixns: instructions, luts: swapIxns.luts };
533
+ }
534
+ async swap(vault, inputMint, outputMint, inAmount) {
535
+ const vaultAssetInReserve = (0, spl_token_1.getAssociatedTokenAddressSync)(inputMint, vault, true, spl_token_1.TOKEN_PROGRAM_ID);
536
+ const vaultAssetOutReserve = (0, spl_token_1.getAssociatedTokenAddressSync)(outputMint, vault, true, spl_token_1.TOKEN_PROGRAM_ID);
537
+ const quote = await this.swapper.getQuote({
538
+ payer: vault,
539
+ inputMint: inputMint,
540
+ inputMintDecimals: 6,
541
+ outputMint: outputMint,
542
+ outputMintDecimals: 6,
543
+ inputAmount: inAmount,
544
+ swapMode: "ExactIn",
545
+ slippageBps: 100,
546
+ });
547
+ // TODO: no setup ixns
548
+ const swapIxns = await this.swapper.getSwapIxns(quote);
549
+ const swapIx = swapIxns.ixns.find((ix) => ix.programId.equals(addresses_1.JUPITER_SWAP_PROGRAM_ID));
550
+ const swapRemainingAccounts = swapIx.keys.map((key) => ({
551
+ pubkey: key.pubkey,
552
+ // If the account is the vault PDA, mark it as NOT a signer for the client transaction.
553
+ isSigner: key.pubkey.equals(vault) ? false : key.isSigner,
554
+ isWritable: key.isWritable,
555
+ }));
556
+ const swapData = swapIx.data;
557
+ const ixns = await this.program.swap(vault, this.address(), inputMint, outputMint, vaultAssetInReserve, vaultAssetOutReserve, swapData, swapRemainingAccounts, addresses_1.JUPITER_SWAP_PROGRAM_ID);
558
+ const txSig = await this.send(ixns);
559
+ return txSig;
560
+ }
561
+ async distributeFees(vault, sharesDestinationOwner) {
562
+ // fetch vault data
563
+ const vaultData = await this.getVault(vault);
564
+ const manager = vaultData.manager;
565
+ const shares = vaultData.sharesMint;
566
+ const ixns = await this.program.distributeFees(vault, manager, shares, sharesDestinationOwner);
567
+ const txSig = await this.send(ixns);
568
+ return txSig;
569
+ }
570
+ async send(ixns, additionalSigners = []) {
571
+ const { blockhash } = await this.connection.getLatestBlockhash("confirmed");
572
+ const msg = new anchor_1.web3.TransactionMessage({
573
+ payerKey: this.address(),
574
+ recentBlockhash: blockhash,
575
+ instructions: ixns,
576
+ }).compileToV0Message();
577
+ const tx = new anchor_1.web3.VersionedTransaction(msg);
578
+ const signedTx = await this.program.program.provider.wallet.signTransaction(tx);
579
+ signedTx.sign(additionalSigners);
580
+ try {
581
+ const txSig = await this.connection.sendRawTransaction(signedTx.serialize(), {
582
+ skipPreflight: this.skipPreflight,
583
+ });
584
+ // will throw an error if not found or tx errored
585
+ await this.confirmTx(txSig);
586
+ return txSig;
587
+ }
588
+ catch (e) {
589
+ if (e instanceof anchor_1.web3.SendTransactionError) {
590
+ throw new Error(`tx failed: ${e}`);
591
+ }
592
+ throw e;
593
+ }
594
+ }
595
+ async confirmTx(txSig) {
596
+ const maxAttempts = 90;
597
+ const sleepDurationMs = 1000; // 1 sec
598
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
599
+ const txResult = await this.connection.getSignatureStatuses([txSig], {
600
+ searchTransactionHistory: false,
601
+ });
602
+ if (txResult.value.length > 0 && txResult.value[0] !== null) {
603
+ const txSigResult = txResult.value[0];
604
+ if (txSigResult.err !== null) {
605
+ throw new Error(`tx returned an error: ${txSigResult.err.toString()}`);
606
+ }
607
+ return; // Transaction confirmed successfully
608
+ }
609
+ if (attempt < maxAttempts - 1) {
610
+ // sleep before next attempt
611
+ await new Promise((resolve) => setTimeout(resolve, sleepDurationMs));
612
+ }
613
+ }
614
+ // If we've exhausted all attempts, throw the error
615
+ throw new Error(`tx sig ${txSig} not found after ${maxAttempts} attempts`);
616
+ }
617
+ }
618
+ exports.ClendVaultsClient = ClendVaultsClient;
619
+ async function buildEquityRemainingAccounts(vaultData, clendClient) {
620
+ // gather all asset mints, oracles, reserves, and clend accounts
621
+ const assetMints = vaultData.assets.map((a) => a.mint.address);
622
+ const assetOracles = vaultData.assets.map((a) => a.oracle);
623
+ const assetReserves = vaultData.assets.map((a) => a.reserve.address);
624
+ const clendAccounts = vaultData.clendAccounts.map((ca) => ca.address);
625
+ const base = [
626
+ // mints
627
+ ...assetMints.map((a) => ({
628
+ pubkey: a,
629
+ isSigner: false,
630
+ isWritable: false,
631
+ })),
632
+ // oracles
633
+ ...assetOracles.map((a) => ({
634
+ pubkey: a,
635
+ isSigner: false,
636
+ isWritable: false,
637
+ })),
638
+ // reserves
639
+ ...assetReserves.map((a) => ({
640
+ pubkey: a,
641
+ isSigner: false,
642
+ isWritable: false,
643
+ })),
644
+ // clend account addrs
645
+ ...clendAccounts.map((ca) => ({
646
+ pubkey: ca,
647
+ isSigner: false,
648
+ isWritable: false,
649
+ })),
650
+ ];
651
+ // include each clend account's active banks + their oracles
652
+ const bankAndOracleMetas = [];
653
+ for (const ca of clendAccounts) {
654
+ const state = await clendClient.getClendAccount(ca);
655
+ if (!state) {
656
+ throw new Error(`Clend account not found: ${ca.toString()}`);
657
+ }
658
+ const activeBanks = (0, clend_rpc_1.getClendAccountActiveBanks)(state);
659
+ const activeBankState = [];
660
+ for (const bank of activeBanks) {
661
+ const bankData = await clendClient.getBank(bank);
662
+ activeBankState.push(bankData);
663
+ }
664
+ const rem = (0, clend_rpc_1.getClendAccountRemainingAccounts)(activeBankState);
665
+ bankAndOracleMetas.push(...rem);
666
+ }
667
+ // de-duplicate metas by pubkey, preserving writability where needed
668
+ const byPk = new Map();
669
+ const absorb = (m) => {
670
+ const k = m.pubkey.toBase58();
671
+ const prev = byPk.get(k);
672
+ if (!prev)
673
+ byPk.set(k, m);
674
+ else
675
+ byPk.set(k, {
676
+ pubkey: m.pubkey,
677
+ isSigner: prev.isSigner || m.isSigner,
678
+ isWritable: prev.isWritable || m.isWritable,
679
+ });
680
+ };
681
+ [...base, ...bankAndOracleMetas].forEach(absorb);
682
+ const remAccounts = [...byPk.values()];
683
+ return remAccounts;
684
+ }
685
+ //# sourceMappingURL=rpc.js.map