Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/lib/curator/CuratorDescription.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ the vault protocol description widget. Sourced from the top-vaults dataset
import { micromark } from 'micromark';
import MetricsBox from '$lib/components/MetricsBox.svelte';
import Button from '$lib/components/Button.svelte';
import { calculateTotalTvl, calculateTvlWeightedApy, isBlacklisted } from '$lib/top-vaults/helpers';
import {
calculateTotalTvl,
calculateTvlWeightedApy,
isBlacklisted,
withVaultCurrentTvlUsd
} from '$lib/top-vaults/helpers';
import { VAULT_TVL_OUTLIER_THRESHOLD } from '$lib/echarts/tvl-outliers';
import { formatDollar, formatPercent } from '$lib/helpers/formatters';
import IconTwitter from '~icons/local/twitter';
Expand All @@ -40,8 +45,9 @@ the vault protocol description widget. Sourced from the top-vaults dataset
let statsVaults = $derived(
vaults.filter((v) => !isBlacklisted(v) && (v.current_nav ?? 0) <= VAULT_TVL_OUTLIER_THRESHOLD)
);
let totalTvl = $derived(calculateTotalTvl(statsVaults));
let averageApy = $derived(calculateTvlWeightedApy(statsVaults));
let statsVaultsWithUsdTvl = $derived(statsVaults.map(withVaultCurrentTvlUsd));
let totalTvl = $derived(calculateTotalTvl(statsVaultsWithUsdTvl));
let averageApy = $derived(calculateTvlWeightedApy(statsVaultsWithUsdTvl));
let vaultCountLabel = $derived(`${statsVaults.length} ${statsVaults.length === 1 ? 'vault' : 'vaults'}`);
let totalTvlLabel = $derived(`${formatDollar(totalTvl, 1)} TVL`);
let averageApyLabel = $derived(averageApy == null ? null : `${formatPercent(averageApy, 1)} APY`);
Expand Down
19 changes: 19 additions & 0 deletions src/lib/top-vaults/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
isNonUsdDenominatedVault,
normaliseKinexysVaultDenomination,
normaliseVaultProtocolDisplayName,
withVaultCurrentTvlUsd,
withVaultDenominationTokenRate,
calculateTotalTvl,
calculateTvlWeightedApy
Expand Down Expand Up @@ -360,6 +361,24 @@ describe('denomination TVL conversion', () => {
expect(getVaultPeakTvlUsd(vault)).toBeCloseTo(129_600);
});

test('uses USD TVL for cross-denomination aggregate inputs', () => {
const eurVault = createTestVault('EUR vault', {
current_nav: 100_000,
one_month_cagr: 0.1,
denomination_token_rate: createDenominationTokenRate({ usd_rate: 1.08 })
});
const usdVault = createTestVault('USD vault', {
current_nav: 100_000,
one_month_cagr: 0,
denomination_token_rate: createDenominationTokenRate({ usd_rate: 1 })
});

const usdVaults = [eurVault, usdVault].map(withVaultCurrentTvlUsd);

expect(calculateTotalTvl(usdVaults)).toBeCloseTo(208_000);
expect(calculateTvlWeightedApy(usdVaults)).toBeCloseTo(108_000 / 208_000 / 10);
});

test('infers EUR denomination when per-vault rate metadata is absent', () => {
const vault = createTestVault('EURCV vault', {
denomination: 'EURCV',
Expand Down
10 changes: 10 additions & 0 deletions src/lib/top-vaults/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,16 @@ export function getVaultCurrentTvlUsd(vault: VaultWithNavAndRate): number | null
return usdRate == null ? null : vault.current_nav * usdRate;
}

/**
* Copy a vault with its current TVL expressed in USD.
*
* Aggregate helpers use `current_nav` as their TVL input. Call this before
* calculating a cross-denomination total or TVL-weighted return.
*/
export function withVaultCurrentTvlUsd(vault: VaultInfo): VaultInfo {
return { ...vault, current_nav: getVaultCurrentTvlUsd(vault) };
}

/**
* Peak vault TVL converted from denomination token units to USD.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { error } from '@sveltejs/kit';
import { getCachedTopVaults } from '$lib/top-vaults/cache';
import { calculateTotalTvl, calculateTvlWeightedApy, isBlacklisted, meetsMinTvl } from '$lib/top-vaults/helpers.js';
import {
calculateTotalTvl,
calculateTvlWeightedApy,
isBlacklisted,
meetsMinTvl,
withVaultCurrentTvlUsd
} from '$lib/top-vaults/helpers.js';

export async function load({ params, fetch }) {
const { curator } = params;
Expand All @@ -12,8 +18,9 @@ export async function load({ params, fetch }) {
// aggregate stats for server-rendered SEO metadata; same eligibility rules
// as the curators index listing
const eligibleVaults = vaults.filter((v) => v.curator_slug === curator && !isBlacklisted(v) && meetsMinTvl(v));
const tvl = calculateTotalTvl(eligibleVaults);
const averageApy = calculateTvlWeightedApy(eligibleVaults);
const eligibleVaultsWithUsdTvl = eligibleVaults.map(withVaultCurrentTvlUsd);
const tvl = calculateTotalTvl(eligibleVaultsWithUsdTvl);
const averageApy = calculateTvlWeightedApy(eligibleVaultsWithUsdTvl);

return {
curatorSlug: curator,
Expand Down
Loading