diff --git a/src/components/Filters/SearchFilter.tsx b/src/components/Filters/SearchFilter.tsx index d30c17745..ee4242a2b 100644 --- a/src/components/Filters/SearchFilter.tsx +++ b/src/components/Filters/SearchFilter.tsx @@ -2,7 +2,8 @@ import React, { useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { useSearchParams } from 'react-router-dom'; -import { isHash, addressIsBech32 } from 'helpers'; +import { METACHAIN_SHARD_ID } from 'appConstants'; +import { isHash, addressIsBech32, isMetachain } from 'helpers'; import { faSearch } from 'icons/regular'; import { TransactionFiltersEnum } from 'types'; @@ -11,7 +12,7 @@ export interface SearchFilterType { filter: TransactionFiltersEnum; placeholder?: string; className?: string; - validation?: 'address' | 'hash'; + validation?: 'address' | 'hash' | 'address-or-metachain'; } export const SearchFilter = ({ @@ -61,9 +62,26 @@ export const SearchFilter = ({ updateUrl(searchValue); } } - } else { - updateUrl(searchValue); + if (validation === 'address-or-metachain') { + const isValid = + isMetachain(searchValue) || addressIsBech32(searchValue); + + setErrorText(isValid ? '' : 'Invalid Address'); + if (isValid) { + if (isMetachain(searchValue)) { + updateUrl(String(METACHAIN_SHARD_ID)); + + return; + } + + updateUrl(searchValue); + } + } + + return; } + + updateUrl(searchValue); }; return ( diff --git a/src/components/Filters/TransactionsFilters/FromColumnFilters.tsx b/src/components/Filters/TransactionsFilters/FromColumnFilters.tsx index 33dc6d67c..3c1a1976f 100644 --- a/src/components/Filters/TransactionsFilters/FromColumnFilters.tsx +++ b/src/components/Filters/TransactionsFilters/FromColumnFilters.tsx @@ -38,7 +38,7 @@ export const FromColumnFilters = ({ name='sender-filter' filter={TransactionFiltersEnum.sender} placeholder='Address' - validation='address' + validation='address-or-metachain' /> diff --git a/src/components/FormatValue/FormatUSD/FormatUSD.tsx b/src/components/FormatValue/FormatUSD/FormatUSD.tsx index fc86be375..39e4a7957 100644 --- a/src/components/FormatValue/FormatUSD/FormatUSD.tsx +++ b/src/components/FormatValue/FormatUSD/FormatUSD.tsx @@ -26,7 +26,6 @@ export const FormatUSD = (props: FormatUSDUIType) => { showPrefix = true, className } = props; - const amount = decimals ? formatAmount({ input: String(unprocessedValue), diff --git a/src/components/Links/CollectionLink/CollectionLink.tsx b/src/components/Links/CollectionLink/CollectionLink.tsx index cb797bf1b..de70b3835 100644 --- a/src/components/Links/CollectionLink/CollectionLink.tsx +++ b/src/components/Links/CollectionLink/CollectionLink.tsx @@ -38,6 +38,10 @@ export const CollectionLink = ({ collection, ...rest }: CollectionLinkType) => ( /> )} - + ); diff --git a/src/components/NftBadge/NftBadge.tsx b/src/components/NftBadge/NftBadge.tsx index 8c0d71432..955f853fb 100644 --- a/src/components/NftBadge/NftBadge.tsx +++ b/src/components/NftBadge/NftBadge.tsx @@ -1,44 +1,30 @@ -import { NftTypeEnum, TokenTypeEnum } from 'types'; +import { Overlay, NftTypeBadge, NftSubTypeBadge } from 'components'; +import { + NftSubtypeEnum, + NftTypeEnum, + TokenTypeEnum, + WithClassnameType +} from 'types'; + +export interface NftBadgeUIType extends WithClassnameType { + type: NftTypeEnum | TokenTypeEnum; + subType?: NftSubtypeEnum; + showTooltip?: boolean; +} export const NftBadge = ({ type, + subType, + showTooltip = true, className -}: { - type: NftTypeEnum | TokenTypeEnum; - className?: string; -}) => { - switch (type) { - case NftTypeEnum.SemiFungibleESDT: - return ( -
- SFT -
- ); - case NftTypeEnum.NonFungibleESDT: - return ( -
- NFT -
- ); - case NftTypeEnum.MetaESDT: - return ( -
- Meta-ESDT -
- ); - default: - return null; +}: NftBadgeUIType) => { + if (showTooltip && subType) { + return ( + }> + + + ); } + + return ; }; diff --git a/src/components/NftBadge/NftSubTypeBadge.tsx b/src/components/NftBadge/NftSubTypeBadge.tsx new file mode 100644 index 000000000..c325487a7 --- /dev/null +++ b/src/components/NftBadge/NftSubTypeBadge.tsx @@ -0,0 +1,41 @@ +import classNames from 'classnames'; + +import { NftSubtypeEnum } from 'types'; + +export const NftSubTypeBadge = ({ + subType, + className +}: { + subType: NftSubtypeEnum; + className?: string; +}) => { + switch (subType) { + // NFT Subtypes + case NftSubtypeEnum.DynamicSemiFungibleESDT: + case NftSubtypeEnum.DynamicNonFungibleESDT: + case NftSubtypeEnum.NonFungibleESDTv2: + case NftSubtypeEnum.DynamicMetaESDT: + return ( +
+ {subType} +
+ ); + + default: + return null; + } +}; diff --git a/src/components/NftBadge/NftTypeBadge.tsx b/src/components/NftBadge/NftTypeBadge.tsx new file mode 100644 index 000000000..39ae381ea --- /dev/null +++ b/src/components/NftBadge/NftTypeBadge.tsx @@ -0,0 +1,35 @@ +import classNames from 'classnames'; + +import { getNftText } from 'helpers'; +import { NftTypeEnum, TokenTypeEnum } from 'types'; + +export const NftTypeBadge = ({ + type, + className +}: { + type: NftTypeEnum | TokenTypeEnum; + className?: string; +}) => { + switch (type) { + // default NFT types + case NftTypeEnum.SemiFungibleESDT: + case NftTypeEnum.NonFungibleESDT: + case NftTypeEnum.MetaESDT: + return ( +
+ {getNftText(type)} +
+ ); + + default: + return null; + } +}; diff --git a/src/components/NftBadge/index.ts b/src/components/NftBadge/index.ts index 20327fe81..40994be10 100644 --- a/src/components/NftBadge/index.ts +++ b/src/components/NftBadge/index.ts @@ -1 +1,3 @@ export * from './NftBadge'; +export * from './NftTypeBadge'; +export * from './NftSubTypeBadge'; diff --git a/src/config/config.devnet.ts b/src/config/config.devnet.ts index 3134fbb71..a119d982b 100644 --- a/src/config/config.devnet.ts +++ b/src/config/config.devnet.ts @@ -25,11 +25,11 @@ export const multiversxApps = allApps([ }, { id: 'explorer', - url: 'http://devnet-explorer.multiversx.com' + url: 'https://devnet-explorer.multiversx.com' }, { id: 'xexchange', - url: 'http://devnet.xexchange.com' + url: 'https://devnet.xexchange.com' }, { id: 'xspotlight', diff --git a/src/config/config.testnet.ts b/src/config/config.testnet.ts index 94d09c1af..7e74adad2 100644 --- a/src/config/config.testnet.ts +++ b/src/config/config.testnet.ts @@ -25,7 +25,11 @@ export const multiversxApps = allApps([ }, { id: 'explorer', - url: 'http://testnet-explorer.multiversx.com' + url: 'https://testnet-explorer.multiversx.com' + }, + { + id: 'xexchange', + url: 'https://testnet.xexchange.com' }, { id: 'xspotlight', diff --git a/src/helpers/formatValue/formatAmount.ts b/src/helpers/formatValue/formatAmount.ts index 62522e4f6..4947c46fc 100644 --- a/src/helpers/formatValue/formatAmount.ts +++ b/src/helpers/formatValue/formatAmount.ts @@ -1,6 +1,6 @@ import { FormatAmountType as SdkDappFormatAmountType } from '@multiversx/sdk-dapp/utils/operations/formatAmount'; import { stringIsInteger } from '@multiversx/sdk-dapp/utils/validation/stringIsInteger'; -import { MAX_DISPLAY_ZERO_DECIMALS, ZERO } from 'appConstants'; +import { ELLIPSIS, MAX_DISPLAY_ZERO_DECIMALS, ZERO } from 'appConstants'; import { DECIMALS, DIGITS } from 'config'; interface FormatAmountType extends SdkDappFormatAmountType { @@ -16,7 +16,8 @@ export function formatAmount({ addCommas = false }: FormatAmountType) { if (!stringIsInteger(input, false)) { - throw new Error('Invalid input'); + console.error('Invalid input', input); + return ELLIPSIS; } showLastNonZeroDecimal = diff --git a/src/helpers/getValue/getAccountDelegationDetails.ts b/src/helpers/getValue/getAccountDelegationDetails.ts new file mode 100644 index 000000000..9ca9a9b2b --- /dev/null +++ b/src/helpers/getValue/getAccountDelegationDetails.ts @@ -0,0 +1,53 @@ +import BigNumber from 'bignumber.js'; + +import { ZERO } from 'appConstants'; +import { AccountDelegationType } from 'types'; + +export const getAccountDelegationDetails = ( + delegation: AccountDelegationType[] +) => { + if (delegation && delegation.length > 0) { + const bNactive = delegation + .map(({ userActiveStake }) => userActiveStake) + .reduce((a, b) => new BigNumber(a).plus(b), new BigNumber(ZERO)); + const bNclaimableRewards = delegation + .map(({ claimableRewards }) => claimableRewards ?? ZERO) + .reduce((a, b) => new BigNumber(a).plus(b), new BigNumber(ZERO)); + const undelegatedAmounts = delegation + .map( + ({ userUndelegatedList }) => + userUndelegatedList?.map(({ amount }) => amount) ?? [] + ) + .reduce((a, b) => a.concat(b), []); + const bNunstaked = undelegatedAmounts.reduce( + (a, b) => new BigNumber(a).plus(b), + new BigNumber(ZERO) + ); + const bNunbondable = delegation + .map(({ userUnBondable }) => userUnBondable) + .reduce((a, b) => new BigNumber(a).plus(b), new BigNumber(ZERO)); + + const activePlusUnStaked = bNactive.plus(bNunstaked); + const bNlocked = activePlusUnStaked + .plus(bNclaimableRewards) + .plus(bNunbondable); + + const show = bNlocked.isGreaterThan(0); + + return { + active: bNactive, + unstaked: bNunstaked.plus(bNunbondable), + claimable: bNclaimableRewards, + locked: bNlocked, + show + }; + } + + return { + active: new BigNumber(ZERO), + unstaked: new BigNumber(ZERO), + claimable: new BigNumber(ZERO), + locked: new BigNumber(ZERO), + show: false + }; +}; diff --git a/src/helpers/getValue/getAccountLegacyDelegationDetails.ts b/src/helpers/getValue/getAccountLegacyDelegationDetails.ts new file mode 100644 index 000000000..c890c40b9 --- /dev/null +++ b/src/helpers/getValue/getAccountLegacyDelegationDetails.ts @@ -0,0 +1,53 @@ +import BigNumber from 'bignumber.js'; + +import { ZERO } from 'appConstants'; +import { AccountDelegationLegacyType } from 'types'; + +export const getAccountLegacyDelegationDetails = ( + delegationLegacy: AccountDelegationLegacyType +) => { + if (delegationLegacy) { + const bNactive = new BigNumber(delegationLegacy.userActiveStake ?? ZERO); + const bNuserWaitingStake = new BigNumber( + delegationLegacy.userWaitingStake ?? ZERO + ); + const bNclaimableRewards = new BigNumber( + delegationLegacy.claimableRewards ?? ZERO + ); + const bNunstakedStakeLegacy = new BigNumber( + delegationLegacy.userUnstakedStake ?? ZERO + ); + const bNdeferredPaymentLegacy = new BigNumber( + delegationLegacy.userDeferredPaymentStake ?? ZERO + ); + const bNwithdrawOnlyStakeLegacy = new BigNumber( + delegationLegacy.userWithdrawOnlyStake ?? ZERO + ); + + const bNunstaked = bNunstakedStakeLegacy.plus(bNdeferredPaymentLegacy); + const bNlocked = bNactive + .plus(bNuserWaitingStake) + .plus(bNclaimableRewards) + .plus(bNunstakedStakeLegacy) + .plus(bNdeferredPaymentLegacy) + .plus(bNwithdrawOnlyStakeLegacy); + + const show = bNlocked.isGreaterThan(0); + + return { + active: bNactive, + unstaked: bNunstaked, + claimable: bNclaimableRewards, + locked: bNlocked, + show + }; + } + + return { + active: new BigNumber(ZERO), + unstaked: new BigNumber(ZERO), + claimable: new BigNumber(ZERO), + locked: new BigNumber(ZERO), + show: false + }; +}; diff --git a/src/helpers/getValue/getAccountStakingDetails.ts b/src/helpers/getValue/getAccountStakingDetails.ts new file mode 100644 index 000000000..9297452a3 --- /dev/null +++ b/src/helpers/getValue/getAccountStakingDetails.ts @@ -0,0 +1,81 @@ +import BigNumber from 'bignumber.js'; + +import { + getAccountDelegationDetails, + getAccountLegacyDelegationDetails, + getAccountValidatorStakeDetails +} from 'helpers'; +import { + AccountDelegationType, + AccountDelegationLegacyType, + AccountStakeType +} from 'types'; + +export const getAccountStakingDetails = ({ + stake, + delegation, + legacyDelegation +}: { + stake: AccountStakeType; + delegation: AccountDelegationType[]; + legacyDelegation: AccountDelegationLegacyType; +}) => { + const { + active: activeDelegation, + claimable: claimableDelegation, + unstaked: unstakedDelegation, + locked: lockedDelegation, + show: showDelegation + } = getAccountDelegationDetails(delegation); + + const { + active: activeLegacyDelegation, + claimable: claimableLegacyDelegation, + unstaked: unstakedLegacyDelegation, + locked: lockedLegacyDelegation, + show: showLegacyDelegation + } = getAccountLegacyDelegationDetails(legacyDelegation); + + const { + active: activeValidatorStake, + unstaked: unstakedValidatorStake, + locked: lockedValidatorStake, + show: showValidatorStake + } = getAccountValidatorStakeDetails(stake); + + const totalActiveStake = activeDelegation + .plus(activeLegacyDelegation) + .plus(activeValidatorStake); + const totalLocked = new BigNumber(lockedDelegation) + .plus(lockedLegacyDelegation) + .plus(lockedValidatorStake); + const totalClaimable = new BigNumber(claimableDelegation).plus( + claimableLegacyDelegation + ); + const totalUnstaked = unstakedDelegation + .plus(unstakedLegacyDelegation) + .plus(unstakedValidatorStake); + + const showStakingDetails = + showDelegation || showLegacyDelegation || showValidatorStake; + + return { + showDelegation, + showLegacyDelegation, + showValidatorStake, + showStakingDetails, + + activeValidatorStake: activeValidatorStake.toString(10), + activeDelegation: activeDelegation.toString(10), + activeLegacyDelegation: activeLegacyDelegation.toString(10), + + lockedValidatorStake: lockedValidatorStake.toString(10), + lockedDelegation: lockedDelegation.toString(10), + lockedLegacyDelegation: lockedLegacyDelegation.toString(10), + + totalActiveStake: totalActiveStake.toString(10), + totalLocked: totalLocked.toString(10), + totalClaimable: totalClaimable.toString(10), + totalUnstaked: totalUnstaked.toString(10) + }; +}; diff --git a/src/helpers/getValue/getAccountValidatorStakeDetails.ts b/src/helpers/getValue/getAccountValidatorStakeDetails.ts new file mode 100644 index 000000000..13192139a --- /dev/null +++ b/src/helpers/getValue/getAccountValidatorStakeDetails.ts @@ -0,0 +1,35 @@ +import BigNumber from 'bignumber.js'; + +import { ZERO } from 'appConstants'; +import { AccountStakeType } from 'types'; + +export const getAccountValidatorStakeDetails = (stake: AccountStakeType) => { + if (stake) { + const bNactive = new BigNumber(stake.totalStaked ?? ZERO); + const bNunstaked = + stake.unstakedTokens && stake.unstakedTokens.length > 0 + ? stake.unstakedTokens + .map(({ amount }) => amount) + .reduce((a, b) => new BigNumber(a).plus(b), new BigNumber(ZERO)) + : new BigNumber(ZERO); + + const bNlocked = bNactive.plus(bNunstaked); + const show = bNlocked.isGreaterThan(0); + + return { + active: bNactive, + unstaked: bNunstaked, + claimable: new BigNumber(ZERO), + locked: bNlocked, + show + }; + } + + return { + active: new BigNumber(ZERO), + unstaked: new BigNumber(ZERO), + claimable: new BigNumber(ZERO), + locked: new BigNumber(ZERO), + show: false + }; +}; diff --git a/src/helpers/getValue/getStringPlural.ts b/src/helpers/getValue/getStringPlural.ts index 99c7e4ba4..6e389b195 100644 --- a/src/helpers/getValue/getStringPlural.ts +++ b/src/helpers/getValue/getStringPlural.ts @@ -9,7 +9,10 @@ export const getStringPlural = ( value: string | number | BigNumber, options?: GetStringPluralOptionsType ) => { - const bNValue = BigNumber.isBigNumber(value) ? value : new BigNumber(value); + const formattedValue = String(value).replace(/[^\d.-]/g, ''); + const bNValue = BigNumber.isBigNumber(value) + ? value + : new BigNumber(formattedValue); if (bNValue.isGreaterThan(1) || bNValue.isZero()) { return `${options?.string}${options?.plural ?? 's'}`; diff --git a/src/helpers/getValue/getTransactionMessages.ts b/src/helpers/getValue/getTransactionMessages.ts index 509d26b19..44e96b59d 100644 --- a/src/helpers/getValue/getTransactionMessages.ts +++ b/src/helpers/getValue/getTransactionMessages.ts @@ -28,9 +28,14 @@ const getDisplayMessages = ({ const transactionActionTransfers = transaction?.action?.arguments?.transfers ?? []; if (transactionActionTransfers.length === 1) { - return `Not enough balance of ${getTokenDisplayType( - transactionActionTransfers[0].type - )} ${transactionActionTransfers[0].identifier}`; + const tokenIdentifier = + transactionActionTransfers[0]?.identifier || + transactionActionTransfers[0]?.ticker; + if (tokenIdentifier) { + return `Not enough balance of ${getTokenDisplayType( + transactionActionTransfers[0].type + )} ${tokenIdentifier}`; + } } return 'Not enough balance of transferred token'; case compareMessage?.includes(TransactionMessagesEnum.invalidLiquidity): diff --git a/src/helpers/getValue/index.ts b/src/helpers/getValue/index.ts index b5eae3721..f5ebe40aa 100644 --- a/src/helpers/getValue/index.ts +++ b/src/helpers/getValue/index.ts @@ -1,3 +1,7 @@ +export * from './getAccountDelegationDetails'; +export * from './getAccountLegacyDelegationDetails'; +export * from './getAccountStakingDetails'; +export * from './getAccountValidatorStakeDetails'; export * from './getColors'; export * from './getDisplayReceiver'; export * from './getNftText'; diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 975d9554c..0a8f12dca 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -13,6 +13,7 @@ export * from './isContract'; export * from './isEgldToken'; export * from './isEllipsisActive'; export * from './isHash'; +export * from './isMetachain'; export * from './isUtf8'; export * from './parseAmount'; export * from './parseJwt'; diff --git a/src/helpers/isMetachain.ts b/src/helpers/isMetachain.ts new file mode 100644 index 000000000..70b007ee4 --- /dev/null +++ b/src/helpers/isMetachain.ts @@ -0,0 +1,8 @@ +import { METACHAIN_SHARD_ID } from 'appConstants'; + +export const isMetachain = (value?: string | number) => { + return ( + METACHAIN_SHARD_ID.toString() === String(value) || + String(value).toLowerCase() === 'metachain' + ); +}; diff --git a/src/hooks/fetch/useFetchAccountStakingDetails.ts b/src/hooks/fetch/useFetchAccountStakingDetails.ts index ff1013f9b..fc231ec76 100644 --- a/src/hooks/fetch/useFetchAccountStakingDetails.ts +++ b/src/hooks/fetch/useFetchAccountStakingDetails.ts @@ -1,10 +1,15 @@ -import BigNumber from 'bignumber.js'; import { useDispatch } from 'react-redux'; import { LEGACY_DELEGATION_NODES_IDENTITY } from 'appConstants'; +import { getAccountStakingDetails } from 'helpers'; import { useAdapter } from 'hooks'; import { setAccountStaking } from 'redux/slices'; -import { IdentityType, ProviderType, AccountDelegationType } from 'types'; +import { + IdentityType, + ProviderType, + AccountDelegationType, + ApiAdapterResponseType +} from 'types'; let currentRequest: any = null; @@ -15,7 +20,7 @@ export const useFetchAccountStakingDetails = () => { getAccountDelegation, getAccountStake, getProviders, - getIdentities + getIdentity } = useAdapter(); const getAccountStakingDetailsOnce = ({ address }: { address: string }) => { @@ -50,254 +55,83 @@ export const useFetchAccountStakingDetails = () => { const [delegationData, stakeData, delegationLegacyData] = await getAccountStakingDetailsOnce({ address }); - let delegationLegacyIdentity: IdentityType | undefined = undefined; - const delegationProviders: ProviderType[] = []; - let bNtotalStaked = new BigNumber(0); - let bNtotalDelegation = new BigNumber(0); - let bNtotalLegacyDelegation = new BigNumber(0); - let bNtotalLocked = new BigNumber(0); - let bNtotalClaimable = new BigNumber(0); - let bNtotalActiveStake = new BigNumber(0); - let bNtotalUnstakedValue = new BigNumber(0); - - const delegationFetched = delegationData.success ? delegationData.data : {}; - const stakeFetched = stakeData.success ? stakeData.data : {}; - const delegationLegacyFetched = delegationLegacyData.success - ? delegationLegacyData.data - : {}; + const delegationFetched = delegationData.success && delegationData.data; + const stakeFetched = stakeData.success && stakeData.data; + const delegationLegacyFetched = + delegationLegacyData.success && delegationLegacyData.data; const delegation: AccountDelegationType[] = delegationFetched ? delegationData.data : []; const stake = stakeFetched ? stakeData.data : {}; - const delegationLegacy = delegationLegacyFetched + const legacyDelegation = delegationLegacyFetched ? delegationLegacyData.data : {}; - if (stake) { - bNtotalStaked = new BigNumber(stake.totalStaked ? stake.totalStaked : 0); - } - if (delegationLegacy) { - const bNuserActiveStake = new BigNumber(delegationLegacy.userActiveStake); - const bNuserWaitingStake = new BigNumber( - delegationLegacy.userWaitingStake - ); - const bNClaimableRewardsLegacy = new BigNumber( - delegationLegacy.claimableRewards - ); - - bNtotalClaimable = bNtotalClaimable.plus(bNClaimableRewardsLegacy); - bNtotalLegacyDelegation = new BigNumber( - bNuserActiveStake - .plus(bNuserWaitingStake) - .plus(bNClaimableRewardsLegacy) - ); - - if (delegationLegacy.userUnstakedStake) { - const bNunstakedStakeLegacy = new BigNumber( - delegationLegacy.userUnstakedStake - ); - bNtotalUnstakedValue = bNtotalUnstakedValue.plus(bNunstakedStakeLegacy); - } - if (delegationLegacy.userDeferredPaymentStake) { - const bNdeferredPaymentLegacy = new BigNumber( - delegationLegacy.userDeferredPaymentStake - ); - bNtotalUnstakedValue = bNtotalUnstakedValue.plus( - bNdeferredPaymentLegacy - ); - } - } - - if (delegation && delegation.length > 0) { - const bNtotalUserActiveStake = delegation - .map(({ userActiveStake }) => userActiveStake) - .reduce((a, b) => new BigNumber(a).plus(b), new BigNumber('0')); - const bNtotalClaimableRewards = delegation - .map(({ claimableRewards }) => claimableRewards || '0') - .reduce((a, b) => new BigNumber(a).plus(b), new BigNumber('0')); - const undelegatedAmounts = delegation - .map( - ({ userUndelegatedList }) => - userUndelegatedList?.map(({ amount }) => amount) ?? [] - ) - .reduce((a, b) => a.concat(b), []); - const bNtotalUserUnStakedValue = undelegatedAmounts.reduce( - (a, b) => new BigNumber(a).plus(b), - new BigNumber('0') - ); - const activePlusUnStaked = bNtotalUserActiveStake.plus( - bNtotalUserUnStakedValue - ); - bNtotalActiveStake = bNtotalUserActiveStake; - bNtotalUnstakedValue = bNtotalUnstakedValue.plus( - bNtotalUserUnStakedValue - ); - bNtotalDelegation = bNtotalClaimableRewards.plus(activePlusUnStaked); - bNtotalClaimable = bNtotalClaimable.plus(bNtotalClaimableRewards); - } - if (stake && delegation && delegationLegacy) { - bNtotalLocked = bNtotalStaked - .plus(bNtotalLegacyDelegation) - .plus(bNtotalDelegation); - } - - const visibleDelegation = - delegation?.filter( - (delegation) => - delegation.userActiveStake !== '0' || - delegation.claimableRewards !== '0' || - (delegation.userUndelegatedList && - delegation.userUndelegatedList.length > 0) - ) ?? []; - const showDelegation = visibleDelegation.length > 0; - - const showDelegationLegacy = - delegationLegacy && - (delegationLegacy.claimableRewards !== '0' || - delegationLegacy.userWaitingStake !== '0' || - delegationLegacy.userActiveStake !== '0' || - delegationLegacy.userUnstakedStake !== '0' || - delegationLegacy.userDeferredPaymentStake !== '0'); - - const showStake = Boolean( - stake && - (stake?.totalStaked !== '0' || - (stake?.unstakedTokens && stake.unstakedTokens.length > 0)) - ); - - //const updatedContracts = contracts ? contracts.join(',') : undefined; - const fields = [ - 'identity', - 'provider', - 'stake', - 'numNodes', - 'apr', - 'serviceFee', - 'delegationCap' - ].join(','); - const contracts = visibleDelegation - .map((delegation) => delegation?.contract) - .join(','); + const stakingDetails = getAccountStakingDetails({ + delegation, + stake, + legacyDelegation + }); + const { showDelegation, showLegacyDelegation } = stakingDetails; - const stakingDataReady = Boolean( + const accountStakingFetched = Boolean( stakeFetched && delegationFetched && delegationLegacyFetched ); const stakingData = { address, - stakingDataReady, - delegation, - showDelegation, stake, - showStake, - delegationLegacy, - showDelegationLegacy, - totalStaked: bNtotalStaked.toString(), - totalDelegation: bNtotalDelegation.toString(), - totalLegacyDelegation: bNtotalLegacyDelegation.toString(), - totalLocked: bNtotalLocked.toString(), - totalClaimable: bNtotalClaimable.toString(), - totalActiveStake: bNtotalActiveStake.toString(), - totalUnstakedValue: bNtotalUnstakedValue.toString() + legacyDelegation, + delegation, + ...stakingDetails }; - if (showDelegation || showDelegationLegacy) { - getProviders({ - fields, - ...(contracts ? { providers: contracts } : {}) - }).then((providersData) => { - if (providersData.success) { - const newProvidersData: ProviderType[] = providersData.data; - - const providerIdentitiesList = newProvidersData - .filter((item) => item.identity) - .map((item) => item.identity); - - if ( - showDelegationLegacy && - !providerIdentitiesList.includes(LEGACY_DELEGATION_NODES_IDENTITY) - ) { - providerIdentitiesList.push(LEGACY_DELEGATION_NODES_IDENTITY); - } - - const identities = providerIdentitiesList.join(','); - - if (identities) { - getIdentities({ identities }).then((identitiesData) => { - if (identitiesData.success) { - newProvidersData.forEach((provider) => { - if (provider.identity) { - const identityDetails = identitiesData.data.find( - (identity: IdentityType) => - identity.identity === provider.identity - ); - - const multiversXNodes = identitiesData.data.filter( - (identity: IdentityType) => { - return ( - identity.identity === LEGACY_DELEGATION_NODES_IDENTITY - ); - } - ); - if (multiversXNodes.length > 0) { - delegationLegacyIdentity = multiversXNodes[0]; - } - - if (identityDetails) { - provider.identityDetails = identityDetails; - } - } - }); + if (showDelegation || showLegacyDelegation) { + const fields = [ + 'identity', + 'provider', + 'stake', + 'numNodes', + 'apr', + 'serviceFee', + 'delegationCap' + ].join(','); + const providers = showDelegation + ? delegation.map((delegation) => delegation?.contract).join(',') + : ''; + + const [providersData, legacyIdentityData] = await Promise.all([ + providers + ? getProviders({ + fields, + providers, + withIdentityInfo: true + }) + : Promise.resolve({ success: true } as ApiAdapterResponseType), - dispatch( - setAccountStaking({ - ...stakingData, - accountStakingFetched: stakingDataReady, - providerDataReady: true, - delegationProviders: newProvidersData, - delegationLegacyIdentity - }) - ); - } + showLegacyDelegation + ? getIdentity(LEGACY_DELEGATION_NODES_IDENTITY) + : Promise.resolve({ success: true } as ApiAdapterResponseType) + ]); - dispatch( - setAccountStaking({ - ...stakingData, - accountStakingFetched: stakingDataReady, - providerDataReady: true, - delegationProviders: newProvidersData, - delegationLegacyIdentity - }) - ); - }); - } else { - dispatch( - setAccountStaking({ - ...stakingData, - accountStakingFetched: stakingDataReady, - providerDataReady: true, - delegationProviders: providersData.data, - delegationLegacyIdentity - }) - ); - } - } else { - dispatch( - setAccountStaking({ - ...stakingData, - accountStakingFetched: stakingDataReady, - providerDataReady: providersData.success, - delegationProviders, - delegationLegacyIdentity - }) - ); - } - }); + dispatch( + setAccountStaking({ + ...stakingData, + accountStakingFetched, + providerDataReady: + providersData.success && legacyIdentityData.success, + delegationProviders: providersData?.data, + delegationLegacyIdentity: legacyIdentityData?.data + }) + ); } else { + const delegationLegacyIdentity: IdentityType | undefined = undefined; + const delegationProviders: ProviderType[] = []; + dispatch( setAccountStaking({ ...stakingData, - accountStakingFetched: stakingDataReady, + accountStakingFetched, providerDataReady: true, delegationProviders, delegationLegacyIdentity diff --git a/src/hooks/useGetShardText.ts b/src/hooks/useGetShardText.ts index 63cd35db1..ec49337fc 100644 --- a/src/hooks/useGetShardText.ts +++ b/src/hooks/useGetShardText.ts @@ -1,10 +1,7 @@ import { useSelector } from 'react-redux'; -import { - METACHAIN_SHARD_ID, - MAIN_SHARD_ID, - ALL_SHARDS_SHARD_ID -} from 'appConstants'; +import { MAIN_SHARD_ID, ALL_SHARDS_SHARD_ID } from 'appConstants'; +import { isMetachain } from 'helpers'; import { useIsSovereign } from 'hooks'; import { activeNetworkSelector } from 'redux/selectors'; @@ -21,10 +18,7 @@ export const useGetShardText = () => { shard = shard.replace('Shard', ''); } - const isMetachain = - METACHAIN_SHARD_ID.toString() === String(shard).toString() || - String(shard) === 'metachain'; - + const isShardMetachain = isMetachain(shard); const isAllShards = ALL_SHARDS_SHARD_ID.toString() === String(shard).toString(); @@ -33,7 +27,7 @@ export const useGetShardText = () => { if (isMainShard) { return 'MultiversX'; } - if (isMetachain) { + if (isShardMetachain) { if (isSovereign) { return ''; } diff --git a/src/layouts/AccountLayout/AccountDetailsCard/components/AccountUsdValueCardItem.tsx b/src/layouts/AccountLayout/AccountDetailsCard/components/AccountUsdValueCardItem.tsx index 3bdf52e40..6994d3f4d 100644 --- a/src/layouts/AccountLayout/AccountDetailsCard/components/AccountUsdValueCardItem.tsx +++ b/src/layouts/AccountLayout/AccountDetailsCard/components/AccountUsdValueCardItem.tsx @@ -25,7 +25,7 @@ export const AccountUsdValueCardItem = ({ const { accountExtra, isFetched: isAccountExtraFetched } = useSelector(accountExtraSelector); const { - stakingDataReady, + accountStakingFetched, totalLocked, address: lockedAddress } = useSelector(accountStakingSelector); @@ -34,8 +34,8 @@ export const AccountUsdValueCardItem = ({ const { tokenBalance, address: extraAddress } = accountExtra; let totalWorth = balance ? new BigNumber(balance) : new BigNumber(0); - if (stakingDataReady) { - totalWorth = totalWorth.plus(new BigNumber(totalLocked)); + if (accountStakingFetched) { + totalWorth = totalWorth.plus(totalLocked); } const formattedTotalWorth = formatAmount({ input: totalWorth.toString(10), @@ -55,7 +55,7 @@ export const AccountUsdValueCardItem = ({ const isDataReady = isAccountExtraFetched && - stakingDataReady && + accountStakingFetched && isEconomicsFetched && isCorrectData; @@ -94,7 +94,7 @@ export const AccountUsdValueCardItem = ({ ) }, { - label: 'Stake', + label: 'Stake (Locked)', value: ( { const { address: lockedAddress, - stakingDataReady, - totalStaked, - totalLegacyDelegation, + accountStakingFetched, + + activeValidatorStake, + activeDelegation, + activeLegacyDelegation, + totalLocked, totalClaimable, - totalActiveStake, - totalUnstakedValue + totalUnstaked } = useSelector(accountStakingSelector); const { account } = useSelector(accountSelector); - - const bNtotalStaked = new BigNumber(totalStaked); - const bNtotalActiveStake = new BigNumber(totalActiveStake); - const bNtotalLegacyDelegation = new BigNumber(totalLegacyDelegation); - const bNtotalLocked = new BigNumber(totalLocked); - const bNtotalClaimable = new BigNumber(totalClaimable); - const bNUnstaked = new BigNumber(totalUnstakedValue); + const hasUnstaked = new BigNumber(totalUnstaked).isGreaterThan(0); const lockedDetails = [ { - label: 'Stake', - value: + label: 'Stake (Validation)', + value: }, { label: 'Delegation', - value: + value: }, { label: 'Legacy Delegation', - value: + value: }, { label: 'Claimable Rewards', - value: + value: } ]; - if (bNUnstaked.isGreaterThan(0)) { + if (hasUnstaked) { lockedDetails.push({ label: 'Unstaked', - value: + value: }); } const isDataReady = - stakingDataReady && account.address && account.address === lockedAddress; + accountStakingFetched && + account.address && + account.address === lockedAddress; return (
- {isDataReady ? ( - - ) : ( - ELLIPSIS - )} + {isDataReady ? : ELLIPSIS} {isDataReady && }
diff --git a/src/layouts/CollectionLayout/CollectionDetailsCard.tsx b/src/layouts/CollectionLayout/CollectionDetailsCard.tsx index b3ace6794..65aaed569 100644 --- a/src/layouts/CollectionLayout/CollectionDetailsCard.tsx +++ b/src/layouts/CollectionLayout/CollectionDetailsCard.tsx @@ -11,7 +11,8 @@ import { SocialWebsite, SpotlightButton, HeroDetailsCard, - Overlay + Overlay, + NftSubTypeBadge } from 'components'; import { formatDate } from 'helpers'; import { useActiveRoute } from 'hooks'; @@ -30,6 +31,7 @@ export const CollectionDetailsCard = () => { name, ticker, type, + subType, timestamp, decimals, owner, @@ -115,6 +117,9 @@ export const CollectionDetailsCard = () => { } : {}, !scamInfo ? { title: 'Type', value: } : {}, + !scamInfo && subType + ? { title: 'Subtype', value: } + : {}, !assets && ticker !== name && !scamInfo ? { title: 'Name', value: name } : {}, diff --git a/src/layouts/NftLayout/NftDetailsCard.tsx b/src/layouts/NftLayout/NftDetailsCard.tsx index bff143dc7..8fc05e26a 100644 --- a/src/layouts/NftLayout/NftDetailsCard.tsx +++ b/src/layouts/NftLayout/NftDetailsCard.tsx @@ -13,7 +13,8 @@ import { HeroDetailsCard, Overlay, SocialIcons, - SocialWebsite + SocialWebsite, + NftSubTypeBadge } from 'components'; import { formatDate, getNftText } from 'helpers'; import { faClock, faExclamationTriangle } from 'icons/regular'; @@ -29,6 +30,7 @@ export const NftDetailsCard = () => { timestamp, nonce, type, + subType, name, creator, royalties, @@ -157,6 +159,12 @@ export const NftDetailsCard = () => { } : {}, { title: 'Type', value: }, + subType + ? { + title: 'Subtype', + value: + } + : {}, !assets && ticker !== name && (!scamInfo || showData) ? { title: 'Name', value: name } : {}, diff --git a/src/pages/AccountDetails/AccountContractCode.tsx b/src/pages/AccountDetails/AccountContractCode/AccountContractCode.tsx similarity index 51% rename from src/pages/AccountDetails/AccountContractCode.tsx rename to src/pages/AccountDetails/AccountContractCode/AccountContractCode.tsx index 29b4c2d36..e5bcd4200 100644 --- a/src/pages/AccountDetails/AccountContractCode.tsx +++ b/src/pages/AccountDetails/AccountContractCode/AccountContractCode.tsx @@ -1,49 +1,51 @@ +import { useEffect, useState } from 'react'; +import { VerifiedContractType } from '@multiversx/sdk-dapp-sc-explorer/types/verifiedContract.types'; import { useSelector } from 'react-redux'; import { Navigate } from 'react-router-dom'; import { CopyButton } from 'components'; -import { downloadFile, urlBuilder } from 'helpers'; -import { useNetworkRoute } from 'hooks'; +import { urlBuilder } from 'helpers'; +import { useNetworkRoute, useAdapter } from 'hooks'; import { AccountTabs } from 'layouts/AccountLayout/AccountTabs'; import { accountSelector } from 'redux/selectors'; -import { AccountVerifiedContract } from './AccountVerifiedContract'; - -export const DownloadContractCode = ({ - code, - fileName -}: { - code: string; - fileName?: string; -}) => { - const download = (e: React.MouseEvent) => { - const name = fileName ?? 'contract'; - - e.preventDefault(); - if (code && name) { - const codeBuffer = Buffer.from(code, 'hex'); - downloadFile({ data: codeBuffer, name, fileType: 'wasm' }); - } - }; - - return ( -
- -
- ); -}; +import { DownloadABIFile } from './DownloadABIFile'; +import { DownloadContractCode } from './DownloadContractCode'; +import { AccountVerifiedContract } from '../AccountVerifiedContract'; export const AccountContractCode = () => { const networkRoute = useNetworkRoute(); + const { getAccountContractVerification } = useAdapter(); const { account } = useSelector(accountSelector); const { codeHash, code, address, isVerified } = account; + const [contract, setContract] = useState(); + const [isDataReady, setIsDataReady] = useState(); + const codeHashBase64Buffer = Buffer.from(String(codeHash ?? ''), 'base64'); const codeHashHexValue = codeHashBase64Buffer.toString('hex'); + const fetchContractVerification = () => { + getAccountContractVerification({ address }).then(({ success, data }) => { + if (success && data) { + setContract(data); + } + setIsDataReady(success); + }); + }; + + useEffect(() => { + if (address && isVerified) { + fetchContractVerification(); + } + }, [address, isVerified]); + + const contractSource = contract?.source as any; // temporary until types extendes in SC explorer + const contractName = + contractSource?.contract?.metadata?.contractName || + contractSource?.abi?.name; + return !code ? ( ) : ( @@ -67,7 +69,12 @@ export const AccountContractCode = () => { )} - {isVerified && } + {isVerified && ( + + )}
Contract Code
@@ -79,7 +86,18 @@ export const AccountContractCode = () => { />
- +
+ + {contract?.source?.abi && ( + + )} +
); diff --git a/src/pages/AccountDetails/AccountContractCode/DownloadABIFile.tsx b/src/pages/AccountDetails/AccountContractCode/DownloadABIFile.tsx new file mode 100644 index 000000000..bab71a34b --- /dev/null +++ b/src/pages/AccountDetails/AccountContractCode/DownloadABIFile.tsx @@ -0,0 +1,30 @@ +import { RawAbiType } from '@multiversx/sdk-dapp-sc-explorer/types/abi.types'; + +import { downloadFile } from 'helpers'; + +export const DownloadABIFile = ({ + abi, + fileName +}: { + abi: RawAbiType; + fileName?: string; +}) => { + const download = (e: React.MouseEvent) => { + const name = (fileName ?? 'contract').toLowerCase(); + + e.preventDefault(); + if (abi && name) { + downloadFile({ + data: JSON.stringify(abi, null, 2), + name: `${name}.abi`, + fileType: 'json' + }); + } + }; + + return ( + + ); +}; diff --git a/src/pages/AccountDetails/AccountContractCode/DownloadContractCode.tsx b/src/pages/AccountDetails/AccountContractCode/DownloadContractCode.tsx new file mode 100644 index 000000000..5ae1e113b --- /dev/null +++ b/src/pages/AccountDetails/AccountContractCode/DownloadContractCode.tsx @@ -0,0 +1,25 @@ +import { downloadFile } from 'helpers'; + +export const DownloadContractCode = ({ + code, + fileName +}: { + code: string; + fileName?: string; +}) => { + const download = (e: React.MouseEvent) => { + const name = (fileName ?? 'contract').toLowerCase(); + + e.preventDefault(); + if (code && name) { + const codeBuffer = Buffer.from(code, 'hex'); + downloadFile({ data: codeBuffer, name, fileType: 'wasm' }); + } + }; + + return ( + + ); +}; diff --git a/src/pages/AccountDetails/AccountContractCode/index.ts b/src/pages/AccountDetails/AccountContractCode/index.ts new file mode 100644 index 000000000..820bfe063 --- /dev/null +++ b/src/pages/AccountDetails/AccountContractCode/index.ts @@ -0,0 +1 @@ +export * from './AccountContractCode'; diff --git a/src/pages/AccountDetails/AccountNfts.tsx b/src/pages/AccountDetails/AccountNfts.tsx index 6322ae984..cfc867070 100644 --- a/src/pages/AccountDetails/AccountNfts.tsx +++ b/src/pages/AccountDetails/AccountNfts.tsx @@ -130,7 +130,11 @@ export const AccountNfts = () => { - + ); diff --git a/src/pages/AccountDetails/AccountStaking/AccountStaking.tsx b/src/pages/AccountDetails/AccountStaking/AccountStaking.tsx index a7e4acdf7..d1bf916f7 100644 --- a/src/pages/AccountDetails/AccountStaking/AccountStaking.tsx +++ b/src/pages/AccountDetails/AccountStaking/AccountStaking.tsx @@ -22,33 +22,28 @@ export const AccountStaking = () => { const stakingDetails = useSelector(accountStakingSelector); const { address: stateAddress, - providerDataReady, - stakingDataReady, + + showDelegation, + showLegacyDelegation, + showValidatorStake, + showStakingDetails, + delegation, - delegationProviders, stake, - delegationLegacy, + legacyDelegation, + + providerDataReady, + delegationProviders, delegationLegacyIdentity, - showDelegation, - showDelegationLegacy, - showStake - } = stakingDetails; - const displayDelegation = delegation - ? delegation.filter( - (delegation) => - delegation.userActiveStake !== '0' || - delegation.claimableRewards !== '0' || - (delegation.userUndelegatedList && - delegation.userUndelegatedList?.length > 0) - ) - : []; + accountStakingFetched + } = stakingDetails; const needsData = address && address !== stateAddress; - const hasStaking = showDelegation || showDelegationLegacy || showStake; + const isReady = providerDataReady && - stakingDataReady && + accountStakingFetched && ((needsData && dataReady) || !needsData); useEffect(() => { @@ -75,27 +70,24 @@ export const AccountStaking = () => {
{isReady ? (
- {hasStaking ? ( + {accountStakingFetched && showStakingDetails ? ( <>
Staking Chart
- +
- {displayDelegation.length > 0 && ( + {showDelegation && delegation && delegation.length > 0 && (
Staking List
- {displayDelegation.map((delegation, i) => { + {delegation.map((delegation, i) => { const provider = delegationProviders?.find( ({ provider }) => delegation.contract === provider ); @@ -110,19 +102,19 @@ export const AccountStaking = () => {
)} - {delegationLegacy && showDelegationLegacy && ( + {showLegacyDelegation && legacyDelegation && (
Legacy Delegation
)} - {stake && showStake && ( + {showValidatorStake && stake && (
Stake{' '} diff --git a/src/pages/AccountDetails/AccountStaking/components/AccountDelegation/AccountDelegation.tsx b/src/pages/AccountDetails/AccountStaking/components/AccountDelegation/AccountDelegation.tsx index 14f1928bc..a4e1db7c6 100644 --- a/src/pages/AccountDetails/AccountStaking/components/AccountDelegation/AccountDelegation.tsx +++ b/src/pages/AccountDetails/AccountStaking/components/AccountDelegation/AccountDelegation.tsx @@ -1,6 +1,7 @@ import BigNumber from 'bignumber.js'; import { useSelector } from 'react-redux'; +import { ZERO } from 'appConstants'; import { FormatAmount } from 'components'; import { activeNetworkSelector } from 'redux/selectors'; import { AccountDelegationType, ProviderType } from 'types'; @@ -18,25 +19,22 @@ export const AccountDelegation = ({ const { egldLabel } = useSelector(activeNetworkSelector); const { userActiveStake } = delegation; - const claimableRewards = delegation.claimableRewards || '0'; + const claimableRewards = delegation.claimableRewards || ZERO; const undelegatedAmounts = delegation?.userUndelegatedList && delegation.userUndelegatedList.length > 0 ? delegation.userUndelegatedList.map(({ amount }) => amount) : []; - const bNtotalUserUnStakedValue = - undelegatedAmounts.length > 0 - ? undelegatedAmounts.reduce( - (a, b) => new BigNumber(a).plus(b), - new BigNumber('0') - ) - : null; + const bNtotalUserUnStakedValue = undelegatedAmounts.reduce( + (a, b) => new BigNumber(a).plus(b), + new BigNumber(ZERO) + ); return (
- {userActiveStake !== '0' && ( + {userActiveStake !== ZERO && ( @@ -45,7 +43,7 @@ export const AccountDelegation = ({ )} - {bNtotalUserUnStakedValue && ( + {bNtotalUserUnStakedValue.isGreaterThan(ZERO) && ( diff --git a/src/pages/AccountDetails/AccountStaking/components/AccountLegacyDelegation/AccountLegacyDelegation.tsx b/src/pages/AccountDetails/AccountStaking/components/AccountLegacyDelegation/AccountLegacyDelegation.tsx index 1745dba35..2dc14720c 100644 --- a/src/pages/AccountDetails/AccountStaking/components/AccountLegacyDelegation/AccountLegacyDelegation.tsx +++ b/src/pages/AccountDetails/AccountStaking/components/AccountLegacyDelegation/AccountLegacyDelegation.tsx @@ -13,10 +13,10 @@ import { AccountDelegationLegacyType, IdentityType } from 'types'; import { DetailsBlock } from '../DetailsBlock'; export const AccountLegacyDelegation = ({ - delegationLegacy, + legacyDelegation, identity }: { - delegationLegacy: AccountDelegationLegacyType; + legacyDelegation: AccountDelegationLegacyType; identity?: IdentityType; }) => { const { @@ -31,7 +31,7 @@ export const AccountLegacyDelegation = ({ userUnstakedStake, userWaitingStake, userDeferredPaymentStake - } = delegationLegacy; + } = legacyDelegation; const [legacyDelegationApr, setLegacyDelegationApr] = useState(ELLIPSIS); diff --git a/src/pages/AccountDetails/AccountStaking/components/DonutChart/DonutChart.tsx b/src/pages/AccountDetails/AccountStaking/components/DonutChart/DonutChart.tsx index a3a47e994..19e7e2480 100644 --- a/src/pages/AccountDetails/AccountStaking/components/DonutChart/DonutChart.tsx +++ b/src/pages/AccountDetails/AccountStaking/components/DonutChart/DonutChart.tsx @@ -1,25 +1,16 @@ -import BigNumber from 'bignumber.js'; +import { useSelector } from 'react-redux'; import { FormatAmount, FormatUSD, Chart } from 'components'; import { ChartConfigType } from 'components/Chart/helpers/types'; import { DECIMALS } from 'config'; -import { ProviderType } from 'types'; -import { AccountStakingSliceType } from 'types/account.types'; +import { accountStakingSelector } from 'redux/selectors'; import { prepareChartData } from './helpers/prepareChartData'; -export const DonutChart = ({ - stakingDetails, - providers -}: { - stakingDetails: AccountStakingSliceType; - providers?: ProviderType[]; -}) => { - const chartData = providers - ? prepareChartData({ stakingDetails, providers }) - : []; - const { totalLocked } = stakingDetails; - const bNtotalLocked = new BigNumber(totalLocked); +export const DonutChart = () => { + const stakingDetails = useSelector(accountStakingSelector); + const chartData = prepareChartData({ stakingDetails }); + const { totalLocked, showStakingDetails } = stakingDetails; const config: ChartConfigType[] = [ { @@ -29,25 +20,20 @@ export const DonutChart = ({ } ]; - const hasNoActiveStake = bNtotalLocked.isEqualTo(0); - return ( <>
- {hasNoActiveStake ? 'No staking' : 'Total Staked'} + {showStakingDetails ? 'Total Locked' : 'No staking'}
- {!hasNoActiveStake && ( + {showStakingDetails && ( <>
- +
diff --git a/src/pages/AccountDetails/AccountStaking/components/DonutChart/helpers/prepareChartData.ts b/src/pages/AccountDetails/AccountStaking/components/DonutChart/helpers/prepareChartData.ts index 39a84eaba..911b3500f 100644 --- a/src/pages/AccountDetails/AccountStaking/components/DonutChart/helpers/prepareChartData.ts +++ b/src/pages/AccountDetails/AccountStaking/components/DonutChart/helpers/prepareChartData.ts @@ -1,8 +1,8 @@ import BigNumber from 'bignumber.js'; +import { ZERO } from 'appConstants'; import { DECIMALS, DIGITS } from 'config'; import { formatAmount, truncateMiddle } from 'helpers'; -import { ProviderType } from 'types'; import { AccountStakingSliceType } from 'types/account.types'; interface DonutChartDataType { @@ -13,53 +13,58 @@ interface DonutChartDataType { } export const prepareChartData = ({ - stakingDetails, - providers + stakingDetails }: { stakingDetails: AccountStakingSliceType; - providers: ProviderType[]; }): DonutChartDataType[] => { const { - accountStakingFetched, + showDelegation, + showValidatorStake, + showLegacyDelegation, + + lockedLegacyDelegation, + lockedValidatorStake, + delegation, - stake, - delegationLegacy, - showStake, - showDelegationLegacy, - totalLegacyDelegation, - totalStaked + delegationProviders, + + accountStakingFetched } = stakingDetails; const defaultData = [{ name: 'No Staking', value: 1, displayValue: 0 }]; if (accountStakingFetched) { - const bNtotalLegacyDelegation = new BigNumber(totalLegacyDelegation); - const bNtotalStaked = new BigNumber(totalStaked); - const chartData: DonutChartDataType[] = []; - const displayDelegations = delegation - ? delegation.filter( - (delegation) => - delegation.userActiveStake !== '0' || - delegation.claimableRewards !== '0' || - (delegation.userUndelegatedList && - delegation.userUndelegatedList.length > 0) - ) - : []; - if (displayDelegations.length > 0) { - displayDelegations.forEach((delegation) => { - const provider = providers.find( + + if (showDelegation && delegation && delegation.length > 0) { + delegation.forEach((delegation) => { + const provider = delegationProviders.find( ({ provider }) => delegation.contract === provider ); if (provider) { - const { userActiveStake, claimableRewards, userUnBondable } = - delegation; - const bNtotalLocked = new BigNumber(userActiveStake) + const { + userActiveStake, + claimableRewards, + userUnBondable, + userUndelegatedList + } = delegation; + + const undelegatedAmounts = + userUndelegatedList && userUndelegatedList.length > 0 + ? userUndelegatedList.map(({ amount }) => amount) + : []; + const bNtotalUserUnStakedValue = undelegatedAmounts.reduce( + (a, b) => new BigNumber(a).plus(b), + new BigNumber(ZERO) + ); + + const bNLocked = new BigNumber(userActiveStake) .plus(claimableRewards) - .plus(userUnBondable); + .plus(userUnBondable) + .plus(bNtotalUserUnStakedValue); const amount = formatAmount({ - input: bNtotalLocked.toString(10), + input: bNLocked.toString(10), decimals: DECIMALS, digits: DIGITS, showLastNonZeroDecimal: false @@ -67,7 +72,7 @@ export const prepareChartData = ({ chartData.push({ name: - provider?.identityDetails?.name ?? + provider?.identityInfo?.name ?? truncateMiddle(provider.provider, 20), identifier: provider?.identity ?? provider.provider, value: Number(amount) @@ -75,9 +80,9 @@ export const prepareChartData = ({ } }); } - if (showDelegationLegacy && delegationLegacy) { + if (showLegacyDelegation && lockedLegacyDelegation) { const amount = formatAmount({ - input: bNtotalLegacyDelegation.toString(10), + input: lockedLegacyDelegation, decimals: DECIMALS, digits: DIGITS, showLastNonZeroDecimal: false @@ -88,9 +93,9 @@ export const prepareChartData = ({ value: Number(amount) }); } - if (showStake && stake) { + if (showValidatorStake && lockedValidatorStake) { const amount = formatAmount({ - input: bNtotalStaked.toString(10), + input: lockedValidatorStake, decimals: DECIMALS, digits: DIGITS, showLastNonZeroDecimal: false diff --git a/src/pages/AccountDetails/AccountStaking/components/ProviderDetails/ProviderDetails.tsx b/src/pages/AccountDetails/AccountStaking/components/ProviderDetails/ProviderDetails.tsx index 3bddd64c9..b22c14f5e 100644 --- a/src/pages/AccountDetails/AccountStaking/components/ProviderDetails/ProviderDetails.tsx +++ b/src/pages/AccountDetails/AccountStaking/components/ProviderDetails/ProviderDetails.tsx @@ -27,7 +27,7 @@ export const ProviderDetails = ({ provider }: { provider: ProviderType }) => { } const websiteLink = getValidLink({ - link: provider?.identityDetails?.website + link: provider?.identityInfo?.website }); return provider ? ( @@ -35,8 +35,8 @@ export const ProviderDetails = ({ provider }: { provider: ProviderType }) => {
@@ -45,9 +45,9 @@ export const ProviderDetails = ({ provider }: { provider: ProviderType }) => { to={urlBuilder.providerDetails(provider.provider)} className='provider-title' > - {provider?.identityDetails?.name ? ( + {provider?.identityInfo?.name ? (
- {provider.identityDetails.name} + {provider.identityInfo.name}
) : ( diff --git a/src/pages/AccountDetails/AccountVerifiedContract/AccountVerifiedContract.tsx b/src/pages/AccountDetails/AccountVerifiedContract/AccountVerifiedContract.tsx index 49ddf21b3..e198cf370 100644 --- a/src/pages/AccountDetails/AccountVerifiedContract/AccountVerifiedContract.tsx +++ b/src/pages/AccountDetails/AccountVerifiedContract/AccountVerifiedContract.tsx @@ -6,11 +6,12 @@ import { TransactionsToastList } from '@multiversx/sdk-dapp/UI/TransactionsToast import { DappProvider } from '@multiversx/sdk-dapp/wrappers/DappProvider/DappProvider'; import { ScExplorerContainer } from '@multiversx/sdk-dapp-sc-explorer/containers/ScExplorerContainer'; import { VerifiedContractTabsEnum } from '@multiversx/sdk-dapp-sc-explorer/types/base.types'; +import { VerifiedContractType } from '@multiversx/sdk-dapp-sc-explorer/types/verifiedContract.types'; import { useSelector } from 'react-redux'; import { useNavigate } from 'react-router-dom'; import { Loader, PageState } from 'components'; -import { useAdapter, useNetworkRoute, useIsMainnet } from 'hooks'; +import { useNetworkRoute, useIsMainnet } from 'hooks'; import { faClone } from 'icons/regular'; import { faAngleDown, @@ -33,23 +34,30 @@ import { } from 'icons/solid'; import { getHeaders } from 'interceptors'; import { accountSelector, activeNetworkSelector } from 'redux/selectors'; +import { WithClassnameType } from 'types'; + import { getVerifiedContractSectionUrl } from './helpers'; import { useGetActiveSection, useGetEnvironment } from './hooks'; -export const AccountVerifiedContract = () => { +export interface AccountVerifiedContractUIType extends WithClassnameType { + contract?: VerifiedContractType; + isDataReady?: boolean; +} + +export const AccountVerifiedContract = ({ + contract, + isDataReady +}: AccountVerifiedContractUIType) => { const networkRoute = useNetworkRoute(); const navigate = useNavigate(); const pathActiveSection = useGetActiveSection(); const isMainnet = useIsMainnet(); - const { getAccountContractVerification } = useAdapter(); const { account } = useSelector(accountSelector); const { address, isVerified } = account; const { apiAddress } = useSelector(activeNetworkSelector); const environment = useGetEnvironment(); const extraRequestHeaders = getHeaders(); - const [contract, setContract] = useState(); - const [isDataReady, setIsDataReady] = useState(); const [activeSection, setActiveSection] = useState(pathActiveSection); @@ -63,21 +71,6 @@ export const AccountVerifiedContract = () => { } }, [activeSection]); - const fetchContractVerification = () => { - getAccountContractVerification({ address }).then(({ success, data }) => { - if (success && data) { - setContract(data); - } - setIsDataReady(success); - }); - }; - - useEffect(() => { - if (address && isVerified) { - fetchContractVerification(); - } - }, [address, isVerified]); - if (!isVerified || !environment) { return null; } diff --git a/src/pages/CollectionDetails/CollectionNfts.tsx b/src/pages/CollectionDetails/CollectionNfts.tsx index 9badb1f72..2399eeb26 100644 --- a/src/pages/CollectionDetails/CollectionNfts.tsx +++ b/src/pages/CollectionDetails/CollectionNfts.tsx @@ -116,7 +116,11 @@ export const CollectionNfts = () => {
{type !== NftTypeEnum.MetaESDT && ( - + )}
diff --git a/src/pages/Nfts/Nfts.tsx b/src/pages/Nfts/Nfts.tsx index a0c6f4e45..3a5fd36d4 100644 --- a/src/pages/Nfts/Nfts.tsx +++ b/src/pages/Nfts/Nfts.tsx @@ -112,6 +112,7 @@ export const Nfts = () => {
diff --git a/src/pages/TransactionDetails/components/TransactionInfo/TransactionInfo.tsx b/src/pages/TransactionDetails/components/TransactionInfo/TransactionInfo.tsx index 96c747f1e..3e8c859e5 100644 --- a/src/pages/TransactionDetails/components/TransactionInfo/TransactionInfo.tsx +++ b/src/pages/TransactionDetails/components/TransactionInfo/TransactionInfo.tsx @@ -135,8 +135,11 @@ export const TransactionInfo = ({ }); const visibleOperations = getVisibleOperations(transaction); - const showLogs = - transaction.logs || (transaction.results && transaction.results.length > 0); + const hasTxResultsLogs = + transaction.results && + transaction.results.length > 0 && + transaction.results.some((ressult) => ressult.logs); + const showLogs = transaction.logs || hasTxResultsLogs; const totalTxTokenUsdValue = getTotalTxTokenUsdValue(transaction); const showTotalTxTokenUsdValue = diff --git a/src/redux/slices/accountStaking.ts b/src/redux/slices/accountStaking.ts index 65fede146..6def557ef 100644 --- a/src/redux/slices/accountStaking.ts +++ b/src/redux/slices/accountStaking.ts @@ -1,21 +1,30 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { ELLIPSIS } from 'appConstants'; import { AccountStakingSliceType } from 'types/account.types'; export const getInitialAccountStakingState = (): AccountStakingSliceType => { return { address: '', + showDelegation: false, - showDelegationLegacy: false, - showStake: false, - totalStaked: '0', - totalDelegation: '0', - totalLegacyDelegation: '0', - totalLocked: '0', - totalClaimable: '0', - totalActiveStake: '0', - totalUnstakedValue: '0', + showLegacyDelegation: false, + showValidatorStake: false, + showStakingDetails: false, + + activeValidatorStake: ELLIPSIS, + activeDelegation: ELLIPSIS, + activeLegacyDelegation: ELLIPSIS, + + lockedValidatorStake: ELLIPSIS, + lockedDelegation: ELLIPSIS, + lockedLegacyDelegation: ELLIPSIS, + + totalActiveStake: ELLIPSIS, + totalLocked: ELLIPSIS, + totalClaimable: ELLIPSIS, + totalUnstaked: ELLIPSIS, + providerDataReady: undefined, - stakingDataReady: undefined, delegationProviders: [], delegationLegacyIdentity: undefined, @@ -32,21 +41,30 @@ export const accountStakingSlice = createSlice({ action: PayloadAction ) => { state.address = action.payload.address; - state.totalStaked = action.payload.totalStaked; - state.totalDelegation = action.payload.totalDelegation; - state.totalLegacyDelegation = action.payload.totalLegacyDelegation; + + state.showDelegation = action.payload.showDelegation; + state.showLegacyDelegation = action.payload.showLegacyDelegation; + state.showValidatorStake = action.payload.showValidatorStake; + state.showStakingDetails = action.payload.showStakingDetails; + + state.activeValidatorStake = action.payload.activeValidatorStake; + state.activeDelegation = action.payload.activeDelegation; + state.activeLegacyDelegation = action.payload.activeLegacyDelegation; + + state.lockedValidatorStake = action.payload.lockedValidatorStake; + state.lockedDelegation = action.payload.lockedDelegation; + state.lockedLegacyDelegation = action.payload.lockedLegacyDelegation; + + state.totalActiveStake = action.payload.totalActiveStake; state.totalLocked = action.payload.totalLocked; state.totalClaimable = action.payload.totalClaimable; - state.totalActiveStake = action.payload.totalActiveStake; - state.totalUnstakedValue = action.payload.totalUnstakedValue; + state.totalUnstaked = action.payload.totalUnstaked; + state.stake = action.payload.stake; - state.showStake = action.payload.showStake; - state.delegationLegacy = action.payload.delegationLegacy; - state.showDelegationLegacy = action.payload.showDelegationLegacy; + state.legacyDelegation = action.payload.legacyDelegation; state.delegation = action.payload.delegation; - state.showDelegation = action.payload.showDelegation; + state.providerDataReady = action.payload.providerDataReady; - state.stakingDataReady = action.payload.stakingDataReady; state.delegationProviders = action.payload.delegationProviders; state.delegationLegacyIdentity = action.payload.delegationLegacyIdentity; diff --git a/src/types/account.types.ts b/src/types/account.types.ts index ea63842ba..60dfbcff6 100644 --- a/src/types/account.types.ts +++ b/src/types/account.types.ts @@ -45,26 +45,37 @@ export interface AccountSliceType extends SliceType { } export interface AccountStakingSliceType { - accountStakingFetched: boolean; - address: string; - totalStaked: string; - totalDelegation: string; - totalLegacyDelegation: string; + + showDelegation: boolean; + showLegacyDelegation: boolean; + showValidatorStake: boolean; + showStakingDetails: boolean; + + activeValidatorStake: string; + activeDelegation: string; + activeLegacyDelegation: string; + + lockedValidatorStake: string; + lockedDelegation: string; + lockedLegacyDelegation: string; + + totalActiveStake: string; totalLocked: string; totalClaimable: string; - totalActiveStake: string; - totalUnstakedValue: string; + totalUnstaked: string; + + // details stake?: AccountStakeType; - showStake: boolean; - delegationLegacy?: AccountDelegationLegacyType; - showDelegationLegacy: boolean; + legacyDelegation?: AccountDelegationLegacyType; delegation?: AccountDelegationType[]; - showDelegation: boolean; + + // provider details providerDataReady: undefined | boolean; - stakingDataReady: undefined | boolean; delegationProviders: ProviderType[]; delegationLegacyIdentity: IdentityType | undefined; + + accountStakingFetched: boolean; } export interface AccountExtraSliceType extends SliceType { diff --git a/src/types/collection.types.ts b/src/types/collection.types.ts index 091926f84..1cc56b82f 100644 --- a/src/types/collection.types.ts +++ b/src/types/collection.types.ts @@ -1,5 +1,5 @@ import { RolesType, ScamInfoType, SliceType } from './general.types'; -import { NftTypeEnum } from './nft.types'; +import { NftTypeEnum, NftSubtypeEnum } from './nft.types'; import { TokenAssetType } from './token.types'; export interface CollectionType { @@ -9,6 +9,7 @@ export interface CollectionType { ticker: string; timestamp: number; owner: string; + subType?: NftSubtypeEnum; decimals?: number; assets?: TokenAssetType; scamInfo?: ScamInfoType; diff --git a/src/types/nft.types.ts b/src/types/nft.types.ts index bab19f000..992a211a6 100644 --- a/src/types/nft.types.ts +++ b/src/types/nft.types.ts @@ -6,6 +6,14 @@ export enum NftTypeEnum { SemiFungibleESDT = 'SemiFungibleESDT', MetaESDT = 'MetaESDT' } + +export enum NftSubtypeEnum { + NonFungibleESDTv2 = 'NonFungibleESDTv2', + DynamicNonFungibleESDT = 'DynamicNonFungibleESDT', + DynamicSemiFungibleESDT = 'DynamicSemiFungibleESDT', + DynamicMetaESDT = 'DynamicMetaESDT' +} + export interface NftType { identifier: string; collection: string; @@ -17,6 +25,7 @@ export interface NftType { creator: string; royalties: number; balance: string; + subType?: NftSubtypeEnum; ticker?: string; uris?: string[]; url?: string;