diff --git a/package.json b/package.json index 6a12b696a..0741ca97a 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "@polkadot/types-support": "^11.2.1", "@polkadot/util": "^12.6.2", "@polkadot/util-crypto": "^12.6.2", - "@polkagate/apps-config": "^0.140.3", + "@polkagate/apps-config": "^0.140.5", "@substrate/connect": "^0.7.32", "@vaadin/icons": "^23.2.3", "babel-plugin-transform-import-meta": "^2.1.1", diff --git a/packages/extension-polkagate/src/components/Assets.tsx b/packages/extension-polkagate/src/components/Assets.tsx index cea0f6237..f9b432d66 100644 --- a/packages/extension-polkagate/src/components/Assets.tsx +++ b/packages/extension-polkagate/src/components/Assets.tsx @@ -51,7 +51,7 @@ function Assets ({ address, assetId, label, onChange, setAssetId, style }: Props return ( { const theme = useTheme(); - const mayBeUserAddedChainColor = useUserAddedChainColor(genesisHash); + const maybeUserAddedChainColor = useUserAddedChainColor(genesisHash); const options = useContext(GenesisHashOptionsContext); const foundChainName = options.find(({ text, value }) => value === genesisHash || text === chainName)?.text; @@ -60,7 +60,7 @@ function ChainLogo ({ chainName, genesisHash, logo, size = 25 }: Props): React.R : | undefined | SubmittableExtrinsic<'promise', ISubmittableResult>; disabled?: boolean; isPasswordError?: boolean; @@ -65,13 +65,13 @@ interface Props { * choose proxy or use other alternatives like signing using ledger * */ -export default function SignArea ({ address, call, disabled, extraInfo, isPasswordError, mayBeApi, onSecondaryClick, params, prevState, previousStep, primaryBtn, primaryBtnText, proxyModalHeight, proxyTypeFilter, secondaryBtnText, selectedProxy, setIsPasswordError, setRefresh, setSelectedProxy, setStep, setTxInfo, showBackButtonWithUseProxy = true, steps, token }: Props): React.ReactElement { +export default function SignArea ({ address, call, disabled, extraInfo, isPasswordError, maybeApi, onSecondaryClick, params, prevState, previousStep, primaryBtn, primaryBtnText, proxyModalHeight, proxyTypeFilter, secondaryBtnText, selectedProxy, setIsPasswordError, setRefresh, setSelectedProxy, setStep, setTxInfo, showBackButtonWithUseProxy = true, steps, token }: Props): React.ReactElement { const { t } = useTranslation(); const theme = useTheme(); const { account, api: apiFromAddress, chain, formatted } = useInfo(address); // To handle system chain apis like people chain - const api = mayBeApi || apiFromAddress; + const api = maybeApi || apiFromAddress; const senderName = useAccountDisplay(address); const selectedProxyName = useAccountDisplay(getSubstrateAddress(selectedProxy?.delegate)); diff --git a/packages/extension-polkagate/src/fullscreen/accountDetails/components/AccountInformationForDetails.tsx b/packages/extension-polkagate/src/fullscreen/accountDetails/components/AccountInformationForDetails.tsx index 88a502c54..48557bd2f 100644 --- a/packages/extension-polkagate/src/fullscreen/accountDetails/components/AccountInformationForDetails.tsx +++ b/packages/extension-polkagate/src/fullscreen/accountDetails/components/AccountInformationForDetails.tsx @@ -144,7 +144,7 @@ interface AddressDetailsProps { pricesInCurrency: Prices | null | undefined; selectedAsset: FetchedBalance | undefined; setSelectedAsset: React.Dispatch>; - setAssetIdOnAssetHub: React.Dispatch>; + setAssetIdOnAssetHub: React.Dispatch>; } export const EyeIconFullScreen = ({ isHidden, onClick }: { isHidden: boolean | undefined, onClick?: React.MouseEventHandler | undefined }) => { @@ -197,7 +197,7 @@ function AccountInformationForDetails ({ accountAssets, address, label, price, p const showAOC = useMemo(() => !!(nonZeroSortedAssets === undefined || (nonZeroSortedAssets && nonZeroSortedAssets.length > 0)), [nonZeroSortedAssets]); useEffect(() => { - /** if chain has been switched and its not among the selected chains */ + /** if chain has been switched and its not among the accounts assets */ if (account?.genesisHash && !accountAssets?.find(({ genesisHash }) => genesisHash === account.genesisHash)) { return setSelectedAsset(undefined); } diff --git a/packages/extension-polkagate/src/fullscreen/accountDetails/components/AssetSelect.tsx b/packages/extension-polkagate/src/fullscreen/accountDetails/components/AssetSelect.tsx index 407cbe0cd..5378de3d9 100644 --- a/packages/extension-polkagate/src/fullscreen/accountDetails/components/AssetSelect.tsx +++ b/packages/extension-polkagate/src/fullscreen/accountDetails/components/AssetSelect.tsx @@ -14,8 +14,8 @@ interface Props { onChange: (value: number | string) => void; label: string; style: SxProps | undefined; - assetId: number | undefined; - setAssetId: React.Dispatch> + assetId: number | string | undefined; + setAssetId: React.Dispatch> } function AssetSelect ({ address, assetId, label, onChange, setAssetId, style }: Props) { diff --git a/packages/extension-polkagate/src/fullscreen/accountDetails/components/CommonTasks.tsx b/packages/extension-polkagate/src/fullscreen/accountDetails/components/CommonTasks.tsx index 076ac6793..9789fff4b 100644 --- a/packages/extension-polkagate/src/fullscreen/accountDetails/components/CommonTasks.tsx +++ b/packages/extension-polkagate/src/fullscreen/accountDetails/components/CommonTasks.tsx @@ -23,7 +23,7 @@ import { popupNumbers } from '..'; interface Props { address: string | undefined; - assetId: number | undefined; + assetId: number | string | undefined; balance: BalancesInfo | FetchedBalance | undefined; genesisHash: string | null | undefined; setDisplayPopup: React.Dispatch>; @@ -134,7 +134,7 @@ export default function CommonTasks ({ address, assetId, balance, genesisHash, s const goToSend = useCallback(() => { address && genesisHash && - openOrFocusTab(`/send/${address}/${assetId || ''}`, true); + openOrFocusTab(`/send/${address}/${assetId}`, true); }, [address, assetId, genesisHash]); const goToReceive = useCallback(() => { diff --git a/packages/extension-polkagate/src/fullscreen/accountDetails/index.tsx b/packages/extension-polkagate/src/fullscreen/accountDetails/index.tsx index f7b6f1d63..529449f5a 100644 --- a/packages/extension-polkagate/src/fullscreen/accountDetails/index.tsx +++ b/packages/extension-polkagate/src/fullscreen/accountDetails/index.tsx @@ -63,12 +63,17 @@ export default function AccountDetails (): React.ReactElement { const pricesInCurrency = usePrices(); const [refreshNeeded, setRefreshNeeded] = useState(false); - const [assetIdOnAssetHub, setAssetIdOnAssetHub] = useState(); + const [assetIdOnAssetHub, setAssetIdOnAssetHub] = useState(); const [selectedAsset, setSelectedAsset] = useState(); const [displayPopup, setDisplayPopup] = useState(); const [unlockInformation, setUnlockInformation] = useState(); - const assetId = useMemo(() => assetIdOnAssetHub !== undefined ? assetIdOnAssetHub : selectedAsset?.assetId, [assetIdOnAssetHub, selectedAsset?.assetId]); + const assetId = useMemo(() => + assetIdOnAssetHub !== undefined + ? assetIdOnAssetHub + : selectedAsset?.assetId + , [assetIdOnAssetHub, selectedAsset?.assetId]); + const { price: currentPrice } = useTokenPrice(address, assetId); const balances = useBalances(address, refreshNeeded, setRefreshNeeded, undefined, assetId || undefined); @@ -116,7 +121,7 @@ export default function AccountDetails (): React.ReactElement { }, [genesisHash]); useEffect(() => { - if (selectedAsset !== undefined && paramAssetId && assetId !== undefined && assetId !== parseInt(paramAssetId)) { + if (selectedAsset !== undefined && paramAssetId && assetId !== undefined && String(assetId) !== paramAssetId) { onAction(`/accountfs/${address}/${assetId}`); } }, [accountAssets, address, assetId, onAction, paramAssetId, selectedAsset]); @@ -126,27 +131,21 @@ export default function AccountDetails (): React.ReactElement { return; } - const mayBeAssetIdSelectedInHomePage = assetId !== undefined ? assetId : parseInt(paramAssetId); + const maybeAssetIdSelectedInHomePage = assetId !== undefined ? assetId : paramAssetId; - if (mayBeAssetIdSelectedInHomePage >= 0 && accountAssets) { - const found = accountAssets.find(({ assetId, genesisHash: _genesisHash }) => assetId === mayBeAssetIdSelectedInHomePage && genesisHash === _genesisHash); + if (maybeAssetIdSelectedInHomePage as number >= 0 && accountAssets) { + const found = accountAssets.find(({ assetId, genesisHash: _genesisHash }) => String(assetId) === String(maybeAssetIdSelectedInHomePage) && genesisHash === _genesisHash); found && setSelectedAsset(found); } }, [genesisHash, accountAssets, assetId, paramAssetId, selectedAsset]); const onChangeAsset = useCallback((id: number | string) => { - if (id === -1) { // this is the id of native token - setAssetIdOnAssetHub(0); - - return; - } - setAssetIdOnAssetHub(id as number); // this works for asset hubs atm }, []); const goToSend = useCallback(() => { - address && onAction(`/send/${address}/${assetId || ''}`); + address && onAction(`/send/${address}/${assetId}`); }, [address, assetId, onAction]); const goToSoloStaking = useCallback(() => { diff --git a/packages/extension-polkagate/src/fullscreen/governance/FullScreenHeader.tsx b/packages/extension-polkagate/src/fullscreen/governance/FullScreenHeader.tsx index e704b7af6..7716b4cc0 100644 --- a/packages/extension-polkagate/src/fullscreen/governance/FullScreenHeader.tsx +++ b/packages/extension-polkagate/src/fullscreen/governance/FullScreenHeader.tsx @@ -11,7 +11,7 @@ import { logoBlack } from '../../assets/logos'; import { ActionContext, GenesisHashOptionsContext } from '../../components'; import { useInfo } from '../../hooks'; import { FullScreenChainSwitch, RemoteNodeSelectorWithSignals } from '../../partials'; -import { EXTENSION_NAME, GOVERNANCE_CHAINS, IDENTITY_CHAINS, SOCIAL_RECOVERY_CHAINS, STAKING_CHAINS } from '../../util/constants'; +import { EXTENSION_NAME, GOVERNANCE_CHAINS, IDENTITY_CHAINS, NATIVE_TOKEN_ASSET_ID, SOCIAL_RECOVERY_CHAINS, STAKING_CHAINS } from '../../util/constants'; import { openOrFocusTab } from '../accountDetails/components/CommonTasks'; import AddressDropdown from './components/AddressDropdown'; import ThemeChanger from './partials/ThemeChanger'; @@ -63,7 +63,7 @@ function FullScreenHeader ({ _otherComponents, noAccountDropDown = false, noChai case 'accountDetails': return onAction(`/accountfs/${selectedAddress}/0`); case 'send': - return onAction(`/send/${selectedAddress}/`); + return onAction(`/send/${selectedAddress}/${NATIVE_TOKEN_ASSET_ID}`); default: return null; } diff --git a/packages/extension-polkagate/src/fullscreen/governance/post/Chronology.tsx b/packages/extension-polkagate/src/fullscreen/governance/post/Chronology.tsx index 398df6d3f..3ae1a61a8 100644 --- a/packages/extension-polkagate/src/fullscreen/governance/post/Chronology.tsx +++ b/packages/extension-polkagate/src/fullscreen/governance/post/Chronology.tsx @@ -79,8 +79,8 @@ export default function Chronology ({ address, currentTreasuryApprovalList, refe const [treasuryAwardedBlock, setTreasuryAwardedBlock] = React.useState(); const isTreasury = TREASURY_TRACKS.includes(toSnakeCase(referendum?.trackName) || ''); const isExecuted = referendum?.status === 'Executed'; - const mayBeExecutionBlock = sortedHistory?.find((h) => h.status === 'Executed')?.block; - const mayBeBeneficiary = useMemo(() => { + const maybeExecutionBlock = sortedHistory?.find((h) => h.status === 'Executed')?.block; + const maybeBeneficiary = useMemo(() => { if (referendum?.call && chain) { return getBeneficiary(referendum, chain); } @@ -89,13 +89,13 @@ export default function Chronology ({ address, currentTreasuryApprovalList, refe // eslint-disable-next-line react-hooks/exhaustive-deps }, [chain, referendum?.call]); - const mayBeAwardedDate = useMemo(() => - (currentBlockNumber && spendPeriod && mayBeExecutionBlock && getAwardedDate(currentBlockNumber, mayBeExecutionBlock, spendPeriod)) || + const maybeAwardedDate = useMemo(() => + (currentBlockNumber && spendPeriod && maybeExecutionBlock && getAwardedDate(currentBlockNumber, maybeExecutionBlock, spendPeriod)) || referendum?.timelinePA?.[1]?.statuses?.[1]?.timestamp - , [currentBlockNumber, mayBeExecutionBlock, spendPeriod, referendum]); + , [currentBlockNumber, maybeExecutionBlock, spendPeriod, referendum]); /** in rare case as ref 160 the proposers are not the same! needs more research */ - const isInTreasuryQueue = useMemo(() => isExecuted && currentTreasuryApprovalList && !!currentTreasuryApprovalList?.find((item) => String(item.value) === referendum.requested && item.beneficiary === mayBeBeneficiary), [currentTreasuryApprovalList, isExecuted, mayBeBeneficiary, referendum]); + const isInTreasuryQueue = useMemo(() => isExecuted && currentTreasuryApprovalList && !!currentTreasuryApprovalList?.find((item) => String(item.value) === referendum.requested && item.beneficiary === maybeBeneficiary), [currentTreasuryApprovalList, isExecuted, maybeBeneficiary, referendum]); const isAwardedBasedOnPA = useMemo(() => referendum?.timelinePA?.[1]?.type === 'TreasuryProposal' && referendum?.timelinePA?.[1]?.statuses?.[1]?.status === 'Awarded', [referendum]); const isTreasuryProposalBasedOnPA = useMemo(() => referendum?.timelinePA?.[1]?.type === 'TreasuryProposal', [referendum]); @@ -146,7 +146,7 @@ export default function Chronology ({ address, currentTreasuryApprovalList, refe - {toFormattedDate(mayBeAwardedDate)} + {toFormattedDate(maybeAwardedDate)} diff --git a/packages/extension-polkagate/src/fullscreen/governance/post/Description.tsx b/packages/extension-polkagate/src/fullscreen/governance/post/Description.tsx index 343c763d2..36ea11249 100644 --- a/packages/extension-polkagate/src/fullscreen/governance/post/Description.tsx +++ b/packages/extension-polkagate/src/fullscreen/governance/post/Description.tsx @@ -51,7 +51,7 @@ export default function ReferendumDescription ({ address, currentTreasuryApprova const [expanded, setExpanded] = useState(false); - const mayBeBeneficiary = useMemo(() => { + const maybeBeneficiary = useMemo(() => { if (referendum?.call && chain) { return getBeneficiary(referendum, chain); } @@ -60,7 +60,7 @@ export default function ReferendumDescription ({ address, currentTreasuryApprova // eslint-disable-next-line react-hooks/exhaustive-deps }, [chain, referendum?.call]); - const mayBeTreasuryProposalId = useMemo(() => currentTreasuryApprovalList?.find((p) => p.beneficiary === mayBeBeneficiary)?.id, [currentTreasuryApprovalList, mayBeBeneficiary]); + const maybeTreasuryProposalId = useMemo(() => currentTreasuryApprovalList?.find((p) => p.beneficiary === maybeBeneficiary)?.id, [currentTreasuryApprovalList, maybeBeneficiary]); const content = useMemo(() => { const res = referendum?.content?.includes('login and tell us more about your proposal') ? t(DEFAULT_CONTENT) : referendum?.content; @@ -82,10 +82,10 @@ export default function ReferendumDescription ({ address, currentTreasuryApprova return ( <> - {mayBeTreasuryProposalId && + {maybeTreasuryProposalId && - {t('This Referendum is now Treasury Proposal #{{proposalId}}', { replace: { proposalId: mayBeTreasuryProposalId } })} + {t('This Referendum is now Treasury Proposal #{{proposalId}}', { replace: { proposalId: maybeTreasuryProposalId } })} } diff --git a/packages/extension-polkagate/src/fullscreen/governance/post/Metadata.tsx b/packages/extension-polkagate/src/fullscreen/governance/post/Metadata.tsx index 2c995d9c6..d52e23195 100644 --- a/packages/extension-polkagate/src/fullscreen/governance/post/Metadata.tsx +++ b/packages/extension-polkagate/src/fullscreen/governance/post/Metadata.tsx @@ -80,7 +80,7 @@ export default function Metadata ({ address, decisionDepositPayer, referendum }: const referendumLinkOnsSubscan = () => `https://${chainName}.subscan.io/referenda_v2/${String(referendum?.index)}`; - const mayBeBeneficiary = useMemo(() => { + const maybeBeneficiary = useMemo(() => { if (referendum?.call && chain) { return getBeneficiary(referendum, chain); } @@ -173,7 +173,7 @@ export default function Metadata ({ address, decisionDepositPayer, referendum }: />} valueStyle={{ fontSize: 16, fontWeight: 500 }} /> - {mayBeBeneficiary && + {maybeBeneficiary && <> } valueStyle={{ fontSize: 16, fontWeight: 500 }} /> - {mayBeBeneficiary && + {maybeBeneficiary && - : + : } valueStyle={{ maxWidth: '75%', width: 'fit-content' }} /> diff --git a/packages/extension-polkagate/src/fullscreen/governance/post/castVote/Cast.tsx b/packages/extension-polkagate/src/fullscreen/governance/post/castVote/Cast.tsx index a019e238b..e1424a22f 100644 --- a/packages/extension-polkagate/src/fullscreen/governance/post/castVote/Cast.tsx +++ b/packages/extension-polkagate/src/fullscreen/governance/post/castVote/Cast.tsx @@ -101,7 +101,7 @@ export default function Cast({ address, notVoted, previousVote, refIndex, setSte const [estimatedFee, setEstimatedFee] = useState(); const [voteType, setVoteType] = useState<'Aye' | 'Nay' | 'Abstain' | undefined>(getVoteType(previousVote)); - const mayBePreviousVote = amountToHuman(previousVote?.standard?.balance || previousVote?.splitAbstain?.abstain || (previousVote?.delegating?.voted ? previousVote?.delegating?.balance : undefined), decimal); + const maybePreviousVote = amountToHuman(previousVote?.standard?.balance || previousVote?.splitAbstain?.abstain || (previousVote?.delegating?.voted ? previousVote?.delegating?.balance : undefined), decimal); const [voteAmount, setVoteAmount] = React.useState('0'); const [conviction, setConviction] = useState(); @@ -140,12 +140,12 @@ export default function Cast({ address, notVoted, previousVote, refIndex, setSte }, [conviction, myDelegations, voteAmountAsBN]); useEffect(() => { - if (mayBePreviousVote) { - setVoteAmount(mayBePreviousVote); + if (maybePreviousVote) { + setVoteAmount(maybePreviousVote); previousVote?.standard && setConviction(getConviction(previousVote.standard.vote)); previousVote?.delegating && previousVote?.delegating?.voted && setConviction(getConviction(String(previousVote.delegating.conviction))); } - }, [mayBePreviousVote, previousVote]); + }, [maybePreviousVote, previousVote]); useEffect(() => { convictionOptions === undefined && setConviction(1); @@ -328,7 +328,7 @@ export default function Cast({ address, notVoted, previousVote, refIndex, setSte return ( - {mayBePreviousVote && + {maybePreviousVote && - + {voteType !== 'Abstain' && { } const b = Number(block); - const mayBeDays = b / DAY_BLOCK_COUNT; + const maybeDays = b / DAY_BLOCK_COUNT; - if (mayBeDays >= 1) { - return `${mayBeDays}` + (!noUnit ? ` ${mayBeDays > 1 ? 'days' : 'day'}` : ''); + if (maybeDays >= 1) { + return `${maybeDays}` + (!noUnit ? ` ${maybeDays > 1 ? 'days' : 'day'}` : ''); } - const mayBeHours = b / HOUR_BLOCK_COUNT; + const maybeHours = b / HOUR_BLOCK_COUNT; - if (mayBeHours >= 1) { - return `${mayBeHours}` + (!noUnit ? ` ${mayBeHours > 1 ? 'hours' : 'hour'}` : ''); + if (maybeHours >= 1) { + return `${maybeHours}` + (!noUnit ? ` ${maybeHours > 1 ? 'hours' : 'hour'}` : ''); } - const mayBeMins = b / MINUTE_BLOCK_COUNT; + const maybeMins = b / MINUTE_BLOCK_COUNT; - if (mayBeMins >= 1) { - return `${mayBeMins}` + (!noUnit ? ` ${mayBeMins > 1 ? 'mins' : 'min'}` : ''); + if (maybeMins >= 1) { + return `${maybeMins}` + (!noUnit ? ` ${maybeMins > 1 ? 'mins' : 'min'}` : ''); } return undefined; diff --git a/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/Chart.tsx b/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/Chart.tsx index d66eeed77..1fc547025 100644 --- a/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/Chart.tsx +++ b/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/Chart.tsx @@ -1,6 +1,5 @@ // Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors // SPDX-License-Identifier: Apache-2.0 -// @ts-nocheck /* eslint-disable react/jsx-max-props-per-line */ @@ -17,7 +16,7 @@ interface Props { color: string | undefined; logo: string | undefined; }; - assetId?: number | undefined; + assetId?: number | string | undefined; chainName: string; decimal: number; genesisHash: string; @@ -26,14 +25,14 @@ interface Props { }[] | undefined; } -function ChartTotal({ assets }: Props): React.ReactElement { +function ChartTotal ({ assets }: Props): React.ReactElement { const chartRef = useRef(null); const theme = useTheme(); Chart.register(...registerables); useEffect(() => { - if (!assets) { + if (!assets || !chartRef.current) { return; } diff --git a/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/TotalBalancePieChart.tsx b/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/TotalBalancePieChart.tsx index 40fa642ac..a7504536e 100644 --- a/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/TotalBalancePieChart.tsx +++ b/packages/extension-polkagate/src/fullscreen/homeFullScreen/partials/TotalBalancePieChart.tsx @@ -34,7 +34,7 @@ export interface AssetsWithUiAndPrice { color: string | undefined; logo: string | undefined; }; - assetId?: number, + assetId?: number | string, chainName: string, date?: number, decimal: number, @@ -192,7 +192,7 @@ function TotalBalancePieChart ({ hideNumbers, setGroupedAssets }: Props): React. const toggleAssets = useCallback(() => setShowMore(!showMore), [showMore]); return ( - + {t('My Portfolio')} diff --git a/packages/extension-polkagate/src/fullscreen/manageIdentity/Review.tsx b/packages/extension-polkagate/src/fullscreen/manageIdentity/Review.tsx index 2407dddfe..d067828e5 100644 --- a/packages/extension-polkagate/src/fullscreen/manageIdentity/Review.tsx +++ b/packages/extension-polkagate/src/fullscreen/manageIdentity/Review.tsx @@ -356,7 +356,7 @@ export default function Review({ address, api, chain, depositToPay, depositValue disabled={feeAndDeposit.isAbleToPay !== true} extraInfo={extraInfo} isPasswordError={isPasswordError} - mayBeApi={api} + maybeApi={api} onSecondaryClick={handleClose} primaryBtnText={t('Confirm')} proxyTypeFilter={PROXY_TYPE.GENERAL} diff --git a/packages/extension-polkagate/src/fullscreen/sendFund/Confirmation.tsx b/packages/extension-polkagate/src/fullscreen/sendFund/Confirmation.tsx index a43fdd76b..c1d60b410 100644 --- a/packages/extension-polkagate/src/fullscreen/sendFund/Confirmation.tsx +++ b/packages/extension-polkagate/src/fullscreen/sendFund/Confirmation.tsx @@ -1,6 +1,5 @@ // Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors // SPDX-License-Identifier: Apache-2.0 -// @ts-nocheck /* eslint-disable react/jsx-max-props-per-line */ @@ -27,8 +26,8 @@ interface DisplayInfoProps { showDivider?: boolean; } -const Account = ({ info, label }: { label: string, info: NameAddress }) => { - const accountName = useAccountDisplay(info.address); +const Account = ({ info, label }: { label: string, info?: NameAddress }) => { + const accountName = useAccountDisplay(info?.address); return ( @@ -39,13 +38,13 @@ const Account = ({ info, label }: { label: string, info: NameAddress }) => { {info?.name || accountName} - + ); }; -export default function Confirmation({ handleDone, txInfo }: Props): React.ReactElement { +export default function Confirmation ({ handleDone, txInfo }: Props): React.ReactElement { const { t } = useTranslation(); const theme = useTheme(); @@ -89,7 +88,7 @@ export default function Confirmation({ handleDone, txInfo }: Props): React.React {txInfo.failureText} } - ('From')} /> + {txInfo.throughProxy && @@ -97,33 +96,33 @@ export default function Confirmation({ handleDone, txInfo }: Props): React.React } ('Amount:')} + caption={t('Amount:')} value={txInfo.amount && txInfo.token ? `${parseFloat(txInfo.amount)} ${txInfo.token}` : '00.00'} /> ('Chain:')} + caption={t('Chain:')} showDivider={false} value={chainName} /> - ('To')} /> + ('Chain:')} + caption={t('Chain:')} showDivider={false} value={txInfo.recipientChainName} /> ('Fee:') : t('Total transaction fee:')} + caption={chainName === txInfo.recipientChainName ? t('Fee:') : t('Total transaction fee:')} showDivider={false} - value={fee?.toHuman() ?? '00.00'} + value={fee?.toHuman() as string ?? '00.00'} /> {txInfo?.txHash && - {t('Hash')}: + {t('Hash')}: ('Done')} + text={t('Done')} /> ); diff --git a/packages/extension-polkagate/src/fullscreen/sendFund/InputPage.tsx b/packages/extension-polkagate/src/fullscreen/sendFund/InputPage.tsx index 3177777d1..d048aafcf 100644 --- a/packages/extension-polkagate/src/fullscreen/sendFund/InputPage.tsx +++ b/packages/extension-polkagate/src/fullscreen/sendFund/InputPage.tsx @@ -6,6 +6,7 @@ import type { IconProp } from '@fortawesome/fontawesome-svg-core'; import type { SubmittableExtrinsicFunction } from '@polkadot/api/types'; import type { Balance } from '@polkadot/types/interfaces'; +import type { HexString } from '@polkadot/util/types'; import type { BalancesInfo, DropdownOption, TransferType } from '../../util/types'; import type { Inputs } from '.'; @@ -21,8 +22,8 @@ import { AmountWithOptions, FullscreenChainNames, Infotip2, InputAccount, ShowBa import { useTranslation } from '../../components/translate'; import { useInfo, useTeleport } from '../../hooks'; import { getValue } from '../../popup/account/util'; -import { ASSET_HUBS } from '../../util/constants'; -import { amountToHuman, amountToMachine } from '../../util/utils'; +import { ASSET_HUBS, NATIVE_TOKEN_ASSET_ID, NATIVE_TOKEN_ASSET_ID_ON_ASSETHUB } from '../../util/constants'; +import { amountToHuman, amountToMachine, decodeMultiLocation } from '../../util/utils'; import { openOrFocusTab } from '../accountDetails/components/CommonTasks'; import { toTitleCase } from '../governance/utils/util'; import { STEPS } from '../stake/pool/stake'; @@ -30,7 +31,7 @@ import { STEPS } from '../stake/pool/stake'; interface Props { address: string; balances: BalancesInfo | undefined; - assetId: number | undefined; + assetId: string | undefined; inputs: Inputs | undefined; setStep: React.Dispatch>; setInputs: React.Dispatch>; @@ -99,6 +100,19 @@ export default function InputPage ({ address, assetId, balances, inputs, setInpu const { api, chain, formatted } = useInfo(address); const teleportState = useTeleport(address); + const isForeignAsset = assetId ? assetId.startsWith('0x') : undefined; + + const noAssetId = assetId === undefined || assetId === 'undefined'; + const isNativeToken = String(assetId) === String(NATIVE_TOKEN_ASSET_ID) || String(assetId) === String(NATIVE_TOKEN_ASSET_ID_ON_ASSETHUB); + const isNonNativeToken = !noAssetId && !isNativeToken; + + const parsedAssetId = useMemo(() => noAssetId || isNativeToken + ? undefined + : isForeignAsset + ? decodeMultiLocation(assetId as HexString) + : parseInt(assetId) +, [assetId, isForeignAsset, isNativeToken, noAssetId]); + const [amount, setAmount] = useState(inputs?.amount || '0'); const [estimatedFee, setEstimatedFee] = useState(); const [estimatedCrossChainFee, setEstimatedCrossChainFee] = useState(); @@ -109,10 +123,6 @@ export default function InputPage ({ address, assetId, balances, inputs, setInpu const [transferType, setTransferType] = useState('Normal'); const [maxFee, setMaxFee] = useState(); - const ED = assetId - ? balances?.ED - : api && api.consts['balances']['existentialDeposit'] as unknown as BN; - const transferableBalance = useMemo(() => getValue('transferable', balances), [balances]); const amountAsBN = useMemo( @@ -122,9 +132,9 @@ export default function InputPage ({ address, assetId, balances, inputs, setInpu [amount, balances]); const warningMessage = useMemo(() => { - if (transferType !== 'All' && amountAsBN && balances?.decimal && ED && transferableBalance) { + if (transferType !== 'All' && amountAsBN && balances?.decimal && balances?.ED && transferableBalance) { const totalBalance = balances.freeBalance.add(balances.reservedBalance); - const toTransferBalance = assetId + const toTransferBalance = isNonNativeToken ? amountAsBN : amountAsBN.add(estimatedFee || BN_ZERO).add(estimatedCrossChainFee || BN_ZERO); @@ -134,23 +144,23 @@ export default function InputPage ({ address, assetId, balances, inputs, setInpu return t('There is no sufficient transferable balance!'); } - if (remainingBalanceAfterTransfer.lt(ED) && remainingBalanceAfterTransfer.gt(BN_ZERO)) { + if (remainingBalanceAfterTransfer.lt(balances.ED) && remainingBalanceAfterTransfer.gt(BN_ZERO)) { return t('This transaction will drop your balance below the Existential Deposit threshold, risking account reaping.'); } } return undefined; - }, [ED, amountAsBN, assetId, balances, transferableBalance, estimatedCrossChainFee, estimatedFee, t, transferType]); + }, [amountAsBN, isNonNativeToken, balances, transferableBalance, estimatedCrossChainFee, estimatedFee, t, transferType]); const destinationGenesisHashes = useMemo((): DropdownOption[] => { const currentChainOption = chain ? [{ text: chain.name, value: chain.genesisHash as string }] : []; - const mayBeTeleportDestinations = - assetId === undefined - ? teleportState?.destinations?.map(({ genesisHash, info, paraId }) => ({ text: toTitleCase(info) as string, value: (paraId || String(genesisHash)) as string })) - : []; + const maybeTeleportDestinations = + isNativeToken + ? teleportState?.destinations?.map(({ genesisHash, info, paraId }) => ({ text: toTitleCase(info) as string, value: (paraId || String(genesisHash)) as string })) + : []; - return currentChainOption.concat(mayBeTeleportDestinations); - }, [assetId, chain, teleportState?.destinations]); + return currentChainOption.concat(maybeTeleportDestinations); + }, [isNativeToken, chain, teleportState?.destinations]); const isCrossChain = useMemo(() => recipientChainGenesisHash !== chain?.genesisHash, [chain?.genesisHash, recipientChainGenesisHash]); @@ -159,26 +169,34 @@ export default function InputPage ({ address, assetId, balances, inputs, setInpu return undefined; } - const module = assetId !== undefined - ? isAssethub(chain.genesisHash) - ? 'assets' - : api.tx?.['currencies'] - ? 'currencies' - : 'tokens' - : 'balances'; + try { + const module = isNonNativeToken + ? isAssethub(chain.genesisHash) + ? isForeignAsset + ? 'foreignAssets' + : 'assets' + : api.tx?.['currencies'] + ? 'currencies' + : 'tokens' + : 'balances'; + + if (['currencies', 'tokens'].includes(module)) { + return api.tx[module]['transfer']; + } - if (['currencies', 'tokens'].includes(module)) { - return api.tx[module]['transfer']; - } + return api.tx?.[module] && ( + transferType === 'Normal' + ? api.tx[module]['transferKeepAlive'] + : isNonNativeToken + ? api.tx[module]['transfer'] + : api.tx[module]['transferAll'] + ); + } catch (e) { + console.log('Something wrong while making on chain call!', e); - return api.tx?.[module] && ( - transferType === 'Normal' - ? api.tx[module]['transferKeepAlive'] - : assetId !== undefined - ? api.tx[module]['transfer'] - : api.tx[module]['transferAll'] - ); - }, [api, assetId, chain, transferType]); + return undefined; + } + }, [api, isNonNativeToken, chain, isForeignAsset, transferType]); const call = useMemo((): SubmittableExtrinsicFunction<'promise'> | undefined => { if (!api) { @@ -218,14 +236,14 @@ export default function InputPage ({ address, assetId, balances, inputs, setInpu return setFeeCall(dummyAmount); } - const _params = assetId !== undefined + const _params = isNonNativeToken ? ['currencies', 'tokens'].includes(onChainCall.section) ? [formatted, balances.currencyId, amount] - : [assetId, formatted, amount] + : [parsedAssetId, formatted, amount] : [formatted, amount]; onChainCall(..._params).paymentInfo(formatted).then((i) => setFeeCall(i?.partialFee)).catch(console.error); - }, [api, formatted, balances, onChainCall, assetId]); + }, [api, formatted, balances, onChainCall, isNonNativeToken, parsedAssetId]); const crossChainParams = useMemo(() => { if (!api || !balances || !teleportState || isCrossChain === false || (recipientParaId === INVALID_PARA_ID && !teleportState?.isParaTeleport) || Number(amount) === 0) { @@ -285,10 +303,10 @@ export default function InputPage ({ address, assetId, balances, inputs, setInpu call, params: (isCrossChain ? crossChainParams - : assetId !== undefined + : isNonNativeToken ? ['currencies', 'tokens'].includes(onChainCall?.section || '') ? [recipientAddress, balances.currencyId, amountAsBN] // this is for transferring on mutliasset chains - : [assetId, recipientAddress, amountAsBN] // this is for transferring on asset hubs + : [parsedAssetId, recipientAddress, amountAsBN] // this is for transferring on asset hubs : transferType === 'All' ? [recipientAddress, false] // transferAll with keepalive = false : [recipientAddress, amountAsBN]) as unknown[], @@ -297,7 +315,7 @@ export default function InputPage ({ address, assetId, balances, inputs, setInpu recipientGenesisHashOrParaId: recipientChainGenesisHash, totalFee: estimatedFee ? estimatedFee.add(estimatedCrossChainFee || BN_ZERO) : undefined }); - }, [amountAsBN, estimatedFee, estimatedCrossChainFee, setInputs, call, recipientAddress, isCrossChain, crossChainParams, assetId, formatted, amount, recipientChainName, recipientChainGenesisHash, transferType, onChainCall?.section, balances]); + }, [amountAsBN, estimatedFee, estimatedCrossChainFee, setInputs, call, parsedAssetId, recipientAddress, isCrossChain, crossChainParams, isNonNativeToken, formatted, amount, recipientChainName, recipientChainGenesisHash, transferType, onChainCall?.section, balances]); useEffect(() => { if (!api || !transferableBalance) { @@ -339,21 +357,21 @@ export default function InputPage ({ address, assetId, balances, inputs, setInpu }, [call, formatted, isCrossChain, crossChainParams]); const setWholeAmount = useCallback(() => { - if (!api || !transferableBalance || !maxFee || !balances || !ED) { + if (!transferableBalance || !maxFee || !balances) { return; } setTransferType('All'); - const _isAvailableZero = transferableBalance.isZero(); + const isAvailableZero = transferableBalance.isZero(); - const _maxFee = assetId === undefined ? maxFee : BN_ZERO; + const _maxFee = isNativeToken ? maxFee : BN_ZERO; - const _canNotTransfer = _isAvailableZero || _maxFee.gte(transferableBalance); - const allAmount = _canNotTransfer ? '0' : amountToHuman(transferableBalance.sub(_maxFee).toString(), balances.decimal); + const canNotTransfer = isAvailableZero || _maxFee.gte(transferableBalance); + const allAmount = canNotTransfer ? '0' : amountToHuman(transferableBalance.sub(_maxFee).toString(), balances.decimal); setAmount(allAmount); - }, [api, assetId, balances, ED, maxFee, transferableBalance]); + }, [balances, isNativeToken, maxFee, transferableBalance]); const _onChangeAmount = useCallback((value: string) => { if (!balances) { @@ -403,7 +421,7 @@ export default function InputPage ({ address, assetId, balances, inputs, setInpu - + {t('Transferable amount')} diff --git a/packages/extension-polkagate/src/fullscreen/sendFund/Review.tsx b/packages/extension-polkagate/src/fullscreen/sendFund/Review.tsx index 19635e1af..136195431 100644 --- a/packages/extension-polkagate/src/fullscreen/sendFund/Review.tsx +++ b/packages/extension-polkagate/src/fullscreen/sendFund/Review.tsx @@ -10,7 +10,7 @@ import { Divider, Grid, Typography, useTheme } from '@mui/material'; import React, { useCallback, useMemo, useState } from 'react'; import { ChainLogo, Identity, Motion, ShowBalance, SignArea2, WrongPasswordAlert } from '../../components'; -import { useApi, useChain } from '../../hooks'; +import { useInfo } from '../../hooks'; import useTranslation from '../../hooks/useTranslation'; import { ThroughProxy } from '../../partials'; import { PROXY_TYPE } from '../../util/constants'; @@ -30,10 +30,10 @@ interface Props { export default function Review ({ address, balances, inputs, setRefresh, setStep, setTxInfo, step }: Props): React.ReactElement { const { t } = useTranslation(); - const api = useApi(address); - const chain = useChain(address); const theme = useTheme(); + const { api, chain } = useInfo(address); + const [isPasswordError, setIsPasswordError] = useState(false); const [selectedProxy, setSelectedProxy] = useState(); diff --git a/packages/extension-polkagate/src/fullscreen/sendFund/index.tsx b/packages/extension-polkagate/src/fullscreen/sendFund/index.tsx index 886ba0a0b..ffd4b25e1 100644 --- a/packages/extension-polkagate/src/fullscreen/sendFund/index.tsx +++ b/packages/extension-polkagate/src/fullscreen/sendFund/index.tsx @@ -49,9 +49,8 @@ export default function SendFund (): React.ReactElement { const ref = useRef(chain); const history = useHistory(); - const parsedAssetId = assetId === undefined || assetId === 'undefined' ? undefined : parseInt(assetId); const [refresh, setRefresh] = useState(false); - const balances = useBalances(address, refresh, setRefresh, undefined, parsedAssetId); + const balances = useBalances(address, refresh, setRefresh, undefined, assetId); const [step, setStep] = useState(STEPS.INDEX); const [inputs, setInputs] = useState(); @@ -80,8 +79,8 @@ export default function SendFund (): React.ReactElement { const closeConfirmation = useCallback(() => { setRefresh(true); - openOrFocusTab(`/accountfs/${address}/0`, true); // TODO: add asset id instead of 0 - }, [address, setRefresh]); + openOrFocusTab(`/accountfs/${address}/${assetId}`, true); + }, [address, assetId]); return ( @@ -106,7 +105,7 @@ export default function SendFund (): React.ReactElement { {(step === STEPS.INDEX) && (); const [newRootAddress, setNewRootAddress] = useState(); diff --git a/packages/extension-polkagate/src/fullscreen/stake/pool/partials/PoolsTable.tsx b/packages/extension-polkagate/src/fullscreen/stake/pool/partials/PoolsTable.tsx index 44ea7fcd2..a4db37d8f 100644 --- a/packages/extension-polkagate/src/fullscreen/stake/pool/partials/PoolsTable.tsx +++ b/packages/extension-polkagate/src/fullscreen/stake/pool/partials/PoolsTable.tsx @@ -147,8 +147,8 @@ export default function PoolsTable({ address, setSearchedPools, api, numberOfFet {poolsToShow ? poolsToShow.length ? poolsToShow.map((pool, index) => { - const mayBeCommission = pool.bondedPool.commission.current.isSome ? pool.bondedPool.commission.current.value[0] : 0; - const commission = Number(mayBeCommission) / (10 ** 7) < 1 ? 0 : Number(mayBeCommission) / (10 ** 7); + const maybeCommission = pool.bondedPool.commission.current.isSome ? pool.bondedPool.commission.current.value[0] : 0; + const commission = Number(maybeCommission) / (10 ** 7) < 1 ? 0 : Number(maybeCommission) / (10 ** 7); return ( diff --git a/packages/extension-polkagate/src/fullscreen/stake/solo/commonTasks/configurePayee/index.tsx b/packages/extension-polkagate/src/fullscreen/stake/solo/commonTasks/configurePayee/index.tsx index a033f0103..5ef8850a0 100644 --- a/packages/extension-polkagate/src/fullscreen/stake/solo/commonTasks/configurePayee/index.tsx +++ b/packages/extension-polkagate/src/fullscreen/stake/solo/commonTasks/configurePayee/index.tsx @@ -199,8 +199,8 @@ export default function ConfigurePayee ({ address, setRefresh, setShow, show }: return; } - const mayBeNew = makePayee(rewardDestinationValue, rewardDestinationAccount); - const payee = mayBeNew && JSON.stringify(settings.payee) !== JSON.stringify(mayBeNew) ? mayBeNew : undefined; + const maybeNew = makePayee(rewardDestinationValue, rewardDestinationAccount); + const payee = maybeNew && JSON.stringify(settings.payee) !== JSON.stringify(maybeNew) ? maybeNew : undefined; setNewPayee(payee); }, [makePayee, rewardDestinationAccount, rewardDestinationValue, settings]); diff --git a/packages/extension-polkagate/src/hooks/index.ts b/packages/extension-polkagate/src/hooks/index.ts index 9b0e8fc3f..f5959e7aa 100644 --- a/packages/extension-polkagate/src/hooks/index.ts +++ b/packages/extension-polkagate/src/hooks/index.ts @@ -21,6 +21,8 @@ export { default as useAssetsBalances } from './useAssetsBalances'; export { default as useAuction } from './useAuction'; export { default as useAvailableToSoloStake } from './useAvailableToSoloStake'; export { default as useBalances } from './useBalances'; +export { default as useBalancesOnAssethub } from './useBalancesOnAssethub'; +export { default as useBalancesOnMultiAssetChain } from './useBalancesOnMultiAssetChain'; export { default as useBlockInterval } from './useBlockInterval'; export { default as useCanPayFee } from './useCanPayFee'; export { default as useCanPayFeeAndDeposit } from './useCanPayFeeAndDeposit'; @@ -66,6 +68,7 @@ export { default as useMinToReceiveRewardsInSolo2 } from './useMinToReceiveRewar export { default as useMyAccountIdentity } from './useMyAccountIdentity'; export { default as useMyPools } from './useMyPools'; export { default as useMyVote } from './useMyVote'; +export { default as useNativeAssetBalances } from './useNativeAssetBalances'; export { default as useNativeTokenPrice } from './useNativeTokenPrice'; export { default as useNeedsPutInFrontOf } from './useNeedsPutInFrontOf'; export { default as useNeedsRebag } from './useNeedsRebag'; @@ -75,6 +78,7 @@ export { default as usePendingRewards } from './usePendingRewards'; export { default as usePendingRewards2 } from './usePendingRewards2'; export { default as usePeopleChain } from './usePeopleChain'; export { default as usePool } from './usePool'; +export { default as usePoolBalances } from './usePoolBalances'; export { default as usePoolConsts } from './usePoolConsts'; export { usePoolMembers } from './usePoolMembers'; export { default as usePools } from './usePools'; diff --git a/packages/extension-polkagate/src/hooks/useAccountAssetsOptions.ts b/packages/extension-polkagate/src/hooks/useAccountAssetsOptions.ts index a2a87a0e2..9c8fb59e1 100644 --- a/packages/extension-polkagate/src/hooks/useAccountAssetsOptions.ts +++ b/packages/extension-polkagate/src/hooks/useAccountAssetsOptions.ts @@ -1,14 +1,14 @@ // Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors // SPDX-License-Identifier: Apache-2.0 -// @ts-nocheck + +import type { DropdownOption } from '../util/types'; import { useContext, useMemo } from 'react'; import { AccountsAssetsContext } from '../components'; -import type { DropdownOption } from '../util/types'; import { useGenesisHash } from '.'; -export default function useAccountAssetsOptions(address: string | undefined): DropdownOption[] | undefined | null { +export default function useAccountAssetsOptions (address: string | undefined): DropdownOption[] | undefined | null { const genesisHash = useGenesisHash(address); const { accountsAssets } = useContext(AccountsAssetsContext); @@ -21,7 +21,7 @@ export default function useAccountAssetsOptions(address: string | undefined): Dr const maybeAssets = accountsAssets.balances[address][genesisHash]; if (maybeAssets?.length) { - return maybeAssets.map(({ assetId, token }) => ({ text: token, value: assetId || -1 })); // since native token does not have asset id we set =1 + return maybeAssets.map(({ assetId, token }) => ({ text: token, value: assetId })); } return null; diff --git a/packages/extension-polkagate/src/hooks/useAccountLocks.ts b/packages/extension-polkagate/src/hooks/useAccountLocks.ts index ebfcf4747..ff018736e 100644 --- a/packages/extension-polkagate/src/hooks/useAccountLocks.ts +++ b/packages/extension-polkagate/src/hooks/useAccountLocks.ts @@ -141,7 +141,7 @@ export default function useAccountLocks (address: string | undefined, palletRefe return; // has not voted!! or any issue } - const mayBePriors: Lock[] = []; + const maybePriors: Lock[] = []; const maybeVotes = maybeVotingFor.map((v, index): null | [BN, BN[], PalletConvictionVotingVoteCasting] => { if (!v.isCasting) { @@ -152,7 +152,7 @@ export default function useAccountLocks (address: string | undefined, palletRefe const classId = params[index][1]; if (!casting.prior[0].eq(BN_ZERO)) { - mayBePriors.push({ + maybePriors.push({ classId, endBlock: casting.prior[0], locked: 'None', @@ -180,7 +180,7 @@ export default function useAccountLocks (address: string | undefined, palletRefe if (!refIds.length) { return setInfo({ - priors: mayBePriors, + priors: maybePriors, referenda: null, votes: maybeVotes }); @@ -197,7 +197,7 @@ export default function useAccountLocks (address: string | undefined, palletRefe : null; setInfo({ - priors: mayBePriors, + priors: maybePriors, referenda: maybeReferenda, votes: maybeVotes }); diff --git a/packages/extension-polkagate/src/hooks/useAssetHubAssets.ts b/packages/extension-polkagate/src/hooks/useAssetHubAssets.ts index 148777fb3..ea7d0d7a3 100644 --- a/packages/extension-polkagate/src/hooks/useAssetHubAssets.ts +++ b/packages/extension-polkagate/src/hooks/useAssetHubAssets.ts @@ -1,61 +1,92 @@ // Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors // SPDX-License-Identifier: Apache-2.0 -// @ts-nocheck - -import type { Option, StorageKey, u32 } from '@polkadot/types'; - -import { useEffect, useState } from 'react'; +import type { bool, Bytes, StorageKey, u8, u128 } from '@polkadot/types'; import type { AccountId } from '@polkadot/types/interfaces/runtime'; - +import type { AnyTuple } from '@polkadot/types-codec/types'; import type { DropdownOption } from '../util/types'; -import { useApi, useGenesisHash } from '.'; + +import { useCallback, useEffect, useState } from 'react'; + +import { getStorage, updateStorage } from '../components/Loading'; +import { encodeMultiLocation } from '../util/utils'; +import { useInfo } from '.'; /** * @description To get all available assets on asset hubs for an address based on its chain */ -export default function useAssetHubAssets(address: AccountId | string | undefined): DropdownOption[] | undefined | null { - const api = useApi(address); - const accountGenesisHash = useGenesisHash(address); +export default function useAssetHubAssets (address: AccountId | string | undefined): DropdownOption[] | undefined | null { + const { api, genesisHash: accountGenesisHash } = useInfo(address); const [assets, setAssets] = useState(); - useEffect(() => { - if (!api) { - return setAssets(undefined); + const handleAssetFetch = useCallback(async (assetType: string) => { + if (!(api?.query[assetType] && api?.query[assetType]['asset'])) { + return []; } - if (api.genesisHash.toString() !== accountGenesisHash) { - return setAssets(undefined); + const keys: StorageKey[] = await api.query[assetType]['asset'].keys(); + const assetIds = keys.map(({ args: [id] }) => id); + + if (!assetIds || !api?.query[assetType]?.['metadata']) { + return []; } - api.query.assets && api.query.assets.asset - ? api.query.assets.asset.keys().then((keys: StorageKey<[u32]>[]) => { - const assetIds = keys.map(({ args: [id] }) => id); + const metadata = await api?.query[assetType]?.['metadata'].multi(assetIds) as unknown as { 'deposit': u128, 'name': Bytes, 'symbol': Bytes, 'decimals': u8, 'isFrozen': bool }[]; + const assetOptions = metadata.map(({ name, symbol }, index) => { + if (!symbol.toHuman()) { + return undefined; + } + + const assetIndex = assetType === 'assets' ? assetIds[index] as unknown as string : encodeMultiLocation(assetIds[index]) as string; + + return { text: `${assetType === 'assets' ? assetIndex : 'Foreign Asset'}: ${symbol.toHuman() as string} (${name.toHuman() as string})`, value: assetIndex.toString() }; + }).filter((item) => !!item); + + //@ts-ignore + assetOptions.sort((a, b) => a.value - b.value); + + return assetOptions; + }, [api]); - // assetIds && api && api.query.assets && api.query.assets.asset.multi(assetIds).then((details: Option[]) => { - // console.log('details:', JSON.parse(JSON.stringify(details))); - // }); + const handleAllAssets = useCallback(async () => { + const assets = await handleAssetFetch('assets'); + const foreignAssets = await handleAssetFetch('foreignAssets'); - assetIds && api && api.query.assets && api.query.assets.metadata.multi(assetIds).then((metadata: Option[]) => { + const maybeAllAssets = assets.concat(foreignAssets); - const assetOptions = metadata.map(({ name, symbol }, index) => { - if (!symbol.toHuman()) { - return undefined; - } + const allAssets = maybeAllAssets?.length ? maybeAllAssets : undefined; - return { text: `${assetIds[index]}: ${symbol.toHuman()} (${name.toHuman()})`, value: assetIds[index].toString() }; - }).filter((item) => !!item); + setAssets(allAssets); - assetOptions.sort((a, b) => a.value - b.value); - setAssets(assetOptions); - }); - }).catch(console.error) - : setAssets(null); + if (accountGenesisHash && api?.genesisHash.toString() === accountGenesisHash && allAssets?.length) { + updateStorage('assetsOnAssetHub1', { [accountGenesisHash]: allAssets }).catch(console.error); + } + }, [accountGenesisHash, api?.genesisHash, handleAssetFetch]); + + const provideFromLocalStorage = useCallback(async () => { + if (!accountGenesisHash) { + return; + } + + const maybeSavedAssetsAllAssetHubs = await getStorage('assetsOnAssetHub1').catch(console.error) as Record; + const maybeSavedAssets = maybeSavedAssetsAllAssetHubs?.[accountGenesisHash]; + + if (maybeSavedAssets) { + setAssets(maybeSavedAssets); + } + }, [accountGenesisHash]); + + useEffect(() => { + if (accountGenesisHash) { + provideFromLocalStorage().catch(console.error); + } + + if (!api || api.genesisHash.toString() !== accountGenesisHash) { + return setAssets(undefined); + } - // api && api.query.assetRegistry.assetMetadatas('stableassetid').then((assetRegistry) => { // erc20, stableassetid, foreignassetid, nativeassetid - // console.log('assets::::',JSON.parse(JSON.stringify(assetRegistry))) - // }) - }, [accountGenesisHash, api]); + handleAllAssets().catch(console.error); + }, [accountGenesisHash, api, handleAllAssets, provideFromLocalStorage]); return assets; } diff --git a/packages/extension-polkagate/src/hooks/useAssetsBalances.ts b/packages/extension-polkagate/src/hooks/useAssetsBalances.ts index d8546b4df..41606bc4c 100644 --- a/packages/extension-polkagate/src/hooks/useAssetsBalances.ts +++ b/packages/extension-polkagate/src/hooks/useAssetsBalances.ts @@ -46,7 +46,7 @@ interface BalancesDetails { } interface MessageBody { - assetId?: number, + assetId: number | string, totalBalance: string, chainName: string, decimal: string, @@ -74,7 +74,7 @@ export const BN_MEMBERS = [ ]; export interface FetchedBalance { - assetId?: number, + assetId: number | string, availableBalance: BN, balanceDetails?: any, totalBalance: BN, diff --git a/packages/extension-polkagate/src/hooks/useBalances.ts b/packages/extension-polkagate/src/hooks/useBalances.ts index e80597640..c31d2bec6 100644 --- a/packages/extension-polkagate/src/hooks/useBalances.ts +++ b/packages/extension-polkagate/src/hooks/useBalances.ts @@ -1,206 +1,55 @@ // Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { Asset } from '@polkagate/apps-config/assets/types'; import type React from 'react'; -import type { bool, Bytes, Option, StorageKey, u8, u128 } from '@polkadot/types'; -import type { Balance } from '@polkadot/types/interfaces'; -// @ts-ignore -import type { FrameSystemAccountInfo, OrmlTokensAccountData, PalletAssetsAssetAccount, PalletAssetsAssetDetails, PalletNominationPoolsBondedPoolInner, PalletNominationPoolsPoolMember } from '@polkadot/types/lookup'; -import type { AnyTuple } from '@polkadot/types/types'; +import type { BN } from '@polkadot/util'; import type { BalancesInfo, SavedBalances } from '../util/types'; -import { createAssets } from '@polkagate/apps-config/assets'; -import { useCallback, useContext, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; -import { BN, BN_ONE, BN_ZERO } from '@polkadot/util'; - -import { FetchingContext } from '../components'; -import { toCamelCase } from '../fullscreen/governance/utils/util'; import { updateMeta } from '../messaging'; -import { ASSET_HUBS, NATIVE_TOKEN_ASSET_ID } from '../util/constants'; -import getPoolAccounts from '../util/getPoolAccounts'; -import { useInfo, useStakingAccount } from '.'; - -const assetsChains = createAssets(); +import { NATIVE_TOKEN_ASSET_ID, NATIVE_TOKEN_ASSET_ID_ON_ASSETHUB } from '../util/constants'; +import { useBalancesOnAssethub, useBalancesOnMultiAssetChain, useInfo, useNativeAssetBalances, usePoolBalances, useStakingAccount } from '.'; -// TODO: decouple thi shook to smaller independent ones like usePoolBalance, useAssetBalance, useNativeBalance ... -export default function useBalances (address: string | undefined, refresh?: boolean, setRefresh?: React.Dispatch>, onlyNew = false, assetId?: number): BalancesInfo | undefined { +export default function useBalances (address: string | undefined, refresh?: boolean, setRefresh?: React.Dispatch>, onlyNew = false, assetId?: string | number): BalancesInfo | undefined { const stakingAccount = useStakingAccount(address); - const { account, api, chain, chainName, decimal: currentDecimal, formatted, token: currentToken } = useInfo(address); - const isFetching = useContext(FetchingContext); + const { account, api, chain, chainName, decimal: currentDecimal, token: currentToken } = useInfo(address); + + const isNativeAssetId = String(assetId) === String(NATIVE_TOKEN_ASSET_ID) || String(assetId) === String(NATIVE_TOKEN_ASSET_ID_ON_ASSETHUB); + const maybeNonNativeAssetId = isNativeAssetId ? undefined : assetId; - const mayBeAssetsOnMultiAssetChains = assetsChains[toCamelCase(chainName || '')]; - const isAssetHub = ASSET_HUBS.includes(chain?.genesisHash || ''); + const balances = useNativeAssetBalances(address, refresh, setRefresh, onlyNew); + const maybeBalancesOnAssetHub = useBalancesOnAssethub(address, maybeNonNativeAssetId); + const maybeBalancesOnMultiChainAssets = useBalancesOnMultiAssetChain(address, maybeNonNativeAssetId); + const pooledBalance = usePoolBalances(address, refresh); + + const assetBalance = maybeBalancesOnAssetHub || maybeBalancesOnMultiChainAssets; - const [assetBalance, setAssetBalance] = useState(); - const [balances, setBalances] = useState(); - const [newBalances, setNewBalances] = useState(); - const [pooledBalance, setPooledBalance] = useState<{ balance: BN, genesisHash: string } | null>(); const [overall, setOverall] = useState(); const token = api?.registry.chainTokens[0]; const decimal = api?.registry.chainDecimals[0]; - const getPoolBalances = useCallback(() => { - if (api && !api.query['nominationPools']) { - return setPooledBalance({ balance: BN_ZERO, genesisHash: api.genesisHash.toString() }); - } - - api && formatted && api.query['nominationPools']['poolMembers'](formatted).then(async (res: any) => { - const member = res?.unwrapOr(undefined) as PalletNominationPoolsPoolMember | undefined; - - const genesisHash = api.genesisHash.toString(); - - if (!member) { - isFetching.fetching[String(formatted)]['pooledBalance'] = false; - isFetching.set(isFetching.fetching); - - return setPooledBalance({ balance: BN_ZERO, genesisHash }); // user does not joined a pool yet. or pool id does not exist - } - - const poolId = member.poolId; - const accounts = poolId && getPoolAccounts(api, poolId); - - if (!accounts) { - console.warn(`useBalances: can not find a pool with id: ${poolId}`); - - isFetching.fetching[String(formatted)]['pooledBalance'] = false; - isFetching.set(isFetching.fetching); - - return setPooledBalance({ balance: BN_ZERO, genesisHash }); - } - - const [bondedPool, stashIdAccount, myClaimable] = await Promise.all([ - api.query['nominationPools']['bondedPools'](poolId) as unknown as Option, - api.derive.staking.account(accounts.stashId), - api.call['nominationPoolsApi']['pendingRewards'](formatted) - ]); - - const active = member.points.isZero() - ? BN_ZERO - : (new BN(String(member.points)).mul(new BN(String(stashIdAccount.stakingLedger.active)))).div(new BN(String(bondedPool.unwrap()?.points ?? BN_ONE))); - const rewards = myClaimable as Balance; - let unlockingValue = BN_ZERO; - - member?.unbondingEras?.forEach((value: BN) => { - unlockingValue = unlockingValue.add(value); - }); - - genesisHash === chain?.genesisHash && setPooledBalance({ balance: active.add(rewards).add(unlockingValue), genesisHash }); - setRefresh && setRefresh(false); - isFetching.fetching[String(formatted)]['pooledBalance'] = false; - isFetching.set(isFetching.fetching); - }).catch(console.error); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [api, formatted, isFetching.fetching[String(formatted)]?.['length'], setRefresh, chain?.genesisHash]); - - const getBalances = useCallback(() => { - if (!chainName || api?.genesisHash?.toString() !== chain?.genesisHash || !decimal || !token) { - return; - } - - const ED = api.consts['balances'] ? api.consts['balances']['existentialDeposit'] as unknown as BN : BN_ZERO; - - formatted && api.derive.balances?.all(formatted).then((allBalances) => { - //@ts-ignore - api.query['system']['account'](formatted).then(({ data: systemBalance }: FrameSystemAccountInfo) => { - // some chains such as PARALLEL does not support this call hence BN_ZERO is set for them - const frozenBalance = systemBalance?.frozen || BN_ZERO; - - setNewBalances({ - ED, - assetId: NATIVE_TOKEN_ASSET_ID, - ...allBalances, - chainName, - date: Date.now(), - decimal, - frozenBalance, - genesisHash: api.genesisHash.toString(), - token - }); - setRefresh && setRefresh(false); - isFetching.fetching[String(formatted)]['balances'] = false; - isFetching.set(isFetching.fetching); - }).catch(console.error); - }).catch(console.error); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [api, chain?.genesisHash, chainName, formatted, isFetching.fetching[String(formatted)]?.['length'], setRefresh]); - useEffect(() => { const apiGenesisHash = api?.genesisHash?.toString(); - if (newBalances && pooledBalance && apiGenesisHash === chain?.genesisHash && apiGenesisHash === newBalances?.genesisHash && apiGenesisHash === pooledBalance.genesisHash) { + if (onlyNew && balances && pooledBalance && apiGenesisHash === chain?.genesisHash && apiGenesisHash === balances?.genesisHash && apiGenesisHash === pooledBalance.genesisHash) { setOverall({ - ...newBalances, + ...balances, pooledBalance: pooledBalance.balance, soloTotal: stakingAccount?.stakingLedger?.total as unknown as BN }); } else { setOverall(undefined); } - }, [pooledBalance, newBalances, api?.genesisHash, account?.genesisHash, chain?.genesisHash, stakingAccount]); - - useEffect(() => { - if (!formatted || !token || !decimal || !chainName || api?.genesisHash?.toString() !== chain?.genesisHash) { - return; - } - - /** to fetch a formatted address's balance if not already fetching */ - if (!isFetching.fetching[String(formatted)]?.['balances']) { - if (!isFetching.fetching[String(formatted)]) { - isFetching.fetching[String(formatted)] = {}; - } - - isFetching.fetching[String(formatted)]['balances'] = true; - isFetching.set(isFetching.fetching); - getBalances(); - } else { - console.info(`Balance is fetching for ${formatted}, hence doesn't need to fetch it again!`); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [api, chain?.genesisHash, chainName, decimal, formatted, getBalances, isFetching.fetching[String(formatted)]?.['length'], token]); - - useEffect(() => { - if (!chain?.genesisHash || !api || !formatted || api.genesisHash.toString() !== chain.genesisHash) { - return; - } - - if (!isFetching.fetching[String(formatted)]?.['pooledBalance']) { - if (!isFetching.fetching[String(formatted)]) { - isFetching.fetching[String(formatted)] = {}; - } - - isFetching.fetching[String(formatted)]['pooledBalance'] = true; - isFetching.set(isFetching.fetching); - getPoolBalances(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [api, chain?.genesisHash, formatted, getPoolBalances, isFetching.fetching[String(formatted)]?.['length']]); - - useEffect(() => { - if (refresh) { - setBalances(undefined); - setNewBalances(undefined); - setPooledBalance(undefined); - - if (isFetching.fetching[String(formatted)]) { - isFetching.fetching[String(formatted)]['pooledBalance'] = false; - isFetching.fetching[String(formatted)]['balances'] = true; - } - - isFetching.set(isFetching.fetching); - getBalances(); - getPoolBalances(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [Object.keys(isFetching?.fetching ?? {})?.length, api, chainName, decimal, formatted, getBalances, getPoolBalances, refresh, token]); + }, [pooledBalance, balances, onlyNew, api?.genesisHash, account?.genesisHash, chain?.genesisHash, stakingAccount]); useEffect(() => { if (!address || !api || api.genesisHash.toString() !== account?.genesisHash || !overall || !chainName || !token || !decimal || account?.genesisHash !== chain?.genesisHash || account?.genesisHash !== overall.genesisHash) { return; } + // TODO: this just saves native assets in local storage! can save other assets as well /** to SAVE fetched balance in local storage, first load saved balances of different chaines if any */ const savedBalances = JSON.parse(account?.balances ?? '{}') as SavedBalances; @@ -227,159 +76,10 @@ export default function useBalances (address: string | undefined, refresh?: bool // eslint-disable-next-line react-hooks/exhaustive-deps }, [Object.keys(account ?? {})?.length, account?.genesisHash, address, api, pooledBalance, chain, chainName, decimal, overall, token]); - useEffect(() => { - if (!chainName || !account || account?.genesisHash !== chain?.genesisHash) { - return; - } - - // to LOAD saved balances - const savedBalances = JSON.parse(account?.balances ?? '{}') as SavedBalances; - - if (savedBalances[chainName]) { - const sb = savedBalances[chainName].balances; - - const lastBalances = { - ED: new BN(sb['ED'] || '0'), - assetId: sb['assetId'] && parseInt(sb['assetId']), - availableBalance: new BN(sb['availableBalance']), - chainName, - date: savedBalances[chainName].date, - decimal: savedBalances[chainName].decimal, - freeBalance: new BN(sb['freeBalance']), - frozenBalance: new BN(sb['frozenBalance'] || '0'), - genesisHash: sb['genesisHash'], - lockedBalance: new BN(sb['lockedBalance']), - pooledBalance: new BN(sb['pooledBalance']), - reservedBalance: new BN(sb['reservedBalance']), - token: savedBalances[chainName].token, - vestedBalance: new BN(sb['vestedBalance']), - vestedClaimable: new BN(sb['vestedClaimable']), - votingBalance: new BN(sb['votingBalance']) - } as BalancesInfo; - - setBalances({ - ...lastBalances, - soloTotal: stakingAccount?.stakingLedger?.total as unknown as BN - }); - - return; - } - - setBalances(undefined); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [Object.keys(account ?? {})?.length, address, chainName, stakingAccount]); - - useEffect(() => { - /** We fetch asset hub assets via this hook for asset ids, other multi chain assets have been fetched in the next useEffect*/ - if (assetId === undefined || !api?.query?.['assets'] || !ASSET_HUBS.includes(chain?.genesisHash || '')) { - return; - } - - fetchAssetOnAssetHub().catch(console.error); - - async function fetchAssetOnAssetHub () { - if (!api) { - return; - } - - try { - const [accountAsset, assetInfo, metadata] = await Promise.all([ - api.query['assets']['account'](assetId, formatted) as unknown as Option, - api.query['assets']['asset'](assetId) as unknown as Option, - api.query['assets']['metadata'](assetId) as unknown as {deposit: u128, name: Bytes, symbol: Bytes, decimals: u8, isFrozen: bool} - - ]); - - const ED = assetInfo.isNone ? BN_ZERO : assetInfo.unwrap().minBalance; - const _AccountAsset = accountAsset.isSome ? accountAsset.unwrap() : null; - const isFrozen = _AccountAsset?.status?.isFrozen; - const balance = _AccountAsset?.balance || BN_ZERO; - - const assetBalances = { - ED, - assetId, - availableBalance: isFrozen ? BN_ZERO : balance, - chainName, - date: Date.now(), - decimal: metadata.decimals.toNumber(), - freeBalance: !isFrozen ? balance : BN_ZERO, - genesisHash: api.genesisHash.toHex(), - isAsset: true, - lockedBalance: isFrozen ? balance : BN_ZERO, - reservedBalance: isFrozen ? balance : BN_ZERO, // JUST to comply with the rule that total=available + reserve - token: metadata.symbol.toHuman() as string - }; - - setAssetBalance(assetBalances as unknown as BalancesInfo); - } catch (error) { - console.error(`Failed to fetch info for assetId ${assetId}:`, error); - } - } - }, [api, assetId, chain?.genesisHash, chainName, formatted]); - - useEffect(() => { - /** We fetch asset on multi chain assets here*/ - if (api && assetId && mayBeAssetsOnMultiAssetChains && !isAssetHub) { - const assetInfo = mayBeAssetsOnMultiAssetChains[assetId]; - - assetInfo && api.query['tokens'] && fetchAssetOnMultiAssetChain(assetInfo).catch(console.error); - } - - async function fetchAssetOnMultiAssetChain (assetInfo: Asset) { - if (!api) { - return; - } - - try { - const assets: [StorageKey, OrmlTokensAccountData][] = await api.query['tokens']['accounts'].entries(address); - const currencyIdScale = (assetInfo.extras?.['currencyIdScale'] as string).replace('0x', ''); - - const found = assets.find((entry) => { - if (!entry.length) { - return false; - } - - const storageKey = entry[0].toString(); - - return storageKey.endsWith(currencyIdScale); - }); - - if (!found?.length) { - return; - } - - const currencyId = (found[0].toHuman() as string[])[1]; - const balance = found[1]; - - const assetBalances = { - ED: new BN(assetInfo.extras?.['existentialDeposit'] as string || 0), - assetId: assetInfo.id, - availableBalance: balance.free as BN, - chainName, - currencyId, - decimal: assetInfo.decimal, - freeBalance: balance.free as BN, - genesisHash: api.genesisHash.toHex(), - isAsset: true, - reservedBalance: balance.reserved as BN, - token: assetInfo.symbol - }; - - setAssetBalance(assetBalances as unknown as BalancesInfo); - } catch (e) { - console.error('Something went wrong while fetching an asset:', e); - } - } - }, [address, api, assetId, chainName, isAssetHub, mayBeAssetsOnMultiAssetChains]); - - if (assetId !== undefined) { + if (maybeNonNativeAssetId) { return assetBalance; } - if (onlyNew) { - return newBalances; // returns balances that have been fetched recently and are not from the local storage, and it does not include the pooledBalance - } - return overall && overall.genesisHash === chain?.genesisHash && overall.token === currentToken && overall.decimal === currentDecimal ? overall : balances && balances.token === currentToken && balances.decimal === currentDecimal diff --git a/packages/extension-polkagate/src/hooks/useBalancesOnAssethub.ts b/packages/extension-polkagate/src/hooks/useBalancesOnAssethub.ts new file mode 100644 index 000000000..cc8df61d9 --- /dev/null +++ b/packages/extension-polkagate/src/hooks/useBalancesOnAssethub.ts @@ -0,0 +1,78 @@ +// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { bool, Bytes, Option, u8, u128 } from '@polkadot/types'; +//@ts-ignore +import type { PalletAssetsAssetAccount, PalletAssetsAssetDetails } from '@polkadot/types/lookup'; +import type { HexString } from '@polkadot/util/types'; +import type { BalancesInfo } from '../util/types'; + +import { useCallback, useEffect, useState } from 'react'; + +import { BN_ZERO } from '@polkadot/util'; + +import { ASSET_HUBS, NATIVE_TOKEN_ASSET_ID_ON_ASSETHUB } from '../util/constants'; +import { decodeMultiLocation } from '../util/utils'; +import { useInfo } from '.'; + +export default function useBalancesOnAssethub (address: string | undefined, assetId?: string | number): BalancesInfo | undefined { + const { api, chain, chainName, formatted } = useInfo(address); + + const isAssetHub = ASSET_HUBS.includes(chain?.genesisHash || ''); + + const isForeignAsset = assetId ? typeof assetId === 'string' && assetId?.startsWith('0x') : undefined; + + const [assetBalance, setAssetBalance] = useState(); + + const fetchAssetOnAssetHub = useCallback(async () => { + if (!api || !assetId) { + return; + } + + const section = isForeignAsset ? 'foreignAssets' : 'assets'; + const _assetId = isForeignAsset ? decodeMultiLocation(assetId as HexString) : assetId; + + try { + const [accountAsset, assetInfo, metadata] = await Promise.all([ + api.query[section]['account'](_assetId, formatted) as unknown as Option, + api.query[section]['asset'](_assetId) as unknown as Option, + api.query[section]['metadata'](_assetId) as unknown as {deposit: u128, name: Bytes, symbol: Bytes, decimals: u8, isFrozen: bool} + + ]); + + const ED = assetInfo.isNone ? BN_ZERO : assetInfo.unwrap().minBalance; + const _AccountAsset = accountAsset.isSome ? accountAsset.unwrap() : null; + const isFrozen = isForeignAsset ? metadata.isFrozen.valueOf() : _AccountAsset?.status?.isFrozen; + const balance = _AccountAsset?.balance || BN_ZERO; + + const assetBalances = { + ED, + assetId, + availableBalance: isFrozen ? BN_ZERO : balance, + chainName, + date: Date.now(), + decimal: metadata.decimals.toNumber(), + freeBalance: !isFrozen ? balance : BN_ZERO, + genesisHash: api.genesisHash.toHex(), + isAsset: true, + lockedBalance: isFrozen ? balance : BN_ZERO, + reservedBalance: isFrozen ? balance : BN_ZERO, // JUST to comply with the rule that total=available + reserve + token: metadata.symbol.toHuman() as string + }; + + setAssetBalance(assetBalances as unknown as BalancesInfo); + } catch (error) { + console.error(`Failed to fetch info for assetId ${assetId}:`, error); + } + }, [api, assetId, chainName, formatted, isForeignAsset]); + + useEffect(() => { + if (assetId === undefined || assetId === NATIVE_TOKEN_ASSET_ID_ON_ASSETHUB || !api?.query?.['assets'] || !isAssetHub) { + return; + } + + fetchAssetOnAssetHub().catch(console.error); + }, [api, assetId, fetchAssetOnAssetHub, isAssetHub]); + + return assetBalance; +} diff --git a/packages/extension-polkagate/src/hooks/useBalancesOnMultiAssetChain.ts b/packages/extension-polkagate/src/hooks/useBalancesOnMultiAssetChain.ts new file mode 100644 index 000000000..d6cefd9d9 --- /dev/null +++ b/packages/extension-polkagate/src/hooks/useBalancesOnMultiAssetChain.ts @@ -0,0 +1,85 @@ +// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { Asset } from '@polkagate/apps-config/assets/types'; +import type { StorageKey } from '@polkadot/types'; +//@ts-ignore +import type { OrmlTokensAccountData } from '@polkadot/types/lookup'; +import type { AnyTuple } from '@polkadot/types/types'; +import type { BalancesInfo } from '../util/types'; + +import { createAssets } from '@polkagate/apps-config/assets'; +import { useCallback, useEffect, useState } from 'react'; + +import { BN } from '@polkadot/util'; + +import { toCamelCase } from '../fullscreen/governance/utils/util'; +import { ASSET_HUBS } from '../util/constants'; +import { useInfo } from '.'; + +const assetsChains = createAssets(); + +export default function useBalancesOnMultiAssetChain (address: string | undefined, assetId?: string | number): BalancesInfo | undefined { + const { api, chain, chainName } = useInfo(address); + + const maybeAssetsOnMultiAssetChains = assetsChains[toCamelCase(chainName || '')]; + const isAssetHub = ASSET_HUBS.includes(chain?.genesisHash || ''); + + const [assetBalance, setAssetBalance] = useState(); + + const fetchAssetOnMultiAssetChain = useCallback(async (assetInfo: Asset) => { + if (!api) { + return; + } + + try { + const assets: [StorageKey, OrmlTokensAccountData][] = await api.query['tokens']['accounts'].entries(address); + const currencyIdScale = ((assetInfo.extras?.['currencyIdScale'] as string | undefined) || '').replace('0x', ''); + + const found = assets.find((entry) => { + if (!entry.length) { + return false; + } + + const storageKey = entry[0].toString(); + + return storageKey.endsWith(currencyIdScale); + }); + + if (!found?.length) { + return; + } + + const currencyId = (found[0].toHuman() as string[])[1]; + const balance = found[1]; + + const assetBalances = { + ED: new BN(assetInfo.extras?.['existentialDeposit'] as string || 0), + assetId: assetInfo.id, + availableBalance: balance.free as BN, + chainName, + currencyId, + decimal: assetInfo.decimal, + freeBalance: balance.free as BN, + genesisHash: api.genesisHash.toHex(), + isAsset: true, + reservedBalance: balance.reserved as BN, + token: assetInfo.symbol + }; + + setAssetBalance(assetBalances as unknown as BalancesInfo); + } catch (e) { + console.error('Something went wrong while fetching an asset:', e); + } + }, [address, api, chainName]); + + useEffect(() => { + if (api && assetId && maybeAssetsOnMultiAssetChains && !isAssetHub && typeof assetId === 'number') { + const assetInfo = maybeAssetsOnMultiAssetChains[assetId]; + + assetInfo && api.query['tokens'] && fetchAssetOnMultiAssetChain(assetInfo).catch(console.error); + } + }, [api, assetId, fetchAssetOnMultiAssetChain, isAssetHub, maybeAssetsOnMultiAssetChains]); + + return assetBalance; +} diff --git a/packages/extension-polkagate/src/hooks/useMyVote.ts b/packages/extension-polkagate/src/hooks/useMyVote.ts index 2e8ac3547..1e41e8ede 100644 --- a/packages/extension-polkagate/src/hooks/useMyVote.ts +++ b/packages/extension-polkagate/src/hooks/useMyVote.ts @@ -24,7 +24,7 @@ export default function useMyVote( const vote = await getAddressVote(String(formatted), api, Number(refIndex), Number(trackId)); setVote(vote); - setRefresh && setRefresh(false); + setRefresh?.(false); } } catch (error) { console.error(error); diff --git a/packages/extension-polkagate/src/hooks/useNativeAssetBalances.ts b/packages/extension-polkagate/src/hooks/useNativeAssetBalances.ts new file mode 100644 index 000000000..22ee8935d --- /dev/null +++ b/packages/extension-polkagate/src/hooks/useNativeAssetBalances.ts @@ -0,0 +1,158 @@ +// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type React from 'react'; +//@ts-ignore +import type { FrameSystemAccountInfo } from '@polkadot/types/lookup'; +import type { HexString } from '@polkadot/util/types'; +import type { BalancesInfo, SavedBalances } from '../util/types'; + +import { useCallback, useContext, useEffect, useState } from 'react'; + +import { BN, BN_ZERO } from '@polkadot/util'; + +import { FetchingContext } from '../components'; +import { ASSET_HUBS, NATIVE_TOKEN_ASSET_ID, NATIVE_TOKEN_ASSET_ID_ON_ASSETHUB } from '../util/constants'; +import { decodeMultiLocation } from '../util/utils'; +import { useInfo, useStakingAccount } from '.'; + +export default function useNativeAssetBalances (address: string | undefined, refresh?: boolean, setRefresh?: React.Dispatch>, onlyNew = false): BalancesInfo | undefined { + const stakingAccount = useStakingAccount(address); + const { account, api, chainName, decimal: currentDecimal, formatted, genesisHash, token: currentToken } = useInfo(address); + const isFetching = useContext(FetchingContext); + + const [balances, setBalances] = useState(); + const [newBalances, setNewBalances] = useState(); + + const isFetchingNativeTokenOfAssetHub = genesisHash && ASSET_HUBS.includes(genesisHash); + + const token = api?.registry.chainTokens[0]; + const decimal = api?.registry.chainDecimals[0]; + + const getBalances = useCallback(() => { + if (!chainName || !genesisHash || api?.genesisHash?.toString() !== genesisHash || !decimal || !token) { + return; + } + + const ED = api.consts['balances'] ? api.consts['balances']['existentialDeposit'] as unknown as BN : BN_ZERO; + + formatted && api.derive.balances?.all(formatted).then((allBalances) => { + //@ts-ignore + api.query['system']['account'](formatted).then(({ data: systemBalance }: FrameSystemAccountInfo) => { + // some chains such as PARALLEL does not support this call hence BN_ZERO is set for them + const frozenBalance = systemBalance?.frozen || BN_ZERO; + + setNewBalances({ + ED, + assetId: isFetchingNativeTokenOfAssetHub ? NATIVE_TOKEN_ASSET_ID_ON_ASSETHUB : NATIVE_TOKEN_ASSET_ID, + ...allBalances, + chainName, + date: Date.now(), + decimal, + frozenBalance, + genesisHash: api.genesisHash.toString(), + token + }); + setRefresh?.(false); + isFetching.fetching[String(formatted)]['balances'] = false; + isFetching.set(isFetching.fetching); + }).catch(console.error); + }).catch(console.error); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [api, genesisHash, chainName, formatted, isFetching.fetching[String(formatted)]?.['length'], setRefresh]); + + useEffect(() => { + if (!formatted || !token || !decimal || !chainName || api?.genesisHash?.toString() !== genesisHash) { + return; + } + + /** to fetch a formatted address's balance if not already fetching */ + if (!isFetching.fetching[String(formatted)]?.['balances']) { + if (!isFetching.fetching[String(formatted)]) { + isFetching.fetching[String(formatted)] = {}; + } + + isFetching.fetching[String(formatted)]['balances'] = true; + isFetching.set(isFetching.fetching); + getBalances(); + } else { + console.info(`Balance is fetching for ${formatted}, hence doesn't need to fetch it again!`); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [api, genesisHash, chainName, decimal, formatted, getBalances, isFetching.fetching[String(formatted)]?.['length'], token]); + + useEffect(() => { + if (refresh) { + setBalances(undefined); + setNewBalances(undefined); + + if (isFetching.fetching[String(formatted)]) { + isFetching.fetching[String(formatted)]['balances'] = true; + } + + isFetching.set(isFetching.fetching); + getBalances(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [Object.keys(isFetching?.fetching ?? {})?.length, formatted, getBalances, refresh]); + + useEffect(() => { + if (!chainName || !account || account?.genesisHash !== genesisHash) { + return; + } + + // to LOAD saved balances + const savedBalances = JSON.parse(account?.balances ?? '{}') as SavedBalances; + + if (savedBalances[chainName]) { + const sb = savedBalances[chainName].balances; + + const maybeAssetId = sb['assetId']; + const isForeignAsset = maybeAssetId && String(maybeAssetId).startsWith('0x'); + const assetId = maybeAssetId === undefined + ? undefined + : isForeignAsset + ? decodeMultiLocation(maybeAssetId as HexString) + : parseInt(maybeAssetId); + + const lastBalances = { + ED: new BN(sb['ED'] || '0'), + assetId, + availableBalance: new BN(sb['availableBalance']), + chainName, + date: savedBalances[chainName].date, + decimal: savedBalances[chainName].decimal, + freeBalance: new BN(sb['freeBalance']), + frozenBalance: new BN(sb['frozenBalance'] || '0'), + genesisHash: sb['genesisHash'], + lockedBalance: new BN(sb['lockedBalance']), + pooledBalance: new BN(sb['pooledBalance']), + reservedBalance: new BN(sb['reservedBalance']), + token: savedBalances[chainName].token, + vestedBalance: new BN(sb['vestedBalance']), + vestedClaimable: new BN(sb['vestedClaimable']), + votingBalance: new BN(sb['votingBalance']) + } as BalancesInfo; + + setBalances({ + ...lastBalances, + soloTotal: stakingAccount?.stakingLedger?.total as unknown as BN + }); + + return; + } + + setBalances(undefined); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [Object.keys(account ?? {})?.length, address, chainName, stakingAccount]); + + if (onlyNew) { + return newBalances; // returns balances that have been fetched recently and are not from the local storage, and it does not include the pooledBalance + } + + const _balances = newBalances || balances; + + return _balances && _balances.token === currentToken && _balances.decimal === currentDecimal + ? _balances + : undefined; +} diff --git a/packages/extension-polkagate/src/hooks/useNativeTokenPrice.ts b/packages/extension-polkagate/src/hooks/useNativeTokenPrice.ts index a9070b893..343cf4801 100644 --- a/packages/extension-polkagate/src/hooks/useNativeTokenPrice.ts +++ b/packages/extension-polkagate/src/hooks/useNativeTokenPrice.ts @@ -1,6 +1,5 @@ // Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors // SPDX-License-Identifier: Apache-2.0 -// @ts-nocheck /* eslint-disable @typescript-eslint/no-non-null-assertion */ @@ -9,15 +8,24 @@ import { useMemo } from 'react'; import { sanitizeChainName } from '../util/utils'; import { useInfo, usePrices } from '.'; -export default function useNativeTokenPrice(address: string): number | undefined | null { +export default function useNativeTokenPrice (address: string): number | undefined | null { const pricesInCurrency = usePrices(); const { chainName } = useInfo(address); return useMemo((): number | undefined => { + if (!chainName) { + return undefined; + } + const currentChainName = sanitizeChainName(chainName)?.toLocaleLowerCase(); - const currentAssetPrices = pricesInCurrency?.prices?.[currentChainName as string]; - const mayBeTestNetPrice = pricesInCurrency?.prices && !currentAssetPrices ? 0 : undefined; - return currentAssetPrices?.value || mayBeTestNetPrice; + if (!currentChainName) { + return undefined; + } + + const currentAssetPrices = pricesInCurrency?.prices?.[currentChainName]; + const maybeTestNetPrice = pricesInCurrency?.prices && !currentAssetPrices ? 0 : undefined; + + return currentAssetPrices?.value || maybeTestNetPrice; }, [chainName, pricesInCurrency?.prices]); } diff --git a/packages/extension-polkagate/src/hooks/usePoolBalances.ts b/packages/extension-polkagate/src/hooks/usePoolBalances.ts new file mode 100644 index 000000000..18cfffdce --- /dev/null +++ b/packages/extension-polkagate/src/hooks/usePoolBalances.ts @@ -0,0 +1,110 @@ +// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type React from 'react'; +import type { Option } from '@polkadot/types'; +import type { Balance } from '@polkadot/types/interfaces'; +//@ts-ignore +import type { PalletNominationPoolsBondedPoolInner, PalletNominationPoolsPoolMember } from '@polkadot/types/lookup'; + +import { useCallback, useContext, useEffect, useState } from 'react'; + +import { BN, BN_ONE, BN_ZERO } from '@polkadot/util'; + +import { FetchingContext } from '../components'; +import getPoolAccounts from '../util/getPoolAccounts'; +import { useInfo } from '.'; + +export default function usePoolBalances (address: string | undefined, refresh?: boolean, setRefresh?: React.Dispatch>): { balance: BN, genesisHash: string } | null | undefined { + const { api, chain, chainName, formatted } = useInfo(address); + const isFetching = useContext(FetchingContext); + + const [pooledBalance, setPooledBalance] = useState<{ balance: BN, genesisHash: string } | null>(); + + const getPoolBalances = useCallback(() => { + if (api && !api.query['nominationPools']) { + return setPooledBalance({ balance: BN_ZERO, genesisHash: api.genesisHash.toString() }); + } + + api && formatted && api.query['nominationPools']['poolMembers'](formatted).then(async (res: any) => { + const member = res?.unwrapOr(undefined) as PalletNominationPoolsPoolMember | undefined; + + const genesisHash = api.genesisHash.toString(); + + if (!member) { + isFetching.fetching[String(formatted)]['pooledBalance'] = false; + isFetching.set(isFetching.fetching); + + return setPooledBalance({ balance: BN_ZERO, genesisHash }); // user does not joined a pool yet. or pool id does not exist + } + + const poolId = member.poolId; + const accounts = poolId && getPoolAccounts(api, poolId); + + if (!accounts) { + console.warn(`useBalances: can not find a pool with id: ${poolId}`); + + isFetching.fetching[String(formatted)]['pooledBalance'] = false; + isFetching.set(isFetching.fetching); + + return setPooledBalance({ balance: BN_ZERO, genesisHash }); + } + + const [bondedPool, stashIdAccount, myClaimable] = await Promise.all([ + api.query['nominationPools']['bondedPools'](poolId) as unknown as Option, + api.derive.staking.account(accounts.stashId), + api.call['nominationPoolsApi']['pendingRewards'](formatted) + ]); + + const active = member.points.isZero() + ? BN_ZERO + : (new BN(String(member.points)).mul(new BN(String(stashIdAccount.stakingLedger.active)))).div(new BN(String(bondedPool.unwrap()?.points ?? BN_ONE))); + const rewards = myClaimable as Balance; + let unlockingValue = BN_ZERO; + + member?.unbondingEras?.forEach((value: BN) => { + unlockingValue = unlockingValue.add(value); + }); + + genesisHash === chain?.genesisHash && setPooledBalance({ balance: active.add(rewards).add(unlockingValue), genesisHash }); + setRefresh?.(false); + isFetching.fetching[String(formatted)]['pooledBalance'] = false; + isFetching.set(isFetching.fetching); + }).catch(console.error); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [api, formatted, isFetching.fetching[String(formatted)]?.['length'], setRefresh, chain?.genesisHash]); + + useEffect(() => { + if (!chain?.genesisHash || !api || !formatted || api.genesisHash.toString() !== chain.genesisHash) { + return; + } + + if (!isFetching.fetching[String(formatted)]?.['pooledBalance']) { + if (!isFetching.fetching[String(formatted)]) { + isFetching.fetching[String(formatted)] = {}; + } + + isFetching.fetching[String(formatted)]['pooledBalance'] = true; + isFetching.set(isFetching.fetching); + getPoolBalances(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [api, chain?.genesisHash, formatted, getPoolBalances, isFetching.fetching[String(formatted)]?.['length']]); + + useEffect(() => { + if (refresh) { + setPooledBalance(undefined); + + if (isFetching.fetching[String(formatted)]) { + isFetching.fetching[String(formatted)]['pooledBalance'] = false; + isFetching.fetching[String(formatted)]['balances'] = true; + } + + isFetching.set(isFetching.fetching); + getPoolBalances(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [Object.keys(isFetching?.fetching ?? {})?.length, chainName, formatted, getPoolBalances, refresh]); + + return pooledBalance; +} diff --git a/packages/extension-polkagate/src/hooks/usePrices.ts b/packages/extension-polkagate/src/hooks/usePrices.ts index 67e50924c..10cb7bb43 100644 --- a/packages/extension-polkagate/src/hooks/usePrices.ts +++ b/packages/extension-polkagate/src/hooks/usePrices.ts @@ -36,18 +36,18 @@ export default function usePrices (): Prices | undefined | null { useEffect(() => { if (updatedPrice && currency) { - const mayBeSavedPrices = (updatedPrice)?.[currency.code]; + const maybeSavedPrices = (updatedPrice)?.[currency.code]; - mayBeSavedPrices && setSavedPrice(mayBeSavedPrices); + maybeSavedPrices && setSavedPrice(maybeSavedPrices); } }, [currency, updatedPrice]); useEffect(() => { /** Response with the saved and not outdated data if exist */ currency?.code && getStorage('pricesInCurrencies').then((pricesInCurrencies) => { - const mayBeSavedPrices = (pricesInCurrencies as PricesInCurrencies)?.[currency.code]; + const maybeSavedPrices = (pricesInCurrencies as PricesInCurrencies)?.[currency.code]; - mayBeSavedPrices && setSavedPrice(mayBeSavedPrices); + maybeSavedPrices && setSavedPrice(maybeSavedPrices); }).catch(console.error); }, [currency]); diff --git a/packages/extension-polkagate/src/hooks/useReferendum.ts b/packages/extension-polkagate/src/hooks/useReferendum.ts index 44003d504..a5557dc2b 100644 --- a/packages/extension-polkagate/src/hooks/useReferendum.ts +++ b/packages/extension-polkagate/src/hooks/useReferendum.ts @@ -240,20 +240,18 @@ export default function useReferendum (address: AccountId | string | undefined, api && id !== undefined && api.query?.['referenda']?.['referendumInfoFor'] && api.query['referenda']['referendumInfoFor'](id) .then((result) => { const res = result as Option; - const mayBeUnwrappedResult = (res.isSome && res.unwrap()) as PalletReferendaReferendumInfoConvictionVotingTally | undefined; - const mayBeOngoingRef = mayBeUnwrappedResult?.isOngoing ? mayBeUnwrappedResult?.asOngoing : undefined; - const mayBeTally = mayBeOngoingRef ? mayBeOngoingRef.tally : undefined; + const maybeUnwrappedResult = (res.isSome && res.unwrap()) as PalletReferendaReferendumInfoConvictionVotingTally | undefined; + const maybeOngoingRef = maybeUnwrappedResult?.isOngoing ? maybeUnwrappedResult?.asOngoing : undefined; + const maybeTally = maybeOngoingRef ? maybeOngoingRef.tally : undefined; - // console.log('referendum Info for:', JSON.parse(JSON.stringify(mayBeUnwrappedResult))) - - setOnchainRefInfo(mayBeUnwrappedResult); - setOnChainTally(mayBeTally); + setOnchainRefInfo(maybeUnwrappedResult); + setOnChainTally(maybeTally); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const mayBeOrigin = mayBeUnwrappedResult?.isOngoing ? JSON.parse(JSON.stringify(mayBeUnwrappedResult?.asOngoing?.origin)) : undefined; + const maybeOrigin = maybeUnwrappedResult?.isOngoing ? JSON.parse(JSON.stringify(maybeUnwrappedResult?.asOngoing?.origin)) : undefined; // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access - setMaybeOriginDC(mayBeOrigin?.origins); + setMaybeOriginDC(maybeOrigin?.origins); }).catch(console.error); }, [api, id, refresh]); diff --git a/packages/extension-polkagate/src/hooks/useReservedDetails.ts b/packages/extension-polkagate/src/hooks/useReservedDetails.ts index 5709fe368..ef6b97b38 100644 --- a/packages/extension-polkagate/src/hooks/useReservedDetails.ts +++ b/packages/extension-polkagate/src/hooks/useReservedDetails.ts @@ -69,11 +69,11 @@ export default function useReservedDetails (address: string | undefined): Reserv /** fetch proxy */ if (api.query?.['proxy'] && PROXY_CHAINS.includes(genesisHash)) { api.query['proxy']['proxies'](formatted).then((p) => { - const mayBeDeposit = p?.[1] as BN; + const maybeDeposit = p?.[1] as BN; - if (!mayBeDeposit?.isZero()) { + if (!maybeDeposit?.isZero()) { setReserved((prev) => { - prev.proxy = toBalance(mayBeDeposit); + prev.proxy = toBalance(maybeDeposit); return prev; }); diff --git a/packages/extension-polkagate/src/hooks/useStakingAccount.ts b/packages/extension-polkagate/src/hooks/useStakingAccount.ts index 8ae44ce76..5d24b0910 100644 --- a/packages/extension-polkagate/src/hooks/useStakingAccount.ts +++ b/packages/extension-polkagate/src/hooks/useStakingAccount.ts @@ -70,7 +70,7 @@ export default function useStakingAccount(address: AccountId | string | undefine temp.rewardDestination = JSON.parse(JSON.stringify(temp.rewardDestination)); fetchedToken === addressCurrentToken && setStakingInfo({ ...temp, date: Date.now(), decimal: fetchedDecimal, era: Number(era), genesisHash: api.genesisHash.toString() }); - refresh && setRefresh && setRefresh(false); + refresh && setRefresh?.(false); }, [addressCurrentToken, api, refresh, setRefresh, stashId]); useEffect(() => { diff --git a/packages/extension-polkagate/src/hooks/useTokenPrice.ts b/packages/extension-polkagate/src/hooks/useTokenPrice.ts index 2a59b0836..5a7c4444a 100644 --- a/packages/extension-polkagate/src/hooks/useTokenPrice.ts +++ b/packages/extension-polkagate/src/hooks/useTokenPrice.ts @@ -8,7 +8,7 @@ import { useMemo } from 'react'; import { useUserAddedPriceId } from '../fullscreen/addNewChain/utils'; import { toCamelCase } from '../fullscreen/governance/utils/util'; -import { ASSET_HUBS, NATIVE_TOKEN_ASSET_ID } from '../util/constants'; +import { ASSET_HUBS, NATIVE_TOKEN_ASSET_ID, NATIVE_TOKEN_ASSET_ID_ON_ASSETHUB } from '../util/constants'; import { getPriceIdByChainName } from '../util/utils'; import { useInfo, usePrices } from '.'; @@ -25,19 +25,21 @@ const assetsChains = createAssets(); * @param address : accounts substrate address * @returns price : price of the token which the address is already switched to */ -export default function useTokenPrice (address: string | undefined, assetId?: number): Price | typeof DEFAULT_PRICE { +export default function useTokenPrice (address: string | undefined, assetId?: number | string): Price | typeof DEFAULT_PRICE { const { chainName, genesisHash } = useInfo(address); const userAddedPriceId = useUserAddedPriceId(genesisHash); const pricesInCurrencies = usePrices(); - const mayBeAssetsOnMultiAssetChains = assetsChains[toCamelCase(chainName || '')]; + const maybeAssetsOnMultiAssetChains = assetsChains[toCamelCase(chainName || '')]; const isAssetHub = ASSET_HUBS.includes(genesisHash || ''); - const _assetId = assetId !== undefined - ? assetId - : isAssetHub - ? NATIVE_TOKEN_ASSET_ID - : undefined; + const _assetId = useMemo(() => + assetId !== undefined + ? assetId + : isAssetHub + ? NATIVE_TOKEN_ASSET_ID_ON_ASSETHUB + : undefined + , [assetId, isAssetHub]); return useMemo(() => { if (!chainName || !pricesInCurrencies) { @@ -45,16 +47,16 @@ export default function useTokenPrice (address: string | undefined, assetId?: nu } // FixMe, on second fetch of asset id its type will get string which is weird!! - const priceId = _assetId !== undefined && _assetId > NATIVE_TOKEN_ASSET_ID - ? mayBeAssetsOnMultiAssetChains?.find(({ id }) => id === Number(_assetId))?.priceId + const priceId = _assetId !== undefined && ((typeof _assetId === 'number' && _assetId > NATIVE_TOKEN_ASSET_ID) || isAssetHub) + ? maybeAssetsOnMultiAssetChains?.find(({ id }) => id === Number(_assetId) || id === _assetId)?.priceId : userAddedPriceId || getPriceIdByChainName(chainName); - const mayBePriceValue = priceId ? pricesInCurrencies.prices?.[priceId]?.value || 0 : 0; + const maybePriceValue = priceId ? pricesInCurrencies.prices?.[priceId]?.value || 0 : 0; return { - price: mayBePriceValue, + price: maybePriceValue, priceChainName: chainName?.toLocaleLowerCase(), priceDate: pricesInCurrencies.date }; - }, [_assetId, chainName, mayBeAssetsOnMultiAssetChains, pricesInCurrencies, userAddedPriceId]); + }, [_assetId, chainName, isAssetHub, maybeAssetsOnMultiAssetChains, pricesInCurrencies, userAddedPriceId]); } diff --git a/packages/extension-polkagate/src/hooks/useTokens.ts b/packages/extension-polkagate/src/hooks/useTokens.ts index db988ea13..685a2d3d7 100644 --- a/packages/extension-polkagate/src/hooks/useTokens.ts +++ b/packages/extension-polkagate/src/hooks/useTokens.ts @@ -18,7 +18,7 @@ export default function useTokens (address: AccountId | string | undefined): Dro return network?.symbols?.length ? network.symbols.map((symbol, index) => { - return { text: symbol, value: index - (network.symbols.length) }; + return { text: symbol, value: index - (network.symbols.length) }; // The native tokens asset ids to show in drop down are negative, such as -1, -2, etc. }) : undefined; } diff --git a/packages/extension-polkagate/src/partials/QuickAction.tsx b/packages/extension-polkagate/src/partials/QuickAction.tsx index dc4747175..8f4d1b6f9 100644 --- a/packages/extension-polkagate/src/partials/QuickAction.tsx +++ b/packages/extension-polkagate/src/partials/QuickAction.tsx @@ -1,9 +1,10 @@ // Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors // SPDX-License-Identifier: Apache-2.0 -// @ts-nocheck /* eslint-disable react/jsx-max-props-per-line */ +import type { AccountId } from '@polkadot/types/interfaces/runtime'; + import { faHistory, faPaperPlane, faVoteYea } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ArrowForwardIos as ArrowForwardIosIcon, Boy as BoyIcon } from '@mui/icons-material'; @@ -11,12 +12,10 @@ import { Grid, IconButton, Slide, useTheme } from '@mui/material'; import React, { useCallback } from 'react'; import { useHistory } from 'react-router-dom'; -import type { AccountId } from '@polkadot/types/interfaces/runtime'; - import { HorizontalMenuItem, PoolStakingIcon, VaadinIcon } from '../components'; import { useInfo, useTranslation } from '../hooks'; import { windowOpen } from '../messaging'; -import { CROWDLOANS_CHAINS, GOVERNANCE_CHAINS, STAKING_CHAINS } from '../util/constants'; +import { CROWDLOANS_CHAINS, GOVERNANCE_CHAINS, NATIVE_TOKEN_ASSET_ID, STAKING_CHAINS } from '../util/constants'; interface Props { address: AccountId | string; @@ -26,45 +25,49 @@ interface Props { const ICON_SIZE = 10; -export default function QuickAction({ address, quickActionOpen, setQuickActionOpen }: Props): React.ReactElement { +export default function QuickAction ({ address, quickActionOpen, setQuickActionOpen }: Props): React.ReactElement { const { t } = useTranslation(); const theme = useTheme(); const history = useHistory(); - const { account, api, formatted } = useInfo(address); + const { api, formatted, genesisHash } = useInfo(address); const handleOpen = useCallback(() => setQuickActionOpen(String(address)), [address, setQuickActionOpen]); const handleClose = useCallback(() => quickActionOpen === address && setQuickActionOpen(undefined), [address, quickActionOpen, setQuickActionOpen]); + const isStakingEnabled = genesisHash && STAKING_CHAINS.includes(genesisHash); + const isCrowdLoanEnabled = genesisHash && CROWDLOANS_CHAINS.includes(genesisHash); + const isGovernanceEnabled = genesisHash && GOVERNANCE_CHAINS.includes(genesisHash); + const goToSend = useCallback(() => { - address && account?.genesisHash && windowOpen(`/send/${String(address)}/undefined`).catch(console.error); - }, [account?.genesisHash, address]); + address && genesisHash && windowOpen(`/send/${String(address)}/${NATIVE_TOKEN_ASSET_ID}`).catch(console.error); + }, [genesisHash, address]); const goToPoolStaking = useCallback(() => { - address && STAKING_CHAINS.includes(account?.genesisHash) && history.push({ + address && isStakingEnabled && history.push({ pathname: `/pool/${String(address)}/`, state: { api } }); - }, [account?.genesisHash, address, api, history]); + }, [address, isStakingEnabled, history, api]); const goToSoloStaking = useCallback(() => { - address && STAKING_CHAINS.includes(account?.genesisHash) && history.push({ + address && isStakingEnabled && history.push({ pathname: `/solo/${String(address)}/`, state: { api } }); - }, [account?.genesisHash, address, api, history]); + }, [address, isStakingEnabled, history, api]); const goToCrowdLoans = useCallback(() => { - formatted && CROWDLOANS_CHAINS.includes(account?.genesisHash) && + formatted && isCrowdLoanEnabled && history.push({ pathname: `/crowdloans/${address}` }); - }, [account?.genesisHash, address, formatted, history]); + }, [formatted, isCrowdLoanEnabled, history, address]); const goToGovernanceOrHistory = useCallback(() => { - GOVERNANCE_CHAINS.includes(account?.genesisHash) - ? windowOpen(`/governance/${address}/referenda`).catch(console.error) - : account?.genesisHash && history.push({ pathname: `/history/${String(address)}` }); - }, [account?.genesisHash, address, history]); + isGovernanceEnabled + ? address && windowOpen(`/governance/${address}/referenda`).catch(console.error) + : genesisHash && history.push({ pathname: `/history/${String(address)}` }); + }, [isGovernanceEnabled, address, genesisHash, history]); const isSlideOpen = quickActionOpen === address; @@ -94,7 +97,7 @@ export default function QuickAction({ address, quickActionOpen, setQuickActionOp icon={ } onClick={goToSend} - textDisabled={!account?.genesisHash} - title={t('Send')} + textDisabled={!genesisHash} + title={t('Send')} titleFontSize={10} /> ('Pool Staking')} + textDisabled={!isStakingEnabled} + title={t('Pool Staking')} titleFontSize={10} titleLineHeight={1} /> @@ -136,7 +139,7 @@ export default function QuickAction({ address, quickActionOpen, setQuickActionOp icon={ ('Solo Staking')} + textDisabled={!isStakingEnabled} + title={t('Solo Staking')} titleFontSize={10} titleLineHeight={1} /> @@ -154,11 +157,11 @@ export default function QuickAction({ address, quickActionOpen, setQuickActionOp divider dividerHeight={20} icon={ - + } onClick={goToCrowdLoans} - textDisabled={!CROWDLOANS_CHAINS.includes(account?.genesisHash)} - title={t('Crowdloans')} + textDisabled={!isCrowdLoanEnabled} + title={t('Crowdloans')} titleFontSize={10} /> } onClick={goToGovernanceOrHistory} - textDisabled={!account?.genesisHash} - title={t(GOVERNANCE_CHAINS.includes(account?.genesisHash) ? 'Governance' : 'History')} + textDisabled={!genesisHash} + title={t(isGovernanceEnabled ? 'Governance' : 'History')} titleFontSize={10} /> diff --git a/packages/extension-polkagate/src/partials/QuickActionFullScreen.tsx b/packages/extension-polkagate/src/partials/QuickActionFullScreen.tsx index c8dd600c0..d70cbcb0f 100644 --- a/packages/extension-polkagate/src/partials/QuickActionFullScreen.tsx +++ b/packages/extension-polkagate/src/partials/QuickActionFullScreen.tsx @@ -1,17 +1,16 @@ // Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors // SPDX-License-Identifier: Apache-2.0 -// @ts-nocheck /* eslint-disable react/jsx-max-props-per-line */ +import type { AccountId } from '@polkadot/types/interfaces/runtime'; + import { faHistory, faPaperPlane, faVoteYea } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ArrowForwardIos as ArrowForwardIosIcon, Boy as BoyIcon } from '@mui/icons-material'; import { Box, ClickAwayListener, Divider, Grid, IconButton, Slide, Typography, useTheme } from '@mui/material'; import React, { useCallback, useMemo, useState } from 'react'; -import type { AccountId } from '@polkadot/types/interfaces/runtime'; - import { PoolStakingIcon } from '../components'; import { openOrFocusTab } from '../fullscreen/accountDetails/components/CommonTasks'; import { useAccount, useTranslation } from '../hooks'; @@ -23,10 +22,10 @@ interface Props { quickActionOpen?: string | boolean; setQuickActionOpen: React.Dispatch>; containerRef: React.RefObject; - assetId?: number | undefined + assetId?: number | string | undefined } -type QuickActionButtonType = { +interface QuickActionButtonType { disabled?: boolean; divider?: boolean; icon: React.ReactNode; @@ -37,12 +36,12 @@ type QuickActionButtonType = { const ARROW_ICON_SIZE = 17; const ACTION_ICON_SIZE = '27px'; -export default function QuickActionFullScreen({ address, assetId, containerRef, quickActionOpen, setQuickActionOpen }: Props): React.ReactElement { +export default function QuickActionFullScreen ({ address, assetId, containerRef, quickActionOpen, setQuickActionOpen }: Props): React.ReactElement { const { t } = useTranslation(); const theme = useTheme(); const account = useAccount(address); - const [showHistory, setShowHistory] = useState(); + const [showHistory, setShowHistory] = useState(); const supportGov = useMemo(() => GOVERNANCE_CHAINS.includes(account?.genesisHash ?? ''), [account?.genesisHash]); @@ -50,7 +49,7 @@ export default function QuickActionFullScreen({ address, assetId, containerRef, const handleClose = useCallback(() => quickActionOpen === address && setQuickActionOpen(undefined), [address, quickActionOpen, setQuickActionOpen]); const goToSend = useCallback(() => { - address && account?.genesisHash && openOrFocusTab(`/send/${String(address)}/${assetId || ''}`); + address && account?.genesisHash && openOrFocusTab(`/send/${String(address)}/${assetId}`); }, [account?.genesisHash, address, assetId]); const goToPoolStaking = useCallback(() => { @@ -66,7 +65,7 @@ export default function QuickActionFullScreen({ address, assetId, containerRef, }, [address, supportGov]); const goToHistory = useCallback(() => { - setShowHistory(true); + setShowHistory(1); }, []); const nullF = useCallback(() => null, []); diff --git a/packages/extension-polkagate/src/popup/account/ReservedReasons.tsx b/packages/extension-polkagate/src/popup/account/ReservedReasons.tsx index 4bdbd5c96..4c15e5554 100644 --- a/packages/extension-polkagate/src/popup/account/ReservedReasons.tsx +++ b/packages/extension-polkagate/src/popup/account/ReservedReasons.tsx @@ -23,7 +23,7 @@ import { HeaderBrand } from '../../partials'; interface Props { identity: DeriveAccountRegistration | null | undefined; show: boolean; - assetId?: number; + assetId?: number | string; address: AccountId | string; setShow: React.Dispatch>; } diff --git a/packages/extension-polkagate/src/popup/account/index.tsx b/packages/extension-polkagate/src/popup/account/index.tsx index adf29564b..5124f2cfa 100644 --- a/packages/extension-polkagate/src/popup/account/index.tsx +++ b/packages/extension-polkagate/src/popup/account/index.tsx @@ -46,7 +46,6 @@ export default function AccountDetails (): React.ReactElement { const identity = useMyAccountIdentity(address); const genesisOptions = useContext(GenesisHashOptionsContext); - const [refresh, setRefresh] = useState(false); const [assetId, setAssetId] = useState(); const balances = useBalances(address, refresh, setRefresh, false, assetId); // if assetId is undefined and chain is assethub it will fetch native token's balance @@ -86,8 +85,8 @@ export default function AccountDetails (): React.ReactElement { }, [chain, goToAccount]); const goToSend = useCallback(() => { - address && windowOpen(`/send/${address}/${assetId || ''}`).catch(console.error); - }, [address, assetId]); + address && windowOpen(`/send/${address}/${assetId || balances?.assetId}`).catch(console.error); + }, [address, assetId, balances?.assetId]); const goToStaking = useCallback(() => { supportStaking && setShowStakingOptions(!showStakingOptions); @@ -149,10 +148,6 @@ export default function AccountDetails (): React.ReactElement { }, [address]); const _onChangeAsset = useCallback((id: number) => { - if (id === -1) { // this is the id of native token - return setAssetId(undefined); - } - setAssetId(id); }, []); diff --git a/packages/extension-polkagate/src/popup/history/Explorer.tsx b/packages/extension-polkagate/src/popup/history/Explorer.tsx index 33292da25..c2bff0775 100644 --- a/packages/extension-polkagate/src/popup/history/Explorer.tsx +++ b/packages/extension-polkagate/src/popup/history/Explorer.tsx @@ -17,7 +17,7 @@ interface Props { function getLink (chainName: string, explorer: 'subscan' | 'polkaholic' | 'statscan', type: 'account' | 'extrinsic', data: string): string { if (type === 'extrinsic') { - const mayBeTheFirstPartOfChain = chainName?.split(' ')?.[0]; + const maybeTheFirstPartOfChain = chainName?.split(' ')?.[0]; const chainNameWithoutSpace = chainName?.replace(/\s/g, ''); switch (explorer) { @@ -26,10 +26,10 @@ function getLink (chainName: string, explorer: 'subscan' | 'polkaholic' | 'stats return `https://assethub-${chainNameWithoutSpace.replace(/AssetHub/, '')}.subscan.io/extrinsic/${String(data)}`; } - return 'https://' + mayBeTheFirstPartOfChain + '.subscan.io/extrinsic/' + String(data); // data here is txHash + return 'https://' + maybeTheFirstPartOfChain + '.subscan.io/extrinsic/' + String(data); // data here is txHash case 'polkaholic': - return 'https://' + mayBeTheFirstPartOfChain + '.polkaholic.io/tx/' + String(data); + return 'https://' + maybeTheFirstPartOfChain + '.polkaholic.io/tx/' + String(data); case 'statscan': return 'https://westmint.statescan.io/#/accounts/' + String(data); // NOTE, data here is formatted address diff --git a/packages/extension-polkagate/src/popup/home/news.ts b/packages/extension-polkagate/src/popup/home/news.ts index 698637846..f992b41dc 100644 --- a/packages/extension-polkagate/src/popup/home/news.ts +++ b/packages/extension-polkagate/src/popup/home/news.ts @@ -13,8 +13,9 @@ export const news: News[] = [ version: '0.14.0', notes: [ 'Add Your Chain: If your favorite chain isn’t available in the extension, you can now add it manually using an RPC endpoint address.', - 'BTC and ETH as currency: View your token balance equivalent in BTC, ETH, and other fiat currencies.', + 'BTC, ETH and DOT as currency: View your token balance equivalent in BTC, ETH, DOT and other fiat currencies.', 'Auto Mode Remote Node: RPCs are now automatically selected based on your location and latency, for optimal performance.', + 'Show Foreign Assets: Foreign assets from the asset hub, like the recent MYTH airdrop, are now displayed in the list.', 'Show Selected Chains Badge: The number of favorite selected chains is displayed in a badge on the header in full-screen home mode.', 'Auto Metadata Update: Metadata updates automatically in the background on supported chains, eliminating the need for manual updates.' ] diff --git a/packages/extension-polkagate/src/popup/staking/partial/ShowPool.tsx b/packages/extension-polkagate/src/popup/staking/partial/ShowPool.tsx index 12f8a75a4..9bad9af7f 100644 --- a/packages/extension-polkagate/src/popup/staking/partial/ShowPool.tsx +++ b/packages/extension-polkagate/src/popup/staking/partial/ShowPool.tsx @@ -50,8 +50,8 @@ export default function ShowPool ({ api, chain, label, labelPosition = 'left', m const hasCommission = pool && 'commission' in (pool.bondedPool as any); const parsedPool = JSON.parse(JSON.stringify(pool)) as MyPoolInfo; //@ts-ignore - const mayBeCommission = hasCommission && parsedPool.bondedPool?.commission?.current ? parsedPool.bondedPool.commission.current[0] as number : 0; - const commission = Number(mayBeCommission) / (10 ** 7) < 1 ? 0 : Number(mayBeCommission) / (10 ** 7); + const maybeCommission = hasCommission && parsedPool.bondedPool?.commission?.current ? parsedPool.bondedPool.commission.current[0] as number : 0; + const commission = Number(maybeCommission) / (10 ** 7) < 1 ? 0 : Number(maybeCommission) / (10 ** 7); // hide show more info for a pool while creating a pool const _showInfo = mode === 'Creating' ? false : showInfo; diff --git a/packages/extension-polkagate/src/popup/staking/partial/ShowRoles.tsx b/packages/extension-polkagate/src/popup/staking/partial/ShowRoles.tsx index 088c43a4b..e2ac070b1 100644 --- a/packages/extension-polkagate/src/popup/staking/partial/ShowRoles.tsx +++ b/packages/extension-polkagate/src/popup/staking/partial/ShowRoles.tsx @@ -45,12 +45,12 @@ export default function ShowRoles({ api, chain, label, mode, pool, style }: Prop ]); } - const mayBeCommissionAddress = pool.bondedPool.commission.current?.[1]; + const maybeCommissionAddress = pool.bondedPool.commission.current?.[1]; return ([ { address: pool.accounts?.stashId?.toString(), label: t('Stash id') }, { address: pool.accounts?.rewardId?.toString() ?? '', label: t('Reward id') }, - ...(mayBeCommissionAddress ? [{ address: mayBeCommissionAddress?.toString() ?? 'N/A', label: t('Com. id') }] : []) + ...(maybeCommissionAddress ? [{ address: maybeCommissionAddress?.toString() ?? 'N/A', label: t('Com. id') }] : []) ]); }, [mode, pool, t]); diff --git a/packages/extension-polkagate/src/popup/staking/pool/myPool/editPool/index.tsx b/packages/extension-polkagate/src/popup/staking/pool/myPool/editPool/index.tsx index d726b557b..4584a6f39 100644 --- a/packages/extension-polkagate/src/popup/staking/pool/myPool/editPool/index.tsx +++ b/packages/extension-polkagate/src/popup/staking/pool/myPool/editPool/index.tsx @@ -51,8 +51,8 @@ export default function EditPool({ address, pool, setRefresh, setShowEdit, showE const depositorAddress = pool?.bondedPool?.roles?.depositor?.toString(); const maybeCommissionPayee = (pool?.bondedPool?.commission?.current as any)?.[1]?.toString() as string | undefined; - const mayBeCommission = ((pool?.bondedPool?.commission?.current as any)?.[0] || 0) as number; - const commissionValue = Number(mayBeCommission) / (10 ** 7) < 1 ? 0 : Number(mayBeCommission) / (10 ** 7); + const maybeCommission = ((pool?.bondedPool?.commission?.current as any)?.[0] || 0) as number; + const commissionValue = Number(maybeCommission) / (10 ** 7) < 1 ? 0 : Number(maybeCommission) / (10 ** 7); const [showReview, setShowReview] = useState(false); const [changes, setChanges] = useState(); diff --git a/packages/extension-polkagate/src/popup/staking/pool/stake/joinPool/index.tsx b/packages/extension-polkagate/src/popup/staking/pool/stake/joinPool/index.tsx index 7cead80ba..e7dd49711 100644 --- a/packages/extension-polkagate/src/popup/staking/pool/stake/joinPool/index.tsx +++ b/packages/extension-polkagate/src/popup/staking/pool/stake/joinPool/index.tsx @@ -130,9 +130,9 @@ export default function JoinPool (): React.ReactElement { return setEstimatedFee(api.createType('Balance', BN_ONE) as Balance); } - const mayBeAmount = amountAsBN || poolStakingConsts?.minJoinBond; + const maybeAmount = amountAsBN || poolStakingConsts?.minJoinBond; - mayBeAmount && api.tx['nominationPools']['join'](mayBeAmount.toString(), BN_ONE).paymentInfo(formatted).then((i) => { + maybeAmount && api.tx['nominationPools']['join'](maybeAmount.toString(), BN_ONE).paymentInfo(formatted).then((i) => { setEstimatedFee(api.createType('Balance', i?.partialFee) as Balance); }).catch(console.error); diff --git a/packages/extension-polkagate/src/popup/staking/pool/stake/joinPool/partials/PoolsTable.tsx b/packages/extension-polkagate/src/popup/staking/pool/stake/joinPool/partials/PoolsTable.tsx index 5d981d7fa..2179e708f 100644 --- a/packages/extension-polkagate/src/popup/staking/pool/stake/joinPool/partials/PoolsTable.tsx +++ b/packages/extension-polkagate/src/popup/staking/pool/stake/joinPool/partials/PoolsTable.tsx @@ -147,8 +147,8 @@ export default function PoolsTable ({ address, api, filteredPools, maxHeight = w {poolsToShow ? poolsToShow.length ? poolsToShow.map((pool, index) => { - const mayBeCommission = (pool.bondedPool as any).commission.current.isSome ? (pool.bondedPool as any).commission.current.value[0] : 0; - const commission = Number(mayBeCommission) / (10 ** 7) < 1 ? 0 : Number(mayBeCommission) / (10 ** 7); + const maybeCommission = (pool.bondedPool as any).commission.current.isSome ? (pool.bondedPool as any).commission.current.value[0] : 0; + const commission = Number(maybeCommission) / (10 ** 7) < 1 ? 0 : Number(maybeCommission) / (10 ** 7); return ( diff --git a/packages/extension-polkagate/src/popup/staking/solo/fastUnstake/index.tsx b/packages/extension-polkagate/src/popup/staking/solo/fastUnstake/index.tsx index 7cec09e9e..f8118e5a7 100644 --- a/packages/extension-polkagate/src/popup/staking/solo/fastUnstake/index.tsx +++ b/packages/extension-polkagate/src/popup/staking/solo/fastUnstake/index.tsx @@ -42,13 +42,13 @@ export default function Index (): React.ReactElement { const stakingAccount = useStakingAccount(formatted, state?.stakingAccount); const myBalances = useBalances(address); - const mayBeMyStashBalances = useBalances(stakingAccount?.stashId as unknown as string); + const maybeMyStashBalances = useBalances(stakingAccount?.stashId as unknown as string); const stakingConsts = useStakingConsts(address, state?.stakingConsts); const isExposed = useIsExposed(address); const fastUnstakeDeposit = api ? api.consts['fastUnstake']['deposit'] as unknown as BN : undefined; - const balances = useMemo(() => mayBeMyStashBalances || myBalances, [mayBeMyStashBalances, myBalances]); + const balances = useMemo(() => maybeMyStashBalances || myBalances, [maybeMyStashBalances, myBalances]); const redeemable = useMemo(() => stakingAccount?.redeemable, [stakingAccount?.redeemable]); const availableBalance = useMemo(() => getValue('available', balances), [balances]); diff --git a/packages/extension-polkagate/src/popup/staking/solo/unstake/Review.tsx b/packages/extension-polkagate/src/popup/staking/solo/unstake/Review.tsx index 48b238854..08f231aa3 100644 --- a/packages/extension-polkagate/src/popup/staking/solo/unstake/Review.tsx +++ b/packages/extension-polkagate/src/popup/staking/solo/unstake/Review.tsx @@ -109,9 +109,9 @@ export default function Review({ address, amount, chilled, estimatedFee, hasNomi } txs.push(unbonded(amountAsBN)); - const mayBeBatchTxs = txs.length > 1 ? api.tx['utility']['batchAll'](txs) : txs[0]; - const mayBeProxiedTx = selectedProxy ? api.tx['proxy']['proxy'](formatted, selectedProxy.proxyType, mayBeBatchTxs) : mayBeBatchTxs; - const { block, failureText, fee, success, txHash } = await signAndSend(api, mayBeProxiedTx, signer, formatted); + const maybeBatchTxs = txs.length > 1 ? api.tx['utility']['batchAll'](txs) : txs[0]; + const maybeProxiedTx = selectedProxy ? api.tx['proxy']['proxy'](formatted, selectedProxy.proxyType, maybeBatchTxs) : maybeBatchTxs; + const { block, failureText, fee, success, txHash } = await signAndSend(api, maybeProxiedTx, signer, formatted); const info = { action: 'Solo Staking', diff --git a/packages/extension-polkagate/src/util/api/postData.ts b/packages/extension-polkagate/src/util/api/postData.ts index bf6c3a916..41cc896ba 100644 --- a/packages/extension-polkagate/src/util/api/postData.ts +++ b/packages/extension-polkagate/src/util/api/postData.ts @@ -3,7 +3,7 @@ // @ts-nocheck /* eslint-disable header/header */ -export default async function postData(url: string, data = {}) { +export default async function postData (url: string, data = {}) { // console.log('calling post data, url:', url); // Default options are marked with * diff --git a/packages/extension-polkagate/src/util/api/signAndSend.ts b/packages/extension-polkagate/src/util/api/signAndSend.ts index 0e169542f..c38666df1 100644 --- a/packages/extension-polkagate/src/util/api/signAndSend.ts +++ b/packages/extension-polkagate/src/util/api/signAndSend.ts @@ -47,9 +47,9 @@ export async function signAndSend ( } catch (error) { success = false; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const mayBeErrorText = result?.dispatchError?.toString() || 'unknown error'; + const maybeErrorText = result?.dispatchError?.toString() || 'unknown error'; - failureText = `${mayBeErrorText}`; + failureText = `${maybeErrorText}`; console.log(error); } diff --git a/packages/extension-polkagate/src/util/constants.tsx b/packages/extension-polkagate/src/util/constants.tsx index 1524bba83..54de5402f 100644 --- a/packages/extension-polkagate/src/util/constants.tsx +++ b/packages/extension-polkagate/src/util/constants.tsx @@ -11,8 +11,8 @@ export const PREFERRED_POOL_NAME = EXTENSION_NAME; export const POLKADOT_SLIP44 = 354; -// fix me, since we have asset ID 0 on asset hub, it can be -1 instead! -export const NATIVE_TOKEN_ASSET_ID = 0; // zero is the native token's assetId on apps-config +export const NATIVE_TOKEN_ASSET_ID = 0; // used for non asset hub chains +export const NATIVE_TOKEN_ASSET_ID_ON_ASSETHUB = -1; // used only for asset hubs export const POLKAGATE_POOL_IDS: Record = { Kusama: 18, @@ -47,7 +47,7 @@ export const ACALA_GENESIS_HASH = '0xfc41b9bd8ef8fe53d58c7ea67c794c7ec9a73daf05e export const WESTMINT_GENESIS_HASH = '0x67f9723393ef76214df0118c34bbbd3dbebc8ed46a10973a8c969d48fe7598c9'; export const STATEMINE_GENESIS_HASH = '0x48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a'; // KUSAMA ASSET HUB export const STATEMINT_GENESIS_HASH = '0x68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f'; -export const PASEO_ASSET_HUB_GENESIS_HASH = '0x862ce2fa5abfdc3d29ead85a9472071efc69433b0128db1d6f009967fae87952'; +export const PASEO_ASSET_HUB_GENESIS_HASH = '0xd6eec26135305a8ad257a20d003357284c8aa03d0bdb2b357ab0a22371e11ef2'; export const POLKADOT_PEOPLE_GENESIS_HASH = '0x67fa177a097bfa18f77ea95ab56e9bcdfeb0e5b8a40e46298bb93e16b6fc5008'; export const KUSAMA_PEOPLE_GENESIS_HASH = '0xc1af4cb4eb3918e5db15086c0cc5ec17fb334f728b7c65dd44bfe1e174ff8b3f'; @@ -85,10 +85,7 @@ export const TEST_NETS = [ ]; export const PROXY_CHAINS = [ - POLKADOT_GENESIS_HASH, - KUSAMA_GENESIS_HASH, - WESTEND_GENESIS_HASH, - PASEO_GENESIS_HASH, + ...RELAY_CHAINS_GENESISHASH, ...ASSET_HUBS ]; @@ -110,10 +107,7 @@ export const SOCIAL_RECOVERY_CHAINS = [ // used to enable/disable staking icon in account page export const STAKING_CHAINS = [ - POLKADOT_GENESIS_HASH, - KUSAMA_GENESIS_HASH, - WESTEND_GENESIS_HASH, - PASEO_GENESIS_HASH + ...RELAY_CHAINS_GENESISHASH ]; export const PEOPLE_CHAINS = ['Polkadot', 'Kusama', 'Westend', 'PolkadotPeople', 'KusamaPeople', 'WestendPeople']; @@ -223,7 +217,7 @@ export const MAYBE_LATER_PERIOD = 5 * 60 * 1000; // ms export const FULLSCREEN_WIDTH = '900px'; export const ALLOWED_URL_ON_RESET_PASSWORD = ['/account/restore-json', '/account/import-seed', '/account/import-raw-seed', '/forgot-password', '/reset-wallet']; -type ProxyTypeIndex = 'CROWDLOAN' | 'GENERAL' | 'GOVERNANCE'| 'NOMINATION_POOLS' | 'SEND_FUND'| 'STAKING'; +type ProxyTypeIndex = 'CROWDLOAN' | 'GENERAL' | 'GOVERNANCE' | 'NOMINATION_POOLS' | 'SEND_FUND' | 'STAKING'; export const PROXY_TYPE: Record = { CROWDLOAN: ['Any', 'NonTransfer', 'Auction'], diff --git a/packages/extension-polkagate/src/util/getLogo.ts b/packages/extension-polkagate/src/util/getLogo.ts index c66e8b1b2..350b97dff 100644 --- a/packages/extension-polkagate/src/util/getLogo.ts +++ b/packages/extension-polkagate/src/util/getLogo.ts @@ -34,20 +34,20 @@ export default function getLogo (info: string | undefined | Chain, token?: strin } } - let mayBeExternalLogo; + let maybeExternalLogo; const iconName = sanitizeChainName(chainNameFromGenesisHash || (info as Chain)?.name || (info as string))?.toLowerCase(); const endpoint = endpoints.find((o) => o.info?.toLowerCase() === iconName); if (!endpoint) { - mayBeExternalLogo = Object + maybeExternalLogo = Object .entries(externalLinks) .find(([name]): React.ReactNode | null => name.toLowerCase() === iconName ); } - const found = iconName ? (endpoint?.ui.logo || mayBeExternalLogo?.[1]?.ui?.logo) : undefined; + const found = iconName ? (endpoint?.ui.logo || maybeExternalLogo?.[1]?.ui?.logo) : undefined; return found; } diff --git a/packages/extension-polkagate/src/util/getLogo2.ts b/packages/extension-polkagate/src/util/getLogo2.ts index b0313815a..6e0b25308 100644 --- a/packages/extension-polkagate/src/util/getLogo2.ts +++ b/packages/extension-polkagate/src/util/getLogo2.ts @@ -41,20 +41,20 @@ export default function getLogo2 (info: string | undefined | null | Chain, token } } - let mayBeExternalLogo; + let maybeExternalLogo; const iconName = sanitizeChainName(chainNameFromGenesisHash || (info as Chain)?.name || (info as string))?.toLowerCase(); const endpoint = endpoints.find((o) => o.info?.toLowerCase() === iconName); if (!endpoint) { - mayBeExternalLogo = Object + maybeExternalLogo = Object .entries(externalLinks) .find(([name]): React.ReactNode | null => name.toLowerCase() === iconName ); } - const found = iconName ? (endpoint?.ui || mayBeExternalLogo?.[1]?.ui) : undefined; + const found = iconName ? (endpoint?.ui || maybeExternalLogo?.[1]?.ui) : undefined; return found; } diff --git a/packages/extension-polkagate/src/util/types.ts b/packages/extension-polkagate/src/util/types.ts index 5c3899911..dc59d61a5 100644 --- a/packages/extension-polkagate/src/util/types.ts +++ b/packages/extension-polkagate/src/util/types.ts @@ -633,7 +633,7 @@ export interface SavedIdentities { export interface BalancesInfo extends DeriveBalancesAll { ED: BN; - assetId?: number; + assetId?: number | string; chainName: string; currencyId?: unknown; date: number; diff --git a/packages/extension-polkagate/src/util/utils.ts b/packages/extension-polkagate/src/util/utils.ts index aafadb0b3..623074b5d 100644 --- a/packages/extension-polkagate/src/util/utils.ts +++ b/packages/extension-polkagate/src/util/utils.ts @@ -8,10 +8,11 @@ import type { Chain } from '@polkadot/extension-chains/types'; import type { Text } from '@polkadot/types'; import type { AccountId } from '@polkadot/types/interfaces'; import type { Compact, u128 } from '@polkadot/types-codec'; +import type { HexString } from '@polkadot/util/types'; import type { DropdownOption, FastestConnectionType, RecentChainsType, TransactionDetail, UserAddedChains } from './types'; import { ApiPromise, WsProvider } from '@polkadot/api'; -import { BN, BN_TEN, BN_ZERO, hexToBn, hexToU8a, isHex } from '@polkadot/util'; +import { BN, BN_TEN, BN_ZERO, hexToBn, hexToString, hexToU8a, isHex, stringToU8a, u8aToHex, u8aToString } from '@polkadot/util'; import { decodeAddress, encodeAddress } from '@polkadot/util-crypto'; import { EXTRA_PRICE_IDS } from './api/getPrices'; @@ -498,3 +499,49 @@ export async function fastestConnection (endpoints: DropdownOption[]): Promise { + try { + const jsonString = JSON.stringify(multiLocation); + const u8aArray = stringToU8a(jsonString); + const hexString = u8aToHex(u8aArray); + + return hexString; + } catch (error) { + console.error('Error encoding multiLocation:', error); + + return null; + } +}; + +export const decodeHexValues = (obj: unknown) => { + if (typeof obj !== 'object' || obj === null) { + return obj; + } + + const objAsRecord = { ...obj } as Record; + + Object.keys(objAsRecord).forEach((key) => { + if (typeof objAsRecord[key] === 'string' && objAsRecord[key].startsWith('0x')) { + objAsRecord[key] = hexToString(objAsRecord[key]); + } + }); + + return objAsRecord; +}; + +export const decodeMultiLocation = (hexString: HexString) => { + const decodedU8a = hexToU8a(hexString); + const decodedJsonString = u8aToString(decodedU8a); + let decodedMultiLocation: unknown; + + try { + decodedMultiLocation = JSON.parse(decodedJsonString); + } catch (error) { + console.error('Error parsing JSON string in decodeMultiLocation:', error); + + return null; + } + + return decodeHexValues(decodedMultiLocation); +}; diff --git a/packages/extension-polkagate/src/util/workers/getAssetOnAssetHub.js b/packages/extension-polkagate/src/util/workers/getAssetOnAssetHub.js index 6d073b5de..375091c18 100644 --- a/packages/extension-polkagate/src/util/workers/getAssetOnAssetHub.js +++ b/packages/extension-polkagate/src/util/workers/getAssetOnAssetHub.js @@ -1,14 +1,68 @@ // Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors // SPDX-License-Identifier: Apache-2.0 -/* eslint-disable import-newlines/enforce */ -/* eslint-disable object-curly-newline */ - import { BN_ZERO } from '@polkadot/util'; -// eslint-disable-next-line import/extensions +import { decodeMultiLocation } from '../utils'; import { closeWebsockets, fastestEndpoint, getChainEndpoints, metadataFromApi, toGetNativeToken } from './utils'; +//@ts-ignore +async function getAssets (addresses, api, assets, chainName, results) { + try { + for (const asset of assets) { + const isForeignAssets = asset.isForeign; + const section = isForeignAssets ? 'foreignAssets' : 'assets'; + const assetId = isForeignAssets ? decodeMultiLocation(asset.id) : asset.id; + // @ts-ignore + const maybeTheAssetOfAddresses = addresses.map((address) => api.query[section].account(assetId, address)); + const assetMetaData = api.query[section].metadata(assetId); + + const response = await Promise.all([assetMetaData, ...maybeTheAssetOfAddresses]); + const metadata = response[0]; + const assetOfAddresses = response.slice(1); + + const decimal = metadata.decimals.toNumber(); + const token = metadata.symbol.toHuman(); + + // @ts-ignore + assetOfAddresses.forEach((_asset, index) => { + const balance = _asset.isNone ? BN_ZERO : _asset.unwrap().balance; + + const isFrozen = isForeignAssets + ? metadata.isFrozen.valueOf() + : _asset.isSome && _asset.unwrap().status.valueOf().toString() === 'Frozen'; + + const _balance = String(balance); + + const item = { + assetId: asset.id, + balanceDetails: { + availableBalance: isFrozen ? 0 : _balance, + lockedBalance: isFrozen ? _balance : 0, + reservedBalance: isFrozen ? balance : 0 // JUST to comply with the rule that total=available + reserve + }, + chainName, + decimal, + freeBalance: isFrozen ? 0 : _balance, // JUST to comply with the rule ... + frozenBalance: isFrozen ? balance : 0, // JUST to comply with the rule that total=available + reserve + genesisHash: api.genesisHash.toString(), + isAsset: true, + isForeignAssets: !!isForeignAssets, + priceId: asset?.priceId, + token, + totalBalance: _balance + }; + + const _index = addresses[index]; + + results[_index]?.push(item) ?? (results[_index] = [item]); + }); + } + } catch (e) { + console.error('Something went wrong while fetching assets', e); + } +} + // @ts-ignore async function getAssetOnAssetHub (addresses, assetsToBeFetched, chainName, userAddedEndpoints) { const endpoints = getChainEndpoints(chainName, userAddedEndpoints); @@ -23,61 +77,30 @@ async function getAssetOnAssetHub (addresses, assetsToBeFetched, chainName, user // @ts-ignore const nonNativeAssets = assetsToBeFetched.filter((asset) => !asset.extras?.isNative); - for (const asset of nonNativeAssets) { - // @ts-ignore - const maybeTheAssetOfAddresses = addresses.map((address) => api.query.assets.account(asset.id, address)); - const assetMetaData = api.query.assets.metadata(asset.id); - - const response = await Promise.all([assetMetaData, ...maybeTheAssetOfAddresses]); - const metadata = response[0]; - const AssetOfAddresses = response.slice(1); - - const decimal = metadata.decimals.toNumber(); - const token = metadata.symbol.toHuman(); - - AssetOfAddresses.forEach((_asset, index) => { - const balance = _asset.isNone ? BN_ZERO : _asset.unwrap().balance; - const parsedAccountAsset = JSON.parse(JSON.stringify(_asset)); - const isFrozen = parsedAccountAsset?.status === 'Frozen'; - const _balance = String(balance); - - const item = { - assetId: asset.id, - balanceDetails: { - availableBalance: isFrozen ? 0 : _balance, - lockedBalance: isFrozen ? _balance : 0, - reservedBalance: isFrozen ? balance : 0 // JUST to comply with the rule that total=available + reserve - }, - chainName, - decimal, - freeBalance: isFrozen ? 0 : _balance, // JUST to comply with the rule ... - frozenBalance: isFrozen ? balance : 0, // JUST to comply with the rule that total=available + reserve - genesisHash: api.genesisHash.toString(), - isAsset: true, - priceId: asset?.priceId, - token, - totalBalance: _balance - }; - - const _index = addresses[index]; + /** to calculate a new Foreign Token like MYTH asset id based on its XCM multi-location */ + // const allForeignAssets = await api.query.foreignAssets.asset.entries(); + // for (const [key, _others] of allForeignAssets) { + // const id = key.args[0]; + // const assetMetaData = await api.query.foreignAssets.metadata(id); - // @ts-ignore - results[_index]?.push(item) ?? (results[_index] = [item]); - }); - } + // if (assetMetaData.toHuman().symbol === 'MYTH') { + // console.log('new foreign asset id:', encodeMultiLocation(id)); + // } + // } + + await getAssets(addresses, api, nonNativeAssets, chainName, results); postMessage(JSON.stringify(results)); closeWebsockets(connections); } onmessage = async (e) => { - const { addresses, assetsToBeFetched, chainName, userAddedEndpoints } = e.data; + let { addresses, assetsToBeFetched, chainName, userAddedEndpoints } = e.data; - /** if assetsToBeFetched === undefined then we don't fetch assets by default at first, but wil fetch them on-demand later in account details page*/ if (!assetsToBeFetched) { - console.warn(`getAssetOnAssetHub: No assets to be fetched on ${chainName}`); + console.warn(`getAssetOnAssetHub: No assets to be fetched on ${chainName}, but just Native Token`); - return postMessage(undefined); + assetsToBeFetched = []; } let tryCount = 1; diff --git a/packages/extension-polkagate/src/util/workers/getAssetOnRelayChain.js b/packages/extension-polkagate/src/util/workers/getAssetOnRelayChain.js index 43cbbff31..a4053e31a 100644 --- a/packages/extension-polkagate/src/util/workers/getAssetOnRelayChain.js +++ b/packages/extension-polkagate/src/util/workers/getAssetOnRelayChain.js @@ -98,7 +98,7 @@ async function getAssetOnRelayChain (addresses, chainName, userAddedEndpoints) { : getPriceIdByChainName(chainName, userAddedEndpoints); results[address] = [{ // since some chains may have more than one asset hence we use an array here! even thought its not needed for relay chains but just to be as a general rule. - assetId: NATIVE_TOKEN_ASSET_ID, // Rule: we set asset id 0 for native tokens + assetId: NATIVE_TOKEN_ASSET_ID, balanceDetails: balancify({ ...balances, pooledBalance, soloTotal }), chainName, decimal: api.registry.chainDecimals[0], diff --git a/packages/extension-polkagate/src/util/workers/utils/toGetNativeToken.js b/packages/extension-polkagate/src/util/workers/utils/toGetNativeToken.js index 7d6639729..6d26335fc 100644 --- a/packages/extension-polkagate/src/util/workers/utils/toGetNativeToken.js +++ b/packages/extension-polkagate/src/util/workers/utils/toGetNativeToken.js @@ -3,7 +3,7 @@ // @ts-nocheck -import { NATIVE_TOKEN_ASSET_ID } from '../../constants'; +import { ASSET_HUBS, NATIVE_TOKEN_ASSET_ID, NATIVE_TOKEN_ASSET_ID_ON_ASSETHUB } from '../../constants'; import { getPriceIdByChainName } from '../../utils'; // eslint-disable-next-line import/extensions import { balancify } from '.'; @@ -20,8 +20,10 @@ export async function toGetNativeToken (addresses, api, chainName) { const totalBalance = balances[index].freeBalance.add(balances[index].reservedBalance); + const isAssetHub = ASSET_HUBS.includes(api.genesisHash.toString()); + _result[address] = [{ - assetId: NATIVE_TOKEN_ASSET_ID, // Rule: we set asset id 0 for native tokens + assetId: isAssetHub ? NATIVE_TOKEN_ASSET_ID_ON_ASSETHUB : NATIVE_TOKEN_ASSET_ID, balanceDetails: balancify(balances[index]), chainName, decimal: api.registry.chainDecimals[0], diff --git a/packages/extension-ui/src/Popup/index.tsx b/packages/extension-ui/src/Popup/index.tsx index 78f49e256..1cb6a6165 100644 --- a/packages/extension-ui/src/Popup/index.tsx +++ b/packages/extension-ui/src/Popup/index.tsx @@ -183,9 +183,9 @@ export default function Popup (): React.ReactElement { getStorage('pricesInCurrencies') .then((res) => { const savedPricesInCurrencies = (res || {}) as PricesInCurrencies; - const mayBeSavedPriceInCurrentCurrencyCode = savedPricesInCurrencies[currency.code]; + const maybeSavedPriceInCurrentCurrencyCode = savedPricesInCurrencies[currency.code]; - if (mayBeSavedPriceInCurrentCurrencyCode && isPriceUpToDate(mayBeSavedPriceInCurrentCurrencyCode.date)) { + if (maybeSavedPriceInCurrentCurrencyCode && isPriceUpToDate(maybeSavedPriceInCurrentCurrencyCode.date)) { /** price in the selected currency is already updated hence no need to fetch again */ // TODO: FixMe: what if users change selected chainS during price validity period? return; diff --git a/replacements/interfaces.js b/replacements/interfaces.js index 8033b9f1e..33314f932 100644 --- a/replacements/interfaces.js +++ b/replacements/interfaces.js @@ -54,7 +54,7 @@ knownGenesis.paseo = [ '0x77afd6190f1554ad45fd0d31aee62aacc33c6db0ea801129acb813f913e0764f' ]; knownGenesis.paseoAssetHub = [ - '0x862ce2fa5abfdc3d29ead85a9472071efc69433b0128db1d6f009967fae87952' + '0xd6eec26135305a8ad257a20d003357284c8aa03d0bdb2b357ab0a22371e11ef2' ]; const testnets = [{ @@ -85,7 +85,7 @@ const assetHubs = [{ "standardAccount": "*25519", "website": "https://polkadot.network" }, - { +{ "prefix": 2, "network": "statemine", "displayName": "Kusama Asset Hub", @@ -94,7 +94,7 @@ const assetHubs = [{ "standardAccount": "*25519", "website": "https://kusama.network" }, - { +{ "prefix": 0, "network": "statemint", "displayName": "Polkadot Asset Hub", diff --git a/yarn.lock b/yarn.lock index 580758b51..de9e3223c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3420,9 +3420,9 @@ __metadata: languageName: node linkType: hard -"@polkagate/apps-config@npm:^0.140.3": - version: 0.140.3 - resolution: "@polkagate/apps-config@npm:0.140.3" +"@polkagate/apps-config@npm:^0.140.5": + version: 0.140.5 + resolution: "@polkagate/apps-config@npm:0.140.5" dependencies: "@acala-network/type-definitions": "npm:5.1.2" "@bifrost-finance/type-definitions": "npm:1.11.3" @@ -3469,7 +3469,7 @@ __metadata: pontem-types-bundle: "npm:1.0.15" rxjs: "npm:^7.8.1" tslib: "npm:^2.6.2" - checksum: 10/db28e49d837a94f8e92000bee3909d776c998e328329e81a5e7df1e533a7bf02e2e756c37912b2b0d7a4a9cdff631c40d7e3b70d6d3b09001ad2153180c581c9 + checksum: 10/563f1d71a90a2c9c3aa16d7f5fdc5ea86e89fd50925bd410aca075fd6c432ac810d771755385da99474b18b97fb702274bbc87d0a424a6ee57086372108c32c2 languageName: node linkType: hard @@ -17565,7 +17565,7 @@ __metadata: "@polkadot/types-support": "npm:^11.2.1" "@polkadot/util": "npm:^12.6.2" "@polkadot/util-crypto": "npm:^12.6.2" - "@polkagate/apps-config": "npm:^0.140.3" + "@polkagate/apps-config": "npm:^0.140.5" "@semantic-release/changelog": "npm:^6.0.3" "@semantic-release/commit-analyzer": "npm:^13.0.0" "@semantic-release/git": "npm:^10.0.1"