@perena/vault-sdk 1.0.28 → 1.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -150,7 +150,7 @@ var require_compat = __commonJS({
150
150
  exports2.toWeb3PublicKey = toWeb3PublicKey;
151
151
  exports2.asWeb3PublicKey = asWeb3PublicKey2;
152
152
  exports2.toKitInstruction = toKitInstruction30;
153
- exports2.fromKitInstruction = fromKitInstruction2;
153
+ exports2.fromKitInstruction = fromKitInstruction3;
154
154
  var kit_1 = require("@solana/kit");
155
155
  var web3_js_1 = require("@solana/web3.js");
156
156
  function fromWeb3PublicKey(pubkey) {
@@ -175,7 +175,7 @@ var require_compat = __commonJS({
175
175
  }))
176
176
  };
177
177
  }
178
- function fromKitInstruction2(ix) {
178
+ function fromKitInstruction3(ix) {
179
179
  return new web3_js_1.TransactionInstruction({
180
180
  programId: toWeb3PublicKey(ix.programAddress),
181
181
  keys: (ix.accounts ?? []).map((account) => ({
@@ -342,7 +342,7 @@ var require_refs = __commonJS({
342
342
  "use strict";
343
343
  Object.defineProperty(exports2, "__esModule", { value: true });
344
344
  exports2.createMultiCpiRefs = createMultiCpiRefs;
345
- exports2.createCpiRefs = createCpiRefs3;
345
+ exports2.createCpiRefs = createCpiRefs4;
346
346
  var kit_1 = require("@solana/kit");
347
347
  var roles_1 = require_roles();
348
348
  var accounts_1 = require_accounts();
@@ -478,7 +478,7 @@ var require_refs = __commonJS({
478
478
  lookupTables: collectLookupTables(allCpis)
479
479
  };
480
480
  }
481
- function createCpiRefs3(cpis, options) {
481
+ function createCpiRefs4(cpis, options) {
482
482
  const accountLists = toCpiAccountLists(cpis);
483
483
  const { pool, poolIndex } = buildPool(accountLists, options);
484
484
  const trackedIndices = resolveTrackedIndices(poolIndex, options?.trackAddresses);
@@ -592,11 +592,11 @@ var require_transaction = __commonJS({
592
592
  "../common/dist/utils/transaction.js"(exports2) {
593
593
  "use strict";
594
594
  Object.defineProperty(exports2, "__esModule", { value: true });
595
- exports2.fetchLookupTables = fetchLookupTables2;
595
+ exports2.fetchLookupTables = fetchLookupTables3;
596
596
  exports2.getSignatureStatus = getSignatureStatus;
597
597
  exports2.sendVersionedTransaction = sendVersionedTransaction;
598
598
  var web3_js_1 = require("@solana/web3.js");
599
- async function fetchLookupTables2(connection, lookupTables) {
599
+ async function fetchLookupTables3(connection, lookupTables) {
600
600
  const accounts2 = await Promise.all(lookupTables.map(async (lookupTable) => {
601
601
  const response = await connection.getAddressLookupTable(new web3_js_1.PublicKey(lookupTable));
602
602
  if (!response.value) {
@@ -613,7 +613,7 @@ var require_transaction = __commonJS({
613
613
  }
614
614
  async function sendVersionedTransaction({ connection, payer, instructions: instructions2, lookupTables }) {
615
615
  const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
616
- const lookupTableAccounts = await fetchLookupTables2(connection, lookupTables);
616
+ const lookupTableAccounts = await fetchLookupTables3(connection, lookupTables);
617
617
  const message2 = new web3_js_1.TransactionMessage({
618
618
  payerKey: payer.publicKey,
619
619
  recentBlockhash: blockhash,
@@ -2921,7 +2921,7 @@ var require_client = __commonJS({
2921
2921
  var extract_1 = require_extract();
2922
2922
  var routePreInstructions_1 = require_routePreInstructions();
2923
2923
  var constants_1 = require_constants4();
2924
- var JupiterSwapClient = class {
2924
+ var JupiterSwapClient2 = class {
2925
2925
  constructor(rpc, config = {}) {
2926
2926
  this.rpc = rpc;
2927
2927
  this.config = config;
@@ -3012,7 +3012,7 @@ var require_client = __commonJS({
3012
3012
  };
3013
3013
  }
3014
3014
  };
3015
- exports2.JupiterSwapClient = JupiterSwapClient;
3015
+ exports2.JupiterSwapClient = JupiterSwapClient2;
3016
3016
  }
3017
3017
  });
3018
3018
 
@@ -3232,6 +3232,7 @@ var require_dist4 = __commonJS({
3232
3232
  var index_exports = {};
3233
3233
  __export(index_exports, {
3234
3234
  ASSET_DECIMALS: () => ASSET_DECIMALS,
3235
+ ASSET_REBALANCE_COOLDOWN_SECS: () => ASSET_REBALANCE_COOLDOWN_SECS,
3235
3236
  AccountClient: () => AccountClient,
3236
3237
  ActivateCircuitBreakerBuilder: () => ActivateCircuitBreakerBuilder,
3237
3238
  CONSENSUS_ORACLE_VARIANT: () => CONSENSUS_ORACLE_VARIANT,
@@ -3242,15 +3243,20 @@ __export(index_exports, {
3242
3243
  CreateAssetHoldingBuilder: () => CreateAssetHoldingBuilder,
3243
3244
  CreateTrancheStateBuilder: () => CreateTrancheStateBuilder,
3244
3245
  CreateVaultBuilder: () => CreateVaultBuilder,
3246
+ DEFAULT_CU_PRICE_MICRO_LAMPORTS: () => DEFAULT_CU_PRICE_MICRO_LAMPORTS,
3245
3247
  DEFAULT_JUNIOR_EARLY_UNSTAKE_FEE_BPS: () => DEFAULT_JUNIOR_EARLY_UNSTAKE_FEE_BPS,
3246
3248
  DEFAULT_JUNIOR_STANDARD_UNSTAKE_FEE_BPS: () => DEFAULT_JUNIOR_STANDARD_UNSTAKE_FEE_BPS,
3247
3249
  DEFAULT_JUNIOR_WITHDRAWAL_LOCKUP_SECS: () => DEFAULT_JUNIOR_WITHDRAWAL_LOCKUP_SECS,
3248
3250
  DEFAULT_MAX_ACCEPTABLE_APY_BPS: () => DEFAULT_MAX_ACCEPTABLE_APY_BPS,
3251
+ DEFAULT_MAX_ACCOUNTS: () => DEFAULT_MAX_ACCOUNTS,
3249
3252
  DEFAULT_MIN_AMOUNT_UI: () => DEFAULT_MIN_AMOUNT_UI,
3250
3253
  DEFAULT_ORACLE_STALENESS_THRESHOLD_SECS: () => DEFAULT_ORACLE_STALENESS_THRESHOLD_SECS,
3251
3254
  DEFAULT_PRICE_ORACLE_ACCOUNT: () => DEFAULT_PRICE_ORACLE_ACCOUNT,
3252
3255
  DEFAULT_PRICE_STALENESS_SECS: () => DEFAULT_PRICE_STALENESS_SECS,
3253
3256
  DEFAULT_PRICE_STALENESS_THRESHOLD_SECS: () => DEFAULT_PRICE_STALENESS_THRESHOLD_SECS,
3257
+ DEFAULT_REBALANCE_BAND_BPS: () => DEFAULT_REBALANCE_BAND_BPS,
3258
+ DEFAULT_SLIPPAGE_BPS: () => DEFAULT_SLIPPAGE_BPS,
3259
+ DEFAULT_TARGET_LOCAL_BPS: () => DEFAULT_TARGET_LOCAL_BPS,
3254
3260
  DEFAULT_VAULT_ID: () => DEFAULT_VAULT_ID,
3255
3261
  DisableCircuitBreakerBuilder: () => DisableCircuitBreakerBuilder,
3256
3262
  EXTERNAL_POSITION_SAMPLES: () => EXTERNAL_POSITION_SAMPLES,
@@ -3264,6 +3270,9 @@ __export(index_exports, {
3264
3270
  FEE_VAULT_CACHE_CATEGORY: () => FEE_VAULT_CACHE_CATEGORY,
3265
3271
  FulfillJuniorTrancheWithdrawBuilder: () => FulfillJuniorTrancheWithdrawBuilder,
3266
3272
  IDL: () => IDL,
3273
+ IDLE_RESERVE_FLOOR_BPS: () => IDLE_RESERVE_FLOOR_BPS,
3274
+ IDLE_RESERVE_TARGET_BPS: () => IDLE_RESERVE_TARGET_BPS,
3275
+ IdleLiquidityService: () => IdleLiquidityService,
3267
3276
  InitializeVaultRolesBuilder: () => InitializeVaultRolesBuilder,
3268
3277
  JupiterBalanceSource: () => JupiterBalanceSource,
3269
3278
  JupiterPriceSource: () => JupiterPriceSource,
@@ -3377,6 +3386,7 @@ __export(index_exports, {
3377
3386
  mintTokensTo: () => mintTokensTo,
3378
3387
  mostFrequent: () => mostFrequent,
3379
3388
  parseExternalLiquidityRefs: () => parseExternalLiquidityRefs,
3389
+ planRebalance: () => planRebalance,
3380
3390
  prepareVaultTransaction: () => prepareVaultTransaction,
3381
3391
  priceInAccountingUnit: () => priceInAccountingUnit,
3382
3392
  readI64LE: () => readI64LE,
@@ -14326,6 +14336,106 @@ var IDL = {
14326
14336
  ]
14327
14337
  }
14328
14338
  },
14339
+ {
14340
+ "name": "RollingRebalanceLimit",
14341
+ "docs": [
14342
+ "Rolling-window cap on rebalance (Jupiter swap) throughput, expressed as a",
14343
+ "share of current TVL rather than a fixed amount.",
14344
+ "",
14345
+ "Sibling of [`super::withdrawal_limits::RollingWithdrawalLimit`]: same",
14346
+ "window-slide-then-charge mechanics, but the ceiling is recomputed from live",
14347
+ "TVL on every call, so the cap tracks the vault as it grows or shrinks",
14348
+ "instead of going stale at whatever amount the curator last set.",
14349
+ "",
14350
+ "Both swap directions charge the same window \u2014 the cap bounds total value",
14351
+ "routed through Jupiter per window, regardless of which asset is sold."
14352
+ ],
14353
+ "serialization": "bytemuck",
14354
+ "repr": {
14355
+ "kind": "c"
14356
+ },
14357
+ "type": {
14358
+ "kind": "struct",
14359
+ "fields": [
14360
+ {
14361
+ "name": "window_start_ts",
14362
+ "docs": [
14363
+ "Unix timestamp the current window opened."
14364
+ ],
14365
+ "type": "i64"
14366
+ },
14367
+ {
14368
+ "name": "window_duration_seconds",
14369
+ "docs": [
14370
+ "Window length, in seconds (e.g. 3_600 for an hourly limit)."
14371
+ ],
14372
+ "type": "u64"
14373
+ },
14374
+ {
14375
+ "name": "value_in_window",
14376
+ "docs": [
14377
+ "Value rebalanced inside the current window, in the vault's accounting",
14378
+ "unit."
14379
+ ],
14380
+ "type": "u64"
14381
+ },
14382
+ {
14383
+ "name": "window_limit_bps",
14384
+ "docs": [
14385
+ "Cap as a share of TVL, in bps. `0` disables the limit."
14386
+ ],
14387
+ "type": "u16"
14388
+ },
14389
+ {
14390
+ "name": "_padding1",
14391
+ "type": {
14392
+ "array": [
14393
+ "u8",
14394
+ 6
14395
+ ]
14396
+ }
14397
+ },
14398
+ {
14399
+ "name": "_padding2",
14400
+ "type": {
14401
+ "array": [
14402
+ "u64",
14403
+ 4
14404
+ ]
14405
+ }
14406
+ }
14407
+ ]
14408
+ }
14409
+ },
14410
+ {
14411
+ "name": "RollingRebalanceLimitConfig",
14412
+ "docs": [
14413
+ "Rolling rebalance-limit config applied via [`SetVaultConfigArgs`].",
14414
+ "",
14415
+ "The cap is a share of TVL rather than a fixed amount, so it stays",
14416
+ "meaningful as the vault grows. Both fields must be zero (disable the cap)",
14417
+ "or both non-zero (enable it)."
14418
+ ],
14419
+ "type": {
14420
+ "kind": "struct",
14421
+ "fields": [
14422
+ {
14423
+ "name": "window_limit_bps",
14424
+ "docs": [
14425
+ "Max share of TVL rebalanced inside one window, in bps (`0` disables)."
14426
+ ],
14427
+ "type": "u16"
14428
+ },
14429
+ {
14430
+ "name": "window_duration_seconds",
14431
+ "docs": [
14432
+ "Window length in seconds (`0` only valid when `window_limit_bps` is `0`)."
14433
+ ],
14434
+ "type": "u64"
14435
+ }
14436
+ ]
14437
+ }
14438
+ },
14329
14439
  {
14330
14440
  "name": "SetAssetPriceOracleArgs",
14331
14441
  "type": {
@@ -14511,6 +14621,20 @@ var IDL = {
14511
14621
  }
14512
14622
  }
14513
14623
  }
14624
+ },
14625
+ {
14626
+ "name": "rebalance_limit",
14627
+ "docs": [
14628
+ "Rolling cap on rebalance (Jupiter swap) throughput. `None` leaves",
14629
+ "unchanged."
14630
+ ],
14631
+ "type": {
14632
+ "option": {
14633
+ "defined": {
14634
+ "name": "RollingRebalanceLimitConfig"
14635
+ }
14636
+ }
14637
+ }
14514
14638
  }
14515
14639
  ]
14516
14640
  }
@@ -15432,6 +15556,14 @@ var IDL = {
15432
15556
  "name": "senior_fixed_apy_bps",
15433
15557
  "type": "u16"
15434
15558
  },
15559
+ {
15560
+ "name": "junior_floor_apy_bps",
15561
+ "docs": [
15562
+ "Floor APY for the junior tranche, in basis points. Zero disables the",
15563
+ "floor. Shortfalls are funded from the untranched sleeve, never senior."
15564
+ ],
15565
+ "type": "u16"
15566
+ },
15435
15567
  {
15436
15568
  "name": "early_unstake_fee_bps",
15437
15569
  "type": "u16"
@@ -16257,12 +16389,38 @@ var IDL = {
16257
16389
  ],
16258
16390
  "type": "u64"
16259
16391
  },
16392
+ {
16393
+ "name": "junior_floor_apy_bps",
16394
+ "docs": [
16395
+ "Curator-set floor APY for the junior tranche, in basis points. Zero",
16396
+ "disables the floor.",
16397
+ "",
16398
+ "When junior's realized growth falls short of this floor, the shortfall is",
16399
+ "topped up from the *untranched* (regular) sleeve's value, never from the",
16400
+ "senior tranche -- senior's fixed-APY entitlement is settled first and is",
16401
+ "untouched by the floor. The top-up is best-effort: it is capped by the",
16402
+ "untranched sleeve's available value, and any unpaid remainder stays",
16403
+ "outstanding against the durable junior anchor",
16404
+ "(`VaultTrancheState::junior_apy_anchor_share_price`) so it can be paid on",
16405
+ "a later settlement."
16406
+ ],
16407
+ "type": "u16"
16408
+ },
16409
+ {
16410
+ "name": "_padding1",
16411
+ "type": {
16412
+ "array": [
16413
+ "u8",
16414
+ 6
16415
+ ]
16416
+ }
16417
+ },
16260
16418
  {
16261
16419
  "name": "_padding2",
16262
16420
  "type": {
16263
16421
  "array": [
16264
16422
  "u64",
16265
- 13
16423
+ 12
16266
16424
  ]
16267
16425
  }
16268
16426
  }
@@ -16340,12 +16498,28 @@ var IDL = {
16340
16498
  ],
16341
16499
  "type": "i64"
16342
16500
  },
16501
+ {
16502
+ "name": "junior_apy_anchor_share_price",
16503
+ "docs": [
16504
+ "Junior share price at the start of the current floor-APY regime. The",
16505
+ "anchor is durable across settlements so an unpaid floor shortfall stays",
16506
+ "outstanding and can be caught up later."
16507
+ ],
16508
+ "type": "u64"
16509
+ },
16510
+ {
16511
+ "name": "junior_apy_anchor_ts",
16512
+ "docs": [
16513
+ "Unix timestamp when the junior floor-APY anchor was set."
16514
+ ],
16515
+ "type": "i64"
16516
+ },
16343
16517
  {
16344
16518
  "name": "_padding2",
16345
16519
  "type": {
16346
16520
  "array": [
16347
16521
  "u64",
16348
- 29
16522
+ 27
16349
16523
  ]
16350
16524
  }
16351
16525
  }
@@ -16363,6 +16537,17 @@ var IDL = {
16363
16537
  "option": "u16"
16364
16538
  }
16365
16539
  },
16540
+ {
16541
+ "name": "junior_floor_apy_bps",
16542
+ "docs": [
16543
+ "Floor APY for the junior tranche, in basis points. Zero disables the",
16544
+ "floor. Setting this starts a fresh regime: any shortfall accrued under",
16545
+ "the previous value is forfeited."
16546
+ ],
16547
+ "type": {
16548
+ "option": "u16"
16549
+ }
16550
+ },
16366
16551
  {
16367
16552
  "name": "early_unstake_fee_bps",
16368
16553
  "type": {
@@ -16645,12 +16830,25 @@ var IDL = {
16645
16830
  ],
16646
16831
  "type": "u64"
16647
16832
  },
16833
+ {
16834
+ "name": "rebalance_limit",
16835
+ "docs": [
16836
+ "Rolling cap on rebalance (Jupiter swap) throughput per window, as a",
16837
+ "share of TVL. Carved out of `_padding1`, so the account layout and size",
16838
+ "are unchanged and existing vaults decode it as disabled."
16839
+ ],
16840
+ "type": {
16841
+ "defined": {
16842
+ "name": "RollingRebalanceLimit"
16843
+ }
16844
+ }
16845
+ },
16648
16846
  {
16649
16847
  "name": "_padding1",
16650
16848
  "type": {
16651
16849
  "array": [
16652
16850
  "u64",
16653
- 30
16851
+ 22
16654
16852
  ]
16655
16853
  }
16656
16854
  },
@@ -17789,6 +17987,7 @@ var CreateTrancheStateBuilder = class extends VaultBuilderBase {
17789
17987
  async getIx(args) {
17790
17988
  return this.program.methods.vaultCreateTrancheState({
17791
17989
  seniorFixedApyBps: args.seniorFixedApyBps,
17990
+ juniorFloorApyBps: args.juniorFloorApyBps,
17792
17991
  earlyUnstakeFeeBps: args.earlyUnstakeFeeBps,
17793
17992
  standardUnstakeFeeBps: args.standardUnstakeFeeBps,
17794
17993
  targetLockupDurationSecs: args.targetLockupDurationSecs,
@@ -17832,6 +18031,7 @@ var CreateTrancheStateBuilder = class extends VaultBuilderBase {
17832
18031
  seniorShareMint: txArgs.seniorShareMint,
17833
18032
  vaultOracle,
17834
18033
  seniorFixedApyBps: txArgs.seniorFixedApyBps ?? 0,
18034
+ juniorFloorApyBps: txArgs.juniorFloorApyBps ?? 0,
17835
18035
  earlyUnstakeFeeBps: txArgs.earlyUnstakeFeeBps ?? DEFAULT_JUNIOR_EARLY_UNSTAKE_FEE_BPS,
17836
18036
  standardUnstakeFeeBps: txArgs.standardUnstakeFeeBps ?? DEFAULT_JUNIOR_STANDARD_UNSTAKE_FEE_BPS,
17837
18037
  targetLockupDurationSecs: new import_core.BN(
@@ -17907,7 +18107,8 @@ function toAnchorSetVaultConfigArgs(args) {
17907
18107
  lossesEnabled: args.lossesEnabled,
17908
18108
  priceStalenessThresholdSecs: args.priceStalenessThresholdSecs,
17909
18109
  userBurnLimit: args.userBurnLimit,
17910
- managerPullLimit: args.managerPullLimit
18110
+ managerPullLimit: args.managerPullLimit,
18111
+ rebalanceLimit: args.rebalanceLimit
17911
18112
  };
17912
18113
  }
17913
18114
  function toAnchorConsensusUpdates(updates) {
@@ -18115,6 +18316,12 @@ var SetVaultConfigBuilder = class extends VaultBuilderBase {
18115
18316
  windowDurationSeconds: new import_core4.BN(
18116
18317
  txArgs.managerPullLimit.windowDurationSeconds.toString()
18117
18318
  )
18319
+ } : null,
18320
+ rebalanceLimit: txArgs.rebalanceLimit !== void 0 ? {
18321
+ windowLimitBps: txArgs.rebalanceLimit.windowLimitBps,
18322
+ windowDurationSeconds: new import_core4.BN(
18323
+ txArgs.rebalanceLimit.windowDurationSeconds.toString()
18324
+ )
18118
18325
  } : null
18119
18326
  }
18120
18327
  };
@@ -19499,6 +19706,7 @@ var UpdateTrancheConfigBuilder = class extends VaultBuilderBase {
19499
19706
  async getIx(args) {
19500
19707
  return this.program.methods.vaultUpdateTrancheConfig({
19501
19708
  seniorFixedApyBps: args.seniorFixedApyBps,
19709
+ juniorFloorApyBps: args.juniorFloorApyBps,
19502
19710
  earlyUnstakeFeeBps: args.earlyUnstakeFeeBps,
19503
19711
  standardUnstakeFeeBps: args.standardUnstakeFeeBps,
19504
19712
  targetLockupDurationSecs: args.targetLockupDurationSecs,
@@ -19521,6 +19729,7 @@ var UpdateTrancheConfigBuilder = class extends VaultBuilderBase {
19521
19729
  vaultTrancheState: txArgs.vaultTrancheState ?? vaultTrancheState,
19522
19730
  vaultOracle: txArgs.vaultOracle ?? vaultOracle,
19523
19731
  seniorFixedApyBps: txArgs.seniorFixedApyBps ?? null,
19732
+ juniorFloorApyBps: txArgs.juniorFloorApyBps ?? null,
19524
19733
  earlyUnstakeFeeBps: txArgs.earlyUnstakeFeeBps ?? null,
19525
19734
  standardUnstakeFeeBps: txArgs.standardUnstakeFeeBps ?? null,
19526
19735
  targetLockupDurationSecs: txArgs.targetLockupDurationSecs === void 0 ? null : new import_core11.BN(txArgs.targetLockupDurationSecs.toString()),
@@ -21576,21 +21785,27 @@ function settleVault(args) {
21576
21785
  args.grossNav,
21577
21786
  args.nowTs
21578
21787
  );
21788
+ const trancheValueBeforeFees = args.tranche.junior.value + args.tranche.senior.value;
21789
+ const juniorFees = settleShareClassPerformanceFee(
21790
+ args.tranche.junior,
21791
+ args.performanceFeeBps,
21792
+ args.protocolFeeBps
21793
+ );
21794
+ const juniorFloorTopup = applyJuniorFloorTopup(
21795
+ args.tranche,
21796
+ regularGrossValue,
21797
+ args.nowTs
21798
+ );
21579
21799
  regularValue = settleFixedApyNav({
21580
21800
  currentSharePrice: args.currentSharePrice,
21581
- reportedNav: regularGrossValue,
21801
+ reportedNav: regularGrossValue - juniorFloorTopup,
21582
21802
  nowTs: args.nowTs,
21583
21803
  totalSupply: args.totalSupply,
21584
21804
  fixedApyBps: args.fixedApyBps,
21585
21805
  anchorSharePrice: args.apyAnchorSharePrice,
21586
21806
  anchorTs: args.apyAnchorTs
21587
21807
  });
21588
- settledAccountingNav = regularValue + args.tranche.junior.value + args.tranche.senior.value;
21589
- const juniorFees = settleShareClassPerformanceFee(
21590
- args.tranche.junior,
21591
- args.performanceFeeBps,
21592
- args.protocolFeeBps
21593
- );
21808
+ settledAccountingNav = regularValue + trancheValueBeforeFees + juniorFloorTopup;
21594
21809
  const regularFees = settleRegularPerformanceFee(
21595
21810
  regularValue,
21596
21811
  args.totalSupply,
@@ -21711,6 +21926,31 @@ function seniorEntitlement(tranche, nowTs) {
21711
21926
  );
21712
21927
  return targetValue > tranche.senior.value ? targetValue - tranche.senior.value : 0n;
21713
21928
  }
21929
+ function applyJuniorFloorTopup(tranche, availableRegularValue, nowTs) {
21930
+ if (tranche.juniorApyAnchorTs <= 0n) {
21931
+ tranche.juniorApyAnchorSharePrice = tranche.junior.sharePrice;
21932
+ tranche.juniorApyAnchorTs = tranche.lastSettledTs > 0n ? tranche.lastSettledTs : nowTs;
21933
+ }
21934
+ if (tranche.juniorFloorApyBps === 0 || tranche.juniorApyAnchorSharePrice === 0n || tranche.junior.totalSupply === 0n || tranche.juniorApyAnchorTs <= 0n || nowTs <= tranche.juniorApyAnchorTs) {
21935
+ return 0n;
21936
+ }
21937
+ const targetPrice = applyEffectiveApy(
21938
+ tranche.juniorApyAnchorSharePrice,
21939
+ tranche.juniorFloorApyBps,
21940
+ nowTs - tranche.juniorApyAnchorTs
21941
+ );
21942
+ const targetValue = mulDiv2(
21943
+ targetPrice,
21944
+ tranche.junior.totalSupply,
21945
+ DEFAULT_SHARE_PRICE
21946
+ );
21947
+ const shortfall = targetValue > tranche.junior.value ? targetValue - tranche.junior.value : 0n;
21948
+ const topup = shortfall < availableRegularValue ? shortfall : availableRegularValue;
21949
+ if (topup === 0n) return 0n;
21950
+ tranche.junior.value += topup;
21951
+ refreshShareClass(tranche.junior);
21952
+ return topup;
21953
+ }
21714
21954
  function settleShareClassPerformanceFee(shareClass, performanceFeeBps, protocolFeeBps) {
21715
21955
  refreshShareClass(shareClass);
21716
21956
  const grossPrice = shareClass.sharePrice;
@@ -21931,9 +22171,12 @@ function decodeTrancheState(decoded, args, warnings, blockers) {
21931
22171
  junior,
21932
22172
  senior,
21933
22173
  seniorFixedApyBps: number(decoded.config.seniorFixedApyBps),
22174
+ juniorFloorApyBps: number(decoded.config.juniorFloorApyBps),
21934
22175
  lastSettledTs: bigint(decoded.lastSettledTs),
21935
22176
  seniorApyAnchorSharePrice: bigint(decoded.seniorApyAnchorSharePrice),
21936
- seniorApyAnchorTs: bigint(decoded.seniorApyAnchorTs)
22177
+ seniorApyAnchorTs: bigint(decoded.seniorApyAnchorTs),
22178
+ juniorApyAnchorSharePrice: bigint(decoded.juniorApyAnchorSharePrice),
22179
+ juniorApyAnchorTs: bigint(decoded.juniorApyAnchorTs)
21937
22180
  };
21938
22181
  }
21939
22182
  function decodeShareClass(value) {
@@ -23427,6 +23670,9 @@ var import_web328 = require("@solana/web3.js");
23427
23670
  var import_common50 = __toESM(require_dist());
23428
23671
  var import_marginfi3 = __toESM(require_dist2());
23429
23672
  var DEFAULT_MIN_AMOUNT_UI = 1;
23673
+ var DEFAULT_TARGET_LOCAL_BPS = 100;
23674
+ var DEFAULT_REBALANCE_BAND_BPS = 2e3;
23675
+ var BPS_DENOMINATOR3 = 10000n;
23430
23676
  function toRawAmount(uiAmount, decimals) {
23431
23677
  return BigInt(Math.floor(uiAmount * 10 ** decimals));
23432
23678
  }
@@ -23463,26 +23709,50 @@ function pubkeyStr(value) {
23463
23709
  function isZeroPubkey2(pk) {
23464
23710
  return !pk || /^1+$/.test(pk) || pk === "11111111111111111111111111111111";
23465
23711
  }
23712
+ function planRebalance(params) {
23713
+ const { localAmount, externalAmount, targetLocalBps, rebalanceBandBps, minRaw } = params;
23714
+ const total = localAmount + externalAmount;
23715
+ const targetLocal = total * BigInt(Math.round(targetLocalBps)) / BPS_DENOMINATOR3;
23716
+ const band = targetLocal * BigInt(Math.round(rebalanceBandBps)) / BPS_DENOMINATOR3;
23717
+ const threshold = band > minRaw ? band : minRaw;
23718
+ if (localAmount > targetLocal) {
23719
+ const amount2 = localAmount - targetLocal;
23720
+ if (amount2 < threshold) return null;
23721
+ return { direction: "deposit", amount: amount2, targetLocal };
23722
+ }
23723
+ const shortfall = targetLocal - localAmount;
23724
+ if (shortfall < threshold) return null;
23725
+ const amount = shortfall < externalAmount ? shortfall : externalAmount;
23726
+ if (amount < minRaw) return null;
23727
+ return { direction: "withdraw", amount, targetLocal };
23728
+ }
23466
23729
  var ExternalLiquidityIntegrityService = class {
23467
23730
  constructor(client) {
23468
23731
  this.client = client;
23469
23732
  }
23470
23733
  /**
23471
- * Scan all vaults and deposit any idle local balance into configured external
23472
- * liquidity positions. Non-fatal per vault — errors are logged and counted.
23734
+ * Scan all vaults and rebalance each holding toward its target local share.
23735
+ * Non-fatal per vault — errors are logged and counted.
23473
23736
  */
23474
23737
  async runAll(hwManager, opts = {}) {
23475
23738
  const log = opts.log ?? (() => {
23476
23739
  });
23477
23740
  const dryRun = opts.dryRun ?? false;
23478
23741
  const minAmountUi = opts.minAmountUi ?? DEFAULT_MIN_AMOUNT_UI;
23479
- if (dryRun) log("DRY RUN \u2014 deposits will be simulated, not sent");
23480
- log(`Minimum deposit: ${minAmountUi} token(s) (~$${minAmountUi})`);
23742
+ const targetLocalBps = opts.targetLocalBps ?? DEFAULT_TARGET_LOCAL_BPS;
23743
+ const rebalanceBandBps = opts.rebalanceBandBps ?? DEFAULT_REBALANCE_BAND_BPS;
23744
+ if (dryRun) log("DRY RUN \u2014 rebalances will be simulated, not sent");
23745
+ log(`Minimum rebalance: ${minAmountUi} token(s) (~$${minAmountUi})`);
23746
+ log(`Target local share: ${targetLocalBps / 100}% of each holding`);
23747
+ log(
23748
+ `Rebalance band: ${rebalanceBandBps / 100}% of target (${targetLocalBps * (1 - rebalanceBandBps / 1e4) / 100}%\u2013${targetLocalBps * (1 + rebalanceBandBps / 1e4) / 100}% of holding)`
23749
+ );
23481
23750
  const allVaults = await this.client.account.fetchAllVaults({ fresh: true });
23482
23751
  log(`Discovered ${allVaults.length} vault(s) on-chain`);
23483
23752
  const summary = {
23484
23753
  vaultsProcessed: 0,
23485
23754
  totalDeposited: 0,
23755
+ totalWithdrawn: 0,
23486
23756
  totalSkipped: 0,
23487
23757
  totalFailed: 0,
23488
23758
  dryRun
@@ -23493,25 +23763,29 @@ var ExternalLiquidityIntegrityService = class {
23493
23763
  vaultState,
23494
23764
  hwManager,
23495
23765
  log,
23496
- { dryRun, minAmountUi }
23766
+ { dryRun, minAmountUi, targetLocalBps, rebalanceBandBps }
23497
23767
  );
23498
23768
  summary.vaultsProcessed++;
23499
23769
  summary.totalDeposited += result.deposited;
23770
+ summary.totalWithdrawn += result.withdrawn;
23500
23771
  summary.totalSkipped += result.skipped;
23501
23772
  summary.totalFailed += result.failed;
23502
23773
  }
23503
23774
  return summary;
23504
23775
  }
23505
23776
  /**
23506
- * Process a single vault: for each active external liquidity slot, deposit
23507
- * any holdings that have a non-zero local_amount.
23777
+ * Process a single vault: for each active external liquidity slot, move each
23778
+ * holding's balance toward the target local/external split.
23508
23779
  */
23509
23780
  async processVault(vault, vaultState, hwManager, log, opts = {}) {
23510
23781
  const dryRun = opts.dryRun ?? false;
23511
23782
  const minAmountUi = opts.minAmountUi ?? DEFAULT_MIN_AMOUNT_UI;
23783
+ const targetLocalBps = opts.targetLocalBps ?? DEFAULT_TARGET_LOCAL_BPS;
23784
+ const rebalanceBandBps = opts.rebalanceBandBps ?? DEFAULT_REBALANCE_BAND_BPS;
23512
23785
  const result = {
23513
23786
  vault,
23514
23787
  deposited: 0,
23788
+ withdrawn: 0,
23515
23789
  skipped: 0,
23516
23790
  failed: 0,
23517
23791
  signatures: []
@@ -23541,24 +23815,33 @@ var ExternalLiquidityIntegrityService = class {
23541
23815
  }
23542
23816
  const holdings = vaultState.holdings.filter((h) => {
23543
23817
  const mint = pubkeyStr(h.mint);
23544
- return !isZeroPubkey2(mint) && toBigInt3(h.localAmount) > 0n;
23818
+ if (isZeroPubkey2(mint)) return false;
23819
+ return toBigInt3(h.localAmount) > 0n || toBigInt3(h.externalAmount) > 0n;
23545
23820
  });
23546
23821
  if (holdings.length === 0) {
23547
- log(`Vault ${vault} slot ${slot.index}: no local balance to deploy`);
23822
+ log(`Vault ${vault} slot ${slot.index}: no balance to rebalance`);
23548
23823
  result.skipped++;
23549
23824
  continue;
23550
23825
  }
23551
23826
  for (const holding of holdings) {
23552
23827
  const mintAddress = (0, import_kit15.address)(pubkeyStr(holding.mint));
23553
23828
  const localAmount = toBigInt3(holding.localAmount);
23829
+ const externalAmount = toBigInt3(holding.externalAmount);
23554
23830
  const decimals = Number(holding.decimals ?? 0);
23555
23831
  const minRaw = toRawAmount(minAmountUi, decimals);
23556
- if (localAmount < minRaw) {
23832
+ const plan = planRebalance({
23833
+ localAmount,
23834
+ externalAmount,
23835
+ targetLocalBps,
23836
+ rebalanceBandBps,
23837
+ minRaw
23838
+ });
23839
+ if (!plan) {
23557
23840
  log(
23558
- `Vault ${vault} slot ${slot.index}: mint ${mintAddress} has ${formatUi(
23559
- localAmount,
23841
+ `Vault ${vault} slot ${slot.index}: mint ${mintAddress} within band (local ${formatUi(localAmount, decimals)}, external ${formatUi(
23842
+ externalAmount,
23560
23843
  decimals
23561
- )} < minimum ${minAmountUi} \u2014 skipping`
23844
+ )}) \u2014 skipping`
23562
23845
  );
23563
23846
  result.skipped++;
23564
23847
  continue;
@@ -23572,31 +23855,36 @@ var ExternalLiquidityIntegrityService = class {
23572
23855
  continue;
23573
23856
  }
23574
23857
  log(
23575
- `Vault ${vault} slot ${slot.index}: ${dryRun ? "simulating deposit of" : "depositing"} ${localAmount} of mint ${mintAddress} into marginfi`
23858
+ `Vault ${vault} slot ${slot.index}: ${dryRun ? "simulating " : ""}${plan.direction} of ${formatUi(plan.amount, decimals)} ${mintAddress} (local ${formatUi(localAmount, decimals)} \u2192 target ${formatUi(
23859
+ plan.targetLocal,
23860
+ decimals
23861
+ )})`
23576
23862
  );
23577
23863
  try {
23578
- const outcome = await this.depositIntoMarginfi({
23864
+ const outcome = await this.rebalanceMarginfi({
23579
23865
  vault,
23580
23866
  vaultPk,
23581
23867
  hwManager,
23582
23868
  hwManagerAddress,
23583
23869
  slot,
23584
23870
  mintAddress,
23585
- amount: localAmount,
23871
+ direction: plan.direction,
23872
+ amount: plan.amount,
23586
23873
  marginfiAccounts,
23587
23874
  dryRun,
23588
23875
  log
23589
23876
  });
23590
23877
  if (outcome.signature) {
23591
23878
  log(
23592
- `Vault ${vault} slot ${slot.index}: deposit tx ${outcome.signature}`
23879
+ `Vault ${vault} slot ${slot.index}: ${plan.direction} tx ${outcome.signature}`
23593
23880
  );
23594
23881
  result.signatures.push(outcome.signature);
23595
23882
  }
23596
- result.deposited++;
23883
+ if (plan.direction === "deposit") result.deposited++;
23884
+ else result.withdrawn++;
23597
23885
  } catch (err) {
23598
23886
  log(
23599
- `Vault ${vault} slot ${slot.index}: deposit failed \u2014 ${err.message}`
23887
+ `Vault ${vault} slot ${slot.index}: ${plan.direction} failed \u2014 ${err.message}`
23600
23888
  );
23601
23889
  result.failed++;
23602
23890
  }
@@ -23604,48 +23892,60 @@ var ExternalLiquidityIntegrityService = class {
23604
23892
  }
23605
23893
  return result;
23606
23894
  }
23607
- async depositIntoMarginfi(params) {
23895
+ /**
23896
+ * Move `amount` of `mintAddress` between the vault's own token account and
23897
+ * its Marginfi position, in `direction`, through `protocol_interaction`.
23898
+ */
23899
+ async rebalanceMarginfi(params) {
23608
23900
  const {
23609
23901
  vault,
23610
23902
  hwManager,
23611
23903
  hwManagerAddress,
23612
23904
  slot,
23613
23905
  mintAddress,
23906
+ direction,
23614
23907
  amount,
23615
23908
  marginfiAccounts,
23616
23909
  dryRun = false,
23617
23910
  log = () => {
23618
23911
  }
23619
23912
  } = params;
23620
- const marginfiClient = new import_marginfi3.CpiClient();
23913
+ const marginfiClient = new import_marginfi3.CpiClient({
23914
+ accounts: marginfiAccounts
23915
+ });
23621
23916
  const { getAtaAddress: getAtaAddress16 } = await Promise.resolve().then(() => __toESM(require_dist()));
23622
- const signerTokenAccount = getAtaAddress16(
23917
+ const vaultAta = getAtaAddress16(
23623
23918
  mintAddress,
23624
23919
  vault,
23625
23920
  marginfiAccounts.tokenProgram,
23626
23921
  true
23627
23922
  );
23628
- const depositRefs = (0, import_common50.createCpiRefs)([
23629
- marginfiClient.deposit_cpi({
23923
+ const cpiRefs = (0, import_common50.createCpiRefs)([
23924
+ direction === "deposit" ? marginfiClient.deposit_cpi({
23630
23925
  marginfiAccount: slot.userAccount,
23631
23926
  authority: vault,
23632
- signerTokenAccount,
23927
+ signerTokenAccount: vaultAta,
23928
+ amount
23929
+ }) : marginfiClient.withdraw_cpi({
23930
+ marginfiAccount: slot.userAccount,
23931
+ authority: vault,
23932
+ destinationTokenAccount: vaultAta,
23633
23933
  amount
23634
23934
  })
23635
23935
  ]);
23636
23936
  const ix = await this.client.tx.protocolInteraction.getIx({
23637
23937
  hwManager: hwManagerAddress,
23638
23938
  vault,
23639
- refs: depositRefs.refs,
23939
+ refs: cpiRefs.refs,
23640
23940
  externalLiquidityIndex: slot.index,
23641
23941
  affectedAssetMint: mintAddress,
23642
- affectedVaultAta: signerTokenAccount,
23942
+ affectedVaultAta: vaultAta,
23643
23943
  affectedTokenProgram: marginfiAccounts.tokenProgram,
23644
- accounts: depositRefs.accounts
23944
+ accounts: cpiRefs.accounts
23645
23945
  });
23646
23946
  const plan = {
23647
23947
  instructions: [ix],
23648
- ...depositRefs.lookupTables?.length ? { lookupTables: depositRefs.lookupTables } : {}
23948
+ ...cpiRefs.lookupTables?.length ? { lookupTables: cpiRefs.lookupTables } : {}
23649
23949
  };
23650
23950
  if (dryRun) {
23651
23951
  const sim = await this.client.simulateTransaction(hwManager, plan);
@@ -23653,9 +23953,7 @@ var ExternalLiquidityIntegrityService = class {
23653
23953
  log(` | ${line}`);
23654
23954
  }
23655
23955
  if (sim.err) {
23656
- throw new Error(
23657
- `simulation failed: ${JSON.stringify(sim.err)}`
23658
- );
23956
+ throw new Error(`simulation failed: ${JSON.stringify(sim.err)}`);
23659
23957
  }
23660
23958
  log(
23661
23959
  `Vault ${vault} slot ${slot.index}: simulation OK (${sim.unitsConsumed ?? "?"} CU)`
@@ -23665,9 +23963,408 @@ var ExternalLiquidityIntegrityService = class {
23665
23963
  return { signature: await this.client.sendTransaction(hwManager, plan) };
23666
23964
  }
23667
23965
  };
23966
+
23967
+ // src/services/idleLiquidityService.ts
23968
+ var import_kit16 = require("@solana/kit");
23969
+ var import_spl_token19 = require("@solana/spl-token");
23970
+ var import_web329 = require("@solana/web3.js");
23971
+ var import_common51 = __toESM(require_dist());
23972
+ var import_jupiter3 = __toESM(require_dist3());
23973
+ var IDLE_RESERVE_FLOOR_BPS = 400;
23974
+ var IDLE_RESERVE_TARGET_BPS = 500;
23975
+ var DEFAULT_SLIPPAGE_BPS = 30;
23976
+ var DEFAULT_CU_PRICE_MICRO_LAMPORTS = 1e4;
23977
+ var DEFAULT_MAX_ACCOUNTS = 20;
23978
+ var COMPUTE_UNIT_LIMIT = 12e5;
23979
+ var ASSET_REBALANCE_COOLDOWN_SECS = 60;
23980
+ function toBigInt4(value) {
23981
+ if (typeof value === "bigint") return value;
23982
+ if (value !== null && value !== void 0) return BigInt(String(value));
23983
+ return 0n;
23984
+ }
23985
+ function pubkeyStr2(value) {
23986
+ if (typeof value === "string") return value;
23987
+ if (value && typeof value.toBase58 === "function") {
23988
+ return value.toBase58();
23989
+ }
23990
+ return String(value);
23991
+ }
23992
+ function coerceBool4(value) {
23993
+ if (typeof value === "boolean") return value;
23994
+ if (typeof value === "number") return value !== 0;
23995
+ const inner = value?.[0];
23996
+ if (typeof inner === "number") return inner !== 0;
23997
+ return false;
23998
+ }
23999
+ function formatUi2(raw, decimals) {
24000
+ return (Number(raw) / 10 ** decimals).toFixed(Math.min(decimals, 6));
24001
+ }
24002
+ function readHolding(raw) {
24003
+ const h = raw;
24004
+ return {
24005
+ mint: pubkeyStr2(h.mint),
24006
+ price: toBigInt4(h.price),
24007
+ decimals: Number(h.decimals ?? 0),
24008
+ localAmount: toBigInt4(h.localAmount),
24009
+ externalAmount: toBigInt4(h.externalAmount),
24010
+ lastUpdateTs: Number(toBigInt4(h.lastUpdateTs)),
24011
+ lastRebalanceTs: Number(toBigInt4(h.lastRebalanceTs))
24012
+ };
24013
+ }
24014
+ function findHolding2(vaultState, mint) {
24015
+ const holdings = vaultState.holdings ?? [];
24016
+ return holdings.map(readHolding).find((h) => h.mint === mint);
24017
+ }
24018
+ function holdingValue2(holding, amount) {
24019
+ return amount * holding.price / 10n ** BigInt(holding.decimals);
24020
+ }
24021
+ var IdleLiquidityService = class {
24022
+ constructor(client) {
24023
+ this.client = client;
24024
+ }
24025
+ /** Current unix time in seconds (override in tests). */
24026
+ now() {
24027
+ return Math.floor(Date.now() / 1e3);
24028
+ }
24029
+ /**
24030
+ * Top up the vault's idle USDC reserve if it has fallen below the floor.
24031
+ * Never throws for an expected no-op — inspect `status` on the result.
24032
+ */
24033
+ async run(vault, hwManager, opts = {}) {
24034
+ const log = opts.log ?? (() => {
24035
+ });
24036
+ const vaultState = await this.client.account.fetchVault(vault, {
24037
+ fresh: true
24038
+ });
24039
+ const registeredHwManager = pubkeyStr2(
24040
+ vaultState.roles.hwManager
24041
+ );
24042
+ if (registeredHwManager !== hwManager.publicKey.toBase58()) {
24043
+ throw new Error(
24044
+ `Signer ${hwManager.publicKey.toBase58()} is not the vault hw_manager ${registeredHwManager}`
24045
+ );
24046
+ }
24047
+ const assessment = this.assess(vault, vaultState, log);
24048
+ if (assessment.result) return assessment.result;
24049
+ const { usdc, pst, base } = assessment;
24050
+ const pstInput = this.sizePstInput(base.shortfallUsdc, usdc, pst);
24051
+ if (pstInput === 0n) {
24052
+ const reason = "top-up rounds to zero PST";
24053
+ return { ...base, status: "above-floor", reason };
24054
+ }
24055
+ const cap = opts.maxPstInput !== void 0 && opts.maxPstInput < pst.localAmount ? opts.maxPstInput : pst.localAmount;
24056
+ if (pstInput > cap) {
24057
+ const reason = `top-up needs ${formatUi2(
24058
+ pstInput,
24059
+ pst.decimals
24060
+ )} PST but only ${formatUi2(cap, pst.decimals)} is available`;
24061
+ log(`Vault ${vault}: ${reason} \u2014 skipping`);
24062
+ return { ...base, status: "insufficient-pst", reason };
24063
+ }
24064
+ const jupiter = new import_jupiter3.JupiterSwapClient(
24065
+ (0, import_kit16.createSolanaRpc)(this.client.provider.connection.rpcEndpoint)
24066
+ );
24067
+ let quote;
24068
+ try {
24069
+ quote = await jupiter.getQuote({
24070
+ inputMint: import_common51.PST_MINT,
24071
+ outputMint: import_common51.USDC_MINT,
24072
+ amount: pstInput,
24073
+ slippageBps: opts.slippageBps ?? DEFAULT_SLIPPAGE_BPS,
24074
+ maxAccounts: DEFAULT_MAX_ACCOUNTS
24075
+ });
24076
+ } catch (err) {
24077
+ log(`Vault ${vault}: Jupiter quote failed \u2014 ${err.message}`);
24078
+ return { ...base, status: "failed", reason: err.message };
24079
+ }
24080
+ log(
24081
+ `Vault ${vault}: swapping ${formatUi2(
24082
+ quote.inputAmount,
24083
+ pst.decimals
24084
+ )} PST -> ${formatUi2(
24085
+ quote.outputAmount,
24086
+ usdc.decimals
24087
+ )} USDC (price impact ${quote.priceImpactPct})`
24088
+ );
24089
+ try {
24090
+ const outcome = await this.executeSwap({
24091
+ vault,
24092
+ vaultState,
24093
+ hwManager,
24094
+ jupiter,
24095
+ quote,
24096
+ cuPriceMicroLamports: opts.cuPriceMicroLamports ?? DEFAULT_CU_PRICE_MICRO_LAMPORTS,
24097
+ dryRun: opts.dryRun ?? false,
24098
+ log
24099
+ });
24100
+ return {
24101
+ ...base,
24102
+ status: outcome.signature ? "swapped" : "simulated",
24103
+ reason: outcome.signature ? `swapped in ${outcome.signature}` : "dry run \u2014 simulation succeeded, nothing submitted",
24104
+ pstSpent: quote.inputAmount,
24105
+ usdcReceived: quote.outputAmount,
24106
+ ...outcome.signature ? { signature: outcome.signature } : {}
24107
+ };
24108
+ } catch (err) {
24109
+ log(`Vault ${vault}: swap failed \u2014 ${err.message}`);
24110
+ return { ...base, status: "failed", reason: err.message };
24111
+ }
24112
+ }
24113
+ // ── Assessment ──────────────────────────────────────────────────────────
24114
+ /**
24115
+ * Decide whether a top-up is warranted. Returns either a terminal `result`
24116
+ * (nothing to do) or the holdings and sizing needed to build the swap.
24117
+ */
24118
+ assess(vault, vaultState, log) {
24119
+ const tvl = toBigInt4(
24120
+ vaultState.accounting?.tvl
24121
+ );
24122
+ const empty = {
24123
+ vault,
24124
+ tvl,
24125
+ idleValue: 0n,
24126
+ idleBps: 0,
24127
+ shortfallUsdc: 0n,
24128
+ pstSpent: 0n,
24129
+ usdcReceived: 0n
24130
+ };
24131
+ if (tvl === 0n) {
24132
+ const reason = "vault TVL is zero";
24133
+ log(`Vault ${vault}: ${reason} \u2014 skipping`);
24134
+ return { result: { ...empty, status: "no-tvl", reason } };
24135
+ }
24136
+ const usdc = findHolding2(vaultState, import_common51.USDC_MINT);
24137
+ const pst = findHolding2(vaultState, import_common51.PST_MINT);
24138
+ if (!usdc || !pst) {
24139
+ const reason = `vault has no ${usdc ? "PST" : "USDC"} holding`;
24140
+ log(`Vault ${vault}: ${reason} \u2014 skipping`);
24141
+ return { result: { ...empty, status: "missing-holding", reason } };
24142
+ }
24143
+ const stalenessThreshold = this.stalenessThresholdSecs(vaultState);
24144
+ const now = this.now();
24145
+ for (const [name, holding] of [
24146
+ ["USDC", usdc],
24147
+ ["PST", pst]
24148
+ ]) {
24149
+ if (holding.price === 0n) {
24150
+ const reason = `${name} holding has no price set`;
24151
+ log(`Vault ${vault}: ${reason} \u2014 skipping`);
24152
+ return { result: { ...empty, status: "stale-price", reason } };
24153
+ }
24154
+ if (now - holding.lastUpdateTs > stalenessThreshold) {
24155
+ const reason = `${name} holding price is stale (last update ${holding.lastUpdateTs})`;
24156
+ log(`Vault ${vault}: ${reason} \u2014 skipping`);
24157
+ return { result: { ...empty, status: "stale-price", reason } };
24158
+ }
24159
+ }
24160
+ const idleAmount = usdc.localAmount + usdc.externalAmount;
24161
+ const idleValue = holdingValue2(usdc, idleAmount);
24162
+ const idleBps = Number(idleValue * 10000n / tvl);
24163
+ const base = { ...empty, idleValue, idleBps };
24164
+ log(
24165
+ `Vault ${vault}: idle USDC ${formatUi2(
24166
+ idleAmount,
24167
+ usdc.decimals
24168
+ )} = ${idleBps}bps of TVL (floor ${IDLE_RESERVE_FLOOR_BPS}bps, target ${IDLE_RESERVE_TARGET_BPS}bps)`
24169
+ );
24170
+ if (idleBps >= IDLE_RESERVE_FLOOR_BPS) {
24171
+ const reason = `idle USDC ${idleBps}bps is at or above the ${IDLE_RESERVE_FLOOR_BPS}bps floor`;
24172
+ return { result: { ...base, status: "above-floor", reason } };
24173
+ }
24174
+ for (const [name, holding] of [
24175
+ ["USDC", usdc],
24176
+ ["PST", pst]
24177
+ ]) {
24178
+ if (holding.lastRebalanceTs !== 0 && now < holding.lastRebalanceTs + ASSET_REBALANCE_COOLDOWN_SECS) {
24179
+ const reason = `${name} holding is in its rebalance cooldown until ${holding.lastRebalanceTs + ASSET_REBALANCE_COOLDOWN_SECS}`;
24180
+ log(`Vault ${vault}: ${reason} \u2014 skipping`);
24181
+ return { result: { ...base, status: "rebalance-cooldown", reason } };
24182
+ }
24183
+ }
24184
+ const targetValue = tvl * BigInt(IDLE_RESERVE_TARGET_BPS) / 10000n;
24185
+ let shortfallValue = targetValue - idleValue;
24186
+ const allowance = this.remainingRebalanceAllowance(vaultState, tvl, now);
24187
+ if (allowance !== void 0 && allowance < shortfallValue) {
24188
+ if (allowance === 0n) {
24189
+ const reason = "rolling rebalance limit is exhausted for this window";
24190
+ log(`Vault ${vault}: ${reason} \u2014 skipping`);
24191
+ return { result: { ...base, status: "rebalance-limit", reason } };
24192
+ }
24193
+ log(
24194
+ `Vault ${vault}: rolling rebalance limit caps this run at ${allowance} of ${shortfallValue} accounting units`
24195
+ );
24196
+ shortfallValue = allowance;
24197
+ }
24198
+ const shortfallUsdc = shortfallValue * 10n ** BigInt(usdc.decimals) / usdc.price;
24199
+ if (shortfallUsdc <= 0n) {
24200
+ const reason = "computed shortfall rounds to zero";
24201
+ return { result: { ...base, status: "above-floor", reason } };
24202
+ }
24203
+ if (pst.localAmount === 0n) {
24204
+ const reason = "vault holds no local PST";
24205
+ log(`Vault ${vault}: ${reason} \u2014 skipping`);
24206
+ return {
24207
+ result: { ...base, shortfallUsdc, status: "insufficient-pst", reason }
24208
+ };
24209
+ }
24210
+ return { usdc, pst, base: { ...base, shortfallUsdc } };
24211
+ }
24212
+ /**
24213
+ * Value still chargeable against the vault's rolling rebalance window, in
24214
+ * accounting units. `undefined` when the cap is disabled (unlimited).
24215
+ */
24216
+ remainingRebalanceAllowance(vaultState, tvl, now) {
24217
+ const limit = vaultState.accounting?.rebalanceLimit;
24218
+ const bps = Number(toBigInt4(limit?.windowLimitBps));
24219
+ if (!limit || bps === 0) return void 0;
24220
+ const windowStartTs = Number(toBigInt4(limit.windowStartTs));
24221
+ const windowDuration = Number(toBigInt4(limit.windowDurationSeconds));
24222
+ const consumed = windowStartTs === 0 || now >= windowStartTs + windowDuration ? 0n : toBigInt4(limit.valueInWindow);
24223
+ const ceiling = tvl * BigInt(bps) / 10000n;
24224
+ return ceiling > consumed ? ceiling - consumed : 0n;
24225
+ }
24226
+ /**
24227
+ * PST input that buys `shortfallUsdc` at the holdings' recorded prices.
24228
+ * Both prices share the vault's accounting unit, so this is a straight
24229
+ * value conversion; slippage is absorbed by landing slightly under target.
24230
+ */
24231
+ sizePstInput(shortfallUsdc, usdc, pst) {
24232
+ const shortfallValue = holdingValue2(usdc, shortfallUsdc);
24233
+ return shortfallValue * 10n ** BigInt(pst.decimals) / pst.price;
24234
+ }
24235
+ stalenessThresholdSecs(vaultState) {
24236
+ const configured = Number(
24237
+ toBigInt4(
24238
+ vaultState.config?.priceStalenessThresholdSecs
24239
+ )
24240
+ );
24241
+ return configured > 0 ? configured : DEFAULT_PRICE_STALENESS_THRESHOLD_SECS;
24242
+ }
24243
+ // ── Execution ───────────────────────────────────────────────────────────
24244
+ /**
24245
+ * Token program owning a mint. Read from the chain rather than from the
24246
+ * holding's enum so a Token-2022 mint can never be passed to the classic
24247
+ * program (and vice versa).
24248
+ */
24249
+ async mintTokenProgram(mint) {
24250
+ const account = await this.client.provider.connection.getAccountInfo(
24251
+ new import_web329.PublicKey(mint),
24252
+ "confirmed"
24253
+ );
24254
+ if (!account) throw new Error(`Mint account not found: ${mint}`);
24255
+ if (!account.owner.equals(import_spl_token19.TOKEN_PROGRAM_ID) && !account.owner.equals(import_spl_token19.TOKEN_2022_PROGRAM_ID)) {
24256
+ throw new Error(
24257
+ `Unsupported token program ${account.owner.toBase58()} for mint ${mint}`
24258
+ );
24259
+ }
24260
+ return (0, import_common51.fromWeb3Pk)(account.owner);
24261
+ }
24262
+ /**
24263
+ * Build, simulate, and (unless `dryRun`) submit the `jupiter_swap`.
24264
+ *
24265
+ * Built as a versioned transaction here rather than through
24266
+ * `client.sendTransaction`, because a Jupiter route needs address lookup
24267
+ * tables and a raised compute-unit limit to fit.
24268
+ */
24269
+ async executeSwap(params) {
24270
+ const {
24271
+ vault,
24272
+ vaultState,
24273
+ hwManager,
24274
+ jupiter,
24275
+ quote,
24276
+ cuPriceMicroLamports,
24277
+ dryRun,
24278
+ log
24279
+ } = params;
24280
+ const signer = (0, import_common51.fromWeb3Pk)(hwManager.publicKey);
24281
+ const [sourceTokenProgram, destinationTokenProgram] = await Promise.all([
24282
+ this.mintTokenProgram(import_common51.PST_MINT),
24283
+ this.mintTokenProgram(import_common51.USDC_MINT)
24284
+ ]);
24285
+ const jupiterCpi = await jupiter.getSwapCpiData(quote, vault, {
24286
+ payer: signer
24287
+ });
24288
+ const { accounts: accounts2, refs, lookupTables } = (0, import_common51.createCpiRefs)([jupiterCpi]);
24289
+ const vaultTrancheState = coerceBool4(
24290
+ vaultState.tranchingEnabled
24291
+ ) ? (await this.client.pda.deriveVaultTrancheStatePda(vault))[0] : void 0;
24292
+ const plan = await this.client.tx.jupiterSwap.getTx({
24293
+ hwManager: signer,
24294
+ vault,
24295
+ sourceMint: import_common51.PST_MINT,
24296
+ destinationMint: import_common51.USDC_MINT,
24297
+ refs,
24298
+ accounts: accounts2,
24299
+ sourceTokenProgram,
24300
+ destinationTokenProgram,
24301
+ vaultTrancheState,
24302
+ lookupTables
24303
+ });
24304
+ const connection = this.client.provider.connection;
24305
+ const instructions2 = [
24306
+ import_web329.ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_UNIT_LIMIT }),
24307
+ ...cuPriceMicroLamports > 0 ? [
24308
+ import_web329.ComputeBudgetProgram.setComputeUnitPrice({
24309
+ microLamports: cuPriceMicroLamports
24310
+ })
24311
+ ] : [],
24312
+ ...(jupiterCpi.preInstructions ?? []).map(import_common51.fromKitInstruction),
24313
+ ...plan.instructions.map(import_common51.fromKitInstruction)
24314
+ ];
24315
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
24316
+ const lookupTableAccounts = await (0, import_common51.fetchLookupTables)(
24317
+ connection,
24318
+ plan.lookupTables ?? []
24319
+ );
24320
+ const message2 = new import_web329.TransactionMessage({
24321
+ payerKey: hwManager.publicKey,
24322
+ recentBlockhash: blockhash,
24323
+ instructions: instructions2
24324
+ }).compileToV0Message(lookupTableAccounts);
24325
+ const transaction = new import_web329.VersionedTransaction(message2);
24326
+ transaction.sign([hwManager]);
24327
+ const simulation = await connection.simulateTransaction(transaction, {
24328
+ sigVerify: true,
24329
+ commitment: "confirmed"
24330
+ });
24331
+ if (simulation.value.err) {
24332
+ for (const line of simulation.value.logs ?? []) log(` | ${line}`);
24333
+ throw new Error(
24334
+ `simulation failed: ${JSON.stringify(simulation.value.err)}`
24335
+ );
24336
+ }
24337
+ log(
24338
+ `Vault ${vault}: simulation OK (${simulation.value.unitsConsumed ?? "?"} CU)`
24339
+ );
24340
+ if (dryRun) {
24341
+ log(`Vault ${vault}: dry run \u2014 transaction NOT submitted`);
24342
+ return {};
24343
+ }
24344
+ const signature = await connection.sendRawTransaction(
24345
+ transaction.serialize(),
24346
+ { skipPreflight: false, maxRetries: 5 }
24347
+ );
24348
+ const confirmation = await connection.confirmTransaction(
24349
+ { signature, blockhash, lastValidBlockHeight },
24350
+ "confirmed"
24351
+ );
24352
+ if (confirmation.value.err) {
24353
+ throw new Error(
24354
+ `transaction ${signature} failed: ${JSON.stringify(
24355
+ confirmation.value.err
24356
+ )}`
24357
+ );
24358
+ }
24359
+ this.client.applyCacheInvalidations(plan);
24360
+ log(`Vault ${vault}: swap tx ${signature}`);
24361
+ return { signature };
24362
+ }
24363
+ };
23668
24364
  // Annotate the CommonJS export names for ESM import in node:
23669
24365
  0 && (module.exports = {
23670
24366
  ASSET_DECIMALS,
24367
+ ASSET_REBALANCE_COOLDOWN_SECS,
23671
24368
  AccountClient,
23672
24369
  ActivateCircuitBreakerBuilder,
23673
24370
  CONSENSUS_ORACLE_VARIANT,
@@ -23678,15 +24375,20 @@ var ExternalLiquidityIntegrityService = class {
23678
24375
  CreateAssetHoldingBuilder,
23679
24376
  CreateTrancheStateBuilder,
23680
24377
  CreateVaultBuilder,
24378
+ DEFAULT_CU_PRICE_MICRO_LAMPORTS,
23681
24379
  DEFAULT_JUNIOR_EARLY_UNSTAKE_FEE_BPS,
23682
24380
  DEFAULT_JUNIOR_STANDARD_UNSTAKE_FEE_BPS,
23683
24381
  DEFAULT_JUNIOR_WITHDRAWAL_LOCKUP_SECS,
23684
24382
  DEFAULT_MAX_ACCEPTABLE_APY_BPS,
24383
+ DEFAULT_MAX_ACCOUNTS,
23685
24384
  DEFAULT_MIN_AMOUNT_UI,
23686
24385
  DEFAULT_ORACLE_STALENESS_THRESHOLD_SECS,
23687
24386
  DEFAULT_PRICE_ORACLE_ACCOUNT,
23688
24387
  DEFAULT_PRICE_STALENESS_SECS,
23689
24388
  DEFAULT_PRICE_STALENESS_THRESHOLD_SECS,
24389
+ DEFAULT_REBALANCE_BAND_BPS,
24390
+ DEFAULT_SLIPPAGE_BPS,
24391
+ DEFAULT_TARGET_LOCAL_BPS,
23690
24392
  DEFAULT_VAULT_ID,
23691
24393
  DisableCircuitBreakerBuilder,
23692
24394
  EXTERNAL_POSITION_SAMPLES,
@@ -23700,6 +24402,9 @@ var ExternalLiquidityIntegrityService = class {
23700
24402
  FEE_VAULT_CACHE_CATEGORY,
23701
24403
  FulfillJuniorTrancheWithdrawBuilder,
23702
24404
  IDL,
24405
+ IDLE_RESERVE_FLOOR_BPS,
24406
+ IDLE_RESERVE_TARGET_BPS,
24407
+ IdleLiquidityService,
23703
24408
  InitializeVaultRolesBuilder,
23704
24409
  JupiterBalanceSource,
23705
24410
  JupiterPriceSource,
@@ -23813,6 +24518,7 @@ var ExternalLiquidityIntegrityService = class {
23813
24518
  mintTokensTo,
23814
24519
  mostFrequent,
23815
24520
  parseExternalLiquidityRefs,
24521
+ planRebalance,
23816
24522
  prepareVaultTransaction,
23817
24523
  priceInAccountingUnit,
23818
24524
  readI64LE,