utils#shortenAddress TypeScript Examples

The following examples show how to use utils#shortenAddress. 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: REPMintInfoLine.tsx    From dxvote with GNU Affero General Public License v3.0 6 votes vote down vote up
REPMintInfoLine: React.FC<ActionViewProps> = ({ decodedCall }) => {
  const { parsedData } = useTotalSupply({ decodedCall });
  const { tokenData } = useTokenData();

  const totalSupply = useBigNumberToNumber(tokenData?.totalSupply, 18);

  const { ensName, imageUrl } = useENSAvatar(parsedData?.toAddress, MAINNET_ID);

  const roundedRepAmount = useBigNumberToNumber(parsedData?.amount, 16, 3);
  const roundedRepPercent = roundedRepAmount / totalSupply;

  return (
    <>
      <Segment>
        <StyledMintIcon src={Mint} />
      </Segment>
      <Segment>Mint {roundedRepPercent} %</Segment>
      <Segment>
        <FiArrowRight />
      </Segment>
      <Segment>
        <Avatar defaultSeed={parsedData?.toAddress} src={imageUrl} size={24} />
      </Segment>
      <Segment>{ensName || shortenAddress(parsedData?.toAddress)}</Segment>
    </>
  );
}
Example #2
Source File: REPMintSummary.tsx    From dxvote with GNU Affero General Public License v3.0 6 votes vote down vote up
REPMintSummary: React.FC<ActionViewProps> = ({ decodedCall }) => {
  const { parsedData } = useTotalSupply({ decodedCall });
  const { tokenData } = useTokenData();
  const { ensName, imageUrl } = useENSAvatar(parsedData?.toAddress, MAINNET_ID);

  const roundedRepAmount = useBigNumberToNumber(parsedData?.amount, 18, 3);

  return (
    <>
      <DetailHeader>
        <DetailCell>Receiver</DetailCell>
        <DetailCell>Amount</DetailCell>
      </DetailHeader>

      <DetailRow>
        <DetailCell>
          <Segment>
            <Avatar
              defaultSeed={parsedData?.toAddress}
              src={imageUrl}
              size={24}
            />
          </Segment>
          <Segment>{ensName || shortenAddress(parsedData?.toAddress)}</Segment>
        </DetailCell>
        <DetailCell>
          {roundedRepAmount} {tokenData?.name}
        </DetailCell>
      </DetailRow>
    </>
  );
}
Example #3
Source File: ERC20TransferSummary.tsx    From dxvote with GNU Affero General Public License v3.0 5 votes vote down vote up
ERC20TransferSummary: React.FC<ActionViewProps> = ({ decodedCall }) => {
  const parsedData = useMemo(() => {
    if (!decodedCall) return null;

    return {
      tokenAddress: decodedCall.to,
      amount: BigNumber.from(decodedCall.args._value),
      source: decodedCall.from,
      destination: decodedCall.args._to,
    };
  }, [decodedCall]);

  const { data: tokenInfo } = useERC20Info(parsedData?.tokenAddress);
  const roundedBalance = useBigNumberToNumber(
    parsedData?.amount,
    tokenInfo?.decimals,
    4
  );
  const { ensName, imageUrl } = useENSAvatar(
    parsedData?.destination,
    MAINNET_ID
  );

  return (
    <>
      <DetailHeader>
        <DetailCell>Receiver</DetailCell>
        <DetailCell>Amount</DetailCell>
      </DetailHeader>

      <DetailRow>
        <DetailCell>
          <Segment>
            <Avatar
              defaultSeed={parsedData?.destination}
              src={imageUrl}
              size={24}
            />
          </Segment>
          <Segment>
            {ensName || shortenAddress(parsedData?.destination)}
          </Segment>
        </DetailCell>
        <DetailCell>
          {roundedBalance} {tokenInfo?.symbol}
        </DetailCell>
      </DetailRow>
    </>
  );
}
Example #4
Source File: ERC20TransferInfoLine.tsx    From dxvote with GNU Affero General Public License v3.0 5 votes vote down vote up
ERC20TransferInfoLine: React.FC<ActionViewProps> = ({ decodedCall }) => {
  const parsedData = useMemo(() => {
    if (!decodedCall) return null;

    return {
      tokenAddress: decodedCall.to,
      amount: BigNumber.from(decodedCall.args._value),
      source: decodedCall.from,
      destination: decodedCall.args._to as string,
    };
  }, [decodedCall]);

  const { data: tokenInfo } = useERC20Info(parsedData?.tokenAddress);
  const roundedBalance = useBigNumberToNumber(
    parsedData?.amount,
    tokenInfo?.decimals,
    4
  );
  const { ensName, imageUrl } = useENSAvatar(
    parsedData?.destination,
    MAINNET_ID
  );

  return (
    <>
      <Segment>
        <FiNavigation size={16} />
      </Segment>
      <Segment>
        Transfer {roundedBalance} {tokenInfo?.symbol}
      </Segment>
      <Segment>
        <FiArrowRight />
      </Segment>
      <Segment>
        <Avatar
          defaultSeed={parsedData?.destination}
          src={imageUrl}
          size={24}
        />
      </Segment>
      <Segment>
        {ensName || parsedData?.destination
          ? shortenAddress(parsedData?.destination)
          : ''}
      </Segment>
    </>
  );
}
Example #5
Source File: Wallet.tsx    From frontend-v1 with GNU Affero General Public License v3.0 5 votes vote down vote up
Wallet: FC = () => {
  const { account, isConnected, chainId } = useConnection();
  const [isOpen, setIsOpen] = useState(false);
  const modalRef = useRef(null);
  const { trackEvent } = useMatomo();
  useClickOutsideModal(modalRef, () => setIsOpen(false));

  // Note: this must be before early returns.
  useEffect(() => {
    if (!isConnected && isOpen) setIsOpen(false);
  }, [isConnected, isOpen]);

  const disconnectWallet = () => {
    setIsOpen(false);
    reset();
  };

  // Add Matomo helpers for connect/disconnect
  const initWithMatomo = () => {
    // Matomo track wallet connect
    // TODO: Eventually add address to `name` field
    trackEvent({ category: "wallet", action: "connect", name: "null" });
    init();
  };

  const disconnectWithMatomo = () => {
    // Matomo track wallet disconnect
    // TODO: Eventually add address to `name` field
    trackEvent({ category: "wallet", action: "disconnect", name: "null" });
    disconnectWallet();
  };

  const { data: balance } = useETHBalance(
    { account: account ?? "", chainId: chainId ?? DEFAULT_FROM_CHAIN_ID },
    { skip: !isConnected }
  );

  if (account && !isConnected && !chainId) {
    return (
      <UnsupportedNetwork>
        Unsupported network. Please change networks.
      </UnsupportedNetwork>
    );
  }

  if (!isConnected) {
    return (
      <ConnectButton onClick={initWithMatomo}>Connect Wallet</ConnectButton>
    );
  }

  return (
    <div ref={modalRef}>
      <Wrapper onClick={() => setIsOpen(!isOpen)}>
        <Info>
          <div>
            {formatEther(balance ?? "0")}{" "}
            {CHAINS[chainId ?? 1].nativeCurrency.symbol}
          </div>
          <div>{CHAINS[chainId ?? 1].name}</div>
        </Info>
        <Account>{shortenAddress(account ?? "")}</Account>
      </Wrapper>
      {isOpen && (
        <WalletModal>
          <WalletModalHeader>Connected</WalletModalHeader>
          <WalletModalAccount>{account}</WalletModalAccount>
          <WalletModalChain>{CHAINS[chainId ?? 1].name}</WalletModalChain>
          <WalletModalDisconnect onClick={() => disconnectWithMatomo()}>
            Disconnect
          </WalletModalDisconnect>
        </WalletModal>
      )}
    </div>
  );
}
Example #6
Source File: SwapModalHeader.tsx    From glide-frontend with GNU General Public License v3.0 4 votes vote down vote up
export default function SwapModalHeader({
  trade,
  allowedSlippage,
  recipient,
  showAcceptChanges,
  onAcceptChanges,
}: {
  trade: Trade
  allowedSlippage: number
  recipient: string | null
  showAcceptChanges: boolean
  onAcceptChanges: () => void
}) {
  const { t } = useTranslation()
  const slippageAdjustedAmounts = useMemo(
    () => computeSlippageAdjustedAmounts(trade, allowedSlippage),
    [trade, allowedSlippage],
  )
  const { priceImpactWithoutFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade])
  const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)

  return (
    <AutoColumn gap="md">
      <RowBetween align="flex-end">
        <RowFixed gap="0px">
          <CurrencyLogo currency={trade.inputAmount.currency} size="24px" style={{ marginRight: '12px' }} />
          <TruncatedText
            fontSize="24px"
            color={showAcceptChanges && trade.tradeType === TradeType.EXACT_OUTPUT ? 'primary' : 'text'}
          >
            {trade.inputAmount.toSignificant(6)}
          </TruncatedText>
        </RowFixed>
        <RowFixed gap="0px">
          <Text fontSize="24px" ml="10px">
            {trade.inputAmount.currency.symbol}
          </Text>
        </RowFixed>
      </RowBetween>
      <RowFixed>
        <ArrowDownIcon width="16px" ml="4px" />
      </RowFixed>
      <RowBetween align="flex-end">
        <RowFixed gap="0px">
          <CurrencyLogo currency={trade.outputAmount.currency} size="24px" style={{ marginRight: '12px' }} />
          <TruncatedText
            fontSize="24px"
            color={
              priceImpactSeverity > 2
                ? 'failure'
                : showAcceptChanges && trade.tradeType === TradeType.EXACT_INPUT
                ? 'primary'
                : 'text'
            }
          >
            {trade.outputAmount.toSignificant(6)}
          </TruncatedText>
        </RowFixed>
        <RowFixed gap="0px">
          <Text fontSize="24px" ml="10px">
            {trade.outputAmount.currency.symbol}
          </Text>
        </RowFixed>
      </RowBetween>
      {showAcceptChanges ? (
        <SwapShowAcceptChanges justify="flex-start" gap="0px">
          <RowBetween>
            <RowFixed>
              <ErrorIcon mr="8px" />
              <Text bold> {t('Price Updated')}</Text>
            </RowFixed>
            <Button onClick={onAcceptChanges}>{t('Accept')}</Button>
          </RowBetween>
        </SwapShowAcceptChanges>
      ) : null}
      <AutoColumn justify="flex-start" gap="sm" style={{ padding: '24px 0 0 0px' }}>
        {trade.tradeType === TradeType.EXACT_INPUT ? (
          <Text small color="textSubtle" textAlign="left" style={{ width: '100%' }}>
            {t(`Output is estimated. You will receive at least`)}{' '}
            <b>
              {slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {trade.outputAmount.currency.symbol}
            </b>
            {' '}{t('or the transaction will revert.')}
          </Text>
        ) : (
          <Text small color="textSubtle" textAlign="left" style={{ width: '100%' }}>
            {t(`Input is estimated. You will sell at most`)}{' '}
            <b>
              {slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {trade.inputAmount.currency.symbol}
            </b>
            {' '}{t('or the transaction will revert.')}
          </Text>
        )}
      </AutoColumn>
      {recipient !== null ? (
        <AutoColumn justify="flex-start" gap="sm" style={{ padding: '12px 0 0 0px' }}>
          <Text color="textSubtle">
            {t('Output will be sent to')}{' '}
            <b title={recipient}>{isAddress(recipient) ? shortenAddress(recipient) : recipient}</b>
          </Text>
        </AutoColumn>
      ) : null}
    </AutoColumn>
  )
}
Example #7
Source File: SwapModalHeader.tsx    From glide-frontend with GNU General Public License v3.0 4 votes vote down vote up
export default function SwapModalHeader({
  trade,
  allowedSlippage,
  recipient,
  showAcceptChanges,
  onAcceptChanges,
}: {
  trade: Trade
  allowedSlippage: number
  recipient: string | null
  showAcceptChanges: boolean
  onAcceptChanges: () => void
}) {
  const slippageAdjustedAmounts = useMemo(
    () => computeSlippageAdjustedAmounts(trade, allowedSlippage),
    [trade, allowedSlippage],
  )
  const { priceImpactWithoutFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade])
  const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)

  return (
    <AutoColumn gap="md">
      <RowBetween align="flex-end">
        <RowFixed gap="0px">
          <CurrencyLogo currency={trade.inputAmount.currency} size="24px" style={{ marginRight: '12px' }} />
          <TruncatedText
            fontSize="24px"
            color={showAcceptChanges && trade.tradeType === TradeType.EXACT_OUTPUT ? 'primary' : 'text'}
          >
            {trade.inputAmount.toSignificant(6)}
          </TruncatedText>
        </RowFixed>
        <RowFixed gap="0px">
          <Text fontSize="24px" ml="10px">
            {trade.inputAmount.currency.symbol}
          </Text>
        </RowFixed>
      </RowBetween>
      <RowFixed>
        <ArrowDownIcon width="16px" ml="4px" />
      </RowFixed>
      <RowBetween align="flex-end">
        <RowFixed gap="0px">
          <CurrencyLogo currency={trade.outputAmount.currency} size="24px" style={{ marginRight: '12px' }} />
          <TruncatedText
            fontSize="24px"
            color={
              priceImpactSeverity > 2
                ? 'failure'
                : showAcceptChanges && trade.tradeType === TradeType.EXACT_INPUT
                ? 'primary'
                : 'text'
            }
          >
            {trade.outputAmount.toSignificant(6)}
          </TruncatedText>
        </RowFixed>
        <RowFixed gap="0px">
          <Text fontSize="24px" ml="10px">
            {trade.outputAmount.currency.symbol}
          </Text>
        </RowFixed>
      </RowBetween>
      {showAcceptChanges ? (
        <SwapShowAcceptChanges justify="flex-start" gap="0px">
          <RowBetween>
            <RowFixed>
              <ErrorIcon mr="8px" />
              <Text bold> Price Updated</Text>
            </RowFixed>
            <Button onClick={onAcceptChanges}>Accept</Button>
          </RowBetween>
        </SwapShowAcceptChanges>
      ) : null}
      <AutoColumn justify="flex-start" gap="sm" style={{ padding: '24px 0 0 0px' }}>
        {trade.tradeType === TradeType.EXACT_INPUT ? (
          <Text small color="textSubtle" textAlign="left" style={{ width: '100%' }}>
            {`Output is estimated. You will receive at least `}
            <b>
              {slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {trade.outputAmount.currency.symbol}
            </b>
            {' or the transaction will revert.'}
          </Text>
        ) : (
          <Text small color="textSubtle" textAlign="left" style={{ width: '100%' }}>
            {`Input is estimated. You will sell at most `}
            <b>
              {slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {trade.inputAmount.currency.symbol}
            </b>
            {' or the transaction will revert.'}
          </Text>
        )}
      </AutoColumn>
      {recipient !== null ? (
        <AutoColumn justify="flex-start" gap="sm" style={{ padding: '12px 0 0 0px' }}>
          <Text color="textSubtle">
            Output will be sent to{' '}
            <b title={recipient}>{isAddress(recipient) ? shortenAddress(recipient) : recipient}</b>
          </Text>
        </AutoColumn>
      ) : null}
    </AutoColumn>
  )
}
Example #8
Source File: index.test.ts    From glide-frontend with GNU General Public License v3.0 4 votes vote down vote up
describe('utils', () => {
  describe('#getBscScanLink', () => {
    it('correct for tx', () => {
      expect(getBscScanLink('abc', 'transaction', ChainId.MAINNET)).toEqual('https://explorer.com/tx/abc')
    })
    it('correct for token', () => {
      expect(getBscScanLink('abc', 'token', ChainId.MAINNET)).toEqual('https://explorer.com/token/abc')
    })
    it('correct for address', () => {
      expect(getBscScanLink('abc', 'address', ChainId.MAINNET)).toEqual('https://explorer.com/address/abc')
    })
    it('enum', () => {
      expect(getBscScanLink('abc', 'address', ChainId.TESTNET)).toEqual('https://testnet.explorer.com/address/abc')
    })
  })

  describe('#calculateSlippageAmount', () => {
    it('bounds are correct', () => {
      const tokenAmount = new TokenAmount(new Token(ChainId.MAINNET, AddressZero, 0), '100')
      expect(() => calculateSlippageAmount(tokenAmount, -1)).toThrow()
      expect(calculateSlippageAmount(tokenAmount, 0).map((bound) => bound.toString())).toEqual(['100', '100'])
      expect(calculateSlippageAmount(tokenAmount, 100).map((bound) => bound.toString())).toEqual(['99', '101'])
      expect(calculateSlippageAmount(tokenAmount, 200).map((bound) => bound.toString())).toEqual(['98', '102'])
      expect(calculateSlippageAmount(tokenAmount, 10000).map((bound) => bound.toString())).toEqual(['0', '200'])
      expect(() => calculateSlippageAmount(tokenAmount, 10001)).toThrow()
    })
  })

  describe('#isAddress', () => {
    it('returns false if not', () => {
      expect(isAddress('')).toBe(false)
      expect(isAddress('0x0000')).toBe(false)
      expect(isAddress(1)).toBe(false)
      expect(isAddress({})).toBe(false)
      expect(isAddress(undefined)).toBe(false)
    })

    it('returns the checksummed address', () => {
      expect(isAddress('0xf164fc0ec4e93095b804a4795bbe1e041497b92a')).toBe('0xf164fC0Ec4E93095b804a4795bBe1e041497b92a')
      expect(isAddress('0xf164fC0Ec4E93095b804a4795bBe1e041497b92a')).toBe('0xf164fC0Ec4E93095b804a4795bBe1e041497b92a')
    })

    it('succeeds even without prefix', () => {
      expect(isAddress('f164fc0ec4e93095b804a4795bbe1e041497b92a')).toBe('0xf164fC0Ec4E93095b804a4795bBe1e041497b92a')
    })
    it('fails if too long', () => {
      expect(isAddress('f164fc0ec4e93095b804a4795bbe1e041497b92a0')).toBe(false)
    })
  })

  describe('#shortenAddress', () => {
    it('throws on invalid address', () => {
      expect(() => shortenAddress('abc')).toThrow("Invalid 'address'")
    })

    it('truncates middle characters', () => {
      expect(shortenAddress('0xf164fc0ec4e93095b804a4795bbe1e041497b92a')).toBe('0xf164...b92a')
    })

    it('truncates middle characters even without prefix', () => {
      expect(shortenAddress('f164fc0ec4e93095b804a4795bbe1e041497b92a')).toBe('0xf164...b92a')
    })

    it('renders checksummed address', () => {
      expect(shortenAddress('0x2E1b342132A67Ea578e4E3B814bae2107dc254CC'.toLowerCase())).toBe('0x2E1b...54CC')
    })
  })

  describe('#calculateGasMargin', () => {
    it('adds 10%', () => {
      expect(calculateGasMargin(BigNumber.from(1000)).toString()).toEqual('1100')
      expect(calculateGasMargin(BigNumber.from(50)).toString()).toEqual('55')
    })
  })

  describe('#basisPointsToPercent', () => {
    it('converts basis points numbers to percents', () => {
      expect(basisPointsToPercent(100).equalTo(new Percent(JSBI.BigInt(1), JSBI.BigInt(100)))).toBeTruthy()
      expect(basisPointsToPercent(500).equalTo(new Percent(JSBI.BigInt(5), JSBI.BigInt(100)))).toBeTruthy()
      expect(basisPointsToPercent(50).equalTo(new Percent(JSBI.BigInt(5), JSBI.BigInt(1000)))).toBeTruthy()
    })
  })
})
Example #9
Source File: AddressSelection.tsx    From frontend-v1 with GNU Affero General Public License v3.0 4 votes vote down vote up
AddressSelection: React.FC = () => {
  const { isConnected } = useConnection();
  const { toChain, toAddress, fromChain, setToAddress } = useSend();
  const [address, setAddress] = useState("");
  const [open, setOpen] = useState(false);
  const dispatch = useAppDispatch();

  const sendState = useAppSelector((state) => state.send);

  const { trackEvent } = useMatomo();

  const {
    isOpen,
    selectedItem,
    getLabelProps,
    getToggleButtonProps,
    getItemProps,
    getMenuProps,
  } = useSelect({
    items: CHAINS_SELECTION,
    defaultSelectedItem: sendState.currentlySelectedToChain,
    selectedItem: sendState.currentlySelectedToChain,
    onSelectedItemChange: ({ selectedItem }) => {
      if (selectedItem) {
        // Matomo track toChain selection
        trackEvent({
          category: "send",
          action: "setToChain",
          name: selectedItem.chainId.toString(),
        });

        const nextState = { ...sendState, toChain: selectedItem.chainId };
        dispatch(actions.toChain(nextState));
        dispatch(actions.updateSelectedToChain(selectedItem));
        const nsToChain = { ...sendState };
        if (selectedItem.chainId === ChainId.MAINNET) {
          nsToChain.fromChain = ChainId.OPTIMISM;
          dispatch(actions.fromChain(nsToChain));
          dispatch(actions.updateSelectedFromChain(CHAINS_SELECTION[0]));
        }
      }
    },
  });

  useEffect(() => {
    if (toAddress) {
      setAddress(toAddress);
    }
  }, [toAddress]);

  const toggle = () => {
    // modal is closing, reset address to the current toAddress
    if (!isConnected) return;
    if (open) setAddress(toAddress || address);
    setOpen((oldOpen) => !oldOpen);
  };
  const clearInput = () => {
    setAddress("");
  };

  const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
    setAddress(evt.target.value);
  };
  const isValid = !address || isValidAddress(address);
  const handleSubmit = () => {
    if (isValid && address) {
      setToAddress({ toAddress: address });
      toggle();
    }
  };

  const isL1toL2 = fromChain === ChainId.MAINNET;

  return (
    <AnimatePresence>
      <LastSection>
        <Wrapper>
          <SectionTitle>To</SectionTitle>
          <InputGroup>
            <RoundBox as="label" {...getLabelProps()}>
              <ToggleButton type="button" {...getToggleButtonProps()}>
                <Logo src={selectedItem?.logoURI} alt={selectedItem?.name} />
                <div>
                  <ToggleChainName>
                    {selectedItem?.name === "Ether"
                      ? "Mainnet"
                      : selectedItem?.name}
                  </ToggleChainName>
                  {toAddress && <Address>{shortenAddress(toAddress)}</Address>}
                </div>
                <ToggleIcon />
              </ToggleButton>
            </RoundBox>
            <Menu isOpen={isOpen} {...getMenuProps()}>
              {isOpen &&
                sendState.currentlySelectedToChain.chainId !==
                  ChainId.MAINNET &&
                CHAINS_SELECTION.map((t, index) => {
                  return (
                    <Item
                      className={
                        t === sendState.currentlySelectedToChain ||
                        t.chainId === ChainId.MAINNET
                          ? "disabled"
                          : ""
                      }
                      {...getItemProps({ item: t, index })}
                      key={t.chainId}
                    >
                      <Logo src={t.logoURI} alt={t.name} />
                      <div>{t.name}</div>
                      <span className="layer-type">
                        {t.chainId !== ChainId.MAINNET ? "L2" : "L1"}
                      </span>
                    </Item>
                  );
                })}
              {isOpen &&
                sendState.currentlySelectedToChain.chainId ===
                  ChainId.MAINNET && (
                  <>
                    <ItemWarning
                      initial={{ y: -10 }}
                      animate={{ y: 0 }}
                      exit={{ y: -10 }}
                    >
                      <p>
                        Transfers between L2 chains is not possible at this time
                      </p>
                    </ItemWarning>
                    {CHAINS_SELECTION.map((t, index) => {
                      return (
                        <Item
                          className={"disabled"}
                          {...getItemProps({ item: t, index })}
                          key={t.chainId}
                          initial={{ y: -10 }}
                          animate={{ y: 0 }}
                          exit={{ y: -10 }}
                        >
                          <Logo src={t.logoURI} alt={t.name} />
                          <div>{t.name}</div>
                          <span className="layer-type">
                            {index !== CHAINS_SELECTION.length - 1
                              ? "L2"
                              : "L1"}
                          </span>
                        </Item>
                      );
                    })}
                  </>
                )}
            </Menu>
          </InputGroup>
          {!isL1toL2 && (
            <ChangeWrapper onClick={toggle}>
              <ChangeButton className={!isConnected ? "disabled" : ""}>
                Change account
              </ChangeButton>
            </ChangeWrapper>
          )}
        </Wrapper>
        <Dialog isOpen={open} onClose={toggle}>
          <h3>Send To</h3>
          <div>Address on {CHAINS[toChain].name}</div>
          <InputWrapper>
            <Input onChange={handleChange} value={address} />
            <ClearButton onClick={clearInput}>
              <XOctagon
                fill="var(--color-gray-300)"
                stroke="var(--color-white)"
              />
            </ClearButton>
            {!isValid && <InputError>Not a valid address</InputError>}
          </InputWrapper>
          <ButtonGroup>
            <CancelButton onClick={toggle}>Cancel</CancelButton>
            <SecondaryButton
              onClick={handleSubmit}
              disabled={!isValid || !address}
            >
              Save Changes
            </SecondaryButton>
          </ButtonGroup>
        </Dialog>
      </LastSection>
    </AnimatePresence>
  );
}
Example #10
Source File: AnalyticsTokenDetails.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
AnalyticsTokenDetails: React.FC = () => {
  const classes = useStyles();
  const { palette, breakpoints } = useTheme();
  const isMobile = useMediaQuery(breakpoints.down('xs'));
  const history = useHistory();
  const match = useRouteMatch<{ id: string }>();
  const tokenAddress = match.params.id;
  const [token, setToken] = useState<any>(null);
  const { chainId } = useActiveWeb3React();
  const currency = token
    ? new Token(ChainId.MATIC, getAddress(token.id), token.decimals)
    : undefined;
  const [tokenPairs, updateTokenPairs] = useState<any>(null);
  const {
    bookmarkTokens,
    addBookmarkToken,
    removeBookmarkToken,
  } = useBookmarkTokens();

  useEffect(() => {
    async function fetchTokenInfo() {
      const [newPrice, oneDayPrice] = await getEthPrice();
      const tokenInfo = await getTokenInfo(newPrice, oneDayPrice, tokenAddress);
      if (tokenInfo) {
        setToken(tokenInfo[0]);
      }
    }
    fetchTokenInfo();
  }, [tokenAddress]);

  useEffect(() => {
    async function fetchTokenPairs() {
      const [newPrice] = await getEthPrice();
      const tokenPairs = await getTokenPairs2(tokenAddress);
      const formattedPairs = tokenPairs
        ? tokenPairs.map((pair: any) => {
            return pair.id;
          })
        : [];
      const pairData = await getBulkPairData(formattedPairs, newPrice);
      if (pairData) {
        updateTokenPairs(pairData);
      }
    }
    fetchTokenPairs();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [updateTokenPairs, tokenAddress]);

  const tokenPercentColor = getPriceColor(
    token ? Number(token.priceChangeUSD) : 0,
    palette,
  );

  return (
    <>
      <AnalyticsHeader type='token' data={token} />
      {token ? (
        <>
          <Box
            width={1}
            display='flex'
            flexWrap='wrap'
            justifyContent='space-between'
          >
            <Box display='flex'>
              <CurrencyLogo currency={currency} size='32px' />
              <Box ml={1.5}>
                <Box display='flex' alignItems='center'>
                  <Box display='flex' alignItems='flex-end' mr={0.5}>
                    <Typography className={classes.heading1}>
                      {token.name}{' '}
                    </Typography>
                    <Typography className={classes.heading2}>
                      ({token.symbol})
                    </Typography>
                  </Box>
                  {bookmarkTokens.includes(token.id) ? (
                    <StarChecked
                      onClick={() => removeBookmarkToken(token.id)}
                    />
                  ) : (
                    <StarUnchecked onClick={() => addBookmarkToken(token.id)} />
                  )}
                </Box>
                <Box mt={1.25} display='flex' alignItems='center'>
                  <Typography
                    variant='h5'
                    style={{ color: palette.text.primary }}
                  >
                    ${formatNumber(token.priceUSD)}
                  </Typography>
                  <Box
                    className={classes.priceChangeWrapper}
                    ml={2}
                    bgcolor={tokenPercentColor.bgColor}
                    color={tokenPercentColor.textColor}
                  >
                    <Typography variant='body2'>
                      {getFormattedPrice(Number(token.priceChangeUSD))}%
                    </Typography>
                  </Box>
                </Box>
              </Box>
            </Box>
            <Box my={2} display='flex'>
              <Box
                className={classes.button}
                mr={1.5}
                border={`1px solid ${palette.primary.main}`}
                onClick={() => {
                  history.push(`/pools?currency0=${token.id}&currency1=ETH`);
                }}
              >
                <Typography variant='body2'>Add Liquidity</Typography>
              </Box>
              <Box
                className={cx(classes.button, classes.filledButton)}
                onClick={() => {
                  history.push(`/swap?currency0=${token.id}&currency1=ETH`);
                }}
              >
                <Typography variant='body2'>Swap</Typography>
              </Box>
            </Box>
          </Box>
          <Box width={1} className={classes.panel} mt={4}>
            <Grid container>
              <Grid item xs={12} sm={12} md={6}>
                <AnalyticsTokenChart token={token} />
              </Grid>
              <Grid item xs={12} sm={12} md={6}>
                <Box
                  my={2}
                  height={1}
                  display='flex'
                  flexDirection='column'
                  alignItems='center'
                  justifyContent='center'
                >
                  <Box
                    width={isMobile ? 1 : 0.8}
                    display='flex'
                    justifyContent='space-between'
                  >
                    <Box width={180}>
                      <Typography
                        variant='caption'
                        style={{ color: palette.text.disabled }}
                      >
                        TOTAL LIQUIDITY
                      </Typography>
                      <Typography variant={isMobile ? 'body1' : 'h5'}>
                        ${token.totalLiquidityUSD.toLocaleString()}
                      </Typography>
                    </Box>
                    <Box width={140}>
                      <Typography
                        variant='caption'
                        style={{ color: palette.text.disabled }}
                      >
                        7d Trading Vol
                      </Typography>
                      <Typography variant={isMobile ? 'body1' : 'h5'}>
                        ${token.oneWeekVolumeUSD.toLocaleString()}
                      </Typography>
                    </Box>
                  </Box>
                  <Box
                    width={isMobile ? 1 : 0.8}
                    mt={4}
                    display='flex'
                    justifyContent='space-between'
                  >
                    <Box width={180}>
                      <Typography
                        variant='caption'
                        style={{ color: palette.text.disabled }}
                      >
                        24h Trading Vol
                      </Typography>
                      <Typography variant={isMobile ? 'body1' : 'h5'}>
                        ${token.oneDayVolumeUSD.toLocaleString()}
                      </Typography>
                    </Box>
                    <Box width={140}>
                      <Typography
                        variant='caption'
                        style={{ color: palette.text.disabled }}
                      >
                        24h FEES
                      </Typography>
                      <Typography variant={isMobile ? 'body1' : 'h5'}>
                        $
                        {(
                          token.oneDayVolumeUSD * GlobalConst.utils.FEEPERCENT
                        ).toLocaleString()}
                      </Typography>
                    </Box>
                  </Box>
                  <Box
                    width={isMobile ? 1 : 0.8}
                    mt={4}
                    display='flex'
                    justifyContent='space-between'
                  >
                    <Box width={180}>
                      <Typography
                        variant='caption'
                        style={{ color: palette.text.disabled }}
                      >
                        Contract Address
                      </Typography>
                      <Typography
                        variant='h5'
                        style={{ color: palette.primary.main }}
                      >
                        {chainId ? (
                          <a
                            href={getEtherscanLink(
                              chainId,
                              token.id,
                              'address',
                            )}
                            target='_blank'
                            rel='noopener noreferrer'
                            style={{
                              color: palette.primary.main,
                              textDecoration: 'none',
                            }}
                          >
                            {shortenAddress(token.id)}
                          </a>
                        ) : (
                          shortenAddress(token.id)
                        )}
                      </Typography>
                    </Box>
                  </Box>
                </Box>
              </Grid>
            </Grid>
          </Box>
          <Box width={1} mt={5}>
            <Typography variant='body1'>{token.symbol} Pools</Typography>
          </Box>
          <Box width={1} className={classes.panel} mt={4}>
            {tokenPairs ? (
              <PairTable data={tokenPairs} />
            ) : (
              <Skeleton variant='rect' width='100%' height={150} />
            )}
          </Box>
        </>
      ) : (
        <Skeleton width='100%' height={100} />
      )}
    </>
  );
}
Example #11
Source File: AnalyticsPairDetails.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
AnalyticsPairDetails: React.FC = () => {
  const classes = useStyles();
  const { palette, breakpoints } = useTheme();
  const isMobile = useMediaQuery(breakpoints.down('xs'));
  const history = useHistory();
  const match = useRouteMatch<{ id: string }>();
  const pairAddress = match.params.id;
  const [pairData, setPairData] = useState<any>(null);
  const [pairTransactions, setPairTransactions] = useState<any>(null);
  const pairTransactionsList = useMemo(() => {
    if (pairTransactions) {
      const mints = pairTransactions.mints.map((item: any) => {
        return { ...item, type: TxnType.ADD };
      });
      const swaps = pairTransactions.swaps.map((item: any) => {
        const amount0 = item.amount0Out > 0 ? item.amount0Out : item.amount1Out;
        const amount1 = item.amount0In > 0 ? item.amount0In : item.amount1In;
        const token0 =
          item.amount0Out > 0 ? item.pair.token0 : item.pair.token1;
        const token1 =
          item.amount0Out > 0 ? item.pair.token1 : item.pair.token0;
        return {
          ...item,
          amount0,
          amount1,
          pair: { token0, token1 },
          type: TxnType.SWAP,
        };
      });
      const burns = pairTransactions.burns.map((item: any) => {
        return { ...item, type: TxnType.REMOVE };
      });
      return mints.concat(swaps).concat(burns);
    } else {
      return null;
    }
  }, [pairTransactions]);
  const { chainId } = useActiveWeb3React();
  const currency0 = pairData
    ? new Token(
        ChainId.MATIC,
        getAddress(pairData.token0.id),
        pairData.token0.decimals,
      )
    : undefined;
  const currency1 = pairData
    ? new Token(
        ChainId.MATIC,
        getAddress(pairData.token1.id),
        pairData.token1.decimals,
      )
    : undefined;

  const token0Rate =
    pairData && pairData.reserve0 && pairData.reserve1
      ? Number(pairData.reserve1) / Number(pairData.reserve0) >= 0.0001
        ? (Number(pairData.reserve1) / Number(pairData.reserve0)).toFixed(
            Number(pairData.reserve1) / Number(pairData.reserve0) > 1 ? 2 : 4,
          )
        : '< 0.0001'
      : '-';
  const token1Rate =
    pairData && pairData.reserve0 && pairData.reserve1
      ? Number(pairData.reserve0) / Number(pairData.reserve1) >= 0.0001
        ? (Number(pairData.reserve0) / Number(pairData.reserve1)).toFixed(
            Number(pairData.reserve0) / Number(pairData.reserve1) > 1 ? 2 : 4,
          )
        : '< 0.0001'
      : '-';
  const usingUtVolume =
    pairData &&
    pairData.oneDayVolumeUSD === 0 &&
    !!pairData.oneDayVolumeUntracked;
  const fees =
    pairData && (pairData.oneDayVolumeUSD || pairData.oneDayVolumeUSD === 0)
      ? usingUtVolume
        ? (
            Number(pairData.oneDayVolumeUntracked) *
            GlobalConst.utils.FEEPERCENT
          ).toLocaleString()
        : (
            Number(pairData.oneDayVolumeUSD) * GlobalConst.utils.FEEPERCENT
          ).toLocaleString()
      : '-';

  useEffect(() => {
    async function checkEthPrice() {
      const [newPrice] = await getEthPrice();
      const pairInfo = await getBulkPairData([pairAddress], newPrice);
      if (pairInfo && pairInfo.length > 0) {
        setPairData(pairInfo[0]);
      }
    }
    async function fetchTransctions() {
      const transactions = await getPairTransactions(pairAddress);
      if (transactions) {
        setPairTransactions(transactions);
      }
    }
    checkEthPrice();
    fetchTransctions();
  }, [pairAddress]);

  return (
    <>
      <AnalyticsHeader type='pair' data={pairData} />
      {pairData ? (
        <>
          <Box
            width={1}
            display='flex'
            flexWrap='wrap'
            justifyContent='space-between'
          >
            <Box>
              <Box display='flex' alignItems='center'>
                <DoubleCurrencyLogo
                  currency0={currency0}
                  currency1={currency1}
                  size={32}
                />
                <Box ml={1}>
                  <Typography className={classes.heading2}>
                    <Link to={`/analytics/token/${pairData.token0.id}`}>
                      {pairData.token0.symbol}
                    </Link>{' '}
                    /{' '}
                    <Link to={`/analytics/token/${pairData.token1.id}`}>
                      {pairData.token1.symbol}
                    </Link>
                  </Typography>
                </Box>
              </Box>
              <Box mt={2} display='flex'>
                <Box
                  paddingY={0.75}
                  paddingX={1.5}
                  borderRadius={20}
                  display='flex'
                  alignItems='center'
                  bgcolor={palette.grey.A700}
                >
                  <CurrencyLogo currency={currency0} size='16px' />
                  <Typography
                    variant='body2'
                    color='textPrimary'
                    style={{ marginLeft: 6 }}
                  >
                    1 {pairData.token0.symbol} = {token0Rate}{' '}
                    {pairData.token1.symbol}
                  </Typography>
                </Box>
                <Box
                  padding={0.75}
                  paddingX={1.5}
                  ml={2}
                  borderRadius={20}
                  display='flex'
                  alignItems='center'
                  bgcolor={palette.grey.A700}
                >
                  <CurrencyLogo currency={currency1} size='16px' />
                  <Typography
                    variant='body2'
                    color='textPrimary'
                    style={{ marginLeft: 6 }}
                  >
                    1 {pairData.token1.symbol} = {token1Rate}{' '}
                    {pairData.token0.symbol}
                  </Typography>
                </Box>
              </Box>
            </Box>
            <Box my={2} display='flex'>
              <Box
                className={classes.button}
                mr={1.5}
                border={`1px solid ${palette.primary.main}`}
                onClick={() => {
                  history.push(
                    `/pools?currency0=${pairData.token0.id}&currency1=${pairData.token1.id}`,
                  );
                }}
              >
                <Typography variant='body2'>Add Liquidity</Typography>
              </Box>
              <Box
                className={cx(classes.button, classes.filledButton)}
                onClick={() => {
                  history.push(
                    `/swap?currency0=${pairData.token0.id}&currency1=${pairData.token1.id}`,
                  );
                }}
              >
                <Typography variant='body2'>Swap</Typography>
              </Box>
            </Box>
          </Box>
          <Box width={1} className={classes.panel} mt={4}>
            <Grid container>
              <Grid item xs={12} sm={12} md={6}>
                <AnalyticsPairChart pairData={pairData} />
              </Grid>
              <Grid item xs={12} sm={12} md={6}>
                <Box
                  my={2}
                  height={1}
                  display='flex'
                  justifyContent='center'
                  alignItems='center'
                >
                  <Box
                    width={isMobile ? 1 : 0.8}
                    display='flex'
                    justifyContent='space-between'
                  >
                    <Box width={212}>
                      <Box>
                        <Typography
                          variant='caption'
                          style={{ color: palette.text.disabled }}
                        >
                          TOTAL TOKENS LOCKED
                        </Typography>
                        <Box
                          mt={1.5}
                          bgcolor={palette.grey.A400}
                          borderRadius={8}
                          padding={1.5}
                        >
                          <Box
                            display='flex'
                            alignItems='center'
                            justifyContent='space-between'
                          >
                            <Box display='flex' alignItems='center'>
                              <CurrencyLogo currency={currency0} size='16px' />
                              <Typography
                                variant='caption'
                                color='textPrimary'
                                style={{ marginLeft: 6 }}
                              >
                                {pairData.token0.symbol} :
                              </Typography>
                            </Box>
                            <Typography variant='caption' color='textPrimary'>
                              {Number(pairData.reserve0).toLocaleString()}
                            </Typography>
                          </Box>
                          <Box
                            mt={1}
                            display='flex'
                            alignItems='center'
                            justifyContent='space-between'
                          >
                            <Box display='flex' alignItems='center'>
                              <CurrencyLogo currency={currency1} size='16px' />
                              <Typography
                                variant='caption'
                                color='textPrimary'
                                style={{ marginLeft: 6 }}
                              >
                                {pairData.token1.symbol} :
                              </Typography>
                            </Box>
                            <Typography variant='caption' color='textPrimary'>
                              {Number(pairData.reserve1).toLocaleString()}
                            </Typography>
                          </Box>
                        </Box>
                      </Box>
                      <Box mt={4}>
                        <Typography
                          variant='caption'
                          style={{ color: palette.text.disabled }}
                        >
                          7d Trading Vol
                        </Typography>
                        <Typography variant={isMobile ? 'body1' : 'h5'}>
                          ${pairData.oneWeekVolumeUSD.toLocaleString()}
                        </Typography>
                      </Box>
                      <Box mt={4}>
                        <Typography
                          variant='caption'
                          style={{ color: palette.text.disabled }}
                        >
                          24h FEES
                        </Typography>
                        <Typography variant={isMobile ? 'body1' : 'h5'}>
                          ${fees}
                        </Typography>
                      </Box>
                    </Box>
                    <Box width={140}>
                      <Typography
                        variant='caption'
                        style={{ color: palette.text.disabled }}
                      >
                        TOTAL LIQUIDITY
                      </Typography>
                      <Typography variant={isMobile ? 'body1' : 'h5'}>
                        $
                        {Number(
                          pairData.reserveUSD
                            ? pairData.reserveUSD
                            : pairData.trackedReserveUSD,
                        ).toLocaleString()}
                      </Typography>
                      <Box mt={4}>
                        <Typography
                          variant='caption'
                          style={{ color: palette.text.disabled }}
                        >
                          24h Trading Vol
                        </Typography>
                        <Typography variant={isMobile ? 'body1' : 'h5'}>
                          ${pairData.oneDayVolumeUSD.toLocaleString()}
                        </Typography>
                      </Box>
                      <Box mt={4}>
                        <Typography
                          variant='caption'
                          style={{ color: palette.text.disabled }}
                        >
                          Contract Address
                        </Typography>
                        <Typography
                          variant='h5'
                          style={{ color: palette.primary.main }}
                        >
                          {chainId ? (
                            <a
                              href={getEtherscanLink(
                                chainId,
                                pairData.id,
                                'address',
                              )}
                              target='_blank'
                              rel='noopener noreferrer'
                              style={{
                                color: palette.primary.main,
                                textDecoration: 'none',
                              }}
                            >
                              {shortenAddress(pairData.id)}
                            </a>
                          ) : (
                            shortenAddress(pairData.id)
                          )}
                        </Typography>
                      </Box>
                    </Box>
                  </Box>
                </Box>
              </Grid>
            </Grid>
          </Box>
          <Box width={1} mt={5}>
            <Typography variant='body1'>Transactions</Typography>
          </Box>
          <Box width={1} className={classes.panel} mt={4}>
            {pairTransactionsList ? (
              <TransactionsTable data={pairTransactionsList} />
            ) : (
              <Skeleton variant='rect' width='100%' height={150} />
            )}
          </Box>
        </>
      ) : (
        <Skeleton width='100%' height={100} />
      )}
    </>
  );
}
Example #12
Source File: AnalyticsHeader.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
AnalyticsHeader: React.FC<AnalyticHeaderProps> = ({ data, type }) => {
  const classes = useStyles();
  const { palette } = useTheme();
  const history = useHistory();
  const { pathname } = useLocation();

  return (
    <Box width='100%' mb={3}>
      <Box mb={4}>
        <Typography variant='h4'>Quickswap Analytics</Typography>
      </Box>
      <Box
        mb={4}
        position='relative'
        display='flex'
        justifyContent='space-between'
        flexWrap='wrap'
      >
        <Box marginY={1.5} display='flex' alignItems='center'>
          {type && data && (
            <Box display='flex' alignItems='center' color={palette.text.hint}>
              <Typography
                variant='caption'
                className={classes.link}
                onClick={() => {
                  history.push('/analytics');
                }}
              >
                Analytics
              </Typography>
              <ArrowForwardIos style={{ width: 16 }} />
              <Typography
                variant='caption'
                className={classes.link}
                onClick={() => {
                  history.push(`/analytics/${type}s`);
                }}
              >
                {type === 'token' ? 'Tokens' : 'Pairs'}
              </Typography>
              <ArrowForwardIos style={{ width: 16 }} />
              <Typography variant='caption'>
                <span style={{ color: '#b6b9cc' }}>
                  {type === 'token'
                    ? data.symbol
                    : `${data.token0.symbol}/${data.token1.symbol}`}
                </span>
                ({shortenAddress(data.id)})
              </Typography>
            </Box>
          )}
          {!type && (
            <>
              <Box
                className={cx(
                  classes.topTab,
                  pathname.indexOf('pair') === -1 &&
                    pathname.indexOf('token') === -1 &&
                    classes.selectedTab,
                )}
                onClick={() => history.push(`/analytics`)}
              >
                <Typography variant='body1'>Overview</Typography>
              </Box>
              <Box
                className={cx(
                  classes.topTab,
                  pathname.indexOf('token') > -1 && classes.selectedTab,
                )}
                onClick={() => history.push(`/analytics/tokens`)}
              >
                <Typography variant='body1'>Tokens</Typography>
              </Box>
              <Box
                className={cx(
                  classes.topTab,
                  pathname.indexOf('pair') > -1 && classes.selectedTab,
                )}
                onClick={() => history.push(`/analytics/pairs`)}
              >
                <Typography variant='body1'>Pairs</Typography>
              </Box>
            </>
          )}
        </Box>

        <Search />
      </Box>
    </Box>
  );
}
Example #13
Source File: useSwapCallback.ts    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
// returns a function that will execute a swap, if the parameters are all valid
// and the user has approved the slippage adjusted input amount for the trade
export function useSwapCallback(
  trade: Trade | undefined, // trade to execute, required
  allowedSlippage: number = GlobalConst.utils.INITIAL_ALLOWED_SLIPPAGE, // in bips
  recipientAddressOrName: string | null, // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
): {
  state: SwapCallbackState;
  callback:
    | null
    | (() => Promise<{ response: TransactionResponse; summary: string }>);
  error: string | null;
} {
  const { account, chainId, library } = useActiveWeb3React();

  const swapCalls = useSwapCallArguments(
    trade,
    allowedSlippage,
    recipientAddressOrName,
  );

  const addTransaction = useTransactionAdder();

  const { address: recipientAddress } = useENS(recipientAddressOrName);
  const recipient =
    recipientAddressOrName === null ? account : recipientAddress;

  return useMemo(() => {
    if (!trade || !library || !account || !chainId) {
      return {
        state: SwapCallbackState.INVALID,
        callback: null,
        error: 'Missing dependencies',
      };
    }
    if (!recipient) {
      if (recipientAddressOrName !== null) {
        return {
          state: SwapCallbackState.INVALID,
          callback: null,
          error: 'Invalid recipient',
        };
      } else {
        return {
          state: SwapCallbackState.LOADING,
          callback: null,
          error: null,
        };
      }
    }

    const tradeVersion = Version.v2;

    return {
      state: SwapCallbackState.VALID,
      callback: async function onSwap(): Promise<{
        response: TransactionResponse;
        summary: string;
      }> {
        const estimatedCalls: EstimatedSwapCall[] = await Promise.all(
          swapCalls.map((call) => {
            const {
              parameters: { methodName, args, value },
              contract,
            } = call;
            const options = !value || isZero(value) ? {} : { value };

            return contract.estimateGas[methodName](...args, options)
              .then((gasEstimate) => {
                return {
                  call,
                  gasEstimate: gasEstimate.add(100000),
                };
              })
              .catch((gasError) => {
                console.debug(
                  'Gas estimate failed, trying eth_call to extract error',
                  call,
                );

                return contract.callStatic[methodName](...args, options)
                  .then((result) => {
                    console.debug(
                      'Unexpected successful call after failed estimate gas',
                      call,
                      gasError,
                      result,
                    );
                    return {
                      call,
                      error: new Error(
                        'Unexpected issue with estimating the gas. Please try again.',
                      ),
                    };
                  })
                  .catch((callError) => {
                    console.debug('Call threw error', call, callError);
                    let errorMessage: string;
                    switch (callError.reason) {
                      case 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT':
                      case 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT':
                        errorMessage =
                          'This transaction will not succeed either due to price movement or fee on transfer. Try increasing your slippage tolerance.';
                        break;
                      default:
                        errorMessage = `The transaction cannot succeed due to error: ${callError.reason}. This is probably an issue with one of the tokens you are swapping.`;
                    }
                    return { call, error: new Error(errorMessage) };
                  });
              });
          }),
        );

        // a successful estimation is a bignumber gas estimate and the next call is also a bignumber gas estimate
        const successfulEstimation = estimatedCalls.find(
          (el, ix, list): el is SuccessfulCall =>
            'gasEstimate' in el &&
            (ix === list.length - 1 || 'gasEstimate' in list[ix + 1]),
        );

        if (!successfulEstimation) {
          const errorCalls = estimatedCalls.filter(
            (call): call is FailedCall => 'error' in call,
          );
          if (errorCalls.length > 0)
            throw errorCalls[errorCalls.length - 1].error;
          throw new Error(
            'Unexpected error. Please contact support: none of the calls threw an error',
          );
        }

        const {
          call: {
            contract,
            parameters: { methodName, args, value },
          },
          gasEstimate,
        } = successfulEstimation;

        return contract[methodName](...args, {
          gasLimit: calculateGasMargin(gasEstimate),
          ...(value && !isZero(value)
            ? { value, from: account }
            : { from: account }),
        })
          .then((response: TransactionResponse) => {
            const inputSymbol = trade.inputAmount.currency.symbol;
            const outputSymbol = trade.outputAmount.currency.symbol;
            const inputAmount = formatTokenAmount(trade.inputAmount);
            const outputAmount = formatTokenAmount(trade.outputAmount);

            const base = `Swap ${inputAmount} ${inputSymbol} for ${outputAmount} ${outputSymbol}`;
            const withRecipient =
              recipient === account
                ? base
                : `${base} to ${
                    recipientAddressOrName && isAddress(recipientAddressOrName)
                      ? shortenAddress(recipientAddressOrName)
                      : recipientAddressOrName
                  }`;

            const withVersion =
              tradeVersion === Version.v2
                ? withRecipient
                : `${withRecipient} on ${(tradeVersion as any).toUpperCase()}`;

            addTransaction(response, {
              summary: withVersion,
            });

            return { response, summary: withVersion };
          })
          .catch((error: any) => {
            // if the user rejected the tx, pass this along
            if (error?.code === 4001) {
              throw new Error('Transaction rejected.');
            } else {
              // otherwise, the error was unexpected and we need to convey that
              console.error(`Swap failed`, error, methodName, args, value);
              throw new Error(`Swap failed: ${error.message}`);
            }
          });
      },
      error: null,
    };
  }, [
    trade,
    library,
    account,
    chainId,
    recipient,
    recipientAddressOrName,
    swapCalls,
    addTransaction,
  ]);
}
Example #14
Source File: SwapTokenDetails.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
SwapTokenDetails: React.FC<{
  token: Token;
}> = ({ token }) => {
  const classes = useStyles();
  const currency = unwrappedToken(token);
  const tokenAddress = token.address;
  const { palette } = useTheme();
  const latestBlock = useBlockNumber();
  const { tokenDetails, updateTokenDetails } = useTokenDetails();
  const [tokenData, setTokenData] = useState<any>(null);
  const [priceData, setPriceData] = useState<any>(null);
  const priceUp = Number(tokenData?.priceChangeUSD) > 0;
  const priceUpPercent = Number(tokenData?.priceChangeUSD).toFixed(2);
  const [isCopied, setCopied] = useCopyClipboard();
  const prices = priceData ? priceData.map((price: any) => price.close) : [];

  useEffect(() => {
    async function fetchTokenData() {
      const tokenDetail = tokenDetails.find(
        (item) => item.address === tokenAddress,
      );
      setTokenData(tokenDetail?.tokenData);
      setPriceData(tokenDetail?.priceData);
      const currentTime = dayjs.utc();
      const startTime = currentTime
        .subtract(1, 'day')
        .startOf('hour')
        .unix();
      const tokenPriceData = await getIntervalTokenData(
        tokenAddress,
        startTime,
        3600,
        latestBlock,
      );
      setPriceData(tokenPriceData);

      const [newPrice, oneDayPrice] = await getEthPrice();
      const tokenInfo = await getTokenInfo(newPrice, oneDayPrice, tokenAddress);
      if (tokenInfo) {
        const token0 = tokenInfo[0];
        setTokenData(token0);
        const tokenDetailToUpdate = {
          address: tokenAddress,
          tokenData: token0,
          priceData: tokenPriceData,
        };
        updateTokenDetails(tokenDetailToUpdate);
      }
    }
    fetchTokenData();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [tokenAddress]);

  return (
    <Box>
      <Box
        display='flex'
        alignItems='center'
        justifyContent='space-between'
        px={2}
        py={1.5}
      >
        <Box display='flex' alignItems='center'>
          <CurrencyLogo currency={currency} size='28px' />
          <Box ml={1}>
            <Typography variant='body2'>{currency.symbol}</Typography>
            {tokenData ? (
              <Box display='flex' alignItems='center'>
                <Typography variant='body2'>
                  ${formatNumber(tokenData.priceUSD)}
                </Typography>
                <Box
                  ml={0.5}
                  display='flex'
                  alignItems='center'
                  className={priceUp ? classes.success : classes.danger}
                >
                  {priceUp ? <ArrowDropUp /> : <ArrowDropDown />}
                  <Typography variant='body2'>{priceUpPercent}%</Typography>
                </Box>
              </Box>
            ) : (
              <Skeleton variant='rect' width={100} height={20} />
            )}
          </Box>
        </Box>
        {tokenData && priceData ? (
          <Box width={88} height={47} position='relative'>
            <Box position='absolute' top={-30} width={1}>
              {prices.length > 0 && (
                <LineChart
                  data={prices}
                  width='100%'
                  height={120}
                  color={priceUp ? palette.success.main : palette.error.main}
                />
              )}
            </Box>
          </Box>
        ) : (
          <Skeleton variant='rect' width={88} height={47} />
        )}
      </Box>
      <Box
        borderTop={`1px solid ${palette.secondary.light}`}
        borderBottom={`1px solid ${palette.secondary.light}`}
        px={2}
      >
        <Grid container>
          <Grid item xs={6}>
            <Box borderRight={`1px solid ${palette.secondary.light}`} py={1}>
              {tokenData ? (
                <Typography
                  variant='body2'
                  style={{ color: palette.text.secondary }}
                >
                  TVL: {formatCompact(tokenData?.totalLiquidityUSD)}
                </Typography>
              ) : (
                <Skeleton variant='rect' width={100} height={16} />
              )}
            </Box>
          </Grid>
          <Grid item xs={6}>
            <Box py={1} pl={2}>
              {tokenData ? (
                <Typography
                  variant='body2'
                  style={{ color: palette.text.secondary }}
                >
                  24h VOL: {formatCompact(tokenData?.oneDayVolumeUSD)}
                </Typography>
              ) : (
                <Skeleton variant='rect' width={100} height={16} />
              )}
            </Box>
          </Grid>
        </Grid>
      </Box>
      <Box
        display='flex'
        justifyContent='space-between'
        alignItems='center'
        py={1}
        px={2}
      >
        <a
          href={`https://polygonscan.com/token/${tokenAddress}`}
          target='_blank'
          rel='noopener noreferrer'
          style={{ textDecoration: 'none' }}
        >
          <Typography variant='body2' style={{ color: palette.primary.main }}>
            {shortenAddress(tokenAddress)}
          </Typography>
        </a>
        <Box
          display='flex'
          style={{ cursor: 'pointer', opacity: isCopied ? 0.5 : 1 }}
          onClick={() => {
            setCopied(tokenAddress);
          }}
        >
          <CopyIcon />
        </Box>
      </Box>
    </Box>
  );
}
Example #15
Source File: Header.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
Header: React.FC = () => {
  const classes = useStyles();
  const { pathname } = useLocation();
  const { account } = useActiveWeb3React();
  const { ethereum } = window as any;
  const { ENSName } = useENSName(account ?? undefined);
  const [openDetailMenu, setOpenDetailMenu] = useState(false);
  const theme = useTheme();
  const allTransactions = useAllTransactions();
  const sortedRecentTransactions = useMemo(() => {
    const txs = Object.values(allTransactions);
    return txs.filter(isTransactionRecent).sort(newTransactionsFirst);
  }, [allTransactions]);

  const pending = sortedRecentTransactions
    .filter((tx: any) => !tx.receipt)
    .map((tx: any) => tx.hash);
  const confirmed = sortedRecentTransactions
    .filter((tx: any) => tx.receipt)
    .map((tx: any) => tx.hash);
  const tabletWindowSize = useMediaQuery(theme.breakpoints.down('sm'));
  const mobileWindowSize = useMediaQuery(theme.breakpoints.down('xs'));
  const toggleWalletModal = useWalletModalToggle();
  const menuItems = [
    {
      link: '/swap',
      text: 'Swap',
      id: 'swap-page-link',
    },
    {
      link: '/pools',
      text: 'Pool',
      id: 'pools-page-link',
    },
    {
      link: '/farm',
      text: 'Farm',
      id: 'farm-page-link',
    },
    {
      link: '/dragons',
      text: 'Dragon’s Lair',
      id: 'dragons-page-link',
    },
    {
      link: '/convert',
      text: 'Convert',
      id: 'convert-quick',
    },
    {
      link: '/analytics',
      text: 'Analytics',
      id: 'analytics-page-link',
    },
  ];

  const outLinks: any[] = [
    // {
    //   link: '/',
    //   text: 'Governance',
    // },
    // {
    //   link: '/',
    //   text: 'Docs',
    // },
    // {
    //   link: '/',
    //   text: 'For Developers',
    // },
    // {
    //   link: '/',
    //   text: 'Help & Tutorials',
    // },
    // {
    //   link: '/',
    //   text: 'Knowledge Base',
    // },
    // {
    //   link: '/',
    //   text: 'News',
    // },
  ];

  return (
    <Box className={classes.header}>
      <WalletModal
        ENSName={ENSName ?? undefined}
        pendingTransactions={pending}
        confirmedTransactions={confirmed}
      />
      <Link to='/'>
        <img
          src={mobileWindowSize ? QuickIcon : QuickLogo}
          alt='QuickLogo'
          height={60}
        />
      </Link>
      {!tabletWindowSize && (
        <Box className={classes.mainMenu}>
          {menuItems.map((val, index) => (
            <Link
              to={val.link}
              key={index}
              id={val.id}
              className={
                pathname.indexOf(val.link) > -1 ? 'active' : 'menuItem'
              }
            >
              <Typography variant='body2'>{val.text}</Typography>
            </Link>
          ))}
          {/* <Box display='flex' className='menuItem'>
            <ThreeDotIcon />
            <Box
              position='absolute'
              top={32}
              left={0}
              width={209}
              paddingTop={10}
            >
              <Box className='subMenu'>
                {outLinks.map((item, ind) => (
                  <a href={item.link} key={ind}>
                    <Typography variant='body2'>{item.text}</Typography>
                  </a>
                ))}
              </Box>
            </Box>
          </Box> */}
        </Box>
      )}
      {tabletWindowSize && (
        <Box className={classes.mobileMenuContainer}>
          <Box className={classes.mobileMenu}>
            {menuItems.slice(0, 4).map((val, index) => (
              <Link
                to={val.link}
                key={index}
                className={
                  pathname.indexOf(val.link) > -1 ? 'active' : 'menuItem'
                }
              >
                <Typography variant='body2'>{val.text}</Typography>
              </Link>
            ))}
            <Box display='flex' className='menuItem'>
              <ThreeDotIcon
                onClick={() => setOpenDetailMenu(!openDetailMenu)}
              />
              {openDetailMenu && (
                <Box
                  position='absolute'
                  bottom={72}
                  right={12}
                  width={209}
                  bgcolor={theme.palette.secondary.dark}
                  borderRadius={20}
                  py={1}
                  border={`1px solid ${theme.palette.divider}`}
                >
                  <Box className='subMenu'>
                    {menuItems.slice(4, menuItems.length).map((val, index) => (
                      <Link
                        to={val.link}
                        key={index}
                        className='menuItem'
                        onClick={() => setOpenDetailMenu(false)}
                      >
                        <Typography variant='body2'>{val.text}</Typography>
                      </Link>
                    ))}
                    {outLinks.map((item, ind) => (
                      <a
                        href={item.link}
                        key={ind}
                        onClick={() => setOpenDetailMenu(false)}
                      >
                        <Typography variant='body2'>{item.text}</Typography>
                      </a>
                    ))}
                  </Box>
                </Box>
              )}
            </Box>
          </Box>
        </Box>
      )}
      <Box>
        <Box
          width={45}
          height={36}
          display='flex'
          alignItems='center'
          justifyContent='center'
          marginRight={1}
        >
          <StyledPollingDot></StyledPollingDot>
          <LightIcon />
        </Box>
        {account && (!ethereum || isSupportedNetwork(ethereum)) ? (
          <Box
            id='web3-status-connected'
            className={classes.accountDetails}
            onClick={toggleWalletModal}
          >
            <Typography>{shortenAddress(account)}</Typography>
            <img src={WalletIcon} alt='Wallet' />
          </Box>
        ) : (
          <Box
            className={cx(
              classes.connectButton,
              ethereum && !isSupportedNetwork(ethereum)
                ? classes.danger
                : classes.primary,
            )}
            onClick={() => {
              if (!ethereum || isSupportedNetwork(ethereum)) {
                toggleWalletModal();
              }
            }}
          >
            {ethereum && !isSupportedNetwork(ethereum)
              ? 'Wrong Network'
              : 'Connect Wallet'}
            {ethereum && !isSupportedNetwork(ethereum) && (
              <Box
                position='absolute'
                top={36}
                width={272}
                right={0}
                paddingTop='18px'
              >
                <Box className={classes.wrongNetworkContent}>
                  <Typography variant='body2'>
                    Please switch your wallet to Polygon Network.
                  </Typography>
                  <Box onClick={addMaticToMetamask}>Switch to Polygon</Box>
                </Box>
              </Box>
            )}
          </Box>
        )}
      </Box>
    </Box>
  );
}
Example #16
Source File: AccountDetails.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
AccountDetails: React.FC<AccountDetailsProps> = ({
  toggleWalletModal,
  pendingTransactions,
  confirmedTransactions,
  ENSName,
  openOptions,
}) => {
  const { chainId, account, connector } = useActiveWeb3React();
  const classes = useStyles();
  const { palette } = useTheme();
  const dispatch = useDispatch<AppDispatch>();

  function formatConnectorName() {
    const { ethereum } = window as any;
    const isMetaMask = !!(
      ethereum &&
      !ethereum.isBitKeep &&
      ethereum.isMetaMask
    );
    const isBitkeep = !!(ethereum && ethereum.isBitKeep);
    const isBlockWallet = !!(ethereum && ethereum.isBlockWallet);
    const name = Object.keys(SUPPORTED_WALLETS)
      .filter(
        (k) =>
          SUPPORTED_WALLETS[k].connector === connector &&
          (connector !== injected ||
            (isBlockWallet && k === 'BLOCKWALLET') ||
            (isBitkeep && k === 'BITKEEP') ||
            (isMetaMask && k === 'METAMASK')),
      )
      .map((k) => SUPPORTED_WALLETS[k].name)[0];
    return <Typography variant='body2'>Connected with {name}</Typography>;
  }

  const clearAllTransactionsCallback = useCallback(() => {
    if (chainId) dispatch(clearAllTransactions({ chainId }));
  }, [dispatch, chainId]);

  return (
    <Box paddingX={3} paddingY={4}>
      <Box display='flex' justifyContent='space-between'>
        <Typography variant='h5'>Account</Typography>
        <Close style={{ cursor: 'pointer' }} onClick={toggleWalletModal} />
      </Box>
      <Box
        mt={2}
        padding={2}
        borderRadius={10}
        bgcolor={palette.secondary.dark}
      >
        <Box display='flex' justifyContent='space-between' alignItems='center'>
          {formatConnectorName()}
          <Box display='flex' alignItems='center'>
            {connector !== injected &&
              connector !== walletlink &&
              connector !== safeApp && (
                <Typography
                  style={{ cursor: 'pointer', marginRight: 8 }}
                  onClick={() => {
                    (connector as any).close();
                  }}
                  variant='body2'
                >
                  Disconnect
                </Typography>
              )}
            {connector !== safeApp && (
              <Typography
                style={{ cursor: 'pointer' }}
                onClick={() => {
                  openOptions();
                }}
                variant='body2'
              >
                Change
              </Typography>
            )}
          </Box>
        </Box>
        <Box display='flex' alignItems='center' my={1.5}>
          <StatusIcon />
          <Typography
            variant='h5'
            style={{ marginLeft: 8 }}
            id='web3-account-identifier-row'
          >
            {ENSName ? ENSName : account && shortenAddress(account)}
          </Typography>
        </Box>
        <Box display='flex' justifyContent='space-between' alignItems='center'>
          {account && (
            <Copy toCopy={account}>
              <span style={{ marginLeft: '4px' }}>Copy Address</span>
            </Copy>
          )}
          {chainId && account && (
            <a
              className={classes.addressLink}
              href={
                chainId &&
                getEtherscanLink(
                  chainId,
                  ENSName ? ENSName : account,
                  'address',
                )
              }
              target='_blank'
              rel='noopener noreferrer'
            >
              <LinkIcon size={16} />
              <Typography variant='body2'>View on Block Explorer</Typography>
            </a>
          )}
        </Box>
      </Box>
      {!!pendingTransactions.length || !!confirmedTransactions.length ? (
        <>
          <Box
            display='flex'
            justifyContent='space-between'
            alignItems='center'
            paddingX={2}
            pt={2}
            mb={1}
          >
            <Typography variant='body2'>Recent Transactions</Typography>
            <Typography
              variant='body2'
              style={{ cursor: 'pointer' }}
              onClick={clearAllTransactionsCallback}
            >
              Clear all
            </Typography>
          </Box>
          <Box paddingX={2} flex={1} overflow='auto'>
            {renderTransactions(pendingTransactions)}
            {renderTransactions(confirmedTransactions)}
          </Box>
        </>
      ) : (
        <Box paddingX={2} pt={2}>
          <Typography variant='body2'>
            Your transactions will appear here...
          </Typography>
        </Box>
      )}
    </Box>
  );
}
Example #17
Source File: REPMintEditor.tsx    From dxvote with GNU Affero General Public License v3.0 4 votes vote down vote up
Mint: React.FC<ActionEditorProps> = ({ decodedCall, updateCall }) => {
  // parse transfer state from calls
  const [repPercent, setRepPercent] = useState(0);
  const [repAmount, setRepAmount] = useState(0);
  const { parsedData } = useTotalSupply({ decodedCall });
  const { tokenData } = useTokenData();

  const totalSupply = useBigNumberToNumber(tokenData?.totalSupply, 18);

  const { imageUrl } = useENSAvatar(parsedData?.toAddress, MAINNET_ID);

  const setCallDataAmount = (value: string) => {
    const amount = value ? ethers.utils.parseUnits(value) : null;
    updateCall({
      ...decodedCall,
      args: {
        ...decodedCall.args,
        amount,
      },
    });
  };

  useEffect(() => {
    setRepAmount((repPercent / 100) * totalSupply);
    if (repAmount) {
      setCallDataAmount(repAmount.toString());
    }
  }, [repPercent, repAmount, totalSupply]);

  const handleRepChange = (e: number) => {
    if (e) {
      setRepPercent(e);
    }
  };
  return (
    <React.Fragment>
      <Control>
        <ControlLabel>
          Recipient
          <StyledIcon src={Info} />
        </ControlLabel>
        <ControlRow>
          <Input
            value={shortenAddress(parsedData?.toAddress)}
            icon={
              <Avatar
                src={imageUrl}
                defaultSeed={parsedData?.toAddress}
                size={18}
              />
            }
            readOnly
          />
        </ControlRow>
      </Control>
      <ControlRow>
        <Control>
          <ControlLabel>
            Reputation in % <StyledIcon src={Info} />
          </ControlLabel>
          <ControlRow>
            <RepMintInput value={repPercent} onUserInput={handleRepChange} />
          </ControlRow>
        </Control>
      </ControlRow>
      <ControlRow>
        <Control>
          <ControlLabel>
            Reputation Amount <StyledIcon src={Info} />
          </ControlLabel>
          <ControlRow>
            <RepMintInput
              value={repAmount}
              onUserInput={handleRepChange}
              readOnly
            />
          </ControlRow>
        </Control>
      </ControlRow>
    </React.Fragment>
  );
}