@perena/vault-sdk 1.0.25 → 1.0.26

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
@@ -10146,7 +10146,9 @@ type Bankineco = {
10146
10146
  "Holdings are addressed by their slot index (1 byte) to keep instruction data",
10147
10147
  "small. To register a brand-new consensus holding, set `mint` to its mint",
10148
10148
  "(and pass the matching `asset_mint` account); the created slot's index is used",
10149
- "and `holding_index` is ignored. For existing holdings leave `mint` as `None`."
10149
+ "and `holding_index` is ignored. For existing holdings leave `mint` as `None`.",
10150
+ "The price is authoritative only for ConsensusOracle holdings. Pyth and nested-vault",
10151
+ "holdings use the report only for external_amount and retain their live-oracle price."
10150
10152
  ];
10151
10153
  "type": {
10152
10154
  "kind": "struct";
@@ -11891,7 +11893,7 @@ type Bankineco = {
11891
11893
  {
11892
11894
  "name": "lossesEnabled";
11893
11895
  "docs": [
11894
- "Allows consensus-oracle NAV updates below the vault's current TVL."
11896
+ "Allows consensus-oracle NAV losses beyond the protocol's small-loss tolerance. Losses inside that tolerance settle without this flag."
11895
11897
  ];
11896
11898
  "type": {
11897
11899
  "defined": {
@@ -15864,10 +15866,20 @@ interface YieldPaymentSnapshot {
15864
15866
  * helpers for tests and local operation.
15865
15867
  */
15866
15868
  interface YieldTracker {
15869
+ /** Account names tagged to a vault. Live prod discovery uses this automatically. */
15870
+ getAccountNamesForVault?(vault: Address): Promise<string[]>;
15867
15871
  /** All payments for an account, including payments not yet confirmed. */
15868
15872
  getYieldPayments(accountName: string): Promise<YieldPaymentSnapshot[]>;
15869
15873
  /** Sum of oracle-safe outstanding yield across the named accounts. */
15870
15874
  getTotalOutstandingYield(accountNames: string[], start?: Date, end?: Date): Promise<number>;
15875
+ /**
15876
+ * Sum the external principal and confirmed-payment-adjusted outstanding yield
15877
+ * across the named accounts. Both values are UI token amounts.
15878
+ */
15879
+ getTotalTrackedAmounts(accountNames: string[], start?: Date, end?: Date): Promise<{
15880
+ principal: number;
15881
+ outstandingYield: number;
15882
+ }>;
15871
15883
  }
15872
15884
  /** Injectable wall clock (ms since epoch). Lets tests pin time deterministically. */
15873
15885
  interface Clock {
@@ -15911,7 +15923,9 @@ interface HoldingUpdatePreview {
15911
15923
  lpAmount: bigint;
15912
15924
  /** Accrued yield attributed to this holding (base units). */
15913
15925
  yieldAmount: bigint;
15914
- /** Total external balance pushed = wallet + lp + yield. */
15926
+ /** Yield-tracker principal attributed to this holding (base units). */
15927
+ trackedPrincipalAmount: bigint;
15928
+ /** Total external balance pushed = wallet + LP + tracked principal + yield. */
15915
15929
  externalAmount: bigint;
15916
15930
  }
15917
15931
  interface VaultOracleResult {
@@ -16236,6 +16250,8 @@ declare function runLiveConsensusOracle(env: VaultEnv, oracleSigner: Keypair, op
16236
16250
  */
16237
16251
 
16238
16252
  interface MockYieldAccountConfig {
16253
+ /** Optional vault address used by production-style account discovery. */
16254
+ tag?: string;
16239
16255
  aprBps: number;
16240
16256
  /** Initial principal (UI amount). Defaults to 0. */
16241
16257
  principal?: number;
@@ -16253,10 +16269,15 @@ declare class MockYieldTracker implements YieldTracker {
16253
16269
  /** Register (or replace) a tracked account. */
16254
16270
  setAccount(name: string, cfg: MockYieldAccountConfig): void;
16255
16271
  private require;
16272
+ getAccountNamesForVault(vault: string): Promise<string[]>;
16256
16273
  getYieldPayments(accountName: string): Promise<YieldPaymentSnapshot[]>;
16257
16274
  getYield(accountName: string, start?: Date, end?: Date): Promise<YieldAccountSnapshot>;
16258
16275
  getTotalYield(accountNames: string[], start?: Date, end?: Date): Promise<number>;
16259
16276
  getTotalOutstandingYield(accountNames: string[], start?: Date, end?: Date): Promise<number>;
16277
+ getTotalTrackedAmounts(accountNames: string[], start?: Date, end?: Date): Promise<{
16278
+ principal: number;
16279
+ outstandingYield: number;
16280
+ }>;
16260
16281
  getBalance(accountName: string, atTime?: Date): Promise<number>;
16261
16282
  addCashflow(params: {
16262
16283
  accountName: string;
@@ -16325,6 +16346,12 @@ declare class ConsensusOracleService {
16325
16346
  * balance source used by this service. Until then, no oracle update is safe.
16326
16347
  */
16327
16348
  private assertNoUnconfirmedYieldPayments;
16349
+ /**
16350
+ * In production, yield accounts are tagged with their vault address. Attach
16351
+ * newly discovered accounts to the base holding, while preserving explicit
16352
+ * per-mint mappings supplied by config or CLI flags.
16353
+ */
16354
+ private withDiscoveredYieldAccounts;
16328
16355
  /**
16329
16356
  * Drive several vaults in sequence. Non-fatal: a failure on one vault is
16330
16357
  * recorded and the rest still run (mirrors the oracle-updater daemon).
@@ -16358,8 +16385,8 @@ declare class ConsensusOracleService {
16358
16385
  private gatherPricingInputs;
16359
16386
  /** Live-LP balances for the target, summed by mint (empty if no provider). */
16360
16387
  private fetchExternalPositions;
16361
- /** Accrued yield (base units) attributed to a holding via its configured accounts. */
16362
- private resolveYieldAmount;
16388
+ /** Tracked external principal + outstanding yield attributed to a holding. */
16389
+ private resolveTrackedAmounts;
16363
16390
  private buildUpdates;
16364
16391
  private buildHoldingUpdate;
16365
16392
  /** Push the consensus report for all holdings, then settle NAV. */
package/dist/index.js CHANGED
@@ -11653,7 +11653,9 @@ var IDL = {
11653
11653
  "Holdings are addressed by their slot index (1 byte) to keep instruction data",
11654
11654
  "small. To register a brand-new consensus holding, set `mint` to its mint",
11655
11655
  "(and pass the matching `asset_mint` account); the created slot's index is used",
11656
- "and `holding_index` is ignored. For existing holdings leave `mint` as `None`."
11656
+ "and `holding_index` is ignored. For existing holdings leave `mint` as `None`.",
11657
+ "The price is authoritative only for ConsensusOracle holdings. Pyth and nested-vault",
11658
+ "holdings use the report only for external_amount and retain their live-oracle price."
11657
11659
  ],
11658
11660
  "type": {
11659
11661
  "kind": "struct",
@@ -13398,7 +13400,7 @@ var IDL = {
13398
13400
  {
13399
13401
  "name": "losses_enabled",
13400
13402
  "docs": [
13401
- "Allows consensus-oracle NAV updates below the vault's current TVL."
13403
+ "Allows consensus-oracle NAV losses beyond the protocol's small-loss tolerance. Losses inside that tolerance settle without this flag."
13402
13404
  ],
13403
13405
  "type": {
13404
13406
  "defined": {
@@ -19600,10 +19602,27 @@ function createLiveConsensusOracleDeps(connection, opts = {}) {
19600
19602
  log
19601
19603
  }),
19602
19604
  yieldTracker: {
19605
+ async getAccountNamesForVault(vault) {
19606
+ const accounts2 = await getAccounts();
19607
+ return accounts2.filter((account) => account.tag === vault.toString()).map((account) => account.name);
19608
+ },
19603
19609
  async getTotalOutstandingYield(accountNames, start, end) {
19604
19610
  const raw = await getTotalOutstandingYield(accountNames, start, end);
19605
19611
  return raw / 10 ** YIELD_TRACKER_DECIMALS;
19606
19612
  },
19613
+ async getTotalTrackedAmounts(accountNames, start, end) {
19614
+ let principal = 0;
19615
+ let outstandingYield = 0;
19616
+ for (const accountName of accountNames) {
19617
+ const snapshot2 = await getYield(accountName, start, end);
19618
+ principal += snapshot2.principal_at_end;
19619
+ outstandingYield += snapshot2.outstanding_yield;
19620
+ }
19621
+ return {
19622
+ principal: principal / 10 ** YIELD_TRACKER_DECIMALS,
19623
+ outstandingYield: outstandingYield / 10 ** YIELD_TRACKER_DECIMALS
19624
+ };
19625
+ },
19607
19626
  getYieldPayments
19608
19627
  }
19609
19628
  };
@@ -19628,6 +19647,7 @@ var import_kit14 = require("@solana/kit");
19628
19647
  var import_web326 = require("@solana/web3.js");
19629
19648
  var DEFAULT_SHARE_PRICE = 1000000n;
19630
19649
  var BPS_DENOMINATOR2 = 10000n;
19650
+ var NAV_LOSS_TOLERANCE_BPS = 25n;
19631
19651
  var SECONDS_PER_YEAR = 31536000n;
19632
19652
  var DEFAULT_STALENESS_SECS = 86400n;
19633
19653
  var CONSENSUS_MAX_DIFF_BPS = 8n;
@@ -19825,18 +19845,19 @@ function applyConsensus(holdings, entries, nowTs, stalenessThresholdSecs) {
19825
19845
  const results = [];
19826
19846
  for (let index = 0; index < holdings.length; index += 1) {
19827
19847
  const holding = holdings[index];
19828
- if (holding.cleared || holding.priceOracleType !== "consensusoracle") {
19848
+ if (holding.cleared || holding.priceOracleType === "none") {
19829
19849
  continue;
19830
19850
  }
19831
19851
  const reports = entries.filter(
19832
19852
  (entry) => entry.signer !== import_web326.PublicKey.default.toBase58() && isFresh(entry.lastUpdateTs, nowTs, stalenessThresholdSecs) && (entry.prices[index] ?? 0n) > 0n
19833
19853
  );
19854
+ const usesConsensusPrice = holding.priceOracleType === "consensusoracle";
19834
19855
  const prices = reports.map((entry) => entry.prices[index] ?? 0n);
19835
19856
  const amounts = reports.map((entry) => entry.externalAmounts[index] ?? 0n);
19836
19857
  let reason;
19837
19858
  if (reports.length < MIN_CONSENSUS_SIGNERS) {
19838
19859
  reason = `only ${reports.length} fresh priced report(s); need ${MIN_CONSENSUS_SIGNERS}`;
19839
- } else if (!withinDeviation(prices)) {
19860
+ } else if (usesConsensusPrice && !withinDeviation(prices)) {
19840
19861
  reason = `price reports exceed ${CONSENSUS_MAX_DIFF_BPS}bps pairwise deviation`;
19841
19862
  } else if (!withinDeviation(amounts)) {
19842
19863
  reason = `external-amount reports exceed ${CONSENSUS_MAX_DIFF_BPS}bps pairwise deviation`;
@@ -19851,18 +19872,20 @@ function applyConsensus(holdings, entries, nowTs, stalenessThresholdSecs) {
19851
19872
  });
19852
19873
  continue;
19853
19874
  }
19854
- const settledPrice = average(prices);
19875
+ const settledPrice = usesConsensusPrice ? average(prices) : void 0;
19855
19876
  const settledExternalAmount = average(amounts);
19856
19877
  const latestTs = reports.reduce(
19857
19878
  (latest, entry) => entry.lastUpdateTs > latest ? entry.lastUpdateTs : latest,
19858
19879
  0n
19859
19880
  );
19860
- const wouldUpdateHolding = latestTs > holding.lastUpdateTs && latestTs <= nowTs;
19881
+ const wouldUpdateHolding = usesConsensusPrice ? latestTs > holding.lastUpdateTs && latestTs <= nowTs : latestTs <= nowTs;
19861
19882
  if (wouldUpdateHolding) {
19862
- holding.price = settledPrice;
19883
+ if (settledPrice !== void 0) {
19884
+ holding.price = settledPrice;
19885
+ holding.lastUpdateTs = latestTs;
19886
+ }
19863
19887
  holding.externalAmount = settledExternalAmount;
19864
- holding.lastUpdateTs = latestTs;
19865
- if (!holding.isBase && holding.localAmount === 0n && holding.externalAmount === 0n) {
19888
+ if (usesConsensusPrice && !holding.isBase && holding.localAmount === 0n && holding.externalAmount === 0n) {
19866
19889
  holding.cleared = true;
19867
19890
  }
19868
19891
  }
@@ -19881,9 +19904,11 @@ function applyConsensus(holdings, entries, nowTs, stalenessThresholdSecs) {
19881
19904
  function settleVault(args) {
19882
19905
  const regularSurplusBefore = args.tranche ? max(args.accruedApyBalance, 0n) : 0n;
19883
19906
  const physicalNavBefore = args.currentTvl + regularSurplusBefore;
19884
- if (args.grossNav < physicalNavBefore && !args.lossesEnabled) {
19907
+ const loss = args.grossNav < physicalNavBefore ? physicalNavBefore - args.grossNav : 0n;
19908
+ const lossExceedsTolerance = loss * BPS_DENOMINATOR2 > physicalNavBefore * NAV_LOSS_TOLERANCE_BPS;
19909
+ if (lossExceedsTolerance && !args.lossesEnabled) {
19885
19910
  throw new Error(
19886
- `crank would fail LossesDisabled: gross NAV ${args.grossNav} < physical NAV ${physicalNavBefore}`
19911
+ `crank would fail LossesDisabled: gross NAV ${args.grossNav} is more than ${NAV_LOSS_TOLERANCE_BPS}bps below physical NAV ${physicalNavBefore}`
19887
19912
  );
19888
19913
  }
19889
19914
  const priorTs = args.priorSettledNavTs > 0n ? args.priorSettledNavTs : max(args.accountingLastUpdateTs, args.apyAnchorTs);
@@ -20545,6 +20570,10 @@ var PRICE_ORACLE_TYPES_BY_INDEX2 = [
20545
20570
  ];
20546
20571
  var CONSENSUS_ORACLE_VARIANT = "consensusoracle";
20547
20572
  var LIVE_ORACLE_VARIANTS = /* @__PURE__ */ new Set(["pyth", "perenavault"]);
20573
+ var REPORTABLE_ORACLE_VARIANTS = /* @__PURE__ */ new Set([
20574
+ CONSENSUS_ORACLE_VARIANT,
20575
+ ...LIVE_ORACLE_VARIANTS
20576
+ ]);
20548
20577
  var LIVE_PRICE_REFRESH_INTERVAL_SECS = 60n * 60n;
20549
20578
  var DEFAULT_PRICE_STALENESS_THRESHOLD_SECS3 = 24n * 60n * 60n;
20550
20579
  function variantName2(value) {
@@ -20626,7 +20655,7 @@ var ConsensusOracleService = class {
20626
20655
  vaultState = await this.fetchDecodedVault(target.vault);
20627
20656
  }
20628
20657
  const receiptMints = await this.fetchReceiptMints(target.vault, vaultState);
20629
- const consensusHoldings = selectConsensusHoldings(
20658
+ const reportableHoldings = selectReportableHoldings(
20630
20659
  vaultState.holdings,
20631
20660
  receiptMints
20632
20661
  );
@@ -20638,18 +20667,24 @@ var ConsensusOracleService = class {
20638
20667
  `vault ${target.vault}: excluded ${filteredCount} vault/tranche receipt mint holding(s)`
20639
20668
  );
20640
20669
  }
20641
- if (consensusHoldings.length === 0) {
20642
- log(`vault ${target.vault}: no consensus holdings, skipping`);
20670
+ if (reportableHoldings.length === 0) {
20671
+ log(`vault ${target.vault}: no oracle-priced holdings, skipping`);
20643
20672
  return { vault: target.vault, updates: [], dryRun };
20644
20673
  }
20645
- const filteredTarget = filterTargetHoldings(target, receiptMints);
20674
+ let filteredTarget = filterTargetHoldings(target, receiptMints);
20675
+ filteredTarget = await this.withDiscoveredYieldAccounts(
20676
+ filteredTarget,
20677
+ vaultState,
20678
+ receiptMints,
20679
+ log
20680
+ );
20646
20681
  await this.assertNoUnconfirmedYieldPayments(filteredTarget);
20647
20682
  const inputs = await this.gatherPricingInputs(
20648
20683
  filteredTarget,
20649
20684
  vaultState,
20650
- consensusHoldings
20685
+ reportableHoldings
20651
20686
  );
20652
- const updates = await this.buildUpdates(consensusHoldings, inputs);
20687
+ const updates = await this.buildUpdates(reportableHoldings, inputs);
20653
20688
  if (dryRun) {
20654
20689
  log(`vault ${target.vault}: dry run, ${updates.length} holding(s)`);
20655
20690
  try {
@@ -20758,6 +20793,56 @@ var ConsensusOracleService = class {
20758
20793
  }
20759
20794
  }
20760
20795
  }
20796
+ /**
20797
+ * In production, yield accounts are tagged with their vault address. Attach
20798
+ * newly discovered accounts to the base holding, while preserving explicit
20799
+ * per-mint mappings supplied by config or CLI flags.
20800
+ */
20801
+ async withDiscoveredYieldAccounts(target, vaultState, excludedMints, log) {
20802
+ const yieldTracker = this.deps.yieldTracker;
20803
+ if (!yieldTracker?.getAccountNamesForVault) return target;
20804
+ const discovered = [
20805
+ ...new Set(await yieldTracker.getAccountNamesForVault(target.vault))
20806
+ ];
20807
+ const alreadyConfigured = new Set(
20808
+ (target.holdings ?? []).flatMap(
20809
+ (holding) => holding.yieldAccountNames ?? []
20810
+ )
20811
+ );
20812
+ const namesToAttach = discovered.filter(
20813
+ (name) => !alreadyConfigured.has(name)
20814
+ );
20815
+ if (namesToAttach.length === 0) return target;
20816
+ const baseHolding = vaultState.holdings.find((holding) => {
20817
+ const mint = (0, import_common41.fromWeb3Pk)(holding.mint).toString();
20818
+ return coerceBool3(holding.isBase) && !excludedMints.has(mint);
20819
+ });
20820
+ if (!baseHolding) {
20821
+ throw new Error(
20822
+ `vault ${target.vault}: tagged yield accounts found but no base holding is available`
20823
+ );
20824
+ }
20825
+ const baseMint = (0, import_common41.fromWeb3Pk)(baseHolding.mint);
20826
+ const holdings = [...target.holdings ?? []];
20827
+ const existingIndex = holdings.findIndex(
20828
+ (holding) => holding.mint.toString() === baseMint.toString()
20829
+ );
20830
+ if (existingIndex >= 0) {
20831
+ const existing = holdings[existingIndex];
20832
+ holdings[existingIndex] = {
20833
+ ...existing,
20834
+ yieldAccountNames: [
20835
+ .../* @__PURE__ */ new Set([...existing.yieldAccountNames ?? [], ...namesToAttach])
20836
+ ]
20837
+ };
20838
+ } else {
20839
+ holdings.push({ mint: baseMint, yieldAccountNames: namesToAttach });
20840
+ }
20841
+ log(
20842
+ `vault ${target.vault}: attached ${namesToAttach.length} tagged yield account(s) to base holding ${baseMint}`
20843
+ );
20844
+ return { ...target, holdings };
20845
+ }
20761
20846
  /**
20762
20847
  * Drive several vaults in sequence. Non-fatal: a failure on one vault is
20763
20848
  * recorded and the rest still run (mirrors the oracle-updater daemon).
@@ -20927,17 +21012,21 @@ var ConsensusOracleService = class {
20927
21012
  return receiptMints;
20928
21013
  }
20929
21014
  /** Fetch every external input the per-holding updates draw on, in parallel-ish. */
20930
- async gatherPricingInputs(target, vaultState, consensusHoldings) {
21015
+ async gatherPricingInputs(target, vaultState, reportableHoldings) {
20931
21016
  const manager = (0, import_common41.fromWeb3Pk)(vaultState.roles.manager);
20932
- const holdingMints = consensusHoldings.map(
20933
- ({ holding }) => (0, import_common41.fromWeb3Pk)(holding.mint)
20934
- );
21017
+ const consensusMints = reportableHoldings.filter(
21018
+ ({ holding }) => variantName2(holding.priceOracleType).toLowerCase() === CONSENSUS_ORACLE_VARIANT
21019
+ ).map(({ holding }) => (0, import_common41.fromWeb3Pk)(holding.mint));
20935
21020
  const baseHolding = vaultState.holdings.find((h) => coerceBool3(h.isBase));
20936
- const baseMint = baseHolding ? (0, import_common41.fromWeb3Pk)(baseHolding.mint) : holdingMints[0];
21021
+ const baseMint = baseHolding ? (0, import_common41.fromWeb3Pk)(baseHolding.mint) : consensusMints[0];
20937
21022
  if (!baseMint) {
20938
21023
  throw new Error(`vault has no holdings \u2014 cannot determine base asset`);
20939
21024
  }
20940
- const mintsToPrice = holdingMints.includes(baseMint) ? holdingMints : [...holdingMints, baseMint];
21025
+ const mintsToPrice = [
21026
+ ...new Map(
21027
+ [...consensusMints, baseMint].map((mint) => [mint.toString(), mint])
21028
+ ).values()
21029
+ ];
20941
21030
  const usdPrices = await this.deps.priceSource.fetchUsdPrices(mintsToPrice);
20942
21031
  const baseUsd = usdPrices[baseMint.toString()];
20943
21032
  if (!baseUsd) {
@@ -20986,18 +21075,23 @@ var ConsensusOracleService = class {
20986
21075
  });
20987
21076
  return sumPositionsByMint(positions);
20988
21077
  }
20989
- /** Accrued yield (base units) attributed to a holding via its configured accounts. */
20990
- async resolveYieldAmount(cfg, decimals) {
20991
- if (!this.deps.yieldTracker || !cfg?.yieldAccountNames?.length) return 0n;
20992
- const totalYieldUi = await this.deps.yieldTracker.getTotalOutstandingYield(
21078
+ /** Tracked external principal + outstanding yield attributed to a holding. */
21079
+ async resolveTrackedAmounts(cfg, decimals) {
21080
+ if (!this.deps.yieldTracker || !cfg?.yieldAccountNames?.length) {
21081
+ return { principalAmount: 0n, yieldAmount: 0n };
21082
+ }
21083
+ const tracked = await this.deps.yieldTracker.getTotalTrackedAmounts(
20993
21084
  cfg.yieldAccountNames
20994
21085
  );
20995
- return totalYieldUi > 0 ? (0, import_common41.fromUiAmount)(totalYieldUi, decimals) : 0n;
21086
+ return {
21087
+ principalAmount: tracked.principal > 0 ? (0, import_common41.fromUiAmount)(tracked.principal, decimals) : 0n,
21088
+ yieldAmount: tracked.outstandingYield > 0 ? (0, import_common41.fromUiAmount)(tracked.outstandingYield, decimals) : 0n
21089
+ };
20996
21090
  }
20997
21091
  // ── Aggregation ──────────────────────────────────────────────────────────────
20998
- async buildUpdates(consensusHoldings, inputs) {
21092
+ async buildUpdates(reportableHoldings, inputs) {
20999
21093
  const updates = [];
21000
- for (const entry of consensusHoldings) {
21094
+ for (const entry of reportableHoldings) {
21001
21095
  updates.push(await this.buildHoldingUpdate(entry, inputs));
21002
21096
  }
21003
21097
  return updates;
@@ -21006,18 +21100,25 @@ var ConsensusOracleService = class {
21006
21100
  const { holding, holdingIndex } = entry;
21007
21101
  const mint = (0, import_common41.fromWeb3Pk)(holding.mint);
21008
21102
  const mintKey = mint.toString();
21009
- const usd = coerceBool3(holding.isBase) ? inputs.baseUsd : inputs.usdPrices[mintKey];
21010
- if (!usd) {
21011
- throw new Error(`no USD price for holding ${mint}`);
21103
+ const oracleType = variantName2(holding.priceOracleType).toLowerCase();
21104
+ let usd;
21105
+ let price;
21106
+ if (oracleType === CONSENSUS_ORACLE_VARIANT) {
21107
+ usd = coerceBool3(holding.isBase) ? inputs.baseUsd : inputs.usdPrices[mintKey];
21108
+ if (!usd) throw new Error(`no USD price for holding ${mint}`);
21109
+ price = priceInAccountingUnit(usd, inputs.baseUsd, inputs.assetDecimals);
21110
+ } else {
21111
+ price = toBigInt2(holding.price);
21112
+ if (price <= 0n) {
21113
+ throw new Error(
21114
+ `live-oracle holding ${mint} has no on-chain price to preserve`
21115
+ );
21116
+ }
21117
+ usd = Number(price) / 10 ** inputs.assetDecimals * inputs.baseUsd;
21012
21118
  }
21013
- const price = priceInAccountingUnit(
21014
- usd,
21015
- inputs.baseUsd,
21016
- inputs.assetDecimals
21017
- );
21018
21119
  const walletAmount = inputs.walletBalances[mintKey] ?? 0n;
21019
21120
  const lpAmount = inputs.lpByMint.get(mintKey) ?? 0n;
21020
- const yieldAmount = await this.resolveYieldAmount(
21121
+ const { principalAmount, yieldAmount } = await this.resolveTrackedAmounts(
21021
21122
  inputs.configByMint.get(mintKey),
21022
21123
  holding.decimals
21023
21124
  );
@@ -21030,7 +21131,8 @@ var ConsensusOracleService = class {
21030
21131
  walletAmount,
21031
21132
  lpAmount,
21032
21133
  yieldAmount,
21033
- externalAmount: walletAmount + lpAmount + yieldAmount
21134
+ trackedPrincipalAmount: principalAmount,
21135
+ externalAmount: walletAmount + lpAmount + principalAmount + yieldAmount
21034
21136
  };
21035
21137
  }
21036
21138
  // ── Settlement ───────────────────────────────────────────────────────────────
@@ -21042,7 +21144,7 @@ var ConsensusOracleService = class {
21042
21144
  );
21043
21145
  for (const update of updates) {
21044
21146
  log(
21045
- ` holding #${update.holdingIndex} ${update.mint}: price=${update.price} external_amount=${update.externalAmount} [wallet=${update.walletAmount} lp=${update.lpAmount} yield=${update.yieldAmount}]`
21147
+ ` holding #${update.holdingIndex} ${update.mint}: price=${update.price} external_amount=${update.externalAmount} [wallet=${update.walletAmount} lp=${update.lpAmount} principal=${update.trackedPrincipalAmount} yield=${update.yieldAmount}]`
21046
21148
  );
21047
21149
  }
21048
21150
  await this.logProspectiveApy(vault, updates, vaultState, log);
@@ -21100,7 +21202,8 @@ var ConsensusOracleService = class {
21100
21202
  const externalAmount = update ? update.externalAmount : toBigInt2(holding.externalAmount);
21101
21203
  const amount = localAmount + externalAmount;
21102
21204
  if (amount === 0n) return sum;
21103
- const price = update ? update.price : toBigInt2(holding.price);
21205
+ const usesConsensusPrice = variantName2(holding.priceOracleType).toLowerCase() === CONSENSUS_ORACLE_VARIANT;
21206
+ const price = update && usesConsensusPrice ? update.price : toBigInt2(holding.price);
21104
21207
  if (price <= 0n) return sum;
21105
21208
  const scale = 10n ** BigInt(holding.decimals);
21106
21209
  return sum + amount * price / scale;
@@ -21157,10 +21260,12 @@ function signerInOracleData(data, signerBytes) {
21157
21260
  }
21158
21261
  return false;
21159
21262
  }
21160
- function selectConsensusHoldings(holdings, excludedMints = /* @__PURE__ */ new Set()) {
21263
+ function selectReportableHoldings(holdings, excludedMints = /* @__PURE__ */ new Set()) {
21161
21264
  return holdings.map((holding, holdingIndex) => ({ holding, holdingIndex })).filter(({ holding }) => {
21162
21265
  const mint = (0, import_common41.fromWeb3Pk)(holding.mint);
21163
- return mint !== SYSTEM_PROGRAM2 && !excludedMints.has(mint.toString()) && variantName2(holding.priceOracleType).toLowerCase() === CONSENSUS_ORACLE_VARIANT;
21266
+ return mint !== SYSTEM_PROGRAM2 && !excludedMints.has(mint.toString()) && REPORTABLE_ORACLE_VARIANTS.has(
21267
+ variantName2(holding.priceOracleType).toLowerCase()
21268
+ );
21164
21269
  });
21165
21270
  }
21166
21271
  function filterTargetHoldings(target, excludedMints) {
@@ -21202,6 +21307,7 @@ var MockYieldTracker = class {
21202
21307
  this.clock = opts.clock ?? systemClock;
21203
21308
  for (const [name, cfg] of Object.entries(opts.accounts ?? {})) {
21204
21309
  this.accounts.set(name, {
21310
+ tag: cfg.tag,
21205
21311
  aprBps: cfg.aprBps,
21206
21312
  initialPrincipal: cfg.principal ?? 0,
21207
21313
  startTs: cfg.startTs ?? this.clock.now(),
@@ -21213,6 +21319,7 @@ var MockYieldTracker = class {
21213
21319
  /** Register (or replace) a tracked account. */
21214
21320
  setAccount(name, cfg) {
21215
21321
  this.accounts.set(name, {
21322
+ tag: cfg.tag,
21216
21323
  aprBps: cfg.aprBps,
21217
21324
  initialPrincipal: cfg.principal ?? 0,
21218
21325
  startTs: cfg.startTs ?? this.clock.now(),
@@ -21227,6 +21334,9 @@ var MockYieldTracker = class {
21227
21334
  }
21228
21335
  return state;
21229
21336
  }
21337
+ async getAccountNamesForVault(vault) {
21338
+ return [...this.accounts.entries()].filter(([, state]) => state.tag === vault.toString()).map(([name]) => name);
21339
+ }
21230
21340
  async getYieldPayments(accountName) {
21231
21341
  return this.require(accountName).yieldPayments.map((payment) => ({
21232
21342
  id: payment.paymentId,
@@ -21288,6 +21398,16 @@ var MockYieldTracker = class {
21288
21398
  }
21289
21399
  return total;
21290
21400
  }
21401
+ async getTotalTrackedAmounts(accountNames, start, end) {
21402
+ let principal = 0;
21403
+ let outstandingYield = 0;
21404
+ for (const name of accountNames) {
21405
+ const snapshot2 = await this.getYield(name, start, end);
21406
+ principal += snapshot2.principalAtEnd;
21407
+ outstandingYield += snapshot2.outstandingYield;
21408
+ }
21409
+ return { principal, outstandingYield };
21410
+ }
21291
21411
  async getBalance(accountName, atTime) {
21292
21412
  const snapshot2 = await this.getYield(accountName, void 0, atTime);
21293
21413
  return snapshot2.principalAtEnd + snapshot2.outstandingYield;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perena/vault-sdk",
3
- "version": "1.0.25",
3
+ "version": "1.0.26",
4
4
  "description": "Vault program helpers for Bankineco integrations.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",