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,