From 63272f517335a056c8693e761245dd5074ecd1bc Mon Sep 17 00:00:00 2001 From: Arjun Porwal Date: Wed, 26 Feb 2025 12:51:35 +0530 Subject: [PATCH 1/3] refactor: remove choosing fee asset from transfer and it's types --- packages/react-components/src/Status/types.ts | 5 +---- packages/react-components/src/TxButton.tsx | 8 ++------ packages/react-components/src/modals/Transfer.tsx | 4 ---- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/packages/react-components/src/Status/types.ts b/packages/react-components/src/Status/types.ts index 0f13efd93a1d..39972117127d 100644 --- a/packages/react-components/src/Status/types.ts +++ b/packages/react-components/src/Status/types.ts @@ -3,8 +3,7 @@ import type { SubmittableResult } from '@polkadot/api'; import type { SubmittableExtrinsic } from '@polkadot/api/promise/types'; -import type { SignerOptions, SignerResult } from '@polkadot/api/types'; -import type { AssetInfoComplete } from '@polkadot/react-hooks/types'; +import type { SignerResult } from '@polkadot/api/types'; import type { AccountId, Address } from '@polkadot/types/interfaces'; import type { DefinitionRpcExt, Registry, SignerPayloadJSON } from '@polkadot/types/types'; @@ -52,7 +51,6 @@ export interface QueueTx extends AccountInfo { txUpdateCb?: TxCallback; values?: unknown[]; status: QueueTxStatus; - signerOptions?: Partial; } export interface QueueStatus extends ActionStatus { @@ -89,7 +87,6 @@ export interface PartialQueueTxExtrinsic extends PartialAccountInfo { txStartCb?: () => void; txUpdateCb?: TxCallback; isUnsigned?: boolean; - signerOptions?: Partial; } export interface PartialQueueTxRpc extends PartialAccountInfo { diff --git a/packages/react-components/src/TxButton.tsx b/packages/react-components/src/TxButton.tsx index 06a7e2b1abce..2b59e0dad614 100644 --- a/packages/react-components/src/TxButton.tsx +++ b/packages/react-components/src/TxButton.tsx @@ -7,8 +7,7 @@ import type { TxButtonProps as Props } from './types.js'; import React, { useCallback, useEffect, useState } from 'react'; -import { useApi, useIsMountedRef, usePayWithAsset, useQueue } from '@polkadot/react-hooks'; -import { getFeeAssetLocation } from '@polkadot/react-hooks/utils/getFeeAssetLocation'; +import { useIsMountedRef, useQueue } from '@polkadot/react-hooks'; import { assert, isFunction } from '@polkadot/util'; import Button from './Button/index.js'; @@ -16,10 +15,8 @@ import { useTranslation } from './translate.js'; function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon, isBasic, isBusy, isDisabled, isIcon, isToplevel, isUnsigned, label, onClick, onFailed, onSendRef, onStart, onSuccess, onUpdate, params, tooltip, tx, withSpinner, withoutLink }: Props): React.ReactElement { const { t } = useTranslation(); - const { api } = useApi(); const mountedRef = useIsMountedRef(); const { queueExtrinsic } = useQueue(); - const { selectedFeeAsset } = usePayWithAsset(); const [isSending, setIsSending] = useState(false); const [isStarted, setIsStarted] = useState(false); @@ -79,7 +76,6 @@ function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon, accountId: accountId?.toString(), extrinsic, isUnsigned, - signerOptions: { assetId: getFeeAssetLocation(api, selectedFeeAsset), feeAsset: selectedFeeAsset }, txFailedCb: withSpinner ? _onFailed : onFailed, txStartCb: _onStart, txSuccessCb: withSpinner ? _onSuccess : onSuccess, @@ -89,7 +85,7 @@ function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon, onClick && onClick(); }, - [_onFailed, _onStart, _onSuccess, accountId, api, isUnsigned, mountedRef, onClick, onFailed, onSuccess, onUpdate, params, propsExtrinsic, queueExtrinsic, selectedFeeAsset, tx, withSpinner] + [_onFailed, _onStart, _onSuccess, accountId, isUnsigned, mountedRef, onClick, onFailed, onSuccess, onUpdate, params, propsExtrinsic, queueExtrinsic, tx, withSpinner] ); if (onSendRef) { diff --git a/packages/react-components/src/modals/Transfer.tsx b/packages/react-components/src/modals/Transfer.tsx index 4bf0bfb55c81..d7d234741349 100644 --- a/packages/react-components/src/modals/Transfer.tsx +++ b/packages/react-components/src/modals/Transfer.tsx @@ -19,7 +19,6 @@ import InputBalance from '../InputBalance.js'; import MarkError from '../MarkError.js'; import MarkWarning from '../MarkWarning.js'; import Modal from '../Modal/index.js'; -import PayWithAsset from '../PayWithAsset.js'; import { styled } from '../styled.js'; import Toggle from '../Toggle.js'; import { useTranslation } from '../translate.js'; @@ -148,9 +147,6 @@ function Transfer ({ className = '', onClose, recipientId: propRecipientId, send )} - - - {canToggleAll && isAll ? ( From b9f1a2c315fbae4e2b17189c86b0ba4c3c8631ef Mon Sep 17 00:00:00 2001 From: Arjun Porwal Date: Wed, 26 Feb 2025 13:51:14 +0530 Subject: [PATCH 2/3] feat: choose fee asset in authorize tx modal --- .../apps/public/locales/en/react-signer.json | 2 + packages/react-components/src/index.ts | 1 - packages/react-hooks/src/ctx/PayWithAsset.tsx | 8 +++- .../src/PayWithAsset.tsx | 45 ++++++++++++++----- packages/react-signer/src/PaymentInfo.tsx | 6 ++- packages/react-signer/src/Transaction.tsx | 4 +- packages/react-signer/src/TxSigned.tsx | 15 ++++--- packages/react-signer/src/types.ts | 4 ++ 8 files changed, 62 insertions(+), 23 deletions(-) rename packages/{react-components => react-signer}/src/PayWithAsset.tsx (53%) diff --git a/packages/apps/public/locales/en/react-signer.json b/packages/apps/public/locales/en/react-signer.json index bec1350be91a..7e6c44c54ba5 100644 --- a/packages/apps/public/locales/en/react-signer.json +++ b/packages/apps/public/locales/en/react-signer.json @@ -1,6 +1,7 @@ { "Adding an optional tip to the transaction could allow for higher priority, especially when the chain is busy.": "Adding an optional tip to the transaction could allow for higher priority, especially when the chain is busy.", "Authorize transaction": "Authorize transaction", + "By selecting this option, the transaction fee will be automatically deducted from the specified asset, ensuring a seamless and efficient payment process.": "By selecting this option, the transaction fee will be automatically deducted from the specified asset, ensuring a seamless and efficient payment process.", "Current account nonce: {{accountNonce}}": "Current account nonce: {{accountNonce}}", "Do not include a tip for the block author": "Do not include a tip for the block author", "Don't use a proxy for this call": "Don't use a proxy for this call", @@ -35,6 +36,7 @@ "Unable to connect to the Ledger, ensure support is enabled in settings and no other app is using it. {{errorMessage}}": "Unable to connect to the Ledger, ensure support is enabled in settings and no other app is using it. {{errorMessage}}", "Unlock the sending account to allow signing of this transaction.": "Unlock the sending account to allow signing of this transaction.", "Use a proxy for this call": "Use a proxy for this call", + "asset to pay the fee": "asset to pay the fee", "call hash": "call hash", "multisig call data": "multisig call data", "multisig signatory": "multisig signatory", diff --git a/packages/react-components/src/index.ts b/packages/react-components/src/index.ts index ba21a9d757f9..1e60ea5b9a7b 100644 --- a/packages/react-components/src/index.ts +++ b/packages/react-components/src/index.ts @@ -74,7 +74,6 @@ export { default as Output } from './Output.js'; export { default as ParaLink } from './ParaLink.js'; export { default as Password } from './Password.js'; export { default as PasswordStrength } from './PasswordStrength.js'; -export { default as PayWithAsset } from './PayWithAsset.js'; export { default as Popup } from './Popup/index.js'; export { default as Progress } from './Progress.js'; export { default as ProgressBar } from './ProgressBar.js'; diff --git a/packages/react-hooks/src/ctx/PayWithAsset.tsx b/packages/react-hooks/src/ctx/PayWithAsset.tsx index 5f55afac0656..c39f8d9b3f30 100644 --- a/packages/react-hooks/src/ctx/PayWithAsset.tsx +++ b/packages/react-hooks/src/ctx/PayWithAsset.tsx @@ -5,7 +5,7 @@ import type { BN } from '@polkadot/util'; import type { AssetInfoComplete } from '../types.js'; import type { PayWithAsset } from './types.js'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useApi, useAssetIds, useAssetInfos } from '@polkadot/react-hooks'; import { formatNumber } from '@polkadot/util'; @@ -44,7 +44,7 @@ function PayWithAssetProvider ({ children }: Props): React.ReactElement { const completeAssetInfos = useMemo( () => (assetInfos ?.filter((i): i is AssetInfoComplete => - !!(i.details && i.metadata) && !i.details.supply.toHuman() && !!i.details?.toJSON().isSufficient) + !!(i.details && i.metadata) && !!i.details?.toJSON().isSufficient) ) || [], [assetInfos] ); @@ -74,6 +74,10 @@ function PayWithAssetProvider ({ children }: Props): React.ReactElement { [api.registry.metadata.extrinsic.signedExtensions, api.tx.assetConversion, completeAssetInfos.length] ); + useEffect(() => { + return () => setSelectedFeeAsset(null); + }); + const values: PayWithAsset = useMemo(() => { return { assetOptions, diff --git a/packages/react-components/src/PayWithAsset.tsx b/packages/react-signer/src/PayWithAsset.tsx similarity index 53% rename from packages/react-components/src/PayWithAsset.tsx rename to packages/react-signer/src/PayWithAsset.tsx index b2656da553ad..e7f5e8ffab66 100644 --- a/packages/react-components/src/PayWithAsset.tsx +++ b/packages/react-signer/src/PayWithAsset.tsx @@ -1,22 +1,28 @@ -// Copyright 2017-2025 @polkadot/react-components authors & contributors +// Copyright 2017-2025 @polkadot/react-signer authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { DropdownItemProps } from 'semantic-ui-react'; +import type { ExtendedSignerOptions } from './types.js'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { Dropdown } from '@polkadot/react-components'; +import { Dropdown, Modal } from '@polkadot/react-components'; import { useApi, usePayWithAsset } from '@polkadot/react-hooks'; +import { getFeeAssetLocation } from '@polkadot/react-hooks/utils/getFeeAssetLocation'; import { BN } from '@polkadot/util'; import { useTranslation } from './translate.js'; -const PayWithAsset = () => { +interface Props { + onChangeFeeAsset: React.Dispatch> +} + +const PayWithAsset = ({ onChangeFeeAsset }: Props) => { const { t } = useTranslation(); const { api } = useApi(); const [selectedAssetValue, setSelectedAssetValue] = useState('0'); - const { assetOptions, isDisabled, onChange } = usePayWithAsset(); + const { assetOptions, isDisabled, onChange, selectedFeeAsset } = usePayWithAsset(); const nativeAsset = useMemo( () => api.registry.chainTokens[0], @@ -46,15 +52,30 @@ const PayWithAsset = () => { } }, [assetOptions, selectedAssetValue, nativeAsset]); + useEffect(() => { + if (selectedFeeAsset) { + onChangeFeeAsset((e) => + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + ({ + ...e, + assetId: getFeeAssetLocation(api, selectedFeeAsset), + feeAsset: selectedFeeAsset + }) + ); + } + }, [api, onChangeFeeAsset, selectedFeeAsset]); + return ( - + + + ); }; diff --git a/packages/react-signer/src/PaymentInfo.tsx b/packages/react-signer/src/PaymentInfo.tsx index c1300cfbdec9..5a352454ae24 100644 --- a/packages/react-signer/src/PaymentInfo.tsx +++ b/packages/react-signer/src/PaymentInfo.tsx @@ -3,8 +3,8 @@ import type { SubmittableExtrinsic } from '@polkadot/api/promise/types'; import type { DeriveBalancesAll } from '@polkadot/api-derive/types'; -import type { QueueTx } from '@polkadot/react-components/Status/types'; import type { RuntimeDispatchInfo } from '@polkadot/types/interfaces'; +import type { ExtendedSignerOptions } from './types.js'; import React, { useEffect, useState } from 'react'; import { Trans } from 'react-i18next'; @@ -22,7 +22,7 @@ interface Props { isHeader?: boolean; onChange?: (hasAvailable: boolean) => void; tip?: BN; - signerOptions?: QueueTx['signerOptions']; + signerOptions: ExtendedSignerOptions; } function PaymentInfo ({ accountId, className = '', extrinsic, isHeader, signerOptions }: Props): React.ReactElement | null { @@ -35,6 +35,8 @@ function PaymentInfo ({ accountId, className = '', extrinsic, isHeader, signerOp useEffect((): void => { accountId && extrinsic && extrinsic.hasPaymentInfo && nextTick(async (): Promise => { + setDispatchInfo(null); + try { const info = await extrinsic.paymentInfo(accountId, signerOptions); diff --git a/packages/react-signer/src/Transaction.tsx b/packages/react-signer/src/Transaction.tsx index 9cc9ce50149d..acbc6bd6a486 100644 --- a/packages/react-signer/src/Transaction.tsx +++ b/packages/react-signer/src/Transaction.tsx @@ -3,6 +3,7 @@ import type { QueueTx } from '@polkadot/react-components/Status/types'; import type { BN } from '@polkadot/util'; +import type { ExtendedSignerOptions } from './types.js'; import React from 'react'; @@ -18,9 +19,10 @@ interface Props { currentItem: QueueTx; onError: () => void; tip?: BN; + signerOptions?: ExtendedSignerOptions; } -function Transaction ({ accountId, className, currentItem: { extrinsic, isUnsigned, payload, signerOptions }, onError, tip }: Props): React.ReactElement | null { +function Transaction ({ accountId, className, currentItem: { extrinsic, isUnsigned, payload }, onError, signerOptions, tip }: Props): React.ReactElement | null { const { t } = useTranslation(); if (!extrinsic) { diff --git a/packages/react-signer/src/TxSigned.tsx b/packages/react-signer/src/TxSigned.tsx index 54016e22190a..4d9854b933c3 100644 --- a/packages/react-signer/src/TxSigned.tsx +++ b/packages/react-signer/src/TxSigned.tsx @@ -15,7 +15,7 @@ import type { Option } from '@polkadot/types'; import type { Multisig, Timepoint } from '@polkadot/types/interfaces'; import type { BN } from '@polkadot/util'; import type { HexString } from '@polkadot/util/types'; -import type { AddressFlags, AddressProxy, QrState } from './types.js'; +import type { AddressFlags, AddressProxy, ExtendedSignerOptions, QrState } from './types.js'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; @@ -29,6 +29,7 @@ import { addressEq } from '@polkadot/util-crypto'; import { AccountSigner, LedgerSigner, QrSigner } from './signers/index.js'; import Address from './Address.js'; +import PayWithAsset from './PayWithAsset.js'; import Qr from './Qr.js'; import SignFields from './SignFields.js'; import Tip from './Tip.js'; @@ -243,7 +244,7 @@ function TxSigned ({ className, currentItem, isQueueSubmit, queueSize, requestAd const [isSubmit, setIsSubmit] = useState(true); const [passwordError, setPasswordError] = useState(null); const [senderInfo, setSenderInfo] = useState(() => ({ isMultiCall: false, isUnlockCached: false, multiRoot: null, proxyRoot: null, signAddress: requestAddress, signPassword: '' })); - const [signedOptions, setSignedOptions] = useState>({}); + const [signedOptions, setSignedOptions] = useState({}); const [signedTx, setSignedTx] = useState(null); const [{ innerHash, innerTx }, setCallInfo] = useState(EMPTY_INNER); const [tip, setTip] = useState(); @@ -339,7 +340,7 @@ function TxSigned ({ className, currentItem, isQueueSubmit, queueSize, requestAd if (senderInfo.signAddress) { const [tx, [status, pairOrAddress, options, isMockSign]] = await Promise.all([ wrapTx(api, currentItem, senderInfo), - extractParams(api, senderInfo.signAddress, { nonce: -1, tip, withSignedTransaction: true, ...currentItem.signerOptions }, getLedger, setQrState) + extractParams(api, senderInfo.signAddress, { nonce: -1, tip, withSignedTransaction: true, ...signedOptions }, getLedger, setQrState) ]); queueSetTxStatus(currentItem.id, status); @@ -347,7 +348,7 @@ function TxSigned ({ className, currentItem, isQueueSubmit, queueSize, requestAd await signAndSend(queueSetTxStatus, currentItem, tx, pairOrAddress, options, api, isMockSign); } }, - [api, getLedger, tip] + [api, getLedger, signedOptions, tip] ); const _onSign = useCallback( @@ -446,6 +447,7 @@ function TxSigned ({ className, currentItem, isQueueSubmit, queueSize, requestAd accountId={senderInfo.signAddress} currentItem={currentItem} onError={toggleRenderError} + signerOptions={signedOptions} />
{!currentItem.payload && ( - + <> + + + )} {!isSubmit && ( ) | undefined; From 958db2c133a2940bc7f6fadbb6901d2bf8712cb2 Mon Sep 17 00:00:00 2001 From: Arjun Porwal Date: Wed, 26 Feb 2025 16:31:19 +0530 Subject: [PATCH 3/3] fix: asset id formatting --- packages/react-hooks/src/ctx/PayWithAsset.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/react-hooks/src/ctx/PayWithAsset.tsx b/packages/react-hooks/src/ctx/PayWithAsset.tsx index c39f8d9b3f30..c2aaba46eb51 100644 --- a/packages/react-hooks/src/ctx/PayWithAsset.tsx +++ b/packages/react-hooks/src/ctx/PayWithAsset.tsx @@ -8,7 +8,6 @@ import type { PayWithAsset } from './types.js'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useApi, useAssetIds, useAssetInfos } from '@polkadot/react-hooks'; -import { formatNumber } from '@polkadot/util'; interface Props { children?: React.ReactNode; @@ -44,7 +43,7 @@ function PayWithAssetProvider ({ children }: Props): React.ReactElement { const completeAssetInfos = useMemo( () => (assetInfos ?.filter((i): i is AssetInfoComplete => - !!(i.details && i.metadata) && !!i.details?.toJSON().isSufficient) + !!(i.details && i.metadata) && !i.details.supply.isZero() && !!i.details?.toJSON().isSufficient) ) || [], [assetInfos] ); @@ -53,7 +52,7 @@ function PayWithAssetProvider ({ children }: Props): React.ReactElement { () => [ { text: `${nativeAsset} (Native)`, value: nativeAsset }, ...completeAssetInfos.map(({ id, metadata }) => ({ - text: `${metadata.name.toUtf8()} (${formatNumber(id)})`, + text: `${metadata.name.toUtf8()} (${id.toString()})`, value: id.toString() }))], [completeAssetInfos, nativeAsset]