import {
  AvailableAmount,
  DenomArr,
  MainContainer,
  Slider,
} from 'src/components';
import Select, { OptionSelect } from 'src/components/Select';
import { CYBER } from 'src/utils/config';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useIbcDenom } from 'src/contexts/ibcDenom';
import BigNumber from 'bignumber.js';
import { getDisplayAmount, getDisplayAmountReverce } from 'src/utils/utils';
import { createSearchParams, useSearchParams } from 'react-router-dom';
import { useChannels } from 'src/hooks/useHub';
import { Networks } from 'src/types/networks';
import { useSetupIbcClient } from '../hooks';
import {
  Col,
  GridContainer,
  TeleportContainer,
} from '../components/containers/Containers';
import { TypeTxsT } from '../type';
import ActionBar from './actionBar.bridge';
import HistoryContextProvider from '../../../services/ibc-history/historyContext';
import DataIbcHistory from './components/dataIbcHistory/DataIbcHistory';
import InputNumberDecimalScale from '../components/Inputs/InputNumberDecimalScale/InputNumberDecimalScale';
import { useTeleport } from '../Teleport.context';

type Query = {
  networkFrom: string;
  networkTo: string;
  token: string;
  amount?: string;
};

export const ibcDenomAtom =
  'ibc/15E9C5CF5969080539DB395FA7D9C0868265217EFC528433671AAF9B1912D159';

const isCyberChain = (chainId: string) => chainId === CYBER.CHAIN_ID;

function Bridge() {
  const { traseDenom } = useIbcDenom();
  const { channels } = useChannels();
  const { totalSupplyProofList, accountBalances, refreshBalances } =
    useTeleport();
  const [searchParams, setSearchParams] = useSearchParams();

  const [tokenSelect, setTokenSelect] = useState<string>(ibcDenomAtom);
  const [tokenAmount, setTokenAmount] = useState<string>('');
  const [networkA, setNetworkA] = useState<string>(Networks.COSMOS);
  const [networkB, setNetworkB] = useState<string>(Networks.BOSTROM);
  const [typeTxs, setTypeTxs] = useState<TypeTxsT>('deposit');
  const [sourceChannel, setSourceChannel] = useState<string | null>(null);
  const [isExceeded, setIsExceeded] = useState<boolean>(false);

  const [tokenA, setTokenA] = useState<string>('');
  const [tokenB, setTokenB] = useState<string>('');

  const [tokenABalance, setTokenABalance] = useState<number>(0);
  const [tokenBBalance, setTokenBBalance] = useState<number>(0);

  const [tokenACoinDecimals, setTokenACoinDecimals] = useState<number>(0);

  const { ibcClient, balanceIbc, denomIbc } = useSetupIbcClient(
    tokenSelect,
    typeTxs === 'deposit' ? networkA : networkB
  );

  const firstEffectOccured = useRef(false);

  useEffect(() => {
    if (firstEffectOccured.current) {
      // eslint-disable-next-line react-hooks/exhaustive-deps
      const query: Query = {
        networkFrom: networkA,
        networkTo: networkB,
        token: tokenSelect,
      };

      if (Number(tokenAmount) > 0) {
        query.amount = tokenAmount;
      }

      setSearchParams(createSearchParams(query), { replace: true });
    } else {
      firstEffectOccured.current = true;
      const param = Object.fromEntries(searchParams.entries());
      if (Object.keys(param).length > 0) {
        const { networkFrom, networkTo, token, amount } = param;
        setNetworkA(networkFrom);
        setNetworkB(networkTo);
        setTokenSelect(token);

        if (Number(amount)) {
          setTokenAmount(amount);
        }
      }
    }
  }, [
    networkA,
    networkB,
    tokenSelect,
    setSearchParams,
    searchParams,
    tokenAmount,
  ]);

  useEffect(() => {
    const isInitialized = channels && networkA && networkB;
    if (!isInitialized) {
      return;
    }

    if (!isCyberChain(networkA) && isCyberChain(networkB)) {
      setTypeTxs('deposit');
      const { destination_channel_id: destChannelId } = channels[networkA];
      setSourceChannel(destChannelId);
    }

    if (isCyberChain(networkA) && !isCyberChain(networkB)) {
      setTypeTxs('withdraw');

      const { source_channel_id: sourceChannelId } = channels[networkB];
      setSourceChannel(sourceChannelId);
    }
  }, [networkB, networkA, channels]);

  const networkOptions = useCallback(
    () =>
      channels
        ? [CYBER.CHAIN_ID, ...Object.keys(channels)].map((key) => ({
            value: key,
            text: (
              <DenomArr
                type="network"
                denomValue={key}
                onlyText
                tooltipStatusText={false}
              />
            ),
            img: (
              <DenomArr
                type="network"
                denomValue={key}
                onlyImg
                tooltipStatusImg={false}
              />
            ),
          }))
        : [],
    [channels]
  );

  const tokenOptions = useMemo(
    () =>
      totalSupplyProofList
        ? Object.keys(totalSupplyProofList).map((key) => ({
            value: key,
            text: (
              <DenomArr
                denomValue={key}
                onlyText
                tooltipStatusText={tokenSelect !== key}
              />
            ),
            img: <DenomArr denomValue={key} onlyImg tooltipStatusImg={false} />,
          }))
        : [],

    [totalSupplyProofList, tokenSelect]
  );

  const validInputAmountToken = useMemo(() => {
    const isValid = Number(tokenAmount) > 0 && !!tokenAmount;

    if (!isValid) {
      return false;
    }

    const amountToken = parseFloat(
      getDisplayAmountReverce(tokenAmount, tokenACoinDecimals)
    );

    return amountToken > tokenABalance;
  }, [tokenABalance, tokenAmount, tokenACoinDecimals]);

  useEffect(() => {
    const validNetwork = networkA && networkB && networkA !== networkB;

    // check set up ibc cliet for deposit
    const validIbcClient = typeTxs === 'deposit' ? ibcClient !== null : true;

    const validTokenAmount = !validInputAmountToken && Number(tokenAmount) > 0;

    setIsExceeded(!(validNetwork && validIbcClient && validTokenAmount));
  }, [
    networkA,
    networkB,
    typeTxs,
    ibcClient,
    tokenAmount,
    validInputAmountToken,
  ]);

  const setPercentageBalanceHook = useCallback(
    (value: number) => {
      const amount = new BigNumber(tokenABalance)
        .multipliedBy(value)
        .dividedBy(100)
        .dp(tokenACoinDecimals, BigNumber.ROUND_FLOOR)
        .toNumber();
      setTokenAmount(getDisplayAmount(amount, tokenACoinDecimals));
    },
    [tokenABalance, tokenACoinDecimals]
  );

  const getPercentsOfToken = useCallback(() => {
    return tokenABalance > 0
      ? new BigNumber(getDisplayAmountReverce(tokenAmount, tokenACoinDecimals))
          .dividedBy(tokenABalance)
          .multipliedBy(100)
          .toNumber()
      : 0;
  }, [tokenAmount, tokenACoinDecimals, tokenABalance]);

  const tokenChange = useCallback(() => {
    setNetworkA(networkB);
    setNetworkB(networkA);
    setTokenAmount(0);
  }, [networkB, networkA]);

  const getDenomToken = useCallback(
    (selectNetwork: string) =>
      !isCyberChain(selectNetwork) && denomIbc ? denomIbc : tokenSelect,
    [tokenSelect, denomIbc]
  );

  useEffect(() => {
    const token = getDenomToken(networkA);
    setTokenA(token);
    const [{ coinDecimals }] = traseDenom(token);
    setTokenACoinDecimals(coinDecimals);
  }, [networkA, traseDenom, getDenomToken]);

  useEffect(() => {
    const token = getDenomToken(networkB);
    setTokenB(token);
  }, [networkB, getDenomToken]);

  const getAccountBalancesToken = useCallback(
    (selectNetwork: string) =>
      !isCyberChain(selectNetwork) ? balanceIbc : accountBalances,
    [accountBalances, balanceIbc]
  );

  useEffect(() => {
    const balance = getAccountBalancesToken(networkA);
    const balanceToken = balance ? balance[tokenA] || 0 : 0;
    setTokenABalance(balanceToken);
  }, [getAccountBalancesToken, networkA, tokenA]);

  useEffect(() => {
    const balance = getAccountBalancesToken(networkB);
    const balanceToken = balance ? balance[tokenB] || 0 : 0;
    setTokenBBalance(balanceToken);
  }, [getAccountBalancesToken, networkB, tokenB]);

  const stateActionBar = {
    tokenAmount,
    tokenSelect,
    updateFunc: () => refreshBalances(),
    isExceeded,
    typeTxs,
    ibcClient,
    denomIbc,
    sourceChannel,
    networkB,
  };

  return (
    <HistoryContextProvider>
      <MainContainer width="62%">
        <TeleportContainer>
          <GridContainer>
            <Col>
              <InputNumberDecimalScale
                value={tokenAmount}
                onValueChange={(value) => setTokenAmount(value)}
                title="choose amount to send"
                validAmount={validInputAmountToken}
                tokenSelect={tokenSelect}
              />
              <AvailableAmount
                amountToken={getDisplayAmount(
                  tokenABalance,
                  tokenACoinDecimals
                )}
              />
            </Col>
            <Col>
              <Select
                valueSelect={tokenSelect}
                currentValue={
                  <OptionSelect
                    text="choose"
                    img={<DenomArr denomValue="choose" onlyImg />}
                    value=""
                    bgrImg
                  />
                }
                onChangeSelect={(item: string) => setTokenSelect(item)}
                width="100%"
                options={tokenOptions}
                title="choose token to send"
              />
              <Select
                valueSelect={networkA}
                currentValue={
                  <OptionSelect
                    text="choose"
                    img={<DenomArr denomValue="choose" onlyImg />}
                    value=""
                    bgrImg
                  />
                }
                onChangeSelect={(item: string) => setNetworkA(item)}
                width="100%"
                title="choose source network"
                options={networkOptions()}
              />
            </Col>
          </GridContainer>

          <Slider
            valuePercents={getPercentsOfToken()}
            onChange={setPercentageBalanceHook}
            onSwapClick={tokenChange}
            disabled={tokenABalance === 0}
          />

          <GridContainer>
            <AvailableAmount
              amountToken={getDisplayAmount(tokenBBalance, tokenACoinDecimals)}
            />
            <Select
              valueSelect={networkB}
              onChangeSelect={(item: string) => setNetworkB(item)}
              width="100%"
              options={networkOptions()}
              title="destination network"
            />
          </GridContainer>
        </TeleportContainer>
        <TeleportContainer>
          <DataIbcHistory />
        </TeleportContainer>
      </MainContainer>
      <ActionBar stateActionBar={stateActionBar} />
    </HistoryContextProvider>
  );
}

export default Bridge;

Synonyms

cyb/src/pages/teleport/bridge/bridge.tsx

Neighbours