From 65367c8c927bd6455a22ea9cecb41750f948562d Mon Sep 17 00:00:00 2001 From: ianwoodard <17186604+IanWoodard@users.noreply.github.com> Date: Thu, 2 Nov 2023 22:33:27 -0700 Subject: [PATCH 1/3] Starting to add fuse2 borrows --- earn/src/components/lend/BorrowingWidget.tsx | 425 +++++++++--------- .../src/components/lend/modal/BorrowModal.tsx | 126 ++++++ earn/src/data/LendingPair.ts | 4 +- shared/src/data/TokenData.ts | 12 +- 4 files changed, 361 insertions(+), 206 deletions(-) create mode 100644 earn/src/components/lend/modal/BorrowModal.tsx diff --git a/earn/src/components/lend/BorrowingWidget.tsx b/earn/src/components/lend/BorrowingWidget.tsx index a38fe083..15bf59ff 100644 --- a/earn/src/components/lend/BorrowingWidget.tsx +++ b/earn/src/components/lend/BorrowingWidget.tsx @@ -10,6 +10,7 @@ import { ALOE_II_LIQUIDATION_INCENTIVE, ALOE_II_MAX_LEVERAGE } from '../../data/ import { LendingPair } from '../../data/LendingPair'; import { MarginAccount } from '../../data/MarginAccount'; import { rgba } from '../../util/Colors'; +import BorrowModal from './modal/BorrowModal'; const SECONDARY_COLOR = 'rgba(130, 160, 182, 1)'; const SECONDARY_COLOR_LIGHT = 'rgba(130, 160, 182, 0.1)'; @@ -167,216 +168,232 @@ export default function BorrowingWidget(props: BorrowingWidgetProps) { }, [collateralEntries, selectedBorrows]); return ( -
- - Collateral - - - - - Active - - -
- {marginAccounts && - marginAccounts.map((account) => { - const hasAssetsForToken0 = account.assets.token0Raw > 0; - const hasAssetsForToken1 = account.assets.token1Raw > 0; - const hasLiabilitiesForToken0 = account.liabilities.amount0 > 0; - const hasLiabilitiesForToken1 = account.liabilities.amount1 > 0; - const AvailableContainerToken0 = - hasAssetsForToken0 && !hasLiabilitiesForToken1 - ? AvailableContainer - : AvailableContainerConnectedLeft; - const AvailableContainerToken1 = - hasAssetsForToken1 && !hasLiabilitiesForToken0 - ? AvailableContainer - : AvailableContainerConnectedRight; - const fact = 1 + 1 / ALOE_II_MAX_LEVERAGE + 1 / ALOE_II_LIQUIDATION_INCENTIVE; - let ltv = 1 / (Math.exp((account.nSigma * account.iv) / 10) * fact); - ltv = Math.max(0.1, Math.min(ltv, 0.9)) * 100; - const token0Color = tokenColors.get(account.token0.address); - const token0Gradient = token0Color - ? `linear-gradient(90deg, ${rgba(token0Color, 0.25)} 0%, ${GREY_700} 100%)` - : undefined; - const token1Color = tokenColors.get(account.token1.address); - const token1Gradient = token1Color - ? `linear-gradient(90deg, ${rgba(token1Color, 0.25)} 0%, ${GREY_700} 100%)` - : undefined; + <> +
+ + Collateral + + + + + Active + + +
+ {marginAccounts && + marginAccounts.map((account) => { + const hasAssetsForToken0 = account.assets.token0Raw > 0; + const hasAssetsForToken1 = account.assets.token1Raw > 0; + const hasLiabilitiesForToken0 = account.liabilities.amount0 > 0; + const hasLiabilitiesForToken1 = account.liabilities.amount1 > 0; + const AvailableContainerToken0 = + hasAssetsForToken0 && !hasLiabilitiesForToken1 + ? AvailableContainer + : AvailableContainerConnectedLeft; + const AvailableContainerToken1 = + hasAssetsForToken1 && !hasLiabilitiesForToken0 + ? AvailableContainer + : AvailableContainerConnectedRight; + const fact = 1 + 1 / ALOE_II_MAX_LEVERAGE + 1 / ALOE_II_LIQUIDATION_INCENTIVE; + let ltv = 1 / (Math.exp((account.nSigma * account.iv) / 10) * fact); + ltv = Math.max(0.1, Math.min(ltv, 0.9)) * 100; + const token0Color = tokenColors.get(account.token0.address); + const token0Gradient = token0Color + ? `linear-gradient(90deg, ${rgba(token0Color, 0.25)} 0%, ${GREY_700} 100%)` + : undefined; + const token1Color = tokenColors.get(account.token1.address); + const token1Gradient = token1Color + ? `linear-gradient(90deg, ${rgba(token1Color, 0.25)} 0%, ${GREY_700} 100%)` + : undefined; + return ( + + {hasAssetsForToken0 && ( + +
+ {account.assets.token0Raw} + {account.token0.symbol} +
+ {roundPercentage(ltv, 3)}% LTV +
+ )} + {hasLiabilitiesForToken0 && !hasAssetsForToken1 && } + {hasAssetsForToken1 && ( + +
+ {account.assets.token1Raw} + {account.token1.symbol} +
+ {roundPercentage(ltv, 3)}% LTV +
+ )} + {hasLiabilitiesForToken1 && !hasAssetsForToken0 && } +
+ ); + })} +
+
+ + + + Available + + { + setSelectedCollateral(null); + }} + > + Clear + + +
+ {filteredCollateralEntries.map((entry, index) => { + const minLtv = entry.matchingPairs.reduce( + (min, current) => Math.min(current.ltv * 100, min), + Infinity + ); + const maxLtv = entry.matchingPairs.reduce( + (max, current) => Math.max(current.ltv * 100, max), + -Infinity + ); + const roundedLtvs = [minLtv, maxLtv].map((ltv) => Math.round(ltv)); + const areLtvsEqual = roundedLtvs[0] === roundedLtvs[1]; + const ltvText = areLtvsEqual ? `${roundedLtvs[0]}% LTV` : `${roundedLtvs[0]}-${roundedLtvs[1]}% LTV`; return ( - - {hasAssetsForToken0 && ( - -
- {account.assets.token0Raw} - {account.token0.symbol} -
- {roundPercentage(ltv, 3)}% LTV -
- )} - {hasLiabilitiesForToken0 && !hasAssetsForToken1 && } - {hasAssetsForToken1 && ( - -
- {account.assets.token1Raw} - {account.token1.symbol} -
- {roundPercentage(ltv, 3)}% LTV -
- )} - {hasLiabilitiesForToken1 && !hasAssetsForToken0 && } -
+ { + setSelectedCollateral(entry); + }} + className={selectedCollateral === entry ? 'selected' : ''} + > +
+ {formatTokenAmount(entry.balance)} + {entry.asset.symbol} +
+ {ltvText} +
); })} -
-
- - - - Available - - { - setSelectedCollateral(null); - }} - > - Clear - - -
- {filteredCollateralEntries.map((entry, index) => { - const minLtv = entry.matchingPairs.reduce((min, current) => Math.min(current.ltv * 100, min), Infinity); - const maxLtv = entry.matchingPairs.reduce( - (max, current) => Math.max(current.ltv * 100, max), - -Infinity - ); - const roundedLtvs = [minLtv, maxLtv].map((ltv) => Math.round(ltv)); - const areLtvsEqual = roundedLtvs[0] === roundedLtvs[1]; - const ltvText = areLtvsEqual ? `${roundedLtvs[0]}% LTV` : `${roundedLtvs[0]}-${roundedLtvs[1]}% LTV`; - return ( - { - setSelectedCollateral(entry); - }} - className={selectedCollateral === entry ? 'selected' : ''} - > -
- {formatTokenAmount(entry.balance)} - {entry.asset.symbol} -
- {ltvText} -
- ); - })} -
-
-
-
- - Borrows - - - - - Active - - -
- {marginAccounts && - marginAccounts.map((account) => { - const hasAssetsForToken0 = account.assets.token0Raw > 0; - const hasAssetsForToken1 = account.assets.token1Raw > 0; - const hasLiabilitiesForToken0 = account.liabilities.amount0 > 0; - const hasLiabilitiesForToken1 = account.liabilities.amount1 > 0; - const token0Color = tokenColors.get(account.token0.address); - const token0Gradient = token0Color - ? `linear-gradient(90deg, ${GREY_700} 0%, ${rgba(token0Color, 0.25)} 100%)` - : undefined; - const token1Color = tokenColors.get(account.token1.address); - const token1Gradient = token1Color - ? `linear-gradient(90deg, ${GREY_700} 0%, ${rgba(token1Color, 0.25)} 100%)` - : undefined; - return ( - - {hasLiabilitiesForToken0 && ( - - - 3% APY - -
- - {formatTokenAmount(account.liabilities.amount0)} +
+ + + + + Borrows + + + + + Active + + +
+ {marginAccounts && + marginAccounts.map((account) => { + const hasAssetsForToken0 = account.assets.token0Raw > 0; + const hasAssetsForToken1 = account.assets.token1Raw > 0; + const hasLiabilitiesForToken0 = account.liabilities.amount0 > 0; + const hasLiabilitiesForToken1 = account.liabilities.amount1 > 0; + const token0Color = tokenColors.get(account.token0.address); + const token0Gradient = token0Color + ? `linear-gradient(90deg, ${GREY_700} 0%, ${rgba(token0Color, 0.25)} 100%)` + : undefined; + const token1Color = tokenColors.get(account.token1.address); + const token1Gradient = token1Color + ? `linear-gradient(90deg, ${GREY_700} 0%, ${rgba(token1Color, 0.25)} 100%)` + : undefined; + return ( + + {hasLiabilitiesForToken0 && ( + + + 3% APY - - {account.token0.symbol} +
+ + {formatTokenAmount(account.liabilities.amount0)} + + + {account.token0.symbol} + +
+
+ )} + {hasAssetsForToken0 && !hasLiabilitiesForToken1 && } + {hasLiabilitiesForToken1 && ( + + + 3% APY -
-
- )} - {hasAssetsForToken0 && !hasLiabilitiesForToken1 && } - {hasLiabilitiesForToken1 && ( - - - 3% APY - -
- - {formatTokenAmount(account.liabilities.amount1)} - - - {account.token1.symbol} - -
-
- )} - {hasAssetsForToken1 && !hasLiabilitiesForToken0 && } -
+
+ + {formatTokenAmount(account.liabilities.amount1)} + + + {account.token1.symbol} + +
+ + )} + {hasAssetsForToken1 && !hasLiabilitiesForToken0 && } + + ); + })} +
+
+ + + { + setSelectedBorrows(null); + }} + > + Clear + + + Available + + +
+ {Object.entries(filteredBorrowEntries).map(([key, entry]) => { + const minApy = entry.reduce((min, current) => (current.apy < min ? current.apy : min), Infinity); + const maxApy = entry.reduce((max, current) => (current.apy > max ? current.apy : max), -Infinity); + const roundedApys = [minApy, maxApy].map((apy) => Math.round(apy * 100) / 100); + const areApysEqual = roundedApys[0] === roundedApys[1]; + const apyText = areApysEqual ? `${roundedApys[0]}% APY` : `${roundedApys[0]}-${roundedApys[1]}% APY`; + const isSelected = + selectedBorrows != null && selectedBorrows.some((borrow) => borrow.asset.symbol === key); + return ( + { + setSelectedBorrows(entry); + }} + > + {apyText} + {key} + ); })} -
-
- - - { - setSelectedBorrows(null); - }} - > - Clear - - - Available - - -
- {Object.entries(filteredBorrowEntries).map(([key, entry]) => { - const minApy = entry.reduce((min, current) => (current.apy < min ? current.apy : min), Infinity); - const maxApy = entry.reduce((max, current) => (current.apy > max ? current.apy : max), -Infinity); - const roundedApys = [minApy, maxApy].map((apy) => Math.round(apy * 100) / 100); - const areApysEqual = roundedApys[0] === roundedApys[1]; - const apyText = areApysEqual ? `${roundedApys[0]}% APY` : `${roundedApys[0]}-${roundedApys[1]}% APY`; - const isSelected = - selectedBorrows != null && selectedBorrows.some((borrow) => borrow.asset.symbol === key); - return ( - { - setSelectedBorrows(entry); - }} - > - {apyText} - {key} - - ); - })} -
-
-
-
-
+
+
+
+
+
+ {selectedBorrows != null && selectedCollateral != null && ( + { + setSelectedBorrows(null); + setSelectedCollateral(null); + }} + /> + )} + ); } diff --git a/earn/src/components/lend/modal/BorrowModal.tsx b/earn/src/components/lend/modal/BorrowModal.tsx new file mode 100644 index 00000000..e2394b4d --- /dev/null +++ b/earn/src/components/lend/modal/BorrowModal.tsx @@ -0,0 +1,126 @@ +import { useState } from 'react'; + +import { FilledGradientButton } from 'shared/lib/components/common/Buttons'; +import Modal from 'shared/lib/components/common/Modal'; +import TokenAmountInput from 'shared/lib/components/common/TokenAmountInput'; +import { Text } from 'shared/lib/components/common/Typography'; +import { GN, GNFormat } from 'shared/lib/data/GoodNumber'; +import { formatNumberInput } from 'shared/lib/util/Numbers'; + +import { BorrowEntry, CollateralEntry } from '../BorrowingWidget'; + +// const SECONDARY_COLOR = 'rgba(130, 160, 182, 1)'; + +enum ConfirmButtonState { + WAITING_FOR_USER, + READY, + LOADING, + INSUFFICIENT_ASSET, + DISABLED, +} + +function getConfirmButton(state: ConfirmButtonState): { text: string; enabled: boolean } { + switch (state) { + case ConfirmButtonState.WAITING_FOR_USER: + return { text: 'Check Wallet', enabled: false }; + case ConfirmButtonState.READY: + return { text: 'Confirm', enabled: true }; + case ConfirmButtonState.LOADING: + return { text: 'Loading', enabled: false }; + case ConfirmButtonState.INSUFFICIENT_ASSET: + return { text: 'Insufficient Asset', enabled: false }; + case ConfirmButtonState.DISABLED: + default: + return { text: 'Confirm', enabled: false }; + } +} + +export type BorrowModalProps = { + isOpen: boolean; + selectedBorrows: BorrowEntry[]; + selectedCollateral: CollateralEntry; + setIsOpen: (isOpen: boolean) => void; +}; + +export default function BorrowModal(props: BorrowModalProps) { + const { isOpen, selectedBorrows, selectedCollateral, setIsOpen } = props; + const [collateralAmountStr, setCollateralAmountStr] = useState(''); + const [borrowAmountStr, setBorrowAmountStr] = useState(''); + // const { activeChain } = useContext(ChainContext); + // const { address: userAddress } = useAccount(); + + const selectedBorrow = selectedBorrows.find( + (borrow) => borrow.collateral.address === selectedCollateral.asset.address + ); + + // const selectedLendingPair = selectedCollateral.matchingPairs.find( + // (pair) => + // pair.token0.address === selectedBorrow?.asset.address || pair.token1.address === selectedBorrow?.asset.address + // ); + + // const { data: consultData } = useContractRead({ + // abi: volatilityOracleAbi, + // address: ALOE_II_ORACLE_ADDRESS[activeChain.id], + // args: [selectedLendingPair?.uniswapPool || '0x', Q32], + // functionName: 'consult', + // enabled: selectedLendingPair !== undefined, + // }); + + const userBalance = GN.fromNumber(selectedCollateral.balance, selectedCollateral.asset.decimals); + const collateralAmount = GN.fromDecimalString(collateralAmountStr || '0', selectedCollateral.asset.decimals); + + let confirmButtonState: ConfirmButtonState; + if (collateralAmount.gt(userBalance)) { + confirmButtonState = ConfirmButtonState.INSUFFICIENT_ASSET; + } else if (collateralAmountStr === '') { + confirmButtonState = ConfirmButtonState.DISABLED; + } else { + confirmButtonState = ConfirmButtonState.READY; + } + + const confirmButton = getConfirmButton(confirmButtonState); + + if (!selectedBorrow) return null; + + return ( + +
+
+ + Collateral + + { + const output = formatNumberInput(value); + if (output != null) { + setCollateralAmountStr(output); + } + }} + /> +
+
+ + Borrow + + { + const output = formatNumberInput(value); + if (output != null) { + setBorrowAmountStr(output); + } + }} + /> + + {confirmButton.text} + +
+
+
+ ); +} diff --git a/earn/src/data/LendingPair.ts b/earn/src/data/LendingPair.ts index ba2daa0b..551d12aa 100644 --- a/earn/src/data/LendingPair.ts +++ b/earn/src/data/LendingPair.ts @@ -44,6 +44,7 @@ export class LendingPair { public kitty1: Kitty, public kitty0Info: KittyInfo, public kitty1Info: KittyInfo, + public uniswapPool: Address, public uniswapFeeTier: FeeTier, public iv: number, public nSigma: number, @@ -175,7 +176,7 @@ export async function getAvailableLendingPairs( const lendingPairs: LendingPair[] = []; - correspondingLendingPairResults.forEach((value) => { + Array.from(correspondingLendingPairResults.entries()).forEach(([uniswapPool, value]) => { const { basics: basicsResults, feeTier: feeTierResults, oracle: oracleResults, factory: factoryResults } = value; const basicsReturnContexts = convertBigNumbersForReturnContexts(basicsResults.callsReturnContext); const feeTierReturnContexts = convertBigNumbersForReturnContexts(feeTierResults.callsReturnContext); @@ -256,6 +257,7 @@ export async function getAvailableLendingPairs( totalSupply: totalSupply1, utilization: utilization1 * 100.0, // Percentage }, + uniswapPool as Address, NumericFeeTierToEnum(feeTier[0]), iv * Math.sqrt(365), nSigma, diff --git a/shared/src/data/TokenData.ts b/shared/src/data/TokenData.ts index 89d17524..a373c29d 100644 --- a/shared/src/data/TokenData.ts +++ b/shared/src/data/TokenData.ts @@ -124,10 +124,19 @@ const UNI_OPTIMISM = new Token( UniLogo ); -const USDC_OPTIMISM = new Token( +const BRIDGED_USDC_OPTIMISM = new Token( optimism.id, '0x7f5c764cbc14f9669b88837ca1490cca17c31607', 6, + 'USDC.e', + 'USD Coin', + UsdcLogo +); + +const USDC_OPTIMISM = new Token( + optimism.id, + '0x0b2c639c533813f4aa9d7837caf62653d097ff85', + 6, 'USDC', 'USD Coin', UsdcLogo @@ -291,6 +300,7 @@ const TOKEN_DATA: { [chainId: number]: { [address: Address]: Token } } = { [WETH_GOERLI.address]: WETH_GOERLI, }, [optimism.id]: { + [BRIDGED_USDC_OPTIMISM.address]: BRIDGED_USDC_OPTIMISM, [DAI_OPTIMISM.address]: DAI_OPTIMISM, [FRAX_OPTIMISM.address]: FRAX_OPTIMISM, [LYRA_OPTIMISM.address]: LYRA_OPTIMISM, From 021a426a1dc7d87951b12786afbf931da621e0f9 Mon Sep 17 00:00:00 2001 From: ianwoodard <17186604+IanWoodard@users.noreply.github.com> Date: Sat, 4 Nov 2023 20:36:01 -0700 Subject: [PATCH 2/3] Adding max borrows --- .../src/components/lend/modal/BorrowModal.tsx | 113 +++++++++++++----- 1 file changed, 84 insertions(+), 29 deletions(-) diff --git a/earn/src/components/lend/modal/BorrowModal.tsx b/earn/src/components/lend/modal/BorrowModal.tsx index e2394b4d..472d58ad 100644 --- a/earn/src/components/lend/modal/BorrowModal.tsx +++ b/earn/src/components/lend/modal/BorrowModal.tsx @@ -1,16 +1,22 @@ -import { useState } from 'react'; +import { useContext, useMemo, useState } from 'react'; +import { BigNumber } from 'ethers'; +import { volatilityOracleAbi } from 'shared/lib/abis/VolatilityOracle'; import { FilledGradientButton } from 'shared/lib/components/common/Buttons'; +import { SquareInputWithMax } from 'shared/lib/components/common/Input'; import Modal from 'shared/lib/components/common/Modal'; import TokenAmountInput from 'shared/lib/components/common/TokenAmountInput'; import { Text } from 'shared/lib/components/common/Typography'; +import { ALOE_II_ORACLE_ADDRESS } from 'shared/lib/data/constants/ChainSpecific'; +import { Q32 } from 'shared/lib/data/constants/Values'; import { GN, GNFormat } from 'shared/lib/data/GoodNumber'; import { formatNumberInput } from 'shared/lib/util/Numbers'; +import { useContractRead } from 'wagmi'; +import { ChainContext } from '../../../App'; +import { ALOE_II_LIQUIDATION_INCENTIVE, ALOE_II_MAX_LEVERAGE } from '../../../data/constants/Values'; import { BorrowEntry, CollateralEntry } from '../BorrowingWidget'; -// const SECONDARY_COLOR = 'rgba(130, 160, 182, 1)'; - enum ConfirmButtonState { WAITING_FOR_USER, READY, @@ -46,28 +52,62 @@ export default function BorrowModal(props: BorrowModalProps) { const { isOpen, selectedBorrows, selectedCollateral, setIsOpen } = props; const [collateralAmountStr, setCollateralAmountStr] = useState(''); const [borrowAmountStr, setBorrowAmountStr] = useState(''); - // const { activeChain } = useContext(ChainContext); - // const { address: userAddress } = useAccount(); + const { activeChain } = useContext(ChainContext); const selectedBorrow = selectedBorrows.find( (borrow) => borrow.collateral.address === selectedCollateral.asset.address ); - // const selectedLendingPair = selectedCollateral.matchingPairs.find( - // (pair) => - // pair.token0.address === selectedBorrow?.asset.address || pair.token1.address === selectedBorrow?.asset.address - // ); + const selectedLendingPair = selectedCollateral.matchingPairs.find( + (pair) => + pair.token0.address === selectedBorrow?.asset.address || pair.token1.address === selectedBorrow?.asset.address + ); - // const { data: consultData } = useContractRead({ - // abi: volatilityOracleAbi, - // address: ALOE_II_ORACLE_ADDRESS[activeChain.id], - // args: [selectedLendingPair?.uniswapPool || '0x', Q32], - // functionName: 'consult', - // enabled: selectedLendingPair !== undefined, - // }); + const { data: consultData } = useContractRead({ + abi: volatilityOracleAbi, + address: ALOE_II_ORACLE_ADDRESS[activeChain.id], + args: [selectedLendingPair?.uniswapPool || '0x', Q32], + functionName: 'consult', + enabled: selectedLendingPair !== undefined, + }); const userBalance = GN.fromNumber(selectedCollateral.balance, selectedCollateral.asset.decimals); const collateralAmount = GN.fromDecimalString(collateralAmountStr || '0', selectedCollateral.asset.decimals); + const borrowAmount = GN.fromDecimalString(borrowAmountStr || '0', selectedBorrow?.asset.decimals ?? 0); + + const maxBorrowAmount = useMemo(() => { + if (consultData === undefined || selectedBorrow === undefined) { + return null; + } + const sqrtPriceX96 = GN.fromBigNumber(consultData?.[1] ?? BigNumber.from('0'), 96, 2); + const nSigma = selectedLendingPair?.nSigma ?? 0; + const iv = consultData[2].div(1e6).toNumber() / 1e6; + let ltv = 1 / ((1 + 1 / ALOE_II_MAX_LEVERAGE + 1 / ALOE_II_LIQUIDATION_INCENTIVE) * Math.exp(nSigma * iv)); + ltv = Math.max(0.1, Math.min(ltv, 0.9)); + + let inTermsOfBorrow = collateralAmount; + if (selectedLendingPair?.token0.address === selectedCollateral.asset.address) { + inTermsOfBorrow = inTermsOfBorrow + .mul(sqrtPriceX96) + .mul(sqrtPriceX96) + .setResolution(selectedBorrow.asset.decimals); + } else { + inTermsOfBorrow = inTermsOfBorrow + .div(sqrtPriceX96) + .div(sqrtPriceX96) + .setResolution(selectedBorrow.asset.decimals); + } + const maxBorrowSupply = GN.fromNumber(selectedBorrow.supply, selectedBorrow.asset.decimals); + const maxPotentialBorrow = inTermsOfBorrow.recklessMul(ltv); + return GN.min(maxPotentialBorrow, maxBorrowSupply).recklessMul(0.8); + }, [ + consultData, + selectedBorrow, + selectedLendingPair?.nSigma, + selectedLendingPair?.token0.address, + collateralAmount, + selectedCollateral.asset.address, + ]); let confirmButtonState: ConfirmButtonState; if (collateralAmount.gt(userBalance)) { @@ -106,20 +146,35 @@ export default function BorrowModal(props: BorrowModalProps) { Borrow - { - const output = formatNumberInput(value); - if (output != null) { - setBorrowAmountStr(output); - } - }} - /> - - {confirmButton.text} - +
+ + {selectedBorrow.asset.symbol} + + ) => { + const output = formatNumberInput(event.target.value); + if (output != null) { + setBorrowAmountStr(output); + } + }} + value={borrowAmountStr} + onMaxClick={() => { + if (maxBorrowAmount) { + setBorrowAmountStr(maxBorrowAmount.toString(GNFormat.DECIMAL)); + } + }} + maxDisabled={maxBorrowAmount === null || borrowAmount.eq(maxBorrowAmount)} + maxButtonText='80% Max' + placeholder='0.00' + fullWidth={true} + inputClassName={borrowAmountStr !== '' ? 'active' : ''} + /> +
+ + {confirmButton.text} + ); From 9ef52b59ea587d21cb84492e9fd1dcea7e817af8 Mon Sep 17 00:00:00 2001 From: ianwoodard <17186604+IanWoodard@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:52:19 -0800 Subject: [PATCH 3/3] Addressing review feedback --- earn/src/components/lend/BorrowingWidget.tsx | 2 ++ earn/src/components/lend/modal/BorrowModal.tsx | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/earn/src/components/lend/BorrowingWidget.tsx b/earn/src/components/lend/BorrowingWidget.tsx index 15bf59ff..2e9d9d68 100644 --- a/earn/src/components/lend/BorrowingWidget.tsx +++ b/earn/src/components/lend/BorrowingWidget.tsx @@ -206,6 +206,7 @@ export default function BorrowingWidget(props: BorrowingWidgetProps) { ? `linear-gradient(90deg, ${rgba(token1Color, 0.25)} 0%, ${GREY_700} 100%)` : undefined; return ( + // TODO: use borrowerNFT id as key {hasAssetsForToken0 && ( @@ -304,6 +305,7 @@ export default function BorrowingWidget(props: BorrowingWidgetProps) { ? `linear-gradient(90deg, ${GREY_700} 0%, ${rgba(token1Color, 0.25)} 100%)` : undefined; return ( + // TODO: use borrowerNFT id as key {hasLiabilitiesForToken0 && ( diff --git a/earn/src/components/lend/modal/BorrowModal.tsx b/earn/src/components/lend/modal/BorrowModal.tsx index 472d58ad..b4cc7833 100644 --- a/earn/src/components/lend/modal/BorrowModal.tsx +++ b/earn/src/components/lend/modal/BorrowModal.tsx @@ -17,6 +17,8 @@ import { ChainContext } from '../../../App'; import { ALOE_II_LIQUIDATION_INCENTIVE, ALOE_II_MAX_LEVERAGE } from '../../../data/constants/Values'; import { BorrowEntry, CollateralEntry } from '../BorrowingWidget'; +const MAX_BORROW_PERCENTAGE = 0.8; + enum ConfirmButtonState { WAITING_FOR_USER, READY, @@ -59,8 +61,7 @@ export default function BorrowModal(props: BorrowModalProps) { ); const selectedLendingPair = selectedCollateral.matchingPairs.find( - (pair) => - pair.token0.address === selectedBorrow?.asset.address || pair.token1.address === selectedBorrow?.asset.address + (pair) => selectedBorrow?.asset?.equals(pair.token0) || selectedBorrow?.asset?.equals(pair.token1) ); const { data: consultData } = useContractRead({ @@ -97,9 +98,9 @@ export default function BorrowModal(props: BorrowModalProps) { .div(sqrtPriceX96) .setResolution(selectedBorrow.asset.decimals); } - const maxBorrowSupply = GN.fromNumber(selectedBorrow.supply, selectedBorrow.asset.decimals); - const maxPotentialBorrow = inTermsOfBorrow.recklessMul(ltv); - return GN.min(maxPotentialBorrow, maxBorrowSupply).recklessMul(0.8); + const maxBorrowSupplyConstraint = GN.fromNumber(selectedBorrow.supply, selectedBorrow.asset.decimals); + const maxBorrowHealthConstraint = inTermsOfBorrow.recklessMul(ltv); + return GN.min(maxBorrowSupplyConstraint, maxBorrowHealthConstraint).recklessMul(MAX_BORROW_PERCENTAGE); }, [ consultData, selectedBorrow,