cyb/src/containers/warp/Warp.tsx

import { Pool } from '@cybercongress/cyber-js/build/codec/tendermint/liquidity/v1beta1/liquidity';
import { Pane } from '@cybercongress/gravity';
import BigNumber from 'bignumber.js';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { createSearchParams, useParams, useSearchParams } from 'react-router-dom';
import { MainContainer } from 'src/components';
import { BASE_DENOM, DENOM_LIQUID } from 'src/constants/config';
import { useIbcDenom } from 'src/contexts/ibcDenom';
import { useQueryClient } from 'src/contexts/queryClient';
import { useAdviser } from 'src/features/adviser/context';
import useGetBalances from 'src/hooks/getBalances';
import useGetTotalSupply from 'src/hooks/useGetTotalSupply';
import usePoolListInterval from 'src/hooks/usePoolListInterval';
import { RootState } from 'src/redux/store';
import { Option } from 'src/types';
import useSetActiveAddress from '../../hooks/useSetActiveAddress';
import { useGetSwapPrice } from '../../pages/teleport/hooks';
import { sortReserveCoinDenoms } from '../../pages/teleport/swap/utils';
import { getDisplayAmountReverce, reduceBalances } from '../../utils/utils';
import ActionBar from './ActionBar';
import DepositCreatePool from './components/DepositCreatePool';
import TabList from './components/tabList';
import Withdraw from './components/withdraw';
import { MyPoolsT, TypeTab } from './type';
import { calculateCounterPairAmount, getMyTokenBalanceNumber, getPoolToken } from './utils';

const tokenADefaultValue = BASE_DENOM;
const tokenBDefaultValue = DENOM_LIQUID;

function Warp() {
  const queryClient = useQueryClient();
  const { tracesDenom } = useIbcDenom();
  const { defaultAccount } = useSelector((state: RootState) => state.pocket);
  const [searchParams, setSearchParams] = useSearchParams();
  const { tab = 'add-liquidity' } = useParams<{ tab: TypeTab }>();
  const { addressActive } = useSetActiveAddress(defaultAccount);
  const [_update, setUpdate] = useState(0);
  const { liquidBalances: accountBalances, refresh } = useGetBalances(addressActive?.bech32);
  const { totalSupplyProofList: totalSupply } = useGetTotalSupply();
  const poolsData = usePoolListInterval({ refetchInterval: 50000 });

  const [tokenA, setTokenA] = useState<string>(tokenADefaultValue);
  const [tokenB, setTokenB] = useState<string>(tokenBDefaultValue);
  const [tokenAAmount, setTokenAAmount] = useState<string | number>('');
  const [tokenBAmount, setTokenBAmount] = useState<string | number>('');
  const [tokenAPoolAmount, setTokenAPoolAmount] = useState<number>(0);
  const [tokenBPoolAmount, setTokenBPoolAmount] = useState<number>(0);
  const [tokenACoinDecimals, setTokenACoinDecimals] = useState<number>(0);
  const [tokenBCoinDecimals, setTokenBCoinDecimals] = useState<number>(0);
  const [selectedPool, setSelectedPool] = useState<Pool | undefined>(undefined);
  const [isExceeded, setIsExceeded] = useState<boolean>(false);
  const [isEmptyPool, setIsEmptyPool] = useState<boolean>(false);
  const [amountPoolCoin, setAmountPoolCoin] = useState<string | number>('');
  const [myPools, setMyPools] = useState<Option<{ [key: string]: MyPoolsT }>>(undefined);
  const [selectMyPool, setSelectMyPool] = useState('');

  const swapPrice = useGetSwapPrice(tokenA, tokenB, tokenAPoolAmount, tokenBPoolAmount);
  const firstEffectOccured = useRef(false);

  const { setAdviser } = useAdviser();

  useEffect(() => {
    let text;

    switch (tab) {
      case 'add-liquidity':
        text = 'play with pools earn more values';
        break;
      case 'create-pool':
        text = (
          <>
            the unlimited number of variations. combine your favorite tokens <br /> cultivate your
            values. place of cyber alchemists
          </>
        );

        break;
      case 'sub-liquidity':
        text = 'manage your liquidity';
        break;

      default:
        break;
    }

    setAdviser(text);
  }, [setAdviser, tab]);

  useEffect(() => {
    const [{ coinDecimals }] = tracesDenom(tokenA);
    setTokenACoinDecimals(coinDecimals);
  }, [tracesDenom, tokenA]);

  useEffect(() => {
    const [{ coinDecimals }] = tracesDenom(tokenB);
    setTokenBCoinDecimals(coinDecimals);
  }, [tracesDenom, tokenB]);

  useEffect(() => {
    if (firstEffectOccured.current) {
      // eslint-disable-next-line react-hooks/exhaustive-deps
      const query = {
        from: tokenA,
        to: tokenB,
      };

      setSearchParams(createSearchParams(query));
    } else {
      firstEffectOccured.current = true;
      const param = Object.fromEntries(searchParams.entries());
      if (Object.keys(param).length > 0) {
        const { from, to } = param;
        setTokenA(from);
        setTokenB(to);
      }
    }

    if (tab === 'sub-liquidity') {
      setSearchParams(createSearchParams({}));
    }
  }, [tokenA, tokenB, setSearchParams, searchParams, tab]);

  useEffect(() => {
    // find pool for current pair
    setSelectedPool(undefined);

    if (!poolsData || !poolsData.length) {
      return;
    }

    if (tokenA.length > 0 && tokenB.length > 0) {
      const findPool = poolsData.find(
        (item) =>
          sortReserveCoinDenoms(item.reserveCoinDenoms[0], item.reserveCoinDenoms[1]).join() ===
          sortReserveCoinDenoms(tokenA, tokenB).join()
      );
      setSelectedPool(findPool);
    }
  }, [poolsData, tokenA, tokenB]);

  useEffect(() => {
    (async () => {
      setTokenAPoolAmount(0);
      setTokenBPoolAmount(0);
      setIsEmptyPool(false);

      if (!queryClient || !selectedPool) {
        return;
      }

      const getAllBalancesPromise = await queryClient.getAllBalances(
        selectedPool.reserveAccountAddress
      );

      setIsEmptyPool(!getAllBalancesPromise.length);

      const dataReduceBalances = reduceBalances(getAllBalancesPromise);

      setTokenAPoolAmount(dataReduceBalances[tokenA] || 0);
      setTokenBPoolAmount(dataReduceBalances[tokenB] || 0);
    })();
  }, [queryClient, tokenA, tokenB, selectedPool]);

  useEffect(() => {
    if (accountBalances !== null && poolsData && poolsData !== null) {
      const poolTokenData = getPoolToken(poolsData, accountBalances);
      let poolTokenDataIndexer = {};

      poolTokenDataIndexer = poolTokenData.reduce(
        (obj, item) => ({
          ...obj,
          [item.poolCoinDenom]: item,
        }),
        {}
      );
      setMyPools(poolTokenDataIndexer);
    }
  }, [accountBalances, poolsData]);

  useEffect(() => {
    let exceeded = true;
    const myATokenBalance = getMyTokenBalanceNumber(tokenA, accountBalances);
    const myATokenBalanceB = getMyTokenBalanceNumber(tokenB, accountBalances);

    if (accountBalances !== null) {
      const validTokensAB =
        Object.hasOwn(accountBalances, tokenA) &&
        Object.hasOwn(accountBalances, tokenB) &&
        accountBalances[tokenA] > 0 &&
        accountBalances[tokenB] > 0;

      const validTokenAmountAB =
        parseFloat(getDisplayAmountReverce(tokenAAmount, tokenACoinDecimals)) <= myATokenBalance &&
        Number(tokenAAmount) > 0 &&
        parseFloat(getDisplayAmountReverce(tokenBAmount, tokenBCoinDecimals)) <= myATokenBalanceB &&
        Number(tokenBAmount) > 0;

      const resultValidSelectTokens = validTokensAB && validTokenAmountAB;

      if (tab === 'add-liquidity' && resultValidSelectTokens && swapPrice !== 0) {
        exceeded = false;
      }

      // valid add-liquidity in empty pool
      if (tab === 'add-liquidity' && isEmptyPool && resultValidSelectTokens && swapPrice === 0) {
        exceeded = false;
      }

      if (tab === 'create-pool' && resultValidSelectTokens) {
        exceeded = false;
      }
    }
    setIsExceeded(exceeded);
  }, [
    accountBalances,
    tokenA,
    tokenB,
    tab,
    tokenAAmount,
    tokenBAmount,
    swapPrice,
    tokenACoinDecimals,
    tokenBCoinDecimals,
    isEmptyPool,
  ]);

  const amountChangeHandler = useCallback(
    (inputAmount: string, e: React.ChangeEvent) => {
      let counterPairValue = new BigNumber(0);
      const isReverse = e.target.id !== 'tokenAAmount';

      if (Number(inputAmount) > 0 && tokenAPoolAmount && tokenBPoolAmount) {
        const state = {
          tokenAPoolAmount,
          tokenBPoolAmount,
          tokenA,
          tokenB,
          tokenACoinDecimals,
          tokenBCoinDecimals,
          isReverse,
        };

        const { counterPairAmount } = calculateCounterPairAmount(inputAmount, state);

        counterPairValue = counterPairAmount;
      }

      if (isReverse) {
        setTokenBAmount(new BigNumber(inputAmount).toNumber());
        setTokenAAmount(counterPairValue.toString(10));
      } else {
        setTokenAAmount(new BigNumber(inputAmount).toNumber());
        setTokenBAmount(counterPairValue.toString(10));
      }
    },
    [tokenAPoolAmount, tokenBPoolAmount, tokenB, tokenA, tokenACoinDecimals, tokenBCoinDecimals]
  );

  const amountChangeHandlerCreatePool = useCallback((values: string, e: React.ChangeEvent) => {
    const inputAmount = values;

    const isReverse = e.target.id !== 'tokenAAmount';

    if (isReverse) {
      setTokenBAmount(new BigNumber(inputAmount).toNumber());
    } else {
      setTokenAAmount(new BigNumber(inputAmount).toNumber());
    }
  }, []);

  const onChangeInputWithdraw = (values: string) => {
    const inputAmount = values;

    const myATokenBalance = getMyTokenBalanceNumber(selectMyPool, accountBalances);
    let exceeded = true;
    if (parseFloat(inputAmount) <= myATokenBalance && parseFloat(inputAmount) > 0) {
      exceeded = false;
    }
    setIsExceeded(exceeded);
    setAmountPoolCoin(new BigNumber(inputAmount).toNumber());
  };

  function tokenChange() {
    const A = tokenB;
    const B = tokenA;
    const AP = tokenBPoolAmount;
    const BP = tokenAPoolAmount;

    setTokenA(A);
    setTokenB(B);
    setTokenAAmount('');
    setTokenBAmount('');
    setTokenAPoolAmount(AP);
    setTokenBPoolAmount(BP);
  }

  const updateFunc = useCallback(() => {
    setUpdate((item) => item + 1);
    refresh();
  }, [refresh]);

  const stateProps = {
    accountBalances,
    tokenAAmount,
    tokenBAmount,
    tokenA,
    tokenB,
    setTokenA,
    setTokenB,
    totalSupply,
    tokenChange,
  };

  const stateWithdraw = {
    accountBalances,
    myPools,
    selectMyPool,
    setSelectMyPool,
    amountPoolCoin,
    onChangeInputWithdraw,
  };

  const stateActionBar = {
    tokenAAmount,
    tokenBAmount,
    tokenA,
    tokenB,
    selectedPool,
    updateFunc,
    isExceeded,
    tab,
    amountPoolCoin,
    myPools,
    selectMyPool,
  };

  return (
    <>
      <MainContainer width="62%">
        <TabList selected={tab} />
        <Pane
          width="375px"
          display="flex"
          alignItems="center"
          flexDirection="column"
          marginX="auto"
        >
          <Pane width="100%" display="flex" alignItems="center" flexDirection="column">
            {tab === 'add-liquidity' && (
              <DepositCreatePool
                stateProps={stateProps}
                amountChangeHandler={
                  isEmptyPool ? amountChangeHandlerCreatePool : amountChangeHandler
                }
              />
            )}
            {tab === 'create-pool' && (
              <DepositCreatePool
                stateProps={stateProps}
                amountChangeHandler={amountChangeHandlerCreatePool}
              />
            )}
            {tab === 'sub-liquidity' && <Withdraw stateProps={stateWithdraw} />}
          </Pane>
        </Pane>

        {/* <TraceTxTable /> */}
      </MainContainer>
      <ActionBar stateActionBar={stateActionBar} />
    </>
  );
}

export default Warp;

Synonyms

pussy-ts/src/containers/warp/Warp.tsx

Neighbours