Skip to content

Commit

Permalink
fix morpho tvl and reuse asset icons
Browse files Browse the repository at this point in the history
  • Loading branch information
vidvidvid committed Dec 17, 2024
1 parent a650745 commit ffd3fea
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Image from 'next/image';

type RewardIconsProps = {
rewards: string[]; // Array of reward types like 'op', 'ionic', 'turtle', 'kelp', etc.
size?: number;
};

const iconMap = {
Expand All @@ -15,7 +16,8 @@ const iconMap = {
lsk: '/img/symbols/32/color/lsk.png'
};

export const RewardIcons = ({ rewards }: RewardIconsProps) => {
export const AssetIcons = ({ rewards, size = 16 }: RewardIconsProps) => {
console.log('size', size);
const getIconPath = (reward: string) => {
if (reward in iconMap) {
return iconMap[reward as keyof typeof iconMap];
Expand All @@ -34,12 +36,12 @@ export const RewardIcons = ({ rewards }: RewardIconsProps) => {
zIndex: rewards.length - index // Higher z-index for earlier icons
}}
>
<div className="size-4 flex items-center justify-center">
<div className="flex items-center justify-center">
<Image
src={getIconPath(reward)}
alt={reward}
width={16}
height={16}
width={size}
height={size}
className="rounded-full"
/>
</div>
Expand Down
15 changes: 5 additions & 10 deletions packages/ui/app/_components/earn/EarnTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import CommonTable from '../CommonTable';
import type { EnhancedColumnDef } from '../CommonTable';
import ActionButton from '../ActionButton';
import { ExternalLink } from 'lucide-react';
import { AssetIcons } from '../AssetIcons';

export default function EarnTable() {
const [rows, setRows] = useState<EarnRow[]>(earnOpps);
Expand All @@ -38,16 +39,10 @@ export default function EarnTable() {
cell: ({ row }) => (
<div className="flex gap-3 items-center">
<div className="flex -space-x-1">
{row.original.asset.map((coin, idx) => (
<Image
key={idx}
src={`/img/symbols/32/color/${coin}.png`}
alt={coin}
width={28}
height={28}
className="w-7 h-7"
/>
))}
<AssetIcons
rewards={row.original.asset}
size={28}
/>
</div>
<div className="flex items-center gap-1">
{row.original.asset.map((val, idx) => (
Expand Down
53 changes: 29 additions & 24 deletions packages/ui/app/_components/earn/MorphoTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import CommonTable from '../CommonTable';
import type { EnhancedColumnDef } from '../CommonTable';
import { useState } from 'react';
import ActionButton from '../ActionButton';
import { AssetIcons } from '../AssetIcons';

export default function MorphoTable() {
const { rows, isLoading } = useMorphoData();
Expand All @@ -25,23 +26,19 @@ export default function MorphoTable() {
cell: ({ row }) => (
<div className="flex gap-3 items-center">
<div className="flex -space-x-1">
{row.original.asset.map((coin, idx) => (
<Image
key={idx}
src={`/img/symbols/32/color/${coin}.png`}
alt={coin}
width={28}
height={28}
className="w-7 h-7"
/>
))}
<AssetIcons
rewards={row.original.asset}
size={28}
/>
</div>
<div className="flex items-center gap-1">
{row.original.asset.map((val, idx) => (
<span key={idx}>
{idx !== 0 && '/'}
{val}
</span>
<>
<span key={idx}>
{idx !== 0 && '/'} {val}
</span>
<span className="w-7" />
</>
))}
</div>
</div>
Expand Down Expand Up @@ -91,22 +88,30 @@ export default function MorphoTable() {
header: 'APR',
sortingFn: 'numerical',
cell: ({ row }) => (
<span>{row.original.apr > 0 ? `${row.original.apr}%` : '∞%'}</span>
<span>
{row.original.apr > 0 ? `${row.original.apr.toFixed(4)}%` : '∞%'}
</span>
)
},
{
id: 'tvl',
header: 'TVL',
sortingFn: 'numerical',
cell: ({ row }) => (
<span>
$
{row.original.tvl > 0
? row.original.tvl.toLocaleString(undefined, {
maximumFractionDigits: 2
})
: '-'}
</span>
<div className="flex flex-col items-start">
<span>
{row.original.tvl.tokenAmount.toLocaleString(undefined, {
maximumFractionDigits: 2
})}{' '}
{row.original.asset[0]}
</span>
<span className="text-xs text-white/40 font-light">
$
{row.original.tvl.usdValue.toLocaleString(undefined, {
maximumFractionDigits: 2
})}
</span>
</div>
)
},
{
Expand All @@ -128,7 +133,7 @@ export default function MorphoTable() {
return (
<>
<CommonTable
data={rows}
data={rows as MorphoRow[]}
columns={columns}
isLoading={isLoading}
/>
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/app/_components/markets/APRCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { pools } from '@ui/constants';
import { useAPRCell } from '@ui/hooks/market/useAPRCell';
import { cn } from '@ui/lib/utils';

import { RewardIcons } from './RewardsIcon';
import { AssetIcons } from '../AssetIcons';

import type { Address, Hex } from 'viem';

Expand Down Expand Up @@ -98,7 +98,7 @@ export default function APRCell(props: APRCellProps) {
)}
>
<span>+ Rewards</span>
<RewardIcons rewards={rewardIcons} />
<AssetIcons rewards={rewardIcons} />
</div>
)}

Expand Down
97 changes: 78 additions & 19 deletions packages/ui/hooks/earn/useMorphoData.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// hooks/useMorphoData.ts
import { useQuery } from '@tanstack/react-query';
import { request, gql } from 'graphql-request';
import { utils } from 'ethers';
import axios from 'axios';

import { morphoVaults } from '@ui/utils/morphoUtils';

const MORPHO_API_URL = 'https://blue-api.morpho.org/graphql';
const COINGECKO_API = 'https://api.coingecko.com/api/v3/simple/price';

const VAULT_QUERY = gql`
{
Expand Down Expand Up @@ -32,7 +34,7 @@ const VAULT_QUERY = gql`
interface VaultData {
symbol: string;
state: {
totalAssets: number | string;
totalAssets: string;
rewards: Array<{
asset: {
address: string;
Expand All @@ -50,37 +52,94 @@ interface MorphoResponse {
};
}

const fetchTokenPrices = async () => {
try {
const response = await axios.get(
`${COINGECKO_API}?ids=ethereum,usd-coin&vs_currencies=usd`
);
return {
WETH: response.data.ethereum.usd,
USDC: response.data['usd-coin'].usd
};
} catch (error) {
console.error('Error fetching token prices:', error);
return { WETH: 0, USDC: 1 }; // Default values
}
};

const formatTVL = (
totalAssets: string,
symbol: string,
prices: { WETH: number; USDC: number }
): {
tokenAmount: number;
usdValue: number;
} => {
try {
// Use appropriate decimals based on the asset
const decimals = symbol.includes('WETH') ? 18 : 6;
// Convert from big number to human readable format
const formatted = utils.formatUnits(totalAssets, decimals);
// Parse to number for display
const tokenAmount = parseFloat(formatted);
// Calculate USD value
const price = symbol.includes('WETH') ? prices.WETH : prices.USDC;
const usdValue = tokenAmount * price;

return { tokenAmount, usdValue };
} catch (error) {
console.error(`Error formatting TVL for ${symbol}:`, error);
return { tokenAmount: 0, usdValue: 0 };
}
};

const fetchMorphoData = async (): Promise<MorphoResponse> => {
return request(MORPHO_API_URL, VAULT_QUERY);
};

export const useMorphoData = () => {
const { data, isLoading, error } = useQuery({
const { data: vaultData, isLoading: isVaultLoading } = useQuery({
queryKey: ['morphoVaults'],
queryFn: fetchMorphoData,
staleTime: 30000,
refetchInterval: 60000
});

const rows = morphoVaults.map((row) => {
const vaultSymbol = `ionic${row.asset[0]}`;
const vaultData = data?.vaults.items.find(
(vault) => vault.symbol === vaultSymbol
);

if (vaultData) {
return {
...row,
apr: vaultData.state.rewards[0]?.supplyApr ?? 0,
tvl: Number(vaultData.state.totalAssets) ?? 0
};
}
return row;
const { data: prices, isLoading: isPricesLoading } = useQuery({
queryKey: ['tokenPrices'],
queryFn: fetchTokenPrices,
staleTime: 60000,
refetchInterval: 300000 // 5 minutes
});

const rows =
vaultData?.vaults.items.map((vault) => {
const baseVault = morphoVaults.find(
(row) => `ionic${row.asset[0]}` === vault.symbol
);

if (baseVault && prices) {
const { tokenAmount, usdValue } = formatTVL(
vault.state.totalAssets,
vault.symbol,
prices
);

return {
...baseVault,
apr: vault.state.rewards[0]?.supplyApr ?? 0,
tvl: {
tokenAmount,
usdValue
}
};
}
return baseVault;
}) ?? [];

return {
rows,
isLoading,
error: error as Error | null
isLoading: isVaultLoading || isPricesLoading,
error: null
};
};
9 changes: 7 additions & 2 deletions packages/ui/types/Earn.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
export interface TVLData {
tokenAmount: number;
usdValue: number;
}

export interface MorphoRow {
asset: string[];
protocol: string;
strategy: string;
network: string;
apr: number;
tvl: number;
tvl: TVLData;
img: string;
link: string;
live: boolean;
getApr?: () => Promise<number>;
getTvl?: () => Promise<number>;
getTvl?: () => Promise<TVLData>;
}

export type EarnRow = {
Expand Down
22 changes: 9 additions & 13 deletions packages/ui/utils/morphoUtils.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
// utils/morphoUtils.ts
import type { MorphoRow } from '@ui/types/Earn';

export const morphoVaults: MorphoRow[] = [
export const morphoVaults: Omit<MorphoRow, 'apr' | 'tvl'>[] = [
{
asset: ['WETH'],
protocol: 'Morpho',
strategy: 'Vault',
network: 'Base',
apr: 0,
tvl: 0,
img: '/img/protocols/morpho.png',
link: '#',
strategy: 'Supply',
network: 'base',
img: '/img/symbols/32/color/morpho.png',
link: 'https://morpho.org',
live: true
},
{
asset: ['USDC'],
protocol: 'Morpho',
strategy: 'Vault',
network: 'Base',
apr: 0,
tvl: 0,
img: '/img/protocols/morpho.png',
link: '#',
strategy: 'Supply',
network: 'base',
img: '/img/symbols/32/color/morpho.png',
link: 'https://morpho.org',
live: true
}
];
Expand Down

0 comments on commit ffd3fea

Please sign in to comment.