@carrot-protocol/clend-rpc 0.1.27-group-refactor1-dev-0a2c078 → 0.1.27-swapper1-dev-0d501ff

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 CHANGED
@@ -33,25 +33,10 @@ class ClendClient {
33
33
  address() {
34
34
  return this.provider.wallet.publicKey;
35
35
  }
36
- // determine type of oracle then fetch price from mint
37
- // TODO: do it better than this hack
38
- async getOraclePrice(mint) {
39
- try {
40
- return await this.getPythOraclePrice(mint);
41
- }
42
- catch (e) {
43
- try {
44
- return await this.getSBOraclePrice(mint);
45
- }
46
- catch (sbError) {
47
- throw sbError;
48
- }
49
- }
50
- }
51
36
  /**
52
37
  * Get pyth oracle price for a token
53
38
  */
54
- async getPythOraclePrice(mint) {
39
+ async getOraclePrice(mint) {
55
40
  const oracle = this.getOracle(mint);
56
41
  // get oracle account info
57
42
  const oracleAccountInfo = await this.connection.getAccountInfo(oracle, {
@@ -70,34 +55,10 @@ class ClendClient {
70
55
  // get ema price
71
56
  const emaPrice = new anchor_1.BN(oracleData.priceMessage.emaPrice);
72
57
  // return price
73
- return (0, utils_1.calculatePriceUi)(emaPrice.toNumber(), oracleData.priceMessage.exponent);
58
+ return (emaPrice.toNumber() *
59
+ Math.pow(10, Number(oracleData.priceMessage.exponent)));
74
60
  }
75
- async getSBOraclePrice(mint) {
76
- const oracle = this.getOracle(mint);
77
- // Fetch the raw account info.
78
- const feedAccountInfo = await this.connection.getAccountInfo(oracle, "processed");
79
- if (feedAccountInfo === null) {
80
- throw new Error(`Feed account ${oracle.toString()} not found.`);
81
- }
82
- // Use our manual parser instead of the Anchor coder.
83
- const pullFeedAccountData = (0, utils_1.parsePullFeedAccountData)(feedAccountInfo.data);
84
- if (!pullFeedAccountData ||
85
- !pullFeedAccountData.submissions ||
86
- pullFeedAccountData.submissions.length === 0) {
87
- throw new Error(`No valid submissions found for feed ${oracle.toString()}.`);
88
- }
89
- // The latest submission will be the first element after sorting.
90
- const latestSubmission = pullFeedAccountData.submissions[0];
91
- // The 'value' is a BN (BigNumber), so we convert it to a standard JavaScript number.
92
- // Be aware of potential precision loss if the number is larger than Number.MAX_SAFE_INTEGER.
93
- const price = Number(latestSubmission.value);
94
- // The exponent for Switchboard v2/v3 feeds is typically -18, but you should
95
- // confirm this for the specific feed you are using.
96
- const exponent = -18;
97
- const priceDecimal = (0, utils_1.calculatePriceUi)(price, exponent);
98
- return priceDecimal;
99
- }
100
- // find oracle for a mint from in memory cache
61
+ // find oracle for a mint
101
62
  getOracle(mint) {
102
63
  const oracleStr = this.mintOracleMapping.get(mint.toString());
103
64
  if (!oracleStr) {
@@ -197,73 +158,14 @@ class ClendClient {
197
158
  return this.send([initIx]);
198
159
  }
199
160
  // Bank Operations
200
- async addBank(clendGroup, bankMint, oracleSetupArgs, bankConfig, interestRateConfig) {
161
+ async addBank(clendGroup, bankMint, pythOracle, pythOracleFeedId, bankConfig, interestRateConfig) {
201
162
  const tokenProgram = (0, utils_1.getTokenProgramForMint)(bankMint);
202
163
  const feeStateAccountData = await this.getFeeState();
203
164
  const globalFeeWallet = feeStateAccountData.globalFeeWallet;
204
- const { bank, ixns } = await this.instructions.addBank(clendGroup, this.address(), globalFeeWallet, bankMint, tokenProgram, oracleSetupArgs, bankConfig, interestRateConfig);
165
+ const { bank, ixns } = await this.instructions.addBank(clendGroup, this.address(), globalFeeWallet, bankMint, tokenProgram, pythOracle, pythOracleFeedId, bankConfig, interestRateConfig);
205
166
  const txSig = await this.send(ixns);
206
167
  return { bank, txSig };
207
168
  }
208
- async setupBankEmissions(bank, emissionsMint, emissionsMode, emissionsRate, totalEmissions) {
209
- // some sanity checks
210
- if (totalEmissions.lte(new anchor_1.BN(0))) {
211
- throw new Error("totalEmissions must be greater than 0");
212
- }
213
- if (emissionsRate.lte(new anchor_1.BN(0))) {
214
- throw new Error("emissionsRate must be greater than 0");
215
- }
216
- if (emissionsRate.gt(totalEmissions)) {
217
- throw new Error("emissionsRate must be less than totalEmissions");
218
- }
219
- const bankData = await this.getBank(bank);
220
- if (!bankData.emissionsMint.equals(anchor_1.web3.PublicKey.default)) {
221
- throw new Error("bank already has emissions mint set");
222
- }
223
- // fetch token program from chain
224
- const emissionsMintTokenProgram = await (0, utils_1.getTokenProgramForMintFromRpc)(this.connection, emissionsMint);
225
- const ixns = await this.instructions.setupBankEmissions(bankData.group, this.address(), bank, emissionsMint, emissionsMintTokenProgram, emissionsMode, emissionsRate, totalEmissions);
226
- const txSig = await this.send(ixns);
227
- return txSig;
228
- }
229
- async updateBankEmissions(bank, emissionsMode, emissionsRate, additionalEmissions) {
230
- // some sanity checks
231
- if (additionalEmissions !== null && additionalEmissions.lte(new anchor_1.BN(0))) {
232
- throw new Error("additionalEmissions must be greater than 0");
233
- }
234
- if (emissionsRate !== null && emissionsRate.lte(new anchor_1.BN(0))) {
235
- throw new Error("emissionsRate must be greater than 0");
236
- }
237
- if (emissionsRate !== null &&
238
- additionalEmissions !== null &&
239
- emissionsRate.gt(additionalEmissions)) {
240
- throw new Error("emissionsRate must be less than additionalEmissions");
241
- }
242
- const bankData = await this.getBank(bank);
243
- // with updateBankEmissions you cannot change the emissions mint
244
- const emissionsMint = bankData.emissionsMint;
245
- if (emissionsMint.equals(anchor_1.web3.PublicKey.default)) {
246
- throw new Error("bank does not have emissions mint set, must call setupBankEmissions first");
247
- }
248
- const emissionsMintTokenProgram = await (0, utils_1.getTokenProgramForMintFromRpc)(this.connection, emissionsMint);
249
- const ix = await this.instructions.updateBankEmissions(bankData.group, this.address(), bank, emissionsMint, emissionsMintTokenProgram, emissionsMode, emissionsRate, additionalEmissions);
250
- const txSig = await this.send([ix]);
251
- return txSig;
252
- }
253
- async withdrawEmissions(clendAccount, bank) {
254
- const bankData = await this.getBank(bank);
255
- const emissionsMint = bankData.emissionsMint;
256
- const mintData = await this.connection.getAccountInfo(emissionsMint, {
257
- commitment: "processed",
258
- });
259
- if (mintData === null) {
260
- throw new Error(`emissions mint not found: ${emissionsMint.toString()}`);
261
- }
262
- const emissionsMintTokenProgram = mintData.owner;
263
- const ixns = await this.instructions.withdrawEmissions(bankData.group, clendAccount, this.address(), bank, emissionsMint, emissionsMintTokenProgram);
264
- const txSig = await this.send(ixns);
265
- return txSig;
266
- }
267
169
  // Account fetching Operations
268
170
  async getClendGroup(clendGroup) {
269
171
  const accountInfo = await this.connection.getAccountInfo(clendGroup, {
@@ -338,9 +240,9 @@ class ClendClient {
338
240
  collectedGroupFeesOutstanding: Number((0, utils_1.wrappedI80F48toBigNumber)(data.collectedGroupFeesOutstanding)),
339
241
  lastUpdate: new anchor_1.BN(data.lastUpdate),
340
242
  config: bankConfig,
341
- flags: (0, state_1.parseBankFlags)(new anchor_1.BN(data.flags)),
243
+ flags: new anchor_1.BN(data.flags),
342
244
  emissionsRate: new anchor_1.BN(data.emissionsRate),
343
- emissionsRemaining: new anchor_1.BN((0, utils_1.wrappedI80F48toBigNumber)(data.emissionsRemaining)),
245
+ emissionsRemaining: (0, utils_1.wrappedI80F48toBigNumber)(data.emissionsRemaining),
344
246
  emissionsMint: new anchor_1.web3.PublicKey(data.emissionsMint),
345
247
  collectedProgramFeesOutstanding: Number((0, utils_1.wrappedI80F48toBigNumber)(data.collectedProgramFeesOutstanding)),
346
248
  assetAmount,
@@ -393,29 +295,21 @@ class ClendClient {
393
295
  const bankData = await this.getBank(bank);
394
296
  const bankMint = bankData.mint;
395
297
  const price = await this.getOraclePrice(bankMint);
396
- const assetShares = Number((0, utils_1.wrappedI80F48toBigNumber)(balance.assetShares));
397
- const liabilityShares = Number((0, utils_1.wrappedI80F48toBigNumber)(balance.liabilityShares));
398
- const lastUpdate = new anchor_1.BN(balance.lastUpdate);
399
- const emissionsOutstanding = new anchor_1.BN((0, utils_1.wrappedI80F48toBigNumber)(balance.emissionsOutstanding));
400
- // calculate oustanding emissions
401
- const emissionsOutstandingAndUnclaimed = (0, utils_1.calculateUnclaimedEmissions)(new anchor_1.BN(assetShares), new anchor_1.BN(liabilityShares), lastUpdate, emissionsOutstanding, bankData.flags, bankData.assetShareValue, bankData.liabilityShareValue, bankData.emissionsRate, bankData.emissionsRemaining, bankData.mintDecimals);
402
298
  const lendingAccountBalance = (0, state_1.newClendAccountBalance)({
403
299
  active,
404
300
  bankPk: bank,
405
301
  bankMint,
406
302
  bankAssetTag: Number(balance.bankAssetTag),
407
- assetShares,
303
+ assetShares: Number((0, utils_1.wrappedI80F48toBigNumber)(balance.assetShares)),
408
304
  assetShareValue: Number(bankData.assetShareValue),
409
305
  assetMintDecimals: Number(bankData.mintDecimals),
410
- liabilityShares,
306
+ liabilityShares: Number((0, utils_1.wrappedI80F48toBigNumber)(balance.liabilityShares)),
411
307
  liabilityShareValue: Number(bankData.liabilityShareValue),
412
308
  liabilityMintDecimals: Number(bankData.mintDecimals),
413
309
  lastUpdate: new anchor_1.BN(balance.lastUpdate),
414
310
  assetWeightMaint: Number(bankData.config.assetWeightMaint),
415
311
  liabilityWeightMaint: Number(bankData.config.liabilityWeightMaint),
416
312
  price,
417
- emissionsOutstanding,
418
- emissionsOutstandingAndUnclaimed,
419
313
  });
420
314
  balances.push(lendingAccountBalance);
421
315
  healthFactorWeightedAssetArgs.push(lendingAccountBalance.assetWeightedMaintValue);
@@ -501,6 +395,13 @@ class ClendClient {
501
395
  // get required remaining accounts based on account balance
502
396
  const clendAccountData = await this.getClendAccount(clendAccount);
503
397
  let clendAccountActiveBanks = (0, utils_1.getClendAccountActiveBanks)(clendAccountData);
398
+ //// If withdrawAll is true, remove the target bank from activeBanks
399
+ //// as we wont have a remaining balance after the withdraw operation
400
+ //if (withdrawAll === true) {
401
+ // clendAccountActiveBanks = clendAccountActiveBanks.filter(
402
+ // (b) => !b.equals(bank),
403
+ // );
404
+ //}
504
405
  // fetch all active banks
505
406
  const activeBankData = [];
506
407
  for (const bank of clendAccountActiveBanks) {
@@ -535,7 +436,7 @@ class ClendClient {
535
436
  const ix = await this.instructions.borrow(clendGroup, clendAccount, this.address(), bank, destinationTokenAccount, tokenProgram, amount, remainingAccounts);
536
437
  return this.send([ix]);
537
438
  }
538
- async repay(clendGroup, clendAccount, mint, amount, repayAll, repayUpToAmount) {
439
+ async repay(clendGroup, clendAccount, mint, amount, repayAll = null) {
539
440
  const bank = (0, addresses_1.getBankPda)(clendGroup, mint);
540
441
  // Get bank data to access the liquidityVault and mint
541
442
  const bankData = await this.getBank(bank);
@@ -551,7 +452,7 @@ class ClendClient {
551
452
  activeBankData.push(bankData);
552
453
  }
553
454
  const remainingAccounts = (0, utils_1.getClendAccountRemainingAccounts)(activeBankData);
554
- const ix = await this.instructions.repay(clendGroup, clendAccount, this.address(), bank, userTokenAccount, tokenProgram, amount, repayAll, repayUpToAmount, remainingAccounts);
455
+ const ix = await this.instructions.repay(clendGroup, clendAccount, this.address(), bank, userTokenAccount, tokenProgram, amount, repayAll, remainingAccounts);
555
456
  return this.send([ix]);
556
457
  }
557
458
  async editGlobalFeeState(clendGroup, newGlobalFeeWallet, newBankInitFlatSolFee, newProgramFeeFixed, newProgramFeeRate) {
@@ -655,10 +556,9 @@ class ClendClient {
655
556
  * returns: instructions, luts, deposit amount (libaility token)
656
557
  */
657
558
  async getLiquidateClendAccountIxns(liquidatorClendAccount, liquidateeClendAccount, assetBankData, liabBankData, targetOnChainMaintenanceHealthValue, slippageBps, swapperOverride) {
658
- let activeSwapper = this.swapper;
659
559
  // override swapper if provided
660
560
  if (swapperOverride) {
661
- activeSwapper = swapperOverride;
561
+ this.swapper = swapperOverride;
662
562
  }
663
563
  utils_1.logger.info("Starting liquidation calculation", {
664
564
  liquidatee: liquidateeClendAccount.authority.toString(),
@@ -779,8 +679,7 @@ class ClendClient {
779
679
  // balance for the asset
780
680
  const withdrawAssetIx = await this.instructions.withdraw(assetBankData.group, liquidatorClendAccount.key, liquidatorClendAccount.authority, assetBankData.key, liquidatorAssetAta, assetTokenProgram, finalAssetAmount, false, remainingAccounts);
781
681
  // get a swap quote
782
- const swapQuote = await activeSwapper.getQuote({
783
- payer: liquidatorClendAccount.authority,
682
+ const swapQuote = await this.swapper.getQuote({
784
683
  inputMint: assetBankData.mint,
785
684
  inputMintDecimals: assetBankData.mintDecimals,
786
685
  outputMint: liabBankData.mint,
@@ -796,7 +695,7 @@ class ClendClient {
796
695
  outAmountMin: outAmountMin.toString(10),
797
696
  });
798
697
  // 5. Get Swap Instructions from Jupiter
799
- const swapIxns = await activeSwapper.getSwapIxns(swapQuote);
698
+ const swapIxns = await this.swapper.getSwapIxns(this.address(), swapQuote);
800
699
  const ixns = [
801
700
  depositIx, // deposit liability mint into liquidator clend account
802
701
  liquidationIx, // liquidate liquidatee clend account
@@ -835,14 +734,9 @@ class ClendClient {
835
734
  const ix = await this.instructions.configureBankOracleMaxAge(clendGroup, this.address(), bank, maxAge);
836
735
  return this.send([ix]);
837
736
  }
838
- async configureBankPythOracle(bank, pythOracle, pythOracleFeed) {
737
+ async configureBankOracle(bank, pythOracle, pythOracleFeed) {
839
738
  const bankData = await this.getBank(bank);
840
- const ix = await this.instructions.configureBankPythOracle(bankData.group, this.address(), bank, pythOracle, pythOracleFeed);
841
- return this.send([ix]);
842
- }
843
- async configureBankSBOracle(bank, pullFeedAccount) {
844
- const bankData = await this.getBank(bank);
845
- const ix = await this.instructions.configureBankSwitchboardOracle(bankData.group, this.address(), bank, pullFeedAccount);
739
+ const ix = await this.instructions.configureBankOracle(bankData.group, this.address(), bank, pythOracle, pythOracleFeed);
846
740
  return this.send([ix]);
847
741
  }
848
742
  async setBankOperationalState(clendGroup, bank, operationalState) {
@@ -855,7 +749,7 @@ class ClendClient {
855
749
  let additionalSigners = [];
856
750
  switch (selectedMint.toString()) {
857
751
  case collateralMint.toString():
858
- const collateralParams = await this.getDepositLeverageFromCollateralParams(clendGroup, collateralMint, debtMint, depositAmount, targetLeverage, slippageBps);
752
+ const collateralParams = await this.getDepositLeverageFromCollateralParams(clendGroup, collateralMint, debtMint, depositAmount, targetLeverage, slippageBps, swapperOverride);
859
753
  const { ixns: collateralIxns, luts: collateralLuts, additionalSigners: collateralAdditionalSigners, } = await this.getDepositLeverageFromCollateralIxns(clendGroup, this.address(), clendAccount, collateralMint, debtMint, collateralParams.collateralBankData.mintDecimals, collateralParams.debtBankData.mintDecimals, collateralParams.borrowAmount, depositAmount, slippageBps, 0, swapperOverride);
860
754
  ixns = collateralIxns;
861
755
  luts = collateralLuts;
@@ -878,9 +772,8 @@ class ClendClient {
878
772
  debtMintDecimals, collateralMintDecimals, initialUserDebtContributionForSwap, finalLoopBorrowAmount, slippageBps, additionalIxnCount, // For JIT liquidity, etc.
879
773
  swapperOverride) {
880
774
  // override swapper if provided
881
- let activeSwapper = this.swapper;
882
775
  if (swapperOverride) {
883
- activeSwapper = swapperOverride;
776
+ this.swapper = swapperOverride;
884
777
  }
885
778
  const collateralTokenProgram = (0, utils_1.getTokenProgramForMint)(collateralMint);
886
779
  const debtTokenProgram = (0, utils_1.getTokenProgramForMint)(debtMint);
@@ -921,8 +814,7 @@ class ClendClient {
921
814
  const totalDebtTokenForSwap = initialUserDebtContributionForSwap.add(finalLoopBorrowAmount);
922
815
  // 3. Get Jupiter Quote for the TOTAL debt token amount
923
816
  const swapMode = "ExactIn";
924
- const swapQuote = await activeSwapper.getQuote({
925
- payer: user,
817
+ const swapQuote = await this.swapper.getQuote({
926
818
  inputMint: debtMint,
927
819
  inputMintDecimals: debtMintDecimals,
928
820
  outputMint: collateralMint,
@@ -944,7 +836,7 @@ class ClendClient {
944
836
  const userDebtAta = (0, spl_token_1.getAssociatedTokenAddressSync)(debtMint, user, true, debtTokenProgram);
945
837
  const userCollateralAta = (0, spl_token_1.getAssociatedTokenAddressSync)(collateralMint, user, true, collateralTokenProgram);
946
838
  // 5. Get Swap Instructions from Jupiter
947
- const swapIxns = await activeSwapper.getSwapIxns(swapQuote);
839
+ const swapIxns = await this.swapper.getSwapIxns(user, swapQuote);
948
840
  // 6. Create Clend Protocol Instructions
949
841
  const borrowIx = await this.instructions.borrow(clendGroup, clendAccount, // clendAccount is guaranteed to be non-null here
950
842
  user, debtBank, userDebtAta, // Borrow into user's ATA
@@ -993,9 +885,8 @@ class ClendClient {
993
885
  async getDepositLeverageFromCollateralIxns(clendGroup, user, clendAccount, collateralMint, debtMint, debtMintDecimals, collateralMintDecimals, borrowAmount, depositAmount, slippageBps, additionalIxnCount, // used to correctly offset the flash loan index
994
886
  swapperOverride) {
995
887
  // override swapper if provided
996
- let activeSwapper = this.swapper;
997
888
  if (swapperOverride) {
998
- activeSwapper = swapperOverride;
889
+ this.swapper = swapperOverride;
999
890
  }
1000
891
  // Get token programs
1001
892
  const collateralTokenProgram = (0, utils_1.getTokenProgramForMint)(collateralMint);
@@ -1004,8 +895,7 @@ class ClendClient {
1004
895
  const collateralBank = (0, addresses_1.getBankPda)(clendGroup, collateralMint);
1005
896
  // Get Jupiter quote for swapping debt token to collateral token
1006
897
  const swapMode = "ExactIn";
1007
- const swapQuote = await activeSwapper.getQuote({
1008
- payer: user,
898
+ const swapQuote = await this.swapper.getQuote({
1009
899
  inputMint: debtMint,
1010
900
  inputMintDecimals: debtMintDecimals,
1011
901
  outputMint: collateralMint,
@@ -1056,7 +946,7 @@ class ClendClient {
1056
946
  }
1057
947
  const remainingAccounts = (0, utils_1.getClendAccountRemainingAccounts)(activeBankData);
1058
948
  // Get swap instructions
1059
- const swapIxns = await activeSwapper.getSwapIxns(swapQuote);
949
+ const swapIxns = await this.swapper.getSwapIxns(user, swapQuote);
1060
950
  // Calculate additional collateral from swap
1061
951
  const additionalCollateralAmount = swapQuote.outAmount;
1062
952
  const totalCollateralAmount = depositAmount.add(additionalCollateralAmount);
@@ -1101,7 +991,11 @@ class ClendClient {
1101
991
  additionalSigners,
1102
992
  };
1103
993
  }
1104
- async getDepositLeverageFromCollateralParams(clendGroup, collateralMint, debtMint, depositAmount, targetLeverage, slippageBps) {
994
+ async getDepositLeverageFromCollateralParams(clendGroup, collateralMint, debtMint, depositAmount, targetLeverage, slippageBps, swapperOverride) {
995
+ // override swapper if provided
996
+ if (swapperOverride) {
997
+ this.swapper = swapperOverride;
998
+ }
1105
999
  // Get decimals
1106
1000
  const collateralDecimals = (0, utils_1.getTokenDecimalsForMint)(collateralMint);
1107
1001
  const debtDecimals = (0, utils_1.getTokenDecimalsForMint)(debtMint);
@@ -1174,9 +1068,8 @@ class ClendClient {
1174
1068
  initialUserDebtContributionForSwap, // User's initial DEBT token for the swap
1175
1069
  targetLeverage, slippageBps, swapperOverride) {
1176
1070
  // override swapper if provided
1177
- let activeSwapper = this.swapper;
1178
1071
  if (swapperOverride) {
1179
- activeSwapper = swapperOverride;
1072
+ this.swapper = swapperOverride;
1180
1073
  }
1181
1074
  // 1. Get decimals and bank data
1182
1075
  const collateralDecimals = (0, utils_1.getTokenDecimalsForMint)(collateralMint);
@@ -1208,8 +1101,7 @@ class ClendClient {
1208
1101
  // This is the user's initial contribution + what's borrowed from the protocol
1209
1102
  const totalDebtTokenForSwap = initialUserDebtContributionForSwap.add(finalLoopBorrowAmount);
1210
1103
  // 7. Calculate Expected COLLATERAL Output from swapping the total DEBT token amount
1211
- const swapQuote = await activeSwapper.getQuote({
1212
- payer: anchor_1.web3.PublicKey.default, // doesnt matter right now
1104
+ const swapQuote = await this.swapper.getQuote({
1213
1105
  inputMint: debtMint,
1214
1106
  inputMintDecimals: debtDecimals,
1215
1107
  outputMint: collateralMint,
@@ -1253,14 +1145,14 @@ class ClendClient {
1253
1145
  let luts = [];
1254
1146
  switch (selectedMint.toString()) {
1255
1147
  case collateralMint.toString():
1256
- const collateralParams = await this.getNetWithdrawLeverageCollateralParams(clendGroup, clendAccount, collateralMint, debtMint, withdrawAmount, withdrawAll);
1257
- const { ixns: collateralIxns, luts: collateralLuts } = await this.getWithdrawLeverageCollateralIxns(clendGroup, clendAccount, collateralMint, debtMint, collateralParams.collateralBankData.mintDecimals, collateralParams.debtBankData.mintDecimals, withdrawAll, collateralParams.collateralToWithdraw, withdrawAmount, slippageBps, 0, swapperOverride);
1148
+ const collateralParams = await this.getNetWithdrawLeverageCollateralParams(clendGroup, clendAccount, collateralMint, debtMint, withdrawAmount, withdrawAll, slippageBps, swapperOverride);
1149
+ const { ixns: collateralIxns, luts: collateralLuts } = await this.getWithdrawLeverageCollateralIxns(clendGroup, clendAccount, collateralMint, debtMint, collateralParams.collateralBankData.mintDecimals, collateralParams.debtBankData.mintDecimals, withdrawAll, collateralParams.debtToRepay, collateralParams.collateralToWithdraw, slippageBps, 0, swapperOverride);
1258
1150
  ixns = collateralIxns;
1259
1151
  luts = collateralLuts;
1260
1152
  break;
1261
1153
  case debtMint.toString():
1262
- const debtParams = await this.getNetWithdrawLeverageDebtParams(clendGroup, clendAccount, collateralMint, debtMint, withdrawAmount, withdrawAll, swapperOverride);
1263
- const { ixns: debtIxns, luts: debtLuts } = await this.getWithdrawLeverageDebtIxns(clendGroup, clendAccount, collateralMint, debtMint, debtParams.collateralBankData.mintDecimals, debtParams.debtBankData.mintDecimals, withdrawAll, debtParams.debtToRepay, debtParams.collateralToWithdraw, slippageBps, 0, swapperOverride);
1154
+ const debtParams = await this.getNetWithdrawLeverageDebtParams(clendGroup, clendAccount, collateralMint, debtMint, withdrawAmount, withdrawAll, slippageBps, swapperOverride);
1155
+ const { ixns: debtIxns, luts: debtLuts } = await this.getWithdrawLeverageDebtIxns(clendGroup, clendAccount, collateralMint, debtMint, debtParams.collateralBankData.mintDecimals, debtParams.debtBankData.mintDecimals, withdrawAll, debtParams.debtToRepay, debtParams.collateralToWithdraw, withdrawAmount, slippageBps, 0, swapperOverride);
1264
1156
  ixns = debtIxns;
1265
1157
  luts = debtLuts;
1266
1158
  break;
@@ -1270,98 +1162,75 @@ class ClendClient {
1270
1162
  // Send transaction
1271
1163
  return this.send(ixns, [], luts);
1272
1164
  }
1273
- async getWithdrawLeverageDebtIxns(clendGroup, clendAccount, collateralMint, debtMint, collateralDecimals, debtDecimals, withdrawAll, debtToRepay, collateralToWithdraw, slippageBps, additionalIxnCount, swapperOverride) {
1165
+ async getWithdrawLeverageCollateralIxns(clendGroup, clendAccount, collateralMint, debtMint, collateralDecimals, debtDecimals, withdrawAll, debtToRepay, collateralToWithdraw, slippageBps, additionalIxnCount, swapperOverride) {
1274
1166
  // override swapper if provided
1275
- let activeSwapper = this.swapper;
1276
1167
  if (swapperOverride) {
1277
- activeSwapper = swapperOverride;
1168
+ this.swapper = swapperOverride;
1278
1169
  }
1279
1170
  const debtBank = (0, addresses_1.getBankPda)(clendGroup, debtMint);
1280
1171
  const collateralBank = (0, addresses_1.getBankPda)(clendGroup, collateralMint);
1172
+ // Get remaining accounts for flash loan
1281
1173
  const clendAccountData = await this.getClendAccount(clendAccount);
1282
1174
  if (!clendAccountData) {
1283
1175
  throw new Error(`Clend account not found: ${clendAccount.toString()}`);
1284
1176
  }
1285
1177
  let activeBanks = (0, utils_1.getClendAccountActiveBanks)(clendAccountData);
1286
- const activeBankData = await Promise.all(activeBanks.map((bankPk) => this.getBank(bankPk)));
1178
+ const activeBankData = [];
1179
+ for (const bank of activeBanks) {
1180
+ const bankData = await this.getBank(bank);
1181
+ activeBankData.push(bankData);
1182
+ }
1287
1183
  const remainingAccounts = (0, utils_1.getClendAccountRemainingAccounts)(activeBankData);
1288
- // Get a fresh 'ExactIn' quote for swapping the total collateral to withdraw
1289
- const swapQuote = await activeSwapper.getQuote({
1290
- payer: clendAccountData.authority,
1184
+ // Get Jupiter quote for swapping collateral to debt token (ExactOut mode)
1185
+ const swapMode = "ExactOut";
1186
+ const swapQuote = await this.swapper.getQuote({
1291
1187
  inputMint: collateralMint,
1292
1188
  inputMintDecimals: collateralDecimals,
1293
1189
  outputMint: debtMint,
1294
1190
  outputMintDecimals: debtDecimals,
1295
- inputAmount: collateralToWithdraw, // Use the total collateral to withdraw as the exact input
1191
+ inputAmount: debtToRepay,
1296
1192
  slippageBps,
1297
- swapMode: "ExactIn",
1193
+ swapMode,
1298
1194
  });
1299
- // The minimum amount of debt token we are guaranteed to receive from the swap
1300
- const minDebtReceivedFromSwap = new anchor_1.BN(swapQuote.otherAmountThreshold);
1301
- // Determine the final amount to repay. It's the lesser of what we need and what we're guaranteed to get.
1302
- const finalDebtToRepay = anchor_1.BN.min(debtToRepay, minDebtReceivedFromSwap);
1303
- utils_1.logger.debug("getWithdrawLeverageDebtIxns: swap and repay details", {
1304
- collateralToWithdraw: collateralToWithdraw.toString(),
1305
- minDebtReceivedFromSwap: minDebtReceivedFromSwap.toString(),
1306
- targetDebtToRepay: debtToRepay.toString(),
1307
- finalDebtToRepay: finalDebtToRepay.toString(),
1195
+ // set collateralToSwap to the inAmount from the swap quote
1196
+ const collateralToSwap = swapQuote.inAmount;
1197
+ utils_1.logger.debug("withdrawLeverage collateralToSwap", {
1198
+ collateralToSwap,
1308
1199
  });
1309
- // Get Swap Instructions from the swapper
1310
- const swapIxns = await activeSwapper.getSwapIxns(swapQuote);
1311
- utils_1.logger.info(`swapIxns fetched for ReceiveDebtToken`);
1312
- // --- ATAs and Clend Instructions ---
1200
+ // Get swap instructions
1201
+ const swapInputs = {
1202
+ inputAmountLamports: new decimal_js_1.default(collateralToSwap.toString()),
1203
+ inputMint: collateralMint,
1204
+ outputMint: debtMint,
1205
+ };
1206
+ utils_1.logger.debug("withdrawLeverage swapInputs", {
1207
+ swapInputs,
1208
+ });
1209
+ const swapIxns = await this.swapper.getSwapIxns(clendAccountData.authority, swapQuote);
1210
+ utils_1.logger.info(`swapIxns fetched`);
1313
1211
  const collateralTokenProgram = (0, utils_1.getTokenProgramForMint)(collateralMint);
1314
1212
  const debtTokenProgram = (0, utils_1.getTokenProgramForMint)(debtMint);
1315
1213
  const userCollateralAta = (0, spl_token_1.getAssociatedTokenAddressSync)(collateralMint, clendAccountData.authority, true, collateralTokenProgram);
1316
1214
  const userDebtAta = (0, spl_token_1.getAssociatedTokenAddressSync)(debtMint, clendAccountData.authority, true, debtTokenProgram);
1317
- // Create withdraw instruction: Withdraw the total collateral calculated by the params function
1215
+ utils_1.logger.info(`begin crafting ixns`);
1216
+ // Create withdraw instruction
1318
1217
  const withdrawIx = await this.instructions.withdraw(clendGroup, clendAccount, clendAccountData.authority, collateralBank, userCollateralAta, collateralTokenProgram, collateralToWithdraw, withdrawAll, remainingAccounts);
1319
- // if withdrawAll == true we need to make sure we withdraw emissions from both banks before withdrawing all the bank tokens
1320
- const emissionsIxns = [];
1321
- if (withdrawAll) {
1322
- // get bank data
1323
- const collateralBankData = activeBankData.find((b) => b.mint.equals(collateralMint));
1324
- if (!collateralBankData) {
1325
- throw new Error(`Collateral bank not found: ${collateralMint.toString()}`);
1326
- }
1327
- const debtBankData = activeBankData.find((b) => b.mint.equals(debtMint));
1328
- if (!debtBankData) {
1329
- throw new Error(`Debt bank not found: ${debtMint.toString()}`);
1330
- }
1331
- // collateral bank emissions
1332
- if (!collateralBankData.emissionsMint.equals(anchor_1.web3.PublicKey.default) &&
1333
- (collateralBankData.flags === state_1.BankFlags.LendingEmissionsActive ||
1334
- collateralBankData.flags ===
1335
- state_1.BankFlags.LendingAndBorrowingEmissionsActive)) {
1336
- const emissionsMint = collateralBankData.emissionsMint;
1337
- const emissionsMintTokenProgram = await (0, utils_1.getTokenProgramForMintFromRpc)(this.connection, emissionsMint);
1338
- const withdrawEmissionsIx = await this.instructions.withdrawEmissions(clendGroup, clendAccount, clendAccountData.authority, collateralBank, emissionsMint, emissionsMintTokenProgram);
1339
- emissionsIxns.push(...withdrawEmissionsIx);
1340
- }
1341
- // debt bank emissions
1342
- if (!debtBankData.emissionsMint.equals(anchor_1.web3.PublicKey.default) &&
1343
- (debtBankData.flags === state_1.BankFlags.BorrowEmissionsActive ||
1344
- debtBankData.flags === state_1.BankFlags.LendingAndBorrowingEmissionsActive)) {
1345
- const emissionsMint = debtBankData.emissionsMint;
1346
- const emissionsMintTokenProgram = await (0, utils_1.getTokenProgramForMintFromRpc)(this.connection, emissionsMint);
1347
- const withdrawEmissionsIx = await this.instructions.withdrawEmissions(clendGroup, clendAccount, clendAccountData.authority, debtBank, emissionsMint, emissionsMintTokenProgram);
1348
- emissionsIxns.push(...withdrawEmissionsIx);
1349
- }
1350
- }
1351
- // Create repay instruction: Repay the debt portion using the guaranteed swap output
1352
- const repayIx = await this.instructions.repay(clendGroup, clendAccount, clendAccountData.authority, debtBank, userDebtAta, debtTokenProgram, finalDebtToRepay, // Use the slippage-protected amount from the final quote
1353
- withdrawAll, true, remainingAccounts);
1354
- // Assemble instructions
1218
+ utils_1.logger.info(`withdrawIx set`);
1219
+ // Create repay instruction
1220
+ const repayIx = await this.instructions.repay(clendGroup, clendAccount, clendAccountData.authority, debtBank, userDebtAta, debtTokenProgram, debtToRepay, withdrawAll, remainingAccounts);
1221
+ utils_1.logger.info(`repayIx set`);
1222
+ // Assemble all instructions without flash loan
1355
1223
  const ixnsWithoutFlashLoan = [
1356
- ...emissionsIxns,
1357
1224
  withdrawIx,
1358
1225
  ...swapIxns.ixns,
1359
1226
  repayIx,
1360
1227
  ];
1361
- // Flash Loan Wrapping
1228
+ utils_1.logger.info(`ixnsWithoutFlashLoan set`);
1229
+ // Create flash loan instructions
1362
1230
  const cuIxns = 2;
1363
1231
  const endIndex = new anchor_1.BN(cuIxns + ixnsWithoutFlashLoan.length + 1 + additionalIxnCount);
1364
1232
  const { beginFlashLoanIx, endFlashLoanIx } = await this.instructions.createFlashLoanInstructions(clendAccount, clendAccountData.authority, endIndex, remainingAccounts);
1233
+ // Assemble all instructions in the correct order
1365
1234
  const instructions = [
1366
1235
  beginFlashLoanIx,
1367
1236
  ...ixnsWithoutFlashLoan,
@@ -1369,13 +1238,92 @@ class ClendClient {
1369
1238
  ];
1370
1239
  return { ixns: instructions, luts: swapIxns.luts };
1371
1240
  }
1372
- async getNetWithdrawLeverageCollateralParams(clendGroup, clendAccount, collateralMint, debtMint, desiredNetWithdraw, withdrawAll) {
1373
- utils_1.logger.info("getNetWithdrawLeverageCollateralParams", {
1241
+ async getWithdrawLeverageDebtIxns(clendGroup, clendAccount, collateralMint, debtMint, collateralDecimals, debtDecimals, withdrawAll, debtToRepay, collateralToWithdraw, desiredNetDebtToReceive, slippageBps, additionalIxnCount, swapperOverride) {
1242
+ // override swapper if provided
1243
+ if (swapperOverride) {
1244
+ this.swapper = swapperOverride;
1245
+ }
1246
+ const debtBank = (0, addresses_1.getBankPda)(clendGroup, debtMint);
1247
+ const collateralBank = (0, addresses_1.getBankPda)(clendGroup, collateralMint);
1248
+ const clendAccountData = await this.getClendAccount(clendAccount);
1249
+ if (!clendAccountData) {
1250
+ throw new Error(`Clend account not found: ${clendAccount.toString()}`);
1251
+ }
1252
+ let activeBanks = (0, utils_1.getClendAccountActiveBanks)(clendAccountData);
1253
+ //if (withdrawAll) {
1254
+ // activeBanks = activeBanks.filter(
1255
+ // (b) => !b.equals(collateralBank) && !b.equals(debtBank),
1256
+ // );
1257
+ //}
1258
+ const activeBankData = await Promise.all(activeBanks.map((bankPk) => this.getBank(bankPk)));
1259
+ const remainingAccounts = (0, utils_1.getClendAccountRemainingAccounts)(activeBankData);
1260
+ // 1. Determine Total USDC Needed from the Swap
1261
+ // This is the amount needed to repay Clend's debt AND give the user their desired net withdrawal.
1262
+ const totalDebtToReceive = debtToRepay.add(desiredNetDebtToReceive);
1263
+ const swapMode = "ExactIn";
1264
+ const swapQuote = await this.swapper.getQuote({
1265
+ inputMint: collateralMint,
1266
+ inputMintDecimals: collateralDecimals,
1267
+ outputMint: debtMint,
1268
+ outputMintDecimals: debtDecimals,
1269
+ inputAmount: collateralToWithdraw,
1270
+ slippageBps,
1271
+ swapMode,
1272
+ });
1273
+ const minUsdcReceivedFromSwap_BN = new anchor_1.BN(swapQuote.otherAmountThreshold);
1274
+ utils_1.logger.debug("getWithdrawLeverageDebtIxns: collateral and debt details", {
1275
+ debtToRepay: debtToRepay.toString(),
1276
+ desiredNetDebtToReceive: desiredNetDebtToReceive.toString(),
1277
+ totalDebtToReceive: totalDebtToReceive.toString(),
1278
+ minUsdcReceivedFromSwap_BN: minUsdcReceivedFromSwap_BN.toString(),
1279
+ });
1280
+ // 3. Get Swap Instructions from swapper
1281
+ const swapIxns = await this.swapper.getSwapIxns(clendAccountData.authority, swapQuote);
1282
+ utils_1.logger.info(`swapIxns fetched for ReceiveDebtToken`);
1283
+ // --- ATAs and Clend Instructions (largely similar structure) ---
1284
+ const collateralTokenProgram = (0, utils_1.getTokenProgramForMint)(collateralMint);
1285
+ const debtTokenProgram = (0, utils_1.getTokenProgramForMint)(debtMint);
1286
+ const userCollateralAta = (0, spl_token_1.getAssociatedTokenAddressSync)(collateralMint, clendAccountData.authority, true, collateralTokenProgram);
1287
+ const userDebtAta = (0, spl_token_1.getAssociatedTokenAddressSync)(debtMint, clendAccountData.authority, true, debtTokenProgram);
1288
+ // Create withdraw instruction: Withdraw ALL the JLP calculated by params function
1289
+ const withdrawIx = await this.instructions.withdraw(clendGroup, clendAccount, clendAccountData.authority, collateralBank, userCollateralAta, collateralTokenProgram, collateralToWithdraw, // Withdraw all the JLP needed for the full swap
1290
+ withdrawAll, // This flag is for Clend's internal logic
1291
+ remainingAccounts);
1292
+ // Create repay instruction: Repay the calculated debt portion to Clend
1293
+ const repayIx = await this.instructions.repay(clendGroup, clendAccount, clendAccountData.authority, debtBank, userDebtAta, debtTokenProgram, debtToRepay, // Repay the specified debt amount
1294
+ withdrawAll, // This flag is for Clend's internal logic
1295
+ remainingAccounts);
1296
+ // Assemble instructions
1297
+ const ixnsWithoutFlashLoan = [
1298
+ withdrawIx,
1299
+ ...swapIxns.ixns, // Swap all withdrawn collateral to debt
1300
+ repayIx, // Repay the debt portion
1301
+ ]; // User is left with the remaining debt in their ATA
1302
+ // Flash Loan Wrapping (same as before)
1303
+ const cuIxns = 2;
1304
+ const endIndex = new anchor_1.BN(cuIxns + ixnsWithoutFlashLoan.length + 1 + additionalIxnCount);
1305
+ const { beginFlashLoanIx, endFlashLoanIx } = await this.instructions.createFlashLoanInstructions(clendAccount, clendAccountData.authority, endIndex, remainingAccounts);
1306
+ const instructions = [
1307
+ beginFlashLoanIx,
1308
+ ...ixnsWithoutFlashLoan,
1309
+ endFlashLoanIx,
1310
+ ];
1311
+ return { ixns: instructions, luts: swapIxns.luts };
1312
+ }
1313
+ // caller will receive desiredNetWithdraw, the position will be adjusted accordingly
1314
+ // receive the collateral mint
1315
+ async getNetWithdrawLeverageCollateralParams(clendGroup, clendAccount, collateralMint, debtMint, desiredNetWithdraw, withdrawAll, slippageBps, swapperOverride) {
1316
+ // override swapper if provided
1317
+ if (swapperOverride) {
1318
+ this.swapper = swapperOverride;
1319
+ }
1320
+ utils_1.logger.info("getNetWithdrawParams", {
1374
1321
  desiredNetWithdraw: desiredNetWithdraw.toString(),
1375
1322
  withdrawAll,
1376
1323
  });
1324
+ // if desiredNetWithdraw is zero, throw an error
1377
1325
  if (desiredNetWithdraw.isZero() && !withdrawAll) {
1378
- throw new Error("Desired net withdrawal is zero for a partial withdraw.");
1326
+ throw new Error("Desired net withdrawal is zero");
1379
1327
  }
1380
1328
  const collateralBank = (0, addresses_1.getBankPda)(clendGroup, collateralMint);
1381
1329
  const debtBank = (0, addresses_1.getBankPda)(clendGroup, debtMint);
@@ -1387,6 +1335,7 @@ class ClendClient {
1387
1335
  if (!clendAccountData) {
1388
1336
  throw new Error(`Clend account not found: ${clendAccount.toString()}`);
1389
1337
  }
1338
+ // fetch balances and pricing
1390
1339
  let debtBalance;
1391
1340
  let debtPrice;
1392
1341
  let collateralBalance;
@@ -1405,32 +1354,75 @@ class ClendClient {
1405
1354
  throw new Error("Could not find debt or collateral position in account");
1406
1355
  }
1407
1356
  const collateralAmount = collateralBalance.assetBalance;
1408
- const debtAmount = debtBalance.liabilityBalance;
1357
+ const collateralAmountUI = collateralBalance.assetBalanceUi;
1358
+ const collateralValue = collateralBalance.assetValue;
1409
1359
  const collateralDecimals = collateralBankData.mintDecimals;
1360
+ const debtAmount = debtBalance.liabilityBalance;
1361
+ const debtAmountUI = debtBalance.liabilityBalanceUi;
1362
+ const debtValue = debtBalance.liabilityValue;
1410
1363
  const debtDecimals = debtBankData.mintDecimals;
1364
+ const desiredNetWithdrawUi = (0, clend_common_1.amountToUi)(desiredNetWithdraw, debtDecimals);
1365
+ utils_1.logger.debug("Current amounts", {
1366
+ desiredNetWithdrawUi,
1367
+ collateralAmount: collateralAmount.toString(),
1368
+ collateralAmountUI,
1369
+ collateralValue,
1370
+ debtAmount: debtAmount.toString(),
1371
+ debtAmountUI,
1372
+ debtValue,
1373
+ });
1374
+ // --- Determine collateral to withdraw and debt to repay ---
1411
1375
  let collateralToWithdraw;
1412
1376
  let debtToRepay;
1413
1377
  if (withdrawAll) {
1414
1378
  utils_1.logger.info("WithdrawAll selected");
1415
1379
  collateralToWithdraw = collateralAmount;
1416
- debtToRepay = (0, clend_common_1.calculateLiabilityInterest)(debtBalance.liabilityShares, debtBankData.liabilityShareValue, debtBankData.borrowApy, debtBankData.lastUpdate.toNumber(), Math.floor(Date.now() / 1000));
1380
+ debtToRepay = (0, clend_common_1.calculateLiabilityInterest)(debtBalance.liabilityShares, debtBankData.liabilityShareValue, debtBankData.borrowApy, debtBankData.lastUpdate.toNumber(), new Date().getTime() / 1000);
1381
+ utils_1.logger.info(`calculated debt to repay`, {
1382
+ debtToRepayWithInterest: debtToRepay.toString(),
1383
+ debtToRepayRaw: debtAmount.toString(),
1384
+ });
1417
1385
  }
1418
1386
  else {
1419
- utils_1.logger.info("Calculating partial deleverage amounts");
1387
+ utils_1.logger.info("Calculating partial deleverage for net withdrawal");
1388
+ // 1. Calculate estimated current debt accurately
1420
1389
  const estimatedCurrentDebt = (0, clend_common_1.calculateLiabilityInterest)(debtBalance.liabilityShares, debtBankData.liabilityShareValue, debtBankData.borrowApy, debtBankData.lastUpdate.toNumber(), Math.floor(Date.now() / 1000));
1421
1390
  const estimatedCurrentDebtUi = (0, clend_common_1.amountToUi)(estimatedCurrentDebt, debtDecimals);
1422
1391
  const collateralAmountUI = (0, clend_common_1.amountToUi)(collateralAmount, collateralDecimals);
1423
- const desiredNetWithdrawUi = (0, clend_common_1.amountToUi)(desiredNetWithdraw, collateralDecimals);
1424
- const { debtToRepayUi, totalCollateralToWithdrawUi } = (0, clend_common_1.computeWithdrawLeverageAmounts)(collateralAmountUI, estimatedCurrentDebtUi, desiredNetWithdrawUi, collateralPrice, debtPrice);
1425
- collateralToWithdraw = (0, clend_common_1.uiToAmount)(totalCollateralToWithdrawUi, collateralDecimals);
1392
+ const { debtToRepayUi, totalCollateralToWithdrawUi, newLeverage, currentLeverage, } = (0, clend_common_1.computeWithdrawLeverageAmounts)(collateralAmountUI, estimatedCurrentDebtUi, desiredNetWithdrawUi, collateralPrice, debtPrice);
1393
+ utils_1.logger.info("computeWithdrawLeverageAmounts", {
1394
+ debtToRepayUi,
1395
+ totalCollateralToWithdrawUi,
1396
+ newLeverage,
1397
+ currentLeverage,
1398
+ });
1426
1399
  const baseDebtToRepay = (0, clend_common_1.uiToAmount)(debtToRepayUi, debtDecimals);
1400
+ // --- Step 5: Add Buffers to the Debt Repayment Target ---
1401
+ // This adds a small buffer for interest that might accrue during the transaction's execution time.
1427
1402
  const timeBufferSec = 15;
1428
1403
  debtToRepay = (0, clend_common_1.addInterestAccrualBuffer)(baseDebtToRepay, debtBankData.borrowApy, timeBufferSec);
1404
+ // --- Step 6: Get a Jupiter Quote for the Swap ---
1405
+ // Query Jupiter for an EXACT_OUT swap to find out exactly how much collateral
1406
+ // is needed to get the final amount of debt we need to repay.
1407
+ const swapQuote = await this.swapper.getQuote({
1408
+ inputMint: collateralMint,
1409
+ inputMintDecimals: collateralDecimals,
1410
+ outputMint: debtMint,
1411
+ outputMintDecimals: debtDecimals,
1412
+ inputAmount: debtToRepay,
1413
+ slippageBps,
1414
+ swapMode: "ExactOut",
1415
+ });
1416
+ const collateralNeededForSwap = new anchor_1.BN(swapQuote.inAmount);
1417
+ // --- Step 7: Calculate the Final Total Collateral to Withdraw ---
1418
+ // This is the sum of what the user gets (desiredNetWithdraw) and what the swap needs.
1419
+ collateralToWithdraw = desiredNetWithdraw.add(collateralNeededForSwap);
1420
+ // Final sanity check to ensure we don't try to withdraw more than exists.
1429
1421
  if (collateralToWithdraw.gt(collateralAmount)) {
1430
- throw new Error("Total required collateral for withdrawal exceeds the available balance.");
1422
+ throw new Error("Total required collateral for withdrawal and swap exceeds the available balance. The position may be too leveraged for this withdrawal amount.");
1431
1423
  }
1432
1424
  }
1433
- utils_1.logger.debug("Final calculated amounts from params function", {
1425
+ utils_1.logger.debug("Final calculated amounts", {
1434
1426
  collateralToWithdraw: collateralToWithdraw.toString(),
1435
1427
  debtToRepay: debtToRepay.toString(),
1436
1428
  });
@@ -1441,84 +1433,12 @@ class ClendClient {
1441
1433
  debtBankData,
1442
1434
  };
1443
1435
  }
1444
- async getWithdrawLeverageCollateralIxns(clendGroup, clendAccount, collateralMint, debtMint, collateralDecimals, debtDecimals, withdrawAll, collateralToWithdraw, desiredNetCollateralToReceive, slippageBps, additionalIxnCount, swapperOverride) {
1445
- // override swapper if provided
1446
- let activeSwapper = this.swapper;
1447
- if (swapperOverride) {
1448
- activeSwapper = swapperOverride;
1449
- }
1450
- const debtBank = (0, addresses_1.getBankPda)(clendGroup, debtMint);
1451
- const collateralBank = (0, addresses_1.getBankPda)(clendGroup, collateralMint);
1452
- const clendAccountData = await this.getClendAccount(clendAccount);
1453
- if (!clendAccountData) {
1454
- throw new Error(`Clend account not found: ${clendAccount.toString()}`);
1455
- }
1456
- let activeBanks = (0, utils_1.getClendAccountActiveBanks)(clendAccountData);
1457
- const activeBankData = [];
1458
- for (const bank of activeBanks) {
1459
- const bankData = await this.getBank(bank);
1460
- activeBankData.push(bankData);
1461
- }
1462
- const remainingAccounts = (0, utils_1.getClendAccountRemainingAccounts)(activeBankData);
1463
- // 1. Determine the amount of collateral that needs to be swapped.
1464
- const collateralToSwap = collateralToWithdraw.sub(desiredNetCollateralToReceive);
1465
- // Safety check
1466
- if (collateralToSwap.isNeg() || collateralToSwap.isZero()) {
1467
- throw new Error(`Invalid calculation: collateral to swap is not positive. Total withdraw: ${collateralToWithdraw}, user receives: ${desiredNetCollateralToReceive}. Collateral to swap: ${collateralToSwap}`);
1468
- }
1469
- // 2. Get a fresh 'ExactIn' quote for swapping that collateral amount.
1470
- const swapQuote = await activeSwapper.getQuote({
1471
- payer: clendAccountData.authority,
1472
- inputMint: collateralMint,
1473
- inputMintDecimals: collateralDecimals,
1474
- outputMint: debtMint,
1475
- outputMintDecimals: debtDecimals,
1476
- inputAmount: collateralToSwap, // This is our exact input
1477
- slippageBps,
1478
- swapMode: "ExactIn",
1479
- });
1480
- // 3. The final amount to repay is the minimum we are guaranteed to get from the swap.
1481
- const finalDebtToRepay = new anchor_1.BN(swapQuote.otherAmountThreshold);
1482
- const swapIxns = await activeSwapper.getSwapIxns(swapQuote);
1483
- utils_1.logger.info(`swapIxns fetched`);
1484
- const collateralTokenProgram = (0, utils_1.getTokenProgramForMint)(collateralMint);
1485
- const debtTokenProgram = (0, utils_1.getTokenProgramForMint)(debtMint);
1486
- const userCollateralAta = (0, spl_token_1.getAssociatedTokenAddressSync)(collateralMint, clendAccountData.authority, true, collateralTokenProgram);
1487
- const userDebtAta = (0, spl_token_1.getAssociatedTokenAddressSync)(debtMint, clendAccountData.authority, true, debtTokenProgram);
1488
- utils_1.logger.info(`begin crafting ixns`);
1489
- const withdrawIx = await this.instructions.withdraw(clendGroup, clendAccount, clendAccountData.authority, collateralBank, userCollateralAta, collateralTokenProgram, collateralToWithdraw, withdrawAll, remainingAccounts);
1490
- utils_1.logger.info(`withdrawIx set`);
1491
- const emissionsIxns = [];
1492
- if (withdrawAll) {
1493
- // ... (emission logic remains the same)
1494
- }
1495
- // Create repay instruction with the amount from the 'ExactIn' swap
1496
- const repayIx = await this.instructions.repay(clendGroup, clendAccount, clendAccountData.authority, debtBank, userDebtAta, debtTokenProgram, finalDebtToRepay, // Use the slippage-protected amount from the final quote
1497
- withdrawAll, true, // Use the new repay_up_to_amount flag
1498
- remainingAccounts);
1499
- utils_1.logger.info(`repayIx set`);
1500
- const ixnsWithoutFlashLoan = [
1501
- ...emissionsIxns,
1502
- withdrawIx,
1503
- ...swapIxns.ixns,
1504
- repayIx,
1505
- ];
1506
- utils_1.logger.info(`ixnsWithoutFlashLoan set`);
1507
- const cuIxns = 2;
1508
- const endIndex = new anchor_1.BN(cuIxns + ixnsWithoutFlashLoan.length + 1 + additionalIxnCount);
1509
- const { beginFlashLoanIx, endFlashLoanIx } = await this.instructions.createFlashLoanInstructions(clendAccount, clendAccountData.authority, endIndex, remainingAccounts);
1510
- const instructions = [
1511
- beginFlashLoanIx,
1512
- ...ixnsWithoutFlashLoan,
1513
- endFlashLoanIx,
1514
- ];
1515
- return { ixns: instructions, luts: swapIxns.luts };
1516
- }
1517
- async getNetWithdrawLeverageDebtParams(clendGroup, clendAccount, collateralMint, debtMint, desiredNetWithdraw, withdrawAll, swapperOverride) {
1436
+ // caller will receive desiredNetWithdraw, the position will be adjusted accordingly
1437
+ // receive the debt mint
1438
+ async getNetWithdrawLeverageDebtParams(clendGroup, clendAccount, collateralMint, debtMint, desiredNetWithdraw, withdrawAll, slippageBps, swapperOverride) {
1518
1439
  // override swapper if provided
1519
- let activeSwapper = this.swapper;
1520
1440
  if (swapperOverride) {
1521
- activeSwapper = swapperOverride;
1441
+ this.swapper = swapperOverride;
1522
1442
  }
1523
1443
  utils_1.logger.info("getNetWithdrawDebtParams", {
1524
1444
  desiredNetWithdraw: desiredNetWithdraw.toString(),
@@ -1565,31 +1485,52 @@ class ClendClient {
1565
1485
  let debtToRepay;
1566
1486
  if (withdrawAll) {
1567
1487
  utils_1.logger.info("WithdrawAll selected for receiving debt token");
1568
- collateralToWithdraw = collateralAmount; // All collateral will be swapped
1488
+ collateralToWithdraw = collateralAmount; // All JLP will be swapped
1569
1489
  debtToRepay = (0, clend_common_1.calculateLiabilityInterest)(currentDebtBalance.liabilityShares, debtBankData.liabilityShareValue, debtBankData.borrowApy, debtBankData.lastUpdate.toNumber(), Math.floor(Date.now() / 1000));
1570
- utils_1.logger.info(`calculated debt to repay for withdrawAll`, {
1490
+ utils_1.logger.info(`calculated debt to repay`, {
1571
1491
  debtToRepayWithInterest: debtToRepay.toString(),
1492
+ debtToRepayRaw: debtAmount.toString(),
1572
1493
  });
1573
1494
  }
1574
1495
  else {
1575
- utils_1.logger.info("Calculating partial deleverage for receiving debt token with ExactIn logic");
1496
+ utils_1.logger.info("Calculating partial deleverage for receiving debt token");
1576
1497
  // 1. Calculate estimated current debt accurately
1577
1498
  const estimatedCurrentDebt = (0, clend_common_1.calculateLiabilityInterest)(currentDebtBalance.liabilityShares, debtBankData.liabilityShareValue, debtBankData.borrowApy, debtBankData.lastUpdate.toNumber(), Math.floor(Date.now() / 1000));
1578
1499
  const estimatedCurrentDebtUi = (0, clend_common_1.amountToUi)(estimatedCurrentDebt, debtDecimals);
1579
1500
  const collateralAmountUI = (0, clend_common_1.amountToUi)(collateralAmount, collateralDecimals);
1580
1501
  const desiredNetWithdrawalOfDebtUi = (0, clend_common_1.amountToUi)(desiredNetWithdraw, debtDecimals);
1581
- // 2. Convert the desired net debt withdrawal into its collateral-equivalent value
1502
+ // Convert the value of the desired debt withdrawal into an equivalent amount of collateral
1503
+ // This is the "collateral-equivalent" value of the equity being removed.
1582
1504
  const netWithdrawAmountInCollateralUi = (0, clend_common_1.calculateWeightedValue)(desiredNetWithdrawalOfDebtUi, debtPrice, 1) /
1583
1505
  collateralPrice;
1584
- // 3. Compute the ideal deleverage amounts
1585
- const { debtToRepayUi, totalCollateralToWithdrawUi } = (0, clend_common_1.computeWithdrawLeverageAmounts)(collateralAmountUI, estimatedCurrentDebtUi, netWithdrawAmountInCollateralUi, collateralPrice, debtPrice);
1586
- // 4. Set the total collateral to withdraw based on the calculation
1587
- collateralToWithdraw = (0, clend_common_1.uiToAmount)(totalCollateralToWithdrawUi, collateralDecimals);
1588
- // 5. Calculate the base debt to repay and add a buffer for interest accrual
1506
+ const { debtToRepayUi, totalCollateralToWithdrawUi, newLeverage, currentLeverage, } = (0, clend_common_1.computeWithdrawLeverageAmounts)(collateralAmountUI, estimatedCurrentDebtUi, netWithdrawAmountInCollateralUi, collateralPrice, debtPrice);
1507
+ utils_1.logger.info("computeWithdrawLeverageAmounts", {
1508
+ debtToRepayUi,
1509
+ totalCollateralToWithdrawUi,
1510
+ newLeverage,
1511
+ currentLeverage,
1512
+ });
1589
1513
  const baseDebtToRepay = (0, clend_common_1.uiToAmount)(debtToRepayUi, debtDecimals);
1514
+ // --- Step 5: Add Buffers to the Debt Repayment Target ---
1515
+ // This adds a small buffer for interest that might accrue during the transaction's execution time.
1590
1516
  const timeBufferSec = 15;
1591
1517
  debtToRepay = (0, clend_common_1.addInterestAccrualBuffer)(baseDebtToRepay, debtBankData.borrowApy, timeBufferSec);
1592
- // 6. Final sanity check
1518
+ // --- Step 2: Determine total USDC needed from the swap ---
1519
+ // The total value we need to get from selling JLP is the debt we must repay
1520
+ const totalDebtNeededFromSwap = debtToRepay.add(desiredNetWithdraw);
1521
+ // Now, find how much collateral is needed to produce this swap via swap
1522
+ const swapQuote = await this.swapper.getQuote({
1523
+ inputMint: collateralMint,
1524
+ inputMintDecimals: collateralDecimals,
1525
+ outputMint: debtMint,
1526
+ outputMintDecimals: debtDecimals,
1527
+ inputAmount: totalDebtNeededFromSwap,
1528
+ slippageBps,
1529
+ swapMode: "ExactOut",
1530
+ });
1531
+ // The answer from swapper is the total collateral we must withdraw.
1532
+ collateralToWithdraw = swapQuote.inAmount;
1533
+ // --- Step 4: Final Sanity Check ---
1593
1534
  if (collateralToWithdraw.gt(collateralAmount)) {
1594
1535
  throw new Error("Total required collateral for withdrawal and swap exceeds the available balance.");
1595
1536
  }
@@ -1751,7 +1692,7 @@ class ClendClient {
1751
1692
  finalDebtToRepayTargetBN: finalDebtToRepayTargetBN.toString(),
1752
1693
  finalAdjustedDebtDeltaUi: finalDebtDeltaUi,
1753
1694
  });
1754
- // 3. Recalculate Collateral Delta needed for the swap
1695
+ // 3. Recalculate Collateral Delta needed for the swap (ExactOut)
1755
1696
  const slippageFactor = new decimal_js_1.default(slippageBps).div(10000);
1756
1697
  if (slippageFactor.gte(1)) {
1757
1698
  throw new Error("Slippage cannot be 100% or more.");
@@ -1817,9 +1758,8 @@ class ClendClient {
1817
1758
  finalCollateralDeltaUi, // Use the final adjusted value from getAdjustLeverageParams
1818
1759
  slippageBps, additionalIxnCount, swapperOverride) {
1819
1760
  // override swapper if provided
1820
- let activeSwapper = this.swapper;
1821
1761
  if (swapperOverride) {
1822
- activeSwapper = swapperOverride;
1762
+ this.swapper = swapperOverride;
1823
1763
  }
1824
1764
  const clendAccountData = await this.getClendAccount(clendAccount);
1825
1765
  if (!clendAccountData) {
@@ -1849,60 +1789,90 @@ class ClendClient {
1849
1789
  let swapLookupTables = [];
1850
1790
  if (isIncrease) {
1851
1791
  utils_1.logger.info("Building instructions for increasing leverage");
1852
- // This logic is already ExactIn and correct. No changes needed.
1792
+ // Use the finalDebtDeltaUi passed in (already adjusted)
1853
1793
  const additionalDebtAmount = (0, clend_common_1.uiToAmount)(finalDebtDeltaUi, debtBankData.mintDecimals);
1854
- const swapQuote = await activeSwapper.getQuote({
1855
- payer: user,
1794
+ utils_1.logger.debug("additionalDebtAmount to borrow (final adjusted)", {
1795
+ additionalDebtAmount: additionalDebtAmount.toString(),
1796
+ });
1797
+ // Get Jupiter quote for swapping debt token to collateral token (ExactIn)
1798
+ const swapMode = "ExactIn";
1799
+ const swapQuote = await this.swapper.getQuote({
1856
1800
  inputMint: debtBankData.mint,
1857
1801
  inputMintDecimals: debtBankData.mintDecimals,
1858
1802
  outputMint: collateralBankData.mint,
1859
1803
  outputMintDecimals: collateralBankData.mintDecimals,
1860
1804
  inputAmount: additionalDebtAmount,
1861
1805
  slippageBps,
1862
- swapMode: "ExactIn",
1806
+ swapMode,
1807
+ });
1808
+ utils_1.logger.debug("swapQuote calculated (Increase)", {
1809
+ inAmount: swapQuote.inAmount.toString(),
1810
+ outAmount: swapQuote.outAmount.toString(),
1811
+ otherAmountThreshold: swapQuote.otherAmountThreshold.toString(),
1863
1812
  });
1864
- const swapIxns = await activeSwapper.getSwapIxns(swapQuote);
1865
- const borrowIx = await this.instructions.borrow(clendGroup, clendAccount, user, debtBankData.key, userDebtAta, debtTokenProgram, additionalDebtAmount, remainingAccounts);
1813
+ // Get swap instructions
1814
+ const swapIxns = await this.swapper.getSwapIxns(user, swapQuote);
1815
+ // Borrow the final adjusted debt amount
1816
+ const borrowIx = await this.instructions.borrow(clendGroup, clendAccount, user, debtBankData.key, userDebtAta, debtTokenProgram, additionalDebtAmount, // Use final adjusted amount
1817
+ remainingAccounts);
1818
+ // Calculate additional collateral expected from swap (based on quote)
1866
1819
  const additionalCollateralAmount = swapQuote.outAmount;
1867
- const depositIx = await this.instructions.deposit(clendGroup, clendAccount, user, collateralBankData.key, userCollateralAta, collateralTokenProgram, additionalCollateralAmount, true, remainingAccounts);
1820
+ utils_1.logger.debug("additionalCollateralAmount expected from swap", {
1821
+ additionalCollateralAmount: additionalCollateralAmount.toString(),
1822
+ });
1823
+ // Deposit additional collateral received from swap
1824
+ const depositIx = await this.instructions.deposit(clendGroup, clendAccount, user, collateralBankData.key, userCollateralAta, collateralTokenProgram, additionalCollateralAmount, // Deposit what the swap provides
1825
+ true, remainingAccounts);
1868
1826
  instructions.push(borrowIx, ...swapIxns.ixns, depositIx);
1869
1827
  swapLookupTables = swapIxns.luts;
1870
1828
  }
1871
1829
  else {
1872
- // --- DECREASING LEVERAGE - NEW ExactIn LOGIC ---
1830
+ // Decreasing Leverage
1873
1831
  utils_1.logger.info("Building instructions for decreasing leverage");
1874
- // 1. The collateral to withdraw/sell is our exact input for the swap.
1875
- const collateralToWithdrawBN = (0, clend_common_1.uiToAmount)(finalCollateralDeltaUi, collateralBankData.mintDecimals);
1876
- // 2. Get a fresh 'ExactIn' quote for swapping that collateral amount.
1877
- const swapQuote = await activeSwapper.getQuote({
1878
- payer: clendAccountData.authority,
1832
+ // Use the finalDebtDeltaUi passed in (already includes future buffer)
1833
+ const finalDebtToRepayBN = (0, clend_common_1.uiToAmount)(finalDebtDeltaUi, debtBankData.mintDecimals);
1834
+ // Use the finalCollateralDeltaUi passed in (already includes slippage adjustment for swap)
1835
+ const finalCollateralToWithdrawBN = (0, clend_common_1.uiToAmount)(finalCollateralDeltaUi, collateralBankData.mintDecimals);
1836
+ utils_1.logger.debug("Final amounts for decrease operation (Native)", {
1837
+ finalDebtToRepayBN: finalDebtToRepayBN.toString(),
1838
+ finalCollateralToWithdrawBN: finalCollateralToWithdrawBN.toString(),
1839
+ });
1840
+ // Get Jupiter quote for swapping collateral token to debt token (ExactOut)
1841
+ // Target the final buffered debt repayment amount
1842
+ const swapMode = "ExactOut";
1843
+ const swapQuote = await this.swapper.getQuote({
1879
1844
  inputMint: collateralBankData.mint,
1880
1845
  inputMintDecimals: collateralBankData.mintDecimals,
1881
1846
  outputMint: debtBankData.mint,
1882
1847
  outputMintDecimals: debtBankData.mintDecimals,
1883
- inputAmount: collateralToWithdrawBN, // Use the collateral delta as the exact input
1848
+ inputAmount: finalDebtToRepayBN,
1884
1849
  slippageBps,
1885
- swapMode: "ExactIn",
1850
+ swapMode,
1886
1851
  });
1887
- // 3. The final amount to repay is the optimistic amount from the quote,
1888
- // as we will use the `repay_up_to_amount` flag on-chain.
1889
- const finalDebtToRepay = swapQuote.outAmount;
1890
- const swapIxns = await activeSwapper.getSwapIxns(swapQuote);
1891
- // Withdraw the calculated collateral needed for the swap
1892
- const withdrawIx = await this.instructions.withdraw(clendGroup, clendAccount, user, collateralBankData.key, userCollateralAta, collateralTokenProgram, collateralToWithdrawBN, false, // Not withdrawing all
1852
+ const estimatedCollateralInput = swapQuote.inAmount;
1853
+ utils_1.logger.debug("swapQuote calculated (Decrease)", {
1854
+ targetOutput: finalDebtToRepayBN.toString(),
1855
+ estimatedInput: estimatedCollateralInput,
1856
+ maxInputCalculated: finalCollateralToWithdrawBN.toString(), // Compare Jupiter estimate vs our max calc
1857
+ });
1858
+ const swapIxns = await this.swapper.getSwapIxns(user, swapQuote);
1859
+ // Withdraw the CALCULATED MAXIMUM collateral needed (includes swap input + buffer)
1860
+ const withdrawIx = await this.instructions.withdraw(clendGroup, clendAccount, user, collateralBankData.key, userCollateralAta, collateralTokenProgram, finalCollateralToWithdrawBN, // Use the final adjusted amount from params
1861
+ false, // Not withdrawing all
1893
1862
  remainingAccounts);
1894
- // Repay the debt with the swap output
1895
- const repayIx = await this.instructions.repay(clendGroup, clendAccount, user, debtBankData.key, userDebtAta, debtTokenProgram, finalDebtToRepay, // Use optimistic amount
1863
+ // Repay the CALCULATED BUFFERED debt amount
1864
+ const repayIx = await this.instructions.repay(clendGroup, clendAccount, user, debtBankData.key, userDebtAta, debtTokenProgram, finalDebtToRepayBN, // Use the final adjusted amount from params
1896
1865
  false, // Not repaying all
1897
- true, // Use the new repay_up_to_amount flag
1898
1866
  remainingAccounts);
1899
1867
  instructions.push(withdrawIx, ...swapIxns.ixns, repayIx);
1900
1868
  swapLookupTables = swapIxns.luts;
1901
1869
  }
1902
1870
  // --- Flash Loan Wrapping ---
1903
- const cuIxns = 2;
1871
+ const cuIxns = 2; // Assuming 2 compute budget instructions are added later
1872
+ // Calculate end index based on actual instructions generated + potential JIT ixns + flash loan ixns
1904
1873
  const endIndex = new anchor_1.BN(cuIxns + instructions.length + 1 + additionalIxnCount);
1905
1874
  const { beginFlashLoanIx, endFlashLoanIx } = await this.instructions.createFlashLoanInstructions(clendAccount, user, endIndex, remainingAccounts);
1875
+ // Create a new array with the flash loan instructions wrapping the existing instructions
1906
1876
  const finalInstructions = [
1907
1877
  beginFlashLoanIx,
1908
1878
  ...instructions,