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

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.
@@ -29,11 +29,18 @@
29
29
  // to track remaining balances across sequential draws.
30
30
  // ============================================================
31
31
 
32
+ import { logger } from "@/utils";
33
+
32
34
  // ---- Types ----
33
35
 
34
- interface RebalanceConfig {
36
+ export interface RebalanceConfig {
35
37
  positionPrecision: number; // decimal places for BTC rounding, e.g. 4
36
38
  hfBuffer: number; // HF tolerance before acting, e.g. 0.05
39
+ /**
40
+ * USD below this cannot be executed as a standalone transfer/borrow/repay step
41
+ * (matches execution-layer dust threshold). Default 0 keeps pure math tests unchanged.
42
+ */
43
+ minRoutableUsd?: number;
37
44
  }
38
45
 
39
46
  export interface ExtendedState {
@@ -227,19 +234,32 @@ interface DrawResult {
227
234
  unmet: number; // remaining USD still needed
228
235
  }
229
236
 
237
+ /**
238
+ * Portion of `need` we can take from `avail` such that execution can actually move it.
239
+ * Chunks at or below `minRoutable` are skipped (caller may satisfy `need` from another source
240
+ * or leave it unmet); matches VA/wallet/borrow dust rules in the state manager.
241
+ */
242
+ function routableDrawAmount(avail: number, need: number, minRoutable: number): number {
243
+ if (avail <= 0 || need <= 0) return 0;
244
+ const raw = Math.min(avail, need);
245
+ if (raw <= 0) return 0;
246
+ if (minRoutable <= 0) return raw;
247
+ return raw > minRoutable ? raw : 0;
248
+ }
249
+
230
250
  /**
231
251
  * Draw `need` USD from funding sources in strict priority order.
232
252
  * Mutates `pool` balances so subsequent draws see reduced availability.
233
- * Each source contributes min(available, remaining_need).
253
+ * Each source contributes a routable amount (see {@link routableDrawAmount}).
234
254
  */
235
- function drawFunds(need: number, keys: FundKey[], pool: FundingPool): DrawResult {
255
+ function drawFunds(need: number, keys: FundKey[], pool: FundingPool, minRoutable: number): DrawResult {
236
256
  const draws: Partial<Record<FundKey, number>> = {};
237
257
  let unmet = need;
238
258
  for (const k of keys) {
239
259
  if (unmet <= 0) break;
240
260
  const avail = pool[k];
241
- if (avail <= 0) continue;
242
- const take = Math.min(avail, unmet);
261
+ const take = routableDrawAmount(avail, unmet, minRoutable);
262
+ if (take <= 0) continue;
243
263
  draws[k] = take;
244
264
  pool[k] -= take;
245
265
  unmet -= take;
@@ -247,6 +267,27 @@ function drawFunds(need: number, keys: FundKey[], pool: FundingPool): DrawResult
247
267
  return { draws, filled: need - unmet, unmet };
248
268
  }
249
269
 
270
+ /**
271
+ * Withdraw + uPnL are separate buckets but execution moves them in one EXTENDED_TO_WALLET leg
272
+ * (waterfall inside the budget). Apply {@link routableDrawAmount} to their **sum**, then drain
273
+ * avl-withdraw first, then uPnL — matches spendExtAvailTrade in the state-manager budget.
274
+ */
275
+ function drawExtendedAggregated(need: number, pool: FundingPool, minRoutable: number): DrawResult {
276
+ const draws: Partial<Record<FundKey, number>> = {};
277
+ if (need <= 0) return { draws, filled: 0, unmet: 0 };
278
+ const totalCap = Math.max(0, pool.extAvlWithdraw) + Math.max(0, pool.extUpnl);
279
+ const want = routableDrawAmount(totalCap, need, minRoutable);
280
+ if (want <= 0) return { draws, filled: 0, unmet: need };
281
+ const fromAvl = Math.min(Math.max(0, pool.extAvlWithdraw), want);
282
+ pool.extAvlWithdraw -= fromAvl;
283
+ const fromUpnl = Math.min(Math.max(0, pool.extUpnl), want - fromAvl);
284
+ pool.extUpnl -= fromUpnl;
285
+ if (fromAvl > 0) draws.extAvlWithdraw = fromAvl;
286
+ if (fromUpnl > 0) draws.extUpnl = fromUpnl;
287
+ const filled = fromAvl + fromUpnl;
288
+ return { draws, filled, unmet: need - filled };
289
+ }
290
+
250
291
  /** Sum draw amounts for a subset of keys (e.g. all Extended-sourced draws). */
251
292
  function sumKeys(draws: Partial<Record<FundKey, number>>, keys: FundKey[]): number {
252
293
  return keys.reduce((s, k) => s + (draws[k] ?? 0), 0);
@@ -286,12 +327,13 @@ interface Phase1Result {
286
327
  * Priority: wallet → VA → vesuBorrowCapacity.
287
328
  * Funds received increase Extended equity (dExtAvlWithdraw).
288
329
  */
289
- function fixExtMargin(ext: ExtendedState, price: number, pool: FundingPool): { d: RebalanceDeltas; unmet: number } {
330
+ function fixExtMargin(ext: ExtendedState, price: number, pool: FundingPool, config: RebalanceConfig): { d: RebalanceDeltas; unmet: number } {
290
331
  const d = emptyDeltas();
291
332
  const deficit = computeExtDeficit(ext, price);
292
333
  if (deficit <= 0) return { d, unmet: 0 };
293
334
 
294
- const { draws, filled, unmet } = drawFunds(deficit, ['walletUsd', 'vaUsd', 'vesuBorrowCapacity'], pool);
335
+ const minR = config.minRoutableUsd ?? 0;
336
+ const { draws, filled, unmet } = drawFunds(deficit, ['walletUsd', 'vaUsd', 'vesuBorrowCapacity'], pool, minR);
295
337
  applyDrawsToDeltas(d, draws, -1);
296
338
 
297
339
  // All filled amount lands as Extended equity
@@ -310,7 +352,7 @@ function fixExtMargin(ext: ExtendedState, price: number, pool: FundingPool): { d
310
352
  * Priority: VA → wallet → extAvlWithdraw → extUpnl.
311
353
  * Repaid amount reduces Vesu debt (dVesuDebt).
312
354
  */
313
- function fixVesuMargin(vesu: VesuState, price: number, pool: FundingPool, hfBuffer: number): { d: RebalanceDeltas; unmet: number } {
355
+ function fixVesuMargin(vesu: VesuState, price: number, pool: FundingPool, hfBuffer: number, config: RebalanceConfig): { d: RebalanceDeltas; unmet: number } {
314
356
  const d = emptyDeltas();
315
357
  const hf = computeVesuHF(vesu, price);
316
358
 
@@ -320,14 +362,19 @@ function fixVesuMargin(vesu: VesuState, price: number, pool: FundingPool, hfBuff
320
362
  const repayTokens = computeVesuDebtRepay(vesu, price);
321
363
  const repayUsd = repayTokens * vesu.debtPrice;
322
364
 
323
- const { draws, filled, unmet } = drawFunds(repayUsd, ['vaUsd', 'walletUsd', 'extAvlWithdraw', 'extUpnl'], pool);
324
- applyDrawsToDeltas(d, draws, -1);
365
+ const minR = config.minRoutableUsd ?? 0;
366
+ const rMain = drawFunds(repayUsd, ['vaUsd', 'walletUsd'], pool, minR);
367
+ applyDrawsToDeltas(d, rMain.draws, -1);
368
+ const rExt = drawExtendedAggregated(rMain.unmet, pool, minR);
369
+ applyDrawsToDeltas(d, rExt.draws, -1);
370
+ const filled = rMain.filled + rExt.filled;
371
+ const unmet = rExt.unmet;
325
372
 
326
373
  // Convert filled USD back to debt tokens for the repayment delta
327
374
  d.dVesuDebt -= filled / vesu.debtPrice;
328
375
 
329
376
  // Funds withdrawn from Extended and sent to Vesu = negative transfer
330
- const fromExt = sumKeys(draws, ['extAvlWithdraw', 'extUpnl']);
377
+ const fromExt = sumKeys(rExt.draws, ['extAvlWithdraw', 'extUpnl']);
331
378
  if (fromExt > 0) d.dTransferVesuToExt -= fromExt;
332
379
 
333
380
  return { d, unmet };
@@ -339,8 +386,8 @@ function fixVesuMargin(vesu: VesuState, price: number, pool: FundingPool, hfBuff
339
386
  * Returns combined deltas and any unresolved deficits.
340
387
  */
341
388
  function phase1(ext: ExtendedState, vesu: VesuState, price: number, pool: FundingPool, config: RebalanceConfig): Phase1Result {
342
- const extResult = fixExtMargin(ext, price, pool);
343
- const vesuResult = fixVesuMargin(vesu, price, pool, config.hfBuffer);
389
+ const extResult = fixExtMargin(ext, price, pool, config);
390
+ const vesuResult = fixVesuMargin(vesu, price, pool, config.hfBuffer, config);
344
391
  return {
345
392
  deltas: mergeDeltas(extResult.d, vesuResult.d),
346
393
  extDeficitRemaining: extResult.unmet,
@@ -564,9 +611,11 @@ function roundFinalPosition(
564
611
  function phase2(
565
612
  extPos: number, vesuPos: number, extEquity: number,
566
613
  vesuDebt: number, vesu: VesuState, extLev: number, price: number,
567
- pool: FundingPool, precision: number,
614
+ pool: FundingPool, config: RebalanceConfig,
568
615
  ): RebalanceDeltas {
569
616
  const d = emptyDeltas();
617
+ const precision = config.positionPrecision;
618
+ const minR = config.minRoutableUsd ?? 0;
570
619
  const targetLTV = computeVesuTargetLTV(vesu);
571
620
 
572
621
  // ---- Step 1: Try to close gap using available funding sources ----
@@ -583,8 +632,11 @@ function phase2(
583
632
  // Extended is larger — try to grow Vesu
584
633
  // Vesu growth is leveraged: equity cost = gapBtc × price × (1 - targetLTV)
585
634
  const equityCostUsd = computeVesuGrowthCost(imbalance, vesu, price);
586
- const { draws, filled } = drawFunds(equityCostUsd, ['vaUsd', 'walletUsd', 'extAvlWithdraw', 'extUpnl'], pool);
587
- applyDrawsToDeltas(d, draws, -1);
635
+ const rMain = drawFunds(equityCostUsd, ['vaUsd', 'walletUsd'], pool, minR);
636
+ applyDrawsToDeltas(d, rMain.draws, -1);
637
+ const rExt = drawExtendedAggregated(rMain.unmet, pool, minR);
638
+ applyDrawsToDeltas(d, rExt.draws, -1);
639
+ const filled = rMain.filled + rExt.filled;
588
640
 
589
641
  // Convert filled equity into leveraged position growth
590
642
  const grownBtc = computeVesuGrowthFromEquity(filled, vesu, price);
@@ -595,7 +647,7 @@ function phase2(
595
647
  d.dVesuDebt += computeVesuGrowthDebt(grownBtc, vesu, price);
596
648
 
597
649
  // Track ext → vesu fund movement
598
- const fromExt = sumKeys(draws, ['extAvlWithdraw', 'extUpnl']);
650
+ const fromExt = sumKeys(rExt.draws, ['extAvlWithdraw', 'extUpnl']);
599
651
  if (fromExt > 0) d.dTransferVesuToExt -= fromExt;
600
652
 
601
653
  } else {
@@ -603,7 +655,7 @@ function phase2(
603
655
  // Extended growth is leveraged: margin cost = gapBtc × price / extLeverage
604
656
  const absImbalance = -imbalance;
605
657
  const marginCostUsd = absImbalance * price / extLev;
606
- const { draws, filled } = drawFunds(marginCostUsd, ['walletUsd', 'vaUsd', 'vesuBorrowCapacity'], pool);
658
+ const { draws, filled } = drawFunds(marginCostUsd, ['walletUsd', 'vaUsd', 'vesuBorrowCapacity'], pool, minR);
607
659
  applyDrawsToDeltas(d, draws, -1);
608
660
 
609
661
  // Each $1 of margin creates extLeverage/price BTC of position
@@ -706,6 +758,7 @@ function phase2(
706
758
  * then solves the unified equation for anything left.
707
759
  */
708
760
  function rebalance(inputs: RebalanceInputs): RebalanceDeltas {
761
+ logger.info(`ltv-imbalance-rebalance-math::rebalance inputs=${JSON.stringify(inputs)}`);
709
762
  const { ext, vesu, btcPrice, config } = inputs;
710
763
  const pool: FundingPool = { ...inputs.funding };
711
764
 
@@ -721,10 +774,10 @@ function rebalance(inputs: RebalanceInputs): RebalanceDeltas {
721
774
 
722
775
  const p2 = phase2(
723
776
  effExtPos, effVesuPos, effExtEquity, effVesuDebt,
724
- vesu, ext.leverage, btcPrice, pool, config.positionPrecision,
777
+ vesu, ext.leverage, btcPrice, pool, config,
725
778
  );
726
779
 
727
780
  return mergeDeltas(p1.deltas, p2);
728
781
  }
729
782
 
730
- export { rebalance, RebalanceInputs, RebalanceDeltas, RebalanceConfig };
783
+ export { rebalance, RebalanceInputs, RebalanceDeltas, routableDrawAmount };
@@ -553,7 +553,7 @@ export class VesuExtendedMultiplierStrategy<
553
553
  * wiring function that connects the strategy to its underlying protocol adapters.
554
554
  */
555
555
  function getLooperSettings(
556
- lstSymbol: string,
556
+ collateralSymbol: string,
557
557
  underlyingSymbol: string,
558
558
  vaultSettings: VesuExtendedStrategySettings,
559
559
  pool1: ContractAddr,
@@ -569,14 +569,14 @@ function getLooperSettings(
569
569
  vaultSettings.leafAdapters = [];
570
570
 
571
571
  const wbtcToken = Global.getDefaultTokens().find(
572
- (token) => token.symbol === lstSymbol,
572
+ (token) => token.symbol === collateralSymbol,
573
573
  )!;
574
574
  const usdcToken = Global.getDefaultTokens().find(
575
575
  (token) => token.symbol === underlyingSymbol,
576
576
  )!;
577
577
 
578
578
  const baseAdapterConfig: BaseAdapterConfig = {
579
- baseToken: wbtcToken,
579
+ baseToken: usdcToken,
580
580
  supportedPositions: [
581
581
  { asset: usdcToken, isDebt: true },
582
582
  { asset: wbtcToken, isDebt: false },
@@ -688,7 +688,7 @@ function getLooperSettings(
688
688
  vaultAddress: vaultSettings.vaultAddress,
689
689
  vaultAllocator: vaultSettings.vaultAllocator,
690
690
  manager: vaultSettings.manager,
691
- asset: wbtcToken.address,
691
+ asset: usdcToken.address,
692
692
  });
693
693
 
694
694
  vaultSettings.leafAdapters.push(() => vesuMultiplyAdapter.getDepositLeaf());
@@ -841,6 +841,41 @@ const re7UsdcPrimeDevansh: VesuExtendedStrategySettings = {
841
841
  walletAddress: '0x024b563C1C7d41B32BF4EFB9F38828508a65Be2d6e25268E9f63F22C5e9E51c5',
842
842
  };
843
843
 
844
+ const pureUsdc: VesuExtendedStrategySettings = {
845
+ vaultAddress: ContractAddr.from(
846
+ "0x745c972db65bdee10022fd875dd328c7f40a90849135b6a0f875a40f3c632ae",
847
+ ),
848
+ manager: ContractAddr.from(
849
+ "0x364e0894edefb616ec090f57f5c0274517fcd98ab276ae1f021c5e962fa1deb",
850
+ ),
851
+ vaultAllocator: ContractAddr.from(
852
+ "0x6fceed28e03a96091877568893df0dd89b9bb80fec30da2b742dacbd5526179",
853
+ ),
854
+ redeemRequestNFT: ContractAddr.from(
855
+ "0x501c2b87728e22c6dfcebe4c0b2b3a9fba5845606e4d59fa7bf591badcbb42",
856
+ ),
857
+ aumOracle: ContractAddr.from(
858
+ "0x6ccd95f5765242695d3c75e1440b1d0b30efac8babb864ce15729977b97cb82",
859
+ ),
860
+ leafAdapters: [],
861
+ adapters: [],
862
+ targetHealthFactor: 1.4,
863
+ minHealthFactor: 1.35,
864
+ underlyingToken: Global.getDefaultTokens().find(
865
+ (token) => token.symbol === "USDC",
866
+ )!,
867
+ quoteAmountToFetchPrice: new Web3Number(
868
+ "0.001",
869
+ Global.getDefaultTokens().find((token) => token.symbol === "USDC")!
870
+ .decimals,
871
+ ),
872
+ borrowable_assets: [
873
+ Global.getDefaultTokens().find((token) => token.symbol === "USDC")!,
874
+ ],
875
+ minimumWBTCDifferenceForAvnuSwap: MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP,
876
+ walletAddress: '0x058571C23da5FEdd4e36003FAE3fE2fA9782f2692E552f081839142B10770D0B',
877
+ };
878
+
844
879
  export const VesuExtendedTestStrategies = (
845
880
  extendedBackendReadUrl: string,
846
881
  extendedBackendWriteUrl: string,
@@ -867,6 +902,21 @@ export const VesuExtendedTestStrategies = (
867
902
  minimumExtendedPriceDifferenceForSwapOpen,
868
903
  maximumExtendedPriceDifferenceForSwapClosing,
869
904
  ),
905
+ getStrategySettingsVesuExtended(
906
+ "WBTC",
907
+ "USDC",
908
+ pureUsdc,
909
+ false,
910
+ false,
911
+ extendedBackendReadUrl,
912
+ extendedBackendWriteUrl,
913
+ vaultIdExtended,
914
+ minimumExtendedMovementAmount,
915
+ minimumVesuMovementAmount,
916
+ minimumExtendedRetriesDelayForOrderStatus,
917
+ minimumExtendedPriceDifferenceForSwapOpen,
918
+ maximumExtendedPriceDifferenceForSwapClosing,
919
+ ),
870
920
  ];
871
921
  };
872
922
 
@@ -875,7 +925,7 @@ export const VesuExtendedTestStrategies = (
875
925
  * including adapter wiring, risk configuration, FAQ, and UI description.
876
926
  */
877
927
  function getStrategySettingsVesuExtended(
878
- lstSymbol: string,
928
+ collateralSymbol: string,
879
929
  underlyingSymbol: string,
880
930
  addresses: VesuExtendedStrategySettings,
881
931
  isPreview: boolean = false,
@@ -892,7 +942,7 @@ function getStrategySettingsVesuExtended(
892
942
  return {
893
943
  id: `extended_${underlyingSymbol.toLowerCase()}_test`,
894
944
  name: `Extended Test ${underlyingSymbol}`,
895
- description: getDescription(lstSymbol, underlyingSymbol),
945
+ description: getDescription(collateralSymbol, underlyingSymbol),
896
946
  address: addresses.vaultAddress,
897
947
  launchBlock: 0,
898
948
  type: "Other",
@@ -906,7 +956,7 @@ function getStrategySettingsVesuExtended(
906
956
  )!,
907
957
  ],
908
958
  additionalInfo: getLooperSettings(
909
- lstSymbol,
959
+ collateralSymbol,
910
960
  underlyingSymbol,
911
961
  addresses,
912
962
  VesuPools.Re7USDCPrime,
@@ -929,8 +979,8 @@ function getStrategySettingsVesuExtended(
929
979
  auditUrl: AUDIT_URL,
930
980
  protocols: [Protocols.ENDUR, Protocols.VESU],
931
981
  contractDetails: getContractDetails(addresses),
932
- faqs: getFAQs(lstSymbol, underlyingSymbol, isLST),
933
- investmentSteps: getInvestmentSteps(lstSymbol, underlyingSymbol),
982
+ faqs: getFAQs(collateralSymbol, underlyingSymbol, isLST),
983
+ investmentSteps: getInvestmentSteps(collateralSymbol, underlyingSymbol),
934
984
  isPreview: isPreview,
935
985
  apyMethodology: isLST
936
986
  ? "Current annualized APY in terms of base asset of the LST. There is no additional fee taken by Troves on LST APY. We charge a 10% performance fee on the additional gain which is already accounted in the APY shown."
@@ -5,6 +5,7 @@ interface CacheData {
5
5
  }
6
6
  export class CacheClass {
7
7
  readonly cache: Map<string, CacheData> = new Map();
8
+ isCacheEnabled: boolean = true;
8
9
 
9
10
  setCache(key: string, data: any, ttl: number = 60000): void {
10
11
  const timestamp = Date.now();
@@ -13,7 +14,7 @@ export class CacheClass {
13
14
 
14
15
  getCache<T>(key: string): T | null {
15
16
  const cachedData = this.cache.get(key);
16
- if (!cachedData || !this.isCacheValid(key)) {
17
+ if (!cachedData || !this.isCacheValid(key) || !this.isCacheEnabled) {
17
18
  return null;
18
19
  }
19
20
  return cachedData.data;
@@ -26,4 +27,12 @@ export class CacheClass {
26
27
  const { timestamp, ttl } = cachedData;
27
28
  return Date.now() - timestamp <= ttl;
28
29
  }
29
- }
30
+
31
+ disableCache(): void {
32
+ this.isCacheEnabled = false;
33
+ }
34
+
35
+ enableCache(): void {
36
+ this.isCacheEnabled = true;
37
+ }
38
+ }