@perena/vault-sdk 1.0.24 → 1.0.25

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.d.ts CHANGED
@@ -8957,11 +8957,11 @@ type Bankineco = {
8957
8957
  "name": "vaultOracle";
8958
8958
  "docs": [
8959
8959
  "Required when enabling tranching organically (`for_migration == false`)",
8960
- "on a vault that still has main shareholders accruing fixed APY: that APY",
8961
- "is only folded into `mint_share_price` while tranching is off, so a fresh",
8962
- "NAV must have been settled just before the switch or the accrued APY owed",
8963
- "to those shareholders is forfeited. Not needed on the migration path,",
8964
- "whose accounting is reconstructed wholesale from the legacy state.",
8960
+ "on a vault that still has main shareholders accruing fixed APY. A fresh",
8961
+ "settlement establishes the regular sleeve's booked NAV and physical",
8962
+ "surplus immediately before the new tranche sleeve is introduced. Not",
8963
+ "needed on the migration path, whose accounting is reconstructed wholesale",
8964
+ "from the legacy state.",
8965
8965
  "",
8966
8966
  "Kept LAST (a trailing optional account) so legacy clients that predate it",
8967
8967
  "— e.g. the migration path building against the frozen bankineco IDL —",
@@ -11900,16 +11900,9 @@ type Bankineco = {
11900
11900
  };
11901
11901
  },
11902
11902
  {
11903
- "name": "migrationInProgress";
11903
+ "name": "paddingBool";
11904
11904
  "docs": [
11905
- "True while a legacy migration destination is still receiving backing",
11906
- "and receipt-supply accounting. Oracle mutation and NAV settlement stay",
11907
- "disabled until every configured receipt mint authority has moved to this vault",
11908
- "and migration finalization clears the flag.",
11909
- "",
11910
- "This occupies the former `apy_settle_skip` padding byte, preserving the",
11911
- "on-chain account layout for already-deployed vaults. Existing vaults read",
11912
- "it as false."
11905
+ "Reserved byte retained for on-chain account layout compatibility."
11913
11906
  ];
11914
11907
  "type": {
11915
11908
  "defined": {
@@ -15955,6 +15948,8 @@ interface DecodedHolding {
15955
15948
  mint: unknown;
15956
15949
  decimals: number;
15957
15950
  priceOracleType: unknown;
15951
+ /** Bound Pyth price-update or nested-vault account for live-priced holdings. */
15952
+ priceOracleAccount?: unknown;
15958
15953
  isBase: unknown;
15959
15954
  localAmount?: unknown;
15960
15955
  externalAmount?: unknown;
@@ -16035,6 +16030,12 @@ interface SettlementSimulation {
16035
16030
  grossNav?: bigint;
16036
16031
  settledAccountingNav?: bigint;
16037
16032
  current: SettlementSimulationSnapshot;
16033
+ /**
16034
+ * True when `projected` was calculated by replaying the otherwise-identical
16035
+ * settlement with the max-APY guard disabled. The real crank remains blocked
16036
+ * until the vault config is updated.
16037
+ */
16038
+ projectionRequiresMaxApyDisabled?: boolean;
16038
16039
  projected?: SettlementSimulationSnapshot;
16039
16040
  performanceFees?: {
16040
16041
  curator: bigint;
@@ -16341,6 +16342,12 @@ declare class ConsensusOracleService {
16341
16342
  log?: (msg: string) => void;
16342
16343
  }): Promise<ConsensusOracleTarget[]>;
16343
16344
  private fetchDecodedVault;
16345
+ /**
16346
+ * Keep active permissionless price sources warm so they cannot block the NAV
16347
+ * crank after the consensus report lands. Prices are refreshed at least once
16348
+ * per hour, or at the vault's shorter staleness interval when configured.
16349
+ */
16350
+ private refreshLiveOraclePrices;
16344
16351
  /**
16345
16352
  * Receipt mints issued by this vault are liabilities/shares, not underlying
16346
16353
  * assets for its own NAV. Exclude them even if they were registered as
@@ -16358,12 +16365,13 @@ declare class ConsensusOracleService {
16358
16365
  /** Push the consensus report for all holdings, then settle NAV. */
16359
16366
  private settleVault;
16360
16367
  /**
16361
- * Fetch the current settled NAV from the oracle account and log the APY
16362
- * implied by the prospective update, so MaxApyExceeded failures are diagnosable.
16368
+ * Fetch the current settlement timestamp and log the APY implied by the
16369
+ * prospective update, so MaxApyExceeded failures are diagnosable.
16363
16370
  *
16364
16371
  * NAV is expressed in the vault's accounting unit (scaled by assetDecimals).
16365
- * prospective NAV = Σ ((localAmount_i + externalAmount_i) * price_i)
16366
- * / 10^assetDecimals
16372
+ * Mirrors `gross_nav_from_holdings`: consensus updates overlay their target
16373
+ * slots, while live-priced and otherwise unchanged holdings retain their
16374
+ * current price/external amount.
16367
16375
  */
16368
16376
  private logProspectiveApy;
16369
16377
  }
package/dist/index.js CHANGED
@@ -1532,7 +1532,8 @@ var PROTOCOL_FEE_RECIPIENT = (0, import_kit.address)(
1532
1532
  var PROGRAM_FEE_EXEMPT_OWNER_WHITELIST = [
1533
1533
  (0, import_kit.address)("Hobbakk1LmW2DhE4nAKnCeU1iK7V7pKHTq3ihaE2i8qG"),
1534
1534
  (0, import_kit.address)("7C39GeKrqLygmgV9D4bicUaiyFhKrX6Eevk6PDACwuj7"),
1535
- (0, import_kit.address)("LYC8YiiSzQfPpxUW2tpxfuPKGZwywAJhXKUfDP2B66f")
1535
+ (0, import_kit.address)("LYC8YiiSzQfPpxUW2tpxfuPKGZwywAJhXKUfDP2B66f"),
1536
+ (0, import_kit.address)("soBBAHNhFyWF4QBCK9BJLVkX6d6NZt37ozNaPV5aq3H")
1536
1537
  ];
1537
1538
  var VAULT_CREATOR_WHITELIST = [
1538
1539
  (0, import_kit.address)("4jMRBkpWrfhhwFYTwG3o7hmsXQwbJUwdyUX8htB6KwLJ")
@@ -10463,11 +10464,11 @@ var IDL = {
10463
10464
  "name": "vault_oracle",
10464
10465
  "docs": [
10465
10466
  "Required when enabling tranching organically (`for_migration == false`)",
10466
- "on a vault that still has main shareholders accruing fixed APY: that APY",
10467
- "is only folded into `mint_share_price` while tranching is off, so a fresh",
10468
- "NAV must have been settled just before the switch or the accrued APY owed",
10469
- "to those shareholders is forfeited. Not needed on the migration path,",
10470
- "whose accounting is reconstructed wholesale from the legacy state.",
10467
+ "on a vault that still has main shareholders accruing fixed APY. A fresh",
10468
+ "settlement establishes the regular sleeve's booked NAV and physical",
10469
+ "surplus immediately before the new tranche sleeve is introduced. Not",
10470
+ "needed on the migration path, whose accounting is reconstructed wholesale",
10471
+ "from the legacy state.",
10471
10472
  "",
10472
10473
  "Kept LAST (a trailing optional account) so legacy clients that predate it",
10473
10474
  "\u2014 e.g. the migration path building against the frozen bankineco IDL \u2014",
@@ -13406,16 +13407,9 @@ var IDL = {
13406
13407
  }
13407
13408
  },
13408
13409
  {
13409
- "name": "migration_in_progress",
13410
+ "name": "_padding_bool",
13410
13411
  "docs": [
13411
- "True while a legacy migration destination is still receiving backing",
13412
- "and receipt-supply accounting. Oracle mutation and NAV settlement stay",
13413
- "disabled until every configured receipt mint authority has moved to this vault",
13414
- "and migration finalization clears the flag.",
13415
- "",
13416
- "This occupies the former `apy_settle_skip` padding byte, preserving the",
13417
- "on-chain account layout for already-deployed vaults. Existing vaults read",
13418
- "it as false."
13412
+ "Reserved byte retained for on-chain account layout compatibility."
13419
13413
  ],
13420
13414
  "type": {
13421
13415
  "defined": {
@@ -19595,6 +19589,7 @@ var MarginfiPositionProvider = class {
19595
19589
  };
19596
19590
 
19597
19591
  // src/services/consensusOracle/liveConsensusOracleDeps.ts
19592
+ var YIELD_TRACKER_DECIMALS = 6;
19598
19593
  function createLiveConsensusOracleDeps(connection, opts = {}) {
19599
19594
  const log = opts.log ?? (() => {
19600
19595
  });
@@ -19605,7 +19600,10 @@ function createLiveConsensusOracleDeps(connection, opts = {}) {
19605
19600
  log
19606
19601
  }),
19607
19602
  yieldTracker: {
19608
- getTotalOutstandingYield,
19603
+ async getTotalOutstandingYield(accountNames, start, end) {
19604
+ const raw = await getTotalOutstandingYield(accountNames, start, end);
19605
+ return raw / 10 ** YIELD_TRACKER_DECIMALS;
19606
+ },
19609
19607
  getYieldPayments
19610
19608
  }
19611
19609
  };
@@ -19744,6 +19742,7 @@ function simulateConsensusOracleSettlement(args) {
19744
19742
  hwmSharePrice: bigint(vaultAccounting.hwmSharePrice),
19745
19743
  fixedApyBps: number(apyConfig.fixedApyBps),
19746
19744
  maxApyBps: number(apyConfig.maxApyBps),
19745
+ accruedApyBalance: bigint(apyConfig.accruedApyBalance),
19747
19746
  apyAnchorSharePrice: bigint(apyConfig.anchorSharePrice),
19748
19747
  apyAnchorTs: bigint(apyConfig.anchorTs),
19749
19748
  tranche: currentTranche
@@ -19763,7 +19762,52 @@ function simulateConsensusOracleSettlement(args) {
19763
19762
  performanceFees: settlement.performanceFees
19764
19763
  };
19765
19764
  } catch (error) {
19766
- blockers.push(message(error));
19765
+ const blocker = message(error);
19766
+ blockers.push(blocker);
19767
+ if (blocker.includes("MaxApyExceeded")) {
19768
+ try {
19769
+ const settlement = settleVault({
19770
+ grossNav,
19771
+ nowTs: args.nowTs,
19772
+ currentTvl,
19773
+ currentSharePrice: bigint(vaultAccounting.mintSharePrice),
19774
+ totalSupply: reconciledSupply,
19775
+ lossesEnabled: bool(args.vault.lossesEnabled),
19776
+ accountingLastUpdateTs: bigint(vaultAccounting.lastUpdateTs),
19777
+ priorSettledNavTs: oracle.settledNavTs,
19778
+ performanceFeeBps: number(feesConfig.performanceFeeBps),
19779
+ protocolFeeBps: number(feesConfig.protocolFeeBps),
19780
+ hwmSharePrice: bigint(vaultAccounting.hwmSharePrice),
19781
+ fixedApyBps: number(apyConfig.fixedApyBps),
19782
+ maxApyBps: 0,
19783
+ accruedApyBalance: bigint(apyConfig.accruedApyBalance),
19784
+ apyAnchorSharePrice: bigint(apyConfig.anchorSharePrice),
19785
+ apyAnchorTs: bigint(apyConfig.anchorTs),
19786
+ tranche: currentTranche
19787
+ });
19788
+ return {
19789
+ nowTs: args.nowTs,
19790
+ assetDecimals: args.vault.config.assetDecimals,
19791
+ ...consensusSummary,
19792
+ canSettle: false,
19793
+ blockers,
19794
+ warnings,
19795
+ consensus,
19796
+ grossNav,
19797
+ settledAccountingNav: settlement.settledAccountingNav,
19798
+ current,
19799
+ projectionRequiresMaxApyDisabled: true,
19800
+ projected: settlement.projected,
19801
+ performanceFees: settlement.performanceFees
19802
+ };
19803
+ } catch (projectionError) {
19804
+ warnings.push(
19805
+ `could not calculate the uncapped projection: ${message(
19806
+ projectionError
19807
+ )}`
19808
+ );
19809
+ }
19810
+ }
19767
19811
  return {
19768
19812
  nowTs: args.nowTs,
19769
19813
  assetDecimals: args.vault.config.assetDecimals,
@@ -19835,16 +19879,18 @@ function applyConsensus(holdings, entries, nowTs, stalenessThresholdSecs) {
19835
19879
  return results;
19836
19880
  }
19837
19881
  function settleVault(args) {
19838
- if (args.grossNav < args.currentTvl && !args.lossesEnabled) {
19882
+ const regularSurplusBefore = args.tranche ? max(args.accruedApyBalance, 0n) : 0n;
19883
+ const physicalNavBefore = args.currentTvl + regularSurplusBefore;
19884
+ if (args.grossNav < physicalNavBefore && !args.lossesEnabled) {
19839
19885
  throw new Error(
19840
- `crank would fail LossesDisabled: gross NAV ${args.grossNav} < current TVL ${args.currentTvl}`
19886
+ `crank would fail LossesDisabled: gross NAV ${args.grossNav} < physical NAV ${physicalNavBefore}`
19841
19887
  );
19842
19888
  }
19843
19889
  const priorTs = args.priorSettledNavTs > 0n ? args.priorSettledNavTs : max(args.accountingLastUpdateTs, args.apyAnchorTs);
19844
19890
  const nextApyValue = args.tranche ? args.grossNav : sharePriceForValue(args.grossNav, args.totalSupply);
19845
19891
  if (nextApyValue !== void 0) {
19846
19892
  validateRealizedApy(
19847
- args.tranche ? args.currentTvl : args.currentSharePrice,
19893
+ args.tranche ? physicalNavBefore : args.currentSharePrice,
19848
19894
  priorTs,
19849
19895
  nextApyValue,
19850
19896
  args.nowTs,
@@ -19857,13 +19903,22 @@ function settleVault(args) {
19857
19903
  let protocolFee = 0n;
19858
19904
  let settledAccountingNav;
19859
19905
  if (args.tranche) {
19860
- settledAccountingNav = args.grossNav;
19861
- regularValue = settleTrancheNav(
19906
+ const regularGrossValue = settleTrancheNav(
19862
19907
  args.tranche,
19863
- args.currentTvl,
19864
- settledAccountingNav,
19908
+ physicalNavBefore,
19909
+ args.grossNav,
19865
19910
  args.nowTs
19866
19911
  );
19912
+ regularValue = settleFixedApyNav({
19913
+ currentSharePrice: args.currentSharePrice,
19914
+ reportedNav: regularGrossValue,
19915
+ nowTs: args.nowTs,
19916
+ totalSupply: args.totalSupply,
19917
+ fixedApyBps: args.fixedApyBps,
19918
+ anchorSharePrice: args.apyAnchorSharePrice,
19919
+ anchorTs: args.apyAnchorTs
19920
+ });
19921
+ settledAccountingNav = regularValue + args.tranche.junior.value + args.tranche.senior.value;
19867
19922
  const juniorFees = settleShareClassPerformanceFee(
19868
19923
  args.tranche.junior,
19869
19924
  args.performanceFeeBps,
@@ -20031,7 +20086,8 @@ function settleRegularPerformanceFee(grossValue, supply, hwmSharePrice, performa
20031
20086
  };
20032
20087
  }
20033
20088
  function settleFixedApyNav(args) {
20034
- if (args.fixedApyBps === 0 || args.currentSharePrice === 0n || args.totalSupply === 0n || args.anchorSharePrice === 0n || args.anchorTs <= 0n) {
20089
+ if (args.totalSupply === 0n) return 0n;
20090
+ if (args.fixedApyBps === 0 || args.currentSharePrice === 0n || args.anchorSharePrice === 0n || args.anchorTs <= 0n) {
20035
20091
  return args.reportedNav;
20036
20092
  }
20037
20093
  if (args.nowTs <= args.anchorTs) {
@@ -20408,6 +20464,9 @@ function formatSettlementSimulation(simulation) {
20408
20464
  );
20409
20465
  }
20410
20466
  if (simulation.projected) {
20467
+ if (simulation.projectionRequiresMaxApyDisabled) {
20468
+ lines.push(" projection with max APY disabled (max_apy_bps=0):");
20469
+ }
20411
20470
  lines.push(
20412
20471
  ` TVL: ${formatTransition(
20413
20472
  simulation.current.tvl,
@@ -20478,8 +20537,23 @@ function formatUnits(value, decimals) {
20478
20537
 
20479
20538
  // src/services/consensusOracle/consensusOracleService.ts
20480
20539
  var SYSTEM_PROGRAM2 = "11111111111111111111111111111111";
20540
+ var PRICE_ORACLE_TYPES_BY_INDEX2 = [
20541
+ "none",
20542
+ "consensusOracle",
20543
+ "pyth",
20544
+ "perenaVault"
20545
+ ];
20481
20546
  var CONSENSUS_ORACLE_VARIANT = "consensusoracle";
20547
+ var LIVE_ORACLE_VARIANTS = /* @__PURE__ */ new Set(["pyth", "perenavault"]);
20548
+ var LIVE_PRICE_REFRESH_INTERVAL_SECS = 60n * 60n;
20549
+ var DEFAULT_PRICE_STALENESS_THRESHOLD_SECS3 = 24n * 60n * 60n;
20482
20550
  function variantName2(value) {
20551
+ if (typeof value === "number") {
20552
+ return PRICE_ORACLE_TYPES_BY_INDEX2[value] ?? String(value);
20553
+ }
20554
+ if (typeof value === "bigint") {
20555
+ return PRICE_ORACLE_TYPES_BY_INDEX2[Number(value)] ?? String(value);
20556
+ }
20483
20557
  if (value && typeof value === "object") return Object.keys(value)[0] ?? "";
20484
20558
  return String(value ?? "");
20485
20559
  }
@@ -20489,6 +20563,13 @@ function coerceBool3(value) {
20489
20563
  const record2 = value;
20490
20564
  return Boolean(record2?.value ?? record2?.[0]);
20491
20565
  }
20566
+ function toBigInt2(value) {
20567
+ if (typeof value === "bigint") return value;
20568
+ if (typeof value === "number") return BigInt(Math.trunc(value));
20569
+ if (typeof value === "string" && value.length > 0) return BigInt(value);
20570
+ if (value !== null && value !== void 0) return BigInt(String(value));
20571
+ return 0n;
20572
+ }
20492
20573
  function priceInAccountingUnit(usd, baseUsd, assetDecimals) {
20493
20574
  return BigInt(Math.round(usd / baseUsd * 10 ** assetDecimals));
20494
20575
  }
@@ -20533,7 +20614,17 @@ var ConsensusOracleService = class {
20533
20614
  const log = opts.log ?? (() => {
20534
20615
  });
20535
20616
  const dryRun = opts.dryRun ?? false;
20536
- const vaultState = await this.fetchDecodedVault(target.vault);
20617
+ let vaultState = await this.fetchDecodedVault(target.vault);
20618
+ const refreshedLivePrices = await this.refreshLiveOraclePrices(
20619
+ signer,
20620
+ target.vault,
20621
+ vaultState,
20622
+ log,
20623
+ dryRun
20624
+ );
20625
+ if (refreshedLivePrices) {
20626
+ vaultState = await this.fetchDecodedVault(target.vault);
20627
+ }
20537
20628
  const receiptMints = await this.fetchReceiptMints(target.vault, vaultState);
20538
20629
  const consensusHoldings = selectConsensusHoldings(
20539
20630
  vaultState.holdings,
@@ -20755,6 +20846,64 @@ var ConsensusOracleService = class {
20755
20846
  fresh: true
20756
20847
  });
20757
20848
  }
20849
+ /**
20850
+ * Keep active permissionless price sources warm so they cannot block the NAV
20851
+ * crank after the consensus report lands. Prices are refreshed at least once
20852
+ * per hour, or at the vault's shorter staleness interval when configured.
20853
+ */
20854
+ async refreshLiveOraclePrices(signer, vault, vaultState, log, dryRun) {
20855
+ const nowSecs = BigInt(Math.floor(this.clock.now() / 1e3));
20856
+ const configuredStaleness = toBigInt2(
20857
+ vaultState.config.priceStalenessThresholdSecs
20858
+ );
20859
+ const stalenessThreshold = configuredStaleness > 0n ? configuredStaleness : DEFAULT_PRICE_STALENESS_THRESHOLD_SECS3;
20860
+ const refreshInterval = stalenessThreshold < LIVE_PRICE_REFRESH_INTERVAL_SECS ? stalenessThreshold : LIVE_PRICE_REFRESH_INTERVAL_SECS;
20861
+ const targets = [];
20862
+ for (const holding of vaultState.holdings) {
20863
+ const oracleType = variantName2(holding.priceOracleType).toLowerCase();
20864
+ if (!LIVE_ORACLE_VARIANTS.has(oracleType)) continue;
20865
+ const mint = (0, import_common41.fromWeb3Pk)(holding.mint);
20866
+ if (mint.toString() === SYSTEM_PROGRAM2) continue;
20867
+ const activeAmount = toBigInt2(holding.localAmount) + toBigInt2(holding.externalAmount);
20868
+ if (activeAmount === 0n) continue;
20869
+ const lastUpdateTs = toBigInt2(holding.lastUpdateTs);
20870
+ const ageSecs = nowSecs - lastUpdateTs;
20871
+ if (lastUpdateTs > 0n && ageSecs >= 0n && ageSecs < refreshInterval) {
20872
+ continue;
20873
+ }
20874
+ if (!holding.priceOracleAccount) {
20875
+ log(
20876
+ `vault ${vault}: cannot refresh ${mint}; ${oracleType} holding has no oracle account`
20877
+ );
20878
+ continue;
20879
+ }
20880
+ const oracleAccount = (0, import_common41.fromWeb3Pk)(holding.priceOracleAccount);
20881
+ if (oracleAccount.toString() === SYSTEM_PROGRAM2) {
20882
+ log(
20883
+ `vault ${vault}: cannot refresh ${mint}; ${oracleType} holding has no oracle account`
20884
+ );
20885
+ continue;
20886
+ }
20887
+ targets.push({ mint, oracleAccount });
20888
+ }
20889
+ if (targets.length === 0) return false;
20890
+ if (dryRun) {
20891
+ log(
20892
+ `vault ${vault}: dry run, would refresh ${targets.length} live asset price(s)`
20893
+ );
20894
+ return false;
20895
+ }
20896
+ log(
20897
+ `vault ${vault}: refreshing ${targets.length} live asset price(s) due for hourly refresh`
20898
+ );
20899
+ const results = await this.oracle.refreshAssetPrices(
20900
+ signer,
20901
+ vault,
20902
+ targets,
20903
+ log
20904
+ );
20905
+ return results.some((result) => Boolean(result.signature));
20906
+ }
20758
20907
  /**
20759
20908
  * Receipt mints issued by this vault are liabilities/shares, not underlying
20760
20909
  * assets for its own NAV. Exclude them even if they were registered as
@@ -20922,12 +21071,13 @@ var ConsensusOracleService = class {
20922
21071
  };
20923
21072
  }
20924
21073
  /**
20925
- * Fetch the current settled NAV from the oracle account and log the APY
20926
- * implied by the prospective update, so MaxApyExceeded failures are diagnosable.
21074
+ * Fetch the current settlement timestamp and log the APY implied by the
21075
+ * prospective update, so MaxApyExceeded failures are diagnosable.
20927
21076
  *
20928
21077
  * NAV is expressed in the vault's accounting unit (scaled by assetDecimals).
20929
- * prospective NAV = Σ ((localAmount_i + externalAmount_i) * price_i)
20930
- * / 10^assetDecimals
21078
+ * Mirrors `gross_nav_from_holdings`: consensus updates overlay their target
21079
+ * slots, while live-priced and otherwise unchanged holdings retain their
21080
+ * current price/external amount.
20931
21081
  */
20932
21082
  async logProspectiveApy(vault, updates, vaultState, log) {
20933
21083
  try {
@@ -20936,22 +21086,38 @@ var ConsensusOracleService = class {
20936
21086
  });
20937
21087
  const data = oracle.data;
20938
21088
  if (!data || data.length < 16) return;
20939
- const settledNav = readI64LE(data, 0);
20940
21089
  const settledNavTs = readI64LE(data, 8);
20941
- if (settledNav <= 0n || settledNavTs <= 0n) return;
20942
- const scale = BigInt(10 ** vaultState.config.assetDecimals);
20943
- const prospectiveNav = updates.reduce((sum, u) => {
20944
- const holding = vaultState.holdings[u.holdingIndex];
20945
- const localAmount = BigInt(holding?.localAmount?.toString() ?? "0");
20946
- return sum + (localAmount + u.externalAmount) * u.price / scale;
20947
- }, 0n);
21090
+ if (settledNavTs <= 0n) return;
21091
+ const updatesByIndex = new Map(
21092
+ updates.map((update) => [update.holdingIndex, update])
21093
+ );
21094
+ const prospectiveNav = vaultState.holdings.reduce(
21095
+ (sum, holding, holdingIndex) => {
21096
+ const mint = (0, import_common41.fromWeb3Pk)(holding.mint).toString();
21097
+ if (mint === SYSTEM_PROGRAM2) return sum;
21098
+ const update = updatesByIndex.get(holdingIndex);
21099
+ const localAmount = toBigInt2(holding.localAmount);
21100
+ const externalAmount = update ? update.externalAmount : toBigInt2(holding.externalAmount);
21101
+ const amount = localAmount + externalAmount;
21102
+ if (amount === 0n) return sum;
21103
+ const price = update ? update.price : toBigInt2(holding.price);
21104
+ if (price <= 0n) return sum;
21105
+ const scale = 10n ** BigInt(holding.decimals);
21106
+ return sum + amount * price / scale;
21107
+ },
21108
+ 0n
21109
+ );
21110
+ const currentTvl = toBigInt2(vaultState.accounting?.tvl);
21111
+ const accruedApyBalance = coerceBool3(vaultState.tranchingEnabled) ? toBigInt2(vaultState.config.apy?.accruedApyBalance) : 0n;
21112
+ const physicalNavBefore = currentTvl + (accruedApyBalance > 0n ? accruedApyBalance : 0n);
21113
+ if (physicalNavBefore <= 0n) return;
20948
21114
  const nowSecs = BigInt(Math.floor(this.clock.now() / 1e3));
20949
21115
  const elapsedSecs = nowSecs - settledNavTs;
20950
21116
  if (elapsedSecs <= 0n) return;
20951
21117
  const SECONDS_PER_YEAR3 = 365n * 24n * 3600n;
20952
- const apyBps = (prospectiveNav - settledNav) * SECONDS_PER_YEAR3 * 10000n / (settledNav * elapsedSecs);
21118
+ const apyBps = (prospectiveNav - physicalNavBefore) * SECONDS_PER_YEAR3 * 10000n / (physicalNavBefore * elapsedSecs);
20953
21119
  log(
20954
- `vault ${vault}: prospective NAV ${prospectiveNav} vs settled ${settledNav} (elapsed ${elapsedSecs}s) \u2192 implied APY ${apyBps}bps`
21120
+ `vault ${vault}: prospective NAV ${prospectiveNav} vs physical baseline ${physicalNavBefore} (elapsed ${elapsedSecs}s) \u2192 implied APY ${apyBps}bps`
20955
21121
  );
20956
21122
  } catch {
20957
21123
  }
@@ -21210,7 +21376,7 @@ function parseActiveSlots(externalLiquidity) {
21210
21376
  }
21211
21377
  return results;
21212
21378
  }
21213
- function toBigInt2(value) {
21379
+ function toBigInt3(value) {
21214
21380
  if (typeof value === "bigint") return value;
21215
21381
  if (value !== null && value !== void 0) return BigInt(String(value));
21216
21382
  return 0n;
@@ -21290,7 +21456,7 @@ var ExternalLiquidityIntegrityService = class {
21290
21456
  }
21291
21457
  const holdings = vaultState.holdings.filter((h) => {
21292
21458
  const mint = pubkeyStr(h.mint);
21293
- return !isZeroPubkey2(mint) && toBigInt2(h.localAmount) > 0n;
21459
+ return !isZeroPubkey2(mint) && toBigInt3(h.localAmount) > 0n;
21294
21460
  });
21295
21461
  if (holdings.length === 0) {
21296
21462
  log(`Vault ${vault} slot ${slot.index}: no local balance to deploy`);
@@ -21299,7 +21465,7 @@ var ExternalLiquidityIntegrityService = class {
21299
21465
  }
21300
21466
  for (const holding of holdings) {
21301
21467
  const mintAddress = (0, import_kit15.address)(pubkeyStr(holding.mint));
21302
- const localAmount = toBigInt2(holding.localAmount);
21468
+ const localAmount = toBigInt3(holding.localAmount);
21303
21469
  const marginfiAccounts = (0, import_marginfi2.marginfiAccountsForMint)(mintAddress);
21304
21470
  if (!marginfiAccounts) {
21305
21471
  log(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perena/vault-sdk",
3
- "version": "1.0.24",
3
+ "version": "1.0.25",
4
4
  "description": "Vault program helpers for Bankineco integrations.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -38,6 +38,7 @@
38
38
  "create-vault": "tsx scripts/createVault.ts",
39
39
  "init-vault-atas": "tsx scripts/initVaultAtas.ts",
40
40
  "inspect": "tsx scripts/inspect.ts",
41
+ "vault-data": "tsx scripts/fetchVaultData.ts",
41
42
  "simulate-withdraw": "tsx scripts/simulate-withdraw.ts",
42
43
  "simulate-message": "tsx scripts/simulate-message.ts",
43
44
  "run-external-liquidity-service": "tsx scripts/runExternalLiquidityService.ts"
@@ -56,6 +57,7 @@
56
57
  },
57
58
  "devDependencies": {
58
59
  "common": "file:../common",
60
+ "jupiter": "file:../jupiter",
59
61
  "marginfi": "file:../marginfi",
60
62
  "@types/node": "^24.3.1",
61
63
  "prettier": "^2.6.2",