diff --git a/package-lock.json b/package-lock.json
index 77d213d5..b2ab9991 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -41,7 +41,7 @@
"node-fetch": "^2.6.1",
"node-vibrant": "^3.1.6",
"polished": "^4.1.3",
- "rari-components": "git+https://github.com/Rari-Capital/rari-components#9541f72a093da5969155d53324d281a90f7c6b1e",
+ "rari-components": "git+https://github.com/Rari-Capital/rari-components#7ddc0eb58507b28e8b4be026cabb26294f179d0b",
"rari-tokens-generator": "^2.0.0",
"react": "^17.0.2",
"react-apexcharts": "1.3.7",
@@ -15788,8 +15788,8 @@
},
"node_modules/rari-components": {
"version": "0.0.1",
- "resolved": "git+ssh://git@github.com/Rari-Capital/rari-components.git#9541f72a093da5969155d53324d281a90f7c6b1e",
- "integrity": "sha512-4bPXZTlG+RbAyrhoZzVSaUNIibshxfv0Sj8ta5Ni1xraUKrTDLCJ+FGQ0q6M34okz4bHNOz8yaT6PHdO+FvU4Q==",
+ "resolved": "git+ssh://git@github.com/Rari-Capital/rari-components.git#7ddc0eb58507b28e8b4be026cabb26294f179d0b",
+ "integrity": "sha512-27en+sZhntPvTW1XKaZ1jt6+NN1RmZcxgdn9/7tt34rBAohRRbZIhh/pkAt7Z5pjwLoBvscT0PSyXWCmXUqmjg==",
"peerDependencies": {
"@chakra-ui/icons": "1.x",
"@chakra-ui/react": "^1.8",
@@ -32578,9 +32578,9 @@
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"rari-components": {
- "version": "git+ssh://git@github.com/Rari-Capital/rari-components.git#9541f72a093da5969155d53324d281a90f7c6b1e",
- "integrity": "sha512-4bPXZTlG+RbAyrhoZzVSaUNIibshxfv0Sj8ta5Ni1xraUKrTDLCJ+FGQ0q6M34okz4bHNOz8yaT6PHdO+FvU4Q==",
- "from": "rari-components@git+https://github.com/Rari-Capital/rari-components#9541f72a093da5969155d53324d281a90f7c6b1e",
+ "version": "git+ssh://git@github.com/Rari-Capital/rari-components.git#7ddc0eb58507b28e8b4be026cabb26294f179d0b",
+ "integrity": "sha512-27en+sZhntPvTW1XKaZ1jt6+NN1RmZcxgdn9/7tt34rBAohRRbZIhh/pkAt7Z5pjwLoBvscT0PSyXWCmXUqmjg==",
+ "from": "rari-components@git+https://github.com/Rari-Capital/rari-components#7ddc0eb58507b28e8b4be026cabb26294f179d0b",
"requires": {}
},
"rari-tokens-generator": {
diff --git a/package.json b/package.json
index 8beec3e9..b904132a 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,7 @@
"node-fetch": "^2.6.1",
"node-vibrant": "^3.1.6",
"polished": "^4.1.3",
- "rari-components": "git+https://github.com/Rari-Capital/rari-components#9541f72a093da5969155d53324d281a90f7c6b1e",
+ "rari-components": "git+https://github.com/Rari-Capital/rari-components#7ddc0eb58507b28e8b4be026cabb26294f179d0b",
"rari-tokens-generator": "^2.0.0",
"react": "^17.0.2",
"react-apexcharts": "1.3.7",
diff --git a/src/components/pages/Fuse/FusePoolPage/IncentivesRows.tsx b/src/components/pages/Fuse/FusePoolPage/IncentivesRows.tsx
index b90ee714..a082e4f5 100644
--- a/src/components/pages/Fuse/FusePoolPage/IncentivesRows.tsx
+++ b/src/components/pages/Fuse/FusePoolPage/IncentivesRows.tsx
@@ -4,6 +4,7 @@ import {
Text,
useDisclosure,
AvatarGroup,
+ HStack,
} from "@chakra-ui/react";
import { Row } from "lib/chakraUtils";
import { SimpleTooltip } from "components/shared/SimpleTooltip";
@@ -77,8 +78,8 @@ export const PluginIncentivesRow: React.FC<{
}> = ({ incentives, market, tokenData }) => {
const { isOpen, onOpen, onClose } = useDisclosure()
-
const rewardTokens = Object.keys(incentives).map((flywheel, i) => incentives[flywheel].rewardToken)
+ const apr = Object.values(incentives).reduce((number, value) => value.formattedAPR + number, 0)
return (
<>
@@ -88,35 +89,42 @@ export const PluginIncentivesRow: React.FC<{
// mb={.5}
crossAxisAlignment="center"
mainAxisAlignment="flex-end"
- py={2}
+ py={4}
zIndex={10}
onClick={(e) => {
e.stopPropagation();
onOpen();
}}
>
-
- + 🔌
-
+
+
+ {`+`}
+
+
-
- {rewardTokens.map((rewardToken, i) => {
- return (
- handleMouseEnter(i)}
- // onMouseLeave={() => handleMouseLeave()}
- _hover={{
- zIndex: 9,
- border: ".5px solid white",
- transform: "scale(1.3);",
- }}
- />
- );
- })}
-
+
+
+ {rewardTokens.map((rewardToken, i) => {
+ return (
+ handleMouseEnter(i)}
+ // onMouseLeave={() => handleMouseLeave()}
+ _hover={{
+ zIndex: 9,
+ border: ".5px solid white",
+ transform: "scale(1.3);",
+ }}
+ />
+ );
+ })}
+
+
+ {apr.toFixed(2)}% APR
+
+
>
diff --git a/src/components/pages/Fuse/Modals/CVXMigrateModal.tsx b/src/components/pages/Fuse/Modals/CVXMigrateModal.tsx
new file mode 100644
index 00000000..de9622c7
--- /dev/null
+++ b/src/components/pages/Fuse/Modals/CVXMigrateModal.tsx
@@ -0,0 +1,437 @@
+import {
+ Modal,
+ ModalOverlay,
+ ModalContent,
+ ModalHeader,
+ ModalFooter,
+ ModalBody,
+ ModalCloseButton,
+ Text,
+ Flex,
+ VStack,
+ HStack,
+ useToast,
+ Avatar,
+ Image,
+ Box,
+ Accordion,
+ AvatarGroup,
+} from "@chakra-ui/react"
+import { CTokenAvatarGroup, CTokenIcon } from "components/shared/Icons/CTokenIcon"
+import { POOL_156_COMPTROLLER } from "constants/convex"
+import { useRari } from "context/RariContext"
+import { getEthUsdPriceBN } from "esm/utils/getUSDPriceBN"
+import { BigNumber, constants } from "ethers"
+import { commify, parseEther } from "ethers/lib/utils"
+import { formatEther } from "ethers/lib/utils"
+import { FlywheelPluginRewardsMap, useConvexPoolIncentives } from "hooks/convex/useConvexRewards"
+import { useCurveLPBalances, useStakedConvexBalances } from "hooks/convex/useStakedConvexBalances"
+import { useBorrowLimit, useTotalSupply } from "hooks/useBorrowLimit"
+import { useFusePoolData } from "hooks/useFusePoolData"
+import useHasApproval from "hooks/useHasApproval"
+import { useTokensDataAsMap } from "hooks/useTokenData"
+import { Button, ExpandableCard, Heading } from "rari-components/standalone";
+import { useEffect, useMemo, useState } from "react"
+import { useQuery } from "react-query"
+import { TokensDataMap } from "types/tokens"
+import { shortUsdFormatter, smallStringUsdFormatter } from "utils/bigUtils"
+import { checkAllowanceAndApprove, collateralize, deposit, unstakeAndWithdrawCVXPool } from "utils/convex/migratePositions"
+import { handleGenericError } from "utils/errorHandling"
+import { USDPricedFuseAsset } from "utils/fetchFusePoolData"
+
+export const CVXMigrateModal = ({
+ isOpen,
+ onClose,
+}: {
+ isOpen: boolean,
+ onClose: () => void,
+}) => {
+
+ const { address, fuse } = useRari()
+ const cvxBalances = useStakedConvexBalances()
+ const curveLPBalances = useCurveLPBalances()
+
+ // Fuse pool Data
+ const fusePoolData = useFusePoolData("156")
+ const { incentives } = useConvexPoolIncentives(fusePoolData?.comptroller) ?? { incentives: {} };
+
+ const assets = !fusePoolData ? [] : fusePoolData.assets.filter(a => Object.keys(cvxBalances).includes(a.cToken) || Object.keys(curveLPBalances).includes(a.underlyingToken))
+ const [assetIndex, setAssetIndex] = useState(0)
+ const tokenData = useTokensDataAsMap(assets.map(a => a.underlyingToken))
+
+ // Steppers
+ const toast = useToast()
+ const [step, setStep] = useState()
+ const [activeStep, setActiveStep] = useState()
+
+
+ // marketAddr -> underlying
+ const marketsUnderlyingMap: { [underlying: string]: string } = assets.reduce((obj, asset) => ({
+ ...obj,
+ [asset.cToken]: asset.underlyingToken
+ }), {})
+
+ // marketAddr -> migratable balances data
+ const marketsBalancesMap: {
+ [marketAddr: string]: {
+ stakedBalance: BigNumber,
+ curveBalance: BigNumber,
+ total: BigNumber
+ }
+ } = assets.reduce((obj, asset) => {
+ const stakedBalance = cvxBalances[asset.cToken]?.balance ?? constants.Zero
+ const curveBalance = curveLPBalances[asset.underlyingToken] ?? constants.Zero
+ const total = stakedBalance.add(curveBalance)
+
+ if (total.isZero()) return { ...obj }
+ return {
+ ...obj,
+ [asset.cToken]: {
+ stakedBalance,
+ curveBalance,
+ total
+ }
+ }
+ }, {})
+
+ const market = assets[assetIndex]
+ const marketBalanceForAsset = marketsBalancesMap[market?.cToken]
+ const pluginIncentivesForAsset = incentives[market?.cToken]
+
+ // Simulates you depositing all your CVX positions into us - to get projected totalSupply & projected borrowLimit
+ const { data: updatedUserAssets } = useQuery('updated assets for convex user ' + address, async () => {
+
+ const ethPrice = await getEthUsdPriceBN()
+
+ const updatedUserAssets: USDPricedFuseAsset[] = fusePoolData?.assets.reduce((arr: USDPricedFuseAsset[], asset) => {
+ if (Object.keys(marketsBalancesMap).includes(asset.cToken)) {
+ const assetToBeUpdated = asset
+ const amount = (marketsBalancesMap[asset.cToken].total)
+
+ const supplyBalance = assetToBeUpdated.supplyBalance.add(amount);
+ const totalSupply = assetToBeUpdated.totalSupply.add(amount);
+
+ // Todo - figure out where to better div by 1e18
+ const updatedAsset: USDPricedFuseAsset = {
+ ...assetToBeUpdated,
+ supplyBalance,
+ supplyBalanceUSD: supplyBalance
+ .mul(assetToBeUpdated.underlyingPrice)
+ .mul(parseEther(ethPrice.toString()))
+ .div(constants.WeiPerEther)
+ .div(constants.WeiPerEther)
+ .div(constants.WeiPerEther)
+ .div(constants.WeiPerEther),
+ totalSupply,
+ membership: true
+ }
+ return [...arr, updatedAsset]
+ }
+ return [...arr, asset]
+ }, []) ?? []
+
+ return updatedUserAssets
+ })
+
+ // Simulated position
+ const borrowLimit = useBorrowLimit(updatedUserAssets ?? [])
+ const newTotalSupply = useTotalSupply(updatedUserAssets ?? [])
+
+ /* Unstakes and Claims all CVX Staked Positions supported by Fuse */
+ const handleUnstake = async () => {
+ const { stakedBalance } = marketBalanceForAsset
+ try {
+ setActiveStep(1)
+ if (stakedBalance.gt(0)) {
+ const baseRewardPool = cvxBalances[assets[assetIndex].cToken].baseRewardsPool
+ const res = await unstakeAndWithdrawCVXPool(fuse, baseRewardPool)
+ setStep(2)
+
+ }
+ } catch (err) {
+ setActiveStep(undefined)
+
+ handleGenericError(err, toast)
+ }
+ }
+
+ // Approve for stakedBalance + curveBalance
+ const handleApproveMarket = async () => {
+ const { cToken, underlyingToken } = assets[assetIndex]
+ const { total } = marketBalanceForAsset
+ try {
+ setActiveStep(2)
+ const res = await checkAllowanceAndApprove(fuse, address, cToken, underlyingToken, total)
+ console.log({ res })
+ setStep(3)
+ } catch (err) {
+ setActiveStep(undefined)
+
+ handleGenericError(err, toast)
+ }
+ }
+
+ // Deposit curveBalance
+ const handleDeposit = async () => {
+ const { cToken } = assets[assetIndex]
+ const { curveBalance } = marketBalanceForAsset
+ try {
+ setActiveStep(3)
+ const res = await deposit(fuse, cToken, curveBalance)
+ console.log({ res })
+ setStep(4)
+ } catch (err) {
+ setActiveStep(undefined)
+
+ handleGenericError(err, toast)
+ }
+ }
+
+ // Collateralize all
+ const handleCollateralize = async () => {
+ const markets = Object.keys(marketsBalancesMap)
+ try {
+ setActiveStep(4)
+ const res = await collateralize(fuse, POOL_156_COMPTROLLER, markets)
+ console.log({ res })
+ setStep(5)
+ } catch (err) {
+ setActiveStep(undefined)
+
+ handleGenericError(err, toast)
+ }
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+ Migrate Staked CVX Positions to Fuse
+
+
+
+
+
+
+
+
+ {/* */}
+
+
+ We detected {newTotalSupply.isZero() ? '?' : smallStringUsdFormatter(newTotalSupply.toString())} from {Object.keys(cvxBalances).length}{' '}
+ staked Convex positions
+ {!!(Object.keys(curveLPBalances)).length && ` and ${Object.keys(curveLPBalances).length} Curve LP tokens`}.
+ You can keep earning CVX + CRV rewards and borrow up to {borrowLimit.isZero() ? '?' : smallStringUsdFormatter(borrowLimit.toString())} by migrating them to Fuse.
+ {/* Select from available markets */}
+ setAssetIndex(i)}>
+
+ {Object.keys(marketsBalancesMap).map((market, i) =>
+
+ )}
+
+
+
+
+
+
+ {!localStorage.RARI_HIDE_MIGRATOR_POPUP && (
+
+ )}
+
+
+
+
+ >
+ )
+}
+
+const Market = ({
+ assetIndex,
+ asset,
+ i,
+ tokensData,
+ marketsUnderlyingMap,
+ marketBalanceForAsset,
+ step,
+ activeStep,
+ setStep,
+ handleUnstake,
+ handleApproveMarket,
+ handleDeposit,
+ handleCollateralize,
+ updatedAssets,
+ pluginIncentivesForAsset
+}: {
+ assetIndex: number,
+ setAssetIndex: (step: number) => void,
+ asset: USDPricedFuseAsset,
+ i: number;
+ tokensData: TokensDataMap,
+ marketsUnderlyingMap: { [underlying: string]: string },
+ marketBalanceForAsset: {
+ stakedBalance: BigNumber,
+ curveBalance: BigNumber,
+ total: BigNumber
+ },
+ step: number | undefined,
+ activeStep: number | undefined,
+ setStep: (step: number | undefined) => void,
+ handleUnstake: any,
+ handleApproveMarket: any,
+ handleDeposit: any,
+ handleCollateralize: any,
+ updatedAssets: USDPricedFuseAsset[] | undefined,
+ pluginIncentivesForAsset: FlywheelPluginRewardsMap | undefined,
+}) => {
+
+ const hasApproval = useHasApproval(asset, marketBalanceForAsset?.total.toString() ?? "0")
+ const showApproval = !hasApproval
+ // We show enable as Collateral only if this asset has not yet been anabled
+ const showEnableAsCollateral = !asset?.membership
+ // If you dont have any staked, you dont need to unstake to enter this market
+ const showUnstake = !marketBalanceForAsset?.stakedBalance?.isZero() ?? true
+
+ const tokenData = tokensData[asset?.underlyingToken]
+ const activeSymbol = tokenData?.symbol ?? asset?.underlyingSymbol
+
+ const [numClicks, setNumClicks] = useState(3)
+
+ const updatedAsset = useMemo(() => {
+ if (!!updatedAssets && !!asset) {
+ return updatedAssets.find(a => a.cToken === asset.cToken)
+ }
+ }, [updatedAssets, asset])
+
+ const rewardTokens = Object.keys(pluginIncentivesForAsset ?? {})?.map((flywheel, i) => pluginIncentivesForAsset![flywheel].rewardToken) ?? []
+ const apr = Object.values(pluginIncentivesForAsset ?? {})?.reduce((number, value) => value.formattedAPR + number, 0) ?? 0
+
+ // Skip to step conditionally
+ useEffect(() => {
+ let clicks = 3
+ if (!showUnstake) {
+ setStep(2)
+ clicks -= 1
+ }
+ if (hasApproval) {
+ setStep(3)
+ clicks -= 1
+ }
+ else setStep(undefined)
+ setNumClicks(clicks)
+ }, [assetIndex])
+
+
+ return (
+
+
+
+ Migrate {activeSymbol} in {numClicks} click{numClicks !== 1 && 's'} and earn
+
+
+
+
+ {apr.toFixed(2)}% APR
+
+
+
+
+
+
+ {showUnstake && (
+
+ )}
+
+ {showApproval && (
+
+ )}
+
+
+
+ {showEnableAsCollateral && step === 4 &&
+
+ }
+
+ {step === 5 && Done!}
+
+
+ }
+ >
+
+
+
+
+ {activeSymbol}
+
+
+
+
+ {commify(parseFloat(formatEther(marketBalanceForAsset.stakedBalance)).toFixed(2))} staked
+
+
+ ·
+
+
+ {commify(parseFloat(formatEther(marketBalanceForAsset.curveBalance)).toFixed(2))} unstaked
+
+
+
+
+ )
+}
+
+
+
+
+export default CVXMigrateModal
diff --git a/src/components/pages/Fuse/Modals/PluginModal/PluginRewardsModal.tsx b/src/components/pages/Fuse/Modals/PluginModal/PluginRewardsModal.tsx
index 4e31b450..448f0cbc 100644
--- a/src/components/pages/Fuse/Modals/PluginModal/PluginRewardsModal.tsx
+++ b/src/components/pages/Fuse/Modals/PluginModal/PluginRewardsModal.tsx
@@ -13,7 +13,8 @@ import {
Link,
Button,
Heading,
-VStack,
+ VStack,
+ HStack,
} from "@chakra-ui/react"
import { TokenData } from "hooks/useTokenData"
import { USDPricedFuseAsset } from "utils/fetchFusePoolData"
@@ -21,7 +22,7 @@ import { USDPricedFuseAsset } from "utils/fetchFusePoolData"
import { InfoIcon } from "@chakra-ui/icons"
import AppLink from "components/shared/AppLink"
import { CTokenAvatarGroup } from "components/shared/Icons/CTokenIcon"
-import { eligibleTokens, tokenInfo } from "constants/convex"
+import { eligibleTokens, CONVEX_CTOKEN_INFO } from "constants/convex"
export const PluginRewardsModal = ({
market,
@@ -59,31 +60,31 @@ export const PluginRewardsModal = ({
{/* */}
- This market streams
- rewards
- from the {tokenInfo[market.underlyingSymbol].convexPoolName} Convex pool
- to suppliers of {tokenInfo[market.underlyingSymbol].curvePoolName} Curve LPs.
- {/* Deposit your {tokenInfo[market.underlyingSymbol].curvePoolName} Curve LP tokens into Fuse to borrow against it while earning all the same rewards from Convex. */}
- {/* View reward rates for {tokenInfo[market.underlyingSymbol].convexPoolName} on Convex */}
+
+ This market streams rewards
+
+ from the {CONVEX_CTOKEN_INFO[market.underlyingSymbol].convexPoolName} Convex pool
+ to suppliers of {CONVEX_CTOKEN_INFO[market.underlyingSymbol].curvePoolName} Curve LPs.
+ {/* Deposit your {CONVEX_CTOKEN_INFO[market.underlyingSymbol].curvePoolName} Curve LP tokens into Fuse to borrow against it while earning all the same rewards from Convex. */}
+ {/* View reward rates for {CONVEX_CTOKEN_INFO[market.underlyingSymbol].convexPoolName} on Convex */}
+ * Rates shown do not include the base Curve vAPRs, but you get those too
{/* */}
-
Info
-
-
-
-
+
+
+
}
-
diff --git a/src/components/pages/Fuse/Modals/PoolModal/AmountSelect.tsx b/src/components/pages/Fuse/Modals/PoolModal/AmountSelect.tsx
index 420c7f66..747a1831 100644
--- a/src/components/pages/Fuse/Modals/PoolModal/AmountSelect.tsx
+++ b/src/components/pages/Fuse/Modals/PoolModal/AmountSelect.tsx
@@ -704,6 +704,9 @@ const StatsColumn = ({
ignoreIsEnabledCheckFor: enableAsCollateral ? asset.cToken : undefined,
}, `new limit`);
+
+ console.log({})
+
// console.log({ assets, updatedAssets })
const isSupplyingOrWithdrawing =
diff --git a/src/components/shared/Layout/Layout.tsx b/src/components/shared/Layout/Layout.tsx
index 9d2be4b8..0bb005ac 100644
--- a/src/components/shared/Layout/Layout.tsx
+++ b/src/components/shared/Layout/Layout.tsx
@@ -7,6 +7,12 @@ import { useMemo, useState, useEffect } from "react";
import NewHeader from "../Header2/NewHeader";
import Footer from "./Footer";
+//CVX
+import { useDisclosure } from "@chakra-ui/react";
+import CVXMigrateModal from "components/pages/Fuse/Modals/CVXMigrateModal";
+import { useCurveLPBalances, useStakedConvexBalances } from "hooks/convex/useStakedConvexBalances";
+import PluginMigratorButton from "./PluginMigratorButton";
+
const Layout = ({ children }) => {
const { chainId } = useRari()
@@ -37,6 +43,16 @@ const Layout = ({ children }) => {
return () => document.removeEventListener("keydown", handler);
}, []);
+ const cvxBalances = useStakedConvexBalances()
+ const curveLPBalances = useCurveLPBalances()
+ const hasCvxBalances = !!Object.keys(cvxBalances ?? {}).length || !!Object.keys(curveLPBalances ?? {}).length
+ const { isOpen, onOpen, onClose } = useDisclosure()
+
+ console.log({ cvxBalances, curveLPBalances })
+
+ useEffect(() => {
+ if (!!hasCvxBalances && (!localStorage.RARI_HIDE_MIGRATOR_POPUP)) onOpen()
+ }, [hasCvxBalances])
return (
{
{children}
+ {!!hasCvxBalances && }
+ {!!hasCvxBalances && }
);
@@ -69,4 +87,5 @@ const Layout = ({ children }) => {
+
export default Layout;
diff --git a/src/components/shared/Layout/PluginMigratorButton.tsx b/src/components/shared/Layout/PluginMigratorButton.tsx
new file mode 100644
index 00000000..540ec9b4
--- /dev/null
+++ b/src/components/shared/Layout/PluginMigratorButton.tsx
@@ -0,0 +1,32 @@
+
+
+import { Button, Image, keyframes, usePrefersReducedMotion } from "@chakra-ui/react"
+
+
+const spin = keyframes`
+ from { transform: rotateY(0deg); }
+ to { transform: rotateY(360deg); }
+`
+
+export const PluginMigratorButton = ({ onOpen }: { onOpen: any }) => {
+ const prefersReducedMotion = usePrefersReducedMotion()
+
+ const animation = prefersReducedMotion
+ ? undefined
+ : `${spin} infinite 5s linear`
+ return (
+
+
+
+ )
+}
+
+export default PluginMigratorButton
\ No newline at end of file
diff --git a/src/components/shared/SwitchNetworkMenu.tsx b/src/components/shared/SwitchNetworkMenu.tsx
index 407670ee..932e78c4 100644
--- a/src/components/shared/SwitchNetworkMenu.tsx
+++ b/src/components/shared/SwitchNetworkMenu.tsx
@@ -86,7 +86,6 @@ const SwitchNetworkMenu: React.FC = () => {
// If user presses meta key or control key + slash they will toggle the private allocation claim mode.
useEffect(() => {
const handler = (e: KeyboardEvent) => {
- console.log(e.code)
if (e.code === "Slash") {
e.preventDefault();
setDevMode(true);
diff --git a/src/constants/convex.ts b/src/constants/convex.ts
index ce99792c..6f70fa7a 100644
--- a/src/constants/convex.ts
+++ b/src/constants/convex.ts
@@ -1,6 +1,6 @@
export const eligibleTokens = ["FRAX3CRV-f", "steCRV", "UST_whv23CRV-f", "crv3crypto", "D3-f", "FEI3CRV3CRV-f", "alUSD3CRV-f"]
-export const tokenInfo: PluginCTokenInfoType = {
+export const CONVEX_CTOKEN_INFO: PluginCTokenInfoType = {
"FRAX3CRV-f": {
"cToken": "0x2ec70d3Ff3FD7ac5c2a72AAA64A398b6CA7428A5",
"plugin": "0xd88e89ac6a0859e9b91078cb2a183a36ba6c8933",
@@ -89,3 +89,8 @@ type PluginCTokenInfoType = {
}
export const POOL_156_COMPTROLLER = "0x07cd53380FE9B2a5E64099591b498c73F0EfaA66"
+
+
+export const SUPPORTED_CONVEX_LPS = [
+
+]
\ No newline at end of file
diff --git a/src/context/BalancesContext.tsx b/src/context/BalancesContext.tsx
index 1d06711c..d2ffd938 100644
--- a/src/context/BalancesContext.tsx
+++ b/src/context/BalancesContext.tsx
@@ -1,9 +1,9 @@
+
import { useTokenBalances } from "hooks/useTokenBalance";
import { SubgraphUnderlyingAsset } from "pages/api/explore";
-import { createContext, useContext, ReactNode, useMemo } from "react";
+import { createContext, useContext, ReactNode, useMemo, useEffect } from "react";
import { queryAllUnderlyingAssets } from "services/gql";
import useSWR from "swr";
-import { UnderlyingAsset } from "types/tokens";
import { useRari } from "./RariContext";
export const BalancesContext = createContext(undefined);
@@ -54,15 +54,16 @@ export const BalancesContextProvider = ({
return ret;
}, [tokenBalances, isAuthed]);
+
return (
-
+
{children}
);
};
export const useAccountBalances = (): [any, string[]] => {
- const balances = useContext(BalancesContext);
+ const { balances} = useContext(BalancesContext);
const significantTokens: string[] = useMemo(
() =>
diff --git a/src/context/RariContext.tsx b/src/context/RariContext.tsx
index 4a02abf0..a747859e 100644
--- a/src/context/RariContext.tsx
+++ b/src/context/RariContext.tsx
@@ -16,7 +16,7 @@ import { DASHBOARD_BOX_PROPS } from "../components/shared/DashboardBox";
import { Fuse } from "../esm";
-import LogRocket, { init } from "logrocket";
+import LogRocket from "logrocket";
import { useToast } from "@chakra-ui/react";
import {
@@ -131,6 +131,10 @@ export const RariProvider = ({ children }: { children: ReactNode }) => {
const queryClient = useQueryClient();
const { t } = useTranslation();
+ useEffect(() => {
+ // if (!!requestedAddress) setAddress(requestedAddress as string)
+ }, [requestedAddress])
+
useEffect(() => {
//toast on brave users
if(typeof navigator !== 'object') return
diff --git a/src/contracts/abi/ConvexBaseRewardPool.json b/src/contracts/abi/ConvexBaseRewardPool.json
new file mode 100644
index 00000000..35189fec
--- /dev/null
+++ b/src/contracts/abi/ConvexBaseRewardPool.json
@@ -0,0 +1 @@
+[{"inputs":[{"internalType":"uint256","name":"pid_","type":"uint256"},{"internalType":"address","name":"stakingToken_","type":"address"},{"internalType":"address","name":"rewardToken_","type":"address"},{"internalType":"address","name":"operator_","type":"address"},{"internalType":"address","name":"rewardManager_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"}],"name":"RewardAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"}],"name":"RewardPaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Staked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawn","type":"event"},{"inputs":[{"internalType":"address","name":"_reward","type":"address"}],"name":"addExtraReward","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"clearExtraRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"currentRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"donate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"duration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"earned","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"extraRewards","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"extraRewardsLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getReward","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"bool","name":"_claimExtras","type":"bool"}],"name":"getReward","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"historicalRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastTimeRewardApplicable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastUpdateTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"newRewardRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"operator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"periodFinish","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pid","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_rewards","type":"uint256"}],"name":"queueNewRewards","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"queuedRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardPerToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardPerTokenStored","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"stake","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stakeAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_for","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"stakeFor","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stakingToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userRewardPerTokenPaid","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bool","name":"claim","type":"bool"}],"name":"withdraw","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"claim","type":"bool"}],"name":"withdrawAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"claim","type":"bool"}],"name":"withdrawAllAndUnwrap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bool","name":"claim","type":"bool"}],"name":"withdrawAndUnwrap","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]
\ No newline at end of file
diff --git a/src/esm/utils/networks.js b/src/esm/utils/networks.js
index 4929a457..bfe6ead8 100644
--- a/src/esm/utils/networks.js
+++ b/src/esm/utils/networks.js
@@ -81,15 +81,15 @@ export const chainMetadata = {
blockExplorerURL: "https://optimistic.etherscan.io",
color: "#FE0521",
},
- // [ChainID.HARDHAT]: {
- // chainId: ChainID.HARDHAT,
- // name: "Hardhat",
- // imageUrl: "/static/networks/optimism.svg", // no logo
- // supported: true,
- // rpcUrl: "http://localhost:8545",
- // blockExplorerURL: "",
- // color: "#BC6C6C"
- // }
+ [ChainID.HARDHAT]: {
+ chainId: ChainID.HARDHAT,
+ name: "Hardhat",
+ imageUrl: "/static/networks/optimism.svg", // no logo
+ supported: true,
+ rpcUrl: "http://localhost:8545",
+ blockExplorerURL: "",
+ color: "#BC6C6C"
+ }
};
export const isSupportedChainId = (chainId) => Object.values(ChainID).includes(chainId);
export function getSupportedChains() {
diff --git a/src/hooks/convex/useConvexPoolSuppliedCTokens.ts b/src/hooks/convex/useConvexPoolSuppliedCTokens.ts
index 4578bb92..2edee266 100644
--- a/src/hooks/convex/useConvexPoolSuppliedCTokens.ts
+++ b/src/hooks/convex/useConvexPoolSuppliedCTokens.ts
@@ -1,4 +1,4 @@
-import { tokenInfo } from 'constants/convex';
+import { CONVEX_CTOKEN_INFO } from 'constants/convex';
import { useRari } from 'context/RariContext'
import { BigNumber } from 'ethers';
import { Interface } from 'ethers/lib/utils';
@@ -11,7 +11,7 @@ export const useConvexPoolSuppliedCTokens = (comptrollerAddress: string) => {
const { data: suppliedMarkets } = useQuery(`Pool ${comptrollerAddress} CTokens User ${address}`, async () => {
- const markets = Object.values(tokenInfo).map((value => value.cToken))
+ const markets = Object.values(CONVEX_CTOKEN_INFO).map((value => value.cToken))
const IComptroller = new Interface(JSON.parse(
fuse.compoundContracts["contracts/CErc20Delegate.sol:CErc20Delegate"].abi
diff --git a/src/hooks/convex/useStakedConvexBalances.ts b/src/hooks/convex/useStakedConvexBalances.ts
new file mode 100644
index 00000000..a2f5e6c2
--- /dev/null
+++ b/src/hooks/convex/useStakedConvexBalances.ts
@@ -0,0 +1,121 @@
+import { providers } from "@0xsequence/multicall";
+import { useRari } from "context/RariContext";
+import { useQuery } from "react-query";
+
+import BaseRewardPoolABI from "contracts/abi/ConvexBaseRewardPool.json"
+import { CONVEX_CTOKEN_INFO } from "constants/convex";
+import { Contract } from "ethers";
+import { BigNumber } from "ethers";
+import { Interface } from "ethers/lib/utils";
+
+export type StakedConvexBalancesMap = {
+ [cToken: string]: {
+ balance: BigNumber,
+ baseRewardsPool: string
+ }
+}
+
+
+/* For Staked CVX positions - there's no ERC20 to query balance for */
+export const useStakedConvexBalances = (): StakedConvexBalancesMap => {
+ const { fuse, address, isAuthed } = useRari();
+ const multiCallProvider = new providers.MulticallProvider(fuse.provider)
+ const { data: stakedConvexBalances } = useQuery(
+ ' staked convex balances for ' + address,
+ async () => {
+ if (!isAuthed) return undefined
+
+ let map: StakedConvexBalancesMap = {}
+
+ await Promise.all(Object.values(CONVEX_CTOKEN_INFO)
+ .map(c => ({ baseRewardsPool: c.rewardsContract, cToken: c.cToken }))
+ .map(({ baseRewardsPool, cToken }) => {
+ let contract = new Contract(baseRewardsPool, BaseRewardPoolABI as any, multiCallProvider)
+ return contract.balanceOf(address)
+ .then((balance: BigNumber) => {
+ if (!balance.isZero()) {
+ map[cToken] = {
+ balance,
+ baseRewardsPool,
+ }
+ }
+ })
+ .catch((_err: any) => { })
+ }
+ ))
+
+ console.log({ map })
+
+ return map
+ },
+ {
+ enabled: !!address ? true : false,
+ refetchOnMount: false,
+ refetchOnWindowFocus: false,
+ }
+ )
+
+ // const hasBalances =
+
+ return stakedConvexBalances ?? {}
+
+}
+
+
+type BalancesMap = {
+ [token: string]: BigNumber
+}
+
+const IERC20 = new Interface([
+ 'function name() public view returns (string)',
+ 'function symbol() public view returns (string)',
+ 'function decimals() public view returns (uint8)',
+ 'function balanceOf(address _owner) public view returns (uint256 balance)',
+])
+
+export const useCurveLPBalances = (): BalancesMap => {
+
+
+ const { fuse, address, isAuthed } = useRari();
+ const multiCallProvider = new providers.MulticallProvider(fuse.provider)
+
+ const { data: curveLPBalances } = useQuery(
+ ' curve convex balances for ' + address,
+ async () => {
+
+ if (!isAuthed) return undefined
+
+ let map: BalancesMap = {}
+
+ await Promise.all(
+ Object.values(CONVEX_CTOKEN_INFO)
+ .map(c => ({ curveLPToken: c.lpToken }))
+ .map(({ curveLPToken }) => {
+ let contract = new Contract(curveLPToken, IERC20, multiCallProvider)
+ return contract.balanceOf(address)
+ .then((balance: BigNumber) => {
+ if (!balance.isZero()) {
+ map[curveLPToken] = balance
+ }
+ })
+ .catch((_err: any) => { })
+ }
+ ))
+ return map
+ },
+ {
+ enabled: !!address ? true : false,
+ refetchOnMount: false,
+ refetchOnWindowFocus: false,
+ }
+ )
+
+
+ console.log({curveLPBalances})
+ // const hasBalances =
+
+ return curveLPBalances ?? {}
+
+}
+
+
diff --git a/src/hooks/fuse/useUpdatedUserAssets.ts b/src/hooks/fuse/useUpdatedUserAssets.ts
index 3c9f2135..623a9822 100644
--- a/src/hooks/fuse/useUpdatedUserAssets.ts
+++ b/src/hooks/fuse/useUpdatedUserAssets.ts
@@ -53,7 +53,6 @@ const useUpdatedUserAssets = ({
.mul(assetToBeUpdated.underlyingPrice)
.mul(parseEther(ethPrice.toString()))
.div(constants.WeiPerEther),
-
totalSupply,
supplyRatePerBlock: interestRateModel.getSupplyRate(
totalSupply.gt(0)
diff --git a/src/hooks/useBorrowLimit.ts b/src/hooks/useBorrowLimit.ts
index 764ca825..9c1a9628 100644
--- a/src/hooks/useBorrowLimit.ts
+++ b/src/hooks/useBorrowLimit.ts
@@ -23,13 +23,32 @@ export const useBorrowLimit = (
}
const result = _maxBorrow.div(constants.WeiPerEther);
- // console.log({result, _maxBorrow, id})
return result
}, [assets, options?.ignoreIsEnabledCheckFor]);
return maxBorrow;
};
+export const useTotalSupply = (
+ assets: USDPricedFuseAsset[],
+): BigNumber => {
+ const totalSupply = useMemo(() => {
+ let _totalSupplyUSD = constants.Zero;
+
+ for (let i = 0; i < assets.length; i++) {
+ let asset = assets[i];
+ _totalSupplyUSD = _totalSupplyUSD.add(
+ asset.supplyBalanceUSD
+ )
+ }
+
+ // console.log({result, _maxBorrow, id})
+ return _totalSupplyUSD
+ }, [assets]);
+
+ return totalSupply;
+};
+
export const useBorrowLimits = (
assetsArray: USDPricedFuseAsset[][] | null,
options?: { ignoreIsEnabledCheckFor?: string }
diff --git a/src/hooks/useHasApproval.ts b/src/hooks/useHasApproval.ts
new file mode 100644
index 00000000..29b6f646
--- /dev/null
+++ b/src/hooks/useHasApproval.ts
@@ -0,0 +1,49 @@
+import { useQuery } from "react-query"
+import { useRari } from "context/RariContext"
+import { BigNumber, Contract } from "ethers"
+import { Interface, parseUnits } from "ethers/lib/utils"
+import { USDPricedFuseAsset } from "utils/fetchFusePoolData"
+import { isAssetETH } from "utils/tokenUtils"
+
+
+const useHasApproval = (market: USDPricedFuseAsset, amount: string) => {
+ const { fuse, address } = useRari()
+ const { provider } = fuse
+
+ const { data } = useQuery(`${address} has approval for ${market?.underlyingSymbol}`, async () => {
+ if (!address || !market) return false
+ let _amount = parseUnits(amount, market.underlyingDecimals)
+ return await checkAllowance(address, market.cToken, market.underlyingToken, _amount, provider)
+ })
+ return data ?? false
+}
+
+export default useHasApproval
+
+export async function checkAllowance(
+ userAddress: string,
+ marketAddress: string,
+ underlyingAddress: string,
+ amount: BigNumber,
+ provider: any
+) {
+ if (isAssetETH(underlyingAddress)) return
+
+ const erc20Interface = new Interface([
+ 'function allowance(address owner, address spender) public view returns (uint256 remaining)',
+ 'function approve(address spender, uint256 value) public returns (bool success)',
+ ])
+
+ const erc20Contract = new Contract(
+ underlyingAddress,
+ erc20Interface,
+ provider.getSigner(userAddress)
+ )
+
+ const hasApproval = (
+ await erc20Contract.callStatic.allowance(userAddress, marketAddress)
+ ).gte(amount);
+
+ return hasApproval
+
+}
\ No newline at end of file
diff --git a/src/hooks/useTokenBalance.ts b/src/hooks/useTokenBalance.ts
index a02458b4..08ff1784 100644
--- a/src/hooks/useTokenBalance.ts
+++ b/src/hooks/useTokenBalance.ts
@@ -14,6 +14,7 @@ import { ETH_TOKEN_DATA } from "./useTokenData";
import { Contract, BigNumber as EthersBigNumber, constants } from "ethers";
import { providers } from "@0xsequence/multicall"
+import { BigNumber } from "ethers";
export const fetchTokenBalance = async (
tokenAddress: string | undefined,
@@ -76,7 +77,7 @@ export const useTokenBalances = (tokenAddresses: string[]): number[] => {
let contract = new Contract(tokenAddress, ERC20ABI as any, multiCallProvider)
return contract.balanceOf(address)
- .then((balance: number) => parseFloat(balance.toString()))
+ .then((balance: BigNumber) => parseFloat(balance.toString()))
.catch((_err: any) => { })
})
)
diff --git a/src/hooks/useTokenData.ts b/src/hooks/useTokenData.ts
index 4b8d4c92..691b57e1 100644
--- a/src/hooks/useTokenData.ts
+++ b/src/hooks/useTokenData.ts
@@ -70,7 +70,7 @@ export const fetchTokenData = async (
try {
// Since running the vercel functions requires a Vercel account and is super slow,
// just fetch this data from the live site in development:
- let url = `https://v2.rari.capital/api/tokenData?address=${address.toLowerCase()}&chainId=${_chainId}`;
+ let url = _chainId === 1 ? `https://v2.rari.capital/api/tokenData?address=${address.toLowerCase()}` : `https://v2.rari.capital/api/tokenData?address=${address.toLowerCase()}&chainId=${_chainId}`;
data = {
...(await fetch(url).then((res) => res.json())),
diff --git a/src/utils/convex/migratePositions.ts b/src/utils/convex/migratePositions.ts
new file mode 100644
index 00000000..7ad97eb3
--- /dev/null
+++ b/src/utils/convex/migratePositions.ts
@@ -0,0 +1,194 @@
+import { testForCTokenErrorAndSend } from "components/pages/Fuse/Modals/PoolModal/AmountSelect";
+import BaseRewardPoolABI from "contracts/abi/ConvexBaseRewardPool.json"
+import { Contract } from "ethers";
+import { constants } from "ethers";
+import { BigNumber } from "ethers";
+import { Interface } from "ethers/lib/utils"
+import { USDPricedFuseAsset } from "utils/fetchFusePoolData";
+import { callStaticWithMultiCall, encodeCall, estimateGasWithMultiCall, sendWithMultiCall } from "utils/multicall"
+import { Fuse } from "../../esm";
+
+
+export const unstakeAndWithdrawCVXPool = async (fuse: Fuse, baseRewardPool: string) => {
+ const IBaseRewardPool = new Interface(JSON.stringify(BaseRewardPoolABI))
+
+ const BaseRewardPool = new Contract(baseRewardPool, IBaseRewardPool, fuse.provider.getSigner())
+
+ try {
+ let estimatedGas = await BaseRewardPool.estimateGas.withdrawAllAndUnwrap(true)
+ console.log({ estimatedGas })
+
+ let result = await BaseRewardPool.callStatic.withdrawAllAndUnwrap(true)
+ console.log({ result })
+
+ let tx = await BaseRewardPool.withdrawAllAndUnwrap(true)
+ console.log({ tx })
+
+ const txConfirmed = await tx.wait(1)
+ return txConfirmed;
+
+ } catch (err) {
+ console.error("Could not unstake and claim CVX Rewards")
+ throw err
+ }
+}
+
+
+/**
+ * @param marketAddress - Market/ctoken to give approval to.
+ * @param underlyingAddress - The token to approve.
+ * @param amount - Amount user is supplying.
+ * @param provider - An initiated ethers provider.
+ */
+export async function checkAllowanceAndApprove(
+ fuse: Fuse,
+ userAddress: string,
+ marketAddress: string,
+ underlyingAddress: string,
+ amount: BigNumber,
+) {
+ const erc20Interface = new Interface([
+ 'function allowance(address owner, address spender) public view returns (uint256 remaining)',
+ 'function approve(address spender, uint256 value) public returns (bool success)',
+ ])
+
+ const erc20Contract = new Contract(
+ underlyingAddress,
+ erc20Interface,
+ fuse.provider.getSigner(userAddress)
+ )
+
+ const hasApprovedEnough = (
+ await erc20Contract.callStatic.allowance(userAddress, marketAddress)
+ ).gte(amount);
+
+ if (!hasApprovedEnough) {
+ const max = BigNumber.from(2).pow(BigNumber.from(256)).sub(constants.One); //big fucking #
+ let tx = await erc20Contract.approve(marketAddress, max);
+ const txConfirmed = await tx.wait(1)
+ return txConfirmed;
+ }
+}
+
+// Todo - remove. Will be replaced by SDK
+export const deposit = async (fuse: Fuse, marketAddress: string, amount: BigNumber) => {
+ const market = new Contract(
+ marketAddress,
+ JSON.parse(
+ fuse.compoundContracts[
+ "contracts/CErc20Delegate.sol:CErc20Delegate"
+ ].abi),
+ fuse.provider.getSigner()
+ );
+
+ let tx = await testForCTokenErrorAndSend(
+ market.callStatic.mint,
+ amount,
+ market.mint,
+ "Cannot deposit this amount right now!"
+ );
+
+ const txConfirmed = await tx.wait(1)
+ console.log({txConfirmed})
+
+ return txConfirmed
+}
+
+export const collateralize = async (fuse: Fuse, comptrollerAddress: string, marketAddresses: string[]) => {
+ const comptroller = new Contract(
+ comptrollerAddress,
+ JSON.parse(
+ fuse.compoundContracts["contracts/Comptroller.sol:Comptroller"].abi
+ ),
+ fuse.provider.getSigner()
+ );
+
+ let tx = await comptroller.enterMarkets(marketAddresses);
+ const txConfirmed = await tx.wait(1)
+ return txConfirmed;
+}
+
+/*
+Can't Multicall these :/
+
+export const unstakeAndWithdrawStakedCVXMulti = async (fuse: Fuse, baseRewardPools: string[]) => {
+ const IBaseRewardPool = new Interface(JSON.stringify(BaseRewardPoolABI))
+
+ const encodedCalls = baseRewardPools.map(baseRewardPool => encodeCall(IBaseRewardPool, baseRewardPool, "withdrawAllAndUnwrap", [true]))
+ try {
+ let estimatedGas = await estimateGasWithMultiCall(fuse.provider, encodedCalls)
+ console.log({ estimatedGas })
+
+ let result = await callStaticWithMultiCall(fuse.provider, encodedCalls)
+ console.log({ result })
+
+ result = await sendWithMultiCall(fuse, encodedCalls)
+ console.log({ result })
+
+ return result
+
+ } catch (err) {
+ console.error("Could not unstake and claim CVX Rewards")
+ }
+}
+
+// Todo - move elswhere
+export const approveAllMarketsMulti = async (fuse: Fuse, assets: USDPricedFuseAsset[]) => {
+ const MAX_APPROVAL = BigNumber.from(2).pow(BigNumber.from(256)).sub(constants.One); //big fucking #
+
+ const IERC20 = new Interface(JSON.parse(
+ fuse.compoundContracts[
+ "contracts/EIP20Interface.sol:EIP20Interface"
+ ].abi
+ ))
+
+ const encodedCalls = assets.map(asset => encodeCall(IERC20, asset.underlyingToken, "approve", [asset.cToken, MAX_APPROVAL]))
+ try {
+ let estimatedGas = await estimateGasWithMultiCall(fuse.provider, encodedCalls)
+ console.log({ estimatedGas })
+
+ let result = await callStaticWithMultiCall(fuse.provider, encodedCalls)
+ console.log({ result })
+
+ result = await sendWithMultiCall(fuse, encodedCalls)
+ console.log({ result })
+
+ return result
+
+ } catch (err) {
+ console.error("Could not unstake and claim CVX Rewards")
+ }
+
+}
+
+
+export const unstakeAndWithdrawStakedCVXAndApproveCtokens = async (fuse: Fuse, baseRewardPools: string[], assets: USDPricedFuseAsset[]) => {
+ const IBaseRewardPool = new Interface(JSON.stringify(BaseRewardPoolABI))
+ const IERC20 = new Interface(JSON.parse(
+ fuse.compoundContracts[
+ "contracts/EIP20Interface.sol:EIP20Interface"
+ ].abi
+ ))
+ const MAX_APPROVAL = BigNumber.from(2).pow(BigNumber.from(256)).sub(constants.One); //big fucking #
+
+ let cvxEncodedCalls = baseRewardPools.map(baseRewardPool => encodeCall(IBaseRewardPool, baseRewardPool, "withdrawAllAndUnwrap", [true]))
+ let approvalEncodedCalls = assets.map(asset => encodeCall(IERC20, asset.underlyingToken, "approve", [asset.cToken, MAX_APPROVAL]))
+ const encodedCalls = [...cvxEncodedCalls, ...approvalEncodedCalls]
+
+ try {
+ let estimatedGas = await estimateGasWithMultiCall(fuse.provider, encodedCalls)
+ console.log({ estimatedGas })
+
+ let result = await callStaticWithMultiCall(fuse.provider, encodedCalls)
+ console.log({ result })
+
+ result = await sendWithMultiCall(fuse, encodedCalls)
+ console.log({ result })
+
+ return result
+
+ } catch (err) {
+ console.error("Could not unstake and claim CVX Rewards and Approve CTokens :( :(")
+ }
+}
+*/
\ No newline at end of file
diff --git a/src/utils/multicall.ts b/src/utils/multicall.ts
index 39efc34d..2b6456ef 100644
--- a/src/utils/multicall.ts
+++ b/src/utils/multicall.ts
@@ -18,16 +18,16 @@ export const createMultiCall = (provider: JsonRpcProvider | Web3Provider,) => {
export const sendWithMultiCall = async (
fuse: Fuse,
encodedCalls: any,
- address: string
+ address?: string
) => {
// @ts-ignore
const multicall = createMultiCall(fuse.provider);
- console.log("sendWithMultiCall", { encodedCalls, multicall });
+ console.log("sendWithMultiCall", { encodedCalls, multicall });
+ let options: any = {}
+ if (!!address) options.address = address
- const returnDatas = await multicall.aggregate(encodedCalls, {
- from: address,
- });
+ const returnDatas = await multicall.aggregate(encodedCalls, options);
return returnDatas;
};
@@ -48,6 +48,21 @@ export const callStaticWithMultiCall = async (
};
+export const estimateGasWithMultiCall = async (
+ provider: JsonRpcProvider | Web3Provider,
+ encodedCalls: EncodedCall[],
+ address?: string
+) => {
+ const multicall = createMultiCall(provider);
+ let options: any = {}
+ if (!!address) options.address = address
+
+ const estimatedGas = await multicall.estimateGas.aggregate(encodedCalls, options)
+
+ return estimatedGas;
+};
+
+
export const callInterfaceWithMulticall = async (
provider: JsonRpcProvider | Web3Provider,
@@ -87,9 +102,6 @@ export const decodeCall = (
iface.decodeFunctionResult(functionName, txResult);
-
-
-
const MULTICALL_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
const MultiCallAbi = [