@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.
@@ -91,6 +91,9 @@ var Web3Number = class _Web3Number2 extends _Web3Number {
91
91
  const bn = new _Web3Number2(weiNumber, decimals).dividedBy(10 ** decimals);
92
92
  return new _Web3Number2(bn.toString(), decimals);
93
93
  }
94
+ static fromNumber(number, decimals) {
95
+ return new _Web3Number2(number.toString(), decimals);
96
+ }
94
97
  };
95
98
 
96
99
  // src/dataTypes/address.ts
@@ -1485,6 +1488,48 @@ _StarknetCallParser.METHOD_BY_SELECTOR = new Map(
1485
1488
  );
1486
1489
  var StarknetCallParser = _StarknetCallParser;
1487
1490
 
1491
+ // src/utils/health-factor-math.ts
1492
+ var HealthFactorMath = class {
1493
+ static getCollateralRequired(debtAmount, debtPrice, targetHF, maxLTV, collateralPrice, collateralTokenInfo) {
1494
+ const numerator = debtAmount.multipliedBy(debtPrice).multipliedBy(targetHF);
1495
+ const denominator = collateralPrice * maxLTV;
1496
+ const collateralAmount = numerator.dividedBy(denominator);
1497
+ const netCollateral = new Web3Number(collateralAmount.toString(), collateralTokenInfo.decimals);
1498
+ return netCollateral;
1499
+ }
1500
+ static getMinCollateralRequiredOnLooping(debtAmount, debtPrice, targetHF, maxLTV, collateralPrice, collateralTokenInfo) {
1501
+ const netCollateral = this.getCollateralRequired(debtAmount, debtPrice, targetHF, maxLTV, collateralPrice, collateralTokenInfo);
1502
+ const collateralFromDebt = new Web3Number(debtAmount.multipliedBy(debtPrice).dividedBy(collateralPrice).toString(), collateralTokenInfo.decimals);
1503
+ return netCollateral.minus(collateralFromDebt);
1504
+ }
1505
+ static getHealthFactor(collateralAmount, collateralPrice, maxLTV, debtAmount, debtPrice) {
1506
+ const numerator = collateralAmount.multipliedBy(collateralPrice).multipliedBy(maxLTV);
1507
+ const denominator = debtAmount.multipliedBy(debtPrice);
1508
+ const healthFactor = numerator.dividedBy(denominator);
1509
+ return healthFactor.toNumber();
1510
+ }
1511
+ static getMaxDebtAmountOnLooping(collateralAmount, collateralPrice, maxLTV, targetHF, debtPrice, debtTokenInfo) {
1512
+ const numerator = collateralAmount.multipliedBy(collateralPrice).multipliedBy(maxLTV);
1513
+ logger.verbose(`HealthFactorMath: Max debt amount on looping numerator: ${numerator.toNumber()}, collateralAmount: ${collateralAmount.toNumber()}, collateralPrice: ${collateralPrice}, maxLTV: ${maxLTV}, targetHF: ${targetHF}, debtPrice: ${debtPrice}`);
1514
+ const denominator = targetHF - maxLTV;
1515
+ logger.verbose(`HealthFactorMath: Max debt amount on looping denominator: ${denominator}`);
1516
+ const debtAmountUSD = numerator.dividedBy(denominator);
1517
+ logger.verbose(`HealthFactorMath: Max debt amount on looping debtAmountUSD: ${debtAmountUSD.toNumber()}`);
1518
+ const debtAmount = debtAmountUSD.dividedBy(debtPrice);
1519
+ logger.verbose(`HealthFactorMath: Max debt amount on looping debtAmount: ${debtAmount.toNumber()}`);
1520
+ return new Web3Number(debtAmount.toString(), debtTokenInfo.decimals);
1521
+ }
1522
+ static getMaxDebtAmount(collateralAmount, collateralPrice, maxLTV, targetHF, debtPrice, debtTokenInfo) {
1523
+ const numerator = collateralAmount.multipliedBy(collateralPrice).multipliedBy(maxLTV);
1524
+ logger.verbose(`HealthFactorMath: Max debt amount numerator: ${numerator.toNumber()}, collateralAmount: ${collateralAmount.toNumber()}, collateralPrice: ${collateralPrice}, maxLTV: ${maxLTV}, targetHF: ${targetHF}, debtPrice: ${debtPrice}`);
1525
+ const denominator = targetHF * debtPrice;
1526
+ logger.verbose(`HealthFactorMath: Max debt amount denominator: ${denominator}, targetHF: ${targetHF}, debtPrice: ${debtPrice}`);
1527
+ const debtAmount = numerator.dividedBy(denominator);
1528
+ logger.verbose(`HealthFactorMath: Max debt amount: ${debtAmount.toNumber()}, numerator: ${numerator.toNumber()}, denominator: ${denominator}`);
1529
+ return new Web3Number(debtAmount.toString(), debtTokenInfo.decimals);
1530
+ }
1531
+ };
1532
+
1488
1533
  // src/utils/index.ts
1489
1534
  function assert(condition, message) {
1490
1535
  if (!condition) {
@@ -6477,6 +6522,10 @@ var VaultProtocol = {
6477
6522
  name: "Vault",
6478
6523
  logo: ""
6479
6524
  };
6525
+ var TrovesProtocol = {
6526
+ name: "Troves",
6527
+ logo: "https://app.troves.fi/favicon.ico"
6528
+ };
6480
6529
  var Protocols = {
6481
6530
  NONE: NoneProtocol,
6482
6531
  VESU: VesuProtocol,
@@ -6484,7 +6533,8 @@ var Protocols = {
6484
6533
  EXTENDED: ExtendedProtocol,
6485
6534
  EKUBO: EkuboProtocol,
6486
6535
  AVNU: AvnuProtocol,
6487
- VAULT: VaultProtocol
6536
+ VAULT: VaultProtocol,
6537
+ TROVES: TrovesProtocol
6488
6538
  };
6489
6539
 
6490
6540
  // src/interfaces/initializable.ts
@@ -34219,48 +34269,6 @@ var AbisConfig = {
34219
34269
  }
34220
34270
  };
34221
34271
 
34222
- // src/utils/health-factor-math.ts
34223
- var HealthFactorMath = class {
34224
- static getCollateralRequired(debtAmount, debtPrice, targetHF, maxLTV, collateralPrice, collateralTokenInfo) {
34225
- const numerator = debtAmount.multipliedBy(debtPrice).multipliedBy(targetHF);
34226
- const denominator = collateralPrice * maxLTV;
34227
- const collateralAmount = numerator.dividedBy(denominator);
34228
- const netCollateral = new Web3Number(collateralAmount.toString(), collateralTokenInfo.decimals);
34229
- return netCollateral;
34230
- }
34231
- static getMinCollateralRequiredOnLooping(debtAmount, debtPrice, targetHF, maxLTV, collateralPrice, collateralTokenInfo) {
34232
- const netCollateral = this.getCollateralRequired(debtAmount, debtPrice, targetHF, maxLTV, collateralPrice, collateralTokenInfo);
34233
- const collateralFromDebt = new Web3Number(debtAmount.multipliedBy(debtPrice).dividedBy(collateralPrice).toString(), collateralTokenInfo.decimals);
34234
- return netCollateral.minus(collateralFromDebt);
34235
- }
34236
- static getHealthFactor(collateralAmount, collateralPrice, maxLTV, debtAmount, debtPrice) {
34237
- const numerator = collateralAmount.multipliedBy(collateralPrice).multipliedBy(maxLTV);
34238
- const denominator = debtAmount.multipliedBy(debtPrice);
34239
- const healthFactor = numerator.dividedBy(denominator);
34240
- return healthFactor.toNumber();
34241
- }
34242
- static getMaxDebtAmountOnLooping(collateralAmount, collateralPrice, maxLTV, targetHF, debtPrice, debtTokenInfo) {
34243
- const numerator = collateralAmount.multipliedBy(collateralPrice).multipliedBy(maxLTV);
34244
- logger.verbose(`HealthFactorMath: Max debt amount on looping numerator: ${numerator.toNumber()}, collateralAmount: ${collateralAmount.toNumber()}, collateralPrice: ${collateralPrice}, maxLTV: ${maxLTV}, targetHF: ${targetHF}, debtPrice: ${debtPrice}`);
34245
- const denominator = targetHF - maxLTV;
34246
- logger.verbose(`HealthFactorMath: Max debt amount on looping denominator: ${denominator}`);
34247
- const debtAmountUSD = numerator.dividedBy(denominator);
34248
- logger.verbose(`HealthFactorMath: Max debt amount on looping debtAmountUSD: ${debtAmountUSD.toNumber()}`);
34249
- const debtAmount = debtAmountUSD.dividedBy(debtPrice);
34250
- logger.verbose(`HealthFactorMath: Max debt amount on looping debtAmount: ${debtAmount.toNumber()}`);
34251
- return new Web3Number(debtAmount.toString(), debtTokenInfo.decimals);
34252
- }
34253
- static getMaxDebtAmount(collateralAmount, collateralPrice, maxLTV, targetHF, debtPrice, debtTokenInfo) {
34254
- const numerator = collateralAmount.multipliedBy(collateralPrice).multipliedBy(maxLTV);
34255
- logger.verbose(`HealthFactorMath: Max debt amount numerator: ${numerator.toNumber()}, collateralAmount: ${collateralAmount.toNumber()}, collateralPrice: ${collateralPrice}, maxLTV: ${maxLTV}, targetHF: ${targetHF}, debtPrice: ${debtPrice}`);
34256
- const denominator = targetHF * debtPrice;
34257
- logger.verbose(`HealthFactorMath: Max debt amount denominator: ${denominator}, targetHF: ${targetHF}, debtPrice: ${debtPrice}`);
34258
- const debtAmount = numerator.dividedBy(denominator);
34259
- logger.verbose(`HealthFactorMath: Max debt amount: ${debtAmount.toNumber()}, numerator: ${numerator.toNumber()}, denominator: ${denominator}`);
34260
- return new Web3Number(debtAmount.toString(), debtTokenInfo.decimals);
34261
- }
34262
- };
34263
-
34264
34272
  // src/strategies/vesu-extended-strategy/utils/helper.ts
34265
34273
  var returnFormattedAmount = (amount, toTokenDecimals) => {
34266
34274
  const formattedAmount = "0x" + BigInt(Math.floor(amount * 10 ** toTokenDecimals)).toString(16);
@@ -34903,6 +34911,7 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34903
34911
  const collateralToken = this.config.collateral;
34904
34912
  const debtToken = this.config.debt;
34905
34913
  const { contract: multiplyContract } = this._getMultiplyContract();
34914
+ this.lastSwapPriceInfo = null;
34906
34915
  const {
34907
34916
  existingCollateralInfo,
34908
34917
  existingDebtInfo,
@@ -34939,9 +34948,15 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34939
34948
  let marginSwapLimitAmount = Web3Number.fromWei(0, collateralToken.decimals);
34940
34949
  let addedCollateral = params.amount;
34941
34950
  let approveAmount = params.amount;
34951
+ let aggregatedFromAmount = 0;
34952
+ let aggregatedToAmount = 0;
34953
+ let aggregatedFromSymbol = debtToken.symbol;
34954
+ const aggregatedToSymbol = collateralToken.symbol;
34955
+ let executedSwapCount = 0;
34942
34956
  if (params.marginSwap) {
34943
34957
  const marginToken = params.marginSwap.marginToken;
34944
34958
  const requiredAmount = params.amount;
34959
+ assert(marginToken.address.eq(debtToken.address), "Margin token must be the same as debt token");
34945
34960
  const marginSwapQuote = await ekuboQuoter.getQuoteExactOutput(
34946
34961
  marginToken.address.address,
34947
34962
  collateralToken.address.address,
@@ -34957,6 +34972,11 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34957
34972
  marginToken,
34958
34973
  collateralToken
34959
34974
  );
34975
+ const marginSwapInputAmount = Web3Number.fromWei(marginSwapQuote.total_calculated, marginToken.decimals).abs().toNumber();
34976
+ const marginSwapOutputAmount = requiredAmount.abs().toNumber();
34977
+ aggregatedFromAmount += marginSwapInputAmount;
34978
+ aggregatedToAmount += marginSwapOutputAmount;
34979
+ executedSwapCount += 1;
34960
34980
  approveAmount = Web3Number.fromWei(marginSwapQuote.total_calculated, marginToken.decimals).multipliedBy(1 + this.maxSlippage).abs();
34961
34981
  }
34962
34982
  let debtAmount = this._computeTargetDebtDelta(
@@ -34980,10 +35000,6 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
34980
35000
  if (!debtAmount.isZero() && debtAmount.greaterThan(0)) {
34981
35001
  try {
34982
35002
  let swapQuote;
34983
- const debtAmountInCollateralUnits = new Web3Number(
34984
- debtAmount.multipliedBy(debtPrice).dividedBy(collateralPrice).toFixed(6),
34985
- collateralToken.decimals
34986
- );
34987
35003
  if (params.leverSwap?.exactOutput) {
34988
35004
  swapQuote = await ekuboQuoter.getQuoteExactOutput(
34989
35005
  debtToken.address.address,
@@ -35007,17 +35023,9 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
35007
35023
  ).abs().toNumber();
35008
35024
  const inputAmt = debtAmount.abs().toNumber();
35009
35025
  const outputAmt = quoteOutputAmount;
35010
- this.lastSwapPriceInfo = {
35011
- source: "ekubo",
35012
- fromTokenSymbol: debtToken.symbol,
35013
- toTokenSymbol: collateralToken.symbol,
35014
- fromAmount: inputAmt,
35015
- toAmount: outputAmt,
35016
- effectivePrice: outputAmt !== 0 ? inputAmt / outputAmt : 0
35017
- };
35018
- logger.verbose(
35019
- `${_VesuMultiplyAdapter.name}::_getIncreaseCalldata stored price info: ${inputAmt} ${debtToken.symbol} \u2192 ${outputAmt} ${collateralToken.symbol}, effectivePrice=${this.lastSwapPriceInfo.effectivePrice}`
35020
- );
35026
+ aggregatedFromAmount += inputAmt;
35027
+ aggregatedToAmount += outputAmt;
35028
+ executedSwapCount += 1;
35021
35029
  leverSwap = ekuboQuoter.getVesuMultiplyQuote(
35022
35030
  swapQuote,
35023
35031
  debtToken,
@@ -35035,6 +35043,19 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
35035
35043
  );
35036
35044
  }
35037
35045
  }
35046
+ if (executedSwapCount > 0) {
35047
+ this.lastSwapPriceInfo = {
35048
+ source: "ekubo",
35049
+ fromTokenSymbol: aggregatedFromSymbol ?? debtToken.symbol,
35050
+ toTokenSymbol: aggregatedToSymbol,
35051
+ fromAmount: aggregatedFromAmount,
35052
+ toAmount: aggregatedToAmount,
35053
+ effectivePrice: aggregatedToAmount !== 0 ? aggregatedFromAmount / aggregatedToAmount : 0
35054
+ };
35055
+ logger.verbose(
35056
+ `${_VesuMultiplyAdapter.name}::_getIncreaseCalldata stored aggregated price info: ${aggregatedFromAmount} ${this.lastSwapPriceInfo.fromTokenSymbol} \u2192 ${aggregatedToAmount} ${aggregatedToSymbol}, effectivePrice=${this.lastSwapPriceInfo.effectivePrice}, swaps=${executedSwapCount}`
35057
+ );
35058
+ }
35038
35059
  logger.verbose(
35039
35060
  `${_VesuMultiplyAdapter.name}::_getIncreaseCalldata leverSwapLimitAmount: ${leverSwapLimitAmount.toWei()}`
35040
35061
  );
@@ -35067,10 +35088,21 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
35067
35088
  approveAmount
35068
35089
  };
35069
35090
  }
35091
+ // private _setLastSwapPriceAsNoSwap(): void {
35092
+ // this.lastSwapPriceInfo = {
35093
+ // source: "no-swap",
35094
+ // fromTokenSymbol: this.config.collateral.symbol,
35095
+ // toTokenSymbol: this.config.debt.symbol,
35096
+ // fromAmount: 0,
35097
+ // toAmount: 0,
35098
+ // effectivePrice: 0,
35099
+ // };
35100
+ // }
35070
35101
  async _buildDecreaseLikeCalldata(params) {
35071
35102
  const collateralToken = this.config.collateral;
35072
35103
  const debtToken = this.config.debt;
35073
35104
  const { contract: multiplyContract } = this._getMultiplyContract();
35105
+ this.lastSwapPriceInfo = null;
35074
35106
  const ekuboQuoter = new EkuboQuoter(
35075
35107
  this.config.networkConfig,
35076
35108
  this.config.pricer
@@ -35079,6 +35111,9 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
35079
35111
  let leverSwapWeights = [];
35080
35112
  let leverSwapLimitAmount = Web3Number.fromWei(0, collateralToken.decimals);
35081
35113
  let leverCollateralUsed = Web3Number.fromWei(0, collateralToken.decimals);
35114
+ let aggregatedFromAmount = 0;
35115
+ let aggregatedToAmount = 0;
35116
+ let executedSwapCount = 0;
35082
35117
  if (params.closePosition) {
35083
35118
  const debtQuote = await ekuboQuoter.getQuoteExactOutput(
35084
35119
  collateralToken.address.address,
@@ -35097,6 +35132,9 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
35097
35132
  collateralToken.decimals
35098
35133
  ).abs();
35099
35134
  leverSwapLimitAmount = leverCollateralUsed.multipliedBy(1 + this.maxSlippage);
35135
+ aggregatedFromAmount += leverCollateralUsed.toNumber();
35136
+ aggregatedToAmount += params.debtToRepayAbs.abs().toNumber();
35137
+ executedSwapCount += 1;
35100
35138
  } else {
35101
35139
  if (params.collateralPrice === void 0 || params.debtPrice === void 0) {
35102
35140
  throw new Error(
@@ -35118,17 +35156,9 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
35118
35156
  leverSwapQuote.total_calculated,
35119
35157
  debtToken.decimals
35120
35158
  ).abs().toNumber();
35121
- this.lastSwapPriceInfo = {
35122
- source: "ekubo",
35123
- fromTokenSymbol: collateralToken.symbol,
35124
- toTokenSymbol: debtToken.symbol,
35125
- fromAmount: inputAmt,
35126
- toAmount: outputAmt,
35127
- effectivePrice: outputAmt !== 0 ? outputAmt / inputAmt : 0
35128
- };
35129
- logger.verbose(
35130
- `${_VesuMultiplyAdapter.name}::_buildDecreaseLikeCalldata stored price info: ${inputAmt} ${collateralToken.symbol} \u2192 ${outputAmt} ${debtToken.symbol}, effectivePrice=${this.lastSwapPriceInfo.effectivePrice}`
35131
- );
35159
+ aggregatedFromAmount += inputAmt;
35160
+ aggregatedToAmount += outputAmt;
35161
+ executedSwapCount += 1;
35132
35162
  leverSwap = ekuboQuoter.getVesuMultiplyQuote(
35133
35163
  leverSwapQuote,
35134
35164
  collateralToken,
@@ -35151,6 +35181,7 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
35151
35181
  );
35152
35182
  const withdrawSwapWeights = [];
35153
35183
  if (params.outputToken && !params.outputToken.address.eq(collateralToken.address)) {
35184
+ assert(params.outputToken.address.eq(debtToken.address), "Withdraw output token must be the same as debt token");
35154
35185
  const residualCollateral = params.closePosition ? params.existingCollateral.minus(leverCollateralUsed) : params.subMargin;
35155
35186
  const outputTokenPrice = await this.config.pricer.getPrice(params.outputToken.symbol);
35156
35187
  if (residualCollateral.greaterThan(0)) {
@@ -35167,12 +35198,34 @@ var VesuMultiplyAdapter = class _VesuMultiplyAdapter extends BaseAdapter {
35167
35198
  );
35168
35199
  withdrawSwap = built.swaps;
35169
35200
  withdrawSwapWeights.push(...built.weights);
35201
+ const withdrawOutputAmount = Web3Number.fromWei(
35202
+ withdrawQuote.total_calculated,
35203
+ params.outputToken.decimals
35204
+ ).abs().toNumber();
35205
+ aggregatedFromAmount += residualCollateral.toNumber();
35206
+ aggregatedToAmount += withdrawOutputAmount;
35207
+ executedSwapCount += 1;
35170
35208
  const estimatedOutput = residualCollateral.multipliedBy(params.collateralPrice).dividedBy(outputTokenPrice.price);
35171
35209
  estimatedOutput.decimals = params.outputToken.decimals;
35172
35210
  withdrawSwapLimitAmount = estimatedOutput.multipliedBy(1 - this.maxSlippage);
35173
35211
  }
35174
35212
  }
35175
35213
  }
35214
+ if (executedSwapCount > 0) {
35215
+ this.lastSwapPriceInfo = {
35216
+ source: "ekubo",
35217
+ fromTokenSymbol: collateralToken.symbol,
35218
+ toTokenSymbol: debtToken.symbol,
35219
+ fromAmount: aggregatedFromAmount,
35220
+ toAmount: aggregatedToAmount,
35221
+ effectivePrice: aggregatedFromAmount !== 0 ? aggregatedToAmount / aggregatedFromAmount : 0
35222
+ };
35223
+ logger.verbose(
35224
+ `${_VesuMultiplyAdapter.name}::_buildDecreaseLikeCalldata stored aggregated price info: ${aggregatedFromAmount} ${collateralToken.symbol} \u2192 ${aggregatedToAmount} ${debtToken.symbol}, effectivePrice=${this.lastSwapPriceInfo.effectivePrice}, swaps=${executedSwapCount}`
35225
+ );
35226
+ } else {
35227
+ this.lastSwapPriceInfo = null;
35228
+ }
35176
35229
  logger.debug(
35177
35230
  `${_VesuMultiplyAdapter.name}::_buildDecreaseLikeCalldata leverSwapCount=${leverSwap.length}, withdrawSwapCount=${withdrawSwap.length}, withdrawSwapLimitAmount=${withdrawSwapLimitAmount.toNumber()}, subMargin=${params.subMargin.toNumber()}`
35178
35231
  );
@@ -35981,13 +36034,13 @@ var ExtendedAdapter = class _ExtendedAdapter extends BaseAdapter {
35981
36034
  this.minimumExtendedMovementAmount = this.config.minimumExtendedMovementAmount ?? 5;
35982
36035
  this.client = client;
35983
36036
  this.retryDelayForOrderStatus = this.config.retryDelayForOrderStatus ?? 3e3;
35984
- this.usdceToken = this.config.supportedPositions[0].asset;
36037
+ this.usdcToken = this.config.supportedPositions[0].asset;
35985
36038
  }
35986
36039
  _depositApproveProofReadableId() {
35987
- return `extended_approve_${this.usdceToken.symbol}`;
36040
+ return `extended_approve_${this.usdcToken.symbol}`;
35988
36041
  }
35989
36042
  _depositCallProofReadableId() {
35990
- return `extended_deposit_${this.usdceToken.symbol}`;
36043
+ return `extended_deposit_${this.usdcToken.symbol}`;
35991
36044
  }
35992
36045
  //abstract means the method has no implementation in this class; instead, child classes must implement it.
35993
36046
  async getAPY(supportedPosition) {
@@ -36056,7 +36109,7 @@ var ExtendedAdapter = class _ExtendedAdapter extends BaseAdapter {
36056
36109
  _getDepositLeaf() {
36057
36110
  return [
36058
36111
  {
36059
- target: this.usdceToken.address,
36112
+ target: this.usdcToken.address,
36060
36113
  method: "approve",
36061
36114
  packedArguments: [this.config.extendedContract.toBigInt()],
36062
36115
  id: this._depositApproveProofReadableId(),
@@ -36076,14 +36129,14 @@ var ExtendedAdapter = class _ExtendedAdapter extends BaseAdapter {
36076
36129
  }
36077
36130
  async getDepositCall(params) {
36078
36131
  try {
36079
- const salt = Math.floor(Math.random() * 10 ** this.usdceToken.decimals);
36132
+ const salt = Math.floor(Math.random() * 10 ** this.usdcToken.decimals);
36080
36133
  const amount = uint25616.bnToUint256(params.amount.toWei());
36081
36134
  return [
36082
36135
  {
36083
36136
  proofReadableId: this._depositApproveProofReadableId(),
36084
36137
  sanitizer: SIMPLE_SANITIZER,
36085
36138
  call: {
36086
- contractAddress: this.usdceToken.address,
36139
+ contractAddress: this.usdcToken.address,
36087
36140
  selector: hash8.getSelectorFromName("approve"),
36088
36141
  calldata: [
36089
36142
  this.config.extendedContract.toBigInt(),
@@ -36984,8 +37037,8 @@ var AvnuAdapter = class _AvnuAdapter extends BaseAdapter {
36984
37037
  }
36985
37038
  };
36986
37039
 
36987
- // src/strategies/universal-strategy.tsx
36988
- import { CallData, Contract as Contract14, num as num12, uint256 as uint25619 } from "starknet";
37040
+ // src/strategies/universal-adapters/svk-troves-adapter.ts
37041
+ import { hash as hash11, uint256 as uint25619, Contract as Contract14 } from "starknet";
36989
37042
 
36990
37043
  // src/data/universal-vault.abi.json
36991
37044
  var universal_vault_abi_default = [
@@ -38669,6 +38722,272 @@ var universal_vault_abi_default = [
38669
38722
  }
38670
38723
  ];
38671
38724
 
38725
+ // src/strategies/universal-adapters/svk-troves-adapter.ts
38726
+ var DEFAULT_TROVES_STRATEGIES_API = "https://app.troves.fi/api/strategies";
38727
+ function parseTrovesApyField(raw) {
38728
+ if (typeof raw === "number" && Number.isFinite(raw)) {
38729
+ return raw;
38730
+ }
38731
+ if (typeof raw === "string") {
38732
+ const n = Number.parseFloat(raw);
38733
+ if (Number.isFinite(n)) {
38734
+ return n;
38735
+ }
38736
+ }
38737
+ return 0;
38738
+ }
38739
+ var SvkTrovesAdapter = class _SvkTrovesAdapter extends BaseAdapter {
38740
+ constructor(config) {
38741
+ super(config, _SvkTrovesAdapter.name, Protocols.TROVES);
38742
+ this.config = config;
38743
+ }
38744
+ /** Owner used for share balance + `due_assets_from_owner`. */
38745
+ _positionOwner() {
38746
+ return this.config.positionOwner ?? this.config.vaultAllocator;
38747
+ }
38748
+ /**
38749
+ * Proof readable IDs must stay ≤ 31 chars (Cairo short string). We derive a short ASCII suffix from
38750
+ * `strategyVault` address so multiple SVK adapters in one tree stay distinct.
38751
+ */
38752
+ _proofSuffix() {
38753
+ return this.config.strategyVault.address.replace(/^0x/, "").slice(-6);
38754
+ }
38755
+ _depositApproveProofReadableId() {
38756
+ return `appr_dep_svk_${this._proofSuffix()}`;
38757
+ }
38758
+ _depositCallProofReadableId() {
38759
+ return `dep_svk_${this._proofSuffix()}`;
38760
+ }
38761
+ _withdrawCallProofReadableId() {
38762
+ return `wtdrw_svk_${this._proofSuffix()}`;
38763
+ }
38764
+ async getAPY(supportedPosition) {
38765
+ const CACHE_KEY = `svk_apy_${this.config.trovesStrategyId}`;
38766
+ const cached = this.getCache(CACHE_KEY);
38767
+ if (cached) {
38768
+ return cached;
38769
+ }
38770
+ const url = this.config.trovesStrategiesApiUrl ?? DEFAULT_TROVES_STRATEGIES_API;
38771
+ try {
38772
+ const res = await fetch(url);
38773
+ if (!res.ok) {
38774
+ logger.warn(`${_SvkTrovesAdapter.name}::getAPY: HTTP ${res.status} from ${url}`);
38775
+ const fallback = { apy: 0, type: "base" /* BASE */ };
38776
+ this.setCache(CACHE_KEY, fallback, 3e5);
38777
+ return fallback;
38778
+ }
38779
+ const body = await res.json();
38780
+ const row = body.strategies?.find((s) => s.id === this.config.trovesStrategyId);
38781
+ if (!row) {
38782
+ logger.warn(
38783
+ `${_SvkTrovesAdapter.name}::getAPY: strategy id not found: ${this.config.trovesStrategyId}`
38784
+ );
38785
+ const fallback = { apy: 0, type: "base" /* BASE */ };
38786
+ this.setCache(CACHE_KEY, fallback, 3e5);
38787
+ return fallback;
38788
+ }
38789
+ const apy = parseTrovesApyField(row.apy);
38790
+ const result = { apy, type: "base" /* BASE */ };
38791
+ this.setCache(CACHE_KEY, result, 3e5);
38792
+ return result;
38793
+ } catch (error) {
38794
+ logger.error(`${_SvkTrovesAdapter.name}::getAPY:`, error);
38795
+ throw error;
38796
+ }
38797
+ }
38798
+ async getPosition(supportedPosition) {
38799
+ const CACHE_KEY = `svk_pos_${this.config.strategyVault.address}_${this._positionOwner().address}`;
38800
+ const cached = this.getCache(CACHE_KEY);
38801
+ if (cached) {
38802
+ return cached;
38803
+ }
38804
+ try {
38805
+ const vault = new Contract14({
38806
+ abi: universal_vault_abi_default,
38807
+ address: this.config.strategyVault.address,
38808
+ providerOrAccount: this.config.networkConfig.provider
38809
+ });
38810
+ const owner = this._positionOwner();
38811
+ const decimals = supportedPosition.asset.decimals;
38812
+ const shares = await vault.balance_of(owner.address);
38813
+ const shareU256 = uint25619.bnToUint256(shares);
38814
+ const liquidAssetsRaw = await vault.convert_to_assets(shareU256);
38815
+ const liquid = Web3Number.fromWei(liquidAssetsRaw.toString(), decimals);
38816
+ let pending = Web3Number.fromWei("0", decimals);
38817
+ try {
38818
+ const dueRaw = await vault.call("due_assets_from_owner", [owner.address]);
38819
+ pending = Web3Number.fromWei(dueRaw.toString(), decimals);
38820
+ } catch (e) {
38821
+ logger.warn(
38822
+ `${_SvkTrovesAdapter.name}::getPosition: due_assets_from_owner failed (treating as 0): ${e.message}`
38823
+ );
38824
+ }
38825
+ const total = liquid.plus(pending);
38826
+ const remarks = `Troves ${this.config.trovesStrategyId} holdings`;
38827
+ const result = {
38828
+ amount: total,
38829
+ remarks
38830
+ };
38831
+ this.setCache(CACHE_KEY, result, 6e4);
38832
+ return result;
38833
+ } catch (error) {
38834
+ logger.error(`${_SvkTrovesAdapter.name}::getPosition:`, error);
38835
+ throw error;
38836
+ }
38837
+ }
38838
+ async maxDeposit(amount) {
38839
+ const baseToken = this.config.baseToken;
38840
+ if (!amount) {
38841
+ return {
38842
+ tokenInfo: baseToken,
38843
+ amount: new Web3Number("999999999999999999999999999", baseToken.decimals),
38844
+ usdValue: 1e27,
38845
+ remarks: "Max deposit (unbounded placeholder)",
38846
+ apy: await this.getAPY({ asset: baseToken, isDebt: false }),
38847
+ protocol: this.protocol
38848
+ };
38849
+ }
38850
+ const usdValue = await this.getUSDValue(baseToken, amount);
38851
+ return {
38852
+ tokenInfo: baseToken,
38853
+ amount,
38854
+ usdValue,
38855
+ remarks: "Deposit amount",
38856
+ apy: await this.getAPY({ asset: baseToken, isDebt: false }),
38857
+ protocol: this.protocol
38858
+ };
38859
+ }
38860
+ async maxWithdraw() {
38861
+ const baseToken = this.config.baseToken;
38862
+ const current = await this.getPosition({ asset: baseToken, isDebt: false });
38863
+ const pos = current ?? { amount: new Web3Number("0", baseToken.decimals), remarks: "" };
38864
+ const usdValue = await this.getUSDValue(baseToken, pos.amount);
38865
+ return {
38866
+ tokenInfo: baseToken,
38867
+ amount: pos.amount,
38868
+ usdValue,
38869
+ remarks: "Max withdraw (liquid + pending redemption, underlying units)",
38870
+ apy: await this.getAPY({ asset: baseToken, isDebt: false }),
38871
+ protocol: this.protocol
38872
+ };
38873
+ }
38874
+ _getDepositLeaf() {
38875
+ const baseToken = this.config.baseToken;
38876
+ const strategyVault = this.config.strategyVault;
38877
+ const receiver = this.config.vaultAllocator;
38878
+ return [
38879
+ {
38880
+ target: baseToken.address,
38881
+ method: "approve",
38882
+ packedArguments: [strategyVault.toBigInt()],
38883
+ sanitizer: SIMPLE_SANITIZER,
38884
+ id: this._depositApproveProofReadableId()
38885
+ },
38886
+ {
38887
+ target: strategyVault,
38888
+ method: "deposit",
38889
+ packedArguments: [receiver.toBigInt()],
38890
+ sanitizer: SIMPLE_SANITIZER,
38891
+ id: this._depositCallProofReadableId()
38892
+ }
38893
+ ];
38894
+ }
38895
+ _getWithdrawLeaf() {
38896
+ const strategyVault = this.config.strategyVault;
38897
+ const recv = this.config.vaultAllocator;
38898
+ const owner = this.config.vaultAllocator;
38899
+ return [
38900
+ {
38901
+ target: strategyVault,
38902
+ method: "withdraw",
38903
+ packedArguments: [recv.toBigInt(), owner.toBigInt()],
38904
+ sanitizer: SIMPLE_SANITIZER,
38905
+ id: this._withdrawCallProofReadableId()
38906
+ }
38907
+ ];
38908
+ }
38909
+ getDepositAdapter() {
38910
+ const leafConfigs = this._getDepositLeaf();
38911
+ const leaves = leafConfigs.map((config) => {
38912
+ const { target, method, packedArguments, sanitizer, id } = config;
38913
+ return this.constructSimpleLeafData({ id, target, method, packedArguments }, sanitizer);
38914
+ });
38915
+ return { leaves, callConstructor: this.getDepositCall.bind(this) };
38916
+ }
38917
+ getWithdrawAdapter() {
38918
+ const leafConfigs = this._getWithdrawLeaf();
38919
+ const leaves = leafConfigs.map((config) => {
38920
+ const { target, method, packedArguments, sanitizer, id } = config;
38921
+ return this.constructSimpleLeafData({ id, target, method, packedArguments }, sanitizer);
38922
+ });
38923
+ return { leaves, callConstructor: this.getWithdrawCall.bind(this) };
38924
+ }
38925
+ async getDepositCall(params) {
38926
+ const baseToken = this.config.baseToken;
38927
+ const strategyVault = this.config.strategyVault;
38928
+ const amount = params.amount;
38929
+ const uint256Amount = uint25619.bnToUint256(amount.toWei());
38930
+ const receiver = this.config.vaultAllocator;
38931
+ return [
38932
+ {
38933
+ proofReadableId: this._depositApproveProofReadableId(),
38934
+ sanitizer: SIMPLE_SANITIZER,
38935
+ call: {
38936
+ contractAddress: baseToken.address,
38937
+ selector: hash11.getSelectorFromName("approve"),
38938
+ calldata: [
38939
+ strategyVault.toBigInt(),
38940
+ toBigInt(uint256Amount.low.toString()),
38941
+ toBigInt(uint256Amount.high.toString())
38942
+ ]
38943
+ }
38944
+ },
38945
+ {
38946
+ proofReadableId: this._depositCallProofReadableId(),
38947
+ sanitizer: SIMPLE_SANITIZER,
38948
+ call: {
38949
+ contractAddress: strategyVault,
38950
+ selector: hash11.getSelectorFromName("deposit"),
38951
+ calldata: [
38952
+ toBigInt(uint256Amount.low.toString()),
38953
+ toBigInt(uint256Amount.high.toString()),
38954
+ receiver.toBigInt()
38955
+ ]
38956
+ }
38957
+ }
38958
+ ];
38959
+ }
38960
+ async getWithdrawCall(params) {
38961
+ const strategyVault = this.config.strategyVault;
38962
+ const amount = params.amount;
38963
+ const uint256Amount = uint25619.bnToUint256(amount.toWei());
38964
+ const recv = this.config.vaultAllocator;
38965
+ const owner = this.config.vaultAllocator;
38966
+ return [
38967
+ {
38968
+ proofReadableId: this._withdrawCallProofReadableId(),
38969
+ sanitizer: SIMPLE_SANITIZER,
38970
+ call: {
38971
+ contractAddress: strategyVault,
38972
+ selector: hash11.getSelectorFromName("withdraw"),
38973
+ calldata: [
38974
+ toBigInt(uint256Amount.low.toString()),
38975
+ toBigInt(uint256Amount.high.toString()),
38976
+ recv.toBigInt(),
38977
+ owner.toBigInt()
38978
+ ]
38979
+ }
38980
+ }
38981
+ ];
38982
+ }
38983
+ getHealthFactor() {
38984
+ return Promise.resolve(10);
38985
+ }
38986
+ };
38987
+
38988
+ // src/strategies/universal-strategy.tsx
38989
+ import { CallData, Contract as Contract15, num as num12, uint256 as uint25620 } from "starknet";
38990
+
38672
38991
  // src/data/vault-manager.abi.json
38673
38992
  var vault_manager_abi_default = [
38674
38993
  {
@@ -39334,12 +39653,12 @@ var UniversalStrategy = class _UniversalStrategy extends BaseStrategy {
39334
39653
  );
39335
39654
  this.metadata = metadata;
39336
39655
  this.address = metadata.address;
39337
- this.contract = new Contract14({
39656
+ this.contract = new Contract15({
39338
39657
  abi: universal_vault_abi_default,
39339
39658
  address: this.address.address,
39340
39659
  providerOrAccount: this.config.provider
39341
39660
  });
39342
- this.managerContract = new Contract14({
39661
+ this.managerContract = new Contract15({
39343
39662
  abi: vault_manager_abi_default,
39344
39663
  address: this.metadata.additionalInfo.manager.address,
39345
39664
  providerOrAccount: this.config.provider
@@ -39393,17 +39712,17 @@ var UniversalStrategy = class _UniversalStrategy extends BaseStrategy {
39393
39712
  amountInfo.tokenInfo.address.eq(this.asset().address),
39394
39713
  "Deposit token mismatch"
39395
39714
  );
39396
- const assetContract = new Contract14({
39715
+ const assetContract = new Contract15({
39397
39716
  abi: universal_vault_abi_default,
39398
39717
  address: this.asset().address.address,
39399
39718
  providerOrAccount: this.config.provider
39400
39719
  });
39401
39720
  const call1 = assetContract.populate("approve", [
39402
39721
  this.address.address,
39403
- uint25619.bnToUint256(amountInfo.amount.toWei())
39722
+ uint25620.bnToUint256(amountInfo.amount.toWei())
39404
39723
  ]);
39405
39724
  const call2 = this.contract.populate("deposit", [
39406
- uint25619.bnToUint256(amountInfo.amount.toWei()),
39725
+ uint25620.bnToUint256(amountInfo.amount.toWei()),
39407
39726
  receiver.address
39408
39727
  ]);
39409
39728
  return [call1, call2];
@@ -39413,9 +39732,9 @@ var UniversalStrategy = class _UniversalStrategy extends BaseStrategy {
39413
39732
  amountInfo.tokenInfo.address.eq(this.asset().address),
39414
39733
  "Withdraw token mismatch"
39415
39734
  );
39416
- const shares = await this.contract.call("convert_to_shares", [uint25619.bnToUint256(amountInfo.amount.toWei())]);
39735
+ const shares = await this.contract.call("convert_to_shares", [uint25620.bnToUint256(amountInfo.amount.toWei())]);
39417
39736
  const call = this.contract.populate("request_redeem", [
39418
- uint25619.bnToUint256(shares.toString()),
39737
+ uint25620.bnToUint256(shares.toString()),
39419
39738
  receiver.address,
39420
39739
  owner.address
39421
39740
  ]);
@@ -39425,7 +39744,7 @@ var UniversalStrategy = class _UniversalStrategy extends BaseStrategy {
39425
39744
  const shares = await this.contract.call("balanceOf", [user.address], { blockIdentifier });
39426
39745
  const assets = await this.contract.call(
39427
39746
  "convert_to_assets",
39428
- [uint25619.bnToUint256(shares)],
39747
+ [uint25620.bnToUint256(shares)],
39429
39748
  { blockIdentifier }
39430
39749
  );
39431
39750
  const amount = Web3Number.fromWei(
@@ -40359,19 +40678,19 @@ var UniversalStrategies = [
40359
40678
  ];
40360
40679
 
40361
40680
  // src/strategies/svk-strategy.ts
40362
- import { Contract as Contract15, num as num13, uint256 as uint25620 } from "starknet";
40681
+ import { Contract as Contract16, num as num13, uint256 as uint25621 } from "starknet";
40363
40682
  var SVKStrategy = class extends BaseStrategy {
40364
40683
  constructor(config, pricer, metadata) {
40365
40684
  super(config);
40366
40685
  this.pricer = pricer;
40367
40686
  this.metadata = metadata;
40368
40687
  this.address = metadata.address;
40369
- this.contract = new Contract15({
40688
+ this.contract = new Contract16({
40370
40689
  abi: universal_vault_abi_default,
40371
40690
  address: this.address.address,
40372
40691
  providerOrAccount: this.config.provider
40373
40692
  });
40374
- this.managerContract = new Contract15({
40693
+ this.managerContract = new Contract16({
40375
40694
  abi: vault_manager_abi_default,
40376
40695
  address: this.metadata.additionalInfo.manager.address,
40377
40696
  providerOrAccount: this.config.provider
@@ -40388,17 +40707,17 @@ var SVKStrategy = class extends BaseStrategy {
40388
40707
  amountInfo.tokenInfo.address.eq(this.asset().address),
40389
40708
  "Deposit token mismatch"
40390
40709
  );
40391
- const assetContract = new Contract15({
40710
+ const assetContract = new Contract16({
40392
40711
  abi: universal_vault_abi_default,
40393
40712
  address: this.asset().address.address,
40394
40713
  providerOrAccount: this.config.provider
40395
40714
  });
40396
40715
  const call1 = assetContract.populate("approve", [
40397
40716
  this.address.address,
40398
- uint25620.bnToUint256(amountInfo.amount.toWei())
40717
+ uint25621.bnToUint256(amountInfo.amount.toWei())
40399
40718
  ]);
40400
40719
  const call2 = this.contract.populate("deposit", [
40401
- uint25620.bnToUint256(amountInfo.amount.toWei()),
40720
+ uint25621.bnToUint256(amountInfo.amount.toWei()),
40402
40721
  receiver.address
40403
40722
  ]);
40404
40723
  return [call1, call2];
@@ -40408,9 +40727,9 @@ var SVKStrategy = class extends BaseStrategy {
40408
40727
  amountInfo.tokenInfo.address.eq(this.asset().address),
40409
40728
  "Withdraw token mismatch"
40410
40729
  );
40411
- const shares = await this.contract.call("convert_to_shares", [uint25620.bnToUint256(amountInfo.amount.toWei())]);
40730
+ const shares = await this.contract.call("convert_to_shares", [uint25621.bnToUint256(amountInfo.amount.toWei())]);
40412
40731
  const call = this.contract.populate("request_redeem", [
40413
- uint25620.bnToUint256(shares.toString()),
40732
+ uint25621.bnToUint256(shares.toString()),
40414
40733
  receiver.address,
40415
40734
  owner.address
40416
40735
  ]);
@@ -40601,7 +40920,7 @@ var SVKStrategy = class extends BaseStrategy {
40601
40920
  };
40602
40921
 
40603
40922
  // src/strategies/universal-lst-muliplier-strategy.tsx
40604
- import { Contract as Contract16, uint256 as uint25621 } from "starknet";
40923
+ import { Contract as Contract17, uint256 as uint25622 } from "starknet";
40605
40924
 
40606
40925
  // src/strategies/universal-adapters/adapter-optimizer.ts
40607
40926
  var AdapterOptimizer = class {
@@ -40754,15 +41073,15 @@ var UniversalLstMultiplierStrategy = class _UniversalLstMultiplierStrategy exten
40754
41073
  async getLSTExchangeRate() {
40755
41074
  const vesuAdapter1 = this.getVesuSameTokenAdapter();
40756
41075
  const lstTokenInfo = vesuAdapter1._vesuAdapter.config.collateral;
40757
- const lstABI = new Contract16({
41076
+ const lstABI = new Contract17({
40758
41077
  abi: erc4626_abi_default,
40759
41078
  address: lstTokenInfo.address.address,
40760
41079
  providerOrAccount: this.config.provider
40761
41080
  });
40762
41081
  const price = await lstABI.call("convert_to_assets", [
40763
- uint25621.bnToUint256(new Web3Number(1, lstTokenInfo.decimals).toWei())
41082
+ uint25622.bnToUint256(new Web3Number(1, lstTokenInfo.decimals).toWei())
40764
41083
  ]);
40765
- const exchangeRate = Number(uint25621.uint256ToBN(price).toString()) / Math.pow(10, lstTokenInfo.decimals);
41084
+ const exchangeRate = Number(uint25622.uint256ToBN(price).toString()) / Math.pow(10, lstTokenInfo.decimals);
40766
41085
  logger.verbose(`${this.getTag()}:: LST Exchange Rate: ${exchangeRate}`);
40767
41086
  return exchangeRate;
40768
41087
  }
@@ -41632,6 +41951,249 @@ var HyperLSTStrategies = [
41632
41951
  getStrategySettings("mRe7YIELD", "mRe7YIELD", hypermRe7YIELD, false, false)
41633
41952
  ];
41634
41953
 
41954
+ // src/strategies/vesu-extended-strategy/services/ltv-imbalance-rebalance-math.ts
41955
+ function ceilBtc(v, precision) {
41956
+ const f = 10 ** precision;
41957
+ return Math.ceil(v * f) / f;
41958
+ }
41959
+ function floorBtc(v, precision) {
41960
+ const f = 10 ** precision;
41961
+ return Math.floor(v * f) / f;
41962
+ }
41963
+ function isNegligible(btc, precision) {
41964
+ return Math.abs(btc) < 10 ** -precision;
41965
+ }
41966
+ function computeExtIdealMargin(posBtc, leverage, price) {
41967
+ return posBtc * price / leverage;
41968
+ }
41969
+ function computeExtDeficit(ext, price) {
41970
+ return Math.max(0, computeExtIdealMargin(ext.positionBtc, ext.leverage, price) - ext.equity);
41971
+ }
41972
+ function computeVesuHF(vesu, price) {
41973
+ const collateralUsd = vesu.positionBtc * price;
41974
+ if (collateralUsd === 0) return Infinity;
41975
+ const ltv = vesu.debt * vesu.debtPrice / collateralUsd;
41976
+ if (ltv === 0) return Infinity;
41977
+ return vesu.maxLTV / ltv;
41978
+ }
41979
+ function computeVesuDebtRepay(vesu, price) {
41980
+ const targetLTV = vesu.maxLTV / vesu.targetHF;
41981
+ const collateralUsd = vesu.positionBtc * price;
41982
+ const requiredDebt = targetLTV * collateralUsd / vesu.debtPrice;
41983
+ return Math.max(0, vesu.debt - requiredDebt);
41984
+ }
41985
+ function computeVesuTargetLTV(vesu) {
41986
+ return vesu.maxLTV / vesu.targetHF;
41987
+ }
41988
+ function computeVesuGrowthCost(gapBtc, vesu, price) {
41989
+ const targetLTV = computeVesuTargetLTV(vesu);
41990
+ return gapBtc * price * (1 - targetLTV);
41991
+ }
41992
+ function computeVesuGrowthDebt(gapBtc, vesu, price) {
41993
+ const targetLTV = computeVesuTargetLTV(vesu);
41994
+ return gapBtc * price * targetLTV / vesu.debtPrice;
41995
+ }
41996
+ function computeVesuGrowthFromEquity(equityUsd, vesu, price) {
41997
+ const targetLTV = computeVesuTargetLTV(vesu);
41998
+ return equityUsd / (price * (1 - targetLTV));
41999
+ }
42000
+ function emptyDeltas() {
42001
+ return {
42002
+ dExtPosition: 0,
42003
+ dVesuPosition: 0,
42004
+ dVesuDebt: 0,
42005
+ dExtAvlWithdraw: 0,
42006
+ dExtUpnl: 0,
42007
+ dVaUsd: 0,
42008
+ dWalletUsd: 0,
42009
+ dVesuBorrowCapacity: 0,
42010
+ dTransferVesuToExt: 0
42011
+ };
42012
+ }
42013
+ function mergeDeltas(a, b) {
42014
+ return {
42015
+ dExtPosition: a.dExtPosition + b.dExtPosition,
42016
+ dVesuPosition: a.dVesuPosition + b.dVesuPosition,
42017
+ dVesuDebt: a.dVesuDebt + b.dVesuDebt,
42018
+ dExtAvlWithdraw: a.dExtAvlWithdraw + b.dExtAvlWithdraw,
42019
+ dExtUpnl: a.dExtUpnl + b.dExtUpnl,
42020
+ dVaUsd: a.dVaUsd + b.dVaUsd,
42021
+ dWalletUsd: a.dWalletUsd + b.dWalletUsd,
42022
+ dVesuBorrowCapacity: a.dVesuBorrowCapacity + b.dVesuBorrowCapacity,
42023
+ dTransferVesuToExt: a.dTransferVesuToExt + b.dTransferVesuToExt
42024
+ };
42025
+ }
42026
+ function drawFunds(need, keys, pool) {
42027
+ const draws = {};
42028
+ let unmet = need;
42029
+ for (const k of keys) {
42030
+ if (unmet <= 0) break;
42031
+ const avail = pool[k];
42032
+ if (avail <= 0) continue;
42033
+ const take = Math.min(avail, unmet);
42034
+ draws[k] = take;
42035
+ pool[k] -= take;
42036
+ unmet -= take;
42037
+ }
42038
+ return { draws, filled: need - unmet, unmet };
42039
+ }
42040
+ function sumKeys(draws, keys) {
42041
+ return keys.reduce((s, k) => s + (draws[k] ?? 0), 0);
42042
+ }
42043
+ function applyDrawsToDeltas(d, draws, sign) {
42044
+ d.dVaUsd += sign * (draws.vaUsd ?? 0);
42045
+ d.dWalletUsd += sign * (draws.walletUsd ?? 0);
42046
+ d.dVesuBorrowCapacity += sign * (draws.vesuBorrowCapacity ?? 0);
42047
+ d.dExtAvlWithdraw += sign * (draws.extAvlWithdraw ?? 0);
42048
+ d.dExtUpnl += sign * (draws.extUpnl ?? 0);
42049
+ }
42050
+ function fixExtMargin(ext, price, pool) {
42051
+ const d = emptyDeltas();
42052
+ const deficit = computeExtDeficit(ext, price);
42053
+ if (deficit <= 0) return { d, unmet: 0 };
42054
+ const { draws, filled, unmet } = drawFunds(deficit, ["walletUsd", "vaUsd", "vesuBorrowCapacity"], pool);
42055
+ applyDrawsToDeltas(d, draws, -1);
42056
+ d.dExtAvlWithdraw += filled;
42057
+ const fromVesu = draws.vesuBorrowCapacity ?? 0;
42058
+ if (fromVesu > 0) d.dTransferVesuToExt += fromVesu;
42059
+ return { d, unmet };
42060
+ }
42061
+ function fixVesuMargin(vesu, price, pool, hfBuffer) {
42062
+ const d = emptyDeltas();
42063
+ const hf = computeVesuHF(vesu, price);
42064
+ if (hf >= vesu.targetHF - hfBuffer) return { d, unmet: 0 };
42065
+ const repayTokens = computeVesuDebtRepay(vesu, price);
42066
+ const repayUsd = repayTokens * vesu.debtPrice;
42067
+ const { draws, filled, unmet } = drawFunds(repayUsd, ["vaUsd", "walletUsd", "extAvlWithdraw", "extUpnl"], pool);
42068
+ applyDrawsToDeltas(d, draws, -1);
42069
+ d.dVesuDebt -= filled / vesu.debtPrice;
42070
+ const fromExt = sumKeys(draws, ["extAvlWithdraw", "extUpnl"]);
42071
+ if (fromExt > 0) d.dTransferVesuToExt -= fromExt;
42072
+ return { d, unmet };
42073
+ }
42074
+ function phase1(ext, vesu, price, pool, config) {
42075
+ const extResult = fixExtMargin(ext, price, pool);
42076
+ const vesuResult = fixVesuMargin(vesu, price, pool, config.hfBuffer);
42077
+ return {
42078
+ deltas: mergeDeltas(extResult.d, vesuResult.d),
42079
+ extDeficitRemaining: extResult.unmet,
42080
+ vesuRepayRemaining: vesuResult.unmet
42081
+ };
42082
+ }
42083
+ function solveUnifiedF(extPos, vesuPos, extEquity, vesuDebt, vesu, extLev, price) {
42084
+ const targetLTV = computeVesuTargetLTV(vesu);
42085
+ const k = 1 - targetLTV + 1 / extLev;
42086
+ const totalEquity = extEquity + vesuPos * price - vesuDebt * vesu.debtPrice;
42087
+ return totalEquity / (price * k);
42088
+ }
42089
+ function solveTransfer(vesuPos, vesuDebt, F, vesu, price) {
42090
+ const targetLTV = computeVesuTargetLTV(vesu);
42091
+ return vesuPos * price - vesuDebt * vesu.debtPrice - F * price * (1 - targetLTV);
42092
+ }
42093
+ function solveDebtRepay(vesuDebt, F, vesu, price) {
42094
+ const targetLTV = computeVesuTargetLTV(vesu);
42095
+ return vesuDebt - targetLTV * F * price / vesu.debtPrice;
42096
+ }
42097
+ function roundFinalPosition(extPos, vesuPos, rawF, precision) {
42098
+ let fFromExt;
42099
+ if (rawF < extPos) {
42100
+ const closeExt = ceilBtc(extPos - rawF, precision);
42101
+ fFromExt = extPos - closeExt;
42102
+ } else {
42103
+ const growExt = floorBtc(rawF - extPos, precision);
42104
+ fFromExt = extPos + growExt;
42105
+ }
42106
+ let fFromVesu;
42107
+ if (rawF < vesuPos) {
42108
+ const closeVesu = ceilBtc(vesuPos - rawF, precision);
42109
+ fFromVesu = vesuPos - closeVesu;
42110
+ } else {
42111
+ const growVesu = floorBtc(rawF - vesuPos, precision);
42112
+ fFromVesu = vesuPos + growVesu;
42113
+ }
42114
+ return Math.max(0, Math.min(fFromExt, fFromVesu));
42115
+ }
42116
+ function phase2(extPos, vesuPos, extEquity, vesuDebt, vesu, extLev, price, pool, precision) {
42117
+ const d = emptyDeltas();
42118
+ const targetLTV = computeVesuTargetLTV(vesu);
42119
+ const imbalance = extPos - vesuPos;
42120
+ let fundedGrowthBtc = 0;
42121
+ if (!isNegligible(imbalance, precision)) {
42122
+ if (imbalance > 0) {
42123
+ const equityCostUsd = computeVesuGrowthCost(imbalance, vesu, price);
42124
+ const { draws, filled } = drawFunds(equityCostUsd, ["vaUsd", "walletUsd", "extAvlWithdraw", "extUpnl"], pool);
42125
+ applyDrawsToDeltas(d, draws, -1);
42126
+ const grownBtc = computeVesuGrowthFromEquity(filled, vesu, price);
42127
+ d.dVesuPosition += grownBtc;
42128
+ fundedGrowthBtc = grownBtc;
42129
+ d.dVesuDebt += computeVesuGrowthDebt(grownBtc, vesu, price);
42130
+ const fromExt = sumKeys(draws, ["extAvlWithdraw", "extUpnl"]);
42131
+ if (fromExt > 0) d.dTransferVesuToExt -= fromExt;
42132
+ } else {
42133
+ const absImbalance = -imbalance;
42134
+ const marginCostUsd = absImbalance * price / extLev;
42135
+ const { draws, filled } = drawFunds(marginCostUsd, ["walletUsd", "vaUsd", "vesuBorrowCapacity"], pool);
42136
+ applyDrawsToDeltas(d, draws, -1);
42137
+ const grownBtc = filled * extLev / price;
42138
+ d.dExtPosition += grownBtc;
42139
+ fundedGrowthBtc = grownBtc;
42140
+ const fromVesu = draws.vesuBorrowCapacity ?? 0;
42141
+ if (fromVesu > 0) d.dTransferVesuToExt += fromVesu;
42142
+ }
42143
+ }
42144
+ const effExtPos = extPos + d.dExtPosition;
42145
+ const effVesuPos = vesuPos + d.dVesuPosition;
42146
+ const effExtEquity = extEquity;
42147
+ const effVesuDebt = vesuDebt + d.dVesuDebt;
42148
+ const rawF = solveUnifiedF(effExtPos, effVesuPos, effExtEquity, effVesuDebt, vesu, extLev, price);
42149
+ const cappedF = Math.min(rawF, Math.max(effExtPos, effVesuPos));
42150
+ const maxGrowableTo = Math.max(effExtPos, effVesuPos);
42151
+ const targetF = Math.max(0, Math.min(cappedF, maxGrowableTo));
42152
+ const remainingImbalance = effExtPos - effVesuPos;
42153
+ const extNeedsMore = effExtEquity < computeExtIdealMargin(effExtPos, extLev, price);
42154
+ const vesuCurrentLTV = effVesuPos > 0 ? effVesuDebt * vesu.debtPrice / (effVesuPos * price) : 0;
42155
+ const vesuNeedsMore = vesuCurrentLTV > targetLTV;
42156
+ const needsFurtherAction = !isNegligible(remainingImbalance, precision) || extNeedsMore || vesuNeedsMore;
42157
+ if (!needsFurtherAction) {
42158
+ return d;
42159
+ }
42160
+ const F = roundFinalPosition(effExtPos, effVesuPos, targetF, precision);
42161
+ const dx = F - effExtPos;
42162
+ const dy = F - effVesuPos;
42163
+ if (isNegligible(dx, precision) && isNegligible(dy, precision)) {
42164
+ return d;
42165
+ }
42166
+ d.dExtPosition += dx;
42167
+ d.dVesuPosition += dy;
42168
+ const debtRepay = solveDebtRepay(effVesuDebt, F, vesu, price);
42169
+ d.dVesuDebt -= debtRepay;
42170
+ const T = solveTransfer(effVesuPos, effVesuDebt, F, vesu, price);
42171
+ d.dTransferVesuToExt += T;
42172
+ d.dExtAvlWithdraw += T;
42173
+ return d;
42174
+ }
42175
+ function rebalance(inputs) {
42176
+ const { ext, vesu, btcPrice, config } = inputs;
42177
+ const pool = { ...inputs.funding };
42178
+ const p1 = phase1(ext, vesu, btcPrice, pool, config);
42179
+ const effExtPos = ext.positionBtc + p1.deltas.dExtPosition;
42180
+ const effVesuPos = vesu.positionBtc + p1.deltas.dVesuPosition;
42181
+ const effExtEquity = ext.equity + p1.deltas.dExtAvlWithdraw + p1.deltas.dExtUpnl;
42182
+ const effVesuDebt = vesu.debt + p1.deltas.dVesuDebt;
42183
+ const p2 = phase2(
42184
+ effExtPos,
42185
+ effVesuPos,
42186
+ effExtEquity,
42187
+ effVesuDebt,
42188
+ vesu,
42189
+ ext.leverage,
42190
+ btcPrice,
42191
+ pool,
42192
+ config.positionPrecision
42193
+ );
42194
+ return mergeDeltas(p1.deltas, p2);
42195
+ }
42196
+
41635
42197
  // src/strategies/vesu-extended-strategy/services/extended-vesu-state-manager.ts
41636
42198
  var RouteType = /* @__PURE__ */ ((RouteType2) => {
41637
42199
  RouteType2["WALLET_TO_EXTENDED"] = "WALLET_TO_EXTENDED";
@@ -41663,6 +42225,7 @@ var CaseCategory = /* @__PURE__ */ ((CaseCategory2) => {
41663
42225
  return CaseCategory2;
41664
42226
  })(CaseCategory || {});
41665
42227
  var CaseId = /* @__PURE__ */ ((CaseId2) => {
42228
+ CaseId2["MANAGE_LTV"] = "MANAGE_LTV";
41666
42229
  CaseId2["LTV_VESU_LOW_TO_EXTENDED"] = "LTV_VESU_LOW_TO_EXTENDED";
41667
42230
  CaseId2["LTV_EXTENDED_PROFITABLE_AVAILABLE"] = "LTV_EXTENDED_PROFITABLE_AVAILABLE";
41668
42231
  CaseId2["LTV_EXTENDED_PROFITABLE_REALIZE"] = "LTV_EXTENDED_PROFITABLE_REALIZE";
@@ -41687,19 +42250,56 @@ function safeUsdcWeb3Number(value) {
41687
42250
  return new Web3Number(value.toFixed(USDC_TOKEN_DECIMALS), USDC_TOKEN_DECIMALS);
41688
42251
  }
41689
42252
  var CASE_ROUTE_TYPES = {
41690
- // LTV Rebalance — Vesu side
42253
+ // LTV Rebalance — unified
42254
+ ["MANAGE_LTV" /* MANAGE_LTV */]: [
42255
+ "REALISE_PNL" /* REALISE_PNL */,
42256
+ "EXTENDED_TO_WALLET" /* EXTENDED_TO_WALLET */,
42257
+ "RETURN_TO_WAIT" /* RETURN_TO_WAIT */,
42258
+ "WALLET_TO_VA" /* WALLET_TO_VA */,
42259
+ "VESU_BORROW" /* VESU_BORROW */,
42260
+ "VESU_REPAY" /* VESU_REPAY */,
42261
+ "VA_TO_EXTENDED" /* VA_TO_EXTENDED */,
42262
+ "VESU_MULTIPLY_DECREASE_LEVER" /* VESU_MULTIPLY_DECREASE_LEVER */,
42263
+ "VESU_MULTIPLY_INCREASE_LEVER" /* VESU_MULTIPLY_INCREASE_LEVER */,
42264
+ "AVNU_DEPOSIT_SWAP" /* AVNU_DEPOSIT_SWAP */,
42265
+ "EXTENDED_DECREASE_LEVER" /* EXTENDED_DECREASE_LEVER */,
42266
+ "EXTENDED_INCREASE_LEVER" /* EXTENDED_INCREASE_LEVER */,
42267
+ // Second-phase VA / Extended funding after lever routes (same types may repeat).
42268
+ "RETURN_TO_WAIT" /* RETURN_TO_WAIT */,
42269
+ "WALLET_TO_VA" /* WALLET_TO_VA */,
42270
+ "VA_TO_EXTENDED" /* VA_TO_EXTENDED */
42271
+ ],
42272
+ /** @deprecated */
41691
42273
  ["LTV_VESU_HIGH_USE_VA_OR_WALLET" /* LTV_VESU_HIGH_USE_VA_OR_WALLET */]: ["WALLET_TO_VA" /* WALLET_TO_VA */, "VESU_REPAY" /* VESU_REPAY */],
41692
- // use wallet to va if wallet has and va doesnt have enough
42274
+ /** @deprecated */
41693
42275
  ["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 */],
42276
+ /** @deprecated */
41694
42277
  ["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 */],
41695
- // LTV Rebalance — Extended side
42278
+ /** @deprecated */
41696
42279
  ["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 */],
42280
+ /** @deprecated */
41697
42281
  ["LTV_VESU_LOW_TO_EXTENDED" /* LTV_VESU_LOW_TO_EXTENDED */]: ["VESU_BORROW" /* VESU_BORROW */, "VA_TO_EXTENDED" /* VA_TO_EXTENDED */],
41698
42282
  // New Deposits
41699
42283
  // @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
41700
42284
  // Sequence: fund-movement transfers first (WALLET_TO_EXTENDED, VA_TO_EXTENDED, WALLET_TO_VA, EXTENDED_TO_WALLET),
41701
42285
  // then RETURN_TO_WAIT, then optional second WALLET_TO_VA + RETURN_TO_WAIT, then lever routes.
41702
- ["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 */],
42286
+ ["DEPOSIT_FRESH_VAULT" /* DEPOSIT_FRESH_VAULT */]: [
42287
+ // May repeat after MANAGE_LTV (VA top-up → Extended → wait → levers).
42288
+ "WALLET_TO_VA" /* WALLET_TO_VA */,
42289
+ "VA_TO_EXTENDED" /* VA_TO_EXTENDED */,
42290
+ "RETURN_TO_WAIT" /* RETURN_TO_WAIT */,
42291
+ "VA_TO_EXTENDED" /* VA_TO_EXTENDED */,
42292
+ "VESU_BORROW" /* VESU_BORROW */,
42293
+ "WALLET_TO_EXTENDED" /* WALLET_TO_EXTENDED */,
42294
+ "VA_TO_EXTENDED" /* VA_TO_EXTENDED */,
42295
+ "WALLET_TO_VA" /* WALLET_TO_VA */,
42296
+ "EXTENDED_TO_WALLET" /* EXTENDED_TO_WALLET */,
42297
+ "RETURN_TO_WAIT" /* RETURN_TO_WAIT */,
42298
+ "WALLET_TO_VA" /* WALLET_TO_VA */,
42299
+ "AVNU_DEPOSIT_SWAP" /* AVNU_DEPOSIT_SWAP */,
42300
+ "VESU_MULTIPLY_INCREASE_LEVER" /* VESU_MULTIPLY_INCREASE_LEVER */,
42301
+ "EXTENDED_INCREASE_LEVER" /* EXTENDED_INCREASE_LEVER */
42302
+ ],
41703
42303
  ["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 */],
41704
42304
  ["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 */],
41705
42305
  ["DEPOSIT_COMBINATION" /* DEPOSIT_COMBINATION */]: [],
@@ -41722,6 +42322,17 @@ var CASE_ROUTE_TYPES = {
41722
42322
  ["IMBALANCE_VESU_EXCESS_LONG_NO_FUNDS" /* IMBALANCE_VESU_EXCESS_LONG_NO_FUNDS */]: ["VESU_MULTIPLY_DECREASE_LEVER" /* VESU_MULTIPLY_DECREASE_LEVER */]
41723
42323
  };
41724
42324
  var CASE_DEFINITIONS = {
42325
+ ["MANAGE_LTV" /* MANAGE_LTV */]: {
42326
+ id: "MANAGE_LTV" /* MANAGE_LTV */,
42327
+ category: "LTV_REBALANCE" /* LTV_REBALANCE */,
42328
+ title: "LTV Rebalance: Unified Vesu repay + Extended margin management",
42329
+ description: "Manages both Vesu high-LTV repayment and Extended low-margin funding in a single pass.",
42330
+ steps: [
42331
+ "Compute vesu repay needed and extended margin needed",
42332
+ "Allocate funds: VA > Wallet > ExtAvl > ExtUpnl for Vesu; Wallet > VA > Borrow for Extended",
42333
+ "Build combined transfer and repay/margin routes"
42334
+ ]
42335
+ },
41725
42336
  ["LTV_VESU_LOW_TO_EXTENDED" /* LTV_VESU_LOW_TO_EXTENDED */]: {
41726
42337
  id: "LTV_VESU_LOW_TO_EXTENDED" /* LTV_VESU_LOW_TO_EXTENDED */,
41727
42338
  category: "LTV_REBALANCE" /* LTV_REBALANCE */,
@@ -41944,199 +42555,590 @@ function routeSummary(r) {
41944
42555
  }
41945
42556
  var SolveBudget = class {
41946
42557
  constructor(state) {
41947
- // should be same length as vesuPerPoolDebtDeltasToBorrow
41948
- // ── Budget tracking (populated by initBudget) ──────────────────────
41949
- this._vaUsd = 0;
41950
- this._walletUsd = 0;
41951
- this._extAvailWithdraw = 0;
41952
- this._extAvailUpnl = 0;
41953
- this._extAvailTrade = 0;
41954
- this._extPendingDeposit = 0;
41955
- this._vesuBorrowCapacity = 0;
41956
- this._totalUnused = 0;
41957
- const buffer = state.limitBalanceBufferFactor;
41958
- this.unusedBalance = state.unusedBalance.map((item) => {
41959
- return {
41960
- ...item,
41961
- amount: item.amount.multipliedBy(1 - buffer),
41962
- usdValue: item.usdValue * (1 - buffer)
41963
- };
42558
+ this.assetToken = state.assetToken;
42559
+ this.usdcToken = state.usdcToken;
42560
+ const cloneTb = (b) => ({
42561
+ token: b.token,
42562
+ amount: new Web3Number(b.amount.toFixed(b.token.decimals), b.token.decimals),
42563
+ usdValue: b.usdValue
41964
42564
  });
41965
- this.walletBalance = state.walletBalance ? {
41966
- ...state.walletBalance,
41967
- amount: state.walletBalance.amount.multipliedBy(1 - buffer),
41968
- usdValue: state.walletBalance.usdValue * (1 - buffer)
41969
- } : null;
41970
- this.vaultBalance = state.vaultBalance ? {
41971
- ...state.vaultBalance,
41972
- amount: state.vaultBalance.amount.multipliedBy(1 - buffer),
41973
- usdValue: state.vaultBalance.usdValue * (1 - buffer)
41974
- } : null;
41975
- this.extendedPositions = state.extendedPositions;
42565
+ this.unusedBalance = state.unusedBalance.map((item) => cloneTb(item));
42566
+ this.walletBalance = state.walletBalance ? cloneTb(state.walletBalance) : null;
42567
+ this.vaultAssetBalance = state.vaultAssetBalance ? cloneTb(state.vaultAssetBalance) : null;
42568
+ this.vaultUsdcBalance = state.vaultUsdcBalance ? cloneTb(state.vaultUsdcBalance) : null;
42569
+ this.extendedPositions = state.extendedPositions.map((p) => ({
42570
+ ...p,
42571
+ size: new Web3Number(p.size.toFixed(8), 8),
42572
+ valueUsd: new Web3Number(p.valueUsd.toFixed(USDC_TOKEN_DECIMALS), USDC_TOKEN_DECIMALS)
42573
+ }));
41976
42574
  this.extendedBalance = state.extendedBalance ? {
41977
- ...state.extendedBalance,
41978
- availableForTrade: state.extendedBalance.availableForTrade.multipliedBy(1 - buffer),
41979
- availableForWithdrawal: state.extendedBalance.availableForWithdrawal.multipliedBy(1 - buffer),
41980
- unrealisedPnl: state.extendedBalance.unrealisedPnl.multipliedBy(1 - buffer),
41981
- balance: state.extendedBalance.balance.multipliedBy(1 - buffer)
42575
+ equity: new Web3Number(
42576
+ state.extendedBalance.equity.toFixed(USDC_TOKEN_DECIMALS),
42577
+ USDC_TOKEN_DECIMALS
42578
+ ),
42579
+ availableForTrade: new Web3Number(
42580
+ state.extendedBalance.availableForTrade.toFixed(USDC_TOKEN_DECIMALS),
42581
+ USDC_TOKEN_DECIMALS
42582
+ ),
42583
+ availableForWithdrawal: new Web3Number(
42584
+ state.extendedBalance.availableForWithdrawal.toFixed(USDC_TOKEN_DECIMALS),
42585
+ USDC_TOKEN_DECIMALS
42586
+ ),
42587
+ unrealisedPnl: new Web3Number(
42588
+ state.extendedBalance.unrealisedPnl.toFixed(USDC_TOKEN_DECIMALS),
42589
+ USDC_TOKEN_DECIMALS
42590
+ ),
42591
+ balance: new Web3Number(
42592
+ state.extendedBalance.balance.toFixed(USDC_TOKEN_DECIMALS),
42593
+ USDC_TOKEN_DECIMALS
42594
+ ),
42595
+ pendingDeposit: new Web3Number(
42596
+ state.extendedBalance.pendingDeposit.toFixed(USDC_TOKEN_DECIMALS),
42597
+ USDC_TOKEN_DECIMALS
42598
+ )
41982
42599
  } : null;
41983
- this.vesuPoolStates = state.vesuPoolStates;
42600
+ this.vesuPoolStates = state.vesuPoolStates.map((p) => ({
42601
+ ...p,
42602
+ collateralAmount: new Web3Number(
42603
+ p.collateralAmount.toFixed(p.collateralToken.decimals),
42604
+ p.collateralToken.decimals
42605
+ ),
42606
+ debtAmount: new Web3Number(
42607
+ p.debtAmount.toFixed(p.debtToken.decimals),
42608
+ p.debtToken.decimals
42609
+ )
42610
+ }));
41984
42611
  const vesuPerPoolDebtDeltasToBorrow = this._computeperPoolDebtDeltasToBorrow();
41985
42612
  assert(vesuPerPoolDebtDeltasToBorrow.length === this.vesuPoolStates.length, "vesuPerPoolDebtDeltasToBorrow length must match vesuPoolStates length");
41986
42613
  this.vesuPerPoolDebtDeltasToBorrow = vesuPerPoolDebtDeltasToBorrow.map((item) => item.deltaDebt);
41987
42614
  this.shouldVesuRebalance = vesuPerPoolDebtDeltasToBorrow.map((item) => item.shouldRebalance);
41988
42615
  }
42616
+ /** `1 - limitBalanceBufferFactor` — multiplier applied to raw notionals for “usable” USD. */
42617
+ _usableFraction() {
42618
+ return 1;
42619
+ }
41989
42620
  /**
41990
- * Initialise budget-tracking values from the current state snapshot.
41991
- * Must be called after state is populated and debt deltas are computed.
41992
- *
41993
- * Accounts for pendingDeposit:
41994
- * - pendingDeposit > 0: funds in transit TO Extended → increase effective Extended available-for-trade
41995
- * - pendingDeposit < 0: funds in transit FROM Extended → increase effective wallet balance
42621
+ * Raw USD notional for a token row. USDC (and configured {@link usdcToken}) uses 1:1 from amount;
42622
+ * non-stable assets (e.g. WBTC in VA) use {@link TokenBalance.usdValue} from the pricer at refresh.
42623
+ */
42624
+ _rawTokenUsd(tb) {
42625
+ if (!tb) return 0;
42626
+ if (this.usdcToken.address.eq(tb.token.address)) {
42627
+ return Number(tb.amount.toFixed(tb.token.decimals));
42628
+ }
42629
+ return tb.usdValue;
42630
+ }
42631
+ /** Apply safety buffer to a raw USD scalar. */
42632
+ bufferedUsd(rawUsd) {
42633
+ return rawUsd * this._usableFraction();
42634
+ }
42635
+ /** Convert a buffered “usable” USD amount to raw nominal USD (inverse of {@link bufferedUsd}). */
42636
+ rawUsdFromBuffered(bufferedUsd) {
42637
+ const bf = this._usableFraction();
42638
+ assert(bf > 0, "SolveBudget::rawUsdFromBuffered usable fraction must be positive");
42639
+ return bufferedUsd / bf;
42640
+ }
42641
+ /** Buffered USD notional for one token balance row. */
42642
+ bufferedTokenUsd(tb) {
42643
+ return this.bufferedUsd(this._rawTokenUsd(tb));
42644
+ }
42645
+ logStateSummary() {
42646
+ console.log("===== state summary =====");
42647
+ const aggregatedData = {
42648
+ unusedBalances: this.unusedBalance.map((b) => ({
42649
+ token: b.token.symbol,
42650
+ amount: b.amount.toNumber()
42651
+ })),
42652
+ walletBalance: this.walletBalance ? {
42653
+ token: this.walletBalance.token.symbol,
42654
+ amount: this.walletBalance.amount.toNumber()
42655
+ } : void 0,
42656
+ vaultAssetBalance: this.vaultAssetBalance ? {
42657
+ token: this.vaultAssetBalance.token.symbol,
42658
+ amount: this.vaultAssetBalance.amount.toNumber()
42659
+ } : void 0,
42660
+ vaultUsdcBalance: this.vaultUsdcBalance ? {
42661
+ token: this.vaultUsdcBalance.token.symbol,
42662
+ amount: this.vaultUsdcBalance.amount.toNumber()
42663
+ } : void 0,
42664
+ vesuPoolStates: this.vesuPoolStates.map((p) => ({
42665
+ poolId: p.poolId,
42666
+ collateralAmount: p.collateralAmount.toNumber(),
42667
+ debtAmount: p.debtAmount.toNumber()
42668
+ })),
42669
+ vesuBorrowCapacity: this.vesuBorrowCapacity,
42670
+ vesuRebalance: this.shouldVesuRebalance,
42671
+ vesuPerPoolDebtDeltasToBorrow: this.vesuPerPoolDebtDeltasToBorrow.map((d) => d.toNumber()),
42672
+ extendedBalance: this.extendedBalance?.balance.toNumber(),
42673
+ extendedEquity: this.extendedBalance?.equity.toNumber(),
42674
+ extendedAvailableForTrade: this.extendedBalance?.availableForTrade.toNumber(),
42675
+ extendedAvailableForWithdrawal: this.extendedBalance?.availableForWithdrawal.toNumber(),
42676
+ extendedUnrealisedPnl: this.extendedBalance?.unrealisedPnl.toNumber(),
42677
+ extendedPendingDeposit: this.extendedBalance?.pendingDeposit.toNumber(),
42678
+ extendedPositions: this.extendedPositions.map((p) => ({
42679
+ instrument: p.instrument,
42680
+ size: p.size.toNumber(),
42681
+ valueUsd: p.valueUsd.toNumber()
42682
+ }))
42683
+ };
42684
+ console.log(
42685
+ "unused balances",
42686
+ aggregatedData.unusedBalances.map((b) => `${b.token}=${b.amount}`).join(", ")
42687
+ );
42688
+ console.log(
42689
+ "wallet balance",
42690
+ aggregatedData.walletBalance ? `${aggregatedData.walletBalance.token}=${aggregatedData.walletBalance.amount}` : void 0
42691
+ );
42692
+ console.log(
42693
+ "vault asset balance",
42694
+ aggregatedData.vaultAssetBalance ? `${aggregatedData.vaultAssetBalance.token}=${aggregatedData.vaultAssetBalance.amount}` : void 0
42695
+ );
42696
+ console.log(
42697
+ "vault usdc balance",
42698
+ aggregatedData.vaultUsdcBalance ? `${aggregatedData.vaultUsdcBalance.token}=${aggregatedData.vaultUsdcBalance.amount}` : void 0
42699
+ );
42700
+ console.log(
42701
+ "vesu pool states",
42702
+ aggregatedData.vesuPoolStates.map(
42703
+ (p) => `${p.poolId.shortString()}=${p.collateralAmount} ${p.debtAmount}`
42704
+ ).join(", ")
42705
+ );
42706
+ console.log("vesu borrow capacity", aggregatedData.vesuBorrowCapacity);
42707
+ console.log(
42708
+ "vesu rebalance",
42709
+ aggregatedData.vesuRebalance.map(String).join(", ")
42710
+ );
42711
+ console.log("vesu per pool debt deltas to borrow", aggregatedData.vesuPerPoolDebtDeltasToBorrow.join(", "));
42712
+ console.log("extended balance", aggregatedData.extendedBalance);
42713
+ console.log("extended equity", aggregatedData.extendedEquity);
42714
+ console.log("extended available for trade", aggregatedData.extendedAvailableForTrade);
42715
+ console.log("extended available for withdrawal", aggregatedData.extendedAvailableForWithdrawal);
42716
+ console.log("extended unrealised pnl", aggregatedData.extendedUnrealisedPnl);
42717
+ console.log("extended pending deposit", aggregatedData.extendedPendingDeposit);
42718
+ console.log(
42719
+ "extended positions",
42720
+ aggregatedData.extendedPositions.map(
42721
+ (p) => `${p.instrument}=${p.size} ${p.valueUsd}`
42722
+ ).join(", ")
42723
+ );
42724
+ return aggregatedData;
42725
+ }
42726
+ /**
42727
+ * Initialise derived views for a solve cycle. Mutates only when pending
42728
+ * withdrawal from Extended is in transit (credits wallet raw balance).
41996
42729
  */
41997
42730
  initBudget() {
41998
- const debtDeltaNum = this.vesuPerPoolDebtDeltasToBorrow.reduce((a, b) => a + b.toNumber(), 0);
41999
- let totalUnusedUsd = this.unusedBalance.reduce((a, b) => a + b.usdValue, 0);
42000
- if (debtDeltaNum > 0) totalUnusedUsd += debtDeltaNum;
42001
- const extAvailTrade = this.extendedBalance?.availableForTrade?.toNumber() ?? 0;
42002
- if (extAvailTrade > 0) totalUnusedUsd += extAvailTrade;
42003
- if (debtDeltaNum > 0) totalUnusedUsd += debtDeltaNum;
42004
- this._vaUsd = this.vaultBalance?.usdValue ?? 0;
42005
- this._walletUsd = this.walletBalance?.usdValue ?? 0;
42006
- this._extAvailWithdraw = this.extendedBalance?.availableForWithdrawal?.toNumber() ?? 0;
42007
- this._extAvailUpnl = this.extendedBalance?.unrealisedPnl?.toNumber() ?? 0;
42008
- this._extAvailTrade = extAvailTrade;
42009
- this._extPendingDeposit = this.extendedBalance?.pendingDeposit?.toNumber() ?? 0;
42010
- this._vesuBorrowCapacity = debtDeltaNum;
42011
- this._totalUnused = totalUnusedUsd;
42012
42731
  const pendingDeposit = this.extendedBalance?.pendingDeposit?.toNumber() ?? 0;
42013
- if (pendingDeposit > 0) {
42014
- this._extAvailTrade += pendingDeposit;
42015
- this._totalUnused += pendingDeposit;
42016
- logger.debug(`SolveBudget::initBudget pendingDeposit=${pendingDeposit} -> increased extAvailTrade`);
42017
- } else if (pendingDeposit < 0) {
42732
+ if (pendingDeposit < 0) {
42018
42733
  const inTransit = Math.abs(pendingDeposit);
42019
- this._walletUsd += inTransit;
42020
- this._totalUnused += inTransit;
42021
- logger.debug(`SolveBudget::initBudget pendingDeposit=${pendingDeposit} -> increased walletUsd by ${inTransit}`);
42734
+ if (this.walletBalance) {
42735
+ this._addUsdToTokenBalance(this.walletBalance, inTransit);
42736
+ }
42737
+ logger.debug(`SolveBudget::initBudget pendingDeposit=${pendingDeposit} -> increased wallet raw USD by ${inTransit}`);
42738
+ }
42739
+ this._recomputeUnusedBalance();
42740
+ }
42741
+ /**
42742
+ * Apply a safety buffer to all liquid balances (VA, wallet, extended trade/withdraw/upnl,
42743
+ * unused balances). Call after withdrawal classification but before LTV/deposit classifiers
42744
+ * so that withdrawal uses full raw amounts while subsequent classifiers see buffered values.
42745
+ */
42746
+ applyBuffer(factor) {
42747
+ if (factor <= 0 || factor >= 1) return;
42748
+ const mult = 1 - factor;
42749
+ const scaleTokenBalance = (tb) => {
42750
+ if (!tb) return;
42751
+ const newAmount = tb.amount.multipliedBy(mult);
42752
+ tb.amount = new Web3Number(newAmount.toFixed(tb.token.decimals), tb.token.decimals);
42753
+ tb.usdValue = tb.usdValue * mult;
42754
+ };
42755
+ scaleTokenBalance(this.vaultAssetBalance);
42756
+ scaleTokenBalance(this.vaultUsdcBalance);
42757
+ scaleTokenBalance(this.walletBalance);
42758
+ for (const ub of this.unusedBalance) {
42759
+ scaleTokenBalance(ub);
42760
+ }
42761
+ if (this.extendedBalance) {
42762
+ this.extendedBalance.availableForTrade = this.extendedBalance.availableForTrade.multipliedBy(mult);
42763
+ this.extendedBalance.availableForWithdrawal = this.extendedBalance.availableForWithdrawal.multipliedBy(mult);
42764
+ this.extendedBalance.unrealisedPnl = this.extendedBalance.unrealisedPnl.multipliedBy(mult);
42022
42765
  }
42766
+ this._recomputeUnusedBalance();
42023
42767
  }
42024
- // ── Read-only getters ────────────────────────────────────────────────
42768
+ get vesuPoolState() {
42769
+ assert(this.vesuPoolStates.length === 1, "SolveBudget::vesuPoolState: vesuPoolStates length must be 1");
42770
+ return this.vesuPoolStates[0];
42771
+ }
42772
+ // ── Derived getters (buffered where applicable) ─────────────────────
42773
+ /** Buffered VA USD: strategy-asset slot + optional USDC slot. */
42025
42774
  get vaUsd() {
42026
- return this._vaUsd;
42775
+ return this.bufferedTokenUsd(this.vaultAssetBalance) + this.bufferedTokenUsd(this.vaultUsdcBalance);
42776
+ }
42777
+ /** Buffered USD in VA strategy-asset bucket only. */
42778
+ get vaAssetUsd() {
42779
+ return this.bufferedTokenUsd(this.vaultAssetBalance);
42780
+ }
42781
+ /** Buffered USD in VA USDC bucket (0 when asset === USDC). */
42782
+ get vaUsdcUsd() {
42783
+ return this.bufferedTokenUsd(this.vaultUsdcBalance);
42027
42784
  }
42028
42785
  get walletUsd() {
42029
- return this._walletUsd;
42786
+ return this.bufferedUsd(this._rawTokenUsd(this.walletBalance));
42030
42787
  }
42031
42788
  get vaWalletUsd() {
42032
- return this._vaUsd + this._walletUsd;
42789
+ return this.vaUsd + this.walletUsd;
42033
42790
  }
42034
42791
  get extAvailWithdraw() {
42035
- return this._extAvailWithdraw;
42792
+ const raw = this.extendedBalance?.availableForWithdrawal?.toNumber() ?? 0;
42793
+ return this.bufferedUsd(raw);
42036
42794
  }
42037
42795
  get extAvailUpnl() {
42038
- return this._extAvailUpnl;
42796
+ const raw = this.extendedBalance?.unrealisedPnl?.toNumber() ?? 0;
42797
+ return this.bufferedUsd(raw);
42039
42798
  }
42799
+ /**
42800
+ * Buffered Extended available-for-trade plus positive {@link ExtendedBalanceState.pendingDeposit}
42801
+ * (deposit in transit is usable the same way as the pre-buffer implementation).
42802
+ */
42040
42803
  get extAvailTrade() {
42041
- return this._extAvailTrade;
42804
+ const raw = this.extendedBalance?.availableForTrade?.toNumber() ?? 0;
42805
+ let v = this.bufferedUsd(raw);
42806
+ const pd = this.extendedBalance?.pendingDeposit?.toNumber() ?? 0;
42807
+ if (pd > 0) v += pd;
42808
+ return v;
42809
+ }
42810
+ get extPendingDeposit() {
42811
+ return this.extendedBalance?.pendingDeposit?.toNumber() ?? 0;
42042
42812
  }
42813
+ /**
42814
+ * Aggregate positive per-pool borrow headroom (USD). Repay/borrow routes update
42815
+ * {@link vesuPerPoolDebtDeltasToBorrow}; no separate counter.
42816
+ */
42043
42817
  get vesuBorrowCapacity() {
42044
- return this._vesuBorrowCapacity;
42818
+ return this.vesuPerPoolDebtDeltasToBorrow.reduce(
42819
+ (a, d) => a + Math.max(0, d.toNumber()),
42820
+ 0
42821
+ );
42045
42822
  }
42823
+ /** Diagnostic: buffered idle + positive debt delta + buffered Extended afT + in-flight deposit. */
42046
42824
  get totalUnused() {
42047
- return this._totalUnused;
42825
+ const debtSum = this.vesuPerPoolDebtDeltasToBorrow.reduce((a, b) => a + b.toNumber(), 0);
42826
+ let u = this.unusedBalance.reduce((a, b) => a + this.bufferedTokenUsd(b), 0);
42827
+ if (debtSum > 0) u += debtSum;
42828
+ const rawAft = this.extendedBalance?.availableForTrade?.toNumber() ?? 0;
42829
+ const aftBuf = this.bufferedUsd(rawAft);
42830
+ if (aftBuf > 0) u += aftBuf;
42831
+ const pd = this.extendedBalance?.pendingDeposit?.toNumber() ?? 0;
42832
+ if (pd > 0) u += pd;
42833
+ return u;
42834
+ }
42835
+ /** Sum of buffered USD across merged unused-balance rows (VA + wallet). */
42836
+ get unusedBalancesBufferedUsdSum() {
42837
+ return this.unusedBalance.reduce((a, b) => a + this.bufferedTokenUsd(b), 0);
42838
+ }
42839
+ /** Read-only snapshot view for validation / logging. */
42840
+ get unusedBalanceRows() {
42841
+ return this.unusedBalance;
42842
+ }
42843
+ /** Read-only Vesu pool view for solve computations. */
42844
+ get vesuPools() {
42845
+ return this.vesuPoolStates;
42846
+ }
42847
+ /** Read-only Extended positions view for solve computations. */
42848
+ get extendedPositionsView() {
42849
+ return this.extendedPositions;
42850
+ }
42851
+ /** Read-only Extended balance view for diagnostics / margin checks. */
42852
+ get extendedBalanceView() {
42853
+ return this.extendedBalance;
42854
+ }
42855
+ /** Current debt deltas per pool (positive=borrow, negative=repay). */
42856
+ get vesuDebtDeltas() {
42857
+ return this.vesuPerPoolDebtDeltasToBorrow;
42858
+ }
42859
+ /** Per-pool rebalance flags derived from target HF checks. */
42860
+ get vesuRebalanceFlags() {
42861
+ return this.shouldVesuRebalance;
42862
+ }
42863
+ /** Raw USD in VA (USDC slot + asset slot); spend caps when executing transfers. */
42864
+ _vaRawUsd() {
42865
+ return this._rawTokenUsd(this.vaultUsdcBalance) + this._rawTokenUsd(this.vaultAssetBalance);
42866
+ }
42867
+ _walletRawUsd() {
42868
+ return this._rawTokenUsd(this.walletBalance);
42869
+ }
42870
+ // ── Token snapshot helpers (keep vault / wallet / unusedBalance aligned) ─
42871
+ /** Remove up to `usd` notional from a token balance, scaling token amount proportionally. */
42872
+ _deductUsdFromTokenBalance(tb, usd) {
42873
+ if (usd <= 0) return;
42874
+ const take = Math.min(usd, tb.usdValue);
42875
+ if (take <= 0) return;
42876
+ const oldUsd = tb.usdValue;
42877
+ const newUsd = Math.max(0, oldUsd - take);
42878
+ tb.usdValue = newUsd;
42879
+ if (oldUsd <= 0) return;
42880
+ const ratio = newUsd / oldUsd;
42881
+ tb.amount = new Web3Number(
42882
+ (tb.amount.toNumber() * ratio).toFixed(tb.token.decimals),
42883
+ tb.token.decimals
42884
+ );
42885
+ }
42886
+ /** Add USD notional; infers price from current amount/usd when possible, else 1:1. */
42887
+ _addUsdToTokenBalance(tb, usd) {
42888
+ if (usd <= 0) return;
42889
+ const amtNum = tb.amount.toNumber();
42890
+ const price = amtNum > 0 && tb.usdValue > 0 ? tb.usdValue / amtNum : 1;
42891
+ const deltaTok = usd / price;
42892
+ tb.usdValue += usd;
42893
+ tb.amount = tb.amount.plus(
42894
+ new Web3Number(deltaTok.toFixed(tb.token.decimals), tb.token.decimals)
42895
+ );
42048
42896
  }
42049
- get extPendingDeposit() {
42050
- return this._extPendingDeposit;
42897
+ /**
42898
+ * Rebuilds {@link unusedBalance} from vault + wallet snapshots (same merge as refresh).
42899
+ */
42900
+ _recomputeUnusedBalance() {
42901
+ const balanceMap = /* @__PURE__ */ new Map();
42902
+ const merge = (b) => {
42903
+ if (!b) return;
42904
+ const key = b.token.address.toString();
42905
+ const row = {
42906
+ token: b.token,
42907
+ amount: new Web3Number(b.amount.toFixed(b.token.decimals), b.token.decimals),
42908
+ usdValue: b.usdValue
42909
+ };
42910
+ const existing = balanceMap.get(key);
42911
+ if (existing) {
42912
+ existing.amount = new Web3Number(
42913
+ existing.amount.plus(row.amount).toFixed(existing.token.decimals),
42914
+ existing.token.decimals
42915
+ );
42916
+ existing.usdValue += row.usdValue;
42917
+ } else {
42918
+ balanceMap.set(key, row);
42919
+ }
42920
+ };
42921
+ merge(this.vaultAssetBalance);
42922
+ merge(this.vaultUsdcBalance);
42923
+ merge(this.walletBalance);
42924
+ this.unusedBalance = Array.from(balanceMap.values());
42051
42925
  }
42052
42926
  // ── Spend methods (return amount consumed, auto-decrement totalUnused) ─
42053
- spendVA(desired) {
42054
- const used = Math.min(this._vaUsd, Math.max(0, desired));
42055
- this._vaUsd -= used;
42056
- this._totalUnused -= used;
42057
- logger.debug(`SolveBudget::spendVA used=${used}, vaUsd=${this._vaUsd}, totalUnused=${this._totalUnused}`);
42058
- return used;
42059
- }
42060
- addToVA(amount) {
42061
- assert(amount >= 0, "SolveBudget::addToVA amount must be positive");
42062
- this._vaUsd += amount;
42063
- this._totalUnused += amount;
42064
- logger.debug(`SolveBudget::addToVA amount=${amount}, vaUsd=${this._vaUsd}, totalUnused=${this._totalUnused}`);
42065
- }
42066
- spendWallet(desired) {
42067
- const used = Math.min(this._walletUsd, Math.max(0, desired));
42068
- this._walletUsd -= used;
42069
- this._totalUnused -= used;
42070
- logger.debug(`SolveBudget::spendWallet used=${used}, walletUsd=${this._walletUsd}, totalUnused=${this._totalUnused}`);
42071
- return used;
42927
+ /**
42928
+ * Spend VA **raw** USD (up to {@link vaRawUsd}). Prefer {@link vaultUsdcBalance} when present, then {@link vaultAssetBalance}.
42929
+ */
42930
+ spendVA(rawDesired) {
42931
+ const capRaw = this._vaRawUsd();
42932
+ const usedRaw = Math.min(capRaw, Math.max(0, rawDesired));
42933
+ if (usedRaw <= CASE_THRESHOLD_USD) return 0;
42934
+ let rem = usedRaw;
42935
+ if (rem > 0 && this.vaultUsdcBalance && this.vaultUsdcBalance.usdValue > 0) {
42936
+ const fromUsdc = Math.min(rem, this.vaultUsdcBalance.usdValue);
42937
+ this._deductUsdFromTokenBalance(this.vaultUsdcBalance, fromUsdc);
42938
+ rem -= fromUsdc;
42939
+ }
42940
+ if (rem > 0 && this.vaultAssetBalance) {
42941
+ this._deductUsdFromTokenBalance(this.vaultAssetBalance, rem);
42942
+ }
42943
+ this._recomputeUnusedBalance();
42944
+ logger.debug(`SolveBudget::spendVA usedRaw=${usedRaw}, vaUsd=${this.vaUsd}, totalUnused=${this.totalUnused}`);
42945
+ return usedRaw;
42072
42946
  }
42073
- addToWallet(amount) {
42074
- assert(amount >= 0, "SolveBudget::addToWallet amount must be positive");
42075
- this._walletUsd += amount;
42076
- this._totalUnused += amount;
42077
- logger.debug(`SolveBudget::addToWallet amount=${amount}, walletUsd=${this._walletUsd}, totalUnused=${this._totalUnused}`);
42947
+ /**
42948
+ * Spend nominal/raw USD from VA (e.g. Vesu repay, on-chain USDC). Does not apply the safety buffer to the cap.
42949
+ */
42950
+ spendVaRawUsd(rawUsdDesired) {
42951
+ const capRaw = this._rawTokenUsd(this.vaultUsdcBalance) + this._rawTokenUsd(this.vaultAssetBalance);
42952
+ const usedRaw = Math.min(capRaw, Math.max(0, rawUsdDesired));
42953
+ if (usedRaw <= CASE_THRESHOLD_USD) return 0;
42954
+ let rem = usedRaw;
42955
+ if (rem > 0 && this.vaultUsdcBalance && this.vaultUsdcBalance.usdValue > 0) {
42956
+ const fromUsdc = Math.min(rem, this.vaultUsdcBalance.usdValue);
42957
+ this._deductUsdFromTokenBalance(this.vaultUsdcBalance, fromUsdc);
42958
+ rem -= fromUsdc;
42959
+ }
42960
+ if (rem > 0 && this.vaultAssetBalance) {
42961
+ this._deductUsdFromTokenBalance(this.vaultAssetBalance, rem);
42962
+ }
42963
+ this._recomputeUnusedBalance();
42964
+ logger.debug(`SolveBudget::spendVaRawUsd usedRaw=${usedRaw}, vaUsd=${this.vaUsd}, totalUnused=${this.totalUnused}`);
42965
+ return usedRaw;
42078
42966
  }
42079
- spendVAWallet(desired) {
42080
- let remaining = Math.max(0, desired);
42967
+ /**
42968
+ * Add **raw nominal USD** to VA (borrow proceeds, wallet→VA in raw USDC, etc.).
42969
+ */
42970
+ addToVA(rawUsd) {
42971
+ assert(rawUsd >= 0, "SolveBudget::addToVA amount must be positive");
42972
+ if (rawUsd === 0) return;
42973
+ if (this.vaultUsdcBalance) {
42974
+ this._addUsdToTokenBalance(this.vaultUsdcBalance, rawUsd);
42975
+ } else if (this.vaultAssetBalance) {
42976
+ this._addUsdToTokenBalance(this.vaultAssetBalance, rawUsd);
42977
+ }
42978
+ this._recomputeUnusedBalance();
42979
+ logger.debug(`SolveBudget::addToVA rawUsd=${rawUsd}, vaUsd=${this.vaUsd}, totalUnused=${this.totalUnused}`);
42980
+ }
42981
+ spendWallet(rawDesired) {
42982
+ const capRaw = this._walletRawUsd();
42983
+ const usedRaw = Math.min(capRaw, Math.max(0, rawDesired));
42984
+ if (usedRaw <= CASE_THRESHOLD_USD) return 0;
42985
+ if (this.walletBalance) {
42986
+ this._deductUsdFromTokenBalance(this.walletBalance, usedRaw);
42987
+ }
42988
+ this._recomputeUnusedBalance();
42989
+ logger.debug(`SolveBudget::spendWallet usedRaw=${usedRaw}, walletUsd=${this.walletUsd}, totalUnused=${this.totalUnused}`);
42990
+ return usedRaw;
42991
+ }
42992
+ /** Add **raw nominal USD** to the operator wallet balance (e.g. Extended→wallet withdrawal). */
42993
+ addToWallet(rawUsd) {
42994
+ assert(rawUsd >= 0, "SolveBudget::addToWallet amount must be positive");
42995
+ if (rawUsd === 0) return;
42996
+ if (this.walletBalance) {
42997
+ this._addUsdToTokenBalance(this.walletBalance, rawUsd);
42998
+ }
42999
+ this._recomputeUnusedBalance();
43000
+ logger.debug(`SolveBudget::addToWallet rawUsd=${rawUsd}, walletUsd=${this.walletUsd}, totalUnused=${this.totalUnused}`);
43001
+ }
43002
+ spendVAWallet(rawDesired) {
43003
+ let remaining = Math.max(0, rawDesired);
42081
43004
  const vaSpent = this.spendVA(remaining);
42082
43005
  remaining -= vaSpent;
42083
43006
  const walletSpent = this.spendWallet(remaining);
42084
43007
  return vaSpent + walletSpent;
42085
43008
  }
42086
- _updateExtAvailWithdraw(desired, isSpend) {
42087
- let amount = desired;
42088
- assert(amount > 0, "SolveBudget::_updateExtAvailWithdraw amount must be positive");
43009
+ _updateExtAvailWithdraw(desiredRaw, isSpend) {
43010
+ assert(desiredRaw > 0, "SolveBudget::_updateExtAvailWithdraw amount must be positive");
43011
+ let rawDelta;
42089
43012
  if (isSpend) {
42090
- amount = Math.min(this._extAvailWithdraw, Math.max(0, desired));
42091
- amount = -amount;
43013
+ const capRaw = this.extendedBalance?.availableForWithdrawal?.toNumber() ?? 0;
43014
+ const useRaw = Math.min(capRaw, desiredRaw);
43015
+ if (useRaw <= CASE_THRESHOLD_USD) return 0;
43016
+ rawDelta = -useRaw;
43017
+ } else {
43018
+ rawDelta = desiredRaw;
42092
43019
  }
42093
- this._extAvailWithdraw += amount;
42094
- this._totalUnused += amount;
42095
- this._extAvailTrade += amount;
42096
43020
  if (this.extendedBalance) {
42097
- this.extendedBalance.availableForWithdrawal = safeUsdcWeb3Number(this.extendedBalance.availableForWithdrawal.toNumber() + amount);
42098
- this.extendedBalance.availableForTrade = safeUsdcWeb3Number(this.extendedBalance.availableForTrade.toNumber() + amount);
42099
- this.extendedBalance.balance = safeUsdcWeb3Number(this.extendedBalance.balance.toNumber() + amount);
42100
- this.extendedBalance.equity = safeUsdcWeb3Number(this.extendedBalance.equity.toNumber() + amount);
43021
+ this.extendedBalance.availableForWithdrawal = safeUsdcWeb3Number(this.extendedBalance.availableForWithdrawal.toNumber() + rawDelta);
43022
+ this.extendedBalance.availableForTrade = safeUsdcWeb3Number(this.extendedBalance.availableForTrade.toNumber() + rawDelta);
43023
+ this.extendedBalance.balance = safeUsdcWeb3Number(this.extendedBalance.balance.toNumber() + rawDelta);
43024
+ this.extendedBalance.equity = safeUsdcWeb3Number(this.extendedBalance.equity.toNumber() + rawDelta);
42101
43025
  }
42102
- logger.debug(`SolveBudget::updateExtAvailWithdraw amount=${amount}, extAvailWithdraw=${this._extAvailWithdraw}, totalUnused=${this._totalUnused}`);
42103
- return amount;
43026
+ logger.debug(`SolveBudget::updateExtAvailWithdraw rawDelta=${rawDelta}, extAvailWithdraw=${this.extAvailWithdraw}, totalUnused=${this.totalUnused}`);
43027
+ return rawDelta;
42104
43028
  }
42105
- _updateExtAvailUpnl(desired, isSpend) {
42106
- let amount = desired;
42107
- assert(amount > 0, "SolveBudget::_updateExtAvailUpnl amount must be positive");
43029
+ _updateExtAvailUpnl(desiredRaw, isSpend) {
43030
+ assert(desiredRaw > 0, "SolveBudget::_updateExtAvailUpnl amount must be positive");
43031
+ let rawDelta;
42108
43032
  if (isSpend) {
42109
- amount = Math.min(this._extAvailUpnl, Math.max(0, desired));
42110
- amount = -amount;
43033
+ const capRaw = this.extendedBalance?.unrealisedPnl?.toNumber() ?? 0;
43034
+ const useRaw = Math.min(capRaw, desiredRaw);
43035
+ if (useRaw <= CASE_THRESHOLD_USD) return 0;
43036
+ rawDelta = -useRaw;
43037
+ } else {
43038
+ rawDelta = desiredRaw;
42111
43039
  }
42112
- this._extAvailUpnl += amount;
42113
- this._totalUnused += amount;
42114
43040
  if (this.extendedBalance) {
42115
- this.extendedBalance.unrealisedPnl = safeUsdcWeb3Number(this.extendedBalance.unrealisedPnl.toNumber() + amount);
42116
- this.extendedBalance.balance = safeUsdcWeb3Number(this.extendedBalance.balance.toNumber() + amount);
42117
- this.extendedBalance.equity = safeUsdcWeb3Number(this.extendedBalance.equity.toNumber() + amount);
42118
- this.extendedBalance.availableForTrade = safeUsdcWeb3Number(this.extendedBalance.availableForTrade.toNumber() + amount);
42119
- }
42120
- logger.debug(`SolveBudget::updateExtAvailUpnl amount=${amount}, extAvailUpnl=${this._extAvailUpnl}, totalUnused=${this._totalUnused}`);
42121
- return amount;
42122
- }
42123
- spendExtAvailTrade(desired) {
42124
- const used = this._updateExtAvailWithdraw(desired, true);
42125
- const usedUpnl = this._updateExtAvailUpnl(desired, true);
42126
- logger.debug(`SolveBudget::updateExtAvailTrade amount=${used + usedUpnl}, extAvailTrade=${this._extAvailTrade}, totalUnused=${this._totalUnused}`);
43041
+ this.extendedBalance.unrealisedPnl = safeUsdcWeb3Number(this.extendedBalance.unrealisedPnl.toNumber() + rawDelta);
43042
+ this.extendedBalance.balance = safeUsdcWeb3Number(this.extendedBalance.balance.toNumber() + rawDelta);
43043
+ this.extendedBalance.equity = safeUsdcWeb3Number(this.extendedBalance.equity.toNumber() + rawDelta);
43044
+ this.extendedBalance.availableForTrade = safeUsdcWeb3Number(this.extendedBalance.availableForTrade.toNumber() + rawDelta);
43045
+ }
43046
+ logger.debug(`SolveBudget::updateExtAvailUpnl rawDelta=${rawDelta}, extAvailUpnl=${this.extAvailUpnl}, totalUnused=${this.totalUnused}`);
43047
+ return rawDelta;
43048
+ }
43049
+ spendExtAvailTrade(rawDesired) {
43050
+ const used = this._updateExtAvailWithdraw(rawDesired, true);
43051
+ const usedUpnl = this._updateExtAvailUpnl(rawDesired, true);
43052
+ logger.debug(`SolveBudget::updateExtAvailTrade rawSum=${used + usedUpnl}, extAvailTrade=${this.extAvailTrade}, totalUnused=${this.totalUnused}`);
42127
43053
  return used + usedUpnl;
42128
43054
  }
42129
- spendExtAvailUpnl(desired) {
42130
- return this._updateExtAvailUpnl(desired, true);
43055
+ // simply reduces available amounts, but maintains equity and balance.
43056
+ spendExtAvailTradeToEquityOnly(rawDesired) {
43057
+ const used = this._updateExtAvailWithdraw(rawDesired, true);
43058
+ const remaining = rawDesired - Math.abs(used);
43059
+ const usedUpnl = remaining > 0 ? this._updateExtAvailUpnl(remaining, true) : 0;
43060
+ if (this.extendedBalance) {
43061
+ const net = Math.abs(used) + Math.abs(usedUpnl);
43062
+ if (net.toFixed(0) != rawDesired.toFixed(0)) {
43063
+ throw new Error(`SolveBudget::spendExtAvailTradeToEquityOnly net=${net} != rawDesired=${rawDesired}`);
43064
+ }
43065
+ this.extendedBalance.equity = safeUsdcWeb3Number(this.extendedBalance.equity.toNumber() + net);
43066
+ this.extendedBalance.balance = safeUsdcWeb3Number(this.extendedBalance.balance.toNumber() + net);
43067
+ }
43068
+ logger.debug(`SolveBudget::updateExtAvailTrade rawSum=${used + usedUpnl}, extAvailTrade=${this.extAvailTrade}, totalUnused=${this.totalUnused}`);
43069
+ return used + usedUpnl;
43070
+ }
43071
+ spendExtAvailWithdraw(rawDesired) {
43072
+ return this._updateExtAvailWithdraw(rawDesired, true);
43073
+ }
43074
+ spendExtAvailUpnl(rawDesired) {
43075
+ return this._updateExtAvailUpnl(rawDesired, true);
43076
+ }
43077
+ /**
43078
+ * Withdraw from Extended **withdrawal bucket only** to operator wallet (planning).
43079
+ * Used when VA must be funded from Extended and withdraw should be exhausted before unrealised PnL.
43080
+ */
43081
+ spendAvailWithdrawToWallet(rawDesired) {
43082
+ const want = Math.max(0, rawDesired);
43083
+ if (want <= CASE_THRESHOLD_USD) return 0;
43084
+ const rawDelta = this._updateExtAvailWithdraw(want, true);
43085
+ if (rawDelta === 0) return 0;
43086
+ const used = -rawDelta;
43087
+ this.addToWallet(used);
43088
+ return used;
43089
+ }
43090
+ /**
43091
+ * Required Extended equity (USD) for current open positions: total notional ÷ strategy leverage.
43092
+ * Same basis as {@link ExtendedSVKVesuStateManager._classifyLtvExtended} margin check.
43093
+ */
43094
+ _extendedMarginRequirementUsd() {
43095
+ const lev = calculateExtendedLevergae();
43096
+ if (lev <= 0 || this.extendedPositions.length === 0) return 0;
43097
+ const totalPosUsd = this.extendedPositions.reduce(
43098
+ (s, p) => s + p.valueUsd.toNumber(),
43099
+ 0
43100
+ );
43101
+ return totalPosUsd / lev;
43102
+ }
43103
+ /** How much more equity is needed before deposits should increase withdraw / trade availability. */
43104
+ _extendedEquityShortfallUsd() {
43105
+ if (!this.extendedBalance) return 0;
43106
+ const req = this._extendedMarginRequirementUsd();
43107
+ const eq = this.extendedBalance.equity.toNumber();
43108
+ return Math.max(0, req - eq);
42131
43109
  }
42132
- addToExtAvailTrade(amount) {
42133
- this._updateExtAvailWithdraw(amount, false);
42134
- logger.debug(`SolveBudget::addToExtAvailTrade amount=${amount}, extAvailTrade=${this._extAvailTrade}, totalUnused=${this._totalUnused}`);
43110
+ /**
43111
+ * Credits a USDC inflow on Extended. Fills margin shortfall (balance+equity only) first;
43112
+ * any remainder is credited across balance, equity, availableForWithdrawal, and availableForTrade.
43113
+ */
43114
+ addToExtAvailTrade(rawUsd) {
43115
+ assert(rawUsd >= 0, "SolveBudget::addToExtAvailTrade amount must be non-negative");
43116
+ if (rawUsd <= CASE_THRESHOLD_USD) return;
43117
+ if (!this.extendedBalance) {
43118
+ logger.warn("SolveBudget::addToExtAvailTrade skipped \u2014 no extendedBalance");
43119
+ return;
43120
+ }
43121
+ const shortfall = this._extendedEquityShortfallUsd();
43122
+ const toMargin = Math.min(rawUsd, shortfall);
43123
+ const toLiquid = rawUsd - toMargin;
43124
+ if (toMargin > CASE_THRESHOLD_USD) {
43125
+ const b = this.extendedBalance.balance.toNumber();
43126
+ const e = this.extendedBalance.equity.toNumber();
43127
+ this.extendedBalance.balance = safeUsdcWeb3Number(b + toMargin);
43128
+ this.extendedBalance.equity = safeUsdcWeb3Number(e + toMargin);
43129
+ logger.debug(
43130
+ `SolveBudget::addToExtAvailTrade margin-first rawUsd=${toMargin} (shortfallBefore=${shortfall}, balance=${b + toMargin}, equity=${e + toMargin})`
43131
+ );
43132
+ }
43133
+ if (toLiquid > CASE_THRESHOLD_USD) {
43134
+ this._updateExtAvailWithdraw(toLiquid, false);
43135
+ }
43136
+ logger.debug(
43137
+ `SolveBudget::addToExtAvailTrade total rawUsd=${rawUsd} toLiquid=${toLiquid} extAvailTrade=${this.extAvailTrade}, totalUnused=${this.totalUnused}`
43138
+ );
42135
43139
  }
42136
43140
  spendVesuBorrowCapacity(desired) {
42137
- const used = Math.min(this._vesuBorrowCapacity, Math.max(0, desired));
42138
- this._vesuBorrowCapacity -= used;
42139
- this._totalUnused -= used;
43141
+ const used = Math.min(this.vesuBorrowCapacity, Math.max(0, desired));
42140
43142
  let spendsByPool = [];
42141
43143
  for (let index = 0; index < this.vesuPerPoolDebtDeltasToBorrow.length; index++) {
42142
43144
  const d = this.vesuPerPoolDebtDeltasToBorrow[index];
@@ -42147,13 +43149,12 @@ var SolveBudget = class {
42147
43149
  this.vesuPoolStates[index].debtUsdValue = this.vesuPoolStates[index].debtAmount.toNumber() * this.vesuPoolStates[index].debtPrice;
42148
43150
  spendsByPool.push({ poolId: this.vesuPoolStates[index].poolId, amount: safeUsdcWeb3Number(borrowed), collateralToken: this.vesuPoolStates[index].collateralToken, debtToken: this.vesuPoolStates[index].debtToken });
42149
43151
  }
42150
- logger.debug(`SolveBudget::spendVesuBorrowCapacity used=${used}, vesuBorrowCapacity=${this._vesuBorrowCapacity}, totalUnused=${this._totalUnused}`);
43152
+ logger.debug(`SolveBudget::spendVesuBorrowCapacity used=${used}, vesuBorrowCapacity=${this.vesuBorrowCapacity}, totalUnused=${this.totalUnused}`);
42151
43153
  return { used, spendsByPool };
42152
43154
  }
42153
43155
  repayVesuBorrowCapacity(desired) {
42154
43156
  assert(desired > 0, "SolveBudget::repayVesuBorrowCapacity desired must be positive");
42155
43157
  const used = desired;
42156
- this._vesuBorrowCapacity += used;
42157
43158
  const spendsByPool = [];
42158
43159
  for (let index = 0; index < this.vesuPerPoolDebtDeltasToBorrow.length; index++) {
42159
43160
  const d = this.vesuPerPoolDebtDeltasToBorrow[index];
@@ -42163,7 +43164,7 @@ var SolveBudget = class {
42163
43164
  this.vesuPoolStates[index].debtAmount = safeUsdcWeb3Number(this.vesuPoolStates[index].debtAmount.toNumber() - repaid);
42164
43165
  spendsByPool.push({ poolId: this.vesuPoolStates[index].poolId, amount: safeUsdcWeb3Number(-repaid), collateralToken: this.vesuPoolStates[index].collateralToken, debtToken: this.vesuPoolStates[index].debtToken });
42165
43166
  }
42166
- logger.debug(`SolveBudget::repayVesuBorrowCapacity used=${used}, vesuBorrowCapacity=${this._vesuBorrowCapacity}, totalUnused=${this._totalUnused}`);
43167
+ logger.debug(`SolveBudget::repayVesuBorrowCapacity used=${used}, vesuBorrowCapacity=${this.vesuBorrowCapacity}, totalUnused=${this.totalUnused}`);
42167
43168
  return { used, spendsByPool };
42168
43169
  }
42169
43170
  // ── State mutation ──────────────────────────────────────────────────
@@ -42185,13 +43186,21 @@ var SolveBudget = class {
42185
43186
  pool.debtUsdValue = pool.debtAmount.toNumber() * pool.debtPrice;
42186
43187
  const vesuPerPoolDebtDeltasToBorrow = this._computeperPoolDebtDeltasToBorrow();
42187
43188
  this.vesuPerPoolDebtDeltasToBorrow = vesuPerPoolDebtDeltasToBorrow.map((item) => item.deltaDebt);
42188
- const sum = this.vesuPerPoolDebtDeltasToBorrow.reduce((a, b) => a.plus(b), new Web3Number(0, USDC_TOKEN_DECIMALS));
42189
- this._vesuBorrowCapacity = sum.toNumber();
42190
43189
  this.shouldVesuRebalance = vesuPerPoolDebtDeltasToBorrow.map((item) => item.shouldRebalance);
42191
43190
  }
42192
- /** Update an Extended position's size after a lever route. Creates the position if new. */
42193
- applyExtendedExposureDelta(instrument, sizeDelta) {
42194
- const pos = this.extendedPositions.find((p) => p.instrument === instrument);
43191
+ /**
43192
+ * Update Extended position size after a lever route; sync {@link ExtendedPositionState.valueUsd}
43193
+ * and margin buckets (released USD on decrease, locked USD on increase).
43194
+ *
43195
+ * @param collateralPriceUsd BTC collateral price for notional / margin math; if omitted, uses
43196
+ * existing valueUsd / |size| or the first Vesu pool collateral price.
43197
+ */
43198
+ applyExtendedExposureDelta(instrument, sizeDelta, collateralPriceUsd) {
43199
+ const btcEps = 10 ** -COLLATERAL_PRECISION;
43200
+ const lev = calculateExtendedLevergae();
43201
+ let pos = this.extendedPositions.find((p) => p.instrument === instrument);
43202
+ const oldAbs = pos ? Math.abs(pos.size.toNumber()) : 0;
43203
+ const oldValUsd = pos ? pos.valueUsd.toNumber() : 0;
42195
43204
  if (pos) {
42196
43205
  pos.size = new Web3Number(pos.size.plus(sizeDelta).toFixed(8), 8);
42197
43206
  } else if (sizeDelta.toNumber() !== 0) {
@@ -42202,6 +43211,56 @@ var SolveBudget = class {
42202
43211
  valueUsd: new Web3Number(0, USDC_TOKEN_DECIMALS),
42203
43212
  leverage: "0"
42204
43213
  });
43214
+ pos = this.extendedPositions[this.extendedPositions.length - 1];
43215
+ } else {
43216
+ return;
43217
+ }
43218
+ const newAbs = Math.abs(pos.size.toNumber());
43219
+ let price = collateralPriceUsd;
43220
+ if (price === void 0 || price <= 0) {
43221
+ price = oldAbs > btcEps ? oldValUsd / oldAbs : this.vesuPoolStates[0]?.collateralPrice ?? 0;
43222
+ }
43223
+ if (price > 0) {
43224
+ pos.valueUsd = new Web3Number(
43225
+ (newAbs * price).toFixed(USDC_TOKEN_DECIMALS),
43226
+ USDC_TOKEN_DECIMALS
43227
+ );
43228
+ }
43229
+ if (!this.extendedBalance || lev <= 0 || price <= 0) return;
43230
+ const dAbs = newAbs - oldAbs;
43231
+ if (dAbs < -btcEps) {
43232
+ const releasedUsd = -dAbs * price / lev;
43233
+ if (releasedUsd > CASE_THRESHOLD_USD) {
43234
+ this.addToExtAvailTrade(releasedUsd);
43235
+ }
43236
+ } else if (dAbs > btcEps) {
43237
+ const lockedUsd = dAbs * price / lev;
43238
+ if (lockedUsd > CASE_THRESHOLD_USD) {
43239
+ this._lockExtendedMarginUsd(lockedUsd);
43240
+ }
43241
+ }
43242
+ }
43243
+ /** Pull margin for larger Extended exposure from liquid buckets, then balance/equity. */
43244
+ _lockExtendedMarginUsd(lockedUsd) {
43245
+ if (lockedUsd <= CASE_THRESHOLD_USD || !this.extendedBalance) return;
43246
+ let rem = lockedUsd;
43247
+ const uw = Math.min(rem, Math.max(0, this.extendedBalance.availableForWithdrawal.toNumber()));
43248
+ if (uw > 0) {
43249
+ this._updateExtAvailWithdraw(uw, true);
43250
+ rem -= uw;
43251
+ }
43252
+ if (rem > 0) {
43253
+ const uu = Math.min(rem, Math.max(0, this.extendedBalance.unrealisedPnl.toNumber()));
43254
+ if (uu > 0) {
43255
+ this._updateExtAvailUpnl(uu, true);
43256
+ rem -= uu;
43257
+ }
43258
+ }
43259
+ if (rem > 0) {
43260
+ const b = this.extendedBalance.balance.toNumber();
43261
+ const e = this.extendedBalance.equity.toNumber();
43262
+ this.extendedBalance.balance = safeUsdcWeb3Number(b - rem);
43263
+ this.extendedBalance.equity = safeUsdcWeb3Number(e - rem);
42205
43264
  }
42206
43265
  }
42207
43266
  /**
@@ -42229,6 +43288,23 @@ var SolveBudget = class {
42229
43288
  return output;
42230
43289
  }
42231
43290
  };
43291
+ function createSolveBudgetFromRawState(params) {
43292
+ const budget = new SolveBudget({
43293
+ assetToken: params.assetToken,
43294
+ usdcToken: params.usdcToken,
43295
+ unusedBalance: params.unusedBalance,
43296
+ walletBalance: params.walletBalance,
43297
+ vaultAssetBalance: params.vaultAssetBalance,
43298
+ vaultUsdcBalance: params.vaultUsdcBalance,
43299
+ extendedPositions: params.extendedPositions,
43300
+ extendedBalance: params.extendedBalance,
43301
+ vesuPoolStates: params.vesuPoolStates
43302
+ });
43303
+ if (params.limitBalanceBufferFactor && params.limitBalanceBufferFactor > 0) {
43304
+ budget.applyBuffer(params.limitBalanceBufferFactor);
43305
+ }
43306
+ return budget;
43307
+ }
42232
43308
  var ExtendedSVKVesuStateManager = class {
42233
43309
  constructor(config) {
42234
43310
  this._tag = "ExtendedSVKVesuStateManager";
@@ -42264,7 +43340,7 @@ var ExtendedSVKVesuStateManager = class {
42264
43340
  vesuAllocationUsd: safeUsdcWeb3Number(0),
42265
43341
  extendedAllocationUsd: safeUsdcWeb3Number(0),
42266
43342
  bringLiquidityAmount: safeUsdcWeb3Number(0),
42267
- pendingDeposit: this._budget.extendedBalance?.pendingDeposit ?? new Web3Number(0, USDC_TOKEN_DECIMALS)
43343
+ pendingDeposit: safeUsdcWeb3Number(0)
42268
43344
  };
42269
43345
  this._logSolveResult(result);
42270
43346
  return result;
@@ -42279,30 +43355,44 @@ var ExtendedSVKVesuStateManager = class {
42279
43355
  async _refresh() {
42280
43356
  logger.info(`${this._tag}::_refresh starting`);
42281
43357
  const [
42282
- vaultAllocatorBalance,
43358
+ vaultAssetBalance,
43359
+ vaultUsdcBalance,
42283
43360
  walletBalance,
42284
43361
  vesuPoolStates,
42285
43362
  extendedBalance,
42286
43363
  extendedPositions
42287
43364
  ] = await Promise.all([
42288
- this._fetchVaultAllocatorBalance(),
43365
+ this._fetchVaultAllocatorAssetBalance(),
43366
+ this._fetchVaultAllocatorUsdcBalanceIfDistinct(),
42289
43367
  this._fetchWalletBalances(),
42290
43368
  this._fetchAllVesuPoolStates(),
42291
43369
  this._fetchExtendedBalance(),
42292
43370
  this._fetchExtendedPositions()
42293
43371
  ]);
42294
- logger.verbose(`_refresh vaultAllocatorBalance: ${vaultAllocatorBalance.usdValue}, walletBalance: ${walletBalance.usdValue}`);
43372
+ logger.verbose(
43373
+ `${this._tag}::_refresh VA asset ${vaultAssetBalance.token.symbol}=$${vaultAssetBalance.usdValue.toFixed(2)}${vaultUsdcBalance ? `, VA USDC=$${vaultUsdcBalance.usdValue.toFixed(2)}` : ""}, wallet=${walletBalance.usdValue}`
43374
+ );
42295
43375
  const unusedBalance = this._computeUnusedBalances(
42296
- vaultAllocatorBalance,
43376
+ vaultAssetBalance,
43377
+ vaultUsdcBalance,
42297
43378
  walletBalance
42298
43379
  );
42299
- this._budget = new SolveBudget({
42300
- limitBalanceBufferFactor: this._config.limitBalanceBufferFactor,
43380
+ this._budget = createSolveBudgetFromRawState({
43381
+ assetToken: this._config.assetToken,
43382
+ usdcToken: this._config.usdcToken,
42301
43383
  unusedBalance,
42302
43384
  walletBalance,
42303
- vaultBalance: vaultAllocatorBalance,
43385
+ vaultAssetBalance,
43386
+ vaultUsdcBalance,
42304
43387
  extendedPositions,
42305
- extendedBalance,
43388
+ extendedBalance: {
43389
+ availableForTrade: extendedBalance?.availableForTrade || new Web3Number(0, USDC_TOKEN_DECIMALS),
43390
+ availableForWithdrawal: extendedBalance?.availableForWithdrawal || new Web3Number(0, USDC_TOKEN_DECIMALS),
43391
+ unrealisedPnl: extendedBalance?.unrealisedPnl || new Web3Number(0, USDC_TOKEN_DECIMALS),
43392
+ balance: extendedBalance?.balance || new Web3Number(0, USDC_TOKEN_DECIMALS),
43393
+ equity: extendedBalance?.equity || new Web3Number(0, USDC_TOKEN_DECIMALS),
43394
+ pendingDeposit: extendedBalance?.pendingDeposit || new Web3Number(0, USDC_TOKEN_DECIMALS)
43395
+ },
42306
43396
  vesuPoolStates
42307
43397
  });
42308
43398
  const totalUnusedUsd = unusedBalance.reduce(
@@ -42314,10 +43404,14 @@ var ExtendedSVKVesuStateManager = class {
42314
43404
  );
42315
43405
  }
42316
43406
  // todo add communication check with python server of extended. if not working, throw error in solve function.
43407
+ /** True when strategy asset and USDC share one token — VA USDC slot is unused (all in asset balance). */
43408
+ _vaultAssetAndUsdcAreSameToken() {
43409
+ return this._config.assetToken.address.eq(this._config.usdcToken.address);
43410
+ }
42317
43411
  /**
42318
- * Reads the asset-token balance sitting idle in the vault allocator contract.
43412
+ * Reads the {@link StateManagerConfig.assetToken} balance idle in the vault allocator.
42319
43413
  */
42320
- async _fetchVaultAllocatorBalance() {
43414
+ async _fetchVaultAllocatorAssetBalance() {
42321
43415
  const { assetToken, vaultAllocator, networkConfig, pricer } = this._config;
42322
43416
  const balance = await new ERC20(networkConfig).balanceOf(
42323
43417
  assetToken.address,
@@ -42329,20 +43423,38 @@ var ExtendedSVKVesuStateManager = class {
42329
43423
  return { token: assetToken, amount: balance, usdValue };
42330
43424
  }
42331
43425
  /**
42332
- * Merges the vault-allocator balance and wallet balances into a
42333
- * deduplicated array of TokenBalance entries keyed by token address.
42334
- *
42335
- * e.g. VA has USDC, wallet has USDC + USDC.e → returns
42336
- * [{ token: USDC, amount: VA+wallet, usdValue: … },
42337
- * { token: USDC.e, amount: wallet, usdValue: }]
43426
+ * Reads {@link StateManagerConfig.usdcToken} in the vault allocator when it differs from
43427
+ * {@link StateManagerConfig.assetToken}. Otherwise returns null (treat VA USDC as 0; stablecoin is only under asset).
43428
+ */
43429
+ async _fetchVaultAllocatorUsdcBalanceIfDistinct() {
43430
+ if (this._vaultAssetAndUsdcAreSameToken()) return null;
43431
+ const { usdcToken, vaultAllocator, networkConfig, pricer } = this._config;
43432
+ const balance = await new ERC20(networkConfig).balanceOf(
43433
+ usdcToken.address,
43434
+ vaultAllocator,
43435
+ usdcToken.decimals
43436
+ );
43437
+ const tokenPrice = await pricer.getPrice(
43438
+ usdcToken.priceProxySymbol || usdcToken.symbol
43439
+ );
43440
+ const usdValue = Number(balance.toFixed(usdcToken.decimals)) * tokenPrice.price;
43441
+ return { token: usdcToken, amount: balance, usdValue };
43442
+ }
43443
+ /**
43444
+ * Merges vault-allocator asset, optional vault-allocator USDC, and operator wallet
43445
+ * balances into entries keyed by token address.
42338
43446
  */
42339
- _computeUnusedBalances(vaultAllocatorBalance, walletBalance) {
43447
+ _computeUnusedBalances(vaultAssetBalance, vaultUsdcBalance, walletBalance) {
42340
43448
  const balanceMap = /* @__PURE__ */ new Map();
42341
- balanceMap.set(vaultAllocatorBalance.token.address.toString(), {
42342
- token: vaultAllocatorBalance.token,
42343
- amount: vaultAllocatorBalance.amount,
42344
- usdValue: vaultAllocatorBalance.usdValue
42345
- });
43449
+ const put = (tb) => {
43450
+ balanceMap.set(tb.token.address.toString(), {
43451
+ token: tb.token,
43452
+ amount: tb.amount,
43453
+ usdValue: tb.usdValue
43454
+ });
43455
+ };
43456
+ put(vaultAssetBalance);
43457
+ if (vaultUsdcBalance) put(vaultUsdcBalance);
42346
43458
  const key = walletBalance.token.address.toString();
42347
43459
  const existing = balanceMap.get(key);
42348
43460
  if (existing) {
@@ -42361,30 +43473,29 @@ var ExtendedSVKVesuStateManager = class {
42361
43473
  return Array.from(balanceMap.values());
42362
43474
  }
42363
43475
  /**
42364
- * Reads the operator wallet's balances for the asset token (USDC.e) and
42365
- * USDC.e (needed for route computation P1 vs P2 decision for Extended deposits).
43476
+ * Reads the operator wallet balance for {@link StateManagerConfig.usdcToken} only
43477
+ * (wallet stablecoin is always USDC, regardless of strategy {@link StateManagerConfig.assetToken}).
42366
43478
  */
42367
43479
  async _fetchWalletBalances() {
42368
43480
  const {
42369
43481
  networkConfig,
42370
43482
  pricer,
42371
43483
  walletAddress,
42372
- assetToken,
42373
- usdceToken
43484
+ usdcToken
42374
43485
  } = this._config;
42375
43486
  const erc20 = new ERC20(networkConfig);
42376
- const [usdceBalance, usdcePrice] = await Promise.all([
43487
+ const [balance, tokenPrice] = await Promise.all([
42377
43488
  erc20.balanceOf(
42378
- usdceToken.address,
43489
+ usdcToken.address,
42379
43490
  walletAddress,
42380
- usdceToken.decimals
43491
+ usdcToken.decimals
42381
43492
  ),
42382
- pricer.getPrice(usdceToken.priceProxySymbol || usdceToken.symbol)
43493
+ pricer.getPrice(usdcToken.priceProxySymbol || usdcToken.symbol)
42383
43494
  ]);
42384
43495
  return {
42385
- token: usdceToken,
42386
- amount: usdceBalance,
42387
- usdValue: Number(usdceBalance.toFixed(usdceToken.decimals)) * usdcePrice.price
43496
+ token: usdcToken,
43497
+ amount: balance,
43498
+ usdValue: Number(balance.toFixed(usdcToken.decimals)) * tokenPrice.price
42388
43499
  };
42389
43500
  }
42390
43501
  /**
@@ -42502,12 +43613,12 @@ var ExtendedSVKVesuStateManager = class {
42502
43613
  * finite, sensible values. Throws on invalid state.
42503
43614
  */
42504
43615
  _validateRefreshedState() {
42505
- if (this._budget.unusedBalance.length === 0) {
43616
+ if (this._budget.unusedBalanceRows.length === 0) {
42506
43617
  throw new Error(
42507
43618
  `${this._tag}: unusedBalance is empty after refresh`
42508
43619
  );
42509
43620
  }
42510
- for (const balance of this._budget.unusedBalance) {
43621
+ for (const balance of this._budget.unusedBalanceRows) {
42511
43622
  this._validateTokenBalanceOrThrow(
42512
43623
  balance,
42513
43624
  `unusedBalance[${balance.token.symbol}]`
@@ -42527,7 +43638,7 @@ var ExtendedSVKVesuStateManager = class {
42527
43638
  }
42528
43639
  }
42529
43640
  _validateVesuPoolPricesOrThrow() {
42530
- for (const pool of this._budget.vesuPoolStates) {
43641
+ for (const pool of this._budget.vesuPools) {
42531
43642
  const poolLabel = pool.poolId.shortString();
42532
43643
  this._assertPositiveFinite(
42533
43644
  pool.collateralPrice,
@@ -42540,8 +43651,8 @@ var ExtendedSVKVesuStateManager = class {
42540
43651
  }
42541
43652
  }
42542
43653
  _validateExtendedBalanceOrThrow() {
42543
- if (!this._budget.extendedBalance) return;
42544
- const { equity, availableForTrade } = this._budget.extendedBalance;
43654
+ if (!this._budget.extendedBalanceView) return;
43655
+ const { equity, availableForTrade } = this._budget.extendedBalanceView;
42545
43656
  if (!Number.isFinite(equity.toNumber()) || !Number.isFinite(availableForTrade.toNumber())) {
42546
43657
  throw new Error(
42547
43658
  `${this._tag}: Extended balance contains non-finite values \u2014 equity: ${equity.toNumber()}, availableForTrade: ${availableForTrade.toNumber()}`
@@ -42580,18 +43691,26 @@ var ExtendedSVKVesuStateManager = class {
42580
43691
  );
42581
43692
  }
42582
43693
  /**
42583
- * Total investable = vault allocator balance + Extended available-for-trade,
42584
- * both reduced by the configured buffer percentage.
43694
+ * Total investable = vault allocator balance + Extended available-for-trade +
43695
+ * buffered unrealised PnL, matching `deposit_cases_extended_vesu.xlsx`:
43696
+ * `(VA + wallet + EXT_WITH_AVL + EXT_UPNL) * (1 − buffer)`.
43697
+ * Positive {@link ExtendedBalanceState.pendingDeposit} stays on the afT leg only (see {@link SolveBudget.extAvailTrade}).
42585
43698
  */
42586
43699
  _computeTotalInvestableAmount() {
42587
- const totalUnusedUsd = this._budget.unusedBalance.reduce(
42588
- (acc, b) => acc + b.usdValue,
42589
- 0
42590
- );
43700
+ const totalUnusedUsd = this._budget.unusedBalancesBufferedUsdSum;
42591
43701
  logger.debug(
42592
- `${this._tag}::_computeTotalInvestableAmount unusedBalances=${JSON.stringify(this._budget.unusedBalance.map((b) => ({ token: b.token.symbol, amount: b.amount.toNumber(), usdValue: b.usdValue })))}`
43702
+ `${this._tag}::_computeTotalInvestableAmount unusedBalances=${JSON.stringify(this._budget.unusedBalanceRows.map((b) => ({ token: b.token.symbol, amount: b.amount.toNumber(), usdValue: b.usdValue })))}`
43703
+ );
43704
+ const extBal = this._budget.extendedBalanceView;
43705
+ const rawAft = extBal?.availableForWithdrawal?.toNumber() ?? 0;
43706
+ const rawUpnl = extBal?.unrealisedPnl?.toNumber() ?? 0;
43707
+ let extBuffered = this._budget.bufferedUsd(rawAft) + this._budget.bufferedUsd(rawUpnl);
43708
+ const pd = extBal?.pendingDeposit?.toNumber() ?? 0;
43709
+ if (pd > 0) extBuffered += pd;
43710
+ const extendedAvailable = new Web3Number(
43711
+ extBuffered.toFixed(USDC_TOKEN_DECIMALS),
43712
+ USDC_TOKEN_DECIMALS
42593
43713
  );
42594
- const extendedAvailable = this._budget.extendedBalance?.availableForTrade ?? new Web3Number(0, USDC_TOKEN_DECIMALS);
42595
43714
  logger.verbose(`_computeTotalInvestableAmount totalUnusedUsd: ${totalUnusedUsd}, extendedAvailable: ${extendedAvailable.toNumber()}`);
42596
43715
  return new Web3Number(totalUnusedUsd.toFixed(USDC_TOKEN_DECIMALS), USDC_TOKEN_DECIMALS).plus(extendedAvailable);
42597
43716
  }
@@ -42622,7 +43741,7 @@ var ExtendedSVKVesuStateManager = class {
42622
43741
  if (denominator === 0) {
42623
43742
  throw new Error(`${this._tag}: Denominator is zero`);
42624
43743
  }
42625
- const collateralPrice = this._budget.vesuPoolStates[0]?.collateralPrice ?? 0;
43744
+ const collateralPrice = this._budget.vesuPools[0]?.collateralPrice ?? 0;
42626
43745
  const totalVesuExposureUsd = this._totalVesuCollateralUsd().plus(new Web3Number((deltaVesuCollateral * collateralPrice).toFixed(6), USDC_TOKEN_DECIMALS));
42627
43746
  const totalExtendedExposureUsd = this._totalExtendedExposureUsd().plus(new Web3Number((deltaExtendedCollateral * collateralPrice).toFixed(6), USDC_TOKEN_DECIMALS));
42628
43747
  const numerator = vesuLeverage * distributableAmount.toNumber() + totalVesuExposureUsd.toNumber() - totalExtendedExposureUsd.toNumber();
@@ -42634,8 +43753,6 @@ var ExtendedSVKVesuStateManager = class {
42634
43753
  distributableAmount.minus(extendedAllocationUsd).toFixed(USDC_TOKEN_DECIMALS),
42635
43754
  USDC_TOKEN_DECIMALS
42636
43755
  );
42637
- const perPoolDebtDeltasToBorrow = this._budget.vesuPerPoolDebtDeltasToBorrow;
42638
- vesuAllocationUsd = vesuAllocationUsd.plus(this._sumDebtDeltas(perPoolDebtDeltasToBorrow).multipliedBy(-1));
42639
43756
  let vesuPositionDelta = Number(new Web3Number((vesuAllocationUsd.toNumber() * vesuLeverage / collateralPrice).toFixed(6), 6).toFixedRoundDown(COLLATERAL_PRECISION));
42640
43757
  let extendedPositionDelta = Number(new Web3Number((extendedAllocationUsd.toNumber() * extendedLeverage / collateralPrice).toFixed(6), 6).toFixedRoundDown(COLLATERAL_PRECISION));
42641
43758
  if (!isRecursive) {
@@ -42654,14 +43771,17 @@ var ExtendedSVKVesuStateManager = class {
42654
43771
  * by existing collateral value, then converts each share to collateral
42655
43772
  * token units.
42656
43773
  */
42657
- _computePerPoolCollateralDeltas(vesuAllocationUsd, perPoolDebtDeltasToBorrow) {
43774
+ _computePerPoolCollateralDeltas(vesuAllocationUsd) {
42658
43775
  const vesuLeverage = calculateVesuLeverage();
42659
- const availableVesuCollateralAllocationUsd = vesuAllocationUsd.plus(this._sumDebtDeltas(perPoolDebtDeltasToBorrow));
43776
+ const availableVesuCollateralAllocationUsd = vesuAllocationUsd;
42660
43777
  const postLeverageAllocationUsd = availableVesuCollateralAllocationUsd.multipliedBy(vesuLeverage);
42661
43778
  const totalCollateralExisting = this._totalVesuCollateral();
42662
- return this._budget.vesuPoolStates.map((pool, index) => {
43779
+ return this._budget.vesuPools.map((pool, index) => {
42663
43780
  const _postLeverageAllocation = postLeverageAllocationUsd.dividedBy(pool.collateralPrice);
42664
- const postLeverageAllocation = new Web3Number(_postLeverageAllocation.plus(totalCollateralExisting).toFixed(COLLATERAL_PRECISION), pool.collateralToken.decimals).minus(totalCollateralExisting);
43781
+ const postLeverageAllocation = new Web3Number(
43782
+ _postLeverageAllocation.plus(totalCollateralExisting).toFixedRoundDown(COLLATERAL_PRECISION),
43783
+ pool.collateralToken.decimals
43784
+ ).minus(totalCollateralExisting);
42665
43785
  const _poolCollateralDelta = this._computePoolCollateralShare(
42666
43786
  pool,
42667
43787
  totalCollateralExisting,
@@ -42671,12 +43791,12 @@ var ExtendedSVKVesuStateManager = class {
42671
43791
  _poolCollateralDelta.toFixed(COLLATERAL_PRECISION),
42672
43792
  pool.collateralToken.decimals
42673
43793
  );
42674
- const newDebt = postLeverageAllocationUsd.minus(availableVesuCollateralAllocationUsd).dividedBy(pool.debtPrice);
43794
+ const newDebt = postLeverageAllocation.multipliedBy(pool.collateralPrice).minus(availableVesuCollateralAllocationUsd).dividedBy(pool.debtPrice);
42675
43795
  return {
42676
43796
  poolId: pool.poolId,
42677
43797
  collateralToken: pool.collateralToken,
42678
43798
  debtToken: pool.debtToken,
42679
- debtDelta: perPoolDebtDeltasToBorrow[index].plus(newDebt),
43799
+ debtDelta: newDebt,
42680
43800
  collateralDelta: poolCollateralDelta,
42681
43801
  collateralPrice: pool.collateralPrice,
42682
43802
  debtPrice: pool.debtPrice
@@ -42689,7 +43809,7 @@ var ExtendedSVKVesuStateManager = class {
42689
43809
  * Multi-pool cases split proportionally by current collateral USD value.
42690
43810
  */
42691
43811
  _computePoolCollateralShare(pool, totalCollateral, totalVesuAllocation) {
42692
- const isSinglePoolOrZeroTotal = this._budget.vesuPoolStates.length === 1 || totalCollateral.toNumber() === 0;
43812
+ const isSinglePoolOrZeroTotal = this._budget.vesuPools.length === 1 || totalCollateral.toNumber() === 0;
42693
43813
  if (isSinglePoolOrZeroTotal) return totalVesuAllocation;
42694
43814
  const poolWeight = pool.collateralAmount.dividedBy(totalCollateral);
42695
43815
  return new Web3Number(
@@ -42726,8 +43846,8 @@ var ExtendedSVKVesuStateManager = class {
42726
43846
  */
42727
43847
  _computeTargetExtendedExposure(vesuDeltas) {
42728
43848
  let totalExposureCollateral = new Web3Number(0, USDC_TOKEN_DECIMALS);
42729
- for (let i = 0; i < this._budget.vesuPoolStates.length; i++) {
42730
- const pool = this._budget.vesuPoolStates[i];
43849
+ for (let i = 0; i < this._budget.vesuPools.length; i++) {
43850
+ const pool = this._budget.vesuPools[i];
42731
43851
  const delta = vesuDeltas[i];
42732
43852
  logger.debug(
42733
43853
  `${this._tag}::_computeTargetExtendedExposure poolId=${pool.poolId.toString()}, collateralAmount=${pool.collateralAmount.toNumber()}, collateralDelta=${delta.collateralDelta.toNumber()}`
@@ -42745,7 +43865,7 @@ var ExtendedSVKVesuStateManager = class {
42745
43865
  );
42746
43866
  }
42747
43867
  _hasNoExtendedPositions() {
42748
- return this._budget.extendedPositions.length === 0;
43868
+ return this._budget.extendedPositionsView.length === 0;
42749
43869
  }
42750
43870
  /**
42751
43871
  * Creates a single-element delta array for the default instrument
@@ -42756,7 +43876,7 @@ var ExtendedSVKVesuStateManager = class {
42756
43876
  {
42757
43877
  instrument: this._config.extendedAdapter.config.extendedMarketName,
42758
43878
  delta: new Web3Number(
42759
- delta.toFixed(COLLATERAL_PRECISION),
43879
+ delta.toFixedRoundDown(COLLATERAL_PRECISION),
42760
43880
  USDC_TOKEN_DECIMALS
42761
43881
  )
42762
43882
  }
@@ -42768,7 +43888,7 @@ var ExtendedSVKVesuStateManager = class {
42768
43888
  */
42769
43889
  _distributeExposureDeltaAcrossPositions(totalDelta) {
42770
43890
  const totalExposure = this._totalExtendedExposure();
42771
- return this._budget.extendedPositions.map((position) => {
43891
+ return this._budget.extendedPositionsView.map((position) => {
42772
43892
  const share = this._positionExposureShareFraction(
42773
43893
  position,
42774
43894
  totalExposure
@@ -42776,7 +43896,7 @@ var ExtendedSVKVesuStateManager = class {
42776
43896
  return {
42777
43897
  instrument: position.instrument,
42778
43898
  delta: new Web3Number(
42779
- totalDelta.multipliedBy(share).toFixed(COLLATERAL_PRECISION),
43899
+ totalDelta.multipliedBy(share).toFixedRoundDown(COLLATERAL_PRECISION),
42780
43900
  USDC_TOKEN_DECIMALS
42781
43901
  )
42782
43902
  };
@@ -42788,7 +43908,7 @@ var ExtendedSVKVesuStateManager = class {
42788
43908
  * or when total exposure is zero.
42789
43909
  */
42790
43910
  _positionExposureShareFraction(position, totalExposure) {
42791
- const isSingleOrZero = totalExposure.toNumber() === 0 || this._budget.extendedPositions.length === 1;
43911
+ const isSingleOrZero = totalExposure.toNumber() === 0 || this._budget.extendedPositionsView.length === 1;
42792
43912
  if (isSingleOrZero) return 1;
42793
43913
  return position.valueUsd.dividedBy(totalExposure).toNumber();
42794
43914
  }
@@ -42800,7 +43920,10 @@ var ExtendedSVKVesuStateManager = class {
42800
43920
  * Positive = need to deposit more, negative = can withdraw excess.
42801
43921
  */
42802
43922
  _computeExtendedDepositDelta(extendedAllocationUsd) {
42803
- const currentAvailableForTrade = this._budget.extendedBalance?.availableForTrade ?? new Web3Number(0, USDC_TOKEN_DECIMALS);
43923
+ const currentAvailableForTrade = new Web3Number(
43924
+ this._budget.extAvailTrade.toFixed(USDC_TOKEN_DECIMALS),
43925
+ USDC_TOKEN_DECIMALS
43926
+ );
42804
43927
  return new Web3Number(
42805
43928
  extendedAllocationUsd.minus(currentAvailableForTrade).toFixed(USDC_TOKEN_DECIMALS),
42806
43929
  USDC_TOKEN_DECIMALS
@@ -42808,8 +43931,8 @@ var ExtendedSVKVesuStateManager = class {
42808
43931
  }
42809
43932
  _computeVesuDepositAmount(vesuDeltas) {
42810
43933
  let totalVesuCollateral = new Web3Number(0, USDC_TOKEN_DECIMALS);
42811
- for (let i = 0; i < this._budget.vesuPoolStates.length; i++) {
42812
- const pool = this._budget.vesuPoolStates[i];
43934
+ for (let i = 0; i < this._budget.vesuPools.length; i++) {
43935
+ const pool = this._budget.vesuPools[i];
42813
43936
  const delta = vesuDeltas[i];
42814
43937
  totalVesuCollateral = totalVesuCollateral.plus(delta.collateralDelta.multipliedBy(pool.collateralPrice));
42815
43938
  totalVesuCollateral = totalVesuCollateral.minus(delta.debtDelta.multipliedBy(pool.debtPrice));
@@ -42854,13 +43977,17 @@ var ExtendedSVKVesuStateManager = class {
42854
43977
  for (const route of spendsByPool) {
42855
43978
  routes.push({ type: "VESU_REPAY" /* VESU_REPAY */, ...route, priority: routes.length });
42856
43979
  }
42857
- this._budget.spendVA(used);
43980
+ this._budget.spendVaRawUsd(used);
42858
43981
  }
42859
- _buildVesuBorrowRoutes(totalUsd, routes) {
43982
+ _buildVesuBorrowRoutes(totalUsd, routes, opts) {
42860
43983
  let borrowable = this._budget.vesuBorrowCapacity;
43984
+ if (opts?.maxBorrowUsd !== void 0) {
43985
+ borrowable = Math.min(borrowable, Math.max(0, opts.maxBorrowUsd));
43986
+ }
42861
43987
  if (totalUsd <= CASE_THRESHOLD_USD) return { routes, remaining: totalUsd };
42862
43988
  if (borrowable <= CASE_THRESHOLD_USD) return { routes, remaining: totalUsd };
42863
- const { used, spendsByPool } = this._budget.spendVesuBorrowCapacity(totalUsd);
43989
+ const borrowTarget = Math.min(totalUsd, borrowable);
43990
+ const { used, spendsByPool } = this._budget.spendVesuBorrowCapacity(borrowTarget);
42864
43991
  for (const route of spendsByPool) {
42865
43992
  routes.push({ type: "VESU_BORROW" /* VESU_BORROW */, ...route, priority: routes.length });
42866
43993
  }
@@ -42916,9 +44043,9 @@ var ExtendedSVKVesuStateManager = class {
42916
44043
  // });
42917
44044
  // }
42918
44045
  _getWalletToVARoute(tryAmount, routes) {
42919
- const usableAmount = Math.min(tryAmount, this._budget.walletUsd);
42920
- if (usableAmount > CASE_THRESHOLD_USD) {
42921
- const walletUsed = this._budget.spendWallet(usableAmount);
44046
+ const usableRaw = Math.min(tryAmount, this._budget.walletUsd);
44047
+ if (usableRaw > CASE_THRESHOLD_USD) {
44048
+ const walletUsed = this._budget.spendWallet(usableRaw);
42922
44049
  this._budget.addToVA(walletUsed);
42923
44050
  const route = { type: "WALLET_TO_VA" /* WALLET_TO_VA */, amount: safeUsdcWeb3Number(walletUsed), priority: routes.length };
42924
44051
  routes.push(route);
@@ -42927,9 +44054,9 @@ var ExtendedSVKVesuStateManager = class {
42927
44054
  return { routes, remaining: tryAmount };
42928
44055
  }
42929
44056
  _getWalletToEXTENDEDRoute(tryAmount, routes, shouldAddWaitRoute = true) {
42930
- const usableAmount = Math.min(tryAmount, this._budget.walletUsd);
42931
- if (usableAmount > CASE_THRESHOLD_USD) {
42932
- const walletUsed = this._budget.spendWallet(usableAmount);
44057
+ const usableRaw = Math.min(tryAmount, this._budget.walletUsd);
44058
+ if (usableRaw > CASE_THRESHOLD_USD) {
44059
+ const walletUsed = this._budget.spendWallet(usableRaw);
42933
44060
  this._budget.addToExtAvailTrade(walletUsed);
42934
44061
  routes.push({ type: "WALLET_TO_EXTENDED" /* WALLET_TO_EXTENDED */, amount: safeUsdcWeb3Number(walletUsed), priority: routes.length });
42935
44062
  if (shouldAddWaitRoute) {
@@ -42940,9 +44067,9 @@ var ExtendedSVKVesuStateManager = class {
42940
44067
  return { routes, remaining: tryAmount };
42941
44068
  }
42942
44069
  _getVAToEXTENDEDRoute(tryAmount, routes, shouldAddWaitRoute = true) {
42943
- const usableAmount = Math.min(tryAmount, this._budget.vaUsd);
42944
- if (usableAmount > CASE_THRESHOLD_USD) {
42945
- const vaUsed = this._budget.spendVA(usableAmount);
44070
+ const usable = Math.min(tryAmount, this._budget.vaUsd);
44071
+ if (usable > CASE_THRESHOLD_USD) {
44072
+ const vaUsed = this._budget.spendVA(usable);
42946
44073
  this._budget.addToExtAvailTrade(vaUsed);
42947
44074
  const route = { type: "VA_TO_EXTENDED" /* VA_TO_EXTENDED */, amount: safeUsdcWeb3Number(vaUsed), priority: routes.length };
42948
44075
  routes.push(route);
@@ -42955,40 +44082,42 @@ var ExtendedSVKVesuStateManager = class {
42955
44082
  }
42956
44083
  _getExtendedToWalletRoute(tryAmount, routes, shouldAddWaitRoute = true) {
42957
44084
  if (tryAmount <= CASE_THRESHOLD_USD) return { routes, remaining: tryAmount };
42958
- assert(tryAmount <= this._budget.extAvailWithdraw, `tryAmount is greater than extAvailTrade, tryAmount: ${tryAmount}, extAvailWithdraw: ${this._budget.extAvailWithdraw}`);
42959
- const extWithdrawUsed = this._budget.spendExtAvailTrade(tryAmount);
42960
- this._budget.addToWallet(Math.abs(extWithdrawUsed));
42961
- const route = { type: "EXTENDED_TO_WALLET" /* EXTENDED_TO_WALLET */, amount: safeUsdcWeb3Number(Math.abs(extWithdrawUsed)), priority: routes.length };
44085
+ const rawCap = this._budget.extAvailWithdraw + this._budget.extAvailUpnl;
44086
+ const rawSpend = Math.min(tryAmount, rawCap);
44087
+ if (rawSpend <= CASE_THRESHOLD_USD) return { routes, remaining: tryAmount };
44088
+ const rawOut = this._budget.spendExtAvailTrade(rawSpend);
44089
+ this._budget.addToWallet(Math.abs(rawOut));
44090
+ const route = { type: "EXTENDED_TO_WALLET" /* EXTENDED_TO_WALLET */, amount: safeUsdcWeb3Number(rawSpend), priority: routes.length };
42962
44091
  routes.push(route);
42963
44092
  if (shouldAddWaitRoute) {
42964
44093
  routes.push({ type: "RETURN_TO_WAIT" /* RETURN_TO_WAIT */, priority: routes.length });
42965
44094
  }
42966
- return { routes, remaining: tryAmount - Math.abs(extWithdrawUsed) };
44095
+ return { routes, remaining: tryAmount - rawSpend };
42967
44096
  }
42968
44097
  _getWALLETToVARoute(tryAmount, routes) {
42969
44098
  if (tryAmount <= CASE_THRESHOLD_USD) return { routes, remaining: tryAmount };
42970
- const usableAmount = Math.min(tryAmount, this._budget.walletUsd);
42971
- if (usableAmount <= CASE_THRESHOLD_USD) return { routes, remaining: tryAmount };
42972
- const walletUsed = this._budget.spendWallet(tryAmount);
44099
+ const usableRaw = Math.min(tryAmount, this._budget.walletUsd);
44100
+ if (usableRaw <= CASE_THRESHOLD_USD) return { routes, remaining: tryAmount };
44101
+ const walletUsed = this._budget.spendWallet(usableRaw);
42973
44102
  this._budget.addToVA(walletUsed);
42974
44103
  const route = { type: "WALLET_TO_VA" /* WALLET_TO_VA */, amount: safeUsdcWeb3Number(walletUsed), priority: routes.length };
42975
44104
  routes.push(route);
42976
44105
  return { routes, remaining: tryAmount - walletUsed };
42977
44106
  }
42978
44107
  _getUpnlRoute(tryAmount, routes) {
42979
- const upnl = this._budget.extendedBalance?.unrealisedPnl?.toNumber() ?? 0;
42980
- const usableAmount = Math.min(tryAmount, upnl);
42981
- if (usableAmount <= CASE_THRESHOLD_USD) return { routes, remaining: tryAmount };
42982
- const upnlUsed = this._budget.spendExtAvailUpnl(usableAmount);
42983
- this._budget.addToExtAvailTrade(upnlUsed);
42984
- assert(this._budget.extendedPositions.length == 1, "SolveBudget::_getUpnlRoute: extendedPositions length must be 1");
44108
+ const rawUpnl = this._budget.extAvailUpnl;
44109
+ const usableRaw = Math.min(tryAmount, rawUpnl);
44110
+ if (usableRaw <= 0) return { routes, remaining: tryAmount };
44111
+ this._budget.spendExtAvailUpnl(usableRaw);
44112
+ this._budget.addToExtAvailTrade(usableRaw);
44113
+ assert(this._budget.extendedPositionsView.length == 1, "SolveBudget::_getUpnlRoute: extendedPositions length must be 1");
42985
44114
  routes.push({
42986
44115
  type: "REALISE_PNL" /* REALISE_PNL */,
42987
- amount: safeUsdcWeb3Number(upnlUsed),
42988
- instrument: this._budget.extendedPositions[0].instrument,
44116
+ amount: safeUsdcWeb3Number(usableRaw),
44117
+ instrument: this._budget.extendedPositionsView[0].instrument,
42989
44118
  priority: routes.length
42990
44119
  });
42991
- return { routes, remaining: tryAmount - upnlUsed };
44120
+ return { routes, remaining: tryAmount - usableRaw };
42992
44121
  }
42993
44122
  // ── Sub-classifiers ────────────────────────────────────────────────────
42994
44123
  // Each sub-classifier builds routes directly from contextual data.
@@ -43003,7 +44132,7 @@ var ExtendedSVKVesuStateManager = class {
43003
44132
  const instrument = this._config.extendedAdapter.config.extendedMarketName ?? "BTC-USD";
43004
44133
  const routes = [];
43005
44134
  let remaining = withdrawAmount.toNumber();
43006
- const vaUsed = this._budget.spendVA(Math.min(this._budget.vaUsd, remaining));
44135
+ const vaUsed = this._budget.spendVA(remaining);
43007
44136
  remaining -= vaUsed;
43008
44137
  let totalExtUsed = 0;
43009
44138
  if (remaining > CASE_THRESHOLD_USD) {
@@ -43026,48 +44155,90 @@ var ExtendedSVKVesuStateManager = class {
43026
44155
  totalExtUsed = usableWithrawAmount + upnlUsed;
43027
44156
  }
43028
44157
  if (remaining > CASE_THRESHOLD_USD) {
43029
- const avgCollPrice = this._budget.vesuPoolStates[0]?.collateralPrice ?? 1;
43030
- assert(this._budget.vesuPoolStates.length == 1, "SolveBudget::_classifyWithdrawal: vesuPoolStates length must be 1");
43031
- const { vesuPositionDelta, extendedPositionDelta, vesuAllocationUsd, extendedAllocationUsd } = this._computeAllocationSplit(new Web3Number((-remaining).toFixed(6), USDC_TOKEN_DECIMALS));
43032
- if (vesuPositionDelta < 0) {
43033
- const vesuAdapter = this._config.vesuAdapters[0];
43034
- const debtDelta = Math.min(0, vesuPositionDelta * avgCollPrice - vesuAllocationUsd.toNumber());
43035
- const withdrawAmount2 = vesuAllocationUsd.dividedBy(avgCollPrice);
43036
- withdrawAmount2.decimals = vesuAdapter.config.collateral.decimals;
44158
+ assert(this._budget.vesuPools.length == 1, "SolveBudget::_classifyWithdrawal: vesuPoolStates length must be 1");
44159
+ const vesuAdapter = this._config.vesuAdapters[0];
44160
+ const avgCollPrice = this._budget.vesuPools[0]?.collateralPrice ?? 1;
44161
+ const vesuLeverage = calculateVesuLeverage();
44162
+ const extLeverage = calculateExtendedLevergae();
44163
+ const freedPerBtcVesu = avgCollPrice / vesuLeverage;
44164
+ const freedPerBtcExt = avgCollPrice / extLeverage;
44165
+ const vesuColBtc = this._budget.vesuPools[0].collateralAmount.toNumber();
44166
+ const extPosBtc = this._totalExtendedExposure().toNumber();
44167
+ let stillNeeded = remaining;
44168
+ let vesuBtcDelta = 0;
44169
+ let extBtcDelta = 0;
44170
+ let extFreed = 0;
44171
+ const roundUpBtc = (x) => {
44172
+ const factor = 10 ** COLLATERAL_PRECISION;
44173
+ return Math.ceil(x * factor) / factor;
44174
+ };
44175
+ const diff = vesuColBtc - extPosBtc;
44176
+ let currentVesuBtc = vesuColBtc;
44177
+ let currentExtBtc = extPosBtc;
44178
+ if (Math.abs(diff) > 1e-8) {
44179
+ if (diff > 0) {
44180
+ const btcRaw = stillNeeded / freedPerBtcVesu;
44181
+ const btc = Math.min(roundUpBtc(Math.min(Math.abs(diff), btcRaw, currentVesuBtc)), currentVesuBtc);
44182
+ vesuBtcDelta += btc;
44183
+ stillNeeded -= btc * freedPerBtcVesu;
44184
+ currentVesuBtc -= btc;
44185
+ } else {
44186
+ const btcRaw = stillNeeded / freedPerBtcExt;
44187
+ const btc = Math.min(roundUpBtc(Math.min(Math.abs(diff), btcRaw, currentExtBtc)), currentExtBtc);
44188
+ extBtcDelta += btc;
44189
+ extFreed += btc * freedPerBtcExt;
44190
+ stillNeeded -= btc * freedPerBtcExt;
44191
+ currentExtBtc -= btc;
44192
+ }
44193
+ }
44194
+ if (stillNeeded > CASE_THRESHOLD_USD) {
44195
+ const combinedFreed = freedPerBtcVesu + freedPerBtcExt;
44196
+ const maxBtc = Math.min(currentVesuBtc, currentExtBtc);
44197
+ const btcRaw = stillNeeded / combinedFreed;
44198
+ const btc = Math.min(roundUpBtc(Math.min(btcRaw, maxBtc)), maxBtc);
44199
+ vesuBtcDelta += btc;
44200
+ extBtcDelta += btc;
44201
+ extFreed += btc * freedPerBtcExt;
44202
+ }
44203
+ const r6 = (n) => Number(n.toFixed(6));
44204
+ if (vesuBtcDelta > 0) {
44205
+ const totalVesuBtcSigned = -vesuBtcDelta;
44206
+ const targetLtv = 1 - 1 / vesuLeverage;
44207
+ const debtDelta = r6(totalVesuBtcSigned * avgCollPrice * targetLtv);
44208
+ const marginBtc = 0;
44209
+ const swappedBtc = Number(vesuBtcDelta.toFixed(COLLATERAL_PRECISION));
43037
44210
  routes.push({
43038
44211
  type: "VESU_MULTIPLY_DECREASE_LEVER" /* VESU_MULTIPLY_DECREASE_LEVER */,
43039
44212
  poolId: vesuAdapter.config.poolId,
43040
44213
  collateralToken: vesuAdapter.config.collateral,
43041
- marginAmount: withdrawAmount2,
43042
- swappedCollateralAmount: new Web3Number(vesuPositionDelta, vesuAdapter.config.collateral.decimals).minus(withdrawAmount2),
44214
+ marginAmount: new Web3Number(marginBtc.toFixed(COLLATERAL_PRECISION), vesuAdapter.config.collateral.decimals),
44215
+ swappedCollateralAmount: new Web3Number(swappedBtc.toFixed(COLLATERAL_PRECISION), vesuAdapter.config.collateral.decimals),
43043
44216
  debtToken: vesuAdapter.config.debt,
43044
44217
  debtAmount: new Web3Number(debtDelta, USDC_TOKEN_DECIMALS),
43045
44218
  priority: routes.length
43046
44219
  });
43047
- this._budget.applyVesuDelta(vesuAdapter.config.poolId, vesuAdapter.config.collateral, vesuAdapter.config.debt, new Web3Number(vesuPositionDelta, USDC_TOKEN_DECIMALS), new Web3Number(debtDelta, USDC_TOKEN_DECIMALS));
43048
- if (vesuAllocationUsd.toNumber() < -CASE_THRESHOLD_USD) {
43049
- const swapAmount = new Web3Number((vesuAllocationUsd.toNumber() / avgCollPrice * 0.998).toFixed(6), USDC_TOKEN_DECIMALS);
43050
- routes.push({
43051
- type: "AVNU_WITHDRAW_SWAP" /* AVNU_WITHDRAW_SWAP */,
43052
- fromToken: vesuAdapter.config.collateral.symbol,
43053
- // add buffer to avoid rounding errors
43054
- fromAmount: swapAmount,
43055
- toToken: vesuAdapter.config.debt.symbol,
43056
- priority: routes.length
43057
- });
43058
- this._budget.addToVA(vesuAllocationUsd.abs().toNumber());
43059
- }
44220
+ this._budget.applyVesuDelta(
44221
+ vesuAdapter.config.poolId,
44222
+ vesuAdapter.config.collateral,
44223
+ vesuAdapter.config.debt,
44224
+ new Web3Number(r6(totalVesuBtcSigned), USDC_TOKEN_DECIMALS),
44225
+ new Web3Number(debtDelta, USDC_TOKEN_DECIMALS)
44226
+ );
44227
+ this._budget.addToVA(vesuBtcDelta * freedPerBtcVesu);
43060
44228
  }
43061
- if (extendedPositionDelta < 0) {
44229
+ if (extBtcDelta > 0) {
43062
44230
  routes.push({
43063
44231
  type: "EXTENDED_DECREASE_LEVER" /* EXTENDED_DECREASE_LEVER */,
43064
- amount: safeUsdcWeb3Number(extendedPositionDelta),
44232
+ amount: safeUsdcWeb3Number(-r6(extBtcDelta)),
43065
44233
  instrument,
43066
44234
  priority: routes.length
43067
44235
  });
43068
- this._budget.applyExtendedExposureDelta(instrument, safeUsdcWeb3Number(extendedPositionDelta));
43069
- this._budget.addToExtAvailTrade(extendedAllocationUsd.abs().toNumber());
43070
- totalExtUsed += extendedAllocationUsd.abs().toNumber();
44236
+ this._budget.applyExtendedExposureDelta(
44237
+ instrument,
44238
+ safeUsdcWeb3Number(-r6(extBtcDelta)),
44239
+ avgCollPrice
44240
+ );
44241
+ totalExtUsed += extFreed;
43071
44242
  }
43072
44243
  }
43073
44244
  if (totalExtUsed > CASE_THRESHOLD_USD) {
@@ -43100,56 +44271,231 @@ var ExtendedSVKVesuStateManager = class {
43100
44271
  *
43101
44272
  * Design: accumulate all ext-to-wallet moves, add transfer routes at the end (principle #3).
43102
44273
  */
43103
- _classifyLtvVesu() {
43104
- const debtDeltaSum = this._budget.vesuPerPoolDebtDeltasToBorrow.reduce(
43105
- (a, b) => a + b.toNumber(),
43106
- 0
44274
+ /**
44275
+ * Unified LTV classifier. Computes both Vesu repay and Extended margin needs,
44276
+ * then builds all routes in a single pass with no duplicate transfers.
44277
+ *
44278
+ * Vesu repay priority: VA > Wallet > ExtAvl > ExtUpnl
44279
+ * Extended margin priority: Wallet > VA > VesuBorrow
44280
+ * Shared sources consumed by Vesu first (higher priority).
44281
+ */
44282
+ _classifyLTV() {
44283
+ assert(this._budget.vesuPools.length === 1, `${this._tag}::_classifyLTV expects exactly one Vesu pool`);
44284
+ const d = rebalance(this._ltvRebalanceInputsFromBudget());
44285
+ if (this._isLtvRebalanceNoop(d)) return [];
44286
+ logger.info(
44287
+ `${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}`
43107
44288
  );
43108
- logger.info(`${this._tag}::_classifyLtvVesu debtDeltaSum: ${debtDeltaSum}`);
43109
- const allHealthLTVs = this._budget.shouldVesuRebalance.every((shouldReb) => !shouldReb);
43110
- if (debtDeltaSum >= -CASE_THRESHOLD_USD || allHealthLTVs) return [];
43111
- const needed = Math.abs(debtDeltaSum);
43112
- const routes = [];
43113
- let remaining = needed;
43114
- let totalExtUsed = 0;
43115
- let caseId = "LTV_VESU_HIGH_USE_VA_OR_WALLET" /* LTV_VESU_HIGH_USE_VA_OR_WALLET */;
43116
- const vaUsed = this._budget.spendVA(Math.min(this._budget.vaUsd, remaining));
43117
- remaining -= vaUsed;
43118
- if (remaining > CASE_THRESHOLD_USD) {
43119
- const { remaining: walletToVaRemaining } = this._getWALLETToVARoute(remaining, routes);
43120
- remaining = walletToVaRemaining;
43121
- }
43122
- if (remaining > CASE_THRESHOLD_USD) {
43123
- const usableWithdrawAmount = Math.min(remaining, this._budget.extAvailWithdraw);
43124
- remaining -= usableWithdrawAmount;
43125
- let upnlUsed = 0;
43126
- if (remaining > CASE_THRESHOLD_USD) {
43127
- const { remaining: upnlRemaining } = this._getUpnlRoute(remaining, routes);
43128
- upnlUsed = remaining - upnlRemaining;
43129
- remaining = upnlRemaining;
43130
- caseId = "LTV_EXTENDED_PROFITABLE_REALIZE" /* LTV_EXTENDED_PROFITABLE_REALIZE */;
43131
- } else {
43132
- caseId = "LTV_EXTENDED_PROFITABLE_AVAILABLE" /* LTV_EXTENDED_PROFITABLE_AVAILABLE */;
43133
- }
43134
- totalExtUsed = usableWithdrawAmount + upnlUsed;
43135
- }
43136
- if (remaining > CASE_THRESHOLD_USD) {
43137
- throw new Error(`${this._tag}: Insufficient funds to cover margin needs`);
43138
- }
43139
- if (totalExtUsed > CASE_THRESHOLD_USD) {
43140
- this._getExtendedToWalletRoute(totalExtUsed, routes);
43141
- this._getWALLETToVARoute(totalExtUsed, routes);
43142
- }
43143
- this._buildVesuRepayRoutes(needed - remaining, routes);
44289
+ const routes = this._buildLtvRoutesFromRebalanceDeltas(d);
44290
+ if (routes.length === 0) return [];
44291
+ 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);
43144
44292
  routes.forEach((r, i) => {
43145
44293
  r.priority = i;
43146
44294
  });
43147
44295
  return [{
43148
- case: CASE_DEFINITIONS[caseId],
43149
- additionalArgs: { amount: safeUsdcWeb3Number(needed) },
44296
+ case: CASE_DEFINITIONS["MANAGE_LTV" /* MANAGE_LTV */],
44297
+ additionalArgs: { amount: safeUsdcWeb3Number(amountUsd) },
43150
44298
  routes
43151
44299
  }];
43152
44300
  }
44301
+ _ltvRebalanceInputsFromBudget() {
44302
+ const pool = this._budget.vesuPools[0];
44303
+ const instrument = this._config.extendedAdapter.config.extendedMarketName ?? "BTC-USD";
44304
+ const extPosBtc = this._budget.extendedPositionsView.filter((p) => p.instrument === instrument).reduce((s, p) => s + Math.abs(p.size.toNumber()), 0);
44305
+ const targetHF = VesuConfig.maxLtv / VesuConfig.targetLtv;
44306
+ return {
44307
+ ext: {
44308
+ positionBtc: extPosBtc,
44309
+ equity: this._budget.extendedBalanceView?.equity?.toNumber() ?? 0,
44310
+ avlWithdraw: this._budget.extAvailWithdraw,
44311
+ upnl: this._budget.extAvailUpnl,
44312
+ leverage: calculateExtendedLevergae()
44313
+ },
44314
+ vesu: {
44315
+ positionBtc: pool.collateralAmount.toNumber(),
44316
+ debt: pool.debtAmount.toNumber(),
44317
+ debtPrice: pool.debtPrice,
44318
+ maxLTV: VesuConfig.maxLtv,
44319
+ targetHF
44320
+ },
44321
+ btcPrice: pool.collateralPrice,
44322
+ funding: {
44323
+ vaUsd: this._budget.vaUsd,
44324
+ walletUsd: this._budget.walletUsd,
44325
+ vesuBorrowCapacity: this._budget.vesuBorrowCapacity,
44326
+ extAvlWithdraw: this._budget.extAvailWithdraw,
44327
+ extUpnl: this._budget.extAvailUpnl
44328
+ },
44329
+ config: {
44330
+ positionPrecision: COLLATERAL_PRECISION,
44331
+ hfBuffer: 0.05
44332
+ }
44333
+ };
44334
+ }
44335
+ _isLtvRebalanceNoop(d) {
44336
+ const btcEps = 10 ** -COLLATERAL_PRECISION;
44337
+ const usdEps = CASE_THRESHOLD_USD;
44338
+ 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;
44339
+ }
44340
+ /**
44341
+ * Turn pure rebalance() deltas into execution routes.
44342
+ * Order: Vesu multiply (decrease/increase) → Extended lever → aggregated transfers
44343
+ * (REALISE_PNL, EXTENDED_TO_WALLET + WAIT, WALLET_TO_VA, VESU_BORROW, VESU_REPAY, VA_TO_EXTENDED).
44344
+ */
44345
+ _buildLtvRoutesFromRebalanceDeltas(d) {
44346
+ const routes = [];
44347
+ const pool = this._budget.vesuPools[0];
44348
+ const vesuAdapter = this._config.vesuAdapters[0];
44349
+ const instrument = this._config.extendedAdapter.config.extendedMarketName ?? "BTC-USD";
44350
+ const price = pool.collateralPrice;
44351
+ const debtPrice = pool.debtPrice;
44352
+ const targetLtv = VesuConfig.targetLtv;
44353
+ const btcEps = 10 ** -COLLATERAL_PRECISION;
44354
+ let multiplyDebtRepayUsd = 0;
44355
+ if (d.dVesuPosition < -btcEps) {
44356
+ const xBtc = -d.dVesuPosition;
44357
+ const transferUsdFromVesu = Math.max(0, d.dTransferVesuToExt);
44358
+ let marginBtc = 0;
44359
+ let swappedBtc = Number(xBtc.toFixed(COLLATERAL_PRECISION));
44360
+ if (transferUsdFromVesu > CASE_THRESHOLD_USD && price > 0) {
44361
+ const marginCapFromTransfer = transferUsdFromVesu / price;
44362
+ marginBtc = Number(
44363
+ Math.min(xBtc, marginCapFromTransfer).toFixed(COLLATERAL_PRECISION)
44364
+ );
44365
+ swappedBtc = Number((xBtc - marginBtc).toFixed(COLLATERAL_PRECISION));
44366
+ }
44367
+ const swapLegMaxRepayUsd = swappedBtc * price * debtPrice;
44368
+ const debtUsdFallback = swappedBtc * price * targetLtv;
44369
+ let debtTokenDelta;
44370
+ if (d.dVesuDebt < 0) {
44371
+ const needRepayUsd = -d.dVesuDebt * debtPrice;
44372
+ const multiplyRepayUsd = Math.min(needRepayUsd, swapLegMaxRepayUsd);
44373
+ debtTokenDelta = -(multiplyRepayUsd / debtPrice);
44374
+ } else {
44375
+ debtTokenDelta = -debtUsdFallback;
44376
+ }
44377
+ const debtAmtW3 = new Web3Number(debtTokenDelta.toFixed(USDC_TOKEN_DECIMALS), USDC_TOKEN_DECIMALS);
44378
+ multiplyDebtRepayUsd = Math.abs(debtTokenDelta) * debtPrice;
44379
+ routes.push({
44380
+ type: "VESU_MULTIPLY_DECREASE_LEVER" /* VESU_MULTIPLY_DECREASE_LEVER */,
44381
+ poolId: vesuAdapter.config.poolId,
44382
+ collateralToken: vesuAdapter.config.collateral,
44383
+ marginAmount: new Web3Number(marginBtc.toFixed(COLLATERAL_PRECISION), vesuAdapter.config.collateral.decimals),
44384
+ swappedCollateralAmount: new Web3Number(swappedBtc.toFixed(COLLATERAL_PRECISION), vesuAdapter.config.collateral.decimals),
44385
+ debtToken: vesuAdapter.config.debt,
44386
+ debtAmount: debtAmtW3,
44387
+ priority: routes.length
44388
+ });
44389
+ this._budget.applyVesuDelta(
44390
+ vesuAdapter.config.poolId,
44391
+ vesuAdapter.config.collateral,
44392
+ vesuAdapter.config.debt,
44393
+ new Web3Number((-xBtc).toFixed(COLLATERAL_PRECISION), vesuAdapter.config.collateral.decimals),
44394
+ debtAmtW3
44395
+ );
44396
+ if (transferUsdFromVesu > CASE_THRESHOLD_USD) {
44397
+ this._budget.addToVA(transferUsdFromVesu);
44398
+ }
44399
+ } else if (d.dVesuPosition > btcEps) {
44400
+ const vesuDepositAmount = new Web3Number(
44401
+ (d.dVesuPosition * price * (1 - targetLtv)).toFixed(USDC_TOKEN_DECIMALS),
44402
+ USDC_TOKEN_DECIMALS
44403
+ );
44404
+ if (vesuDepositAmount.toNumber() > CASE_THRESHOLD_USD) {
44405
+ routes.push({
44406
+ type: "AVNU_DEPOSIT_SWAP" /* AVNU_DEPOSIT_SWAP */,
44407
+ priority: routes.length,
44408
+ fromToken: vesuAdapter.config.collateral.symbol,
44409
+ fromAmount: vesuDepositAmount,
44410
+ toToken: vesuAdapter.config.debt.symbol
44411
+ });
44412
+ }
44413
+ const collateralDelta = new Web3Number(
44414
+ d.dVesuPosition.toFixed(COLLATERAL_PRECISION),
44415
+ vesuAdapter.config.collateral.decimals
44416
+ );
44417
+ const availableBorrowCapacity = this._budget.vesuBorrowCapacity;
44418
+ const externalDepositAmount = vesuDepositAmount.minus(
44419
+ new Web3Number(Math.min(availableBorrowCapacity, vesuDepositAmount.toNumber()).toFixed(USDC_TOKEN_DECIMALS), USDC_TOKEN_DECIMALS)
44420
+ );
44421
+ const collPx = pool.collateralPrice || 1;
44422
+ const swappedAmount = new Web3Number(
44423
+ (externalDepositAmount.toNumber() * (pool.debtPrice ?? 1) / collPx).toFixed(6),
44424
+ vesuAdapter.config.collateral.decimals
44425
+ );
44426
+ const debtDeltaTokens = new Web3Number(
44427
+ d.dVesuDebt.toFixed(USDC_TOKEN_DECIMALS),
44428
+ USDC_TOKEN_DECIMALS
44429
+ );
44430
+ routes.push({
44431
+ type: "VESU_MULTIPLY_INCREASE_LEVER" /* VESU_MULTIPLY_INCREASE_LEVER */,
44432
+ priority: routes.length,
44433
+ collateralToken: vesuAdapter.config.collateral,
44434
+ debtToken: vesuAdapter.config.debt,
44435
+ marginAmount: swappedAmount,
44436
+ swappedCollateralAmount: collateralDelta.minus(swappedAmount),
44437
+ debtAmount: debtDeltaTokens.plus(new Web3Number(Math.min(availableBorrowCapacity, vesuDepositAmount.toNumber()).toFixed(USDC_TOKEN_DECIMALS), USDC_TOKEN_DECIMALS)),
44438
+ poolId: vesuAdapter.config.poolId
44439
+ });
44440
+ this._budget.applyVesuDelta(
44441
+ vesuAdapter.config.poolId,
44442
+ vesuAdapter.config.collateral,
44443
+ vesuAdapter.config.debt,
44444
+ collateralDelta,
44445
+ debtDeltaTokens
44446
+ );
44447
+ }
44448
+ if (d.dExtPosition < -btcEps) {
44449
+ const amt = new Web3Number(d.dExtPosition.toFixed(COLLATERAL_PRECISION), 8);
44450
+ routes.push({
44451
+ type: "EXTENDED_DECREASE_LEVER" /* EXTENDED_DECREASE_LEVER */,
44452
+ amount: amt,
44453
+ instrument,
44454
+ priority: routes.length
44455
+ });
44456
+ this._budget.applyExtendedExposureDelta(instrument, amt, price);
44457
+ } else if (d.dExtPosition > btcEps) {
44458
+ const amt = new Web3Number(d.dExtPosition.toFixed(COLLATERAL_PRECISION), 8);
44459
+ routes.push({
44460
+ type: "EXTENDED_INCREASE_LEVER" /* EXTENDED_INCREASE_LEVER */,
44461
+ amount: amt,
44462
+ instrument,
44463
+ priority: routes.length
44464
+ });
44465
+ this._budget.applyExtendedExposureDelta(instrument, amt, price);
44466
+ }
44467
+ const negUpnl = Math.min(0, d.dExtUpnl);
44468
+ const negExtAvl = Math.min(0, d.dExtAvlWithdraw);
44469
+ let hadExtendedOut = false;
44470
+ if (negUpnl < -CASE_THRESHOLD_USD) {
44471
+ this._getUpnlRoute(Math.abs(negUpnl), routes);
44472
+ hadExtendedOut = true;
44473
+ }
44474
+ const extToWalletUsd = (negExtAvl < -CASE_THRESHOLD_USD ? Math.abs(negExtAvl) : 0) + (negUpnl < -CASE_THRESHOLD_USD ? Math.abs(negUpnl) : 0);
44475
+ if (extToWalletUsd > CASE_THRESHOLD_USD) {
44476
+ this._getExtendedToWalletRoute(extToWalletUsd, routes);
44477
+ hadExtendedOut = true;
44478
+ }
44479
+ const walletPull = Math.abs(Math.min(0, d.dWalletUsd));
44480
+ const walletToVaUsd = walletPull + extToWalletUsd;
44481
+ if (walletToVaUsd > CASE_THRESHOLD_USD) {
44482
+ this._getWALLETToVARoute(walletToVaUsd, routes);
44483
+ }
44484
+ if (d.dVesuBorrowCapacity < -CASE_THRESHOLD_USD) {
44485
+ this._buildVesuBorrowRoutes(Math.abs(d.dVesuBorrowCapacity), routes);
44486
+ }
44487
+ const totalDebtRepayUsd = d.dVesuDebt < 0 ? -d.dVesuDebt * debtPrice : 0;
44488
+ const standaloneRepayUsd = Math.max(0, totalDebtRepayUsd - multiplyDebtRepayUsd);
44489
+ if (standaloneRepayUsd > CASE_THRESHOLD_USD) {
44490
+ this._buildVesuRepayRoutes(standaloneRepayUsd, routes);
44491
+ }
44492
+ const posExtEq = Math.max(0, d.dExtAvlWithdraw);
44493
+ const vaToExtUsd = posExtEq > CASE_THRESHOLD_USD ? posExtEq : 0;
44494
+ if (vaToExtUsd > CASE_THRESHOLD_USD) {
44495
+ this._getVAToEXTENDEDRoute(vaToExtUsd, routes, hadExtendedOut);
44496
+ }
44497
+ return routes;
44498
+ }
43153
44499
  // ── LTV Vesu route builders ───────────────────────────────────────────
43154
44500
  /**
43155
44501
  * LTV_EXTENDED_PROFITABLE_AVAILABLE:
@@ -43226,60 +44572,7 @@ var ExtendedSVKVesuStateManager = class {
43226
44572
  // routes.forEach((r, i) => { r.priority = i; });
43227
44573
  // return routes;
43228
44574
  // }
43229
- /** 2b. LTV Rebalance Extended side */
43230
- /**
43231
- * 2b. LTV Rebalance — Extended side
43232
- *
43233
- * Triggered when Extended equity is below the required margin for current positions.
43234
- * Sources funds to Extended via VA/Wallet or Vesu borrow.
43235
- *
43236
- * Priority: 1) VA/Wallet → Extended 2) Vesu borrow → VA → Extended
43237
- */
43238
- _classifyLtvExtended() {
43239
- const totalExtPosUsd = this._totalExtendedExposureUsd().toNumber();
43240
- const extEquity = this._budget.extendedBalance?.equity?.toNumber() ?? 0;
43241
- const lev = calculateExtendedLevergae();
43242
- const marginNeeded = lev > 0 ? totalExtPosUsd / lev - extEquity : 0;
43243
- if (marginNeeded <= CASE_THRESHOLD_USD) return [];
43244
- let caseId = "LTV_EXTENDED_HIGH_USE_VA_OR_WALLET" /* LTV_EXTENDED_HIGH_USE_VA_OR_WALLET */;
43245
- let remaining = marginNeeded;
43246
- const routes = [];
43247
- if (this._budget.vaWalletUsd > CASE_THRESHOLD_USD && remaining > CASE_THRESHOLD_USD) {
43248
- const use = Math.min(this._budget.vaWalletUsd, remaining);
43249
- if (this._budget.vaUsd > CASE_THRESHOLD_USD) {
43250
- const { remaining: vaRem } = this._getVAToEXTENDEDRoute(remaining, routes, false);
43251
- remaining = vaRem;
43252
- }
43253
- if (remaining > CASE_THRESHOLD_USD) {
43254
- const { remaining: walletRem } = this._getWalletToEXTENDEDRoute(remaining, routes, false);
43255
- remaining = walletRem;
43256
- }
43257
- }
43258
- if (remaining > CASE_THRESHOLD_USD && this._budget.vesuBorrowCapacity > CASE_THRESHOLD_USD) {
43259
- const { remaining: borrowRem } = this._buildVesuBorrowRoutes(Math.min(remaining, this._budget.vesuBorrowCapacity), routes);
43260
- const borrowed = remaining - borrowRem;
43261
- if (remaining != borrowRem) {
43262
- const { remaining: vaRem } = this._getVAToEXTENDEDRoute(borrowed, routes, false);
43263
- }
43264
- remaining = borrowRem;
43265
- routes.forEach((r, i) => {
43266
- r.priority = i;
43267
- });
43268
- remaining -= borrowed;
43269
- caseId = "LTV_VESU_LOW_TO_EXTENDED" /* LTV_VESU_LOW_TO_EXTENDED */;
43270
- }
43271
- if (remaining > CASE_THRESHOLD_USD) {
43272
- throw new Error(`${this._tag}: Insufficient funds to cover margin needs`);
43273
- }
43274
- routes.forEach((r, i) => {
43275
- r.priority = i;
43276
- });
43277
- return [{
43278
- case: CASE_DEFINITIONS[caseId],
43279
- additionalArgs: { amount: safeUsdcWeb3Number(marginNeeded) },
43280
- routes
43281
- }];
43282
- }
44575
+ // _classifyLtvExtended has been merged into the unified _classifyLTV above.
43283
44576
  // ── LTV Extended route builders ───────────────────────────────────────
43284
44577
  /**
43285
44578
  * LTV_EXTENDED_HIGH_USE_VA_OR_WALLET:
@@ -43346,6 +44639,62 @@ var ExtendedSVKVesuStateManager = class {
43346
44639
  // return routes;
43347
44640
  // }
43348
44641
  // ! todo implement max lever amount per execution cycle
44642
+ _rebalanceFunds({
44643
+ extAvlWithdraw,
44644
+ extUpnl,
44645
+ vaUsd,
44646
+ walletUsd,
44647
+ vesuBorrowCapacity,
44648
+ vesuLeverage,
44649
+ extendedLeverage
44650
+ }) {
44651
+ const total = extAvlWithdraw + extUpnl + vaUsd + walletUsd + vesuBorrowCapacity;
44652
+ const extendedTarget = total / (1 + extendedLeverage / vesuLeverage);
44653
+ const extendedInitial = extAvlWithdraw + extUpnl;
44654
+ let delta = extendedTarget - extendedInitial;
44655
+ let dExtAvlWithdraw = 0, dExtUpnl = 0, dVaUsd = 0, dWalletUsd = 0, dVesuBorrowCapacity = 0;
44656
+ if (delta > 0) {
44657
+ let need = delta;
44658
+ const takeWalletUsd = Math.min(walletUsd, need);
44659
+ dWalletUsd -= takeWalletUsd;
44660
+ need -= takeWalletUsd;
44661
+ const takeVaUsd = Math.min(vaUsd, need);
44662
+ dVaUsd -= takeVaUsd;
44663
+ need -= takeVaUsd;
44664
+ const takeVesuBorrowCapacity = Math.min(vesuBorrowCapacity, need);
44665
+ dVesuBorrowCapacity -= takeVesuBorrowCapacity;
44666
+ need -= takeVesuBorrowCapacity;
44667
+ const received = delta - need;
44668
+ const eco1Sum = extAvlWithdraw + extUpnl;
44669
+ if (eco1Sum >= 0) {
44670
+ dExtAvlWithdraw += received;
44671
+ } else {
44672
+ throw new Error(`${this._tag}: Unexpected case`);
44673
+ }
44674
+ if (need > 0) {
44675
+ throw new Error(`${this._tag}: Insufficient funds to cover margin needs`);
44676
+ }
44677
+ } else if (delta < 0) {
44678
+ let need = -delta;
44679
+ const takeExtAvlWithdraw = Math.min(extAvlWithdraw, need);
44680
+ dExtAvlWithdraw -= takeExtAvlWithdraw;
44681
+ need -= takeExtAvlWithdraw;
44682
+ const takeExtUpnl = Math.min(extUpnl, need);
44683
+ dExtUpnl -= takeExtUpnl;
44684
+ need -= takeExtUpnl;
44685
+ const sent = -delta - need;
44686
+ const eco2Sum = vaUsd + walletUsd + vesuBorrowCapacity;
44687
+ if (eco2Sum >= 0) {
44688
+ dWalletUsd += sent;
44689
+ } else {
44690
+ throw new Error(`${this._tag}: Unexpected case`);
44691
+ }
44692
+ if (need > 0) {
44693
+ throw new Error(`${this._tag}: Insufficient funds to cover margin needs`);
44694
+ }
44695
+ }
44696
+ return { dExtAvlWithdraw, dExtUpnl, dVaUsd, dWalletUsd, dVesuBorrowCapacity, isExtendedToVesu: delta < 0 };
44697
+ }
43349
44698
  /**
43350
44699
  * 3. New Deposits / Excess Funds
43351
44700
  *
@@ -43360,81 +44709,84 @@ var ExtendedSVKVesuStateManager = class {
43360
44709
  * Computes allocation split between Vesu and Extended, then sources
43361
44710
  * funds and creates lever-increase routes.
43362
44711
  *
43363
- * Fund flow (principle #3accumulate transfers, defer wait):
43364
- * Phase A: fund Extended (wallet→ext, VA→ext, vesu-borrow→VA→ext)
43365
- * Phase B: fund Vesu VA shortfall (wallet→VA, ext→wallet + wallet→VA)
43366
- * Phase C: RETURN_TO_WAIT (if any transfer to Extended occurred)
43367
- * Phase D: lever routes (VESU_MULTIPLY, EXTENDED_INCREASE) near each other (#4)
44712
+ * Fund flow (single passavoid VA→Extended then Extended→wallet round-trips):
44713
+ * 1) Treat Vesu borrow headroom that the multiply route will consume as covering
44714
+ * part of the Vesu USDC need (no standalone VESU_BORROW for that slice). Cap
44715
+ * standalone VESU_BORROW→VA→Extended by remaining headroom.
44716
+ * 2) Cover Extended deposit delta: REALISE_PNL first, then withdrawal→avail-trade,
44717
+ * then wallet→Extended, then VA→Extended only up to VA surplus above the USDC
44718
+ * that must remain for Vesu (after step 1), then borrow→VA→Extended.
44719
+ * 3) Cover Vesu VA shortfall: wallet→VA, Extended withdrawal→wallet→VA, REALISE_PNL,
44720
+ * then combined Extended→wallet→VA for the remainder.
44721
+ * 4) RETURN_TO_WAIT when needed; then AVNU + VESU_MULTIPLY + EXTENDED_INCREASE.
44722
+ */
44723
+ /**
44724
+ * @param skipAvnuDepositSwap Omit AVNU before Vesu multiply when LTV cases already ran this cycle
44725
+ * (matrix tests expect deposit routes without that step).
43368
44726
  */
43369
- _classifyDeposits(withdrawAmount) {
44727
+ _classifyDeposits(withdrawAmount, skipAvnuDepositSwap = false) {
43370
44728
  if (withdrawAmount.toNumber() > CASE_THRESHOLD_USD) return [];
43371
44729
  const distributableAmount = this._computeDistributableAmount(
43372
- this._budget.vesuPerPoolDebtDeltasToBorrow,
44730
+ this._budget.vesuDebtDeltas,
43373
44731
  withdrawAmount
43374
44732
  );
43375
44733
  if (distributableAmount.toNumber() <= CASE_THRESHOLD_USD) return [];
43376
44734
  const { vesuAllocationUsd, extendedAllocationUsd } = this._computeAllocationSplit(distributableAmount);
43377
44735
  const vesuDeltas = this._computePerPoolCollateralDeltas(
43378
- vesuAllocationUsd,
43379
- this._budget.vesuPerPoolDebtDeltasToBorrow
44736
+ vesuAllocationUsd
43380
44737
  );
43381
44738
  const extendedPositionDeltas = this._computeExtendedPositionDeltas(vesuDeltas);
43382
- const extendedDepositDelta = this._computeExtendedDepositDelta(extendedAllocationUsd);
43383
44739
  const vesuDepositAmount = this._computeVesuDepositAmount(vesuDeltas);
44740
+ const vesuLeverage = calculateVesuLeverage();
44741
+ const extendedLeverage = calculateExtendedLevergae();
44742
+ const { dExtAvlWithdraw, dExtUpnl, dVaUsd, dWalletUsd, dVesuBorrowCapacity, isExtendedToVesu } = this._rebalanceFunds({
44743
+ extAvlWithdraw: this._budget.extAvailWithdraw,
44744
+ extUpnl: this._budget.extAvailUpnl,
44745
+ vaUsd: this._budget.vaUsd,
44746
+ walletUsd: this._budget.walletUsd,
44747
+ vesuBorrowCapacity: this._budget.vesuBorrowCapacity,
44748
+ vesuLeverage,
44749
+ extendedLeverage
44750
+ });
43384
44751
  const routes = [];
43385
- let needsWait = false;
43386
- if (extendedDepositDelta.toNumber() > CASE_THRESHOLD_USD) {
43387
- let rem = extendedDepositDelta.toNumber();
43388
- if (rem > CASE_THRESHOLD_USD) {
43389
- const { remaining } = this._getWalletToEXTENDEDRoute(rem, routes, false);
43390
- if (remaining < rem) needsWait = true;
43391
- rem = remaining;
43392
- }
43393
- if (rem > CASE_THRESHOLD_USD) {
43394
- const { remaining } = this._getVAToEXTENDEDRoute(rem, routes, false);
43395
- if (remaining < rem) needsWait = true;
43396
- rem = remaining;
43397
- }
43398
- if (rem > CASE_THRESHOLD_USD && this._budget.vesuBorrowCapacity > CASE_THRESHOLD_USD) {
43399
- const { remaining: borrowRem } = this._buildVesuBorrowRoutes(rem, routes);
43400
- const borrowed = rem - borrowRem;
43401
- if (borrowRem != rem) {
43402
- this._getVAToEXTENDEDRoute(borrowed, routes, false);
43403
- needsWait = true;
43404
- rem = borrowRem;
43405
- }
44752
+ if (isExtendedToVesu) {
44753
+ if (dExtUpnl < 0) {
44754
+ this._getUpnlRoute(Math.abs(dExtUpnl), routes);
43406
44755
  }
43407
- }
43408
- if (vesuDepositAmount.toNumber() > CASE_THRESHOLD_USD) {
43409
- const vaShortfall = vesuDepositAmount.toNumber() - this._budget.vaUsd;
43410
- if (vaShortfall > CASE_THRESHOLD_USD) {
43411
- let rem = vaShortfall;
43412
- if (rem > CASE_THRESHOLD_USD && this._budget.walletUsd > CASE_THRESHOLD_USD) {
43413
- const { remaining } = this._getWalletToVARoute(Math.min(this._budget.walletUsd, rem), routes);
43414
- rem = remaining;
43415
- }
43416
- const isWithdrawalEnough = rem <= this._budget.extAvailWithdraw;
43417
- if (!isWithdrawalEnough && rem > CASE_THRESHOLD_USD) {
43418
- const { remaining: upnlRem } = this._getUpnlRoute(rem, routes);
43419
- rem = upnlRem;
43420
- }
43421
- if (rem > CASE_THRESHOLD_USD && this._budget.extAvailWithdraw > CASE_THRESHOLD_USD) {
43422
- const extUse = Math.min(rem, this._budget.extAvailWithdraw);
43423
- this._getExtendedToWalletRoute(extUse, routes);
43424
- this._getWALLETToVARoute(extUse, routes);
43425
- rem -= extUse;
43426
- needsWait = false;
43427
- }
44756
+ if (dExtUpnl < 0 || dExtAvlWithdraw < 0) {
44757
+ const netAmount = (dExtAvlWithdraw < 0 ? Math.abs(dExtAvlWithdraw) : 0) + (dExtUpnl < 0 ? Math.abs(dExtUpnl) : 0);
44758
+ const walletUsd = this._budget.walletUsd;
44759
+ this._getExtendedToWalletRoute(netAmount, routes);
44760
+ this._getWALLETToVARoute(netAmount + walletUsd, routes);
44761
+ }
44762
+ } else {
44763
+ let netDVaUsd = dVaUsd;
44764
+ if (dWalletUsd < 0) {
44765
+ this._getWalletToVARoute(this._budget.walletUsd, routes);
44766
+ netDVaUsd += dWalletUsd;
44767
+ }
44768
+ if (dVesuBorrowCapacity < 0) {
44769
+ this._buildVesuBorrowRoutes(Math.abs(dVesuBorrowCapacity), routes);
44770
+ netDVaUsd += dVesuBorrowCapacity;
44771
+ }
44772
+ if (netDVaUsd < 0) {
44773
+ this._getVAToEXTENDEDRoute(Math.abs(netDVaUsd), routes, true);
43428
44774
  }
43429
- }
43430
- if (needsWait) {
43431
- routes.push({ type: "RETURN_TO_WAIT" /* RETURN_TO_WAIT */, priority: routes.length });
43432
44775
  }
43433
44776
  for (const vesuDelta of vesuDeltas) {
43434
- if (vesuDepositAmount.toNumber() > CASE_THRESHOLD_USD) {
44777
+ if (!skipAvnuDepositSwap && vesuDepositAmount.toNumber() > CASE_THRESHOLD_USD) {
44778
+ routes.push({
44779
+ type: "AVNU_DEPOSIT_SWAP" /* AVNU_DEPOSIT_SWAP */,
44780
+ priority: routes.length,
44781
+ fromToken: vesuDelta.collateralToken.symbol,
44782
+ fromAmount: vesuDepositAmount,
44783
+ toToken: vesuDelta.debtToken.symbol
44784
+ });
43435
44785
  }
43436
44786
  if (vesuDelta.collateralDelta.toNumber() > 0) {
43437
- const swappedAmount = new Web3Number((vesuDepositAmount.toNumber() * vesuDelta.debtPrice / (vesuDelta.collateralPrice ?? 0)).toFixed(6), vesuDelta.collateralToken.decimals);
44787
+ const availableBorrowCapacity = this._budget.vesuBorrowCapacity;
44788
+ const externalDepositAmount = vesuDepositAmount.minus(availableBorrowCapacity);
44789
+ const swappedAmount = new Web3Number((externalDepositAmount.toNumber() * vesuDelta.debtPrice / (vesuDelta.collateralPrice ?? 0)).toFixed(6), vesuDelta.collateralToken.decimals);
43438
44790
  routes.push({
43439
44791
  type: "VESU_MULTIPLY_INCREASE_LEVER" /* VESU_MULTIPLY_INCREASE_LEVER */,
43440
44792
  priority: routes.length,
@@ -43443,7 +44795,7 @@ var ExtendedSVKVesuStateManager = class {
43443
44795
  marginAmount: swappedAmount,
43444
44796
  // should be the swapped amount as per vesu multiply adapter
43445
44797
  swappedCollateralAmount: vesuDelta.collateralDelta.minus(swappedAmount),
43446
- debtAmount: vesuDelta.debtDelta,
44798
+ debtAmount: vesuDelta.debtDelta.plus(availableBorrowCapacity),
43447
44799
  poolId: vesuDelta.poolId
43448
44800
  });
43449
44801
  }
@@ -43566,8 +44918,13 @@ var ExtendedSVKVesuStateManager = class {
43566
44918
  */
43567
44919
  _buildImbalanceExtExcessShortNoFundsRoutes(exposureDiffBtc) {
43568
44920
  const instrument = this._config.extendedAdapter.config.extendedMarketName ?? "BTC-USD";
43569
- const decDelta = new Web3Number(exposureDiffBtc.toFixed(COLLATERAL_PRECISION), USDC_TOKEN_DECIMALS);
43570
- this._budget.applyExtendedExposureDelta(instrument, new Web3Number(decDelta.negated().toFixed(COLLATERAL_PRECISION), USDC_TOKEN_DECIMALS));
44921
+ const decDelta = new Web3Number(Web3Number.fromNumber(exposureDiffBtc, 8).toFixedRoundDown(COLLATERAL_PRECISION), USDC_TOKEN_DECIMALS);
44922
+ const collPx = this._budget.vesuPools[0]?.collateralPrice ?? 1;
44923
+ this._budget.applyExtendedExposureDelta(
44924
+ instrument,
44925
+ new Web3Number(Web3Number.fromNumber(decDelta.negated().toNumber(), 8).toFixedRoundDown(COLLATERAL_PRECISION), USDC_TOKEN_DECIMALS),
44926
+ collPx
44927
+ );
43571
44928
  return [{
43572
44929
  type: "EXTENDED_DECREASE_LEVER" /* EXTENDED_DECREASE_LEVER */,
43573
44930
  amount: decDelta,
@@ -43633,13 +44990,15 @@ var ExtendedSVKVesuStateManager = class {
43633
44990
  _classifyCases(withdrawAmount) {
43634
44991
  this._budget.initBudget();
43635
44992
  const withdrawalCases = this._classifyWithdrawal(withdrawAmount);
43636
- const ltvVesuCases = this._classifyLtvVesu();
43637
- const ltvExtendedCases = this._classifyLtvExtended();
43638
- const depositCases = this._classifyDeposits(withdrawAmount);
44993
+ this._budget.applyBuffer(this._config.limitBalanceBufferFactor);
44994
+ const ltvCases = this._classifyLTV();
44995
+ const depositCases = this._classifyDeposits(
44996
+ withdrawAmount,
44997
+ ltvCases.length > 0
44998
+ );
43639
44999
  return [
43640
45000
  ...withdrawalCases,
43641
- ...ltvVesuCases,
43642
- ...ltvExtendedCases,
45001
+ ...ltvCases,
43643
45002
  ...depositCases
43644
45003
  ];
43645
45004
  }
@@ -43647,7 +45006,7 @@ var ExtendedSVKVesuStateManager = class {
43647
45006
  // Private — aggregation helpers
43648
45007
  // ═══════════════════════════════════════════════════════════════════════════
43649
45008
  _totalVesuCollateral() {
43650
- return this._budget.vesuPoolStates.reduce(
45009
+ return this._budget.vesuPools.reduce(
43651
45010
  (acc, pool) => acc.plus(
43652
45011
  pool.collateralAmount
43653
45012
  ),
@@ -43655,7 +45014,7 @@ var ExtendedSVKVesuStateManager = class {
43655
45014
  );
43656
45015
  }
43657
45016
  _totalVesuCollateralUsd() {
43658
- return this._budget.vesuPoolStates.reduce(
45017
+ return this._budget.vesuPools.reduce(
43659
45018
  (acc, pool) => acc.plus(
43660
45019
  pool.collateralAmount.multipliedBy(pool.collateralPrice)
43661
45020
  ),
@@ -43663,13 +45022,13 @@ var ExtendedSVKVesuStateManager = class {
43663
45022
  );
43664
45023
  }
43665
45024
  _totalExtendedExposure() {
43666
- return this._budget.extendedPositions.reduce(
45025
+ return this._budget.extendedPositionsView.reduce(
43667
45026
  (acc, position) => acc.plus(position.size),
43668
45027
  new Web3Number(0, USDC_TOKEN_DECIMALS)
43669
45028
  );
43670
45029
  }
43671
45030
  _totalExtendedExposureUsd() {
43672
- return this._budget.extendedPositions.reduce(
45031
+ return this._budget.extendedPositionsView.reduce(
43673
45032
  (acc, position) => acc.plus(position.valueUsd),
43674
45033
  new Web3Number(0, USDC_TOKEN_DECIMALS)
43675
45034
  );
@@ -43711,7 +45070,7 @@ var ExtendedSVKVesuStateManager = class {
43711
45070
  };
43712
45071
 
43713
45072
  // src/strategies/vesu-extended-strategy/services/executionService.ts
43714
- import { uint256 as uint25622 } from "starknet";
45073
+ import { uint256 as uint25623 } from "starknet";
43715
45074
 
43716
45075
  // src/strategies/vesu-extended-strategy/types/transaction-metadata.ts
43717
45076
  var CycleType = /* @__PURE__ */ ((CycleType2) => {
@@ -43756,11 +45115,10 @@ var _ExecutionService = class _ExecutionService {
43756
45115
  this._tokenSymbols = StarknetCallParser.buildTokenSymbolLookup([
43757
45116
  config.wbtcToken,
43758
45117
  config.usdcToken,
43759
- config.usdceToken,
43760
- config.vesuAdapter.config.baseToken,
43761
- config.vesuAdapter.config.collateral,
43762
- config.vesuAdapter.config.debt,
43763
- config.vesuAdapter.config.marginToken,
45118
+ config.vesuMultiplyAdapter.config.baseToken,
45119
+ config.vesuMultiplyAdapter.config.collateral,
45120
+ config.vesuMultiplyAdapter.config.debt,
45121
+ config.vesuMultiplyAdapter.config.marginToken,
43764
45122
  config.vesuModifyPositionAdapter.config.collateral,
43765
45123
  config.vesuModifyPositionAdapter.config.debt,
43766
45124
  ...avnuTokens
@@ -43768,19 +45126,18 @@ var _ExecutionService = class _ExecutionService {
43768
45126
  this._tokenDecimals = StarknetCallParser.buildTokenDecimalsLookup([
43769
45127
  config.wbtcToken,
43770
45128
  config.usdcToken,
43771
- config.usdceToken,
43772
- config.vesuAdapter.config.baseToken,
43773
- config.vesuAdapter.config.collateral,
43774
- config.vesuAdapter.config.debt,
43775
- config.vesuAdapter.config.marginToken,
45129
+ config.vesuMultiplyAdapter.config.baseToken,
45130
+ config.vesuMultiplyAdapter.config.collateral,
45131
+ config.vesuMultiplyAdapter.config.debt,
45132
+ config.vesuMultiplyAdapter.config.marginToken,
43776
45133
  config.vesuModifyPositionAdapter.config.collateral,
43777
45134
  config.vesuModifyPositionAdapter.config.debt,
43778
45135
  ...avnuTokens
43779
45136
  ]);
43780
45137
  this._poolNames = StarknetCallParser.buildPoolNameLookup([
43781
45138
  {
43782
- poolId: config.vesuAdapter.config.poolId.toBigInt(),
43783
- name: `${config.vesuAdapter.config.collateral.symbol}/${config.vesuAdapter.config.debt.symbol}`
45139
+ poolId: config.vesuMultiplyAdapter.config.poolId.toBigInt(),
45140
+ name: `${config.vesuMultiplyAdapter.config.collateral.symbol}/${config.vesuMultiplyAdapter.config.debt.symbol}`
43784
45141
  },
43785
45142
  {
43786
45143
  poolId: config.vesuModifyPositionAdapter.config.poolId.toBigInt(),
@@ -44183,12 +45540,14 @@ var _ExecutionService = class _ExecutionService {
44183
45540
  *
44184
45541
  * For deposit (USDC→BTC): price = sum(USDC sold) / sum(BTC bought)
44185
45542
  * For withdraw (BTC→USDC): price = sum(USDC bought) / sum(BTC sold)
44186
- */
45543
+ * @returns no-swap means, logic is fine and explicit no swap is needed
45544
+ */
44187
45545
  _getNetExecutionPrice(isDeposit) {
44188
45546
  const prices = [
44189
45547
  this._config.avnuAdapter.lastSwapPriceInfo,
44190
- this._config.vesuAdapter.lastSwapPriceInfo
45548
+ this._config.vesuMultiplyAdapter.lastSwapPriceInfo
44191
45549
  ].filter((p) => p !== null);
45550
+ assert(prices.length <= 1, "Only one swap price info is allowed");
44192
45551
  if (prices.length === 0) return null;
44193
45552
  if (isDeposit) {
44194
45553
  const totalUsdc = prices.reduce((s, p) => s + p.fromAmount, 0);
@@ -44203,7 +45562,7 @@ var _ExecutionService = class _ExecutionService {
44203
45562
  /** Clears cached swap price info on all adapters to prevent stale data across cycles. */
44204
45563
  _clearAdapterPriceInfo() {
44205
45564
  this._config.avnuAdapter.lastSwapPriceInfo = null;
44206
- this._config.vesuAdapter.lastSwapPriceInfo = null;
45565
+ this._config.vesuMultiplyAdapter.lastSwapPriceInfo = null;
44207
45566
  }
44208
45567
  // ═══════════════════════════════════════════════════════════════════════════
44209
45568
  // Public API
@@ -44631,7 +45990,7 @@ var _ExecutionService = class _ExecutionService {
44631
45990
  }
44632
45991
  // ── Transfer routes ─────────────────────────────────────────────────────
44633
45992
  /**
44634
- * WALLET_TO_EXTENDED: Deposit USDC.e from operator wallet directly to Extended.
45993
+ * WALLET_TO_EXTENDED: Deposit USDC from operator wallet directly to Extended.
44635
45994
  *
44636
45995
  * Builds raw approve + deposit calls (NOT through the manager/merkle system)
44637
45996
  * because the wallet interacts with Extended directly, not via vault allocator.
@@ -44646,13 +46005,13 @@ var _ExecutionService = class _ExecutionService {
44646
46005
  );
44647
46006
  return [];
44648
46007
  }
44649
- const { usdceToken, extendedAdapter } = this._config;
46008
+ const { usdcToken, extendedAdapter } = this._config;
44650
46009
  const extendedContract = extendedAdapter.config.extendedContract;
44651
46010
  const vaultId = extendedAdapter.config.vaultIdExtended;
44652
- const salt = Math.floor(Math.random() * 10 ** usdceToken.decimals);
44653
- const uint256Amount = uint25622.bnToUint256(amount.toWei());
46011
+ const salt = Math.floor(Math.random() * 10 ** usdcToken.decimals);
46012
+ const uint256Amount = uint25623.bnToUint256(amount.toWei());
44654
46013
  const approveCall = {
44655
- contractAddress: usdceToken.address.address,
46014
+ contractAddress: usdcToken.address.address,
44656
46015
  entrypoint: "approve",
44657
46016
  calldata: [
44658
46017
  extendedContract.address,
@@ -44675,7 +46034,7 @@ var _ExecutionService = class _ExecutionService {
44675
46034
  return [approveCall, depositCall];
44676
46035
  }
44677
46036
  /**
44678
- * VA_TO_EXTENDED: Deposit USDC.e from vault allocator to Extended.
46037
+ * VA_TO_EXTENDED: Deposit USDC from vault allocator to Extended.
44679
46038
  *
44680
46039
  * Uses the extended adapter's getDepositCall to build ManageCalls,
44681
46040
  * then wraps them in a merkle-verified manage call through the manager contract.
@@ -44690,43 +46049,31 @@ var _ExecutionService = class _ExecutionService {
44690
46049
  );
44691
46050
  return [];
44692
46051
  }
44693
- const swapCall = await this._buildAdapterManageCall(
44694
- this._config.usdcToUsdceAdapter,
44695
- true,
44696
- { amount }
44697
- );
44698
46052
  const manageCall = await this._buildAdapterManageCall(
44699
46053
  this._config.extendedAdapter,
44700
46054
  true,
44701
46055
  { amount }
44702
46056
  );
44703
- return [swapCall, manageCall];
46057
+ return [manageCall];
44704
46058
  }
44705
46059
  /**
44706
- * WALLET_TO_VA: Transfer USDC.e from operator wallet to vault allocator.
46060
+ * WALLET_TO_VA: Transfer USDC from operator wallet to vault allocator.
44707
46061
  * Caps amount by actual wallet balance.
44708
46062
  */
44709
46063
  async _buildWalletToVACalls(route) {
44710
- const erc20 = new ERC20(this._config.networkConfig);
44711
- const { usdceToken, vaultAllocator, walletAddress } = this._config;
44712
46064
  const transferAmount = route.amount;
44713
46065
  if (transferAmount.lessThanOrEqualTo(0)) {
44714
46066
  logger.warn(
44715
- `${this._tag}::_buildWalletToVACalls no USDC.e in wallet to transfer`
46067
+ `${this._tag}::_buildWalletToVACalls no USDC in wallet to transfer`
44716
46068
  );
44717
46069
  return [];
44718
46070
  }
44719
46071
  const transferCall = await this._buildAdapterManageCall(
44720
- this._config.usdceTransferAdapter,
46072
+ this._config.usdcTransferAdapter,
44721
46073
  false,
44722
46074
  { amount: transferAmount }
44723
46075
  );
44724
- const swapCall = await this._buildAdapterManageCall(
44725
- this._config.usdcToUsdceAdapter,
44726
- false,
44727
- { amount: transferAmount }
44728
- );
44729
- return [transferCall, swapCall];
46076
+ return [transferCall];
44730
46077
  }
44731
46078
  // ── AVNU swap routes ────────────────────────────────────────────────────
44732
46079
  /**
@@ -44759,12 +46106,12 @@ var _ExecutionService = class _ExecutionService {
44759
46106
  */
44760
46107
  async _buildVesuIncreaseLeverCalls(route) {
44761
46108
  const depositManageCall = await this._buildAdapterManageCall(
44762
- this._config.vesuAdapter,
46109
+ this._config.vesuMultiplyAdapter,
44763
46110
  true,
44764
46111
  {
44765
46112
  amount: route.marginAmount,
44766
46113
  marginSwap: {
44767
- marginToken: this._config.vesuAdapter.config.marginToken
46114
+ marginToken: this._config.vesuMultiplyAdapter.config.marginToken
44768
46115
  // todo, must be vault token
44769
46116
  },
44770
46117
  leverSwap: {
@@ -44784,11 +46131,11 @@ var _ExecutionService = class _ExecutionService {
44784
46131
  async _buildVesuDecreaseLeverCalls(route) {
44785
46132
  const collateralAmount = route.marginAmount.abs();
44786
46133
  const withdrawManageCall = await this._buildAdapterManageCall(
44787
- this._config.vesuAdapter,
46134
+ this._config.vesuMultiplyAdapter,
44788
46135
  false,
44789
46136
  {
44790
46137
  amount: collateralAmount,
44791
- withdrawSwap: { outputToken: this._config.vesuAdapter.config.marginToken }
46138
+ withdrawSwap: { outputToken: this._config.vesuMultiplyAdapter.config.marginToken }
44792
46139
  }
44793
46140
  );
44794
46141
  return [withdrawManageCall];
@@ -45379,140 +46726,6 @@ _ExecutionService.EXTENDED_EXPOSURE_ROUTES = /* @__PURE__ */ new Set([
45379
46726
  ]);
45380
46727
  var ExecutionService = _ExecutionService;
45381
46728
 
45382
- // src/strategies/universal-adapters/usdc<>usdce-adapter.ts
45383
- import { hash as hash11, uint256 as uint25623 } from "starknet";
45384
- var UsdcToUsdceAdapter = class _UsdcToUsdceAdapter extends BaseAdapter {
45385
- _approveProofReadableId(usdcToUsdce) {
45386
- const method = usdcToUsdce ? "swap_to_legacy" : "swap_to_new";
45387
- return `approve_${method}`;
45388
- }
45389
- _swapProofReadableId(usdcToUsdce) {
45390
- const method = usdcToUsdce ? "swap_to_legacy" : "swap_to_new";
45391
- const target = usdcToUsdce ? this.config.supportedPositions[0].asset : this.config.supportedPositions[1].asset;
45392
- return `${method}_${target.symbol}`;
45393
- }
45394
- buildSwapLeafConfigs(usdcToUsdce) {
45395
- const method = usdcToUsdce ? "swap_to_legacy" : "swap_to_new";
45396
- const target = usdcToUsdce ? this.config.supportedPositions[0].asset : this.config.supportedPositions[1].asset;
45397
- return [
45398
- {
45399
- target: target.address,
45400
- method: "approve",
45401
- packedArguments: [AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt()],
45402
- id: this._approveProofReadableId(usdcToUsdce),
45403
- sanitizer: AVNU_LEGACY_SANITIZER
45404
- },
45405
- {
45406
- target: AVNU_EXCHANGE_FOR_LEGACY_USDC,
45407
- method,
45408
- packedArguments: [],
45409
- id: this._swapProofReadableId(usdcToUsdce),
45410
- sanitizer: AVNU_LEGACY_SANITIZER
45411
- }
45412
- ];
45413
- }
45414
- async buildSwapCalls(params, usdcToUsdce) {
45415
- const approveAmount = uint25623.bnToUint256(params.amount.toWei());
45416
- const target = usdcToUsdce ? this.config.supportedPositions[0].asset : this.config.supportedPositions[1].asset;
45417
- const method = usdcToUsdce ? "swap_to_legacy" : "swap_to_new";
45418
- return [
45419
- {
45420
- proofReadableId: this._approveProofReadableId(usdcToUsdce),
45421
- sanitizer: AVNU_LEGACY_SANITIZER,
45422
- call: {
45423
- contractAddress: target.address,
45424
- selector: hash11.getSelectorFromName("approve"),
45425
- calldata: [
45426
- AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt(),
45427
- toBigInt(approveAmount.low.toString()),
45428
- toBigInt(approveAmount.high.toString())
45429
- ]
45430
- }
45431
- },
45432
- {
45433
- proofReadableId: this._swapProofReadableId(usdcToUsdce),
45434
- sanitizer: AVNU_LEGACY_SANITIZER,
45435
- call: {
45436
- contractAddress: AVNU_EXCHANGE_FOR_LEGACY_USDC,
45437
- selector: hash11.getSelectorFromName(method),
45438
- calldata: [
45439
- toBigInt(approveAmount.low.toString()),
45440
- // amount low
45441
- toBigInt(approveAmount.high.toString())
45442
- // amount high
45443
- ]
45444
- }
45445
- }
45446
- ];
45447
- }
45448
- constructor(config) {
45449
- super(config, _UsdcToUsdceAdapter.name, Protocols.AVNU);
45450
- this.config = config;
45451
- assert(this.config.supportedPositions.length === 2, "UsdcToUsdceAdapter must have 2 supported positions");
45452
- assert(this.config.supportedPositions[0].asset.symbol === "USDC", "UsdcToUsdceAdapter must have USDC as the first supported position");
45453
- assert(this.config.supportedPositions[1].asset.symbol === "USDC.e", "UsdcToUsdceAdapter must have USDCE as the second supported position");
45454
- }
45455
- //abstract means the method has no implementation in this class; instead, child classes must implement it.
45456
- async getAPY(supportedPosition) {
45457
- return Promise.resolve({ apy: 0, type: "base" /* BASE */ });
45458
- }
45459
- async getPosition(supportedPosition) {
45460
- const toToken = this.config.supportedPositions[1].asset;
45461
- if (supportedPosition.asset.symbol != toToken.symbol) {
45462
- return null;
45463
- }
45464
- try {
45465
- const balance = await new ERC20(this.config.networkConfig).balanceOf(
45466
- toToken.address,
45467
- this.config.vaultAllocator.address,
45468
- toToken.decimals
45469
- );
45470
- return { amount: balance, remarks: `USDC.e unused balance (VA)` };
45471
- } catch (_e) {
45472
- logger.error(`${_UsdcToUsdceAdapter.name}::getPosition: failed for ${toToken.symbol}`);
45473
- throw new Error(`${_UsdcToUsdceAdapter.name}: failed to get balance for ${toToken.symbol}`);
45474
- }
45475
- }
45476
- async maxDeposit(amount) {
45477
- return Promise.resolve({
45478
- tokenInfo: this.config.baseToken,
45479
- amount: new Web3Number(0, 0),
45480
- usdValue: 0,
45481
- apy: { apy: 0, type: "base" /* BASE */ },
45482
- protocol: Protocols.AVNU,
45483
- remarks: ""
45484
- });
45485
- }
45486
- async maxWithdraw() {
45487
- return Promise.resolve({
45488
- tokenInfo: this.config.baseToken,
45489
- amount: new Web3Number(0, 0),
45490
- usdValue: 0,
45491
- apy: { apy: 0, type: "base" /* BASE */ },
45492
- protocol: Protocols.AVNU,
45493
- remarks: ""
45494
- });
45495
- }
45496
- _getDepositLeaf() {
45497
- return this.buildSwapLeafConfigs(true);
45498
- }
45499
- _getWithdrawLeaf() {
45500
- return this.buildSwapLeafConfigs(false);
45501
- }
45502
- async getDepositCall(params) {
45503
- const calls = await this.buildSwapCalls(params, true);
45504
- return calls;
45505
- }
45506
- //Swap wbtc to usdc
45507
- async getWithdrawCall(params) {
45508
- const calls = await this.buildSwapCalls(params, false);
45509
- return calls;
45510
- }
45511
- async getHealthFactor() {
45512
- return Promise.resolve(1);
45513
- }
45514
- };
45515
-
45516
46729
  // src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx
45517
46730
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
45518
46731
  var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy extends SVKStrategy {
@@ -45528,9 +46741,6 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
45528
46741
  });
45529
46742
  this.wbtcToken = Global.getDefaultTokens().find((token) => token.symbol === "WBTC");
45530
46743
  this.usdcToken = this.metadata.additionalInfo.borrowable_assets[0];
45531
- this.usdceToken = Global.getDefaultTokens().find(
45532
- (token) => token.symbol === "USDC.e"
45533
- );
45534
46744
  this.stateManager = this._initializeStateManager();
45535
46745
  }
45536
46746
  /**
@@ -45554,9 +46764,8 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
45554
46764
  extendedAdapter: extendedAdapterEntry.adapter,
45555
46765
  vaultAllocator: this.metadata.additionalInfo.vaultAllocator,
45556
46766
  walletAddress: this.metadata.additionalInfo.walletAddress,
45557
- assetToken: Global.getDefaultTokens().find((token) => token.symbol === "USDC"),
45558
- // ! TODO change to asset() latest
45559
- usdceToken: this.usdceToken,
46767
+ assetToken: this.asset(),
46768
+ usdcToken: this.usdcToken,
45560
46769
  collateralToken: this.wbtcToken,
45561
46770
  limitBalanceBufferFactor: LIMIT_BALANCE
45562
46771
  };
@@ -45590,7 +46799,7 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
45590
46799
  }
45591
46800
  return { collateralPrice, debtPrice };
45592
46801
  }
45593
- async getVesuAdapter() {
46802
+ async getVesuMultiplyAdapter() {
45594
46803
  const vesuAdapter = this.metadata.additionalInfo.adapters.find(
45595
46804
  (adapter) => adapter.adapter.name === VesuMultiplyAdapter.name
45596
46805
  );
@@ -45612,16 +46821,16 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
45612
46821
  }
45613
46822
  return vesuModifyPositionAdapter.adapter;
45614
46823
  }
45615
- async getUsdceTransferAdapter() {
45616
- const usdceTransferAdapter = this.metadata.additionalInfo.adapters.find(
46824
+ async getUsdcTransferAdapter() {
46825
+ const usdcTransferAdapter = this.metadata.additionalInfo.adapters.find(
45617
46826
  (adapter) => adapter.adapter.name === TokenTransferAdapter.name
45618
46827
  );
45619
- if (!usdceTransferAdapter) {
46828
+ if (!usdcTransferAdapter) {
45620
46829
  throw new Error(
45621
- `${this.getTag()} Usdce transfer adapter not configured in metadata.`
46830
+ `${this.getTag()} Usdc transfer adapter not configured in metadata.`
45622
46831
  );
45623
46832
  }
45624
- return usdceTransferAdapter.adapter;
46833
+ return usdcTransferAdapter.adapter;
45625
46834
  }
45626
46835
  async getAvnuAdapter() {
45627
46836
  const avnuAdapter = this.metadata.additionalInfo.adapters.find(
@@ -45650,17 +46859,6 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
45650
46859
  }
45651
46860
  return extendedAdapter.adapter;
45652
46861
  }
45653
- async getUsdcToUsdceAdapter() {
45654
- const usdcToUsdceAdapter = this.metadata.additionalInfo.adapters.find(
45655
- (adapter) => adapter.adapter.name === UsdcToUsdceAdapter.name
45656
- );
45657
- if (!usdcToUsdceAdapter) {
45658
- throw new Error(
45659
- `${this.getTag()} UsdcToUsdce adapter not configured in metadata.`
45660
- );
45661
- }
45662
- return usdcToUsdceAdapter.adapter;
45663
- }
45664
46862
  /**
45665
46863
  * Creates an ExecutionService wired to this strategy's adapters and config.
45666
46864
  * Use with `stateManager.solve()` to get a SolveResult, then pass it to
@@ -45672,34 +46870,30 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
45672
46870
  */
45673
46871
  async createExecutionService(opts) {
45674
46872
  const [
45675
- vesuAdapter,
46873
+ vesuMultiplyAdapter,
45676
46874
  vesuModifyPositionAdapter,
45677
- usdceTransferAdapter,
45678
46875
  extendedAdapter,
45679
46876
  avnuAdapter,
45680
- usdcToUsdceAdapter
46877
+ usdcTransferAdapter
45681
46878
  ] = await Promise.all([
45682
- this.getVesuAdapter(),
46879
+ this.getVesuMultiplyAdapter(),
45683
46880
  this.getVesuModifyPositionAdapter(),
45684
- this.getUsdceTransferAdapter(),
45685
46881
  this.getExtendedAdapter(),
45686
46882
  this.getAvnuAdapter(),
45687
- this.getUsdcToUsdceAdapter()
46883
+ this.getUsdcTransferAdapter()
45688
46884
  ]);
45689
46885
  const executionConfig = {
45690
46886
  networkConfig: this.config,
45691
46887
  pricer: this.pricer,
45692
- vesuAdapter,
46888
+ vesuMultiplyAdapter,
45693
46889
  vesuModifyPositionAdapter,
45694
46890
  extendedAdapter,
45695
46891
  avnuAdapter,
45696
- usdcToUsdceAdapter,
46892
+ usdcTransferAdapter,
45697
46893
  vaultAllocator: this.metadata.additionalInfo.vaultAllocator,
45698
46894
  walletAddress: this.metadata.additionalInfo.walletAddress,
45699
- usdceTransferAdapter,
45700
46895
  wbtcToken: this.wbtcToken,
45701
46896
  usdcToken: this.usdcToken,
45702
- usdceToken: this.usdceToken,
45703
46897
  getMerkleTree: () => this.getMerkleTree(),
45704
46898
  getManageCall: (proofs, manageCalls) => this.getManageCall(proofs, manageCalls),
45705
46899
  getBringLiquidityCall: (params) => this.getBringLiquidityCall(params),
@@ -45720,9 +46914,7 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
45720
46914
  for (let adapter of this.metadata.additionalInfo.adapters) {
45721
46915
  let positions = await adapter.adapter.getPositions();
45722
46916
  if (positions && positions.length > 0) {
45723
- const filteredPositions = positions.filter((position) => {
45724
- return position.tokenInfo.address !== this.usdceToken.address;
45725
- });
46917
+ const filteredPositions = positions;
45726
46918
  allPositions.push(...filteredPositions);
45727
46919
  }
45728
46920
  }
@@ -45917,34 +47109,22 @@ var VesuExtendedMultiplierStrategy = class _VesuExtendedMultiplierStrategy exten
45917
47109
  };
45918
47110
  }
45919
47111
  /**
45920
- * Fetches the operator wallet's current holdings for USDC.e, USDC, and WBTC,
47112
+ * Fetches the operator wallet's current holdings for USDC and WBTC,
45921
47113
  * returning each token's balance and USD value.
45922
47114
  */
45923
47115
  async getWalletHoldings() {
45924
- if (!this.usdceToken || !this.wbtcToken || !this.usdcToken) {
47116
+ if (!this.wbtcToken || !this.usdcToken) {
45925
47117
  return [];
45926
47118
  }
45927
47119
  const walletAddress = this.metadata.additionalInfo.walletAddress;
45928
- const usdceWalletBalance = await new ERC20(this.config).balanceOf(
45929
- this.usdceToken.address,
45930
- walletAddress,
45931
- this.usdceToken.decimals
45932
- );
45933
47120
  const usdcWalletBalance = await new ERC20(this.config).balanceOf(
45934
47121
  this.usdcToken.address,
45935
47122
  walletAddress,
45936
47123
  this.usdcToken.decimals
45937
47124
  );
45938
- const price = await this.pricer.getPrice(this.usdceToken.symbol);
45939
- const wbtcPrice = await this.pricer.getPrice(this.wbtcToken.symbol);
45940
- const usdceUsdValue = Number(usdceWalletBalance.toFixed(this.usdceToken.decimals)) * price.price;
47125
+ const price = await this.pricer.getPrice(this.usdcToken.symbol);
45941
47126
  const usdcUsdValue = Number(usdcWalletBalance.toFixed(this.usdcToken.decimals)) * price.price;
45942
47127
  return [
45943
- {
45944
- tokenInfo: this.usdceToken,
45945
- amount: usdceWalletBalance,
45946
- usdValue: usdceUsdValue
45947
- },
45948
47128
  {
45949
47129
  tokenInfo: this.usdcToken,
45950
47130
  amount: usdcWalletBalance,
@@ -45961,9 +47141,6 @@ function getLooperSettings3(lstSymbol, underlyingSymbol, vaultSettings, pool1, e
45961
47141
  const usdcToken = Global.getDefaultTokens().find(
45962
47142
  (token) => token.symbol === underlyingSymbol
45963
47143
  );
45964
- const usdceToken = Global.getDefaultTokens().find(
45965
- (token) => token.symbol === "USDC.e"
45966
- );
45967
47144
  const baseAdapterConfig = {
45968
47145
  baseToken: wbtcToken,
45969
47146
  supportedPositions: [
@@ -45984,17 +47161,10 @@ function getLooperSettings3(lstSymbol, underlyingSymbol, vaultSettings, pool1, e
45984
47161
  minimumExtendedPriceDifferenceForSwapOpen,
45985
47162
  maximumExtendedPriceDifferenceForSwapClosing
45986
47163
  });
45987
- const usdcToUsdceAdapter = new UsdcToUsdceAdapter({
45988
- ...baseAdapterConfig,
45989
- supportedPositions: [
45990
- { asset: usdcToken, isDebt: true },
45991
- { asset: usdceToken, isDebt: false }
45992
- ]
45993
- });
45994
47164
  const extendedAdapter = new ExtendedAdapter({
45995
47165
  ...baseAdapterConfig,
45996
47166
  supportedPositions: [
45997
- { asset: usdceToken, isDebt: false }
47167
+ { asset: usdcToken, isDebt: false }
45998
47168
  ],
45999
47169
  vaultIdExtended,
46000
47170
  extendedContract: EXTENDED_CONTRACT,
@@ -46040,10 +47210,10 @@ function getLooperSettings3(lstSymbol, underlyingSymbol, vaultSettings, pool1, e
46040
47210
  { asset: usdcToken, isDebt: true }
46041
47211
  ]
46042
47212
  });
46043
- const usdceTransferAdapter = new TokenTransferAdapter({
47213
+ const usdcTransferAdapter = new TokenTransferAdapter({
46044
47214
  ...baseAdapterConfig,
46045
- baseToken: usdceToken,
46046
- supportedPositions: [{ asset: usdceToken, isDebt: false }],
47215
+ baseToken: usdcToken,
47216
+ supportedPositions: [{ asset: usdcToken, isDebt: false }],
46047
47217
  fromAddress: vaultSettings.vaultAllocator,
46048
47218
  toAddress: ContractAddr.from(vaultSettings.walletAddress)
46049
47219
  });
@@ -46056,12 +47226,8 @@ function getLooperSettings3(lstSymbol, underlyingSymbol, vaultSettings, pool1, e
46056
47226
  adapter: vesuModifyPositionAdapter
46057
47227
  });
46058
47228
  vaultSettings.adapters.push({
46059
- id: `${usdceTransferAdapter.name}_${usdceToken.symbol}`,
46060
- adapter: usdceTransferAdapter
46061
- });
46062
- vaultSettings.adapters.push({
46063
- id: `${usdcToUsdceAdapter.name}_${usdceToken.symbol}_${usdcToken.symbol}`,
46064
- adapter: usdcToUsdceAdapter
47229
+ id: `${usdcTransferAdapter.name}_${usdcToken.symbol}`,
47230
+ adapter: usdcTransferAdapter
46065
47231
  });
46066
47232
  vaultSettings.adapters.push({
46067
47233
  id: `${extendedAdapter.name}_${wbtcToken.symbol}`,
@@ -46083,12 +47249,10 @@ function getLooperSettings3(lstSymbol, underlyingSymbol, vaultSettings, pool1, e
46083
47249
  vaultSettings.leafAdapters.push(() => vesuModifyPositionAdapter.getDepositLeaf());
46084
47250
  vaultSettings.leafAdapters.push(() => vesuModifyPositionAdapter.getWithdrawLeaf());
46085
47251
  vaultSettings.leafAdapters.push(() => extendedAdapter.getDepositLeaf());
46086
- vaultSettings.leafAdapters.push(() => usdcToUsdceAdapter.getDepositLeaf());
46087
- vaultSettings.leafAdapters.push(() => usdcToUsdceAdapter.getWithdrawLeaf());
46088
47252
  vaultSettings.leafAdapters.push(() => avnuAdapter.getDepositLeaf());
46089
47253
  vaultSettings.leafAdapters.push(() => avnuAdapter.getWithdrawLeaf());
46090
- vaultSettings.leafAdapters.push(() => usdceTransferAdapter.getDepositLeaf());
46091
- vaultSettings.leafAdapters.push(() => usdceTransferAdapter.getWithdrawLeaf());
47254
+ vaultSettings.leafAdapters.push(() => usdcTransferAdapter.getDepositLeaf());
47255
+ vaultSettings.leafAdapters.push(() => usdcTransferAdapter.getWithdrawLeaf());
46092
47256
  vaultSettings.leafAdapters.push(
46093
47257
  commonAdapter.getApproveAdapter(
46094
47258
  usdcToken.address,
@@ -46533,11 +47697,13 @@ export {
46533
47697
  BaseAdapter,
46534
47698
  BaseStrategy,
46535
47699
  CASE_ROUTE_TYPES,
47700
+ COLLATERAL_PRECISION,
46536
47701
  CaseCategory,
46537
47702
  CaseId,
46538
47703
  CommonAdapter,
46539
47704
  ContractAddr,
46540
47705
  CycleType,
47706
+ DEFAULT_TROVES_STRATEGIES_API,
46541
47707
  ERC20,
46542
47708
  EXTENDED_CONTRACT,
46543
47709
  EXTENDED_SANITIZER,
@@ -46555,6 +47721,7 @@ export {
46555
47721
  FatalError,
46556
47722
  FlowChartColors,
46557
47723
  Global,
47724
+ HealthFactorMath,
46558
47725
  HyperLSTStrategies,
46559
47726
  ILending,
46560
47727
  Initializable,
@@ -46592,6 +47759,7 @@ export {
46592
47759
  StrategyLiveStatus,
46593
47760
  StrategyTag,
46594
47761
  StrategyType,
47762
+ SvkTrovesAdapter,
46595
47763
  TRANSFER_SANITIZER,
46596
47764
  TimeInForce,
46597
47765
  TokenMarketData,
@@ -46639,6 +47807,7 @@ export {
46639
47807
  createEkuboCLStrategy,
46640
47808
  createHyperLSTStrategy,
46641
47809
  createSenseiStrategy,
47810
+ createSolveBudgetFromRawState,
46642
47811
  createStrategy,
46643
47812
  createUniversalStrategy2 as createUniversalStrategy,
46644
47813
  createVesuRebalanceStrategy2 as createVesuRebalanceStrategy,