diff --git a/packages/ui/app/_components/ResultHandler.tsx b/packages/ui/app/_components/ResultHandler.tsx index 385a3bd366..7df8f3b05d 100644 --- a/packages/ui/app/_components/ResultHandler.tsx +++ b/packages/ui/app/_components/ResultHandler.tsx @@ -33,7 +33,6 @@ export default function ResultHandler({ margin: center ? 'auto' : '0px' }} ariaLabel="three-circles-loading" - wrapperClass="" /> ); } diff --git a/packages/ui/app/_components/popup/Amount.tsx b/packages/ui/app/_components/popup/Amount.tsx index f34896911b..fc8db205af 100644 --- a/packages/ui/app/_components/popup/Amount.tsx +++ b/packages/ui/app/_components/popup/Amount.tsx @@ -4,6 +4,7 @@ import { MarketData } from '@ui/types/TokensDataMap'; import dynamic from 'next/dynamic'; import React, { useState } from 'react'; import { useAccount, useBalance } from 'wagmi'; +import ResultHandler from '../ResultHandler'; interface IAmount { selectedMarketData: MarketData; @@ -12,6 +13,7 @@ interface IAmount { hintText?: string; max?: number; symbol: string; + isLoading?: boolean; } const Amount = ({ selectedMarketData, @@ -19,7 +21,8 @@ const Amount = ({ amount, hintText = 'Wallet Balance', max = 0, - symbol + symbol, + isLoading = false }: IAmount) => { const marketDataDecimals = parseInt( selectedMarketData.underlyingDecimals.toString() @@ -39,15 +42,25 @@ const Amount = ({
Amount - - {hintText} {max.toFixed(marketDataDecimals)} - - +
+ + <> + + {hintText} {max.toFixed(marketDataDecimals)} + + + + +
void; }; -function LoadingButtonWithText({ text }: LoadingButtonWithTextProps) { +function TransactionStepsHandler({ + transactionSteps, + resetTransactionSteps +}: TransactionStepsHandlerProps) { return ( - +
+ {transactionSteps.map((transactionStep, i) => ( +
+
+ {!transactionStep.error && !transactionStep.success && ( + + )} + + {transactionStep.error && } + + {transactionStep.success && } + + {transactionStep.message} +
+ + {transactionStep.txHash && ( +
+ + 0x{transactionStep.txHash.slice(2, 4)}... + {transactionStep.txHash.slice(-6)} + +
+ )} +
+ ))} + + {(transactionSteps.filter((step) => step.success).length === + transactionSteps.length || + transactionSteps.find((step) => step.error) !== undefined) && ( +
+ +
+ )} +
); } @@ -61,18 +110,49 @@ const Popup = ({ selectedMarketData, comptrollerAddress }: IPopup) => { - // console.log(mode); + const [enableCollateral, setEnableCollateral] = useState(false); + const [transactionSteps, upsertTransactionStep] = useReducer( + ( + prevState: TransactionStep[], + updatedStep: + | { transactionStep: TransactionStep; index: number } + | undefined + ): TransactionStep[] => { + if (!updatedStep) { + return []; + } + + const currentSteps = prevState.slice(); + + currentSteps[updatedStep.index] = { + ...currentSteps[updatedStep.index], + ...updatedStep.transactionStep + }; + + if ( + updatedStep.transactionStep.error && + updatedStep.index + 1 < currentSteps.length + ) { + for (let i = updatedStep.index + 1; i < currentSteps.length; i++) { + currentSteps[i] = { + ...currentSteps[i], + error: true + }; + } + } + + return currentSteps; + }, + [] + ); const { currentSdk, address } = useMultiMidas(); const chainId = useChainId(); const { data: minBorrowAmount } = useBorrowMinimum( selectedMarketData, chainId ); - const { data: maxSupplyAmount } = useMaxSupplyAmount( - selectedMarketData, - comptrollerAddress, - chainId - ); + const { data: maxSupplyAmount, isLoading: isLoadingMaxSupply } = + useMaxSupplyAmount(selectedMarketData, comptrollerAddress, chainId); const { data: assetsSupplyAprData } = useTotalSupplyAPYs( [selectedMarketData], chainId @@ -87,7 +167,6 @@ const Popup = ({ return '0.00%'; }, [assetsSupplyAprData]); - const [currentInfoMessage, setCurrentInfoMessage] = useState(); const [active, setActive] = useState(''); const slide = useRef(null!); const router = useRouter(); @@ -103,29 +182,23 @@ const Popup = ({ }, undefined ); - const { data: maxRepayAmount } = useMaxRepayAmount( - selectedMarketData, - chainId - ); - const amountAsBInt = useMemo( + const { data: maxRepayAmount, isLoading: isLoadingMaxRepayAmount } = + useMaxRepayAmount(selectedMarketData, chainId); + const amountAsBInt = useMemo( () => - amount - ? ( - amount * + BigNumber.from( + Math.round( + (amount ?? 0) * Math.pow( 10, parseInt(selectedMarketData.underlyingDecimals.toString()) ) - ).toString() - : '0', + ) + ), [amount] ); - const [isExecutingAction, setIsExecutingAction] = useState(false); - const { data: maxBorrowAmount } = useMaxBorrowAmount( - selectedMarketData, - comptrollerAddress, - chainId - ); + const { data: maxBorrowAmount, isLoading: isLoadingMaxBorrowAmount } = + useMaxBorrowAmount(selectedMarketData, comptrollerAddress, chainId); const currentBorrowAmountAsFloat = useMemo( () => parseFloat(selectedMarketData.borrowBalance.toString()), [selectedMarketData] @@ -138,15 +211,13 @@ const Popup = ({ useUpdatedUserAssets({ mode: currentFundOperation, poolChainId: chainId, - amount: amountAsBInt as any, + amount: amountAsBInt, assets: [selectedMarketData], index: 0 }); const updatedAsset = updatedAssets ? updatedAssets[0] : undefined; - const { data: maxWithdrawAmount } = useMaxWithdrawAmount( - selectedMarketData, - chainId - ); + const { data: maxWithdrawAmount, isLoading: isLoadingMaxWithdrawAmount } = + useMaxWithdrawAmount(selectedMarketData, chainId); const { supplyAPY, borrowAPR, @@ -221,6 +292,16 @@ const Popup = ({ return {}; }, [chainId, updatedAsset, selectedMarketData, updatedAssets, currentSdk]); + const minBorrowAmountAsNumber = useMemo( + () => + parseFloat( + formatUnits( + minBorrowAmount?.minBorrowAsset ?? '0', + selectedMarketData.underlyingDecimals + ) + ), + [minBorrowAmount] + ); const queryClient = useQueryClient(); /** @@ -230,16 +311,7 @@ const Popup = ({ switch (active) { case 'COLLATERAL': setCurrentUtilizationPercentage( - Math.round( - ((amount ?? 0) / - parseFloat( - formatUnits( - maxSupplyAmount?.bigNumber ?? '0', - selectedMarketData.underlyingDecimals - ) - )) * - 100 - ) + Math.round(((amount ?? 0) / (maxSupplyAmount?.number ?? 0)) * 100) ); break; @@ -262,16 +334,7 @@ const Popup = ({ case 'BORROW': setCurrentUtilizationPercentage( - Math.round( - ((amount ?? 0) / - parseFloat( - formatUnits( - maxBorrowAmount?.bigNumber ?? '0', - selectedMarketData.underlyingDecimals - ) ?? '1' - )) * - 100 - ) + Math.round(((amount ?? 0) / (maxBorrowAmount?.number ?? 1)) * 100) ); break; @@ -315,6 +378,7 @@ const Popup = ({ useEffect(() => { setAmount(0); setCurrentUtilizationPercentage(0); + upsertTransactionStep(undefined); switch (active) { case 'COLLATERAL': @@ -375,12 +439,7 @@ const Popup = ({ parseFloat( ( (utilizationPercentage / 100) * - parseFloat( - formatUnits( - maxSupplyAmount?.bigNumber ?? '0', - selectedMarketData.underlyingDecimals - ) ?? '0.0' - ) + (maxSupplyAmount?.number ?? 0) ).toFixed(parseInt(selectedMarketData.underlyingDecimals.toString())) ) ); @@ -407,12 +466,7 @@ const Popup = ({ parseFloat( ( (utilizationPercentage / 100) * - parseFloat( - formatUnits( - maxBorrowAmount?.bigNumber ?? '0', - selectedMarketData.underlyingDecimals - ) ?? '0.0' - ) + (maxBorrowAmount?.number ?? 0) ).toFixed(parseInt(selectedMarketData.underlyingDecimals.toString())) ) ); @@ -434,9 +488,39 @@ const Popup = ({ ); }; - const supplyAmount = async (collateral: boolean = false) => { + const addStepsForAction = (steps: TransactionStep[]) => { + steps.forEach((step, i) => + upsertTransactionStep({ transactionStep: step, index: i }) + ); + }; + + const resetTransactionSteps = () => { + refetchUsedQueries(); + upsertTransactionStep(undefined); + }; + + const refetchUsedQueries = async () => { + queryClient.invalidateQueries({ queryKey: ['useFusePoolData'] }); + queryClient.invalidateQueries({ queryKey: ['useBorrowMinimum'] }); + queryClient.invalidateQueries({ queryKey: ['useUsdPrice'] }); + queryClient.invalidateQueries({ queryKey: ['useAllUsdPrices'] }); + queryClient.invalidateQueries({ queryKey: ['useTotalSupplyAPYs'] }); + queryClient.invalidateQueries({ queryKey: ['useUpdatedUserAssets'] }); + queryClient.invalidateQueries({ queryKey: ['useMaxSupplyAmount'] }); + queryClient.invalidateQueries({ queryKey: ['useMaxWithdrawAmount'] }); + queryClient.invalidateQueries({ queryKey: ['useMaxBorrowAmount'] }); + queryClient.invalidateQueries({ queryKey: ['useMaxRepayAmount'] }); + queryClient.invalidateQueries({ + queryKey: ['useSupplyCapsDataForPool'] + }); + queryClient.invalidateQueries({ + queryKey: ['useBorrowCapsDataForAsset'] + }); + }; + + const supplyAmount = async () => { if ( - !isExecutingAction && + !transactionSteps.length && currentSdk && address && amount && @@ -444,11 +528,30 @@ const Popup = ({ maxSupplyAmount && amount <= maxSupplyAmount.number ) { - setIsExecutingAction(true); + let currentTransactionStep = 0; + addStepsForAction([ + { + message: INFO_MESSAGES.SUPPLY.APPROVE, + success: false, + error: false + }, + ...(enableCollateral + ? [ + { + message: INFO_MESSAGES.SUPPLY.COLLATERAL, + success: false, + error: false + } + ] + : []), + { + message: INFO_MESSAGES.SUPPLY.SUPPLYING, + success: false, + error: false + } + ]); try { - setCurrentInfoMessage(INFO_MESSAGES.SUPPLY.APPROVE); - const token = currentSdk.getEIP20TokenInstance( selectedMarketData.underlyingToken, currentSdk.signer @@ -463,61 +566,101 @@ const Popup = ({ selectedMarketData.underlyingToken ); + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + txHash: tx.hash + }, + index: currentTransactionStep + }); + await tx.wait(); } - if (collateral) { - setCurrentInfoMessage(INFO_MESSAGES.SUPPLY.COLLATERAL); + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + success: true + }, + index: currentTransactionStep + }); + + currentTransactionStep++; + if (enableCollateral) { const tx = await currentSdk.enterMarkets( selectedMarketData.cToken, comptrollerAddress ); + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + txHash: tx.hash + }, + index: currentTransactionStep + }); + await tx.wait(); - } - setCurrentInfoMessage(INFO_MESSAGES.SUPPLY.SUPPLYING); + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + success: true + }, + index: currentTransactionStep + }); + + currentTransactionStep++; + } const { tx, errorCode } = await currentSdk.mint( selectedMarketData.cToken, - amountAsBInt as any + amountAsBInt ); if (errorCode) { throw new Error('Error during supplying!'); } + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + txHash: tx?.hash + }, + index: currentTransactionStep + }); + await tx?.wait(); - await Promise.all([ - queryClient.refetchQueries({ queryKey: ['useFusePoolData'] }), - queryClient.refetchQueries({ queryKey: ['useMaxSupplyAmount'] }), - queryClient.refetchQueries({ queryKey: ['useMaxWithdrawAmount'] }), - queryClient.refetchQueries({ queryKey: ['useMaxBorrowAmount'] }), - queryClient.refetchQueries({ queryKey: ['useMaxRepayAmount'] }), - queryClient.refetchQueries({ - queryKey: ['useSupplyCapsDataForPool'] - }), - queryClient.refetchQueries({ - queryKey: ['useBorrowCapsDataForAsset'] - }) - ]); + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + success: true + }, + index: currentTransactionStep + }); toast.success( `Supplied ${amount} ${selectedMarketData.underlyingSymbol}` ); } catch (error) { toast.error('Error while supplying!'); + + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + error: true + }, + index: currentTransactionStep + }); } } - - setIsExecutingAction(false); }; const withdrawAmount = async () => { if ( - !isExecutingAction && + !transactionSteps.length && currentSdk && address && amount && @@ -530,11 +673,16 @@ const Popup = ({ ) ) ) { - setIsExecutingAction(true); + let currentTransactionStep = 0; + addStepsForAction([ + { + message: INFO_MESSAGES.WITHDRAW.WITHDRAWING, + success: false, + error: false + } + ]); try { - setCurrentInfoMessage(INFO_MESSAGES.WITHDRAW.WITHDRAWING); - if ( parseFloat( formatUnits( @@ -552,72 +700,110 @@ const Popup = ({ throw new Error('Error during withdrawing!'); } + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + txHash: tx?.hash + }, + index: currentTransactionStep + }); + await tx?.wait(); } else { const { tx, errorCode } = await currentSdk.withdraw( selectedMarketData.cToken, - amountAsBInt as any + amountAsBInt ); if (errorCode) { throw new Error('Error during withdrawing!'); } + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + txHash: tx?.hash + }, + index: currentTransactionStep + }); + await tx?.wait(); } + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + success: true + }, + index: currentTransactionStep + }); + toast.success( `Withdrawn ${amount} ${selectedMarketData.underlyingSymbol}` ); } catch (error) { console.error(error); + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + error: true + }, + index: currentTransactionStep + }); + toast.error('Error while withdrawing!'); } } - - setIsExecutingAction(false); }; const borrowAmount = async () => { if ( - !isExecutingAction && + !transactionSteps.length && currentSdk && address && amount && amount > 0 && minBorrowAmount && - amount > (minBorrowAmount?.minBorrowUSD ?? 0) && + amount > minBorrowAmountAsNumber && maxBorrowAmount && amount <= maxBorrowAmount.number ) { - setIsExecutingAction(true); + let currentTransactionStep = 0; + addStepsForAction([ + { + message: INFO_MESSAGES.BORROW.BORROWING, + success: false, + error: false + } + ]); try { - setCurrentInfoMessage(INFO_MESSAGES.BORROW.BORROWING); - const { tx, errorCode } = await currentSdk.borrow( selectedMarketData.cToken, - amountAsBInt as any + amountAsBInt ); if (errorCode) { - throw new Error('Error during withdrawing!'); + throw new Error('Error during borrowing!'); } - await tx?.wait(); - await queryClient.refetchQueries({ queryKey: ['useFusePoolData'] }); - await queryClient.refetchQueries({ queryKey: ['useMaxSupplyAmount'] }); - await queryClient.refetchQueries({ - queryKey: ['useMaxWithdrawAmount'] + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + txHash: tx?.hash + }, + index: currentTransactionStep }); - await queryClient.refetchQueries({ queryKey: ['useMaxBorrowAmount'] }); - await queryClient.refetchQueries({ queryKey: ['useMaxRepayAmount'] }); - await queryClient.refetchQueries({ - queryKey: ['useSupplyCapsDataForPool'] - }); - await queryClient.refetchQueries({ - queryKey: ['useBorrowCapsDataForAsset'] + + await tx?.wait(); + + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + success: true + }, + index: currentTransactionStep }); toast.success( @@ -626,27 +812,43 @@ const Popup = ({ } catch (error) { console.error(error); + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + error: true + }, + index: currentTransactionStep + }); + toast.error('Error while borrowing!'); } } - - setIsExecutingAction(false); }; const repayAmount = async () => { if ( - !isExecutingAction && + !transactionSteps.length && currentSdk && address && amount && amount > 0 && currentBorrowAmountAsFloat ) { - setIsExecutingAction(true); + let currentTransactionStep = 0; + addStepsForAction([ + { + message: INFO_MESSAGES.REPAY.APPROVE, + success: false, + error: false + }, + { + message: INFO_MESSAGES.REPAY.REPAYING, + success: false, + error: false + } + ]); try { - setCurrentInfoMessage(INFO_MESSAGES.REPAY.APPROVE); - const token = currentSdk.getEIP20TokenInstance( selectedMarketData.underlyingToken, currentSdk.signer @@ -661,53 +863,71 @@ const Popup = ({ selectedMarketData.underlyingToken ); + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + txHash: tx.hash + }, + index: currentTransactionStep + }); + await tx.wait(); } - setCurrentInfoMessage(INFO_MESSAGES.REPAY.REPAYING); + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + success: true + }, + index: currentTransactionStep + }); + + currentTransactionStep++; - const isRepayingMax = - parseInt((maxRepayAmount ?? '0').toString()) <= - parseInt(amountAsBInt); + const isRepayingMax = amountAsBInt.gte(maxRepayAmount ?? '0'); const { tx, errorCode } = await currentSdk.repay( selectedMarketData.cToken, isRepayingMax, - isRepayingMax - ? selectedMarketData.borrowBalance - : (amountAsBInt as any) + isRepayingMax ? selectedMarketData.borrowBalance : amountAsBInt ); if (errorCode) { throw new Error('Error during repaying!'); } - await tx?.wait(); - await queryClient.refetchQueries({ queryKey: ['useFusePoolData'] }); - await queryClient.refetchQueries({ queryKey: ['useMaxSupplyAmount'] }); - await queryClient.refetchQueries({ - queryKey: ['useMaxWithdrawAmount'] + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + txHash: tx?.hash + }, + index: currentTransactionStep }); - await queryClient.refetchQueries({ queryKey: ['useMaxBorrowAmount'] }); - await queryClient.refetchQueries({ queryKey: ['useMaxRepayAmount'] }); - await queryClient.refetchQueries({ - queryKey: ['useSupplyCapsDataForPool'] - }); - await queryClient.refetchQueries({ - queryKey: ['useBorrowCapsDataForAsset'] + + await tx?.wait(); + + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + success: true + }, + index: currentTransactionStep }); } catch (error) { console.error(error); + upsertTransactionStep({ + transactionStep: { + ...transactionSteps[currentTransactionStep], + error: true + }, + index: currentTransactionStep + }); + toast.error('Error while repaying!'); } } - - setIsExecutingAction(false); }; - // console.log(supplyUtilization); - // console.log(amount); - return (
setAmount(val)} amount={amount} - max={parseFloat( - formatUnits( - maxSupplyAmount?.bigNumber ?? '0', - selectedMarketData.underlyingDecimals - ) ?? '0.0' - )} + max={maxSupplyAmount?.number ?? 0} symbol={selectedMarketData.underlyingSymbol} + isLoading={isLoadingMaxSupply} />
+
+
+ Enable collateral +
+ + !transactionSteps.length && + setEnableCollateral(!enableCollateral) + } + /> +
+
- {isExecutingAction ? ( - + {transactionSteps.length > 0 ? ( + ) : ( <> - - )}
@@ -856,6 +1081,7 @@ const Popup = ({ )} symbol={selectedMarketData.underlyingSymbol} hintText="Max Withdraw" + isLoading={isLoadingMaxWithdrawAmount} /> - {isExecutingAction ? ( - + {transactionSteps.length > 0 ? ( + ) : (
@@ -1000,15 +1227,18 @@ const Popup = ({
- {isExecutingAction ? ( - + {transactionSteps.length > 0 ? ( + ) : (
@@ -105,7 +100,7 @@ export default function Market() { className={`bg-grayone min-h-[60vh] pb-20 w-full px-[3%] mt-3 rounded-xl`} >
-

Mode Lending & Borrowing

+ {/*

Mode Lending & Borrowing

*/} {/*
@@ -135,7 +130,7 @@ export default function Market() {
*/}
- + {/* */}
diff --git a/packages/ui/hooks/useTokenBalance.ts b/packages/ui/hooks/useTokenBalance.ts index b56f6d2167..96a24778a6 100644 --- a/packages/ui/hooks/useTokenBalance.ts +++ b/packages/ui/hooks/useTokenBalance.ts @@ -20,7 +20,7 @@ export const fetchTokenBalance = async ( balance = await sdk.provider.getBalance(address); } else { // const contract = sdk.createCTokenWithExtensions(tokenAddress); - balance = await sdk.provider.getBalance(address, tokenAddress); + // balance = await sdk.provider.; console.log(balance); } } catch (e) {