From c7f8ab6c88e0f0dfb031752fc96b6348735ab580 Mon Sep 17 00:00:00 2001 From: Anton Cheng Date: Thu, 24 Oct 2024 00:07:08 +0800 Subject: [PATCH 1/4] feat: add supply modal to position page --- app/markets/components/markets.tsx | 2 +- app/positions/components/PositionsContent.tsx | 23 +- .../components/PositionsSummaryTable.tsx | 54 +++-- .../components/SuppliedMarketsDetail.tsx | 24 ++- .../components/SupplyProcessModal.tsx | 0 .../components/supplyModal.tsx | 0 .../components/withdrawModal.tsx | 0 src/graphql/queries.ts | 198 ++++++++++++++++++ src/hooks/useMarkets.ts | 142 +------------ src/hooks/useUserPositions.ts | 162 +------------- src/utils/types.ts | 63 +----- 11 files changed, 277 insertions(+), 391 deletions(-) rename {app/markets => src}/components/SupplyProcessModal.tsx (100%) rename {app/markets => src}/components/supplyModal.tsx (100%) rename {app/positions => src}/components/withdrawModal.tsx (100%) create mode 100644 src/graphql/queries.ts diff --git a/app/markets/components/markets.tsx b/app/markets/components/markets.tsx index a26cf000..8d6bed5c 100644 --- a/app/markets/components/markets.tsx +++ b/app/markets/components/markets.tsx @@ -10,12 +10,12 @@ import * as keys from '@/utils/storageKeys'; import { ERC20Token, getUniqueTokens } from '@/utils/tokens'; import { Market } from '@/utils/types'; +import { SupplyModal } from '../../../src/components/supplyModal'; import AssetFilter from './AssetFilter'; import CheckFilter from './CheckFilter'; import { SortColumn } from './constants'; import MarketsTable from './marketsTable'; import NetworkFilter from './NetworkFilter'; -import { SupplyModal } from './supplyModal'; import { applyFilterAndSort } from './utils'; const defaultSortColumn = Number( diff --git a/app/positions/components/PositionsContent.tsx b/app/positions/components/PositionsContent.tsx index b952b5b1..79d20a8e 100644 --- a/app/positions/components/PositionsContent.tsx +++ b/app/positions/components/PositionsContent.tsx @@ -10,11 +10,13 @@ import LoadingScreen from '@/components/Status/LoadingScreen'; import useUserPositions from '@/hooks/useUserPositions'; import { MarketPosition } from '@/utils/types'; +import { SupplyModal } from '../../../src/components/supplyModal'; +import { WithdrawModal } from '../../../src/components/withdrawModal'; import { PositionsSummaryTable } from './PositionsSummaryTable'; -import { WithdrawModal } from './withdrawModal'; export default function Positions() { - const [showModal, setShowModal] = useState(false); + const [showSupplyModal, setShowSupplyModal] = useState(false); + const [showWithdrawModal, setShowWithdrawModal] = useState(false); const [selectedPosition, setSelectedPosition] = useState(null); const { account } = useParams<{ account: string }>(); @@ -49,17 +51,27 @@ export default function Positions() { - {showModal && selectedPosition && ( + {showWithdrawModal && selectedPosition && ( { - setShowModal(false); + setShowWithdrawModal(false); setSelectedPosition(null); }} refetch={refetch} /> )} + {showSupplyModal && selectedPosition && ( + { + setShowSupplyModal(false); + setSelectedPosition(null); + }} + /> + )} + {loading ? ( ) : !hasSuppliedMarkets ? ( @@ -68,7 +80,8 @@ export default function Positions() {
void; + setShowWithdrawModal: (show: boolean) => void; + setShowSupplyModal: (show: boolean) => void; setSelectedPosition: (position: MarketPosition) => void; - refetch: (onSuccess?: () => void) => void; + refetch: () => void; isRefetching: boolean; }; export function PositionsSummaryTable({ marketPositions, - setShowModal, + setShowWithdrawModal, + setShowSupplyModal, setSelectedPosition, refetch, isRefetching, -}: PositionTableProps) { - const { address: account } = useAccount(); - +}: PositionsSummaryTableProps) { const [expandedRows, setExpandedRows] = useState>(new Set()); const [showRebalanceModal, setShowRebalanceModal] = useState(false); const [selectedGroupedPosition, setSelectedGroupedPosition] = useState( @@ -70,15 +69,15 @@ export function PositionsSummaryTable({ // Combine warnings from all markets groupedPosition.allWarnings = [ - ...new Set([...groupedPosition.allWarnings, ...position.warningsWithDetail]), - ]; + ...new Set([...groupedPosition.allWarnings, ...(position.market.warningsWithDetail || [])]), + ] as WarningWithDetail[]; const supplyAmount = Number( formatBalance(position.supplyAssets, position.market.loanAsset.decimals), ); groupedPosition.totalSupply += supplyAmount; - const weightedApy = supplyAmount * position.market.dailyApys.netSupplyApy; + const weightedApy = supplyAmount * position.market.state.supplyApy; groupedPosition.totalWeightedApy += weightedApy; const collateralAddress = position.market.collateralAsset?.address; @@ -163,9 +162,8 @@ export function PositionsSummaryTable({ }; const handleManualRefresh = () => { - refetch(() => { - toast.info('Data refreshed', { icon: 🚀 }); - }); + refetch(); + toast.info('Data refreshed', { icon: 🚀 }); }; return ( @@ -268,21 +266,18 @@ export function PositionsSummaryTable({
- + }} + > + Rebalance + + {isExpanded && ( @@ -290,7 +285,8 @@ export function PositionsSummaryTable({ diff --git a/app/positions/components/SuppliedMarketsDetail.tsx b/app/positions/components/SuppliedMarketsDetail.tsx index aeae1aeb..6e442bd4 100644 --- a/app/positions/components/SuppliedMarketsDetail.tsx +++ b/app/positions/components/SuppliedMarketsDetail.tsx @@ -11,7 +11,8 @@ import { getCollateralColor } from '../utils/colors'; type SuppliedMarketsDetailProps = { groupedPosition: GroupedPosition; - setShowModal: (show: boolean) => void; + setShowWithdrawModal: (show: boolean) => void; + setShowSupplyModal: (show: boolean) => void; setSelectedPosition: (position: MarketPosition) => void; }; @@ -38,7 +39,8 @@ function WarningTooltip({ warnings }: { warnings: WarningWithDetail[] }) { export function SuppliedMarketsDetail({ groupedPosition, - setShowModal, + setShowWithdrawModal, + setShowSupplyModal, setSelectedPosition, }: SuppliedMarketsDetailProps) { const sortedMarkets = [...groupedPosition.markets].sort( @@ -115,16 +117,16 @@ export function SuppliedMarketsDetail({ ); const percentageOfPortfolio = totalSupply > 0 ? (suppliedAmount / totalSupply) * 100 : 0; - const warningColor = getWarningColor(position.warningsWithDetail); + const warningColor = getWarningColor(position.market.warningsWithDetail); return (
- {position.warningsWithDetail.length > 0 ? ( + {position.market.warningsWithDetail.length > 0 ? ( } + content={} placement="top" >
@@ -198,12 +200,22 @@ export function SuppliedMarketsDetail({ type="button" className="bg-hovered rounded-sm p-1 text-xs duration-300 ease-in-out hover:bg-orange-500" onClick={() => { - setShowModal(true); setSelectedPosition(position); + setShowWithdrawModal(true); }} > Withdraw + ); diff --git a/app/markets/components/SupplyProcessModal.tsx b/src/components/SupplyProcessModal.tsx similarity index 100% rename from app/markets/components/SupplyProcessModal.tsx rename to src/components/SupplyProcessModal.tsx diff --git a/app/markets/components/supplyModal.tsx b/src/components/supplyModal.tsx similarity index 100% rename from app/markets/components/supplyModal.tsx rename to src/components/supplyModal.tsx diff --git a/app/positions/components/withdrawModal.tsx b/src/components/withdrawModal.tsx similarity index 100% rename from app/positions/components/withdrawModal.tsx rename to src/components/withdrawModal.tsx diff --git a/src/graphql/queries.ts b/src/graphql/queries.ts new file mode 100644 index 00000000..e647c73d --- /dev/null +++ b/src/graphql/queries.ts @@ -0,0 +1,198 @@ +export const feedFieldsFragment = ` + fragment FeedFields on OracleFeed { + address + chain { + id + } + description + id + pair + vendor + } +`; + +export const marketFragment = ` + fragment MarketFields on Market { + id + lltv + uniqueKey + irmAddress + oracleAddress + collateralPrice + morphoBlue { + id + address + chain { + id + } + } + oracleInfo { + type + } + loanAsset { + id + address + symbol + name + decimals + priceUsd + } + collateralAsset { + id + address + symbol + name + decimals + priceUsd + } + state { + borrowAssets + supplyAssets + borrowAssetsUsd + supplyAssetsUsd + borrowShares + supplyShares + liquidityAssets + liquidityAssetsUsd + collateralAssets + collateralAssetsUsd + utilization + supplyApy + borrowApy + fee + timestamp + rateAtUTarget + rewards { + yearlySupplyTokens + asset { + address + priceUsd + spotPriceEth + } + amountPerSuppliedToken + amountPerBorrowedToken + } + } + dailyApys { + netSupplyApy + netBorrowApy + } + warnings { + type + level + __typename + } + badDebt { + underlying + usd + } + realizedBadDebt { + underlying + usd + } + oracle { + data { + ... on MorphoChainlinkOracleData { + baseFeedOne { + ...FeedFields + } + baseFeedTwo { + ...FeedFields + } + quoteFeedOne { + ...FeedFields + } + quoteFeedTwo { + ...FeedFields + } + } + ... on MorphoChainlinkOracleV2Data { + baseFeedOne { + ...FeedFields + } + baseFeedTwo { + ...FeedFields + } + quoteFeedOne { + ...FeedFields + } + quoteFeedTwo { + ...FeedFields + } + } + } + } + } + ${feedFieldsFragment} +`; + +export const marketsQuery = ` + query getMarkets($first: Int, $where: MarketFilters) { + markets(first: $first, where: $where) { + items { + ...MarketFields + } + pageInfo { + countTotal + count + limit + skip + __typename + } + __typename + } + } + ${marketFragment} +`; + +export const userPositionsQuery = ` + query getUserMarketPositions($address: String!, $chainId: Int) { + userByAddress(address: $address, chainId: $chainId) { + marketPositions { + supplyShares + supplyAssets + supplyAssetsUsd + borrowShares + borrowAssets + borrowAssetsUsd + market { + ...MarketFields + } + } + 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 + } + } + } + } + } + } + } + ${marketFragment} +`; diff --git a/src/hooks/useMarkets.ts b/src/hooks/useMarkets.ts index 5fb5d147..f6347c64 100644 --- a/src/hooks/useMarkets.ts +++ b/src/hooks/useMarkets.ts @@ -2,6 +2,7 @@ 'use client'; import { useState, useEffect, useCallback } from 'react'; +import { marketsQuery } from '@/graphql/queries'; import { getRewardPer1000USD } from '@/utils/morpho'; import { isSupportedChain } from '@/utils/networks'; import { MORPHOTokenAddress } from '@/utils/tokens'; @@ -24,145 +25,6 @@ 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 { - id - lltv - uniqueKey - irmAddress - oracleAddress - collateralPrice - morphoBlue { - id - address - chain { - id - __typename - } - __typename - } - oracleInfo { - type - __typename - } - oracle { - data { - ... on MorphoChainlinkOracleData { - baseFeedOne { - ...FeedFields - } - baseFeedTwo { - ...FeedFields - } - quoteFeedOne { - ...FeedFields - } - quoteFeedTwo { - ...FeedFields - } - } - ... on MorphoChainlinkOracleV2Data { - baseFeedOne { - ...FeedFields - } - baseFeedTwo { - ...FeedFields - } - quoteFeedOne { - ...FeedFields - } - quoteFeedTwo { - ...FeedFields - } - } - } - } - loanAsset { - id - address - symbol - name - decimals - priceUsd - __typename - } - collateralAsset { - id - address - symbol - name - decimals - priceUsd - __typename - } - state { - borrowAssets - supplyAssets - borrowAssetsUsd - supplyAssetsUsd - borrowShares - supplyShares - liquidityAssets - liquidityAssetsUsd - collateralAssets - collateralAssetsUsd - utilization - supplyApy - borrowApy - fee - timestamp - rateAtUTarget - rewards { - yearlySupplyTokens - asset { - address - priceUsd - spotPriceEth - } - amountPerSuppliedToken - amountPerBorrowedToken - } - __typename - } - warnings { - type - level - __typename - } - badDebt { - underlying - usd - } - realizedBadDebt { - underlying - usd - } - } - pageInfo { - countTotal - count - limit - skip - __typename - } - __typename - } - } -`; - const useMarkets = () => { const [loading, setLoading] = useState(true); const [isRefetching, setIsRefetching] = useState(false); @@ -175,6 +37,8 @@ const useMarkets = () => { refetch: refetchLiquidations, } = useLiquidations(); + console.log('data', data); + const fetchData = useCallback( async (isRefetch = false) => { try { diff --git a/src/hooks/useUserPositions.ts b/src/hooks/useUserPositions.ts index 2971c821..be8ed381 100644 --- a/src/hooks/useUserPositions.ts +++ b/src/hooks/useUserPositions.ts @@ -1,160 +1,11 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { useState, useEffect, useCallback } from 'react'; +import { userPositionsQuery } from '@/graphql/queries'; import { SupportedNetworks } from '@/utils/networks'; import { MarketPosition, UserTransaction } from '@/utils/types'; import { getMarketWarningsWithDetail } from '@/utils/warnings'; -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 - 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 - } - } - 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 - } - } - } - 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 - } - } - } - } - } - } - }`; - const useUserPositions = (user: string | undefined) => { const [loading, setLoading] = useState(true); const [isRefetching, setIsRefetching] = useState(false); @@ -180,7 +31,7 @@ const useUserPositions = (user: string | undefined) => { 'Content-Type': 'application/json', }, body: JSON.stringify({ - query, + query: userPositionsQuery, variables: { address: user, chainId: SupportedNetworks.Mainnet, @@ -193,7 +44,7 @@ const useUserPositions = (user: string | undefined) => { 'Content-Type': 'application/json', }, body: JSON.stringify({ - query, + query: userPositionsQuery, variables: { address: user, chainId: SupportedNetworks.Base, @@ -226,7 +77,12 @@ const useUserPositions = (user: string | undefined) => { .filter((position: MarketPosition) => position.supplyShares.toString() !== '0') .map((position: MarketPosition) => ({ ...position, - warningsWithDetail: getMarketWarningsWithDetail(position.market), + + // add warningWithDetail to each market + market: { + ...position.market, + warningsWithDetail: getMarketWarningsWithDetail(position.market), + } })); setHistory(transactions); diff --git a/src/utils/types.ts b/src/utils/types.ts index c40d12b3..755d76f9 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -5,64 +5,7 @@ export type MarketPosition = { borrowShares: string; borrowAssets: string; borrowAssetsUsd: number; - market: { - id: string; - uniqueKey: string; - lltv: string; - oracleAddress: string; - oracleFeed?: OracleFeedsInfo; - oracleInfo: { - type: string; - }; - irmAddress: string; - morphoBlue: { - id: string; - address: string; - chain: { - id: number; - }; - }; - dailyApys: { - netSupplyApy: number; - }; - weeklyApys: { - netSupplyApy: number; - }; - monthlyApys: { - netSupplyApy: number; - }; - loanAsset: { - address: string; - symbol: string; - decimals: number; - }; - collateralAsset: { - address: string; - symbol: string; - decimals: number; - }; - state: { - liquidityAssets: string; - supplyAssets: string; - supplyAssetsUsd: number; - borrowAssets: string; - borrowAssetsUsd: number; - rewards: { - yearlySupplyTokens: string; - asset: { - address: string; - priceUsd: string | null; - spotPriceEth: string | null; - }; - }[]; - utilization: number; - }; - warnings: MarketWarning[]; - oracle: { - data: MorphoChainlinkOracleData; - }; - }; - warningsWithDetail: WarningWithDetail[]; + market: Market; // Now using the full Market type }; export enum UserTxTypes { @@ -322,6 +265,10 @@ export type Market = { underlying: number; usd: number; }; + dailyApys: { + netSupplyApy: number; + netBorrowApy: number; + }; // appended by us rewardPer1000USD?: string; From 6460c4442605fd6cec6dfe1057b6bc084aebfe8b Mon Sep 17 00:00:00 2001 From: Anton Cheng Date: Thu, 24 Oct 2024 00:09:18 +0800 Subject: [PATCH 2/4] chore: styling tweak --- app/positions/components/SuppliedMarketsDetail.tsx | 2 +- src/hooks/useUserPositions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/positions/components/SuppliedMarketsDetail.tsx b/app/positions/components/SuppliedMarketsDetail.tsx index 6e442bd4..86798379 100644 --- a/app/positions/components/SuppliedMarketsDetail.tsx +++ b/app/positions/components/SuppliedMarketsDetail.tsx @@ -195,7 +195,7 @@ export function SuppliedMarketsDetail({
- +