diff --git a/src/components/Earning/EarningBaseInfo/parts/EarningAccountInfo/index.tsx b/src/components/Earning/EarningBaseInfo/parts/EarningAccountInfo/index.tsx index 4636f9c0b..57f60e601 100644 --- a/src/components/Earning/EarningBaseInfo/parts/EarningAccountInfo/index.tsx +++ b/src/components/Earning/EarningBaseInfo/parts/EarningAccountInfo/index.tsx @@ -6,7 +6,6 @@ import { YieldPoolType, YieldPositionInfo, } from '@subwallet/extension-base/types'; -import { isSameAddress } from '@subwallet/extension-base/utils'; import BigN from 'bignumber.js'; import { Button, Icon, Typography } from 'components/design-system-ui'; import MetaInfo from 'components/MetaInfo'; @@ -25,6 +24,7 @@ import i18n from 'utils/i18n/i18n'; import { findAccountByAddress, toShort } from 'utils/index'; import createStyles from './styles'; import { AccountProxyAvatar } from 'components/design-system-ui/avatar/account-proxy-avatar'; +import { isSameAddress } from 'utils/account/account'; type Props = { compound: YieldPositionInfo; diff --git a/src/components/common/StakingValidatorItem/index.tsx b/src/components/common/StakingValidatorItem/index.tsx index c5dfd2043..aab80b4ba 100644 --- a/src/components/common/StakingValidatorItem/index.tsx +++ b/src/components/common/StakingValidatorItem/index.tsx @@ -5,11 +5,11 @@ import { FontMedium } from 'styles/sharedStyles'; import { CheckCircle, DotsThree, Medal } from 'phosphor-react-native'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import StakingValidatorItemStyle from './style'; -import { ValidatorDataType } from 'hooks/screen/Staking/useGetValidatorList'; import { isEthereumAddress } from '@polkadot/util-crypto'; import { toShort } from 'utils/index'; import { getValidatorKey } from 'utils/transaction/stake'; import i18n from 'utils/i18n/i18n'; +import { ValidatorDataType } from 'types/earning'; interface Props { validatorInfo: ValidatorDataType; diff --git a/src/constants/localStorage.ts b/src/constants/localStorage.ts index 8c264e1ca..595aa2230 100644 --- a/src/constants/localStorage.ts +++ b/src/constants/localStorage.ts @@ -1,6 +1,7 @@ export const SELECTED_MNEMONIC_TYPE = 'account.selected-mnemonic-type'; export const SEED_PREVENT_MODAL = 'seed.prevent-modal'; export const CONFIRM_TERM_SEED_PHRASE = 'seed-phrase.term-and-condition'; +export const EARNING_WARNING_ANNOUNCEMENT = 'announcement.earning-position'; export const TRANSFER_TRANSACTION = 'transaction.transfer'; export const NFT_TRANSACTION = 'transaction.nft'; @@ -11,4 +12,6 @@ export const UN_STAKE_TRANSACTION = 'transaction.un-stake'; export const CANCEL_UN_STAKE_TRANSACTION = 'transaction.cancel-un-stake'; export const WITHDRAW_TRANSACTION = 'transaction.withdraw'; export const CLAIM_REWARD_TRANSACTION = 'transaction.claim-reward'; +export const IMPORT_NFT = 'import.nft'; +export const IMPORT_TOKEN = 'import.token'; export const CLAIM_AVAIL_BRIDGE_TRANSACTION = 'transaction.claim-avail-bridge'; diff --git a/src/hooks/account/usePreCheckAction.ts b/src/hooks/account/usePreCheckAction.ts index 43b333a35..b7cd84a73 100644 --- a/src/hooks/account/usePreCheckAction.ts +++ b/src/hooks/account/usePreCheckAction.ts @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 import { ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes'; -import { AccountJson } from '@subwallet/extension-base/background/types'; import { useCallback } from 'react'; import { VoidFunction } from 'types/index'; @@ -13,6 +12,7 @@ import { ALL_STAKING_ACTIONS } from 'constants/transaction'; import { useToast } from 'react-native-toast-notifications'; import { isEthereumAddress } from '@polkadot/util-crypto'; import { getDevMode } from 'utils/storage'; +import { AccountJson } from '@subwallet/extension-base/types'; //todo: i18n //todo: solve error diff --git a/src/hooks/common/useGetChainSlugsByCurrentAccount.ts b/src/hooks/common/useGetChainSlugsByCurrentAccount.ts deleted file mode 100644 index f223ff87a..000000000 --- a/src/hooks/common/useGetChainSlugsByCurrentAccount.ts +++ /dev/null @@ -1,136 +0,0 @@ -import type { KeypairType } from '@polkadot/util-crypto/types'; - -import { _ChainInfo, _ChainStatus } from '@subwallet/chain-list/types'; -import { AccountJson } from '@subwallet/extension-base/background/types'; -import { _isChainEvmCompatible } from '@subwallet/extension-base/services/chain-service/utils'; -import { useMemo } from 'react'; -import { useSelector } from 'react-redux'; -import { isEthereumAddress } from '@polkadot/util-crypto'; -import { AccountType } from 'types/ui-types'; -import { isAccountAll } from 'utils/accountAll'; -import { RootState } from 'stores/index'; -import { findNetworkJsonByGenesisHash } from 'utils/getNetworkJsonByGenesisHash'; - -function getChainsAccountType( - accountType: AccountType, - chainInfoMap: Record, - accountNetworks?: string[], -): string[] { - const result: string[] = []; - - for (const [chain, { chainStatus }] of Object.entries(chainInfoMap)) { - if (chainStatus !== _ChainStatus.ACTIVE) { - continue; - } - - if (accountNetworks) { - if (accountNetworks.includes(chain)) { - result.push(chain); - } - } else { - const isChainEvmCompatible = _isChainEvmCompatible(chainInfoMap[chain]); - - if (isChainEvmCompatible) { - if (accountType === 'ALL' || accountType === 'ETHEREUM') { - result.push(chain); - } - } else { - if (accountType === 'ALL' || accountType === 'SUBSTRATE') { - result.push(chain); - } - } - } - } - - return result; -} - -function getAccountType(type: KeypairType) { - if (type === 'ethereum') { - return 'ETHEREUM'; - } else if (['ed25519', 'sr25519', 'ecdsa'].includes(type)) { - return 'SUBSTRATE'; - } - - return undefined; -} - -function analysisAccounts(accounts: AccountJson[]): [boolean, boolean] { - let substrateCounter = 0; - let ethereumCounter = 0; - - if (!accounts.length) { - return [false, false]; - } - - accounts.forEach(a => { - if (isAccountAll(a.address)) { - return; - } - - if (isEthereumAddress(a.address)) { - ethereumCounter++; - } else { - substrateCounter++; - } - }); - - return [ethereumCounter === 0 && substrateCounter > 0, ethereumCounter > 0 && substrateCounter === 0]; -} - -// todo: may merge with useGetChainSlugsByAccountType, need review -export default function useGetChainSlugsByCurrentAccount(): string[] { - const chainInfoMap = useSelector((state: RootState) => state.chainStore.chainInfoMap); - const currentAccount = useSelector((state: RootState) => state.accountState.currentAccount); - const accounts = useSelector((state: RootState) => state.accountState.accounts); - - const accountType = useMemo(() => { - const foundAccountType = currentAccount?.type ? getAccountType(currentAccount?.type) : undefined; - - if (foundAccountType) { - return foundAccountType; - } - - let currentAccountType: AccountType = 'ALL'; - - if (currentAccount?.address) { - if (isAccountAll(currentAccount.address)) { - const [isContainOnlySubstrate, isContainOnlyEthereum] = analysisAccounts(accounts); - - if (isContainOnlyEthereum) { - currentAccountType = 'ETHEREUM'; - } else if (isContainOnlySubstrate) { - currentAccountType = 'SUBSTRATE'; - } - } else if (isEthereumAddress(currentAccount?.address)) { - currentAccountType = 'ETHEREUM'; - } else { - currentAccountType = 'SUBSTRATE'; - } - } - - return currentAccountType; - }, [accounts, currentAccount?.address, currentAccount?.type]); - - const accountNetwork = useMemo(() => { - const account: AccountJson | null = currentAccount; - - if (account?.isHardware) { - const isEthereum = isEthereumAddress(account.address || ''); - - if (isEthereum) { - return undefined; - } else { - const availableGen: string[] = account.availableGenesisHashes || []; - - return availableGen.map(gen => findNetworkJsonByGenesisHash(chainInfoMap, gen)?.slug || ''); - } - } else { - return undefined; - } - }, [chainInfoMap, currentAccount]); - - return useMemo(() => { - return getChainsAccountType(accountType, chainInfoMap, accountNetwork); - }, [accountType, chainInfoMap, accountNetwork]); -} diff --git a/src/hooks/earning/useGetYieldPositionForSpecificAccount.ts b/src/hooks/earning/useGetYieldPositionForSpecificAccount.ts new file mode 100644 index 000000000..0520e97b6 --- /dev/null +++ b/src/hooks/earning/useGetYieldPositionForSpecificAccount.ts @@ -0,0 +1,53 @@ +// Copyright 2019-2022 @polkadot/extension-koni-ui authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { YieldPositionInfo } from '@subwallet/extension-base/types'; +import BigN from 'bignumber.js'; +import { useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { RootState } from 'stores/index'; +import { useGetChainSlugsByAccount } from 'hooks/useGetChainSlugsByAccount'; +import { isSameAddress } from 'utils/account/account'; + +const useGetYieldPositionForSpecificAccount = (address?: string): YieldPositionInfo[] => { + const poolInfoMap = useSelector((state: RootState) => state.earning.poolInfoMap); + const yieldPositions = useSelector((state: RootState) => state.earning.yieldPositions); + const isAllAccount = useSelector((state: RootState) => state.accountState.isAllAccount); + const currentAccountProxy = useSelector((state: RootState) => state.accountState.currentAccountProxy); + const chainsByAccountType = useGetChainSlugsByAccount(); + + return useMemo(() => { + const infoSpecificList: YieldPositionInfo[] = []; + + const checkAddress = (item: YieldPositionInfo) => { + if (isAllAccount) { + if (address) { + return isSameAddress(address, item.address); + } + + return true; + } else { + return currentAccountProxy?.accounts.some(({ address: _address }) => { + const compareAddress = address ? isSameAddress(address, _address) : true; + + return compareAddress && isSameAddress(_address, item.address); + }); + } + }; + + for (const info of yieldPositions) { + if (chainsByAccountType.includes(info.chain) && poolInfoMap[info.slug]) { + const isValid = checkAddress(info); + const haveStake = new BigN(info.totalStake).gt(0); + + if (isValid && haveStake) { + infoSpecificList.push(info); + } + } + } + + return infoSpecificList; + }, [address, chainsByAccountType, currentAccountProxy?.accounts, isAllAccount, poolInfoMap, yieldPositions]); +}; + +export default useGetYieldPositionForSpecificAccount; diff --git a/src/hooks/earning/useGroupYieldPosition.ts b/src/hooks/earning/useGroupYieldPosition.ts index 1c070659d..4e6100140 100644 --- a/src/hooks/earning/useGroupYieldPosition.ts +++ b/src/hooks/earning/useGroupYieldPosition.ts @@ -12,31 +12,28 @@ import { YieldPoolType, YieldPositionInfo, } from '@subwallet/extension-base/types'; -import { isAccountAll } from '@subwallet/extension-base/utils'; import BigN from 'bignumber.js'; -import { useGetChainSlugs } from 'hooks/screen/Home/useGetChainSlugs'; import { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { RootState } from 'stores/index'; import { isSameAddress } from 'utils/account/account'; +import { useGetChainSlugsByAccount } from 'hooks/useGetChainSlugsByAccount'; -const useGroupYieldPosition = (selectedAddress?: string): YieldPositionInfo[] => { - const { poolInfoMap, yieldPositions } = useSelector((state: RootState) => state.earning); - const { currentAccount } = useSelector((state: RootState) => state.accountState); - const chainsByAccountType = useGetChainSlugs(); +const useGroupYieldPosition = (): YieldPositionInfo[] => { + const poolInfoMap = useSelector((state: RootState) => state.earning.poolInfoMap); + const yieldPositions = useSelector((state: RootState) => state.earning.yieldPositions); + const { currentAccountProxy, isAllAccount } = useSelector((state: RootState) => state.accountState); + const chainsByAccountType = useGetChainSlugsByAccount(); return useMemo(() => { const raw: Record = {}; const result: YieldPositionInfo[] = []; - const address = selectedAddress ? selectedAddress : currentAccount?.address || ''; - const isAll = isAccountAll(address); - const checkAddress = (item: YieldPositionInfo) => { - if (isAll) { + if (isAllAccount) { return true; } else { - return isSameAddress(address, item.address); + return currentAccountProxy?.accounts.some(({ address }) => isSameAddress(address, item.address)); } }; @@ -62,7 +59,7 @@ const useGroupYieldPosition = (selectedAddress?: string): YieldPositionInfo[] => continue; } - if (isAll) { + if (isAllAccount) { const base: AbstractYieldPositionInfo = { slug: slug, chain: positionInfo.chain, @@ -120,7 +117,7 @@ const useGroupYieldPosition = (selectedAddress?: string): YieldPositionInfo[] => } return result; - }, [chainsByAccountType, currentAccount?.address, poolInfoMap, selectedAddress, yieldPositions]); + }, [currentAccountProxy?.accounts, isAllAccount, yieldPositions, chainsByAccountType, poolInfoMap]); }; export default useGroupYieldPosition; diff --git a/src/hooks/earning/useYieldGroupInfo.ts b/src/hooks/earning/useYieldGroupInfo.ts index 81df5f766..73a55337c 100644 --- a/src/hooks/earning/useYieldGroupInfo.ts +++ b/src/hooks/earning/useYieldGroupInfo.ts @@ -16,7 +16,7 @@ import { BN_TEN } from 'utils/number'; import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; import { _getAssetOriginChain } from '@subwallet/extension-base/services/chain-service/utils'; import { BN_ZERO } from '@subwallet/extension-base/utils'; -import useGetChainSlugsByCurrentAccount from 'hooks/common/useGetChainSlugsByCurrentAccount'; +import { useGetChainSlugsByAccount } from 'hooks/useGetChainSlugsByAccount'; const isRelatedToRelayChain = ( group: string, @@ -57,7 +57,7 @@ const useYieldGroupInfo = (): YieldGroupInfo[] => { const { poolInfoMap } = useSelector((state: RootState) => state.earning); const { assetRegistry, multiChainAssetMap } = useSelector((state: RootState) => state.assetRegistry); const { chainInfoMap } = useSelector((state: RootState) => state.chainStore); - const chainsByAccountType = useGetChainSlugsByCurrentAccount(); + const chainsByAccountType = useGetChainSlugsByAccount(); const { tokenGroupMap } = useTokenGroup(chainsByAccountType); const { tokenBalanceMap, tokenGroupBalanceMap } = useAccountBalance(tokenGroupMap, true); const { priceMap } = useSelector((state: RootState) => state.price); diff --git a/src/hooks/earning/useYieldPositionDetail.ts b/src/hooks/earning/useYieldPositionDetail.ts index 354ed9967..15fbced3b 100644 --- a/src/hooks/earning/useYieldPositionDetail.ts +++ b/src/hooks/earning/useYieldPositionDetail.ts @@ -12,12 +12,12 @@ import { YieldPoolType, YieldPositionInfo, } from '@subwallet/extension-base/types'; -import { isAccountAll, isSameAddress } from '@subwallet/extension-base/utils'; import BigN from 'bignumber.js'; -import { useGetChainSlugs } from 'hooks/screen/Home/useGetChainSlugs'; import { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { RootState } from 'stores/index'; +import { isSameAddress } from 'utils/account/account'; +import { useGetChainSlugsByAccount } from 'hooks/useGetChainSlugsByAccount'; interface Result { compound: YieldPositionInfo | undefined; @@ -26,36 +26,41 @@ interface Result { const useYieldPositionDetail = (slug: string, address?: string): Result => { const { poolInfoMap, yieldPositions } = useSelector((state: RootState) => state.earning); - const { currentAccount } = useSelector((state: RootState) => state.accountState); - const chainsByAccountType = useGetChainSlugs(); + const { currentAccountProxy, isAllAccount } = useSelector((state: RootState) => state.accountState); + const chainsByAccountType = useGetChainSlugsByAccount(); return useMemo(() => { - const _address = address || currentAccount?.address || ''; - const isAll = isAccountAll(_address); - const checkAddress = (item: YieldPositionInfo) => { - if (isAll) { + if (isAllAccount) { + if (address) { + return isSameAddress(address, item.address); + } + return true; } else { - return isSameAddress(_address, item.address); + return currentAccountProxy?.accounts.some(({ address: _address }) => { + const compareAddress = address ? isSameAddress(address, _address) : true; + + return compareAddress && isSameAddress(_address, item.address); + }); } }; const infoList: YieldPositionInfo[] = []; for (const info of yieldPositions) { - if (chainsByAccountType.includes(info.chain) && poolInfoMap[info.slug]) { + if (info.slug === slug && chainsByAccountType.includes(info.chain) && poolInfoMap[info.slug]) { const isValid = checkAddress(info); const haveStake = new BigN(info.totalStake).gt(0); - if (isValid && haveStake && info.slug === slug) { + if (isValid && haveStake) { infoList.push(info); } } } if (infoList.length) { - if (isAll) { + if (isAllAccount && !address) { const positionInfo = infoList[0]; const base: AbstractYieldPositionInfo = { slug: slug, @@ -141,7 +146,7 @@ const useYieldPositionDetail = (slug: string, address?: string): Result => { list: infoList, }; } - }, [chainsByAccountType, currentAccount?.address, poolInfoMap, slug, yieldPositions, address]); + }, [isAllAccount, address, currentAccountProxy?.accounts, yieldPositions, slug, chainsByAccountType, poolInfoMap]); }; export default useYieldPositionDetail; diff --git a/src/hooks/earning/useYieldRewardTotal.ts b/src/hooks/earning/useYieldRewardTotal.ts index c337a8c70..03724d1d1 100644 --- a/src/hooks/earning/useYieldRewardTotal.ts +++ b/src/hooks/earning/useYieldRewardTotal.ts @@ -2,37 +2,34 @@ // SPDX-License-Identifier: Apache-2.0 import { EarningRewardItem, YieldPoolType } from '@subwallet/extension-base/types'; -import { isAccountAll, isSameAddress } from '@subwallet/extension-base/utils'; -import { useGetChainSlugs } from 'hooks/screen/Home/useGetChainSlugs'; import { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { RootState } from 'stores/index'; import { BN_ZERO } from 'utils/chainBalances'; import { findAccountByAddress } from 'utils/index'; +import { isSameAddress } from 'utils/account/account'; +import { useGetChainSlugsByAccount } from 'hooks/useGetChainSlugsByAccount'; const useYieldRewardTotal = (slug: string): string | undefined => { const { poolInfoMap, earningRewards } = useSelector((state: RootState) => state.earning); - const { currentAccount, accounts } = useSelector((state: RootState) => state.accountState); - const chainsByAccountType = useGetChainSlugs(); + const { currentAccountProxy, accounts, isAllAccount } = useSelector((state: RootState) => state.accountState); + const chainsByAccountType = useGetChainSlugsByAccount(); return useMemo(() => { - const address = currentAccount?.address || ''; - const isAll = isAccountAll(address); - const checkAddress = (item: EarningRewardItem) => { - if (isAll) { + if (isAllAccount) { const account = findAccountByAddress(accounts, item.address); return !!account; } else { - return isSameAddress(address, item.address); + return currentAccountProxy?.accounts.some(({ address }) => isSameAddress(address, item.address)); } }; const poolInfo = poolInfoMap[slug]; if (poolInfo) { - if (poolInfo?.type !== YieldPoolType.NOMINATION_POOL) { + if (poolInfo.type !== YieldPoolType.NOMINATION_POOL) { return '0'; } else { if (earningRewards.length) { @@ -56,7 +53,7 @@ const useYieldRewardTotal = (slug: string): string | undefined => { } else { return undefined; } - }, [accounts, chainsByAccountType, currentAccount?.address, earningRewards, poolInfoMap, slug]); + }, [accounts, chainsByAccountType, currentAccountProxy?.accounts, earningRewards, isAllAccount, poolInfoMap, slug]); }; export default useYieldRewardTotal; diff --git a/src/hooks/screen/Home/useGetChainSlugs.ts b/src/hooks/screen/Home/useGetChainSlugs.ts index 7ca14b1a1..f7bef6e7e 100644 --- a/src/hooks/screen/Home/useGetChainSlugs.ts +++ b/src/hooks/screen/Home/useGetChainSlugs.ts @@ -1,5 +1,4 @@ import { _ChainInfo } from '@subwallet/chain-list/types'; -import { AccountJson } from '@subwallet/extension-base/background/types'; import { _isChainEvmCompatible } from '@subwallet/extension-base/services/chain-service/utils'; import { useMemo } from 'react'; @@ -12,6 +11,7 @@ import { findNetworkJsonByGenesisHash } from 'utils/getNetworkJsonByGenesisHash' import { isAccountAll } from 'utils/accountAll'; import { analysisAccounts } from 'hooks/screen/Home/Crypto/useGetChainSlugsByAccountType'; import { KeypairType } from '@polkadot/util-crypto/types'; +import { AccountJson } from '@subwallet/extension-base/types'; function getChainsAccountType( accountType: AccountType, diff --git a/src/hooks/screen/Transaction/useTransaction.ts b/src/hooks/screen/Transaction/useTransaction.ts index 0122856bd..94a3c9d03 100644 --- a/src/hooks/screen/Transaction/useTransaction.ts +++ b/src/hooks/screen/Transaction/useTransaction.ts @@ -12,6 +12,7 @@ import { FieldValues, UseFormProps } from 'react-hook-form/dist/types'; import { FieldPath, useForm } from 'react-hook-form'; import { FieldPathValue } from 'react-hook-form/dist/types/path'; import i18n from 'utils/i18n/i18n'; +import { AccountProxy } from '@subwallet/extension-base/types'; export interface TransactionFormValues extends FieldValues { from: string; @@ -28,11 +29,15 @@ export interface TransactionDoneInfo { address: string; } +export const getTransactionFromAccountProxyValue = (accountProxy: AccountProxy | null): string => { + return accountProxy?.id ? (isAccountAll(accountProxy.id) ? '' : accountProxy.id) : ''; +}; + export const useTransaction = ( action: string, formOptions: UseFormProps = {}, ) => { - const { currentAccount } = useSelector((state: RootState) => state.accountState); + const { currentAccountProxy } = useSelector((state: RootState) => state.accountState); const chainInfoMap = useSelector((state: RootState) => state.chainStore.chainInfoMap); const { turnOnChain, checkChainConnected } = useChainChecker(); const assetRegistry = useSelector((state: RootState) => state.assetRegistry.assetRegistry); @@ -89,13 +94,13 @@ export const useTransaction = ({ - from: (!isAccountAll(currentAccount?.address as string) && currentAccount?.address) || '', + from: getTransactionFromAccountProxyValue(currentAccountProxy), chain: '', asset: '', value: '', ...formOptions.defaultValues, }), - [currentAccount?.address, formOptions.defaultValues], + [currentAccountProxy, formOptions.defaultValues], ); const form = useForm({ diff --git a/src/hooks/transaction/useHandleSubmitTransaction.ts b/src/hooks/transaction/useHandleSubmitTransaction.ts index 3e796aa68..9c519f837 100644 --- a/src/hooks/transaction/useHandleSubmitTransaction.ts +++ b/src/hooks/transaction/useHandleSubmitTransaction.ts @@ -24,6 +24,7 @@ const useHandleSubmitTransaction = ( const onSuccess = useCallback( (rs: SWTransactionResponse) => { const { errors, id, warnings, estimateFee } = rs; + console.log('errors', errors); if (errors.length || warnings.length) { if (errors[0]?.message !== 'Rejected by user') { if (errors[0]?.message?.startsWith('Unable to fetch staking data.')) { @@ -96,6 +97,7 @@ const useHandleSubmitTransaction = ( const onError = useCallback( (error: Error) => { + console.log('error', error); setTransactionDone(false); hideAll(); show(error.message, { type: 'danger', duration: 8000 }); diff --git a/src/screens/Home/Earning/List/index.tsx b/src/screens/Home/Earning/List/index.tsx index 814d128d4..dd3d01b62 100644 --- a/src/screens/Home/Earning/List/index.tsx +++ b/src/screens/Home/Earning/List/index.tsx @@ -120,7 +120,7 @@ export const EarningList = ({ text: 'Create new', onPress: () => { isShowAlert = false; - rootNavigation.navigate('CreateAccount', { keyTypes: [accountType], isBack: true }); + rootNavigation.navigate('CreateAccount', { isBack: true }); }, }, ], diff --git a/src/screens/Home/Earning/PositionDetail/index.tsx b/src/screens/Home/Earning/PositionDetail/index.tsx index e1ad2b74c..2c4f10a5e 100644 --- a/src/screens/Home/Earning/PositionDetail/index.tsx +++ b/src/screens/Home/Earning/PositionDetail/index.tsx @@ -31,6 +31,9 @@ import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning import useChainChecker from 'hooks/chain/useChainChecker'; import { isLendingPool, isLiquidPool } from '@subwallet/extension-base/services/earning-service/utils'; import { GettingDataModal } from 'components/Modal/GettingDataModal'; +import { isAccountAll } from '@subwallet/extension-base/utils'; +import { ALL_ACCOUNT_KEY } from '@subwallet/extension-base/constants'; +import { isChainInfoAccordantAccountChainType } from 'utils/chain'; interface Props { compound: YieldPositionInfo; @@ -44,8 +47,9 @@ const Component: React.FC = (props: Props) => { const navigation = useNavigation(); const isShowBalance = useSelector((state: RootState) => state.settings.isShowBalance); const { assetRegistry } = useSelector((state: RootState) => state.assetRegistry); + const { chainInfoMap } = useSelector((state: RootState) => state.chainStore); const { currencyData, priceMap } = useSelector((state: RootState) => state.price); - const { isAllAccount, currentAccount } = useSelector((state: RootState) => state.accountState); + const { isAllAccount, currentAccountProxy } = useSelector((state: RootState) => state.accountState); const [dAppStakingWarningModalVisible, setDAppStakingWarningModalVisible] = useState(false); const isOpenDAppWarningInPositionDetail = mmkvStore.getBoolean('isOpenDAppWarningInPositionDetail'); const { checkChainConnected, turnOnChain } = useChainChecker(false); @@ -53,6 +57,23 @@ const Component: React.FC = (props: Props) => { const loadingRef = useRef(isLoading); const [state, setState] = React.useState({ num: 0 }); const counter = useRef(0); + const targetAddress = useMemo(() => { + if (currentAccountProxy && isAccountAll(currentAccountProxy?.id)) { + return ALL_ACCOUNT_KEY; + } + + const accountAddress = currentAccountProxy?.accounts.find(({ chainType }) => { + if (chainInfoMap[poolInfo.chain]) { + const chainInfo = chainInfoMap[poolInfo.chain]; + + return isChainInfoAccordantAccountChainType(chainInfo, chainType); + } + + return false; + }); + + return accountAddress?.address; + }, [chainInfoMap, currentAccountProxy, poolInfo.chain]); const isChainUnsupported = useMemo(() => { if (poolInfo.chain === 'parallel' && poolInfo.type === YieldPoolType.LIQUID_STAKING) { @@ -157,12 +178,12 @@ const Component: React.FC = (props: Props) => { }, [activeStake, inputAsset?.decimals, price]); const filteredRewardHistories = useMemo(() => { - if (!isAllAccount && currentAccount) { - return rewardHistories.filter(item => item.slug === poolInfo?.slug && item.address === currentAccount.address); + if (!isAllAccount && targetAddress) { + return rewardHistories.filter(item => item.slug === poolInfo.slug && item.address === targetAddress); } else { return []; } - }, [currentAccount, isAllAccount, poolInfo?.slug, rewardHistories]); + }, [targetAddress, isAllAccount, poolInfo?.slug, rewardHistories]); const _goBack = useCallback(() => { navigation.navigate('Home', { diff --git a/src/screens/Home/Earning/PositionList/index.tsx b/src/screens/Home/Earning/PositionList/index.tsx index 7738dfe52..f0686607c 100644 --- a/src/screens/Home/Earning/PositionList/index.tsx +++ b/src/screens/Home/Earning/PositionList/index.tsx @@ -1,5 +1,5 @@ import { useIsFocused, useNavigation } from '@react-navigation/native'; -import { YieldPoolType } from '@subwallet/extension-base/types'; +import { YieldPoolType, YieldPositionInfo } from '@subwallet/extension-base/types'; import BigNumber from 'bignumber.js'; import { EmptyList } from 'components/EmptyList'; import { FlatListScreen } from 'components/FlatListScreen'; @@ -8,8 +8,8 @@ import { useGroupYieldPosition } from 'hooks/earning'; import { useRefresh } from 'hooks/useRefresh'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { reloadCron } from 'messaging/index'; -import { Plus, Vault } from 'phosphor-react-native'; -import React, { useCallback, useEffect, useMemo } from 'react'; +import { Plus, Vault, Warning } from 'phosphor-react-native'; +import React, { useCallback, useContext, useEffect, useMemo } from 'react'; import { Alert, Keyboard, Linking, RefreshControl, View } from 'react-native'; import { useSelector } from 'react-redux'; import { setAdjustPan } from 'rn-android-keyboard-adjust'; @@ -24,8 +24,12 @@ import { isRelatedToAstar } from 'utils/earning'; import { BannerGenerator } from 'components/common/BannerGenerator'; import useGetBannerByScreen from 'hooks/campaign/useGetBannerByScreen'; import { ListRenderItemInfo } from '@shopify/flash-list'; - -let cacheData: Record = {}; +import { isAccountAll } from '@subwallet/extension-base/utils'; +import useGetYieldPositionForSpecificAccount from 'hooks/earning/useGetYieldPositionForSpecificAccount'; +import { mmkvStore } from 'utils/storage'; +import { EARNING_WARNING_ANNOUNCEMENT } from 'constants/localStorage'; +import { AppModalContext } from 'providers/AppModalContext'; +import { PageIcon, Typography } from 'components/design-system-ui'; interface Props { setStep: (value: number) => void; @@ -98,7 +102,13 @@ export const PositionList = ({ setStep, loading }: Props) => { const { isShowBalance } = useSelector((state: RootState) => state.settings); const { currencyData, priceMap } = useSelector((state: RootState) => state.price); const { assetRegistry: assetInfoMap } = useSelector((state: RootState) => state.assetRegistry); - const { currentAccount } = useSelector((state: RootState) => state.accountState); + const { currentAccountProxy, accounts } = useSelector((state: RootState) => state.accountState); + const { confirmModal } = useContext(AppModalContext); + const specificList = useGetYieldPositionForSpecificAccount(); + const announcement = mmkvStore.getString(EARNING_WARNING_ANNOUNCEMENT) || 'nonConfirmed'; + const setAnnouncement = (value: string) => { + mmkvStore.set(EARNING_WARNING_ANNOUNCEMENT, value); + }; const styles = useMemo(() => createStyles(theme), [theme]); @@ -131,6 +141,48 @@ export const PositionList = ({ setStep, loading }: Props) => { }); }, [assetInfoMap, currencyData, data, priceMap]); + const chainStakingBoth = useMemo(() => { + if (!currentAccountProxy) { + return null; + } + + const chains = ['polkadot', 'kusama']; + + const findChainWithStaking = (list: YieldPositionInfo[]) => { + const hasNativeStaking = (chain: string) => + list.some(item => item.chain === chain && item.type === YieldPoolType.NATIVE_STAKING); + const hasNominationPool = (chain: string) => + list.some(item => item.chain === chain && item.type === YieldPoolType.NOMINATION_POOL); + + for (const chain of chains) { + if (hasNativeStaking(chain) && hasNominationPool(chain)) { + return chain; + } + } + + return null; + }; + + if (isAccountAll(currentAccountProxy.id)) { + return findChainWithStaking(specificList); + } + + for (const acc of accounts) { + if (isAccountAll(acc.address)) { + continue; + } + + const listStaking = specificList.filter(item => item.address === acc.address); + const chain = findChainWithStaking(listStaking); + + if (chain) { + return chain; + } + } + + return null; + }, [accounts, currentAccountProxy, specificList]); + const handleOnPress = useCallback( (positionInfo: ExtraYieldPositionInfo): (() => void) => { if (isRelatedToAstar(positionInfo.slug)) { @@ -221,12 +273,49 @@ export const PositionList = ({ setStep, loading }: Props) => { [chainInfoMap], ); + const learnMore = useCallback(() => { + Linking.openURL( + 'https://support.polkadot.network/support/solutions/articles/65000188140-changes-for-nomination-pool-members-and-opengov-participation', + ); + }, []); + useEffect(() => { - const address = currentAccount?.address || ''; - if (cacheData[address] === undefined && isFocused) { - cacheData = { [address]: !items.length }; + if (chainStakingBoth && announcement.includes('nonConfirmed')) { + const chainInfo = chainStakingBoth && chainInfoMap[chainStakingBoth]; + const symbol = (!!chainInfo && chainInfo?.substrateInfo?.symbol) || ''; + const originChain = (!!chainInfo && chainInfo?.name) || ''; + + confirmModal.setConfirmModal({ + visible: true, + title: `Unstake your ${symbol} now!`, + customIcon: , + message: ( + + + {`You’re dual staking via both direct nomination and nomination pool, which will not be supported in the upcoming ${originChain} runtime upgrade. Read more to learn about the upgrade, and`} + + { + Linking.openURL('https://docs.subwallet.app/main/mobile-app-user-guide/manage-staking/unstake'); + }} + style={styles.highlightText}>{` unstake your ${symbol} `} + {'from one of the methods to avoid issues'} + + ), + completeBtnTitle: 'Read update', + cancelBtnTitle: 'Dismiss', + onCompleteModal: () => { + learnMore(); + setAnnouncement('confirmed'); + confirmModal.hideConfirmModal(); + }, + onCancelModal: () => { + setAnnouncement('confirmed'); + confirmModal.hideConfirmModal(); + }, + }); } - }, [items.length, currentAccount, isFocused]); + }, [announcement, chainInfoMap, chainStakingBoth, confirmModal, learnMore, styles.highlightText, theme.colorWarning]); useEffect(() => { if (isFocused) { diff --git a/src/screens/Home/Earning/PositionList/style.ts b/src/screens/Home/Earning/PositionList/style.ts index f94bda0f3..59cfeaba1 100644 --- a/src/screens/Home/Earning/PositionList/style.ts +++ b/src/screens/Home/Earning/PositionList/style.ts @@ -1,4 +1,4 @@ -import { StyleSheet, ViewStyle } from 'react-native'; +import { StyleSheet, TextStyle, ViewStyle } from 'react-native'; import { ColorMap } from 'styles/color'; import { ThemeTypes } from 'styles/themes'; @@ -6,6 +6,7 @@ export interface ComponentStyle { wrapper: ViewStyle; container: ViewStyle; refreshIndicator: ViewStyle; + highlightText: TextStyle; } export default (theme: ThemeTypes) => { @@ -21,5 +22,9 @@ export default (theme: ThemeTypes) => { refreshIndicator: { backgroundColor: ColorMap.dark1, }, + highlightText: { + color: theme.colorPrimary, + textDecorationLine: 'underline', + }, }); }; diff --git a/src/screens/Transaction/CancelUnstake/index.tsx b/src/screens/Transaction/CancelUnstake/index.tsx index 33d5ae174..18b797cd0 100644 --- a/src/screens/Transaction/CancelUnstake/index.tsx +++ b/src/screens/Transaction/CancelUnstake/index.tsx @@ -1,4 +1,4 @@ -import { YieldPoolType, YieldPositionInfo } from '@subwallet/extension-base/types'; +import { AccountProxy, YieldPoolType, YieldPositionInfo } from '@subwallet/extension-base/types'; import { useYieldPositionDetail } from 'hooks/earning'; import { yieldSubmitStakingCancelWithdrawal } from 'messaging/index'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -14,8 +14,6 @@ import { ScrollView, View } from 'react-native'; import { AccountSelectField } from 'components/Field/AccountSelect'; import useGetAccountByAddress from 'hooks/screen/useGetAccountByAddress'; import { _ChainInfo } from '@subwallet/chain-list/types'; -import { AccountJson } from '@subwallet/extension-base/background/types'; -import { isSameAddress } from '@subwallet/extension-base/utils'; import { CancelUnstakeSelector } from 'components/Modal/common/CancelUnstakeSelector'; import { Button, Icon } from 'components/design-system-ui'; import { ArrowCircleRight, XCircle } from 'phosphor-react-native'; @@ -29,6 +27,9 @@ import { TransactionDone } from 'screens/Transaction/TransactionDone'; import { GeneralFreeBalance } from 'screens/Transaction/parts/GeneralFreeBalance'; import usePreCheckAction from 'hooks/account/usePreCheckAction'; import { ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes'; +import { isSameAddress } from 'utils/account/account'; +import { AccountAddressItemType } from 'types/account'; +import { getReformatedAddressRelatedToChain } from 'utils/account'; interface CancelUnstakeFormValues extends TransactionFormValues { unstakeIndex: string; @@ -39,9 +40,11 @@ const filterAccount = ( allNominatorInfo: YieldPositionInfo[], stakingType: YieldPoolType, stakingChain?: string, -): ((account: AccountJson) => boolean) => { - return (account: AccountJson): boolean => { - const nomination = allNominatorInfo.find(data => isSameAddress(data.address, account.address)); +): ((account: AccountProxy) => boolean) => { + return (account: AccountProxy): boolean => { + const nomination = allNominatorInfo.find(data => + account.accounts.some(ac => isSameAddress(data.address, ac.address)), + ); return ( (nomination ? nomination.unstakings.length > 0 : false) && @@ -58,7 +61,7 @@ export const CancelUnstake = ({ const navigation = useNavigation(); const theme = useSubWalletTheme().swThemes; - const { isAllAccount, accounts } = useSelector((state: RootState) => state.accountState); + const { isAllAccount, accountProxies } = useSelector((state: RootState) => state.accountState); const { chainInfoMap } = useSelector((state: RootState) => state.chainStore); const { poolInfoMap } = useSelector((state: RootState) => state.earning); @@ -105,8 +108,34 @@ export const CancelUnstake = ({ const [isBalanceReady, setIsBalanceReady] = useState(true); const accountList = useMemo(() => { - return accounts.filter(filterAccount(chainInfoMap, allPositionInfos, poolType, poolChain)); - }, [accounts, allPositionInfos, chainInfoMap, poolChain, poolType]); + const chainInfo = poolChain ? chainInfoMap[poolChain] : undefined; + + if (!chainInfo) { + return []; + } + const filteredAccountProxyList = accountProxies.filter( + filterAccount(chainInfoMap, allPositionInfos, poolType, poolChain), + ); + const result: AccountAddressItemType[] = []; + + filteredAccountProxyList.forEach(ap => { + ap.accounts.forEach(a => { + const address = getReformatedAddressRelatedToChain(a, chainInfo); + + if (address) { + result.push({ + accountName: ap.name, + accountProxyId: ap.id, + accountProxyType: ap.accountType, + accountType: a.type, + address, + }); + } + }); + }); + + return result; + }, [accountProxies, allPositionInfos, chainInfoMap, poolChain, poolType]); const { onError, onSuccess } = useHandleSubmitTransaction(onDone, setTransactionDone); const onPreCheck = usePreCheckAction(fromValue); diff --git a/src/screens/Transaction/ClaimReward/index.tsx b/src/screens/Transaction/ClaimReward/index.tsx index 3d69189b8..3be1eec04 100644 --- a/src/screens/Transaction/ClaimReward/index.tsx +++ b/src/screens/Transaction/ClaimReward/index.tsx @@ -1,5 +1,5 @@ import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; -import { EarningRewardItem, YieldPoolType, YieldPositionInfo } from '@subwallet/extension-base/types'; +import { AccountProxy, EarningRewardItem, YieldPoolType, YieldPositionInfo } from '@subwallet/extension-base/types'; import { useYieldPositionDetail } from 'hooks/earning'; import { yieldSubmitStakingClaimReward } from 'messaging/index'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -14,15 +14,10 @@ import useGetNativeTokenBasicInfo from 'hooks/useGetNativeTokenBasicInfo'; import useHandleSubmitTransaction from 'hooks/transaction/useHandleSubmitTransaction'; import { AccountSelectField } from 'components/Field/AccountSelect'; import useGetAccountByAddress from 'hooks/screen/useGetAccountByAddress'; -import { AccountJson } from '@subwallet/extension-base/background/types'; import { _ChainInfo } from '@subwallet/chain-list/types'; -import { - _getSubstrateGenesisHash, - _isChainEvmCompatible, -} from '@subwallet/extension-base/services/chain-service/utils'; +import { _isChainEvmCompatible } from '@subwallet/extension-base/services/chain-service/utils'; import { isAccountAll } from 'utils/accountAll'; import { isEthereumAddress } from '@polkadot/util-crypto'; -import { isSameAddress } from '@subwallet/extension-base/utils'; import BigN from 'bignumber.js'; import { BN_ZERO } from 'utils/chainBalances'; import MetaInfo from 'components/MetaInfo'; @@ -39,6 +34,9 @@ import { useWatch } from 'react-hook-form'; import { TransactionDone } from 'screens/Transaction/TransactionDone'; import { GeneralFreeBalance } from 'screens/Transaction/parts/GeneralFreeBalance'; import usePreCheckAction from 'hooks/account/usePreCheckAction'; +import { isSameAddress } from 'utils/account/account'; +import { AccountAddressItemType } from 'types/account'; +import { getReformatedAddressRelatedToChain } from 'utils/account'; interface ClaimRewardFormValues extends TransactionFormValues { bondReward: string; @@ -50,36 +48,38 @@ const filterAccount = ( rewardList: EarningRewardItem[], poolType: YieldPoolType, poolChain?: string, -): ((account: AccountJson) => boolean) => { +): ((account: AccountProxy) => boolean) => { const _poolChain = poolChain || ''; const chain = chainInfoMap[_poolChain]; - return (account: AccountJson): boolean => { + return (account: AccountProxy): boolean => { if (!chain) { return false; } - if (account.originGenesisHash && _getSubstrateGenesisHash(chain) !== account.originGenesisHash) { + if (account.specialChain && _poolChain !== account.specialChain) { return false; } - if (isAccountAll(account.address)) { + if (isAccountAll(account.id)) { return false; } const isEvmChain = _isChainEvmCompatible(chain); - if (isEvmChain !== isEthereumAddress(account.address)) { + if (isEvmChain !== isEthereumAddress(account.id)) { return false; } - const nominatorMetadata = yieldPositions.find(value => isSameAddress(value.address, account.address)); + const nominatorMetadata = yieldPositions.find(value => + account.accounts.some(ac => isSameAddress(value.address, ac.address)), + ); if (!nominatorMetadata) { return false; } - const reward = rewardList.find(value => isSameAddress(value.address, account.address)); + const reward = rewardList.find(value => account.accounts.some(ac => isSameAddress(value.address, ac.address))); const isAstarNetwork = _STAKING_CHAIN_GROUP.astar.includes(_poolChain); const isAmplitudeNetwork = _STAKING_CHAIN_GROUP.amplitude.includes(_poolChain); @@ -100,7 +100,7 @@ const ClaimReward = ({ const accountSelectorRef = useRef(); const navigation = useNavigation(); const theme = useSubWalletTheme().swThemes; - const { isAllAccount, accounts } = useSelector((state: RootState) => state.accountState); + const { isAllAccount, accountProxies } = useSelector((state: RootState) => state.accountState); const { earningRewards, poolInfoMap } = useSelector((state: RootState) => state.earning); const { chainInfoMap } = useSelector((state: RootState) => state.chainStore); @@ -198,8 +198,34 @@ const ClaimReward = ({ }, [errors.from, fromValue]); const accountList = useMemo(() => { - return accounts.filter(filterAccount(chainInfoMap, allPositions, rewardList, poolType, poolChain)); - }, [accounts, allPositions, chainInfoMap, rewardList, poolChain, poolType]); + const chainInfo = poolChain ? chainInfoMap[poolChain] : undefined; + + if (!chainInfo) { + return []; + } + const filteredAccountProxyList = accountProxies.filter( + filterAccount(chainInfoMap, allPositions, rewardList, poolType, poolChain), + ); + const result: AccountAddressItemType[] = []; + + filteredAccountProxyList.forEach(ap => { + ap.accounts.forEach(a => { + const address = getReformatedAddressRelatedToChain(a, chainInfo); + + if (address) { + result.push({ + accountName: ap.name, + accountProxyId: ap.id, + accountProxyType: ap.accountType, + accountType: a.type, + address, + }); + } + }); + }); + + return result; + }, [accountProxies, allPositions, chainInfoMap, rewardList, poolChain, poolType]); const onChangeBondReward = (value: string) => { setValue('bondReward', value); diff --git a/src/screens/Transaction/Earn/index.tsx b/src/screens/Transaction/Earn/index.tsx index 2641182a4..95c80eda9 100644 --- a/src/screens/Transaction/Earn/index.tsx +++ b/src/screens/Transaction/Earn/index.tsx @@ -1,9 +1,5 @@ import { ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes'; -import { - _getAssetDecimals, - _getAssetSymbol, - _isChainEvmCompatible, -} from '@subwallet/extension-base/services/chain-service/utils'; +import { _getAssetDecimals, _getAssetSymbol } from '@subwallet/extension-base/services/chain-service/utils'; import { SWTransactionResponse } from '@subwallet/extension-base/services/transaction-service/types'; import { EarningStatus, @@ -34,7 +30,6 @@ import { EarningValidatorSelector, ValidatorSelectorRef } from 'components/Modal import usePreCheckAction from 'hooks/account/usePreCheckAction'; import { TransactionFormValues, useTransaction } from 'hooks/screen/Transaction/useTransaction'; import useFetchChainState from 'hooks/screen/useFetchChainState'; -import useGetAccountByAddress from 'hooks/screen/useGetAccountByAddress'; import useGetNativeTokenSlug from 'hooks/useGetNativeTokenSlug'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { @@ -61,18 +56,16 @@ import { MarginBottomForSubmitButton } from 'styles/sharedStyles'; import { ModalRef } from 'types/modalRef'; import i18n from 'utils/i18n/i18n'; import { parseNominations } from 'utils/transaction/stake'; -import { accountFilterFunc, getJoinYieldParams } from '../helper/earning'; +import { getJoinYieldParams } from '../helper/earning'; import createStyle from './style'; -import { useGroupYieldPosition, useYieldPositionDetail } from 'hooks/earning'; +import { useYieldPositionDetail } from 'hooks/earning'; import { useIsFocused, useNavigation } from '@react-navigation/native'; import { RootNavigationProps } from 'routes/index'; import AlertBox from 'components/design-system-ui/alert-box/simple'; import { STAKE_ALERT_DATA } from 'constants/earning/EarningDataRaw'; import { useGetBalance } from 'hooks/balance'; -import reformatAddress from 'utils/index'; import { getValidatorLabel } from '@subwallet/extension-base/koni/api/staking/bonding/utils'; import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; -import { EVM_ACCOUNT_TYPE, SUBSTRATE_ACCOUNT_TYPE } from 'constants/index'; import { _ChainAsset } from '@subwallet/chain-list/types'; import { _handleDisplayForEarningError, @@ -81,6 +74,11 @@ import { import useGetConfirmationByScreen from 'hooks/static-content/useGetConfirmationByScreen'; import { GlobalModalContext } from 'providers/GlobalModalContext'; import { AppModalContext } from 'providers/AppModalContext'; +import { AccountAddressItemType } from 'types/account'; +import { getReformatedAddressRelatedToChain } from 'utils/account'; +import { reformatAddress } from 'utils/account/account'; +import useGetYieldPositionForSpecificAccount from 'hooks/earning/useGetYieldPositionForSpecificAccount'; +import { VoidFunction } from 'types/index'; interface StakeFormValues extends TransactionFormValues { slug: string; @@ -92,13 +90,6 @@ const loadingStepPromiseKey = 'earning.step.loading'; // Not enough balance to xcm; export const insufficientXCMMessages = ['You can only enter a maximum']; -const SHOW_WARNING_CASES = [ - 'DOT___nomination_pool___polkadot', - 'DOT___native_staking___polkadot', - 'KSM___nomination_pool___kusama', - 'KSM___native_staking___kusama', -]; - const DO_NOT_SHOW_VALIDATOR_ALERT_CASES = [ 'TAO___native_staking___bittensor', 'TAO___native_staking___bittensor_devnet', @@ -115,7 +106,7 @@ const EarnTransaction: React.FC = (props: EarningProps) => { const isFocused = useIsFocused(); const theme = useSubWalletTheme().swThemes; const { show, hideAll } = useToast(); - const { accounts, isAllAccount } = useSelector((state: RootState) => state.accountState); + const { accountProxies, isAllAccount } = useSelector((state: RootState) => state.accountState); const { chainInfoMap } = useSelector((state: RootState) => state.chainStore); const { poolInfoMap, poolTargetsMap } = useSelector((state: RootState) => state.earning); const { assetRegistry: chainAsset } = useSelector((state: RootState) => state.assetRegistry); @@ -133,6 +124,7 @@ const EarnTransaction: React.FC = (props: EarningProps) => { getValues, setFocus, }, + defaultValues, onChangeFromValue: setFrom, onChangeAssetValue: setAsset, onTransactionDone: onDone, @@ -164,48 +156,55 @@ const EarnTransaction: React.FC = (props: EarningProps) => { const currentStep = processState.currentStep; const firstStep = currentStep === 0; const submitStepType = processState.steps?.[!currentStep ? currentStep + 1 : currentStep]?.type; - - const accountInfo = useGetAccountByAddress(currentFrom); const preCheckAction = usePreCheckAction(currentFrom); const { compound } = useYieldPositionDetail(slug); + const specificList = useGetYieldPositionForSpecificAccount(currentFrom); const { nativeTokenBalance } = useGetBalance(chain, currentFrom); const poolInfo = poolInfoMap[slug]; const poolType = poolInfo?.type || ''; const poolChain = poolInfo?.chain || ''; - const yieldPositionsData = useGroupYieldPosition(currentFrom); - // hotfix for mkt campaign - const warningConfirmationData = useMemo(() => { - if (SHOW_WARNING_CASES.includes(slug)) { - const getWarningConfirmationData = (currentSlug: string, prevSlug: string) => { - const splitPrevSlug = prevSlug.split('___'); - const prevType = splitPrevSlug[1] === 'native_staking' ? 'direct nomination' : 'nomination pool'; - const splitCurrentSlug = currentSlug.split('___'); - const currentType = splitCurrentSlug[1] === 'native_staking' ? 'direct nomination' : 'nomination pool'; - const isShowWarningConfirmation = !!yieldPositionsData.find(y => y.slug === prevSlug); - - return { isShowWarningConfirmation, symbol: splitCurrentSlug[0], currentType, prevType }; - }; - switch (slug) { - case 'DOT___nomination_pool___polkadot': - return getWarningConfirmationData('DOT___nomination_pool___polkadot', 'DOT___native_staking___polkadot'); - case 'DOT___native_staking___polkadot': - return getWarningConfirmationData('DOT___native_staking___polkadot', 'DOT___nomination_pool___polkadot'); - case 'KSM___nomination_pool___kusama': - return getWarningConfirmationData('KSM___nomination_pool___kusama', 'KSM___native_staking___kusama'); - case 'KSM___native_staking___kusama': - return getWarningConfirmationData('KSM___native_staking___kusama', 'KSM___nomination_pool___kusama'); - } - } else { - return { isShowWarningConfirmation: false, symbol: '', currentType: '', prevType: '' }; - } - }, [slug, yieldPositionsData]); const styles = useMemo(() => createStyle(theme), [theme]); - const accountSelectorList = useMemo( - () => accounts.filter(accountFilterFunc(chainInfoMap, poolType, poolChain)), - [accounts, poolChain, chainInfoMap, poolType], - ); + const accountAddressItems = useMemo(() => { + const chainInfo = poolChain ? chainInfoMap[poolChain] : undefined; + + if (!chainInfo) { + return []; + } + + const result: AccountAddressItemType[] = []; + + accountProxies.forEach(ap => { + if (!(!defaultValues.from || ap.id === defaultValues.from)) { + return; + } + + ap.accounts.forEach(a => { + const address = getReformatedAddressRelatedToChain(a, chainInfo); + + if (address) { + result.push({ + accountName: ap.name, + accountProxyId: ap.id, + accountProxyType: ap.accountType, + accountType: a.type, + address, + }); + } + }); + }); + + return result; + }, [accountProxies, chainInfoMap, defaultValues.from, poolChain]); + + const accountInfo = useMemo(() => { + if (!currentFrom) { + return undefined; + } + + return accountAddressItems.find(i => i.address === currentFrom); + }, [accountAddressItems, currentFrom]); const mustChooseTarget = useMemo( () => [YieldPoolType.NATIVE_STAKING, YieldPoolType.NOMINATION_POOL].includes(poolType), @@ -380,6 +379,37 @@ const EarnTransaction: React.FC = (props: EarningProps) => { } }, [slug, getCurrentConfirmation]); + const chainStakingBoth = useMemo(() => { + const hasNativeStaking = (_chain: string) => + specificList.some(item => item.chain === _chain && item.type === YieldPoolType.NATIVE_STAKING); + const hasNominationPool = (_chain: string) => + specificList.some(item => item.chain === _chain && item.type === YieldPoolType.NOMINATION_POOL); + + const chains = ['polkadot', 'kusama']; + let chainStakingInBoth; + + for (const _chain of chains) { + if ( + hasNativeStaking(_chain) && + hasNominationPool(_chain) && + [YieldPoolType.NOMINATION_POOL, YieldPoolType.NATIVE_STAKING].includes(poolType) && + _chain === chain + ) { + chainStakingInBoth = _chain; + break; + } else if ( + ((hasNativeStaking(_chain) && poolType === YieldPoolType.NOMINATION_POOL) || + (hasNominationPool(_chain) && poolType === YieldPoolType.NATIVE_STAKING)) && + _chain === chain + ) { + chainStakingInBoth = _chain; + break; + } + } + + return chainStakingInBoth; + }, [specificList, poolType, chain]); + const handleOpenDetailModal = useCallback((): void => { Keyboard.dismiss(); isPressInfoBtnRef.current = true; @@ -602,14 +632,41 @@ const EarnTransaction: React.FC = (props: EarningProps) => { chainAsset, ]); + const showValidatorMaxCountWarning = useCallback( + (maxCount: number, userSelectedPoolCount: number, callback: VoidFunction) => { + return Alert.alert( + 'Pay attention!', + `You are recommended to choose ${maxCount} validators to optimize your earnings. Do you wish to continue with ${userSelectedPoolCount} validator${ + userSelectedPoolCount === 1 ? '' : 's' + }?`, + [ + { + text: 'Go back', + onPress: () => { + setSubmitLoading(false); + }, + style: 'default', + }, + { + text: 'Continue', + style: 'default', + isPreferred: false, + onPress: callback, + }, + ], + ); + }, + [], + ); + const onSubmit = useCallback(() => { if (!poolInfo) { Alert.alert('Unable to get earning data', 'Please, go back and try again later'); } + setSubmitLoading(true); const values = getValues(); const { from, value: _currentAmount } = values; - const getData = (submitStep: number): SubmitYieldJoinData => { if ([YieldPoolType.NOMINATION_POOL, YieldPoolType.NATIVE_STAKING].includes(poolInfo?.type) && poolTarget) { const targets = getTargetedPool; @@ -633,12 +690,10 @@ const EarnTransaction: React.FC = (props: EarningProps) => { return getJoinYieldParams(poolInfo, from, _currentAmount, processState.feeStructure[submitStep]); } }; - const path: OptimalYieldPath = { steps: processState.steps, totalFee: processState.feeStructure, }; - const submitData = async (step: number): Promise => { dispatchProcessState({ type: EarningActionType.STEP_SUBMIT, @@ -694,7 +749,6 @@ const EarnTransaction: React.FC = (props: EarningProps) => { return false; } }; - const maxCount = poolInfo?.statistic?.maxCandidatePerFarmer ?? 1; const userSelectedPoolCount = poolTarget.split(',').length ?? 1; const label = getValidatorLabel(chain); @@ -703,33 +757,15 @@ const EarnTransaction: React.FC = (props: EarningProps) => { label === 'Validator' && !DO_NOT_SHOW_VALIDATOR_ALERT_CASES.includes(slug) ) { - return Alert.alert( - 'Pay attention!', - `You are recommended to choose ${maxCount} validators to optimize your earnings. Do you wish to continue with ${userSelectedPoolCount} validator${ - userSelectedPoolCount === 1 ? '' : 's' - }?`, - [ - { - text: 'Go back', - onPress: () => { - setSubmitLoading(false); - }, - style: 'default', - }, - { - text: 'Continue', - style: 'default', - isPreferred: false, - onPress: () => { - submitData(currentStep) - .catch(onError) - .finally(() => { - setSubmitLoading(false); - }); - }, - }, - ], - ); + showValidatorMaxCountWarning(maxCount, userSelectedPoolCount, () => { + submitData(currentStep) + .catch(onError) + .finally(() => { + setSubmitLoading(false); + }); + }); + + return; // recheck this code } submitData(currentStep) .catch(onError) @@ -747,6 +783,7 @@ const EarnTransaction: React.FC = (props: EarningProps) => { poolTarget, processState.feeStructure, processState.steps, + showValidatorMaxCountWarning, slug, ]); @@ -764,8 +801,21 @@ const EarnTransaction: React.FC = (props: EarningProps) => { globalAppModalContext.hideGlobalModal(); }), }); - } else if (warningConfirmationData && warningConfirmationData.isShowWarningConfirmation) { + } else if (chainStakingBoth) { // hotfix for mkt campaign + const chainInfo = chainStakingBoth && chainInfoMap[chainStakingBoth]; + + const symbol = (!!chainInfo && chainInfo?.substrateInfo?.symbol) || ''; + const originChain = (!!chainInfo && chainInfo?.name) || ''; + let currentPoolType; + let stakedPoolType; + if (poolType === YieldPoolType.NOMINATION_POOL) { + currentPoolType = 'nomination pool'; + stakedPoolType = 'direct nomination'; + } else if (poolType === YieldPoolType.NATIVE_STAKING) { + currentPoolType = 'direct nomination'; + stakedPoolType = 'nomination pool'; + } confirmModal.setConfirmModal({ visible: true, completeBtnTitle: i18n.buttonTitles.continue, @@ -773,7 +823,7 @@ const EarnTransaction: React.FC = (props: EarningProps) => { title: 'Continue staking?', message: ( - {`You're currently staking ${warningConfirmationData.symbol} via ${warningConfirmationData.prevType}. Due to Polkadot's `} + {`You're currently staking ${symbol} via ${stakedPoolType}. Due to ${originChain}'s `} @@ -784,7 +834,7 @@ const EarnTransaction: React.FC = (props: EarningProps) => { {'upcoming changes'} - {`, continuing to stake via ${warningConfirmationData.currentType} will lead to pool-staked funds being frozen (e.g., can't unstake, claim rewards)`} + {`, continuing to stake via ${currentPoolType} will lead to pool-staked funds being frozen (e.g., can't unstake, claim rewards)`} ), @@ -799,14 +849,16 @@ const EarnTransaction: React.FC = (props: EarningProps) => { } }, 100); }, [ + chainInfoMap, + chainStakingBoth, confirmModal, currentConfirmations, globalAppModalContext, onSubmit, + poolType, renderConfirmationButtons, theme.colorPrimary, theme.colorWarning, - warningConfirmationData, ]); const onBack = useCallback(() => { @@ -880,12 +932,12 @@ const EarnTransaction: React.FC = (props: EarningProps) => { }, [inputAsset, setAsset]); useEffect(() => { - if (!currentFrom && (isAllAccount || accountSelectorList.length === 1)) { - if ((redirectFromPreviewRef.current && accountSelectorList.length >= 1) || accountSelectorList.length === 1) { - setFrom(accountSelectorList[0].address); + if (!currentFrom && (isAllAccount || accountAddressItems.length === 1)) { + if ((redirectFromPreviewRef.current && accountAddressItems.length >= 1) || accountAddressItems.length === 1) { + setFrom(accountAddressItems[0].address); } } - }, [accountSelectorList, currentFrom, isAllAccount, setFrom]); + }, [accountAddressItems, currentFrom, isAllAccount, setFrom]); useEffect(() => { if (currentStep === 0) { @@ -996,21 +1048,22 @@ const EarnTransaction: React.FC = (props: EarningProps) => { }, [chain, chainState?.active, forceFetchValidator, currentFrom, slug]); useEffect(() => { - if (redirectFromPreviewRef.current && !accountSelectorList.length && checkValidAccountLoading) { - const isChainEvm = chainInfoMap[poolChain] && _isChainEvmCompatible(chainInfoMap[poolChain]); - const accountType = isChainEvm ? EVM_ACCOUNT_TYPE : SUBSTRATE_ACCOUNT_TYPE; + if (redirectFromPreviewRef.current && !accountAddressItems.length && checkValidAccountLoading) { const chainName = chainInfoMap[poolChain]?.name; navigation.navigate('Home', { screen: 'Main', params: { screen: 'Earning', - params: { screen: 'EarningList', params: { step: 1, noAccountValid: true, accountType, chain: chainName } }, + params: { + screen: 'EarningList', + params: { step: 1, noAccountValid: true, accountType: undefined, chain: chainName }, + }, }, }); } else { setCheckValidAccountLoading(false); } - }, [accountSelectorList.length, chainInfoMap, checkValidAccountLoading, navigation, poolChain]); + }, [accountAddressItems.length, chainInfoMap, checkValidAccountLoading, navigation, poolChain]); useEffect(() => { if (!isLoading && !checkValidAccountLoading && (!compound || redirectFromPreviewRef.current)) { @@ -1205,7 +1258,7 @@ const EarnTransaction: React.FC = (props: EarningProps) => { )} = (props: EarningProps) => { accountSelectorRef && accountSelectorRef.current?.onCloseModal(); }} renderSelected={() => ( - + )} /> , poolType: YieldPoolType, poolChain?: string, -): ((account: AccountJson) => boolean) => { - return (account: AccountJson): boolean => { - const nominator = positionInfos.find(item => item.address.toLowerCase() === account.address.toLowerCase()); +): ((account: AccountProxy) => boolean) => { + return (account: AccountProxy): boolean => { + const nominator = positionInfos.find( + item => account.accounts && account.accounts.some(ap => ap.address.toLowerCase() === item.address.toLowerCase()), + ); return ( new BigN(nominator?.activeStake || BN_ZERO).gt(BN_ZERO) && @@ -101,7 +104,7 @@ export const Unbond = ({ const currentValue = useWatch({ name: 'value', control }); const fastLeave = useWatch({ name: 'fastLeave', control }); - const { accounts, isAllAccount } = useSelector((state: RootState) => state.accountState); + const { accountProxies, isAllAccount } = useSelector((state: RootState) => state.accountState); const { chainInfoMap } = useSelector((state: RootState) => state.chainStore); const { poolInfoMap } = useSelector((state: RootState) => state.earning); const poolInfo = poolInfoMap[slug]; @@ -219,9 +222,34 @@ export const Unbond = ({ }, [poolInfo?.statistic]); const [loading, setLoading] = useState(false); - const accountList = useMemo(() => { - return accounts.filter(_accountFilterFunc(allPositions, chainInfoMap, poolType, poolChain)); - }, [accounts, allPositions, chainInfoMap, poolChain, poolType]); + const accountList: AccountAddressItemType[] = useMemo(() => { + const chainInfo = poolChain ? chainInfoMap[poolChain] : undefined; + if (!chainInfo) { + return []; + } + const filteredAccountList = accountProxies.filter( + _accountFilterFunc(allPositions, chainInfoMap, poolType, poolChain), + ); + + const result: AccountAddressItemType[] = []; + filteredAccountList.forEach(ap => { + ap.accounts.forEach(a => { + const address = getReformatedAddressRelatedToChain(a, chainInfo); + + if (address) { + result.push({ + accountName: ap.name, + accountProxyId: ap.id, + accountProxyType: ap.accountType, + accountType: a.type, + address, + }); + } + }); + }); + + return result; + }, [accountProxies, allPositions, chainInfoMap, poolChain, poolType]); const renderBounded = useCallback(() => { return ; diff --git a/src/screens/Transaction/Withdraw/index.tsx b/src/screens/Transaction/Withdraw/index.tsx index bc9508e9a..24aee25fb 100644 --- a/src/screens/Transaction/Withdraw/index.tsx +++ b/src/screens/Transaction/Withdraw/index.tsx @@ -1,5 +1,6 @@ import { getAstarWithdrawable } from '@subwallet/extension-base/services/earning-service/handlers/native-staking/astar'; import { + AccountProxy, RequestYieldWithdrawal, UnstakingInfo, UnstakingStatus, @@ -17,8 +18,6 @@ import { useSelector } from 'react-redux'; import { accountFilterFunc } from 'screens/Transaction/helper/earning'; import { RootState } from 'stores/index'; import useGetAccountByAddress from 'hooks/screen/useGetAccountByAddress'; -import { AccountJson } from '@subwallet/extension-base/background/types'; -import { isSameAddress } from '@subwallet/extension-base/utils'; import { AmountData, ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes'; import { _ChainInfo } from '@subwallet/chain-list/types'; import MetaInfo from 'components/MetaInfo'; @@ -42,15 +41,20 @@ import { yieldSubmitStakingWithdrawal } from 'messaging/index'; import usePreCheckAction from 'hooks/account/usePreCheckAction'; import useGetConfirmationByScreen from 'hooks/static-content/useGetConfirmationByScreen'; import { GlobalModalContext } from 'providers/GlobalModalContext'; +import { isSameAddress } from 'utils/account/account'; +import { AccountAddressItemType } from 'types/account'; +import { getReformatedAddressRelatedToChain } from 'utils/account'; const filterAccount = ( chainInfoMap: Record, allPositionInfos: YieldPositionInfo[], poolType: YieldPoolType, poolChain?: string, -): ((account: AccountJson) => boolean) => { - return (account: AccountJson): boolean => { - const nomination = allPositionInfos.find(data => isSameAddress(data.address, account.address)); +): ((account: AccountProxy) => boolean) => { + return (account: AccountProxy): boolean => { + const nomination = allPositionInfos.find(data => + account.accounts.some(ap => isSameAddress(data.address, ap.address)), + ); return ( (nomination @@ -68,7 +72,7 @@ export const Withdraw = ({ const theme = useSubWalletTheme().swThemes; const navigation = useNavigation(); - const { isAllAccount, accounts } = useSelector((state: RootState) => state.accountState); + const { isAllAccount, accountProxies } = useSelector((state: RootState) => state.accountState); const { chainInfoMap } = useSelector((state: RootState) => state.chainStore); const { poolInfoMap } = useSelector((state: RootState) => state.earning); @@ -103,7 +107,7 @@ export const Withdraw = ({ const accountInfo = useGetAccountByAddress(fromValue); const poolInfo = useMemo(() => poolInfoMap[slug], [poolInfoMap, slug]); const stakingChain = useMemo(() => poolInfo?.chain || '', [poolInfo?.chain]); - + const poolChain = poolInfo?.chain; const inputAsset = useGetChainAssetInfo(poolInfo?.metadata.inputAsset); const decimals = inputAsset?.decimals || 0; const symbol = inputAsset?.symbol || ''; @@ -136,8 +140,31 @@ export const Withdraw = ({ ); const accountList = useMemo(() => { - return accounts.filter(filterAccount(chainInfoMap, allPositionInfos, poolInfo?.type)); - }, [accounts, allPositionInfos, chainInfoMap, poolInfo?.type]); + const chainInfo = poolChain ? chainInfoMap[poolChain] : undefined; + if (!chainInfo) { + return []; + } + const filteredAccountList = accountProxies.filter(filterAccount(chainInfoMap, allPositionInfos, poolInfo?.type)); + + const result: AccountAddressItemType[] = []; + filteredAccountList.forEach(ap => { + ap.accounts.forEach(a => { + const address = getReformatedAddressRelatedToChain(a, chainInfo); + + if (address) { + result.push({ + accountName: ap.name, + accountProxyId: ap.id, + accountProxyType: ap.accountType, + accountType: a.type, + address, + }); + } + }); + }); + + return result; + }, [accountProxies, allPositionInfos, chainInfoMap, poolChain, poolInfo?.type]); const unstakingInfo = useMemo((): UnstakingInfo | undefined => { if (fromValue && !isAccountAll(fromValue) && !!yieldPosition) { diff --git a/src/screens/Transaction/helper/earning/base.ts b/src/screens/Transaction/helper/earning/base.ts index 5de5c1890..783d8b7f2 100644 --- a/src/screens/Transaction/helper/earning/base.ts +++ b/src/screens/Transaction/helper/earning/base.ts @@ -1,30 +1,25 @@ import { _ChainInfo } from '@subwallet/chain-list/types'; -import { AccountJson } from '@subwallet/extension-base/background/types'; -import { - _getSubstrateGenesisHash, - _isChainEvmCompatible, -} from '@subwallet/extension-base/services/chain-service/utils'; - +import { _isChainEvmCompatible } from '@subwallet/extension-base/services/chain-service/utils'; import { isEthereumAddress } from '@polkadot/util-crypto'; -import { YieldPoolType } from '@subwallet/extension-base/types'; +import { AccountProxy, AccountProxyType, YieldPoolType } from '@subwallet/extension-base/types'; import { isAccountAll } from '@subwallet/extension-base/utils'; import { ALL_KEY } from 'constants/index'; -const defaultAccountFilter = (poolType: YieldPoolType, chain?: _ChainInfo): ((account: AccountJson) => boolean) => { - return (account: AccountJson) => { - if (account.originGenesisHash && chain && _getSubstrateGenesisHash(chain) !== account.originGenesisHash) { +const defaultAccountFilter = (poolType: YieldPoolType, poolChain?: string): ((account: AccountProxy) => boolean) => { + return (account: AccountProxy) => { + if (account.specialChain && poolChain !== account.specialChain) { return false; } - if (isAccountAll(account.address)) { + if (isAccountAll(account.id)) { return false; } - // if (account.isReadOnly) { - // return false; - // } + if (account.accountType === AccountProxyType.READ_ONLY) { + return false; + } - return !(poolType === YieldPoolType.NOMINATION_POOL && isEthereumAddress(account.address)); + return !(poolType === YieldPoolType.NOMINATION_POOL && account.accounts.some(ap => isEthereumAddress(ap.address))); }; }; @@ -32,14 +27,14 @@ export const accountFilterFunc = ( chainInfoMap: Record, poolType: YieldPoolType, poolChain?: string, -): ((account: AccountJson) => boolean) => { - return (account: AccountJson) => { +): ((account: AccountProxy) => boolean) => { + return (account: AccountProxy) => { if (poolChain && poolChain !== ALL_KEY) { const chain = chainInfoMap[poolChain]; - const defaultFilter = defaultAccountFilter(poolType, chain); + const defaultFilter = defaultAccountFilter(poolType, poolChain); const isEvmChain = _isChainEvmCompatible(chain); - return defaultFilter(account) && isEvmChain === isEthereumAddress(account.address); + return defaultFilter(account) && account.accounts.some(ap => isEvmChain === isEthereumAddress(ap.address)); } else { return defaultAccountFilter(poolType)(account); } diff --git a/src/utils/transaction/persist.ts b/src/utils/transaction/persist.ts index 82090c202..18db979c0 100644 --- a/src/utils/transaction/persist.ts +++ b/src/utils/transaction/persist.ts @@ -15,8 +15,9 @@ import { UN_STAKE_TRANSACTION, WITHDRAW_TRANSACTION, } from 'constants/localStorage'; +import { ExtraExtrinsicType, ExtrinsicTypeMobile } from 'types/transaction'; -export const detectTransactionPersistKey = (type?: ExtrinsicType): string => { +export const detectTransactionPersistKey = (type?: ExtrinsicTypeMobile): string => { switch (type) { case ExtrinsicType.SEND_NFT: return NFT_TRANSACTION; @@ -41,6 +42,10 @@ export const detectTransactionPersistKey = (type?: ExtrinsicType): string => { return CLAIM_REWARD_TRANSACTION; case ExtrinsicType.SWAP: return SWAP_TRANSACTION; + case ExtraExtrinsicType.IMPORT_NFT: + return ExtraExtrinsicType.IMPORT_NFT; + case ExtraExtrinsicType.IMPORT_TOKEN: + return ExtraExtrinsicType.IMPORT_TOKEN; // case ExtrinsicType.CLAIM_AVAIL_BRIDGE: // return CLAIM_AVAIL_BRIDGE_TRANSACTION; default: