@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 +32 -5
- package/dist/index.js +164 -44
- package/package.json +1 -1
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
16362
|
-
private
|
|
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
|
|
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
|
|
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
|
-
|
|
19883
|
+
if (settledPrice !== void 0) {
|
|
19884
|
+
holding.price = settledPrice;
|
|
19885
|
+
holding.lastUpdateTs = latestTs;
|
|
19886
|
+
}
|
|
19863
19887
|
holding.externalAmount = settledExternalAmount;
|
|
19864
|
-
holding.
|
|
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
|
-
|
|
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}
|
|
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
|
|
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 (
|
|
20642
|
-
log(`vault ${target.vault}: no
|
|
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
|
-
|
|
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
|
-
|
|
20685
|
+
reportableHoldings
|
|
20651
20686
|
);
|
|
20652
|
-
const updates = await this.buildUpdates(
|
|
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,
|
|
21015
|
+
async gatherPricingInputs(target, vaultState, reportableHoldings) {
|
|
20931
21016
|
const manager = (0, import_common41.fromWeb3Pk)(vaultState.roles.manager);
|
|
20932
|
-
const
|
|
20933
|
-
({ holding }) => (
|
|
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) :
|
|
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 =
|
|
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
|
-
/**
|
|
20990
|
-
async
|
|
20991
|
-
if (!this.deps.yieldTracker || !cfg?.yieldAccountNames?.length)
|
|
20992
|
-
|
|
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
|
|
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(
|
|
21092
|
+
async buildUpdates(reportableHoldings, inputs) {
|
|
20999
21093
|
const updates = [];
|
|
21000
|
-
for (const entry of
|
|
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
|
|
21010
|
-
|
|
21011
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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()) &&
|
|
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;
|