- {/* warnings */}
-
-
Oracle:
+
- {market.oracleFeed && (
- <>
-
-
Base feed
-
-
+ {hasFeeds && (
+
+
- {/* only shows base feed 2 if non-zero */}
- {market.oracleFeed.baseFeedTwoAddress !== zeroAddress && (
-
- )}
-
- {market.oracleFeed.quoteFeedOneAddress !== zeroAddress && (
-
- )}
-
- {/* only shows quote feed 2 if non-zero */}
- {market.oracleFeed.quoteFeedTwoAddress !== zeroAddress && (
-
- )}
- >
+
+
+
+
+
)}
diff --git a/app/markets/components/MarketTableBody.tsx b/app/markets/components/MarketTableBody.tsx
index 870e4975..5603a36e 100644
--- a/app/markets/components/MarketTableBody.tsx
+++ b/app/markets/components/MarketTableBody.tsx
@@ -4,6 +4,7 @@ import { ExternalLinkIcon } from '@radix-ui/react-icons';
import Image from 'next/image';
import { FaShieldAlt } from 'react-icons/fa';
import { GoStarFill, GoStar } from 'react-icons/go';
+import OracleVendorBadge from '@/components/OracleVendorBadge';
import { formatReadable } from '@/utils/balance';
import { getMarketURL } from '@/utils/external';
import { getNetworkImg } from '@/utils/networks';
@@ -116,6 +117,11 @@ export function MarketTableBody({
img={collatImg}
symbol={collatToShow}
/>
+
+
+
+
+ |
{Number(item.lltv) / 1e16}%
|
@@ -174,7 +180,7 @@ export function MarketTableBody({
{expandedRowId === item.uniqueKey && (
- |
+ |
|
diff --git a/app/markets/components/marketsTable.tsx b/app/markets/components/marketsTable.tsx
index 7c622cd4..88a6e5e6 100644
--- a/app/markets/components/marketsTable.tsx
+++ b/app/markets/components/marketsTable.tsx
@@ -62,6 +62,7 @@ function MarketsTable({
sortDirection={sortDirection}
targetColumn={SortColumn.CollateralAsset}
/>
+
Oracle |
Market |
Collateral |
+ Oracle |
LLTV |
APY |
Supplied |
@@ -111,7 +113,8 @@ export function SuppliedMarketsDetail({
const suppliedAmount = Number(
formatBalance(position.supplyAssets, position.market.loanAsset.decimals),
);
- const percentageOfPortfolio = totalSupply > 0 ? (suppliedAmount / totalSupply) * 100 : 0;
+ const percentageOfPortfolio =
+ totalSupply > 0 ? (suppliedAmount / totalSupply) * 100 : 0;
const warningColor = getWarningColor(position.warningsWithDetail);
return (
@@ -163,6 +166,11 @@ export function SuppliedMarketsDetail({
'N/A'
)}
+
+
+
+
+ |
{formatBalance(position.market.lltv, 16)}%
|
diff --git a/src/components/FeedInfo/OracleFeedInfo.tsx b/src/components/FeedInfo/OracleFeedInfo.tsx
index 77ad5763..96a85b1e 100644
--- a/src/components/FeedInfo/OracleFeedInfo.tsx
+++ b/src/components/FeedInfo/OracleFeedInfo.tsx
@@ -1,37 +1,59 @@
+import { Tooltip } from '@nextui-org/tooltip';
import { ExternalLinkIcon } from '@radix-ui/react-icons';
+import Image from 'next/image';
import Link from 'next/link';
-import { Address, zeroAddress } from 'viem';
+import { IoIosSwap } from 'react-icons/io';
+import { IoWarningOutline } from 'react-icons/io5';
+import { Address } from 'viem';
import { getSlicedAddress } from '@/utils/address';
import { getExplorerURL } from '@/utils/external';
+import { OracleVendors, OracleVendorIcons } from '@/utils/oracle';
+import { OracleFeed } from '@/utils/types';
export function OracleFeedInfo({
- address,
- title,
+ feed,
chainId,
}: {
- address: string;
- title: string | null;
+ feed: OracleFeed | null;
chainId: number;
-}): JSX.Element {
- const isLink = address !== zeroAddress;
+}): JSX.Element | null {
+ if (!feed) return null;
+
+ const fromAsset = feed.pair?.[0] ?? 'Unknown';
+ const toAsset = feed.pair?.[1] ?? 'Unknown';
+
+ const vendorIcon =
+ OracleVendorIcons[feed.vendor as OracleVendors] ?? OracleVendorIcons[OracleVendors.Unknown];
+
+ const content = (
+
+
+ {fromAsset}
+
+ {toAsset}
+
+ {vendorIcon ? (
+
+ ) : (
+
+ )}
+
+ );
- if (isLink)
return (
-
- {title ? (
- {title}
- ) : (
- {getSlicedAddress(address as Address)}
- )}
-
-
+
+ {content}
+
+
+
);
+ }
- return (
- Hardcoded 1
- );
-}
diff --git a/src/components/OracleVendorBadge.tsx b/src/components/OracleVendorBadge.tsx
new file mode 100644
index 00000000..e9bace9f
--- /dev/null
+++ b/src/components/OracleVendorBadge.tsx
@@ -0,0 +1,70 @@
+import React from 'react';
+import { Tooltip } from '@nextui-org/tooltip';
+import Image from 'next/image';
+import { IoWarningOutline } from 'react-icons/io5';
+import { OracleVendorIcons, OracleVendors, parseOracleVendors } from '@/utils/oracle';
+import { MorphoChainlinkOracleData } from '@/utils/types';
+
+type OracleVendorBadgeProps = {
+ oracleData: MorphoChainlinkOracleData | null;
+ useTooltip?: boolean;
+};
+
+const renderVendorIcon = (vendor: OracleVendors) => (
+ OracleVendorIcons[vendor]
+ ?
+ :
+);
+
+function OracleVendorBadge({ oracleData, useTooltip = true }: OracleVendorBadgeProps) {
+ const { vendors } = parseOracleVendors(oracleData);
+
+ const noFeeds = vendors.length === 0;
+
+ const content = (
+
+ {!useTooltip && (
+
+ {noFeeds ? 'No Oracle' : vendors.join(', ')}:
+
+ )}
+ {noFeeds ? (
+
+ ) : (
+ vendors.map((vendor, index) => (
+
+ {renderVendorIcon(vendor)}
+
+ ))
+ )}
+
+ );
+
+ if (useTooltip) {
+ return (
+
+
+ {noFeeds ? 'No Oracle Feed Used' : 'Oracle Vendors:'}
+
+
+ {vendors.map((vendor, index) => (
+ -
+ {vendor}
+
+ ))}
+
+
+ }
+ className="rounded-sm"
+ >
+ {content}
+
+ );
+ }
+
+ return content;
+};
+
+export default OracleVendorBadge;
diff --git a/src/components/TokenIcon.tsx b/src/components/TokenIcon.tsx
index 21511675..414c678c 100644
--- a/src/components/TokenIcon.tsx
+++ b/src/components/TokenIcon.tsx
@@ -12,7 +12,6 @@ type TokenIconProps = {
export function TokenIcon({ address, chainId, width, height }: TokenIconProps) {
const token = findToken(address, chainId);
-
if (!token?.img) {
return
;
}
diff --git a/src/hooks/useMarkets.ts b/src/hooks/useMarkets.ts
index df5b67c6..5fb5d147 100644
--- a/src/hooks/useMarkets.ts
+++ b/src/hooks/useMarkets.ts
@@ -25,6 +25,17 @@ export type Reward = {
};
const marketsQuery = `
+ fragment FeedFields on OracleFeed {
+ address
+ chain {
+ id
+ }
+ description
+ id
+ pair
+ vendor
+ }
+
query getMarkets($first: Int, $where: MarketFilters) {
markets(first: $first, where: $where) {
items {
@@ -47,21 +58,37 @@ const marketsQuery = `
type
__typename
}
- oracleFeed {
- baseFeedOneAddress
- baseFeedOneDescription
- baseFeedTwoAddress
- baseFeedTwoDescription
- quoteFeedOneAddress
- quoteFeedOneDescription
- quoteFeedTwoAddress
- quoteFeedTwoDescription
- baseVault
- baseVaultDescription
- baseVaultVendor
- quoteVault
- quoteVaultDescription
- quoteVaultVendor
+ oracle {
+ data {
+ ... on MorphoChainlinkOracleData {
+ baseFeedOne {
+ ...FeedFields
+ }
+ baseFeedTwo {
+ ...FeedFields
+ }
+ quoteFeedOne {
+ ...FeedFields
+ }
+ quoteFeedTwo {
+ ...FeedFields
+ }
+ }
+ ... on MorphoChainlinkOracleV2Data {
+ baseFeedOne {
+ ...FeedFields
+ }
+ baseFeedTwo {
+ ...FeedFields
+ }
+ quoteFeedOne {
+ ...FeedFields
+ }
+ quoteFeedTwo {
+ ...FeedFields
+ }
+ }
+ }
}
loanAsset {
id
diff --git a/src/hooks/useUserPositions.ts b/src/hooks/useUserPositions.ts
index 1fa731c7..2971c821 100644
--- a/src/hooks/useUserPositions.ts
+++ b/src/hooks/useUserPositions.ts
@@ -5,127 +5,155 @@ import { SupportedNetworks } from '@/utils/networks';
import { MarketPosition, UserTransaction } from '@/utils/types';
import { getMarketWarningsWithDetail } from '@/utils/warnings';
-const query = `query getUserMarketPositions(
- $address: String!
- $chainId: Int
-) {
- userByAddress(address: $address, chainId: $chainId) {
- marketPositions {
- supplyShares
- supplyAssets
- supplyAssetsUsd
- borrowShares
- borrowAssets
- borrowAssetsUsd
- market {
- id
- uniqueKey
- lltv
- oracleAddress
- irmAddress
- morphoBlue {
+const query = `
+ fragment FeedFields on OracleFeed {
+ address
+ chain {
+ id
+ }
+ description
+ id
+ pair
+ vendor
+ }
+
+ query getUserMarketPositions(
+ $address: String!
+ $chainId: Int
+ ) {
+ userByAddress(address: $address, chainId: $chainId) {
+ marketPositions {
+ supplyShares
+ supplyAssets
+ supplyAssetsUsd
+ borrowShares
+ borrowAssets
+ borrowAssetsUsd
+ market {
id
- address
- chain {
+ uniqueKey
+ lltv
+ oracleAddress
+ irmAddress
+ morphoBlue {
id
+ address
+ chain {
+ id
+ }
}
- }
- dailyApys {
- netSupplyApy
- }
- weeklyApys {
- netSupplyApy
- }
- monthlyApys {
- netSupplyApy
- }
- loanAsset {
- address
- symbol
- decimals
- }
- collateralAsset {
- address
- symbol
- decimals
- }
- state {
- liquidityAssets
- supplyAssetsUsd
- supplyAssets
- borrowAssets
- borrowAssetsUsd
- rewards {
- yearlySupplyTokens
- asset {
- address
- priceUsd
- spotPriceEth
+ dailyApys {
+ netSupplyApy
+ }
+ weeklyApys {
+ netSupplyApy
+ }
+ monthlyApys {
+ netSupplyApy
+ }
+ loanAsset {
+ address
+ symbol
+ decimals
+ }
+ collateralAsset {
+ address
+ symbol
+ decimals
+ }
+ state {
+ liquidityAssets
+ supplyAssetsUsd
+ supplyAssets
+ borrowAssets
+ borrowAssetsUsd
+ rewards {
+ yearlySupplyTokens
+ asset {
+ address
+ priceUsd
+ spotPriceEth
+ }
}
+ utilization
+ }
+ oracle {
+ data {
+ ... on MorphoChainlinkOracleData {
+ baseFeedOne {
+ ...FeedFields
+ }
+ baseFeedTwo {
+ ...FeedFields
+ }
+ quoteFeedOne {
+ ...FeedFields
+ }
+ quoteFeedTwo {
+ ...FeedFields
+ }
+ }
+ ... on MorphoChainlinkOracleV2Data {
+ baseFeedOne {
+ ...FeedFields
+ }
+ baseFeedTwo {
+ ...FeedFields
+ }
+ quoteFeedOne {
+ ...FeedFields
+ }
+ quoteFeedTwo {
+ ...FeedFields
+ }
+ }
+ }
+ }
+ oracleInfo {
+ type
+ }
+ warnings {
+ type
+ level
+ __typename
}
- utilization
- }
- oracleFeed {
- baseFeedOneAddress
- baseFeedOneDescription
- baseFeedTwoAddress
- baseFeedTwoDescription
- quoteFeedOneAddress
- quoteFeedOneDescription
- quoteFeedTwoAddress
- quoteFeedTwoDescription
- baseVault
- baseVaultDescription
- baseVaultVendor
- quoteVault
- quoteVaultDescription
- quoteVaultVendor
- }
- oracleInfo {
- type
- }
- warnings {
- type
- level
- __typename
}
}
- }
- transactions {
- hash
- timestamp
- type
- data {
- __typename
- ... on MarketTransferTransactionData {
- assetsUsd
- shares
- assets
- market {
- id
- uniqueKey
- morphoBlue {
- chain {
+ transactions {
+ hash
+ timestamp
+ type
+ data {
+ __typename
+ ... on MarketTransferTransactionData {
+ assetsUsd
+ shares
+ assets
+ market {
+ id
+ uniqueKey
+ morphoBlue {
+ chain {
+ id
+ }
+ }
+ collateralAsset {
id
+ address
+ decimals
}
+ loanAsset {
+ id
+ address
+ decimals
+ symbol
+ }
}
- collateralAsset {
- id
- address
- decimals
- }
- loanAsset {
- id
- address
- decimals
- symbol
- }
}
}
}
}
- }
-}`;
+ }`;
const useUserPositions = (user: string | undefined) => {
const [loading, setLoading] = useState(true);
diff --git a/src/imgs/oracles/chainlink.png b/src/imgs/oracles/chainlink.png
new file mode 100644
index 00000000..63a1f357
Binary files /dev/null and b/src/imgs/oracles/chainlink.png differ
diff --git a/src/imgs/oracles/compound.webp b/src/imgs/oracles/compound.webp
new file mode 100644
index 00000000..e0eed57e
Binary files /dev/null and b/src/imgs/oracles/compound.webp differ
diff --git a/src/imgs/oracles/lido.png b/src/imgs/oracles/lido.png
new file mode 100644
index 00000000..b0800b61
Binary files /dev/null and b/src/imgs/oracles/lido.png differ
diff --git a/src/imgs/oracles/pyth.png b/src/imgs/oracles/pyth.png
new file mode 100644
index 00000000..65288213
Binary files /dev/null and b/src/imgs/oracles/pyth.png differ
diff --git a/src/imgs/oracles/redstone.png b/src/imgs/oracles/redstone.png
new file mode 100644
index 00000000..932d5056
Binary files /dev/null and b/src/imgs/oracles/redstone.png differ
diff --git a/src/imgs/oracles/uma.png b/src/imgs/oracles/uma.png
new file mode 100644
index 00000000..bb637190
Binary files /dev/null and b/src/imgs/oracles/uma.png differ
diff --git a/src/utils/oracle.ts b/src/utils/oracle.ts
new file mode 100644
index 00000000..61a871df
--- /dev/null
+++ b/src/utils/oracle.ts
@@ -0,0 +1,57 @@
+import { MorphoChainlinkOracleData } from './types';
+
+type VendorInfo = {
+ vendors: OracleVendors[];
+ isUnknown: boolean;
+};
+
+export enum OracleVendors {
+ Chainlink = 'Chainlink',
+ PythNetwork = 'Pyth Network',
+ Redstone = 'Redstone',
+ Oval = 'Oval',
+ Compound = 'Compound',
+ Lido = 'Lido',
+ Unknown = 'Unknown',
+}
+
+export const OracleVendorIcons: Record
= {
+ [OracleVendors.Chainlink]: require('../imgs/oracles/chainlink.png') as string,
+ [OracleVendors.PythNetwork]: require('../imgs/oracles/pyth.png') as string,
+ [OracleVendors.Redstone]: require('../imgs/oracles/redstone.png') as string,
+ [OracleVendors.Oval]: require('../imgs/oracles/uma.png') as string,
+ [OracleVendors.Compound]: require('../imgs/oracles/compound.webp') as string,
+ [OracleVendors.Lido]: require('../imgs/oracles/lido.png') as string,
+ [OracleVendors.Unknown]: '',
+};
+
+export function parseOracleVendors(oracleData: MorphoChainlinkOracleData | null): VendorInfo {
+ if (!oracleData) return { vendors: [], isUnknown: false };
+ if (
+ !oracleData.baseFeedOne &&
+ !oracleData.baseFeedTwo &&
+ !oracleData.quoteFeedOne &&
+ !oracleData.quoteFeedTwo
+ )
+ return { vendors: [], isUnknown: true };
+
+ const feeds = [
+ oracleData.baseFeedOne,
+ oracleData.baseFeedTwo,
+ oracleData.quoteFeedOne,
+ oracleData.quoteFeedTwo,
+ ];
+
+ const vendors = new Set(
+ feeds
+ .filter(feed => feed?.vendor)
+ .map(feed => Object.values(OracleVendors)
+ .find(v => v.toLowerCase() === feed!.vendor!.toLowerCase()) ?? OracleVendors.Unknown
+ )
+ );
+
+ return {
+ vendors: Array.from(vendors),
+ isUnknown: vendors.has(OracleVendors.Unknown) || vendors.size === 0,
+ };
+}
diff --git a/src/utils/types.ts b/src/utils/types.ts
index a7ec64db..c40d12b3 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -58,6 +58,9 @@ export type MarketPosition = {
utilization: number;
};
warnings: MarketWarning[];
+ oracle: {
+ data: MorphoChainlinkOracleData;
+ };
};
warningsWithDetail: WarningWithDetail[];
};
@@ -243,7 +246,26 @@ export type GroupedPosition = {
allWarnings: WarningWithDetail[];
};
-// Add this type to the existing types in the file
+// Add these new types
+export type OracleFeed = {
+ address: string;
+ chain: {
+ id: number;
+ };
+ description: string | null;
+ id: string;
+ pair: string[] | null;
+ vendor: string | null;
+};
+
+export type MorphoChainlinkOracleData = {
+ baseFeedOne: OracleFeed | null;
+ baseFeedTwo: OracleFeed | null;
+ quoteFeedOne: OracleFeed | null;
+ quoteFeedTwo: OracleFeed | null;
+};
+
+// Update the Market type
export type Market = {
id: string;
lltv: string;
@@ -261,7 +283,6 @@ export type Market = {
oracleInfo: {
type: string;
};
- oracleFeed?: OracleFeedsInfo;
loanAsset: TokenInfo;
collateralAsset: TokenInfo;
state: {
@@ -306,4 +327,7 @@ export type Market = {
rewardPer1000USD?: string;
warningsWithDetail: WarningWithDetail[];
isProtectedByLiquidationBots: boolean;
+ oracle: {
+ data: MorphoChainlinkOracleData;
+ };
};
diff --git a/src/utils/warnings.ts b/src/utils/warnings.ts
index 6aca8fb6..1e645d1b 100644
--- a/src/utils/warnings.ts
+++ b/src/utils/warnings.ts
@@ -5,13 +5,13 @@ const morphoOfficialWarnings: WarningWithDetail[] = [
{
code: 'hardcoded_oracle',
level: 'warning',
- description: 'This market uses a hardcoded oracle value',
+ description: 'This market uses a hardcoded oracle value (or missing one or more feed routes)',
category: WarningCategory.oracle,
},
{
code: 'hardcoded_oracle_feed',
level: 'warning',
- description: 'This market is using a hardcoded value in its oracle. ',
+ description: 'This market is using a hardcoded value in one or more of its feed routes',
category: WarningCategory.oracle,
},
{
@@ -92,7 +92,7 @@ const morphoOfficialWarnings: WarningWithDetail[] = [
},
];
-export const getMarketWarningsWithDetail = (market: {warnings: MarketWarning[]}) => {
+export const getMarketWarningsWithDetail = (market: { warnings: MarketWarning[] }) => {
const result = [];
// process official warnings