diff --git a/earn/public/privacy.pdf b/earn/public/privacy.pdf deleted file mode 100644 index 4a23ad2aa..000000000 Binary files a/earn/public/privacy.pdf and /dev/null differ diff --git a/earn/public/terms.pdf b/earn/public/terms.pdf deleted file mode 100644 index bb8492ecb..000000000 Binary files a/earn/public/terms.pdf and /dev/null differ diff --git a/earn/src/components/advanced/BorrowGraph.tsx b/earn/src/components/advanced/BorrowGraph.tsx index bf8b1d21b..24bf93f03 100644 --- a/earn/src/components/advanced/BorrowGraph.tsx +++ b/earn/src/components/advanced/BorrowGraph.tsx @@ -8,7 +8,7 @@ import { RESPONSIVE_BREAKPOINT_MD, RESPONSIVE_BREAKPOINT_SM, RESPONSIVE_BREAKPOINTS, -} from '../../data/constants/Breakpoints'; +} from 'shared/lib/data/constants/Breakpoints'; import Graph from '../graph/Graph'; const TEXT_COLOR = '#82a0b6'; diff --git a/earn/src/components/advanced/BorrowMetrics.tsx b/earn/src/components/advanced/BorrowMetrics.tsx index 3b0c1b6d3..9cfacd24f 100644 --- a/earn/src/components/advanced/BorrowMetrics.tsx +++ b/earn/src/components/advanced/BorrowMetrics.tsx @@ -4,17 +4,18 @@ import { formatDistanceToNowStrict } from 'date-fns'; import Tooltip from 'shared/lib/components/common/Tooltip'; import { Display, Text } from 'shared/lib/components/common/Typography'; import { auctionCurve, sqrtRatioToTick } from 'shared/lib/data/BalanceSheet'; +import { RESPONSIVE_BREAKPOINT_MD, RESPONSIVE_BREAKPOINT_SM } from 'shared/lib/data/constants/Breakpoints'; import { MANAGER_NAME_MAP } from 'shared/lib/data/constants/ChainSpecific'; import { GREY_700 } from 'shared/lib/data/constants/Colors'; +import { LendingPair } from 'shared/lib/data/LendingPair'; import useChain from 'shared/lib/hooks/UseChain'; -import { getEtherscanUrlForChain } from 'shared/lib/util/Chains'; +import { getBlockExplorerUrl } from 'shared/lib/util/Chains'; import { formatTokenAmount } from 'shared/lib/util/Numbers'; import styled from 'styled-components'; import { Address } from 'viem'; import { usePublicClient } from 'wagmi'; -import { BorrowerNftBorrower } from '../../data/BorrowerNft'; -import { RESPONSIVE_BREAKPOINT_MD, RESPONSIVE_BREAKPOINT_SM } from '../../data/constants/Breakpoints'; +import { BorrowerNftBorrower } from '../../hooks/useDeprecatedMarginAccountShim'; const BORROW_TITLE_TEXT_COLOR = 'rgba(130, 160, 182, 1)'; const MAX_HEALTH = 10; @@ -165,14 +166,16 @@ function HealthMetricCard(props: { health: number }) { } export type BorrowMetricsProps = { - marginAccount?: BorrowerNftBorrower; - dailyInterest0: number; - dailyInterest1: number; + borrowerNft?: BorrowerNftBorrower; + market?: LendingPair; userHasNoMarginAccounts: boolean; }; export function BorrowMetrics(props: BorrowMetricsProps) { - const { marginAccount, dailyInterest0, dailyInterest1, userHasNoMarginAccounts } = props; + const { borrowerNft, market, userHasNoMarginAccounts } = props; + + const dailyInterest0 = ((market?.kitty0Info.borrowAPR || 0) / 365) * (borrowerNft?.liabilities.amount0 || 0); + const dailyInterest1 = ((market?.kitty1Info.borrowAPR || 0) / 365) * (borrowerNft?.liabilities.amount1 || 0); const activeChain = useChain(); @@ -180,27 +183,27 @@ export function BorrowMetrics(props: BorrowMetricsProps) { const [mostRecentModifyTime, setMostRecentModifyTime] = useState(null); const [token0Collateral, token1Collateral] = useMemo( - () => marginAccount?.assets.amountsAt(sqrtRatioToTick(marginAccount.sqrtPriceX96)) ?? [0, 0], - [marginAccount] + () => borrowerNft?.assets.amountsAt(sqrtRatioToTick(borrowerNft.sqrtPriceX96)) ?? [0, 0], + [borrowerNft] ); const publicClient = usePublicClient({ chainId: activeChain.id }); useEffect(() => { (async () => { setMostRecentModifyTime(null); - if (!publicClient || !marginAccount?.mostRecentModify) return; - const block = await publicClient.getBlock({ blockNumber: marginAccount.mostRecentModify.blockNumber }); + if (!publicClient || !borrowerNft?.mostRecentModify?.blockNumber) return; + const block = await publicClient.getBlock({ blockNumber: borrowerNft.mostRecentModify.blockNumber }); setMostRecentModifyTime(new Date(Number(block.timestamp) * 1000)); })(); - }, [publicClient, marginAccount, setMostRecentModifyTime]); + }, [publicClient, borrowerNft?.mostRecentModify?.blockNumber, setMostRecentModifyTime]); useEffect(() => { const interval = setInterval(() => setCurrentTime(Date.now()), 200); - if (!marginAccount?.warningTime) clearInterval(interval); + if (!borrowerNft?.warningTime) clearInterval(interval); return () => clearInterval(interval); - }, [marginAccount?.warningTime]); + }, [borrowerNft]); - if (!marginAccount) + if (!borrowerNft) return ( @@ -218,17 +221,17 @@ export function BorrowMetrics(props: BorrowMetricsProps) { ); - const etherscanUrl = getEtherscanUrlForChain(activeChain); + const etherscanUrl = getBlockExplorerUrl(activeChain); - const mostRecentManager = marginAccount.mostRecentModify - ? (marginAccount.mostRecentModify.args.manager as Address) + const mostRecentManager = borrowerNft.mostRecentModify + ? (borrowerNft.mostRecentModify.args.manager as Address) : '0x'; const mostRecentManagerName = Object.hasOwn(MANAGER_NAME_MAP, mostRecentManager) ? MANAGER_NAME_MAP[mostRecentManager] : undefined; const mostRecentManagerUrl = `${etherscanUrl}/address/${mostRecentManager}`; - const mostRecentModifyHash = marginAccount.mostRecentModify?.transactionHash; + const mostRecentModifyHash = borrowerNft.mostRecentModify?.transactionHash; const mostRecentModifyUrl = `${etherscanUrl}/tx/${mostRecentModifyHash}`; const mostRecentModifyTimeStr = mostRecentModifyTime ? formatDistanceToNowStrict(mostRecentModifyTime, { @@ -238,8 +241,8 @@ export function BorrowMetrics(props: BorrowMetricsProps) { : ''; let liquidationAuctionStr = 'Not started'; - if (marginAccount.warningTime > 0) { - const auctionStartTime = marginAccount.warningTime + 5 * 60; + if (borrowerNft.warningTime > 0) { + const auctionStartTime = borrowerNft.warningTime + 5 * 60; const currentTime = Date.now() / 1000; if (currentTime < auctionStartTime) { liquidationAuctionStr = `Begins in ${(auctionStartTime - currentTime).toFixed(1)} seconds`; @@ -252,34 +255,34 @@ export function BorrowMetrics(props: BorrowMetricsProps) { - + {mostRecentModifyHash && ( @@ -295,9 +298,9 @@ export function BorrowMetrics(props: BorrowMetricsProps) { )} - {marginAccount.userDataHex} + {borrowerNft.userDataHex} - {marginAccount.warningTime > 0 && ( + {borrowerNft.warningTime > 0 && ( {liquidationAuctionStr} diff --git a/earn/src/components/advanced/GlobalStatsTable.tsx b/earn/src/components/advanced/GlobalStatsTable.tsx index e19f1f192..b8d04108f 100644 --- a/earn/src/components/advanced/GlobalStatsTable.tsx +++ b/earn/src/components/advanced/GlobalStatsTable.tsx @@ -1,12 +1,11 @@ import { Display, Text } from 'shared/lib/components/common/Typography'; +import { RESPONSIVE_BREAKPOINT_XS } from 'shared/lib/data/constants/Breakpoints'; import { GREY_700 } from 'shared/lib/data/constants/Colors'; import { GNFormat } from 'shared/lib/data/GoodNumber'; import { LendingPair } from 'shared/lib/data/LendingPair'; import { roundPercentage } from 'shared/lib/util/Numbers'; import styled from 'styled-components'; -import { RESPONSIVE_BREAKPOINT_XS } from '../../data/constants/Breakpoints'; - const STAT_LABEL_TEXT_COLOR = 'rgba(130, 160, 182, 1)'; const STAT_VALUE_TEXT_COLOR = 'rgba(255, 255, 255, 1)'; diff --git a/earn/src/components/advanced/SmartWalletButton.tsx b/earn/src/components/advanced/SmartWalletButton.tsx index 2ee0bf967..81e315118 100644 --- a/earn/src/components/advanced/SmartWalletButton.tsx +++ b/earn/src/components/advanced/SmartWalletButton.tsx @@ -5,7 +5,7 @@ import { rgba } from 'shared/lib/util/Colors'; import styled from 'styled-components'; import { ReactComponent as PlusIcon } from '../../assets/svg/plus.svg'; -import useProminentColor from '../../data/hooks/UseProminentColor'; +import useProminentColor from '../../hooks/UseProminentColor'; const Container = styled.button.attrs( (props: { backgroundGradient: string; active: boolean; $animate: boolean }) => props diff --git a/earn/src/components/advanced/TokenAllocationPieChartWidget.tsx b/earn/src/components/advanced/TokenAllocationPieChartWidget.tsx index 4f1178282..737dd4ac9 100644 --- a/earn/src/components/advanced/TokenAllocationPieChartWidget.tsx +++ b/earn/src/components/advanced/TokenAllocationPieChartWidget.tsx @@ -9,7 +9,7 @@ import { rgb, rgba } from 'shared/lib/util/Colors'; import styled from 'styled-components'; import tw from 'twin.macro'; -import { BorrowerNftBorrower } from '../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../hooks/useDeprecatedMarginAccountShim'; // MARK: Capturing Mouse Data on container div --------------------------------------- diff --git a/earn/src/components/advanced/TokenAllocationWidget.tsx b/earn/src/components/advanced/TokenAllocationWidget.tsx index 72771e222..7f857e606 100644 --- a/earn/src/components/advanced/TokenAllocationWidget.tsx +++ b/earn/src/components/advanced/TokenAllocationWidget.tsx @@ -7,7 +7,7 @@ import styled from 'styled-components'; import TokenAllocationPieChartWidget from './TokenAllocationPieChartWidget'; import { ReactComponent as PieChartIcon } from '../../assets/svg/pie_chart.svg'; -import { BorrowerNftBorrower } from '../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../hooks/useDeprecatedMarginAccountShim'; const ACCENT_COLOR = 'rgba(130, 160, 182, 1)'; diff --git a/earn/src/components/advanced/UniswapPositionList.tsx b/earn/src/components/advanced/UniswapPositionList.tsx index 508c936c9..e1907dd1c 100644 --- a/earn/src/components/advanced/UniswapPositionList.tsx +++ b/earn/src/components/advanced/UniswapPositionList.tsx @@ -17,7 +17,7 @@ import styled from 'styled-components'; import ImportUniswapNFTModal from './modal/ImportUniswapNFTModal'; import { WithdrawUniswapNFTModal } from './modal/WithdrawUniswapNFTModal'; -import { BorrowerNftBorrower } from '../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../hooks/useDeprecatedMarginAccountShim'; import TokenPairIcons from '../common/TokenPairIcons'; import { InRangeBadge, diff --git a/earn/src/components/advanced/modal/AddCollateralModal.tsx b/earn/src/components/advanced/modal/AddCollateralModal.tsx index 792d2542f..95d2606c0 100644 --- a/earn/src/components/advanced/modal/AddCollateralModal.tsx +++ b/earn/src/components/advanced/modal/AddCollateralModal.tsx @@ -8,8 +8,7 @@ import { UniswapNFTPosition } from 'shared/lib/data/Uniswap'; import { AddCollateralTab } from './tab/AddCollateralTab'; import { AddUniswapNFTAsCollateralTab } from './tab/AddUniswapNFTAsCollateralTab'; -import { BorrowerNftBorrower } from '../../../data/BorrowerNft'; -import { MAX_UNISWAP_POSITIONS } from '../../../data/constants/Values'; +import { BorrowerNftBorrower } from '../../../hooks/useDeprecatedMarginAccountShim'; const SECONDARY_COLOR = '#CCDFED'; @@ -33,7 +32,7 @@ export default function AddCollateralModal(props: AddCollateralModalProps) { const [modalState, setModalState] = useState(() => { // Only show the select collateral type modal if there are uniswap NFT positions and the user has not already // added the maximum number of uniswap positions. - if (uniswapNFTPositions.size > 0 && borrower.assets.uniswapPositions.length < MAX_UNISWAP_POSITIONS) { + if (uniswapNFTPositions.size > 0 && borrower.assets.uniswapPositions.length < 3) { return AddCollateralModalState.SELECT_COLLATERAL_TYPE; } return AddCollateralModalState.TOKENS; @@ -44,7 +43,7 @@ export default function AddCollateralModal(props: AddCollateralModalProps) { setModalState(() => { // Only show the select collateral type modal if there are uniswap NFT positions and the user has not already // added the maximum number of uniswap positions. - if (uniswapNFTPositions.size > 0 && borrower.assets.uniswapPositions.length < MAX_UNISWAP_POSITIONS) { + if (uniswapNFTPositions.size > 0 && borrower.assets.uniswapPositions.length < 3) { return AddCollateralModalState.SELECT_COLLATERAL_TYPE; } return AddCollateralModalState.TOKENS; diff --git a/earn/src/components/advanced/modal/BorrowModal.tsx b/earn/src/components/advanced/modal/BorrowModal.tsx index 60404998c..9f4311be7 100644 --- a/earn/src/components/advanced/modal/BorrowModal.tsx +++ b/earn/src/components/advanced/modal/BorrowModal.tsx @@ -1,9 +1,7 @@ -import { useState, useMemo, useEffect } from 'react'; +import { useState, useEffect } from 'react'; -import { ethers } from 'ethers'; import { borrowerAbi } from 'shared/lib/abis/Borrower'; import { borrowerNftAbi } from 'shared/lib/abis/BorrowerNft'; -import { factoryAbi } from 'shared/lib/abis/Factory'; import { FilledStylizedButton } from 'shared/lib/components/common/Buttons'; import { CustomMaxButton } from 'shared/lib/components/common/Input'; import Modal from 'shared/lib/components/common/Modal'; @@ -13,7 +11,6 @@ import { Assets, Liabilities } from 'shared/lib/data/Borrower'; import { ALOE_II_BORROWER_NFT_ADDRESS, ALOE_II_BORROWER_NFT_SIMPLE_MANAGER_ADDRESS, - ALOE_II_FACTORY_ADDRESS, } from 'shared/lib/data/constants/ChainSpecific'; import { TERMS_OF_SERVICE_URL } from 'shared/lib/data/constants/Values'; import { GN, GNFormat } from 'shared/lib/data/GoodNumber'; @@ -22,10 +19,10 @@ import { Token } from 'shared/lib/data/Token'; import useChain from 'shared/lib/hooks/UseChain'; import { formatNumberInput, truncateDecimals } from 'shared/lib/util/Numbers'; import styled from 'styled-components'; -import { Address, Hex, WriteContractReturnType } from 'viem'; -import { useAccount, useReadContract, useSimulateContract, useWriteContract } from 'wagmi'; +import { Address, encodeFunctionData, formatEther, WriteContractReturnType } from 'viem'; +import { useAccount, useSimulateContract, useWriteContract } from 'wagmi'; -import { BorrowerNftBorrower } from '../../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../../hooks/useDeprecatedMarginAccountShim'; import HealthBar from '../../common/HealthBar'; import TokenAmountSelectInput from '../../portfolio/TokenAmountSelectInput'; @@ -68,7 +65,7 @@ function getConfirmButton(state: ConfirmButtonState, token: Token): { text: stri type BorrowButtonProps = { borrower: BorrowerNftBorrower; - etherToSend: GN; + etherToSend: bigint; userAddress: Address; borrowToken: Token; borrowAmount: GN; @@ -101,12 +98,11 @@ function BorrowButton(props: BorrowButtonProps) { const amount0Big = isBorrowingToken0 ? borrowAmount : GN.zero(borrowToken.decimals); const amount1Big = isBorrowingToken0 ? GN.zero(borrowToken.decimals) : borrowAmount; - const borrowerInterface = new ethers.utils.Interface(borrowerAbi); - const encodedData = borrowerInterface.encodeFunctionData('borrow', [ - amount0Big.toBigNumber(), - amount1Big.toBigNumber(), - shouldWithdrawToWallet ? userAddress : borrower.address, - ]); + const encodedData = encodeFunctionData({ + abi: borrowerAbi, + functionName: 'borrow', + args: [amount0Big.toBigInt(), amount1Big.toBigInt(), shouldWithdrawToWallet ? userAddress : borrower.address], + }); const { data: borrowConfig, isLoading: prepareContractIsLoading } = useSimulateContract({ address: ALOE_II_BORROWER_NFT_ADDRESS[activeChain.id], @@ -116,10 +112,10 @@ function BorrowButton(props: BorrowButtonProps) { userAddress, [borrower.index], [ALOE_II_BORROWER_NFT_SIMPLE_MANAGER_ADDRESS[activeChain.id]], - [encodedData as Hex], - [etherToSend.toBigNumber().div(1e13).toNumber()], + [encodedData], + [Number(etherToSend / 10_000_000_000_000n)], ], - value: etherToSend.toBigInt(), + value: etherToSend, query: { enabled: Boolean(userAddress) && borrowAmount.isGtZero() && !isUnhealthy && !notEnoughSupply }, chainId: activeChain.id, }); @@ -175,15 +171,13 @@ function BorrowButton(props: BorrowButtonProps) { export type BorrowModalProps = { borrower: BorrowerNftBorrower; market: LendingPair; - accountEtherBalance?: GN; isOpen: boolean; setIsOpen: (open: boolean) => void; setPendingTxn: (pendingTxn: WriteContractReturnType | null) => void; }; export default function BorrowModal(props: BorrowModalProps) { - const { borrower, market, accountEtherBalance, isOpen, setIsOpen, setPendingTxn } = props; - const activeChain = useChain(); + const { borrower, market, isOpen, setIsOpen, setPendingTxn } = props; const [borrowAmountStr, setBorrowAmountStr] = useState(''); const [borrowToken, setBorrowToken] = useState(borrower.token0); @@ -199,19 +193,6 @@ export default function BorrowModal(props: BorrowModalProps) { setShouldWithdrawToWallet(true); }, [isOpen, borrower.token0]); - const { data: anteData } = useReadContract({ - abi: factoryAbi, - address: ALOE_II_FACTORY_ADDRESS[activeChain.id], - functionName: 'getParameters', - args: [borrower.uniswapPool as Address], - chainId: activeChain.id, - }); - - const ante = useMemo(() => { - if (!anteData) return GN.zero(18); - return GN.fromBigInt(anteData[0], 18); - }, [anteData]); - const tokenOptions = [borrower.token0, borrower.token1]; const isToken0 = borrowToken.equals(borrower.token0); @@ -221,10 +202,7 @@ export default function BorrowModal(props: BorrowModalProps) { const newLiability = existingLiability.add(borrowAmount); - let etherToSend = GN.zero(18, 10); - if (accountEtherBalance !== undefined && accountEtherBalance.lt(ante)) { - etherToSend = ante.sub(accountEtherBalance); - } + let etherToSend = market.amountEthRequiredBeforeBorrowing(borrower.ethBalance!.toBigInt()); if (!userAddress || !isOpen) { return null; @@ -342,10 +320,10 @@ export default function BorrowModal(props: BorrowModalProps) { {newLiability.toString(GNFormat.DECIMAL)}. The borrowed funds will be{' '} {shouldWithdrawToWallet ? 'withdrawn to your wallet for immediate use.' : 'left in the NFT for future use.'} - {etherToSend.isGtZero() && ( + {etherToSend > 0n && ( - You will need to provide an additional {etherToSend.toString(GNFormat.DECIMAL)} ETH to cover the gas fees - in the event that you are liquidated. + You will need to provide an additional {formatEther(etherToSend)} ETH to + cover the gas fees in the event that you are liquidated. )}
@@ -370,7 +348,7 @@ export default function BorrowModal(props: BorrowModalProps) { setPendingTxn={setPendingTxn} /> - By using our service, you agree to our{' '} + By using this interface, you agree to our{' '} Terms of Service {' '} diff --git a/earn/src/components/advanced/modal/ClearWarningModal.tsx b/earn/src/components/advanced/modal/ClearWarningModal.tsx index 3e640a2ea..95f46b848 100644 --- a/earn/src/components/advanced/modal/ClearWarningModal.tsx +++ b/earn/src/components/advanced/modal/ClearWarningModal.tsx @@ -4,12 +4,12 @@ import { FilledStylizedButton } from 'shared/lib/components/common/Buttons'; import Modal from 'shared/lib/components/common/Modal'; import { Text } from 'shared/lib/components/common/Typography'; import { Q32, TERMS_OF_SERVICE_URL } from 'shared/lib/data/constants/Values'; -import { GN, GNFormat } from 'shared/lib/data/GoodNumber'; import { LendingPair } from 'shared/lib/data/LendingPair'; import useChain from 'shared/lib/hooks/UseChain'; +import { formatEther } from 'viem'; import { useSimulateContract, useWriteContract } from 'wagmi'; -import { BorrowerNftBorrower } from '../../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../../hooks/useDeprecatedMarginAccountShim'; const SECONDARY_COLOR = '#CCDFED'; const TERTIARY_COLOR = '#4b6980'; @@ -37,7 +37,7 @@ function getConfirmButton(state: ConfirmButtonState): { text: string; enabled: b type ClearWarningButtonProps = { borrower: BorrowerNftBorrower; - etherToSend: GN; + etherToSend: bigint; setIsOpen: (open: boolean) => void; setPendingTxn: (result: WriteContractReturnType | null) => void; }; @@ -55,7 +55,7 @@ function ClearWarningButton(props: ClearWarningButtonProps) { abi: borrowerAbi, functionName: 'clear', args: [Q32], - value: etherToSend.toBigInt(), + value: etherToSend, chainId: activeChain.id, }); const { writeContractAsync: clearWarning, isPending: isAskingUserToClearWarning } = useWriteContract(); @@ -93,22 +93,17 @@ function ClearWarningButton(props: ClearWarningButtonProps) { export type ClearWarningModalProps = { borrower: BorrowerNftBorrower; market: LendingPair; - accountEtherBalance?: GN; isOpen: boolean; setIsOpen: (open: boolean) => void; setPendingTxn: (pendingTxn: WriteContractReturnType | null) => void; }; export default function ClearWarningModal(props: ClearWarningModalProps) { - const { borrower, market, accountEtherBalance, isOpen, setIsOpen, setPendingTxn } = props; + const { borrower, market, isOpen, setIsOpen, setPendingTxn } = props; if (!isOpen) return null; - const ante = market.factoryData.ante; - let etherToSend = GN.zero(18, 10); - if (accountEtherBalance !== undefined && accountEtherBalance.lt(ante)) { - etherToSend = ante.sub(accountEtherBalance); - } + let etherToSend = market.amountEthRequiredBeforeBorrowing(borrower.ethBalance!.toBigInt()); return ( @@ -119,7 +114,7 @@ export default function ClearWarningModal(props: ClearWarningModalProps) { Your account is healthy, and you're ending the liquidation auction by replenishing the ante with{' '} - {etherToSend.toString(GNFormat.DECIMAL)} ETH. This is necessary to cover gas fees in the event that you are + {formatEther(etherToSend)} ETH. This is necessary to cover gas fees in the event that you are liquidated again.
@@ -131,7 +126,7 @@ export default function ClearWarningModal(props: ClearWarningModalProps) { setPendingTxn={setPendingTxn} /> - By using our service, you agree to our{' '} + By using this interface, you agree to our{' '} Terms of Service {' '} diff --git a/earn/src/components/advanced/modal/ImportUniswapNFTModal.tsx b/earn/src/components/advanced/modal/ImportUniswapNFTModal.tsx index a851b01bf..fee3f699f 100644 --- a/earn/src/components/advanced/modal/ImportUniswapNFTModal.tsx +++ b/earn/src/components/advanced/modal/ImportUniswapNFTModal.tsx @@ -3,7 +3,7 @@ import Modal from 'shared/lib/components/common/Modal'; import { UniswapNFTPosition, UniswapPosition } from 'shared/lib/data/Uniswap'; import { AddUniswapNFTAsCollateralTab } from './tab/AddUniswapNFTAsCollateralTab'; -import { BorrowerNftBorrower } from '../../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../../hooks/useDeprecatedMarginAccountShim'; export type ImportUniswapNFTModalProps = { isOpen: boolean; diff --git a/earn/src/components/advanced/modal/NewSmartWalletModal.tsx b/earn/src/components/advanced/modal/NewSmartWalletModal.tsx index 748cfa10a..fbece7732 100644 --- a/earn/src/components/advanced/modal/NewSmartWalletModal.tsx +++ b/earn/src/components/advanced/modal/NewSmartWalletModal.tsx @@ -9,6 +9,7 @@ import Pagination from 'shared/lib/components/common/Pagination'; import { Text } from 'shared/lib/components/common/Typography'; import { ALOE_II_BORROWER_NFT_ADDRESS } from 'shared/lib/data/constants/ChainSpecific'; import { TERMS_OF_SERVICE_URL } from 'shared/lib/data/constants/Values'; +import { Token } from 'shared/lib/data/Token'; import useChain from 'shared/lib/hooks/UseChain'; import { generateBytes12Salt } from 'shared/lib/util/Salt'; import styled from 'styled-components'; @@ -16,7 +17,6 @@ import { Address } from 'viem'; import { useAccount, useSimulateContract, useWriteContract } from 'wagmi'; import { ReactComponent as SearchIcon } from '../../../assets/svg/search.svg'; -import { UniswapPoolInfo } from '../../../data/MarginAccount'; import SmartWalletButton from '../SmartWalletButton'; const ITEMS_PER_PAGE = 5; @@ -30,6 +30,12 @@ const SmartWalletOptionsPage = styled.div` min-height: 212px; `; +type UniswapPoolInfo = { + token0: Token; + token1: Token; + fee: number; +}; + type CreateSmartWalletButtonProps = { poolAddress: string; uniswapPoolInfo: UniswapPoolInfo; @@ -210,7 +216,7 @@ export default function NewSmartWalletModal(props: NewSmartWalletModalProps) { setPendingTxn={setPendingTxn} /> - By using our service, you agree to our{' '} + By using this interface, you agree to our{' '} Terms of Service {' '} diff --git a/earn/src/components/advanced/modal/RemoveCollateralModal.tsx b/earn/src/components/advanced/modal/RemoveCollateralModal.tsx index 7129e9bce..10f8634ea 100644 --- a/earn/src/components/advanced/modal/RemoveCollateralModal.tsx +++ b/earn/src/components/advanced/modal/RemoveCollateralModal.tsx @@ -2,7 +2,6 @@ import { useEffect, useMemo, useState } from 'react'; import { type WriteContractReturnType } from '@wagmi/core'; import Big from 'big.js'; -import { BigNumber, ethers } from 'ethers'; import { borrowerAbi } from 'shared/lib/abis/Borrower'; import { borrowerNftAbi } from 'shared/lib/abis/BorrowerNft'; import { FilledStylizedButton } from 'shared/lib/components/common/Buttons'; @@ -20,10 +19,10 @@ import { GN, GNFormat } from 'shared/lib/data/GoodNumber'; import { Token } from 'shared/lib/data/Token'; import useChain from 'shared/lib/hooks/UseChain'; import { formatNumberInput, truncateDecimals } from 'shared/lib/util/Numbers'; -import { Address, Hex } from 'viem'; +import { Address, encodeFunctionData, formatUnits, Hex } from 'viem'; import { useAccount, useSimulateContract, useWriteContract } from 'wagmi'; -import { BorrowerNftBorrower } from '../../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../../hooks/useDeprecatedMarginAccountShim'; import HealthBar from '../../common/HealthBar'; import TokenAmountSelectInput from '../../portfolio/TokenAmountSelectInput'; @@ -75,12 +74,11 @@ function RemoveCollateralButton(props: RemoveCollateralButtonProps) { const amount1 = isToken0Collateral ? GN.zero(collateralToken.decimals) : collateralAmount; const encodedData = useMemo(() => { - const borrowerInterface = new ethers.utils.Interface(borrowerAbi); - return borrowerInterface.encodeFunctionData('transfer', [ - amount0.toBigNumber(), - amount1.toBigNumber(), - userAddress, - ]); + return encodeFunctionData({ + abi: borrowerAbi, + functionName: 'transfer', + args: [amount0.toBigInt(), amount1.toBigInt(), userAddress], + }) }, [amount0, amount1, userAddress]); const { data: removeCollateralConfig } = useSimulateContract({ @@ -176,8 +174,8 @@ export default function RemoveCollateralModal(props: RemoveCollateralModalProps) )[isToken0 ? 0 : 1]; const max = Math.min(existingCollateral.toNumber(), maxWithdrawBasedOnHealth); // Mitigate the case when the number is represented in scientific notation - const bigMax = BigNumber.from(new Big(max).mul(10 ** collateralToken.decimals).toFixed(0)); - const maxString = ethers.utils.formatUnits(bigMax, collateralToken.decimals); + const bigMax = BigInt(new Big(max).mul(10 ** collateralToken.decimals).toFixed(0)); + const maxString = formatUnits(bigMax, collateralToken.decimals);; const newAssets = new Assets( isToken0 ? newCollateralAmount : borrower.assets.amount0, @@ -263,7 +261,7 @@ export default function RemoveCollateralModal(props: RemoveCollateralModalProps) setPendingTxn={setPendingTxn} /> - By using our service, you agree to our{' '} + By using this interface, you agree to our{' '} Terms of Service {' '} diff --git a/earn/src/components/advanced/modal/RepayModal.tsx b/earn/src/components/advanced/modal/RepayModal.tsx index 2e2b212a8..0e1dfa4f3 100644 --- a/earn/src/components/advanced/modal/RepayModal.tsx +++ b/earn/src/components/advanced/modal/RepayModal.tsx @@ -1,7 +1,6 @@ import { useState, useEffect } from 'react'; import { type WriteContractReturnType } from '@wagmi/core'; -import { ethers } from 'ethers'; import { borrowerAbi } from 'shared/lib/abis/Borrower'; import { borrowerNftAbi } from 'shared/lib/abis/BorrowerNft'; import { routerAbi } from 'shared/lib/abis/Router'; @@ -23,11 +22,10 @@ import useChain from 'shared/lib/hooks/UseChain'; import { usePermit2, Permit2State } from 'shared/lib/hooks/UsePermit2'; import { formatNumberInput, truncateDecimals } from 'shared/lib/util/Numbers'; import styled from 'styled-components'; -import { Address, Chain, Hex } from 'viem'; +import { Address, Chain, encodeFunctionData, Hex } from 'viem'; import { useAccount, useBalance, useSimulateContract, useWriteContract } from 'wagmi'; -import { BorrowerNftBorrower } from '../../../data/BorrowerNft'; -import { MarginAccount } from '../../../data/MarginAccount'; +import { BorrowerNftBorrower, MarginAccount } from '../../../hooks/useDeprecatedMarginAccountShim'; import HealthBar from '../../common/HealthBar'; import TokenAmountSelectInput from '../../portfolio/TokenAmountSelectInput'; @@ -233,11 +231,11 @@ function RepayButton(props: RepayButtonProps) { const amount0Big = isToken0 ? repayAmount : GN.zero(repayToken.decimals); const amount1Big = isToken0 ? GN.zero(repayToken.decimals) : repayAmount; - const borrowerInterface = new ethers.utils.Interface(borrowerAbi); - const encodedData = borrowerInterface.encodeFunctionData('repay', [ - amount0Big.toBigNumber(), - amount1Big.toBigNumber(), - ]); + const encodedData = encodeFunctionData({ + abi: borrowerAbi, + functionName: 'repay', + args: [amount0Big.toBigInt(), amount1Big.toBigInt()], + }); const repayTokenBalance = borrower.assets[isToken0 ? 'amount0' : 'amount1']; @@ -478,7 +476,7 @@ export default function RepayModal(props: RepayModalProps) { /> )} - By using our service, you agree to our{' '} + By using this interface, you agree to our{' '} Terms of Service {' '} diff --git a/earn/src/components/advanced/modal/WithdrawAnteModal.tsx b/earn/src/components/advanced/modal/WithdrawAnteModal.tsx index f4bad42c9..5d8f3d5da 100644 --- a/earn/src/components/advanced/modal/WithdrawAnteModal.tsx +++ b/earn/src/components/advanced/modal/WithdrawAnteModal.tsx @@ -1,5 +1,4 @@ import { type WriteContractReturnType } from '@wagmi/core'; -import { ethers } from 'ethers'; import { borrowerAbi } from 'shared/lib/abis/Borrower'; import { borrowerNftAbi } from 'shared/lib/abis/BorrowerNft'; import { FilledStylizedButton } from 'shared/lib/components/common/Buttons'; @@ -10,12 +9,12 @@ import { ALOE_II_BORROWER_NFT_SIMPLE_MANAGER_ADDRESS, } from 'shared/lib/data/constants/ChainSpecific'; import { TERMS_OF_SERVICE_URL } from 'shared/lib/data/constants/Values'; -import { GN, GNFormat } from 'shared/lib/data/GoodNumber'; +import { GNFormat } from 'shared/lib/data/GoodNumber'; import useChain from 'shared/lib/hooks/UseChain'; -import { Address, Chain, Hex } from 'viem'; -import { useAccount, useBalance, useSimulateContract, useWriteContract } from 'wagmi'; +import { Address, Chain, encodeFunctionData } from 'viem'; +import { useAccount, useSimulateContract, useWriteContract } from 'wagmi'; -import { BorrowerNftBorrower } from '../../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../../hooks/useDeprecatedMarginAccountShim'; const SECONDARY_COLOR = '#CCDFED'; const TERTIARY_COLOR = '#4b6980'; @@ -53,13 +52,11 @@ type WithdrawAnteButtonProps = { function WithdrawAnteButton(props: WithdrawAnteButtonProps) { const { activeChain, borrower, userAddress, setIsOpen, setPendingTxn } = props; - const { data: borrowerBalance } = useBalance({ - address: borrower.address, - chainId: activeChain.id, - }); - - const borrowerInterface = new ethers.utils.Interface(borrowerAbi); - const encodedData = borrowerInterface.encodeFunctionData('transferEth', [borrowerBalance?.value ?? 0, userAddress]); + const encodedData = encodeFunctionData({ + abi: borrowerAbi, + functionName: 'transferEth', + args: [borrower.ethBalance?.toBigInt() ?? 0n, userAddress], + }) const { data: withdrawAnteConfig, @@ -73,7 +70,7 @@ function WithdrawAnteButton(props: WithdrawAnteButtonProps) { userAddress, [borrower.index], [ALOE_II_BORROWER_NFT_SIMPLE_MANAGER_ADDRESS[activeChain.id]], - [encodedData as Hex], + [encodedData], [0], ], query: { enabled: Boolean(userAddress) }, @@ -116,18 +113,17 @@ function WithdrawAnteButton(props: WithdrawAnteButtonProps) { export type WithdrawAnteModalProps = { borrower: BorrowerNftBorrower; - accountEthBalance?: GN; isOpen: boolean; setIsOpen: (open: boolean) => void; setPendingTxn: (pendingTxn: WriteContractReturnType | null) => void; }; export default function WithdrawAnteModal(props: WithdrawAnteModalProps) { - const { borrower, accountEthBalance, isOpen, setIsOpen, setPendingTxn } = props; + const { borrower, isOpen, setIsOpen, setPendingTxn } = props; const activeChain = useChain(); const { address: userAddress } = useAccount(); - if (!userAddress || !accountEthBalance) { + if (!userAddress || !borrower.ethBalance) { return null; } @@ -139,7 +135,7 @@ export default function WithdrawAnteModal(props: WithdrawAnteModalProps) { Summary - You're about to withdraw your {accountEthBalance.toString(GNFormat.DECIMAL)} ETH Ante from this smart + You're about to withdraw your {borrower.ethBalance.toString(GNFormat.DECIMAL)} ETH Ante from this smart wallet. @@ -152,7 +148,7 @@ export default function WithdrawAnteModal(props: WithdrawAnteModalProps) { setPendingTxn={setPendingTxn} /> - By using our service, you agree to our{' '} + By using this interface, you agree to our{' '} Terms of Service {' '} diff --git a/earn/src/components/advanced/modal/WithdrawUniswapNFTModal.tsx b/earn/src/components/advanced/modal/WithdrawUniswapNFTModal.tsx index 69b955467..df1c904e3 100644 --- a/earn/src/components/advanced/modal/WithdrawUniswapNFTModal.tsx +++ b/earn/src/components/advanced/modal/WithdrawUniswapNFTModal.tsx @@ -26,7 +26,7 @@ import styled from 'styled-components'; import { Address, Hex } from 'viem'; import { useAccount, useSimulateContract, useWriteContract } from 'wagmi'; -import { BorrowerNftBorrower } from '../../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../../hooks/useDeprecatedMarginAccountShim'; import TokenPairIcons from '../../common/TokenPairIcons'; import { InRangeBadge, OutOfRangeBadge } from '../../common/UniswapPositionCard'; @@ -282,7 +282,7 @@ export function WithdrawUniswapNFTModal(props: WithdrawUniswapNFTModalProps) { setPendingTxn={setPendingTxn} /> - By using our service, you agree to our{' '} + By using this interface, you agree to our{' '} Terms of Service {' '} diff --git a/earn/src/components/advanced/modal/tab/AddCollateralTab.tsx b/earn/src/components/advanced/modal/tab/AddCollateralTab.tsx index 0674ee84c..06e4c512e 100644 --- a/earn/src/components/advanced/modal/tab/AddCollateralTab.tsx +++ b/earn/src/components/advanced/modal/tab/AddCollateralTab.tsx @@ -14,7 +14,7 @@ import useChain from 'shared/lib/hooks/UseChain'; import { formatNumberInput, truncateDecimals } from 'shared/lib/util/Numbers'; import { useAccount, useBalance, useSimulateContract, useWriteContract } from 'wagmi'; -import { MarginAccount } from '../../../../data/MarginAccount'; +import { MarginAccount } from '../../../../hooks/useDeprecatedMarginAccountShim'; import HealthBar from '../../../common/HealthBar'; import TokenAmountSelectInput from '../../../portfolio/TokenAmountSelectInput'; @@ -235,7 +235,7 @@ export function AddCollateralTab(props: AddCollateralTabProps) { setPendingTxn={setPendingTxn} /> - By using our service, you agree to our{' '} + By using this interface, you agree to our{' '} Terms of Service {' '} diff --git a/earn/src/components/advanced/modal/tab/AddUniswapNFTAsCollateralTab.tsx b/earn/src/components/advanced/modal/tab/AddUniswapNFTAsCollateralTab.tsx index 4eb81fea5..aba1995c9 100644 --- a/earn/src/components/advanced/modal/tab/AddUniswapNFTAsCollateralTab.tsx +++ b/earn/src/components/advanced/modal/tab/AddUniswapNFTAsCollateralTab.tsx @@ -21,7 +21,7 @@ import styled from 'styled-components'; import { Address, erc721Abi, Hex } from 'viem'; import { useAccount, usePublicClient, useReadContract, useSimulateContract, useWriteContract } from 'wagmi'; -import { BorrowerNftBorrower } from '../../../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../../../hooks/useDeprecatedMarginAccountShim'; import TokenPairIcons from '../../../common/TokenPairIcons'; const SECONDARY_COLOR = '#CCDFED'; @@ -361,7 +361,7 @@ export function AddUniswapNFTAsCollateralTab(props: AddUniswapNFTAsCollateralTab setPendingTxn={setPendingTxn} /> - By using our service, you agree to our{' '} + By using this interface, you agree to our{' '} Terms of Service {' '} diff --git a/earn/src/components/boost/BurnBoostModal.tsx b/earn/src/components/boost/BurnBoostModal.tsx index 8b888eee4..2818a44f1 100644 --- a/earn/src/components/boost/BurnBoostModal.tsx +++ b/earn/src/components/boost/BurnBoostModal.tsx @@ -14,8 +14,8 @@ import useChain from 'shared/lib/hooks/UseChain'; import { Hex } from 'viem'; import { useAccount, useSimulateContract, useWriteContract } from 'wagmi'; -import { MarginAccount } from '../../data/MarginAccount'; import { BoostCardInfo } from '../../data/Uniboost'; +import { MarginAccount } from '../../hooks/useDeprecatedMarginAccountShim'; import MaxSlippageInput from '../common/MaxSlippageInput'; const SECONDARY_COLOR = '#CCDFED'; diff --git a/earn/src/components/boost/ImportBoostWidget.tsx b/earn/src/components/boost/ImportBoostWidget.tsx index da21f9292..28128d1a2 100644 --- a/earn/src/components/boost/ImportBoostWidget.tsx +++ b/earn/src/components/boost/ImportBoostWidget.tsx @@ -15,7 +15,7 @@ import { ALOE_II_BORROWER_NFT_ADDRESS, UNISWAP_NONFUNGIBLE_POSITION_MANAGER_ADDRESS, } from 'shared/lib/data/constants/ChainSpecific'; -import { TERMS_OF_SERVICE_URL } from 'shared/lib/data/constants/Values'; +import { API_PRICE_RELAY_LATEST_URL, TERMS_OF_SERVICE_URL } from 'shared/lib/data/constants/Values'; import { GN, GNFormat } from 'shared/lib/data/GoodNumber'; import { Token } from 'shared/lib/data/Token'; import { getTokenBySymbol } from 'shared/lib/data/TokenData'; @@ -32,7 +32,7 @@ import { getTheGraphClient, Uniswap24HourPoolDataQuery } from 'shared/lib/util/G import { formatUSD } from 'shared/lib/util/Numbers'; import { generateBytes12Salt } from 'shared/lib/util/Salt'; import styled from 'styled-components'; -import { erc721Abi, Hex } from 'viem'; +import { encodeFunctionData, erc721Abi, Hex } from 'viem'; import { useAccount, useBalance, @@ -43,7 +43,6 @@ import { } from 'wagmi'; import SlippageWidget from './SlippageWidget'; -import { API_PRICE_RELAY_LATEST_URL } from '../../data/constants/Values'; import { BoostCardInfo } from '../../data/Uniboost'; import { BOOST_MAX, BOOST_MIN } from '../../pages/boost/ImportBoostPage'; @@ -186,7 +185,7 @@ export default function ImportBoostWidget(props: ImportBoostWidgetProps) { const { address: userAddress } = useAccount(); const { lendingPairs } = useLendingPairs(activeChain.id); - const lendingPair = useLendingPair(lendingPairs, cardInfo.token0.address, cardInfo.token1.address); + const lendingPair = useLendingPair(lendingPairs, cardInfo.uniswapPool); // Generate labels for input range (slider) const labels: string[] = []; @@ -384,26 +383,32 @@ export default function ImportBoostWidget(props: ImportBoostWidgetProps) { return lendingPair.amountEthRequiredBeforeBorrowing(borrowerBalance?.value ?? 0n); }, [lendingPair, borrowerBalance]); - // Prepare for actual import/mint transaction - const borrowerNft = useMemo(() => new ethers.utils.Interface(borrowerNftAbi), []); // First, we `mint` so that they have a `Borrower` to put stuff in const encodedMint = useMemo(() => { if (!userAddress) return '0x'; const to = userAddress; const pools = [cardInfo.uniswapPool]; const salts = [generateBytes12Salt()]; - return borrowerNft.encodeFunctionData('mint', [to, pools, salts]) as Hex; - }, [borrowerNft, userAddress, cardInfo]); + return encodeFunctionData({ + abi: borrowerNftAbi, + functionName: 'mint', + args: [to, pools, salts], + }); + }, [userAddress, cardInfo]); // Then we `modify`, calling the BoostManager to import the Uniswap position const encodedModify = useMemo(() => { - if (!userAddress || nextNftPtrIdx === undefined) return '0x'; + if (!userAddress || nextNftPtrIdx === undefined || modifyData === undefined) return '0x'; const owner = userAddress; - const indices = [availableNft !== undefined ? availableNft.ptrIdx : nextNftPtrIdx]; + const indices = [availableNft !== undefined ? availableNft.ptrIdx : Number(nextNftPtrIdx)]; const managers = [ALOE_II_BOOST_MANAGER_ADDRESS[activeChain.id]]; const datas = [modifyData]; - const antes = [ethToSend / 10_000_000_000_000n]; - return borrowerNft.encodeFunctionData('modify', [owner, indices, managers, datas, antes]) as Hex; - }, [borrowerNft, userAddress, activeChain, availableNft, nextNftPtrIdx, modifyData, ethToSend]); + const antes = [Number(ethToSend / 10_000_000_000_000n)]; + return encodeFunctionData({ + abi: borrowerNftAbi, + functionName: 'modify', + args: [owner, indices, managers, datas, antes], + }); + }, [userAddress, activeChain, availableNft, nextNftPtrIdx, modifyData, ethToSend]); const { data: configMint, @@ -575,7 +580,7 @@ export default function ImportBoostWidget(props: ImportBoostWidgetProps) { {buttonState.label} - By using our service, you agree to our{' '} + By using this interface, you agree to our{' '} Terms of Service {' '} diff --git a/earn/src/components/common/MaxSlippageInput.tsx b/earn/src/components/common/MaxSlippageInput.tsx index e6ef9bcf3..ac2fdfb1a 100644 --- a/earn/src/components/common/MaxSlippageInput.tsx +++ b/earn/src/components/common/MaxSlippageInput.tsx @@ -1,14 +1,13 @@ -import React, { Fragment, useEffect, useState } from 'react'; +import { Fragment, useEffect, useState } from 'react'; import { Tab } from '@headlessui/react'; import Tooltip from 'shared/lib/components/common/Tooltip'; import { Text } from 'shared/lib/components/common/Typography'; +import { RESPONSIVE_BREAKPOINT_XS } from 'shared/lib/data/constants/Breakpoints'; import { GREY_400 } from 'shared/lib/data/constants/Colors'; import { formatNumberInput } from 'shared/lib/util/Numbers'; import styled from 'styled-components'; -import { RESPONSIVE_BREAKPOINT_XS } from '../../data/constants/Breakpoints'; - const SLIPPAGE_TOOLTIP_TEXT = `Slippage tolerance is the maximum price difference you are willing to accept between the estimated price and the execution price.`; diff --git a/earn/src/components/common/PendingTxnModal.tsx b/earn/src/components/common/PendingTxnModal.tsx index 27aebb618..67748f707 100644 --- a/earn/src/components/common/PendingTxnModal.tsx +++ b/earn/src/components/common/PendingTxnModal.tsx @@ -2,7 +2,7 @@ import { FilledGradientButton } from 'shared/lib/components/common/Buttons'; import Modal, { LoadingModal, MESSAGE_TEXT_COLOR } from 'shared/lib/components/common/Modal'; import { Text } from 'shared/lib/components/common/Typography'; import useChain from 'shared/lib/hooks/UseChain'; -import { getEtherscanUrlForChain } from 'shared/lib/util/Chains'; +import { getBlockExplorerUrl } from 'shared/lib/util/Chains'; import { Hash } from 'viem'; import { ReactComponent as ErrorIcon } from '../../assets/svg/error.svg'; @@ -25,7 +25,7 @@ function EtherscanLink(props: { txnHash: string }) { return ( { const borrowerAddress = availableNft?.address ?? predictedAddress; if (!userAddress || !borrowerAddress || !permit2Result.signature) return null; - const permit2 = new ethers.utils.Interface(permit2Abi); - return permit2.encodeFunctionData( - 'permitTransferFrom(((address,uint256),uint256,uint256),(address,uint256),address,bytes)', - [ + + return encodeFunctionData({ + abi: permit2Abi, + functionName: 'permitTransferFrom', + args: [ { permitted: { token: selectedCollateral.address, - amount: permit2Result.amount.toBigNumber(), + amount: permit2Result.amount.toBigInt(), }, - nonce: BigNumber.from(permit2Result.nonce ?? '0'), - deadline: BigNumber.from(permit2Result.deadline), + nonce: BigInt(permit2Result.nonce ?? '0'), + deadline: BigInt(permit2Result.deadline), }, { to: borrowerAddress, - requestedAmount: permit2Result.amount.toBigNumber(), + requestedAmount: permit2Result.amount.toBigInt(), }, userAddress, permit2Result.signature, - ] - ); + ], + }) }, [permit2Result, availableNft, predictedAddress, selectedCollateral.address, userAddress]); - // Prepare for actual import/mint transaction - const borrowerNft = useMemo(() => new ethers.utils.Interface(borrowerNftAbi), []); // First, we `mint` so that they have a `Borrower` to put stuff in const encodedMint = useMemo(() => { if (!userAddress || selectedLendingPair?.uniswapPool === undefined) return null; const to = userAddress; - const pools = [selectedLendingPair.uniswapPool ?? '0x']; + const pools = [selectedLendingPair.uniswapPool]; const salts = [generatedSalt]; - return borrowerNft.encodeFunctionData('mint', [to, pools, salts]) as Hex; - }, [userAddress, selectedLendingPair?.uniswapPool, generatedSalt, borrowerNft]); + + return encodeFunctionData({ + abi: borrowerNftAbi, + functionName: 'mint', + args: [to, pools, salts], + }); + }, [userAddress, selectedLendingPair?.uniswapPool, generatedSalt]); const encodedBorrowCall = useMemo(() => { if (!userAddress || !selectedLendingPair || !selectedBorrow) return null; - const borrower = new ethers.utils.Interface(borrowerAbi); const amount0 = selectedLendingPair.token0.address === selectedBorrow.address ? borrowAmount : GN.zero(selectedBorrow.decimals); const amount1 = selectedLendingPair.token1.address === selectedBorrow.address ? borrowAmount : GN.zero(selectedBorrow.decimals); - return borrower.encodeFunctionData('borrow', [amount0.toBigNumber(), amount1.toBigNumber(), userAddress]); + return encodeFunctionData({ + abi: borrowerAbi, + functionName: 'borrow', + args: [amount0.toBigInt(), amount1.toBigInt(), userAddress], + }); }, [borrowAmount, selectedBorrow, selectedLendingPair, userAddress]); const encodedModify = useMemo(() => { - const index = Boolean(availableNft) ? availableNft!.index : nextNftPtrIdx; + const index = Boolean(availableNft) + ? availableNft!.index + : nextNftPtrIdx !== undefined + ? Number(nextNftPtrIdx) + : undefined; if (!userAddress || index === undefined || ante === undefined || !encodedPermit2 || !encodedBorrowCall) return null; const owner = userAddress; const indices = [index]; const managers = [ALOE_II_PERMIT2_MANAGER_ADDRESS[activeChain.id]]; - const datas = [encodedPermit2.concat(encodedBorrowCall.slice(2))]; - const antes = [ante.toBigNumber().div(1e13)]; - return borrowerNft.encodeFunctionData('modify', [owner, indices, managers, datas, antes]) as Hex; - }, [availableNft, nextNftPtrIdx, userAddress, ante, encodedPermit2, encodedBorrowCall, activeChain.id, borrowerNft]); + const datas = [encodedPermit2.concat(encodedBorrowCall.slice(2)) as Hex]; + const antes = [ante.toBigNumber().div(1e13).toNumber()]; + + return encodeFunctionData({ + abi: borrowerNftAbi, + functionName: 'modify', + args: [owner, indices, managers, datas, antes], + }); + }, [availableNft, nextNftPtrIdx, userAddress, ante, encodedPermit2, encodedBorrowCall, activeChain.id]); const { data: configMulticallOps, diff --git a/earn/src/components/markets/modal/BorrowModalUniswap.tsx b/earn/src/components/markets/modal/BorrowModalUniswap.tsx index 84afe0638..8899ff0b1 100644 --- a/earn/src/components/markets/modal/BorrowModalUniswap.tsx +++ b/earn/src/components/markets/modal/BorrowModalUniswap.tsx @@ -2,7 +2,7 @@ import { useMemo, useState } from 'react'; import { type WriteContractReturnType } from '@wagmi/core'; import Big from 'big.js'; -import { BigNumber, ethers } from 'ethers'; +import { ethers } from 'ethers'; import { borrowerAbi } from 'shared/lib/abis/Borrower'; import { borrowerNftAbi } from 'shared/lib/abis/BorrowerNft'; import { volatilityOracleAbi } from 'shared/lib/abis/VolatilityOracle'; @@ -29,7 +29,7 @@ import { UniswapNFTPosition, zip } from 'shared/lib/data/Uniswap'; import useChain from 'shared/lib/hooks/UseChain'; import { formatNumberInput, formatTokenAmount } from 'shared/lib/util/Numbers'; import { generateBytes12Salt } from 'shared/lib/util/Salt'; -import { erc721Abi, Hex } from 'viem'; +import { encodeFunctionData, erc721Abi, Hex } from 'viem'; import { useAccount, useBalance, usePublicClient, useReadContract, useSimulateContract, useWriteContract } from 'wagmi'; const MAX_BORROW_PERCENTAGE = 0.8; @@ -191,16 +191,19 @@ export default function BorrowModalUniswap(props: BorrowModalProps) { const generatedSalt = useMemo(() => generateBytes12Salt(), []); - // Prepare for actual import/mint transaction - const borrowerNft = useMemo(() => new ethers.utils.Interface(borrowerNftAbi), []); // First, we `mint` so that they have a `Borrower` to put stuff in const encodedMint = useMemo(() => { if (!userAddress) return null; const to = userAddress; const pools = [selectedLendingPair.uniswapPool]; const salts = [generatedSalt]; - return borrowerNft.encodeFunctionData('mint', [to, pools, salts]) as Hex; - }, [userAddress, selectedLendingPair, generatedSalt, borrowerNft]); + + return encodeFunctionData({ + abi: borrowerNftAbi, + functionName: 'mint', + args: [to, pools, salts], + }); + }, [userAddress, selectedLendingPair, generatedSalt]); // Then we use the UniswapNFTManager to import the Uniswap NFT as collateral const encodedImportCall = useMemo(() => { @@ -219,7 +222,6 @@ export default function BorrowModalUniswap(props: BorrowModalProps) { // Finally, we borrow the requested tokens const encodedBorrowCall = useMemo(() => { if (!userAddress) return null; - const borrower = new ethers.utils.Interface(borrowerAbi); const amount0 = selectedBorrow.equals(selectedLendingPair.token0) ? borrowAmount : GN.zero(selectedLendingPair.token0.decimals); @@ -227,21 +229,30 @@ export default function BorrowModalUniswap(props: BorrowModalProps) { ? borrowAmount : GN.zero(selectedLendingPair.token1.decimals); - return borrower.encodeFunctionData('borrow', [amount0.toBigNumber(), amount1.toBigNumber(), userAddress]); + return encodeFunctionData({ + abi: borrowerAbi, + functionName: 'borrow', + args: [amount0.toBigInt(), amount1.toBigInt(), userAddress], + }) }, [borrowAmount, selectedBorrow, selectedLendingPair, userAddress]); const encodedModify = useMemo(() => { if (!userAddress || nextNftPtrIdx === undefined || !encodedBorrowCall) return null; const owner = userAddress; - const indices = [nextNftPtrIdx, nextNftPtrIdx]; + const indices = [Number(nextNftPtrIdx), Number(nextNftPtrIdx)]; const managers = [ ALOE_II_UNISWAP_NFT_MANAGER_ADDRESS[activeChain.id], ALOE_II_BORROWER_NFT_SIMPLE_MANAGER_ADDRESS[activeChain.id], ]; const datas = [encodedImportCall, encodedBorrowCall]; - const antes = [ante.toBigNumber().div(1e13), BigNumber.from(0)]; - return borrowerNft.encodeFunctionData('modify', [owner, indices, managers, datas, antes]) as Hex; - }, [userAddress, nextNftPtrIdx, ante, activeChain.id, encodedImportCall, encodedBorrowCall, borrowerNft]); + const antes = [ante.toBigNumber().div(1e13).toNumber(), 0]; + + return encodeFunctionData({ + abi: borrowerNftAbi, + functionName: 'modify', + args: [owner, indices, managers, datas, antes], + }); + }, [userAddress, nextNftPtrIdx, ante, activeChain.id, encodedImportCall, encodedBorrowCall]); const { data: multicallConfig } = useSimulateContract({ address: ALOE_II_BORROWER_NFT_ADDRESS[activeChain.id], diff --git a/earn/src/components/markets/modal/UpdateBorrowerModal.tsx b/earn/src/components/markets/modal/UpdateBorrowerModal.tsx index 9577ebeae..6626585dd 100644 --- a/earn/src/components/markets/modal/UpdateBorrowerModal.tsx +++ b/earn/src/components/markets/modal/UpdateBorrowerModal.tsx @@ -10,7 +10,7 @@ import styled from 'styled-components'; import BorrowModalContent from './content/BorrowModalContent'; import RepayModalContent from './content/RepayModalContent'; -import { BorrowerNftBorrower } from '../../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../../hooks/useDeprecatedMarginAccountShim'; export enum ConfirmationType { BORROW = 'BORROW', diff --git a/earn/src/components/markets/modal/UpdateCollateralModal.tsx b/earn/src/components/markets/modal/UpdateCollateralModal.tsx index 566f4b309..acdd6b15b 100644 --- a/earn/src/components/markets/modal/UpdateCollateralModal.tsx +++ b/earn/src/components/markets/modal/UpdateCollateralModal.tsx @@ -2,7 +2,6 @@ import { Fragment, useState } from 'react'; import { Tab } from '@headlessui/react'; import { type WriteContractReturnType } from '@wagmi/core'; -import { BigNumber } from 'ethers'; import Modal from 'shared/lib/components/common/Modal'; import { Text } from 'shared/lib/components/common/Typography'; import { GREY_700 } from 'shared/lib/data/constants/Colors'; @@ -12,7 +11,7 @@ import styled from 'styled-components'; import AddCollateralModalContent from './content/AddCollateralModalContent'; import RemoveCollateralModalContent from './content/RemoveCollateralModalContent'; import ToUniswapNFTModalContent from './content/ToUniswapNFTModalContent'; -import { BorrowerNftBorrower } from '../../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../../hooks/useDeprecatedMarginAccountShim'; export enum ConfirmationType { DEPOSIT = 'DEPOSIT', @@ -75,7 +74,7 @@ export default function UpdateCollateralModal(props: UpdateCollateralModalProps) diff --git a/earn/src/components/markets/modal/WithdrawModal.tsx b/earn/src/components/markets/modal/WithdrawModal.tsx index 74742626b..4e4a5704e 100644 --- a/earn/src/components/markets/modal/WithdrawModal.tsx +++ b/earn/src/components/markets/modal/WithdrawModal.tsx @@ -14,7 +14,7 @@ import { formatNumberInput } from 'shared/lib/util/Numbers'; import { Address } from 'viem'; import { useReadContract } from 'wagmi'; -import { RedeemState, useRedeem } from '../../../data/hooks/UseRedeem'; +import { RedeemState, useRedeem } from '../../../hooks/UseRedeem'; import { TokenIconsWithTooltip } from '../../common/TokenIconsWithTooltip'; import { SupplyTableRow } from '../supply/SupplyTable'; diff --git a/earn/src/components/markets/modal/content/AddCollateralModalContent.tsx b/earn/src/components/markets/modal/content/AddCollateralModalContent.tsx index e994d7087..e864dc190 100644 --- a/earn/src/components/markets/modal/content/AddCollateralModalContent.tsx +++ b/earn/src/components/markets/modal/content/AddCollateralModalContent.tsx @@ -14,7 +14,7 @@ import { Token } from 'shared/lib/data/Token'; import useChain from 'shared/lib/hooks/UseChain'; import { useAccount, useBalance, useSimulateContract, useWriteContract } from 'wagmi'; -import { BorrowerNftBorrower } from '../../../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../../../hooks/useDeprecatedMarginAccountShim'; import HealthBar from '../../../common/HealthBar'; const SECONDARY_COLOR = '#CCDFED'; diff --git a/earn/src/components/markets/modal/content/BorrowModalContent.tsx b/earn/src/components/markets/modal/content/BorrowModalContent.tsx index 20398e722..f0cb30621 100644 --- a/earn/src/components/markets/modal/content/BorrowModalContent.tsx +++ b/earn/src/components/markets/modal/content/BorrowModalContent.tsx @@ -4,7 +4,6 @@ import { type WriteContractReturnType } from '@wagmi/core'; import { ethers } from 'ethers'; import { borrowerAbi } from 'shared/lib/abis/Borrower'; import { borrowerNftAbi } from 'shared/lib/abis/BorrowerNft'; -import { factoryAbi } from 'shared/lib/abis/Factory'; import { FilledStylizedButton } from 'shared/lib/components/common/Buttons'; import { SquareInputWithMax } from 'shared/lib/components/common/Input'; import { MODAL_BLACK_TEXT_COLOR } from 'shared/lib/components/common/Modal'; @@ -14,7 +13,6 @@ import { Liabilities } from 'shared/lib/data/Borrower'; import { ALOE_II_BORROWER_NFT_ADDRESS, ALOE_II_BORROWER_NFT_SIMPLE_MANAGER_ADDRESS, - ALOE_II_FACTORY_ADDRESS, } from 'shared/lib/data/constants/ChainSpecific'; import { TERMS_OF_SERVICE_URL } from 'shared/lib/data/constants/Values'; import { GN, GNFormat } from 'shared/lib/data/GoodNumber'; @@ -22,10 +20,10 @@ import { LendingPair } from 'shared/lib/data/LendingPair'; import { Token } from 'shared/lib/data/Token'; import useChain from 'shared/lib/hooks/UseChain'; import { formatNumberInput } from 'shared/lib/util/Numbers'; -import { Address, Hex } from 'viem'; -import { useAccount, useBalance, useReadContract, useSimulateContract, useWriteContract } from 'wagmi'; +import { Address, formatEther, Hex } from 'viem'; +import { useAccount, useSimulateContract, useWriteContract } from 'wagmi'; -import { BorrowerNftBorrower } from '../../../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../../../hooks/useDeprecatedMarginAccountShim'; import HealthBar from '../../../common/HealthBar'; const SECONDARY_COLOR = '#CCDFED'; @@ -67,7 +65,7 @@ type ConfirmButtonProps = { isLoading: boolean; isUnhealthy: boolean; notEnoughSupply: boolean; - requiredAnte?: GN; + requiredAnte?: bigint; token: Token; isBorrowingToken0: boolean; accountAddress?: Address; @@ -113,9 +111,9 @@ function ConfirmButton(props: ConfirmButtonProps) { [borrower.index], [ALOE_II_BORROWER_NFT_SIMPLE_MANAGER_ADDRESS[activeChain.id]], [encodedBorrowCall ?? '0x'], - [requiredAnte?.toBigNumber().div(1e13).toNumber() ?? 0], + [Number((requiredAnte ?? 0n) / 10_000_000_000_000n)], ], - value: requiredAnte?.toBigInt(), + value: requiredAnte, chainId: activeChain.id, query: { enabled: @@ -177,27 +175,6 @@ export default function BorrowModalContent(props: BorrowModalContentProps) { const [additionalBorrowAmountStr, setAdditionalBorrowAmountStr] = useState(''); const { address: accountAddress } = useAccount(); - const activeChain = useChain(); - - const { data: anteData } = useReadContract({ - abi: factoryAbi, - address: ALOE_II_FACTORY_ADDRESS[activeChain.id], - functionName: 'getParameters', - args: [borrower.uniswapPool as Address], - chainId: activeChain.id, - }); - - const ante = useMemo(() => { - if (!anteData) return GN.zero(18); - return GN.fromBigInt(anteData[0], 18); - }, [anteData]); - - const { data: accountEtherBalanceResult } = useBalance({ - address: borrower.address as Address, - chainId: activeChain.id, - }); - - const accountEtherBalance = accountEtherBalanceResult && GN.fromBigInt(accountEtherBalanceResult.value, 18); // TODO: This assumes that only one token is borrowed and one token is collateralized const isBorrowingToken0 = borrower.liabilities.amount0 > 0; @@ -211,8 +188,7 @@ export default function BorrowModalContent(props: BorrowModalContentProps) { const borrowAmount = GN.fromDecimalString(additionalBorrowAmountStr || '0', borrowToken.decimals); const newLiability = existingLiability.add(borrowAmount); - const requiredAnte = - accountEtherBalance !== undefined && accountEtherBalance.lt(ante) ? ante.sub(accountEtherBalance) : GN.zero(18); + const requiredAnte = lendingPair?.amountEthRequiredBeforeBorrowing(borrower.ethBalance!.toBigInt()); const lenderInfo = lendingPair?.[isBorrowingToken0 ? 'kitty0Info' : 'kitty1Info']; @@ -302,9 +278,9 @@ export default function BorrowModalContent(props: BorrowModalContentProps) { . - {requiredAnte.isGtZero() && ( + {(requiredAnte !== undefined && requiredAnte > 0n) && ( - You will need to provide an additional {requiredAnte.toString(GNFormat.LOSSY_HUMAN)} ETH to cover the gas + You will need to provide an additional {formatEther(requiredAnte)} ETH to cover the gas fees in the event that you are liquidated. )} diff --git a/earn/src/components/markets/modal/content/RemoveCollateralModalContent.tsx b/earn/src/components/markets/modal/content/RemoveCollateralModalContent.tsx index 763145607..5c5e36a8c 100644 --- a/earn/src/components/markets/modal/content/RemoveCollateralModalContent.tsx +++ b/earn/src/components/markets/modal/content/RemoveCollateralModalContent.tsx @@ -19,10 +19,10 @@ import { TERMS_OF_SERVICE_URL } from 'shared/lib/data/constants/Values'; import { GN, GNFormat } from 'shared/lib/data/GoodNumber'; import { Token } from 'shared/lib/data/Token'; import useChain from 'shared/lib/hooks/UseChain'; -import { Address, Hex } from 'viem'; -import { useAccount, useBalance, useSimulateContract, useWriteContract } from 'wagmi'; +import { Address, encodeFunctionData, Hex } from 'viem'; +import { useAccount, useSimulateContract, useWriteContract } from 'wagmi'; -import { BorrowerNftBorrower } from '../../../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../../../hooks/useDeprecatedMarginAccountShim'; import HealthBar from '../../../common/HealthBar'; const SECONDARY_COLOR = '#CCDFED'; @@ -83,39 +83,31 @@ function ConfirmButton(props: ConfirmButtonProps) { const isRedeemingTooMuch = withdrawAmount.gt(maxWithdrawAmount); - const { data: borrowerBalance } = useBalance({ - address: borrower.address, - chainId: activeChain.id, - }); - const encodedWithdrawCall = useMemo(() => { if (!accountAddress) return null; - const borrowerInterface = new ethers.utils.Interface(borrowerAbi); const amount0 = isWithdrawingToken0 ? withdrawAmount : GN.zero(borrower.token0.decimals); const amount1 = isWithdrawingToken0 ? GN.zero(borrower.token1.decimals) : withdrawAmount; - return borrowerInterface.encodeFunctionData('transfer', [ - amount0.toBigNumber(), - amount1.toBigNumber(), - accountAddress, - ]) as Hex; + return encodeFunctionData({ + abi: borrowerAbi, + functionName: 'transfer', + args: [amount0.toBigInt(), amount1.toBigInt(), accountAddress], + }); }, [withdrawAmount, borrower.token0.decimals, borrower.token1.decimals, isWithdrawingToken0, accountAddress]); const encodedWithdrawAnteCall = useMemo(() => { - const borrowerInterface = new ethers.utils.Interface(borrowerAbi); - if (!accountAddress || !borrowerBalance) return null; - return borrowerInterface.encodeFunctionData('transferEth', [ - borrowerBalance?.value, - accountAddress, - ]) as Hex; - }, [accountAddress, borrowerBalance]); + if (!accountAddress || !borrower.ethBalance) return null; + + return encodeFunctionData({ + abi: borrowerAbi, + functionName: 'transferEth', + args: [borrower.ethBalance.toBigInt(), accountAddress], + }); + }, [accountAddress, borrower.ethBalance]); const combinedEncodingsForMultiManager = useMemo(() => { if (!encodedWithdrawCall || !encodedWithdrawAnteCall) return null; - return ethers.utils.defaultAbiCoder.encode( - ['bytes[]'], - [[encodedWithdrawCall, encodedWithdrawAnteCall]] - ) as Hex; + return ethers.utils.defaultAbiCoder.encode(['bytes[]'], [[encodedWithdrawCall, encodedWithdrawAnteCall]]) as Hex; }, [encodedWithdrawCall, encodedWithdrawAnteCall]); const { data: withdrawConfig, isLoading: isCheckingIfAbleToWithdraw } = useSimulateContract({ diff --git a/earn/src/components/markets/modal/content/RepayModalContent.tsx b/earn/src/components/markets/modal/content/RepayModalContent.tsx index 9e6677283..cf789397b 100644 --- a/earn/src/components/markets/modal/content/RepayModalContent.tsx +++ b/earn/src/components/markets/modal/content/RepayModalContent.tsx @@ -16,7 +16,7 @@ import { Permit2State, usePermit2 } from 'shared/lib/hooks/UsePermit2'; import { Address, Chain } from 'viem'; import { useAccount, useBalance, useSimulateContract, useWriteContract } from 'wagmi'; -import { BorrowerNftBorrower } from '../../../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../../../hooks/useDeprecatedMarginAccountShim'; import HealthBar from '../../../common/HealthBar'; const SECONDARY_COLOR = '#CCDFED'; diff --git a/earn/src/components/markets/modal/content/ToUniswapNFTModalContent.tsx b/earn/src/components/markets/modal/content/ToUniswapNFTModalContent.tsx index fb07b0ed3..667a261d9 100644 --- a/earn/src/components/markets/modal/content/ToUniswapNFTModalContent.tsx +++ b/earn/src/components/markets/modal/content/ToUniswapNFTModalContent.tsx @@ -1,7 +1,7 @@ import { useMemo } from 'react'; import { type WriteContractReturnType } from '@wagmi/core'; -import { BigNumber, ethers } from 'ethers'; +import { ethers } from 'ethers'; import { borrowerNftAbi } from 'shared/lib/abis/BorrowerNft'; import { FilledStylizedButton } from 'shared/lib/components/common/Buttons'; import { Text } from 'shared/lib/components/common/Typography'; @@ -17,7 +17,7 @@ import useChain from 'shared/lib/hooks/UseChain'; import { Hex } from 'viem'; import { useAccount, useSimulateContract, useWriteContract } from 'wagmi'; -import { BorrowerNftBorrower } from '../../../../data/BorrowerNft'; +import { BorrowerNftBorrower } from '../../../../hooks/useDeprecatedMarginAccountShim'; import HealthBar from '../../../common/HealthBar'; const SECONDARY_COLOR = '#CCDFED'; @@ -53,7 +53,7 @@ function getConfirmButton(state: ConfirmButtonState): { text: string; enabled: b type ConfirmButtonProps = { borrower: BorrowerNftBorrower; positionToWithdraw: UniswapPosition; - uniswapNftId: BigNumber; + uniswapNftId: number; setIsOpen: (open: boolean) => void; setPendingTxnResult: (result: WriteContractReturnType | null) => void; }; @@ -134,7 +134,7 @@ function ConfirmButton(props: ConfirmButtonProps) { export type RemoveCollateralModalContentProps = { borrower: BorrowerNftBorrower; positionToWithdraw: UniswapPosition; - uniswapNftId: BigNumber; + uniswapNftId: number; setIsOpen: (isOpen: boolean) => void; setPendingTxnResult: (result: WriteContractReturnType | null) => void; }; diff --git a/earn/src/components/markets/monitor/StatsTable.tsx b/earn/src/components/markets/monitor/StatsTable.tsx index 0e188b883..9714a3765 100644 --- a/earn/src/components/markets/monitor/StatsTable.tsx +++ b/earn/src/components/markets/monitor/StatsTable.tsx @@ -21,7 +21,7 @@ import { LendingPair } from 'shared/lib/data/LendingPair'; import useChain from 'shared/lib/hooks/UseChain'; import { useChainDependentState } from 'shared/lib/hooks/UseChainDependentState'; import useSortableData from 'shared/lib/hooks/UseSortableData'; -import { getEtherscanUrlForChain } from 'shared/lib/util/Chains'; +import { getBlockExplorerUrl } from 'shared/lib/util/Chains'; import { roundPercentage } from 'shared/lib/util/Numbers'; import styled from 'styled-components'; import { Address } from 'viem'; @@ -160,7 +160,7 @@ function StatsTableRow(props: StatsTableRowProps) { const { writeContractAsync } = useWriteContract(); - const uniswapLink = `${getEtherscanUrlForChain(activeChain)}/address/${pair.uniswapPool}`; + const uniswapLink = `${getBlockExplorerUrl(activeChain)}/address/${pair.uniswapPool}`; const manipulationMetric = pair.oracleData.manipulationMetric; const manipulationThreshold = pair.manipulationThreshold; @@ -193,7 +193,7 @@ function StatsTableRow(props: StatsTableRowProps) { const isPaused = pausedUntilTime > Date.now() / 1000; const lenderLinks = [pair.kitty0.address, pair.kitty1.address].map( - (addr) => `${getEtherscanUrlForChain(activeChain)}/address/${addr}` + (addr) => `${getBlockExplorerUrl(activeChain)}/address/${addr}` ); const reserveFactorTexts = [pair.kitty0Info.reserveFactor, pair.kitty1Info.reserveFactor].map((rf) => diff --git a/earn/src/components/portfolio/LendingPairPeerCard.tsx b/earn/src/components/portfolio/LendingPairPeerCard.tsx index 113f96e71..b1ebe5ff4 100644 --- a/earn/src/components/portfolio/LendingPairPeerCard.tsx +++ b/earn/src/components/portfolio/LendingPairPeerCard.tsx @@ -3,6 +3,7 @@ import { useEffect, useMemo, useState } from 'react'; import { Dropdown, DropdownOption } from 'shared/lib/components/common/Dropdown'; import Tooltip from 'shared/lib/components/common/Tooltip'; import { Display, Text } from 'shared/lib/components/common/Typography'; +import { RESPONSIVE_BREAKPOINT_SM } from 'shared/lib/data/constants/Breakpoints'; import { GREY_800 } from 'shared/lib/data/constants/Colors'; import { LendingPair } from 'shared/lib/data/LendingPair'; import { Token } from 'shared/lib/data/Token'; @@ -11,8 +12,7 @@ import { formatTokenAmount, roundPercentage } from 'shared/lib/util/Numbers'; import styled from 'styled-components'; import { Config, useClient } from 'wagmi'; -import { RESPONSIVE_BREAKPOINT_SM } from '../../data/constants/Breakpoints'; -import useNumberOfUsers from '../../data/hooks/UseNumberOfUsers'; +import useNumberOfUsers from '../../hooks/UseNumberOfUsers'; import { useEthersProvider } from '../../util/Provider'; const Container = styled.div` diff --git a/earn/src/components/portfolio/PortfolioGrid.tsx b/earn/src/components/portfolio/PortfolioGrid.tsx index fa0c07235..0a1a0d53d 100644 --- a/earn/src/components/portfolio/PortfolioGrid.tsx +++ b/earn/src/components/portfolio/PortfolioGrid.tsx @@ -1,4 +1,5 @@ import { Display, Text } from 'shared/lib/components/common/Typography'; +import { RESPONSIVE_BREAKPOINT_SM } from 'shared/lib/data/constants/Breakpoints'; import { GREY_800 } from 'shared/lib/data/constants/Colors'; import { Token } from 'shared/lib/data/Token'; import { rgb } from 'shared/lib/util/Colors'; @@ -6,7 +7,6 @@ import styled from 'styled-components'; import AssetPriceChartWidget from './AssetPriceChartWidget'; import PortfolioMetrics from './PortfolioMetrics'; -import { RESPONSIVE_BREAKPOINT_SM } from '../../data/constants/Breakpoints'; import { TokenBalance, TokenPriceData, TokenQuote } from '../../pages/PortfolioPage'; const STATUS_GREEN = 'rgba(0, 196, 140, 1)'; diff --git a/earn/src/components/portfolio/PortfolioPieChartWidget.tsx b/earn/src/components/portfolio/PortfolioPieChartWidget.tsx index 20dcc3906..8c89111cf 100644 --- a/earn/src/components/portfolio/PortfolioPieChartWidget.tsx +++ b/earn/src/components/portfolio/PortfolioPieChartWidget.tsx @@ -1,13 +1,12 @@ import { useMemo, useRef } from 'react'; import { Text } from 'shared/lib/components/common/Typography'; +import { RESPONSIVE_BREAKPOINT_LG } from 'shared/lib/data/constants/Breakpoints'; import { GREY_800 } from 'shared/lib/data/constants/Colors'; import { Token } from 'shared/lib/data/Token'; import styled from 'styled-components'; import tw from 'twin.macro'; -import { RESPONSIVE_BREAKPOINT_LG } from '../../data/constants/Breakpoints'; - // MARK: Capturing Mouse Data on container div --------------------------------------- const PIE_CHART_HOVER_GROWTH = 1.05; diff --git a/earn/src/components/portfolio/PortfolioTooltip.tsx b/earn/src/components/portfolio/PortfolioTooltip.tsx index 3d529eac0..1d0a276e9 100644 --- a/earn/src/components/portfolio/PortfolioTooltip.tsx +++ b/earn/src/components/portfolio/PortfolioTooltip.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { Text } from 'shared/lib/components/common/Typography'; +import { RESPONSIVE_BREAKPOINT_MD } from 'shared/lib/data/constants/Breakpoints'; import { GREY_700, GREY_900 } from 'shared/lib/data/constants/Colors'; import styled from 'styled-components'; import tw from 'twin.macro'; import { ReactComponent as CloseModalIcon } from '../../assets/svg/close_modal.svg'; -import { RESPONSIVE_BREAKPOINT_MD } from '../../data/constants/Breakpoints'; const TooltipContainer = styled.div.attrs((props: { verticallyCentered?: boolean; filled?: boolean }) => props)` ${tw`flex flex-col items-center justify-center absolute`} diff --git a/earn/src/components/portfolio/modal/BridgeModal.tsx b/earn/src/components/portfolio/modal/BridgeModal.tsx deleted file mode 100644 index 3b6d994e8..000000000 --- a/earn/src/components/portfolio/modal/BridgeModal.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useMemo } from 'react'; - -import { Bridge } from '@socket.tech/plugin'; -import { ethers } from 'ethers'; -import Modal from 'shared/lib/components/common/Modal'; -import { BRIDGE_SUPPORTED_CHAINS } from 'shared/lib/data/constants/ChainSpecific'; -import { GREY_800, GREY_900 } from 'shared/lib/data/constants/Colors'; -import { Token } from 'shared/lib/data/Token'; -import { getTokens } from 'shared/lib/data/TokenData'; -import useChain from 'shared/lib/hooks/UseChain'; -import { mainnet } from 'wagmi/chains'; - -export type BridgeModalProps = { - isOpen: boolean; - selectedAsset: Token; - setIsOpen: (isOpen: boolean) => void; -}; - -export default function BridgeModal(props: BridgeModalProps) { - const { isOpen, selectedAsset, setIsOpen } = props; - const activeChain = useChain(); - // @ts-ignore - const provider = new ethers.providers.Web3Provider(window.ethereum as any); - - const supportedChainIds = BRIDGE_SUPPORTED_CHAINS.map((chain) => chain.id); - const tokens = useMemo(() => { - return supportedChainIds - .map((chainId) => - getTokens(chainId).map((token) => { - return { ...token }; - }) - ) - .flat(); - }, [supportedChainIds]); - - if (!provider) { - return null; - } - - return ( - - - - ); -} diff --git a/earn/src/components/portfolio/modal/WithdrawModal.tsx b/earn/src/components/portfolio/modal/WithdrawModal.tsx index 26f0b6812..f21630c67 100644 --- a/earn/src/components/portfolio/modal/WithdrawModal.tsx +++ b/earn/src/components/portfolio/modal/WithdrawModal.tsx @@ -12,10 +12,10 @@ import { LendingPair } from 'shared/lib/data/LendingPair'; import { Token } from 'shared/lib/data/Token'; import useChain from 'shared/lib/hooks/UseChain'; import { formatNumberInput, truncateDecimals } from 'shared/lib/util/Numbers'; +import { zeroAddress } from 'viem'; import { useAccount } from 'wagmi'; -import { ZERO_ADDRESS } from '../../../data/constants/Addresses'; -import { RedeemState, useRedeem } from '../../../data/hooks/UseRedeem'; +import { RedeemState, useRedeem } from '../../../hooks/UseRedeem'; import { TokenBalance } from '../../../pages/PortfolioPage'; import PairDropdown from '../../common/PairDropdown'; import TokenAmountSelectInput from '../TokenAmountSelectInput'; @@ -109,7 +109,7 @@ export default function WithdrawModal(props: WithdrawModalProps) { activeChain.id, lender?.address, inputValue[1] ? GN.Q(112) : amount, - isOpen && account.address ? account.address : ZERO_ADDRESS + isOpen && account.address ? account.address : zeroAddress ); const maxAmount = GN.fromBigInt(maxAmountBN, selectedToken.decimals); diff --git a/earn/src/connector/FactoryActions.ts b/earn/src/connector/FactoryActions.ts deleted file mode 100644 index d3fa3aaab..000000000 --- a/earn/src/connector/FactoryActions.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { BigNumber, Contract, ContractReceipt, Signer } from 'ethers'; -import { factoryAbi } from 'shared/lib/abis/Factory'; -import { ALOE_II_FACTORY_ADDRESS } from 'shared/lib/data/constants/ChainSpecific'; -import { Chain } from 'viem'; - -import { BLOCKS_TO_WAIT, GAS_ESTIMATION_SCALING } from '../data/constants/Values'; - -export async function createMarginAccount( - signer: Signer, - poolAddress: string, - ownerAddress: string, - completionCallback: (receipt?: ContractReceipt) => void, - chain: Chain -): Promise { - const factory = new Contract(ALOE_II_FACTORY_ADDRESS[chain.id], factoryAbi, signer); - - let transactionOptions: any = {}; - - try { - const estimatedGas = ( - (await factory.estimateGas.createMarginAccount(poolAddress, ownerAddress)) as BigNumber - ).toNumber(); - - transactionOptions['gasLimit'] = (estimatedGas * GAS_ESTIMATION_SCALING).toFixed(0); - } catch (e) { - console.error('Error while estimating gas'); - console.error(e); - } - - try { - const transactionResponse = await factory.createMarginAccount(poolAddress, ownerAddress, transactionOptions); - const receipt = await transactionResponse.wait(BLOCKS_TO_WAIT); - completionCallback(receipt); - } catch (e) { - // User probably rejected in MetaMask or wallet - console.error(e); - completionCallback(); - } -} diff --git a/earn/src/connector/MarginAccountActions.ts b/earn/src/connector/MarginAccountActions.ts deleted file mode 100644 index 8298439f7..000000000 --- a/earn/src/connector/MarginAccountActions.ts +++ /dev/null @@ -1,57 +0,0 @@ -import Big from 'big.js'; -import { ethers } from 'ethers'; -import JSBI from 'jsbi'; -import { Kitty } from 'shared/lib/data/Kitty'; -import { Token } from 'shared/lib/data/Token'; - -export function getTransferInActionArgs(token: Token, amount: number): string { - const address = token.address; - const bigAmount = new Big(amount.toFixed(Math.min(token.decimals, 20))).mul(10 ** token.decimals); - - return ethers.utils.defaultAbiCoder.encode(['address', 'uint256'], [address, bigAmount.toFixed(0)]); -} - -export function getTransferOutActionArgs(token: Token, amount: number): string { - const address = token.address; - const bigAmount = new Big(amount.toFixed(Math.min(token.decimals, 20))).mul(10 ** token.decimals); - - return ethers.utils.defaultAbiCoder.encode(['address', 'uint256'], [address, bigAmount.toFixed(0)]); -} - -export function getMintActionArgs(token: Token, kitty: Kitty, amount: number): string { - const address = kitty.address; - const bigAmount = new Big(amount.toFixed(Math.min(token.decimals, 20))).mul(10 ** token.decimals); - - return ethers.utils.defaultAbiCoder.encode(['address', 'uint256'], [address, bigAmount.toFixed(0)]); -} - -export function getBurnActionArgs(token: Token, kitty: Kitty, amount: number): string { - const address = kitty.address; - const bigAmount = new Big(amount.toFixed(Math.min(token.decimals, 20))).mul(10 ** token.decimals); - - return ethers.utils.defaultAbiCoder.encode(['address', 'uint256'], [address, bigAmount.toFixed(0)]); -} - -export function getBorrowActionArgs(token0: Token, amount0: number, token1: Token, amount1: number): string { - const bigAmount0 = new Big(amount0.toFixed(Math.min(token0.decimals, 20))).mul(10 ** token0.decimals); - const bigAmount1 = new Big(amount1.toFixed(Math.min(token1.decimals, 20))).mul(10 ** token1.decimals); - - return ethers.utils.defaultAbiCoder.encode(['uint256', 'uint256'], [bigAmount0.toFixed(0), bigAmount1.toFixed(0)]); -} - -export function getRepayActionArgs(token0: Token, amount0: number, token1: Token, amount1: number): string { - const bigAmount0 = new Big(amount0.toFixed(Math.min(token0.decimals, 20))).mul(10 ** token0.decimals); - const bigAmount1 = new Big(amount1.toFixed(Math.min(token1.decimals, 20))).mul(10 ** token1.decimals); - - return ethers.utils.defaultAbiCoder.encode(['uint256', 'uint256'], [bigAmount0.toFixed(0), bigAmount1.toFixed(0)]); -} - -export function getAddLiquidityActionArgs(lower: number, upper: number, liquidity: JSBI): string { - if (lower > upper) [lower, upper] = [upper, lower]; - return ethers.utils.defaultAbiCoder.encode(['int24', 'int24', 'uint128'], [lower, upper, liquidity.toString(10)]); -} - -export function getRemoveLiquidityActionArgs(lower: number, upper: number, liquidity: JSBI): string { - if (lower > upper) [lower, upper] = [upper, lower]; - return ethers.utils.defaultAbiCoder.encode(['int24', 'int24', 'uint128'], [lower, upper, liquidity.toString(10)]); -} diff --git a/earn/src/data/GlobalStats.ts b/earn/src/data/GlobalStats.ts deleted file mode 100644 index 07b264ad1..000000000 --- a/earn/src/data/GlobalStats.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type GlobalStats = { - poolCount: number; - users: number; - totalValueLocked: number; -}; diff --git a/earn/src/data/MarginAccount.ts b/earn/src/data/MarginAccount.ts deleted file mode 100644 index a06b22c22..000000000 --- a/earn/src/data/MarginAccount.ts +++ /dev/null @@ -1,352 +0,0 @@ -import Big from 'big.js'; -import { CallReturnContext, ContractCallContext, ContractCallReturnContext, Multicall } from 'ethereum-multicall'; -import { BigNumber, ethers } from 'ethers'; -import JSBI from 'jsbi'; -import { borrowerAbi } from 'shared/lib/abis/Borrower'; -import { borrowerLensAbi } from 'shared/lib/abis/BorrowerLens'; -import { erc20Abi } from 'shared/lib/abis/ERC20'; -import { factoryAbi } from 'shared/lib/abis/Factory'; -import { volatilityOracleAbi } from 'shared/lib/abis/VolatilityOracle'; -import { Assets, Liabilities } from 'shared/lib/data/Borrower'; -import { - ALOE_II_FACTORY_ADDRESS, - ALOE_II_BORROWER_LENS_ADDRESS, - ALOE_II_ORACLE_ADDRESS, - MULTICALL_ADDRESS, -} from 'shared/lib/data/constants/ChainSpecific'; -import { Q32 } from 'shared/lib/data/constants/Values'; -import { FeeTier, NumericFeeTierToEnum } from 'shared/lib/data/FeeTier'; -import { GN } from 'shared/lib/data/GoodNumber'; -import { Token } from 'shared/lib/data/Token'; -import { getToken } from 'shared/lib/data/TokenData'; -import { UniswapPosition } from 'shared/lib/data/Uniswap'; -import { toBig, toImpreciseNumber } from 'shared/lib/util/Numbers'; -import { Address, Hex } from 'viem'; - -import { TOPIC0_CREATE_BORROWER_EVENT } from './constants/Signatures'; - -type ContractCallReturnContextEntries = { - [key: string]: ContractCallReturnContext; -}; - -function convertBigNumbersForReturnContexts(callReturnContexts: CallReturnContext[]): CallReturnContext[] { - return callReturnContexts.map((callReturnContext) => { - callReturnContext.returnValues = callReturnContext.returnValues.map((returnValue) => { - // If the return value is a BigNumber, convert it to an ethers BigNumber - if (returnValue?.type === 'BigNumber' && returnValue?.hex) { - returnValue = BigNumber.from(returnValue.hex); - } - return returnValue; - }); - return callReturnContext; - }); -} - -/** - * For the use-cases that require all of the data - */ -export type MarginAccount = { - address: Address; - uniswapPool: string; - token0: Token; - token1: Token; - feeTier: FeeTier; - assets: Assets; - liabilities: Liabilities; - sqrtPriceX96: Big; - health: number; - lender0: Address; - lender1: Address; - iv: number; - nSigma: number; - userDataHex: Hex; - warningTime: number; -}; - -/** - * For the use-cases that may not require all of the data - * (When we don't want to fetch more than we need) - */ -export type MarginAccountPreview = Omit; - -export async function getMarginAccountsForUser( - chainId: number, - provider: ethers.providers.Provider, - userAddress: string -): Promise<{ address: string; uniswapPool: string }[]> { - let logs: ethers.providers.Log[] = []; - try { - logs = await provider.getLogs({ - fromBlock: 0, - toBlock: 'latest', - address: ALOE_II_FACTORY_ADDRESS[chainId], - topics: [TOPIC0_CREATE_BORROWER_EVENT, null, `0x000000000000000000000000${userAddress.slice(2)}`], - }); - } catch (e) { - console.error(e); - } - if (logs.length === 0) return []; - - const accounts: { address: string; uniswapPool: string }[] = logs.map((item: any) => { - return { - address: item.data.slice(0, 2) + item.data.slice(26), - uniswapPool: item.topics[1].slice(26), - }; - }); - - return accounts; -} - -export type UniswapPoolInfo = { - token0: Token; - token1: Token; - fee: number; -}; - -export async function fetchBorrowerDatas( - chainId: number, - provider: ethers.providers.BaseProvider, - addresses: Address[], - uniswapPoolDataMap: Map -): Promise { - const multicall = new Multicall({ - ethersProvider: provider, - tryAggregate: true, - multicallCustomContractAddress: MULTICALL_ADDRESS[chainId], - }); - const borrowerUniswapPoolCallContext: ContractCallContext[] = addresses.map((borrowerAddress) => ({ - reference: `${borrowerAddress}`, - contractAddress: borrowerAddress, - abi: borrowerAbi as any, - calls: [{ reference: 'uniswapPool', methodName: 'UNISWAP_POOL', methodParameters: [] }], - })); - const borrowerUniswapPools = (await multicall.call(borrowerUniswapPoolCallContext)).results; - - const marginAccountCallContext: ContractCallContext[] = []; - - // Fetch all the data for the margin accounts - addresses.forEach((accountAddress) => { - const uniswapPool = borrowerUniswapPools[accountAddress].callsReturnContext[0].returnValues[0]; - const uniswapPoolInfo = uniswapPoolDataMap.get(uniswapPool) ?? null; - - if (uniswapPoolInfo === null) return; - - const token0 = uniswapPoolInfo.token0; - const token1 = uniswapPoolInfo.token1; - const fee = uniswapPoolInfo.fee; - - if (!token0 || !token1) return; - // Fetching the data for the margin account using three contracts - marginAccountCallContext.push({ - reference: `${accountAddress}-account`, - contractAddress: accountAddress, - abi: borrowerAbi as any, - calls: [ - { - reference: 'lender0', - methodName: 'LENDER0', - methodParameters: [], - }, - { - reference: 'lender1', - methodName: 'LENDER1', - methodParameters: [], - }, - { - reference: 'slot0', - methodName: 'slot0', - methodParameters: [], - }, - { - reference: 'getLiabilities', - methodName: 'getLiabilities', - methodParameters: [], - }, - ], - }); - marginAccountCallContext.push({ - reference: `${accountAddress}-oracle`, - contractAddress: ALOE_II_ORACLE_ADDRESS[chainId], - abi: volatilityOracleAbi as any, - calls: [ - { - reference: 'consult', - methodName: 'consult', - methodParameters: [uniswapPool, Q32], - }, - ], - }); - marginAccountCallContext.push({ - reference: `${accountAddress}-token0`, - contractAddress: token0.address, - abi: erc20Abi as any, - calls: [ - { - reference: 'balanceOf', - methodName: 'balanceOf', - methodParameters: [accountAddress], - }, - ], - }); - marginAccountCallContext.push({ - reference: `${accountAddress}-token1`, - contractAddress: token1.address, - abi: erc20Abi as any, - calls: [ - { - reference: 'balanceOf', - methodName: 'balanceOf', - methodParameters: [accountAddress], - }, - ], - }); - marginAccountCallContext.push({ - reference: `${accountAddress}-lens`, - contractAddress: ALOE_II_BORROWER_LENS_ADDRESS[chainId], - abi: borrowerLensAbi as any, - calls: [ - { - reference: 'getHealth', - methodName: 'getHealth', - methodParameters: [accountAddress], - }, - { - reference: 'getUniswapPositions', - methodName: 'getUniswapPositions', - methodParameters: [accountAddress], - }, - ], - context: { - fee: fee, - token0Address: token0.address, - token1Address: token1.address, - chainId: chainId, - accountAddress: accountAddress, - uniswapPool: uniswapPool, - }, - }); - marginAccountCallContext.push({ - reference: `${accountAddress}-nSigma`, - contractAddress: ALOE_II_FACTORY_ADDRESS[chainId], - abi: factoryAbi as any, - calls: [ - { - reference: 'getParameters', - methodName: 'getParameters', - methodParameters: [uniswapPool], - }, - ], - }); - }); - - const marginAccountResults = (await multicall.call(marginAccountCallContext)).results; - - const correspondingMarginAccountResults: Map = new Map(); - - // Convert the results into a map of account address to the results - Object.entries(marginAccountResults).forEach(([key, value]) => { - const entryAccountAddress = key.split('-')[0]; - const entryType = key.split('-')[1]; - const existingValue = correspondingMarginAccountResults.get(entryAccountAddress); - if (existingValue) { - existingValue[entryType] = value; - correspondingMarginAccountResults.set(entryAccountAddress, existingValue); - } else { - correspondingMarginAccountResults.set(entryAccountAddress, { [entryType]: value }); - } - }); - - const marginAccounts: MarginAccount[] = []; - - correspondingMarginAccountResults.forEach((value) => { - const { - lens: lensResults, - account: accountResults, - oracle: oracleResults, - token0: token0Results, - token1: token1Results, - } = value; - const accountReturnContexts = convertBigNumbersForReturnContexts(accountResults.callsReturnContext); - const lensReturnContexts = convertBigNumbersForReturnContexts(lensResults.callsReturnContext); - const token0ReturnContexts = convertBigNumbersForReturnContexts(token0Results.callsReturnContext); - const token1ReturnContexts = convertBigNumbersForReturnContexts(token1Results.callsReturnContext); - const { fee, token0Address, token1Address, chainId, accountAddress, uniswapPool } = - lensResults.originalContractCallContext.context; - // Reconstruct the objects (since we can't transfer them as is through the context) - const feeTier = NumericFeeTierToEnum(fee); - const token0 = getToken(chainId, token0Address)!; - const token1 = getToken(chainId, token1Address)!; - const liabilitiesData = accountReturnContexts[3].returnValues; - const token0Balance = token0ReturnContexts[0].returnValues[0]; - const token1Balance = token1ReturnContexts[0].returnValues[0]; - const healthData = lensReturnContexts[0].returnValues; - const nSigma = convertBigNumbersForReturnContexts(value.nSigma.callsReturnContext)[0].returnValues[1] / 10; - - const health = toImpreciseNumber(healthData[0].lt(healthData[1]) ? healthData[0] : healthData[1], 18); - const liabilities: Liabilities = { - amount0: toImpreciseNumber(liabilitiesData[0], token0.decimals), - amount1: toImpreciseNumber(liabilitiesData[1], token1.decimals), - }; - - const uniswapPositionData = lensReturnContexts[1].returnValues; - const uniswapPositionBounds = uniswapPositionData[0] as number[]; - const uniswapPositionLiquidity = uniswapPositionData[1] as { hex: Hex }[]; - - const uniswapPositions: UniswapPosition[] = []; - uniswapPositionLiquidity.forEach((liquidity, i) => { - uniswapPositions.push({ - lower: uniswapPositionBounds[i * 2], - upper: uniswapPositionBounds[i * 2 + 1], - liquidity: JSBI.BigInt(liquidity.hex), - }); - }); - // const uniswapPositionFees = uniswapPositionData[2]; - - const assets = new Assets( - GN.fromBigNumber(token0Balance, token0.decimals), - GN.fromBigNumber(token1Balance, token1.decimals), - uniswapPositions - ); - - const slot0 = accountReturnContexts[2].returnValues[0] as BigNumber; - const userDataHex = slot0.shr(144).mask(64).toHexString() as Hex; - const warningTime = slot0.shr(208).mask(40).toNumber(); - - const oracleReturnValues = convertBigNumbersForReturnContexts(oracleResults.callsReturnContext)[0].returnValues; - const marginAccount: MarginAccount = { - address: accountAddress, - sqrtPriceX96: toBig(oracleReturnValues[1]), - iv: toImpreciseNumber(oracleReturnValues[2], 12), - uniswapPool, - feeTier, - assets, - liabilities, - health, - token0, - token1, - lender0: accountReturnContexts[0].returnValues[0], - lender1: accountReturnContexts[1].returnValues[0], - nSigma, - userDataHex, - warningTime, - }; - marginAccounts.push(marginAccount); - }); - - return marginAccounts; -} - -export async function fetchMarginAccounts( - chainId: number, - provider: ethers.providers.BaseProvider, - userAddress: string, - uniswapPoolDataMap: Map -): Promise { - const borrowers = await getMarginAccountsForUser(chainId, provider, userAddress); - return fetchBorrowerDatas( - chainId, - provider, - borrowers.map((b) => b.address as Address), - uniswapPoolDataMap - ); -} diff --git a/earn/src/data/PriceRelayResponse.ts b/earn/src/data/PriceRelayResponse.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/earn/src/data/RedeemRewardReponse.ts b/earn/src/data/RedeemRewardReponse.ts deleted file mode 100644 index 0487d7a5b..000000000 --- a/earn/src/data/RedeemRewardReponse.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type RedeemRewardResponse = { - success: boolean; - message: string; -}; diff --git a/earn/src/data/Uniboost.ts b/earn/src/data/Uniboost.ts index 04c29bd2e..810ef527c 100644 --- a/earn/src/data/Uniboost.ts +++ b/earn/src/data/Uniboost.ts @@ -28,7 +28,7 @@ import { } from 'shared/lib/data/Uniswap'; import { Address, erc20Abi } from 'viem'; -import { MarginAccount } from './MarginAccount'; +import { MarginAccount } from '../hooks/useDeprecatedMarginAccountShim'; export enum BoostCardType { UNISWAP_NFT, diff --git a/earn/src/data/constants/Addresses.ts b/earn/src/data/constants/Addresses.ts deleted file mode 100644 index 7305a0b9e..000000000 --- a/earn/src/data/constants/Addresses.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; - -export const UNISWAP_POOL_DENYLIST = [ - '0x27807dD7ADF218e1f4d885d54eD51C70eFb9dE50', // USDT-MIM on Arbitrum - '0xa9bA2535BdAc4c6A5dc3c3f112763A84BD472816', -].map((address) => address.toLowerCase()); diff --git a/earn/src/data/constants/Breakpoints.ts b/earn/src/data/constants/Breakpoints.ts deleted file mode 100644 index 96e272b7c..000000000 --- a/earn/src/data/constants/Breakpoints.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const RESPONSIVE_BREAKPOINTS = { - LG: 1400, - MD: 1200, - SM: 768, - XS: 480, -}; - -export const RESPONSIVE_BREAKPOINT_LG = `${RESPONSIVE_BREAKPOINTS.LG}px`; -export const RESPONSIVE_BREAKPOINT_MD = `${RESPONSIVE_BREAKPOINTS.MD}px`; -export const RESPONSIVE_BREAKPOINT_SM = `${RESPONSIVE_BREAKPOINTS.SM}px`; -export const RESPONSIVE_BREAKPOINT_XS = `${RESPONSIVE_BREAKPOINTS.XS}px`; - -export const BROWSE_CARD_WIDTH_XL = '580px'; -export const BROWSE_CARD_WIDTH_LG = '500px'; -export const BROWSE_CARD_WIDTH_MD = '400px'; diff --git a/earn/src/data/constants/Signatures.ts b/earn/src/data/constants/Signatures.ts deleted file mode 100644 index 5c2562383..000000000 --- a/earn/src/data/constants/Signatures.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const TOPIC0_CREATE_BORROWER_EVENT = '0x1ff0a9a76572c6e0f2f781872c1e45b4bab3a0d90df274ebf884b4c11e3068f4'; -export const TOPIC0_ENROLL_COURIER_EVENT = '0x9038af148c4bb36b89ff46ec17f02b27c5ab1e0ca181c0f0981b986a78b002d5'; -export const TOPIC0_CREATE_MARKET_EVENT = '0x3f53d2c2743b2b162c0aa5d678be4058d3ae2043700424be52c04105df3e2411'; -export const TOPIC0_UPDATE_ORACLE = '0x965518ee86dc5d32ef4108b1f8b7b15e2192959e251d990dac29c49ed3dcb79c'; diff --git a/earn/src/data/constants/Values.ts b/earn/src/data/constants/Values.ts deleted file mode 100644 index 13464366a..000000000 --- a/earn/src/data/constants/Values.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { BigNumber } from 'ethers'; -import { toBig } from 'shared/lib/util/Numbers'; -import { isDappnet } from 'shared/lib/util/Utils'; - -export const UINT256_MAX = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; -export const BLOCKS_TO_WAIT = 1; -export const DEFAULT_RATIO_CHANGE = '5.0'; -export const RATIO_CHANGE_CUTOFF = 0; -export const API_URL = 'https://api.aloe.capital'; -export const GAS_ESTIMATION_SCALING = 1.1; -export const Q48 = BigNumber.from('0x1000000000000'); -export const Q96 = BigNumber.from('0x1000000000000000000000000'); -export const BIGQ96 = toBig(Q96); -export const MAX_UNISWAP_POSITIONS = 3; - -export const API_PRICE_RELAY_LATEST_URL = 'https://api-price.aloe.capital/price-relay/v1/latest'; -export const API_PRICE_RELAY_HISTORICAL_URL = 'https://api-price.aloe.capital/price-relay/v1/historical'; -export const API_PRICE_RELAY_CONSOLIDATED_URL = 'https://api-price.aloe.capital/price-relay/v1/consolidated'; - -export function primeUrl() { - // NOTE: trailing `/` is important for .eth domain resolution to work - return isDappnet() ? 'https://prime.aloe.eth/' : 'https://prime.aloe.capital/'; -} diff --git a/earn/src/data/hooks/UseNumberOfUsers.ts b/earn/src/hooks/UseNumberOfUsers.ts similarity index 100% rename from earn/src/data/hooks/UseNumberOfUsers.ts rename to earn/src/hooks/UseNumberOfUsers.ts diff --git a/earn/src/data/hooks/UseProminentColor.ts b/earn/src/hooks/UseProminentColor.ts similarity index 100% rename from earn/src/data/hooks/UseProminentColor.ts rename to earn/src/hooks/UseProminentColor.ts diff --git a/earn/src/data/hooks/UseRedeem.ts b/earn/src/hooks/UseRedeem.ts similarity index 95% rename from earn/src/data/hooks/UseRedeem.ts rename to earn/src/hooks/UseRedeem.ts index c988cb2e1..625bdf758 100644 --- a/earn/src/data/hooks/UseRedeem.ts +++ b/earn/src/hooks/UseRedeem.ts @@ -1,7 +1,7 @@ import { lenderLensAbi } from 'shared/lib/abis/LenderLens'; import { ALOE_II_LENDER_LENS_ADDRESS } from 'shared/lib/data/constants/ChainSpecific'; import { GN } from 'shared/lib/data/GoodNumber'; -import { Address, erc4626Abi, maxUint256 } from 'viem'; +import { Address, erc4626Abi, maxUint256, zeroAddress } from 'viem'; import { useReadContract, useReadContracts, @@ -9,8 +9,6 @@ import { useWriteContract, } from 'wagmi'; -import { ZERO_ADDRESS } from '../constants/Addresses'; - export enum RedeemState { WAITING_FOR_INPUT, FETCHING_DATA, @@ -49,7 +47,7 @@ export function useRedeem( contracts: [ { ...erc4626, functionName: 'maxWithdraw', args: [owner] }, { ...erc4626, functionName: 'maxRedeem', args: [owner] }, - { ...lenderLens, functionName: 'isMaxRedeemDynamic', args: [lender ?? ZERO_ADDRESS, owner] }, + { ...lenderLens, functionName: 'isMaxRedeemDynamic', args: [lender ?? zeroAddress, owner] }, ] as const, allowFailure: false, query: { enabled: lender !== undefined }, diff --git a/earn/src/data/BorrowerNft.ts b/earn/src/hooks/useDeprecatedMarginAccountShim.ts similarity index 50% rename from earn/src/data/BorrowerNft.ts rename to earn/src/hooks/useDeprecatedMarginAccountShim.ts index 13026b500..4a32b009e 100644 --- a/earn/src/data/BorrowerNft.ts +++ b/earn/src/hooks/useDeprecatedMarginAccountShim.ts @@ -2,14 +2,32 @@ import { useMemo } from 'react'; import Big from 'big.js'; import { borrowerNftAbi } from 'shared/lib/abis/BorrowerNft'; -import { Borrower } from 'shared/lib/data/Borrower'; -import { NumericFeeTierToEnum } from 'shared/lib/data/FeeTier'; -import { GNFormat } from 'shared/lib/data/GoodNumber'; +import { Assets, Liabilities } from 'shared/lib/data/Borrower'; +import { FeeTier, NumericFeeTierToEnum } from 'shared/lib/data/FeeTier'; +import { GN, GNFormat } from 'shared/lib/data/GoodNumber'; import { LendingPair } from 'shared/lib/data/LendingPair'; -import { BorrowerNftRef } from 'shared/lib/hooks/UseBorrowerNft'; -import { GetContractEventsReturnType } from 'viem'; +import { Token } from 'shared/lib/data/Token'; +import { BorrowerNft } from 'shared/lib/hooks/UseBorrowerNft'; +import { Address, GetContractEventsReturnType, Hex } from 'viem'; -import { MarginAccount } from './MarginAccount'; +export type MarginAccount = { + address: Address; + uniswapPool: Address; + token0: Token; + token1: Token; + feeTier: FeeTier; + assets: Assets; + liabilities: Liabilities; + sqrtPriceX96: Big; + health: number; + lender0: Address; + lender1: Address; + iv: number; + nSigma: number; + userDataHex: Hex; + warningTime: number; + ethBalance?: GN; +}; export type BorrowerNftBorrower = MarginAccount & { tokenId: string; @@ -19,25 +37,24 @@ export type BorrowerNftBorrower = MarginAccount & { export function useDeprecatedMarginAccountShim( lendingPairs: LendingPair[], - borrowerNftRefs: BorrowerNftRef[], - borrowers: Borrower[] | undefined + borrowerNfts: BorrowerNft[] ): BorrowerNftBorrower[] | null { return useMemo(() => { - if (borrowers === undefined || borrowerNftRefs.length !== borrowers.length) return null; + if (borrowerNfts.some((nft) => nft.borrower === undefined)) return null; - const borrowerNfts = borrowerNftRefs.map((ref, i) => { - const borrower = borrowers[i]; - const pair = lendingPairs.find((pair) => pair.uniswapPool.toLowerCase() === ref.uniswapPool.toLowerCase())!; + const results = borrowerNfts.map((nft) => { + const borrower = nft.borrower!; + const pair = lendingPairs.find((pair) => pair.uniswapPool.toLowerCase() === nft.uniswapPool.toLowerCase())!; const sqrtPriceX96 = new Big(pair.oracleData.sqrtPriceX96.toString(GNFormat.INT)); const iv = pair.iv; return { - tokenId: ref.tokenId, - index: ref.index, - mostRecentModify: ref.mostRecentModify, - address: ref.address, - uniswapPool: ref.uniswapPool, + tokenId: nft.tokenId, + index: nft.index, + mostRecentModify: nft.mostRecentModify, + address: nft.address, + uniswapPool: nft.uniswapPool, token0: pair.token0, token1: pair.token1, feeTier: NumericFeeTierToEnum(pair.uniswapFeeTier), @@ -54,9 +71,10 @@ export function useDeprecatedMarginAccountShim( nSigma: pair.factoryData.nSigma, userDataHex: borrower.userData, warningTime: borrower.warnTime, + ethBalance: borrower.ethBalance, } as BorrowerNftBorrower; }); - borrowerNfts.reverse(); - return borrowerNfts; - }, [lendingPairs, borrowerNftRefs, borrowers]); + results.sort((a, b) => Number((b.mostRecentModify?.blockNumber || 0n) - (a.mostRecentModify?.blockNumber || 0n))); + return results; + }, [lendingPairs, borrowerNfts]); } diff --git a/earn/src/pages/AdvancedPage.tsx b/earn/src/pages/AdvancedPage.tsx index 6d311cddf..2dfffd153 100644 --- a/earn/src/pages/AdvancedPage.tsx +++ b/earn/src/pages/AdvancedPage.tsx @@ -1,15 +1,14 @@ import { useEffect, useMemo, useState } from 'react'; import { type WriteContractReturnType } from '@wagmi/core'; -import { ethers } from 'ethers'; import JSBI from 'jsbi'; import { useSearchParams } from 'react-router-dom'; import Banner from 'shared/lib/components/banner/Banner'; import AppPage from 'shared/lib/components/common/AppPage'; import { Text } from 'shared/lib/components/common/Typography'; +import { RESPONSIVE_BREAKPOINT_SM } from 'shared/lib/data/constants/Breakpoints'; import { ALOE_II_BORROWER_NFT_ADDRESS } from 'shared/lib/data/constants/ChainSpecific'; import { GetNumericFeeTier } from 'shared/lib/data/FeeTier'; -import { GN } from 'shared/lib/data/GoodNumber'; import { Token } from 'shared/lib/data/Token'; import { fetchUniswapNFTPositions, UniswapNFTPosition } from 'shared/lib/data/Uniswap'; import { useBorrowerNfts } from 'shared/lib/hooks/UseBorrowerNft'; @@ -17,11 +16,10 @@ import useChain from 'shared/lib/hooks/UseChain'; import { useLendingPair, useLendingPairs } from 'shared/lib/hooks/UseLendingPairs'; import { useTokenColors } from 'shared/lib/hooks/UseTokenColors'; import { useUniswapPools } from 'shared/lib/hooks/UseUniswapPools'; -import { getEtherscanUrlForChain } from 'shared/lib/util/Chains'; +import { getBlockExplorerUrl } from 'shared/lib/util/Chains'; import styled from 'styled-components'; -import { Address } from 'viem'; import { linea } from 'viem/chains'; -import { Config, useAccount, useBalance, useBlockNumber, useClient, usePublicClient } from 'wagmi'; +import { Config, useAccount, useBlockNumber, useClient, usePublicClient } from 'wagmi'; import { ReactComponent as InfoIcon } from '../assets/svg/info.svg'; import { BorrowMetrics } from '../components/advanced/BorrowMetrics'; @@ -38,12 +36,11 @@ import SmartWalletButton, { NewSmartWalletButton } from '../components/advanced/ import { TokenAllocationWidget } from '../components/advanced/TokenAllocationWidget'; import { UniswapPositionList } from '../components/advanced/UniswapPositionList'; import PendingTxnModal, { PendingTxnModalStatus } from '../components/common/PendingTxnModal'; -import { useDeprecatedMarginAccountShim } from '../data/BorrowerNft'; -import { RESPONSIVE_BREAKPOINT_SM } from '../data/constants/Breakpoints'; +import { useDeprecatedMarginAccountShim } from '../hooks/useDeprecatedMarginAccountShim'; import { useEthersProvider } from '../util/Provider'; const BORROW_TITLE_TEXT_COLOR = 'rgba(130, 160, 182, 1)'; -const SELECTED_MARGIN_ACCOUNT_KEY = 'account'; +const SELECTED_BORROWER_NFT_ID = 'nft'; const Container = styled.div` display: grid; @@ -158,12 +155,13 @@ export default function AdvancedPage() { const { lendingPairs, refetchOracleData, refetchLenderData } = useLendingPairs(activeChain.id); const { data: tokenColors } = useTokenColors(lendingPairs); - const { borrowerNftRefs, borrowers, refetchBorrowerNftRefs, refetchBorrowers } = useBorrowerNfts( - lendingPairs, + const availablePools = useUniswapPools(lendingPairs); + const { borrowerNfts, refetchBorrowerNftRefs, refetchBorrowers } = useBorrowerNfts( + availablePools, userAddress, activeChain.id ); - const borrowerNftBorrowers = useDeprecatedMarginAccountShim(lendingPairs, borrowerNftRefs, borrowers); + const borrowerNftsDeprecated = useDeprecatedMarginAccountShim(lendingPairs, borrowerNfts); // Poll for `blockNumber` when app is in the foreground. Not much different than a `useInterval` that stops // when in the background @@ -191,32 +189,17 @@ export default function AdvancedPage() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [blockNumber]); - const selectedMarginAccount = useMemo(() => { - const marginAccountSearchParam = searchParams.get(SELECTED_MARGIN_ACCOUNT_KEY); - if (!marginAccountSearchParam) return borrowerNftBorrowers?.[0]; - return ( - borrowerNftBorrowers?.find((account) => account.address === marginAccountSearchParam) ?? borrowerNftBorrowers?.[0] - ); - }, [borrowerNftBorrowers, searchParams]); - - const market = useLendingPair( - lendingPairs, - selectedMarginAccount?.token0.address, - selectedMarginAccount?.token1.address - ); + const selection = useMemo(() => { + if (!borrowerNftsDeprecated || borrowerNftsDeprecated.length === 0) return undefined; - const availablePools = useUniswapPools(lendingPairs); + const nftId = searchParams.get(SELECTED_BORROWER_NFT_ID); + let idx = borrowerNftsDeprecated.findIndex((ref) => parseInt(ref.tokenId.slice(-4), 16).toString() === nftId); + if (idx === -1) idx = 0; - // MARK: Reset search param if margin account doesn't exist - useEffect(() => { - if ( - borrowerNftBorrowers?.length && - selectedMarginAccount?.address !== searchParams.get(SELECTED_MARGIN_ACCOUNT_KEY) - ) { - searchParams.delete(SELECTED_MARGIN_ACCOUNT_KEY); - setSearchParams(searchParams); - } - }, [borrowerNftBorrowers?.length, searchParams, selectedMarginAccount, setSearchParams]); + return borrowerNftsDeprecated[idx]; + }, [searchParams, borrowerNftsDeprecated]); + + const market = useLendingPair(lendingPairs, selection?.uniswapPool); useEffect(() => { (async () => { @@ -224,7 +207,7 @@ export default function AdvancedPage() { const fetchedUniswapNFTPositions = await fetchUniswapNFTPositions(userAddress, provider); setUniswapNFTPositions(fetchedUniswapNFTPositions); })(); - }, [provider, setUniswapNFTPositions, userAddress]); + }, [userAddress, provider, setUniswapNFTPositions]); const publicClient = usePublicClient({ chainId: activeChain.id }); useEffect(() => { @@ -243,34 +226,28 @@ export default function AdvancedPage() { })(); }, [publicClient, pendingTxn, setOpenedModal, setPendingTxnModalStatus]); - const { data: accountEtherBalanceResult } = useBalance({ - address: selectedMarginAccount?.address as Address, - chainId: activeChain.id, - query: { enabled: selectedMarginAccount !== undefined }, - }); - - const accountEtherBalance = accountEtherBalanceResult && GN.fromBigInt(accountEtherBalanceResult.value, 18); - const filteredNonZeroUniswapNFTPositions = useMemo(() => { const filteredPositions: Map = new Map(); - if (selectedMarginAccount == null) return filteredPositions; + if (!selection) return filteredPositions; + uniswapNFTPositions.forEach((position, tokenId) => { if ( - selectedMarginAccount.token0.equals(position.token0) && - selectedMarginAccount.token1.equals(position.token1) && - GetNumericFeeTier(selectedMarginAccount.feeTier) === position.fee && + position.token0.equals(selection.token0) && + position.token1.equals(selection.token1) && + position.fee === GetNumericFeeTier(selection.feeTier) && JSBI.greaterThan(position.liquidity, JSBI.BigInt('0')) ) { filteredPositions.set(tokenId, position); } }); return filteredPositions; - }, [selectedMarginAccount, uniswapNFTPositions]); + }, [selection, uniswapNFTPositions]); const withdrawableUniswapNFTPositions = useMemo(() => { const filteredPositions: Map = new Map(); - if (selectedMarginAccount == null) return filteredPositions; - selectedMarginAccount.assets.uniswapPositions.forEach((uniswapPosition) => { + if (!selection) return filteredPositions; + + selection.assets.uniswapPositions.forEach((uniswapPosition) => { const isNonZero = JSBI.greaterThan(uniswapPosition.liquidity, JSBI.BigInt('0')); const matchingNFTPosition = Array.from(uniswapNFTPositions.entries()).find(([, position]) => { return position.lower === uniswapPosition.lower && position.upper === uniswapPosition.upper; @@ -280,32 +257,22 @@ export default function AdvancedPage() { } }); return filteredPositions; - }, [selectedMarginAccount, uniswapNFTPositions]); - - const defaultPool = Array.from(availablePools.keys())[0]; + }, [selection, uniswapNFTPositions]); - const dailyInterest0 = - ((market?.kitty0Info.borrowAPR || 0) / 365) * (selectedMarginAccount?.liabilities.amount0 || 0); - const dailyInterest1 = - ((market?.kitty1Info.borrowAPR || 0) / 365) * (selectedMarginAccount?.liabilities.amount1 || 0); - - const baseEtherscanUrl = getEtherscanUrlForChain(activeChain); - const selectedMarginAccountEtherscanUrl = `${baseEtherscanUrl}/address/${selectedMarginAccount?.address}`; - const selectedBorrowerOpenseaUrl = `https://opensea.io/assets/${activeChain.name}/${ + const blockExplorerUrl = getBlockExplorerUrl(activeChain); + const selectionUrlBlockExplorer = `${blockExplorerUrl}/address/${selection?.address}`; + const selectionUrlOpensea = `https://opensea.io/assets/${activeChain.name}/${ ALOE_II_BORROWER_NFT_ADDRESS[activeChain.id] - }/${selectedMarginAccount ? ethers.BigNumber.from(selectedMarginAccount!.tokenId).toString() : ''}`; + }/${selection ? BigInt(selection.tokenId).toString(10) : ''}`; - const hasLiabilities = Object.values(selectedMarginAccount?.liabilities ?? {}).some((liability) => { - return liability > 0; - }); - - const accountHasEther = accountEtherBalance?.isGtZero() ?? false; - - const userHasNoMarginAccounts = borrowerNftBorrowers?.length === 0; + const selectionHasEther = selection?.ethBalance!.isGtZero() ?? false; + const selectionHasLiabilities = Boolean( + (selection?.liabilities.amount0 ?? 0) > 0 || (selection?.liabilities.amount1 ?? 0) > 0 + ); - const canWithdrawAnte = !hasLiabilities && accountHasEther; - const canClearWarning = - selectedMarginAccount && selectedMarginAccount.health >= 1 && selectedMarginAccount.warningTime > 0; + const userHasBorrowers = borrowerNfts.length > 0; + const canWithdrawAnte = selectionHasEther && !selectionHasLiabilities; + const canClearWarning = selection && selection.health >= 1 && selection.warningTime > 0; return ( <> @@ -330,51 +297,50 @@ export default function AdvancedPage() { onRepay={() => setOpenedModal(OpenedModal.REPAY)} onWithdrawAnte={canWithdrawAnte ? () => setOpenedModal(OpenedModal.WITHDRAW_ANTE) : undefined} onClearWarning={canClearWarning ? () => setOpenedModal(OpenedModal.CLEAR_WARNING) : undefined} - isDisabled={!selectedMarginAccount || !isConnected} + isDisabled={!selection || !isConnected} /> - {borrowerNftBorrowers?.map((account) => ( - { - // When a new account is selected, we need to update the - // selectedMarginAccount, selectedMarketInfo, and uniswapPositions - // setSelectedMarginAccount(account); - setSearchParams({ [SELECTED_MARGIN_ACCOUNT_KEY]: account.address }); - }} - key={account.address} - /> - ))} + {borrowerNftsDeprecated?.map((nft, i) => { + const tokenCounter = parseInt(nft.tokenId.slice(-4), 16); + return ( + { + setSearchParams({ [SELECTED_BORROWER_NFT_ID]: tokenCounter.toString() }); + }} + key={nft.address} + /> + ); + })} setOpenedModal(OpenedModal.NEW_SMART_WALLET)} /> - + - {selectedMarginAccount && ( + {selection && (
- + View on Etherscan @@ -382,7 +348,7 @@ export default function AdvancedPage() { - + View on OpenSea @@ -394,52 +360,49 @@ export default function AdvancedPage() { {availablePools.size > 0 && ( setOpenedModal(isOpen ? OpenedModal.NEW_SMART_WALLET : OpenedModal.NONE)} setPendingTxn={setPendingTxn} /> )} - {selectedMarginAccount && market && ( + {selection && market && ( <> setOpenedModal(isOpen ? OpenedModal.ADD_COLLATERAL : OpenedModal.NONE)} setPendingTxn={setPendingTxn} /> setOpenedModal(isOpen ? OpenedModal.REMOVE_COLLATERAL : OpenedModal.NONE)} setPendingTxn={setPendingTxn} /> setOpenedModal(isOpen ? OpenedModal.BORROW : OpenedModal.NONE)} setPendingTxn={setPendingTxn} /> setOpenedModal(isOpen ? OpenedModal.REPAY : OpenedModal.NONE)} setPendingTxn={setPendingTxn} /> setOpenedModal(isOpen ? OpenedModal.WITHDRAW_ANTE : OpenedModal.NONE)} setPendingTxn={setPendingTxn} /> setOpenedModal(isOpen ? OpenedModal.CLEAR_WARNING : OpenedModal.NONE)} setPendingTxn={setPendingTxn} diff --git a/earn/src/pages/BoostPage.tsx b/earn/src/pages/BoostPage.tsx index 29882d9cf..17bc7cf55 100644 --- a/earn/src/pages/BoostPage.tsx +++ b/earn/src/pages/BoostPage.tsx @@ -1,6 +1,5 @@ import { useEffect, useMemo, useState } from 'react'; -import { ethers } from 'ethers'; import JSBI from 'jsbi'; import { factoryAbi } from 'shared/lib/abis/Factory'; import { uniswapV3PoolAbi } from 'shared/lib/abis/UniswapV3Pool'; @@ -15,7 +14,7 @@ import useChain from 'shared/lib/hooks/UseChain'; import { useChainDependentState } from 'shared/lib/hooks/UseChainDependentState'; import { getProminentColor, rgb } from 'shared/lib/util/Colors'; import styled from 'styled-components'; -import { Address } from 'viem'; +import { Address, zeroAddress } from 'viem'; import { Config, useAccount, useClient, useReadContracts } from 'wagmi'; import BoostCard from '../components/boost/BoostCard'; @@ -261,7 +260,7 @@ export default function BoostPage() { null ); }) - .filter((info) => info.lender0 !== ethers.constants.AddressZero || info.lender1 !== ethers.constants.AddressZero); + .filter((info) => info.lender0 !== zeroAddress || info.lender1 !== zeroAddress); }, [slot0Data, uniswapNFTPositions, marketDatas, userAddress, activeChain.id, colors]); /*////////////////////////////////////////////////////////////// diff --git a/earn/src/pages/MarketsPage.tsx b/earn/src/pages/MarketsPage.tsx index e45aad96f..1e0c8d710 100644 --- a/earn/src/pages/MarketsPage.tsx +++ b/earn/src/pages/MarketsPage.tsx @@ -18,8 +18,10 @@ import { useLendingPairsBalances } from 'shared/lib/hooks/UseLendingPairBalances import { useLendingPairs } from 'shared/lib/hooks/UseLendingPairs'; import { useLatestPriceRelay } from 'shared/lib/hooks/UsePriceRelay'; import { useTokenColors } from 'shared/lib/hooks/UseTokenColors'; +import { useUniswapPools } from 'shared/lib/hooks/UseUniswapPools'; import { formatUSDAuto } from 'shared/lib/util/Numbers'; import styled from 'styled-components'; +import { zeroAddress } from 'viem'; import { base, linea } from 'viem/chains'; import { useAccount, useBlockNumber, usePublicClient } from 'wagmi'; import { useCapabilities } from 'wagmi/experimental'; @@ -29,8 +31,7 @@ import BorrowingWidget from '../components/markets/borrow/BorrowingWidget'; import LiquidateTab from '../components/markets/liquidate/LiquidateTab'; import InfoTab from '../components/markets/monitor/InfoTab'; import SupplyTable, { SupplyTableRow } from '../components/markets/supply/SupplyTable'; -import { useDeprecatedMarginAccountShim } from '../data/BorrowerNft'; -import { ZERO_ADDRESS } from '../data/constants/Addresses'; +import { useDeprecatedMarginAccountShim } from '../hooks/useDeprecatedMarginAccountShim'; const SECONDARY_COLOR = 'rgba(130, 160, 182, 1)'; const SELECTED_TAB_KEY = 'selectedTab'; @@ -107,13 +108,13 @@ export default function MarketsPage() { () => activeChain.id === base.id && (capabilities?.[84532]?.auxiliaryFunds.supported ?? false), [activeChain.id, capabilities] ); - console.log(capabilities?.[84532]?.auxiliaryFunds.supported); // MARK: custom hooks const { lendingPairs, refetchOracleData, refetchLenderData } = useLendingPairs(activeChain.id); const { data: tokenColors } = useTokenColors(lendingPairs); const { data: tokenQuotes } = useLatestPriceRelay(lendingPairs); const { balances: balancesMap, refetch: refetchBalances } = useLendingPairsBalances(lendingPairs, activeChain.id); + const uniswapPools = useUniswapPools(lendingPairs); const borrowerNftFilterParams = useMemo( () => ({ @@ -128,12 +129,11 @@ export default function MarketsPage() { [activeChain.id] ); const { - borrowerNftRefs, - borrowers: rawBorrowers, + borrowerNfts, refetchBorrowerNftRefs, refetchBorrowers, - } = useBorrowerNfts(lendingPairs, userAddress, activeChain.id, borrowerNftFilterParams); - const borrowers = useDeprecatedMarginAccountShim(lendingPairs, borrowerNftRefs, rawBorrowers); + } = useBorrowerNfts(uniswapPools, userAddress, activeChain.id, borrowerNftFilterParams); + const borrowers = useDeprecatedMarginAccountShim(lendingPairs, borrowerNfts); // Poll for `blockNumber` when app is in the foreground. Not much different than a `useInterval` that stops // when in the background @@ -184,7 +184,7 @@ export default function MarketsPage() { const supplyRows = useMemo(() => { const rows: SupplyTableRow[] = []; - const ethBalance = balancesMap.get(ZERO_ADDRESS); + const ethBalance = balancesMap.get(zeroAddress); lendingPairs.forEach((pair) => { const isToken0Weth = pair.token0.name === 'Wrapped Ether'; const isToken1Weth = pair.token1.name === 'Wrapped Ether'; diff --git a/earn/src/pages/PortfolioPage.tsx b/earn/src/pages/PortfolioPage.tsx index 408621bfb..3b3cde4ab 100644 --- a/earn/src/pages/PortfolioPage.tsx +++ b/earn/src/pages/PortfolioPage.tsx @@ -3,6 +3,7 @@ import { useEffect, useMemo, useState } from 'react'; import { type WriteContractReturnType } from '@wagmi/core'; import AppPage from 'shared/lib/components/common/AppPage'; import { Text } from 'shared/lib/components/common/Typography'; +import { RESPONSIVE_BREAKPOINT_SM, RESPONSIVE_BREAKPOINT_XS } from 'shared/lib/data/constants/Breakpoints'; import { GREY_700 } from 'shared/lib/data/constants/Colors'; import { Token } from 'shared/lib/data/Token'; import { getTokenBySymbol } from 'shared/lib/data/TokenData'; @@ -23,7 +24,6 @@ import PendingTxnModal, { PendingTxnModalStatus } from '../components/common/Pen import { AssetBar } from '../components/portfolio/AssetBar'; import { AssetBarPlaceholder } from '../components/portfolio/AssetBarPlaceholder'; import LendingPairPeerCard from '../components/portfolio/LendingPairPeerCard'; -import BridgeModal from '../components/portfolio/modal/BridgeModal'; import EarnInterestModal from '../components/portfolio/modal/EarnInterestModal'; import SendCryptoModal from '../components/portfolio/modal/SendCryptoModal'; import WithdrawModal from '../components/portfolio/modal/WithdrawModal'; @@ -31,7 +31,6 @@ import PortfolioActionButton from '../components/portfolio/PortfolioActionButton import PortfolioBalance from '../components/portfolio/PortfolioBalance'; import PortfolioGrid from '../components/portfolio/PortfolioGrid'; import PortfolioPageWidgetWrapper from '../components/portfolio/PortfolioPageWidgetWrapper'; -import { RESPONSIVE_BREAKPOINT_SM, RESPONSIVE_BREAKPOINT_XS } from '../data/constants/Breakpoints'; const ASSET_BAR_TOOLTIP_TEXT = `This bar shows the assets in your portfolio. Hover/click on a segment to see more details.`; @@ -105,7 +104,6 @@ export default function PortfolioPage() { const [isSendCryptoModalOpen, setIsSendCryptoModalOpen] = useState(false); const [isEarnInterestModalOpen, setIsEarnInterestModalOpen] = useState(false); const [isWithdrawModalOpen, setIsWithdrawModalOpen] = useState(false); - const [isBridgeModalOpen, setIsBridgeModalOpen] = useState(false); const [isPendingTxnModalOpen, setIsPendingTxnModalOpen] = useState(false); const [pendingTxnModalStatus, setPendingTxnModalStatus] = useState(null); @@ -322,9 +320,7 @@ export default function PortfolioPage() { label={'Bridge'} disabled={true} Icon={} - onClick={() => { - if (isConnected) setIsBridgeModalOpen(true); - }} + onClick={() => {}} />
@@ -379,7 +375,6 @@ export default function PortfolioPage() { setIsOpen={setIsWithdrawModalOpen} setPendingTxn={setPendingTxn} /> - )} diff --git a/prime/src/components/borrow/modal/PendingTxnModal.tsx b/prime/src/components/borrow/modal/PendingTxnModal.tsx index c30574961..80ada5126 100644 --- a/prime/src/components/borrow/modal/PendingTxnModal.tsx +++ b/prime/src/components/borrow/modal/PendingTxnModal.tsx @@ -1,7 +1,7 @@ import { LoadingModal, MESSAGE_TEXT_COLOR } from 'shared/lib/components/common/Modal'; import { Text } from 'shared/lib/components/common/Typography'; import useChain from 'shared/lib/hooks/UseChain'; -import { getEtherscanUrlForChain } from 'shared/lib/util/Chains'; +import { getBlockExplorerUrl } from 'shared/lib/util/Chains'; export type PendingTxnModalProps = { open: boolean; @@ -19,7 +19,7 @@ export default function PendingTxnModal(props: PendingTxnModalProps) { {props.txnHash && ( [c.id, []])); @@ -63,7 +62,7 @@ export const wagmiConfig = createConfig({ appName: metadata.name, // appLogoUrl: // TODO: do better than favicon chainId: DEFAULT_CHAIN.id, - preference: isDevelopment() ? 'all' : 'eoaOnly', + preference: 'all', }), safe(), ], diff --git a/shared/src/data/Borrower.ts b/shared/src/data/Borrower.ts index 3e411385d..892ca57e6 100644 --- a/shared/src/data/Borrower.ts +++ b/shared/src/data/Borrower.ts @@ -3,6 +3,7 @@ import { GN } from './GoodNumber'; import { UniswapPosition, getAmountsForLiquidity, getAmountsForLiquidityGN } from './Uniswap'; import { ALOE_II_LIQUIDATION_GRACE_PERIOD } from './constants/Values'; import { auctionCurve, computeAuctionAmounts, isHealthy } from './BalanceSheet'; +import { Token } from './Token'; export class Assets { constructor( @@ -63,7 +64,14 @@ type Data = { /** The owner of the borrower */ readonly owner: Address; /** The Uniswap pool to which the borrower belongs */ - readonly uniswapPool: Address; + readonly uniswapPool: { + readonly address: Address; + readonly fee: number; + }; + /** The Uniswap pool's `token0` */ + readonly token0: Token; + /** The Uniswap pool's `token1` */ + readonly token1: Token; }; export type Borrower = DerivedBorrower & Data; diff --git a/shared/src/hooks/UseBorrowerNft.ts b/shared/src/hooks/UseBorrowerNft.ts index ce4fb090e..90bd39448 100644 --- a/shared/src/hooks/UseBorrowerNft.ts +++ b/shared/src/hooks/UseBorrowerNft.ts @@ -1,4 +1,4 @@ -import { Address, Log, getAddress } from 'viem'; +import { Address, GetContractEventsReturnType, getAddress } from 'viem'; import { useReadContract, useReadContracts } from 'wagmi'; import { borrowerNftAbi } from '../abis/BorrowerNft'; import { ALOE_II_BORROWER_LENS_ADDRESS, ALOE_II_BORROWER_NFT_ADDRESS } from '../data/constants/ChainSpecific'; @@ -7,7 +7,8 @@ import { borrowerLensAbi } from '../abis/BorrowerLens'; import { useCallback, useMemo } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { BorrowerRef, useBorrowers } from './UseBorrowers'; -import { LendingPair } from '../data/LendingPair'; +import { UniswapPoolsMap } from './UseUniswapPools'; +import { Borrower } from '../data/Borrower'; export type BorrowerNftRef = BorrowerRef & { /// The NFT's owner @@ -17,7 +18,11 @@ export type BorrowerNftRef = BorrowerRef & { /// The NFT's index in the holder's SSTORE2 array index: number; /// The most recent `Modify` event (undefined if `modify` hasn't yet been called on the Borrower) - mostRecentModify?: Log; // TODO: fuller type info + mostRecentModify?: GetContractEventsReturnType[number]; +}; + +export type BorrowerNft = BorrowerNftRef & { + borrower?: Borrower; }; type BorrowerNftFilterParams = { @@ -193,14 +198,21 @@ export function useBorrowerNftRefs( } export function useBorrowerNfts( - lendingPairs: LendingPair[], + uniswapPools: UniswapPoolsMap, owner: Address | undefined, chainId: number, filterParams?: BorrowerNftFilterParams, staleTime = 60 * 1_000 ) { const { borrowerNftRefs, refetchBorrowerNftRefs } = useBorrowerNftRefs(owner, chainId, filterParams, staleTime); - const { borrowers, refetchBorrowers } = useBorrowers(lendingPairs, borrowerNftRefs, chainId, staleTime); + const { borrowers, refetchBorrowers } = useBorrowers(uniswapPools, borrowerNftRefs, chainId, staleTime); + + const borrowerNfts = useMemo(() => { + return borrowerNftRefs.map((borrowerNftRef, i) => ({ + ...borrowerNftRef, + borrower: borrowers.at(i), + } as BorrowerNft)); + }, [borrowerNftRefs, borrowers]); - return { borrowerNftRefs, borrowers, refetchBorrowerNftRefs, refetchBorrowers }; + return { borrowerNfts, refetchBorrowerNftRefs, refetchBorrowers }; } diff --git a/shared/src/hooks/UseBorrowers.ts b/shared/src/hooks/UseBorrowers.ts index 6536a583e..a9f0c87c7 100644 --- a/shared/src/hooks/UseBorrowers.ts +++ b/shared/src/hooks/UseBorrowers.ts @@ -1,5 +1,4 @@ import { Address } from 'viem'; -import { LendingPair } from '../data/LendingPair'; import { useReadContracts } from 'wagmi'; import { borrowerLensAbi } from '../abis/BorrowerLens'; import { ALOE_II_BORROWER_LENS_ADDRESS } from '../data/constants/ChainSpecific'; @@ -9,6 +8,7 @@ import { GN } from '../data/GoodNumber'; import JSBI from 'jsbi'; import { UniswapPosition } from '../data/Uniswap'; import { useQueryClient } from '@tanstack/react-query'; +import { UniswapPoolsMap } from './UseUniswapPools'; export type BorrowerRef = { /** @@ -27,7 +27,7 @@ export type BorrowerRef = { }; export function useBorrowers( - lendingPairs: LendingPair[], + uniswapPools: UniswapPoolsMap, borrowerRefs: BorrowerRef[], chainId: number, staleTime = 60 * 1_000 @@ -53,25 +53,14 @@ export function useBorrowers( }, }); - const lendingPairsForEvents = useMemo(() => { - let missing = false; - const res = borrowerRefs.map((ref) => { - const pair = lendingPairs.find((pair) => pair.uniswapPool.toLowerCase() === ref.uniswapPool.toLowerCase()); - if (pair === undefined) missing = true; - return pair; - }); - - if (missing) return undefined; - return res as LendingPair[]; - }, [borrowerRefs, lendingPairs]); - const borrowers = useMemo(() => { - if (summaryData === undefined || lendingPairsForEvents === undefined) return undefined; + if (summaryData === undefined) return []; return borrowerRefs.map((ref, i) => { const { uniswapPool, owner, address } = ref; const [balanceEth, balance0, balance1, liabilities0, liabilities1, slot0, liquidity] = summaryData[i]; - const pair = lendingPairsForEvents[i]; + if (!uniswapPools.has(uniswapPool)) return undefined; + const { token0, token1, fee: uniswapFee } = uniswapPools.get(uniswapPool)!; const positionTicks: { lower: number; upper: number }[] = []; for (let i = 0; i < 3; i++) { @@ -85,21 +74,26 @@ export function useBorrowers( return DerivedBorrower.from({ ethBalance: GN.fromBigInt(balanceEth, 18), assets: new Assets( - GN.fromBigInt(balance0, pair.token0.decimals), - GN.fromBigInt(balance1, pair.token1.decimals), + GN.fromBigInt(balance0, token0.decimals), + GN.fromBigInt(balance1, token1.decimals), positionTicks.map((v, i) => ({ ...v, liquidity: JSBI.BigInt(liquidity[i].toString()) } as UniswapPosition)) ), liabilities: { - amount0: GN.fromBigInt(liabilities0, pair.token0.decimals), - amount1: GN.fromBigInt(liabilities1, pair.token1.decimals), + amount0: GN.fromBigInt(liabilities0, token0.decimals), + amount1: GN.fromBigInt(liabilities1, token1.decimals), }, slot0, - address: address!, - owner: owner!, - uniswapPool: uniswapPool!, + address, + owner, + uniswapPool: { + address: uniswapPool, + fee: uniswapFee, + }, + token0, + token1, }); }); - }, [borrowerRefs, lendingPairsForEvents, summaryData]); + }, [uniswapPools, borrowerRefs, summaryData]); const queryClient = useQueryClient(); const refetchBorrowers = useCallback(() => { diff --git a/shared/src/hooks/UseLendingPairs.ts b/shared/src/hooks/UseLendingPairs.ts index 8893adbbb..b3d427b8d 100644 --- a/shared/src/hooks/UseLendingPairs.ts +++ b/shared/src/hooks/UseLendingPairs.ts @@ -41,14 +41,19 @@ export function useLendingPairs(chainId: number) { strict: true, }); - const query = { + const refetchManually = { enabled: !isFetching, staleTime: Infinity, - refetchOnMount: 'always', + refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false, } as const; + const refetchManuallyAndOnMount = { + ...refetchManually, + refetchOnMount: 'always', + } as const; + // Get fee tier of each Uniswap pool that has a market. Only refreshes when `logs` changes. const { data: feeData } = useReadContracts({ contracts: logs?.map( @@ -61,13 +66,7 @@ export function useLendingPairs(chainId: number) { } as const) ), allowFailure: false, - query: { - enabled: !isFetching, - staleTime: Infinity, - refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }, + query: refetchManually, }); // Get factory parameters for each market. Only refreshes on page load. @@ -81,7 +80,7 @@ export function useLendingPairs(chainId: number) { } as const) ), allowFailure: false, - query, + query: refetchManuallyAndOnMount, }); // Get instantaneous price and other oracle data for each market. Refreshes on page load and/or manually. @@ -107,7 +106,7 @@ export function useLendingPairs(chainId: number) { ]) .flat(), allowFailure: false, - query, + query: refetchManuallyAndOnMount, }); // Get main data for each market. Refreshes on page load and/or manually. @@ -131,7 +130,7 @@ export function useLendingPairs(chainId: number) { ) ?? []), ], allowFailure: false, - query, + query: refetchManuallyAndOnMount, }); const queryClient = useQueryClient(); @@ -199,14 +198,16 @@ export function useLendingPairs(chainId: number) { }; } -export function useLendingPair(lendingPairs: LendingPair[], token0?: Address, token1?: Address) { - return useMemo( - () => - lendingPairs.find( +export function useLendingPair(lendingPairs: LendingPair[], pool?: Address, token0?: Address, token1?: Address) { + return useMemo(() => { + if (pool) { + return lendingPairs.find((pair) => pool.toLowerCase() === pair.uniswapPool.toLowerCase()); + } else { + return lendingPairs.find( (pair) => pair.token0.address.toLowerCase() === token0?.toLowerCase() && pair.token1.address.toLowerCase() === token1?.toLowerCase() - ), - [lendingPairs, token0, token1] - ); + ); + } + }, [lendingPairs, pool, token0, token1]); } diff --git a/shared/src/hooks/UseLocalStorage.ts b/shared/src/hooks/UseLocalStorage.ts index e5f12c912..7e86ed685 100644 --- a/shared/src/hooks/UseLocalStorage.ts +++ b/shared/src/hooks/UseLocalStorage.ts @@ -9,19 +9,19 @@ export default function useLocalStorage(key: string, initialValue: T) { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { - console.error(error); + console.error('Error retrieving value from local storage:', error); return initialValue; } }); - const setValue = (value: T | ((val: T) => T)) => { + const setValue = (valueOrOperator: T | ((x: T) => T)) => { try { - const valueToStore = value instanceof Function ? value(storedValue) : value; - setStoredValue(valueToStore); + const value = valueOrOperator instanceof Function ? valueOrOperator(storedValue) : valueOrOperator; + setStoredValue(value); if (typeof window !== 'undefined') { - window.localStorage.setItem(key, JSON.stringify(valueToStore)); + window.localStorage.setItem(key, JSON.stringify(value)); } } catch (error) { - console.error(error); + console.error('Error setting value in local storage:', error); } }; return [storedValue, setValue] as const; diff --git a/shared/src/hooks/UseUniswapPools.ts b/shared/src/hooks/UseUniswapPools.ts index 731f21b65..caf9d1bcb 100644 --- a/shared/src/hooks/UseUniswapPools.ts +++ b/shared/src/hooks/UseUniswapPools.ts @@ -4,7 +4,7 @@ import { Token } from '../data/Token'; import { GetNumericFeeTier } from '../data/FeeTier'; import { Address } from 'viem'; -type UniswapPoolsMap = Map; +export type UniswapPoolsMap = Map; /** * Isolates Uniswap pool changes from other `lendingPairs` changes to prevent unnecessary renders/fetches. diff --git a/shared/src/util/Chains.ts b/shared/src/util/Chains.ts index 0b0fa87ca..ee0f83417 100644 --- a/shared/src/util/Chains.ts +++ b/shared/src/util/Chains.ts @@ -6,6 +6,6 @@ import { DEFAULT_ETHERSCAN_URL } from '../data/constants/Values'; * @param chain the chain to get the Etherscan url for * @returns the Etherscan url for the given chain */ -export function getEtherscanUrlForChain(chain: Chain): string { +export function getBlockExplorerUrl(chain: Chain): string { return chain.blockExplorers?.default.url ?? DEFAULT_ETHERSCAN_URL; }