diff --git a/app/market/[chainId]/[marketid]/RateChart.tsx b/app/market/[chainId]/[marketid]/RateChart.tsx
index a2197587..88137715 100644
--- a/app/market/[chainId]/[marketid]/RateChart.tsx
+++ b/app/market/[chainId]/[marketid]/RateChart.tsx
@@ -39,18 +39,18 @@ function RateChart({
const [visibleLines, setVisibleLines] = useState({
supplyApy: true,
borrowApy: true,
- rateAtUTarget: true,
+ apyAtTarget: true,
});
const getChartData = () => {
if (!historicalData) return [];
- const { supplyApy, borrowApy, rateAtUTarget } = historicalData;
+ const { supplyApy, borrowApy, apyAtTarget } = historicalData;
return supplyApy.map((point: TimeseriesDataPoint, index: number) => ({
x: point.x,
supplyApy: point.y,
borrowApy: borrowApy[index]?.y || 0,
- rateAtUTarget: rateAtUTarget[index]?.y || 0,
+ apyAtTarget: apyAtTarget[index]?.y || 0,
}));
};
@@ -70,17 +70,17 @@ function RateChart({
: 0;
};
- const getCurrentRateAtUTargetValue = () => {
- return market.state.rateAtUTarget;
+ const getCurrentapyAtTargetValue = () => {
+ return market.state.apyAtTarget;
};
- const getAverageRateAtUTargetValue = () => {
- if (!historicalData?.rateAtUTarget || historicalData.rateAtUTarget.length === 0) return 0;
+ const getAverageapyAtTargetValue = () => {
+ if (!historicalData?.apyAtTarget || historicalData.apyAtTarget.length === 0) return 0;
return (
- historicalData.rateAtUTarget.reduce(
+ historicalData.apyAtTarget.reduce(
(sum: number, point: TimeseriesDataPoint) => sum + point.y,
0,
- ) / historicalData.rateAtUTarget.length
+ ) / historicalData.apyAtTarget.length
);
};
@@ -162,13 +162,13 @@ function RateChart({
- {item.state.rateAtUTarget ? `${(item.state.rateAtUTarget * 100).toFixed(2)}%` : '—'} + {item.state.apyAtTarget ? `${(item.state.apyAtTarget * 100).toFixed(2)}%` : '—'}
| Market | -APY | -Supplied Amount | +Market | +APY | +Supplied Amount |
|---|---|---|---|---|---|
| @@ -86,11 +101,14 @@ export function FromMarketsTable({ /> | -- {formatReadable(position.market.state.supplyApy * 100)}% + |
+ |
-
+
{formatReadable(
(Number(position.state.supplyAssets) + Number(position.pendingDelta)) /
diff --git a/app/positions/components/RebalanceActionInput.tsx b/app/positions/components/RebalanceActionInput.tsx
index 3386c865..8ba862ce 100644
--- a/app/positions/components/RebalanceActionInput.tsx
+++ b/app/positions/components/RebalanceActionInput.tsx
@@ -1,8 +1,12 @@
-import React from 'react';
+import React, { useMemo } from 'react';
import { ArrowRightIcon, Cross2Icon } from '@radix-ui/react-icons';
+import { parseUnits } from 'viem';
import { Button } from '@/components/common';
import { MarketIdentity, MarketIdentityMode } from '@/components/MarketIdentity';
+import { TokenIcon } from '@/components/TokenIcon';
+import { previewMarketState } from '@/utils/morpho';
import { GroupedPosition, Market } from '@/utils/types';
+import { ApyPreview } from './ApyPreview';
type RebalanceActionInputProps = {
amount: string;
@@ -39,95 +43,128 @@ export function RebalanceActionInput({
(m) => m.uniqueKey === selectedToMarketUniqueKey,
);
+ // Calculate preview APY for the selected "to" market
+ const previewState = useMemo(() => {
+ if (!selectedToMarket || !amount || Number(amount) <= 0) {
+ return null;
+ }
+ try {
+ const amountBigInt = parseUnits(amount, groupedPosition.loanAssetDecimals);
+ return previewMarketState(selectedToMarket, amountBigInt);
+ } catch {
+ return null;
+ }
+ }, [selectedToMarket, amount, groupedPosition.loanAssetDecimals]);
+
return (
Add Rebalance Action
-
- {/* From Market Section */}
-
- From
-
-
- {/* Arrow */}
-
- {selectedFromMarket ? (
-
-
- To
-
-
);
diff --git a/app/positions/components/RebalanceCart.tsx b/app/positions/components/RebalanceCart.tsx
index 6e3aaad4..8659c37e 100644
--- a/app/positions/components/RebalanceCart.tsx
+++ b/app/positions/components/RebalanceCart.tsx
@@ -3,8 +3,10 @@ import { ArrowRightIcon, TrashIcon } from '@radix-ui/react-icons';
import { formatUnits } from 'viem';
import { MarketIdentity, MarketIdentityMode } from '@/components/MarketIdentity';
import { TokenIcon } from '@/components/TokenIcon';
+import { previewMarketState } from '@/utils/morpho';
import { Market } from '@/utils/types';
import { GroupedPosition, RebalanceAction } from '@/utils/types';
+import { ApyPreview } from './ApyPreview';
type RebalanceCartProps = {
rebalanceActions: RebalanceAction[];
@@ -37,72 +39,94 @@ export function RebalanceCart({
)?.market;
const toMarket = eligibleMarkets.find((m) => m.uniqueKey === action.toMarket.uniqueKey);
+ let apyPreview: ReturnType
- {/* From Market */}
-
diff --git a/src/components/common/MarketSelectionModal.tsx b/src/components/common/MarketSelectionModal.tsx
index 41db3abd..8c6671a1 100644
--- a/src/components/common/MarketSelectionModal.tsx
+++ b/src/components/common/MarketSelectionModal.tsx
@@ -107,7 +107,7 @@ export function MarketSelectionModal({
size="4xl"
scrollBehavior="inside"
classNames={{
- wrapper: 'z-[2200]',
+ wrapper: 'z-[2200] max-h-[80%] overflow-y-auto',
backdrop: 'z-[2190] bg-black/60',
base: 'rounded-sm bg-surface',
header: 'px-6 pt-6 pb-2',
diff --git a/src/components/common/MarketsTableWithSameLoanAsset.tsx b/src/components/common/MarketsTableWithSameLoanAsset.tsx
index 3ecf26fb..2ab4493b 100644
--- a/src/components/common/MarketsTableWithSameLoanAsset.tsx
+++ b/src/components/common/MarketsTableWithSameLoanAsset.tsx
@@ -467,7 +467,7 @@ function MarketRow({
{columnVisibility.rateAtTarget && (
- From
-
{showRewards && hasActiveRewards && (
- {fromMarket ? (
-
{mode === 'supply' ? (
-
+
- {/* Arrow */}
-
+ From
+
-
+ {fromMarket ? (
+
- To
-
);
})}
diff --git a/app/positions/components/RebalanceModal.tsx b/app/positions/components/RebalanceModal.tsx
index dd99297b..cf425d63 100644
--- a/app/positions/components/RebalanceModal.tsx
+++ b/app/positions/components/RebalanceModal.tsx
@@ -282,7 +282,7 @@ export function RebalanceModal({
isOpen={isOpen}
onClose={onClose}
isDismissable={false}
- size="3xl"
+ size="4xl"
classNames={{
base: 'p-4 rounded-sm',
}}
diff --git a/docs/Styling.md b/docs/Styling.md
index f10ff66b..41980dc8 100644
--- a/docs/Styling.md
+++ b/docs/Styling.md
@@ -266,6 +266,7 @@ Works with all three modes (Normal, Focused, Minimum). Use in table cells with a
```
**MarketDetailsBlock** (`@/components/common/MarketDetailsBlock`)
+- Used for previewing transactions onto a existing market.
- Use as an expandable row in modals (e.g., supply/borrow flows)
- Shows market state details when expanded (APY, liquidity, utilization, etc.)
- Includes collapse/expand functionality
@@ -283,8 +284,8 @@ import { MarketDetailsBlock } from '@/components/common/MarketDetailsBlock';
```
**When to use which:**
-- Tables/Lists/Cards → Use `MarketIdentity`
-- Modal flows with expandable details → Use `MarketDetailsBlock`
+- Tables/Lists/Cards, Data display → Use `MarketIdentity`
+- Modal flows during a transaction, with expandable details → Use `MarketDetailsBlock`
**MarketIdBadge** (`@/components/MarketIdBadge`)
- Use to display a short market ID badge with optional network icon and warning indicator
diff --git a/package.json b/package.json
index 14a98d58..dc3a8065 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
"@heroui/theme": "^2.2.6",
"@heroui/tooltip": "^2.0.36",
"@internationalized/date": "^3.8.2",
+ "@morpho-org/blue-sdk": "^5.3.0",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-navigation-menu": "^1.1.4",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0956ecd1..0f62521f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -44,6 +44,9 @@ importers:
'@internationalized/date':
specifier: ^3.8.2
version: 3.8.2
+ '@morpho-org/blue-sdk':
+ specifier: ^5.3.0
+ version: 5.3.0(@morpho-org/morpho-ts@2.4.4)
'@radix-ui/react-dropdown-menu':
specifier: ^2.0.6
version: 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -2354,6 +2357,14 @@ packages:
resolution: {integrity: sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==}
engines: {node: '>=16.0.0'}
+ '@morpho-org/blue-sdk@5.3.0':
+ resolution: {integrity: sha512-fHjpNJZZF+zi/ElDkh2pCsWCODcmxJmgwgkNeRPRtHK5ITeQ3hJ+Eccr7sLg6R1dsqC4JNbxkQPDtIaa8h5QHw==}
+ peerDependencies:
+ '@morpho-org/morpho-ts': ^2.4.3
+
+ '@morpho-org/morpho-ts@2.4.4':
+ resolution: {integrity: sha512-Nb6sVXOtE6CajMBceXpEnPbV42nszvTIeDOJt4+E66nWtTHLvk2z5xAx/qbrQyiBtyDQmCWhP1oQnjf7yg8Zow==}
+
'@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
@@ -3500,6 +3511,15 @@ packages:
'@types/json5@0.0.29':
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+ '@types/lodash.isplainobject@4.0.9':
+ resolution: {integrity: sha512-QC8nKcap5hRrbtIaPRjUMlcXXnLeayqQZPSaWJDx3xeuN17+2PW5wkmEJ4+lZgNnQRlSPzxjTYKCfV1uTnPaEg==}
+
+ '@types/lodash.mergewith@4.6.9':
+ resolution: {integrity: sha512-fgkoCAOF47K7sxrQ7Mlud2TH023itugZs2bUg8h/KzT+BnZNrR2jAOmaokbLunHNnobXVWOezAeNn/lZqwxkcw==}
+
+ '@types/lodash@4.17.20':
+ resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==}
+
'@types/mdast@4.0.4':
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
@@ -5916,12 +5936,18 @@ packages:
lodash.debounce@4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
+ lodash.isplainobject@4.0.6:
+ resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
+
lodash.memoize@4.1.2:
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+ lodash.mergewith@4.6.2:
+ resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==}
+
lodash.truncate@4.4.2:
resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==}
@@ -10881,6 +10907,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@morpho-org/blue-sdk@5.3.0(@morpho-org/morpho-ts@2.4.4)':
+ dependencies:
+ '@morpho-org/morpho-ts': 2.4.4
+ '@noble/hashes': 1.8.0
+ '@types/lodash.isplainobject': 4.0.9
+ '@types/lodash.mergewith': 4.6.9
+ lodash.isplainobject: 4.0.6
+ lodash.mergewith: 4.6.2
+
+ '@morpho-org/morpho-ts@2.4.4': {}
+
'@napi-rs/wasm-runtime@0.2.12':
dependencies:
'@emnapi/core': 1.4.5
@@ -12581,6 +12618,16 @@ snapshots:
'@types/json5@0.0.29': {}
+ '@types/lodash.isplainobject@4.0.9':
+ dependencies:
+ '@types/lodash': 4.17.20
+
+ '@types/lodash.mergewith@4.6.9':
+ dependencies:
+ '@types/lodash': 4.17.20
+
+ '@types/lodash@4.17.20': {}
+
'@types/mdast@4.0.4':
dependencies:
'@types/unist': 3.0.3
@@ -16039,10 +16086,14 @@ snapshots:
lodash.debounce@4.0.8: {}
+ lodash.isplainobject@4.0.6: {}
+
lodash.memoize@4.1.2: {}
lodash.merge@4.6.2: {}
+ lodash.mergewith@4.6.2: {}
+
lodash.truncate@4.4.2: {}
lodash@4.17.21: {}
diff --git a/src/components/SupplyModalContent.tsx b/src/components/SupplyModalContent.tsx
index 5de40202..28bacb31 100644
--- a/src/components/SupplyModalContent.tsx
+++ b/src/components/SupplyModalContent.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, useEffect } from 'react';
import { Switch } from '@heroui/react';
import { useAccount } from 'wagmi';
import Input from '@/components/Input/Input';
@@ -17,12 +17,14 @@ type SupplyModalContentProps = {
market: Market;
onClose: () => void;
refetch: () => void;
+ onAmountChange?: (amount: bigint | undefined) => void;
};
export function SupplyModalContent({
onClose,
market,
refetch,
+ onAmountChange,
}: SupplyModalContentProps): JSX.Element {
const [usePermit2Setting] = useLocalStorage('usePermit2', true);
const { isConnected } = useAccount();
@@ -53,6 +55,11 @@ export function SupplyModalContent({
signAndSupply,
} = useSupplyMarket(market, onSuccess);
+ // Notify parent component when supply amount changes
+ useEffect(() => {
+ onAmountChange?.(supplyAmount);
+ }, [supplyAmount, onAmountChange]);
+
// Use the market network hook to handle network switching
const { needSwitchChain, switchToNetwork } = useMarketNetwork({
targetChainId: market.morphoBlue.chain.id,
diff --git a/src/components/SupplyModalV2.tsx b/src/components/SupplyModalV2.tsx
index cf399c5a..85d4c62c 100644
--- a/src/components/SupplyModalV2.tsx
+++ b/src/components/SupplyModalV2.tsx
@@ -25,6 +25,7 @@ export function SupplyModalV2({
defaultMode = 'supply',
}: SupplyModalV2Props): JSX.Element {
const [mode, setMode] = useState<'supply' | 'withdraw'>(defaultMode);
+ const [supplyPreviewAmount, setSupplyPreviewAmount] = useState
- {toMarket ? (
-
- {/* Amount */}
-
+ To
+
+ {toMarket ? (
+
-
- {formatUnits(action.amount, groupedPosition.loanAssetDecimals)}
-
-
- {/* Remove Button */}
-
-
+ ) : (
+ --
+ )}
+
+
+ {formatUnits(action.amount, groupedPosition.loanAssetDecimals)}
+
+
+
+
+
+
{/* Collapsible Market Details */}
@@ -108,7 +124,18 @@ export function MarketDetailsBlock({
chainId={market.morphoBlue.chain.id}
/>
·
- {getAPY()}% APY
+ {previewState !== null ? (
+
+ {getAPY()}%
+ {' → '}
+
+ {getPreviewAPY()}%
+
+ {' APY'}
+
+ ) : (
+ {getAPY()}% APY
+ )}
·
{(Number(market.lltv) / 1e16).toFixed(0)}% LLTV
@@ -156,7 +183,17 @@ export function MarketDetailsBlock({
{mode === 'supply' ? 'Supply' : 'Borrow'} APY: -{getAPY()}% + {previewState !== null ? ( ++ {getAPY()}% + {' → '} + + {getPreviewAPY()}% + + + ) : ( +{getAPY()}% + )}
@@ -182,25 +219,69 @@ export function MarketDetailsBlock({
)}
Total Supply: -- {formatReadable( - formatBalance(market.state.supplyAssets, market.loanAsset.decimals), - )} - + {previewState !== null ? ( ++ + {formatReadable( + formatBalance(market.state.supplyAssets, market.loanAsset.decimals), + )} + + {' → '} + + {formatReadable( + formatBalance(previewState.totalSupplyAssets.toString(), market.loanAsset.decimals), + )} + + + ) : ( ++ {formatReadable( + formatBalance(market.state.supplyAssets, market.loanAsset.decimals), + )} + + )}Liquidity: -- {formatReadable( - formatBalance(market.state.liquidityAssets, market.loanAsset.decimals), - )} - + {previewState !== null ? ( ++ + {formatReadable( + formatBalance(market.state.liquidityAssets, market.loanAsset.decimals), + )} + + {' → '} + + {formatReadable( + formatBalance(previewState.liquidityAssets.toString(), market.loanAsset.decimals), + )} + + + ) : ( ++ {formatReadable( + formatBalance(market.state.liquidityAssets, market.loanAsset.decimals), + )} + + )}Utilization: -- {formatReadable(market.state.utilization * 100)}% - + {previewState !== null ? ( ++ + {formatReadable(market.state.utilization * 100)}% + + {' → '} + + {formatReadable(previewState.utilization * 100)}% + + + ) : ( ++ {formatReadable(market.state.utilization * 100)}% + + )}
|
)}
@@ -668,7 +668,7 @@ export function MarketsTableWithSameLoanAsset({
[SortColumn.Liquidity]: 'state.liquidityAssets',
[SortColumn.Borrow]: 'state.borrowAssetsUsd',
[SortColumn.BorrowAPY]: 'state.borrowApy',
- [SortColumn.RateAtTarget]: 'state.rateAtUTarget',
+ [SortColumn.RateAtTarget]: 'state.apyAtTarget',
[SortColumn.Risk]: '', // No sorting for risk
};
@@ -724,7 +724,7 @@ export function MarketsTableWithSameLoanAsset({
return (
- {market.state.rateAtUTarget ? `${(market.state.rateAtUTarget * 100).toFixed(2)}%` : '—'} + {market.state.apyAtTarget ? `${(market.state.apyAtTarget * 100).toFixed(2)}%` : '—'}
- {/* Cart/Staging Area - MarketDetailsBlock Style */}
+ {/* Cart/Staging Area Style */}
{showCart && selectedMarkets.length > 0 && (
{selectedMarkets.map(({ market }) => (
diff --git a/src/constants/chartColors.ts b/src/constants/chartColors.ts
index 26aa111e..4ccac946 100644
--- a/src/constants/chartColors.ts
+++ b/src/constants/chartColors.ts
@@ -18,7 +18,7 @@ export const CHART_COLORS = {
endOpacity: 0,
},
},
- rateAtUTarget: {
+ apyAtTarget: {
stroke: '#F59E0B',
gradient: {
start: '#F59E0B',
diff --git a/src/data-sources/morpho-api/historical.ts b/src/data-sources/morpho-api/historical.ts
index 02a2bf35..c9b96d40 100644
--- a/src/data-sources/morpho-api/historical.ts
+++ b/src/data-sources/morpho-api/historical.ts
@@ -71,7 +71,7 @@ export const fetchMorphoMarketHistoricalData = async (
const rates: MarketRates = {
supplyApy: historicalState.supplyApy ?? [],
borrowApy: historicalState.borrowApy ?? [],
- rateAtUTarget: historicalState.rateAtUTarget ?? [],
+ apyAtTarget: historicalState.apyAtTarget ?? [],
utilization: historicalState.utilization ?? [],
};
const volumes: MarketVolumes = {
diff --git a/src/data-sources/subgraph/historical.ts b/src/data-sources/subgraph/historical.ts
index d5fe8766..f8a63fc6 100644
--- a/src/data-sources/subgraph/historical.ts
+++ b/src/data-sources/subgraph/historical.ts
@@ -44,7 +44,7 @@ const transformSubgraphSnapshotsToHistoricalResult = (
const rates: MarketRates = {
supplyApy: [] as TimeseriesDataPoint[],
borrowApy: [] as TimeseriesDataPoint[],
- rateAtUTarget: [] as TimeseriesDataPoint[],
+ apyAtTarget: [] as TimeseriesDataPoint[],
utilization: [] as TimeseriesDataPoint[],
};
const volumes: MarketVolumes = {
@@ -73,7 +73,7 @@ const transformSubgraphSnapshotsToHistoricalResult = (
rates.supplyApy.push({ x: timestamp, y: !isNaN(supplyApyValue) ? supplyApyValue : 0 });
rates.borrowApy.push({ x: timestamp, y: !isNaN(borrowApyValue) ? borrowApyValue : 0 });
- rates.rateAtUTarget.push({ x: timestamp, y: 0 });
+ rates.apyAtTarget.push({ x: timestamp, y: 0 });
rates.utilization.push({ x: timestamp, y: 0 });
const supplyNative = BigInt(snapshot.inputTokenBalance ?? '0');
diff --git a/src/data-sources/subgraph/market.ts b/src/data-sources/subgraph/market.ts
index 87f794b8..1b03a590 100644
--- a/src/data-sources/subgraph/market.ts
+++ b/src/data-sources/subgraph/market.ts
@@ -248,7 +248,12 @@ const transformSubgraphMarketToMarket = (
borrowApy: borrowApy,
fee: safeParseFloat(fee) / 10000, // Subgraph fee is likely basis points (needs verification)
timestamp: timestamp,
- rateAtUTarget: 0, // Not available from subgraph
+
+ // AdaptiveCurveIRM APY if utilization was at target
+ apyAtTarget: 0, // Not available from subgraph
+
+ // AdaptiveCurveIRM rate per second if utilization was at target
+ rateAtTarget: '0', // Not available from subgraph
},
oracleAddress: oracleAddress,
morphoBlue: {
diff --git a/src/graphql/morpho-api-queries.ts b/src/graphql/morpho-api-queries.ts
index eea9f7b9..8daad291 100644
--- a/src/graphql/morpho-api-queries.ts
+++ b/src/graphql/morpho-api-queries.ts
@@ -59,7 +59,8 @@ state {
borrowApy
fee
timestamp
- rateAtUTarget
+ apyAtTarget
+ rateAtTarget
}
warnings {
type
@@ -204,7 +205,8 @@ export const marketsQuery = `
borrowApy
fee
timestamp
- rateAtUTarget
+ apyAtTarget
+ rateAtTarget
}
warnings {
type
@@ -345,7 +347,7 @@ export const marketHistoricalDataQuery = `
x
y
}
- rateAtUTarget(options: $options) {
+ apyAtTarget(options: $options) {
x
y
}
diff --git a/src/utils/morpho.ts b/src/utils/morpho.ts
index d85b0a72..cbeab461 100644
--- a/src/utils/morpho.ts
+++ b/src/utils/morpho.ts
@@ -1,6 +1,7 @@
+import { Market as BlueMarket, MarketParams as BlueMarketParams } from '@morpho-org/blue-sdk';
import { Address, decodeAbiParameters, encodeAbiParameters, keccak256, parseAbiParameters, zeroAddress } from 'viem';
import { SupportedNetworks } from './networks';
-import { MarketParams, UserTxTypes } from './types';
+import { Market, MarketParams, UserTxTypes } from './types';
// appended to the end of datahash to identify a monarch tx
export const MONARCH_TX_IDENTIFIER = 'beef';
@@ -215,9 +216,9 @@ export function parseCapIdParams(idParams: string): {
);
if (decoded[0] === 'this/marketParams') {
-
+
const marketParamsBlock = decoded[2] as [any];
-
+
const marketParams = marketParamsBlock[0] as any as MarketParams;
// Create a market ID hash from the market params
@@ -241,3 +242,60 @@ export function parseCapIdParams(idParams: string): {
return { type: 'unknown' };
}
}
+
+// ============================================================================
+// Supply Preview Utilities
+// ============================================================================
+
+type MarketStatePreview = {
+ supplyApy: number;
+ borrowApy: number;
+ utilization: number; // scaled down WAD
+ totalSupplyAssets: bigint;
+ totalBorrowAssets: bigint;
+ liquidityAssets: bigint;
+};
+
+/**
+ * Simulates a supply operation and returns the full market state preview.
+ *
+ * @param market - The market configuration and state
+ * @param supplyAmount - The amount to simulate supplying (in asset units, positive for supply)
+ * @returns The estimated market state after the action, or null if simulation fails
+ */
+export function previewMarketState(market: Market, supplyAmount: bigint): MarketStatePreview | null {
+ try {
+ const params = new BlueMarketParams({
+ loanToken: market.loanAsset.address as Address,
+ collateralToken: market.collateralAsset.address as Address,
+ oracle: market.oracleAddress as Address,
+ irm: market.irmAddress as Address,
+ lltv: BigInt(market.lltv),
+ });
+
+ const blueMarket = new BlueMarket({
+ params,
+ totalSupplyAssets: BigInt(market.state.supplyAssets),
+ totalBorrowAssets: BigInt(market.state.borrowAssets),
+ totalSupplyShares: BigInt(market.state.supplyShares),
+ totalBorrowShares: BigInt(market.state.borrowShares),
+ lastUpdate: BigInt(market.state.timestamp), // not really the last timestamp but doesn't matter.
+ rateAtTarget: BigInt(market.state.rateAtTarget),
+ fee: BigInt(Math.floor(market.state.fee * 1e18)),
+ });
+
+ const { market: updated } = blueMarket.supply(supplyAmount, 0n);
+
+ return {
+ supplyApy: updated.supplyApy,
+ borrowApy: updated.borrowApy,
+ utilization: Number(updated.utilization) / 1e18,
+ totalSupplyAssets: updated.totalSupplyAssets,
+ totalBorrowAssets: updated.totalBorrowAssets,
+ liquidityAssets: updated.liquidity,
+ };
+ } catch (error) {
+ console.error('Error previewing market state:', error);
+ return null;
+ }
+}
diff --git a/src/utils/types.ts b/src/utils/types.ts
index bdcce659..dc0d3cfa 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -298,7 +298,12 @@ export type Market = {
borrowApy: number;
fee: number;
timestamp: number;
- rateAtUTarget: number;
+
+ // AdaptiveCurveIRM APY if utilization was at target
+ apyAtTarget: number;
+
+ // AdaptiveCurveIRM rate per second if utilization was at target
+ rateAtTarget: string;
};
realizedBadDebt: {
underlying: string
@@ -329,7 +334,7 @@ export type TimeseriesOptions = {
export type MarketRates = {
supplyApy: TimeseriesDataPoint[];
borrowApy: TimeseriesDataPoint[];
- rateAtUTarget: TimeseriesDataPoint[];
+ apyAtTarget: TimeseriesDataPoint[];
utilization: TimeseriesDataPoint[];
};
|