diff --git a/package.json b/package.json index abb103f1..56f309f9 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "@walletconnect/web3-provider": "1.1.1-alpha.0", "@web3-react/core": "^6.0.9", "@web3-react/injected-connector": "^6.0.7", - "@web3-react/walletconnect-connector": "^6.1.1", - "@web3-react/walletlink-connector": "^6.0.9", + "@web3-react/walletconnect-connector": "6.2.4", + "@web3-react/walletlink-connector": "6.2.3", "async-retry": "^1.3.1", "axios": "^0.20.0", "classnames": "^2.2.6", diff --git a/src/components/SafeOperations/SafeBody.tsx b/src/components/SafeOperations/SafeBody.tsx index 19f3f665..d48b5206 100644 --- a/src/components/SafeOperations/SafeBody.tsx +++ b/src/components/SafeOperations/SafeBody.tsx @@ -790,6 +790,7 @@ const Value = styled.div` const Body = styled.div` padding: 20px; + position: relative; ` const Footer = styled.div` diff --git a/src/containers/OnBoarding/SafeDetails.tsx b/src/containers/OnBoarding/SafeDetails.tsx index 9b12e619..657e5bc9 100644 --- a/src/containers/OnBoarding/SafeDetails.tsx +++ b/src/containers/OnBoarding/SafeDetails.tsx @@ -1,7 +1,7 @@ -import React, { useEffect, useState } from 'react' +import React, { useCallback, useEffect, useState } from 'react' import { Link2 } from 'react-feather' import { useTranslation } from 'react-i18next' -import { useHistory } from 'react-router' +import { useHistory } from 'react-router-dom' import styled from 'styled-components' import AlertLabel from '../../components/AlertLabel' import Button from '../../components/Button' @@ -25,11 +25,13 @@ const SafeDetails = ({ ...props }) => { const { account, library } = useActiveWeb3React() const [loading, setIsLoading] = useState(false) const geb = useGeb() - const { - safeModel: safeActions, - popupsModel: popupsActions, - } = useStoreActions((state) => state) - const { safeModel: safeState } = useStoreState((state) => state) + const { safeModel: safeActions, popupsModel: popupsActions } = + useStoreActions((state) => state) + const { safeModel: safeState, connectWalletModel: connectWalletState } = + useStoreState((state) => state) + + const { fiatPrice: ethPrice } = connectWalletState + const safeId = props.match.params.id as string const hasSaviour = useHasSaviour( @@ -106,6 +108,20 @@ const SafeDetails = ({ ...props }) => { safeId, ]) + const fetchSaviourDataCallback = useCallback(() => { + if (!account || !geb || !safeId) return + safeActions.fetchSaviourData({ + account, + geb, + safeId, + ethPrice, + }) + }, [account, ethPrice, geb, safeActions, safeId]) + + useEffect(() => { + fetchSaviourDataCallback() + }, [fetchSaviourDataCallback]) + const handleSaviourBtnClick = async (data: { status: boolean saviourAddress: string diff --git a/src/hooks/useSaviour.ts b/src/hooks/useSaviour.ts index 1fd69439..c9b22701 100644 --- a/src/hooks/useSaviour.ts +++ b/src/hooks/useSaviour.ts @@ -15,9 +15,10 @@ import { SaviourWithdrawPayload, } from '../utils/interfaces' import { BigNumber } from '@ethersproject/bignumber' -import { formatNumber } from '../utils/helper' +import { formatNumber, toFixedString } from '../utils/helper' export const LIQUIDATION_POINT = 125 // percent +export const LIQUIDATION_CRATIO = 135 // percent export type SaviourData = { safeId: string @@ -202,7 +203,6 @@ export async function fetchSaviourData({ const uniPoolPrice = numeral(numerator) .divide(formattedCoinTotalSupply) .value() - return { safeId, hasSaviour: saviourAddress !== EMPTY_ADDRESS, @@ -237,32 +237,47 @@ export function useSaviourData(): SaviourData | undefined { return saviourData } +export function useTargetedCRatio(): number { + const { safeModel: safeState } = useStoreState((state) => state) + const { targetedCRatio } = safeState + return targetedCRatio +} + // minSaviourBalance export function useMinSaviourBalance() { const HUNDRED = 100 - const saviourData = useSaviourData() - const getMinSaviourBalance = useCallback( - (targetCRatio: number) => { + ( + targetCRatio?: number, + totalDebt?: string, + totalCollateral?: string + ) => { const WAD_COMPLEMENT = BigNumber.from(10 ** 9) if (!saviourData || !targetCRatio) return '0' const { RAY } = gebUtils const { redemptionPrice, - generatedDebt, + generatedDebt: safeDebt, accumulatedRate, - lockedCollateral, - reserveETH: ethReserve, - reserveRAI: raiReserve, - coinTotalSupply: lpTotalSupply, + lockedCollateral: safeCollateral, keeperPayOut, } = saviourData + const generatedDebt = totalDebt + ? BigNumber.from(toFixedString(totalDebt, 'WAD')) + .mul(RAY) + .div(accumulatedRate) + : safeDebt + + const lockedCollateral = totalCollateral + ? BigNumber.from(toFixedString(totalCollateral, 'WAD')) + : safeCollateral + // Liquidation price formula // - // debt * accumulatedRate * targetCRatio * RP + // debt * accumulatedRate * RP // liquidationPrice = ----------------------------------------------- // collateral @@ -270,74 +285,53 @@ export function useMinSaviourBalance() { ? redemptionPrice .mul(generatedDebt.mul(WAD_COMPLEMENT)) .mul(accumulatedRate) - .div(lockedCollateral.mul(WAD_COMPLEMENT)) - .div(RAY) .mul(LIQUIDATION_POINT) .div(HUNDRED) + .div(lockedCollateral.mul(WAD_COMPLEMENT)) + .div(RAY) : BigNumber.from('0') - // Formula for min savior balance - // - // targetCRatio * RP * accumulatedRate * debt - collateralPrice * collateral - // Min savior balance = ---------------------------------------------------------------------------------------------------------------------- - // collateralPrice * (reserveETH / totalLPsupply) + RP * accumulatedRate * (reserveRAI / totalLPsupply) * targetCRatio + // The calculation below refers to the formula described at: + // https://docs.reflexer.finance/liquidation-protection/uni-v2-rai-eth-savior-math - // (All calculation are made in RAY) - const numerator = redemptionPrice + const jVar = redemptionPrice .mul(accumulatedRate) .div(RAY) - .mul(generatedDebt.mul(WAD_COMPLEMENT)) - .div(RAY) .mul(targetCRatio) - .div(100) - .sub( - liquidationPrice - .mul(lockedCollateral) - .mul(WAD_COMPLEMENT) - .div(RAY) - ) - - const denominator = liquidationPrice - .mul(ethReserve.mul(WAD_COMPLEMENT)) - .div(lpTotalSupply.mul(WAD_COMPLEMENT)) - .add( - redemptionPrice - .mul(accumulatedRate) - .div(RAY) - .mul(raiReserve.mul(WAD_COMPLEMENT)) - .div(lpTotalSupply.mul(WAD_COMPLEMENT)) - .mul(targetCRatio) - .div(100) - ) - - let balanceBN = !generatedDebt.isZero() - ? numerator.mul(RAY).div(denominator) - : BigNumber.from('0') - - // Price USD RAY price of a LP share - // lpUsdPrice = (reserveETH * priceEth + reserveRAI * priceRAI) / lpTotalSupply - const lpTokenUsdPrice = ethReserve - .mul(WAD_COMPLEMENT) - .mul(liquidationPrice) - .div(RAY) - .add( - raiReserve.mul(WAD_COMPLEMENT).mul(redemptionPrice).div(RAY) - ) + .div(HUNDRED) .mul(RAY) - .div(lpTotalSupply.mul(WAD_COMPLEMENT)) + .div(liquidationPrice) - // Calculate keeper fee and add it to the min balance - const keeperPayoutInLP = keeperPayOut - .mul(WAD_COMPLEMENT) - .mul(RAY) - .div(lpTokenUsdPrice) + // TODO: Rai market price as RAY + // const currentRaiMarketPrice = BigNumber.from( + // '3050000000000000000000000000' + // ) - balanceBN = !generatedDebt.isZero() - ? balanceBN.add(keeperPayoutInLP) - : BigNumber.from('0') + const pVar = redemptionPrice.mul(RAY).div(liquidationPrice) - const minSaviorBalance = parseInt(balanceBN.toString()) / 1e27 - return formatNumber(minSaviorBalance.toString(), 4, true) + // Leave out sqrt(p) from the minimum bal equation because BignNumber doesn't do square root + const minSaviorBalanceRayWithoutSqrtP = lockedCollateral + .mul(WAD_COMPLEMENT) + .sub(generatedDebt.mul(WAD_COMPLEMENT).mul(jVar).div(RAY)) + .div(jVar.add(pVar)) + // TODO: Find a better way doing square root if there is + const minSaviorBalanceNumber = + Math.sqrt(Number(pVar.toString()) / 1e27) * + Number(minSaviorBalanceRayWithoutSqrtP.toString()) + + const keeperPayoutInLP = + Number(keeperPayOut.mul(WAD_COMPLEMENT).toString()) / + (Math.sqrt( + Number(liquidationPrice.mul(redemptionPrice).toString()) + ) * + 2) + + // Add the keeper balance + const minSaviorBalanceFinal = + Math.abs(minSaviorBalanceNumber) + + Number(keeperPayoutInLP.toString()) + + return formatNumber(minSaviorBalanceFinal.toString(), 4, true) }, [saviourData] )