cyb/src/containers/portal/gift/ActionBarPortalGift.tsx

/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable no-await-in-loop */
/* eslint-disable no-restricted-syntax */
/* eslint-disable jsx-a11y/control-has-associated-label */

import { toAscii, toBase64 } from '@cosmjs/encoding';
import { GasPrice } from '@cosmjs/launchpad';
import BigNumber from 'bignumber.js';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useLocation, useNavigate } from 'react-router-dom';
import { CHAIN_ID } from 'src/constants/config';
import { PATTERN_CYBER } from 'src/constants/patterns';
import { useBackend } from 'src/contexts/backend/backend';
import { useSigningClient } from 'src/contexts/signerClient';
import useCurrentAddress from 'src/hooks/useCurrentAddress';
import useWaitForTransaction from 'src/hooks/useWaitForTransaction';
import Soft3MessageFactory from 'src/services/soft.js/api/msgs';
import { Nullable } from 'src/types';
import { Citizenship } from 'src/types/citizenship';
import { toHex } from 'src/utils/encoding';
import { useAdviser } from 'src/features/adviser/context';
import { hasSignArbitrary } from 'src/utils/offlineSigner';
import { ActionBar as ActionBarSteps, BtnGrd, ButtonIcon, Dots } from '../../../components';
import { addAddress, deleteAddress } from '../../../features/passport/passports.redux';
import imgCosmos from '../../../image/cosmos-2.svg';
import imgEth from '../../../image/Ethereum_logo_2014.svg';
import imgMetaMask from '../../../image/mm-logo.svg';
import imgOsmosis from '../../../image/osmosis.svg';
import imgSpacePussy from '../../../image/space-pussy.svg';
import imgTerra from '../../../image/terra.svg';
import { groupMsg, trimString } from '../../../utils/utils';
import { TxHash } from '../hook/usePingTxs';
import {
  BOOT_ICON,
  CONSTITUTION_HASH,
  CONTRACT_ADDRESS_GIFT,
  CONTRACT_ADDRESS_PASSPORT,
  getSignerKeyInfo,
} from '../utils';
import { ClaimMsg } from './type';
import STEP_INFO from './utils';

const _gasPrice = GasPrice.fromString('0.001boot');

const proofAddressMsg = (address, nickname, signature) => {
  return {
    proof_address: {
      address,
      nickname,
      signature,
    },
  };
};

const deleteAddressMsg = (address, nickname) => {
  return {
    remove_address: {
      address,
      nickname,
    },
  };
};

const claimMsg = (
  nickname: string,
  giftClaimingAddress: string,
  giftAmount: number,
  proof: string[]
): ClaimMsg => {
  return {
    claim: {
      proof,
      gift_amount: giftAmount.toString(),
      gift_claiming_address: giftClaimingAddress,
      nickname,
    },
  };
};

type Props = {
  addressActive?: {
    bech32: string;
  };
  citizenship: Nullable<Citizenship>;
  updateTxHash?: (data: TxHash) => void;
  isClaimed: any;
  selectedAddress?: string;
  currentGift: any;
  activeStep: any;
  setStepApp: any;

  setLoadingGift: any;
  loadingGift: any;
  progressClaim: number;
  currentBonus: number;
};

function ActionBarPortalGift({
  addressActive,
  citizenship,
  updateTxHash = () => {},
  isClaimed,
  selectedAddress,
  currentGift,
  activeStep,
  setStepApp,
  setLoadingGift,
  loadingGift,
  progressClaim,
  currentBonus,
}: Props) {
  const { setAdviser } = useAdviser();
  const { isIpfsInitialized, ipfsApi } = useBackend();

  const navigate = useNavigate();
  const location = useLocation();

  const { signer, signingClient, isLedgerAccount, getSignerForChain } = useSigningClient();
  const [selectMethod, setSelectMethod] = useState('');
  const [selectNetwork, setSelectNetwork] = useState('');
  const [signedMessage, setSignedMessage] = useState(null);

  const currentAddress = useCurrentAddress();

  const isGiftPage = location.pathname === '/gift';

  const [currentTx, setCurrentTx] = useState<{
    hash: string;
    onSuccess: () => void;
  }>();

  const dispatch = useDispatch();

  useWaitForTransaction({
    hash: currentTx?.hash,
    onSuccess: currentTx?.onSuccess,
  });

  useEffect(() => {
    const checkAddress = async () => {
      if (
        activeStep === STEP_INFO.STATE_PROVE_CHANGE_ACCOUNT ||
        activeStep === STEP_INFO.STATE_PROVE_CHECK_ACCOUNT
      ) {
        if (signer && addressActive !== null) {
          const [{ address }] = await signer.getAccounts();
          const { bech32 } = addressActive;
          if (address === bech32) {
            setStepApp(STEP_INFO.STATE_PROVE_SEND_SIGN);
          } else {
            setStepApp(STEP_INFO.STATE_PROVE_CHANGE_ACCOUNT);
          }
        }
      }
    };
    checkAddress();
  }, [signer, addressActive, activeStep, setStepApp]);

  const useAddressOwner = useMemo(() => {
    if (citizenship && addressActive !== null) {
      const { owner } = citizenship;
      const { name } = addressActive;
      if (name !== undefined && name !== null) {
        return (
          <>
            account
            <span style={{ color: '#36d6ae', padding: '0 5px' }}>{name}</span>
          </>
        );
      }
      return (
        <>
          address
          <span style={{ color: '#36d6ae', padding: '0 5px' }}>{trimString(owner, 10, 4)}</span>
        </>
      );
    }
    return '';
  }, [citizenship, addressActive]);

  const signMsgCosmos = useCallback(async () => {
    if (!citizenship || selectNetwork === '') return null;

    const { owner, extension } = citizenship;
    const { addresses } = extension;

    const chainSigner = await getSignerForChain(selectNetwork);

    if (hasSignArbitrary(chainSigner)) {
      const [{ address }] = await chainSigner.getAccounts();

      if (addresses !== null && Object.keys(addresses).length > 0) {
        const result = Object.keys(addresses).filter((key) => addresses[key].address === address);
        if (result.length > 0) {
          setStepApp(STEP_INFO.STATE_PROVE_YOU_ADDED_ADDR);
          return null;
        }
      }

      const data = `${owner}:${CONSTITUTION_HASH}`;
      const res = await chainSigner.signArbitrary(selectNetwork, address, data);

      const proveData = {
        pub_key: res.pub_key.value,
        signature: res.signature,
      };

      const signature = toBase64(toAscii(JSON.stringify(proveData)));
      setSignedMessage({ signature, address });
      setStepApp(STEP_INFO.STATE_PROVE_CHECK_ACCOUNT);
    } else if (chainSigner) {
      setAdviser('Ledger cannot sign messages. Use a seed phrase wallet for this step');
    } else {
      setAdviser('Wallet is locked. Enter your password to unlock');
    }
    return null;
  }, [citizenship, selectNetwork, setStepApp, getSignerForChain, setAdviser]);

  const signMsgETH = useCallback(async () => {
    if (window.ethereum && citizenship) {
      const { owner, extension } = citizenship;
      const { addresses } = extension;

      const { ethereum } = window;

      const accounts = await ethereum.request({
        method: 'eth_requestAccounts',
      });

      const address = accounts[0];
      const message = `${owner}:${CONSTITUTION_HASH}`;
      const msg = `0x${toHex(new TextEncoder().encode(message))}`;
      const from = address;

      if (addresses !== null && Object.keys(addresses).length > 0) {
        const result = Object.keys(addresses).filter((key) => addresses[key].address === address);

        if (result.length > 0) {
          setStepApp(STEP_INFO.STATE_PROVE_YOU_ADDED_ADDR);
          return null;
        }
      }

      const signature = await ethereum.request({
        method: 'personal_sign',
        params: [msg, from, 'proveAddress'],
      });

      setSignedMessage({ signature, address });
      setStepApp(STEP_INFO.STATE_PROVE_CHECK_ACCOUNT);
    }
    return null;
  }, [citizenship, setStepApp]);

  const sendSignedMessage = useCallback(async () => {
    if (signer && signingClient && citizenship && signedMessage !== null) {
      const { nickname } = citizenship.extension;

      const msgObject = proofAddressMsg(
        signedMessage.address,
        nickname,
        signedMessage.signature
      );

      try {
        const [{ address }] = await signer.getAccounts();

        const executeResponseResult = await signingClient.execute(
          address,
          CONTRACT_ADDRESS_PASSPORT,
          msgObject,
          Soft3MessageFactory.fee(2),
          'cyber'
        );

        if (executeResponseResult.code === 0) {
          updateTxHash({
            status: 'pending',
            txHash: executeResponseResult.transactionHash,
          });

          setCurrentTx({
            hash: executeResponseResult.transactionHash,
            onSuccess: () => {
              dispatch(
                addAddress({
                  address: signedMessage.address,
                  currentAddress,
                })
              );
            },
          });

          setStepApp(STEP_INFO.STATE_PROVE_IN_PROCESS);
          if (setLoadingGift) {
            setLoadingGift(true);
          }
        }

        if (executeResponseResult.code) {
          updateTxHash({
            txHash: executeResponseResult?.transactionHash,
            status: 'error',
            rawLog: executeResponseResult?.rawLog.toString(),
          });
        }
        if (isIpfsInitialized) {
          ipfsApi?.addContent(signedMessage.address);
        }
      } catch (error) {
        console.log('error', error);
        setStepApp(STEP_INFO.STATE_PROVE);
      }
    }
  }, [
    signer,
    signingClient,
    citizenship,
    signedMessage,
    isIpfsInitialized,
    ipfsApi,
    currentAddress,
    dispatch,
    setLoadingGift,
    setStepApp,
    updateTxHash,
  ]);

  const claim = useCallback(async () => {
    try {
      if (
        signer &&
        signingClient &&
        selectedAddress !== null &&
        currentGift !== null &&
        citizenship
      ) {
        const { nickname } = citizenship.extension;
        if (Object.keys(currentGift).length > 0) {
          const msgs: ClaimMsg[] = [];
          Object.keys(currentGift).forEach((key) => {
            const { address, proof, amount } = currentGift[key];
            const msgObject = claimMsg(nickname, address, amount, proof);
            msgs.push(msgObject);
          });
          const { bech32Address, isNanoLedger } = await getSignerKeyInfo(signer, CHAIN_ID);

          if (!msgs.length) {
            return;
          }

          let elementMsg = msgs;

          if (isNanoLedger) {
            elementMsg = groupMsg(msgs, 1)[0] as ClaimMsg[];
          }

          const multiplier = new BigNumber(2.5)
            .multipliedBy(Object.keys(elementMsg).length)
            .toNumber();

          const executeResponseResult = await signingClient.executeArray(
            bech32Address,
            CONTRACT_ADDRESS_GIFT,
            elementMsg,
            Soft3MessageFactory.fee(multiplier),
            'cyber'
          );

          console.log('executeResponseResult', executeResponseResult);
          if (executeResponseResult.code === 0) {
            updateTxHash({
              status: 'pending',
              txHash: executeResponseResult.transactionHash,
            });

            if (setLoadingGift) {
              setLoadingGift(true);
            }
            setStepApp(STEP_INFO.STATE_CLAIM_IN_PROCESS);
            // setStep(STEP_INIT);
          }

          if (executeResponseResult.code) {
            updateTxHash({
              txHash: executeResponseResult?.transactionHash,
              status: 'error',
              rawLog: executeResponseResult?.rawLog.toString(),
            });
          }
        }
      }
    } catch (error) {
      console.log('error', error);
      // setStep(STEP_INIT);
      setStepApp(STEP_INFO.STATE_CLAIME);
    }
  }, [
    signer,
    signingClient,
    selectedAddress,
    currentGift,
    citizenship,
    setLoadingGift, // setStep(STEP_INIT);
    setStepApp,
    updateTxHash,
  ]);

  const isProve = useMemo(() => {
    if (citizenship && !citizenship.extension.addresses) {
      return false;
    }

    if (
      !!citizenship?.extension.addresses &&
      Object.keys(citizenship.extension.addresses).length <= 8
    ) {
      return false;
    }

    return true;
  }, [citizenship]);

  const isClaime = useMemo(() => {
    if (isClaimed !== undefined && isClaimed !== null && !isClaimed) {
      return false;
    }
    return true;
  }, [isClaimed]);

  const useDeleteAddress = useCallback(async () => {
    if (!signer || !signingClient || !selectedAddress || !citizenship) {
      return;
    }

    // not possible to delete cyber address
    if (selectedAddress.match(PATTERN_CYBER)) {
      return;
    }

    const { nickname } = citizenship.extension;
    const msgObject = deleteAddressMsg(selectedAddress, nickname);
    try {
      const [{ address }] = await signer.getAccounts();
      const executeResponseResult = await signingClient.execute(
        address,
        CONTRACT_ADDRESS_PASSPORT,
        msgObject,
        Soft3MessageFactory.fee(2),
        'cyber'
      );

      if (executeResponseResult.code === 0) {
        updateTxHash({
          status: 'pending',
          txHash: executeResponseResult.transactionHash,
        });
        setCurrentTx({
          hash: executeResponseResult.transactionHash,
          onSuccess: () => {
            dispatch(
              deleteAddress({
                address: selectedAddress,
                currentAddress,
              })
            );
            setStepApp(STEP_INFO.STATE_INIT);
          },
        });
        setStepApp(STEP_INFO.STATE_DELETE_IN_PROCESS);
      }

      if (executeResponseResult.code) {
        updateTxHash({
          txHash: executeResponseResult?.transactionHash,
          status: 'error',
          rawLog: executeResponseResult?.rawLog.toString(),
        });
      }
    } catch (error) {
      console.log('error', error);
      setStepApp(STEP_INFO.STATE_INIT);
    }
  }, [
    signer,
    signingClient,
    selectedAddress,
    citizenship,
    currentAddress,
    dispatch,
    setStepApp,
    updateTxHash,
  ]);

  const useGetSelectAddress = useMemo(() => {
    if (selectedAddress && selectedAddress !== null) {
      return (
        <span style={{ color: '#36d6ae', padding: '0 5px' }}>
          {trimString(selectedAddress, 10, 4)}
        </span>
      );
    }
    return '';
  }, [selectedAddress]);

  const onClickMMSigner = () => {
    setSelectNetwork('eth');
    setSelectMethod('MetaMask');
  };

  if (activeStep === STEP_INFO.STATE_INIT_NULL) {
    return (
      <ActionBarSteps>
        <BtnGrd onClick={() => navigate('/citizenship')} text="get citizenship" />
      </ActionBarSteps>
    );
  }

  if (activeStep === STEP_INFO.STATE_INIT_PROVE || activeStep === STEP_INFO.STATE_PROVE) {
    return (
      <ActionBarSteps>
        <BtnGrd
          disabled={isProve}
          onClick={() => setStepApp(STEP_INFO.STATE_PROVE_CONNECT)}
          text="prove address"
        />
      </ActionBarSteps>
    );
  }

  if (activeStep === STEP_INFO.STATE_INIT_CLAIM) {
    return (
      <ActionBarSteps>
        <BtnGrd onClick={() => setStepApp(STEP_INFO.STATE_CLAIME)} text="go to claim" />
      </ActionBarSteps>
    );
  }

  if (
    activeStep === STEP_INFO.STATE_CLAIME_TO_PROVE ||
    activeStep === STEP_INFO.STATE_GIFT_NULL_ALL
  ) {
    return (
      <ActionBarSteps gridGap="35px">
        <BtnGrd
          disabled={isProve}
          onClick={() => setStepApp(STEP_INFO.STATE_PROVE_CONNECT)}
          text="prove one more address"
        />
        <BtnGrd onClick={() => navigate('/teleport')} text={`buy ${BOOT_ICON}`} />
      </ActionBarSteps>
    );
  }

  if (
    activeStep === STEP_INFO.STATE_PROVE_CONNECT ||
    activeStep === STEP_INFO.STATE_PROVE_YOU_ADDED_ADDR
  ) {
    return (
      <ActionBarSteps
        onClickBack={() => setStepApp(STEP_INFO.STATE_INIT_PROVE)}
        button={{
          onClick: () =>
            setStepApp(
              selectMethod === 'MetaMask'
                ? STEP_INFO.STATE_PROVE_SIGN_MM
                : STEP_INFO.STATE_PROVE_SIGN
            ),
          text: 'connect',
          disabled: selectMethod === '',
        }}
      >
        <ButtonIcon
          onClick={() => onClickMMSigner()}
          active={selectMethod === 'MetaMask'}
          img={imgMetaMask}
          text="metaMask"
        />
      </ActionBarSteps>
    );
  }

  if (activeStep === STEP_INFO.STATE_PROVE_SIGN) {
    return (
      <ActionBarSteps
        onClickBack={() => setStepApp(STEP_INFO.STATE_PROVE_CONNECT)}
        button={{
          onClick: () => signMsgCosmos(),
          text: 'sign Moon Code',
          disabled: selectNetwork === '',
        }}
      >
        <ButtonIcon
          onClick={() => setSelectNetwork('cosmoshub')}
          active={selectNetwork === 'cosmoshub'}
          img={imgCosmos}
          text="cosmoshub"
        />
        <ButtonIcon
          onClick={() => setSelectNetwork('osmosis')}
          active={selectNetwork === 'osmosis'}
          img={imgOsmosis}
          text="osmosis"
        />

        {!isGiftPage && (
          <ButtonIcon
            onClick={() => setSelectNetwork('space-pussy')}
            active={selectNetwork === 'space-pussy'}
            img={imgSpacePussy}
            text="space pussy"
          />
        )}

        <ButtonIcon
          onClick={() => setSelectNetwork('columbus-5')}
          active={selectNetwork === 'columbus-5'}
          img={imgTerra}
          text="terra"
        />
      </ActionBarSteps>
    );
  }

  if (activeStep === STEP_INFO.STATE_PROVE_SIGN_MM) {
    return (
      <ActionBarSteps
        onClickBack={() => setStepApp(STEP_INFO.STATE_PROVE_CONNECT)}
        button={{
          onClick: () => signMsgETH(),
          text: 'sign Moon Code in metamask',
        }}
      >
        <ButtonIcon
          onClick={() => setSelectNetwork('eth')}
          active={selectNetwork === 'eth'}
          img={imgEth}
          text="eth"
        />
      </ActionBarSteps>
    );
  }

  if (activeStep === STEP_INFO.STATE_PROVE_CHECK_ACCOUNT) {
    return (
      <ActionBarSteps onClickBack={() => setStepApp(STEP_INFO.STATE_PROVE_CONNECT)}>
        address comparison <Dots />
      </ActionBarSteps>
    );
  }

  if (activeStep === STEP_INFO.STATE_PROVE_CHANGE_ACCOUNT) {
    return (
      <ActionBarSteps onClickBack={() => setStepApp(STEP_INFO.STATE_PROVE_CONNECT)}>
        choose {useAddressOwner} in your wallet
      </ActionBarSteps>
    );
  }

  if (activeStep === STEP_INFO.STATE_PROVE_SEND_SIGN) {
    return (
      <ActionBarSteps onClickBack={() => setStepApp(STEP_INFO.STATE_PROVE_CONNECT)}>
        <BtnGrd onClick={() => sendSignedMessage()} text="send signature of Moon Code" />
      </ActionBarSteps>
    );
  }

  if (loadingGift && activeStep === STEP_INFO.STATE_RELEASE) {
    return (
      <ActionBarSteps>
        <BtnGrd pending />
      </ActionBarSteps>
    );
  }

  if (
    (activeStep === STEP_INFO.STATE_INIT_RELEASE || activeStep === STEP_INFO.STATE_RELEASE) &&
    isClaimed
  ) {
    return (
      <ActionBarSteps>
        <BtnGrd onClick={() => setStepApp(STEP_INFO.STATE_RELEASE_INIT)} text="go to release" />
      </ActionBarSteps>
    );
  }

  if (activeStep === STEP_INFO.STATE_CLAIME) {
    return (
      <ActionBarSteps>
        <BtnGrd
          disabled={isProve}
          onClick={() => setStepApp(STEP_INFO.STATE_PROVE_CONNECT)}
          text="prove one more address"
        />
        <BtnGrd disabled={isClaime} onClick={() => claim()} text="claim" />
      </ActionBarSteps>
    );
  }
  if (activeStep === STEP_INFO.STATE_DELETE_ADDRESS) {
    return (
      <ActionBarSteps
        onClickBack={() => setStepApp(STEP_INFO.STATE_INIT)}
        button={{ onClick: useDeleteAddress, text: 'delete' }}
      >
        you want to delete {useGetSelectAddress} from your passport
      </ActionBarSteps>
    );
  }

  if (
    activeStep === STEP_INFO.STATE_CLAIM_IN_PROCESS ||
    activeStep === STEP_INFO.STATE_PROVE_IN_PROCESS ||
    activeStep === STEP_INFO.STATE_DELETE_IN_PROCESS
  ) {
    return (
      <ActionBarSteps>
        <BtnGrd pending />
      </ActionBarSteps>
    );
  }

  return null;
}

export default ActionBarPortalGift;

Synonyms

pussy-ts/src/containers/portal/gift/ActionBarPortalGift.tsx

Neighbours