diff --git a/app/positions/components/PositionsContent.tsx b/app/positions/components/PositionsContent.tsx index a551fce4..e8cf8db7 100644 --- a/app/positions/components/PositionsContent.tsx +++ b/app/positions/components/PositionsContent.tsx @@ -16,11 +16,13 @@ import { SupplyModal } from '@/components/supplyModal'; import { WithdrawModal } from '@/components/withdrawModal'; import useUserPositionsWithEarning from '@/hooks/useUserPositionsWithEarning'; import { MarketPosition } from '@/utils/types'; +import { OnboardingModal } from './onboarding/Modal'; import { PositionsSummaryTable } from './PositionsSummaryTable'; export default function Positions() { const [showSupplyModal, setShowSupplyModal] = useState(false); const [showWithdrawModal, setShowWithdrawModal] = useState(false); + const [showOnboardingModal, setShowOnboardingModal] = useState(false); const [selectedPosition, setSelectedPosition] = useState(null); const { account } = useParams<{ account: string }>(); @@ -84,18 +86,17 @@ export default function Positions() { {isOwner && ( - - - + )} @@ -121,17 +122,16 @@ export default function Positions() { /> )} + setShowOnboardingModal(false)} + /> + {isLoading ? ( ) : !hasSuppliedMarkets ? (
- - -
) : (
diff --git a/app/positions/components/SmartOnboarding.tsx b/app/positions/components/SmartOnboarding.tsx deleted file mode 100644 index 64226b9c..00000000 --- a/app/positions/components/SmartOnboarding.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { AssetSelection } from './onboarding/AssetSelection'; -import { OnboardingProvider } from './onboarding/OnboardingContext'; -import { RiskSelection } from './onboarding/RiskSelection'; - -export function SmartOnboarding() { - return ( -
- - - - -
- ); -} diff --git a/app/positions/components/onboarding/AssetSelection.tsx b/app/positions/components/onboarding/AssetSelection.tsx index d1c8fef3..5cfbd4aa 100644 --- a/app/positions/components/onboarding/AssetSelection.tsx +++ b/app/positions/components/onboarding/AssetSelection.tsx @@ -1,13 +1,13 @@ import { useMemo } from 'react'; -import { Button } from '@nextui-org/react'; import { motion } from 'framer-motion'; import Image from 'next/image'; import Link from 'next/link'; -import { useRouter } from 'next/navigation'; +import { formatUnits } from 'viem'; +import { Button } from '@/components/common/Button'; +import { Spinner } from '@/components/common/Spinner'; import { useMarkets } from '@/hooks/useMarkets'; import { useUserBalances } from '@/hooks/useUserBalances'; -import { formatBalance } from '@/utils/balance'; -import { getNetworkImg, getNetworkName } from '@/utils/networks'; +import { getNetworkImg, getNetworkName, SupportedNetworks } from '@/utils/networks'; import { useOnboarding } from './OnboardingContext'; import { TokenWithMarkets } from './types'; @@ -27,8 +27,7 @@ function NetworkIcon({ networkId }: { networkId: number }) { export function AssetSelection() { const { balances, loading: balancesLoading } = useUserBalances(); const { markets, loading: marketsLoading } = useMarkets(); - const { setSelectedToken, setSelectedMarkets } = useOnboarding(); - const router = useRouter(); + const { setSelectedToken, setSelectedMarkets, goToNextStep } = useOnboarding(); const tokensWithMarkets = useMemo(() => { if (!balances || !markets) return []; @@ -72,28 +71,22 @@ export function AssetSelection() { const handleTokenSelect = (token: TokenWithMarkets) => { setSelectedToken(token); setSelectedMarkets([]); // Reset selected markets when changing token - router.push('/positions/onboarding?step=risk-selection'); + goToNextStep(); }; if (balancesLoading || marketsLoading) { return ( -
-
-

Select an Asset

-

Choose which asset you want to supply

+
+
+ {' '} + {' '}
-
Loading...
); } return (
-
-

Select an Asset

-

Choose which asset you want to supply

-
- {tokensWithMarkets.length === 0 ? (

No assets available

@@ -107,7 +100,7 @@ export function AssetSelection() {
) : ( -
+
{tokensWithMarkets.map((token) => ( handleTokenSelect(token)} className="group relative flex items-start gap-4 rounded border border-gray-200 bg-white p-4 text-left transition-all duration-300 hover:border-primary hover:shadow-lg dark:border-gray-700 dark:bg-gray-800/50 dark:hover:bg-gray-800" - whileHover={{ scale: 1.02 }} transition={{ type: 'spring', stiffness: 300, damping: 20 }} >
@@ -139,12 +131,33 @@ export function AssetSelection() { {getNetworkName(token.network)}
+ + {/* if base network, show agent badge */} + {token.network === SupportedNetworks.Base && ( + // + //
Monarch Autopilot 🎉
+ //
+ // Monarch Autopilot is now in beta on Base! Setup the agent to start + // automating your reallocations. + //
+ //
+ // } + // > + //
+ // 🤖 + // beta + //
+ // +
+ )}

- Balance: {formatBalance(token.balance, token.decimals)} {token.symbol} + Balance: {formatUnits(BigInt(token.balance), token.decimals)} {token.symbol}

diff --git a/app/positions/components/onboarding/Modal.tsx b/app/positions/components/onboarding/Modal.tsx new file mode 100644 index 00000000..5f98717f --- /dev/null +++ b/app/positions/components/onboarding/Modal.tsx @@ -0,0 +1,101 @@ +import { Modal, ModalContent, ModalHeader, Button } from '@nextui-org/react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { RxCross2 } from 'react-icons/rx'; +import { AssetSelection } from './AssetSelection'; +import { useOnboarding } from './OnboardingContext'; +import { ONBOARDING_STEPS } from './OnboardingContext'; +import { RiskSelection } from './RiskSelection'; +import { SetupPositions } from './SetupPositions'; +import { SuccessPage } from './SuccessPage'; + +const StepComponents = { + 'asset-selection': AssetSelection, + 'risk-selection': RiskSelection, + setup: SetupPositions, + success: SuccessPage, +} as const; + +function StepIndicator({ currentStep }: { currentStep: string }) { + const currentIndex = ONBOARDING_STEPS.findIndex((s) => s.id === currentStep); + + return ( +
+ {ONBOARDING_STEPS.map((step, index) => { + const isPast = index < currentIndex; + const isCurrent = index === currentIndex; + + return ( +
+
+
+ ); + })} +
+ ); +} + +export function OnboardingModal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) { + const { step } = useOnboarding(); + const currentStepIndex = ONBOARDING_STEPS.findIndex((s) => s.id === step); + const CurrentStepComponent = StepComponents[step]; + + return ( + + + {/* Header */} + +
+

+ {ONBOARDING_STEPS[currentStepIndex].title} +

+

+ {ONBOARDING_STEPS[currentStepIndex].description} +

+
+ +
+ + {/* Content */} +
+ + + + + +
+ + {/* Footer with Step Indicator */} +
+ +
+
+
+ ); +} diff --git a/app/positions/components/onboarding/OnboardingContent.tsx b/app/positions/components/onboarding/OnboardingContent.tsx deleted file mode 100644 index 15fbc870..00000000 --- a/app/positions/components/onboarding/OnboardingContent.tsx +++ /dev/null @@ -1,40 +0,0 @@ -'use client'; - -import { useSearchParams } from 'next/navigation'; -import Header from '@/components/layout/header/Header'; -import { AssetSelection } from './AssetSelection'; -import { OnboardingProvider } from './OnboardingContext'; -import { RiskSelection } from './RiskSelection'; -import { SetupPositions } from './SetupPositions'; -import { SuccessPage } from './SuccessPage'; - -export function OnboardingContent() { - const searchParams = useSearchParams(); - const step = searchParams.get('step') ?? 'asset-selection'; - - const renderStep = () => { - switch (step) { - case 'asset-selection': - return ; - case 'risk-selection': - return ; - case 'setup': - return ; - case 'success': - return ; - default: - return ; - } - }; - - return ( -
-
-
- -
{renderStep()}
-
-
-
- ); -} diff --git a/app/positions/components/onboarding/OnboardingContext.tsx b/app/positions/components/onboarding/OnboardingContext.tsx index 750d3605..38064934 100644 --- a/app/positions/components/onboarding/OnboardingContext.tsx +++ b/app/positions/components/onboarding/OnboardingContext.tsx @@ -1,9 +1,19 @@ -import { createContext, useContext, useState, useMemo } from 'react'; -import { useRouter, useSearchParams } from 'next/navigation'; +import { createContext, useContext, useState, useMemo, useCallback } from 'react'; import { Market } from '@/utils/types'; import { TokenWithMarkets } from './types'; -type OnboardingStep = 'asset-selection' | 'risk-selection' | 'setup' | 'success'; +export const ONBOARDING_STEPS = [ + { + id: 'asset-selection', + title: 'Select Asset', + description: 'Choose the asset you want to supply', + }, + { id: 'risk-selection', title: 'Select Markets', description: 'Set your risk preferences' }, + { id: 'setup', title: 'Position Setup', description: 'Configure your initial position' }, + { id: 'success', title: 'Complete', description: 'Position created successfully' }, +] as const; + +export type OnboardingStep = (typeof ONBOARDING_STEPS)[number]['id']; type OnboardingContextType = { selectedToken: TokenWithMarkets | null; @@ -12,52 +22,72 @@ type OnboardingContextType = { setSelectedMarkets: (markets: Market[]) => void; step: OnboardingStep; setStep: (step: OnboardingStep) => void; + canGoNext: boolean; + goToNextStep: () => void; + goToPrevStep: () => void; + resetOnboarding: () => void; }; const OnboardingContext = createContext(null); export function OnboardingProvider({ children }: { children: React.ReactNode }) { - const router = useRouter(); - const searchParams = useSearchParams(); - const currentStep = (searchParams.get('step') as OnboardingStep) || 'asset-selection'; - const [selectedToken, setSelectedToken] = useState(null); const [selectedMarkets, setSelectedMarkets] = useState([]); - const setStep = (newStep: OnboardingStep) => { - const params = new URLSearchParams(searchParams.toString()); - params.set('step', newStep); - router.push(`/positions/onboarding?${params.toString()}`); + const [currentStep, setStep] = useState('asset-selection'); + + const currentStepIndex = ONBOARDING_STEPS.findIndex((s) => s.id === currentStep); + + const canGoNext = useMemo(() => { + switch (currentStep) { + case 'asset-selection': + return !!selectedToken; + case 'risk-selection': + return selectedMarkets.length > 0; + case 'setup': + return true; + default: + return false; + } + }, [currentStep, selectedToken, selectedMarkets]); + + const goToNextStep = () => { + const nextStep = ONBOARDING_STEPS[currentStepIndex + 1]; + if (nextStep) { + setStep(nextStep.id); + } }; + const goToPrevStep = () => { + const prevStep = ONBOARDING_STEPS[currentStepIndex - 1]; + if (prevStep) { + setStep(prevStep.id); + } + }; + + const resetOnboarding = useCallback(() => { + setSelectedToken(null); + setSelectedMarkets([]); + setStep('asset-selection'); + }, [setSelectedToken, setSelectedMarkets, setStep]); + const contextValue = useMemo( () => ({ selectedToken, setSelectedToken: (token: TokenWithMarkets | null) => { setSelectedToken(token); - // Reset markets when token changes setSelectedMarkets([]); }, selectedMarkets, - setSelectedMarkets: (markets: Market[]) => { - setSelectedMarkets(markets); - }, + setSelectedMarkets, step: currentStep, - setStep: (newStep: OnboardingStep) => { - // Validate step transitions - if (newStep !== 'asset-selection' && !selectedToken) { - throw new Error('Token must be selected before proceeding'); - } - if (newStep === 'setup' && selectedMarkets.length === 0) { - throw new Error('Markets must be selected before setup'); - } - if (newStep === 'success' && !selectedToken) { - throw new Error('Token must be selected before showing success'); - } - setStep(newStep); - }, + setStep, + canGoNext, + goToNextStep, + goToPrevStep, + resetOnboarding, }), - [selectedToken, selectedMarkets, currentStep], + [selectedToken, selectedMarkets, currentStep, canGoNext], ); return {children}; diff --git a/app/positions/components/onboarding/RiskSelection.tsx b/app/positions/components/onboarding/RiskSelection.tsx index 873ce684..ec5447b8 100644 --- a/app/positions/components/onboarding/RiskSelection.tsx +++ b/app/positions/components/onboarding/RiskSelection.tsx @@ -1,11 +1,9 @@ import { useMemo, useState } from 'react'; -import { Button } from '@nextui-org/react'; -import Image from 'next/image'; -import { useRouter, useSearchParams } from 'next/navigation'; +import { motion } from 'framer-motion'; import { formatUnits } from 'viem'; -import OracleVendorBadge from '@/components/OracleVendorBadge'; +import { Button } from '@/components/common/Button'; +import { MarketInfoBlock } from '@/components/common/MarketInfoBlock'; import { formatReadable } from '@/utils/balance'; -import { getAssetURL } from '@/utils/external'; import { OracleVendors, parseOracleVendors } from '@/utils/oracle'; import { findToken, getUniqueTokens } from '@/utils/tokens'; import { Market } from '@/utils/types'; @@ -19,12 +17,16 @@ import { import { useOnboarding } from './OnboardingContext'; export function RiskSelection() { - const router = useRouter(); - const searchParams = useSearchParams(); - const { selectedToken, setSelectedMarkets } = useOnboarding(); + const { + selectedToken, + selectedMarkets, + setSelectedMarkets, + canGoNext, + goToNextStep, + goToPrevStep, + } = useOnboarding(); const [selectedCollaterals, setSelectedCollaterals] = useState([]); const [selectedOracles, setSelectedOracles] = useState([]); - const [selectedMarkets, setSelectedMarketsLocal] = useState>(new Set()); const collateralTokens = useMemo(() => { if (!selectedToken?.markets) return []; @@ -72,46 +74,32 @@ export function RiskSelection() { }); }, [selectedToken, selectedCollaterals, selectedOracles]); - const handleNext = () => { - if (selectedMarkets.size > 0) { - const selectedMarketsArray = Array.from(selectedMarkets) - .map((key) => filteredMarkets.find((m) => m.uniqueKey === key)) - .filter((m): m is Market => m !== undefined); - - setSelectedMarkets(selectedMarketsArray); - router.push('/positions/onboarding?step=setup'); - } - }; + // Check if criteria is met to show markets + const shouldShowMarkets = selectedCollaterals.length > 0 && selectedOracles.length > 0; const handleMarketDetails = (market: Market, e: React.MouseEvent) => { e.stopPropagation(); - const currentParams = searchParams.toString(); const marketPath = `/market/${market.morphoBlue.chain.id}/${market.uniqueKey}`; - const targetPath = currentParams ? `${marketPath}?${currentParams}` : marketPath; - - // open in tab - window.open(targetPath, '_blank'); + window.open(marketPath, '_blank'); }; const toggleMarketSelection = (market: Market) => { - const newSelection = new Set(selectedMarkets); - if (selectedMarkets.has(market.uniqueKey)) { - newSelection.delete(market.uniqueKey); + if (selectedMarkets.some((m) => m.uniqueKey === market.uniqueKey)) { + setSelectedMarkets(selectedMarkets.filter((m) => m.uniqueKey !== market.uniqueKey)); } else { - newSelection.add(market.uniqueKey); + setSelectedMarkets([...selectedMarkets, market]); } - setSelectedMarketsLocal(newSelection); }; return (
+ {/* Input Section */}
-

Select Your Risk Preference

-

Choose which assets and oracles you want to trust

+

Choose collateral and oracle you trust

+

- {/* Input Section */} -
+
-
-

Choose markets you want to trust

-
- - {/* Markets Table */} -
-
- - - - - - - - - - - - - - - {filteredMarkets.map((market) => { + {shouldShowMarkets && ( +
+

Choose markets

+

selected markets: {selectedMarkets.length}

+
+ )} + + {/* Markets List - Scrollable Section */} +
+
+ {!shouldShowMarkets ? ( +
+
+

Select your preferences

+

+ {selectedCollaterals.length === 0 && 'Choose at least one collateral asset'} + {selectedCollaterals.length > 0 && + selectedOracles.length === 0 && + 'Now select oracle vendors'} +

+
+
+ ) : ( + + {filteredMarkets.map((market, index) => { const collateralToken = findToken( market.collateralAsset.address, market.morphoBlue.chain.id, ); if (!collateralToken) return null; - const isSelected = selectedMarkets.has(market.uniqueKey); - const { vendors } = parseOracleVendors(market.oracle.data); + const isSelected = selectedMarkets.some((m) => m.uniqueKey === market.uniqueKey); return ( -
toggleMarketSelection(market)} - className={`cursor-pointer transition-all duration-200 ease-in-out hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800 ${ - isSelected ? 'bg-primary-50 dark:bg-primary-900/20' : '' - }`} + className={`relative cursor-pointer rounded p-1 transition-all duration-200 ease-in-out ${ + index === 0 ? 'mt-2' : '' + } ${isSelected ? 'bg-hovered border-2' : ''}`} > - - - - - - - - - + + ); })} - -
MarketOracleWarningsLLTVSupply APYTotal SupplyUtilizationActions
-
- {collateralToken?.img && ( -
- {market.collateralAsset.symbol} +
+
+ +
+ +
+ {/* Risk Indicators */} +
+ + + +
+ + {/* Total Supply */} +
+ Total Supply: +
+ {formatReadable( + Number( + formatUnits( + BigInt(market.state.supplyAssets), + market.loanAsset.decimals, + ), + ), + )}{' '} + {market.loanAsset.symbol}
- )} -
-
- e.stopPropagation()} - className="flex items-center gap-1 no-underline hover:underline" - > - {market.collateralAsset.symbol} - - +
+ + {/* Utilization Rate */} +
+ Utilization: +
+ {formatReadable(market.state.utilization * 100)}%
- as collateral
+ + {/* Details Button */} +
-
-
- {vendors.map((vendor) => ( - - ))} -
-
-
- - - -
-
- {formatUnits(BigInt(market.lltv), 16)}% - - {formatReadable(market.state.supplyApy * 100)}% - - {formatReadable( - Number( - formatUnits(BigInt(market.state.supplyAssets), market.loanAsset.decimals), - ), - )}{' '} - {market.loanAsset.symbol} - - {formatReadable(market.state.utilization * 100)}% - - -
+ + )}
{/* Navigation */} -
-
diff --git a/app/positions/components/onboarding/SetupPositions.tsx b/app/positions/components/onboarding/SetupPositions.tsx index f59b3237..7cadbd24 100644 --- a/app/positions/components/onboarding/SetupPositions.tsx +++ b/app/positions/components/onboarding/SetupPositions.tsx @@ -1,25 +1,23 @@ import { useState, useEffect, useMemo, useCallback } from 'react'; -import { Button, Slider } from '@nextui-org/react'; +import { Slider } from '@nextui-org/react'; import { LockClosedIcon, LockOpen1Icon } from '@radix-ui/react-icons'; import Image from 'next/image'; -import { useRouter } from 'next/navigation'; import { toast } from 'react-toastify'; import { formatUnits, parseUnits } from 'viem'; import { useChainId, useSwitchChain } from 'wagmi'; -import OracleVendorBadge from '@/components/OracleVendorBadge'; +import { Button } from '@/components/common'; +import { MarketInfoBlock } from '@/components/common/MarketInfoBlock'; import { SupplyProcessModal } from '@/components/SupplyProcessModal'; import { useLocalStorage } from '@/hooks/useLocalStorage'; import { useMultiMarketSupply } from '@/hooks/useMultiMarketSupply'; import { useUserBalances } from '@/hooks/useUserBalances'; -import { formatBalance, formatReadable } from '@/utils/balance'; -import { parseOracleVendors } from '@/utils/oracle'; +import { formatBalance } from '@/utils/balance'; import { findToken } from '@/utils/tokens'; import { useOnboarding } from './OnboardingContext'; export function SetupPositions() { - const router = useRouter(); const chainId = useChainId(); - const { selectedToken, selectedMarkets } = useOnboarding(); + const { selectedToken, selectedMarkets, goToNextStep, goToPrevStep } = useOnboarding(); const { balances } = useUserBalances(); const [useEth] = useLocalStorage('useEth', false); const [usePermit2Setting] = useLocalStorage('usePermit2', true); @@ -37,17 +35,6 @@ export function SetupPositions() { const { switchChain } = useSwitchChain(); - // Redirect if no token selected - useEffect(() => { - if (!selectedToken) { - router.push('/positions/onboarding?step=asset-selection'); - return; - } - if (!selectedMarkets || selectedMarkets.length === 0) { - router.push('/positions/onboarding?step=risk-selection'); - } - }, [router, selectedToken, selectedMarkets]); - // Compute token balance and decimals const tokenBalance = useMemo(() => { if (!selectedToken) return 0n; @@ -229,7 +216,7 @@ export function SetupPositions() { isLoadingPermit2, approveAndSupply, supplyPending, - } = useMultiMarketSupply(selectedToken!, supplies, useEth, usePermit2Setting); + } = useMultiMarketSupply(selectedToken!, supplies, useEth, usePermit2Setting, goToNextStep); const handleSupply = async () => { if (isSupplying) { @@ -251,10 +238,8 @@ export function SetupPositions() { setIsSupplying(true); try { - const success = await approveAndSupply(); - if (success) { - router.push('/positions/onboarding?step=success'); - } + // trigger the tx. goToNextStep() be called as a `onSuccess` callback + await approveAndSupply(); } catch (supplyError) { console.error('Supply failed:', supplyError); // Error toast is already shown in useMultiMarketSupply @@ -269,14 +254,6 @@ export function SetupPositions() { return (
-
-

Setup Your Positions

-

- Choose how much {selectedToken.symbol} you want to supply in total and distribute it - across markets -

-
- {/* Total Amount Section */}
@@ -324,10 +301,7 @@ export function SetupPositions() { - - - - + @@ -339,60 +313,24 @@ export function SetupPositions() { ); if (!collateralToken) return null; - const { vendors } = parseOracleVendors(market.oracle.data); const currentPercentage = percentages[market.uniqueKey] ?? 0; const isLocked = lockedAmounts.has(market.uniqueKey); return ( - - - -
Market IDCollateralMarket ParamsSupply APYMarket Distribution
+ - {market.uniqueKey.slice(2, 8)} + -
- {collateralToken?.img && ( -
- {market.collateralAsset.symbol} -
- )} -
- {market.collateralAsset.symbol} - as collateral -
-
-
-
-
- {vendors.map((vendor) => ( - - ))} -
- - {formatUnits(BigInt(market.lltv), 16)}% LTV - -
-
- {formatReadable(market.state.supplyApy * 100)}% -
@@ -472,19 +410,15 @@ export function SetupPositions() { {/* Navigation */}
- diff --git a/app/positions/components/onboarding/SmartOnboarding.tsx b/app/positions/components/onboarding/SmartOnboarding.tsx deleted file mode 100644 index 3085b4de..00000000 --- a/app/positions/components/onboarding/SmartOnboarding.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { AssetSelection } from './AssetSelection'; -import { OnboardingProvider, useOnboarding } from './OnboardingContext'; -import { RiskSelection } from './RiskSelection'; - -function OnboardingContent() { - const { step } = useOnboarding(); - - return ( -
- {step === 'asset-selection' && } - {step === 'risk-selection' && } -
- ); -} - -export function SmartOnboarding() { - return ( - - - - ); -} diff --git a/app/positions/components/onboarding/SuccessPage.tsx b/app/positions/components/onboarding/SuccessPage.tsx index 8b87ba90..fbc4efa9 100644 --- a/app/positions/components/onboarding/SuccessPage.tsx +++ b/app/positions/components/onboarding/SuccessPage.tsx @@ -1,36 +1,51 @@ -import { Button } from '@nextui-org/react'; +import { useMemo } from 'react'; import Link from 'next/link'; import { FaCheckCircle } from 'react-icons/fa'; import { useAccount } from 'wagmi'; +import { Button } from '@/components/common/Button'; import { useOnboarding } from './OnboardingContext'; +// import { SupportedNetworks } from '@/utils/networks'; -export function SuccessPage() { - const { selectedToken } = useOnboarding(); +export function SuccessPage({ onClose }: { onClose: () => void }) { + const { selectedToken, resetOnboarding } = useOnboarding(); const { address } = useAccount(); + const allowAgentSetting = useMemo(() => { + return false; + // TODO: enable for next release with agent + // return selectedToken?.network === SupportedNetworks.Base + }, [selectedToken?.network]); + + const handleFinished = () => { + onClose(); + resetOnboarding(); + }; + return (
- -

Success!

+ +

Success!

-

- Your {selectedToken?.symbol} has been successfully supplied to Morpho Blue. +

+ Your {selectedToken?.symbol} has been successfully supplied to Morpho.{' '} + {allowAgentSetting && + 'You can set Monarch AutoPilot to automate reallocate your positions.'}

-
- - - - - - +
+ + {allowAgentSetting && ( + + + + )}
); diff --git a/app/positions/onboarding/page.tsx b/app/positions/onboarding/page.tsx deleted file mode 100644 index 70b6437d..00000000 --- a/app/positions/onboarding/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { Metadata } from 'next'; -import { OnboardingContent } from '../components/onboarding/OnboardingContent'; - -export const metadata: Metadata = { - title: 'New Position | Monarch', - description: 'Create a new position on Morpho Blue', -}; - -export default function OnboardingPage() { - return ; -} diff --git a/app/positions/report/components/ReportTable.tsx b/app/positions/report/components/ReportTable.tsx index ce8fd16f..0abe295c 100644 --- a/app/positions/report/components/ReportTable.tsx +++ b/app/positions/report/components/ReportTable.tsx @@ -56,7 +56,7 @@ const formatDays = (seconds: number) => { return days.toFixed(2); }; -function MarketInfoBlock({ +function MarketSummaryBlock({ market, interestEarned, decimals, @@ -186,7 +186,7 @@ export function ReportTable({ report, asset, startDate, endDate, chainId }: Repo

Markets

{report.marketReports - .filter(m => m.market.collateralAsset !== null) + .filter((m) => m.market.collateralAsset !== null) .slice() .sort((a, b) => { // First sort by active status @@ -217,7 +217,7 @@ export function ReportTable({ report, asset, startDate, endDate, chainId }: Repo className="w-full hover:bg-gray-50 dark:hover:bg-gray-800" onClick={() => toggleMarket(marketKey)} > - +
{showText && ( {noFeeds ? 'No Oracle' : vendors.join(', ')} diff --git a/src/components/SupplyProcessModal.tsx b/src/components/SupplyProcessModal.tsx index 34345307..8ccb0a89 100644 --- a/src/components/SupplyProcessModal.tsx +++ b/src/components/SupplyProcessModal.tsx @@ -3,7 +3,7 @@ import { Cross1Icon } from '@radix-ui/react-icons'; import { motion, AnimatePresence } from 'framer-motion'; import { FaCheckCircle, FaCircle } from 'react-icons/fa'; import { Market } from '@/utils/types'; -import { MarketAmountBlock } from './common/MarketInfoBlock'; +import { MarketInfoBlock } from './common/MarketInfoBlock'; type MarketSupply = { market: Market; @@ -120,7 +120,7 @@ export function SupplyProcessModal({
{supplies.map((supply) => { return ( -
{collateralToken?.img && ( @@ -46,7 +44,7 @@ export function MarketAmountBlock({ market, amount }: MarketAmountBlockProps): J {formatBalance(amount, market.loanAsset.decimals)} {market.loanAsset.symbol} ) : ( - + )}
diff --git a/src/components/providers/ClientProviders.tsx b/src/components/providers/ClientProviders.tsx index c0b7aef1..130cc169 100644 --- a/src/components/providers/ClientProviders.tsx +++ b/src/components/providers/ClientProviders.tsx @@ -3,6 +3,7 @@ import { ReactNode } from 'react'; import { ToastContainer } from 'react-toastify'; import { MarketsProvider } from '@/contexts/MarketsContext'; +import { OnboardingProvider } from 'app/positions/components/onboarding/OnboardingContext'; type ClientProvidersProps = { children: ReactNode; @@ -11,8 +12,10 @@ type ClientProvidersProps = { export function ClientProviders({ children }: ClientProvidersProps) { return ( - {children} - + + {children} + + ); } diff --git a/src/components/supplyModal.tsx b/src/components/supplyModal.tsx index d105d5cc..f9203512 100644 --- a/src/components/supplyModal.tsx +++ b/src/components/supplyModal.tsx @@ -19,7 +19,7 @@ import { getBundlerV2, getIRMTitle, MONARCH_TX_IDENTIFIER } from '@/utils/morpho import { findToken } from '@/utils/tokens'; import { Market } from '@/utils/types'; import { Button } from './common'; -import { MarketAmountBlock } from './common/MarketInfoBlock'; +import { MarketInfoBlock } from './common/MarketInfoBlock'; import OracleVendorBadge from './OracleVendorBadge'; import { SupplyProcessModal } from './SupplyProcessModal'; @@ -341,7 +341,7 @@ export function SupplyModal({ market, onClose }: SupplyModalProps): JSX.Element

- +
Details
diff --git a/src/components/withdrawModal.tsx b/src/components/withdrawModal.tsx index bbfe6044..b009a513 100644 --- a/src/components/withdrawModal.tsx +++ b/src/components/withdrawModal.tsx @@ -7,7 +7,7 @@ import { toast } from 'react-toastify'; import { Address, encodeFunctionData } from 'viem'; import { useAccount, useSwitchChain } from 'wagmi'; import morphoAbi from '@/abis/morpho'; -import { MarketAmountBlock } from '@/components/common/MarketInfoBlock'; +import { MarketInfoBlock } from '@/components/common/MarketInfoBlock'; import Input from '@/components/Input/Input'; import AccountConnect from '@/components/layout/header/AccountConnect'; import { useTransactionWithToast } from '@/hooks/useTransactionWithToast'; @@ -128,7 +128,7 @@ export function WithdrawModal({ position, onClose, refetch }: ModalProps): JSX.E

- +

Available Liquidity:

diff --git a/src/hooks/useMultiMarketSupply.ts b/src/hooks/useMultiMarketSupply.ts index 54b574c8..0747bce8 100644 --- a/src/hooks/useMultiMarketSupply.ts +++ b/src/hooks/useMultiMarketSupply.ts @@ -22,6 +22,7 @@ export function useMultiMarketSupply( supplies: MarketSupply[], useEth: boolean, usePermit2Setting: boolean, + onSuccess?: () => void, ) { const [currentStep, setCurrentStep] = useState<'approve' | 'signing' | 'supplying'>('approve'); const [showProcessModal, setShowProcessModal] = useState(false); @@ -66,6 +67,7 @@ export function useMultiMarketSupply( successDescription: `Successfully supplied to ${supplies.length} market${ supplies.length > 1 ? 's' : '' }`, + onSuccess, }); const executeSupplyTransaction = useCallback(async () => { diff --git a/src/hooks/useTransactionWithToast.tsx b/src/hooks/useTransactionWithToast.tsx index 2b66fa03..d5d841c9 100644 --- a/src/hooks/useTransactionWithToast.tsx +++ b/src/hooks/useTransactionWithToast.tsx @@ -37,6 +37,8 @@ export function useTransactionWithToast({ hash, }); + console.log('isConfirmed', isConfirmed, toastId, hash); + const onClick = useCallback(() => { if (hash) { // if chainId is not supported, use 1 diff --git a/src/imgs/tokens/usol.png b/src/imgs/tokens/usol.png new file mode 100644 index 00000000..2e51e926 Binary files /dev/null and b/src/imgs/tokens/usol.png differ diff --git a/src/imgs/tokens/usui.png b/src/imgs/tokens/usui.png new file mode 100644 index 00000000..4610ffd5 Binary files /dev/null and b/src/imgs/tokens/usui.png differ diff --git a/src/utils/monarch-agent.ts b/src/utils/monarch-agent.ts new file mode 100644 index 00000000..1a715399 --- /dev/null +++ b/src/utils/monarch-agent.ts @@ -0,0 +1 @@ +export const AGENT_CONTRACT = '0x3ec3a96DA83b486d73C65a35d9eA1936195Af99F'; diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts index dea084b3..4ee28eab 100644 --- a/src/utils/tokens.ts +++ b/src/utils/tokens.ts @@ -240,7 +240,10 @@ const supportedTokens = [ symbol: 'lBTC', img: require('../imgs/tokens/lbtc.webp') as string, decimals: 8, - networks: [{ chain: mainnet, address: '0x8236a87084f8B84306f72007F36F2618A5634494' }], + networks: [ + { chain: mainnet, address: '0x8236a87084f8B84306f72007F36F2618A5634494' }, + { chain: base, address: '0xecAc9C5F704e954931349Da37F60E39f515c11c1' }, + ], }, { symbol: 'rsETH', @@ -382,6 +385,18 @@ const supportedTokens = [ decimals: 18, networks: [{ chain: base, address: '0x7FcD174E80f264448ebeE8c88a7C4476AAF58Ea6' }], }, + { + symbol: 'uSOL', + img: require('../imgs/tokens/usol.png') as string, + decimals: 18, + networks: [{ chain: base, address: '0x9B8Df6E244526ab5F6e6400d331DB28C8fdDdb55' }], + }, + { + symbol: 'uSui', + img: require('../imgs/tokens/usui.png') as string, + decimals: 18, + networks: [{ chain: base, address: '0xb0505e5a99abd03d94a1169e638B78EDfEd26ea4' }], + }, ]; const isWhitelisted = (address: string, chainId: number) => {