diff --git a/app/positions/report/components/ReportTable.tsx b/app/positions/report/components/ReportTable.tsx
index b4f02681..0cc4d287 100644
--- a/app/positions/report/components/ReportTable.tsx
+++ b/app/positions/report/components/ReportTable.tsx
@@ -75,6 +75,7 @@ function MarketSummaryBlock({
diff --git a/next.config.js b/next.config.js
index d8d54fa8..be1c119b 100644
--- a/next.config.js
+++ b/next.config.js
@@ -18,6 +18,10 @@ const nextConfig = {
protocol: 'https',
hostname: 'api.dicebear.com',
},
+ {
+ protocol: 'https',
+ hostname: '**',
+ },
],
},
};
diff --git a/package.json b/package.json
index 06c434bb..466c1a00 100644
--- a/package.json
+++ b/package.json
@@ -71,7 +71,8 @@
"unified": "^11.0.4",
"viem": "2.x",
"wagmi": "^2.10.2",
- "workbox-webpack-plugin": "^7.0.0"
+ "workbox-webpack-plugin": "^7.0.0",
+ "zod": "^3.24.2"
},
"devDependencies": {
"@babel/preset-env": "^7.23.5",
diff --git a/src/components/Borrow/AddCollateralAndBorrow.tsx b/src/components/Borrow/AddCollateralAndBorrow.tsx
index 585d4233..ceb43da1 100644
--- a/src/components/Borrow/AddCollateralAndBorrow.tsx
+++ b/src/components/Borrow/AddCollateralAndBorrow.tsx
@@ -178,6 +178,7 @@ export function AddCollateralAndBorrow({
@@ -196,6 +197,7 @@ export function AddCollateralAndBorrow({
diff --git a/src/components/Borrow/WithdrawCollateralAndRepay.tsx b/src/components/Borrow/WithdrawCollateralAndRepay.tsx
index 808d5e77..002887eb 100644
--- a/src/components/Borrow/WithdrawCollateralAndRepay.tsx
+++ b/src/components/Borrow/WithdrawCollateralAndRepay.tsx
@@ -185,6 +185,7 @@ export function WithdrawCollateralAndRepay({
@@ -203,6 +204,7 @@ export function WithdrawCollateralAndRepay({
diff --git a/src/components/BorrowModal.tsx b/src/components/BorrowModal.tsx
index ac62c540..7f47c01b 100644
--- a/src/components/BorrowModal.tsx
+++ b/src/components/BorrowModal.tsx
@@ -68,6 +68,7 @@ export function BorrowModal({ market, onClose }: BorrowModalProps): JSX.Element
diff --git a/src/components/TokenIcon.tsx b/src/components/TokenIcon.tsx
index 7bbdb0db..c97a0fa5 100644
--- a/src/components/TokenIcon.tsx
+++ b/src/components/TokenIcon.tsx
@@ -1,30 +1,57 @@
-import React from 'react';
+import React, { useMemo } from 'react';
+import { Tooltip } from '@nextui-org/tooltip';
import Image from 'next/image';
-import { findToken } from '@/utils/tokens';
-
+import { useTokens } from '@/components/providers/TokenProvider';
+import { TooltipContent } from './TooltipContent';
type TokenIconProps = {
address: string;
chainId: number;
width: number;
height: number;
opacity?: number;
+ symbol?: string;
};
export function TokenIcon({ address, chainId, width, height, opacity }: TokenIconProps) {
- const token = findToken(address, chainId);
- if (!token?.img) {
- return
;
- }
+ const { findToken } = useTokens();
- return (
-
findToken(address, chainId), [address, chainId, findToken]);
+
+ // If we have a token with an image, use that
+ if (token?.img) {
+ const img =
- );
+
+ const detail = token.isFactoryToken
+ ? `This token is auto-detected from ${token.protocol?.name} `
+ : `This token is trusted by Monarch whitelist`;
+
+ return (
+ }
+ >
+
+
+ );
+ }
+
+ // Fallback to placeholder
+ return ;
}
diff --git a/src/components/common/MarketInfoBlock.tsx b/src/components/common/MarketInfoBlock.tsx
index b53084b3..43af1739 100644
--- a/src/components/common/MarketInfoBlock.tsx
+++ b/src/components/common/MarketInfoBlock.tsx
@@ -23,8 +23,9 @@ export function MarketInfoBlock({ market, amount, className }: MarketInfoBlockPr
@@ -64,8 +65,9 @@ export function MarketInfoBlockCompact({
diff --git a/src/components/providers/ClientProviders.tsx b/src/components/providers/ClientProviders.tsx
index 899514b7..de5389a6 100644
--- a/src/components/providers/ClientProviders.tsx
+++ b/src/components/providers/ClientProviders.tsx
@@ -3,6 +3,9 @@
import { ReactNode } from 'react';
import { MarketsProvider } from '@/contexts/MarketsContext';
import { OnboardingProvider } from 'app/positions/components/onboarding/OnboardingContext';
+import { ConnectRedirectProvider } from './ConnectRedirectProvider';
+import { ThemeProviders } from './ThemeProvider';
+import { TokenProvider } from './TokenProvider';
type ClientProvidersProps = {
children: ReactNode;
@@ -10,8 +13,14 @@ type ClientProvidersProps = {
export function ClientProviders({ children }: ClientProvidersProps) {
return (
-
- {children}
-
+
+
+
+
+ {children}
+
+
+
+
);
}
diff --git a/src/components/providers/TokenProvider.tsx b/src/components/providers/TokenProvider.tsx
new file mode 100644
index 00000000..938bc6d1
--- /dev/null
+++ b/src/components/providers/TokenProvider.tsx
@@ -0,0 +1,118 @@
+import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
+import { mainnet, base } from 'viem/chains';
+import { z } from 'zod';
+import { supportedTokens } from '@/utils/tokens';
+import type { ERC20Token } from '@/utils/tokens';
+
+// Only parse the fields we need
+const PendleAssetSchema = z.object({
+ address: z.string(),
+ chainId: z.number(),
+ symbol: z.string(),
+ decimals: z.number(),
+ proIcon: z.string().nullable(),
+});
+
+type PendleAsset = z.infer
;
+
+type TokenContextType = {
+ allTokens: ERC20Token[];
+ findToken: (address: string, chainId: number) => ERC20Token | undefined;
+ getUniqueTokens: (tokenList: { address: string; chainId: number }[]) => ERC20Token[];
+}
+
+const TokenContext = createContext(null);
+
+async function fetchPendleAssets(chainId: number): Promise {
+ try {
+ const response = await fetch(`https://api-v2.pendle.finance/core/v1/${chainId}/assets/all`);
+ if (!response.ok) return [];
+ const data = await response.json() as PendleAsset[];
+ return z.array(PendleAssetSchema).parse(data);
+ } catch (error) {
+ console.error(`Error fetching Pendle assets for chain ${chainId}:`, error);
+ return [];
+ }
+}
+
+function convertPendleAssetToToken(asset: PendleAsset): ERC20Token {
+ return {
+ symbol: asset.symbol,
+ decimals: asset.decimals,
+ img: asset.proIcon ?? undefined,
+ networks: [
+ {
+ chain: asset.chainId === 1 ? mainnet : base,
+ address: asset.address,
+ },
+ ],
+ isFactoryToken: true,
+ protocol: {
+ name: 'Pendle',
+ },
+ };
+}
+
+export function TokenProvider({ children }: { children: React.ReactNode }) {
+ const [allTokens, setAllTokens] = useState(supportedTokens);
+
+ useEffect(() => {
+ async function fetchAllAssets() {
+ try {
+ const [mainnetAssets, baseAssets] = await Promise.all([
+ fetchPendleAssets(1),
+ fetchPendleAssets(8453),
+ ]);
+ const pendleTokens = [
+ ...mainnetAssets.map(convertPendleAssetToToken),
+ ...baseAssets.map(convertPendleAssetToToken),
+ ];
+
+ setAllTokens([...supportedTokens, ...pendleTokens]);
+ } catch (err) {
+ console.error('Error fetching Pendle assets:', err);
+ }
+ }
+
+ void fetchAllAssets();
+ }, []);
+
+ const findToken = useCallback((address: string, chainId: number) => {
+ return allTokens.find((token) =>
+ token.networks.some(
+ (network) =>
+ network.address.toLowerCase() === address.toLowerCase() && network.chain.id === chainId,
+ ),
+ );
+ },
+ [allTokens],
+ );
+
+ const getUniqueTokens = useCallback((tokenList: { address: string; chainId: number }[]) => {
+ return allTokens.filter((token) => {
+ return tokenList.find((item) =>
+ token.networks.find(
+ (network) =>
+ network.address.toLowerCase() === item.address.toLowerCase() &&
+ network.chain.id === item.chainId,
+ ),
+ );
+ });
+ }, [allTokens]);
+
+ const value = useMemo(() => ({ allTokens, findToken, getUniqueTokens }), [allTokens, findToken, getUniqueTokens]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useTokens() {
+ const context = useContext(TokenContext);
+ if (!context) {
+ throw new Error('useTokens must be used within a TokenProvider');
+ }
+ return context;
+}
\ No newline at end of file
diff --git a/src/components/supplyModal.tsx b/src/components/supplyModal.tsx
index cae60f62..99f487a4 100644
--- a/src/components/supplyModal.tsx
+++ b/src/components/supplyModal.tsx
@@ -89,6 +89,7 @@ export function SupplyModal({ market, onClose }: SupplyModalProps): JSX.Element
@@ -185,6 +186,7 @@ export function SupplyModal({ market, onClose }: SupplyModalProps): JSX.Element
diff --git a/src/components/withdrawModal.tsx b/src/components/withdrawModal.tsx
index 20a6eaae..5c39177a 100644
--- a/src/components/withdrawModal.tsx
+++ b/src/components/withdrawModal.tsx
@@ -116,6 +116,7 @@ export function WithdrawModal({ position, onClose, refetch }: ModalProps): JSX.E
@@ -145,6 +146,7 @@ export function WithdrawModal({ position, onClose, refetch }: ModalProps): JSX.E
@@ -166,6 +168,7 @@ export function WithdrawModal({ position, onClose, refetch }: ModalProps): JSX.E
diff --git a/src/hooks/useUserBalances.ts b/src/hooks/useUserBalances.ts
index c6729bd6..bbc7c688 100644
--- a/src/hooks/useUserBalances.ts
+++ b/src/hooks/useUserBalances.ts
@@ -1,8 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import { useAccount } from 'wagmi';
+import { useTokens } from '@/components/providers/TokenProvider';
import { SupportedNetworks } from '@/utils/networks';
-import { findToken } from '@/utils/tokens';
-
type TokenBalance = {
address: string;
balance: string;
@@ -25,6 +24,8 @@ export function useUserBalances() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
+ const { findToken } = useTokens();
+
const fetchBalances = useCallback(
async (chainId: number): Promise => {
try {
diff --git a/src/utils/positions.ts b/src/utils/positions.ts
index 6e9152dc..9cc53165 100644
--- a/src/utils/positions.ts
+++ b/src/utils/positions.ts
@@ -251,6 +251,7 @@ export function groupPositionsByLoanAsset(
loanAsset: position.market.loanAsset.symbol || 'Unknown',
loanAssetAddress,
loanAssetDecimals,
+ loanAssetSymbol: position.market.loanAsset.symbol || 'Unknown',
chainId,
totalSupply: 0,
totalWeightedApy: 0,
diff --git a/src/utils/statsDataProcessing.ts b/src/utils/statsDataProcessing.ts
index f97c7063..bf1b6c94 100644
--- a/src/utils/statsDataProcessing.ts
+++ b/src/utils/statsDataProcessing.ts
@@ -1,7 +1,7 @@
import { formatUnits } from 'viem';
import { SupportedNetworks } from './networks';
import { AssetVolumeData, Transaction } from './statsUtils';
-import { findToken } from './tokens';
+import { findToken as findTokenStatic } from './tokens';
/**
* Process transaction data to extract detailed asset metrics
@@ -27,7 +27,7 @@ export const processTransactionData = (
if (!assetMetrics[assetKey]) {
// Try to find token info using findToken
- const token = findToken(assetAddress, chainId);
+ const token = findTokenStatic(assetAddress, chainId);
const symbol = token?.symbol ?? assetSymbolMap[assetAddress] ?? 'Unknown';
@@ -205,7 +205,7 @@ export const calculateHumanReadableVolumes = (
})[] => {
return assetMetrics.map((asset) => {
// Find token to get decimals
- const token = findToken(asset.assetAddress, asset.chainId ?? SupportedNetworks.Base);
+ const token = findTokenStatic(asset.assetAddress, asset.chainId ?? SupportedNetworks.Base);
const decimals = token?.decimals ?? 18;
// Calculate formatted values
diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts
index 37575650..366033ae 100644
--- a/src/utils/tokens.ts
+++ b/src/utils/tokens.ts
@@ -14,8 +14,8 @@ export type ERC20Token = {
networks: { chain: Chain; address: string }[];
protocol?: {
name: string;
- isProxy: boolean;
};
+ isFactoryToken?: boolean;
};
export type UnknownERC20Token = {
@@ -34,15 +34,15 @@ const MORPHO_LEGACY = '0x9994E35Db50125E0DF82e4c2dde62496CE330999';
const MORPHO_TOKEN_WRAPPER = '0x9d03bb2092270648d7480049d0e58d2fcf0e5123';
const supportedTokens = [
- // {
- // symbol: 'USDC',
- // img: require('../imgs/tokens/usdc.webp') as string,
- // decimals: 6,
- // networks: [
- // { chain: mainnet, address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' },
- // { chain: base, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' },
- // ],
- // },
+ {
+ symbol: 'USDC',
+ img: require('../imgs/tokens/usdc.webp') as string,
+ decimals: 6,
+ networks: [
+ { chain: mainnet, address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' },
+ { chain: base, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' },
+ ],
+ },
{
symbol: 'USDT',
img: require('../imgs/tokens/usdt.webp') as string,
@@ -485,14 +485,6 @@ const supportedTokens = [
},
];
-const isWhitelisted = (address: string, chainId: number) => {
- return supportedTokens.some((token) =>
- token.networks.some(
- (network) =>
- network.address.toLowerCase() === address.toLowerCase() && network.chain.id === chainId,
- ),
- );
-};
const findToken = (address: string, chainId: number) => {
return supportedTokens.find((token) =>
@@ -542,7 +534,6 @@ const isWETH = (address: string, chainId: number) => {
export {
supportedTokens,
isWETH,
- isWhitelisted,
findTokenWithKey,
findToken,
getUniqueTokens,
diff --git a/src/utils/types.ts b/src/utils/types.ts
index 8935b121..f5a5017b 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -218,6 +218,7 @@ export type GroupedPosition = {
loanAssetAddress: string;
loanAssetDecimals: number;
chainId: number;
+ loanAssetSymbol: string;
totalSupply: number;
totalWeightedApy: number;
diff --git a/yarn.lock b/yarn.lock
index b2e2256a..a4502061 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9579,9 +9579,9 @@ __metadata:
linkType: hard
"caniuse-lite@npm:^1.0.30001406, caniuse-lite@npm:^1.0.30001524, caniuse-lite@npm:^1.0.30001599, caniuse-lite@npm:^1.0.30001646":
- version: 1.0.30001646
- resolution: "caniuse-lite@npm:1.0.30001646"
- checksum: 10c0/ecdd87c08cd63fa32e11311dfa3543a52613b0b99498b6fe6f2c66af65cc27e2f7436fa5b2bc2bcf72174448a7670715b284d420de838bcf3e811864371a2465
+ version: 1.0.30001707
+ resolution: "caniuse-lite@npm:1.0.30001707"
+ checksum: 10c0/a1beaf84bad4f6617bbc5616d6bc0c9cab73e73f7e9e09b6466af5195b1bc393e0f6f19643d7a1c88bd3f4bfa122d7bea81cf6225ec3ade57d5b1dd3478c1a1b
languageName: node
linkType: hard
@@ -15315,6 +15315,7 @@ __metadata:
viem: "npm:2.x"
wagmi: "npm:^2.10.2"
workbox-webpack-plugin: "npm:^7.0.0"
+ zod: "npm:^3.24.2"
languageName: unknown
linkType: soft
@@ -20303,6 +20304,13 @@ __metadata:
languageName: node
linkType: hard
+"zod@npm:^3.24.2":
+ version: 3.24.2
+ resolution: "zod@npm:3.24.2"
+ checksum: 10c0/c638c7220150847f13ad90635b3e7d0321b36cce36f3fc6050ed960689594c949c326dfe2c6fa87c14b126ee5d370ccdebd6efb304f41ef5557a4aaca2824565
+ languageName: node
+ linkType: hard
+
"zustand@npm:4.4.1":
version: 4.4.1
resolution: "zustand@npm:4.4.1"