components#CustomModal TypeScript Examples

The following examples show how to use components#CustomModal. 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: MoonpayModal.tsx    From interface-v2 with GNU General Public License v3.0 6 votes vote down vote up
MoonpayModal: React.FC<MoonpayModalProps> = ({ open, onClose }) => {
  return (
    <CustomModal
      open={open}
      onClose={onClose}
      background='#fff'
      overflow='hidden'
    >
      <div style={{ height: '100%', width: '100%', overflowY: 'auto' }}>
        <iframe
          title='moonpay'
          allow='accelerometer; autoplay; camera; gyroscope; payment'
          frameBorder='0'
          height='600px'
          src={`https://buy.moonpay.com?apiKey=${process.env.REACT_APP_MOONPAY_KEY}`}
          width='100%'
        >
          <p>Your browser does not support iframes.</p>
        </iframe>
      </div>
    </CustomModal>
  );
}
Example #2
Source File: BuyFiatModal.tsx    From interface-v2 with GNU General Public License v3.0 5 votes vote down vote up
BuyFiatModal: React.FC<BuyFiatModalProps> = ({
  open,
  onClose,
  buyMoonpay,
}) => {
  const classes = useStyles();
  const { account } = useActiveWeb3React();
  const { breakpoints } = useTheme();
  const mobileWindowSize = useMediaQuery(breakpoints.down('sm'));
  const { initTransak } = useInitTransak();

  return (
    <CustomModal open={open} onClose={onClose}>
      <Box padding={3}>
        <Box display='flex' justifyContent='space-between' alignItems='center'>
          <Typography variant='subtitle2' color='textPrimary'>
            Fiat gateway providers
          </Typography>
          <CloseIcon style={{ cursor: 'pointer' }} onClick={onClose} />
        </Box>
        <Box className={classes.paymentBox}>
          <img src={Moonpay} alt='moonpay' />
          <Box className={classes.buyButton} onClick={buyMoonpay}>
            Buy
          </Box>
        </Box>
        <Box className={classes.paymentBox}>
          <img src={Transak} alt='transak' />
          <Box
            className={classes.buyButton}
            onClick={() => {
              onClose();
              initTransak(account, mobileWindowSize);
            }}
          >
            Buy
          </Box>
        </Box>
        <Box mt={3} display='flex'>
          <Box display='flex' mt={0.3}>
            <HelpIcon />
          </Box>
          <Box ml={1.5} width='calc(100% - 32px)'>
            <Typography variant='body2'>
              Fiat services on Quickswap are provided by third-parties.
              Quickswap is not associated with, responsible or liable for the
              performance of these third-party services. Any claims & questions
              should be addressed with the selected provider.
            </Typography>
          </Box>
        </Box>
      </Box>
    </CustomModal>
  );
}
Example #3
Source File: CurrencySearchModal.tsx    From interface-v2 with GNU General Public License v3.0 5 votes vote down vote up
CurrencySearchModal: React.FC<CurrencySearchModalProps> = ({
  isOpen,
  onDismiss,
  onCurrencySelect,
  selectedCurrency,
  otherSelectedCurrency,
  showCommonBases = false,
}) => {
  const [listView, setListView] = useState<boolean>(false);
  const lastOpen = useLast(isOpen);

  useEffect(() => {
    if (isOpen && !lastOpen) {
      setListView(false);
    }
  }, [isOpen, lastOpen]);

  const handleCurrencySelect = useCallback(
    (currency: Currency) => {
      onCurrencySelect(currency);
      onDismiss();
    },
    [onDismiss, onCurrencySelect],
  );

  const handleClickChangeList = useCallback(() => {
    ReactGA.event({
      category: 'Lists',
      action: 'Change Lists',
    });
    setListView(true);
  }, []);
  const handleClickBack = useCallback(() => {
    ReactGA.event({
      category: 'Lists',
      action: 'Back',
    });
    setListView(false);
  }, []);

  return (
    <CustomModal open={isOpen} onClose={onDismiss}>
      {listView ? (
        <ListSelect onDismiss={onDismiss} onBack={handleClickBack} />
      ) : (
        <CurrencySearch
          isOpen={isOpen}
          onDismiss={onDismiss}
          onCurrencySelect={handleCurrencySelect}
          onChangeList={handleClickChangeList}
          selectedCurrency={selectedCurrency}
          otherSelectedCurrency={otherSelectedCurrency}
          showCommonBases={showCommonBases}
        />
      )}
    </CustomModal>
  );
}
Example #4
Source File: TransactionConfirmationModal.tsx    From interface-v2 with GNU General Public License v3.0 5 votes vote down vote up
TransactionConfirmationModal: React.FC<ConfirmationModalProps> = ({
  isOpen,
  onDismiss,
  attemptingTxn,
  txPending,
  hash,
  pendingText,
  content,
  modalContent,
}) => {
  const { chainId } = useActiveWeb3React();
  const classes = useStyles();

  if (!chainId) return null;

  // confirmation screen
  return (
    <CustomModal open={isOpen} onClose={onDismiss}>
      <img src={ModalBg} alt='Modal Back' className={classes.modalBG} />
      <Box position='relative' zIndex={2}>
        {attemptingTxn ? (
          <ConfirmationPendingContent
            onDismiss={onDismiss}
            pendingText={pendingText}
          />
        ) : hash ? (
          <TransactionSubmittedContent
            chainId={chainId}
            txPending={txPending}
            hash={hash}
            onDismiss={onDismiss}
            modalContent={modalContent}
          />
        ) : (
          content()
        )}
      </Box>
    </CustomModal>
  );
}
Example #5
Source File: PoolFinderModal.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
PoolFinderModal: React.FC<PoolFinderModalProps> = ({ open, onClose }) => {
  const classes = useStyles();
  const { palette } = useTheme();

  const { account } = useActiveWeb3React();

  const [showSearch, setShowSearch] = useState<boolean>(false);
  const [activeField, setActiveField] = useState<number>(Fields.TOKEN1);

  const [currency0, setCurrency0] = useState<Currency | null>(ETHER);
  const [currency1, setCurrency1] = useState<Currency | null>(null);

  const [pairState, pair] = usePair(
    currency0 ?? undefined,
    currency1 ?? undefined,
  );
  const addPair = usePairAdder();
  useEffect(() => {
    if (pair) {
      addPair(pair);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [pair?.liquidityToken.address, addPair]);

  const validPairNoLiquidity: boolean =
    pairState === PairState.NOT_EXISTS ||
    Boolean(
      pairState === PairState.EXISTS &&
        pair &&
        JSBI.equal(pair.reserve0.raw, JSBI.BigInt(0)) &&
        JSBI.equal(pair.reserve1.raw, JSBI.BigInt(0)),
    );

  const position: TokenAmount | undefined = useTokenBalance(
    account ?? undefined,
    pair?.liquidityToken,
  );
  const hasPosition = Boolean(
    position && JSBI.greaterThan(position.raw, JSBI.BigInt(0)),
  );

  const handleCurrencySelect = useCallback(
    (currency: Currency) => {
      if (activeField === Fields.TOKEN0) {
        setCurrency0(currency);
      } else {
        setCurrency1(currency);
      }
    },
    [activeField],
  );

  const handleSearchDismiss = useCallback(() => {
    setShowSearch(false);
  }, [setShowSearch]);

  return (
    <CustomModal open={open} onClose={onClose}>
      <Box paddingX={3} paddingY={4}>
        <Box display='flex' alignItems='center' justifyContent='space-between'>
          <ArrowLeft
            color={palette.text.secondary}
            style={{ cursor: 'pointer' }}
            onClick={onClose}
          />
          <Typography
            variant='subtitle2'
            style={{ color: palette.text.primary }}
          >
            Import Pool
          </Typography>
          <CloseIcon style={{ cursor: 'pointer' }} onClick={onClose} />
        </Box>
        <Box
          mt={2}
          className={classes.borderedCard}
          onClick={() => {
            setShowSearch(true);
            setActiveField(Fields.TOKEN0);
          }}
        >
          {currency0 ? (
            <Box display='flex' alignItems='center'>
              <CurrencyLogo currency={currency0} size='20px' />
              <Typography variant='h6' style={{ marginLeft: 6 }}>
                {currency0.symbol}
              </Typography>
            </Box>
          ) : (
            <Typography variant='h6'>Select a Token</Typography>
          )}
        </Box>
        <Box my={1} display='flex' justifyContent='center'>
          <Plus size='20' color={palette.text.secondary} />
        </Box>
        <Box
          className={classes.borderedCard}
          onClick={() => {
            setShowSearch(true);
            setActiveField(Fields.TOKEN1);
          }}
        >
          {currency1 ? (
            <Box display='flex'>
              <CurrencyLogo currency={currency1} />
              <Typography variant='h6' style={{ marginLeft: 6 }}>
                {currency1.symbol}
              </Typography>
            </Box>
          ) : (
            <Typography variant='h6'>Select a Token</Typography>
          )}
        </Box>
        {hasPosition && (
          <Box textAlign='center' mt={2}>
            <Typography variant='body1'>Pool Found!</Typography>
            <Typography
              variant='body1'
              style={{ cursor: 'pointer', color: palette.primary.main }}
              onClick={onClose}
            >
              Manage this pool.
            </Typography>
          </Box>
        )}
        <Box
          mt={2}
          p={1}
          borderRadius={10}
          display='flex'
          justifyContent='center'
          border={`1px solid ${palette.divider}`}
        >
          {currency0 && currency1 ? (
            pairState === PairState.EXISTS ? (
              hasPosition && pair ? (
                <MinimalPositionCard pair={pair} border='none' />
              ) : (
                <Box textAlign='center'>
                  <Typography>
                    You don’t have liquidity in this pool yet.
                  </Typography>
                  <Link
                    to={`/pools?currency0=${currencyId(
                      currency0,
                    )}&currency1=${currencyId(currency1)}`}
                    style={{
                      color: palette.primary.main,
                      textDecoration: 'none',
                    }}
                    onClick={onClose}
                  >
                    <Typography>Add liquidity.</Typography>
                  </Link>
                </Box>
              )
            ) : validPairNoLiquidity ? (
              <Box textAlign='center'>
                <Typography>No pool found.</Typography>
                <Link
                  to={`/pools?currency0=${currencyId(
                    currency0,
                  )}&currency1=${currencyId(currency1)}`}
                  style={{
                    color: palette.primary.main,
                    textDecoration: 'none',
                  }}
                  onClick={onClose}
                >
                  Create pool.
                </Link>
              </Box>
            ) : pairState === PairState.INVALID ? (
              <Typography>Invalid pair.</Typography>
            ) : pairState === PairState.LOADING ? (
              <Typography>Loading...</Typography>
            ) : null
          ) : (
            <Typography>
              {!account
                ? 'Connect to a wallet to find pools'
                : 'Select a token to find your liquidity.'}
            </Typography>
          )}
        </Box>
      </Box>
      {showSearch && (
        <CurrencySearchModal
          isOpen={showSearch}
          onCurrencySelect={handleCurrencySelect}
          onDismiss={handleSearchDismiss}
          showCommonBases
          selectedCurrency={
            (activeField === Fields.TOKEN0 ? currency1 : currency0) ?? undefined
          }
        />
      )}
    </CustomModal>
  );
}
Example #6
Source File: RemoveLiquidityModal.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
  currency0,
  currency1,
  open,
  onClose,
}) => {
  const classes = useStyles();
  const { palette } = useTheme();
  const [showConfirm, setShowConfirm] = useState(false);
  const [txPending, setTxPending] = useState(false);
  const [approving, setApproving] = useState(false);
  const [attemptingTxn, setAttemptingTxn] = useState(false);
  const [removeErrorMessage, setRemoveErrorMessage] = useState('');
  const [errorMsg, setErrorMsg] = useState('');
  const [txHash, setTxHash] = useState('');
  const addTransaction = useTransactionAdder();
  const finalizedTransaction = useTransactionFinalizer();
  const { chainId, account, library } = useActiveWeb3React();
  const [tokenA, tokenB] = useMemo(
    () => [
      wrappedCurrency(currency0, chainId),
      wrappedCurrency(currency1, chainId),
    ],
    [currency0, currency1, chainId],
  );

  const { independentField, typedValue } = useBurnState();
  const { pair, parsedAmounts, error } = useDerivedBurnInfo(
    currency0,
    currency1,
  );
  const deadline = useTransactionDeadline();
  const { onUserInput: _onUserInput } = useBurnActionHandlers();
  const [allowedSlippage] = useUserSlippageTolerance();

  const onUserInput = useCallback(
    (field: Field, typedValue: string) => {
      return _onUserInput(field, typedValue);
    },
    [_onUserInput],
  );

  const onLiquidityInput = useCallback(
    (typedValue: string): void => onUserInput(Field.LIQUIDITY, typedValue),
    [onUserInput],
  );

  const liquidityPercentChangeCallback = useCallback(
    (value: number) => {
      onUserInput(Field.LIQUIDITY_PERCENT, value.toString());
    },
    [onUserInput],
  );

  const [
    innerLiquidityPercentage,
    setInnerLiquidityPercentage,
  ] = useDebouncedChangeHandler(
    Number.parseInt(parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0)),
    liquidityPercentChangeCallback,
  );
  const userPoolBalance = useTokenBalance(
    account ?? undefined,
    pair?.liquidityToken,
  );
  const totalPoolTokens = useTotalSupply(pair?.liquidityToken);
  const poolTokenPercentage =
    !!userPoolBalance &&
    !!totalPoolTokens &&
    JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
      ? new Percent(userPoolBalance.raw, totalPoolTokens.raw)
      : undefined;

  const formattedAmounts = {
    [Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo(
      '0',
    )
      ? '0'
      : parsedAmounts[Field.LIQUIDITY_PERCENT].lessThan(new Percent('1', '100'))
      ? '<1'
      : parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0),
    [Field.LIQUIDITY]:
      independentField === Field.LIQUIDITY
        ? typedValue
        : parsedAmounts[Field.LIQUIDITY]?.toExact() ?? '',
    [Field.CURRENCY_A]:
      independentField === Field.CURRENCY_A
        ? typedValue
        : parsedAmounts[Field.CURRENCY_A]?.toExact() ?? '',
    [Field.CURRENCY_B]:
      independentField === Field.CURRENCY_B
        ? typedValue
        : parsedAmounts[Field.CURRENCY_B]?.toExact() ?? '',
  };

  const [token0Deposited, token1Deposited] =
    !!pair &&
    !!totalPoolTokens &&
    !!userPoolBalance &&
    JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
      ? [
          pair.getLiquidityValue(
            pair.token0,
            totalPoolTokens,
            userPoolBalance,
            false,
          ),
          pair.getLiquidityValue(
            pair.token1,
            totalPoolTokens,
            userPoolBalance,
            false,
          ),
        ]
      : [undefined, undefined];

  const pairContract: Contract | null = usePairContract(
    pair?.liquidityToken?.address,
  );
  const [approval, approveCallback] = useApproveCallback(
    parsedAmounts[Field.LIQUIDITY],
    chainId ? GlobalConst.addresses.ROUTER_ADDRESS[chainId] : undefined,
  );
  const onAttemptToApprove = async () => {
    if (!pairContract || !pair || !library || !deadline) {
      setErrorMsg('missing dependencies');
      return;
    }
    const liquidityAmount = parsedAmounts[Field.LIQUIDITY];
    if (!liquidityAmount) {
      setErrorMsg('missing liquidity amount');
      return;
    }
    setApproving(true);
    try {
      await approveCallback();
      setApproving(false);
    } catch (e) {
      setApproving(false);
    }
  };

  const handleDismissConfirmation = useCallback(() => {
    setShowConfirm(false);
    setTxHash('');
  }, []);

  const router = useRouterContract();

  const onRemove = async () => {
    if (!chainId || !library || !account || !deadline || !router)
      throw new Error('missing dependencies');
    const {
      [Field.CURRENCY_A]: currencyAmountA,
      [Field.CURRENCY_B]: currencyAmountB,
    } = parsedAmounts;
    if (!currencyAmountA || !currencyAmountB) {
      throw new Error('missing currency amounts');
    }

    const amountsMin = {
      [Field.CURRENCY_A]: calculateSlippageAmount(
        currencyAmountA,
        allowedSlippage,
      )[0],
      [Field.CURRENCY_B]: calculateSlippageAmount(
        currencyAmountB,
        allowedSlippage,
      )[0],
    };

    const liquidityAmount = parsedAmounts[Field.LIQUIDITY];
    if (!liquidityAmount) throw new Error('missing liquidity amount');

    const currencyBIsETH = currency1 === ETHER;
    const oneCurrencyIsETH = currency0 === ETHER || currencyBIsETH;

    if (!tokenA || !tokenB) throw new Error('could not wrap');

    let methodNames: string[],
      args: Array<string | string[] | number | boolean>;
    // we have approval, use normal remove liquidity
    if (approval === ApprovalState.APPROVED) {
      // removeLiquidityETH
      if (oneCurrencyIsETH) {
        methodNames = [
          'removeLiquidityETH',
          'removeLiquidityETHSupportingFeeOnTransferTokens',
        ];
        args = [
          currencyBIsETH ? tokenA.address : tokenB.address,
          liquidityAmount.raw.toString(),
          amountsMin[
            currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B
          ].toString(),
          amountsMin[
            currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A
          ].toString(),
          account,
          deadline.toHexString(),
        ];
      }
      // removeLiquidity
      else {
        methodNames = ['removeLiquidity'];
        args = [
          tokenA.address,
          tokenB.address,
          liquidityAmount.raw.toString(),
          amountsMin[Field.CURRENCY_A].toString(),
          amountsMin[Field.CURRENCY_B].toString(),
          account,
          deadline.toHexString(),
        ];
      }
    } else {
      throw new Error(
        'Attempting to confirm without approval. Please contact support.',
      );
    }

    const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
      methodNames.map((methodName) =>
        router.estimateGas[methodName](...args)
          .then(calculateGasMargin)
          .catch((error) => {
            console.error(`estimateGas failed`, methodName, args, error);
            return undefined;
          }),
      ),
    );

    const indexOfSuccessfulEstimation = safeGasEstimates.findIndex(
      (safeGasEstimate) => BigNumber.isBigNumber(safeGasEstimate),
    );

    // all estimations failed...
    if (indexOfSuccessfulEstimation === -1) {
      console.error('This transaction would fail. Please contact support.');
    } else {
      const methodName = methodNames[indexOfSuccessfulEstimation];
      const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation];

      setAttemptingTxn(true);
      await router[methodName](...args, {
        gasLimit: safeGasEstimate,
      })
        .then(async (response: TransactionResponse) => {
          setAttemptingTxn(false);
          setTxPending(true);
          const summary =
            'Remove ' +
            parsedAmounts[Field.CURRENCY_A]?.toSignificant(3) +
            ' ' +
            currency0.symbol +
            ' and ' +
            parsedAmounts[Field.CURRENCY_B]?.toSignificant(3) +
            ' ' +
            currency1.symbol;

          addTransaction(response, {
            summary,
          });

          setTxHash(response.hash);

          try {
            const receipt = await response.wait();
            finalizedTransaction(receipt, {
              summary,
            });
            setTxPending(false);
          } catch (error) {
            setTxPending(false);
            setRemoveErrorMessage('There is an error in transaction.');
          }

          ReactGA.event({
            category: 'Liquidity',
            action: 'Remove',
            label: [currency0.symbol, currency1.symbol].join('/'),
          });
        })
        .catch((error: Error) => {
          setAttemptingTxn(false);
          // we only care if the error is something _other_ than the user rejected the tx
          console.error(error);
        });
    }
  };

  const modalHeader = () => {
    return (
      <Box>
        <Box mt={10} mb={3} display='flex' justifyContent='center'>
          <DoubleCurrencyLogo
            currency0={currency0}
            currency1={currency1}
            size={48}
          />
        </Box>
        <Box mb={6} color={palette.text.primary} textAlign='center'>
          <Typography variant='h6'>
            Removing {formattedAmounts[Field.LIQUIDITY]} {currency0.symbol} /{' '}
            {currency1.symbol} LP Tokens
            <br />
            You will receive {parsedAmounts[Field.CURRENCY_A]?.toSignificant(
              2,
            )}{' '}
            {currency0.symbol} and{' '}
            {parsedAmounts[Field.CURRENCY_B]?.toSignificant(2)}{' '}
            {currency1.symbol}
          </Typography>
        </Box>
        <Box mb={3} color={palette.text.secondary} textAlign='center'>
          <Typography variant='body2'>
            {`Output is estimated. If the price changes by more than ${allowedSlippage /
              100}% your transaction will revert.`}
          </Typography>
        </Box>
        <Box mt={2}>
          <Button
            style={{ width: '100%' }}
            className={classes.removeButton}
            onClick={onRemove}
          >
            Confirm
          </Button>
        </Box>
      </Box>
    );
  };

  return (
    <CustomModal open={open} onClose={onClose}>
      <Box paddingX={3} paddingY={4}>
        {showConfirm && (
          <TransactionConfirmationModal
            isOpen={showConfirm}
            onDismiss={handleDismissConfirmation}
            attemptingTxn={attemptingTxn}
            txPending={txPending}
            hash={txHash}
            content={() =>
              removeErrorMessage ? (
                <TransactionErrorContent
                  onDismiss={handleDismissConfirmation}
                  message={removeErrorMessage}
                />
              ) : (
                <ConfirmationModalContent
                  title='Removing Liquidity'
                  onDismiss={handleDismissConfirmation}
                  content={modalHeader}
                />
              )
            }
            pendingText=''
            modalContent={
              txPending
                ? 'Submitted transaction to remove liquidity'
                : 'Successfully removed liquidity'
            }
          />
        )}
        <Box display='flex' alignItems='center' justifyContent='space-between'>
          <ArrowLeft
            color={palette.text.secondary}
            style={{ cursor: 'pointer' }}
            onClick={onClose}
          />
          <Typography
            variant='subtitle2'
            style={{ color: palette.text.primary }}
          >
            Remove Liquidity
          </Typography>
          <CloseIcon style={{ cursor: 'pointer' }} onClick={onClose} />
        </Box>
        <Box
          mt={3}
          bgcolor={palette.background.default}
          border='1px solid rgba(105, 108, 128, 0.12)'
          borderRadius='10px'
          padding='16px'
        >
          <Box
            display='flex'
            alignItems='center'
            justifyContent='space-between'
          >
            <Typography variant='body2'>
              {currency0.symbol} / {currency1.symbol} LP
            </Typography>
            <Typography variant='body2'>
              Balance: {formatTokenAmount(userPoolBalance)}
            </Typography>
          </Box>
          <Box mt={2}>
            <NumericalInput
              placeholder='0'
              value={formattedAmounts[Field.LIQUIDITY]}
              fontSize={28}
              onUserInput={(value) => {
                onLiquidityInput(value);
              }}
            />
          </Box>
          <Box display='flex' alignItems='center'>
            <Box flex={1} mr={2} mt={0.5}>
              <ColoredSlider
                min={1}
                max={100}
                step={1}
                value={innerLiquidityPercentage}
                onChange={(evt: any, value) =>
                  setInnerLiquidityPercentage(value as number)
                }
              />
            </Box>
            <Typography variant='body2'>
              {formattedAmounts[Field.LIQUIDITY_PERCENT]}%
            </Typography>
          </Box>
        </Box>
        <Box display='flex' my={3} justifyContent='center'>
          <ArrowDown color={palette.text.secondary} />
        </Box>
        <Box
          padding='16px'
          bgcolor={palette.secondary.light}
          borderRadius='10px'
        >
          <Box
            display='flex'
            justifyContent='space-between'
            alignItems='center'
          >
            <Typography variant='body1'>Pooled {currency0.symbol}</Typography>
            <Box display='flex' alignItems='center'>
              <Typography variant='body1' style={{ marginRight: 6 }}>
                {formatTokenAmount(token0Deposited)}
              </Typography>
              <CurrencyLogo currency={currency0} />
            </Box>
          </Box>
          <Box
            mt={1}
            display='flex'
            justifyContent='space-between'
            alignItems='center'
          >
            <Typography
              variant='body1'
              style={{ color: 'rgba(68, 138, 255, 0.5)' }}
            >
              - Withdraw {currency0.symbol}
            </Typography>
            <Typography
              variant='body1'
              style={{ color: 'rgba(68, 138, 255, 0.5)' }}
            >
              {formattedAmounts[Field.CURRENCY_A]}
            </Typography>
          </Box>
          <Box
            mt={1}
            display='flex'
            justifyContent='space-between'
            alignItems='center'
          >
            <Typography variant='body1'>Pooled {currency1.symbol}</Typography>
            <Box display='flex' alignItems='center'>
              <Typography variant='body1' style={{ marginRight: 6 }}>
                {formatTokenAmount(token1Deposited)}
              </Typography>
              <CurrencyLogo currency={currency1} />
            </Box>
          </Box>
          <Box
            mt={1}
            display='flex'
            justifyContent='space-between'
            alignItems='center'
          >
            <Typography
              variant='body1'
              style={{ color: 'rgba(68, 138, 255, 0.5)' }}
            >
              - Withdraw {currency1.symbol}
            </Typography>
            <Typography
              variant='body1'
              style={{ color: 'rgba(68, 138, 255, 0.5)' }}
            >
              {formattedAmounts[Field.CURRENCY_B]}
            </Typography>
          </Box>
          <Box
            mt={1}
            display='flex'
            justifyContent='space-between'
            alignItems='center'
          >
            <Typography variant='body1'>Your Pool Share</Typography>
            <Typography variant='body1'>
              {poolTokenPercentage
                ? poolTokenPercentage.toSignificant() + '%'
                : '-'}
            </Typography>
          </Box>
        </Box>
        {pair && (
          <Box
            display='flex'
            mt={2}
            px={2}
            alignItems='center'
            justifyContent='space-between'
          >
            <Typography variant='body2'>
              1 {currency0.symbol} ={' '}
              {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'}{' '}
              {currency1.symbol}
            </Typography>
            <Typography variant='body2'>
              1 {currency1.symbol} ={' '}
              {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'}{' '}
              {currency0.symbol}
            </Typography>
          </Box>
        )}
        <Box
          mt={2}
          display='flex'
          alignItems='center'
          justifyContent='space-between'
        >
          <Button
            className={classes.removeButton}
            onClick={onAttemptToApprove}
            disabled={approving || approval !== ApprovalState.NOT_APPROVED}
          >
            {approving
              ? 'Approving...'
              : approval === ApprovalState.APPROVED
              ? 'Approved'
              : 'Approve'}
          </Button>
          <Button
            className={classes.removeButton}
            onClick={() => {
              setShowConfirm(true);
            }}
            disabled={Boolean(error) || approval !== ApprovalState.APPROVED}
          >
            {error || 'Remove'}
          </Button>
        </Box>
        <Box mt={2}>
          <Typography variant='body1' style={{ color: palette.error.main }}>
            {errorMsg}
          </Typography>
        </Box>
      </Box>
    </CustomModal>
  );
}
Example #7
Source File: SettingsModal.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
SettingsModal: React.FC<SettingsModalProps> = ({ open, onClose }) => {
  const classes = useStyles();
  const { palette } = useTheme();
  const [
    userSlippageTolerance,
    setUserslippageTolerance,
  ] = useUserSlippageTolerance();
  const [ttl, setTtl] = useUserTransactionTTL();
  const { onChangeRecipient } = useSwapActionHandlers();
  const [expertMode, toggleExpertMode] = useExpertModeManager();
  const [slippageInput, setSlippageInput] = useState('');
  const [deadlineInput, setDeadlineInput] = useState('');
  const [expertConfirm, setExpertConfirm] = useState(false);
  const [expertConfirmText, setExpertConfirmText] = useState('');

  const slippageInputIsValid =
    slippageInput === '' ||
    (userSlippageTolerance / 100).toFixed(2) ===
      Number.parseFloat(slippageInput).toFixed(2);
  const deadlineInputIsValid =
    deadlineInput === '' || (ttl / 60).toString() === deadlineInput;

  const slippageError = useMemo(() => {
    if (slippageInput !== '' && !slippageInputIsValid) {
      return SlippageError.InvalidInput;
    } else if (slippageInputIsValid && userSlippageTolerance < 50) {
      return SlippageError.RiskyLow;
    } else if (slippageInputIsValid && userSlippageTolerance > 500) {
      return SlippageError.RiskyHigh;
    } else {
      return undefined;
    }
  }, [slippageInput, userSlippageTolerance, slippageInputIsValid]);

  const slippageAlert =
    !!slippageInput &&
    (slippageError === SlippageError.RiskyLow ||
      slippageError === SlippageError.RiskyHigh);

  const deadlineError = useMemo(() => {
    if (deadlineInput !== '' && !deadlineInputIsValid) {
      return DeadlineError.InvalidInput;
    } else {
      return undefined;
    }
  }, [deadlineInput, deadlineInputIsValid]);

  const parseCustomSlippage = (value: string) => {
    setSlippageInput(value);

    try {
      const valueAsIntFromRoundedFloat = Number.parseInt(
        (Number.parseFloat(value) * 100).toString(),
      );
      if (
        !Number.isNaN(valueAsIntFromRoundedFloat) &&
        valueAsIntFromRoundedFloat < 5000
      ) {
        setUserslippageTolerance(valueAsIntFromRoundedFloat);
      }
    } catch {}
  };

  const parseCustomDeadline = (value: string) => {
    setDeadlineInput(value);

    try {
      const valueAsInt: number = Number.parseInt(value) * 60;
      if (!Number.isNaN(valueAsInt) && valueAsInt > 0) {
        setTtl(valueAsInt);
      }
    } catch {}
  };

  return (
    <CustomModal open={open} onClose={onClose}>
      <CustomModal open={expertConfirm} onClose={() => setExpertConfirm(false)}>
        <Box paddingX={3} paddingY={4}>
          <Box
            mb={3}
            display='flex'
            justifyContent='space-between'
            alignItems='center'
          >
            <Typography variant='h5'>Are you sure?</Typography>
            <CloseIcon
              style={{ cursor: 'pointer' }}
              onClick={() => setExpertConfirm(false)}
            />
          </Box>
          <Divider />
          <Box mt={2.5} mb={1.5}>
            <Typography variant='body1'>
              Expert mode turns off the confirm transaction prompt and allows
              high slippage trades that often result in bad rates and lost
              funds.
            </Typography>
            <Typography
              variant='body1'
              style={{ fontWeight: 'bold', marginTop: 24 }}
            >
              ONLY USE THIS MODE IF YOU KNOW WHAT YOU ARE DOING.
            </Typography>
            <Typography
              variant='body1'
              style={{ fontWeight: 'bold', marginTop: 24 }}
            >
              Please type the word &quot;confirm&quot; to enable expert mode.
            </Typography>
          </Box>
          <Box
            height={40}
            borderRadius={10}
            mb={2.5}
            px={2}
            display='flex'
            alignItems='center'
            bgcolor={palette.background.default}
            border={`1px solid ${palette.secondary.light}`}
          >
            <input
              style={{ textAlign: 'left' }}
              className={classes.settingsInput}
              value={expertConfirmText}
              onChange={(e: any) => setExpertConfirmText(e.target.value)}
            />
          </Box>
          <Box
            style={{
              cursor: 'pointer',
              opacity: expertConfirmText === 'confirm' ? 1 : 0.6,
            }}
            bgcolor='rgb(255, 104, 113)'
            height={42}
            borderRadius={10}
            display='flex'
            alignItems='center'
            justifyContent='center'
            onClick={() => {
              if (expertConfirmText === 'confirm') {
                toggleExpertMode();
                setExpertConfirm(false);
              }
            }}
          >
            <Typography variant='h6'>Turn on Expert Mode</Typography>
          </Box>
        </Box>
      </CustomModal>
      <Box paddingX={3} paddingY={4}>
        <Box
          mb={3}
          display='flex'
          justifyContent='space-between'
          alignItems='center'
        >
          <Typography variant='h5'>Settings</Typography>
          <CloseIcon onClick={onClose} />
        </Box>
        <Divider />
        <Box my={2.5} display='flex' alignItems='center'>
          <Typography variant='body1' style={{ marginRight: 6 }}>
            Slippage Tolerance
          </Typography>
          <QuestionHelper
            size={20}
            text='Your transaction will revert if the price changes unfavorably by more than this percentage.'
          />
        </Box>
        <Box mb={2.5}>
          <Box display='flex' alignItems='center'>
            <Box
              className={cx(
                classes.slippageButton,
                userSlippageTolerance === 10 && classes.activeSlippageButton,
              )}
              onClick={() => {
                setSlippageInput('');
                setUserslippageTolerance(10);
              }}
            >
              <Typography variant='body2'>0.1%</Typography>
            </Box>
            <Box
              className={cx(
                classes.slippageButton,
                userSlippageTolerance === 50 && classes.activeSlippageButton,
              )}
              onClick={() => {
                setSlippageInput('');
                setUserslippageTolerance(50);
              }}
            >
              <Typography variant='body2'>0.5%</Typography>
            </Box>
            <Box
              className={cx(
                classes.slippageButton,
                userSlippageTolerance === 100 && classes.activeSlippageButton,
              )}
              onClick={() => {
                setSlippageInput('');
                setUserslippageTolerance(100);
              }}
            >
              <Typography variant='body2'>1%</Typography>
            </Box>
            <Box
              flex={1}
              height={40}
              borderRadius={10}
              px={2}
              display='flex'
              alignItems='center'
              bgcolor={palette.background.default}
              border={`1px solid
                ${
                  slippageAlert ? palette.primary.main : palette.secondary.light
                }
              `}
            >
              {slippageAlert && <AlertTriangle color='#ffa000' size={16} />}
              <NumericalInput
                placeholder={(userSlippageTolerance / 100).toFixed(2)}
                value={slippageInput}
                fontSize={14}
                fontWeight={500}
                align='right'
                color='rgba(212, 229, 255, 0.8)'
                onBlur={() => {
                  parseCustomSlippage((userSlippageTolerance / 100).toFixed(2));
                }}
                onUserInput={(value) => parseCustomSlippage(value)}
              />
              <Typography variant='body2'>%</Typography>
            </Box>
          </Box>
          {slippageError && (
            <Typography
              variant='body2'
              style={{ color: '#ffa000', marginTop: 12 }}
            >
              {slippageError === SlippageError.InvalidInput
                ? 'Enter a valid slippage percentage'
                : slippageError === SlippageError.RiskyLow
                ? 'Your transaction may fail'
                : 'Your transaction may be frontrun'}
            </Typography>
          )}
        </Box>
        <Divider />
        <Box my={2.5} display='flex' alignItems='center'>
          <Typography variant='body1' style={{ marginRight: 6 }}>
            Transaction Deadline
          </Typography>
          <QuestionHelper
            size={20}
            text='Your transaction will revert if it is pending for more than this long.'
          />
        </Box>
        <Box mb={2.5} display='flex' alignItems='center'>
          <Box
            height={40}
            borderRadius={10}
            px={2}
            display='flex'
            alignItems='center'
            bgcolor={palette.background.default}
            border={`1px solid ${palette.secondary.light}`}
            maxWidth={168}
          >
            <NumericalInput
              placeholder={(ttl / 60).toString()}
              value={deadlineInput}
              fontSize={14}
              fontWeight={500}
              color='rgba(212, 229, 255, 0.8)'
              onBlur={() => {
                parseCustomDeadline((ttl / 60).toString());
              }}
              onUserInput={(value) => parseCustomDeadline(value)}
            />
          </Box>
          <Typography variant='body2' style={{ marginLeft: 8 }}>
            minutes
          </Typography>
        </Box>
        {deadlineError && (
          <Typography
            variant='body2'
            style={{ color: '#ffa000', marginTop: 12 }}
          >
            Enter a valid deadline
          </Typography>
        )}
        <Divider />
        <Box
          my={2.5}
          display='flex'
          justifyContent='space-between'
          alignItems='center'
        >
          <Box display='flex' alignItems='center'>
            <Typography variant='body1' style={{ marginRight: 6 }}>
              Expert Mode
            </Typography>
            <QuestionHelper
              size={20}
              text='Bypasses confirmation modals and allows high slippage trades. Use at your own risk.'
            />
          </Box>
          <ToggleSwitch
            toggled={expertMode}
            onToggle={() => {
              if (expertMode) {
                toggleExpertMode();
                onChangeRecipient(null);
              } else {
                setExpertConfirm(true);
              }
            }}
          />
        </Box>
        <Divider />
        <Box
          mt={2.5}
          display='flex'
          justifyContent='space-between'
          alignItems='center'
        >
          <Typography variant='body1'>Language</Typography>
          <Box display='flex' alignItems='center'>
            <Typography variant='body1'>English (default)</Typography>
            <KeyboardArrowDown />
          </Box>
        </Box>
      </Box>
    </CustomModal>
  );
}
Example #8
Source File: StakeQuickModal.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
StakeQuickModal: React.FC<StakeQuickModalProps> = ({ open, onClose }) => {
  const classes = useStyles();
  const { palette } = useTheme();
  const [attempting, setAttempting] = useState(false);
  const { account } = useActiveWeb3React();
  const addTransaction = useTransactionAdder();
  const finalizedTransaction = useTransactionFinalizer();
  const quickBalance = useCurrencyBalance(
    account ?? undefined,
    returnTokenFromKey('QUICK'),
  );
  const userLiquidityUnstaked = useTokenBalance(
    account ?? undefined,
    returnTokenFromKey('QUICK'),
  );

  const [typedValue, setTypedValue] = useState('');
  const [stakePercent, setStakePercent] = useState(0);
  const [approving, setApproving] = useState(false);
  const { parsedAmount, error } = useDerivedLairInfo(
    typedValue,
    returnTokenFromKey('QUICK'),
    userLiquidityUnstaked,
  );

  const lairContract = useLairContract();
  const [approval, approveCallback] = useApproveCallback(
    parsedAmount,
    GlobalConst.addresses.LAIR_ADDRESS,
  );

  const onAttemptToApprove = async () => {
    if (!lairContract) throw new Error('missing dependencies');
    const liquidityAmount = parsedAmount;
    if (!liquidityAmount) throw new Error('missing liquidity amount');
    return approveCallback();
  };

  const onStake = async () => {
    setAttempting(true);
    if (lairContract && parsedAmount) {
      if (approval === ApprovalState.APPROVED) {
        try {
          const response: TransactionResponse = await lairContract.enter(
            `0x${parsedAmount.raw.toString(16)}`,
            {
              gasLimit: 350000,
            },
          );
          addTransaction(response, {
            summary: `Stake QUICK`,
          });
          const receipt = await response.wait();
          finalizedTransaction(receipt, {
            summary: `Deposit dQUICK`,
          });
          setAttempting(false);
          setStakePercent(0);
          setTypedValue('');
        } catch (err) {
          setAttempting(false);
        }
      } else {
        setAttempting(false);
        throw new Error(
          'Attempting to stake without approval or a signature. Please contact support.',
        );
      }
    }
  };

  return (
    <CustomModal open={open} onClose={onClose}>
      <Box paddingX={3} paddingY={4}>
        <Box display='flex' alignItems='center' justifyContent='space-between'>
          <Typography variant='h5'>Stake QUICK</Typography>
          <CloseIcon style={{ cursor: 'pointer' }} onClick={onClose} />
        </Box>
        <Box
          mt={3}
          bgcolor={palette.background.default}
          border='1px solid rgba(105, 108, 128, 0.12)'
          borderRadius='10px'
          padding='16px'
        >
          <Box
            display='flex'
            alignItems='center'
            justifyContent='space-between'
          >
            <Typography variant='body2'>QUICK</Typography>
            <Typography variant='body2'>
              Balance: {formatTokenAmount(quickBalance)}
            </Typography>
          </Box>
          <Box mt={2} display='flex' alignItems='center'>
            <NumericalInput
              placeholder='0'
              value={typedValue}
              fontSize={28}
              onUserInput={(value) => {
                const totalBalance = quickBalance
                  ? Number(quickBalance.toExact())
                  : 0;
                setTypedValue(value);
                setStakePercent(
                  totalBalance > 0 ? (Number(value) / totalBalance) * 100 : 0,
                );
              }}
            />
            <Typography
              variant='caption'
              style={{
                color: palette.primary.main,
                fontWeight: 'bold',
                cursor: 'pointer',
              }}
              onClick={() => {
                setTypedValue(quickBalance ? quickBalance.toExact() : '0');
                setStakePercent(100);
              }}
            >
              MAX
            </Typography>
          </Box>
          <Box display='flex' alignItems='center'>
            <Box flex={1} mr={2} mt={0.5}>
              <ColoredSlider
                min={1}
                max={100}
                step={1}
                value={stakePercent}
                onChange={(evt: any, value) => {
                  setStakePercent(value as number);
                  setTypedValue(
                    quickBalance
                      ? stakePercent < 100
                        ? (
                            (Number(quickBalance.toExact()) * stakePercent) /
                            100
                          ).toString()
                        : quickBalance.toExact()
                      : '0',
                  );
                }}
              />
            </Box>
            <Typography variant='body2'>
              {Math.min(stakePercent, 100).toLocaleString()}%
            </Typography>
          </Box>
        </Box>
        <Box
          mt={3}
          display='flex'
          justifyContent='space-between'
          alignItems='center'
        >
          <Button
            className={classes.stakeButton}
            disabled={approving || approval !== ApprovalState.NOT_APPROVED}
            onClick={async () => {
              setApproving(true);
              try {
                await onAttemptToApprove();
                setApproving(false);
              } catch (e) {
                setApproving(false);
              }
            }}
          >
            {approving ? 'Approving...' : 'Approve'}
          </Button>
          <Button
            className={classes.stakeButton}
            disabled={
              !!error || attempting || approval !== ApprovalState.APPROVED
            }
            onClick={onStake}
          >
            {attempting ? 'Staking...' : 'Stake'}
          </Button>
        </Box>
      </Box>
    </CustomModal>
  );
}
Example #9
Source File: StakeSyrupModal.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
StakeSyrupModal: React.FC<StakeSyrupModalProps> = ({
  open,
  onClose,
  syrup,
}) => {
  const classes = useStyles();
  const { palette } = useTheme();
  const [attempting, setAttempting] = useState(false);
  const [hash, setHash] = useState('');
  const { account, chainId, library } = useActiveWeb3React();
  const addTransaction = useTransactionAdder();
  const finalizedTransaction = useTransactionFinalizer();
  const userLiquidityUnstaked = useTokenBalance(
    account ?? undefined,
    syrup.stakedAmount?.token,
  );
  const [typedValue, setTypedValue] = useState('');
  const [stakePercent, setStakePercent] = useState(0);
  const [approving, setApproving] = useState(false);
  const maxAmountInput = maxAmountSpend(userLiquidityUnstaked);
  const { parsedAmount, error } = useDerivedSyrupInfo(
    typedValue,
    syrup.stakedAmount?.token,
    userLiquidityUnstaked,
  );

  const parsedAmountWrapped = wrappedCurrencyAmount(parsedAmount, chainId);

  let hypotheticalRewardRate = syrup.rewardRate
    ? new TokenAmount(syrup.rewardRate.token, '0')
    : undefined;
  if (parsedAmountWrapped && parsedAmountWrapped.greaterThan('0')) {
    hypotheticalRewardRate =
      syrup.stakedAmount && syrup.totalStakedAmount
        ? syrup.getHypotheticalRewardRate(
            syrup.stakedAmount.add(parsedAmountWrapped),
            syrup.totalStakedAmount.add(parsedAmountWrapped),
          )
        : undefined;
  }

  const deadline = useTransactionDeadline();

  const [approval, approveCallback] = useApproveCallback(
    parsedAmount,
    syrup.stakingRewardAddress,
  );
  const [signatureData, setSignatureData] = useState<{
    v: number;
    r: string;
    s: string;
    deadline: number;
  } | null>(null);

  const stakingContract = useStakingContract(syrup.stakingRewardAddress);

  const onAttemptToApprove = async () => {
    if (!library || !deadline) throw new Error('missing dependencies');
    const liquidityAmount = parsedAmount;
    if (!liquidityAmount) throw new Error('missing liquidity amount');
    return approveCallback();
  };

  const onStake = async () => {
    setAttempting(true);
    if (stakingContract && parsedAmount && deadline) {
      if (approval === ApprovalState.APPROVED) {
        stakingContract
          .stake(`0x${parsedAmount.raw.toString(16)}`, { gasLimit: 350000 })
          .then(async (response: TransactionResponse) => {
            addTransaction(response, {
              summary: `Deposit ${syrup.stakingToken.symbol}`,
            });
            try {
              const receipt = await response.wait();
              finalizedTransaction(receipt, {
                summary: `Deposit ${syrup.stakingToken.symbol}`,
              });
              setAttempting(false);
              setStakePercent(0);
              setTypedValue('');
            } catch (e) {
              setAttempting(false);
              setStakePercent(0);
              setTypedValue('');
            }
          })
          .catch((error: any) => {
            setAttempting(false);
            console.log(error);
          });
      } else if (signatureData) {
        stakingContract
          .stakeWithPermit(
            `0x${parsedAmount.raw.toString(16)}`,
            signatureData.deadline,
            signatureData.v,
            signatureData.r,
            signatureData.s,
            { gasLimit: 350000 },
          )
          .then((response: TransactionResponse) => {
            addTransaction(response, {
              summary: `Deposit liquidity`,
            });
            setHash(response.hash);
          })
          .catch((error: any) => {
            setAttempting(false);
            console.log(error);
          });
      } else {
        setAttempting(false);
        throw new Error(
          'Attempting to stake without approval or a signature. Please contact support.',
        );
      }
    }
  };

  return (
    <CustomModal open={open} onClose={onClose}>
      <Box paddingX={3} paddingY={4}>
        <Box display='flex' alignItems='center' justifyContent='space-between'>
          <Typography variant='h5'>
            Stake {syrup.stakingToken.symbol}
          </Typography>
          <CloseIcon style={{ cursor: 'pointer' }} onClick={onClose} />
        </Box>
        <Box
          mt={3}
          bgcolor={palette.background.default}
          border='1px solid rgba(105, 108, 128, 0.12)'
          borderRadius='10px'
          padding='16px'
        >
          <Box
            display='flex'
            alignItems='center'
            justifyContent='space-between'
          >
            <Typography variant='body2'>{syrup.stakingToken.symbol}</Typography>
            <Typography variant='body2'>
              Balance: {formatTokenAmount(maxAmountInput)}
            </Typography>
          </Box>
          <Box mt={2} display='flex' alignItems='center'>
            <NumericalInput
              placeholder='0'
              value={typedValue}
              fontSize={28}
              onUserInput={(value) => {
                setSignatureData(null);
                const totalBalance = getExactTokenAmount(maxAmountInput);
                const exactTypedValue = getValueTokenDecimals(
                  value,
                  syrup.stakedAmount?.token,
                );
                // this is to avoid input amount more than balance
                if (Number(exactTypedValue) <= totalBalance) {
                  setTypedValue(exactTypedValue);
                  setStakePercent(
                    totalBalance > 0
                      ? (Number(exactTypedValue) / totalBalance) * 100
                      : 0,
                  );
                }
              }}
            />
            <Typography
              variant='caption'
              style={{
                color: palette.primary.main,
                fontWeight: 'bold',
                cursor: 'pointer',
              }}
              onClick={() => {
                setTypedValue(maxAmountInput ? maxAmountInput.toExact() : '0');
                setStakePercent(100);
              }}
            >
              MAX
            </Typography>
          </Box>
          <Box display='flex' alignItems='center'>
            <Box flex={1} mr={2} mt={0.5}>
              <ColoredSlider
                min={1}
                max={100}
                step={1}
                value={stakePercent}
                onChange={(_, value) => {
                  const percent = value as number;
                  setStakePercent(percent);
                  setTypedValue(getPartialTokenAmount(percent, maxAmountInput));
                }}
              />
            </Box>
            <Typography variant='body2'>
              {Math.min(stakePercent, 100).toLocaleString()}%
            </Typography>
          </Box>
        </Box>
        <Box
          mt={2}
          display='flex'
          alignItems='center'
          justifyContent='space-between'
        >
          <Typography variant='body1'>Daily Rewards</Typography>
          <Typography variant='body1'>
            {hypotheticalRewardRate
              ? formatNumber(
                  Number(hypotheticalRewardRate.toExact()) * getSecondsOneDay(),
                )
              : '-'}{' '}
            {syrup.token.symbol} / day
          </Typography>
        </Box>
        <Box
          mt={3}
          display='flex'
          justifyContent='space-between'
          alignItems='center'
        >
          <Button
            className={classes.stakeButton}
            disabled={approving || approval !== ApprovalState.NOT_APPROVED}
            onClick={async () => {
              setApproving(true);
              try {
                await onAttemptToApprove();
                setApproving(false);
              } catch (e) {
                setApproving(false);
              }
            }}
          >
            {approving ? 'Approving...' : 'Approve'}
          </Button>
          <Button
            className={classes.stakeButton}
            disabled={
              !!error || attempting || approval !== ApprovalState.APPROVED
            }
            onClick={onStake}
          >
            {attempting ? 'Staking...' : 'Stake'}
          </Button>
        </Box>
      </Box>
    </CustomModal>
  );
}
Example #10
Source File: UnstakeQuickModal.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
UnstakeQuickModal: React.FC<UnstakeQuickModalProps> = ({
  open,
  onClose,
}) => {
  const classes = useStyles();
  const { palette } = useTheme();
  const [attempting, setAttempting] = useState(false);
  const addTransaction = useTransactionAdder();
  const lairInfo = useLairInfo();
  const dQuickBalance = lairInfo.dQUICKBalance;
  const [typedValue, setTypedValue] = useState('');
  const [stakePercent, setStakePercent] = useState(0);

  const lairContract = useLairContract();
  const error =
    Number(typedValue) > Number(dQuickBalance.toExact()) || !typedValue;

  const onWithdraw = () => {
    if (lairContract && lairInfo?.dQUICKBalance) {
      setAttempting(true);
      const balance = web3.utils.toWei(typedValue, 'ether');
      lairContract
        .leave(balance.toString(), { gasLimit: 300000 })
        .then(async (response: TransactionResponse) => {
          addTransaction(response, {
            summary: `Unstake dQUICK`,
          });
          await response.wait();
          setAttempting(false);
        })
        .catch((error: any) => {
          setAttempting(false);
          console.log(error);
        });
    }
  };

  return (
    <CustomModal open={open} onClose={onClose}>
      <Box paddingX={3} paddingY={4}>
        <Box display='flex' alignItems='center' justifyContent='space-between'>
          <Typography variant='h5'>Unstake dQUICK</Typography>
          <CloseIcon style={{ cursor: 'pointer' }} onClick={onClose} />
        </Box>
        <Box
          mt={3}
          bgcolor={palette.background.default}
          border='1px solid rgba(105, 108, 128, 0.12)'
          borderRadius='10px'
          padding='16px'
        >
          <Box
            display='flex'
            alignItems='center'
            justifyContent='space-between'
          >
            <Typography variant='body2'>dQUICK</Typography>
            <Typography variant='body2'>
              Balance: {formatTokenAmount(dQuickBalance)}
            </Typography>
          </Box>
          <Box mt={2} display='flex' alignItems='center'>
            <NumericalInput
              placeholder='0'
              value={typedValue}
              fontSize={28}
              onUserInput={(value) => {
                const totalBalance = dQuickBalance
                  ? Number(dQuickBalance.toExact())
                  : 0;
                setTypedValue(value);
                setStakePercent(
                  totalBalance > 0 ? (Number(value) / totalBalance) * 100 : 0,
                );
              }}
            />
            <Typography
              variant='caption'
              style={{
                color: palette.primary.main,
                fontWeight: 'bold',
                cursor: 'pointer',
              }}
              onClick={() => {
                setTypedValue(dQuickBalance ? dQuickBalance.toExact() : '0');
                setStakePercent(100);
              }}
            >
              MAX
            </Typography>
          </Box>
          <Box display='flex' alignItems='center'>
            <Box flex={1} mr={2} mt={0.5}>
              <ColoredSlider
                min={1}
                max={100}
                step={1}
                value={stakePercent}
                onChange={(evt: any, value) => {
                  setStakePercent(value as number);
                  setTypedValue(
                    dQuickBalance
                      ? stakePercent < 100
                        ? (
                            (Number(dQuickBalance.toExact()) * stakePercent) /
                            100
                          ).toString()
                        : dQuickBalance.toExact()
                      : '0',
                  );
                }}
              />
            </Box>
            <Typography variant='body2'>
              {Math.min(stakePercent, 100).toLocaleString()}%
            </Typography>
          </Box>
        </Box>
        <Box mt={3}>
          <Button
            className={classes.stakeButton}
            disabled={!!error || attempting}
            onClick={onWithdraw}
          >
            {attempting ? 'Unstaking...' : 'Unstake'}
          </Button>
        </Box>
      </Box>
    </CustomModal>
  );
}
Example #11
Source File: WalletModal.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
WalletModal: React.FC<WalletModalProps> = ({
  pendingTransactions,
  confirmedTransactions,
  ENSName,
}) => {
  const classes = useStyles();
  // important that these are destructed from the account-specific web3-react context
  const {
    active,
    account,
    connector,
    activate,
    error,
    deactivate,
  } = useWeb3React();

  const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT);

  const [pendingWallet, setPendingWallet] = useState<
    AbstractConnector | undefined
  >();

  const [pendingError, setPendingError] = useState<boolean>();

  const walletModalOpen = useModalOpen(ApplicationModal.WALLET);
  const toggleWalletModal = useWalletModalToggle();

  const previousAccount = usePrevious(account);

  // close on connection, when logged out before
  useEffect(() => {
    if (account && !previousAccount && walletModalOpen) {
      toggleWalletModal();
    }
    if (!walletModalOpen && error) {
      deactivate();
    }
  }, [
    account,
    previousAccount,
    toggleWalletModal,
    walletModalOpen,
    deactivate,
    error,
  ]);

  // always reset to account view
  useEffect(() => {
    if (walletModalOpen) {
      setPendingError(false);
      setWalletView(WALLET_VIEWS.ACCOUNT);
    }
  }, [walletModalOpen]);

  // close modal when a connection is successful
  const activePrevious = usePrevious(active);
  const connectorPrevious = usePrevious(connector);
  useEffect(() => {
    if (
      walletModalOpen &&
      ((active && !activePrevious) ||
        (connector && connector !== connectorPrevious && !error))
    ) {
      setWalletView(WALLET_VIEWS.ACCOUNT);
    }
  }, [
    setWalletView,
    active,
    error,
    connector,
    walletModalOpen,
    activePrevious,
    connectorPrevious,
  ]);

  const tryActivation = async (connector: AbstractConnector | undefined) => {
    let name = '';
    Object.keys(SUPPORTED_WALLETS).map((key) => {
      if (connector === SUPPORTED_WALLETS[key].connector) {
        return (name = SUPPORTED_WALLETS[key].name);
      }
      return true;
    });
    // log selected wallet
    ReactGA.event({
      category: 'Wallet',
      action: 'Change Wallet',
      label: name,
    });
    setPendingWallet(connector); // set wallet for pending view
    setWalletView(WALLET_VIEWS.PENDING);

    // if the connector is walletconnect and the user has already tried to connect, manually reset the connector
    if (
      connector instanceof WalletConnectConnector &&
      connector.walletConnectProvider?.wc?.uri
    ) {
      connector.walletConnectProvider = undefined;
    }

    connector &&
      activate(connector, undefined, true).catch((error) => {
        if (error instanceof UnsupportedChainIdError) {
          activate(connector); // a little janky...can't use setError because the connector isn't set
        } else {
          setPendingError(true);
        }
      });
  };

  // close wallet modal if fortmatic modal is active
  useEffect(() => {
    fortmatic.on(OVERLAY_READY, () => {
      toggleWalletModal();
    });
  }, [toggleWalletModal]);

  // get wallets user can switch too, depending on device/browser
  function getOptions() {
    const { ethereum, web3 } = window as any;
    const isMetamask = ethereum && !ethereum.isBitKeep && ethereum.isMetaMask;
    const isBlockWallet = ethereum && ethereum.isBlockWallet;
    const isBitKeep = ethereum && ethereum.isBitKeep;
    return Object.keys(SUPPORTED_WALLETS).map((key) => {
      const option = SUPPORTED_WALLETS[key];
      //disable safe app by in the list
      if (option.connector === safeApp) {
        return null;
      }
      // check for mobile options
      if (isMobile) {
        //disable portis on mobile for now
        if (option.connector === portis) {
          return null;
        }

        if (!web3 && !ethereum && option.mobile) {
          return (
            <Option
              onClick={() => {
                option.connector !== connector &&
                  !option.href &&
                  tryActivation(option.connector);
              }}
              id={`connect-${key}`}
              key={key}
              active={
                option.connector === connector &&
                (connector !== injected ||
                  isBlockWallet ===
                    (option.name === GlobalConst.walletName.BLOCKWALLET) ||
                  isBitKeep ===
                    (option.name === GlobalConst.walletName.BITKEEP) ||
                  isMetamask ===
                    (option.name === GlobalConst.walletName.METAMASK))
              }
              color={option.color}
              link={option.href}
              header={option.name}
              subheader={null}
              icon={option.iconName}
            />
          );
        }
        return null;
      }

      // overwrite injected when needed
      if (option.connector === injected) {
        // don't show injected if there's no injected provider
        if (!(web3 || ethereum)) {
          if (option.name === GlobalConst.walletName.METAMASK) {
            return (
              <Option
                id={`connect-${key}`}
                key={key}
                color={'#E8831D'}
                header={'Install Metamask'}
                subheader={null}
                link={'https://metamask.io/'}
                icon={MetamaskIcon}
              />
            );
          } else {
            return null; //dont want to return install twice
          }
        }
        // don't return metamask if injected provider isn't metamask
        else if (
          option.name === GlobalConst.walletName.METAMASK &&
          !isMetamask
        ) {
          return null;
        } else if (
          option.name === GlobalConst.walletName.BITKEEP &&
          !isBitKeep
        ) {
          return null;
        } else if (
          option.name === GlobalConst.walletName.BLOCKWALLET &&
          !isBlockWallet
        ) {
          return null;
        }
        // likewise for generic
        else if (
          option.name === GlobalConst.walletName.INJECTED &&
          (isMetamask || isBitKeep || isBlockWallet)
        ) {
          return null;
        }
      }

      // return rest of options
      return (
        !isMobile &&
        !option.mobileOnly && (
          <Option
            id={`connect-${key}`}
            onClick={() => {
              option.connector === connector
                ? setWalletView(WALLET_VIEWS.ACCOUNT)
                : !option.href && tryActivation(option.connector);
            }}
            key={key}
            active={
              option.connector === connector &&
              (connector !== injected ||
                isBlockWallet ===
                  (option.name === GlobalConst.walletName.BLOCKWALLET) ||
                isBitKeep ===
                  (option.name === GlobalConst.walletName.BITKEEP) ||
                isMetamask ===
                  (option.name === GlobalConst.walletName.METAMASK))
            }
            color={option.color}
            link={option.href}
            header={option.name}
            subheader={null} //use option.descriptio to bring back multi-line
            icon={option.iconName}
          />
        )
      );
    });
  }

  function getModalContent() {
    if (error) {
      return (
        <Box position='relative'>
          <Box position='absolute' top='16px' right='16px' display='flex'>
            <Close style={{ cursor: 'pointer' }} onClick={toggleWalletModal} />
          </Box>
          <Box mt={2} textAlign='center'>
            <Typography variant='subtitle2'>
              {error instanceof UnsupportedChainIdError
                ? 'Wrong Network'
                : 'Error connecting'}
            </Typography>
          </Box>
          <Box mt={3} mb={2} textAlign='center'>
            <Typography variant='body2'>
              {error instanceof UnsupportedChainIdError
                ? 'Please connect to the appropriate Polygon network.'
                : 'Error connecting. Try refreshing the page.'}
            </Typography>
          </Box>
        </Box>
      );
    }
    if (account && walletView === WALLET_VIEWS.ACCOUNT) {
      return (
        <AccountDetails
          toggleWalletModal={toggleWalletModal}
          pendingTransactions={pendingTransactions}
          confirmedTransactions={confirmedTransactions}
          ENSName={ENSName}
          openOptions={() => setWalletView(WALLET_VIEWS.OPTIONS)}
        />
      );
    }
    return (
      <Box paddingX={3} paddingY={4}>
        <Box display='flex' justifyContent='space-between'>
          <Typography variant='h5'>Connect wallet</Typography>
          <Close style={{ cursor: 'pointer' }} onClick={toggleWalletModal} />
        </Box>
        <Box mt={4}>
          {walletView === WALLET_VIEWS.PENDING ? (
            <PendingView
              connector={pendingWallet}
              error={pendingError}
              setPendingError={setPendingError}
              tryActivation={tryActivation}
            />
          ) : (
            getOptions()
          )}
          {walletView !== WALLET_VIEWS.PENDING && (
            <Box className={classes.blurb}>
              <Typography variant='body2'>New to Matic?</Typography>
              <a
                href='https://docs.matic.network/docs/develop/wallets/getting-started'
                target='_blank'
                rel='noopener noreferrer'
              >
                <Typography variant='body2'>Learn about Wallets ↗</Typography>
              </a>
            </Box>
          )}
        </Box>
      </Box>
    );
  }

  return (
    <CustomModal open={walletModalOpen} onClose={toggleWalletModal}>
      <Box
        maxHeight='80vh'
        display='flex'
        flexDirection='column'
        overflow='auto'
      >
        {getModalContent()}
      </Box>
    </CustomModal>
  );
}