@strkfarm/sdk 2.0.0-dev.28 → 2.0.0-dev.29

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/index.mjs CHANGED
@@ -94,6 +94,9 @@ var Web3Number = class _Web3Number2 extends _Web3Number {
94
94
  const bn = new _Web3Number2(weiNumber, decimals).dividedBy(10 ** decimals);
95
95
  return new _Web3Number2(bn.toString(), decimals);
96
96
  }
97
+ static fromNumber(number, decimals) {
98
+ return new _Web3Number2(number.toString(), decimals);
99
+ }
97
100
  [util.inspect.custom](depth, opts) {
98
101
  return this.toString();
99
102
  }
@@ -1687,6 +1690,48 @@ _StarknetCallParser.METHOD_BY_SELECTOR = new Map(
1687
1690
  );
1688
1691
  var StarknetCallParser = _StarknetCallParser;
1689
1692
 
1693
+ // src/utils/health-factor-math.ts
1694
+ var HealthFactorMath = class {
1695
+ static getCollateralRequired(debtAmount, debtPrice, targetHF, maxLTV, collateralPrice, collateralTokenInfo) {
1696
+ const numerator = debtAmount.multipliedBy(debtPrice).multipliedBy(targetHF);
1697
+ const denominator = collateralPrice * maxLTV;
1698
+ const collateralAmount = numerator.dividedBy(denominator);
1699
+ const netCollateral = new Web3Number(collateralAmount.toString(), collateralTokenInfo.decimals);
1700
+ return netCollateral;
1701
+ }
1702
+ static getMinCollateralRequiredOnLooping(debtAmount, debtPrice, targetHF, maxLTV, collateralPrice, collateralTokenInfo) {
1703
+ const netCollateral = this.getCollateralRequired(debtAmount, debtPrice, targetHF, maxLTV, collateralPrice, collateralTokenInfo);
1704
+ const collateralFromDebt = new Web3Number(debtAmount.multipliedBy(debtPrice).dividedBy(collateralPrice).toString(), collateralTokenInfo.decimals);
1705
+ return netCollateral.minus(collateralFromDebt);
1706
+ }
1707
+ static getHealthFactor(collateralAmount, collateralPrice, maxLTV, debtAmount, debtPrice) {
1708
+ const numerator = collateralAmount.multipliedBy(collateralPrice).multipliedBy(maxLTV);
1709
+ const denominator = debtAmount.multipliedBy(debtPrice);
1710
+ const healthFactor = numerator.dividedBy(denominator);
1711
+ return healthFactor.toNumber();
1712
+ }
1713
+ static getMaxDebtAmountOnLooping(collateralAmount, collateralPrice, maxLTV, targetHF, debtPrice, debtTokenInfo) {
1714
+ const numerator = collateralAmount.multipliedBy(collateralPrice).multipliedBy(maxLTV);
1715
+ logger.verbose(`HealthFactorMath: Max debt amount on looping numerator: ${numerator.toNumber()}, collateralAmount: ${collateralAmount.toNumber()}, collateralPrice: ${collateralPrice}, maxLTV: ${maxLTV}, targetHF: ${targetHF}, debtPrice: ${debtPrice}`);
1716
+ const denominator = targetHF - maxLTV;
1717
+ logger.verbose(`HealthFactorMath: Max debt amount on looping denominator: ${denominator}`);
1718
+ const debtAmountUSD = numerator.dividedBy(denominator);
1719
+ logger.verbose(`HealthFactorMath: Max debt amount on looping debtAmountUSD: ${debtAmountUSD.toNumber()}`);
1720
+ const debtAmount = debtAmountUSD.dividedBy(debtPrice);
1721
+ logger.verbose(`HealthFactorMath: Max debt amount on looping debtAmount: ${debtAmount.toNumber()}`);
1722
+ return new Web3Number(debtAmount.toString(), debtTokenInfo.decimals);
1723
+ }
1724
+ static getMaxDebtAmount(collateralAmount, collateralPrice, maxLTV, targetHF, debtPrice, debtTokenInfo) {
1725
+ const numerator = collateralAmount.multipliedBy(collateralPrice).multipliedBy(maxLTV);
1726
+ logger.verbose(`HealthFactorMath: Max debt amount numerator: ${numerator.toNumber()}, collateralAmount: ${collateralAmount.toNumber()}, collateralPrice: ${collateralPrice}, maxLTV: ${maxLTV}, targetHF: ${targetHF}, debtPrice: ${debtPrice}`);
1727
+ const denominator = targetHF * debtPrice;
1728
+ logger.verbose(`HealthFactorMath: Max debt amount denominator: ${denominator}, targetHF: ${targetHF}, debtPrice: ${debtPrice}`);
1729
+ const debtAmount = numerator.dividedBy(denominator);
1730
+ logger.verbose(`HealthFactorMath: Max debt amount: ${debtAmount.toNumber()}, numerator: ${numerator.toNumber()}, denominator: ${denominator}`);
1731
+ return new Web3Number(debtAmount.toString(), debtTokenInfo.decimals);
1732
+ }
1733
+ };
1734
+
1690
1735
  // src/utils/index.ts
1691
1736
  function assert(condition, message) {
1692
1737
  if (!condition) {
@@ -4086,6 +4131,9 @@ var Web3Number2 = class _Web3Number2 extends _Web3Number {
4086
4131
  const bn = new _Web3Number2(weiNumber, decimals).dividedBy(10 ** decimals);
4087
4132
  return new _Web3Number2(bn.toString(), decimals);
4088
4133
  }
4134
+ static fromNumber(number, decimals) {
4135
+ return new _Web3Number2(number.toString(), decimals);
4136
+ }
4089
4137
  };
4090
4138
 
4091
4139
  // src/interfaces/lending.ts
@@ -6260,6 +6308,10 @@ var VaultProtocol = {
6260
6308
  name: "Vault",
6261
6309
  logo: ""
6262
6310
  };
6311
+ var TrovesProtocol = {
6312
+ name: "Troves",
6313
+ logo: "https://app.troves.fi/favicon.ico"
6314
+ };
6263
6315
  var Protocols = {
6264
6316
  NONE: NoneProtocol,
6265
6317
  VESU: VesuProtocol,
@@ -6267,7 +6319,8 @@ var Protocols = {
6267
6319
  EXTENDED: ExtendedProtocol,
6268
6320
  EKUBO: EkuboProtocol,
6269
6321
  AVNU: AvnuProtocol,
6270
- VAULT: VaultProtocol
6322
+ VAULT: VaultProtocol,
6323
+ TROVES: TrovesProtocol
6271
6324
  };
6272
6325
 
6273
6326
  // src/interfaces/initializable.ts
@@ -34002,48 +34055,6 @@ var AbisConfig = {
34002
34055
  }
34003
34056
  };
34004
34057
 
34005
- // src/utils/health-factor-math.ts
34006
- var HealthFactorMath = class {
34007
- static getCollateralRequired(debtAmount, debtPrice, targetHF, maxLTV, collateralPrice, collateralTokenInfo) {
34008
- const numerator = debtAmount.multipliedBy(debtPrice).multipliedBy(targetHF);
34009
- const denominator = collateralPrice * maxLTV;
34010
- const collateralAmount = numerator.dividedBy(denominator);
34011
- const netCollateral = new Web3Number(collateralAmount.toString(), collateralTokenInfo.decimals);
34012
- return netCollateral;
34013
- }
34014
- static getMinCollateralRequiredOnLooping(debtAmount, debtPrice, targetHF, maxLTV, collateralPrice, collateralTokenInfo) {
34015
- const netCollateral = this.getCollateralRequired(debtAmount, debtPrice, targetHF, maxLTV, collateralPrice, collateralTokenInfo);
34016
- const collateralFromDebt = new Web3Number(debtAmount.multipliedBy(debtPrice).dividedBy(collateralPrice).toString(), collateralTokenInfo.decimals);
34017
- return netCollateral.minus(collateralFromDebt);
34018
- }
34019
- static getHealthFactor(collateralAmount, collateralPrice, maxLTV, debtAmount, debtPrice) {
34020
- const numerator = collateralAmount.multipliedBy(collateralPrice).multipliedBy(maxLTV);
34021
- const denominator = debtAmount.multipliedBy(debtPrice);
34022
- const healthFactor = numerator.dividedBy(denominator);
34023
- return healthFactor.toNumber();
34024
- }
34025
- static getMaxDebtAmountOnLooping(collateralAmount, collateralPrice, maxLTV, targetHF, debtPrice, debtTokenInfo) {
34026
- const numerator = collateralAmount.multipliedBy(collateralPrice).multipliedBy(maxLTV);
34027
- logger.verbose(`HealthFactorMath: Max debt amount on looping numerator: ${numerator.toNumber()}, collateralAmount: ${collateralAmount.toNumber()}, collateralPrice: ${collateralPrice}, maxLTV: ${maxLTV}, targetHF: ${targetHF}, debtPrice: ${debtPrice}`);
34028
- const denominator = targetHF - maxLTV;
34029
- logger.verbose(`HealthFactorMath: Max debt amount on looping denominator: ${denominator}`);
34030
- const debtAmountUSD = numerator.dividedBy(denominator);
34031
- logger.verbose(`HealthFactorMath: Max debt amount on looping debtAmountUSD: ${debtAmountUSD.toNumber()}`);
34032
- const debtAmount = debtAmountUSD.dividedBy(debtPrice);
34033
- logger.verbose(`HealthFactorMath: Max debt amount on looping debtAmount: ${debtAmount.toNumber()}`);
34034
- return new Web3Number(debtAmount.toString(), debtTokenInfo.decimals);
34035
- }
34036
- static getMaxDebtAmount(collateralAmount, collateralPrice, maxLTV, targetHF, debtPrice, debtTokenInfo) {
34037
- const numerator = collateralAmount.multipliedBy(collateralPrice).multipliedBy(maxLTV);
34038
- logger.verbose(`HealthFactorMath: Max debt amount numerator: ${numerator.toNumber()}, collateralAmount: ${collateralAmount.toNumber()}, collateralPrice: ${collateralPrice}, maxLTV: ${maxLTV}, targetHF: ${targetHF}, debtPrice: ${debtPrice}`);
34039
- const denominator = targetHF * debtPrice;
34040
- logger.verbose(`HealthFactorMath: Max debt amount denominator: ${denominator}, targetHF: ${targetHF}, debtPrice: ${debtPrice}`);
34041
- const debtAmount = numerator.dividedBy(denominator);
34042
- logger.verbose(`HealthFactorMath: Max debt amount: ${debtAmount.toNumber()}, numerator: ${numerator.toNumber()}, denominator: ${denominator}`);
34043
- return new Web3Number(debtAmount.toString(), debtTokenInfo.decimals);
34044
- }
34045
- };
34046
-
34047
34058
  // src/strategies/vesu-extended-strategy/utils/helper.ts
34048
34059
  var returnFormattedAmount = (amount, toTokenDecimals) => {
34049
34060
  const formattedAmount = "0x" + BigInt(Math.floor(amount * 10 ** toTokenDecimals)).toString(16);
@@ -34686,6 +34697,7 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34686
34697
  const collateralToken = this.config.collateral;
34687
34698
  const debtToken = this.config.debt;
34688
34699
  const { contract: multiplyContract } = this._getMultiplyContract();
34700
+ this.lastSwapPriceInfo = null;
34689
34701
  const {
34690
34702
  existingCollateralInfo,
34691
34703
  existingDebtInfo,
@@ -34722,9 +34734,15 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34722
34734
  let marginSwapLimitAmount = Web3Number.fromWei(0, collateralToken.decimals);
34723
34735
  let addedCollateral = params.amount;
34724
34736
  let approveAmount = params.amount;
34737
+ let aggregatedFromAmount = 0;
34738
+ let aggregatedToAmount = 0;
34739
+ let aggregatedFromSymbol = debtToken.symbol;
34740
+ const aggregatedToSymbol = collateralToken.symbol;
34741
+ let executedSwapCount = 0;
34725
34742
  if (params.marginSwap) {
34726
34743
  const marginToken = params.marginSwap.marginToken;
34727
34744
  const requiredAmount = params.amount;
34745
+ assert(marginToken.address.eq(debtToken.address), "Margin token must be the same as debt token");
34728
34746
  const marginSwapQuote = await ekuboQuoter.getQuoteExactOutput(
34729
34747
  marginToken.address.address,
34730
34748
  collateralToken.address.address,
@@ -34740,6 +34758,11 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34740
34758
  marginToken,
34741
34759
  collateralToken
34742
34760
  );
34761
+ const marginSwapInputAmount = Web3Number.fromWei(marginSwapQuote.total_calculated, marginToken.decimals).abs().toNumber();
34762
+ const marginSwapOutputAmount = requiredAmount.abs().toNumber();
34763
+ aggregatedFromAmount += marginSwapInputAmount;
34764
+ aggregatedToAmount += marginSwapOutputAmount;
34765
+ executedSwapCount += 1;
34743
34766
  approveAmount = Web3Number.fromWei(marginSwapQuote.total_calculated, marginToken.decimals).multipliedBy(1 + this.maxSlippage).abs();
34744
34767
  }
34745
34768
  let debtAmount = this._computeTargetDebtDelta(
@@ -34763,10 +34786,6 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34763
34786
  if (!debtAmount.isZero() && debtAmount.greaterThan(0)) {
34764
34787
  try {
34765
34788
  let swapQuote;
34766
- const debtAmountInCollateralUnits = new Web3Number(
34767
- debtAmount.multipliedBy(debtPrice).dividedBy(collateralPrice).toFixed(6),
34768
- collateralToken.decimals
34769
- );
34770
34789
  if (params.leverSwap?.exactOutput) {
34771
34790
  swapQuote = await ekuboQuoter.getQuoteExactOutput(
34772
34791
  debtToken.address.address,
@@ -34790,17 +34809,9 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34790
34809
  ).abs().toNumber();
34791
34810
  const inputAmt = debtAmount.abs().toNumber();
34792
34811
  const outputAmt = quoteOutputAmount;
34793
- this.lastSwapPriceInfo = {
34794
- source: "ekubo",
34795
- fromTokenSymbol: debtToken.symbol,
34796
- toTokenSymbol: collateralToken.symbol,
34797
- fromAmount: inputAmt,
34798
- toAmount: outputAmt,
34799
- effectivePrice: outputAmt !== 0 ? inputAmt / outputAmt : 0
34800
- };
34801
- logger.verbose(
34802
- `${_VesuMultiplyAdapter.name}::_getIncreaseCalldata stored price info: ${inputAmt} ${debtToken.symbol} \u2192 ${outputAmt} ${collateralToken.symbol}, effectivePrice=${this.lastSwapPriceInfo.effectivePrice}`
34803
- );
34812
+ aggregatedFromAmount += inputAmt;
34813
+ aggregatedToAmount += outputAmt;
34814
+ executedSwapCount += 1;
34804
34815
  leverSwap = ekuboQuoter.getVesuMultiplyQuote(
34805
34816
  swapQuote,
34806
34817
  debtToken,
@@ -34818,6 +34829,19 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34818
34829
  );
34819
34830
  }
34820
34831
  }
34832
+ if (executedSwapCount > 0) {
34833
+ this.lastSwapPriceInfo = {
34834
+ source: "ekubo",
34835
+ fromTokenSymbol: aggregatedFromSymbol ?? debtToken.symbol,
34836
+ toTokenSymbol: aggregatedToSymbol,
34837
+ fromAmount: aggregatedFromAmount,
34838
+ toAmount: aggregatedToAmount,
34839
+ effectivePrice: aggregatedToAmount !== 0 ? aggregatedFromAmount / aggregatedToAmount : 0
34840
+ };
34841
+ logger.verbose(
34842
+ `${_VesuMultiplyAdapter.name}::_getIncreaseCalldata stored aggregated price info: ${aggregatedFromAmount} ${this.lastSwapPriceInfo.fromTokenSymbol} \u2192 ${aggregatedToAmount} ${aggregatedToSymbol}, effectivePrice=${this.lastSwapPriceInfo.effectivePrice}, swaps=${executedSwapCount}`
34843
+ );
34844
+ }
34821
34845
  logger.verbose(
34822
34846
  `${_VesuMultiplyAdapter.name}::_getIncreaseCalldata leverSwapLimitAmount: ${leverSwapLimitAmount.toWei()}`
34823
34847
  );
@@ -34850,10 +34874,21 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34850
34874
  approveAmount
34851
34875
  };
34852
34876
  }
34877
+ // private _setLastSwapPriceAsNoSwap(): void {
34878
+ // this.lastSwapPriceInfo = {
34879
+ // source: "no-swap",
34880
+ // fromTokenSymbol: this.config.collateral.symbol,
34881
+ // toTokenSymbol: this.config.debt.symbol,
34882
+ // fromAmount: 0,
34883
+ // toAmount: 0,
34884
+ // effectivePrice: 0,
34885
+ // };
34886
+ // }
34853
34887
  async _buildDecreaseLikeCalldata(params) {
34854
34888
  const collateralToken = this.config.collateral;
34855
34889
  const debtToken = this.config.debt;
34856
34890
  const { contract: multiplyContract } = this._getMultiplyContract();
34891
+ this.lastSwapPriceInfo = null;
34857
34892
  const ekuboQuoter = new EkuboQuoter(
34858
34893
  this.config.networkConfig,
34859
34894
  this.config.pricer
@@ -34862,6 +34897,9 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34862
34897
  let leverSwapWeights = [];
34863
34898
  let leverSwapLimitAmount = Web3Number.fromWei(0, collateralToken.decimals);
34864
34899
  let leverCollateralUsed = Web3Number.fromWei(0, collateralToken.decimals);
34900
+ let aggregatedFromAmount = 0;
34901
+ let aggregatedToAmount = 0;
34902
+ let executedSwapCount = 0;
34865
34903
  if (params.closePosition) {
34866
34904
  const debtQuote = await ekuboQuoter.getQuoteExactOutput(
34867
34905
  collateralToken.address.address,
@@ -34880,6 +34918,9 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34880
34918
  collateralToken.decimals
34881
34919
  ).abs();
34882
34920
  leverSwapLimitAmount = leverCollateralUsed.multipliedBy(1 + this.maxSlippage);
34921
+ aggregatedFromAmount += leverCollateralUsed.toNumber();
34922
+ aggregatedToAmount += params.debtToRepayAbs.abs().toNumber();
34923
+ executedSwapCount += 1;
34883
34924
  } else {
34884
34925
  if (params.collateralPrice === void 0 || params.debtPrice === void 0) {
34885
34926
  throw new Error(
@@ -34901,17 +34942,9 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34901
34942
  leverSwapQuote.total_calculated,
34902
34943
  debtToken.decimals
34903
34944
  ).abs().toNumber();
34904
- this.lastSwapPriceInfo = {
34905
- source: "ekubo",
34906
- fromTokenSymbol: collateralToken.symbol,
34907
- toTokenSymbol: debtToken.symbol,
34908
- fromAmount: inputAmt,
34909
- toAmount: outputAmt,
34910
- effectivePrice: outputAmt !== 0 ? outputAmt / inputAmt : 0
34911
- };
34912
- logger.verbose(
34913
- `${_VesuMultiplyAdapter.name}::_buildDecreaseLikeCalldata stored price info: ${inputAmt} ${collateralToken.symbol} \u2192 ${outputAmt} ${debtToken.symbol}, effectivePrice=${this.lastSwapPriceInfo.effectivePrice}`
34914
- );
34945
+ aggregatedFromAmount += inputAmt;
34946
+ aggregatedToAmount += outputAmt;
34947
+ executedSwapCount += 1;
34915
34948
  leverSwap = ekuboQuoter.getVesuMultiplyQuote(
34916
34949
  leverSwapQuote,
34917
34950
  collateralToken,
@@ -34934,6 +34967,7 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34934
34967
  );
34935
34968
  const withdrawSwapWeights = [];
34936
34969
  if (params.outputToken && !params.outputToken.address.eq(collateralToken.address)) {
34970
+ assert(params.outputToken.address.eq(debtToken.address), "Withdraw output token must be the same as debt token");
34937
34971
  const residualCollateral = params.closePosition ? params.existingCollateral.minus(leverCollateralUsed) : params.subMargin;
34938
34972
  const outputTokenPrice = await this.config.pricer.getPrice(params.outputToken.symbol);
34939
34973
  if (residualCollateral.greaterThan(0)) {
@@ -34950,12 +34984,34 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34950
34984
  );
34951
34985
  withdrawSwap = built.swaps;
34952
34986
  withdrawSwapWeights.push(...built.weights);
34987
+ const withdrawOutputAmount = Web3Number.fromWei(
34988
+ withdrawQuote.total_calculated,
34989
+ params.outputToken.decimals
34990
+ ).abs().toNumber();
34991
+ aggregatedFromAmount += residualCollateral.toNumber();
34992
+ aggregatedToAmount += withdrawOutputAmount;
34993
+ executedSwapCount += 1;
34953
34994
  const estimatedOutput = residualCollateral.multipliedBy(params.collateralPrice).dividedBy(outputTokenPrice.price);
34954
34995
  estimatedOutput.decimals = params.outputToken.decimals;
34955
34996
  withdrawSwapLimitAmount = estimatedOutput.multipliedBy(1 - this.maxSlippage);
34956
34997
  }
34957
34998
  }
34958
34999
  }
35000
+ if (executedSwapCount > 0) {
35001
+ this.lastSwapPriceInfo = {
35002
+ source: "ekubo",
35003
+ fromTokenSymbol: collateralToken.symbol,
35004
+ toTokenSymbol: debtToken.symbol,
35005
+ fromAmount: aggregatedFromAmount,
35006
+ toAmount: aggregatedToAmount,
35007
+ effectivePrice: aggregatedFromAmount !== 0 ? aggregatedToAmount / aggregatedFromAmount : 0
35008
+ };
35009
+ logger.verbose(
35010
+ `${_VesuMultiplyAdapter.name}::_buildDecreaseLikeCalldata stored aggregated price info: ${aggregatedFromAmount} ${collateralToken.symbol} \u2192 ${aggregatedToAmount} ${debtToken.symbol}, effectivePrice=${this.lastSwapPriceInfo.effectivePrice}, swaps=${executedSwapCount}`
35011
+ );
35012
+ } else {
35013
+ this.lastSwapPriceInfo = null;
35014
+ }
34959
35015
  logger.debug(
34960
35016
  `${_VesuMultiplyAdapter.name}::_buildDecreaseLikeCalldata leverSwapCount=${leverSwap.length}, withdrawSwapCount=${withdrawSwap.length}, withdrawSwapLimitAmount=${withdrawSwapLimitAmount.toNumber()}, subMargin=${params.subMargin.toNumber()}`
34961
35017
  );
@@ -36143,13 +36199,13 @@ var ExtendedAdapter = class _ExtendedAdapter extends BaseAdapter {
36143
36199
  this.minimumExtendedMovementAmount = this.config.minimumExtendedMovementAmount ?? 5;
36144
36200
  this.client = client;
36145
36201
  this.retryDelayForOrderStatus = this.config.retryDelayForOrderStatus ?? 3e3;
36146
- this.usdceToken = this.config.supportedPositions[0].asset;
36202
+ this.usdcToken = this.config.supportedPositions[0].asset;
36147
36203
  }
36148
36204
  _depositApproveProofReadableId() {
36149
- return `extended_approve_${this.usdceToken.symbol}`;
36205
+ return `extended_approve_${this.usdcToken.symbol}`;
36150
36206
  }
36151
36207
  _depositCallProofReadableId() {
36152
- return `extended_deposit_${this.usdceToken.symbol}`;
36208
+ return `extended_deposit_${this.usdcToken.symbol}`;
36153
36209
  }
36154
36210
  //abstract means the method has no implementation in this class; instead, child classes must implement it.
36155
36211
  async getAPY(supportedPosition) {
@@ -36218,7 +36274,7 @@ var ExtendedAdapter = class _ExtendedAdapter extends BaseAdapter {
36218
36274
  _getDepositLeaf() {
36219
36275
  return [
36220
36276
  {
36221
- target: this.usdceToken.address,
36277
+ target: this.usdcToken.address,
36222
36278
  method: "approve",
36223
36279
  packedArguments: [this.config.extendedContract.toBigInt()],
36224
36280
  id: this._depositApproveProofReadableId(),
@@ -36238,14 +36294,14 @@ var ExtendedAdapter = class _ExtendedAdapter extends BaseAdapter {
36238
36294
  }
36239
36295
  async getDepositCall(params) {
36240
36296
  try {
36241
- const salt = Math.floor(Math.random() * 10 ** this.usdceToken.decimals);
36297
+ const salt = Math.floor(Math.random() * 10 ** this.usdcToken.decimals);
36242
36298
  const amount = uint25616.bnToUint256(params.amount.toWei());
36243
36299
  return [
36244
36300
  {
36245
36301
  proofReadableId: this._depositApproveProofReadableId(),
36246
36302
  sanitizer: SIMPLE_SANITIZER,
36247
36303
  call: {
36248
- contractAddress: this.usdceToken.address,
36304
+ contractAddress: this.usdcToken.address,
36249
36305
  selector: hash8.getSelectorFromName("approve"),
36250
36306
  calldata: [
36251
36307
  this.config.extendedContract.toBigInt(),
@@ -37146,8 +37202,8 @@ var AvnuAdapter = class _AvnuAdapter extends BaseAdapter {
37146
37202
  }
37147
37203
  };
37148
37204
 
37149
- // src/strategies/universal-strategy.tsx
37150
- import { CallData, Contract as Contract14, num as num12, uint256 as uint25619 } from "starknet";
37205
+ // src/strategies/universal-adapters/svk-troves-adapter.ts
37206
+ import { hash as hash11, uint256 as uint25619, Contract as Contract14 } from "starknet";
37151
37207
 
37152
37208
  // src/data/universal-vault.abi.json
37153
37209
  var universal_vault_abi_default = [
@@ -38831,6 +38887,272 @@ var universal_vault_abi_default = [
38831
38887
  }
38832
38888
  ];
38833
38889
 
38890
+ // src/strategies/universal-adapters/svk-troves-adapter.ts
38891
+ var DEFAULT_TROVES_STRATEGIES_API = "https://app.troves.fi/api/strategies";
38892
+ function parseTrovesApyField(raw) {
38893
+ if (typeof raw === "number" && Number.isFinite(raw)) {
38894
+ return raw;
38895
+ }
38896
+ if (typeof raw === "string") {
38897
+ const n = Number.parseFloat(raw);
38898
+ if (Number.isFinite(n)) {
38899
+ return n;
38900
+ }
38901
+ }
38902
+ return 0;
38903
+ }
38904
+ var SvkTrovesAdapter = class _SvkTrovesAdapter extends BaseAdapter {
38905
+ constructor(config) {
38906
+ super(config, _SvkTrovesAdapter.name, Protocols.TROVES);
38907
+ this.config = config;
38908
+ }
38909
+ /** Owner used for share balance + `due_assets_from_owner`. */
38910
+ _positionOwner() {
38911
+ return this.config.positionOwner ?? this.config.vaultAllocator;
38912
+ }
38913
+ /**
38914
+ * Proof readable IDs must stay ≤ 31 chars (Cairo short string). We derive a short ASCII suffix from
38915
+ * `strategyVault` address so multiple SVK adapters in one tree stay distinct.
38916
+ */
38917
+ _proofSuffix() {
38918
+ return this.config.strategyVault.address.replace(/^0x/, "").slice(-6);
38919
+ }
38920
+ _depositApproveProofReadableId() {
38921
+ return `appr_dep_svk_${this._proofSuffix()}`;
38922
+ }
38923
+ _depositCallProofReadableId() {
38924
+ return `dep_svk_${this._proofSuffix()}`;
38925
+ }
38926
+ _withdrawCallProofReadableId() {
38927
+ return `wtdrw_svk_${this._proofSuffix()}`;
38928
+ }
38929
+ async getAPY(supportedPosition) {
38930
+ const CACHE_KEY = `svk_apy_${this.config.trovesStrategyId}`;
38931
+ const cached = this.getCache(CACHE_KEY);
38932
+ if (cached) {
38933
+ return cached;
38934
+ }
38935
+ const url = this.config.trovesStrategiesApiUrl ?? DEFAULT_TROVES_STRATEGIES_API;
38936
+ try {
38937
+ const res = await fetch(url);
38938
+ if (!res.ok) {
38939
+ logger.warn(`${_SvkTrovesAdapter.name}::getAPY: HTTP ${res.status} from ${url}`);
38940
+ const fallback = { apy: 0, type: "base" /* BASE */ };
38941
+ this.setCache(CACHE_KEY, fallback, 3e5);
38942
+ return fallback;
38943
+ }
38944
+ const body = await res.json();
38945
+ const row = body.strategies?.find((s) => s.id === this.config.trovesStrategyId);
38946
+ if (!row) {
38947
+ logger.warn(
38948
+ `${_SvkTrovesAdapter.name}::getAPY: strategy id not found: ${this.config.trovesStrategyId}`
38949
+ );
38950
+ const fallback = { apy: 0, type: "base" /* BASE */ };
38951
+ this.setCache(CACHE_KEY, fallback, 3e5);
38952
+ return fallback;
38953
+ }
38954
+ const apy = parseTrovesApyField(row.apy);
38955
+ const result = { apy, type: "base" /* BASE */ };
38956
+ this.setCache(CACHE_KEY, result, 3e5);
38957
+ return result;
38958
+ } catch (error) {
38959
+ logger.error(`${_SvkTrovesAdapter.name}::getAPY:`, error);
38960
+ throw error;
38961
+ }
38962
+ }
38963
+ async getPosition(supportedPosition) {
38964
+ const CACHE_KEY = `svk_pos_${this.config.strategyVault.address}_${this._positionOwner().address}`;
38965
+ const cached = this.getCache(CACHE_KEY);
38966
+ if (cached) {
38967
+ return cached;
38968
+ }
38969
+ try {
38970
+ const vault = new Contract14({
38971
+ abi: universal_vault_abi_default,
38972
+ address: this.config.strategyVault.address,
38973
+ providerOrAccount: this.config.networkConfig.provider
38974
+ });
38975
+ const owner = this._positionOwner();
38976
+ const decimals = supportedPosition.asset.decimals;
38977
+ const shares = await vault.balance_of(owner.address);
38978
+ const shareU256 = uint25619.bnToUint256(shares);
38979
+ const liquidAssetsRaw = await vault.convert_to_assets(shareU256);
38980
+ const liquid = Web3Number.fromWei(liquidAssetsRaw.toString(), decimals);
38981
+ let pending = Web3Number.fromWei("0", decimals);
38982
+ try {
38983
+ const dueRaw = await vault.call("due_assets_from_owner", [owner.address]);
38984
+ pending = Web3Number.fromWei(dueRaw.toString(), decimals);
38985
+ } catch (e) {
38986
+ logger.warn(
38987
+ `${_SvkTrovesAdapter.name}::getPosition: due_assets_from_owner failed (treating as 0): ${e.message}`
38988
+ );
38989
+ }
38990
+ const total = liquid.plus(pending);
38991
+ const remarks = `Troves ${this.config.trovesStrategyId} holdings`;
38992
+ const result = {
38993
+ amount: total,
38994
+ remarks
38995
+ };
38996
+ this.setCache(CACHE_KEY, result, 6e4);
38997
+ return result;
38998
+ } catch (error) {
38999
+ logger.error(`${_SvkTrovesAdapter.name}::getPosition:`, error);
39000
+ throw error;
39001
+ }
39002
+ }
39003
+ async maxDeposit(amount) {
39004
+ const baseToken = this.config.baseToken;
39005
+ if (!amount) {
39006
+ return {
39007
+ tokenInfo: baseToken,
39008
+ amount: new Web3Number("999999999999999999999999999", baseToken.decimals),
39009
+ usdValue: 1e27,
39010
+ remarks: "Max deposit (unbounded placeholder)",
39011
+ apy: await this.getAPY({ asset: baseToken, isDebt: false }),
39012
+ protocol: this.protocol
39013
+ };
39014
+ }
39015
+ const usdValue = await this.getUSDValue(baseToken, amount);
39016
+ return {
39017
+ tokenInfo: baseToken,
39018
+ amount,
39019
+ usdValue,
39020
+ remarks: "Deposit amount",
39021
+ apy: await this.getAPY({ asset: baseToken, isDebt: false }),
39022
+ protocol: this.protocol
39023
+ };
39024
+ }
39025
+ async maxWithdraw() {
39026
+ const baseToken = this.config.baseToken;
39027
+ const current = await this.getPosition({ asset: baseToken, isDebt: false });
39028
+ const pos = current ?? { amount: new Web3Number("0", baseToken.decimals), remarks: "" };
39029
+ const usdValue = await this.getUSDValue(baseToken, pos.amount);
39030
+ return {
39031
+ tokenInfo: baseToken,
39032
+ amount: pos.amount,
39033
+ usdValue,
39034
+ remarks: "Max withdraw (liquid + pending redemption, underlying units)",
39035
+ apy: await this.getAPY({ asset: baseToken, isDebt: false }),
39036
+ protocol: this.protocol
39037
+ };
39038
+ }
39039
+ _getDepositLeaf() {
39040
+ const baseToken = this.config.baseToken;
39041
+ const strategyVault = this.config.strategyVault;
39042
+ const receiver = this.config.vaultAllocator;
39043
+ return [
39044
+ {
39045
+ target: baseToken.address,
39046
+ method: "approve",
39047
+ packedArguments: [strategyVault.toBigInt()],
39048
+ sanitizer: SIMPLE_SANITIZER,
39049
+ id: this._depositApproveProofReadableId()
39050
+ },
39051
+ {
39052
+ target: strategyVault,
39053
+ method: "deposit",
39054
+ packedArguments: [receiver.toBigInt()],
39055
+ sanitizer: SIMPLE_SANITIZER,
39056
+ id: this._depositCallProofReadableId()
39057
+ }
39058
+ ];
39059
+ }
39060
+ _getWithdrawLeaf() {
39061
+ const strategyVault = this.config.strategyVault;
39062
+ const recv = this.config.vaultAllocator;
39063
+ const owner = this.config.vaultAllocator;
39064
+ return [
39065
+ {
39066
+ target: strategyVault,
39067
+ method: "withdraw",
39068
+ packedArguments: [recv.toBigInt(), owner.toBigInt()],
39069
+ sanitizer: SIMPLE_SANITIZER,
39070
+ id: this._withdrawCallProofReadableId()
39071
+ }
39072
+ ];
39073
+ }
39074
+ getDepositAdapter() {
39075
+ const leafConfigs = this._getDepositLeaf();
39076
+ const leaves = leafConfigs.map((config) => {
39077
+ const { target, method, packedArguments, sanitizer, id } = config;
39078
+ return this.constructSimpleLeafData({ id, target, method, packedArguments }, sanitizer);
39079
+ });
39080
+ return { leaves, callConstructor: this.getDepositCall.bind(this) };
39081
+ }
39082
+ getWithdrawAdapter() {
39083
+ const leafConfigs = this._getWithdrawLeaf();
39084
+ const leaves = leafConfigs.map((config) => {
39085
+ const { target, method, packedArguments, sanitizer, id } = config;
39086
+ return this.constructSimpleLeafData({ id, target, method, packedArguments }, sanitizer);
39087
+ });
39088
+ return { leaves, callConstructor: this.getWithdrawCall.bind(this) };
39089
+ }
39090
+ async getDepositCall(params) {
39091
+ const baseToken = this.config.baseToken;
39092
+ const strategyVault = this.config.strategyVault;
39093
+ const amount = params.amount;
39094
+ const uint256Amount = uint25619.bnToUint256(amount.toWei());
39095
+ const receiver = this.config.vaultAllocator;
39096
+ return [
39097
+ {
39098
+ proofReadableId: this._depositApproveProofReadableId(),
39099
+ sanitizer: SIMPLE_SANITIZER,
39100
+ call: {
39101
+ contractAddress: baseToken.address,
39102
+ selector: hash11.getSelectorFromName("approve"),
39103
+ calldata: [
39104
+ strategyVault.toBigInt(),
39105
+ toBigInt(uint256Amount.low.toString()),
39106
+ toBigInt(uint256Amount.high.toString())
39107
+ ]
39108
+ }
39109
+ },
39110
+ {
39111
+ proofReadableId: this._depositCallProofReadableId(),
39112
+ sanitizer: SIMPLE_SANITIZER,
39113
+ call: {
39114
+ contractAddress: strategyVault,
39115
+ selector: hash11.getSelectorFromName("deposit"),
39116
+ calldata: [
39117
+ toBigInt(uint256Amount.low.toString()),
39118
+ toBigInt(uint256Amount.high.toString()),
39119
+ receiver.toBigInt()
39120
+ ]
39121
+ }
39122
+ }
39123
+ ];
39124
+ }
39125
+ async getWithdrawCall(params) {
39126
+ const strategyVault = this.config.strategyVault;
39127
+ const amount = params.amount;
39128
+ const uint256Amount = uint25619.bnToUint256(amount.toWei());
39129
+ const recv = this.config.vaultAllocator;
39130
+ const owner = this.config.vaultAllocator;
39131
+ return [
39132
+ {
39133
+ proofReadableId: this._withdrawCallProofReadableId(),
39134
+ sanitizer: SIMPLE_SANITIZER,
39135
+ call: {
39136
+ contractAddress: strategyVault,
39137
+ selector: hash11.getSelectorFromName("withdraw"),
39138
+ calldata: [
39139
+ toBigInt(uint256Amount.low.toString()),
39140
+ toBigInt(uint256Amount.high.toString()),
39141
+ recv.toBigInt(),
39142
+ owner.toBigInt()
39143
+ ]
39144
+ }
39145
+ }
39146
+ ];
39147
+ }
39148
+ getHealthFactor() {
39149
+ return Promise.resolve(10);
39150
+ }
39151
+ };
39152
+
39153
+ // src/strategies/universal-strategy.tsx
39154
+ import { CallData, Contract as Contract15, num as num12, uint256 as uint25620 } from "starknet";
39155
+
38834
39156
  // src/data/vault-manager.abi.json
38835
39157
  var vault_manager_abi_default = [
38836
39158
  {
@@ -39496,12 +39818,12 @@ var UniversalStrategy = class _UniversalStrategy extends BaseStrategy {
39496
39818
  );
39497
39819
  this.metadata = metadata;
39498
39820
  this.address = metadata.address;
39499
- this.contract = new Contract14({
39821
+ this.contract = new Contract15({
39500
39822
  abi: universal_vault_abi_default,
39501
39823
  address: this.address.address,
39502
39824
  providerOrAccount: this.config.provider
39503
39825
  });
39504
- this.managerContract = new Contract14({
39826
+ this.managerContract = new Contract15({
39505
39827
  abi: vault_manager_abi_default,
39506
39828
  address: this.metadata.additionalInfo.manager.address,
39507
39829
  providerOrAccount: this.config.provider
@@ -39555,17 +39877,17 @@ var UniversalStrategy = class _UniversalStrategy extends BaseStrategy {
39555
39877
  amountInfo.tokenInfo.address.eq(this.asset().address),
39556
39878
  "Deposit token mismatch"
39557
39879
  );
39558
- const assetContract = new Contract14({
39880
+ const assetContract = new Contract15({
39559
39881
  abi: universal_vault_abi_default,
39560
39882
  address: this.asset().address.address,
39561
39883
  providerOrAccount: this.config.provider
39562
39884
  });
39563
39885
  const call1 = assetContract.populate("approve", [
39564
39886
  this.address.address,
39565
- uint25619.bnToUint256(amountInfo.amount.toWei())
39887
+ uint25620.bnToUint256(amountInfo.amount.toWei())
39566
39888
  ]);
39567
39889
  const call2 = this.contract.populate("deposit", [
39568
- uint25619.bnToUint256(amountInfo.amount.toWei()),
39890
+ uint25620.bnToUint256(amountInfo.amount.toWei()),
39569
39891
  receiver.address
39570
39892
  ]);
39571
39893
  return [call1, call2];
@@ -39575,9 +39897,9 @@ var UniversalStrategy = class _UniversalStrategy extends BaseStrategy {
39575
39897
  amountInfo.tokenInfo.address.eq(this.asset().address),
39576
39898
  "Withdraw token mismatch"
39577
39899
  );
39578
- const shares = await this.contract.call("convert_to_shares", [uint25619.bnToUint256(amountInfo.amount.toWei())]);
39900
+ const shares = await this.contract.call("convert_to_shares", [uint25620.bnToUint256(amountInfo.amount.toWei())]);
39579
39901
  const call = this.contract.populate("request_redeem", [
39580
- uint25619.bnToUint256(shares.toString()),
39902
+ uint25620.bnToUint256(shares.toString()),
39581
39903
  receiver.address,
39582
39904
  owner.address
39583
39905
  ]);
@@ -39587,7 +39909,7 @@ var UniversalStrategy = class _UniversalStrategy extends BaseStrategy {
39587
39909
  const shares = await this.contract.call("balanceOf", [user.address], { blockIdentifier });
39588
39910
  const assets = await this.contract.call(
39589
39911
  "convert_to_assets",
39590
- [uint25619.bnToUint256(shares)],
39912
+ [uint25620.bnToUint256(shares)],
39591
39913
  { blockIdentifier }
39592
39914
  );
39593
39915
  const amount = Web3Number.fromWei(
@@ -40521,19 +40843,19 @@ var UniversalStrategies = [
40521
40843
  ];
40522
40844
 
40523
40845
  // src/strategies/svk-strategy.ts
40524
- import { Contract as Contract15, num as num13, uint256 as uint25620 } from "starknet";
40846
+ import { Contract as Contract16, num as num13, uint256 as uint25621 } from "starknet";
40525
40847
  var SVKStrategy = class extends BaseStrategy {
40526
40848
  constructor(config, pricer, metadata) {
40527
40849
  super(config);
40528
40850
  this.pricer = pricer;
40529
40851
  this.metadata = metadata;
40530
40852
  this.address = metadata.address;
40531
- this.contract = new Contract15({
40853
+ this.contract = new Contract16({
40532
40854
  abi: universal_vault_abi_default,
40533
40855
  address: this.address.address,
40534
40856
  providerOrAccount: this.config.provider
40535
40857
  });
40536
- this.managerContract = new Contract15({
40858
+ this.managerContract = new Contract16({
40537
40859
  abi: vault_manager_abi_default,
40538
40860
  address: this.metadata.additionalInfo.manager.address,
40539
40861
  providerOrAccount: this.config.provider
@@ -40550,17 +40872,17 @@ var SVKStrategy = class extends BaseStrategy {
40550
40872
  amountInfo.tokenInfo.address.eq(this.asset().address),
40551
40873
  "Deposit token mismatch"
40552
40874
  );
40553
- const assetContract = new Contract15({
40875
+ const assetContract = new Contract16({
40554
40876
  abi: universal_vault_abi_default,
40555
40877
  address: this.asset().address.address,
40556
40878
  providerOrAccount: this.config.provider
40557
40879
  });
40558
40880
  const call1 = assetContract.populate("approve", [
40559
40881
  this.address.address,
40560
- uint25620.bnToUint256(amountInfo.amount.toWei())
40882
+ uint25621.bnToUint256(amountInfo.amount.toWei())
40561
40883
  ]);
40562
40884
  const call2 = this.contract.populate("deposit", [
40563
- uint25620.bnToUint256(amountInfo.amount.toWei()),
40885
+ uint25621.bnToUint256(amountInfo.amount.toWei()),
40564
40886
  receiver.address
40565
40887
  ]);
40566
40888
  return [call1, call2];
@@ -40570,9 +40892,9 @@ var SVKStrategy = class extends BaseStrategy {
40570
40892
  amountInfo.tokenInfo.address.eq(this.asset().address),
40571
40893
  "Withdraw token mismatch"
40572
40894
  );
40573
- const shares = await this.contract.call("convert_to_shares", [uint25620.bnToUint256(amountInfo.amount.toWei())]);
40895
+ const shares = await this.contract.call("convert_to_shares", [uint25621.bnToUint256(amountInfo.amount.toWei())]);
40574
40896
  const call = this.contract.populate("request_redeem", [
40575
- uint25620.bnToUint256(shares.toString()),
40897
+ uint25621.bnToUint256(shares.toString()),
40576
40898
  receiver.address,
40577
40899
  owner.address
40578
40900
  ]);
@@ -40763,7 +41085,7 @@ var SVKStrategy = class extends BaseStrategy {
40763
41085
  };
40764
41086
 
40765
41087
  // src/strategies/universal-lst-muliplier-strategy.tsx
40766
- import { Contract as Contract16, uint256 as uint25621 } from "starknet";
41088
+ import { Contract as Contract17, uint256 as uint25622 } from "starknet";
40767
41089
 
40768
41090
  // src/strategies/universal-adapters/adapter-optimizer.ts
40769
41091
  var AdapterOptimizer = class {
@@ -40916,15 +41238,15 @@ var UniversalLstMultiplierStrategy = class _UniversalLstMultiplierStrategy exten
40916
41238
  async getLSTExchangeRate() {
40917
41239
  const vesuAdapter1 = this.getVesuSameTokenAdapter();
40918
41240
  const lstTokenInfo = vesuAdapter1._vesuAdapter.config.collateral;
40919
- const lstABI = new Contract16({
41241
+ const lstABI = new Contract17({
40920
41242
  abi: erc4626_abi_default,
40921
41243
  address: lstTokenInfo.address.address,
40922
41244
  providerOrAccount: this.config.provider
40923
41245
  });
40924
41246
  const price = await lstABI.call("convert_to_assets", [
40925
- uint25621.bnToUint256(new Web3Number(1, lstTokenInfo.decimals).toWei())
41247
+ uint25622.bnToUint256(new Web3Number(1, lstTokenInfo.decimals).toWei())
40926
41248
  ]);
40927
- const exchangeRate = Number(uint25621.uint256ToBN(price).toString()) / Math.pow(10, lstTokenInfo.decimals);
41249
+ const exchangeRate = Number(uint25622.uint256ToBN(price).toString()) / Math.pow(10, lstTokenInfo.decimals);
40928
41250
  logger.verbose(`${this.getTag()}:: LST Exchange Rate: ${exchangeRate}`);
40929
41251
  return exchangeRate;
40930
41252
  }
@@ -41794,6 +42116,249 @@ var HyperLSTStrategies = [
41794
42116
  getStrategySettings("mRe7YIELD", "mRe7YIELD", hypermRe7YIELD, false, false)
41795
42117
  ];
41796
42118
 
42119
+ // src/strategies/vesu-extended-strategy/services/ltv-imbalance-rebalance-math.ts
42120
+ function ceilBtc(v, precision) {
42121
+ const f = 10 ** precision;
42122
+ return Math.ceil(v * f) / f;
42123
+ }
42124
+ function floorBtc(v, precision) {
42125
+ const f = 10 ** precision;
42126
+ return Math.floor(v * f) / f;
42127
+ }
42128
+ function isNegligible(btc, precision) {
42129
+ return Math.abs(btc) < 10 ** -precision;
42130
+ }
42131
+ function computeExtIdealMargin(posBtc, leverage, price) {
42132
+ return posBtc * price / leverage;
42133
+ }
42134
+ function computeExtDeficit(ext, price) {
42135
+ return Math.max(0, computeExtIdealMargin(ext.positionBtc, ext.leverage, price) - ext.equity);
42136
+ }
42137
+ function computeVesuHF(vesu, price) {
42138
+ const collateralUsd = vesu.positionBtc * price;
42139
+ if (collateralUsd === 0) return Infinity;
42140
+ const ltv = vesu.debt * vesu.debtPrice / collateralUsd;
42141
+ if (ltv === 0) return Infinity;
42142
+ return vesu.maxLTV / ltv;
42143
+ }
42144
+ function computeVesuDebtRepay(vesu, price) {
42145
+ const targetLTV = vesu.maxLTV / vesu.targetHF;
42146
+ const collateralUsd = vesu.positionBtc * price;
42147
+ const requiredDebt = targetLTV * collateralUsd / vesu.debtPrice;
42148
+ return Math.max(0, vesu.debt - requiredDebt);
42149
+ }
42150
+ function computeVesuTargetLTV(vesu) {
42151
+ return vesu.maxLTV / vesu.targetHF;
42152
+ }
42153
+ function computeVesuGrowthCost(gapBtc, vesu, price) {
42154
+ const targetLTV = computeVesuTargetLTV(vesu);
42155
+ return gapBtc * price * (1 - targetLTV);
42156
+ }
42157
+ function computeVesuGrowthDebt(gapBtc, vesu, price) {
42158
+ const targetLTV = computeVesuTargetLTV(vesu);
42159
+ return gapBtc * price * targetLTV / vesu.debtPrice;
42160
+ }
42161
+ function computeVesuGrowthFromEquity(equityUsd, vesu, price) {
42162
+ const targetLTV = computeVesuTargetLTV(vesu);
42163
+ return equityUsd / (price * (1 - targetLTV));
42164
+ }
42165
+ function emptyDeltas() {
42166
+ return {
42167
+ dExtPosition: 0,
42168
+ dVesuPosition: 0,
42169
+ dVesuDebt: 0,
42170
+ dExtAvlWithdraw: 0,
42171
+ dExtUpnl: 0,
42172
+ dVaUsd: 0,
42173
+ dWalletUsd: 0,
42174
+ dVesuBorrowCapacity: 0,
42175
+ dTransferVesuToExt: 0
42176
+ };
42177
+ }
42178
+ function mergeDeltas(a, b) {
42179
+ return {
42180
+ dExtPosition: a.dExtPosition + b.dExtPosition,
42181
+ dVesuPosition: a.dVesuPosition + b.dVesuPosition,
42182
+ dVesuDebt: a.dVesuDebt + b.dVesuDebt,
42183
+ dExtAvlWithdraw: a.dExtAvlWithdraw + b.dExtAvlWithdraw,
42184
+ dExtUpnl: a.dExtUpnl + b.dExtUpnl,
42185
+ dVaUsd: a.dVaUsd + b.dVaUsd,
42186
+ dWalletUsd: a.dWalletUsd + b.dWalletUsd,
42187
+ dVesuBorrowCapacity: a.dVesuBorrowCapacity + b.dVesuBorrowCapacity,
42188
+ dTransferVesuToExt: a.dTransferVesuToExt + b.dTransferVesuToExt
42189
+ };
42190
+ }
42191
+ function drawFunds(need, keys, pool) {
42192
+ const draws = {};
42193
+ let unmet = need;
42194
+ for (const k of keys) {
42195
+ if (unmet <= 0) break;
42196
+ const avail = pool[k];
42197
+ if (avail <= 0) continue;
42198
+ const take = Math.min(avail, unmet);
42199
+ draws[k] = take;
42200
+ pool[k] -= take;
42201
+ unmet -= take;
42202
+ }
42203
+ return { draws, filled: need - unmet, unmet };
42204
+ }
42205
+ function sumKeys(draws, keys) {
42206
+ return keys.reduce((s, k) => s + (draws[k] ?? 0), 0);
42207
+ }
42208
+ function applyDrawsToDeltas(d, draws, sign) {
42209
+ d.dVaUsd += sign * (draws.vaUsd ?? 0);
42210
+ d.dWalletUsd += sign * (draws.walletUsd ?? 0);
42211
+ d.dVesuBorrowCapacity += sign * (draws.vesuBorrowCapacity ?? 0);
42212
+ d.dExtAvlWithdraw += sign * (draws.extAvlWithdraw ?? 0);
42213
+ d.dExtUpnl += sign * (draws.extUpnl ?? 0);
42214
+ }
42215
+ function fixExtMargin(ext, price, pool) {
42216
+ const d = emptyDeltas();
42217
+ const deficit = computeExtDeficit(ext, price);
42218
+ if (deficit <= 0) return { d, unmet: 0 };
42219
+ const { draws, filled, unmet } = drawFunds(deficit, ["walletUsd", "vaUsd", "vesuBorrowCapacity"], pool);
42220
+ applyDrawsToDeltas(d, draws, -1);
42221
+ d.dExtAvlWithdraw += filled;
42222
+ const fromVesu = draws.vesuBorrowCapacity ?? 0;
42223
+ if (fromVesu > 0) d.dTransferVesuToExt += fromVesu;
42224
+ return { d, unmet };
42225
+ }
42226
+ function fixVesuMargin(vesu, price, pool, hfBuffer) {
42227
+ const d = emptyDeltas();
42228
+ const hf = computeVesuHF(vesu, price);
42229
+ if (hf >= vesu.targetHF - hfBuffer) return { d, unmet: 0 };
42230
+ const repayTokens = computeVesuDebtRepay(vesu, price);
42231
+ const repayUsd = repayTokens * vesu.debtPrice;
42232
+ const { draws, filled, unmet } = drawFunds(repayUsd, ["vaUsd", "walletUsd", "extAvlWithdraw", "extUpnl"], pool);
42233
+ applyDrawsToDeltas(d, draws, -1);
42234
+ d.dVesuDebt -= filled / vesu.debtPrice;
42235
+ const fromExt = sumKeys(draws, ["extAvlWithdraw", "extUpnl"]);
42236
+ if (fromExt > 0) d.dTransferVesuToExt -= fromExt;
42237
+ return { d, unmet };
42238
+ }
42239
+ function phase1(ext, vesu, price, pool, config) {
42240
+ const extResult = fixExtMargin(ext, price, pool);
42241
+ const vesuResult = fixVesuMargin(vesu, price, pool, config.hfBuffer);
42242
+ return {
42243
+ deltas: mergeDeltas(extResult.d, vesuResult.d),
42244
+ extDeficitRemaining: extResult.unmet,
42245
+ vesuRepayRemaining: vesuResult.unmet
42246
+ };
42247
+ }
42248
+ function solveUnifiedF(extPos, vesuPos, extEquity, vesuDebt, vesu, extLev, price) {
42249
+ const targetLTV = computeVesuTargetLTV(vesu);
42250
+ const k = 1 - targetLTV + 1 / extLev;
42251
+ const totalEquity = extEquity + vesuPos * price - vesuDebt * vesu.debtPrice;
42252
+ return totalEquity / (price * k);
42253
+ }
42254
+ function solveTransfer(vesuPos, vesuDebt, F, vesu, price) {
42255
+ const targetLTV = computeVesuTargetLTV(vesu);
42256
+ return vesuPos * price - vesuDebt * vesu.debtPrice - F * price * (1 - targetLTV);
42257
+ }
42258
+ function solveDebtRepay(vesuDebt, F, vesu, price) {
42259
+ const targetLTV = computeVesuTargetLTV(vesu);
42260
+ return vesuDebt - targetLTV * F * price / vesu.debtPrice;
42261
+ }
42262
+ function roundFinalPosition(extPos, vesuPos, rawF, precision) {
42263
+ let fFromExt;
42264
+ if (rawF < extPos) {
42265
+ const closeExt = ceilBtc(extPos - rawF, precision);
42266
+ fFromExt = extPos - closeExt;
42267
+ } else {
42268
+ const growExt = floorBtc(rawF - extPos, precision);
42269
+ fFromExt = extPos + growExt;
42270
+ }
42271
+ let fFromVesu;
42272
+ if (rawF < vesuPos) {
42273
+ const closeVesu = ceilBtc(vesuPos - rawF, precision);
42274
+ fFromVesu = vesuPos - closeVesu;
42275
+ } else {
42276
+ const growVesu = floorBtc(rawF - vesuPos, precision);
42277
+ fFromVesu = vesuPos + growVesu;
42278
+ }
42279
+ return Math.max(0, Math.min(fFromExt, fFromVesu));
42280
+ }
42281
+ function phase2(extPos, vesuPos, extEquity, vesuDebt, vesu, extLev, price, pool, precision) {
42282
+ const d = emptyDeltas();
42283
+ const targetLTV = computeVesuTargetLTV(vesu);
42284
+ const imbalance = extPos - vesuPos;
42285
+ let fundedGrowthBtc = 0;
42286
+ if (!isNegligible(imbalance, precision)) {
42287
+ if (imbalance > 0) {
42288
+ const equityCostUsd = computeVesuGrowthCost(imbalance, vesu, price);
42289
+ const { draws, filled } = drawFunds(equityCostUsd, ["vaUsd", "walletUsd", "extAvlWithdraw", "extUpnl"], pool);
42290
+ applyDrawsToDeltas(d, draws, -1);
42291
+ const grownBtc = computeVesuGrowthFromEquity(filled, vesu, price);
42292
+ d.dVesuPosition += grownBtc;
42293
+ fundedGrowthBtc = grownBtc;
42294
+ d.dVesuDebt += computeVesuGrowthDebt(grownBtc, vesu, price);
42295
+ const fromExt = sumKeys(draws, ["extAvlWithdraw", "extUpnl"]);
42296
+ if (fromExt > 0) d.dTransferVesuToExt -= fromExt;
42297
+ } else {
42298
+ const absImbalance = -imbalance;
42299
+ const marginCostUsd = absImbalance * price / extLev;
42300
+ const { draws, filled } = drawFunds(marginCostUsd, ["walletUsd", "vaUsd", "vesuBorrowCapacity"], pool);
42301
+ applyDrawsToDeltas(d, draws, -1);
42302
+ const grownBtc = filled * extLev / price;
42303
+ d.dExtPosition += grownBtc;
42304
+ fundedGrowthBtc = grownBtc;
42305
+ const fromVesu = draws.vesuBorrowCapacity ?? 0;
42306
+ if (fromVesu > 0) d.dTransferVesuToExt += fromVesu;
42307
+ }
42308
+ }
42309
+ const effExtPos = extPos + d.dExtPosition;
42310
+ const effVesuPos = vesuPos + d.dVesuPosition;
42311
+ const effExtEquity = extEquity;
42312
+ const effVesuDebt = vesuDebt + d.dVesuDebt;
42313
+ const rawF = solveUnifiedF(effExtPos, effVesuPos, effExtEquity, effVesuDebt, vesu, extLev, price);
42314
+ const cappedF = Math.min(rawF, Math.max(effExtPos, effVesuPos));
42315
+ const maxGrowableTo = Math.max(effExtPos, effVesuPos);
42316
+ const targetF = Math.max(0, Math.min(cappedF, maxGrowableTo));
42317
+ const remainingImbalance = effExtPos - effVesuPos;
42318
+ const extNeedsMore = effExtEquity < computeExtIdealMargin(effExtPos, extLev, price);
42319
+ const vesuCurrentLTV = effVesuPos > 0 ? effVesuDebt * vesu.debtPrice / (effVesuPos * price) : 0;
42320
+ const vesuNeedsMore = vesuCurrentLTV > targetLTV;
42321
+ const needsFurtherAction = !isNegligible(remainingImbalance, precision) || extNeedsMore || vesuNeedsMore;
42322
+ if (!needsFurtherAction) {
42323
+ return d;
42324
+ }
42325
+ const F = roundFinalPosition(effExtPos, effVesuPos, targetF, precision);
42326
+ const dx = F - effExtPos;
42327
+ const dy = F - effVesuPos;
42328
+ if (isNegligible(dx, precision) && isNegligible(dy, precision)) {
42329
+ return d;
42330
+ }
42331
+ d.dExtPosition += dx;
42332
+ d.dVesuPosition += dy;
42333
+ const debtRepay = solveDebtRepay(effVesuDebt, F, vesu, price);
42334
+ d.dVesuDebt -= debtRepay;
42335
+ const T = solveTransfer(effVesuPos, effVesuDebt, F, vesu, price);
42336
+ d.dTransferVesuToExt += T;
42337
+ d.dExtAvlWithdraw += T;
42338
+ return d;
42339
+ }
42340
+ function rebalance(inputs) {
42341
+ const { ext, vesu, btcPrice, config } = inputs;
42342
+ const pool = { ...inputs.funding };
42343
+ const p1 = phase1(ext, vesu, btcPrice, pool, config);
42344
+ const effExtPos = ext.positionBtc + p1.deltas.dExtPosition;
42345
+ const effVesuPos = vesu.positionBtc + p1.deltas.dVesuPosition;
42346
+ const effExtEquity = ext.equity + p1.deltas.dExtAvlWithdraw + p1.deltas.dExtUpnl;
42347
+ const effVesuDebt = vesu.debt + p1.deltas.dVesuDebt;
42348
+ const p2 = phase2(
42349
+ effExtPos,
42350
+ effVesuPos,
42351
+ effExtEquity,
42352
+ effVesuDebt,
42353
+ vesu,
42354
+ ext.leverage,
42355
+ btcPrice,
42356
+ pool,
42357
+ config.positionPrecision
42358
+ );
42359
+ return mergeDeltas(p1.deltas, p2);
42360
+ }
42361
+
41797
42362
  // src/strategies/vesu-extended-strategy/services/extended-vesu-state-manager.ts
41798
42363
  var RouteType = /* @__PURE__ */ ((RouteType2) => {
41799
42364
  RouteType2["WALLET_TO_EXTENDED"] = "WALLET_TO_EXTENDED";
@@ -41825,6 +42390,7 @@ var CaseCategory = /* @__PURE__ */ ((CaseCategory2) => {
41825
42390
  return CaseCategory2;
41826
42391
  })(CaseCategory || {});
41827
42392
  var CaseId = /* @__PURE__ */ ((CaseId2) => {
42393
+ CaseId2["MANAGE_LTV"] = "MANAGE_LTV";
41828
42394
  CaseId2["LTV_VESU_LOW_TO_EXTENDED"] = "LTV_VESU_LOW_TO_EXTENDED";
41829
42395
  CaseId2["LTV_EXTENDED_PROFITABLE_AVAILABLE"] = "LTV_EXTENDED_PROFITABLE_AVAILABLE";
41830
42396
  CaseId2["LTV_EXTENDED_PROFITABLE_REALIZE"] = "LTV_EXTENDED_PROFITABLE_REALIZE";
@@ -41849,19 +42415,56 @@ function safeUsdcWeb3Number(value) {
41849
42415
  return new Web3Number(value.toFixed(USDC_TOKEN_DECIMALS), USDC_TOKEN_DECIMALS);
41850
42416
  }
41851
42417
  var CASE_ROUTE_TYPES = {
41852
- // LTV Rebalance — Vesu side
42418
+ // LTV Rebalance — unified
42419
+ ["MANAGE_LTV" /* MANAGE_LTV */]: [
42420
+ "REALISE_PNL" /* REALISE_PNL */,
42421
+ "EXTENDED_TO_WALLET" /* EXTENDED_TO_WALLET */,
42422
+ "RETURN_TO_WAIT" /* RETURN_TO_WAIT */,
42423
+ "WALLET_TO_VA" /* WALLET_TO_VA */,
42424
+ "VESU_BORROW" /* VESU_BORROW */,
42425
+ "VESU_REPAY" /* VESU_REPAY */,
42426
+ "VA_TO_EXTENDED" /* VA_TO_EXTENDED */,
42427
+ "VESU_MULTIPLY_DECREASE_LEVER" /* VESU_MULTIPLY_DECREASE_LEVER */,
42428
+ "VESU_MULTIPLY_INCREASE_LEVER" /* VESU_MULTIPLY_INCREASE_LEVER */,
42429
+ "AVNU_DEPOSIT_SWAP" /* AVNU_DEPOSIT_SWAP */,
42430
+ "EXTENDED_DECREASE_LEVER" /* EXTENDED_DECREASE_LEVER */,
42431
+ "EXTENDED_INCREASE_LEVER" /* EXTENDED_INCREASE_LEVER */,
42432
+ // Second-phase VA / Extended funding after lever routes (same types may repeat).
42433
+ "RETURN_TO_WAIT" /* RETURN_TO_WAIT */,
42434
+ "WALLET_TO_VA" /* WALLET_TO_VA */,
42435
+ "VA_TO_EXTENDED" /* VA_TO_EXTENDED */
42436
+ ],
42437
+ /** @deprecated */
41853
42438
  ["LTV_VESU_HIGH_USE_VA_OR_WALLET" /* LTV_VESU_HIGH_USE_VA_OR_WALLET */]: ["WALLET_TO_VA" /* WALLET_TO_VA */, "VESU_REPAY" /* VESU_REPAY */],
41854
- // use wallet to va if wallet has and va doesnt have enough
42439
+ /** @deprecated */
41855
42440
  ["LTV_EXTENDED_PROFITABLE_AVAILABLE" /* LTV_EXTENDED_PROFITABLE_AVAILABLE */]: ["EXTENDED_TO_WALLET" /* EXTENDED_TO_WALLET */, "RETURN_TO_WAIT" /* RETURN_TO_WAIT */, "WALLET_TO_VA" /* WALLET_TO_VA */, "VESU_REPAY" /* VESU_REPAY */],
42441
+ /** @deprecated */
41856
42442
  ["LTV_EXTENDED_PROFITABLE_REALIZE" /* LTV_EXTENDED_PROFITABLE_REALIZE */]: ["REALISE_PNL" /* REALISE_PNL */, "EXTENDED_TO_WALLET" /* EXTENDED_TO_WALLET */, "RETURN_TO_WAIT" /* RETURN_TO_WAIT */, "WALLET_TO_VA" /* WALLET_TO_VA */, "VESU_REPAY" /* VESU_REPAY */],
41857
- // LTV Rebalance — Extended side
42443
+ /** @deprecated */
41858
42444
  ["LTV_EXTENDED_HIGH_USE_VA_OR_WALLET" /* LTV_EXTENDED_HIGH_USE_VA_OR_WALLET */]: ["VA_TO_EXTENDED" /* VA_TO_EXTENDED */, "WALLET_TO_EXTENDED" /* WALLET_TO_EXTENDED */],
42445
+ /** @deprecated */
41859
42446
  ["LTV_VESU_LOW_TO_EXTENDED" /* LTV_VESU_LOW_TO_EXTENDED */]: ["VESU_BORROW" /* VESU_BORROW */, "VA_TO_EXTENDED" /* VA_TO_EXTENDED */],
41860
42447
  // New Deposits
41861
42448
  // @dev, when handling routes, after VA_TO_EXTENDED and/or WALLET_TO_EXTENDED, return. bcz, funds take time to reach extended. Anyways, in next cycle, these funds will be computed to increase lever
41862
42449
  // Sequence: fund-movement transfers first (WALLET_TO_EXTENDED, VA_TO_EXTENDED, WALLET_TO_VA, EXTENDED_TO_WALLET),
41863
42450
  // then RETURN_TO_WAIT, then optional second WALLET_TO_VA + RETURN_TO_WAIT, then lever routes.
41864
- ["DEPOSIT_FRESH_VAULT" /* DEPOSIT_FRESH_VAULT */]: ["WALLET_TO_EXTENDED" /* WALLET_TO_EXTENDED */, "VA_TO_EXTENDED" /* VA_TO_EXTENDED */, "WALLET_TO_VA" /* WALLET_TO_VA */, "EXTENDED_TO_WALLET" /* EXTENDED_TO_WALLET */, "RETURN_TO_WAIT" /* RETURN_TO_WAIT */, "WALLET_TO_VA" /* WALLET_TO_VA */, "AVNU_DEPOSIT_SWAP" /* AVNU_DEPOSIT_SWAP */, "VESU_MULTIPLY_INCREASE_LEVER" /* VESU_MULTIPLY_INCREASE_LEVER */, "EXTENDED_INCREASE_LEVER" /* EXTENDED_INCREASE_LEVER */],
42451
+ ["DEPOSIT_FRESH_VAULT" /* DEPOSIT_FRESH_VAULT */]: [
42452
+ // May repeat after MANAGE_LTV (VA top-up → Extended → wait → levers).
42453
+ "WALLET_TO_VA" /* WALLET_TO_VA */,
42454
+ "VA_TO_EXTENDED" /* VA_TO_EXTENDED */,
42455
+ "RETURN_TO_WAIT" /* RETURN_TO_WAIT */,
42456
+ "VA_TO_EXTENDED" /* VA_TO_EXTENDED */,
42457
+ "VESU_BORROW" /* VESU_BORROW */,
42458
+ "WALLET_TO_EXTENDED" /* WALLET_TO_EXTENDED */,
42459
+ "VA_TO_EXTENDED" /* VA_TO_EXTENDED */,
42460
+ "WALLET_TO_VA" /* WALLET_TO_VA */,
42461
+ "EXTENDED_TO_WALLET" /* EXTENDED_TO_WALLET */,
42462
+ "RETURN_TO_WAIT" /* RETURN_TO_WAIT */,
42463
+ "WALLET_TO_VA" /* WALLET_TO_VA */,
42464
+ "AVNU_DEPOSIT_SWAP" /* AVNU_DEPOSIT_SWAP */,
42465
+ "VESU_MULTIPLY_INCREASE_LEVER" /* VESU_MULTIPLY_INCREASE_LEVER */,
42466
+ "EXTENDED_INCREASE_LEVER" /* EXTENDED_INCREASE_LEVER */
42467
+ ],
41865
42468
  ["DEPOSIT_EXTENDED_AVAILABLE" /* DEPOSIT_EXTENDED_AVAILABLE */]: ["EXTENDED_TO_WALLET" /* EXTENDED_TO_WALLET */, "RETURN_TO_WAIT" /* RETURN_TO_WAIT */, "WALLET_TO_VA" /* WALLET_TO_VA */, "AVNU_DEPOSIT_SWAP" /* AVNU_DEPOSIT_SWAP */, "VESU_MULTIPLY_INCREASE_LEVER" /* VESU_MULTIPLY_INCREASE_LEVER */, "EXTENDED_INCREASE_LEVER" /* EXTENDED_INCREASE_LEVER */],
41866
42469
  ["DEPOSIT_VESU_BORROW_CAPACITY" /* DEPOSIT_VESU_BORROW_CAPACITY */]: ["VESU_BORROW" /* VESU_BORROW */, "VA_TO_EXTENDED" /* VA_TO_EXTENDED */, "RETURN_TO_WAIT" /* RETURN_TO_WAIT */, "AVNU_DEPOSIT_SWAP" /* AVNU_DEPOSIT_SWAP */, "VESU_MULTIPLY_INCREASE_LEVER" /* VESU_MULTIPLY_INCREASE_LEVER */, "EXTENDED_INCREASE_LEVER" /* EXTENDED_INCREASE_LEVER */],
41867
42470
  ["DEPOSIT_COMBINATION" /* DEPOSIT_COMBINATION */]: [],
@@ -41884,6 +42487,17 @@ var CASE_ROUTE_TYPES = {
41884
42487
  ["IMBALANCE_VESU_EXCESS_LONG_NO_FUNDS" /* IMBALANCE_VESU_EXCESS_LONG_NO_FUNDS */]: ["VESU_MULTIPLY_DECREASE_LEVER" /* VESU_MULTIPLY_DECREASE_LEVER */]
41885
42488
  };
41886
42489
  var CASE_DEFINITIONS = {
42490
+ ["MANAGE_LTV" /* MANAGE_LTV */]: {
42491
+ id: "MANAGE_LTV" /* MANAGE_LTV */,
42492
+ category: "LTV_REBALANCE" /* LTV_REBALANCE */,
42493
+ title: "LTV Rebalance: Unified Vesu repay + Extended margin management",
42494
+ description: "Manages both Vesu high-LTV repayment and Extended low-margin funding in a single pass.",
42495
+ steps: [
42496
+ "Compute vesu repay needed and extended margin needed",
42497
+ "Allocate funds: VA > Wallet > ExtAvl > ExtUpnl for Vesu; Wallet > VA > Borrow for Extended",
42498
+ "Build combined transfer and repay/margin routes"
42499
+ ]
42500
+ },
41887
42501
  ["LTV_VESU_LOW_TO_EXTENDED" /* LTV_VESU_LOW_TO_EXTENDED */]: {
41888
42502
  id: "LTV_VESU_LOW_TO_EXTENDED" /* LTV_VESU_LOW_TO_EXTENDED */,
41889
42503
  category: "LTV_REBALANCE" /* LTV_REBALANCE */,
@@ -42106,199 +42720,590 @@ function routeSummary(r) {
42106
42720
  }
42107
42721
  var SolveBudget = class {
42108
42722
  constructor(state) {
42109
- // should be same length as vesuPerPoolDebtDeltasToBorrow
42110
- // ── Budget tracking (populated by initBudget) ──────────────────────
42111
- this._vaUsd = 0;
42112
- this._walletUsd = 0;
42113
- this._extAvailWithdraw = 0;
42114
- this._extAvailUpnl = 0;
42115
- this._extAvailTrade = 0;
42116
- this._extPendingDeposit = 0;
42117
- this._vesuBorrowCapacity = 0;
42118
- this._totalUnused = 0;
42119
- const buffer = state.limitBalanceBufferFactor;
42120
- this.unusedBalance = state.unusedBalance.map((item) => {
42121
- return {
42122
- ...item,
42123
- amount: item.amount.multipliedBy(1 - buffer),
42124
- usdValue: item.usdValue * (1 - buffer)
42125
- };
42723
+ this.assetToken = state.assetToken;
42724
+ this.usdcToken = state.usdcToken;
42725
+ const cloneTb = (b) => ({
42726
+ token: b.token,
42727
+ amount: new Web3Number(b.amount.toFixed(b.token.decimals), b.token.decimals),
42728
+ usdValue: b.usdValue
42126
42729
  });
42127
- this.walletBalance = state.walletBalance ? {
42128
- ...state.walletBalance,
42129
- amount: state.walletBalance.amount.multipliedBy(1 - buffer),
42130
- usdValue: state.walletBalance.usdValue * (1 - buffer)
42131
- } : null;
42132
- this.vaultBalance = state.vaultBalance ? {
42133
- ...state.vaultBalance,
42134
- amount: state.vaultBalance.amount.multipliedBy(1 - buffer),
42135
- usdValue: state.vaultBalance.usdValue * (1 - buffer)
42136
- } : null;
42137
- this.extendedPositions = state.extendedPositions;
42730
+ this.unusedBalance = state.unusedBalance.map((item) => cloneTb(item));
42731
+ this.walletBalance = state.walletBalance ? cloneTb(state.walletBalance) : null;
42732
+ this.vaultAssetBalance = state.vaultAssetBalance ? cloneTb(state.vaultAssetBalance) : null;
42733
+ this.vaultUsdcBalance = state.vaultUsdcBalance ? cloneTb(state.vaultUsdcBalance) : null;
42734
+ this.extendedPositions = state.extendedPositions.map((p) => ({
42735
+ ...p,
42736
+ size: new Web3Number(p.size.toFixed(8), 8),
42737
+ valueUsd: new Web3Number(p.valueUsd.toFixed(USDC_TOKEN_DECIMALS), USDC_TOKEN_DECIMALS)
42738
+ }));
42138
42739
  this.extendedBalance = state.extendedBalance ? {
42139
- ...state.extendedBalance,
42140
- availableForTrade: state.extendedBalance.availableForTrade.multipliedBy(1 - buffer),
42141
- availableForWithdrawal: state.extendedBalance.availableForWithdrawal.multipliedBy(1 - buffer),
42142
- unrealisedPnl: state.extendedBalance.unrealisedPnl.multipliedBy(1 - buffer),
42143
- balance: state.extendedBalance.balance.multipliedBy(1 - buffer)
42740
+ equity: new Web3Number(
42741
+ state.extendedBalance.equity.toFixed(USDC_TOKEN_DECIMALS),
42742
+ USDC_TOKEN_DECIMALS
42743
+ ),
42744
+ availableForTrade: new Web3Number(
42745
+ state.extendedBalance.availableForTrade.toFixed(USDC_TOKEN_DECIMALS),
42746
+ USDC_TOKEN_DECIMALS
42747
+ ),
42748
+ availableForWithdrawal: new Web3Number(
42749
+ state.extendedBalance.availableForWithdrawal.toFixed(USDC_TOKEN_DECIMALS),
42750
+ USDC_TOKEN_DECIMALS
42751
+ ),
42752
+ unrealisedPnl: new Web3Number(
42753
+ state.extendedBalance.unrealisedPnl.toFixed(USDC_TOKEN_DECIMALS),
42754
+ USDC_TOKEN_DECIMALS
42755
+ ),
42756
+ balance: new Web3Number(
42757
+ state.extendedBalance.balance.toFixed(USDC_TOKEN_DECIMALS),
42758
+ USDC_TOKEN_DECIMALS
42759
+ ),
42760
+ pendingDeposit: new Web3Number(
42761
+ state.extendedBalance.pendingDeposit.toFixed(USDC_TOKEN_DECIMALS),
42762
+ USDC_TOKEN_DECIMALS
42763
+ )
42144
42764
  } : null;
42145
- this.vesuPoolStates = state.vesuPoolStates;
42765
+ this.vesuPoolStates = state.vesuPoolStates.map((p) => ({
42766
+ ...p,
42767
+ collateralAmount: new Web3Number(
42768
+ p.collateralAmount.toFixed(p.collateralToken.decimals),
42769
+ p.collateralToken.decimals
42770
+ ),
42771
+ debtAmount: new Web3Number(
42772
+ p.debtAmount.toFixed(p.debtToken.decimals),
42773
+ p.debtToken.decimals
42774
+ )
42775
+ }));
42146
42776
  const vesuPerPoolDebtDeltasToBorrow = this._computeperPoolDebtDeltasToBorrow();
42147
42777
  assert(vesuPerPoolDebtDeltasToBorrow.length === this.vesuPoolStates.length, "vesuPerPoolDebtDeltasToBorrow length must match vesuPoolStates length");
42148
42778
  this.vesuPerPoolDebtDeltasToBorrow = vesuPerPoolDebtDeltasToBorrow.map((item) => item.deltaDebt);
42149
42779
  this.shouldVesuRebalance = vesuPerPoolDebtDeltasToBorrow.map((item) => item.shouldRebalance);
42150
42780
  }
42781
+ /** `1 - limitBalanceBufferFactor` — multiplier applied to raw notionals for “usable” USD. */
42782
+ _usableFraction() {
42783
+ return 1;
42784
+ }
42151
42785
  /**
42152
- * Initialise budget-tracking values from the current state snapshot.
42153
- * Must be called after state is populated and debt deltas are computed.
42154
- *
42155
- * Accounts for pendingDeposit:
42156
- * - pendingDeposit > 0: funds in transit TO Extended → increase effective Extended available-for-trade
42157
- * - pendingDeposit < 0: funds in transit FROM Extended → increase effective wallet balance
42786
+ * Raw USD notional for a token row. USDC (and configured {@link usdcToken}) uses 1:1 from amount;
42787
+ * non-stable assets (e.g. WBTC in VA) use {@link TokenBalance.usdValue} from the pricer at refresh.
42788
+ */
42789
+ _rawTokenUsd(tb) {
42790
+ if (!tb) return 0;
42791
+ if (this.usdcToken.address.eq(tb.token.address)) {
42792
+ return Number(tb.amount.toFixed(tb.token.decimals));
42793
+ }
42794
+ return tb.usdValue;
42795
+ }
42796
+ /** Apply safety buffer to a raw USD scalar. */
42797
+ bufferedUsd(rawUsd) {
42798
+ return rawUsd * this._usableFraction();
42799
+ }
42800
+ /** Convert a buffered “usable” USD amount to raw nominal USD (inverse of {@link bufferedUsd}). */
42801
+ rawUsdFromBuffered(bufferedUsd) {
42802
+ const bf = this._usableFraction();
42803
+ assert(bf > 0, "SolveBudget::rawUsdFromBuffered usable fraction must be positive");
42804
+ return bufferedUsd / bf;
42805
+ }
42806
+ /** Buffered USD notional for one token balance row. */
42807
+ bufferedTokenUsd(tb) {
42808
+ return this.bufferedUsd(this._rawTokenUsd(tb));
42809
+ }
42810
+ logStateSummary() {
42811
+ console.log("===== state summary =====");
42812
+ const aggregatedData = {
42813
+ unusedBalances: this.unusedBalance.map((b) => ({
42814
+ token: b.token.symbol,
42815
+ amount: b.amount.toNumber()
42816
+ })),
42817
+ walletBalance: this.walletBalance ? {
42818
+ token: this.walletBalance.token.symbol,
42819
+ amount: this.walletBalance.amount.toNumber()
42820
+ } : void 0,
42821
+ vaultAssetBalance: this.vaultAssetBalance ? {
42822
+ token: this.vaultAssetBalance.token.symbol,
42823
+ amount: this.vaultAssetBalance.amount.toNumber()
42824
+ } : void 0,
42825
+ vaultUsdcBalance: this.vaultUsdcBalance ? {
42826
+ token: this.vaultUsdcBalance.token.symbol,
42827
+ amount: this.vaultUsdcBalance.amount.toNumber()
42828
+ } : void 0,
42829
+ vesuPoolStates: this.vesuPoolStates.map((p) => ({
42830
+ poolId: p.poolId,
42831
+ collateralAmount: p.collateralAmount.toNumber(),
42832
+ debtAmount: p.debtAmount.toNumber()
42833
+ })),
42834
+ vesuBorrowCapacity: this.vesuBorrowCapacity,
42835
+ vesuRebalance: this.shouldVesuRebalance,
42836
+ vesuPerPoolDebtDeltasToBorrow: this.vesuPerPoolDebtDeltasToBorrow.map((d) => d.toNumber()),
42837
+ extendedBalance: this.extendedBalance?.balance.toNumber(),
42838
+ extendedEquity: this.extendedBalance?.equity.toNumber(),
42839
+ extendedAvailableForTrade: this.extendedBalance?.availableForTrade.toNumber(),
42840
+ extendedAvailableForWithdrawal: this.extendedBalance?.availableForWithdrawal.toNumber(),
42841
+ extendedUnrealisedPnl: this.extendedBalance?.unrealisedPnl.toNumber(),
42842
+ extendedPendingDeposit: this.extendedBalance?.pendingDeposit.toNumber(),
42843
+ extendedPositions: this.extendedPositions.map((p) => ({
42844
+ instrument: p.instrument,
42845
+ size: p.size.toNumber(),
42846
+ valueUsd: p.valueUsd.toNumber()
42847
+ }))
42848
+ };
42849
+ console.log(
42850
+ "unused balances",
42851
+ aggregatedData.unusedBalances.map((b) => `${b.token}=${b.amount}`).join(", ")
42852
+ );
42853
+ console.log(
42854
+ "wallet balance",
42855
+ aggregatedData.walletBalance ? `${aggregatedData.walletBalance.token}=${aggregatedData.walletBalance.amount}` : void 0
42856
+ );
42857
+ console.log(
42858
+ "vault asset balance",
42859
+ aggregatedData.vaultAssetBalance ? `${aggregatedData.vaultAssetBalance.token}=${aggregatedData.vaultAssetBalance.amount}` : void 0
42860
+ );
42861
+ console.log(
42862
+ "vault usdc balance",
42863
+ aggregatedData.vaultUsdcBalance ? `${aggregatedData.vaultUsdcBalance.token}=${aggregatedData.vaultUsdcBalance.amount}` : void 0
42864
+ );
42865
+ console.log(
42866
+ "vesu pool states",
42867
+ aggregatedData.vesuPoolStates.map(
42868
+ (p) => `${p.poolId.shortString()}=${p.collateralAmount} ${p.debtAmount}`
42869
+ ).join(", ")
42870
+ );
42871
+ console.log("vesu borrow capacity", aggregatedData.vesuBorrowCapacity);
42872
+ console.log(
42873
+ "vesu rebalance",
42874
+ aggregatedData.vesuRebalance.map(String).join(", ")
42875
+ );
42876
+ console.log("vesu per pool debt deltas to borrow", aggregatedData.vesuPerPoolDebtDeltasToBorrow.join(", "));
42877
+ console.log("extended balance", aggregatedData.extendedBalance);
42878
+ console.log("extended equity", aggregatedData.extendedEquity);
42879
+ console.log("extended available for trade", aggregatedData.extendedAvailableForTrade);
42880
+ console.log("extended available for withdrawal", aggregatedData.extendedAvailableForWithdrawal);
42881
+ console.log("extended unrealised pnl", aggregatedData.extendedUnrealisedPnl);
42882
+ console.log("extended pending deposit", aggregatedData.extendedPendingDeposit);
42883
+ console.log(
42884
+ "extended positions",
42885
+ aggregatedData.extendedPositions.map(
42886
+ (p) => `${p.instrument}=${p.size} ${p.valueUsd}`
42887
+ ).join(", ")
42888
+ );
42889
+ return aggregatedData;
42890
+ }
42891
+ /**
42892
+ * Initialise derived views for a solve cycle. Mutates only when pending
42893
+ * withdrawal from Extended is in transit (credits wallet raw balance).
42158
42894
  */
42159
42895
  initBudget() {
42160
- const debtDeltaNum = this.vesuPerPoolDebtDeltasToBorrow.reduce((a, b) => a + b.toNumber(), 0);
42161
- let totalUnusedUsd = this.unusedBalance.reduce((a, b) => a + b.usdValue, 0);
42162
- if (debtDeltaNum > 0) totalUnusedUsd += debtDeltaNum;
42163
- const extAvailTrade = this.extendedBalance?.availableForTrade?.toNumber() ?? 0;
42164
- if (extAvailTrade > 0) totalUnusedUsd += extAvailTrade;
42165
- if (debtDeltaNum > 0) totalUnusedUsd += debtDeltaNum;
42166
- this._vaUsd = this.vaultBalance?.usdValue ?? 0;
42167
- this._walletUsd = this.walletBalance?.usdValue ?? 0;
42168
- this._extAvailWithdraw = this.extendedBalance?.availableForWithdrawal?.toNumber() ?? 0;
42169
- this._extAvailUpnl = this.extendedBalance?.unrealisedPnl?.toNumber() ?? 0;
42170
- this._extAvailTrade = extAvailTrade;
42171
- this._extPendingDeposit = this.extendedBalance?.pendingDeposit?.toNumber() ?? 0;
42172
- this._vesuBorrowCapacity = debtDeltaNum;
42173
- this._totalUnused = totalUnusedUsd;
42174
42896
  const pendingDeposit = this.extendedBalance?.pendingDeposit?.toNumber() ?? 0;
42175
- if (pendingDeposit > 0) {
42176
- this._extAvailTrade += pendingDeposit;
42177
- this._totalUnused += pendingDeposit;
42178
- logger.debug(`SolveBudget::initBudget pendingDeposit=${pendingDeposit} -> increased extAvailTrade`);
42179
- } else if (pendingDeposit < 0) {
42897
+ if (pendingDeposit < 0) {
42180
42898
  const inTransit = Math.abs(pendingDeposit);
42181
- this._walletUsd += inTransit;
42182
- this._totalUnused += inTransit;
42183
- logger.debug(`SolveBudget::initBudget pendingDeposit=${pendingDeposit} -> increased walletUsd by ${inTransit}`);
42899
+ if (this.walletBalance) {
42900
+ this._addUsdToTokenBalance(this.walletBalance, inTransit);
42901
+ }
42902
+ logger.debug(`SolveBudget::initBudget pendingDeposit=${pendingDeposit} -> increased wallet raw USD by ${inTransit}`);
42903
+ }
42904
+ this._recomputeUnusedBalance();
42905
+ }
42906
+ /**
42907
+ * Apply a safety buffer to all liquid balances (VA, wallet, extended trade/withdraw/upnl,
42908
+ * unused balances). Call after withdrawal classification but before LTV/deposit classifiers
42909
+ * so that withdrawal uses full raw amounts while subsequent classifiers see buffered values.
42910
+ */
42911
+ applyBuffer(factor) {
42912
+ if (factor <= 0 || factor >= 1) return;
42913
+ const mult = 1 - factor;
42914
+ const scaleTokenBalance = (tb) => {
42915
+ if (!tb) return;
42916
+ const newAmount = tb.amount.multipliedBy(mult);
42917
+ tb.amount = new Web3Number(newAmount.toFixed(tb.token.decimals), tb.token.decimals);
42918
+ tb.usdValue = tb.usdValue * mult;
42919
+ };
42920
+ scaleTokenBalance(this.vaultAssetBalance);
42921
+ scaleTokenBalance(this.vaultUsdcBalance);
42922
+ scaleTokenBalance(this.walletBalance);
42923
+ for (const ub of this.unusedBalance) {
42924
+ scaleTokenBalance(ub);
42925
+ }
42926
+ if (this.extendedBalance) {
42927
+ this.extendedBalance.availableForTrade = this.extendedBalance.availableForTrade.multipliedBy(mult);
42928
+ this.extendedBalance.availableForWithdrawal = this.extendedBalance.availableForWithdrawal.multipliedBy(mult);
42929
+ this.extendedBalance.unrealisedPnl = this.extendedBalance.unrealisedPnl.multipliedBy(mult);
42184
42930
  }
42931
+ this._recomputeUnusedBalance();
42185
42932
  }
42186
- // ── Read-only getters ────────────────────────────────────────────────
42933
+ get vesuPoolState() {
42934
+ assert(this.vesuPoolStates.length === 1, "SolveBudget::vesuPoolState: vesuPoolStates length must be 1");
42935
+ return this.vesuPoolStates[0];
42936
+ }
42937
+ // ── Derived getters (buffered where applicable) ─────────────────────
42938
+ /** Buffered VA USD: strategy-asset slot + optional USDC slot. */
42187
42939
  get vaUsd() {
42188
- return this._vaUsd;
42940
+ return this.bufferedTokenUsd(this.vaultAssetBalance) + this.bufferedTokenUsd(this.vaultUsdcBalance);
42941
+ }
42942
+ /** Buffered USD in VA strategy-asset bucket only. */
42943
+ get vaAssetUsd() {
42944
+ return this.bufferedTokenUsd(this.vaultAssetBalance);
42945
+ }
42946
+ /** Buffered USD in VA USDC bucket (0 when asset === USDC). */
42947
+ get vaUsdcUsd() {
42948
+ return this.bufferedTokenUsd(this.vaultUsdcBalance);
42189
42949
  }
42190
42950
  get walletUsd() {
42191
- return this._walletUsd;
42951
+ return this.bufferedUsd(this._rawTokenUsd(this.walletBalance));
42192
42952
  }
42193
42953
  get vaWalletUsd() {
42194
- return this._vaUsd + this._walletUsd;
42954
+ return this.vaUsd + this.walletUsd;
42195
42955
  }
42196
42956
  get extAvailWithdraw() {
42197
- return this._extAvailWithdraw;
42957
+ const raw = this.extendedBalance?.availableForWithdrawal?.toNumber() ?? 0;
42958
+ return this.bufferedUsd(raw);
42198
42959
  }
42199
42960
  get extAvailUpnl() {
42200
- return this._extAvailUpnl;
42961
+ const raw = this.extendedBalance?.unrealisedPnl?.toNumber() ?? 0;
42962
+ return this.bufferedUsd(raw);
42201
42963
  }
42964
+ /**
42965
+ * Buffered Extended available-for-trade plus positive {@link ExtendedBalanceState.pendingDeposit}
42966
+ * (deposit in transit is usable the same way as the pre-buffer implementation).
42967
+ */
42202
42968
  get extAvailTrade() {
42203
- return this._extAvailTrade;
42969
+ const raw = this.extendedBalance?.availableForTrade?.toNumber() ?? 0;
42970
+ let v = this.bufferedUsd(raw);
42971
+ const pd = this.extendedBalance?.pendingDeposit?.toNumber() ?? 0;
42972
+ if (pd > 0) v += pd;
42973
+ return v;
42974
+ }
42975
+ get extPendingDeposit() {
42976
+ return this.extendedBalance?.pendingDeposit?.toNumber() ?? 0;
42204
42977
  }
42978
+ /**
42979
+ * Aggregate positive per-pool borrow headroom (USD). Repay/borrow routes update
42980
+ * {@link vesuPerPoolDebtDeltasToBorrow}; no separate counter.
42981
+ */
42205
42982
  get vesuBorrowCapacity() {
42206
- return this._vesuBorrowCapacity;
42983
+ return this.vesuPerPoolDebtDeltasToBorrow.reduce(
42984
+ (a, d) => a + Math.max(0, d.toNumber()),
42985
+ 0
42986
+ );
42207
42987
  }
42988
+ /** Diagnostic: buffered idle + positive debt delta + buffered Extended afT + in-flight deposit. */
42208
42989
  get totalUnused() {
42209
- return this._totalUnused;
42990
+ const debtSum = this.vesuPerPoolDebtDeltasToBorrow.reduce((a, b) => a + b.toNumber(), 0);
42991
+ let u = this.unusedBalance.reduce((a, b) => a + this.bufferedTokenUsd(b), 0);
42992
+ if (debtSum > 0) u += debtSum;
42993
+ const rawAft = this.extendedBalance?.availableForTrade?.toNumber() ?? 0;
42994
+ const aftBuf = this.bufferedUsd(rawAft);
42995
+ if (aftBuf > 0) u += aftBuf;
42996
+ const pd = this.extendedBalance?.pendingDeposit?.toNumber() ?? 0;
42997
+ if (pd > 0) u += pd;
42998
+ return u;
42999
+ }
43000
+ /** Sum of buffered USD across merged unused-balance rows (VA + wallet). */
43001
+ get unusedBalancesBufferedUsdSum() {
43002
+ return this.unusedBalance.reduce((a, b) => a + this.bufferedTokenUsd(b), 0);
43003
+ }
43004
+ /** Read-only snapshot view for validation / logging. */
43005
+ get unusedBalanceRows() {
43006
+ return this.unusedBalance;
43007
+ }
43008
+ /** Read-only Vesu pool view for solve computations. */
43009
+ get vesuPools() {
43010
+ return this.vesuPoolStates;
43011
+ }
43012
+ /** Read-only Extended positions view for solve computations. */
43013
+ get extendedPositionsView() {
43014
+ return this.extendedPositions;
43015
+ }
43016
+ /** Read-only Extended balance view for diagnostics / margin checks. */
43017
+ get extendedBalanceView() {
43018
+ return this.extendedBalance;
43019
+ }
43020
+ /** Current debt deltas per pool (positive=borrow, negative=repay). */
43021
+ get vesuDebtDeltas() {
43022
+ return this.vesuPerPoolDebtDeltasToBorrow;
43023
+ }
43024
+ /** Per-pool rebalance flags derived from target HF checks. */
43025
+ get vesuRebalanceFlags() {
43026
+ return this.shouldVesuRebalance;
43027
+ }
43028
+ /** Raw USD in VA (USDC slot + asset slot); spend caps when executing transfers. */
43029
+ _vaRawUsd() {
43030
+ return this._rawTokenUsd(this.vaultUsdcBalance) + this._rawTokenUsd(this.vaultAssetBalance);
43031
+ }
43032
+ _walletRawUsd() {
43033
+ return this._rawTokenUsd(this.walletBalance);
43034
+ }
43035
+ // ── Token snapshot helpers (keep vault / wallet / unusedBalance aligned) ─
43036
+ /** Remove up to `usd` notional from a token balance, scaling token amount proportionally. */
43037
+ _deductUsdFromTokenBalance(tb, usd) {
43038
+ if (usd <= 0) return;
43039
+ const take = Math.min(usd, tb.usdValue);
43040
+ if (take <= 0) return;
43041
+ const oldUsd = tb.usdValue;
43042
+ const newUsd = Math.max(0, oldUsd - take);
43043
+ tb.usdValue = newUsd;
43044
+ if (oldUsd <= 0) return;
43045
+ const ratio = newUsd / oldUsd;
43046
+ tb.amount = new Web3Number(
43047
+ (tb.amount.toNumber() * ratio).toFixed(tb.token.decimals),
43048
+ tb.token.decimals
43049
+ );
43050
+ }
43051
+ /** Add USD notional; infers price from current amount/usd when possible, else 1:1. */
43052
+ _addUsdToTokenBalance(tb, usd) {
43053
+ if (usd <= 0) return;
43054
+ const amtNum = tb.amount.toNumber();
43055
+ const price = amtNum > 0 && tb.usdValue > 0 ? tb.usdValue / amtNum : 1;
43056
+ const deltaTok = usd / price;
43057
+ tb.usdValue += usd;
43058
+ tb.amount = tb.amount.plus(
43059
+ new Web3Number(deltaTok.toFixed(tb.token.decimals), tb.token.decimals)
43060
+ );
42210
43061
  }
42211
- get extPendingDeposit() {
42212
- return this._extPendingDeposit;
43062
+ /**
43063
+ * Rebuilds {@link unusedBalance} from vault + wallet snapshots (same merge as refresh).
43064
+ */
43065
+ _recomputeUnusedBalance() {
43066
+ const balanceMap = /* @__PURE__ */ new Map();
43067
+ const merge = (b) => {
43068
+ if (!b) return;
43069
+ const key = b.token.address.toString();
43070
+ const row = {
43071
+ token: b.token,
43072
+ amount: new Web3Number(b.amount.toFixed(b.token.decimals), b.token.decimals),
43073
+ usdValue: b.usdValue
43074
+ };
43075
+ const existing = balanceMap.get(key);
43076
+ if (existing) {
43077
+ existing.amount = new Web3Number(
43078
+ existing.amount.plus(row.amount).toFixed(existing.token.decimals),
43079
+ existing.token.decimals
43080
+ );
43081
+ existing.usdValue += row.usdValue;
43082
+ } else {
43083
+ balanceMap.set(key, row);
43084
+ }
43085
+ };
43086
+ merge(this.vaultAssetBalance);
43087
+ merge(this.vaultUsdcBalance);
43088
+ merge(this.walletBalance);
43089
+ this.unusedBalance = Array.from(balanceMap.values());
42213
43090
  }
42214
43091
  // ── Spend methods (return amount consumed, auto-decrement totalUnused) ─
42215
- spendVA(desired) {
42216
- const used = Math.min(this._vaUsd, Math.max(0, desired));
42217
- this._vaUsd -= used;
42218
- this._totalUnused -= used;
42219
- logger.debug(`SolveBudget::spendVA used=${used}, vaUsd=${this._vaUsd}, totalUnused=${this._totalUnused}`);
42220
- return used;
42221
- }
42222
- addToVA(amount) {
42223
- assert(amount >= 0, "SolveBudget::addToVA amount must be positive");
42224
- this._vaUsd += amount;
42225
- this._totalUnused += amount;
42226
- logger.debug(`SolveBudget::addToVA amount=${amount}, vaUsd=${this._vaUsd}, totalUnused=${this._totalUnused}`);
42227
- }
42228
- spendWallet(desired) {
42229
- const used = Math.min(this._walletUsd, Math.max(0, desired));
42230
- this._walletUsd -= used;
42231
- this._totalUnused -= used;
42232
- logger.debug(`SolveBudget::spendWallet used=${used}, walletUsd=${this._walletUsd}, totalUnused=${this._totalUnused}`);
42233
- return used;
43092
+ /**
43093
+ * Spend VA **raw** USD (up to {@link vaRawUsd}). Prefer {@link vaultUsdcBalance} when present, then {@link vaultAssetBalance}.
43094
+ */
43095
+ spendVA(rawDesired) {
43096
+ const capRaw = this._vaRawUsd();
43097
+ const usedRaw = Math.min(capRaw, Math.max(0, rawDesired));
43098
+ if (usedRaw <= CASE_THRESHOLD_USD) return 0;
43099
+ let rem = usedRaw;
43100
+ if (rem > 0 && this.vaultUsdcBalance && this.vaultUsdcBalance.usdValue > 0) {
43101
+ const fromUsdc = Math.min(rem, this.vaultUsdcBalance.usdValue);
43102
+ this._deductUsdFromTokenBalance(this.vaultUsdcBalance, fromUsdc);
43103
+ rem -= fromUsdc;
43104
+ }
43105
+ if (rem > 0 && this.vaultAssetBalance) {
43106
+ this._deductUsdFromTokenBalance(this.vaultAssetBalance, rem);
43107
+ }
43108
+ this._recomputeUnusedBalance();
43109
+ logger.debug(`SolveBudget::spendVA usedRaw=${usedRaw}, vaUsd=${this.vaUsd}, totalUnused=${this.totalUnused}`);
43110
+ return usedRaw;
42234
43111
  }
42235
- addToWallet(amount) {
42236
- assert(amount >= 0, "SolveBudget::addToWallet amount must be positive");
42237
- this._walletUsd += amount;
42238
- this._totalUnused += amount;
42239
- logger.debug(`SolveBudget::addToWallet amount=${amount}, walletUsd=${this._walletUsd}, totalUnused=${this._totalUnused}`);
43112
+ /**
43113
+ * Spend nominal/raw USD from VA (e.g. Vesu repay, on-chain USDC). Does not apply the safety buffer to the cap.
43114
+ */
43115
+ spendVaRawUsd(rawUsdDesired) {
43116
+ const capRaw = this._rawTokenUsd(this.vaultUsdcBalance) + this._rawTokenUsd(this.vaultAssetBalance);
43117
+ const usedRaw = Math.min(capRaw, Math.max(0, rawUsdDesired));
43118
+ if (usedRaw <= CASE_THRESHOLD_USD) return 0;
43119
+ let rem = usedRaw;
43120
+ if (rem > 0 && this.vaultUsdcBalance && this.vaultUsdcBalance.usdValue > 0) {
43121
+ const fromUsdc = Math.min(rem, this.vaultUsdcBalance.usdValue);
43122
+ this._deductUsdFromTokenBalance(this.vaultUsdcBalance, fromUsdc);
43123
+ rem -= fromUsdc;
43124
+ }
43125
+ if (rem > 0 && this.vaultAssetBalance) {
43126
+ this._deductUsdFromTokenBalance(this.vaultAssetBalance, rem);
43127
+ }
43128
+ this._recomputeUnusedBalance();
43129
+ logger.debug(`SolveBudget::spendVaRawUsd usedRaw=${usedRaw}, vaUsd=${this.vaUsd}, totalUnused=${this.totalUnused}`);
43130
+ return usedRaw;
42240
43131
  }
42241
- spendVAWallet(desired) {
42242
- let remaining = Math.max(0, desired);
43132
+ /**
43133
+ * Add **raw nominal USD** to VA (borrow proceeds, wallet→VA in raw USDC, etc.).
43134
+ */
43135
+ addToVA(rawUsd) {
43136
+ assert(rawUsd >= 0, "SolveBudget::addToVA amount must be positive");
43137
+ if (rawUsd === 0) return;
43138
+ if (this.vaultUsdcBalance) {
43139
+ this._addUsdToTokenBalance(this.vaultUsdcBalance, rawUsd);
43140
+ } else if (this.vaultAssetBalance) {
43141
+ this._addUsdToTokenBalance(this.vaultAssetBalance, rawUsd);
43142
+ }
43143
+ this._recomputeUnusedBalance();
43144
+ logger.debug(`SolveBudget::addToVA rawUsd=${rawUsd}, vaUsd=${this.vaUsd}, totalUnused=${this.totalUnused}`);
43145
+ }
43146
+ spendWallet(rawDesired) {
43147
+ const capRaw = this._walletRawUsd();
43148
+ const usedRaw = Math.min(capRaw, Math.max(0, rawDesired));
43149
+ if (usedRaw <= CASE_THRESHOLD_USD) return 0;
43150
+ if (this.walletBalance) {
43151
+ this._deductUsdFromTokenBalance(this.walletBalance, usedRaw);
43152
+ }
43153
+ this._recomputeUnusedBalance();
43154
+ logger.debug(`SolveBudget::spendWallet usedRaw=${usedRaw}, walletUsd=${this.walletUsd}, totalUnused=${this.totalUnused}`);
43155
+ return usedRaw;
43156
+ }
43157
+ /** Add **raw nominal USD** to the operator wallet balance (e.g. Extended→wallet withdrawal). */
43158
+ addToWallet(rawUsd) {
43159
+ assert(rawUsd >= 0, "SolveBudget::addToWallet amount must be positive");
43160
+ if (rawUsd === 0) return;
43161
+ if (this.walletBalance) {
43162
+ this._addUsdToTokenBalance(this.walletBalance, rawUsd);
43163
+ }
43164
+ this._recomputeUnusedBalance();
43165
+ logger.debug(`SolveBudget::addToWallet rawUsd=${rawUsd}, walletUsd=${this.walletUsd}, totalUnused=${this.totalUnused}`);
43166
+ }
43167
+ spendVAWallet(rawDesired) {
43168
+ let remaining = Math.max(0, rawDesired);
42243
43169
  const vaSpent = this.spendVA(remaining);
42244
43170
  remaining -= vaSpent;
42245
43171
  const walletSpent = this.spendWallet(remaining);
42246
43172
  return vaSpent + walletSpent;
42247
43173
  }
42248
- _updateExtAvailWithdraw(desired, isSpend) {
42249
- let amount = desired;
42250
- assert(amount > 0, "SolveBudget::_updateExtAvailWithdraw amount must be positive");
43174
+ _updateExtAvailWithdraw(desiredRaw, isSpend) {
43175
+ assert(desiredRaw > 0, "SolveBudget::_updateExtAvailWithdraw amount must be positive");
43176
+ let rawDelta;
42251
43177
  if (isSpend) {
42252
- amount = Math.min(this._extAvailWithdraw, Math.max(0, desired));
42253
- amount = -amount;
43178
+ const capRaw = this.extendedBalance?.availableForWithdrawal?.toNumber() ?? 0;
43179
+ const useRaw = Math.min(capRaw, desiredRaw);
43180
+ if (useRaw <= CASE_THRESHOLD_USD) return 0;
43181
+ rawDelta = -useRaw;
43182
+ } else {
43183
+ rawDelta = desiredRaw;
42254
43184
  }
42255
- this._extAvailWithdraw += amount;
42256
- this._totalUnused += amount;
42257
- this._extAvailTrade += amount;
42258
43185
  if (this.extendedBalance) {
42259
- this.extendedBalance.availableForWithdrawal = safeUsdcWeb3Number(this.extendedBalance.availableForWithdrawal.toNumber() + amount);
42260
- this.extendedBalance.availableForTrade = safeUsdcWeb3Number(this.extendedBalance.availableForTrade.toNumber() + amount);
42261
- this.extendedBalance.balance = safeUsdcWeb3Number(this.extendedBalance.balance.toNumber() + amount);
42262
- this.extendedBalance.equity = safeUsdcWeb3Number(this.extendedBalance.equity.toNumber() + amount);
43186
+ this.extendedBalance.availableForWithdrawal = safeUsdcWeb3Number(this.extendedBalance.availableForWithdrawal.toNumber() + rawDelta);
43187
+ this.extendedBalance.availableForTrade = safeUsdcWeb3Number(this.extendedBalance.availableForTrade.toNumber() + rawDelta);
43188
+ this.extendedBalance.balance = safeUsdcWeb3Number(this.extendedBalance.balance.toNumber() + rawDelta);
43189
+ this.extendedBalance.equity = safeUsdcWeb3Number(this.extendedBalance.equity.toNumber() + rawDelta);
42263
43190
  }
42264
- logger.debug(`SolveBudget::updateExtAvailWithdraw amount=${amount}, extAvailWithdraw=${this._extAvailWithdraw}, totalUnused=${this._totalUnused}`);
42265
- return amount;
43191
+ logger.debug(`SolveBudget::updateExtAvailWithdraw rawDelta=${rawDelta}, extAvailWithdraw=${this.extAvailWithdraw}, totalUnused=${this.totalUnused}`);
43192
+ return rawDelta;
42266
43193
  }
42267
- _updateExtAvailUpnl(desired, isSpend) {
42268
- let amount = desired;
42269
- assert(amount > 0, "SolveBudget::_updateExtAvailUpnl amount must be positive");
43194
+ _updateExtAvailUpnl(desiredRaw, isSpend) {
43195
+ assert(desiredRaw > 0, "SolveBudget::_updateExtAvailUpnl amount must be positive");
43196
+ let rawDelta;
42270
43197
  if (isSpend) {
42271
- amount = Math.min(this._extAvailUpnl, Math.max(0, desired));
42272
- amount = -amount;
43198
+ const capRaw = this.extendedBalance?.unrealisedPnl?.toNumber() ?? 0;
43199
+ const useRaw = Math.min(capRaw, desiredRaw);
43200
+ if (useRaw <= CASE_THRESHOLD_USD) return 0;
43201
+ rawDelta = -useRaw;
43202
+ } else {
43203
+ rawDelta = desiredRaw;
42273
43204
  }
42274
- this._extAvailUpnl += amount;
42275
- this._totalUnused += amount;
42276
43205
  if (this.extendedBalance) {
42277
- this.extendedBalance.unrealisedPnl = safeUsdcWeb3Number(this.extendedBalance.unrealisedPnl.toNumber() + amount);
42278
- this.extendedBalance.balance = safeUsdcWeb3Number(this.extendedBalance.balance.toNumber() + amount);
42279
- this.extendedBalance.equity = safeUsdcWeb3Number(this.extendedBalance.equity.toNumber() + amount);
42280
- this.extendedBalance.availableForTrade = safeUsdcWeb3Number(this.extendedBalance.availableForTrade.toNumber() + amount);
42281
- }
42282
- logger.debug(`SolveBudget::updateExtAvailUpnl amount=${amount}, extAvailUpnl=${this._extAvailUpnl}, totalUnused=${this._totalUnused}`);
42283
- return amount;
42284
- }
42285
- spendExtAvailTrade(desired) {
42286
- const used = this._updateExtAvailWithdraw(desired, true);
42287
- const usedUpnl = this._updateExtAvailUpnl(desired, true);
42288
- logger.debug(`SolveBudget::updateExtAvailTrade amount=${used + usedUpnl}, extAvailTrade=${this._extAvailTrade}, totalUnused=${this._totalUnused}`);
43206
+ this.extendedBalance.unrealisedPnl = safeUsdcWeb3Number(this.extendedBalance.unrealisedPnl.toNumber() + rawDelta);
43207
+ this.extendedBalance.balance = safeUsdcWeb3Number(this.extendedBalance.balance.toNumber() + rawDelta);
43208
+ this.extendedBalance.equity = safeUsdcWeb3Number(this.extendedBalance.equity.toNumber() + rawDelta);
43209
+ this.extendedBalance.availableForTrade = safeUsdcWeb3Number(this.extendedBalance.availableForTrade.toNumber() + rawDelta);
43210
+ }
43211
+ logger.debug(`SolveBudget::updateExtAvailUpnl rawDelta=${rawDelta}, extAvailUpnl=${this.extAvailUpnl}, totalUnused=${this.totalUnused}`);
43212
+ return rawDelta;
43213
+ }
43214
+ spendExtAvailTrade(rawDesired) {
43215
+ const used = this._updateExtAvailWithdraw(rawDesired, true);
43216
+ const usedUpnl = this._updateExtAvailUpnl(rawDesired, true);
43217
+ logger.debug(`SolveBudget::updateExtAvailTrade rawSum=${used + usedUpnl}, extAvailTrade=${this.extAvailTrade}, totalUnused=${this.totalUnused}`);
42289
43218
  return used + usedUpnl;
42290
43219
  }
42291
- spendExtAvailUpnl(desired) {
42292
- return this._updateExtAvailUpnl(desired, true);
43220
+ // simply reduces available amounts, but maintains equity and balance.
43221
+ spendExtAvailTradeToEquityOnly(rawDesired) {
43222
+ const used = this._updateExtAvailWithdraw(rawDesired, true);
43223
+ const remaining = rawDesired - Math.abs(used);
43224
+ const usedUpnl = remaining > 0 ? this._updateExtAvailUpnl(remaining, true) : 0;
43225
+ if (this.extendedBalance) {
43226
+ const net = Math.abs(used) + Math.abs(usedUpnl);
43227
+ if (net.toFixed(0) != rawDesired.toFixed(0)) {
43228
+ throw new Error(`SolveBudget::spendExtAvailTradeToEquityOnly net=${net} != rawDesired=${rawDesired}`);
43229
+ }
43230
+ this.extendedBalance.equity = safeUsdcWeb3Number(this.extendedBalance.equity.toNumber() + net);
43231
+ this.extendedBalance.balance = safeUsdcWeb3Number(this.extendedBalance.balance.toNumber() + net);
43232
+ }
43233
+ logger.debug(`SolveBudget::updateExtAvailTrade rawSum=${used + usedUpnl}, extAvailTrade=${this.extAvailTrade}, totalUnused=${this.totalUnused}`);
43234
+ return used + usedUpnl;
43235
+ }
43236
+ spendExtAvailWithdraw(rawDesired) {
43237
+ return this._updateExtAvailWithdraw(rawDesired, true);
43238
+ }
43239
+ spendExtAvailUpnl(rawDesired) {
43240
+ return this._updateExtAvailUpnl(rawDesired, true);
42293
43241
  }
42294
- addToExtAvailTrade(amount) {
42295
- this._updateExtAvailWithdraw(amount, false);
42296
- logger.debug(`SolveBudget::addToExtAvailTrade amount=${amount}, extAvailTrade=${this._extAvailTrade}, totalUnused=${this._totalUnused}`);
43242
+ /**
43243
+ * Withdraw from Extended **withdrawal bucket only** to operator wallet (planning).
43244
+ * Used when VA must be funded from Extended and withdraw should be exhausted before unrealised PnL.
43245
+ */
43246
+ spendAvailWithdrawToWallet(rawDesired) {
43247
+ const want = Math.max(0, rawDesired);
43248
+ if (want <= CASE_THRESHOLD_USD) return 0;
43249
+ const rawDelta = this._updateExtAvailWithdraw(want, true);
43250
+ if (rawDelta === 0) return 0;
43251
+ const used = -rawDelta;
43252
+ this.addToWallet(used);
43253
+ return used;
43254
+ }
43255
+ /**
43256
+ * Required Extended equity (USD) for current open positions: total notional ÷ strategy leverage.
43257
+ * Same basis as {@link ExtendedSVKVesuStateManager._classifyLtvExtended} margin check.
43258
+ */
43259
+ _extendedMarginRequirementUsd() {
43260
+ const lev = calculateExtendedLevergae();
43261
+ if (lev <= 0 || this.extendedPositions.length === 0) return 0;
43262
+ const totalPosUsd = this.extendedPositions.reduce(
43263
+ (s, p) => s + p.valueUsd.toNumber(),
43264
+ 0
43265
+ );
43266
+ return totalPosUsd / lev;
43267
+ }
43268
+ /** How much more equity is needed before deposits should increase withdraw / trade availability. */
43269
+ _extendedEquityShortfallUsd() {
43270
+ if (!this.extendedBalance) return 0;
43271
+ const req = this._extendedMarginRequirementUsd();
43272
+ const eq = this.extendedBalance.equity.toNumber();
43273
+ return Math.max(0, req - eq);
43274
+ }
43275
+ /**
43276
+ * Credits a USDC inflow on Extended. Fills margin shortfall (balance+equity only) first;
43277
+ * any remainder is credited across balance, equity, availableForWithdrawal, and availableForTrade.
43278
+ */
43279
+ addToExtAvailTrade(rawUsd) {
43280
+ assert(rawUsd >= 0, "SolveBudget::addToExtAvailTrade amount must be non-negative");
43281
+ if (rawUsd <= CASE_THRESHOLD_USD) return;
43282
+ if (!this.extendedBalance) {
43283
+ logger.warn("SolveBudget::addToExtAvailTrade skipped \u2014 no extendedBalance");
43284
+ return;
43285
+ }
43286
+ const shortfall = this._extendedEquityShortfallUsd();
43287
+ const toMargin = Math.min(rawUsd, shortfall);
43288
+ const toLiquid = rawUsd - toMargin;
43289
+ if (toMargin > CASE_THRESHOLD_USD) {
43290
+ const b = this.extendedBalance.balance.toNumber();
43291
+ const e = this.extendedBalance.equity.toNumber();
43292
+ this.extendedBalance.balance = safeUsdcWeb3Number(b + toMargin);
43293
+ this.extendedBalance.equity = safeUsdcWeb3Number(e + toMargin);
43294
+ logger.debug(
43295
+ `SolveBudget::addToExtAvailTrade margin-first rawUsd=${toMargin} (shortfallBefore=${shortfall}, balance=${b + toMargin}, equity=${e + toMargin})`
43296
+ );
43297
+ }
43298
+ if (toLiquid > CASE_THRESHOLD_USD) {
43299
+ this._updateExtAvailWithdraw(toLiquid, false);
43300
+ }
43301
+ logger.debug(
43302
+ `SolveBudget::addToExtAvailTrade total rawUsd=${rawUsd} toLiquid=${toLiquid} extAvailTrade=${this.extAvailTrade}, totalUnused=${this.totalUnused}`
43303
+ );
42297
43304
  }
42298
43305
  spendVesuBorrowCapacity(desired) {
42299
- const used = Math.min(this._vesuBorrowCapacity, Math.max(0, desired));
42300
- this._vesuBorrowCapacity -= used;
42301
- this._totalUnused -= used;
43306
+ const used = Math.min(this.vesuBorrowCapacity, Math.max(0, desired));
42302
43307
  let spendsByPool = [];
42303
43308
  for (let index = 0; index < this.vesuPerPoolDebtDeltasToBorrow.length; index++) {
42304
43309
  const d = this.vesuPerPoolDebtDeltasToBorrow[index];
@@ -42309,13 +43314,12 @@ var SolveBudget = class {
42309
43314
  this.vesuPoolStates[index].debtUsdValue = this.vesuPoolStates[index].debtAmount.toNumber() * this.vesuPoolStates[index].debtPrice;
42310
43315
  spendsByPool.push({ poolId: this.vesuPoolStates[index].poolId, amount: safeUsdcWeb3Number(borrowed), collateralToken: this.vesuPoolStates[index].collateralToken, debtToken: this.vesuPoolStates[index].debtToken });
42311
43316
  }
42312
- logger.debug(`SolveBudget::spendVesuBorrowCapacity used=${used}, vesuBorrowCapacity=${this._vesuBorrowCapacity}, totalUnused=${this._totalUnused}`);
43317
+ logger.debug(`SolveBudget::spendVesuBorrowCapacity used=${used}, vesuBorrowCapacity=${this.vesuBorrowCapacity}, totalUnused=${this.totalUnused}`);
42313
43318
  return { used, spendsByPool };
42314
43319
  }
42315
43320
  repayVesuBorrowCapacity(desired) {
42316
43321
  assert(desired > 0, "SolveBudget::repayVesuBorrowCapacity desired must be positive");
42317
43322
  const used = desired;
42318
- this._vesuBorrowCapacity += used;
42319
43323
  const spendsByPool = [];
42320
43324
  for (let index = 0; index < this.vesuPerPoolDebtDeltasToBorrow.length; index++) {
42321
43325
  const d = this.vesuPerPoolDebtDeltasToBorrow[index];
@@ -42325,7 +43329,7 @@ var SolveBudget = class {
42325
43329
  this.vesuPoolStates[index].debtAmount = safeUsdcWeb3Number(this.vesuPoolStates[index].debtAmount.toNumber() - repaid);
42326
43330
  spendsByPool.push({ poolId: this.vesuPoolStates[index].poolId, amount: safeUsdcWeb3Number(-repaid), collateralToken: this.vesuPoolStates[index].collateralToken, debtToken: this.vesuPoolStates[index].debtToken });
42327
43331
  }
42328
- logger.debug(`SolveBudget::repayVesuBorrowCapacity used=${used}, vesuBorrowCapacity=${this._vesuBorrowCapacity}, totalUnused=${this._totalUnused}`);
43332
+ logger.debug(`SolveBudget::repayVesuBorrowCapacity used=${used}, vesuBorrowCapacity=${this.vesuBorrowCapacity}, totalUnused=${this.totalUnused}`);
42329
43333
  return { used, spendsByPool };
42330
43334
  }
42331
43335
  // ── State mutation ──────────────────────────────────────────────────
@@ -42347,13 +43351,21 @@ var SolveBudget = class {
42347
43351
  pool.debtUsdValue = pool.debtAmount.toNumber() * pool.debtPrice;
42348
43352
  const vesuPerPoolDebtDeltasToBorrow = this._computeperPoolDebtDeltasToBorrow();
42349
43353
  this.vesuPerPoolDebtDeltasToBorrow = vesuPerPoolDebtDeltasToBorrow.map((item) => item.deltaDebt);
42350
- const sum = this.vesuPerPoolDebtDeltasToBorrow.reduce((a, b) => a.plus(b), new Web3Number(0, USDC_TOKEN_DECIMALS));
42351
- this._vesuBorrowCapacity = sum.toNumber();
42352
43354
  this.shouldVesuRebalance = vesuPerPoolDebtDeltasToBorrow.map((item) => item.shouldRebalance);
42353
43355
  }
42354
- /** Update an Extended position's size after a lever route. Creates the position if new. */
42355
- applyExtendedExposureDelta(instrument, sizeDelta) {
42356
- const pos = this.extendedPositions.find((p) => p.instrument === instrument);
43356
+ /**
43357
+ * Update Extended position size after a lever route; sync {@link ExtendedPositionState.valueUsd}
43358
+ * and margin buckets (released USD on decrease, locked USD on increase).
43359
+ *
43360
+ * @param collateralPriceUsd BTC collateral price for notional / margin math; if omitted, uses
43361
+ * existing valueUsd / |size| or the first Vesu pool collateral price.
43362
+ */
43363
+ applyExtendedExposureDelta(instrument, sizeDelta, collateralPriceUsd) {
43364
+ const btcEps = 10 ** -COLLATERAL_PRECISION;
43365
+ const lev = calculateExtendedLevergae();
43366
+ let pos = this.extendedPositions.find((p) => p.instrument === instrument);
43367
+ const oldAbs = pos ? Math.abs(pos.size.toNumber()) : 0;
43368
+ const oldValUsd = pos ? pos.valueUsd.toNumber() : 0;
42357
43369
  if (pos) {
42358
43370
  pos.size = new Web3Number(pos.size.plus(sizeDelta).toFixed(8), 8);
42359
43371
  } else if (sizeDelta.toNumber() !== 0) {
@@ -42364,6 +43376,56 @@ var SolveBudget = class {
42364
43376
  valueUsd: new Web3Number(0, USDC_TOKEN_DECIMALS),
42365
43377
  leverage: "0"
42366
43378
  });
43379
+ pos = this.extendedPositions[this.extendedPositions.length - 1];
43380
+ } else {
43381
+ return;
43382
+ }
43383
+ const newAbs = Math.abs(pos.size.toNumber());
43384
+ let price = collateralPriceUsd;
43385
+ if (price === void 0 || price <= 0) {
43386
+ price = oldAbs > btcEps ? oldValUsd / oldAbs : this.vesuPoolStates[0]?.collateralPrice ?? 0;
43387
+ }
43388
+ if (price > 0) {
43389
+ pos.valueUsd = new Web3Number(
43390
+ (newAbs * price).toFixed(USDC_TOKEN_DECIMALS),
43391
+ USDC_TOKEN_DECIMALS
43392
+ );
43393
+ }
43394
+ if (!this.extendedBalance || lev <= 0 || price <= 0) return;
43395
+ const dAbs = newAbs - oldAbs;
43396
+ if (dAbs < -btcEps) {
43397
+ const releasedUsd = -dAbs * price / lev;
43398
+ if (releasedUsd > CASE_THRESHOLD_USD) {
43399
+ this.addToExtAvailTrade(releasedUsd);
43400
+ }
43401
+ } else if (dAbs > btcEps) {
43402
+ const lockedUsd = dAbs * price / lev;
43403
+ if (lockedUsd > CASE_THRESHOLD_USD) {
43404
+ this._lockExtendedMarginUsd(lockedUsd);
43405
+ }
43406
+ }
43407
+ }
43408
+ /** Pull margin for larger Extended exposure from liquid buckets, then balance/equity. */
43409
+ _lockExtendedMarginUsd(lockedUsd) {
43410
+ if (lockedUsd <= CASE_THRESHOLD_USD || !this.extendedBalance) return;
43411
+ let rem = lockedUsd;
43412
+ const uw = Math.min(rem, Math.max(0, this.extendedBalance.availableForWithdrawal.toNumber()));
43413
+ if (uw > 0) {
43414
+ this._updateExtAvailWithdraw(uw, true);
43415
+ rem -= uw;
43416
+ }
43417
+ if (rem > 0) {
43418
+ const uu = Math.min(rem, Math.max(0, this.extendedBalance.unrealisedPnl.toNumber()));
43419
+ if (uu > 0) {
43420
+ this._updateExtAvailUpnl(uu, true);
43421
+ rem -= uu;
43422
+ }
43423
+ }
43424
+ if (rem > 0) {
43425
+ const b = this.extendedBalance.balance.toNumber();
43426
+ const e = this.extendedBalance.equity.toNumber();
43427
+ this.extendedBalance.balance = safeUsdcWeb3Number(b - rem);
43428
+ this.extendedBalance.equity = safeUsdcWeb3Number(e - rem);
42367
43429
  }
42368
43430
  }
42369
43431
  /**
@@ -42391,6 +43453,23 @@ var SolveBudget = class {
42391
43453
  return output;
42392
43454
  }
42393
43455
  };
43456
+ function createSolveBudgetFromRawState(params) {
43457
+ const budget = new SolveBudget({
43458
+ assetToken: params.assetToken,
43459
+ usdcToken: params.usdcToken,
43460
+ unusedBalance: params.unusedBalance,
43461
+ walletBalance: params.walletBalance,
43462
+ vaultAssetBalance: params.vaultAssetBalance,
43463
+ vaultUsdcBalance: params.vaultUsdcBalance,
43464
+ extendedPositions: params.extendedPositions,
43465
+ extendedBalance: params.extendedBalance,
43466
+ vesuPoolStates: params.vesuPoolStates
43467
+ });
43468
+ if (params.limitBalanceBufferFactor && params.limitBalanceBufferFactor > 0) {
43469
+ budget.applyBuffer(params.limitBalanceBufferFactor);
43470
+ }
43471
+ return budget;
43472
+ }
42394
43473
  var ExtendedSVKVesuStateManager = class {
42395
43474
  constructor(config) {
42396
43475
  this._tag = "ExtendedSVKVesuStateManager";
@@ -42426,7 +43505,7 @@ var ExtendedSVKVesuStateManager = class {
42426
43505
  vesuAllocationUsd: safeUsdcWeb3Number(0),
42427
43506
  extendedAllocationUsd: safeUsdcWeb3Number(0),
42428
43507
  bringLiquidityAmount: safeUsdcWeb3Number(0),
42429
- pendingDeposit: this._budget.extendedBalance?.pendingDeposit ?? new Web3Number(0, USDC_TOKEN_DECIMALS)
43508
+ pendingDeposit: safeUsdcWeb3Number(0)
42430
43509
  };
42431
43510
  this._logSolveResult(result);
42432
43511
  return result;
@@ -42441,30 +43520,44 @@ var ExtendedSVKVesuStateManager = class {
42441
43520
  async _refresh() {
42442
43521
  logger.info(`${this._tag}::_refresh starting`);
42443
43522
  const [
42444
- vaultAllocatorBalance,
43523
+ vaultAssetBalance,
43524
+ vaultUsdcBalance,
42445
43525
  walletBalance,
42446
43526
  vesuPoolStates,
42447
43527
  extendedBalance,
42448
43528
  extendedPositions
42449
43529
  ] = await Promise.all([
42450
- this._fetchVaultAllocatorBalance(),
43530
+ this._fetchVaultAllocatorAssetBalance(),
43531
+ this._fetchVaultAllocatorUsdcBalanceIfDistinct(),
42451
43532
  this._fetchWalletBalances(),
42452
43533
  this._fetchAllVesuPoolStates(),
42453
43534
  this._fetchExtendedBalance(),
42454
43535
  this._fetchExtendedPositions()
42455
43536
  ]);
42456
- logger.verbose(`_refresh vaultAllocatorBalance: ${vaultAllocatorBalance.usdValue}, walletBalance: ${walletBalance.usdValue}`);
43537
+ logger.verbose(
43538
+ `${this._tag}::_refresh VA asset ${vaultAssetBalance.token.symbol}=$${vaultAssetBalance.usdValue.toFixed(2)}${vaultUsdcBalance ? `, VA USDC=$${vaultUsdcBalance.usdValue.toFixed(2)}` : ""}, wallet=${walletBalance.usdValue}`
43539
+ );
42457
43540
  const unusedBalance = this._computeUnusedBalances(
42458
- vaultAllocatorBalance,
43541
+ vaultAssetBalance,
43542
+ vaultUsdcBalance,
42459
43543
  walletBalance
42460
43544
  );
42461
- this._budget = new SolveBudget({
42462
- limitBalanceBufferFactor: this._config.limitBalanceBufferFactor,
43545
+ this._budget = createSolveBudgetFromRawState({
43546
+ assetToken: this._config.assetToken,
43547
+ usdcToken: this._config.usdcToken,
42463
43548
  unusedBalance,
42464
43549
  walletBalance,
42465
- vaultBalance: vaultAllocatorBalance,
43550
+ vaultAssetBalance,
43551
+ vaultUsdcBalance,
42466
43552
  extendedPositions,
42467
- extendedBalance,
43553
+ extendedBalance: {
43554
+ availableForTrade: extendedBalance?.availableForTrade || new Web3Number(0, USDC_TOKEN_DECIMALS),
43555
+ availableForWithdrawal: extendedBalance?.availableForWithdrawal || new Web3Number(0, USDC_TOKEN_DECIMALS),
43556
+ unrealisedPnl: extendedBalance?.unrealisedPnl || new Web3Number(0, USDC_TOKEN_DECIMALS),
43557
+ balance: extendedBalance?.balance || new Web3Number(0, USDC_TOKEN_DECIMALS),
43558
+ equity: extendedBalance?.equity || new Web3Number(0, USDC_TOKEN_DECIMALS),
43559
+ pendingDeposit: extendedBalance?.pendingDeposit || new Web3Number(0, USDC_TOKEN_DECIMALS)
43560
+ },
42468
43561
  vesuPoolStates
42469
43562
  });
42470
43563
  const totalUnusedUsd = unusedBalance.reduce(
@@ -42476,10 +43569,14 @@ var ExtendedSVKVesuStateManager = class {
42476
43569
  );
42477
43570
  }
42478
43571
  // todo add communication check with python server of extended. if not working, throw error in solve function.
43572
+ /** True when strategy asset and USDC share one token — VA USDC slot is unused (all in asset balance). */
43573
+ _vaultAssetAndUsdcAreSameToken() {
43574
+ return this._config.assetToken.address.eq(this._config.usdcToken.address);
43575
+ }
42479
43576
  /**
42480
- * Reads the asset-token balance sitting idle in the vault allocator contract.
43577
+ * Reads the {@link StateManagerConfig.assetToken} balance idle in the vault allocator.
42481
43578
  */
42482
- async _fetchVaultAllocatorBalance() {
43579
+ async _fetchVaultAllocatorAssetBalance() {
42483
43580
  const { assetToken, vaultAllocator, networkConfig, pricer } = this._config;
42484
43581
  const balance = await new ERC20(networkConfig).balanceOf(
42485
43582
  assetToken.address,
@@ -42491,20 +43588,38 @@ var ExtendedSVKVesuStateManager = class {
42491
43588
  return { token: assetToken, amount: balance, usdValue };
42492
43589
  }
42493
43590
  /**
42494
- * Merges the vault-allocator balance and wallet balances into a
42495
- * deduplicated array of TokenBalance entries keyed by token address.
42496
- *
42497
- * e.g. VA has USDC, wallet has USDC + USDC.e → returns
42498
- * [{ token: USDC, amount: VA+wallet, usdValue: … },
42499
- * { token: USDC.e, amount: wallet, usdValue: … }]
43591
+ * Reads {@link StateManagerConfig.usdcToken} in the vault allocator when it differs from
43592
+ * {@link StateManagerConfig.assetToken}. Otherwise returns null (treat VA USDC as 0; stablecoin is only under asset).
42500
43593
  */
42501
- _computeUnusedBalances(vaultAllocatorBalance, walletBalance) {
43594
+ async _fetchVaultAllocatorUsdcBalanceIfDistinct() {
43595
+ if (this._vaultAssetAndUsdcAreSameToken()) return null;
43596
+ const { usdcToken, vaultAllocator, networkConfig, pricer } = this._config;
43597
+ const balance = await new ERC20(networkConfig).balanceOf(
43598
+ usdcToken.address,
43599
+ vaultAllocator,
43600
+ usdcToken.decimals
43601
+ );
43602
+ const tokenPrice = await pricer.getPrice(
43603
+ usdcToken.priceProxySymbol || usdcToken.symbol
43604
+ );
43605
+ const usdValue = Number(balance.toFixed(usdcToken.decimals)) * tokenPrice.price;
43606
+ return { token: usdcToken, amount: balance, usdValue };
43607
+ }
43608
+ /**
43609
+ * Merges vault-allocator asset, optional vault-allocator USDC, and operator wallet
43610
+ * balances into entries keyed by token address.
43611
+ */
43612
+ _computeUnusedBalances(vaultAssetBalance, vaultUsdcBalance, walletBalance) {
42502
43613
  const balanceMap = /* @__PURE__ */ new Map();
42503
- balanceMap.set(vaultAllocatorBalance.token.address.toString(), {
42504
- token: vaultAllocatorBalance.token,
42505
- amount: vaultAllocatorBalance.amount,
42506
- usdValue: vaultAllocatorBalance.usdValue
42507
- });
43614
+ const put = (tb) => {
43615
+ balanceMap.set(tb.token.address.toString(), {
43616
+ token: tb.token,
43617
+ amount: tb.amount,
43618
+ usdValue: tb.usdValue
43619
+ });
43620
+ };
43621
+ put(vaultAssetBalance);
43622
+ if (vaultUsdcBalance) put(vaultUsdcBalance);
42508
43623
  const key = walletBalance.token.address.toString();
42509
43624
  const existing = balanceMap.get(key);
42510
43625
  if (existing) {
@@ -42523,30 +43638,29 @@ var ExtendedSVKVesuStateManager = class {
42523
43638
  return Array.from(balanceMap.values());
42524
43639
  }
42525
43640
  /**
42526
- * Reads the operator wallet's balances for the asset token (USDC.e) and
42527
- * USDC.e (needed for route computation P1 vs P2 decision for Extended deposits).
43641
+ * Reads the operator wallet balance for {@link StateManagerConfig.usdcToken} only
43642
+ * (wallet stablecoin is always USDC, regardless of strategy {@link StateManagerConfig.assetToken}).
42528
43643
  */
42529
43644
  async _fetchWalletBalances() {
42530
43645
  const {
42531
43646
  networkConfig,
42532
43647
  pricer,
42533
43648
  walletAddress,
42534
- assetToken,
42535
- usdceToken
43649
+ usdcToken
42536
43650
  } = this._config;
42537
43651
  const erc20 = new ERC20(networkConfig);
42538
- const [usdceBalance, usdcePrice] = await Promise.all([
43652
+ const [balance, tokenPrice] = await Promise.all([
42539
43653
  erc20.balanceOf(
42540
- usdceToken.address,
43654
+ usdcToken.address,
42541
43655
  walletAddress,
42542
- usdceToken.decimals
43656
+ usdcToken.decimals
42543
43657
  ),
42544
- pricer.getPrice(usdceToken.priceProxySymbol || usdceToken.symbol)
43658
+ pricer.getPrice(usdcToken.priceProxySymbol || usdcToken.symbol)
42545
43659
  ]);
42546
43660
  return {
42547
- token: usdceToken,
42548
- amount: usdceBalance,
42549
- usdValue: Number(usdceBalance.toFixed(usdceToken.decimals)) * usdcePrice.price
43661
+ token: usdcToken,
43662
+ amount: balance,
43663
+ usdValue: Number(balance.toFixed(usdcToken.decimals)) * tokenPrice.price
42550
43664
  };
42551
43665
  }
42552
43666
  /**
@@ -42664,12 +43778,12 @@ var ExtendedSVKVesuStateManager = class {
42664
43778
  * finite, sensible values. Throws on invalid state.
42665
43779
  */
42666
43780
  _validateRefreshedState() {
42667
- if (this._budget.unusedBalance.length === 0) {
43781
+ if (this._budget.unusedBalanceRows.length === 0) {
42668
43782
  throw new Error(
42669
43783
  `${this._tag}: unusedBalance is empty after refresh`
42670
43784
  );
42671
43785
  }
42672
- for (const balance of this._budget.unusedBalance) {
43786
+ for (const balance of this._budget.unusedBalanceRows) {
42673
43787
  this._validateTokenBalanceOrThrow(
42674
43788
  balance,
42675
43789
  `unusedBalance[${balance.token.symbol}]`
@@ -42689,7 +43803,7 @@ var ExtendedSVKVesuStateManager = class {
42689
43803
  }
42690
43804
  }
42691
43805
  _validateVesuPoolPricesOrThrow() {
42692
- for (const pool of this._budget.vesuPoolStates) {
43806
+ for (const pool of this._budget.vesuPools) {
42693
43807
  const poolLabel = pool.poolId.shortString();
42694
43808
  this._assertPositiveFinite(
42695
43809
  pool.collateralPrice,
@@ -42702,8 +43816,8 @@ var ExtendedSVKVesuStateManager = class {
42702
43816
  }
42703
43817
  }
42704
43818
  _validateExtendedBalanceOrThrow() {
42705
- if (!this._budget.extendedBalance) return;
42706
- const { equity, availableForTrade } = this._budget.extendedBalance;
43819
+ if (!this._budget.extendedBalanceView) return;
43820
+ const { equity, availableForTrade } = this._budget.extendedBalanceView;
42707
43821
  if (!Number.isFinite(equity.toNumber()) || !Number.isFinite(availableForTrade.toNumber())) {
42708
43822
  throw new Error(
42709
43823
  `${this._tag}: Extended balance contains non-finite values \u2014 equity: ${equity.toNumber()}, availableForTrade: ${availableForTrade.toNumber()}`
@@ -42742,18 +43856,26 @@ var ExtendedSVKVesuStateManager = class {
42742
43856
  );
42743
43857
  }
42744
43858
  /**
42745
- * Total investable = vault allocator balance + Extended available-for-trade,
42746
- * both reduced by the configured buffer percentage.
43859
+ * Total investable = vault allocator balance + Extended available-for-trade +
43860
+ * buffered unrealised PnL, matching `deposit_cases_extended_vesu.xlsx`:
43861
+ * `(VA + wallet + EXT_WITH_AVL + EXT_UPNL) * (1 − buffer)`.
43862
+ * Positive {@link ExtendedBalanceState.pendingDeposit} stays on the afT leg only (see {@link SolveBudget.extAvailTrade}).
42747
43863
  */
42748
43864
  _computeTotalInvestableAmount() {
42749
- const totalUnusedUsd = this._budget.unusedBalance.reduce(
42750
- (acc, b) => acc + b.usdValue,
42751
- 0
42752
- );
43865
+ const totalUnusedUsd = this._budget.unusedBalancesBufferedUsdSum;
42753
43866
  logger.debug(
42754
- `${this._tag}::_computeTotalInvestableAmount unusedBalances=${JSON.stringify(this._budget.unusedBalance.map((b) => ({ token: b.token.symbol, amount: b.amount.toNumber(), usdValue: b.usdValue })))}`
43867
+ `${this._tag}::_computeTotalInvestableAmount unusedBalances=${JSON.stringify(this._budget.unusedBalanceRows.map((b) => ({ token: b.token.symbol, amount: b.amount.toNumber(), usdValue: b.usdValue })))}`
43868
+ );
43869
+ const extBal = this._budget.extendedBalanceView;
43870
+ const rawAft = extBal?.availableForWithdrawal?.toNumber() ?? 0;
43871
+ const rawUpnl = extBal?.unrealisedPnl?.toNumber() ?? 0;
43872
+ let extBuffered = this._budget.bufferedUsd(rawAft) + this._budget.bufferedUsd(rawUpnl);
43873
+ const pd = extBal?.pendingDeposit?.toNumber() ?? 0;
43874
+ if (pd > 0) extBuffered += pd;
43875
+ const extendedAvailable = new Web3Number(
43876
+ extBuffered.toFixed(USDC_TOKEN_DECIMALS),
43877
+ USDC_TOKEN_DECIMALS
42755
43878
  );
42756
- const extendedAvailable = this._budget.extendedBalance?.availableForTrade ?? new Web3Number(0, USDC_TOKEN_DECIMALS);
42757
43879
  logger.verbose(`_computeTotalInvestableAmount totalUnusedUsd: ${totalUnusedUsd}, extendedAvailable: ${extendedAvailable.toNumber()}`);
42758
43880
  return new Web3Number(totalUnusedUsd.toFixed(USDC_TOKEN_DECIMALS), USDC_TOKEN_DECIMALS).plus(extendedAvailable);
42759
43881
  }
@@ -42784,7 +43906,7 @@ var ExtendedSVKVesuStateManager = class {
42784
43906
  if (denominator === 0) {
42785
43907
  throw new Error(`${this._tag}: Denominator is zero`);
42786
43908
  }
42787
- const collateralPrice = this._budget.vesuPoolStates[0]?.collateralPrice ?? 0;
43909
+ const collateralPrice = this._budget.vesuPools[0]?.collateralPrice ?? 0;
42788
43910
  const totalVesuExposureUsd = this._totalVesuCollateralUsd().plus(new Web3Number((deltaVesuCollateral * collateralPrice).toFixed(6), USDC_TOKEN_DECIMALS));
42789
43911
  const totalExtendedExposureUsd = this._totalExtendedExposureUsd().plus(new Web3Number((deltaExtendedCollateral * collateralPrice).toFixed(6), USDC_TOKEN_DECIMALS));
42790
43912
  const numerator = vesuLeverage * distributableAmount.toNumber() + totalVesuExposureUsd.toNumber() - totalExtendedExposureUsd.toNumber();
@@ -42796,8 +43918,6 @@ var ExtendedSVKVesuStateManager = class {
42796
43918
  distributableAmount.minus(extendedAllocationUsd).toFixed(USDC_TOKEN_DECIMALS),
42797
43919
  USDC_TOKEN_DECIMALS
42798
43920
  );
42799
- const perPoolDebtDeltasToBorrow = this._budget.vesuPerPoolDebtDeltasToBorrow;
42800
- vesuAllocationUsd = vesuAllocationUsd.plus(this._sumDebtDeltas(perPoolDebtDeltasToBorrow).multipliedBy(-1));
42801
43921
  let vesuPositionDelta = Number(new Web3Number((vesuAllocationUsd.toNumber() * vesuLeverage / collateralPrice).toFixed(6), 6).toFixedRoundDown(COLLATERAL_PRECISION));
42802
43922
  let extendedPositionDelta = Number(new Web3Number((extendedAllocationUsd.toNumber() * extendedLeverage / collateralPrice).toFixed(6), 6).toFixedRoundDown(COLLATERAL_PRECISION));
42803
43923
  if (!isRecursive) {
@@ -42816,14 +43936,17 @@ var ExtendedSVKVesuStateManager = class {
42816
43936
  * by existing collateral value, then converts each share to collateral
42817
43937
  * token units.
42818
43938
  */
42819
- _computePerPoolCollateralDeltas(vesuAllocationUsd, perPoolDebtDeltasToBorrow) {
43939
+ _computePerPoolCollateralDeltas(vesuAllocationUsd) {
42820
43940
  const vesuLeverage = calculateVesuLeverage();
42821
- const availableVesuCollateralAllocationUsd = vesuAllocationUsd.plus(this._sumDebtDeltas(perPoolDebtDeltasToBorrow));
43941
+ const availableVesuCollateralAllocationUsd = vesuAllocationUsd;
42822
43942
  const postLeverageAllocationUsd = availableVesuCollateralAllocationUsd.multipliedBy(vesuLeverage);
42823
43943
  const totalCollateralExisting = this._totalVesuCollateral();
42824
- return this._budget.vesuPoolStates.map((pool, index) => {
43944
+ return this._budget.vesuPools.map((pool, index) => {
42825
43945
  const _postLeverageAllocation = postLeverageAllocationUsd.dividedBy(pool.collateralPrice);
42826
- const postLeverageAllocation = new Web3Number(_postLeverageAllocation.plus(totalCollateralExisting).toFixed(COLLATERAL_PRECISION), pool.collateralToken.decimals).minus(totalCollateralExisting);
43946
+ const postLeverageAllocation = new Web3Number(
43947
+ _postLeverageAllocation.plus(totalCollateralExisting).toFixedRoundDown(COLLATERAL_PRECISION),
43948
+ pool.collateralToken.decimals
43949
+ ).minus(totalCollateralExisting);
42827
43950
  const _poolCollateralDelta = this._computePoolCollateralShare(
42828
43951
  pool,
42829
43952
  totalCollateralExisting,
@@ -42833,12 +43956,12 @@ var ExtendedSVKVesuStateManager = class {
42833
43956
  _poolCollateralDelta.toFixed(COLLATERAL_PRECISION),
42834
43957
  pool.collateralToken.decimals
42835
43958
  );
42836
- const newDebt = postLeverageAllocationUsd.minus(availableVesuCollateralAllocationUsd).dividedBy(pool.debtPrice);
43959
+ const newDebt = postLeverageAllocation.multipliedBy(pool.collateralPrice).minus(availableVesuCollateralAllocationUsd).dividedBy(pool.debtPrice);
42837
43960
  return {
42838
43961
  poolId: pool.poolId,
42839
43962
  collateralToken: pool.collateralToken,
42840
43963
  debtToken: pool.debtToken,
42841
- debtDelta: perPoolDebtDeltasToBorrow[index].plus(newDebt),
43964
+ debtDelta: newDebt,
42842
43965
  collateralDelta: poolCollateralDelta,
42843
43966
  collateralPrice: pool.collateralPrice,
42844
43967
  debtPrice: pool.debtPrice
@@ -42851,7 +43974,7 @@ var ExtendedSVKVesuStateManager = class {
42851
43974
  * Multi-pool cases split proportionally by current collateral USD value.
42852
43975
  */
42853
43976
  _computePoolCollateralShare(pool, totalCollateral, totalVesuAllocation) {
42854
- const isSinglePoolOrZeroTotal = this._budget.vesuPoolStates.length === 1 || totalCollateral.toNumber() === 0;
43977
+ const isSinglePoolOrZeroTotal = this._budget.vesuPools.length === 1 || totalCollateral.toNumber() === 0;
42855
43978
  if (isSinglePoolOrZeroTotal) return totalVesuAllocation;
42856
43979
  const poolWeight = pool.collateralAmount.dividedBy(totalCollateral);
42857
43980
  return new Web3Number(
@@ -42888,8 +44011,8 @@ var ExtendedSVKVesuStateManager = class {
42888
44011
  */
42889
44012
  _computeTargetExtendedExposure(vesuDeltas) {
42890
44013
  let totalExposureCollateral = new Web3Number(0, USDC_TOKEN_DECIMALS);
42891
- for (let i = 0; i < this._budget.vesuPoolStates.length; i++) {
42892
- const pool = this._budget.vesuPoolStates[i];
44014
+ for (let i = 0; i < this._budget.vesuPools.length; i++) {
44015
+ const pool = this._budget.vesuPools[i];
42893
44016
  const delta = vesuDeltas[i];
42894
44017
  logger.debug(
42895
44018
  `${this._tag}::_computeTargetExtendedExposure poolId=${pool.poolId.toString()}, collateralAmount=${pool.collateralAmount.toNumber()}, collateralDelta=${delta.collateralDelta.toNumber()}`
@@ -42907,7 +44030,7 @@ var ExtendedSVKVesuStateManager = class {
42907
44030
  );
42908
44031
  }
42909
44032
  _hasNoExtendedPositions() {
42910
- return this._budget.extendedPositions.length === 0;
44033
+ return this._budget.extendedPositionsView.length === 0;
42911
44034
  }
42912
44035
  /**
42913
44036
  * Creates a single-element delta array for the default instrument
@@ -42918,7 +44041,7 @@ var ExtendedSVKVesuStateManager = class {
42918
44041
  {
42919
44042
  instrument: this._config.extendedAdapter.config.extendedMarketName,
42920
44043
  delta: new Web3Number(
42921
- delta.toFixed(COLLATERAL_PRECISION),
44044
+ delta.toFixedRoundDown(COLLATERAL_PRECISION),
42922
44045
  USDC_TOKEN_DECIMALS
42923
44046
  )
42924
44047
  }
@@ -42930,7 +44053,7 @@ var ExtendedSVKVesuStateManager = class {
42930
44053
  */
42931
44054
  _distributeExposureDeltaAcrossPositions(totalDelta) {
42932
44055
  const totalExposure = this._totalExtendedExposure();
42933
- return this._budget.extendedPositions.map((position) => {
44056
+ return this._budget.extendedPositionsView.map((position) => {
42934
44057
  const share = this._positionExposureShareFraction(
42935
44058
  position,
42936
44059
  totalExposure
@@ -42938,7 +44061,7 @@ var ExtendedSVKVesuStateManager = class {
42938
44061
  return {
42939
44062
  instrument: position.instrument,
42940
44063
  delta: new Web3Number(
42941
- totalDelta.multipliedBy(share).toFixed(COLLATERAL_PRECISION),
44064
+ totalDelta.multipliedBy(share).toFixedRoundDown(COLLATERAL_PRECISION),
42942
44065
  USDC_TOKEN_DECIMALS
42943
44066
  )
42944
44067
  };
@@ -42950,7 +44073,7 @@ var ExtendedSVKVesuStateManager = class {
42950
44073
  * or when total exposure is zero.
42951
44074
  */
42952
44075
  _positionExposureShareFraction(position, totalExposure) {
42953
- const isSingleOrZero = totalExposure.toNumber() === 0 || this._budget.extendedPositions.length === 1;
44076
+ const isSingleOrZero = totalExposure.toNumber() === 0 || this._budget.extendedPositionsView.length === 1;
42954
44077
  if (isSingleOrZero) return 1;
42955
44078
  return position.valueUsd.dividedBy(totalExposure).toNumber();
42956
44079
  }
@@ -42962,7 +44085,10 @@ var ExtendedSVKVesuStateManager = class {
42962
44085
  * Positive = need to deposit more, negative = can withdraw excess.
42963
44086
  */
42964
44087
  _computeExtendedDepositDelta(extendedAllocationUsd) {
42965
- const currentAvailableForTrade = this._budget.extendedBalance?.availableForTrade ?? new Web3Number(0, USDC_TOKEN_DECIMALS);
44088
+ const currentAvailableForTrade = new Web3Number(
44089
+ this._budget.extAvailTrade.toFixed(USDC_TOKEN_DECIMALS),
44090
+ USDC_TOKEN_DECIMALS
44091
+ );
42966
44092
  return new Web3Number(
42967
44093
  extendedAllocationUsd.minus(currentAvailableForTrade).toFixed(USDC_TOKEN_DECIMALS),
42968
44094
  USDC_TOKEN_DECIMALS
@@ -42970,8 +44096,8 @@ var ExtendedSVKVesuStateManager = class {
42970
44096
  }
42971
44097
  _computeVesuDepositAmount(vesuDeltas) {
42972
44098
  let totalVesuCollateral = new Web3Number(0, USDC_TOKEN_DECIMALS);
42973
- for (let i = 0; i < this._budget.vesuPoolStates.length; i++) {
42974
- const pool = this._budget.vesuPoolStates[i];
44099
+ for (let i = 0; i < this._budget.vesuPools.length; i++) {
44100
+ const pool = this._budget.vesuPools[i];
42975
44101
  const delta = vesuDeltas[i];
42976
44102
  totalVesuCollateral = totalVesuCollateral.plus(delta.collateralDelta.multipliedBy(pool.collateralPrice));
42977
44103
  totalVesuCollateral = totalVesuCollateral.minus(delta.debtDelta.multipliedBy(pool.debtPrice));
@@ -43016,13 +44142,17 @@ var ExtendedSVKVesuStateManager = class {
43016
44142
  for (const route of spendsByPool) {
43017
44143
  routes.push({ type: "VESU_REPAY" /* VESU_REPAY */, ...route, priority: routes.length });
43018
44144
  }
43019
- this._budget.spendVA(used);
44145
+ this._budget.spendVaRawUsd(used);
43020
44146
  }
43021
- _buildVesuBorrowRoutes(totalUsd, routes) {
44147
+ _buildVesuBorrowRoutes(totalUsd, routes, opts) {
43022
44148
  let borrowable = this._budget.vesuBorrowCapacity;
44149
+ if (opts?.maxBorrowUsd !== void 0) {
44150
+ borrowable = Math.min(borrowable, Math.max(0, opts.maxBorrowUsd));
44151
+ }
43023
44152
  if (totalUsd <= CASE_THRESHOLD_USD) return { routes, remaining: totalUsd };
43024
44153
  if (borrowable <= CASE_THRESHOLD_USD) return { routes, remaining: totalUsd };
43025
- const { used, spendsByPool } = this._budget.spendVesuBorrowCapacity(totalUsd);
44154
+ const borrowTarget = Math.min(totalUsd, borrowable);
44155
+ const { used, spendsByPool } = this._budget.spendVesuBorrowCapacity(borrowTarget);
43026
44156
  for (const route of spendsByPool) {
43027
44157
  routes.push({ type: "VESU_BORROW" /* VESU_BORROW */, ...route, priority: routes.length });
43028
44158
  }
@@ -43078,9 +44208,9 @@ var ExtendedSVKVesuStateManager = class {
43078
44208
  // });
43079
44209
  // }
43080
44210
  _getWalletToVARoute(tryAmount, routes) {
43081
- const usableAmount = Math.min(tryAmount, this._budget.walletUsd);
43082
- if (usableAmount > CASE_THRESHOLD_USD) {
43083
- const walletUsed = this._budget.spendWallet(usableAmount);
44211
+ const usableRaw = Math.min(tryAmount, this._budget.walletUsd);
44212
+ if (usableRaw > CASE_THRESHOLD_USD) {
44213
+ const walletUsed = this._budget.spendWallet(usableRaw);
43084
44214
  this._budget.addToVA(walletUsed);
43085
44215
  const route = { type: "WALLET_TO_VA" /* WALLET_TO_VA */, amount: safeUsdcWeb3Number(walletUsed), priority: routes.length };
43086
44216
  routes.push(route);
@@ -43089,9 +44219,9 @@ var ExtendedSVKVesuStateManager = class {
43089
44219
  return { routes, remaining: tryAmount };
43090
44220
  }
43091
44221
  _getWalletToEXTENDEDRoute(tryAmount, routes, shouldAddWaitRoute = true) {
43092
- const usableAmount = Math.min(tryAmount, this._budget.walletUsd);
43093
- if (usableAmount > CASE_THRESHOLD_USD) {
43094
- const walletUsed = this._budget.spendWallet(usableAmount);
44222
+ const usableRaw = Math.min(tryAmount, this._budget.walletUsd);
44223
+ if (usableRaw > CASE_THRESHOLD_USD) {
44224
+ const walletUsed = this._budget.spendWallet(usableRaw);
43095
44225
  this._budget.addToExtAvailTrade(walletUsed);
43096
44226
  routes.push({ type: "WALLET_TO_EXTENDED" /* WALLET_TO_EXTENDED */, amount: safeUsdcWeb3Number(walletUsed), priority: routes.length });
43097
44227
  if (shouldAddWaitRoute) {
@@ -43102,9 +44232,9 @@ var ExtendedSVKVesuStateManager = class {
43102
44232
  return { routes, remaining: tryAmount };
43103
44233
  }
43104
44234
  _getVAToEXTENDEDRoute(tryAmount, routes, shouldAddWaitRoute = true) {
43105
- const usableAmount = Math.min(tryAmount, this._budget.vaUsd);
43106
- if (usableAmount > CASE_THRESHOLD_USD) {
43107
- const vaUsed = this._budget.spendVA(usableAmount);
44235
+ const usable = Math.min(tryAmount, this._budget.vaUsd);
44236
+ if (usable > CASE_THRESHOLD_USD) {
44237
+ const vaUsed = this._budget.spendVA(usable);
43108
44238
  this._budget.addToExtAvailTrade(vaUsed);
43109
44239
  const route = { type: "VA_TO_EXTENDED" /* VA_TO_EXTENDED */, amount: safeUsdcWeb3Number(vaUsed), priority: routes.length };
43110
44240
  routes.push(route);
@@ -43117,40 +44247,42 @@ var ExtendedSVKVesuStateManager = class {
43117
44247
  }
43118
44248
  _getExtendedToWalletRoute(tryAmount, routes, shouldAddWaitRoute = true) {
43119
44249
  if (tryAmount <= CASE_THRESHOLD_USD) return { routes, remaining: tryAmount };
43120
- assert(tryAmount <= this._budget.extAvailWithdraw, `tryAmount is greater than extAvailTrade, tryAmount: ${tryAmount}, extAvailWithdraw: ${this._budget.extAvailWithdraw}`);
43121
- const extWithdrawUsed = this._budget.spendExtAvailTrade(tryAmount);
43122
- this._budget.addToWallet(Math.abs(extWithdrawUsed));
43123
- const route = { type: "EXTENDED_TO_WALLET" /* EXTENDED_TO_WALLET */, amount: safeUsdcWeb3Number(Math.abs(extWithdrawUsed)), priority: routes.length };
44250
+ const rawCap = this._budget.extAvailWithdraw + this._budget.extAvailUpnl;
44251
+ const rawSpend = Math.min(tryAmount, rawCap);
44252
+ if (rawSpend <= CASE_THRESHOLD_USD) return { routes, remaining: tryAmount };
44253
+ const rawOut = this._budget.spendExtAvailTrade(rawSpend);
44254
+ this._budget.addToWallet(Math.abs(rawOut));
44255
+ const route = { type: "EXTENDED_TO_WALLET" /* EXTENDED_TO_WALLET */, amount: safeUsdcWeb3Number(rawSpend), priority: routes.length };
43124
44256
  routes.push(route);
43125
44257
  if (shouldAddWaitRoute) {
43126
44258
  routes.push({ type: "RETURN_TO_WAIT" /* RETURN_TO_WAIT */, priority: routes.length });
43127
44259
  }
43128
- return { routes, remaining: tryAmount - Math.abs(extWithdrawUsed) };
44260
+ return { routes, remaining: tryAmount - rawSpend };
43129
44261
  }
43130
44262
  _getWALLETToVARoute(tryAmount, routes) {
43131
44263
  if (tryAmount <= CASE_THRESHOLD_USD) return { routes, remaining: tryAmount };
43132
- const usableAmount = Math.min(tryAmount, this._budget.walletUsd);
43133
- if (usableAmount <= CASE_THRESHOLD_USD) return { routes, remaining: tryAmount };
43134
- const walletUsed = this._budget.spendWallet(tryAmount);
44264
+ const usableRaw = Math.min(tryAmount, this._budget.walletUsd);
44265
+ if (usableRaw <= CASE_THRESHOLD_USD) return { routes, remaining: tryAmount };
44266
+ const walletUsed = this._budget.spendWallet(usableRaw);
43135
44267
  this._budget.addToVA(walletUsed);
43136
44268
  const route = { type: "WALLET_TO_VA" /* WALLET_TO_VA */, amount: safeUsdcWeb3Number(walletUsed), priority: routes.length };
43137
44269
  routes.push(route);
43138
44270
  return { routes, remaining: tryAmount - walletUsed };
43139
44271
  }
43140
44272
  _getUpnlRoute(tryAmount, routes) {
43141
- const upnl = this._budget.extendedBalance?.unrealisedPnl?.toNumber() ?? 0;
43142
- const usableAmount = Math.min(tryAmount, upnl);
43143
- if (usableAmount <= CASE_THRESHOLD_USD) return { routes, remaining: tryAmount };
43144
- const upnlUsed = this._budget.spendExtAvailUpnl(usableAmount);
43145
- this._budget.addToExtAvailTrade(upnlUsed);
43146
- assert(this._budget.extendedPositions.length == 1, "SolveBudget::_getUpnlRoute: extendedPositions length must be 1");
44273
+ const rawUpnl = this._budget.extAvailUpnl;
44274
+ const usableRaw = Math.min(tryAmount, rawUpnl);
44275
+ if (usableRaw <= 0) return { routes, remaining: tryAmount };
44276
+ this._budget.spendExtAvailUpnl(usableRaw);
44277
+ this._budget.addToExtAvailTrade(usableRaw);
44278
+ assert(this._budget.extendedPositionsView.length == 1, "SolveBudget::_getUpnlRoute: extendedPositions length must be 1");
43147
44279
  routes.push({
43148
44280
  type: "REALISE_PNL" /* REALISE_PNL */,
43149
- amount: safeUsdcWeb3Number(upnlUsed),
43150
- instrument: this._budget.extendedPositions[0].instrument,
44281
+ amount: safeUsdcWeb3Number(usableRaw),
44282
+ instrument: this._budget.extendedPositionsView[0].instrument,
43151
44283
  priority: routes.length
43152
44284
  });
43153
- return { routes, remaining: tryAmount - upnlUsed };
44285
+ return { routes, remaining: tryAmount - usableRaw };
43154
44286
  }
43155
44287
  // ── Sub-classifiers ────────────────────────────────────────────────────
43156
44288
  // Each sub-classifier builds routes directly from contextual data.
@@ -43165,7 +44297,7 @@ var ExtendedSVKVesuStateManager = class {
43165
44297
  const instrument = this._config.extendedAdapter.config.extendedMarketName ?? "BTC-USD";
43166
44298
  const routes = [];
43167
44299
  let remaining = withdrawAmount.toNumber();
43168
- const vaUsed = this._budget.spendVA(Math.min(this._budget.vaUsd, remaining));
44300
+ const vaUsed = this._budget.spendVA(remaining);
43169
44301
  remaining -= vaUsed;
43170
44302
  let totalExtUsed = 0;
43171
44303
  if (remaining > CASE_THRESHOLD_USD) {
@@ -43188,48 +44320,90 @@ var ExtendedSVKVesuStateManager = class {
43188
44320
  totalExtUsed = usableWithrawAmount + upnlUsed;
43189
44321
  }
43190
44322
  if (remaining > CASE_THRESHOLD_USD) {
43191
- const avgCollPrice = this._budget.vesuPoolStates[0]?.collateralPrice ?? 1;
43192
- assert(this._budget.vesuPoolStates.length == 1, "SolveBudget::_classifyWithdrawal: vesuPoolStates length must be 1");
43193
- const { vesuPositionDelta, extendedPositionDelta, vesuAllocationUsd, extendedAllocationUsd } = this._computeAllocationSplit(new Web3Number((-remaining).toFixed(6), USDC_TOKEN_DECIMALS));
43194
- if (vesuPositionDelta < 0) {
43195
- const vesuAdapter = this._config.vesuAdapters[0];
43196
- const debtDelta = Math.min(0, vesuPositionDelta * avgCollPrice - vesuAllocationUsd.toNumber());
43197
- const withdrawAmount2 = vesuAllocationUsd.dividedBy(avgCollPrice);
43198
- withdrawAmount2.decimals = vesuAdapter.config.collateral.decimals;
44323
+ assert(this._budget.vesuPools.length == 1, "SolveBudget::_classifyWithdrawal: vesuPoolStates length must be 1");
44324
+ const vesuAdapter = this._config.vesuAdapters[0];
44325
+ const avgCollPrice = this._budget.vesuPools[0]?.collateralPrice ?? 1;
44326
+ const vesuLeverage = calculateVesuLeverage();
44327
+ const extLeverage = calculateExtendedLevergae();
44328
+ const freedPerBtcVesu = avgCollPrice / vesuLeverage;
44329
+ const freedPerBtcExt = avgCollPrice / extLeverage;
44330
+ const vesuColBtc = this._budget.vesuPools[0].collateralAmount.toNumber();
44331
+ const extPosBtc = this._totalExtendedExposure().toNumber();
44332
+ let stillNeeded = remaining;
44333
+ let vesuBtcDelta = 0;
44334
+ let extBtcDelta = 0;
44335
+ let extFreed = 0;
44336
+ const roundUpBtc = (x) => {
44337
+ const factor = 10 ** COLLATERAL_PRECISION;
44338
+ return Math.ceil(x * factor) / factor;
44339
+ };
44340
+ const diff = vesuColBtc - extPosBtc;
44341
+ let currentVesuBtc = vesuColBtc;
44342
+ let currentExtBtc = extPosBtc;
44343
+ if (Math.abs(diff) > 1e-8) {
44344
+ if (diff > 0) {
44345
+ const btcRaw = stillNeeded / freedPerBtcVesu;
44346
+ const btc = Math.min(roundUpBtc(Math.min(Math.abs(diff), btcRaw, currentVesuBtc)), currentVesuBtc);
44347
+ vesuBtcDelta += btc;
44348
+ stillNeeded -= btc * freedPerBtcVesu;
44349
+ currentVesuBtc -= btc;
44350
+ } else {
44351
+ const btcRaw = stillNeeded / freedPerBtcExt;
44352
+ const btc = Math.min(roundUpBtc(Math.min(Math.abs(diff), btcRaw, currentExtBtc)), currentExtBtc);
44353
+ extBtcDelta += btc;
44354
+ extFreed += btc * freedPerBtcExt;
44355
+ stillNeeded -= btc * freedPerBtcExt;
44356
+ currentExtBtc -= btc;
44357
+ }
44358
+ }
44359
+ if (stillNeeded > CASE_THRESHOLD_USD) {
44360
+ const combinedFreed = freedPerBtcVesu + freedPerBtcExt;
44361
+ const maxBtc = Math.min(currentVesuBtc, currentExtBtc);
44362
+ const btcRaw = stillNeeded / combinedFreed;
44363
+ const btc = Math.min(roundUpBtc(Math.min(btcRaw, maxBtc)), maxBtc);
44364
+ vesuBtcDelta += btc;
44365
+ extBtcDelta += btc;
44366
+ extFreed += btc * freedPerBtcExt;
44367
+ }
44368
+ const r6 = (n) => Number(n.toFixed(6));
44369
+ if (vesuBtcDelta > 0) {
44370
+ const totalVesuBtcSigned = -vesuBtcDelta;
44371
+ const targetLtv = 1 - 1 / vesuLeverage;
44372
+ const debtDelta = r6(totalVesuBtcSigned * avgCollPrice * targetLtv);
44373
+ const marginBtc = 0;
44374
+ const swappedBtc = Number(vesuBtcDelta.toFixed(COLLATERAL_PRECISION));
43199
44375
  routes.push({
43200
44376
  type: "VESU_MULTIPLY_DECREASE_LEVER" /* VESU_MULTIPLY_DECREASE_LEVER */,
43201
44377
  poolId: vesuAdapter.config.poolId,
43202
44378
  collateralToken: vesuAdapter.config.collateral,
43203
- marginAmount: withdrawAmount2,
43204
- swappedCollateralAmount: new Web3Number(vesuPositionDelta, vesuAdapter.config.collateral.decimals).minus(withdrawAmount2),
44379
+ marginAmount: new Web3Number(marginBtc.toFixed(COLLATERAL_PRECISION), vesuAdapter.config.collateral.decimals),
44380
+ swappedCollateralAmount: new Web3Number(swappedBtc.toFixed(COLLATERAL_PRECISION), vesuAdapter.config.collateral.decimals),
43205
44381
  debtToken: vesuAdapter.config.debt,
43206
44382
  debtAmount: new Web3Number(debtDelta, USDC_TOKEN_DECIMALS),
43207
44383
  priority: routes.length
43208
44384
  });
43209
- this._budget.applyVesuDelta(vesuAdapter.config.poolId, vesuAdapter.config.collateral, vesuAdapter.config.debt, new Web3Number(vesuPositionDelta, USDC_TOKEN_DECIMALS), new Web3Number(debtDelta, USDC_TOKEN_DECIMALS));
43210
- if (vesuAllocationUsd.toNumber() < -CASE_THRESHOLD_USD) {
43211
- const swapAmount = new Web3Number((vesuAllocationUsd.toNumber() / avgCollPrice * 0.998).toFixed(6), USDC_TOKEN_DECIMALS);
43212
- routes.push({
43213
- type: "AVNU_WITHDRAW_SWAP" /* AVNU_WITHDRAW_SWAP */,
43214
- fromToken: vesuAdapter.config.collateral.symbol,
43215
- // add buffer to avoid rounding errors
43216
- fromAmount: swapAmount,
43217
- toToken: vesuAdapter.config.debt.symbol,
43218
- priority: routes.length
43219
- });
43220
- this._budget.addToVA(vesuAllocationUsd.abs().toNumber());
43221
- }
44385
+ this._budget.applyVesuDelta(
44386
+ vesuAdapter.config.poolId,
44387
+ vesuAdapter.config.collateral,
44388
+ vesuAdapter.config.debt,
44389
+ new Web3Number(r6(totalVesuBtcSigned), USDC_TOKEN_DECIMALS),
44390
+ new Web3Number(debtDelta, USDC_TOKEN_DECIMALS)
44391
+ );
44392
+ this._budget.addToVA(vesuBtcDelta * freedPerBtcVesu);
43222
44393
  }
43223
- if (extendedPositionDelta < 0) {
44394
+ if (extBtcDelta > 0) {
43224
44395
  routes.push({
43225
44396
  type: "EXTENDED_DECREASE_LEVER" /* EXTENDED_DECREASE_LEVER */,
43226
- amount: safeUsdcWeb3Number(extendedPositionDelta),
44397
+ amount: safeUsdcWeb3Number(-r6(extBtcDelta)),
43227
44398
  instrument,
43228
44399
  priority: routes.length
43229
44400
  });
43230
- this._budget.applyExtendedExposureDelta(instrument, safeUsdcWeb3Number(extendedPositionDelta));
43231
- this._budget.addToExtAvailTrade(extendedAllocationUsd.abs().toNumber());
43232
- totalExtUsed += extendedAllocationUsd.abs().toNumber();
44401
+ this._budget.applyExtendedExposureDelta(
44402
+ instrument,
44403
+ safeUsdcWeb3Number(-r6(extBtcDelta)),
44404
+ avgCollPrice
44405
+ );
44406
+ totalExtUsed += extFreed;
43233
44407
  }
43234
44408
  }
43235
44409
  if (totalExtUsed > CASE_THRESHOLD_USD) {
@@ -43262,56 +44436,231 @@ var ExtendedSVKVesuStateManager = class {
43262
44436
  *
43263
44437
  * Design: accumulate all ext-to-wallet moves, add transfer routes at the end (principle #3).
43264
44438
  */
43265
- _classifyLtvVesu() {
43266
- const debtDeltaSum = this._budget.vesuPerPoolDebtDeltasToBorrow.reduce(
43267
- (a, b) => a + b.toNumber(),
43268
- 0
44439
+ /**
44440
+ * Unified LTV classifier. Computes both Vesu repay and Extended margin needs,
44441
+ * then builds all routes in a single pass with no duplicate transfers.
44442
+ *
44443
+ * Vesu repay priority: VA > Wallet > ExtAvl > ExtUpnl
44444
+ * Extended margin priority: Wallet > VA > VesuBorrow
44445
+ * Shared sources consumed by Vesu first (higher priority).
44446
+ */
44447
+ _classifyLTV() {
44448
+ assert(this._budget.vesuPools.length === 1, `${this._tag}::_classifyLTV expects exactly one Vesu pool`);
44449
+ const d = rebalance(this._ltvRebalanceInputsFromBudget());
44450
+ if (this._isLtvRebalanceNoop(d)) return [];
44451
+ logger.info(
44452
+ `${this._tag}::_classifyLTV deltas extPos=${d.dExtPosition} vesuPos=${d.dVesuPosition} vesuDebt=${d.dVesuDebt} va=${d.dVaUsd} wallet=${d.dWalletUsd} borrow=${d.dVesuBorrowCapacity} T=${d.dTransferVesuToExt}`
43269
44453
  );
43270
- logger.info(`${this._tag}::_classifyLtvVesu debtDeltaSum: ${debtDeltaSum}`);
43271
- const allHealthLTVs = this._budget.shouldVesuRebalance.every((shouldReb) => !shouldReb);
43272
- if (debtDeltaSum >= -CASE_THRESHOLD_USD || allHealthLTVs) return [];
43273
- const needed = Math.abs(debtDeltaSum);
43274
- const routes = [];
43275
- let remaining = needed;
43276
- let totalExtUsed = 0;
43277
- let caseId = "LTV_VESU_HIGH_USE_VA_OR_WALLET" /* LTV_VESU_HIGH_USE_VA_OR_WALLET */;
43278
- const vaUsed = this._budget.spendVA(Math.min(this._budget.vaUsd, remaining));
43279
- remaining -= vaUsed;
43280
- if (remaining > CASE_THRESHOLD_USD) {
43281
- const { remaining: walletToVaRemaining } = this._getWALLETToVARoute(remaining, routes);
43282
- remaining = walletToVaRemaining;
43283
- }
43284
- if (remaining > CASE_THRESHOLD_USD) {
43285
- const usableWithdrawAmount = Math.min(remaining, this._budget.extAvailWithdraw);
43286
- remaining -= usableWithdrawAmount;
43287
- let upnlUsed = 0;
43288
- if (remaining > CASE_THRESHOLD_USD) {
43289
- const { remaining: upnlRemaining } = this._getUpnlRoute(remaining, routes);
43290
- upnlUsed = remaining - upnlRemaining;
43291
- remaining = upnlRemaining;
43292
- caseId = "LTV_EXTENDED_PROFITABLE_REALIZE" /* LTV_EXTENDED_PROFITABLE_REALIZE */;
43293
- } else {
43294
- caseId = "LTV_EXTENDED_PROFITABLE_AVAILABLE" /* LTV_EXTENDED_PROFITABLE_AVAILABLE */;
43295
- }
43296
- totalExtUsed = usableWithdrawAmount + upnlUsed;
43297
- }
43298
- if (remaining > CASE_THRESHOLD_USD) {
43299
- throw new Error(`${this._tag}: Insufficient funds to cover margin needs`);
43300
- }
43301
- if (totalExtUsed > CASE_THRESHOLD_USD) {
43302
- this._getExtendedToWalletRoute(totalExtUsed, routes);
43303
- this._getWALLETToVARoute(totalExtUsed, routes);
43304
- }
43305
- this._buildVesuRepayRoutes(needed - remaining, routes);
44454
+ const routes = this._buildLtvRoutesFromRebalanceDeltas(d);
44455
+ if (routes.length === 0) return [];
44456
+ const amountUsd = Math.abs(d.dVaUsd) + Math.abs(d.dWalletUsd) + Math.abs(d.dVesuBorrowCapacity) + Math.abs(d.dExtAvlWithdraw) + Math.abs(d.dExtUpnl) + Math.abs(d.dTransferVesuToExt);
43306
44457
  routes.forEach((r, i) => {
43307
44458
  r.priority = i;
43308
44459
  });
43309
44460
  return [{
43310
- case: CASE_DEFINITIONS[caseId],
43311
- additionalArgs: { amount: safeUsdcWeb3Number(needed) },
44461
+ case: CASE_DEFINITIONS["MANAGE_LTV" /* MANAGE_LTV */],
44462
+ additionalArgs: { amount: safeUsdcWeb3Number(amountUsd) },
43312
44463
  routes
43313
44464
  }];
43314
44465
  }
44466
+ _ltvRebalanceInputsFromBudget() {
44467
+ const pool = this._budget.vesuPools[0];
44468
+ const instrument = this._config.extendedAdapter.config.extendedMarketName ?? "BTC-USD";
44469
+ const extPosBtc = this._budget.extendedPositionsView.filter((p) => p.instrument === instrument).reduce((s, p) => s + Math.abs(p.size.toNumber()), 0);
44470
+ const targetHF = VesuConfig.maxLtv / VesuConfig.targetLtv;
44471
+ return {
44472
+ ext: {
44473
+ positionBtc: extPosBtc,
44474
+ equity: this._budget.extendedBalanceView?.equity?.toNumber() ?? 0,
44475
+ avlWithdraw: this._budget.extAvailWithdraw,
44476
+ upnl: this._budget.extAvailUpnl,
44477
+ leverage: calculateExtendedLevergae()
44478
+ },
44479
+ vesu: {
44480
+ positionBtc: pool.collateralAmount.toNumber(),
44481
+ debt: pool.debtAmount.toNumber(),
44482
+ debtPrice: pool.debtPrice,
44483
+ maxLTV: VesuConfig.maxLtv,
44484
+ targetHF
44485
+ },
44486
+ btcPrice: pool.collateralPrice,
44487
+ funding: {
44488
+ vaUsd: this._budget.vaUsd,
44489
+ walletUsd: this._budget.walletUsd,
44490
+ vesuBorrowCapacity: this._budget.vesuBorrowCapacity,
44491
+ extAvlWithdraw: this._budget.extAvailWithdraw,
44492
+ extUpnl: this._budget.extAvailUpnl
44493
+ },
44494
+ config: {
44495
+ positionPrecision: COLLATERAL_PRECISION,
44496
+ hfBuffer: 0.05
44497
+ }
44498
+ };
44499
+ }
44500
+ _isLtvRebalanceNoop(d) {
44501
+ const btcEps = 10 ** -COLLATERAL_PRECISION;
44502
+ const usdEps = CASE_THRESHOLD_USD;
44503
+ return Math.abs(d.dExtPosition) < btcEps && Math.abs(d.dVesuPosition) < btcEps && Math.abs(d.dVesuDebt) < 1e-6 && Math.abs(d.dExtAvlWithdraw) < usdEps && Math.abs(d.dExtUpnl) < usdEps && Math.abs(d.dVaUsd) < usdEps && Math.abs(d.dWalletUsd) < usdEps && Math.abs(d.dVesuBorrowCapacity) < usdEps && Math.abs(d.dTransferVesuToExt) < usdEps;
44504
+ }
44505
+ /**
44506
+ * Turn pure rebalance() deltas into execution routes.
44507
+ * Order: Vesu multiply (decrease/increase) → Extended lever → aggregated transfers
44508
+ * (REALISE_PNL, EXTENDED_TO_WALLET + WAIT, WALLET_TO_VA, VESU_BORROW, VESU_REPAY, VA_TO_EXTENDED).
44509
+ */
44510
+ _buildLtvRoutesFromRebalanceDeltas(d) {
44511
+ const routes = [];
44512
+ const pool = this._budget.vesuPools[0];
44513
+ const vesuAdapter = this._config.vesuAdapters[0];
44514
+ const instrument = this._config.extendedAdapter.config.extendedMarketName ?? "BTC-USD";
44515
+ const price = pool.collateralPrice;
44516
+ const debtPrice = pool.debtPrice;
44517
+ const targetLtv = VesuConfig.targetLtv;
44518
+ const btcEps = 10 ** -COLLATERAL_PRECISION;
44519
+ let multiplyDebtRepayUsd = 0;
44520
+ if (d.dVesuPosition < -btcEps) {
44521
+ const xBtc = -d.dVesuPosition;
44522
+ const transferUsdFromVesu = Math.max(0, d.dTransferVesuToExt);
44523
+ let marginBtc = 0;
44524
+ let swappedBtc = Number(xBtc.toFixed(COLLATERAL_PRECISION));
44525
+ if (transferUsdFromVesu > CASE_THRESHOLD_USD && price > 0) {
44526
+ const marginCapFromTransfer = transferUsdFromVesu / price;
44527
+ marginBtc = Number(
44528
+ Math.min(xBtc, marginCapFromTransfer).toFixed(COLLATERAL_PRECISION)
44529
+ );
44530
+ swappedBtc = Number((xBtc - marginBtc).toFixed(COLLATERAL_PRECISION));
44531
+ }
44532
+ const swapLegMaxRepayUsd = swappedBtc * price * debtPrice;
44533
+ const debtUsdFallback = swappedBtc * price * targetLtv;
44534
+ let debtTokenDelta;
44535
+ if (d.dVesuDebt < 0) {
44536
+ const needRepayUsd = -d.dVesuDebt * debtPrice;
44537
+ const multiplyRepayUsd = Math.min(needRepayUsd, swapLegMaxRepayUsd);
44538
+ debtTokenDelta = -(multiplyRepayUsd / debtPrice);
44539
+ } else {
44540
+ debtTokenDelta = -debtUsdFallback;
44541
+ }
44542
+ const debtAmtW3 = new Web3Number(debtTokenDelta.toFixed(USDC_TOKEN_DECIMALS), USDC_TOKEN_DECIMALS);
44543
+ multiplyDebtRepayUsd = Math.abs(debtTokenDelta) * debtPrice;
44544
+ routes.push({
44545
+ type: "VESU_MULTIPLY_DECREASE_LEVER" /* VESU_MULTIPLY_DECREASE_LEVER */,
44546
+ poolId: vesuAdapter.config.poolId,
44547
+ collateralToken: vesuAdapter.config.collateral,
44548
+ marginAmount: new Web3Number(marginBtc.toFixed(COLLATERAL_PRECISION), vesuAdapter.config.collateral.decimals),
44549
+ swappedCollateralAmount: new Web3Number(swappedBtc.toFixed(COLLATERAL_PRECISION), vesuAdapter.config.collateral.decimals),
44550
+ debtToken: vesuAdapter.config.debt,
44551
+ debtAmount: debtAmtW3,
44552
+ priority: routes.length
44553
+ });
44554
+ this._budget.applyVesuDelta(
44555
+ vesuAdapter.config.poolId,
44556
+ vesuAdapter.config.collateral,
44557
+ vesuAdapter.config.debt,
44558
+ new Web3Number((-xBtc).toFixed(COLLATERAL_PRECISION), vesuAdapter.config.collateral.decimals),
44559
+ debtAmtW3
44560
+ );
44561
+ if (transferUsdFromVesu > CASE_THRESHOLD_USD) {
44562
+ this._budget.addToVA(transferUsdFromVesu);
44563
+ }
44564
+ } else if (d.dVesuPosition > btcEps) {
44565
+ const vesuDepositAmount = new Web3Number(
44566
+ (d.dVesuPosition * price * (1 - targetLtv)).toFixed(USDC_TOKEN_DECIMALS),
44567
+ USDC_TOKEN_DECIMALS
44568
+ );
44569
+ if (vesuDepositAmount.toNumber() > CASE_THRESHOLD_USD) {
44570
+ routes.push({
44571
+ type: "AVNU_DEPOSIT_SWAP" /* AVNU_DEPOSIT_SWAP */,
44572
+ priority: routes.length,
44573
+ fromToken: vesuAdapter.config.collateral.symbol,
44574
+ fromAmount: vesuDepositAmount,
44575
+ toToken: vesuAdapter.config.debt.symbol
44576
+ });
44577
+ }
44578
+ const collateralDelta = new Web3Number(
44579
+ d.dVesuPosition.toFixed(COLLATERAL_PRECISION),
44580
+ vesuAdapter.config.collateral.decimals
44581
+ );
44582
+ const availableBorrowCapacity = this._budget.vesuBorrowCapacity;
44583
+ const externalDepositAmount = vesuDepositAmount.minus(
44584
+ new Web3Number(Math.min(availableBorrowCapacity, vesuDepositAmount.toNumber()).toFixed(USDC_TOKEN_DECIMALS), USDC_TOKEN_DECIMALS)
44585
+ );
44586
+ const collPx = pool.collateralPrice || 1;
44587
+ const swappedAmount = new Web3Number(
44588
+ (externalDepositAmount.toNumber() * (pool.debtPrice ?? 1) / collPx).toFixed(6),
44589
+ vesuAdapter.config.collateral.decimals
44590
+ );
44591
+ const debtDeltaTokens = new Web3Number(
44592
+ d.dVesuDebt.toFixed(USDC_TOKEN_DECIMALS),
44593
+ USDC_TOKEN_DECIMALS
44594
+ );
44595
+ routes.push({
44596
+ type: "VESU_MULTIPLY_INCREASE_LEVER" /* VESU_MULTIPLY_INCREASE_LEVER */,
44597
+ priority: routes.length,
44598
+ collateralToken: vesuAdapter.config.collateral,
44599
+ debtToken: vesuAdapter.config.debt,
44600
+ marginAmount: swappedAmount,
44601
+ swappedCollateralAmount: collateralDelta.minus(swappedAmount),
44602
+ debtAmount: debtDeltaTokens.plus(new Web3Number(Math.min(availableBorrowCapacity, vesuDepositAmount.toNumber()).toFixed(USDC_TOKEN_DECIMALS), USDC_TOKEN_DECIMALS)),
44603
+ poolId: vesuAdapter.config.poolId
44604
+ });
44605
+ this._budget.applyVesuDelta(
44606
+ vesuAdapter.config.poolId,
44607
+ vesuAdapter.config.collateral,
44608
+ vesuAdapter.config.debt,
44609
+ collateralDelta,
44610
+ debtDeltaTokens
44611
+ );
44612
+ }
44613
+ if (d.dExtPosition < -btcEps) {
44614
+ const amt = new Web3Number(d.dExtPosition.toFixed(COLLATERAL_PRECISION), 8);
44615
+ routes.push({
44616
+ type: "EXTENDED_DECREASE_LEVER" /* EXTENDED_DECREASE_LEVER */,
44617
+ amount: amt,
44618
+ instrument,
44619
+ priority: routes.length
44620
+ });
44621
+ this._budget.applyExtendedExposureDelta(instrument, amt, price);
44622
+ } else if (d.dExtPosition > btcEps) {
44623
+ const amt = new Web3Number(d.dExtPosition.toFixed(COLLATERAL_PRECISION), 8);
44624
+ routes.push({
44625
+ type: "EXTENDED_INCREASE_LEVER" /* EXTENDED_INCREASE_LEVER */,
44626
+ amount: amt,
44627
+ instrument,
44628
+ priority: routes.length
44629
+ });
44630
+ this._budget.applyExtendedExposureDelta(instrument, amt, price);
44631
+ }
44632
+ const negUpnl = Math.min(0, d.dExtUpnl);
44633
+ const negExtAvl = Math.min(0, d.dExtAvlWithdraw);
44634
+ let hadExtendedOut = false;
44635
+ if (negUpnl < -CASE_THRESHOLD_USD) {
44636
+ this._getUpnlRoute(Math.abs(negUpnl), routes);
44637
+ hadExtendedOut = true;
44638
+ }
44639
+ const extToWalletUsd = (negExtAvl < -CASE_THRESHOLD_USD ? Math.abs(negExtAvl) : 0) + (negUpnl < -CASE_THRESHOLD_USD ? Math.abs(negUpnl) : 0);
44640
+ if (extToWalletUsd > CASE_THRESHOLD_USD) {
44641
+ this._getExtendedToWalletRoute(extToWalletUsd, routes);
44642
+ hadExtendedOut = true;
44643
+ }
44644
+ const walletPull = Math.abs(Math.min(0, d.dWalletUsd));
44645
+ const walletToVaUsd = walletPull + extToWalletUsd;
44646
+ if (walletToVaUsd > CASE_THRESHOLD_USD) {
44647
+ this._getWALLETToVARoute(walletToVaUsd, routes);
44648
+ }
44649
+ if (d.dVesuBorrowCapacity < -CASE_THRESHOLD_USD) {
44650
+ this._buildVesuBorrowRoutes(Math.abs(d.dVesuBorrowCapacity), routes);
44651
+ }
44652
+ const totalDebtRepayUsd = d.dVesuDebt < 0 ? -d.dVesuDebt * debtPrice : 0;
44653
+ const standaloneRepayUsd = Math.max(0, totalDebtRepayUsd - multiplyDebtRepayUsd);
44654
+ if (standaloneRepayUsd > CASE_THRESHOLD_USD) {
44655
+ this._buildVesuRepayRoutes(standaloneRepayUsd, routes);
44656
+ }
44657
+ const posExtEq = Math.max(0, d.dExtAvlWithdraw);
44658
+ const vaToExtUsd = posExtEq > CASE_THRESHOLD_USD ? posExtEq : 0;
44659
+ if (vaToExtUsd > CASE_THRESHOLD_USD) {
44660
+ this._getVAToEXTENDEDRoute(vaToExtUsd, routes, hadExtendedOut);
44661
+ }
44662
+ return routes;
44663
+ }
43315
44664
  // ── LTV Vesu route builders ───────────────────────────────────────────
43316
44665
  /**
43317
44666
  * LTV_EXTENDED_PROFITABLE_AVAILABLE:
@@ -43388,60 +44737,7 @@ var ExtendedSVKVesuStateManager = class {
43388
44737
  // routes.forEach((r, i) => { r.priority = i; });
43389
44738
  // return routes;
43390
44739
  // }
43391
- /** 2b. LTV Rebalance Extended side */
43392
- /**
43393
- * 2b. LTV Rebalance — Extended side
43394
- *
43395
- * Triggered when Extended equity is below the required margin for current positions.
43396
- * Sources funds to Extended via VA/Wallet or Vesu borrow.
43397
- *
43398
- * Priority: 1) VA/Wallet → Extended 2) Vesu borrow → VA → Extended
43399
- */
43400
- _classifyLtvExtended() {
43401
- const totalExtPosUsd = this._totalExtendedExposureUsd().toNumber();
43402
- const extEquity = this._budget.extendedBalance?.equity?.toNumber() ?? 0;
43403
- const lev = calculateExtendedLevergae();
43404
- const marginNeeded = lev > 0 ? totalExtPosUsd / lev - extEquity : 0;
43405
- if (marginNeeded <= CASE_THRESHOLD_USD) return [];
43406
- let caseId = "LTV_EXTENDED_HIGH_USE_VA_OR_WALLET" /* LTV_EXTENDED_HIGH_USE_VA_OR_WALLET */;
43407
- let remaining = marginNeeded;
43408
- const routes = [];
43409
- if (this._budget.vaWalletUsd > CASE_THRESHOLD_USD && remaining > CASE_THRESHOLD_USD) {
43410
- const use = Math.min(this._budget.vaWalletUsd, remaining);
43411
- if (this._budget.vaUsd > CASE_THRESHOLD_USD) {
43412
- const { remaining: vaRem } = this._getVAToEXTENDEDRoute(remaining, routes, false);
43413
- remaining = vaRem;
43414
- }
43415
- if (remaining > CASE_THRESHOLD_USD) {
43416
- const { remaining: walletRem } = this._getWalletToEXTENDEDRoute(remaining, routes, false);
43417
- remaining = walletRem;
43418
- }
43419
- }
43420
- if (remaining > CASE_THRESHOLD_USD && this._budget.vesuBorrowCapacity > CASE_THRESHOLD_USD) {
43421
- const { remaining: borrowRem } = this._buildVesuBorrowRoutes(Math.min(remaining, this._budget.vesuBorrowCapacity), routes);
43422
- const borrowed = remaining - borrowRem;
43423
- if (remaining != borrowRem) {
43424
- const { remaining: vaRem } = this._getVAToEXTENDEDRoute(borrowed, routes, false);
43425
- }
43426
- remaining = borrowRem;
43427
- routes.forEach((r, i) => {
43428
- r.priority = i;
43429
- });
43430
- remaining -= borrowed;
43431
- caseId = "LTV_VESU_LOW_TO_EXTENDED" /* LTV_VESU_LOW_TO_EXTENDED */;
43432
- }
43433
- if (remaining > CASE_THRESHOLD_USD) {
43434
- throw new Error(`${this._tag}: Insufficient funds to cover margin needs`);
43435
- }
43436
- routes.forEach((r, i) => {
43437
- r.priority = i;
43438
- });
43439
- return [{
43440
- case: CASE_DEFINITIONS[caseId],
43441
- additionalArgs: { amount: safeUsdcWeb3Number(marginNeeded) },
43442
- routes
43443
- }];
43444
- }
44740
+ // _classifyLtvExtended has been merged into the unified _classifyLTV above.
43445
44741
  // ── LTV Extended route builders ───────────────────────────────────────
43446
44742
  /**
43447
44743
  * LTV_EXTENDED_HIGH_USE_VA_OR_WALLET:
@@ -43508,6 +44804,62 @@ var ExtendedSVKVesuStateManager = class {
43508
44804
  // return routes;
43509
44805
  // }
43510
44806
  // ! todo implement max lever amount per execution cycle
44807
+ _rebalanceFunds({
44808
+ extAvlWithdraw,
44809
+ extUpnl,
44810
+ vaUsd,
44811
+ walletUsd,
44812
+ vesuBorrowCapacity,
44813
+ vesuLeverage,
44814
+ extendedLeverage
44815
+ }) {
44816
+ const total = extAvlWithdraw + extUpnl + vaUsd + walletUsd + vesuBorrowCapacity;
44817
+ const extendedTarget = total / (1 + extendedLeverage / vesuLeverage);
44818
+ const extendedInitial = extAvlWithdraw + extUpnl;
44819
+ let delta = extendedTarget - extendedInitial;
44820
+ let dExtAvlWithdraw = 0, dExtUpnl = 0, dVaUsd = 0, dWalletUsd = 0, dVesuBorrowCapacity = 0;
44821
+ if (delta > 0) {
44822
+ let need = delta;
44823
+ const takeWalletUsd = Math.min(walletUsd, need);
44824
+ dWalletUsd -= takeWalletUsd;
44825
+ need -= takeWalletUsd;
44826
+ const takeVaUsd = Math.min(vaUsd, need);
44827
+ dVaUsd -= takeVaUsd;
44828
+ need -= takeVaUsd;
44829
+ const takeVesuBorrowCapacity = Math.min(vesuBorrowCapacity, need);
44830
+ dVesuBorrowCapacity -= takeVesuBorrowCapacity;
44831
+ need -= takeVesuBorrowCapacity;
44832
+ const received = delta - need;
44833
+ const eco1Sum = extAvlWithdraw + extUpnl;
44834
+ if (eco1Sum >= 0) {
44835
+ dExtAvlWithdraw += received;
44836
+ } else {
44837
+ throw new Error(`${this._tag}: Unexpected case`);
44838
+ }
44839
+ if (need > 0) {
44840
+ throw new Error(`${this._tag}: Insufficient funds to cover margin needs`);
44841
+ }
44842
+ } else if (delta < 0) {
44843
+ let need = -delta;
44844
+ const takeExtAvlWithdraw = Math.min(extAvlWithdraw, need);
44845
+ dExtAvlWithdraw -= takeExtAvlWithdraw;
44846
+ need -= takeExtAvlWithdraw;
44847
+ const takeExtUpnl = Math.min(extUpnl, need);
44848
+ dExtUpnl -= takeExtUpnl;
44849
+ need -= takeExtUpnl;
44850
+ const sent = -delta - need;
44851
+ const eco2Sum = vaUsd + walletUsd + vesuBorrowCapacity;
44852
+ if (eco2Sum >= 0) {
44853
+ dWalletUsd += sent;
44854
+ } else {
44855
+ throw new Error(`${this._tag}: Unexpected case`);
44856
+ }
44857
+ if (need > 0) {
44858
+ throw new Error(`${this._tag}: Insufficient funds to cover margin needs`);
44859
+ }
44860
+ }
44861
+ return { dExtAvlWithdraw, dExtUpnl, dVaUsd, dWalletUsd, dVesuBorrowCapacity, isExtendedToVesu: delta < 0 };
44862
+ }
43511
44863
  /**
43512
44864
  * 3. New Deposits / Excess Funds
43513
44865
  *
@@ -43522,81 +44874,84 @@ var ExtendedSVKVesuStateManager = class {
43522
44874
  * Computes allocation split between Vesu and Extended, then sources
43523
44875
  * funds and creates lever-increase routes.
43524
44876
  *
43525
- * Fund flow (principle #3accumulate transfers, defer wait):
43526
- * Phase A: fund Extended (wallet→ext, VA→ext, vesu-borrow→VA→ext)
43527
- * Phase B: fund Vesu VA shortfall (wallet→VA, ext→wallet + wallet→VA)
43528
- * Phase C: RETURN_TO_WAIT (if any transfer to Extended occurred)
43529
- * Phase D: lever routes (VESU_MULTIPLY, EXTENDED_INCREASE) near each other (#4)
44877
+ * Fund flow (single passavoid VA→Extended then Extended→wallet round-trips):
44878
+ * 1) Treat Vesu borrow headroom that the multiply route will consume as covering
44879
+ * part of the Vesu USDC need (no standalone VESU_BORROW for that slice). Cap
44880
+ * standalone VESU_BORROW→VA→Extended by remaining headroom.
44881
+ * 2) Cover Extended deposit delta: REALISE_PNL first, then withdrawal→avail-trade,
44882
+ * then wallet→Extended, then VA→Extended only up to VA surplus above the USDC
44883
+ * that must remain for Vesu (after step 1), then borrow→VA→Extended.
44884
+ * 3) Cover Vesu VA shortfall: wallet→VA, Extended withdrawal→wallet→VA, REALISE_PNL,
44885
+ * then combined Extended→wallet→VA for the remainder.
44886
+ * 4) RETURN_TO_WAIT when needed; then AVNU + VESU_MULTIPLY + EXTENDED_INCREASE.
44887
+ */
44888
+ /**
44889
+ * @param skipAvnuDepositSwap Omit AVNU before Vesu multiply when LTV cases already ran this cycle
44890
+ * (matrix tests expect deposit routes without that step).
43530
44891
  */
43531
- _classifyDeposits(withdrawAmount) {
44892
+ _classifyDeposits(withdrawAmount, skipAvnuDepositSwap = false) {
43532
44893
  if (withdrawAmount.toNumber() > CASE_THRESHOLD_USD) return [];
43533
44894
  const distributableAmount = this._computeDistributableAmount(
43534
- this._budget.vesuPerPoolDebtDeltasToBorrow,
44895
+ this._budget.vesuDebtDeltas,
43535
44896
  withdrawAmount
43536
44897
  );
43537
44898
  if (distributableAmount.toNumber() <= CASE_THRESHOLD_USD) return [];
43538
44899
  const { vesuAllocationUsd, extendedAllocationUsd } = this._computeAllocationSplit(distributableAmount);
43539
44900
  const vesuDeltas = this._computePerPoolCollateralDeltas(
43540
- vesuAllocationUsd,
43541
- this._budget.vesuPerPoolDebtDeltasToBorrow
44901
+ vesuAllocationUsd
43542
44902
  );
43543
44903
  const extendedPositionDeltas = this._computeExtendedPositionDeltas(vesuDeltas);
43544
- const extendedDepositDelta = this._computeExtendedDepositDelta(extendedAllocationUsd);
43545
44904
  const vesuDepositAmount = this._computeVesuDepositAmount(vesuDeltas);
44905
+ const vesuLeverage = calculateVesuLeverage();
44906
+ const extendedLeverage = calculateExtendedLevergae();
44907
+ const { dExtAvlWithdraw, dExtUpnl, dVaUsd, dWalletUsd, dVesuBorrowCapacity, isExtendedToVesu } = this._rebalanceFunds({
44908
+ extAvlWithdraw: this._budget.extAvailWithdraw,
44909
+ extUpnl: this._budget.extAvailUpnl,
44910
+ vaUsd: this._budget.vaUsd,
44911
+ walletUsd: this._budget.walletUsd,
44912
+ vesuBorrowCapacity: this._budget.vesuBorrowCapacity,
44913
+ vesuLeverage,
44914
+ extendedLeverage
44915
+ });
43546
44916
  const routes = [];
43547
- let needsWait = false;
43548
- if (extendedDepositDelta.toNumber() > CASE_THRESHOLD_USD) {
43549
- let rem = extendedDepositDelta.toNumber();
43550
- if (rem > CASE_THRESHOLD_USD) {
43551
- const { remaining } = this._getWalletToEXTENDEDRoute(rem, routes, false);
43552
- if (remaining < rem) needsWait = true;
43553
- rem = remaining;
43554
- }
43555
- if (rem > CASE_THRESHOLD_USD) {
43556
- const { remaining } = this._getVAToEXTENDEDRoute(rem, routes, false);
43557
- if (remaining < rem) needsWait = true;
43558
- rem = remaining;
43559
- }
43560
- if (rem > CASE_THRESHOLD_USD && this._budget.vesuBorrowCapacity > CASE_THRESHOLD_USD) {
43561
- const { remaining: borrowRem } = this._buildVesuBorrowRoutes(rem, routes);
43562
- const borrowed = rem - borrowRem;
43563
- if (borrowRem != rem) {
43564
- this._getVAToEXTENDEDRoute(borrowed, routes, false);
43565
- needsWait = true;
43566
- rem = borrowRem;
43567
- }
44917
+ if (isExtendedToVesu) {
44918
+ if (dExtUpnl < 0) {
44919
+ this._getUpnlRoute(Math.abs(dExtUpnl), routes);
43568
44920
  }
43569
- }
43570
- if (vesuDepositAmount.toNumber() > CASE_THRESHOLD_USD) {
43571
- const vaShortfall = vesuDepositAmount.toNumber() - this._budget.vaUsd;
43572
- if (vaShortfall > CASE_THRESHOLD_USD) {
43573
- let rem = vaShortfall;
43574
- if (rem > CASE_THRESHOLD_USD && this._budget.walletUsd > CASE_THRESHOLD_USD) {
43575
- const { remaining } = this._getWalletToVARoute(Math.min(this._budget.walletUsd, rem), routes);
43576
- rem = remaining;
43577
- }
43578
- const isWithdrawalEnough = rem <= this._budget.extAvailWithdraw;
43579
- if (!isWithdrawalEnough && rem > CASE_THRESHOLD_USD) {
43580
- const { remaining: upnlRem } = this._getUpnlRoute(rem, routes);
43581
- rem = upnlRem;
43582
- }
43583
- if (rem > CASE_THRESHOLD_USD && this._budget.extAvailWithdraw > CASE_THRESHOLD_USD) {
43584
- const extUse = Math.min(rem, this._budget.extAvailWithdraw);
43585
- this._getExtendedToWalletRoute(extUse, routes);
43586
- this._getWALLETToVARoute(extUse, routes);
43587
- rem -= extUse;
43588
- needsWait = false;
43589
- }
44921
+ if (dExtUpnl < 0 || dExtAvlWithdraw < 0) {
44922
+ const netAmount = (dExtAvlWithdraw < 0 ? Math.abs(dExtAvlWithdraw) : 0) + (dExtUpnl < 0 ? Math.abs(dExtUpnl) : 0);
44923
+ const walletUsd = this._budget.walletUsd;
44924
+ this._getExtendedToWalletRoute(netAmount, routes);
44925
+ this._getWALLETToVARoute(netAmount + walletUsd, routes);
44926
+ }
44927
+ } else {
44928
+ let netDVaUsd = dVaUsd;
44929
+ if (dWalletUsd < 0) {
44930
+ this._getWalletToVARoute(this._budget.walletUsd, routes);
44931
+ netDVaUsd += dWalletUsd;
44932
+ }
44933
+ if (dVesuBorrowCapacity < 0) {
44934
+ this._buildVesuBorrowRoutes(Math.abs(dVesuBorrowCapacity), routes);
44935
+ netDVaUsd += dVesuBorrowCapacity;
44936
+ }
44937
+ if (netDVaUsd < 0) {
44938
+ this._getVAToEXTENDEDRoute(Math.abs(netDVaUsd), routes, true);
43590
44939
  }
43591
- }
43592
- if (needsWait) {
43593
- routes.push({ type: "RETURN_TO_WAIT" /* RETURN_TO_WAIT */, priority: routes.length });
43594
44940
  }
43595
44941
  for (const vesuDelta of vesuDeltas) {
43596
- if (vesuDepositAmount.toNumber() > CASE_THRESHOLD_USD) {
44942
+ if (!skipAvnuDepositSwap && vesuDepositAmount.toNumber() > CASE_THRESHOLD_USD) {
44943
+ routes.push({
44944
+ type: "AVNU_DEPOSIT_SWAP" /* AVNU_DEPOSIT_SWAP */,
44945
+ priority: routes.length,
44946
+ fromToken: vesuDelta.collateralToken.symbol,
44947
+ fromAmount: vesuDepositAmount,
44948
+ toToken: vesuDelta.debtToken.symbol
44949
+ });
43597
44950
  }
43598
44951
  if (vesuDelta.collateralDelta.toNumber() > 0) {
43599
- const swappedAmount = new Web3Number((vesuDepositAmount.toNumber() * vesuDelta.debtPrice / (vesuDelta.collateralPrice ?? 0)).toFixed(6), vesuDelta.collateralToken.decimals);
44952
+ const availableBorrowCapacity = this._budget.vesuBorrowCapacity;
44953
+ const externalDepositAmount = vesuDepositAmount.minus(availableBorrowCapacity);
44954
+ const swappedAmount = new Web3Number((externalDepositAmount.toNumber() * vesuDelta.debtPrice / (vesuDelta.collateralPrice ?? 0)).toFixed(6), vesuDelta.collateralToken.decimals);
43600
44955
  routes.push({
43601
44956
  type: "VESU_MULTIPLY_INCREASE_LEVER" /* VESU_MULTIPLY_INCREASE_LEVER */,
43602
44957
  priority: routes.length,
@@ -43605,7 +44960,7 @@ var ExtendedSVKVesuStateManager = class {
43605
44960
  marginAmount: swappedAmount,
43606
44961
  // should be the swapped amount as per vesu multiply adapter
43607
44962
  swappedCollateralAmount: vesuDelta.collateralDelta.minus(swappedAmount),
43608
- debtAmount: vesuDelta.debtDelta,
44963
+ debtAmount: vesuDelta.debtDelta.plus(availableBorrowCapacity),
43609
44964
  poolId: vesuDelta.poolId
43610
44965
  });
43611
44966
  }
@@ -43728,8 +45083,13 @@ var ExtendedSVKVesuStateManager = class {
43728
45083
  */
43729
45084
  _buildImbalanceExtExcessShortNoFundsRoutes(exposureDiffBtc) {
43730
45085
  const instrument = this._config.extendedAdapter.config.extendedMarketName ?? "BTC-USD";
43731
- const decDelta = new Web3Number(exposureDiffBtc.toFixed(COLLATERAL_PRECISION), USDC_TOKEN_DECIMALS);
43732
- this._budget.applyExtendedExposureDelta(instrument, new Web3Number(decDelta.negated().toFixed(COLLATERAL_PRECISION), USDC_TOKEN_DECIMALS));
45086
+ const decDelta = new Web3Number(Web3Number.fromNumber(exposureDiffBtc, 8).toFixedRoundDown(COLLATERAL_PRECISION), USDC_TOKEN_DECIMALS);
45087
+ const collPx = this._budget.vesuPools[0]?.collateralPrice ?? 1;
45088
+ this._budget.applyExtendedExposureDelta(
45089
+ instrument,
45090
+ new Web3Number(Web3Number.fromNumber(decDelta.negated().toNumber(), 8).toFixedRoundDown(COLLATERAL_PRECISION), USDC_TOKEN_DECIMALS),
45091
+ collPx
45092
+ );
43733
45093
  return [{
43734
45094
  type: "EXTENDED_DECREASE_LEVER" /* EXTENDED_DECREASE_LEVER */,
43735
45095
  amount: decDelta,
@@ -43795,13 +45155,15 @@ var ExtendedSVKVesuStateManager = class {
43795
45155
  _classifyCases(withdrawAmount) {
43796
45156
  this._budget.initBudget();
43797
45157
  const withdrawalCases = this._classifyWithdrawal(withdrawAmount);
43798
- const ltvVesuCases = this._classifyLtvVesu();
43799
- const ltvExtendedCases = this._classifyLtvExtended();
43800
- const depositCases = this._classifyDeposits(withdrawAmount);
45158
+ this._budget.applyBuffer(this._config.limitBalanceBufferFactor);
45159
+ const ltvCases = this._classifyLTV();
45160
+ const depositCases = this._classifyDeposits(
45161
+ withdrawAmount,
45162
+ ltvCases.length > 0
45163
+ );
43801
45164
  return [
43802
45165
  ...withdrawalCases,
43803
- ...ltvVesuCases,
43804
- ...ltvExtendedCases,
45166
+ ...ltvCases,
43805
45167
  ...depositCases
43806
45168
  ];
43807
45169
  }
@@ -43809,7 +45171,7 @@ var ExtendedSVKVesuStateManager = class {
43809
45171
  // Private — aggregation helpers
43810
45172
  // ═══════════════════════════════════════════════════════════════════════════
43811
45173
  _totalVesuCollateral() {
43812
- return this._budget.vesuPoolStates.reduce(
45174
+ return this._budget.vesuPools.reduce(
43813
45175
  (acc, pool) => acc.plus(
43814
45176
  pool.collateralAmount
43815
45177
  ),
@@ -43817,7 +45179,7 @@ var ExtendedSVKVesuStateManager = class {
43817
45179
  );
43818
45180
  }
43819
45181
  _totalVesuCollateralUsd() {
43820
- return this._budget.vesuPoolStates.reduce(
45182
+ return this._budget.vesuPools.reduce(
43821
45183
  (acc, pool) => acc.plus(
43822
45184
  pool.collateralAmount.multipliedBy(pool.collateralPrice)
43823
45185
  ),
@@ -43825,13 +45187,13 @@ var ExtendedSVKVesuStateManager = class {
43825
45187
  );
43826
45188
  }
43827
45189
  _totalExtendedExposure() {
43828
- return this._budget.extendedPositions.reduce(
45190
+ return this._budget.extendedPositionsView.reduce(
43829
45191
  (acc, position) => acc.plus(position.size),
43830
45192
  new Web3Number(0, USDC_TOKEN_DECIMALS)
43831
45193
  );
43832
45194
  }
43833
45195
  _totalExtendedExposureUsd() {
43834
- return this._budget.extendedPositions.reduce(
45196
+ return this._budget.extendedPositionsView.reduce(
43835
45197
  (acc, position) => acc.plus(position.valueUsd),
43836
45198
  new Web3Number(0, USDC_TOKEN_DECIMALS)
43837
45199
  );
@@ -43873,7 +45235,7 @@ var ExtendedSVKVesuStateManager = class {
43873
45235
  };
43874
45236
 
43875
45237
  // src/strategies/vesu-extended-strategy/services/executionService.ts
43876
- import { uint256 as uint25622 } from "starknet";
45238
+ import { uint256 as uint25623 } from "starknet";
43877
45239
 
43878
45240
  // src/strategies/vesu-extended-strategy/types/transaction-metadata.ts
43879
45241
  var CycleType = /* @__PURE__ */ ((CycleType2) => {
@@ -43918,11 +45280,10 @@ var _ExecutionService = class _ExecutionService {
43918
45280
  this._tokenSymbols = StarknetCallParser.buildTokenSymbolLookup([
43919
45281
  config.wbtcToken,
43920
45282
  config.usdcToken,
43921
- config.usdceToken,
43922
- config.vesuAdapter.config.baseToken,
43923
- config.vesuAdapter.config.collateral,
43924
- config.vesuAdapter.config.debt,
43925
- config.vesuAdapter.config.marginToken,
45283
+ config.vesuMultiplyAdapter.config.baseToken,
45284
+ config.vesuMultiplyAdapter.config.collateral,
45285
+ config.vesuMultiplyAdapter.config.debt,
45286
+ config.vesuMultiplyAdapter.config.marginToken,
43926
45287
  config.vesuModifyPositionAdapter.config.collateral,
43927
45288
  config.vesuModifyPositionAdapter.config.debt,
43928
45289
  ...avnuTokens
@@ -43930,19 +45291,18 @@ var _ExecutionService = class _ExecutionService {
43930
45291
  this._tokenDecimals = StarknetCallParser.buildTokenDecimalsLookup([
43931
45292
  config.wbtcToken,
43932
45293
  config.usdcToken,
43933
- config.usdceToken,
43934
- config.vesuAdapter.config.baseToken,
43935
- config.vesuAdapter.config.collateral,
43936
- config.vesuAdapter.config.debt,
43937
- config.vesuAdapter.config.marginToken,
45294
+ config.vesuMultiplyAdapter.config.baseToken,
45295
+ config.vesuMultiplyAdapter.config.collateral,
45296
+ config.vesuMultiplyAdapter.config.debt,
45297
+ config.vesuMultiplyAdapter.config.marginToken,
43938
45298
  config.vesuModifyPositionAdapter.config.collateral,
43939
45299
  config.vesuModifyPositionAdapter.config.debt,
43940
45300
  ...avnuTokens
43941
45301
  ]);
43942
45302
  this._poolNames = StarknetCallParser.buildPoolNameLookup([
43943
45303
  {
43944
- poolId: config.vesuAdapter.config.poolId.toBigInt(),
43945
- name: `${config.vesuAdapter.config.collateral.symbol}/${config.vesuAdapter.config.debt.symbol}`
45304
+ poolId: config.vesuMultiplyAdapter.config.poolId.toBigInt(),
45305
+ name: `${config.vesuMultiplyAdapter.config.collateral.symbol}/${config.vesuMultiplyAdapter.config.debt.symbol}`
43946
45306
  },
43947
45307
  {
43948
45308
  poolId: config.vesuModifyPositionAdapter.config.poolId.toBigInt(),
@@ -44345,12 +45705,14 @@ var _ExecutionService = class _ExecutionService {
44345
45705
  *
44346
45706
  * For deposit (USDC→BTC): price = sum(USDC sold) / sum(BTC bought)
44347
45707
  * For withdraw (BTC→USDC): price = sum(USDC bought) / sum(BTC sold)
44348
- */
45708
+ * @returns no-swap means, logic is fine and explicit no swap is needed
45709
+ */
44349
45710
  _getNetExecutionPrice(isDeposit) {
44350
45711
  const prices = [
44351
45712
  this._config.avnuAdapter.lastSwapPriceInfo,
44352
- this._config.vesuAdapter.lastSwapPriceInfo
45713
+ this._config.vesuMultiplyAdapter.lastSwapPriceInfo
44353
45714
  ].filter((p) => p !== null);
45715
+ assert(prices.length <= 1, "Only one swap price info is allowed");
44354
45716
  if (prices.length === 0) return null;
44355
45717
  if (isDeposit) {
44356
45718
  const totalUsdc = prices.reduce((s, p) => s + p.fromAmount, 0);
@@ -44365,7 +45727,7 @@ var _ExecutionService = class _ExecutionService {
44365
45727
  /** Clears cached swap price info on all adapters to prevent stale data across cycles. */
44366
45728
  _clearAdapterPriceInfo() {
44367
45729
  this._config.avnuAdapter.lastSwapPriceInfo = null;
44368
- this._config.vesuAdapter.lastSwapPriceInfo = null;
45730
+ this._config.vesuMultiplyAdapter.lastSwapPriceInfo = null;
44369
45731
  }
44370
45732
  // ═══════════════════════════════════════════════════════════════════════════
44371
45733
  // Public API
@@ -44793,7 +46155,7 @@ var _ExecutionService = class _ExecutionService {
44793
46155
  }
44794
46156
  // ── Transfer routes ─────────────────────────────────────────────────────
44795
46157
  /**
44796
- * WALLET_TO_EXTENDED: Deposit USDC.e from operator wallet directly to Extended.
46158
+ * WALLET_TO_EXTENDED: Deposit USDC from operator wallet directly to Extended.
44797
46159
  *
44798
46160
  * Builds raw approve + deposit calls (NOT through the manager/merkle system)
44799
46161
  * because the wallet interacts with Extended directly, not via vault allocator.
@@ -44808,13 +46170,13 @@ var _ExecutionService = class _ExecutionService {
44808
46170
  );
44809
46171
  return [];
44810
46172
  }
44811
- const { usdceToken, extendedAdapter } = this._config;
46173
+ const { usdcToken, extendedAdapter } = this._config;
44812
46174
  const extendedContract = extendedAdapter.config.extendedContract;
44813
46175
  const vaultId = extendedAdapter.config.vaultIdExtended;
44814
- const salt = Math.floor(Math.random() * 10 ** usdceToken.decimals);
44815
- const uint256Amount = uint25622.bnToUint256(amount.toWei());
46176
+ const salt = Math.floor(Math.random() * 10 ** usdcToken.decimals);
46177
+ const uint256Amount = uint25623.bnToUint256(amount.toWei());
44816
46178
  const approveCall = {
44817
- contractAddress: usdceToken.address.address,
46179
+ contractAddress: usdcToken.address.address,
44818
46180
  entrypoint: "approve",
44819
46181
  calldata: [
44820
46182
  extendedContract.address,
@@ -44837,7 +46199,7 @@ var _ExecutionService = class _ExecutionService {
44837
46199
  return [approveCall, depositCall];
44838
46200
  }
44839
46201
  /**
44840
- * VA_TO_EXTENDED: Deposit USDC.e from vault allocator to Extended.
46202
+ * VA_TO_EXTENDED: Deposit USDC from vault allocator to Extended.
44841
46203
  *
44842
46204
  * Uses the extended adapter's getDepositCall to build ManageCalls,
44843
46205
  * then wraps them in a merkle-verified manage call through the manager contract.
@@ -44852,43 +46214,31 @@ var _ExecutionService = class _ExecutionService {
44852
46214
  );
44853
46215
  return [];
44854
46216
  }
44855
- const swapCall = await this._buildAdapterManageCall(
44856
- this._config.usdcToUsdceAdapter,
44857
- true,
44858
- { amount }
44859
- );
44860
46217
  const manageCall = await this._buildAdapterManageCall(
44861
46218
  this._config.extendedAdapter,
44862
46219
  true,
44863
46220
  { amount }
44864
46221
  );
44865
- return [swapCall, manageCall];
46222
+ return [manageCall];
44866
46223
  }
44867
46224
  /**
44868
- * WALLET_TO_VA: Transfer USDC.e from operator wallet to vault allocator.
46225
+ * WALLET_TO_VA: Transfer USDC from operator wallet to vault allocator.
44869
46226
  * Caps amount by actual wallet balance.
44870
46227
  */
44871
46228
  async _buildWalletToVACalls(route) {
44872
- const erc20 = new ERC20(this._config.networkConfig);
44873
- const { usdceToken, vaultAllocator, walletAddress } = this._config;
44874
46229
  const transferAmount = route.amount;
44875
46230
  if (transferAmount.lessThanOrEqualTo(0)) {
44876
46231
  logger.warn(
44877
- `${this._tag}::_buildWalletToVACalls no USDC.e in wallet to transfer`
46232
+ `${this._tag}::_buildWalletToVACalls no USDC in wallet to transfer`
44878
46233
  );
44879
46234
  return [];
44880
46235
  }
44881
46236
  const transferCall = await this._buildAdapterManageCall(
44882
- this._config.usdceTransferAdapter,
44883
- false,
44884
- { amount: transferAmount }
44885
- );
44886
- const swapCall = await this._buildAdapterManageCall(
44887
- this._config.usdcToUsdceAdapter,
46237
+ this._config.usdcTransferAdapter,
44888
46238
  false,
44889
46239
  { amount: transferAmount }
44890
46240
  );
44891
- return [transferCall, swapCall];
46241
+ return [transferCall];
44892
46242
  }
44893
46243
  // ── AVNU swap routes ────────────────────────────────────────────────────
44894
46244
  /**
@@ -44921,12 +46271,12 @@ var _ExecutionService = class _ExecutionService {
44921
46271
  */
44922
46272
  async _buildVesuIncreaseLeverCalls(route) {
44923
46273
  const depositManageCall = await this._buildAdapterManageCall(
44924
- this._config.vesuAdapter,
46274
+ this._config.vesuMultiplyAdapter,
44925
46275
  true,
44926
46276
  {
44927
46277
  amount: route.marginAmount,
44928
46278
  marginSwap: {
44929
- marginToken: this._config.vesuAdapter.config.marginToken
46279
+ marginToken: this._config.vesuMultiplyAdapter.config.marginToken
44930
46280
  // todo, must be vault token
44931
46281
  },
44932
46282
  leverSwap: {
@@ -44946,11 +46296,11 @@ var _ExecutionService = class _ExecutionService {
44946
46296
  async _buildVesuDecreaseLeverCalls(route) {
44947
46297
  const collateralAmount = route.marginAmount.abs();
44948
46298
  const withdrawManageCall = await this._buildAdapterManageCall(
44949
- this._config.vesuAdapter,
46299
+ this._config.vesuMultiplyAdapter,
44950
46300
  false,
44951
46301
  {
44952
46302
  amount: collateralAmount,
44953
- withdrawSwap: { outputToken: this._config.vesuAdapter.config.marginToken }
46303
+ withdrawSwap: { outputToken: this._config.vesuMultiplyAdapter.config.marginToken }
44954
46304
  }
44955
46305
  );
44956
46306
  return [withdrawManageCall];
@@ -45541,140 +46891,6 @@ _ExecutionService.EXTENDED_EXPOSURE_ROUTES = /* @__PURE__ */ new Set([
45541
46891
  ]);
45542
46892
  var ExecutionService = _ExecutionService;
45543
46893
 
45544
- // src/strategies/universal-adapters/usdc<>usdce-adapter.ts
45545
- import { hash as hash11, uint256 as uint25623 } from "starknet";
45546
- var UsdcToUsdceAdapter = class _UsdcToUsdceAdapter extends BaseAdapter {
45547
- _approveProofReadableId(usdcToUsdce) {
45548
- const method = usdcToUsdce ? "swap_to_legacy" : "swap_to_new";
45549
- return `approve_${method}`;
45550
- }
45551
- _swapProofReadableId(usdcToUsdce) {
45552
- const method = usdcToUsdce ? "swap_to_legacy" : "swap_to_new";
45553
- const target = usdcToUsdce ? this.config.supportedPositions[0].asset : this.config.supportedPositions[1].asset;
45554
- return `${method}_${target.symbol}`;
45555
- }
45556
- buildSwapLeafConfigs(usdcToUsdce) {
45557
- const method = usdcToUsdce ? "swap_to_legacy" : "swap_to_new";
45558
- const target = usdcToUsdce ? this.config.supportedPositions[0].asset : this.config.supportedPositions[1].asset;
45559
- return [
45560
- {
45561
- target: target.address,
45562
- method: "approve",
45563
- packedArguments: [AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt()],
45564
- id: this._approveProofReadableId(usdcToUsdce),
45565
- sanitizer: AVNU_LEGACY_SANITIZER
45566
- },
45567
- {
45568
- target: AVNU_EXCHANGE_FOR_LEGACY_USDC,
45569
- method,
45570
- packedArguments: [],
45571
- id: this._swapProofReadableId(usdcToUsdce),
45572
- sanitizer: AVNU_LEGACY_SANITIZER
45573
- }
45574
- ];
45575
- }
45576
- async buildSwapCalls(params, usdcToUsdce) {
45577
- const approveAmount = uint25623.bnToUint256(params.amount.toWei());
45578
- const target = usdcToUsdce ? this.config.supportedPositions[0].asset : this.config.supportedPositions[1].asset;
45579
- const method = usdcToUsdce ? "swap_to_legacy" : "swap_to_new";
45580
- return [
45581
- {
45582
- proofReadableId: this._approveProofReadableId(usdcToUsdce),
45583
- sanitizer: AVNU_LEGACY_SANITIZER,
45584
- call: {
45585
- contractAddress: target.address,
45586
- selector: hash11.getSelectorFromName("approve"),
45587
- calldata: [
45588
- AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt(),
45589
- toBigInt(approveAmount.low.toString()),
45590
- toBigInt(approveAmount.high.toString())
45591
- ]
45592
- }
45593
- },
45594
- {
45595
- proofReadableId: this._swapProofReadableId(usdcToUsdce),
45596
- sanitizer: AVNU_LEGACY_SANITIZER,
45597
- call: {
45598
- contractAddress: AVNU_EXCHANGE_FOR_LEGACY_USDC,
45599
- selector: hash11.getSelectorFromName(method),
45600
- calldata: [
45601
- toBigInt(approveAmount.low.toString()),
45602
- // amount low
45603
- toBigInt(approveAmount.high.toString())
45604
- // amount high
45605
- ]
45606
- }
45607
- }
45608
- ];
45609
- }
45610
- constructor(config) {
45611
- super(config, _UsdcToUsdceAdapter.name, Protocols.AVNU);
45612
- this.config = config;
45613
- assert(this.config.supportedPositions.length === 2, "UsdcToUsdceAdapter must have 2 supported positions");
45614
- assert(this.config.supportedPositions[0].asset.symbol === "USDC", "UsdcToUsdceAdapter must have USDC as the first supported position");
45615
- assert(this.config.supportedPositions[1].asset.symbol === "USDC.e", "UsdcToUsdceAdapter must have USDCE as the second supported position");
45616
- }
45617
- //abstract means the method has no implementation in this class; instead, child classes must implement it.
45618
- async getAPY(supportedPosition) {
45619
- return Promise.resolve({ apy: 0, type: "base" /* BASE */ });
45620
- }
45621
- async getPosition(supportedPosition) {
45622
- const toToken = this.config.supportedPositions[1].asset;
45623
- if (supportedPosition.asset.symbol != toToken.symbol) {
45624
- return null;
45625
- }
45626
- try {
45627
- const balance = await new ERC20(this.config.networkConfig).balanceOf(
45628
- toToken.address,
45629
- this.config.vaultAllocator.address,
45630
- toToken.decimals
45631
- );
45632
- return { amount: balance, remarks: `USDC.e unused balance (VA)` };
45633
- } catch (_e) {
45634
- logger.error(`${_UsdcToUsdceAdapter.name}::getPosition: failed for ${toToken.symbol}`);
45635
- throw new Error(`${_UsdcToUsdceAdapter.name}: failed to get balance for ${toToken.symbol}`);
45636
- }
45637
- }
45638
- async maxDeposit(amount) {
45639
- return Promise.resolve({
45640
- tokenInfo: this.config.baseToken,
45641
- amount: new Web3Number(0, 0),
45642
- usdValue: 0,
45643
- apy: { apy: 0, type: "base" /* BASE */ },
45644
- protocol: Protocols.AVNU,
45645
- remarks: ""
45646
- });
45647
- }
45648
- async maxWithdraw() {
45649
- return Promise.resolve({
45650
- tokenInfo: this.config.baseToken,
45651
- amount: new Web3Number(0, 0),
45652
- usdValue: 0,
45653
- apy: { apy: 0, type: "base" /* BASE */ },
45654
- protocol: Protocols.AVNU,
45655
- remarks: ""
45656
- });
45657
- }
45658
- _getDepositLeaf() {
45659
- return this.buildSwapLeafConfigs(true);
45660
- }
45661
- _getWithdrawLeaf() {
45662
- return this.buildSwapLeafConfigs(false);
45663
- }
45664
- async getDepositCall(params) {
45665
- const calls = await this.buildSwapCalls(params, true);
45666
- return calls;
45667
- }
45668
- //Swap wbtc to usdc
45669
- async getWithdrawCall(params) {
45670
- const calls = await this.buildSwapCalls(params, false);
45671
- return calls;
45672
- }
45673
- async getHealthFactor() {
45674
- return Promise.resolve(1);
45675
- }
45676
- };
45677
-
45678
46894
  // src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx
45679
46895
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
45680
46896
  var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy extends SVKStrategy {
@@ -45690,9 +46906,6 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
45690
46906
  });
45691
46907
  this.wbtcToken = Global.getDefaultTokens().find((token) => token.symbol === "WBTC");
45692
46908
  this.usdcToken = this.metadata.additionalInfo.borrowable_assets[0];
45693
- this.usdceToken = Global.getDefaultTokens().find(
45694
- (token) => token.symbol === "USDC.e"
45695
- );
45696
46909
  this.stateManager = this._initializeStateManager();
45697
46910
  }
45698
46911
  /**
@@ -45716,9 +46929,8 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
45716
46929
  extendedAdapter: extendedAdapterEntry.adapter,
45717
46930
  vaultAllocator: this.metadata.additionalInfo.vaultAllocator,
45718
46931
  walletAddress: this.metadata.additionalInfo.walletAddress,
45719
- assetToken: Global.getDefaultTokens().find((token) => token.symbol === "USDC"),
45720
- // ! TODO change to asset() latest
45721
- usdceToken: this.usdceToken,
46932
+ assetToken: this.asset(),
46933
+ usdcToken: this.usdcToken,
45722
46934
  collateralToken: this.wbtcToken,
45723
46935
  limitBalanceBufferFactor: LIMIT_BALANCE
45724
46936
  };
@@ -45752,7 +46964,7 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
45752
46964
  }
45753
46965
  return { collateralPrice, debtPrice };
45754
46966
  }
45755
- async getVesuAdapter() {
46967
+ async getVesuMultiplyAdapter() {
45756
46968
  const vesuAdapter = this.metadata.additionalInfo.adapters.find(
45757
46969
  (adapter) => adapter.adapter.name === VesuMultiplyAdapter.name
45758
46970
  );
@@ -45774,16 +46986,16 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
45774
46986
  }
45775
46987
  return vesuModifyPositionAdapter.adapter;
45776
46988
  }
45777
- async getUsdceTransferAdapter() {
45778
- const usdceTransferAdapter = this.metadata.additionalInfo.adapters.find(
46989
+ async getUsdcTransferAdapter() {
46990
+ const usdcTransferAdapter = this.metadata.additionalInfo.adapters.find(
45779
46991
  (adapter) => adapter.adapter.name === TokenTransferAdapter.name
45780
46992
  );
45781
- if (!usdceTransferAdapter) {
46993
+ if (!usdcTransferAdapter) {
45782
46994
  throw new Error(
45783
- `${this.getTag()} Usdce transfer adapter not configured in metadata.`
46995
+ `${this.getTag()} Usdc transfer adapter not configured in metadata.`
45784
46996
  );
45785
46997
  }
45786
- return usdceTransferAdapter.adapter;
46998
+ return usdcTransferAdapter.adapter;
45787
46999
  }
45788
47000
  async getAvnuAdapter() {
45789
47001
  const avnuAdapter = this.metadata.additionalInfo.adapters.find(
@@ -45812,17 +47024,6 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
45812
47024
  }
45813
47025
  return extendedAdapter.adapter;
45814
47026
  }
45815
- async getUsdcToUsdceAdapter() {
45816
- const usdcToUsdceAdapter = this.metadata.additionalInfo.adapters.find(
45817
- (adapter) => adapter.adapter.name === UsdcToUsdceAdapter.name
45818
- );
45819
- if (!usdcToUsdceAdapter) {
45820
- throw new Error(
45821
- `${this.getTag()} UsdcToUsdce adapter not configured in metadata.`
45822
- );
45823
- }
45824
- return usdcToUsdceAdapter.adapter;
45825
- }
45826
47027
  /**
45827
47028
  * Creates an ExecutionService wired to this strategy's adapters and config.
45828
47029
  * Use with `stateManager.solve()` to get a SolveResult, then pass it to
@@ -45834,34 +47035,30 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
45834
47035
  */
45835
47036
  async createExecutionService(opts) {
45836
47037
  const [
45837
- vesuAdapter,
47038
+ vesuMultiplyAdapter,
45838
47039
  vesuModifyPositionAdapter,
45839
- usdceTransferAdapter,
45840
47040
  extendedAdapter,
45841
47041
  avnuAdapter,
45842
- usdcToUsdceAdapter
47042
+ usdcTransferAdapter
45843
47043
  ] = await Promise.all([
45844
- this.getVesuAdapter(),
47044
+ this.getVesuMultiplyAdapter(),
45845
47045
  this.getVesuModifyPositionAdapter(),
45846
- this.getUsdceTransferAdapter(),
45847
47046
  this.getExtendedAdapter(),
45848
47047
  this.getAvnuAdapter(),
45849
- this.getUsdcToUsdceAdapter()
47048
+ this.getUsdcTransferAdapter()
45850
47049
  ]);
45851
47050
  const executionConfig = {
45852
47051
  networkConfig: this.config,
45853
47052
  pricer: this.pricer,
45854
- vesuAdapter,
47053
+ vesuMultiplyAdapter,
45855
47054
  vesuModifyPositionAdapter,
45856
47055
  extendedAdapter,
45857
47056
  avnuAdapter,
45858
- usdcToUsdceAdapter,
47057
+ usdcTransferAdapter,
45859
47058
  vaultAllocator: this.metadata.additionalInfo.vaultAllocator,
45860
47059
  walletAddress: this.metadata.additionalInfo.walletAddress,
45861
- usdceTransferAdapter,
45862
47060
  wbtcToken: this.wbtcToken,
45863
47061
  usdcToken: this.usdcToken,
45864
- usdceToken: this.usdceToken,
45865
47062
  getMerkleTree: () => this.getMerkleTree(),
45866
47063
  getManageCall: (proofs, manageCalls) => this.getManageCall(proofs, manageCalls),
45867
47064
  getBringLiquidityCall: (params) => this.getBringLiquidityCall(params),
@@ -45882,9 +47079,7 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
45882
47079
  for (let adapter of this.metadata.additionalInfo.adapters) {
45883
47080
  let positions = await adapter.adapter.getPositions();
45884
47081
  if (positions && positions.length > 0) {
45885
- const filteredPositions = positions.filter((position) => {
45886
- return position.tokenInfo.address !== this.usdceToken.address;
45887
- });
47082
+ const filteredPositions = positions;
45888
47083
  allPositions.push(...filteredPositions);
45889
47084
  }
45890
47085
  }
@@ -46079,34 +47274,22 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
46079
47274
  };
46080
47275
  }
46081
47276
  /**
46082
- * Fetches the operator wallet's current holdings for USDC.e, USDC, and WBTC,
47277
+ * Fetches the operator wallet's current holdings for USDC and WBTC,
46083
47278
  * returning each token's balance and USD value.
46084
47279
  */
46085
47280
  async getWalletHoldings() {
46086
- if (!this.usdceToken || !this.wbtcToken || !this.usdcToken) {
47281
+ if (!this.wbtcToken || !this.usdcToken) {
46087
47282
  return [];
46088
47283
  }
46089
47284
  const walletAddress = this.metadata.additionalInfo.walletAddress;
46090
- const usdceWalletBalance = await new ERC20(this.config).balanceOf(
46091
- this.usdceToken.address,
46092
- walletAddress,
46093
- this.usdceToken.decimals
46094
- );
46095
47285
  const usdcWalletBalance = await new ERC20(this.config).balanceOf(
46096
47286
  this.usdcToken.address,
46097
47287
  walletAddress,
46098
47288
  this.usdcToken.decimals
46099
47289
  );
46100
- const price = await this.pricer.getPrice(this.usdceToken.symbol);
46101
- const wbtcPrice = await this.pricer.getPrice(this.wbtcToken.symbol);
46102
- const usdceUsdValue = Number(usdceWalletBalance.toFixed(this.usdceToken.decimals)) * price.price;
47290
+ const price = await this.pricer.getPrice(this.usdcToken.symbol);
46103
47291
  const usdcUsdValue = Number(usdcWalletBalance.toFixed(this.usdcToken.decimals)) * price.price;
46104
47292
  return [
46105
- {
46106
- tokenInfo: this.usdceToken,
46107
- amount: usdceWalletBalance,
46108
- usdValue: usdceUsdValue
46109
- },
46110
47293
  {
46111
47294
  tokenInfo: this.usdcToken,
46112
47295
  amount: usdcWalletBalance,
@@ -46123,9 +47306,6 @@ function getLooperSettings3(lstSymbol, underlyingSymbol, vaultSettings, pool1, e
46123
47306
  const usdcToken = Global.getDefaultTokens().find(
46124
47307
  (token) => token.symbol === underlyingSymbol
46125
47308
  );
46126
- const usdceToken = Global.getDefaultTokens().find(
46127
- (token) => token.symbol === "USDC.e"
46128
- );
46129
47309
  const baseAdapterConfig = {
46130
47310
  baseToken: wbtcToken,
46131
47311
  supportedPositions: [
@@ -46146,17 +47326,10 @@ function getLooperSettings3(lstSymbol, underlyingSymbol, vaultSettings, pool1, e
46146
47326
  minimumExtendedPriceDifferenceForSwapOpen,
46147
47327
  maximumExtendedPriceDifferenceForSwapClosing
46148
47328
  });
46149
- const usdcToUsdceAdapter = new UsdcToUsdceAdapter({
46150
- ...baseAdapterConfig,
46151
- supportedPositions: [
46152
- { asset: usdcToken, isDebt: true },
46153
- { asset: usdceToken, isDebt: false }
46154
- ]
46155
- });
46156
47329
  const extendedAdapter = new ExtendedAdapter({
46157
47330
  ...baseAdapterConfig,
46158
47331
  supportedPositions: [
46159
- { asset: usdceToken, isDebt: false }
47332
+ { asset: usdcToken, isDebt: false }
46160
47333
  ],
46161
47334
  vaultIdExtended,
46162
47335
  extendedContract: EXTENDED_CONTRACT,
@@ -46202,10 +47375,10 @@ function getLooperSettings3(lstSymbol, underlyingSymbol, vaultSettings, pool1, e
46202
47375
  { asset: usdcToken, isDebt: true }
46203
47376
  ]
46204
47377
  });
46205
- const usdceTransferAdapter = new TokenTransferAdapter({
47378
+ const usdcTransferAdapter = new TokenTransferAdapter({
46206
47379
  ...baseAdapterConfig,
46207
- baseToken: usdceToken,
46208
- supportedPositions: [{ asset: usdceToken, isDebt: false }],
47380
+ baseToken: usdcToken,
47381
+ supportedPositions: [{ asset: usdcToken, isDebt: false }],
46209
47382
  fromAddress: vaultSettings.vaultAllocator,
46210
47383
  toAddress: ContractAddr.from(vaultSettings.walletAddress)
46211
47384
  });
@@ -46218,12 +47391,8 @@ function getLooperSettings3(lstSymbol, underlyingSymbol, vaultSettings, pool1, e
46218
47391
  adapter: vesuModifyPositionAdapter
46219
47392
  });
46220
47393
  vaultSettings.adapters.push({
46221
- id: `${usdceTransferAdapter.name}_${usdceToken.symbol}`,
46222
- adapter: usdceTransferAdapter
46223
- });
46224
- vaultSettings.adapters.push({
46225
- id: `${usdcToUsdceAdapter.name}_${usdceToken.symbol}_${usdcToken.symbol}`,
46226
- adapter: usdcToUsdceAdapter
47394
+ id: `${usdcTransferAdapter.name}_${usdcToken.symbol}`,
47395
+ adapter: usdcTransferAdapter
46227
47396
  });
46228
47397
  vaultSettings.adapters.push({
46229
47398
  id: `${extendedAdapter.name}_${wbtcToken.symbol}`,
@@ -46245,12 +47414,10 @@ function getLooperSettings3(lstSymbol, underlyingSymbol, vaultSettings, pool1, e
46245
47414
  vaultSettings.leafAdapters.push(() => vesuModifyPositionAdapter.getDepositLeaf());
46246
47415
  vaultSettings.leafAdapters.push(() => vesuModifyPositionAdapter.getWithdrawLeaf());
46247
47416
  vaultSettings.leafAdapters.push(() => extendedAdapter.getDepositLeaf());
46248
- vaultSettings.leafAdapters.push(() => usdcToUsdceAdapter.getDepositLeaf());
46249
- vaultSettings.leafAdapters.push(() => usdcToUsdceAdapter.getWithdrawLeaf());
46250
47417
  vaultSettings.leafAdapters.push(() => avnuAdapter.getDepositLeaf());
46251
47418
  vaultSettings.leafAdapters.push(() => avnuAdapter.getWithdrawLeaf());
46252
- vaultSettings.leafAdapters.push(() => usdceTransferAdapter.getDepositLeaf());
46253
- vaultSettings.leafAdapters.push(() => usdceTransferAdapter.getWithdrawLeaf());
47419
+ vaultSettings.leafAdapters.push(() => usdcTransferAdapter.getDepositLeaf());
47420
+ vaultSettings.leafAdapters.push(() => usdcTransferAdapter.getWithdrawLeaf());
46254
47421
  vaultSettings.leafAdapters.push(
46255
47422
  commonAdapter.getApproveAdapter(
46256
47423
  usdcToken.address,
@@ -47282,11 +48449,13 @@ export {
47282
48449
  BaseAdapter,
47283
48450
  BaseStrategy,
47284
48451
  CASE_ROUTE_TYPES,
48452
+ COLLATERAL_PRECISION,
47285
48453
  CaseCategory,
47286
48454
  CaseId,
47287
48455
  CommonAdapter,
47288
48456
  ContractAddr,
47289
48457
  CycleType,
48458
+ DEFAULT_TROVES_STRATEGIES_API,
47290
48459
  deployer_default as Deployer,
47291
48460
  ERC20,
47292
48461
  EXTENDED_CONTRACT,
@@ -47305,6 +48474,7 @@ export {
47305
48474
  FatalError,
47306
48475
  FlowChartColors,
47307
48476
  Global,
48477
+ HealthFactorMath,
47308
48478
  HyperLSTStrategies,
47309
48479
  ILending,
47310
48480
  Initializable,
@@ -47345,6 +48515,7 @@ export {
47345
48515
  StrategyLiveStatus,
47346
48516
  StrategyTag,
47347
48517
  StrategyType,
48518
+ SvkTrovesAdapter,
47348
48519
  TRANSFER_SANITIZER,
47349
48520
  TelegramGroupNotif,
47350
48521
  TelegramNotif,
@@ -47394,6 +48565,7 @@ export {
47394
48565
  createEkuboCLStrategy,
47395
48566
  createHyperLSTStrategy,
47396
48567
  createSenseiStrategy,
48568
+ createSolveBudgetFromRawState,
47397
48569
  createStrategy,
47398
48570
  createUniversalStrategy2 as createUniversalStrategy,
47399
48571
  createVesuRebalanceStrategy2 as createVesuRebalanceStrategy,