Skip to content

Commit

Permalink
[Issue-3479] WebApp - Add validate for Solochain when receive, transf…
Browse files Browse the repository at this point in the history
…er with Generic ledger account
  • Loading branch information
Thiendekaco committed Aug 26, 2024
1 parent c149f3f commit 67eece2
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { approveSpending, getMaxTransfer, getOptimalTransferProcess, makeCrossCh
import { CommonActionType, commonProcessReducer, DEFAULT_COMMON_PROCESS } from '@subwallet/extension-web-ui/reducer';
import { RootState } from '@subwallet/extension-web-ui/stores';
import { ChainItemType, FormCallbacks, Theme, ThemeProps, TransferParams } from '@subwallet/extension-web-ui/types';
import { findAccountByAddress, formatBalance, noop, reformatAddress, transactionDefaultFilterAccount } from '@subwallet/extension-web-ui/utils';
import { findAccountByAddress, formatBalance, ledgerMustCheckNetwork, noop, reformatAddress, transactionDefaultFilterAccount } from '@subwallet/extension-web-ui/utils';
import { findNetworkJsonByGenesisHash } from '@subwallet/extension-web-ui/utils/chain/getNetworkJsonByGenesisHash';
import { Button, Form, Icon } from '@subwallet/react-ui';
import { Rule } from '@subwallet/react-ui/es/form';
Expand Down Expand Up @@ -248,7 +248,7 @@ const _SendFund = ({ className = '', modalContent }: Props): React.ReactElement<
const assetInfo = useFetchChainAssetInfo(asset);
const { alertProps, closeAlert, openAlert } = useAlert(alertModalId);

const { chainInfoMap, chainStatusMap } = useSelector((root) => root.chainStore);
const { chainInfoMap, chainStatusMap, ledgerGenericAllowNetworks } = useSelector((root) => root.chainStore);
const { assetRegistry, assetSettingMap, multiChainAssetMap, xcmRefMap } = useSelector((root) => root.assetRegistry);
const { accounts, isAllAccount } = useSelector((state: RootState) => state.accountState);
const [maxTransfer, setMaxTransfer] = useState<string>('0');
Expand Down Expand Up @@ -389,10 +389,16 @@ const _SendFund = ({ className = '', modalContent }: Props): React.ReactElement<

return Promise.reject(t('Wrong network. Your Ledger account is not supported by {{network}}. Please choose another receiving account and try again.', { replace: { network: destChainName } }));
}

const ledgerCheck = ledgerMustCheckNetwork(account);

if (ledgerCheck !== 'unnecessary' && !ledgerGenericAllowNetworks.includes(destChainInfo.slug)) {
return Promise.reject(t('Ledger {{ledgerApp}} address is not supported for this transfer', { replace: { ledgerApp: ledgerCheck === 'polkadot' ? 'Polkadot' : 'Migration' } }));
}
}

return Promise.resolve();
}, [accounts, chainInfoMap, form, t]);
}, [accounts, chainInfoMap, form, ledgerGenericAllowNetworks, t]);

const validateAmount = useCallback((rule: Rule, amount: string): Promise<void> => {
if (!amount) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,17 @@

import { _ChainAsset } from '@subwallet/chain-list/types';
import { _MANTA_ZK_CHAIN_GROUP, _ZK_ASSET_PREFIX } from '@subwallet/extension-base/services/chain-service/constants';
import { TokenEmptyList, TokenSelectionItem } from '@subwallet/extension-web-ui/components';
import { BaseModal } from '@subwallet/extension-web-ui/components/Modal/BaseModal';
import { TokenSelectionItem } from '@subwallet/extension-web-ui/components/TokenItem/TokenSelectionItem';
import { RECEIVE_QR_MODAL, RECEIVE_TOKEN_SELECTOR_MODAL } from '@subwallet/extension-web-ui/constants/modal';
import { useSelector } from '@subwallet/extension-web-ui/hooks';
import { useGetZkAddress } from '@subwallet/extension-web-ui/hooks/account/useGetZkAddress';
import useTranslation from '@subwallet/extension-web-ui/hooks/common/useTranslation';
import { RECEIVE_QR_MODAL, RECEIVE_TOKEN_SELECTOR_MODAL, WARNING_LEDGER_RECEIVE_MODAL } from '@subwallet/extension-web-ui/constants';
import { useConfirmModal, useGetAccountByAddress, useGetZkAddress, useSelector, useTranslation } from '@subwallet/extension-web-ui/hooks';
import { ThemeProps } from '@subwallet/extension-web-ui/types';
import { ModalContext, SwList } from '@subwallet/react-ui';
import { ledgerMustCheckNetwork } from '@subwallet/extension-web-ui/utils';
import { ModalContext, SwList, SwModalFuncProps } from '@subwallet/react-ui';
import { SwListSectionRef } from '@subwallet/react-ui/es/sw-list';
import React, { useCallback, useContext, useEffect, useRef } from 'react';
import React, { useCallback, useContext, useEffect, useMemo, useRef } from 'react';
import styled from 'styled-components';

import { TokenEmptyList } from '../../EmptyList';

interface Props extends ThemeProps {
onSelectItem?: (item: _ChainAsset) => void,
address?: string,
Expand All @@ -33,8 +30,29 @@ function Component ({ address, className = '', items, onSelectItem }: Props): Re

const zkAddress = useGetZkAddress(address);

const { chainInfoMap } = useSelector((state) => state.chainStore);
const { chainInfoMap, ledgerGenericAllowNetworks } = useSelector((state) => state.chainStore);

const account = useGetAccountByAddress(address);

const ledgerCheck = useMemo(() => ledgerMustCheckNetwork(account), [account]);

const confirmModalProps = useMemo((): SwModalFuncProps => ({
id: WARNING_LEDGER_RECEIVE_MODAL,
title: t<string>('Unsupported network'),
maskClosable: true,
closable: true,
subTitle: t<string>('Do you still want to get the address?'),
okText: t<string>('Get address'),
okCancel: true,
type: 'warn',
cancelButtonProps: {
children: t<string>('Cancel'),
schema: 'secondary'
},
className: 'ledger-warning-modal'
}), [t]);

const { handleSimpleConfirmModal } = useConfirmModal(confirmModalProps);
const isActive = checkActive(modalId);

const sectionRef = useRef<SwListSectionRef>(null);
Expand All @@ -56,12 +74,35 @@ function Component ({ address, className = '', items, onSelectItem }: Props): Re

const onClickQrBtn = useCallback((item: _ChainAsset) => {
return () => {
if (ledgerCheck !== 'unnecessary' && !ledgerGenericAllowNetworks.includes(item.originChain)) {
handleSimpleConfirmModal({
content: t<string>(
'Ledger {{ledgerApp}} accounts are NOT compatible with {{networkName}} network. Tokens will get stuck (i.e., can’t be transferred out or staked) when sent to this account type.',
{
replace: {
ledgerApp: ledgerCheck === 'polkadot' ? 'Polkadot' : 'Migration',
networkName: chainInfoMap[item.originChain]?.name
}
}
)
})
.then(() => {
onSelectItem && onSelectItem(item);
// checkAsset(item.slug);
inactiveModal(modalId);
activeModal(RECEIVE_QR_MODAL);
})
.catch(console.error);

return;
}

onSelectItem && onSelectItem(item);
// checkAsset(item.slug);
inactiveModal(modalId);
activeModal(RECEIVE_QR_MODAL);
};
}, [activeModal, inactiveModal, onSelectItem]);
}, [activeModal, chainInfoMap, handleSimpleConfirmModal, inactiveModal, ledgerCheck, ledgerGenericAllowNetworks, onSelectItem, t]);

useEffect(() => {
if (!isActive) {
Expand All @@ -70,9 +111,24 @@ function Component ({ address, className = '', items, onSelectItem }: Props): Re
}, 100);
}
}, [isActive]);

const onPreCopy = useCallback((item: _ChainAsset, ledgerCheck: string) => {
return () => {
return handleSimpleConfirmModal({
content: t<string>(
'Ledger {{ledgerApp}} accounts are NOT compatible with {{networkName}} network. Tokens will get stuck (i.e., can’t be transferred out or staked) when sent to this account type.',
{
replace: {
ledgerApp: ledgerCheck === 'polkadot' ? 'Polkadot' : 'Migration',
networkName: chainInfoMap[item.originChain]?.name
}
}
)
});
};
}, [chainInfoMap, handleSimpleConfirmModal, t]);
const renderItem = useCallback((item: _ChainAsset) => {
const isMantaZkAsset = _MANTA_ZK_CHAIN_GROUP.includes(item.originChain) && item.symbol.startsWith(_ZK_ASSET_PREFIX);
const needConfirm = ledgerCheck !== 'unnecessary' && !ledgerGenericAllowNetworks.includes(item.originChain);

return (
<TokenSelectionItem
Expand All @@ -81,10 +137,19 @@ function Component ({ address, className = '', items, onSelectItem }: Props): Re
item={item}
key={item.slug}
onClickQrBtn={onClickQrBtn(item)}
onPreCopy={needConfirm ? onPreCopy(item, ledgerCheck) : undefined}
onPressItem={onClickQrBtn(item)}
/>
);
}, [address, onClickQrBtn, zkAddress]);
}, [address, ledgerCheck, ledgerGenericAllowNetworks, onClickQrBtn, onPreCopy, zkAddress]);

useEffect(() => {
if (!isActive) {
setTimeout(() => {
sectionRef.current?.setSearchValue('');
}, 100);
}
}, [isActive]);

return (
<BaseModal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { _getChainSubstrateAddressPrefix } from '@subwallet/extension-base/servi
import { ScreenContext } from '@subwallet/extension-web-ui/contexts/ScreenContext';
import { useFetchChainInfo, useNotification, useTranslation } from '@subwallet/extension-web-ui/hooks';
import { ThemeProps } from '@subwallet/extension-web-ui/types';
import { copyToClipboard, noop } from '@subwallet/extension-web-ui/utils';
import reformatAddress from '@subwallet/extension-web-ui/utils/account/reformatAddress';
import { Button, Icon } from '@subwallet/react-ui';
import TokenItem, { TokenItemProps } from '@subwallet/react-ui/es/web3-block/token-item';
Expand All @@ -21,10 +22,11 @@ interface Props extends ThemeProps, Omit<TokenItemProps, 'name' | 'subName' | 's
item: _ChainAsset;
onClickCopyBtn?: () => void;
onClickQrBtn?: () => void;
onPreCopy?: () => Promise<void>;
}

const Component = (props: Props) => {
const { address, className, item, onClickCopyBtn, onClickQrBtn, onPressItem, ...restProps } = props;
const { address, className, item, onClickCopyBtn, onClickQrBtn, onPreCopy, onPressItem, ...restProps } = props;
const { name, originChain: chain, slug, symbol } = item;
const chainInfo = useFetchChainInfo(chain || '');
const notify = useNotification();
Expand All @@ -42,8 +44,22 @@ const Component = (props: Props) => {
return reformatAddress(address || '', networkPrefix, isEvmChain);
}, [address, chainInfo, symbol]);

const _onClickCopyBtnAsync = useCallback((e: React.SyntheticEvent) => {
e.stopPropagation();
onPreCopy?.()
.then(() => {
notify({
message: t('Copied to clipboard')
});
copyToClipboard(formattedAddress);
onClickCopyBtn && onClickCopyBtn();
})
.catch(noop);
}, [formattedAddress, notify, onClickCopyBtn, onPreCopy, t]);

const _onCLickCopyBtn = useCallback((e: React.SyntheticEvent) => {
e.stopPropagation();

notify({
message: t('Copied to clipboard')
});
Expand Down Expand Up @@ -81,20 +97,39 @@ const Component = (props: Props) => {
rightItem={
(
<>
<CopyToClipboard text={formattedAddress}>
<Button
icon={
<Icon
phosphorIcon={Copy}
size='sm'
{
onPreCopy
? (
<Button
icon={
<Icon
phosphorIcon={Copy}
size='sm'
/>
}
onClick={_onClickCopyBtnAsync}
size='xs'
tooltip={isWebUI ? t('Copy address') : undefined}
type='ghost'
/>
}
onClick={_onCLickCopyBtn}
size='xs'
tooltip={isWebUI ? t('Copy address') : undefined}
type='ghost'
/>
</CopyToClipboard>
)
: (
<CopyToClipboard text={formattedAddress}>
<Button
icon={
<Icon
phosphorIcon={Copy}
size='sm'
/>
}
onClick={_onCLickCopyBtn}
size='xs'
tooltip={isWebUI ? t('Copy address') : undefined}
type='ghost'
/>
</CopyToClipboard>
)
}
<Button
disabled={_MANTA_ZK_CHAIN_GROUP.includes(chainInfo.slug) && symbol?.startsWith(_ZK_ASSET_PREFIX)}
icon={
Expand Down Expand Up @@ -144,20 +179,11 @@ export const TokenSelectionItem = styled(Component)<Props>(({ theme: { token } }
fontWeight: token.fontWeightStrong,
color: token.colorWhite,

'.__symbol': {
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap'
},

'.__token-name': {
color: token.colorTextTertiary,
display: 'flex',
flexDirection: 'row',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
flex: 1,

'.name': {
textOverflow: 'ellipsis',
Expand Down
4 changes: 4 additions & 0 deletions packages/extension-web-ui/src/constants/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export const EXPORT_ACCOUNTS_PASSWORD_MODAL = 'export-accounts-password-modal';

/* Extension */

/* Ledger */
export const WARNING_LEDGER_RECEIVE_MODAL = 'warning-ledger-receive-modal';
/* Ledger */

/* Earning */

export const STAKING_PROCESS_MODAL = 'staking-process-modal';
Expand Down
3 changes: 2 additions & 1 deletion packages/extension-web-ui/src/contexts/DataContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import { persistor, store, StoreName } from '@subwallet/extension-web-ui/stores';
import { getDAppsData, getMissionPoolData, subscribeAccountsData, subscribeAddressBook, subscribeAssetLogoMaps, subscribeAssetRegistry, subscribeAssetSettings, subscribeAuthorizeRequests, subscribeAuthUrls, subscribeBalance, subscribeBuyServices, subscribeBuyTokens, subscribeCampaignPopupVisibility, subscribeChainInfoMap, subscribeChainLogoMaps, subscribeChainStateMap, subscribeChainStatusMap, subscribeConfirmationRequests, subscribeConnectWCRequests, subscribeCrowdloan, subscribeKeyringState, subscribeMantaPayConfig, subscribeMantaPaySyncingState, subscribeMetadataRequests, subscribeMultiChainAssetMap, subscribeNftCollections, subscribeNftItems, subscribePrice, subscribeProcessingCampaign, subscribeRewardHistory, subscribeSigningRequests, subscribeSwapPairs, subscribeTransactionRequests, subscribeTxHistory, subscribeUiSettings, subscribeWalletConnectSessions, subscribeWCNotSupportRequests, subscribeXcmRefMap, subscribeYieldMinAmountPercent, subscribeYieldPoolInfo, subscribeYieldPositionInfo, subscribeYieldReward } from '@subwallet/extension-web-ui/stores/utils';
import { getDAppsData, getMissionPoolData, subscribeAccountsData, subscribeAddressBook, subscribeAssetLogoMaps, subscribeAssetRegistry, subscribeAssetSettings, subscribeAuthorizeRequests, subscribeAuthUrls, subscribeBalance, subscribeBuyServices, subscribeBuyTokens, subscribeCampaignPopupVisibility, subscribeChainInfoMap, subscribeChainLogoMaps, subscribeChainStateMap, subscribeChainStatusMap, subscribeConfirmationRequests, subscribeConnectWCRequests, subscribeCrowdloan, subscribeKeyringState, subscribeLedgerGenericAllowNetworks, subscribeMantaPayConfig, subscribeMantaPaySyncingState, subscribeMetadataRequests, subscribeMultiChainAssetMap, subscribeNftCollections, subscribeNftItems, subscribePrice, subscribeProcessingCampaign, subscribeRewardHistory, subscribeSigningRequests, subscribeSwapPairs, subscribeTransactionRequests, subscribeTxHistory, subscribeUiSettings, subscribeWalletConnectSessions, subscribeWCNotSupportRequests, subscribeXcmRefMap, subscribeYieldMinAmountPercent, subscribeYieldPoolInfo, subscribeYieldPositionInfo, subscribeYieldReward } from '@subwallet/extension-web-ui/stores/utils';
import Bowser from 'bowser';
import React from 'react';
import { Provider } from 'react-redux';
Expand Down Expand Up @@ -197,6 +197,7 @@ export const DataContextProvider = ({ children }: DataContextProviderProps) => {
_DataContext.addHandler({ ...subscribeChainStateMap, name: 'subscribeChainStateMap', relatedStores: ['chainStore'], isStartImmediately: true });
_DataContext.addHandler({ ...subscribeChainStatusMap, name: 'subscribeChainStatusMap', relatedStores: ['chainStore'], isStartImmediately: true });
_DataContext.addHandler({ ...subscribeChainInfoMap, name: 'subscribeChainInfoMap', relatedStores: ['chainStore'], isStartImmediately: true });
_DataContext.addHandler({ ...subscribeLedgerGenericAllowNetworks, name: 'subscribeLedgerGenericAllowNetworks', relatedStores: ['chainStore'], isStartImmediately: true });
_DataContext.addHandler({ ...subscribeAssetRegistry, name: 'subscribeAssetRegistry', relatedStores: ['assetRegistry'], isStartImmediately: true });
_DataContext.addHandler({ ...subscribeMultiChainAssetMap, name: 'subscribeMultiChainAssetMap', relatedStores: ['assetRegistry'], isStartImmediately: true });
_DataContext.addHandler({ ...subscribeAssetSettings, name: 'subscribeAssetSettings', relatedStores: ['assetRegistry'], isStartImmediately: true });
Expand Down
14 changes: 14 additions & 0 deletions packages/extension-web-ui/src/contexts/ThemeContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,20 @@ const GlobalStyle = createGlobalStyle<ThemeProps>(({ theme }) => {
}
},

'.ledger-warning-modal': {
'.ant-sw-modal-confirm-btns': {
flexDirection: 'row',

button: {
flex: 1,

'.anticon': {
display: 'none'
}
}
}
},

'.ant-sw-header-left-part + .ant-sw-header-center-part .ant-sw-sub-header-title': {
display: 'block',
textAlign: 'center'
Expand Down
3 changes: 2 additions & 1 deletion packages/extension-web-ui/src/hooks/modal/useConfirmModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ export default function useConfirmModal (props: Partial<SwModalFuncProps>) {
const confirmationModalId = props.id || CONFIRMATION_MODAL_ID;
const { isWebUI } = useContext(ScreenContext);

const handleSimpleConfirmModal = useCallback(() => new Promise<void>((resolve, reject) => {
const handleSimpleConfirmModal = useCallback((_props?: Partial<SwModalFuncProps>) => new Promise<void>((resolve, reject) => {
addConfirmModal({
...props,
..._props,
id: confirmationModalId,
width: props.width || !isWebUI ? '100%' : undefined,
className: CN('general-modal', {
Expand Down
Loading

0 comments on commit 67eece2

Please sign in to comment.