From e8daf216b4135f1353a889f88ddd976e15b9e4f7 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Tue, 11 Nov 2025 13:27:36 -0800 Subject: [PATCH 1/3] feat: add preview and change Input component props --- .../Borrow/AddCollateralAndBorrow.tsx | 2 +- .../Borrow/WithdrawCollateralAndRepay.tsx | 2 +- src/components/Input/Input.tsx | 4 +-- src/components/SupplyModalContent.tsx | 18 +++++++----- src/components/SupplyModalV2.tsx | 10 ++++++- src/components/WithdrawModalContent.tsx | 13 ++++++++- src/components/common/MarketDetailsBlock.tsx | 26 +++++++++++++---- src/utils/morpho.ts | 29 ++++++++++++++++--- 8 files changed, 81 insertions(+), 23 deletions(-) diff --git a/src/components/Borrow/AddCollateralAndBorrow.tsx b/src/components/Borrow/AddCollateralAndBorrow.tsx index 9564bcb2..72f0184e 100644 --- a/src/components/Borrow/AddCollateralAndBorrow.tsx +++ b/src/components/Borrow/AddCollateralAndBorrow.tsx @@ -247,7 +247,7 @@ export function AddCollateralAndBorrow({ {/* Market Details Block - includes position overview and collapsible details */}
- +
{isConnected && ( diff --git a/src/components/Borrow/WithdrawCollateralAndRepay.tsx b/src/components/Borrow/WithdrawCollateralAndRepay.tsx index 753430d2..d7bef6a7 100644 --- a/src/components/Borrow/WithdrawCollateralAndRepay.tsx +++ b/src/components/Borrow/WithdrawCollateralAndRepay.tsx @@ -253,7 +253,7 @@ export function WithdrawCollateralAndRepay({ {/* Market Details Block - includes position overview and collapsible details */}
- +
{isConnected && ( diff --git a/src/components/Input/Input.tsx b/src/components/Input/Input.tsx index c4d2a6df..146ebbf7 100644 --- a/src/components/Input/Input.tsx +++ b/src/components/Input/Input.tsx @@ -5,9 +5,9 @@ import { formatBalance } from '@/utils/balance'; type InputProps = { decimals: number; - setValue: React.Dispatch>; + setValue: (value: bigint) => void; max?: bigint; - setError?: React.Dispatch>; + setError?: ((error: string | null) => void) | React.Dispatch>; exceedMaxErrMessage?: string; allowExceedMax?: boolean; // whether to still "setValue" when the input exceeds max onMaxClick?: () => void; diff --git a/src/components/SupplyModalContent.tsx b/src/components/SupplyModalContent.tsx index 28bacb31..f4eaa3a4 100644 --- a/src/components/SupplyModalContent.tsx +++ b/src/components/SupplyModalContent.tsx @@ -55,10 +55,14 @@ export function SupplyModalContent({ signAndSupply, } = useSupplyMarket(market, onSuccess); - // Notify parent component when supply amount changes - useEffect(() => { - onAmountChange?.(supplyAmount); - }, [supplyAmount, onAmountChange]); + // Handle supply amount change + const handleSupplyAmountChange = useCallback( + (amount: bigint) => { + setSupplyAmount(amount); + onAmountChange?.(amount); + }, + [setSupplyAmount, onAmountChange], + ); // Use the market network hook to handle network switching const { needSwitchChain, switchToNetwork } = useMarketNetwork({ @@ -119,10 +123,8 @@ export function SupplyModalContent({ string | null), - ) => { + setValue={handleSupplyAmountChange} + setError={(error: string | null) => { if ( typeof error === 'string' && !error.includes("You don't have any supplied assets") diff --git a/src/components/SupplyModalV2.tsx b/src/components/SupplyModalV2.tsx index 85d4c62c..7045cf39 100644 --- a/src/components/SupplyModalV2.tsx +++ b/src/components/SupplyModalV2.tsx @@ -26,6 +26,7 @@ export function SupplyModalV2({ }: SupplyModalV2Props): JSX.Element { const [mode, setMode] = useState<'supply' | 'withdraw'>(defaultMode); const [supplyPreviewAmount, setSupplyPreviewAmount] = useState(); + const [withdrawPreviewAmount, setWithdrawPreviewAmount] = useState(); const hasPosition = position && BigInt(position.state.supplyAssets) > 0n; @@ -83,7 +84,13 @@ export function SupplyModalV2({ defaultCollapsed mode="supply" showRewards - loanAssetDelta={mode === 'supply' ? supplyPreviewAmount : undefined} + loanAssetDelta={ + mode === 'supply' + ? supplyPreviewAmount + : withdrawPreviewAmount + ? -withdrawPreviewAmount + : undefined + } /> @@ -100,6 +107,7 @@ export function SupplyModalV2({ market={market} onClose={onClose} refetch={refetch ?? (() => {})} + onAmountChange={setWithdrawPreviewAmount} /> )} diff --git a/src/components/WithdrawModalContent.tsx b/src/components/WithdrawModalContent.tsx index 0eaaab0e..246a65c1 100644 --- a/src/components/WithdrawModalContent.tsx +++ b/src/components/WithdrawModalContent.tsx @@ -19,6 +19,7 @@ type WithdrawModalContentProps = { market?: Market; onClose: () => void; refetch: () => void; + onAmountChange?: (amount: bigint) => void; }; export function WithdrawModalContent({ @@ -26,10 +27,20 @@ export function WithdrawModalContent({ market, onClose, refetch, + onAmountChange, }: WithdrawModalContentProps): JSX.Element { const toast = useStyledToast(); const [inputError, setInputError] = useState(null); const [withdrawAmount, setWithdrawAmount] = useState(BigInt(0)); + + // Notify parent component when withdraw amount changes + const handleWithdrawAmountChange = useCallback( + (amount: bigint) => { + setWithdrawAmount(amount); + onAmountChange?.(amount); + }, + [onAmountChange], + ); const { address: account, isConnected, chainId } = useAccount(); // Use market from either position or direct prop @@ -153,7 +164,7 @@ export function WithdrawModalContent({ ) : BigInt(0) } - setValue={setWithdrawAmount} + setValue={handleWithdrawAmountChange} setError={setInputError} exceedMaxErrMessage="Insufficient Liquidity" /> diff --git a/src/components/common/MarketDetailsBlock.tsx b/src/components/common/MarketDetailsBlock.tsx index 1fc8a86f..b61e8f92 100644 --- a/src/components/common/MarketDetailsBlock.tsx +++ b/src/components/common/MarketDetailsBlock.tsx @@ -18,6 +18,8 @@ type MarketDetailsBlockProps = { showRewards?: boolean; disableExpansion?: boolean; loanAssetDelta?: bigint; + repayAmount?: bigint; + borrowAmount?: bigint; }; export function MarketDetailsBlock({ @@ -28,6 +30,8 @@ export function MarketDetailsBlock({ showRewards = false, disableExpansion = false, loanAssetDelta, + repayAmount, + borrowAmount, }: MarketDetailsBlockProps): JSX.Element { const [isExpanded, setIsExpanded] = useState(!defaultCollapsed && !disableExpansion); @@ -38,13 +42,25 @@ export function MarketDetailsBlock({ whitelisted: market.whitelisted && !market.isMonarchWhitelisted }); - // Calculate preview state when loanAssetDelta is provided + // Calculate preview state when loanAssetDelta, repayAmount, or borrowAmount is provided const previewState = useMemo(() => { - if (!loanAssetDelta || loanAssetDelta <= 0n || mode !== 'supply') { - return null; + // For supply mode: show preview if supplying (positive) or withdrawing (negative) + if (mode === 'supply' && loanAssetDelta && loanAssetDelta !== 0n) { + return previewMarketState(market, loanAssetDelta); } - return previewMarketState(market, loanAssetDelta); - }, [market, loanAssetDelta, mode]); + + // For borrow mode: show preview if repaying or borrowing + if (mode === 'borrow') { + const hasRepay = repayAmount && repayAmount > 0n; + const hasBorrow = borrowAmount && borrowAmount > 0n; + + if (hasRepay || hasBorrow) { + return previewMarketState(market, 0n, repayAmount, borrowAmount); + } + } + + return null; + }, [market, loanAssetDelta, repayAmount, borrowAmount, mode]); // Helper to format APY based on mode const getAPY = () => { diff --git a/src/utils/morpho.ts b/src/utils/morpho.ts index cbeab461..04cd5746 100644 --- a/src/utils/morpho.ts +++ b/src/utils/morpho.ts @@ -257,13 +257,15 @@ type MarketStatePreview = { }; /** - * Simulates a supply operation and returns the full market state preview. + * Simulates a supply, borrow, or repay 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) + * @param supplyAmount - The amount to simulate supplying (in asset units, positive for supply, negative for withdraw) + * @param repayAmount - The amount to simulate repaying (in asset units, positive for repay) + * @param borrowAmount - The amount to simulate borrowing (in asset units, positive for borrow) * @returns The estimated market state after the action, or null if simulation fails */ -export function previewMarketState(market: Market, supplyAmount: bigint): MarketStatePreview | null { +export function previewMarketState(market: Market, supplyAmount: bigint, repayAmount?: bigint, borrowAmount?: bigint): MarketStatePreview | null { try { const params = new BlueMarketParams({ loanToken: market.loanAsset.address as Address, @@ -284,7 +286,26 @@ export function previewMarketState(market: Market, supplyAmount: bigint): Market fee: BigInt(Math.floor(market.state.fee * 1e18)), }); - const { market: updated } = blueMarket.supply(supplyAmount, 0n); + let updated = blueMarket; + + // Apply supply/withdraw if specified + if (supplyAmount !== 0n) { + console.log('supplyAmount', supplyAmount) + const result = blueMarket.supply(supplyAmount, 0n); + updated = result.market; + } + + // Apply repay if specified + if (repayAmount && repayAmount > 0n) { + const result = updated.repay(repayAmount, 0n); + updated = result.market; + } + + // Apply borrow if specified + if (borrowAmount && borrowAmount > 0n) { + const result = updated.borrow(borrowAmount, 0n); + updated = result.market; + } return { supplyApy: updated.supplyApy, From ab3278b1715f86a33ddb737b0f52f51e964c5d7c Mon Sep 17 00:00:00 2001 From: antoncoding Date: Wed, 12 Nov 2025 15:59:24 -0300 Subject: [PATCH 2/3] feat: preview withdraw --- app/positions/components/FromMarketsTable.tsx | 2 +- .../components/RebalanceActionInput.tsx | 2 +- app/positions/components/RebalanceCart.tsx | 2 +- .../Borrow/AddCollateralAndBorrow.tsx | 2 +- .../Borrow/WithdrawCollateralAndRepay.tsx | 2 +- src/components/SupplyModalV2.tsx | 2 +- src/components/common/MarketDetailsBlock.tsx | 31 +++++-------- src/utils/morpho.ts | 46 +++++++++++-------- 8 files changed, 44 insertions(+), 45 deletions(-) diff --git a/app/positions/components/FromMarketsTable.tsx b/app/positions/components/FromMarketsTable.tsx index 3b9e1335..ade637de 100644 --- a/app/positions/components/FromMarketsTable.tsx +++ b/app/positions/components/FromMarketsTable.tsx @@ -34,7 +34,7 @@ export function FromMarketsTable({ try { const deltaBigInt = BigInt(Math.floor(position.pendingDelta)); - return previewMarketState(position.market, deltaBigInt); + return previewMarketState(position.market, deltaBigInt, undefined); } catch { return null; } diff --git a/app/positions/components/RebalanceActionInput.tsx b/app/positions/components/RebalanceActionInput.tsx index 8ba862ce..8e1a0805 100644 --- a/app/positions/components/RebalanceActionInput.tsx +++ b/app/positions/components/RebalanceActionInput.tsx @@ -50,7 +50,7 @@ export function RebalanceActionInput({ } try { const amountBigInt = parseUnits(amount, groupedPosition.loanAssetDecimals); - return previewMarketState(selectedToMarket, amountBigInt); + return previewMarketState(selectedToMarket, amountBigInt, undefined); } catch { return null; } diff --git a/app/positions/components/RebalanceCart.tsx b/app/positions/components/RebalanceCart.tsx index 8659c37e..b72c1c47 100644 --- a/app/positions/components/RebalanceCart.tsx +++ b/app/positions/components/RebalanceCart.tsx @@ -42,7 +42,7 @@ export function RebalanceCart({ let apyPreview: ReturnType | null = null; if (toMarket) { try { - apyPreview = previewMarketState(toMarket, action.amount); + apyPreview = previewMarketState(toMarket, action.amount, undefined); } catch { apyPreview = null; } diff --git a/src/components/Borrow/AddCollateralAndBorrow.tsx b/src/components/Borrow/AddCollateralAndBorrow.tsx index 72f0184e..229b6e00 100644 --- a/src/components/Borrow/AddCollateralAndBorrow.tsx +++ b/src/components/Borrow/AddCollateralAndBorrow.tsx @@ -247,7 +247,7 @@ export function AddCollateralAndBorrow({ {/* Market Details Block - includes position overview and collapsible details */}
- +
{isConnected && ( diff --git a/src/components/Borrow/WithdrawCollateralAndRepay.tsx b/src/components/Borrow/WithdrawCollateralAndRepay.tsx index d7bef6a7..18ca4c4e 100644 --- a/src/components/Borrow/WithdrawCollateralAndRepay.tsx +++ b/src/components/Borrow/WithdrawCollateralAndRepay.tsx @@ -253,7 +253,7 @@ export function WithdrawCollateralAndRepay({ {/* Market Details Block - includes position overview and collapsible details */}
- +
{isConnected && ( diff --git a/src/components/SupplyModalV2.tsx b/src/components/SupplyModalV2.tsx index 7045cf39..cd1fd2ee 100644 --- a/src/components/SupplyModalV2.tsx +++ b/src/components/SupplyModalV2.tsx @@ -84,7 +84,7 @@ export function SupplyModalV2({ defaultCollapsed mode="supply" showRewards - loanAssetDelta={ + supplyDelta={ mode === 'supply' ? supplyPreviewAmount : withdrawPreviewAmount diff --git a/src/components/common/MarketDetailsBlock.tsx b/src/components/common/MarketDetailsBlock.tsx index b61e8f92..e44808ba 100644 --- a/src/components/common/MarketDetailsBlock.tsx +++ b/src/components/common/MarketDetailsBlock.tsx @@ -17,9 +17,8 @@ type MarketDetailsBlockProps = { mode?: 'supply' | 'borrow'; showRewards?: boolean; disableExpansion?: boolean; - loanAssetDelta?: bigint; - repayAmount?: bigint; - borrowAmount?: bigint; + supplyDelta?: bigint; + borrowDelta?: bigint; }; export function MarketDetailsBlock({ @@ -29,9 +28,8 @@ export function MarketDetailsBlock({ mode = 'supply', showRewards = false, disableExpansion = false, - loanAssetDelta, - repayAmount, - borrowAmount, + supplyDelta, + borrowDelta, }: MarketDetailsBlockProps): JSX.Element { const [isExpanded, setIsExpanded] = useState(!defaultCollapsed && !disableExpansion); @@ -42,25 +40,20 @@ export function MarketDetailsBlock({ whitelisted: market.whitelisted && !market.isMonarchWhitelisted }); - // Calculate preview state when loanAssetDelta, repayAmount, or borrowAmount is provided + // Calculate preview state when supplyDelta or borrowDelta is provided const previewState = useMemo(() => { - // For supply mode: show preview if supplying (positive) or withdrawing (negative) - if (mode === 'supply' && loanAssetDelta && loanAssetDelta !== 0n) { - return previewMarketState(market, loanAssetDelta); + // For supply mode: show preview if supplyDelta is non-zero + if (mode === 'supply' && supplyDelta && supplyDelta !== 0n) { + return previewMarketState(market, supplyDelta, undefined); } - // For borrow mode: show preview if repaying or borrowing - if (mode === 'borrow') { - const hasRepay = repayAmount && repayAmount > 0n; - const hasBorrow = borrowAmount && borrowAmount > 0n; - - if (hasRepay || hasBorrow) { - return previewMarketState(market, 0n, repayAmount, borrowAmount); - } + // For borrow mode: show preview if borrowDelta is non-zero + if (mode === 'borrow' && borrowDelta && borrowDelta !== 0n) { + return previewMarketState(market, undefined, borrowDelta); } return null; - }, [market, loanAssetDelta, repayAmount, borrowAmount, mode]); + }, [market, supplyDelta, borrowDelta, mode]); // Helper to format APY based on mode const getAPY = () => { diff --git a/src/utils/morpho.ts b/src/utils/morpho.ts index 04cd5746..ff7294be 100644 --- a/src/utils/morpho.ts +++ b/src/utils/morpho.ts @@ -257,15 +257,14 @@ type MarketStatePreview = { }; /** - * Simulates a supply, borrow, or repay operation and returns the full market state preview. + * Simulates market state changes based on supply and borrow deltas. * * @param market - The market configuration and state - * @param supplyAmount - The amount to simulate supplying (in asset units, positive for supply, negative for withdraw) - * @param repayAmount - The amount to simulate repaying (in asset units, positive for repay) - * @param borrowAmount - The amount to simulate borrowing (in asset units, positive for borrow) + * @param supplyDelta - Supply delta (positive: supply(), negative: withdraw()) + * @param borrowDelta - Borrow delta (positive: borrow(), negative: repay()) * @returns The estimated market state after the action, or null if simulation fails */ -export function previewMarketState(market: Market, supplyAmount: bigint, repayAmount?: bigint, borrowAmount?: bigint): MarketStatePreview | null { +export function previewMarketState(market: Market, supplyDelta?: bigint, borrowDelta?: bigint): MarketStatePreview | null { try { const params = new BlueMarketParams({ loanToken: market.loanAsset.address as Address, @@ -288,23 +287,30 @@ export function previewMarketState(market: Market, supplyAmount: bigint, repayAm let updated = blueMarket; - // Apply supply/withdraw if specified - if (supplyAmount !== 0n) { - console.log('supplyAmount', supplyAmount) - const result = blueMarket.supply(supplyAmount, 0n); - updated = result.market; - } - - // Apply repay if specified - if (repayAmount && repayAmount > 0n) { - const result = updated.repay(repayAmount, 0n); - updated = result.market; + // Apply supply delta + if (supplyDelta && supplyDelta !== 0n) { + if (supplyDelta > 0n) { + // Positive delta: supply + const result = updated.supply(supplyDelta, 0n); + updated = result.market; + } else { + // Negative delta: withdraw (pass positive amount) + const result = updated.withdraw(-supplyDelta, 0n); + updated = result.market; + } } - // Apply borrow if specified - if (borrowAmount && borrowAmount > 0n) { - const result = updated.borrow(borrowAmount, 0n); - updated = result.market; + // Apply borrow delta + if (borrowDelta && borrowDelta !== 0n) { + if (borrowDelta > 0n) { + // Positive delta: borrow + const result = updated.borrow(borrowDelta, 0n); + updated = result.market; + } else { + // Negative delta: repay (pass positive amount) + const result = updated.repay(-borrowDelta, 0n); + updated = result.market; + } } return { From 773b63772d21ecb642b00ef498d9fc927dd09b94 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Wed, 12 Nov 2025 16:16:46 -0300 Subject: [PATCH 3/3] chore: lint --- src/components/SupplyModalContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SupplyModalContent.tsx b/src/components/SupplyModalContent.tsx index f4eaa3a4..8c9bc4d2 100644 --- a/src/components/SupplyModalContent.tsx +++ b/src/components/SupplyModalContent.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback } from 'react'; import { Switch } from '@heroui/react'; import { useAccount } from 'wagmi'; import Input from '@/components/Input/Input';