Skip to content

Commit 94d083d

Browse files
committed
Update vault returns and grouping UI
1 parent 82cbbc9 commit 94d083d

18 files changed

Lines changed: 379 additions & 104 deletions

File tree

src/lib/echarts/stablecoin-chain-heatmap.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { getLogoUrl } from '$lib/helpers/assets';
33
import {
44
buildStablecoinMetadataLookup,
55
formatStablecoinDisplayName,
6+
getStablecoinDetailsHref,
67
getStablecoinLogoUrl,
78
resolveStablecoinSlug
89
} from '$lib/stablecoin-metadata/helpers';
@@ -69,7 +70,7 @@ interface ResolvedStablecoinGroup {
6970
label: string;
7071
tooltipLabel: string;
7172
stablecoinSlug: string;
72-
href: string;
73+
href?: string;
7374
logoUrl?: string;
7475
}
7576

@@ -151,7 +152,7 @@ function resolveStablecoinGroup(
151152
label,
152153
tooltipLabel,
153154
stablecoinSlug,
154-
href: `/trading-view/vaults/stablecoins/${stablecoinSlug}`,
155+
href: getStablecoinDetailsHref(stablecoinSlug),
155156
logoUrl: getStablecoinLogoUrl(stablecoinSlug)
156157
};
157158
}
@@ -179,7 +180,7 @@ export function buildStablecoinChainHeatmapPayload(
179180
tooltipLabel: string;
180181
totalTvl: number;
181182
stablecoinSlug: string;
182-
href: string;
183+
href?: string;
183184
logoUrl?: string;
184185
}
185186
>();

src/lib/stablecoin-metadata/helpers.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@ import {
44
findStablecoinMetadata,
55
formatStablecoinDisplayName,
66
getStablecoinCoingeckoLink,
7+
getStablecoinDetailsHref,
8+
getStablecoinLogoUrl,
79
getStablecoinNativeRate,
10+
getVaultDenominationLogoUrl,
811
isStablecoinDepegged,
12+
isMidasRawUsdVault,
13+
OFFCHAIN_USD_SHORT_DESCRIPTION,
914
resolveStablecoinSlug
1015
} from './helpers';
1116
import type { StablecoinMetadata } from './schemas';
@@ -70,6 +75,25 @@ describe('stablecoin metadata helpers', () => {
7075
expect(slug).toBe('usd-offchain');
7176
});
7277

78+
test('uses the US flag and hides off-chain USD from stablecoin navigation', () => {
79+
expect(getStablecoinLogoUrl('usd-offchain')).toBe('/flags/us.svg');
80+
expect(getStablecoinDetailsHref('usd-offchain')).toBeUndefined();
81+
expect(getStablecoinDetailsHref('usdc')).toBe('/trading-view/vaults/stablecoins/usdc');
82+
expect(OFFCHAIN_USD_SHORT_DESCRIPTION).toBe('U.S. Dollars in the banking system without onchain transparency');
83+
});
84+
85+
test('uses the US flag only for Midas vaults denominated in raw USD', () => {
86+
expect(isMidasRawUsdVault({ protocol_slug: 'midas', denomination: 'USD' })).toBe(true);
87+
expect(isMidasRawUsdVault({ protocol_slug: 'midas', denomination: 'USDT' })).toBe(false);
88+
expect(getVaultDenominationLogoUrl({ protocol_slug: 'midas', denomination: 'USD' }, 'usd')).toBe('/flags/us.svg');
89+
expect(getVaultDenominationLogoUrl({ protocol_slug: 'midas', denomination: 'USDT' }, 'usdt')).not.toBe(
90+
'/flags/us.svg'
91+
);
92+
expect(getVaultDenominationLogoUrl({ protocol_slug: 'morpho', denomination: 'USD' }, 'usd')).not.toBe(
93+
'/flags/us.svg'
94+
);
95+
});
96+
7397
test('appends symbol to display name when missing', () => {
7498
expect(formatStablecoinDisplayName('USD Coin (Circle)', 'USDC')).toBe('USD Coin (Circle) USDC');
7599
});

src/lib/stablecoin-metadata/helpers.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { slugify } from '$lib/helpers/slugify';
44
import type { StablecoinMetadata } from './schemas';
55

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

811
interface StablecoinLookupInput {
912
slug?: string | null;
@@ -127,6 +130,54 @@ export function resolveStablecoinSlug(
127130
return undefined;
128131
}
129132

133+
/**
134+
* Return the stablecoin details page URL when the denomination is navigable.
135+
*
136+
* Off-chain USD has a standalone page, but is intentionally not linked from
137+
* stablecoin listings because it is a vault accounting denomination rather
138+
* than an on-chain stablecoin product.
139+
*
140+
* @param slug stablecoin denomination slug
141+
*/
142+
export function getStablecoinDetailsHref(slug: string | null | undefined): string | undefined {
143+
const trimmedSlug = slug?.trim().toLowerCase();
144+
if (!trimmedSlug || trimmedSlug === OFFCHAIN_USD_STABLECOIN_SLUG) return undefined;
145+
146+
return `/trading-view/vaults/stablecoins/${trimmedSlug}`;
147+
}
148+
149+
/**
150+
* Whether a vault is denominated in Midas' raw USD accounting currency.
151+
*
152+
* This is distinct from tokenised stablecoins such as USDT, even though their
153+
* metadata can expose a USD symbol alias.
154+
*
155+
* @param vault vault denomination fields
156+
*/
157+
export function isMidasRawUsdVault(vault: { protocol_slug?: string | null; denomination?: string | null }): boolean {
158+
return vault.protocol_slug === 'midas' && vault.denomination?.trim().toLowerCase() === 'usd';
159+
}
160+
161+
/**
162+
* Return the denomination logo for a vault.
163+
*
164+
* Midas reports certain vaults as raw USD rather than an on-chain token. These
165+
* use the US flag; tokenised denominations such as USDT retain their own logos.
166+
*
167+
* @param vault vault denomination fields
168+
* @param slug resolved stablecoin denomination slug
169+
*/
170+
export function getVaultDenominationLogoUrl(
171+
vault: { protocol_slug?: string | null; denomination?: string | null },
172+
slug: string | null | undefined
173+
): string | undefined {
174+
if (isMidasRawUsdVault(vault)) {
175+
return OFFCHAIN_USD_LOGO_URL;
176+
}
177+
178+
return slug ? getStablecoinLogoUrl(slug) : undefined;
179+
}
180+
130181
/**
131182
* Return the CoinGecko URL for a stablecoin metadata record.
132183
*
@@ -193,6 +244,10 @@ export function isStablecoinDepegged(metadata: StablecoinRateInput | null | unde
193244
* Returns undefined if the stablecoin metadata URL is not configured.
194245
*/
195246
export function getStablecoinLogoUrl(slug: string, options: MetadataLogoOptions = {}): string | undefined {
247+
const normalisedSlug = slug.trim().toLowerCase();
248+
if (normalisedSlug === OFFCHAIN_USD_STABLECOIN_SLUG) {
249+
return OFFCHAIN_USD_LOGO_URL;
250+
}
196251
if (!stablecoinMetadataUrl) return undefined;
197252
return buildMetadataLogoProxyPath('stablecoin', slug, {
198253
format: 'webp',

src/lib/top-vaults/TopVaultsPage.svelte

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
includeBlacklisted?: boolean;
3434
protocolMetadata?: VaultProtocolMetadata;
3535
stablecoinMetadata?: StablecoinMetadata;
36+
/** Stablecoin slug used for a logo when no metadata record exists. */
37+
stablecoinLogoSlug?: string;
3638
curatorMetadata?: CuratorInfo;
3739
/** Show interactive filter dropdowns (Min TVL, Min age, Max risk) */
3840
showFilters?: boolean;
@@ -83,6 +85,7 @@
8385
includeBlacklisted,
8486
protocolMetadata,
8587
stablecoinMetadata,
88+
stablecoinLogoSlug,
8689
curatorMetadata,
8790
showFilters,
8891
defaultTvlKey,
@@ -132,10 +135,10 @@
132135
<img src={protocolLogoUrl} alt={protocolMetadata.name} />
133136
{/if}
134137
{/if}
135-
{#if stablecoinMetadata?.logos.light}
136-
{@const stablecoinLogoUrl = getStablecoinLogoUrl(stablecoinMetadata.slug)}
138+
{#if stablecoinMetadata?.logos.light || stablecoinLogoSlug}
139+
{@const stablecoinLogoUrl = getStablecoinLogoUrl(stablecoinMetadata?.slug ?? stablecoinLogoSlug ?? '')}
137140
{#if stablecoinLogoUrl}
138-
<img src={stablecoinLogoUrl} alt={stablecoinMetadata.name} />
141+
<img src={stablecoinLogoUrl} alt={stablecoinMetadata?.name ?? pageTitle} />
139142
{/if}
140143
{/if}
141144
{#if curatorMetadata?.logos.generic}

src/lib/top-vaults/VaultGroupTable.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
page?: number;
3838
sort?: SortOptions['keys'][number];
3939
direction?: SortOptions['directions'][number];
40-
getHref?: (slug: string) => string;
40+
getHref?: (slug: string) => string | undefined;
4141
getWarningLabel?: (row: VaultGroup) => string | undefined;
4242
}
4343
@@ -161,7 +161,8 @@
161161
id: 'cta',
162162
header: '',
163163
accessor: (row) => getHref(row.slug),
164-
cell: ({ value }) => createRender(TableRowTarget, { size: 'sm', label: 'View vaults', href: value }),
164+
cell: ({ value }) =>
165+
value ? createRender(TableRowTarget, { size: 'sm', label: 'View vaults', href: value }) : '',
165166
plugins: { sort: { disable: true } }
166167
})
167168
]);

src/routes/trading-view/vaults/MarketSharePieChart.svelte

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,10 @@
9090
: `<span>${titleLabel}</span>`;
9191
const showNameRow = slice.name !== slice.label;
9292
const groupedLabel = `${toTitleCase(groupLabelPlural)} grouped`;
93-
const hint = slice.isOther
94-
? ''
95-
: `<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>`;
93+
const hint =
94+
slice.isOther || !slice.url
95+
? ''
96+
: `<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>`;
9697
9798
return [
9899
`<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>`,

src/routes/trading-view/vaults/VaultGroupDescription.svelte

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,16 @@ excluded vault count from the loaded vault data.
3030
interface Props {
3131
/** MetricsBox heading, e.g. "About Base vaults" */
3232
title: string;
33+
/** Optional introductory sentence shown before the generated vault summary. */
34+
description?: string;
3335
/** Sentence opener naming the group, e.g. "Base blockchain" or "Circle USDC" */
3436
subject: string;
3537
/** Verb phrase linking the subject to the vault count, e.g. "has" or "is used in" */
3638
verbPhrase?: string;
3739
vaults: VaultInfo[];
3840
}
3941
40-
let { title, subject, verbPhrase = 'has', vaults }: Props = $props();
42+
let { title, description, subject, verbPhrase = 'has', vaults }: Props = $props();
4143
4244
// all headline stats derive from this set: exclude blacklisted vaults and broken
4345
// TVL outliers so a single bad data point doesn't inflate the figures
@@ -89,6 +91,9 @@ excluded vault count from the loaded vault data.
8991

9092
<div class="vault-group-description">
9193
<MetricsBox {title}>
94+
{#if description}
95+
<p>{description}</p>
96+
{/if}
9297
<p>
9398
{subject}
9499
{verbPhrase}

src/routes/trading-view/vaults/[vault=slug]/+page.server.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { getChain } from '$lib/helpers/chain';
22
import { fetchStablecoinMetadataIndex } from '$lib/stablecoin-metadata/client';
3-
import { buildStablecoinMetadataLookup, findStablecoinMetadata } from '$lib/stablecoin-metadata/helpers';
3+
import {
4+
buildStablecoinMetadataLookup,
5+
findStablecoinMetadata,
6+
isMidasRawUsdVault
7+
} from '$lib/stablecoin-metadata/helpers';
48
import { getCachedTopVaults } from '$lib/top-vaults/cache';
59
import {
610
getCore3ProtocolForVault,
@@ -35,12 +39,14 @@ export async function load({ params, fetch }) {
3539
fetchStablecoinMetadataIndex(fetch)
3640
]);
3741
const stablecoinMetadataLookup = buildStablecoinMetadataLookup(stablecoinMetadataIndex);
38-
const stablecoinMetadata = findStablecoinMetadata(
39-
stablecoinMetadataLookup,
40-
vault.denomination_slug,
41-
vault.denomination,
42-
vault.normalised_denomination
43-
);
42+
const stablecoinMetadata = isMidasRawUsdVault(vault)
43+
? undefined
44+
: findStablecoinMetadata(
45+
stablecoinMetadataLookup,
46+
vault.denomination_slug,
47+
vault.denomination,
48+
vault.normalised_denomination
49+
);
4450
const vaultWithRates = withVaultDenominationTokenRate(
4551
vault,
4652
stablecoinMetadata,

0 commit comments

Comments
 (0)