Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/openbit-dev-issue-64' into openb…
Browse files Browse the repository at this point in the history
…it-dev
  • Loading branch information
dungnguyen-art committed May 30, 2024
2 parents 9be9daa + f1a8840 commit dee450a
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -245,11 +245,12 @@ function Component (): React.ReactElement {

setStorage({
...DEFAULT_TRANSFER_PARAMS,
defaultSlug: tokenGroupSlug || '',
fromProxyId
});
navigate('/transaction/send-fund');
},
[currentAccountProxy, navigate, setStorage]
[currentAccountProxy, navigate, setStorage, tokenGroupSlug]
);

const onOpenBuyTokens = useCallback(() => {
Expand Down
217 changes: 101 additions & 116 deletions packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
// Copyright 2019-2022 @polkadot/extension-koni-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { _AssetRef, _AssetType, _ChainAsset, _ChainInfo, _MultiChainAsset } from '@subwallet/chain-list/types';
import { _AssetType, _ChainAsset, _ChainInfo, _MultiChainAsset } from '@subwallet/chain-list/types';
import { ExtrinsicType, NotificationType } from '@subwallet/extension-base/background/KoniTypes';
import { AbstractAddressJson, AccountJson } from '@subwallet/extension-base/background/types';
import { _getAssetDecimals, _getOriginChainOfAsset, _getTokenMinAmount, _isAssetFungibleToken, _isChainEvmCompatible, _isNativeToken, _isTokenTransferredByEvm } from '@subwallet/extension-base/services/chain-service/utils';
import { _getAssetDecimals, _getTokenMinAmount, _isNativeToken, _isPureBitcoinChain, _isPureEvmChain, _isTokenTransferredByEvm } from '@subwallet/extension-base/services/chain-service/utils';
import { SWTransactionResponse } from '@subwallet/extension-base/services/transaction-service/types';
import { BitcoinFeeDetail, ResponseSubscribeTransfer, TransactionFee } from '@subwallet/extension-base/types';
import { BN_ZERO, detectTranslate } from '@subwallet/extension-base/utils';
import { AccountSelector, AddressInput, AlertBox, AlertModal, AmountInput, BitcoinFeeSelector, ChainSelector, HiddenInput, TokenItemType, TokenSelector } from '@subwallet/extension-koni-ui/components';
import { BITCOIN_CHAINS, SUPPORT_CHAINS } from '@subwallet/extension-koni-ui/constants';
import { AccountSelector, AddressInput, AlertBox, AlertModal, AmountInput, BitcoinFeeSelector, HiddenInput, TokenItemType, TokenSelector } from '@subwallet/extension-koni-ui/components';
import { BITCOIN_CHAINS } from '@subwallet/extension-koni-ui/constants';
import { useAlert, useFetchChainAssetInfo, useGetChainPrefixBySlug, useGetNativeTokenBasicInfo, useHandleSubmitTransaction, useInitValidateTransaction, useNotification, usePreCheckAction, useRestoreTransaction, useSelector, useSetCurrentPage, useTransactionContext, useWatchTransaction } from '@subwallet/extension-koni-ui/hooks';
import { cancelSubscription, makeCrossChainTransfer, makeTransfer, subscribeMaxTransfer } from '@subwallet/extension-koni-ui/messaging';
import { FreeBalance } from '@subwallet/extension-koni-ui/Popup/Transaction/parts';
import { RootState } from '@subwallet/extension-koni-ui/stores';
import { ChainItemType, FormCallbacks, Theme, ThemeProps, TransferParams } from '@subwallet/extension-koni-ui/types';
import { FormCallbacks, Theme, ThemeProps, TransferParams } from '@subwallet/extension-koni-ui/types';
import { findAccountByAddress, formatBalance, noop, reformatAddress } from '@subwallet/extension-koni-ui/utils';
import { getKeypairTypeByAddress } from '@subwallet/keyring';
import { KeypairType } from '@subwallet/keyring/types';
import { Button, Form, Icon, Number } from '@subwallet/react-ui';
import { Rule } from '@subwallet/react-ui/es/form';
import BigN from 'bignumber.js';
import CN from 'classnames';
import { PaperPlaneRight, PaperPlaneTilt } from 'phosphor-react';
import { PaperPlaneTilt } from 'phosphor-react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
Expand All @@ -34,31 +33,26 @@ import { TransactionContent, TransactionFooter } from '../parts';

type Props = ThemeProps;

function checkValidBetweenAddressTypeAndChain (addressType: KeypairType, chain: string): boolean {
if (chain === 'bitcoin') {
return ['bitcoin-44', 'bitcoin-84'].includes(addressType);
}

if (chain === 'bitcoinTestnet') {
return ['bittest-44', 'bittest-84'].includes(addressType);
}

return chain === 'ethereum' && addressType === 'ethereum';
}
// function checkValidBetweenAddressTypeAndChain (addressType: KeypairType, chain: string): boolean {
// if (chain === 'bitcoin') {
// return ['bitcoin-44', 'bitcoin-84'].includes(addressType);
// }
//
// if (chain === 'bitcoinTestnet') {
// return ['bittest-44', 'bittest-84'].includes(addressType);
// }
//
// return chain === 'ethereum' && addressType === 'ethereum';
// }

function getTokenItems (
address: string,
chainInfoMap: Record<string, _ChainInfo>,
assetRegistry: Record<string, _ChainAsset>,
multiChainAssetMap: Record<string, _MultiChainAsset>,
tokenGroupSlug?: string // is ether a token slug or a multiChainAsset slug
): TokenItemType[] {
if (!address) {
return [];
}

const isSetTokenSlug = !!tokenGroupSlug && !!assetRegistry[tokenGroupSlug];
const isSetMultiChainAssetSlug = !!tokenGroupSlug && !!multiChainAssetMap[tokenGroupSlug];
const addressType = getKeypairTypeByAddress(address);

if (tokenGroupSlug) {
if (!(isSetTokenSlug || isSetMultiChainAssetSlug)) {
Expand All @@ -70,10 +64,6 @@ function getTokenItems (
if (isSetTokenSlug && chainAsset) {
const { name, originChain, slug, symbol } = chainAsset;

if (!checkValidBetweenAddressTypeAndChain(addressType, originChain)) {
return [];
}

return [
{
name,
Expand All @@ -88,75 +78,66 @@ function getTokenItems (
const items: TokenItemType[] = [];

Object.values(assetRegistry).forEach((chainAsset) => {
const isTokenFungible = _isAssetFungibleToken(chainAsset);
const chainInfo = chainInfoMap[chainAsset.originChain];

if (!(isTokenFungible && _isNativeToken(chainAsset) && SUPPORT_CHAINS.includes(chainAsset.originChain))) {
if (chainAsset.assetType === 'RUNE' || chainAsset.assetType === 'BRC20') {
return;
}

if (!checkValidBetweenAddressTypeAndChain(addressType, chainAsset.originChain)) {
return;
}

if (isSetMultiChainAssetSlug) {
if (chainAsset.multiChainAsset === tokenGroupSlug) {
if (_isPureBitcoinChain(chainInfo) || _isPureEvmChain(chainInfo)) {
if (isSetMultiChainAssetSlug) {
if (chainAsset.multiChainAsset === tokenGroupSlug) {
items.push({
name: chainAsset.name,
slug: chainAsset.slug,
symbol: chainAsset.symbol,
originChain: chainAsset.originChain
});
}
} else {
items.push({
name: chainAsset.name,
slug: chainAsset.slug,
symbol: chainAsset.symbol,
originChain: chainAsset.originChain
});
}
} else {
items.push({
name: chainAsset.name,
slug: chainAsset.slug,
symbol: chainAsset.symbol,
originChain: chainAsset.originChain
});
}
});

return items.sort((a, b) => {
if (a.originChain.toLowerCase() === 'bitcoin' || a.originChain.toLowerCase() === 'bitcoinTestnet') {
return -1; // Place 'bitcoin' or 'BitcoinTestnet' at the top
} else if (b.originChain.toLowerCase() === 'bitcoin' || b.originChain.toLowerCase() === 'bitcoinTestnet') {
return 1;
} else {
return a.originChain.localeCompare(b.originChain);
const aChain = a.originChain.toLowerCase();
const bChain = b.originChain.toLowerCase();

if (aChain === 'bitcoin') {
return -1;
}
});
}

function getTokenAvailableDestinations (tokenSlug: string, xcmRefMap: Record<string, _AssetRef>, chainInfoMap: Record<string, _ChainInfo>): ChainItemType[] {
if (!tokenSlug) {
return [];
}
if (bChain === 'bitcoin') {
return 1;
}

const result: ChainItemType[] = [];
const originChain = chainInfoMap[_getOriginChainOfAsset(tokenSlug)];
if (aChain === 'bitcointestnet') {
return -1;
}

// Firstly, push the originChain of token
result.push({
name: originChain.name,
slug: originChain.slug
});
if (bChain === 'bitcointestnet') {
return 1;
}

Object.values(xcmRefMap).forEach((xcmRef) => {
if (xcmRef.srcAsset === tokenSlug) {
const destinationChain = chainInfoMap[xcmRef.destChain];
if (aChain === 'ethereum' && a.name.toLowerCase() === 'ethereum') {
return -1;
}

result.push({
name: destinationChain.name,
slug: destinationChain.slug
});
if (bChain === 'ethereum' && b.name.toLowerCase() === 'ethereum') {
return 1;
}
});

return result;
return a.originChain.localeCompare(b.originChain);
});
}

const hiddenFields: Array<keyof TransferParams> = ['chain', 'fromProxyId'];
const hiddenFields: Array<keyof TransferParams> = ['chain', 'fromProxyId', 'destChain'];
const validateFields: Array<keyof TransferParams> = ['value', 'to'];
const alertModalId = 'confirmation-alert-modal';

Expand Down Expand Up @@ -185,9 +166,8 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement<Props> => {

const assetInfo = useFetchChainAssetInfo(assetValue);
const { alertProps, closeAlert, openAlert } = useAlert(alertModalId);

const { chainInfoMap, chainStatusMap } = useSelector((root) => root.chainStore);
const { assetRegistry, multiChainAssetMap, xcmRefMap } = useSelector((root) => root.assetRegistry);
const { assetRegistry, multiChainAssetMap } = useSelector((root) => root.assetRegistry);
const { accounts } = useSelector((state: RootState) => state.accountState);

const destChainNetworkPrefix = useGetChainPrefixBySlug(destChainValue);
Expand All @@ -201,15 +181,11 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement<Props> => {
const hideMaxButton = useMemo(() => {
const chainInfo = chainInfoMap[chainValue];

return !!chainInfo && !!assetInfo && _isChainEvmCompatible(chainInfo) && destChainValue === chainValue && _isNativeToken(assetInfo);
return !!chainInfo && !!assetInfo && _isPureEvmChain(chainInfo) && destChainValue === chainValue && _isNativeToken(assetInfo);
}, [chainInfoMap, chainValue, destChainValue, assetInfo]);

const chainStatus = useMemo(() => chainStatusMap[chainValue]?.connectionStatus, [chainValue, chainStatusMap]);

const destChainItems = useMemo<ChainItemType[]>(() => {
return getTokenAvailableDestinations(assetValue, xcmRefMap, chainInfoMap);
}, [chainInfoMap, assetValue, xcmRefMap]);

const currentChainAsset = useMemo(() => {
const _asset = isFirstRender ? defaultData.asset : assetValue;

Expand Down Expand Up @@ -238,12 +214,12 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement<Props> => {

const tokenItems = useMemo<TokenItemType[]>(() => {
return getTokenItems(
fromValue,
chainInfoMap,
assetRegistry,
multiChainAssetMap,
sendFundSlug
);
}, [assetRegistry, fromValue, multiChainAssetMap, sendFundSlug]);
}, [assetRegistry, chainInfoMap, multiChainAssetMap, sendFundSlug]);

const [loading, setLoading] = useState(false);
const [isTransferAll, setIsTransferAll] = useState(false);
Expand Down Expand Up @@ -331,13 +307,6 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement<Props> => {
}
}

if (part.from) {
setForceUpdateMaxValue(undefined);
form.resetFields(['asset']);
// Because cache data, so next data may be same with default data
form.setFields([{ name: 'asset', value: '' }]);
}

if (part.destChain) {
setForceUpdateMaxValue(isTransferAll ? {} : undefined);

Expand Down Expand Up @@ -504,8 +473,18 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement<Props> => {
const addressBookFilter = useCallback((addressJson: AbstractAddressJson): boolean => {
const addressType = getKeypairTypeByAddress(addressJson.address);

return ['ethereum', 'bitcoin-84', 'bittest-84'].includes(addressType);
}, []);
const chainInfo = chainInfoMap[chainValue];

if (chainValue === 'bitcoin') {
return 'bitcoin-84'.includes(addressType) && addressJson.address !== fromValue;
} else if (chainValue === 'bitcoinTestnet') {
return 'bittest-84'.includes(addressType) && addressJson.address !== fromValue;
} else if (!!chainInfo && _isPureEvmChain(chainInfo)) {
return 'ethereum'.includes(addressType) && addressJson.address !== fromValue;
}

return false;
}, [chainInfoMap, chainValue, fromValue]);

// TODO: Need to review
// Auto fill logic
Expand Down Expand Up @@ -604,9 +583,22 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement<Props> => {
}

const accountType = account.type || getKeypairTypeByAddress(account.address);
const chainInfo = chainInfoMap[chainValue];

if (chainValue === 'bitcoin') {
return 'bitcoin-84'.includes(accountType);
} else if (chainValue === 'bitcoinTestnet') {
return 'bittest-84'.includes(accountType);
} else if (!!chainInfo && _isPureEvmChain(chainInfo)) {
return 'ethereum'.includes(accountType);
}

return ['ethereum', 'bitcoin-84', 'bittest-84'].includes(accountType);
}, [fromProxyId]);
return false;
}, [chainInfoMap, chainValue, fromProxyId]);

const accountList = useMemo(() => {
return accounts.filter(accountsFilter);
}, [accounts, accountsFilter]);

useEffect(() => {
const bnTransferAmount = new BigN(transferAmountValue || '0');
Expand All @@ -617,6 +609,14 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement<Props> => {
}
}, [transferInfo, transferAmountValue]);

useEffect(() => {
if (accountList.length === 1) {
form.setFieldsValue({
from: accountList?.[0]?.address || ''
});
}
}, [accountList, form]);

useRestoreTransaction(form);
useInitValidateTransaction(validateFields, form, defaultData);

Expand All @@ -634,15 +634,6 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement<Props> => {
onFinish={onSubmit}
onValuesChange={onValuesChange}
>
<Form.Item
name={'from'}
>
<AccountSelector
filter={accountsFilter}
label={t('Send from')}
/>
</Form.Item>

<div className={'form-row'}>
<Form.Item name={'asset'}>
<TokenSelector
Expand All @@ -653,22 +644,16 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement<Props> => {
tooltip={t('Select token')}
/>
</Form.Item>

<Icon
className={'middle-item'}
phosphorIcon={PaperPlaneRight}
size={'md'}
/>

<Form.Item name={'destChain'}>
<ChainSelector
disabled={!destChainItems.length}
items={destChainItems}
title={t('Select destination chain')}
tooltip={t('Select destination chain')}
/>
</Form.Item>
</div>
<Form.Item
name={'from'}
>
<AccountSelector
disabled={accountList.length === 1}
externalAccounts={accountList}
label={t('Send from')}
/>
</Form.Item>

<HiddenInput fields={hiddenFields} />

Expand Down

1 comment on commit dee450a

@saltict
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.