@chakra-ui/icons#QuestionIcon TypeScript Examples

The following examples show how to use @chakra-ui/icons#QuestionIcon. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: FusePoolCreatePage.tsx    From rari-dApp with GNU Affero General Public License v3.0 4 votes vote down vote up
PoolConfiguration = () => {
  const { t } = useTranslation();
  const toast = useToast();
  const { fuse, address } = useRari();
  const navigate = useNavigate();

  const { isOpen, onOpen, onClose } = useDisclosure();

  const [name, setName] = useState("");
  const [isWhitelisted, setIsWhitelisted] = useState(false);
  const [whitelist, setWhitelist] = useState<string[]>([]);

  const [closeFactor, setCloseFactor] = useState(50);
  const [liquidationIncentive, setLiquidationIncentive] = useState(8);

  const [isUsingMPO, setIsUsingMPO] = useState(true)
  const [customOracleAddress, setCustomOracleAddress] = useState('')

  const [isCreating, setIsCreating] = useState(false);

  const [activeStep, setActiveStep] = useState<number>(0);

  const increaseActiveStep = (step: string) => {
    setActiveStep(steps.indexOf(step));
  };

  const [needsRetry, setNeedsRetry] = useState<boolean>(false);
  const [retryFlag, setRetryFlag] = useState<number>(1);

  const [deployedPriceOracle, setDeployedPriceOracle] = useState<string>("");

  const postDeploymentHandle = (priceOracle: string) => {
    setDeployedPriceOracle(priceOracle);
  };

  const deployPool = async (
    bigCloseFactor: string,
    bigLiquidationIncentive: string,
    options: any,
    priceOracle: string
  ) => {
    const [poolAddress] = await fuse.deployPool(
      name,
      isWhitelisted,
      bigCloseFactor,
      
      bigLiquidationIncentive,
      priceOracle,
      {},
      options,
      isWhitelisted ? whitelist : null
    );

    return poolAddress;
  };

  const onDeploy = async () => {
    let priceOracle = deployedPriceOracle;

    if (name === "") {
      toast({
        title: "Error!",
        description: "You must specify a name for your Fuse pool!",
        status: "error",
        duration: 2000,
        isClosable: true,
        position: "top-right",
      });

      return;
    }

    if (isWhitelisted && whitelist.length < 2 ) {
      toast({
        title: "Error!",
        description: "You must add an address to your whitelist!",
        status:"error",
        duration: 2000,
        isClosable: true,
        position: "top-right",
      })

      return
    }

    if (!isUsingMPO && !fuse.web3.utils.isAddress(customOracleAddress)) {
      
      toast({
        title: "Error!",
        description: "You must add an address for your oracle or use the default oracle.",
        status:"error",
        duration: 2000,
        isClosable: true,
        position: "top-right",
      })

      return
  }


    setIsCreating(true);
    onOpen();

    // 50% -> 0.5 * 1e18
    const bigCloseFactor = new BigNumber(closeFactor)
      .dividedBy(100)
      .multipliedBy(1e18)
      .toFixed(0);

    // 8% -> 1.08 * 1e8
    const bigLiquidationIncentive = new BigNumber(liquidationIncentive)
      .dividedBy(100)
      .plus(1)
      .multipliedBy(1e18)
      .toFixed(0);

    let _retryFlag = retryFlag;

    try {
      const options = { from: address };

      setNeedsRetry(false);

      if (!isUsingMPO && _retryFlag === 1) {
        _retryFlag = 2;
        priceOracle = customOracleAddress
      }

      if (_retryFlag === 1) {
        priceOracle = await fuse.deployPriceOracle(
          "MasterPriceOracle",
          {
            underlyings: [],
            oracles: [],
            canAdminOverwrite: true,
            defaultOracle:
              Fuse.PUBLIC_PRICE_ORACLE_CONTRACT_ADDRESSES.MasterPriceOracle, // We give the MasterPriceOracle a default "fallback" oracle of the Rari MasterPriceOracle
          },
          options
        );
        postDeploymentHandle(priceOracle);
        increaseActiveStep("Deploying Pool!");
        _retryFlag = 2;
      }

      let poolAddress: string;

      if (_retryFlag === 2) {
        poolAddress = await deployPool(
          bigCloseFactor,
          bigLiquidationIncentive,
          options,
          priceOracle
        );

        const event = (
          await fuse.contracts.FusePoolDirectory.getPastEvents(
            "PoolRegistered",
            {
              fromBlock: (await fuse.web3.eth.getBlockNumber()) - 10,
              toBlock: "latest",
            }
          )
        ).filter(
          (event) =>
            event.returnValues.pool.comptroller.toLowerCase() ===
            poolAddress.toLowerCase()
        )[0];

        LogRocket.track("Fuse-CreatePool");

        toast({
          title: "Your pool has been deployed!",
          description: "You may now add assets to it.",
          status: "success",
          duration: 2000,
          isClosable: true,
          position: "top-right",
        });

        let id = event.returnValues.index;
        onClose();
        navigate(`/fuse/pool/${id}/edit`);
      }
    } catch (e) {
      handleGenericError(e, toast);
      setRetryFlag(_retryFlag);
      setNeedsRetry(true);
    }
  };

  return (
    <>
      <TransactionStepperModal
        handleRetry={onDeploy}
        needsRetry={needsRetry}
        activeStep={activeStep}
        isOpen={isOpen}
        onClose={onClose}
      />
      <DashboardBox width="100%" mt={4}>
        <Column mainAxisAlignment="flex-start" crossAxisAlignment="flex-start">
          <Heading size="sm" px={4} py={4}>
            {t("Create Pool")}
          </Heading>

          <ModalDivider />

          <OptionRow>
            <Text fontWeight="bold" mr={4}>
              {t("Name")}
            </Text>
            <Input
              width="20%"
              value={name}
              onChange={(event) => setName(event.target.value)}
            />
          </OptionRow>

          <ModalDivider />

          <ModalDivider />

          <OptionRow>
            <SimpleTooltip
              label={t(
                "If enabled you will be able to limit the ability to supply to the pool to a select group of addresses. The pool will not show up on the 'all pools' list."
              )}
            >
              <Text fontWeight="bold">
                {t("Whitelisted")} <QuestionIcon ml={1} mb="4px" />
              </Text>
            </SimpleTooltip>

            <Switch
              h="20px"
              isChecked={isWhitelisted}
              onChange={() => {
                setIsWhitelisted((past) => !past);
                // Add the user to the whitelist by default
                if (whitelist.length === 0) {
                  setWhitelist([address]);
                }
              }}
              className="black-switch"
              colorScheme="#121212"
            />
          </OptionRow>

          {isWhitelisted ? (
            <WhitelistInfo
              whitelist={whitelist}
              addToWhitelist={(user) => {
                setWhitelist((past) => [...past, user]);
              }}
              removeFromWhitelist={(user) => {
                setWhitelist((past) =>
                  past.filter(function (item) {
                    return item !== user;
                  })
                );
              }}
            />
          ) : null}

          <ModalDivider />

          <OptionRow>
            <SimpleTooltip
              label={t(
                "The percent, ranging from 0% to 100%, of a liquidatable account's borrow that can be repaid in a single liquidate transaction. If a user has multiple borrowed assets, the closeFactor applies to any single borrowed asset, not the aggregated value of a user’s outstanding borrowing. Compound's close factor is 50%."
              )}
            >
              <Text fontWeight="bold">
                {t("Close Factor")} <QuestionIcon ml={1} mb="4px" />
              </Text>
            </SimpleTooltip>

            <SliderWithLabel
              value={closeFactor}
              setValue={setCloseFactor}
              formatValue={formatPercentage}
              min={5}
              max={90}
            />
          </OptionRow>

          <ModalDivider />

          <OptionRow>
            <SimpleTooltip
              label={t(
                "The additional collateral given to liquidators as an incentive to perform liquidation of underwater accounts. For example, if the liquidation incentive is 10%, liquidators receive an extra 10% of the borrowers collateral for every unit they close. Compound's liquidation incentive is 8%."
              )}
            >
              <Text fontWeight="bold">
                {t("Liquidation Incentive")} <QuestionIcon ml={1} mb="4px" />
              </Text>
            </SimpleTooltip>

            <SliderWithLabel
              value={liquidationIncentive}
              setValue={setLiquidationIncentive}
              formatValue={formatPercentage}
              min={0}
              max={50}
            />
          </OptionRow>

          <ModalDivider />

          <OptionRow>
            <SimpleTooltip
              label={t(
                "We will deploy a price oracle for your pool. This price oracle will contain price feeds for popular ERC20 tokens."
              )}
            >
              <Text fontWeight="bold">
                {isUsingMPO ? t("Default Price Oracle") : t("Custom Price Oracle")} <QuestionIcon ml={1} mb="4px" />
              </Text>
            </SimpleTooltip>

            <Box display="flex" alignItems='flex-end' flexDirection="column"> 
              <Checkbox
                isChecked={isUsingMPO}
                onChange={(e) => setIsUsingMPO(!isUsingMPO)}
                marginBottom={3}
              />
              {
                !isUsingMPO ? (
                  <>
                  <Input 
                    value={customOracleAddress}
                    onChange={(e) => setCustomOracleAddress(e.target.value)}
                  />
                  <Text mt={3} opacity="0.6" fontSize="sm">
                    Please make sure you know what you're doing.
                  </Text>
                  </>
                ) 
                : null
              }
              
            </Box>
          </OptionRow>
        </Column>
      </DashboardBox>

      <DashboardBox
        width="100%"
        height="60px"
        mt={4}
        py={3}
        fontSize="xl"
        as="button"
        onClick={useAuthedCallback(onDeploy)}
      >
        <Center expand fontWeight="bold">
          {isCreating ? <Spinner /> : t("Create")}
        </Center>
      </DashboardBox>
    </>
  );
}
Example #2
Source File: AssetConfig.tsx    From rari-dApp with GNU Affero General Public License v3.0 4 votes vote down vote up
AssetConfig = () => {
  const queryClient = useQueryClient();
  const { fuse, address } = useRari();
  const { t } = useTranslation();
  const toast = useToast();

  const {
    cTokenData,
    collateralFactor,
    setCollateralFactor,
    cTokenAddress,
    isBorrowPaused,
    adminFee,
    setAdminFee,
    activeOracleModel,
    oracleData,
    tokenAddress,
    mode,
    setInterestRateModel,
    interestRateModel,
    tokenData,
    setReserveFactor,
    reserveFactor,
    comptrollerAddress,
  } = useAddAssetContext();

  const curves = useIRMCurves({ interestRateModel, adminFee, reserveFactor });

  // Liquidation incentive. (This is configured at pool level)
  const liquidationIncentiveMantissa =
    useLiquidationIncentive(comptrollerAddress);

  const scaleCollateralFactor = (_collateralFactor: number) => {
    return _collateralFactor / 1e16;
  };

  const scaleReserveFactor = (_reserveFactor: number) => {
    return _reserveFactor / 1e16;
  };

  const scaleAdminFee = (_adminFee: number) => {
    return _adminFee / 1e16;
  };

  // Updates asset's Interest Rate Model.
  const updateInterestRateModel = async () => {
    const cToken = createCToken(fuse, cTokenAddress!);

    try {
      await testForCTokenErrorAndSend(
        cToken.methods._setInterestRateModel(interestRateModel),
        address,
        ""
      );

      LogRocket.track("Fuse-UpdateInterestRateModel");

      queryClient.refetchQueries();
    } catch (e) {
      handleGenericError(e, toast);
    }
  };

  // Determines if users can borrow an asset or not.
  const togglePause = async () => {
    const comptroller = createComptroller(comptrollerAddress, fuse);

    try {
      await comptroller.methods
        ._setBorrowPaused(cTokenAddress, !isBorrowPaused)
        .send({ from: address });

      LogRocket.track("Fuse-PauseToggle");

      queryClient.refetchQueries();
    } catch (e) {
      handleGenericError(e, toast);
    }
  };

  // Updates loan to Value ratio.
  const updateCollateralFactor = async () => {
    const comptroller = createComptroller(comptrollerAddress, fuse);

    // 70% -> 0.7 * 1e18
    const bigCollateralFactor = new BigNumber(collateralFactor)
      .dividedBy(100)
      .multipliedBy(1e18)
      .toFixed(0);

    try {
      await testForComptrollerErrorAndSend(
        comptroller.methods._setCollateralFactor(
          cTokenAddress,
          bigCollateralFactor
        ),
        address,
        ""
      );

      LogRocket.track("Fuse-UpdateCollateralFactor");

      queryClient.refetchQueries();
    } catch (e) {
      handleGenericError(e, toast);
    }
  };

  // Updated portion of accrued reserves that goes into reserves.
  const updateReserveFactor = async () => {
    const cToken = createCToken(fuse, cTokenAddress!);

    // 10% -> 0.1 * 1e18
    const bigReserveFactor = new BigNumber(reserveFactor)
      .dividedBy(100)
      .multipliedBy(1e18)
      .toFixed(0);
    try {
      await testForCTokenErrorAndSend(
        cToken.methods._setReserveFactor(bigReserveFactor),
        address,
        ""
      );

      LogRocket.track("Fuse-UpdateReserveFactor");

      queryClient.refetchQueries();
    } catch (e) {
      handleGenericError(e, toast);
    }
  };

  // Updates asset's admin fee.
  const updateAdminFee = async () => {
    const cToken = createCToken(fuse, cTokenAddress!);

    // 5% -> 0.05 * 1e18
    const bigAdminFee = new BigNumber(adminFee)
      .dividedBy(100)
      .multipliedBy(1e18)
      .toFixed(0);

    try {
      await testForCTokenErrorAndSend(
        cToken.methods._setAdminFee(bigAdminFee),
        address,
        ""
      );

      LogRocket.track("Fuse-UpdateAdminFee");

      queryClient.refetchQueries();
    } catch (e) {
      handleGenericError(e, toast);
    }
  };

  return (
    <>
      <Column
        width={mode === "Editing" ? "100%" : "60%"}
        maxWidth={mode === "Editing" ? "100%" : "50%"}
        height="100%"
        overflowY="auto"
        mainAxisAlignment={mode === "Adding" ? "center" : "flex-start"}
        crossAxisAlignment={mode === "Adding" ? "center" : "flex-start"}
      >
        {mode === "Editing" ? (
          <>
            <MarketCapConfigurator
              mode="Borrow"
              tokenData={tokenData}
              cTokenAddress={cTokenAddress}
              comptrollerAddress={comptrollerAddress}
            />

            <ModalDivider />

            <MarketCapConfigurator
              mode="Supply"
              tokenData={tokenData}
              cTokenAddress={cTokenAddress}
              comptrollerAddress={comptrollerAddress}
            />
          </>
        ) : null}

        <ModalDivider />

        <ConfigRow height="35px">
          <SimpleTooltip
            label={t(
              "Collateral factor can range from 0-90%, and represents the proportionate increase in liquidity (borrow limit) that an account receives by depositing the asset."
            )}
          >
            <Text fontWeight="bold">
              {t("Collateral Factor")} <QuestionIcon ml={1} mb="4px" />
            </Text>
          </SimpleTooltip>

          {cTokenData !== undefined &&
          mode === "Editing" &&
          collateralFactor !==
            scaleCollateralFactor(cTokenData?.collateralFactorMantissa) ? (
            <SaveButton ml={3} onClick={updateCollateralFactor} />
          ) : null}

          <SliderWithLabel
            ml="auto"
            value={collateralFactor}
            setValue={setCollateralFactor}
            formatValue={formatPercentage}
            step={0.5}
            max={
              liquidationIncentiveMantissa
                ? // 100% CF - Liquidation Incentive (ie: 8%) - 5% buffer
                  100 -
                  (liquidationIncentiveMantissa.toString() / 1e16 - 100) -
                  5
                : 90
            }
          />
        </ConfigRow>

        <ModalDivider />

        {cTokenAddress ? (
          <ConfigRow>
            <SimpleTooltip
              label={t("If enabled borrowing this asset will be disabled.")}
            >
              <Text fontWeight="bold">
                {t("Pause Borrowing")} <QuestionIcon ml={1} mb="4px" />
              </Text>
            </SimpleTooltip>

            <SaveButton
              ml="auto"
              onClick={togglePause}
              fontSize="xs"
              altText={
                isBorrowPaused ? t("Enable Borrowing") : t("Pause Borrowing")
              }
            />
          </ConfigRow>
        ) : null}

        <ModalDivider />

        <ConfigRow height="35px">
          <SimpleTooltip
            label={t(
              "The fraction of interest generated on a given asset that is routed to the asset's Reserve Pool. The Reserve Pool protects lenders against borrower default and liquidation malfunction."
            )}
          >
            <Text fontWeight="bold">
              {t("Reserve Factor")} <QuestionIcon ml={1} mb="4px" />
            </Text>
          </SimpleTooltip>

          {cTokenData &&
          reserveFactor !==
            scaleReserveFactor(cTokenData.reserveFactorMantissa) ? (
            <SaveButton ml={3} onClick={updateReserveFactor} />
          ) : null}

          <SliderWithLabel
            ml="auto"
            value={reserveFactor}
            setValue={setReserveFactor}
            formatValue={formatPercentage}
            max={50}
          />
        </ConfigRow>
        <ModalDivider />

        <ConfigRow height="35px">
          <SimpleTooltip
            label={t(
              "The fraction of interest generated on a given asset that is routed to the asset's admin address as a fee."
            )}
          >
            <Text fontWeight="bold">
              {t("Admin Fee")} <QuestionIcon ml={1} mb="4px" />
            </Text>
          </SimpleTooltip>

          {cTokenData &&
          adminFee !== scaleAdminFee(cTokenData.adminFeeMantissa) ? (
            <SaveButton ml={3} onClick={updateAdminFee} />
          ) : null}

          <SliderWithLabel
            ml="auto"
            value={adminFee}
            setValue={setAdminFee}
            formatValue={formatPercentage}
            max={30}
          />
        </ConfigRow>

        <ModalDivider />

        {(activeOracleModel === "MasterPriceOracleV2" ||
          activeOracleModel === "MasterPriceOracleV3") &&
          oracleData !== undefined &&
          !isTokenETHOrWETH(tokenAddress) &&
          mode === "Editing" && (
            <>
              <OracleConfig />

              <ModalDivider />
            </>
          )}

        <ModalDivider />

        <ConfigRow>
          <SimpleTooltip
            label={t(
              "The interest rate model chosen for an asset defines the rates of interest for borrowers and suppliers at different utilization levels."
            )}
          >
            <Text fontWeight="bold">
              {t("Interest Model")} <QuestionIcon ml={1} mb="4px" />
            </Text>
          </SimpleTooltip>

          <Select
            {...DASHBOARD_BOX_PROPS}
            ml="auto"
            borderRadius="7px"
            fontWeight="bold"
            _focus={{ outline: "none" }}
            width="260px"
            value={interestRateModel.toLowerCase()}
            onChange={(event) => setInterestRateModel(event.target.value)}
          >
            {Object.entries(
              Fuse.PUBLIC_INTEREST_RATE_MODEL_CONTRACT_ADDRESSES
            ).map(([key, value]) => {
              return (
                <option
                  className="black-bg-option"
                  value={value.toLowerCase()}
                  key={key}
                >
                  {key}
                </option>
              );
            })}
          </Select>

          {cTokenData &&
          cTokenData.interestRateModelAddress.toLowerCase() !==
            interestRateModel.toLowerCase() ? (
            <SaveButton
              height="40px"
              borderRadius="7px"
              onClick={updateInterestRateModel}
            />
          ) : null}
        </ConfigRow>

        {mode === "Editing" && (
          <IRMChart curves={curves} tokenData={tokenData} />
        )}
      </Column>

      {mode === "Adding" ? (
        <Column
          width="40%"
          height="100%"
          overflowY="auto"
          mainAxisAlignment={mode === "Adding" ? "center" : "flex-start"}
          crossAxisAlignment={mode === "Adding" ? "center" : "flex-start"}
        >
          <IRMChart curves={curves} tokenData={tokenData} />
          <Text textAlign="center">
            {fuse
              .identifyInterestRateModelName(interestRateModel)
              .replace("_", " ")}
          </Text>
        </Column>
      ) : null}
    </>
  );
}
Example #3
Source File: BaseTokenOracleConfig.tsx    From rari-dApp with GNU Affero General Public License v3.0 4 votes vote down vote up
BaseTokenOracleConfig = () => {
  const { t } = useTranslation();

  const { address } = useRari();

  const {
    mode,
    oracleData,
    uniV3BaseTokenAddress,
    uniV3BaseTokenOracle,
    setUniV3BaseTokenOracle,
    baseTokenActiveOracleName,
    setBaseTokenActiveOracleName,
  } = useAddAssetContext();

  const isUserAdmin = address === oracleData?.admin ?? false;

  // We get all oracle options.
  const options = useGetOracleOptions(oracleData, uniV3BaseTokenAddress);

  console.log("helo there", { options });
  // If we're editing the asset, show master price oracle as a default.
  // Should run only once, when component renders.
  useEffect(() => {
    if (
      mode === "Editing" &&
      baseTokenActiveOracleName === "" &&
      options &&
      options["Current_Price_Oracle"]
    )
      setBaseTokenActiveOracleName("Current_Price_Oracle");
  }, [mode, baseTokenActiveOracleName, options, setBaseTokenActiveOracleName]);

  // This will update the oracle address, after user chooses which options they want to use.
  // If option is Custom_Oracle oracle address is typed in by user, so we dont trigger this.
  useEffect(() => {
    if (
      !!baseTokenActiveOracleName &&
      baseTokenActiveOracleName !== "Custom_Oracle" &&
      options
    )
      setUniV3BaseTokenOracle(options[baseTokenActiveOracleName]);
  }, [baseTokenActiveOracleName, options, setUniV3BaseTokenOracle]);

  return (
    <>
      <Row
        crossAxisAlignment="center"
        mainAxisAlignment="center"
        width="100%"
        my={2}
      >
        <Alert status="info" width="80%" height="50px" borderRadius={5} my={1}>
          <AlertIcon />
          <Text fontSize="sm" align="center" color="black">
            {"This Uniswap V3 TWAP Oracle needs an oracle for the BaseToken."}
          </Text>
        </Alert>
      </Row>
      <Column
        mainAxisAlignment="flex-start"
        crossAxisAlignment="center"
        w="100%"
        h="100%"
      >
        <Column
          my={4}
          width="100%"
          crossAxisAlignment="center"
          mainAxisAlignment="space-between"
        >
          <Column
            mainAxisAlignment="center"
            crossAxisAlignment="center"
            height="50%"
            justifyContent="space-around"
          >
            <CTokenIcon address={uniV3BaseTokenAddress} boxSize={"50px"} />
            <SimpleTooltip
              label={t("Choose the best price oracle for this BaseToken.")}
            >
              <Text fontWeight="bold" fontSize="sm" align="center">
                {t("BaseToken Price Oracle")} <QuestionIcon ml={1} mb="4px" />
              </Text>
            </SimpleTooltip>
          </Column>

          {options ? (
            <Box alignItems="center" height="50%">
              <Select
                {...DASHBOARD_BOX_PROPS}
                ml="auto"
                my={2}
                borderRadius="7px"
                _focus={{ outline: "none" }}
                width="260px"
                placeholder={
                  baseTokenActiveOracleName.length === 0
                    ? t("Choose Oracle")
                    : baseTokenActiveOracleName.replaceAll("_", " ")
                }
                value={baseTokenActiveOracleName.toLowerCase()}
                disabled={
                  !isUserAdmin ||
                  (!oracleData?.adminOverwrite &&
                    !options.Current_Price_Oracle === null)
                }
                onChange={(event) =>
                  setBaseTokenActiveOracleName(event.target.value)
                }
              >
                {Object.entries(options).map(([key, value]) =>
                  value !== null &&
                  value !== undefined &&
                  key !== "Uniswap_V3_Oracle" &&
                  key !== "Uniswap_V2_Oracle" ? (
                    <option className="black-bg-option" value={key} key={key}>
                      {key.replaceAll("_", " ")}
                    </option>
                  ) : null
                )}
              </Select>

              {baseTokenActiveOracleName.length > 0 ? (
                <Input
                  width="100%"
                  textAlign="center"
                  height="40px"
                  variant="filled"
                  size="sm"
                  mt={2}
                  mb={2}
                  value={uniV3BaseTokenOracle}
                  onChange={(event) => {
                    const address = event.target.value;
                    setUniV3BaseTokenOracle(address);
                  }}
                  disabled={
                    baseTokenActiveOracleName === "Custom_Oracle" ? false : true
                  }
                  {...DASHBOARD_BOX_PROPS}
                  _placeholder={{ color: "#e0e0e0" }}
                  _focus={{ bg: "#121212" }}
                  _hover={{ bg: "#282727" }}
                  bg="#282727"
                />
              ) : null}
            </Box>
          ) : null}
        </Column>
      </Column>
    </>
  );
}
Example #4
Source File: OracleConfig.tsx    From rari-dApp with GNU Affero General Public License v3.0 4 votes vote down vote up
OracleConfig = () => {
  const toast = useToast();
  const { t } = useTranslation();
  const queryClient = useQueryClient();

  const { fuse, address } = useRari();

  const {
    mode,
    feeTier,
    oracleData,
    activeOracleModel,
    tokenAddress,
    oracleAddress,
    oracleTouched,
    uniV3BaseTokenAddress,
    setOracleTouched,
    activeUniSwapPair,
    setActiveOracleModel,
    setOracleAddress,
    poolOracleAddress,
    uniV3BaseTokenOracle,
    baseTokenActiveOracleName,
    shouldShowUniV3BaseTokenOracleForm,
  } = useAddAssetContext();

  const isUserAdmin = !!oracleData ? address === oracleData.admin : false;

  // Available oracle options for asset
  const options = useGetOracleOptions(oracleData, tokenAddress);

  // Identify token oracle address
  const oracleIdentity = useIdentifyOracle(oracleAddress);

  const [inputTouched, setInputTouched] = useState(false);

  // If user's editing the asset's properties, show the Ctoken's active Oracle
  useEffect(() => {
    // Map oracleIdentity to whatever the type of `activeOracle` can be

    // "Current_Price_Oracle" would only be avialable if you are editing
    if (
      mode === "Editing" &&
      options &&
      options["Current_Price_Oracle"] &&
      !oracleTouched
    ) {
      setActiveOracleModel("Current_Price_Oracle");
    }

    // if avaiable, set to "Default_Price_Oracle" if you are adding
    if (
      mode === "Adding" &&
      options &&
      !!options["Default_Price_Oracle"] &&
      !oracleTouched
    ) {
      setActiveOracleModel("Default_Price_Oracle");
    }

    // if avaiable, set to "Default_Price_Oracle" if you are adding
    if (
      mode === "Adding" &&
      options &&
      !!options["Current_Price_Oracle"] &&
      !oracleTouched
    ) {
      setActiveOracleModel("Current_Price_Oracle");
    }
  }, [
    mode,
    activeOracleModel,
    options,
    setActiveOracleModel,
    oracleIdentity,
    oracleTouched,
  ]);

  // Update the oracle address, after user chooses which option they want to use.
  // If option is Custom_Oracle or Uniswap_V3_Oracle, oracle address is changed differently so we dont trigger this.
  useEffect(() => {
    if (
      activeOracleModel.length > 0 &&
      activeOracleModel !== "Custom_Oracle" &&
      activeOracleModel !== "Uniswap_V3_Oracle" &&
      activeOracleModel !== "Uniswap_V2_Oracle" &&
      activeOracleModel !== "SushiSwap_Oracle" &&
      options
    )
      setOracleAddress(options[activeOracleModel]);
    if (
      activeUniSwapPair === "" &&
      (activeOracleModel === "Custom_Oracle" ||
        activeOracleModel === "Uniswap_V3_Oracle" ||
        activeOracleModel === "Uniswap_V2_Oracle" ||
        activeOracleModel === "SushiSwap_Oracle") &&
      !inputTouched
    )
      setOracleAddress("");
  }, [activeOracleModel, options, setOracleAddress, activeUniSwapPair]);

  // Will update oracle for the asset. This is used only if user is editing asset.
  const updateOracle = async () => {
    const poolOracleContract = createOracle(
      poolOracleAddress,
      fuse,
      "MasterPriceOracle"
    );

    // This variable will change if we deploy an oracle. (i.e TWAP Oracles)
    // If we're using an option that has been deployed it stays the same.
    let oracleAddressToUse = oracleAddress;

    try {
      if (options === null) return null;

      // If activeOracle if a TWAP Oracle
      if (activeOracleModel === "Uniswap_V3_Oracle") {
        // Check for observation cardinality and fix if necessary
        await fuse.primeUniswapV3Oracle(oracleAddressToUse, { from: address });

        // Deploy oracle
        oracleAddressToUse = await fuse.deployPriceOracle(
          "UniswapV3TwapPriceOracleV2",
          {
            feeTier,
            baseToken: uniV3BaseTokenAddress,
          },
          { from: address }
        );
      }

      const tokenArray =
        shouldShowUniV3BaseTokenOracleForm &&
        !isTokenETHOrWETH(uniV3BaseTokenAddress)
          ? [tokenAddress, uniV3BaseTokenAddress]
          : [tokenAddress];
      const oracleAddressArray =
        shouldShowUniV3BaseTokenOracleForm &&
        !isTokenETHOrWETH(uniV3BaseTokenAddress)
          ? [oracleAddressToUse, uniV3BaseTokenOracle]
          : [oracleAddressToUse];

      console.log({ tokenArray, oracleAddressArray });

      // Add oracle to Master Price Oracle
      await poolOracleContract.methods
        .add(tokenArray, oracleAddressArray)
        .send({ from: address });

      queryClient.refetchQueries();

      // Wait 2 seconds for refetch and then close modal.
      // We do this instead of waiting the refetch because some refetches take a while or error out and we want to close now.
      await new Promise((resolve) => setTimeout(resolve, 2000));

      toast({
        title: "You have successfully updated the oracle to this asset!",
        description: "Oracle will now point to the new selected address.",
        status: "success",
        duration: 2000,
        isClosable: true,
        position: "top-right",
      });
      setActiveOracleModel("Current_Price_Oracle");
      setOracleAddress(options["Current_Price_Oracle"]);
    } catch (e) {
      handleGenericError(e, toast);
    }
  };

  if (!options)
    return (
      <Center>
        <Spinner />
      </Center>
    );

  return (
    <>
      <Row
        mainAxisAlignment={mode === "Editing" ? "space-between" : "flex-start"}
        // background="gold"
        crossAxisAlignment={"center"}
        width={
          mode === "Editing"
            ? !shouldShowUniV3BaseTokenOracleForm
              ? "100%"
              : "50%"
            : "100%"
        }
        flexGrow={1}
        pt={mode === "Editing" ? 4 : 0}
        pb={mode === "Editing" ? 1 : 0}
        px={mode === "Editing" ? 4 : 0}
        id="PRICEORACLE"
      >
        <SimpleTooltip label={t("Choose the best price oracle for the asset.")}>
          <Text fontWeight="bold">
            {t("Price Oracle")} <QuestionIcon ml={1} mb="4px" />
          </Text>
        </SimpleTooltip>

        {/* Oracles */}
        <Box
          width={mode === "Editing" ? "50%" : "100%"}
          alignItems="flex-end"
          flexDirection="column"
          alignContent="center"
          display="flex"
        >
          <Select
            mb={2}
            ml="auto"
            width="260px"
            {...DASHBOARD_BOX_PROPS}
            borderRadius="7px"
            _focus={{ outline: "none" }}
            value={activeOracleModel.toLowerCase()}
            onChange={(event) => {
              // if (mode === "Editing") {
              // }
              setOracleTouched(true);
              setActiveOracleModel(event.target.value);
            }}
            placeholder={
              activeOracleModel.length === 0
                ? t("Choose Oracle")
                : activeOracleModel.replaceAll("_", " ")
            }
            disabled={
              !isUserAdmin ||
              (!oracleData?.adminOverwrite &&
                !options.Current_Price_Oracle === null)
            }
          >
            {Object.entries(options).map(([key, value]) =>
              value !== null &&
              value !== undefined &&
              key !== activeOracleModel &&
              (mode === "Adding" ? key !== "Current_Price_Oracle" : true) ? (
                // dont show the selected choice in the list
                <option key={key} value={key} className="black-bg-option">
                  {key.replaceAll("_", " ")}
                </option>
              ) : null
            )}
            {/* <option disabled={true}>Loading...</option> */}
          </Select>

          {activeOracleModel.length > 0 ? (
            <Input
              mt={2}
              mb={2}
              ml="auto"
              size="sm"
              bg="#282727"
              height="40px"
              width="260px"
              variant="filled"
              textAlign="center"
              value={oracleAddress}
              onChange={(event) => {
                const address = event.target.value;
                setInputTouched(true);
                setOracleAddress(address);
              }}
              {...DASHBOARD_BOX_PROPS}
              _focus={{ bg: "#121212" }}
              _hover={{ bg: "#282727" }}
              _placeholder={{ color: "#e0e0e0" }}
              disabled={activeOracleModel === "Custom_Oracle" ? false : true}
            />
          ) : null}
          <Text color="grey" fontSize="sm" textAlign="center">
            {oracleIdentity}
          </Text>
          {activeOracleModel === "Custom_Oracle" && (
            <Text color="red" fontSize="sm" textAlign="center">
              Make sure you know what you are doing!
            </Text>
          )}
        </Box>
      </Row>

      <Row
        mainAxisAlignment={mode === "Editing" ? "center" : "center"}
        crossAxisAlignment={mode === "Editing" ? "flex-start" : "center"}
        flexDirection="column"
        width={
          mode === "Adding" && !shouldShowUniV3BaseTokenOracleForm
            ? "100%"
            : "50%"
        }
        // bg="pink"
        ml={mode === "Editing" ? "auto" : ""}
        px={mode === "Editing" ? 4 : 0}
        id="UNIV3Config"
      >
        {activeOracleModel === "Uniswap_V3_Oracle" ? (
          <UniswapV3PriceOracleConfigurator />
        ) : null}

        {activeOracleModel === "Uniswap_V2_Oracle" ? (
          <UniswapV2OrSushiPriceOracleConfigurator type="UniswapV2" />
        ) : null}

        {activeOracleModel === "SushiSwap_Oracle" ? (
          <UniswapV2OrSushiPriceOracleConfigurator type="Sushiswap" />
        ) : null}
      </Row>

      {shouldShowUniV3BaseTokenOracleForm && mode === "Editing" ? (
        <BaseTokenOracleConfig />
      ) : null}

      {activeOracleModel !== "Current_Price_Oracle" && mode === "Editing" ? (
        <SaveButton
          ml={"auto"}
          mb={3}
          mr={4}
          fontSize="xs"
          altText={t("Update")}
          onClick={updateOracle}
        />
      ) : null}
    </>
  );
}
Example #5
Source File: UniswapV2OrSushiPriceOracleConfigurator.tsx    From rari-dApp with GNU Affero General Public License v3.0 4 votes vote down vote up
UniswapV2OrSushiPriceOracleConfigurator = ({
  type,
}: {
  // Asset's Address. i.e DAI, USDC

  // Either SushiSwap or Uniswap V2
  type: string;
}) => {
  const { t } = useTranslation();

  // This will be used to index whitelistPools array (fetched from the graph.)
  // It also helps us know if user has selected anything or not. If they have, detail fields are shown.
  const [activePool, setActivePair] = useState<string>("");

  // Checks if user has started the TWAP bot.
  const [checked, setChecked] = useState<boolean>(false);

  // Will store oracle response. This helps us know if its safe to add it to Master Price Oracle
  const [checkedStepTwo, setCheckedStepTwo] = useState<boolean>(false);

  const { tokenAddress, setOracleAddress, setUniV3BaseTokenAddress } =
    useAddAssetContext();

  // Get pair options from sushiswap and uniswap
  const { SushiPairs, SushiError, UniV2Pairs, univ2Error } =
    useSushiOrUniswapV2Pairs(tokenAddress);

  // This is where we conditionally store data depending on type.
  // Uniswap V2 or SushiSwap
  const Pairs = type === "UniswapV2" ? UniV2Pairs : SushiPairs;
  const Error = type === "UniswapV2" ? univ2Error : SushiError;

  // Will update active pair, set oracle address and base token.
  const updateInfo = (value: string) => {
    const pair = Pairs[value];
    setActivePair(value);
    setOracleAddress(pair.id);
    setUniV3BaseTokenAddress(
      pair.token1.id === tokenAddress ? pair.token0.id : pair.token1.id
    );
  };

  // If pairs are still being fetched, if theres and error or if there are none, return nothing.
  if (Pairs === undefined || Error || Pairs === null) return null;

  return (
    <>
      <Row
        crossAxisAlignment="center"
        mainAxisAlignment="space-between"
        width="260px"
        my={3}
      >
        <Checkbox isChecked={checked} onChange={() => setChecked(!checked)}>
          <Text fontSize="xs" align="center">
            Using this type of oracle requires you to run a TWAP bot.
          </Text>
        </Checkbox>
      </Row>

      {checked ? (
        <Row
          crossAxisAlignment="center"
          mainAxisAlignment="space-between"
          width="260px"
          my={3}
        >
          <Button colorScheme="teal">Check</Button>

          <Text fontSize="xs" align="center">
            After deploying your oracle, you have to wait about 15 - 25 minutes
            for the oracle to be set.
          </Text>
        </Row>
      ) : null}

      {true ? (
        <Row
          crossAxisAlignment="center"
          mainAxisAlignment="space-between"
          width="260px"
          my={2}
        >
          <SimpleTooltip
            label={t(
              "This field will determine which pool your oracle reads from. Its safer with more liquidity."
            )}
          >
            <Text fontWeight="bold">
              {t("Pool:")} <QuestionIcon ml={1} mb="4px" />
            </Text>
          </SimpleTooltip>
          <Select
            {...DASHBOARD_BOX_PROPS}
            ml={2}
            mb={2}
            borderRadius="7px"
            _focus={{ outline: "none" }}
            width="180px"
            placeholder={
              activePool.length === 0 ? t("Choose Pool") : activePool
            }
            value={activePool}
            disabled={!checked}
            onChange={(event) => {
              updateInfo(event.target.value);
            }}
          >
            {typeof Pairs !== undefined
              ? Object.entries(Pairs).map(([key, value]: any[]) =>
                  value.totalSupply !== null &&
                  value.totalSupply !== undefined &&
                  value.totalSupply >= 100 ? (
                    <option
                      className="black-bg-option"
                      value={key}
                      key={value.id}
                    >
                      {`${value.token0.symbol} / ${
                        value.token1.symbol
                      } (${shortUsdFormatter(value.totalSupply)})`}
                    </option>
                  ) : null
                )
              : null}
          </Select>
        </Row>
      ) : null}

      {activePool.length > 0 ? (
        <Row
          crossAxisAlignment="center"
          mainAxisAlignment="space-between"
          width="260px"
          my={2}
        >
          <SimpleTooltip label={t("TVL in pool as of this moment.")}>
            <Text fontWeight="bold">
              {t("Liquidity:")} <QuestionIcon ml={1} mb="4px" />
            </Text>
          </SimpleTooltip>
          <h1>
            {activePool !== ""
              ? smallUsdFormatter(Pairs[activePool].totalSupply)
              : null}
          </h1>
        </Row>
      ) : null}
    </>
  );
}
Example #6
Source File: UniswapV3PriceOracleConfigurator.tsx    From rari-dApp with GNU Affero General Public License v3.0 4 votes vote down vote up
UniswapV3PriceOracleConfigurator = () => {
  const { t } = useTranslation();

  const {
    setFeeTier,
    tokenAddress,
    setOracleAddress,
    setUniV3BaseTokenAddress,
    activeUniSwapPair,
    setActiveUniSwapPair,
  } = useAddAssetContext();

  // We get a list of whitelistedPools from uniswap-v3's the graph.
  const { data: liquidity, error } = useQuery(
    "UniswapV3 pool liquidity for " + tokenAddress,
    async () =>
      (
        await axios.post(
          "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3",
          {
            query: `{
              token(id:"${tokenAddress.toLowerCase()}") {
                whitelistPools {
                  id,
                  feeTier,
                  volumeUSD,
                  totalValueLockedUSD,
                  token0 {
                    symbol,
                    id,
                    name
                  },
                  token1 {
                    symbol,
                    id,
                    name
                  }
                }
              }
            }`,
          }
        )
      ).data,
    { refetchOnMount: false }
  );

  // When user selects an option this function will be called.
  // Active pool, fee Tier, and base token are updated and we set the oracle address to the address of the pool we chose.
  const updateBoth = (value: string) => {
    const uniPool = liquidity.data.token.whitelistPools[value];

    const baseToken: string =
      uniPool.token0.id.toLowerCase() === tokenAddress.toLocaleLowerCase()
        ? uniPool.token1.id
        : uniPool.token0.id;
    setActiveUniSwapPair(value);
    setFeeTier(uniPool.feeTier);
    setOracleAddress(uniPool.id);
    setUniV3BaseTokenAddress(baseToken);
  };

  // If liquidity is undefined, theres an error or theres no token found return nothing.
  if (liquidity === undefined || liquidity.data === undefined)
    return null;

  // Sort whitelisted pools by TVL. Greatest to smallest. Greater TVL is safer for users so we show it first.
  // Filter out pools where volume is less than $100,000
  const liquiditySorted = liquidity.data.token.whitelistPools.sort(
    (a: any, b: any): any =>
      parseInt(a.totalValueLockedUSD) > parseInt(b.totalValueLockedUSD) ? -1 : 1
  );
  // .filter((pool: any) => pool.volumeUSD >= 100000);

  const selectedOracle = liquidity.data.token.whitelistPools[activeUniSwapPair];
  console.log({ selectedOracle });
  // const warning = useMemo(() => {
  //   if (selectedOracle.liquidityProviderCount <=100)
  // }, [selectedOracle]);

  return (
    <>
      <Column
        crossAxisAlignment="flex-start"
        mainAxisAlignment="flex-start"
        width={"100%"}
        my={2}
        px={4}
        ml={"auto"}
        // bg="aqua"
        id="UNIv3COLUMN"
      >
        <Row
          crossAxisAlignment="center"
          mainAxisAlignment="space-between"
          w="100%"
          // bg="green"
        >
          <SimpleTooltip
            label={t(
              "This field will determine which pool your oracle reads from. Its safer with more liquidity."
            )}
          >
            <Text fontWeight="bold">
              {t("Pool:")} <QuestionIcon ml={1} mb="4px" />
            </Text>
          </SimpleTooltip>
          <Select
            {...DASHBOARD_BOX_PROPS}
            ml={2}
            mb={2}
            width="180px"
            borderRadius="7px"
            value={activeUniSwapPair}
            _focus={{ outline: "none" }}
            placeholder={
              activeUniSwapPair === "" ? t("Choose Pool") : activeUniSwapPair
            }
            onChange={(event) => {
              updateBoth(event.target.value);
            }}
          >
            {typeof liquidity !== undefined
              ? Object.entries(liquiditySorted).map(([key, value]: any[]) =>
                  value.totalValueLockedUSD !== null &&
                  value.totalValueLockedUSD !== undefined &&
                  value.totalValueLockedUSD >= 100 ? (
                    <option
                      className="black-bg-option"
                      value={key}
                      key={value.id}
                    >
                      {`${value.token0.symbol} / ${
                        value.token1.symbol
                      } (${shortUsdFormatter(value.totalValueLockedUSD)})`}
                    </option>
                  ) : null
                )
              : null}
          </Select>
        </Row>

        {activeUniSwapPair !== "" ? (
          <Column
            mainAxisAlignment="flex-start"
            crossAxisAlignment="flex-start"
          >
            <Row mainAxisAlignment="flex-start" crossAxisAlignment="flex-start">
              <Alert
                status="warning"
                width="100%"
                height="70px"
                borderRadius={5}
                my={1}
              >
                <AlertIcon />
                <Text fontSize="sm" align="center" color="black">
                  {
                    "Make sure this Uniswap V3 Pool has full-range liquidity. If not, your pool could be compromised."
                  }
                </Text>
              </Alert>
            </Row>

            <Row
              mainAxisAlignment="space-between"
              crossAxisAlignment="center"
              my={2}
              w="100%"
              // bg="pink"
            >
              <SimpleTooltip label={t("TVL in pool as of this moment.")}>
                <Text fontWeight="bold">
                  {t("Liquidity:")} <QuestionIcon ml={1} mb="4px" />
                </Text>
              </SimpleTooltip>
              <h1>
                {activeUniSwapPair !== ""
                  ? shortUsdFormatter(
                      liquidity.data.token.whitelistPools[activeUniSwapPair]
                        .totalValueLockedUSD
                    )
                  : null}
              </h1>
            </Row>
            <Row
              mainAxisAlignment="space-between"
              crossAxisAlignment="center"
              my={2}
              w="100%"
              // bg="pink"
            >
              <SimpleTooltip label={t("Volume of pool.")}>
                <Text fontWeight="bold">
                  {t("Volume:")} <QuestionIcon ml={1} mb="4px" />
                </Text>
              </SimpleTooltip>

              <h1>
                {activeUniSwapPair !== ""
                  ? shortUsdFormatter(
                      liquidity.data.token.whitelistPools[activeUniSwapPair]
                        .volumeUSD
                    )
                  : null}
              </h1>
            </Row>
            {/* <Row
              mainAxisAlignment="space-between"
              crossAxisAlignment="center"
              my={2}
              w="100%"
              // bg="pink"
            >
              <SimpleTooltip
                label={t(
                  "The fee percentage for the pool on Uniswap (0.05%, 0.3%, 1%)"
                )}
              >
                <Text fontWeight="bold">
                  {t("Fee Tier:")} <QuestionIcon ml={1} mb="4px" />
                </Text>
              </SimpleTooltip>
              <Text>
                %
                {activeUniSwapPair !== ""
                  ? liquidity.data.token.whitelistPools[activeUniSwapPair]
                      .feeTier / 10000
                  : null}
              </Text>
            </Row> */}
            <Row
              crossAxisAlignment="center"
              mainAxisAlignment="center"
              width="260px"
              my={0}
            >
              <Link
                href={`https://info.uniswap.org/#/pools/${liquidity.data.token.whitelistPools[activeUniSwapPair].id}`}
                isExternal
              >
                Visit Pool in Uniswap
              </Link>
            </Row>
          </Column>
        ) : null}
      </Column>
    </>
  );
}
Example #7
Source File: AddAssetModal.tsx    From rari-dApp with GNU Affero General Public License v3.0 4 votes vote down vote up
AssetSettings = ({
  poolName,
  poolID,
  tokenData,
  comptrollerAddress,
  cTokenAddress,
  existingAssets,
  closeModal,
}: {
  poolName: string;
  poolID: string;
  comptrollerAddress: string;
  tokenData: TokenData;

  // Only for editing mode
  cTokenAddress?: string;

  // Only for add asset modal
  existingAssets?: USDPricedFuseAsset[];
  closeModal: () => any;
}) => {
  const { t } = useTranslation();
  const { fuse, address } = useRari();
  const toast = useToast();
  const queryClient = useQueryClient();

  const [isDeploying, setIsDeploying] = useState(false);

  const [collateralFactor, setCollateralFactor] = useState(50);
  const [reserveFactor, setReserveFactor] = useState(10);
  const [adminFee, setAdminFee] = useState(0);

  const [isBorrowPaused, setIsBorrowPaused] = useState(false);

  const scaleCollateralFactor = (_collateralFactor: number) => {
    return _collateralFactor / 1e16;
  };

  const scaleReserveFactor = (_reserveFactor: number) => {
    return _reserveFactor / 1e16;
  };

  const scaleAdminFee = (_adminFee: number) => {
    return _adminFee / 1e16;
  };

  const [interestRateModel, setInterestRateModel] = useState(
    Fuse.PUBLIC_INTEREST_RATE_MODEL_CONTRACT_ADDRESSES
      .JumpRateModel_Cream_Stables_Majors
  );

  const { data: curves } = useQuery(
    interestRateModel + adminFee + reserveFactor + " irm",
    async () => {
      const IRM = await fuse.identifyInterestRateModel(interestRateModel);

      if (IRM === null) {
        return null;
      }

      await IRM._init(
        fuse.web3,
        interestRateModel,
        // reserve factor
        reserveFactor * 1e16,
        // admin fee
        adminFee * 1e16,
        // hardcoded 10% Fuse fee
        0.1e18
      );

      return convertIRMtoCurve(IRM, fuse);
    }
  );

  const deploy = async () => {
    // If pool already contains this asset:
    if (
      existingAssets!.some(
        (asset) => asset.underlyingToken === tokenData.address
      )
    ) {
      toast({
        title: "Error!",
        description: "You have already added this asset to this pool.",
        status: "error",
        duration: 2000,
        isClosable: true,
        position: "top-right",
      });

      return;
    }

    setIsDeploying(true);

    // 50% -> 0.5 * 1e18
    const bigCollateralFacotr = new BigNumber(collateralFactor)
      .dividedBy(100)
      .multipliedBy(1e18)
      .toFixed(0);

    // 10% -> 0.1 * 1e18
    const bigReserveFactor = new BigNumber(reserveFactor)
      .dividedBy(100)
      .multipliedBy(1e18)
      .toFixed(0);

    // 5% -> 0.05 * 1e18
    const bigAdminFee = new BigNumber(adminFee)
      .dividedBy(100)
      .multipliedBy(1e18)
      .toFixed(0);

    const conf: any = {
      underlying: tokenData.address,
      comptroller: comptrollerAddress,
      interestRateModel,
      initialExchangeRateMantissa: fuse.web3.utils.toBN(1e18),

      // Ex: BOGGED USDC
      name: poolName + " " + tokenData.name,
      // Ex: fUSDC-456
      symbol: "f" + tokenData.symbol + "-" + poolID,
      decimals: 8,
    };

    try {
      await fuse.deployAsset(
        conf,
        bigCollateralFacotr,
        bigReserveFactor,
        bigAdminFee,
        { from: address },
        // TODO: Disable this. This bypasses the price feed check. Only using now because only trusted partners are deploying assets.
        true
      );

      LogRocket.track("Fuse-DeployAsset");

      queryClient.refetchQueries();
      // Wait 2 seconds for refetch and then close modal.
      // We do this instead of waiting the refetch because some refetches take a while or error out and we want to close now.
      await new Promise((resolve) => setTimeout(resolve, 2000));

      toast({
        title: "You have successfully added an asset to this pool!",
        description: "You may now lend and borrow with this asset.",
        status: "success",
        duration: 2000,
        isClosable: true,
        position: "top-right",
      });

      closeModal();
    } catch (e) {
      handleGenericError(e, toast);
    }
  };

  const liquidationIncentiveMantissa =
    useLiquidationIncentive(comptrollerAddress);

  const cTokenData = useCTokenData(comptrollerAddress, cTokenAddress);

  // Update values on refetch!
  useEffect(() => {
    if (cTokenData) {
      setCollateralFactor(cTokenData.collateralFactorMantissa / 1e16);
      setReserveFactor(cTokenData.reserveFactorMantissa / 1e16);
      setAdminFee(cTokenData.adminFeeMantissa / 1e16);
      setInterestRateModel(cTokenData.interestRateModelAddress);
      setIsBorrowPaused(cTokenData.isPaused);
    }
  }, [cTokenData]);

  const togglePause = async () => {
    const comptroller = createComptroller(comptrollerAddress, fuse);

    try {
      await comptroller.methods
        ._setBorrowPaused(cTokenAddress, !isBorrowPaused)
        .send({ from: address });

      LogRocket.track("Fuse-PauseToggle");

      queryClient.refetchQueries();
    } catch (e) {
      handleGenericError(e, toast);
    }
  };

  const updateCollateralFactor = async () => {
    const comptroller = createComptroller(comptrollerAddress, fuse);

    // 70% -> 0.7 * 1e18
    const bigCollateralFactor = new BigNumber(collateralFactor)
      .dividedBy(100)
      .multipliedBy(1e18)
      .toFixed(0);

    try {
      await testForComptrollerErrorAndSend(
        comptroller.methods._setCollateralFactor(
          cTokenAddress,
          bigCollateralFactor
        ),
        address,
        ""
      );

      LogRocket.track("Fuse-UpdateCollateralFactor");

      queryClient.refetchQueries();
    } catch (e) {
      handleGenericError(e, toast);
    }
  };

  const updateReserveFactor = async () => {
    const cToken = createCToken(fuse, cTokenAddress!);

    // 10% -> 0.1 * 1e18
    const bigReserveFactor = new BigNumber(reserveFactor)
      .dividedBy(100)
      .multipliedBy(1e18)
      .toFixed(0);
    try {
      await testForCTokenErrorAndSend(
        cToken.methods._setReserveFactor(bigReserveFactor),
        address,
        ""
      );

      LogRocket.track("Fuse-UpdateReserveFactor");

      queryClient.refetchQueries();
    } catch (e) {
      handleGenericError(e, toast);
    }
  };

  const updateAdminFee = async () => {
    const cToken = createCToken(fuse, cTokenAddress!);

    // 5% -> 0.05 * 1e18
    const bigAdminFee = new BigNumber(adminFee)
      .dividedBy(100)
      .multipliedBy(1e18)
      .toFixed(0);

    try {
      await testForCTokenErrorAndSend(
        cToken.methods._setAdminFee(bigAdminFee),
        address,
        ""
      );

      LogRocket.track("Fuse-UpdateAdminFee");

      queryClient.refetchQueries();
    } catch (e) {
      handleGenericError(e, toast);
    }
  };

  const updateInterestRateModel = async () => {
    const cToken = createCToken(fuse, cTokenAddress!);

    try {
      await testForCTokenErrorAndSend(
        cToken.methods._setInterestRateModel(interestRateModel),
        address,
        ""
      );

      LogRocket.track("Fuse-UpdateInterestRateModel");

      queryClient.refetchQueries();
    } catch (e) {
      handleGenericError(e, toast);
    }
  };

  return (
    cTokenAddress ? cTokenData?.cTokenAddress === cTokenAddress : true
  ) ? (
    <Column
      mainAxisAlignment="flex-start"
      crossAxisAlignment="flex-start"
      overflowY="auto"
      width="100%"
      height="100%"
    >
      <ConfigRow height="35px">
        <SimpleTooltip
          label={t(
            "Collateral factor can range from 0-90%, and represents the proportionate increase in liquidity (borrow limit) that an account receives by depositing the asset."
          )}
        >
          <Text fontWeight="bold">
            {t("Collateral Factor")} <QuestionIcon ml={1} mb="4px" />
          </Text>
        </SimpleTooltip>

        {cTokenData &&
        collateralFactor !==
          scaleCollateralFactor(cTokenData.collateralFactorMantissa) ? (
          <SaveButton ml={3} onClick={updateCollateralFactor} />
        ) : null}

        <SliderWithLabel
          ml="auto"
          value={collateralFactor}
          setValue={setCollateralFactor}
          formatValue={formatPercentage}
          step={0.5}
          max={
            liquidationIncentiveMantissa
              ? // 100% CF - Liquidation Incentive (ie: 8%) - 5% buffer
                100 - (liquidationIncentiveMantissa.toString() / 1e16 - 100) - 5
              : 90
          }
        />
      </ConfigRow>

      <ModalDivider />
      {cTokenAddress ? (
        <ConfigRow>
          <SimpleTooltip
            label={t("If enabled borrowing this asset will be disabled.")}
          >
            <Text fontWeight="bold">
              {t("Pause Borrowing")} <QuestionIcon ml={1} mb="4px" />
            </Text>
          </SimpleTooltip>

          <SaveButton
            ml="auto"
            onClick={togglePause}
            fontSize="xs"
            altText={
              isBorrowPaused ? t("Enable Borrowing") : t("Pause Borrowing")
            }
          />
        </ConfigRow>
      ) : null}

      <ModalDivider />

      <ConfigRow height="35px">
        <SimpleTooltip
          label={t(
            "The fraction of interest generated on a given asset that is routed to the asset's Reserve Pool. The Reserve Pool protects lenders against borrower default and liquidation malfunction."
          )}
        >
          <Text fontWeight="bold">
            {t("Reserve Factor")} <QuestionIcon ml={1} mb="4px" />
          </Text>
        </SimpleTooltip>

        {cTokenData &&
        reserveFactor !==
          scaleReserveFactor(cTokenData.reserveFactorMantissa) ? (
          <SaveButton ml={3} onClick={updateReserveFactor} />
        ) : null}

        <SliderWithLabel
          ml="auto"
          value={reserveFactor}
          setValue={setReserveFactor}
          formatValue={formatPercentage}
          max={50}
        />
      </ConfigRow>
      <ModalDivider />

      <ConfigRow height="35px">
        <SimpleTooltip
          label={t(
            "The fraction of interest generated on a given asset that is routed to the asset's admin address as a fee."
          )}
        >
          <Text fontWeight="bold">
            {t("Admin Fee")} <QuestionIcon ml={1} mb="4px" />
          </Text>
        </SimpleTooltip>

        {cTokenData &&
        adminFee !== scaleAdminFee(cTokenData.adminFeeMantissa) ? (
          <SaveButton ml={3} onClick={updateAdminFee} />
        ) : null}

        <SliderWithLabel
          ml="auto"
          value={adminFee}
          setValue={setAdminFee}
          formatValue={formatPercentage}
          max={30}
        />
      </ConfigRow>

      <ModalDivider />

      <ConfigRow>
        <SimpleTooltip
          label={t(
            "The interest rate model chosen for an asset defines the rates of interest for borrowers and suppliers at different utilization levels."
          )}
        >
          <Text fontWeight="bold">
            {t("Interest Model")} <QuestionIcon ml={1} mb="4px" />
          </Text>
        </SimpleTooltip>

        <Select
          {...DASHBOARD_BOX_PROPS}
          ml="auto"
          borderRadius="7px"
          fontWeight="bold"
          _focus={{ outline: "none" }}
          width="260px"
          value={interestRateModel.toLowerCase()}
          onChange={(event) => setInterestRateModel(event.target.value)}
        >
          {Object.entries(
            Fuse.PUBLIC_INTEREST_RATE_MODEL_CONTRACT_ADDRESSES
          ).map(([key, value]) => {
            return (
              <option
                className="black-bg-option"
                value={value.toLowerCase()}
                key={key}
              >
                {key}
              </option>
            );
          })}
        </Select>

        {cTokenData &&
        cTokenData.interestRateModelAddress.toLowerCase() !==
          interestRateModel.toLowerCase() ? (
          <SaveButton
            height="40px"
            borderRadius="7px"
            onClick={updateInterestRateModel}
          />
        ) : null}
      </ConfigRow>

      <Box
        height="170px"
        width="100%"
        color="#000000"
        overflow="hidden"
        pl={2}
        pr={3}
        className="hide-bottom-tooltip"
        flexShrink={0}
      >
        {curves ? (
          <Chart
            options={
              {
                ...FuseIRMDemoChartOptions,
                colors: ["#FFFFFF", tokenData.color! ?? "#282727"],
              } as any
            }
            type="line"
            width="100%"
            height="100%"
            series={[
              {
                name: "Borrow Rate",
                data: curves.borrowerRates,
              },
              {
                name: "Deposit Rate",
                data: curves.supplierRates,
              },
            ]}
          />
        ) : curves === undefined ? (
          <Center expand color="#FFF">
            <Spinner my={8} />
          </Center>
        ) : (
          <Center expand color="#FFFFFF">
            <Text>
              {t("No graph is available for this asset's interest curves.")}
            </Text>
          </Center>
        )}
      </Box>

      {cTokenAddress ? null : (
        <Box px={4} mt={4} width="100%">
          <Button
            fontWeight="bold"
            fontSize="2xl"
            borderRadius="10px"
            width="100%"
            height="70px"
            color={tokenData.overlayTextColor! ?? "#000"}
            bg={tokenData.color! ?? "#FFF"}
            _hover={{ transform: "scale(1.02)" }}
            _active={{ transform: "scale(0.95)" }}
            isLoading={isDeploying}
            onClick={deploy}
          >
            {t("Confirm")}
          </Button>
        </Box>
      )}
    </Column>
  ) : (
    <Center expand>
      <Spinner my={8} />
    </Center>
  );
}
Example #8
Source File: MarketCapConfigurator.tsx    From rari-dApp with GNU Affero General Public License v3.0 4 votes vote down vote up
MarketCapConfigurator = ({
  comptrollerAddress,
  cTokenAddress,
  tokenData,
  mode,
}: {
  comptrollerAddress: string;
  cTokenAddress: string | undefined;
  tokenData: TokenData;
  mode: "Supply" | "Borrow";
}) => {
  const { t } = useTranslation();
  const [newSupplyCap, setNewSupplyCap] = useState<string>("");
  const { fuse, address } = useRari();
  const toast = useToast();

  const tokenSymbol = tokenData.symbol

  const comptroller = createComptroller(comptrollerAddress, fuse);

  const { data: supplyCap } = useQuery(
    "Get " + mode + " cap for: " + tokenSymbol,
    async () => {
      if (cTokenAddress) {
        if (mode === "Supply") {
          return await comptroller.methods.supplyCaps(cTokenAddress).call();
        }

        if (mode === "Borrow") {
          return await comptroller.methods.borrowCaps(cTokenAddress).call();
        }
      }
    }
  );


  const handleSubmit = async (
    cTokenAddress: string | undefined,
    newSupplyCap: number
  ) => {
    const tokenDecimals = tokenData.decimals ?? 18
    const newSupplyCapInWei = newSupplyCap * (10 ** tokenDecimals)

    if (!cTokenAddress) return

    try {
      if (mode === "Supply")
        await comptroller.methods
          ._setMarketSupplyCaps([cTokenAddress], [newSupplyCapInWei])
          .send({ from: address });

      if (mode === "Borrow")
        await comptroller.methods
          ._setMarketBorrowCaps([cTokenAddress], [newSupplyCapInWei])
          .send({ from: address });

      toast({
        title: "Success!",
        description: "You've updated the asset's" + mode + " cap.",
        status: "success",
        duration: 2000,
        isClosable: true,
        position: "top-right",
      });
    } catch (e) {
      handleGenericError(e, toast);
    }
  };


  const displayedSupplyCap = (parseInt(supplyCap) / (10 ** (tokenData?.decimals ?? 18))).toLocaleString() + " " + tokenSymbol

  return (
    <>
      <Row
        mainAxisAlignment="center"
        justifyContent="space-between"
        crossAxisAlignment="center"
        width="100%"
        my={4}
        px={4}
        height="100%"
      >
        <SimpleTooltip
          label={t(
            "Sets cap for the market. Users will not be able to supply/borrow once the cap is met."
          )}
        >
          <Text fontWeight="bold">
            {mode + " caps"} <QuestionIcon ml={1} mb="4px" />
          </Text>
        </SimpleTooltip>
        <Box
          width="50%"
          height="auto"
          display="flex"
          flexDirection="column"
          justifyContent="center"
          alignItems="flex-end"
          alignContent="center"
        >
          <Input
            width="150px"
            height="30px"
            extAlign="center"
            mb={newSupplyCap !== "" ? 5 : 0}
            placeholder={
              supplyCap > 0
                ? displayedSupplyCap
                : t(`${tokenSymbol} Cap`)
            }
            type="number"
            size="sm"
            value={newSupplyCap}
            onChange={(event) => {
              setNewSupplyCap(event.target.value);
            }}
          />

          {newSupplyCap !== "" ? (
            <Box
              height="100%"
              width="100%"
              display="flex"
              justifyContent="flex-end"
              flexDirection="column"
            >
              <Box
                display="flex"
                justifyContent="space-between"
                alignContent="center"
              >
                <Text size="sm" opacity="0.7">
                  New supply cap:
                </Text>
                <Box height="100%" width="40%">
                  <Text opacity="0.5" textAlign="end">
                    {parseInt(newSupplyCap).toLocaleString()} {tokenSymbol} 
                  </Text>
                </Box>
              </Box>

              {supplyCap === "0" ? null : (
                <Box
                  display="flex"
                  justifyContent="space-between"
                  alignContent="center"
                >
                  <Text size="sm" opacity="0.7">
                    Current supply cap:
                  </Text>
                  <Box height="100%" width="40%">
                    <Text opacity="0.5" textAlign="end">
                     {displayedSupplyCap}
                    </Text>
                  </Box>
                </Box>
              )}
              <SaveButton
                mt="2"
                ml="auto"
                onClick={() =>
                  handleSubmit(cTokenAddress, parseInt(newSupplyCap))
                }
                fontSize="xs"
                altText={"Submit"}
              />
            </Box>
          ) : null}
        </Box>
      </Row>
    </>
  );
}
Example #9
Source File: MultiPoolPortal.tsx    From rari-dApp with GNU Affero General Public License v3.0 4 votes vote down vote up
PoolDetailCard = ({ pool }: { pool: Pool }) => {
  const { t } = useTranslation();

  const { rari, isAuthed } = useRari();

  const { poolType, poolName, poolLogo } = usePoolInfo(pool);

  const {
    isOpen: isDepositModalOpen,
    onOpen: openDepositModal,
    onClose: closeDepositModal,
  } = useDisclosure();

  const authedOpenModal = useAuthedCallback(openDepositModal);

  const { data: balanceData, isLoading: isPoolBalanceLoading } =
    usePoolBalance(pool);

  const poolAPY = usePoolAPY(pool);

  const noSlippageCurrencies = useNoSlippageCurrencies(pool);

  if (isPoolBalanceLoading) {
    return (
      <Center
        height={{
          md: isAuthed ? "235px" : "110px",
          base: isAuthed ? "330px" : "215px",
        }}
      >
        <Spinner />
      </Center>
    );
  }

  const myBalance = balanceData!;
  const formattedBalance = formatBalanceBN(rari, myBalance, pool === Pool.ETH);

  // const rgtAPR = useRGTAPR();

  return (
    <>
      {isDepositModalOpen ? (
        <PoolTypeProvider pool={pool}>
          <DepositModal
            isOpen={isDepositModalOpen}
            onClose={closeDepositModal}
          />
        </PoolTypeProvider>
      ) : null}
      <Column
        mainAxisAlignment="flex-start"
        crossAxisAlignment="center"
        expand
        p={4}
      >
        <Box width="50px" height="50px" flexShrink={0}>
          <Image src={poolLogo} />
        </Box>

        <Row mainAxisAlignment="flex-start" crossAxisAlignment="center" mt={2}>
          <Heading fontSize="xl" lineHeight="2.5rem" ml="12px">
            {poolName}
          </Heading>

          <SimpleTooltip
            label={
              "Rebalances " +
              (noSlippageCurrencies
                ? noSlippageCurrencies.join(" + ")
                : " ? ") +
              " between " +
              getSDKPool({ rari, pool: poolType })
                // Filter out empty pools
                .allocations.POOLS.filter((el) => !!el)
                .join(", ")
            }
          >
            <QuestionIcon ml={2} mb="3px" boxSize="12px" />
          </SimpleTooltip>
        </Row>

        <SimpleTooltip label={t("Your balance in this pool")}>
          <Text mt={4} mb={5} fontSize="md" textAlign="center">
            {isPoolBalanceLoading ? "$?" : formattedBalance}
          </Text>
        </SimpleTooltip>

        <Text fontWeight="bold" textAlign="center">
          {poolAPY ?? "?"}% APY
          {/* +{" "}
          <SimpleTooltip label={t("Extra returns from $RGT")}>
            <span>
              ({rgtAPR ?? "?"}%{" "}
              <Image display="inline" src={SmallLogo} boxSize="20px" />)
            </span>
          </SimpleTooltip> */}
        </Text>

        <Row
          mainAxisAlignment="flex-start"
          crossAxisAlignment="center"
          width="100%"
          mt="auto"
        >
          <Link
            /* @ts-ignore */
            as={RouterLink}
            width="100%"
            className="no-underline"
            to={"/pools/" + pool.toString()}
          >
            <DashboardBox
              mt={4}
              width="100%"
              height="45px"
              borderRadius="7px"
              fontSize="xl"
              fontWeight="bold"
            >
              <Center expand>{t("Access")}</Center>
            </DashboardBox>
          </Link>

          <DashboardBox
            mt={4}
            flexShrink={0}
            as="button"
            onClick={authedOpenModal}
            height="45px"
            ml={2}
            width="45px"
            borderRadius="7px"
            fontSize="xl"
            fontWeight="bold"
          >
            <Center expand>
              <Icon as={MdSwapHoriz} boxSize="30px" />
            </Center>
          </DashboardBox>
        </Row>
      </Column>
    </>
  );
}