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
4 changes: 2 additions & 2 deletions src/features/markets/components/market-risk-indicators.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Market } from '@/utils/types';
import { MarketAssetIndicator, MarketOracleIndicator, MarketDebtIndicator } from './risk-indicator';
import { MarketAssetIndicator, MarketOracleIndicator, MarketStatusIndicator } from './risk-indicator';

type MarketRiskIndicatorsProps = {
market: Market;
Expand All @@ -24,7 +24,7 @@ export function MarketRiskIndicators({ market, isBatched = false, mode = 'simple
isBatched={isBatched}
mode={mode}
/>
<MarketDebtIndicator
<MarketStatusIndicator
market={market}
isBatched={isBatched}
mode={mode}
Expand Down
43 changes: 35 additions & 8 deletions src/features/markets/components/risk-indicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export function MarketOracleIndicator({
);
}

export function MarketDebtIndicator({
export function MarketStatusIndicator({
market,
isBatched = false,
mode = 'simple',
Expand All @@ -201,15 +201,42 @@ export function MarketDebtIndicator({
isBatched?: boolean;
mode?: 'simple' | 'complex';
}) {
const warningsWithDetail = useMarketWarnings(market, true);

// Combine debt + general category warnings
const warnings = warningsWithDetail.filter((w) => w.category === WarningCategory.debt || w.category === WarningCategory.general);

if (warnings.length === 0) {
return (
<RiskIndicator
level="green"
description="No market state warnings"
mode={mode}
/>
);
}

if (warnings.some((warning) => warning.level === 'alert')) {
const alertWarning = warnings.find((w) => w.level === 'alert');
return (
<RiskIndicator
level="red"
description={isBatched ? 'One or more markets have warnings' : 'Market has critical warning'}
mode={mode}
warningDetail={alertWarning}
/>
);
}

return (
<RiskIndicatorFromWarning
market={market}
category={WarningCategory.debt}
greenDescription="No bad debt"
yellowDescription="Bad debt has occurred"
redDescription="Bad debt higher than 1% of supply"
isBatched={isBatched}
<RiskIndicator
level="yellow"
description={isBatched ? 'One or more markets have warnings' : 'Market has warning'}
mode={mode}
warningDetail={warnings[0]}
/>
);
}

// Keep old name as alias for backward compatibility
export const MarketDebtIndicator = MarketStatusIndicator;
18 changes: 9 additions & 9 deletions src/hooks/useProcessedMarkets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useMarketsQuery } from '@/hooks/queries/useMarketsQuery';
import { useOracleDataQuery } from '@/hooks/queries/useOracleDataQuery';
import { useBlacklistedMarkets } from '@/stores/useBlacklistedMarkets';
import { useAppSettings } from '@/stores/useAppSettings';
import { isForceUnwhitelisted } from '@/utils/markets';

/**
* Processes raw markets data with blacklist filtering and oracle enrichment.
Expand Down Expand Up @@ -49,17 +50,16 @@ export const useProcessedMarkets = () => {
// Apply blacklist filter
const blacklistFiltered = rawMarketsUnfiltered.filter((market) => !allBlacklistedMarketKeys.has(market.uniqueKey));

// Enrich with oracle data
// Enrich with oracle data and apply force-unwhitelisted overrides
const enriched = blacklistFiltered.map((market) => {
const oracleData = getOracleData(market.oracleAddress, market.morphoBlue.chain.id);
return oracleData
? {
...market,
oracle: {
data: oracleData,
},
}
: market;
const shouldForceUnwhitelist = isForceUnwhitelisted(market.uniqueKey);

return {
...market,
...(oracleData && { oracle: { data: oracleData } }),
...(shouldForceUnwhitelist && { whitelisted: false }),
};
});

// allMarkets: all markets (whitelisted + unwhitelisted, excluding blacklisted)
Expand Down
53 changes: 53 additions & 0 deletions src/utils/markets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,56 @@ export const monarchWhitelistedMarkets: WhitelistMarketData[] = [
offsetWarnings: ['unrecognized_collateral_asset'],
},
];

// Market override rules - group multiple markets under the same rule
export type MarketOverrideWarning = {
code: string;
level: 'warning' | 'alert';
description: string;
category: 'asset' | 'oracle' | 'debt' | 'general';
};

export type MarketOverrideRule = {
// Markets this rule applies to (uniqueKey, lowercase)
marketIds: string[];

// Force these markets to appear as unwhitelisted
forceUnwhitelisted?: boolean;

// Custom warnings to attach
warnings?: MarketOverrideWarning[];
};

export const marketOverrideRules: MarketOverrideRule[] = [
{
marketIds: [
'0xdb2cf3ad3ef91c9bb673bf35744e7141bc2950b27a75c8d11b0ead9f6742d927',
'0xe0ede98b4425285a9c93d51f8ba27d9a09bc0033874e4a883d3f29d41f9f2e4a',
'0x2b62c4153d81d5b5a233d1d2b7ef899d3fca4076d458e215ff3a00176b415b0d',
'0x216bd19960f140177a4a3fb9cf258edcbadb1f5d54740fc944503bff4a00e65e',
'0xf474c9a0cbd8f2b65d9480d94b56880ca13f10fc3b3c717d47bdf3ac9c4417b7',
],
forceUnwhitelisted: true,
warnings: [
{
code: 'deprecated',
level: 'alert',
description: 'The loan asset (rUSDC) of this market is deprecating.',
category: 'general',
},
],
},
];

// Helper functions to query the override rules
export const isForceUnwhitelisted = (marketId: string): boolean => {
const normalizedId = marketId.toLowerCase();
return marketOverrideRules.some((rule) => rule.forceUnwhitelisted && rule.marketIds.includes(normalizedId));
};

export const getMarketOverrideWarnings = (marketId: string): MarketOverrideWarning[] => {
const normalizedId = marketId.toLowerCase();
return marketOverrideRules
.filter((rule) => rule.marketIds.includes(normalizedId) && rule.warnings)
.flatMap((rule) => rule.warnings ?? []);
};
12 changes: 7 additions & 5 deletions src/utils/warnings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Market, MarketWarning } from '@/utils/types';
import { monarchWhitelistedMarkets } from './markets';
import { monarchWhitelistedMarkets, getMarketOverrideWarnings } from './markets';
import { getOracleType, OracleType, parsePriceFeedVendors, checkFeedsPath } from './oracle';
import { WarningCategory, type WarningWithDetail } from './types';

Expand Down Expand Up @@ -155,10 +155,6 @@ export const getMarketWarningsWithDetail = (market: Market, considerWhitelist =
for (const warning of market.warnings) {
const foundWarning = allDetails.find((w) => w.code === warning.type);

if (whitelistedMarketData) {
console.log('whitelistedMarketData', whitelistedMarketData);
}

// if this market is whitelisted, there might be warnings we want to "offset"
const isOffset = whitelistedMarketData?.offsetWarnings.includes(warning.type);

Expand Down Expand Up @@ -227,5 +223,11 @@ export const getMarketWarningsWithDetail = (market: Market, considerWhitelist =
}
}

// Inject custom market warnings from override rules
const overrideWarnings = getMarketOverrideWarnings(market.uniqueKey);
for (const warning of overrideWarnings) {
result.push({ ...warning, category: warning.category as WarningCategory });
}

return result;
};