diff --git a/src/components/PositionAvatar.tsx b/src/components/PositionAvatar.tsx index 7782a72e6..5dc3ca58d 100644 --- a/src/components/PositionAvatar.tsx +++ b/src/components/PositionAvatar.tsx @@ -58,7 +58,15 @@ function PositionAvatar({ return ( diff --git a/src/components/positionItems/DummyVaultItem.tsx b/src/components/positionItems/DummyVaultItem.tsx index 4a43bc8fb..1b360e37c 100644 --- a/src/components/positionItems/DummyVaultItem.tsx +++ b/src/components/positionItems/DummyVaultItem.tsx @@ -1,8 +1,6 @@ import { useRouter } from 'next/router'; - import { Box, Text } from 'grommet'; import { ActionType, ISeries } from '../../types'; - import PositionAvatar from '../PositionAvatar'; import ItemWrap from '../wraps/ItemWrap'; import { abbreviateHash } from '../../utils/appUtils'; @@ -13,7 +11,7 @@ function DummyVaultItem({ index = 0, condensed, }: { - series: ISeries; + series: ISeries | undefined; vaultId: string; index?: number; condensed?: boolean; @@ -28,7 +26,7 @@ function DummyVaultItem({ return ( handleSelect(vaultId)} index={index}> - + - {series.displayName} + {series?.displayName} diff --git a/src/components/positionItems/VaultItem.tsx b/src/components/positionItems/VaultItem.tsx index 5788b8f38..e08b31386 100644 --- a/src/components/positionItems/VaultItem.tsx +++ b/src/components/positionItems/VaultItem.tsx @@ -34,10 +34,10 @@ function VaultItem({ vault, index, condensed }: { vault: IVault; index: number; } as GA_Properties.position_opened); }; + const { isLoading: vaultsLoadingVR } = useVaultsVR(); const vaultBase = assetMap?.get(vault.baseId); const vaultIlk = assetMap?.get(vault.ilkId); - const vaultIsVR = !vault?.seriesId; - const { isLoading: vaultsLoadingVR } = useVaultsVR(); + const vaultIsVR = !vault.seriesId; const { data: assetPair } = useAssetPair(vaultBase?.id, vaultIlk?.id); @@ -84,7 +84,7 @@ function VaultItem({ vault, index, condensed }: { vault: IVault; index: number; )} {vaultIsVR && ( - {!debtInBase_ ? : cleanValue(debtInBase_, 2)} + {!debtInBase_ || vaultsLoadingVR ? : cleanValue(debtInBase_, 2)} )} diff --git a/src/components/selectors/SeriesSelector.tsx b/src/components/selectors/SeriesSelector.tsx index 8701160fb..deaed91fa 100644 --- a/src/components/selectors/SeriesSelector.tsx +++ b/src/components/selectors/SeriesSelector.tsx @@ -164,11 +164,6 @@ function SeriesSelector({ selectSeriesLocally, inputValue, actionType, cardLayou /* Keeping options/selection fresh and valid: */ useEffect(() => { - console.log('seriesSelector useEffect', selectedSeries, options); - // if (selectedVR && selectedSeries === null) { - // setOptions(options); - // return; - // } const opts = Array.from(seriesMap?.values()!); /* filter out options based on base Id ( or proxyId ) and if mature */ @@ -189,14 +184,12 @@ function SeriesSelector({ selectSeriesLocally, inputValue, actionType, cardLayou setOptions(filteredOpts.sort((a, b) => a.maturity - b.maturity)); }, [ - seriesMap, - selectedBase, selectSeriesLocally, - _selectedSeries, - userActions, - selectedSeries, - actionType, - selectedVault, + selectedBase?.proxyId, + selectedSeries?.baseId, + selectedSeries?.id, + selectedSeries?.maturity, + seriesMap, ]); const handleSelect = (_series: ISeries) => { diff --git a/src/components/selectors/VariableRate.tsx b/src/components/selectors/VariableRate.tsx index 3f0710a21..c2806dec3 100644 --- a/src/components/selectors/VariableRate.tsx +++ b/src/components/selectors/VariableRate.tsx @@ -75,7 +75,7 @@ const VariableRate = () => { const { userState: { selectedVR }, - userActions: { setSelectedVR }, + userActions: { setSelectedVR, setSelectedSeries }, } = useContext(UserContext); return ( @@ -84,7 +84,10 @@ const VariableRate = () => { round="large" background={selectedVR ? 'purple' : '#00000007'} elevation="xsmall" - onClick={() => setSelectedVR(true)} + onClick={() => { + setSelectedVR(true); + setSelectedSeries(null); + }} className="VR-container" > diff --git a/src/components/views/Borrow.tsx b/src/components/views/Borrow.tsx index ffb024da2..8e96f4196 100644 --- a/src/components/views/Borrow.tsx +++ b/src/components/views/Borrow.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useContext, useEffect, useState } from 'react'; import { Box, CheckBox, Keyboard, ResponsiveContext, Text, TextInput } from 'grommet'; - import { FiClock, FiPocket, FiPercent, FiTrendingUp } from 'react-icons/fi'; import SeriesSelector from '../selectors/SeriesSelector'; @@ -9,7 +8,6 @@ import AssetSelector from '../selectors/AssetSelector'; import InputWrap from '../wraps/InputWrap'; import ActionButtonWrap from '../wraps/ActionButtonWrap'; import SectionWrap from '../wraps/SectionWrap'; - import MaxButton from '../buttons/MaxButton'; import { UserContext } from '../../contexts/UserContext'; @@ -18,7 +16,6 @@ import PanelWrap from '../wraps/PanelWrap'; import CenterPanelWrap from '../wraps/CenterPanelWrap'; import VaultSelector from '../selectors/VaultPositionSelector'; import ActiveTransaction from '../ActiveTransaction'; - import { cleanValue, getVaultIdFromReceipt, nFormatter } from '../../utils/appUtils'; import YieldInfo from '../FooterInfo'; @@ -41,22 +38,22 @@ import { useBorrowHelpersVR } from '../../hooks/viewHelperHooks/useBorrowHelpers import InputInfoWrap from '../wraps/InputInfoWrap'; import ColorText from '../texts/ColorText'; import { useProcess } from '../../hooks/useProcess'; - import DummyVaultItem from '../positionItems/DummyVaultItem'; import SeriesOrStrategySelectorModal from '../selectors/SeriesOrStrategySelectorModal'; import Navigation from '../Navigation'; import VaultItem from '../positionItems/VaultItem'; import Line from '../elements/Line'; -import { useAccount, useNetwork } from 'wagmi'; import { GA_Event, GA_Properties, GA_View } from '../../types/analytics'; import useAnalytics from '../../hooks/useAnalytics'; import { WETH } from '../../config/assets'; import useContracts from '../../hooks/useContracts'; import useAccountPlus from '../../hooks/useAccountPlus'; - import VariableRate from '../selectors/VariableRate'; import useBasesVR from '../../hooks/views/useBasesVR'; import useAssetPair from '../../hooks/viewHelperHooks/useAssetPair/useAssetPair'; +import useVaultsVR from '../../hooks/entities/useVaultsVR'; +import { ContractNames } from '../../config/contracts'; +import { Cauldron, VRCauldron } from '../../contracts'; const Borrow = () => { const mobile: boolean = useContext(ResponsiveContext) === 'small'; @@ -65,17 +62,8 @@ const Borrow = () => { /* STATE FROM CONTEXT */ const { userState, userActions } = useContext(UserContext); - const { - assetMap, - vaultMap, - vaultsLoading, - seriesMap, - selectedSeries, - selectedIlk, - selectedBase, - selectedVault, - selectedVR, - } = userState; + const { assetMap, vaultMap, vaultsLoading, selectedSeries, selectedIlk, selectedBase, selectedVault, selectedVR } = + userState; const { setSelectedIlk } = userActions; const { address: activeAccount } = useAccountPlus(); @@ -103,6 +91,7 @@ const Borrow = () => { const { apr } = useApr(borrowInput, ActionType.BORROW, selectedSeries); const { data: assetPair } = useAssetPair(selectedBase?.id, selectedIlk?.id); const { data: basesVR } = useBasesVR(); + const { data: vaultsVR } = useVaultsVR(); const { collateralizationPercent, @@ -121,13 +110,13 @@ const Borrow = () => { maxDebt_: maxDebtFR_, borrowPossible: borrowPossibleFR, borrowEstimate_, - } = useBorrowHelpersFR(borrowInput, collatInput, vaultToUse, assetPair, selectedSeries); + } = useBorrowHelpersFR(borrowInput, collatInput, selectedSeries ? vaultToUse : undefined, assetPair, selectedSeries); const { minDebt_: minDebtVR_, maxDebt_: maxDebtVR_, borrowPossible: borrowPossibleVR, - } = useBorrowHelpersVR(borrowInput, vaultToUse, assetPair); + } = useBorrowHelpersVR(borrowInput, selectedVR ? vaultToUse : undefined, assetPair); const minDebt_ = selectedVR ? minDebtVR_ : minDebtFR_; const maxDebt_ = selectedVR ? maxDebtVR_ : maxDebtFR_; @@ -145,7 +134,10 @@ const Borrow = () => { ]); /* TX info (for disabling buttons) */ - const { txProcess: borrowProcess, resetProcess } = useProcess(ActionCodes.BORROW, selectedSeries?.id!); + const { txProcess: borrowProcess, resetProcess } = useProcess( + ActionCodes.BORROW, + selectedVR ? 'VR' : selectedSeries?.id! + ); /** LOCAL ACTION FNS */ const handleBorrow = () => { @@ -263,18 +255,18 @@ const Borrow = () => { /* CHECK the list of current vaults which match the current series/ilk selection */ // TODO look at moving this to helper hook? useEffect(() => { - if (selectedBase && selectedSeries && selectedIlk) { - const arr: IVault[] = Array.from(vaultMap?.values()!) as IVault[]; - const _matchingVaults = arr.filter( - (v: IVault) => + if (selectedBase && selectedIlk) { + const vaults = [...(selectedVR ? (vaultsVR || []).values() : (vaultMap || []).values())]; + const matchingVaults = vaults.filter( + (v) => v.ilkId === selectedIlk.proxyId && v.baseId === selectedBase.proxyId && - v.seriesId === selectedSeries.id && + (selectedVR ? true : v.seriesId === selectedSeries?.id) && v.isActive ); - setMatchingVaults(_matchingVaults); + setMatchingVaults(matchingVaults); } - }, [vaultMap, selectedBase, selectedIlk, selectedSeries]); + }, [vaultMap, selectedBase, selectedIlk, selectedSeries, vaultsVR, selectedVR]); /* handle selected vault */ useEffect(() => { @@ -283,11 +275,19 @@ const Borrow = () => { } if (selectedVault) { - return setVaultToUse(selectedVault); + // if using an already selected vault with series + if (selectedSeries && selectedVault.seriesId === selectedSeries.id) { + return setVaultToUse(selectedVault); + } + + // if using an already selected vr vault + if (selectedVR && selectedVault.baseId === selectedBase?.id) { + return setVaultToUse(selectedVault); + } } setVaultToUse(undefined); - }, [matchingVaults, selectedVault]); + }, [matchingVaults, selectedBase?.id, selectedSeries, selectedVR, selectedVault]); useEffect(() => { if ( @@ -295,10 +295,13 @@ const Borrow = () => { borrowProcess?.tx.status === TxState.SUCCESSFUL && !vaultToUse ) { - setNewVaultId(getVaultIdFromReceipt(borrowProcess?.tx?.receipt, contracts)!); + const cauldron = contracts?.get(selectedVR ? ContractNames.VR_CAULDRON : ContractNames.CAULDRON) as + | VRCauldron + | Cauldron; + setNewVaultId(getVaultIdFromReceipt(borrowProcess?.tx?.receipt, cauldron!)); } borrowProcess?.stage === ProcessStage.PROCESS_COMPLETE_TIMEOUT && resetInputs(); - }, [borrowProcess, contracts, resetInputs, vaultToUse]); + }, [borrowProcess, contracts, resetInputs, selectedVR, vaultToUse]); return ( setCollatInput('')} onEnter={() => console.log('ENTER smashed')} target="document"> @@ -471,7 +474,8 @@ const Borrow = () => { maxCollateral && handleMaxAction(ActionCodes.ADD_COLLATERAL)} disabled={ - !selectedSeries || collatInput === maxCollateral || selectedSeries.seriesIsMature + ((!selectedSeries || selectedSeries.seriesIsMature) && !selectedVR) || + collatInput === maxCollateral } clearAction={() => setCollatInput('')} showingMax={!!collatInput && collatInput === maxCollateral} @@ -541,12 +545,12 @@ const Borrow = () => { icon={} value={`${cleanValue(borrowInput, selectedBase?.digitFormat!)} ${selectedBase?.displaySymbol}`} /> - {!selectedVR && ( + {selectedSeries && (
} - value={`${selectedSeries?.displayName}`} + value={`${selectedSeries.displayName}`} /> { />
)} - } value={`${apr}%`} /> + } + value={`${apr}%`} + /> { borrowProcess?.stage === ProcessStage.PROCESS_COMPLETE && borrowProcess?.tx.status === TxState.SUCCESSFUL && ( - View Vault: - {vaultToUse && !vaultsLoading && ( - - )} + View Vault: + {vaultToUse && } {!vaultToUse && newVaultId && ( - + )} )} diff --git a/src/components/views/VaultPosition.tsx b/src/components/views/VaultPosition.tsx index 572672ea7..82e055694 100644 --- a/src/components/views/VaultPosition.tsx +++ b/src/components/views/VaultPosition.tsx @@ -61,9 +61,10 @@ const VaultPosition = () => { /* STATE FROM CONTEXT */ const { userState, userActions } = useContext(UserContext); - const { assetMap, seriesMap, vaultMap, vaultsLoading } = userState; + const { assetMap, seriesMap, vaultMap, vaultsLoading: vaultsLoadingFR, selectedVR } = userState; const { setSelectedBase, setSelectedIlk, setSelectedSeries, setSelectedVault, setSelectedVR } = userActions; - const { data: vaultsVR } = useVaultsVR(); + const { data: vaultsVR, isLoading: vaultsLoadingVR } = useVaultsVR(); + const vaultsLoading = selectedVR ? vaultsLoadingVR : vaultsLoadingFR; const { address: account } = useAccountPlus(); @@ -251,7 +252,7 @@ const VaultPosition = () => { const handleRepay = () => { if (repayDisabled) return; setRepayDisabled(true); - repay(_selectedVault!, repayInput?.toString(), reclaimCollateral); + if (_selectedVault) repay(_selectedVault, repayInput, reclaimCollateral); logAnalyticsEvent(GA_Event.transaction_initiated, { view: GA_View.BORROW, @@ -340,8 +341,8 @@ const VaultPosition = () => { /* ACTION DISABLING LOGIC */ useEffect(() => { /* if ANY of the following conditions are met: block action */ - !repayInput || repayError || !_selectedVault ? setRepayDisabled(true) : setRepayDisabled(false); - !rollToSeries || rollError || !_selectedVault ? setRollDisabled(true) : setRollDisabled(false); + !repayInput || repayError || !_selectedVault || vaultsLoading ? setRepayDisabled(true) : setRepayDisabled(false); + !rollToSeries || rollError || !_selectedVault || vaultsLoading ? setRollDisabled(true) : setRollDisabled(false); !addCollatInput || addCollatError || !_selectedVault ? setAddCollateralDisabled(true) : setAddCollateralDisabled(false); @@ -358,6 +359,7 @@ const VaultPosition = () => { removeCollatError, rollError, _selectedVault, + vaultsLoading, ]); /* EXTRA INITIATIONS */ @@ -400,6 +402,11 @@ const VaultPosition = () => { rollProcess?.stage === ProcessStage.PROCESS_COMPLETE_TIMEOUT && resetInputs(ActionCodes.ROLL_DEBT); }, [addCollateralProcess, removeCollateralProcess, repayProcess, resetInputs, rollProcess]); + // update selectedVR when vault is VR + useEffect(() => { + vaultIsVR && setSelectedVR(true); + }, [setSelectedVR, vaultIsVR]); + return ( <> {_selectedVault && ( diff --git a/src/contexts/ChainContext.tsx b/src/contexts/ChainContext.tsx index 3df1dc91d..c74d01326 100644 --- a/src/contexts/ChainContext.tsx +++ b/src/contexts/ChainContext.tsx @@ -1,5 +1,5 @@ import React, { createContext, Dispatch, ReactNode, useCallback, useEffect, useReducer, useContext } from 'react'; -import { BigNumber, Contract } from 'ethers'; +import { BigNumber, Contract, ethers } from 'ethers'; import { format } from 'date-fns'; import { useCachedState } from '../hooks/generalHooks'; @@ -25,6 +25,7 @@ import { Pool__factory } from '../contracts'; import { useProvider } from 'wagmi'; import { SettingsContext } from './SettingsContext'; +import { MAX_256, ZERO_BN } from '@yield-protocol/ui-math'; const initState: IChainContextState = { /* flags */ @@ -117,7 +118,10 @@ const ChainProvider = ({ children }: { children: ReactNode }) => { case TokenType.ERC1155_: assetContract = contractTypes.ERC1155__factory.connect(asset.address, provider); - getAllowance = async (acc: string, spender: string) => assetContract.isApprovedForAll(acc, spender); + getAllowance = async (acc: string, spender: string) => + (await (assetContract as contractTypes.ERC1155).isApprovedForAll(acc, spender)) + ? ethers.constants.MaxUint256 + : ethers.constants.Zero; setAllowance = async (spender: string) => { console.log(spender); console.log(asset.address); diff --git a/src/hooks/actionHooks/useBorrow/useBorrowVR.ts b/src/hooks/actionHooks/useBorrow/useBorrowVR.ts index e339f2041..270c6ac9d 100644 --- a/src/hooks/actionHooks/useBorrow/useBorrowVR.ts +++ b/src/hooks/actionHooks/useBorrow/useBorrowVR.ts @@ -14,10 +14,12 @@ import useAccountPlus from '../../useAccountPlus'; import { ContractNames } from '../../../config/contracts'; import useAssetPair from '../../viewHelperHooks/useAssetPair/useAssetPair'; import { useSWRConfig } from 'swr'; +import useVaultsVR from '../../entities/useVaultsVR'; export const useBorrowVR = () => { const { mutate } = useSWRConfig(); const { genKey: genAssetPairKey } = useAssetPair(); + const { key: vaultsKey } = useVaultsVR(); const { userState, userActions } = useContext(UserContext); const { selectedBase, selectedIlk, assetMap } = userState; const { updateAssets } = userActions; @@ -37,7 +39,7 @@ export const useBorrowVR = () => { }); const borrowVR = async (vault: IVault | undefined, input: string | undefined, collInput: string | undefined) => { - if (!contracts) return; + if (!contracts || !account || !selectedBase || !assetMap || !selectedIlk) return; /* generate the reproducible txCode for tx tracking and tracing */ const txCode = getTxCode(ActionCodes.BORROW, 'VR'); @@ -48,45 +50,31 @@ export const useBorrowVR = () => { const ladleAddress = contracts.get(ContractNames.VR_LADLE)?.address; /* Set the series and ilk based on the vault that has been selected or if it's a new vault, get from the globally selected SeriesId */ - const base = assetMap?.get(selectedBase!.id)!; + const base = assetMap.get(selectedBase.id); - const ilkToUse = vault ? assetMap?.get(vault.ilkId)! : assetMap?.get(selectedIlk?.proxyId!)!; // note: we use the wrapped version if required + if (!base) return console.error('base not found'); - /* is ETH used as collateral */ - const isEthCollateral = ETH_BASED_ASSETS.includes(selectedIlk?.proxyId!); - /* is ETH being Borrowed */ - const isEthBase = ETH_BASED_ASSETS.includes(selectedBase!.id); + const ilkToUse = vault ? assetMap.get(vault.ilkId)! : assetMap.get(selectedIlk.proxyId); // note: we use the wrapped version if required - /* parse inputs (clean down to base/ilk decimals so that there is never an underlow) */ + if (!ilkToUse) return console.error('ilk not found'); + if (!ilkToUse.joinAddressVR) return console.error('ilkToUse.joinAddressVR not found'); + + /* is ETH used as collateral */ + const isEthCollateral = ETH_BASED_ASSETS.includes(selectedIlk.proxyId); + + /* is ETH being borrowed */ + const isEthBase = ETH_BASED_ASSETS.includes(selectedBase.id); + + /* parse inputs (clean down to base/ilk decimals so that there is never an underlow) */ const cleanInput = cleanValue(input, base.decimals); const _input = input ? ethers.utils.parseUnits(cleanInput, base.decimals) : ethers.constants.Zero; const cleanCollInput = cleanValue(collInput, ilkToUse.decimals); const _collInput = collInput ? ethers.utils.parseUnits(cleanCollInput, ilkToUse.decimals) : ethers.constants.Zero; - /* if approveMAx, check if signature is required : note: getAllowance may return FALSE if ERC1155 */ - const _allowance = await ilkToUse.getAllowance(account!, ilkToUse.joinAddressVR!); - - const alreadyApproved = ethers.BigNumber.isBigNumber(_allowance) ? _allowance.gte(_collInput) : _allowance; + /* check if signature is required */ + const _allowance = await ilkToUse.getAllowance(account, ilkToUse.joinAddressVR!); + const alreadyApproved = _allowance.gte(_collInput); - /* handle ETH deposit as Collateral, if required (only if collateral used is ETH-based ), else send ZERO_BN */ - const addEthCallData = isEthCollateral - ? [ - { - operation: LadleActions.Fn.WRAP_ETHER, - args: [selectedBase?.joinAddressVR] as LadleActions.Args.WRAP_ETHER, - overrides: { value: _input }, - }, - ] - : []; - - // TODO update for vr - // const removeEthCallData = removeEth(isEthBase ? ONE_BN : ZERO_BN); // (exit_ether sweeps all the eth out the ladle, so exact amount is not importnat -> just greater than zero) - - /* handle wrapping of collateral if required */ - // TODO figure out vr wrapping methodology - // const wrapAssetCallData = await wrapAsset(_collInput, selectedIlk!, txCode); // note: selected ilk used here, not wrapped version - - /* Gather all the required signatures - sign() processes them and returns them as ICallData types */ const permitCallData = await sign( [ { @@ -110,8 +98,8 @@ export const useBorrowVR = () => { /* Include all the signatures gathered, if required */ ...permitCallData, - /* add in the ETH deposit if required */ - ...addEthCallData, + /* add in the ETH collateral deposit if required */ + ...(isEthCollateral ? addEth(_collInput, ilkToUse.joinAddressVR) : []), /* If vault is null, build a new vault, else ignore */ { @@ -122,20 +110,24 @@ export const useBorrowVR = () => { { operation: LadleActions.Fn.POUR, - args: [vaultId, ladleAddress, _collInput, _input] as LadleActions.Args.POUR, + args: [vaultId, isEthBase ? ladleAddress : account, _collInput, _input] as LadleActions.Args.POUR, ignoreIf: false, }, + + /* remove eth if being borrowed */ + ...(isEthBase ? removeEth(_input, account) : []), ]; /* finally, handle the transaction */ await transact(calls, txCode, true); - if (selectedBase?.id !== WETH) refetchBaseBal(); - if (selectedIlk?.proxyId !== WETH) refetchIlkBal(); - updateAssets([base, ilkToUse, selectedIlk!]); - mutate(genAssetPairKey(selectedBase!.id, selectedIlk!.id)); + if (selectedBase.id !== WETH) refetchBaseBal(); + if (selectedIlk.proxyId !== WETH) refetchIlkBal(); + updateAssets([base, ilkToUse, selectedIlk]); + mutate(genAssetPairKey(selectedBase.id, selectedIlk.id)); + mutate(vaultsKey); - // TODO update all vaults + // TODO update borrow history }; return borrowVR; diff --git a/src/hooks/actionHooks/useRepayDebt/useRepayDebtVR.ts b/src/hooks/actionHooks/useRepayDebt/useRepayDebtVR.ts index f7f755e80..9f98350a3 100644 --- a/src/hooks/actionHooks/useRepayDebt/useRepayDebtVR.ts +++ b/src/hooks/actionHooks/useRepayDebt/useRepayDebtVR.ts @@ -1,7 +1,6 @@ import { ethers } from 'ethers'; import { useContext } from 'react'; import { calculateSlippage, MAX_256 } from '@yield-protocol/ui-math'; - import { UserContext } from '../../../contexts/UserContext'; import { ICallData, IVault, ActionCodes, LadleActions, IAsset, RoutedActions } from '../../../types'; import { cleanValue, getTxCode } from '../../../utils/appUtils'; @@ -17,7 +16,8 @@ import useContracts from '../../useContracts'; import useChainId from '../../useChainId'; import useAccountPlus from '../../useAccountPlus'; import { ContractNames } from '../../../config/contracts'; -import useAllowAction from '../../useAllowAction'; +import { mutate } from 'swr'; +import useVaultsVR from '../../entities/useVaultsVR'; export const useRepayDebtVR = () => { const { @@ -40,8 +40,8 @@ export const useRepayDebtVR = () => { token: selectedBase?.id === WETH ? undefined : (selectedBase?.address as Address), }); + const { key: vaultsKey } = useVaultsVR(); const { addEth, removeEth } = useAddRemoveEth(); - const { unwrapAsset } = useWrapUnwrapAsset(); const { sign, transact } = useChain(); const chainId = useChainId(); @@ -51,150 +51,108 @@ export const useRepayDebtVR = () => { * @param input * @param reclaimCollateral */ - const repayVariableRate = async (vault: IVault, input: string | undefined, reclaimCollateral: boolean) => { - if (!contracts) return; + const repay = async (vault: IVault, input: string | undefined, reclaimCollateral: boolean) => { + if (!contracts || !input || !assetMap || !account) return; const txCode = getTxCode(ActionCodes.REPAY, vault.id); const ladleAddress = contracts.get(ContractNames.VR_LADLE)?.address; + if (!ladleAddress) return console.error('Ladle address not found'); - const vrCauldronAddress = contracts.get(ContractNames.VR_CAULDRON)?.address; - // const series: ISeries = seriesMap?.get(vault.seriesId)!; - const base: IAsset = assetMap?.get(vault.baseId)!; - const ilk: IAsset = assetMap?.get(vault.ilkId)!; + const base = assetMap.get(vault.baseId); + const ilk = assetMap.get(vault.ilkId); - // if (!isActionAllowed(ActionCodes.REPAY)) return; // return if action is not allowed + if (!base || !ilk) return console.error('Base or ilk not found'); const isEthCollateral = ETH_BASED_ASSETS.includes(vault.ilkId); - const isEthBase = ETH_BASED_ASSETS.includes(base.id); - - /* is convex-type collateral */ - const isConvexCollateral = CONVEX_BASED_ASSETS.includes(ilk.proxyId); - const convexJoinContract = ConvexJoin__factory.connect(ilk.joinAddress, provider); + const isEthBase = ETH_BASED_ASSETS.includes(vault.baseId); /* Parse inputs */ const cleanInput = cleanValue(input, base.decimals); - const _input = input ? ethers.utils.parseUnits(cleanInput, base.decimals) : ethers.constants.Zero; - - /* - we won't be able to do the below maxSharesIn calculation for VR because - theres no series. Will need to follow up on what to do here - jacob b - - can call Cauldron.balances here to get the debt - call when selects user vault - also worth it to try - and calcultate in the UI - */ - - /* Check if the trade of that size is possible */ - // assuming all trades are possible for now, but will need to revisit - jacob b - const tradeIsNotPossible = false; - - diagnostics && tradeIsNotPossible ? console.log('Trade is not possible:') : console.log('Trade is possible:'); - diagnostics && tradeIsNotPossible && console.log('Trade input', _input.toString()); - - const _inputAsFyTokenWithSlippage = calculateSlippage( - // remove fyToken references - jacob b - _input, - slippageTolerance.toString(), - true // minimize - ); + const _input = ethers.utils.parseUnits(cleanInput, base.decimals); /* Check if input is more than the debt */ - const inputGreaterThanEqualDebt: boolean = ethers.BigNumber.from(_input).gte(vault.accruedArt); + const inputGreaterThanEqualDebt = _input.gte(vault.accruedArt); /* If requested, and all debt will be repaid, automatically remove collateral */ const _collateralToRemove = reclaimCollateral && inputGreaterThanEqualDebt ? vault.ink.mul(-1) : ethers.constants.Zero; - /* Cap the amount to transfer: check that if input is greater than debt, used after maturity only repay the max debt (or accrued debt) */ + /* Cap the amount of debt to repay at total debt */ const _inputCappedAtArt = vault.art.gt(ZERO_BN) && vault.art.lte(_input) ? vault.art : _input; - /* Set the amount to transfer ( + 0.1% after maturity ) */ - /* assuming mature here as well - is this right? - jacob b */ - const amountToTransfer = _input.mul(10001).div(10000); - - /* In low liq situations/or mature, send repay funds to join not pool */ - /* I believe we transfer our repayment directly to the ladle, could be wrong here - jacob b */ - // maybe the cauldron directly? - jacob b - const transferToAddress = vrCauldronAddress; + /* Set the amount of base to transfer for repay with slight buffer */ + const amountToTransfer = _inputCappedAtArt.mul(10001).div(10000); /* Check if already approved */ - const alreadyApproved = (await base.getAllowance(account!, ladleAddress!)).gte(amountToTransfer); + const alreadyApproved = (await base.getAllowance(account, ladleAddress)).gte(amountToTransfer); - // const wrapAssetCallData : ICallData[] = await wrapAsset(ilk, account!); - const unwrapAssetCallData: ICallData[] = reclaimCollateral ? await unwrapAsset(ilk, account!) : []; + const approveAmount = base.id === USDT && chainId !== 42161 ? MAX_256 : amountToTransfer; - const approveAmount = base.id === USDT && chainId !== 42161 ? MAX_256 : amountToTransfer.mul(110).div(100); - const permitCallData: ICallData[] = await sign( + const permitCallData = await sign( [ { target: base, spender: 'LADLE', - amount: approveAmount, // generous approval permits on repayment we can refine at a later stage - ignoreIf: alreadyApproved === true, + amount: approveAmount, + ignoreIf: alreadyApproved, }, ], txCode ); - /* Remove ETH collateral. (exit_ether sweeps all the eth out of the ladle, so exact amount is not importnat -> just greater than zero) */ - const removeEthCallData = isEthCollateral ? removeEth(ONE_BN) : []; + const removeEthCallData = isEthCollateral ? removeEth(ONE_BN, account) : []; - /* Address to send the funds to either ladle (if eth is used as collateral) or account */ - // TODO - Need to test with this after we get a VR fork with WETH module - jacob b - const reclaimToAddress = () => { - if (isEthCollateral) return ladleAddress; - if (unwrapAssetCallData.length && ilk.unwrapHandlerAddresses?.has(chain?.id!)) - return ilk.unwrapHandlerAddresses?.get(chain?.id!); // if there is somethign to unwrap - return account; - }; + /* Address to send the collateral to: either ladle (if eth is used as collateral) or account */ + const reclaimCollatToAddress = isEthCollateral ? ladleAddress : account; const calls: ICallData[] = [ ...permitCallData, - - /* Reqd. when we have a wrappedBase */ - // ...wrapAssetCallData - - /* If ethBase, Send ETH to either base join or pool */ - ...addEth(isEthBase ? amountToTransfer : ZERO_BN, transferToAddress), // destination = either join or series depending if tradeable - ...addEth(isEthBase ? amountToTransfer : ZERO_BN), // no destination defined after maturity , input +1% will will go to weth join - - /* Else, Send Token to either join or pool via a ladle.transfer() */ + ...(isEthBase ? addEth(amountToTransfer, base.joinAddressVR) : []), { operation: LadleActions.Fn.TRANSFER, - args: [base.address, transferToAddress, amountToTransfer] as LadleActions.Args.TRANSFER, + args: [base.address, base.joinAddressVR, amountToTransfer] as LadleActions.Args.TRANSFER, ignoreIf: isEthBase, }, - /* convex-type collateral; ensure checkpoint before giving collateral back to account */ + /* repay less than all debt */ { - operation: LadleActions.Fn.ROUTE, - args: [vault.owner] as RoutedActions.Args.CHECKPOINT, - fnName: RoutedActions.Fn.CHECKPOINT, - targetContract: convexJoinContract, // use the convex join contract to checkpoint - ignoreIf: !isConvexCollateral || _collateralToRemove.eq(ethers.constants.Zero), + operation: LadleActions.Fn.POUR, + args: [ + vault.id, + reclaimCollatToAddress, + _collateralToRemove, + _inputCappedAtArt.mul(-1), + ] as LadleActions.Args.POUR, + ignoreIf: inputGreaterThanEqualDebt, }, - /* BEFORE MATURITY - !series.seriesIsMature */ + /* repay all debt */ { operation: LadleActions.Fn.REPAY, - args: [vault.id, account, ladleAddress, _inputAsFyTokenWithSlippage] as LadleActions.Args.REPAY, - ignoreIf: inputGreaterThanEqualDebt || tradeIsNotPossible, + args: [ + vault.id, + reclaimCollatToAddress, + reclaimCollatToAddress, + _collateralToRemove, + ] as LadleActions.Args.REPAY_VR, + ignoreIf: !inputGreaterThanEqualDebt, }, ...removeEthCallData, - ...unwrapAssetCallData, ]; + await transact(calls, txCode); if (selectedBase?.proxyId !== WETH) refetchBaseBal(); if (selectedIlk?.proxyId !== WETH) refetchIlkBal(); - updateVaults([vault]); updateAssets([base, ilk, userState.selectedIlk!]); - // updateSeries([series]); + mutate(vaultsKey); + + // TODO update vault history }; - return repayVariableRate; + return repay; }; diff --git a/src/hooks/entities/useVaultsVR.ts b/src/hooks/entities/useVaultsVR.ts index cb534b94f..934a688f8 100644 --- a/src/hooks/entities/useVaultsVR.ts +++ b/src/hooks/entities/useVaultsVR.ts @@ -134,13 +134,13 @@ const useVaultsVR = () => { [account, forkStartBlock, useForkedEnv, assetRootMap, forkUrl] ); - const { data, error, isLoading } = useSWR(key, getVaults, { + const { data, error, isLoading, isValidating } = useSWR(key, getVaults, { revalidateOnFocus: false, revalidateIfStale: false, shouldRetryOnError: false, }); - return { data, error, isLoading, key }; + return { data, error, isLoading: isLoading || isValidating, key }; }; export default useVaultsVR; diff --git a/src/hooks/useApr.ts b/src/hooks/useApr.ts index 343aa2e4c..0de033737 100644 --- a/src/hooks/useApr.ts +++ b/src/hooks/useApr.ts @@ -75,7 +75,8 @@ export const useApr = (input: string | undefined, actionType: ActionType, series _apr ? setApr(cleanValue(_apr, 2)) : setApr(_selectedSeries.apr); } else if (selectedBase) { /* logic for VR */ - const baseAmount = ethers.utils.parseUnits(_input || _fallbackInput, selectedBase.decimals); + const cleanedInput = cleanValue(_input || _fallbackInput, selectedBase.decimals); + const baseAmount = ethers.utils.parseUnits(cleanedInput, selectedBase.decimals); const now = Date.now() + 20000; // trying to call interest rate oracle @@ -87,7 +88,7 @@ export const useApr = (input: string | undefined, actionType: ActionType, series const interestRateOracleAddr = await VRCauldron.rateOracles(selectedBase.id); const interestRateOracle = VRInterestRateOracle__factory.connect(interestRateOracleAddr, provider); const joinAddress = selectedBase.joinAddressVR; - console.log('INTEREST RATE ORACLE', interestRateOracleAddr, interestRateOracle); + // console.log('INTEREST RATE ORACLE', interestRateOracleAddr, interestRateOracle); let rate: any = ethers.constants.Zero; // TODO - fix this type @@ -103,7 +104,7 @@ export const useApr = (input: string | undefined, actionType: ActionType, series ); } - console.log('rate in useAPR', rate); + // console.log('rate in useAPR', rate); return rate; } catch (e) { diff --git a/src/hooks/useInputValidation.ts b/src/hooks/useInputValidation.ts index f1d442156..d71626772 100644 --- a/src/hooks/useInputValidation.ts +++ b/src/hooks/useInputValidation.ts @@ -13,8 +13,6 @@ export const useInputValidation = ( limits: (number | string | undefined)[], vault?: IVault | undefined ) => { - console.log('useInputValidation params', input, actionCode, series, limits, vault); - /* STATE FROM CONTEXT */ const { userState } = useContext(UserContext); const { assetMap, selectedSeries, selectedBase, selectedVR } = userState; @@ -110,7 +108,7 @@ export const useInputValidation = ( break; } } else setInputError(null); - }, [actionCode, activeAccount, input, limits, _selectedBase?.symbol, _selectedSeries]); + }, [actionCode, activeAccount, input, limits, _selectedBase?.symbol, _selectedSeries, protocolLimited, selectedVR]); return { inputError, diff --git a/src/hooks/viewHelperHooks/useBorrowHelpers/useBorrowHelpersFR.ts b/src/hooks/viewHelperHooks/useBorrowHelpers/useBorrowHelpersFR.ts index f61b406dc..3b5b87542 100644 --- a/src/hooks/viewHelperHooks/useBorrowHelpers/useBorrowHelpersFR.ts +++ b/src/hooks/viewHelperHooks/useBorrowHelpers/useBorrowHelpersFR.ts @@ -139,7 +139,7 @@ export const useBorrowHelpersFR = ( /* SET MAX ROLL and ROLLABLE including Check if the rollToSeries have sufficient base value AND won't be undercollaterallised */ useEffect(() => { - if (futureSeries && vault && vault.accruedArt && vault.seriesId) { + if (futureSeries && vault && vault.accruedArt && vault.seriesId && assetPairInfo) { const _maxFyTokenIn = maxFyTokenIn( futureSeries.sharesReserves, futureSeries.fyTokenReserves, @@ -164,9 +164,9 @@ export const useBorrowHelpersFR = ( ); const _minCollat = calculateMinCollateral( - assetPairInfo?.pairPrice!, + assetPairInfo.pairPrice, newDebt, - assetPairInfo?.minRatio.toString()!, + assetPairInfo.minRatio.toString(), undefined ); diff --git a/src/hooks/viewHelperHooks/useBorrowHelpers/useBorrowHelpersVR.ts b/src/hooks/viewHelperHooks/useBorrowHelpers/useBorrowHelpersVR.ts index 51b532524..236c0a1a6 100644 --- a/src/hooks/viewHelperHooks/useBorrowHelpers/useBorrowHelpersVR.ts +++ b/src/hooks/viewHelperHooks/useBorrowHelpers/useBorrowHelpersVR.ts @@ -20,10 +20,6 @@ export const useBorrowHelpersVR = ( assetPairInfo: IAssetPair | null | undefined ) => { /* STATE FROM CONTEXT */ - const { - settingsState: { diagnostics }, - } = useContext(SettingsContext); - const { userState: { assetMap, selectedBase }, } = useContext(UserContext); @@ -39,7 +35,6 @@ export const useBorrowHelpersVR = ( }); /* LOCAL STATE */ - const [debtAfterRepay, setDebtAfterRepay] = useState(); const [debtInBase, setDebtInBase] = useState(); diff --git a/src/hooks/viewHelperHooks/useCollateralHelpers.ts b/src/hooks/viewHelperHooks/useCollateralHelpers.ts index 40490f743..558705805 100644 --- a/src/hooks/viewHelperHooks/useCollateralHelpers.ts +++ b/src/hooks/viewHelperHooks/useCollateralHelpers.ts @@ -7,13 +7,12 @@ import { calculateMinCollateral, decimalNToDecimal18, } from '@yield-protocol/ui-math'; - import { UserContext } from '../../contexts/UserContext'; import { IAssetPair, IVault } from '../../types'; import { cleanValue } from '../../utils/appUtils'; import { ZERO_BN } from '../../utils/constants'; import useTimeTillMaturity from '../useTimeTillMaturity'; -import { Address, useAccount, useBalance } from 'wagmi'; +import { Address, useBalance } from 'wagmi'; import { WETH } from '../../config/assets'; import useAccountPlus from '../useAccountPlus'; import { parseUnits } from 'ethers/lib/utils.js'; @@ -44,31 +43,31 @@ export const useCollateralHelpers = ( }); /* LOCAL STATE */ - const [collateralizationRatio, setCollateralizationRatio] = useState(); - const [collateralizationPercent, setCollateralizationPercent] = useState(); + const [collateralizationRatio, setCollateralizationRatio] = useState(); + const [collateralizationPercent, setCollateralizationPercent] = useState(); const [undercollateralized, setUndercollateralized] = useState(true); const [unhealthyCollatRatio, setUnhealthyCollatRatio] = useState(false); const [oraclePrice, setOraclePrice] = useState(ethers.constants.Zero); - const [liquidationPrice_, setLiquidationPrice_] = useState(); + const [liquidationPrice_, setLiquidationPrice_] = useState(); const [minCollateral, setMinCollateral] = useState(); - const [minCollateral_, setMinCollateral_] = useState(); + const [minCollateral_, setMinCollateral_] = useState(); - const [minCollatRatio, setMinCollatRatio] = useState(); - const [minCollatRatioPct, setMinCollatRatioPct] = useState(); - const [minSafeCollatRatio, setMinSafeCollatRatio] = useState(); - const [minSafeCollatRatioPct, setMinSafeCollatRatioPct] = useState(); - const [minSafeCollateral, setMinSafeCollateral] = useState(); - const [maxRemovableCollateral, setMaxRemovableCollateral] = useState(); - const [maxCollateral, setMaxCollateral] = useState(); + const [minCollatRatio, setMinCollatRatio] = useState(); + const [minCollatRatioPct, setMinCollatRatioPct] = useState(); + const [minSafeCollatRatio, setMinSafeCollatRatio] = useState(); + const [minSafeCollatRatioPct, setMinSafeCollatRatioPct] = useState(); + const [minSafeCollateral, setMinSafeCollateral] = useState(); + const [maxRemovableCollateral, setMaxRemovableCollateral] = useState(); + const [maxCollateral, setMaxCollateral] = useState(); const [totalDebt, setTotalDebt] = useState(); - const [totalDebt_, setTotalDebt_] = useState(); + const [totalDebt_, setTotalDebt_] = useState(); const [totalCollateral, setTotalCollateral] = useState(); - const [totalCollateral_, setTotalCollateral_] = useState(); + const [totalCollateral_, setTotalCollateral_] = useState(); /* update the prices/limits if anything changes with the asset pair */ useEffect(() => { @@ -215,6 +214,8 @@ export const useCollateralHelpers = ( minSafeCollatRatio, _selectedSeries, getTimeTillMaturity, + selectedBase?.decimals, + selectedVR, ]); /* Monitor for undercollaterization/ danger-collateralisation, and set flags if reqd. */ diff --git a/src/types/operations.ts b/src/types/operations.ts index 8caf16a6e..86a3517bb 100644 --- a/src/types/operations.ts +++ b/src/types/operations.ts @@ -50,6 +50,7 @@ export namespace LadleActions { export type SERVE = [vaultId: string, to: string, ink: BigNumberish, base: BigNumberish, max: BigNumberish]; export type CLOSE = [vaultId: string, to: string, ink: BigNumberish, art: BigNumberish]; export type REPAY = [vaultId: string, to: string, ink: BigNumberish, min: BigNumberish]; + export type REPAY_VR = [vaultId: string, inkTo: string, refundTo: string, ink: BigNumberish]; export type REPAY_VAULT = [vaultId: string, to: string, ink: BigNumberish, max: BigNumberish]; export type REPAY_LADLE = [vaultId: string]; export type RETRIEVE = [assetAddress: string, to: string]; diff --git a/src/utils/appUtils.ts b/src/utils/appUtils.ts index 3a0e029da..3d90fee7d 100644 --- a/src/utils/appUtils.ts +++ b/src/utils/appUtils.ts @@ -4,6 +4,7 @@ import { uniqueNamesGenerator, Config, adjectives, animals } from 'unique-names- import { ContractMap, ContractNames } from '../config/contracts'; import { ActionCodes, ISeries } from '../types'; +import { Cauldron, VRCauldron } from '../contracts'; export const copyToClipboard = (str: string) => { const el = document.createElement('textarea'); @@ -223,11 +224,8 @@ export const getPositionPath = ( } }; -export const getVaultIdFromReceipt = (receipt: ContractReceipt | undefined, contractMap: ContractMap | undefined) => { - if (!receipt || !contractMap) return ''; - - const cauldron = contractMap.get(ContractNames.CAULDRON); - if (!cauldron) return ''; +export const getVaultIdFromReceipt = (receipt: ContractReceipt | undefined, cauldron: Cauldron | VRCauldron) => { + if (!receipt) return ''; const cauldronAddr = cauldron.address; const vaultIdHex = receipt.events?.filter((e) => e.address === cauldronAddr)[0]?.topics[1]!;