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
7 changes: 4 additions & 3 deletions src/lib/echarts/stablecoin-chain-heatmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getLogoUrl } from '$lib/helpers/assets';
import {
buildStablecoinMetadataLookup,
formatStablecoinDisplayName,
getStablecoinDetailsHref,
getStablecoinLogoUrl,
resolveStablecoinSlug
} from '$lib/stablecoin-metadata/helpers';
Expand Down Expand Up @@ -69,7 +70,7 @@ interface ResolvedStablecoinGroup {
label: string;
tooltipLabel: string;
stablecoinSlug: string;
href: string;
href?: string;
logoUrl?: string;
}

Expand Down Expand Up @@ -151,7 +152,7 @@ function resolveStablecoinGroup(
label,
tooltipLabel,
stablecoinSlug,
href: `/trading-view/vaults/stablecoins/${stablecoinSlug}`,
href: getStablecoinDetailsHref(stablecoinSlug),
logoUrl: getStablecoinLogoUrl(stablecoinSlug)
};
}
Expand Down Expand Up @@ -179,7 +180,7 @@ export function buildStablecoinChainHeatmapPayload(
tooltipLabel: string;
totalTvl: number;
stablecoinSlug: string;
href: string;
href?: string;
logoUrl?: string;
}
>();
Expand Down
24 changes: 24 additions & 0 deletions src/lib/stablecoin-metadata/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import {
findStablecoinMetadata,
formatStablecoinDisplayName,
getStablecoinCoingeckoLink,
getStablecoinDetailsHref,
getStablecoinLogoUrl,
getStablecoinNativeRate,
getVaultDenominationLogoUrl,
isStablecoinDepegged,
isMidasRawUsdVault,
OFFCHAIN_USD_SHORT_DESCRIPTION,
resolveStablecoinSlug
} from './helpers';
import type { StablecoinMetadata } from './schemas';
Expand Down Expand Up @@ -70,6 +75,25 @@ describe('stablecoin metadata helpers', () => {
expect(slug).toBe('usd-offchain');
});

test('uses the US flag and hides off-chain USD from stablecoin navigation', () => {
expect(getStablecoinLogoUrl('usd-offchain')).toBe('/flags/us.svg');
expect(getStablecoinDetailsHref('usd-offchain')).toBeUndefined();
expect(getStablecoinDetailsHref('usdc')).toBe('/trading-view/vaults/stablecoins/usdc');
expect(OFFCHAIN_USD_SHORT_DESCRIPTION).toBe('U.S. Dollars in the banking system without onchain transparency');
});

test('uses the US flag only for Midas vaults denominated in raw USD', () => {
expect(isMidasRawUsdVault({ protocol_slug: 'midas', denomination: 'USD' })).toBe(true);
expect(isMidasRawUsdVault({ protocol_slug: 'midas', denomination: 'USDT' })).toBe(false);
expect(getVaultDenominationLogoUrl({ protocol_slug: 'midas', denomination: 'USD' }, 'usd')).toBe('/flags/us.svg');
expect(getVaultDenominationLogoUrl({ protocol_slug: 'midas', denomination: 'USDT' }, 'usdt')).not.toBe(
'/flags/us.svg'
);
expect(getVaultDenominationLogoUrl({ protocol_slug: 'morpho', denomination: 'USD' }, 'usd')).not.toBe(
'/flags/us.svg'
);
});

test('appends symbol to display name when missing', () => {
expect(formatStablecoinDisplayName('USD Coin (Circle)', 'USDC')).toBe('USD Coin (Circle) USDC');
});
Expand Down
55 changes: 55 additions & 0 deletions src/lib/stablecoin-metadata/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { slugify } from '$lib/helpers/slugify';
import type { StablecoinMetadata } from './schemas';

export const STABLECOIN_DEPEG_RATE_THRESHOLD = 0.9;
export const OFFCHAIN_USD_STABLECOIN_SLUG = 'usd-offchain';
export const OFFCHAIN_USD_SHORT_DESCRIPTION = 'U.S. Dollars in the banking system without onchain transparency';
const OFFCHAIN_USD_LOGO_URL = '/flags/us.svg';

interface StablecoinLookupInput {
slug?: string | null;
Expand Down Expand Up @@ -127,6 +130,54 @@ export function resolveStablecoinSlug(
return undefined;
}

/**
* Return the stablecoin details page URL when the denomination is navigable.
*
* Off-chain USD has a standalone page, but is intentionally not linked from
* stablecoin listings because it is a vault accounting denomination rather
* than an on-chain stablecoin product.
*
* @param slug stablecoin denomination slug
*/
export function getStablecoinDetailsHref(slug: string | null | undefined): string | undefined {
const trimmedSlug = slug?.trim().toLowerCase();
if (!trimmedSlug || trimmedSlug === OFFCHAIN_USD_STABLECOIN_SLUG) return undefined;

return `/trading-view/vaults/stablecoins/${trimmedSlug}`;
}

/**
* Whether a vault is denominated in Midas' raw USD accounting currency.
*
* This is distinct from tokenised stablecoins such as USDT, even though their
* metadata can expose a USD symbol alias.
*
* @param vault vault denomination fields
*/
export function isMidasRawUsdVault(vault: { protocol_slug?: string | null; denomination?: string | null }): boolean {
return vault.protocol_slug === 'midas' && vault.denomination?.trim().toLowerCase() === 'usd';
}

/**
* Return the denomination logo for a vault.
*
* Midas reports certain vaults as raw USD rather than an on-chain token. These
* use the US flag; tokenised denominations such as USDT retain their own logos.
*
* @param vault vault denomination fields
* @param slug resolved stablecoin denomination slug
*/
export function getVaultDenominationLogoUrl(
vault: { protocol_slug?: string | null; denomination?: string | null },
slug: string | null | undefined
): string | undefined {
if (isMidasRawUsdVault(vault)) {
return OFFCHAIN_USD_LOGO_URL;
}

return slug ? getStablecoinLogoUrl(slug) : undefined;
}

/**
* Return the CoinGecko URL for a stablecoin metadata record.
*
Expand Down Expand Up @@ -193,6 +244,10 @@ export function isStablecoinDepegged(metadata: StablecoinRateInput | null | unde
* Returns undefined if the stablecoin metadata URL is not configured.
*/
export function getStablecoinLogoUrl(slug: string, options: MetadataLogoOptions = {}): string | undefined {
const normalisedSlug = slug.trim().toLowerCase();
if (normalisedSlug === OFFCHAIN_USD_STABLECOIN_SLUG) {
return OFFCHAIN_USD_LOGO_URL;
}
if (!stablecoinMetadataUrl) return undefined;
return buildMetadataLogoProxyPath('stablecoin', slug, {
format: 'webp',
Expand Down
9 changes: 6 additions & 3 deletions src/lib/top-vaults/TopVaultsPage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
includeBlacklisted?: boolean;
protocolMetadata?: VaultProtocolMetadata;
stablecoinMetadata?: StablecoinMetadata;
/** Stablecoin slug used for a logo when no metadata record exists. */
stablecoinLogoSlug?: string;
curatorMetadata?: CuratorInfo;
/** Show interactive filter dropdowns (Min TVL, Min age, Max risk) */
showFilters?: boolean;
Expand Down Expand Up @@ -83,6 +85,7 @@
includeBlacklisted,
protocolMetadata,
stablecoinMetadata,
stablecoinLogoSlug,
curatorMetadata,
showFilters,
defaultTvlKey,
Expand Down Expand Up @@ -132,10 +135,10 @@
<img src={protocolLogoUrl} alt={protocolMetadata.name} />
{/if}
{/if}
{#if stablecoinMetadata?.logos.light}
{@const stablecoinLogoUrl = getStablecoinLogoUrl(stablecoinMetadata.slug)}
{#if stablecoinMetadata?.logos.light || stablecoinLogoSlug}
{@const stablecoinLogoUrl = getStablecoinLogoUrl(stablecoinMetadata?.slug ?? stablecoinLogoSlug ?? '')}
{#if stablecoinLogoUrl}
<img src={stablecoinLogoUrl} alt={stablecoinMetadata.name} />
<img src={stablecoinLogoUrl} alt={stablecoinMetadata?.name ?? pageTitle} />
{/if}
{/if}
{#if curatorMetadata?.logos.generic}
Expand Down
5 changes: 3 additions & 2 deletions src/lib/top-vaults/VaultGroupTable.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
page?: number;
sort?: SortOptions['keys'][number];
direction?: SortOptions['directions'][number];
getHref?: (slug: string) => string;
getHref?: (slug: string) => string | undefined;
getWarningLabel?: (row: VaultGroup) => string | undefined;
}

Expand Down Expand Up @@ -161,7 +161,8 @@
id: 'cta',
header: '',
accessor: (row) => getHref(row.slug),
cell: ({ value }) => createRender(TableRowTarget, { size: 'sm', label: 'View vaults', href: value }),
cell: ({ value }) =>
value ? createRender(TableRowTarget, { size: 'sm', label: 'View vaults', href: value }) : '',
plugins: { sort: { disable: true } }
})
]);
Expand Down
7 changes: 4 additions & 3 deletions src/routes/trading-view/vaults/MarketSharePieChart.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,10 @@
: `<span>${titleLabel}</span>`;
const showNameRow = slice.name !== slice.label;
const groupedLabel = `${toTitleCase(groupLabelPlural)} grouped`;
const hint = slice.isOther
? ''
: `<div class="tooltip-hint" style="margin-top: 0.75rem; display: inline-flex; align-items: center; border: 1px solid rgba(191, 219, 254, 0.16); border-radius: 999px; padding: 0.38rem 0.72rem; color: #f8fafc; font-family: ${chartFontFamily}; background: rgba(125, 211, 252, 0.08); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);"><span style="text-decoration: underline;">Click to view more</span></div>`;
const hint =
slice.isOther || !slice.url
? ''
: `<div class="tooltip-hint" style="margin-top: 0.75rem; display: inline-flex; align-items: center; border: 1px solid rgba(191, 219, 254, 0.16); border-radius: 999px; padding: 0.38rem 0.72rem; color: #f8fafc; font-family: ${chartFontFamily}; background: rgba(125, 211, 252, 0.08); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);"><span style="text-decoration: underline;">Click to view more</span></div>`;

return [
`<div style="margin-bottom: 0.7rem; padding-bottom: 0.65rem; border-bottom: 1px solid rgba(191, 219, 254, 0.12);"><div class="tooltip-title" style="${TOOLTIP_TITLE_STYLE}">${title}</div>${showNameRow ? `<div style="margin-top: 0.18rem; color: rgba(226, 232, 240, 0.76); font-family: ${chartFontFamily}; line-height: 1.35;">${escapeHtml(slice.name)}</div>` : ''}</div>`,
Expand Down
7 changes: 6 additions & 1 deletion src/routes/trading-view/vaults/VaultGroupDescription.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ excluded vault count from the loaded vault data.
interface Props {
/** MetricsBox heading, e.g. "About Base vaults" */
title: string;
/** Optional introductory sentence shown before the generated vault summary. */
description?: string;
/** Sentence opener naming the group, e.g. "Base blockchain" or "Circle USDC" */
subject: string;
/** Verb phrase linking the subject to the vault count, e.g. "has" or "is used in" */
verbPhrase?: string;
vaults: VaultInfo[];
}

let { title, subject, verbPhrase = 'has', vaults }: Props = $props();
let { title, description, subject, verbPhrase = 'has', vaults }: Props = $props();

// all headline stats derive from this set: exclude blacklisted vaults and broken
// TVL outliers so a single bad data point doesn't inflate the figures
Expand Down Expand Up @@ -89,6 +91,9 @@ excluded vault count from the loaded vault data.

<div class="vault-group-description">
<MetricsBox {title}>
{#if description}
<p>{description}</p>
{/if}
<p>
{subject}
{verbPhrase}
Expand Down
20 changes: 13 additions & 7 deletions src/routes/trading-view/vaults/[vault=slug]/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { getChain } from '$lib/helpers/chain';
import { fetchStablecoinMetadataIndex } from '$lib/stablecoin-metadata/client';
import { buildStablecoinMetadataLookup, findStablecoinMetadata } from '$lib/stablecoin-metadata/helpers';
import {
buildStablecoinMetadataLookup,
findStablecoinMetadata,
isMidasRawUsdVault
} from '$lib/stablecoin-metadata/helpers';
import { getCachedTopVaults } from '$lib/top-vaults/cache';
import {
getCore3ProtocolForVault,
Expand Down Expand Up @@ -35,12 +39,14 @@ export async function load({ params, fetch }) {
fetchStablecoinMetadataIndex(fetch)
]);
const stablecoinMetadataLookup = buildStablecoinMetadataLookup(stablecoinMetadataIndex);
const stablecoinMetadata = findStablecoinMetadata(
stablecoinMetadataLookup,
vault.denomination_slug,
vault.denomination,
vault.normalised_denomination
);
const stablecoinMetadata = isMidasRawUsdVault(vault)
? undefined
: findStablecoinMetadata(
stablecoinMetadataLookup,
vault.denomination_slug,
vault.denomination,
vault.normalised_denomination
);
const vaultWithRates = withVaultDenominationTokenRate(
vault,
stablecoinMetadata,
Expand Down
Loading
Loading