From bdfcedae72f8b1fef5e010c145c06275d000d177 Mon Sep 17 00:00:00 2001 From: Arjun Porwal <65214523+ap211unitech@users.noreply.github.com> Date: Thu, 6 Feb 2025 21:20:08 +0530 Subject: [PATCH] AssetHub: Fee payment with any sufficient asset (#11229) * feat: created useAssetIds and useAssetInfo hook * feat: created pay with asset component * feat: add paywith asset component with Transfer modal * fix: signer options as prop * fix: payment info * feat: created PayWithAsset provider and integrated with transfer form * chore: rename variable * fix: PayWithAsset provider wrapping * feat: integrate PayWithAsset context and enhance asset info fetching * fix: some build errors * refactor: update imports and simplify getFeeAssetLocation function * refactor: simplify component structure and update PayWithAsset import paths * fix: all build errors * fix: avoid redundant type * feat: added translation * feat: enhance fee payment info & fee asset handling * fix: translations ordering * fix: remove redundant types and hooks * chore: added statemine chain * refactor: generalize fee acceptance check for non-native assets * fix: remove constants and generalize palletIndex --- .../apps/public/locales/ar/translation.json | 2 + .../public/locales/en/react-components.json | 2 + .../apps/public/locales/en/translation.json | 2 + .../apps/public/locales/es/translation.json | 2 + .../apps/public/locales/fr/translation.json | 2 + .../apps/public/locales/id/translation.json | 2 + .../apps/public/locales/it/translation.json | 2 + .../apps/public/locales/ja/translation.json | 2 + .../apps/public/locales/ko/translation.json | 2 + .../apps/public/locales/pt/translation.json | 2 + .../apps/public/locales/ru/translation.json | 2 + .../apps/public/locales/zh/translation.json | 2 + packages/apps/src/Root.tsx | 6 +- packages/page-assets/src/Balances/index.tsx | 2 +- packages/page-assets/src/Overview/Asset.tsx | 2 +- packages/page-assets/src/Overview/Assets.tsx | 2 +- packages/page-assets/src/Overview/index.tsx | 2 +- packages/page-assets/src/index.tsx | 4 +- packages/page-assets/src/types.ts | 21 ----- .../src/AccountSidebar/JudgementTag.tsx | 3 +- .../react-components/src/PayWithAsset.tsx | 61 +++++++++++++ packages/react-components/src/Status/types.ts | 5 +- packages/react-components/src/TxButton.tsx | 8 +- packages/react-components/src/index.ts | 1 + .../react-components/src/modals/Transfer.tsx | 4 + packages/react-components/src/types.ts | 10 ++ packages/react-hooks/src/ctx/PayWithAsset.tsx | 91 +++++++++++++++++++ packages/react-hooks/src/ctx/index.ts | 1 + packages/react-hooks/src/ctx/types.ts | 9 ++ packages/react-hooks/src/index.ts | 3 + packages/react-hooks/src/types.ts | 28 ++++-- .../src/useAssetIds.ts | 16 ++-- .../src/useAssetInfos.ts | 13 ++- packages/react-hooks/src/useJudgements.tsx | 2 +- packages/react-hooks/src/usePayWithAsset.ts | 15 +++ .../src/utils/getFeeAssetLocation.ts | 25 +++++ .../matchRegistrarAccountsWithIndexes.ts | 3 +- packages/react-hooks/tsconfig.xref.json | 4 +- packages/react-signer/src/PaymentInfo.tsx | 34 +++++-- packages/react-signer/src/Transaction.tsx | 3 +- packages/react-signer/src/TxSigned.tsx | 2 +- 41 files changed, 335 insertions(+), 69 deletions(-) delete mode 100644 packages/page-assets/src/types.ts create mode 100644 packages/react-components/src/PayWithAsset.tsx create mode 100644 packages/react-hooks/src/ctx/PayWithAsset.tsx rename packages/{page-assets => react-hooks}/src/useAssetIds.ts (67%) rename packages/{page-assets => react-hooks}/src/useAssetInfos.ts (79%) create mode 100644 packages/react-hooks/src/usePayWithAsset.ts create mode 100644 packages/react-hooks/src/utils/getFeeAssetLocation.ts diff --git a/packages/apps/public/locales/ar/translation.json b/packages/apps/public/locales/ar/translation.json index b1a12222eb..69205f1329 100644 --- a/packages/apps/public/locales/ar/translation.json +++ b/packages/apps/public/locales/ar/translation.json @@ -95,6 +95,7 @@ "Bond more": "رهن المزيد", "Bond more funds": "زيادة رهون الأموال", "Bonding Preferences": "تفضيلات الرهن", + "By selecting this option, the transaction fee will be automatically deducted from the specified asset, ensuring a seamless and efficient payment process.": "من خلال تحديد هذا الخيار، سيتم خصم رسوم المعاملة تلقائيًا من الأصل المحدد، مما يضمن عملية دفع سلسة وفعالة.", "Call": "مكالمة", "Call a contract": "اتصل بعقد", "Call a method on this contract": "اتصل بطريقة على هذا العقد", @@ -856,6 +857,7 @@ "approval type": "نوع الاعتماد", "approved": "موافق عليه", "asset id": "تعريف الأصل", + "asset to pay the fee": "الأصول لدفع الرسوم", "auto-selected targets for nomination": "أهداف محددة تلقائيًا للترشيح", "available": "متوفر", "available signatories": "الموقعون المتاحين", diff --git a/packages/apps/public/locales/en/react-components.json b/packages/apps/public/locales/en/react-components.json index 9c18177a6a..9538bd03dd 100644 --- a/packages/apps/public/locales/en/react-components.json +++ b/packages/apps/public/locales/en/react-components.json @@ -2,6 +2,7 @@ "0.1x voting balance, no lockup period": "0.1x voting balance, no lockup period", "": "", "Add identity judgment": "Add identity judgment", + "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.", "Cancel": "Cancel", "Confirm account removal": "Confirm account removal", "Confirm address removal": "Confirm address removal", @@ -62,6 +63,7 @@ "You are about to remove this contract from your list of available contracts. Once completed, should you need to access it again, you will have to manually add the contract's address in the Instantiate tab.": "You are about to remove this contract from your list of available contracts. Once completed, should you need to access it again, you will have to manually add the contract's address in the Instantiate tab.", "address copied": "address copied", "amount": "amount", + "asset to pay the fee": "asset to pay the fee", "available to be unlocked": "available to be unlocked", "balance": "balance", "beneficiary": "beneficiary", diff --git a/packages/apps/public/locales/en/translation.json b/packages/apps/public/locales/en/translation.json index c234a2ffd1..2262e1e1b1 100644 --- a/packages/apps/public/locales/en/translation.json +++ b/packages/apps/public/locales/en/translation.json @@ -138,6 +138,7 @@ "Bonding Preferences": "", "Bounty approval under voting": "", "Bounty rejection under voting": "", + "By selecting this option, the transaction fee will be automatically deducted from the specified asset, ensuring a seamless and efficient payment process.": "", "Call a contract": "", "Call results": "", "Calls": "", @@ -1189,6 +1190,7 @@ "asset id or name to query": "", "asset name": "", "asset symbol": "", + "asset to pay the fee": "asset to pay the fee", "assets": "", "assignment": "", "at specific block": "", diff --git a/packages/apps/public/locales/es/translation.json b/packages/apps/public/locales/es/translation.json index addca24963..bea865c545 100644 --- a/packages/apps/public/locales/es/translation.json +++ b/packages/apps/public/locales/es/translation.json @@ -73,6 +73,7 @@ "Bond more": "Vincular más", "Bond more funds": "Vincular más fondos", "Bonding Preferences": "Preferencias de la vinculación", + "By selecting this option, the transaction fee will be automatically deducted from the specified asset, ensuring a seamless and efficient payment process.": "Al seleccionar esta opción, la tarifa de transacción se deducirá automáticamente del activo especificado, lo que garantiza un proceso de pago eficiente y sin inconvenientes.", "Call": "Llamada", "Call a contract": "Llamada a contrato", "Call a method on this contract": "Llamada a un método en este contrato", @@ -714,6 +715,7 @@ "approval type": "tipo de aprobación", "approved": "aprobado", "asset id": "id del activo", + "asset to pay the fee": "asseto para pagar la tarifa", "assignment": "asignación", "auto-selected targets for nomination": "candidatos auto seleccionados para la nominación", "available": "disponible", diff --git a/packages/apps/public/locales/fr/translation.json b/packages/apps/public/locales/fr/translation.json index 67b151d547..64e995f498 100644 --- a/packages/apps/public/locales/fr/translation.json +++ b/packages/apps/public/locales/fr/translation.json @@ -107,6 +107,7 @@ "Bond more": "Allouer davantage", "Bond more funds": "Allouer plus de fonds", "Bonding Preferences": "Préférences d'allocation", + "By selecting this option, the transaction fee will be automatically deducted from the specified asset, ensuring a seamless and efficient payment process.": "En sélectionnant cette option, les frais de transaction seront automatiquement déduits de l'actif spécifié, garantissant un processus de paiement transparent et efficace.", "Call": "Appel", "Call a contract": "Appel d'un contrat", "Call a method on this contract": "Appel d'une méthode sur ce contrat", @@ -976,6 +977,7 @@ "approval type": "type d'approbation", "approved": "approuvé", "asset id": "identifiant de l'élément", + "asset to pay the fee": "actif pour payer les frais", "auto-selected targets for nomination": "cibles sélectionnées automatiquement pour la nomination", "available": "disponible", "available signatories": "signataires disponibles", diff --git a/packages/apps/public/locales/id/translation.json b/packages/apps/public/locales/id/translation.json index 20d9e13872..ba6e66637f 100644 --- a/packages/apps/public/locales/id/translation.json +++ b/packages/apps/public/locales/id/translation.json @@ -112,6 +112,7 @@ "Bond more": "Tambah obligasi", "Bond more funds": "Tambah dana obligasi", "Bonding Preferences": "Preferensi obligasi", + "By selecting this option, the transaction fee will be automatically deducted from the specified asset, ensuring a seamless and efficient payment process.": "Dengan memilih opsi ini, biaya transaksi akan secara otomatis dipotong dari aset yang ditentukan, memastikan proses pembayaran yang lancar dan efisien", "Call": "Panggil", "Call a contract": "Panggil kontrak", "Call a method on this contract": "Panggile metode dalam kontrak ini", @@ -1014,6 +1015,7 @@ "approval type": "tipe persetujuan", "approved": "Disetujui", "asset id": "id aset", + "asset to pay the fee": "aset untuk membayar biayanya", "auto-selected targets for nomination": "target yang dipilih secara otomatis untuk nominasi", "available": "Tersedia", "available signatories": "penandatangan yang tersedia", diff --git a/packages/apps/public/locales/it/translation.json b/packages/apps/public/locales/it/translation.json index 0bde77003a..f33472401e 100644 --- a/packages/apps/public/locales/it/translation.json +++ b/packages/apps/public/locales/it/translation.json @@ -134,6 +134,7 @@ "Bounties": "Bounties", "Bounty approval under voting": "Approvazione del bounty in votazione", "Bounty rejection under voting": "Sospensione / Rifiuto del bounty in votazione", + "By selecting this option, the transaction fee will be automatically deducted from the specified asset, ensuring a seamless and efficient payment process.": "Selezionando questa opzione, la commissione di transazione verrà automaticamente detratta dall'asset specificato, garantendo un processo di pagamento fluido ed efficiente", "Call": "Chiama", "Call a contract": "Richiama un contratto", "Call a method on this contract": "Richiama un metodo su questo contratto", @@ -1266,6 +1267,7 @@ "asset id": "asset id", "asset name": "nome asset", "asset symbol": "simbolo asset", + "asset to pay the fee": "bene per pagare la tassa", "assets": "assets", "auctions": "aste", "auto-selected targets for nomination": "target auto-selezionati per la nomina", diff --git a/packages/apps/public/locales/ja/translation.json b/packages/apps/public/locales/ja/translation.json index f6222a782b..7d25bb6f18 100644 --- a/packages/apps/public/locales/ja/translation.json +++ b/packages/apps/public/locales/ja/translation.json @@ -45,6 +45,7 @@ "Aye, I approve": "はい、承認します", "Backup account": "バックアップアカウント", "Block details": "ブロックの詳細", + "By selecting this option, the transaction fee will be automatically deducted from the specified asset, ensuring a seamless and efficient payment process.": "このオプションを選択すると、取引手数料が指定された資産から自動的に差し引かれ、シームレスで効率的な支払いプロセスが保証されます。", "Call": "コール", "Call a contract": "コントラクトをコールする", "Call a method on this contract": "このコントラクトのメソッドをコールする", @@ -362,6 +363,7 @@ "amount": "量", "approval type": "承認のタイプ", "asset id": "アセットid", + "asset to pay the fee": "料金を支払うための資産", "available signatories": "利用可能な署名", "available social recovery helpers": "利用可能なソーシャルリカバリーヘルパー", "aye: bool": "はい: bool", diff --git a/packages/apps/public/locales/ko/translation.json b/packages/apps/public/locales/ko/translation.json index 8498ef884d..1ff49140c0 100644 --- a/packages/apps/public/locales/ko/translation.json +++ b/packages/apps/public/locales/ko/translation.json @@ -104,6 +104,7 @@ "Bond more": "본드를 더하다", "Bond more funds": "추가적인 펀드를 본드하다", "Bonding Preferences": "선호하는 본딍", + "By selecting this option, the transaction fee will be automatically deducted from the specified asset, ensuring a seamless and efficient payment process.": "이 옵션을 선택하면 거래 수수료가 지정된 자산에서 자동으로 공제되어 원활하고 효율적인 결제 프로세스가 보장됩니다.", "Call a contract": "계약서를 콜하다", "Call results": "결과를 콜하다", "Cancel": "취소", @@ -948,6 +949,7 @@ "approval type": "승인 형식", "approved": "승인된", "asset id": "자산 id", + "asset to pay the fee": "수수료를 지불할 자산", "available": "가용", "available signatories": "현재 서명이 가능한 서명인", "available social recovery helpers": "현재 도움을 줄 수 있는 \"계정 복구 도우미\"", diff --git a/packages/apps/public/locales/pt/translation.json b/packages/apps/public/locales/pt/translation.json index 554267826a..e7b1892ab7 100644 --- a/packages/apps/public/locales/pt/translation.json +++ b/packages/apps/public/locales/pt/translation.json @@ -70,6 +70,7 @@ "Bond more": "Vincular mais", "Bond more funds": "Vincular mais fundos", "Bonding Preferences": "Preferências de vínculação", + "By selecting this option, the transaction fee will be automatically deducted from the specified asset, ensuring a seamless and efficient payment process.": "Ao selecionar esta opção, as taxas de transação serão automaticamente deduzidas dos ativos especificados, garantindo um processo de pagamento contínuo e eficiente.", "Call": "Chamar", "Call a contract": "Chamar um contrato", "Call a method on this contract": "Chamar um método neste contrato", @@ -712,6 +713,7 @@ "approval type": "tipo de aprovação", "approved": "aprovado", "asset id": "ID do recurso", + "asset to pay the fee": "ativo para pagar a taxa", "auto-selected targets for nomination": "destinos selecionados automaticamente para nomeação", "available": "acessível", "available signatories": "signatários disponíveis", diff --git a/packages/apps/public/locales/ru/translation.json b/packages/apps/public/locales/ru/translation.json index 45ccba83f7..48af7d02df 100644 --- a/packages/apps/public/locales/ru/translation.json +++ b/packages/apps/public/locales/ru/translation.json @@ -101,6 +101,7 @@ "Bonding Preferences": "Настройки депонирования", "Bounty approval under voting": "Утверждение награды находится в обсуждении", "Bounty rejection under voting": "Отмена награды в обсеждении", + "By selecting this option, the transaction fee will be automatically deducted from the specified asset, ensuring a seamless and efficient payment process.": "При выборе этой опции комиссия за транзакцию будет автоматически вычтена из указанного актива, обеспечивая беспрепятственный и эффективный процесс оплаты.", "Call": "Вызвать", "Call a contract": "Вызвать контракт", "Call a method on this contract": "Вызвать метод этого контракта", @@ -901,6 +902,7 @@ "asset id": "№ актива", "asset name": "имя средства", "asset symbol": "символ средства", + "asset to pay the fee": "актив для оплаты пошлины", "assets": "средства", "auto-selected targets for nomination": "автоматически выбранные валидаторы", "available": "доступный", diff --git a/packages/apps/public/locales/zh/translation.json b/packages/apps/public/locales/zh/translation.json index 52f3638ab8..b192e08a3e 100644 --- a/packages/apps/public/locales/zh/translation.json +++ b/packages/apps/public/locales/zh/translation.json @@ -99,6 +99,7 @@ "Bond more": "绑定更多", "Bond more funds": "绑定更多资金", "Bonding Preferences": "绑定偏好设置", + "By selecting this option, the transaction fee will be automatically deducted from the specified asset, ensuring a seamless and efficient payment process.": "通过选择此选项,交易费用将从指定资产中自动扣除,确保支付流程无缝高效。", "Call": "调用", "Call a contract": "调用一个合约", "Call a method on this contract": "在合约上调用一个方法", @@ -927,6 +928,7 @@ "approved": "已赞成", "asset id": "资产 id", "asset id or name to query": "要查询的资产id或名称", + "asset to pay the fee": "支付費用的資產", "auto-selected targets for nomination": "自动选择的提名对象", "available": "可用的", "available signatories": "可用签署人", diff --git a/packages/apps/src/Root.tsx b/packages/apps/src/Root.tsx index bf3f6e4fae..58a360d6e6 100644 --- a/packages/apps/src/Root.tsx +++ b/packages/apps/src/Root.tsx @@ -9,7 +9,7 @@ import { HashRouter } from 'react-router-dom'; import { ThemeProvider } from 'styled-components'; import { ApiCtxRoot } from '@polkadot/react-api'; -import { ApiStatsCtxRoot, BlockAuthorsCtxRoot, BlockEventsCtxRoot, KeyringCtxRoot, QueueCtxRoot, WindowSizeCtxRoot } from '@polkadot/react-hooks'; +import { ApiStatsCtxRoot, BlockAuthorsCtxRoot, BlockEventsCtxRoot, KeyringCtxRoot, PayWithAssetCtxRoot, QueueCtxRoot, WindowSizeCtxRoot } from '@polkadot/react-hooks'; import { settings } from '@polkadot/ui-settings'; import Apps from './Apps.js'; @@ -53,7 +53,9 @@ function Root ({ isElectron, store }: Props): React.ReactElement { - + + + diff --git a/packages/page-assets/src/Balances/index.tsx b/packages/page-assets/src/Balances/index.tsx index c5b0e2bede..d9bcb9a17a 100644 --- a/packages/page-assets/src/Balances/index.tsx +++ b/packages/page-assets/src/Balances/index.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { DropdownItemProps } from 'semantic-ui-react'; -import type { AssetInfo, AssetInfoComplete } from '../types.js'; +import type { AssetInfo, AssetInfoComplete } from '@polkadot/react-hooks/types'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; diff --git a/packages/page-assets/src/Overview/Asset.tsx b/packages/page-assets/src/Overview/Asset.tsx index 6dafedcd4b..476f2f8a78 100644 --- a/packages/page-assets/src/Overview/Asset.tsx +++ b/packages/page-assets/src/Overview/Asset.tsx @@ -1,7 +1,7 @@ // Copyright 2017-2025 @polkadot/app-assets authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { AssetInfo } from '../types.js'; +import type { AssetInfo } from '@polkadot/react-hooks/types'; import React, { useMemo } from 'react'; diff --git a/packages/page-assets/src/Overview/Assets.tsx b/packages/page-assets/src/Overview/Assets.tsx index 1c66e19dc2..673d0a7057 100644 --- a/packages/page-assets/src/Overview/Assets.tsx +++ b/packages/page-assets/src/Overview/Assets.tsx @@ -1,7 +1,7 @@ // Copyright 2017-2025 @polkadot/app-assets authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { AssetInfo } from '../types.js'; +import type { AssetInfo } from '@polkadot/react-hooks/types'; import React, { useRef } from 'react'; diff --git a/packages/page-assets/src/Overview/index.tsx b/packages/page-assets/src/Overview/index.tsx index a13f9f651a..1c3b433df0 100644 --- a/packages/page-assets/src/Overview/index.tsx +++ b/packages/page-assets/src/Overview/index.tsx @@ -1,8 +1,8 @@ // Copyright 2017-2025 @polkadot/app-assets authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { AssetInfo } from '@polkadot/react-hooks/types'; import type { BN } from '@polkadot/util'; -import type { AssetInfo } from '../types.js'; import React, { useState } from 'react'; diff --git a/packages/page-assets/src/index.tsx b/packages/page-assets/src/index.tsx index b00b97a527..bc8d15a021 100644 --- a/packages/page-assets/src/index.tsx +++ b/packages/page-assets/src/index.tsx @@ -12,14 +12,12 @@ import { Route, Routes } from 'react-router'; import { getGenesis } from '@polkadot/apps-config'; import { Tabs } from '@polkadot/react-components'; -import { useAccounts, useApi } from '@polkadot/react-hooks'; +import { useAccounts, useApi, useAssetIds, useAssetInfos } from '@polkadot/react-hooks'; import { BN_ONE } from '@polkadot/util'; import Balances from './Balances/index.js'; import Overview from './Overview/index.js'; import { useTranslation } from './translate.js'; -import useAssetIds from './useAssetIds.js'; -import useAssetInfos from './useAssetInfos.js'; interface Props { basePath: string; diff --git a/packages/page-assets/src/types.ts b/packages/page-assets/src/types.ts deleted file mode 100644 index 3b42f982bd..0000000000 --- a/packages/page-assets/src/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017-2025 @polkadot/app-assets authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import type { PalletAssetsAssetDetails, PalletAssetsAssetMetadata } from '@polkadot/types/lookup'; -import type { BN } from '@polkadot/util'; - -export interface AssetInfo { - details: PalletAssetsAssetDetails | null; - id: BN; - isAdminMe: boolean; - isIssuerMe: boolean; - isFreezerMe: boolean; - isOwnerMe: boolean; - key: string; - metadata: PalletAssetsAssetMetadata | null; -} - -export interface AssetInfoComplete extends AssetInfo { - details: PalletAssetsAssetDetails; - metadata: PalletAssetsAssetMetadata; -} diff --git a/packages/react-components/src/AccountSidebar/JudgementTag.tsx b/packages/react-components/src/AccountSidebar/JudgementTag.tsx index 8852ad6c2a..a0360e91b2 100644 --- a/packages/react-components/src/AccountSidebar/JudgementTag.tsx +++ b/packages/react-components/src/AccountSidebar/JudgementTag.tsx @@ -1,8 +1,7 @@ // Copyright 2017-2025 @polkadot/react-components authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { Judgement } from '@polkadot/react-hooks/types'; -import type { DisplayedJudgement } from '../types.js'; +import type { DisplayedJudgement, Judgement } from '../types.js'; import React, { useMemo } from 'react'; diff --git a/packages/react-components/src/PayWithAsset.tsx b/packages/react-components/src/PayWithAsset.tsx new file mode 100644 index 0000000000..b2656da553 --- /dev/null +++ b/packages/react-components/src/PayWithAsset.tsx @@ -0,0 +1,61 @@ +// Copyright 2017-2025 @polkadot/react-components authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { DropdownItemProps } from 'semantic-ui-react'; + +import React, { useCallback, useEffect, useMemo, useState } from 'react'; + +import { Dropdown } from '@polkadot/react-components'; +import { useApi, usePayWithAsset } from '@polkadot/react-hooks'; +import { BN } from '@polkadot/util'; + +import { useTranslation } from './translate.js'; + +const PayWithAsset = () => { + const { t } = useTranslation(); + const { api } = useApi(); + const [selectedAssetValue, setSelectedAssetValue] = useState('0'); + + const { assetOptions, isDisabled, onChange } = usePayWithAsset(); + + const nativeAsset = useMemo( + () => api.registry.chainTokens[0], + [api] + ); + + const onSearch = useCallback( + (options: DropdownItemProps[], value: string): DropdownItemProps[] => + options.filter((options) => { + const { text: optText, value: optValue } = options as { text: string, value: number }; + + return parseInt(value) === optValue || optText.includes(value); + }), + [] + ); + + const onSelect = useCallback((value: string) => { + onChange(value === nativeAsset ? new BN(-1) : new BN(value), () => setSelectedAssetValue(value)); + }, [nativeAsset, onChange]); + + useEffect((): void => { + const info = assetOptions.find(({ value }) => value === selectedAssetValue); + + // if no info found (usually happens on first load), select the first one automatically + if (!info) { + setSelectedAssetValue(assetOptions.at(0)?.value ?? nativeAsset); + } + }, [assetOptions, selectedAssetValue, nativeAsset]); + + return ( + + ); +}; + +export default React.memo(PayWithAsset); diff --git a/packages/react-components/src/Status/types.ts b/packages/react-components/src/Status/types.ts index 3997211712..0f13efd93a 100644 --- a/packages/react-components/src/Status/types.ts +++ b/packages/react-components/src/Status/types.ts @@ -3,7 +3,8 @@ import type { SubmittableResult } from '@polkadot/api'; import type { SubmittableExtrinsic } from '@polkadot/api/promise/types'; -import type { SignerResult } from '@polkadot/api/types'; +import type { SignerOptions, SignerResult } from '@polkadot/api/types'; +import type { AssetInfoComplete } from '@polkadot/react-hooks/types'; import type { AccountId, Address } from '@polkadot/types/interfaces'; import type { DefinitionRpcExt, Registry, SignerPayloadJSON } from '@polkadot/types/types'; @@ -51,6 +52,7 @@ export interface QueueTx extends AccountInfo { txUpdateCb?: TxCallback; values?: unknown[]; status: QueueTxStatus; + signerOptions?: Partial; } export interface QueueStatus extends ActionStatus { @@ -87,6 +89,7 @@ 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 7e4469c11b..06a7e2b1ab 100644 --- a/packages/react-components/src/TxButton.tsx +++ b/packages/react-components/src/TxButton.tsx @@ -7,7 +7,8 @@ import type { TxButtonProps as Props } from './types.js'; import React, { useCallback, useEffect, useState } from 'react'; -import { useIsMountedRef, useQueue } from '@polkadot/react-hooks'; +import { useApi, useIsMountedRef, usePayWithAsset, useQueue } from '@polkadot/react-hooks'; +import { getFeeAssetLocation } from '@polkadot/react-hooks/utils/getFeeAssetLocation'; import { assert, isFunction } from '@polkadot/util'; import Button from './Button/index.js'; @@ -15,8 +16,10 @@ 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); @@ -76,6 +79,7 @@ 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, @@ -85,7 +89,7 @@ function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon, onClick && onClick(); }, - [_onFailed, _onStart, _onSuccess, accountId, isUnsigned, onClick, onFailed, onSuccess, onUpdate, params, propsExtrinsic, queueExtrinsic, setIsSending, tx, withSpinner, mountedRef] + [_onFailed, _onStart, _onSuccess, accountId, api, isUnsigned, mountedRef, onClick, onFailed, onSuccess, onUpdate, params, propsExtrinsic, queueExtrinsic, selectedFeeAsset, tx, withSpinner] ); if (onSendRef) { diff --git a/packages/react-components/src/index.ts b/packages/react-components/src/index.ts index 1e60ea5b9a..ba21a9d757 100644 --- a/packages/react-components/src/index.ts +++ b/packages/react-components/src/index.ts @@ -74,6 +74,7 @@ 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-components/src/modals/Transfer.tsx b/packages/react-components/src/modals/Transfer.tsx index d7d2347413..4bf0bfb55c 100644 --- a/packages/react-components/src/modals/Transfer.tsx +++ b/packages/react-components/src/modals/Transfer.tsx @@ -19,6 +19,7 @@ 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'; @@ -147,6 +148,9 @@ function Transfer ({ className = '', onClose, recipientId: propRecipientId, send )} + + + {canToggleAll && isAll ? ( diff --git a/packages/react-components/src/types.ts b/packages/react-components/src/types.ts index 72d6b02779..3f36e4bc12 100644 --- a/packages/react-components/src/types.ts +++ b/packages/react-components/src/types.ts @@ -1,10 +1,13 @@ // Copyright 2017-2025 @polkadot/react-components authors & contributors // SPDX-License-Identifier: Apache-2.0 +import '@polkadot/api-augment'; + import type { IconName } from '@fortawesome/fontawesome-svg-core'; import type React from 'react'; import type { SubmittableExtrinsic } from '@polkadot/api/types'; import type { Abi } from '@polkadot/api-contract'; +import type { Registrar } from '@polkadot/react-hooks/types'; import type { AccountId, AccountIndex, Address, Index } from '@polkadot/types/interfaces'; import type { ActionStatus, TxCallback, TxFailedCallback } from './Status/types.js'; @@ -85,6 +88,13 @@ export type AccountIdIsh = AccountId | AccountIndex | Address | string | Uint8Ar export type DisplayedJudgement = 'Erroneous' | 'Low quality' | 'Known good' | 'Reasonable'; +export interface Judgement { + judgementName: DisplayedJudgement; + registrars: (Registrar | undefined)[]; +} + +export type UseJudgements = Judgement[] + export interface TabItem { alias?: string; count?: number; diff --git a/packages/react-hooks/src/ctx/PayWithAsset.tsx b/packages/react-hooks/src/ctx/PayWithAsset.tsx new file mode 100644 index 0000000000..982c514aa0 --- /dev/null +++ b/packages/react-hooks/src/ctx/PayWithAsset.tsx @@ -0,0 +1,91 @@ +// Copyright 2017-2025 @polkadot/react-hooks authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +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 { useApi, useAssetIds, useAssetInfos } from '@polkadot/react-hooks'; +import { formatNumber } from '@polkadot/util'; + +interface Props { + children?: React.ReactNode; +} + +const EMPTY_STATE: PayWithAsset = { + assetOptions: [], + isDisabled: true, + // eslint-disable-next-line @typescript-eslint/no-empty-function + onChange: () => {}, + selectedFeeAsset: null +}; + +export const PayWithAssetCtx = React.createContext(EMPTY_STATE); + +export function PayWithAssetCtxRoot ({ children }: Props): React.ReactElement { + const { isApiReady } = useApi(); + + return isApiReady ? {children} : <>{children}; +} + +function PayWithAssetProvider ({ children }: Props): React.ReactElement { + const { api } = useApi(); + const ids = useAssetIds(); + const assetInfos = useAssetInfos(ids); + const [selectedFeeAsset, setSelectedFeeAsset] = useState(null); + + const nativeAsset = useMemo( + () => api.registry.chainTokens[0], + [api] + ); + + const completeAssetInfos = useMemo( + () => (assetInfos + ?.filter((i): i is AssetInfoComplete => + !!(i.details && i.metadata) && !i.details.supply.isZero() && !!i.details?.toJSON().isSufficient) + .sort((a, b) => a.id.cmp(b.id))) || [], + [assetInfos] + ); + + const assetOptions = useMemo( + () => [ + { text: `${nativeAsset} (Native)`, value: nativeAsset }, + ...completeAssetInfos.map(({ id, metadata }) => ({ + text: `${metadata.name.toUtf8()} (${formatNumber(id)})`, + value: id.toString() + }))], + [completeAssetInfos, nativeAsset] + ); + + const onChange = useCallback((assetId: BN, cb?: () => void) => { + const selectedFeeAsset = completeAssetInfos.find((a) => a.id.toString() === assetId.toString()); + + setSelectedFeeAsset(selectedFeeAsset ?? null); + cb?.(); + }, [completeAssetInfos]); + + const isEnabled = useMemo(() => + api.registry.metadata.extrinsic.signedExtensions.some( + (a) => a.identifier.toString() === 'ChargeAssetTxPayment' + ) && + api.tx.assetConversion && completeAssetInfos.length > 0, + [api.registry.metadata.extrinsic.signedExtensions, api.tx.assetConversion, completeAssetInfos.length] + ); + + const values: PayWithAsset = useMemo(() => { + return { + assetOptions, + isDisabled: !isEnabled, + onChange, + selectedFeeAsset + }; + }, [assetOptions, isEnabled, onChange, selectedFeeAsset]); + + return ( + + {children} + + ); +} diff --git a/packages/react-hooks/src/ctx/index.ts b/packages/react-hooks/src/ctx/index.ts index a9f07550c8..fe2e5ecb53 100644 --- a/packages/react-hooks/src/ctx/index.ts +++ b/packages/react-hooks/src/ctx/index.ts @@ -5,5 +5,6 @@ export { ApiStatsCtxRoot } from './ApiStats.js'; export { BlockAuthorsCtxRoot } from './BlockAuthors.js'; export { BlockEventsCtxRoot } from './BlockEvents.js'; export { KeyringCtxRoot } from './Keyring.js'; +export { PayWithAssetCtxRoot } from './PayWithAsset.js'; export { QueueCtxRoot } from './Queue.js'; export { WindowSizeCtxRoot } from './WindowSize.js'; diff --git a/packages/react-hooks/src/ctx/types.ts b/packages/react-hooks/src/ctx/types.ts index bdf74409db..10e936d3f9 100644 --- a/packages/react-hooks/src/ctx/types.ts +++ b/packages/react-hooks/src/ctx/types.ts @@ -9,6 +9,8 @@ import type { LinkOption } from '@polkadot/apps-config/endpoints/types'; import type { InjectedExtension } from '@polkadot/extension-inject/types'; import type { ProviderStats } from '@polkadot/rpc-provider/types'; import type { BlockNumber, EventRecord } from '@polkadot/types/interfaces'; +import type { BN } from '@polkadot/util'; +import type { AssetInfoComplete } from '../types.js'; export interface ApiState { apiDefaultTx: SubmittableExtrinsicFunction; @@ -66,6 +68,13 @@ export interface ApiStats { when: number; } +export interface PayWithAsset { + isDisabled: boolean; + assetOptions: {text: string, value: string}[]; + onChange: (assetId: BN, cb?: () => void) => void; + selectedFeeAsset: AssetInfoComplete | null; +} + export interface BlockAuthors { byAuthor: Record; eraPoints: Record; diff --git a/packages/react-hooks/src/index.ts b/packages/react-hooks/src/index.ts index 621241373c..332fbebed8 100644 --- a/packages/react-hooks/src/index.ts +++ b/packages/react-hooks/src/index.ts @@ -13,6 +13,8 @@ export { useAddresses } from './useAddresses.js'; export { useApi } from './useApi.js'; export { useApiStats } from './useApiStats.js'; export { useApiUrl } from './useApiUrl.js'; +export { useAssetIds } from './useAssetIds.js'; +export { useAssetInfos } from './useAssetInfos.js'; export { useAvailableSlashes } from './useAvailableSlashes.js'; export { useBalancesAll } from './useBalancesAll.js'; export { useBestHash } from './useBestHash.js'; @@ -69,6 +71,7 @@ export { useOwnStashInfos } from './useOwnStashInfos.js'; export { useParaApi } from './useParaApi.js'; export { useIsParasLinked, useParaEndpoints } from './useParaEndpoints.js'; export { usePassword } from './usePassword.js'; +export { usePayWithAsset } from './usePayWithAsset.js'; export { usePeopleEndpoint } from './usePeopleEndpoint.js'; export { usePopupWindow } from './usePopupWindow.js'; export { usePreimage } from './usePreimage.js'; diff --git a/packages/react-hooks/src/types.ts b/packages/react-hooks/src/types.ts index e344f82d33..730148efbb 100644 --- a/packages/react-hooks/src/types.ts +++ b/packages/react-hooks/src/types.ts @@ -1,14 +1,15 @@ // Copyright 2017-2025 @polkadot/react-hooks authors & contributors // SPDX-License-Identifier: Apache-2.0 +import '@polkadot/api-augment'; + import type React from 'react'; import type { ApiPromise } from '@polkadot/api'; import type { SubmittableExtrinsic } from '@polkadot/api/types'; import type { DeriveAccountFlags, DeriveAccountRegistration } from '@polkadot/api-derive/types'; -import type { DisplayedJudgement } from '@polkadot/react-components/types'; import type { Option, u32, u128, Vec } from '@polkadot/types'; import type { AccountId, BlockNumber, Call, Hash, SessionIndex, ValidatorPrefs } from '@polkadot/types/interfaces'; -import type { PalletPreimageRequestStatus, PalletStakingRewardDestination, PalletStakingStakingLedger, PolkadotRuntimeParachainsAssignerCoretimeCoreDescriptor, SpStakingExposurePage, SpStakingPagedExposureMetadata } from '@polkadot/types/lookup'; +import type { PalletAssetsAssetDetails, PalletAssetsAssetMetadata, PalletPreimageRequestStatus, PalletStakingRewardDestination, PalletStakingStakingLedger, PolkadotRuntimeParachainsAssignerCoretimeCoreDescriptor, SpStakingExposurePage, SpStakingPagedExposureMetadata } from '@polkadot/types/lookup'; import type { ICompact, IExtrinsic, INumber } from '@polkadot/types/types'; import type { KeyringJson$Meta } from '@polkadot/ui-keyring/types'; import type { BN } from '@polkadot/util'; @@ -52,6 +53,22 @@ export interface Inflation { stakedReturn: number; } +export interface AssetInfo { + details: PalletAssetsAssetDetails | null; + id: BN; + isAdminMe: boolean; + isIssuerMe: boolean; + isFreezerMe: boolean; + isOwnerMe: boolean; + key: string; + metadata: PalletAssetsAssetMetadata | null; +} + +export interface AssetInfoComplete extends AssetInfo { + details: PalletAssetsAssetDetails; + metadata: PalletAssetsAssetMetadata; +} + export interface Slash { accountId: AccountId; amount: u128; @@ -165,13 +182,6 @@ export interface Registrar { index: number; } -export interface Judgement { - judgementName: DisplayedJudgement; - registrars: (Registrar | undefined)[]; -} - -export type UseJudgements = Judgement[] - export type BatchType = 'all' | 'default' | 'force'; export interface BatchOptions { diff --git a/packages/page-assets/src/useAssetIds.ts b/packages/react-hooks/src/useAssetIds.ts similarity index 67% rename from packages/page-assets/src/useAssetIds.ts rename to packages/react-hooks/src/useAssetIds.ts index 17d3238249..312a9d0f7a 100644 --- a/packages/page-assets/src/useAssetIds.ts +++ b/packages/react-hooks/src/useAssetIds.ts @@ -1,11 +1,11 @@ -// Copyright 2017-2025 @polkadot/app-assets authors & contributors +// Copyright 2017-2025 @polkadot/react-hooks authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { Changes } from '@polkadot/react-hooks/useEventChanges'; import type { StorageKey, u32 } from '@polkadot/types'; import type { EventRecord } from '@polkadot/types/interfaces'; +import type { Changes } from './useEventChanges.js'; -import { createNamedHook, useApi, useEventChanges, useMapKeys } from '@polkadot/react-hooks'; +import { createNamedHook, useApi, useEventChanges, useMapKeys } from './index.js'; const EMPTY_PARAMS: unknown[] = []; @@ -31,13 +31,13 @@ function filter (records: EventRecord[]): Changes { function useAssetIdsImpl (): u32[] | undefined { const { api } = useApi(); - const startValue = useMapKeys(api.query.assets.asset, EMPTY_PARAMS, OPT_KEY); + const startValue = useMapKeys(api.query.assets?.asset, EMPTY_PARAMS, OPT_KEY) || []; return useEventChanges([ - api.events.assets.Created, - api.events.assets.Destroyed, - api.events.assets.ForceCreated + api.events.assets?.Created, + api.events.assets?.Destroyed, + api.events.assets?.ForceCreated ], filter, startValue); } -export default createNamedHook('useAssetIds', useAssetIdsImpl); +export const useAssetIds = createNamedHook('useAssetIds', useAssetIdsImpl); diff --git a/packages/page-assets/src/useAssetInfos.ts b/packages/react-hooks/src/useAssetInfos.ts similarity index 79% rename from packages/page-assets/src/useAssetInfos.ts rename to packages/react-hooks/src/useAssetInfos.ts index 870cbabe71..c984bc5f81 100644 --- a/packages/page-assets/src/useAssetInfos.ts +++ b/packages/react-hooks/src/useAssetInfos.ts @@ -1,4 +1,4 @@ -// Copyright 2017-2025 @polkadot/app-assets authors & contributors +// Copyright 2017-2025 @polkadot/react-hooks authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { Option } from '@polkadot/types'; @@ -7,7 +7,7 @@ import type { PalletAssetsAssetDetails, PalletAssetsAssetMetadata } from '@polka import type { BN } from '@polkadot/util'; import type { AssetInfo } from './types.js'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { createNamedHook, useAccounts, useApi, useCall } from '@polkadot/react-hooks'; @@ -51,8 +51,11 @@ function extractInfo (allAccounts: string[], id: BN, optDetails: Option(api.query.assets.metadata.multi, [ids], QUERY_OPTS); - const details = useCall<[[BN[]], Option[]]>(api.query.assets.asset.multi, [ids], QUERY_OPTS); + + const isReady = useMemo(() => !!api.tx.assets?.setMetadata && !!api.tx.assets?.transferKeepAlive, [api.tx.assets?.setMetadata, api.tx.assets?.transferKeepAlive]); + + const metadata = useCall<[[BN[]], PalletAssetsAssetMetadata[]]>(isReady && api.query.assets.metadata.multi, [ids], QUERY_OPTS); + const details = useCall<[[BN[]], Option[]]>(isReady && api.query.assets.asset.multi, [ids], QUERY_OPTS); const [state, setState] = useState(); useEffect((): void => { @@ -67,4 +70,4 @@ function useAssetInfosImpl (ids?: BN[]): AssetInfo[] | undefined { return state; } -export default createNamedHook('useAssetInfos', useAssetInfosImpl); +export const useAssetInfos = createNamedHook('useAssetInfos', useAssetInfosImpl); diff --git a/packages/react-hooks/src/useJudgements.tsx b/packages/react-hooks/src/useJudgements.tsx index 2f0b0b74f0..3efbe57e0a 100644 --- a/packages/react-hooks/src/useJudgements.tsx +++ b/packages/react-hooks/src/useJudgements.tsx @@ -1,7 +1,7 @@ // Copyright 2017-2025 @polkadot/react-hooks authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { UseJudgements } from './types.js'; +import type { UseJudgements } from '@polkadot/react-components/types'; import { useMemo } from 'react'; diff --git a/packages/react-hooks/src/usePayWithAsset.ts b/packages/react-hooks/src/usePayWithAsset.ts new file mode 100644 index 0000000000..b13cebe714 --- /dev/null +++ b/packages/react-hooks/src/usePayWithAsset.ts @@ -0,0 +1,15 @@ +// Copyright 2017-2025 @polkadot/react-hooks authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { PayWithAsset } from './ctx/types.js'; + +import { useContext } from 'react'; + +import { PayWithAssetCtx } from './ctx/PayWithAsset.js'; +import { createNamedHook } from './createNamedHook.js'; + +function usePayWithAssetImpl (): PayWithAsset { + return useContext(PayWithAssetCtx); +} + +export const usePayWithAsset = createNamedHook('usePayWithAsset', usePayWithAssetImpl); diff --git a/packages/react-hooks/src/utils/getFeeAssetLocation.ts b/packages/react-hooks/src/utils/getFeeAssetLocation.ts new file mode 100644 index 0000000000..db438b4dbe --- /dev/null +++ b/packages/react-hooks/src/utils/getFeeAssetLocation.ts @@ -0,0 +1,25 @@ +// Copyright 2017-2025 @polkadot/react-hooks authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { ApiPromise } from '@polkadot/api'; +import type { AnyNumber } from '@polkadot/types-codec/types'; +import type { AssetInfoComplete } from '../types.js'; + +export const getFeeAssetLocation = (api: ApiPromise, feeAsset: AssetInfoComplete | null): AnyNumber | object | undefined => { + if (!feeAsset?.id) { + return undefined; + } + + const metadata = api.registry.metadata; + + const palletIndex = metadata.pallets.filter((a) => a.name.toString() === 'Assets')[0].index.toString(); + + // FIX ME: Might have to fix it later as it may not be applicable for all chains + const palletInstance = { PalletInstance: palletIndex }; + const generalIndex = { GeneralIndex: feeAsset.id }; + + return { + interior: { X2: [palletInstance, generalIndex] }, + parents: 0 + }; +}; diff --git a/packages/react-hooks/src/utils/matchRegistrarAccountsWithIndexes.ts b/packages/react-hooks/src/utils/matchRegistrarAccountsWithIndexes.ts index 6d7baa779f..fc06df625a 100644 --- a/packages/react-hooks/src/utils/matchRegistrarAccountsWithIndexes.ts +++ b/packages/react-hooks/src/utils/matchRegistrarAccountsWithIndexes.ts @@ -1,8 +1,9 @@ // Copyright 2017-2025 @polkadot/react-hooks authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { Judgement } from '@polkadot/react-components/types'; import type { SortedJudgements } from '@polkadot/react-components/util/types'; -import type { Judgement, Registrar } from '../types.js'; +import type { Registrar } from '../types.js'; export function matchRegistrarAccountsWithIndexes ( judgementsWithRegistrarIndexes: SortedJudgements, diff --git a/packages/react-hooks/tsconfig.xref.json b/packages/react-hooks/tsconfig.xref.json index 30dcfbd702..34aafddbfe 100644 --- a/packages/react-hooks/tsconfig.xref.json +++ b/packages/react-hooks/tsconfig.xref.json @@ -7,7 +7,9 @@ }, "include": [ "src/ctx/Api.tsx", - "src/ctx/types.ts" + "src/ctx/types.ts", + "src/types.ts", + "src/constants.ts", ], "references": [ { "path": "../apps-config/tsconfig.build.json" } diff --git a/packages/react-signer/src/PaymentInfo.tsx b/packages/react-signer/src/PaymentInfo.tsx index bf47b32559..c1300cfbde 100644 --- a/packages/react-signer/src/PaymentInfo.tsx +++ b/packages/react-signer/src/PaymentInfo.tsx @@ -3,15 +3,15 @@ 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 { BN } from '@polkadot/util'; import React, { useEffect, useState } from 'react'; import { Trans } from 'react-i18next'; import { Expander, MarkWarning } from '@polkadot/react-components'; import { useApi, useCall, useIsMountedRef } from '@polkadot/react-hooks'; -import { formatBalance, nextTick } from '@polkadot/util'; +import { BN, formatBalance, nextTick } from '@polkadot/util'; import { useTranslation } from './translate.js'; @@ -22,9 +22,10 @@ interface Props { isHeader?: boolean; onChange?: (hasAvailable: boolean) => void; tip?: BN; + signerOptions?: QueueTx['signerOptions']; } -function PaymentInfo ({ accountId, className = '', extrinsic, isHeader }: Props): React.ReactElement | null { +function PaymentInfo ({ accountId, className = '', extrinsic, isHeader, signerOptions }: Props): React.ReactElement | null { const { t } = useTranslation(); const { api } = useApi(); const [dispatchInfo, setDispatchInfo] = useState(null); @@ -35,14 +36,30 @@ function PaymentInfo ({ accountId, className = '', extrinsic, isHeader }: Props) accountId && extrinsic && extrinsic.hasPaymentInfo && nextTick(async (): Promise => { try { - const info = await extrinsic.paymentInfo(accountId); + const info = await extrinsic.paymentInfo(accountId, signerOptions); - mountedRef.current && setDispatchInfo(info); + if (signerOptions?.assetId) { + const convertedFee = new BN((await api.call.assetConversionApi.quotePriceTokensForExactTokens( + signerOptions?.assetId as string, + { + interior: 'Here', + parents: 1 + } as unknown as string, + info.partialFee, + true + )).toString()); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + mountedRef.current && setDispatchInfo({ ...info, partialFee: convertedFee }); + } else { + mountedRef.current && setDispatchInfo(info); + } } catch (error) { console.error(error); } }); - }, [api, accountId, extrinsic, mountedRef]); + }, [api, accountId, extrinsic, mountedRef, signerOptions]); if (!dispatchInfo || !extrinsic) { return null; @@ -60,7 +77,10 @@ function PaymentInfo ({ accountId, className = '', extrinsic, isHeader }: Props) isHeader={isHeader} summary={ - Fees of {formatBalance(dispatchInfo.partialFee, { withSiFull: true })} will be applied to the submission + Fees of + {formatBalance(dispatchInfo.partialFee, { decimals: signerOptions?.feeAsset?.metadata.decimals.toNumber() ?? api.registry.chainDecimals.at(0), withSiFull: true }).split(' ').slice(0, -1).join(' ')}{' '} + {signerOptions?.feeAsset?.metadata.symbol.toHuman()?.toString() ?? api.registry.chainTokens.at(0) } + will be applied to the submission } /> diff --git a/packages/react-signer/src/Transaction.tsx b/packages/react-signer/src/Transaction.tsx index 9aee0cf068..9cc9ce5014 100644 --- a/packages/react-signer/src/Transaction.tsx +++ b/packages/react-signer/src/Transaction.tsx @@ -20,7 +20,7 @@ interface Props { tip?: BN; } -function Transaction ({ accountId, className, currentItem: { extrinsic, isUnsigned, payload }, onError, tip }: Props): React.ReactElement | null { +function Transaction ({ accountId, className, currentItem: { extrinsic, isUnsigned, payload, signerOptions }, onError, tip }: Props): React.ReactElement | null { const { t } = useTranslation(); if (!extrinsic) { @@ -43,6 +43,7 @@ function Transaction ({ accountId, className, currentItem: { extrinsic, isUnsign className='paymentInfo' extrinsic={extrinsic} isHeader + signerOptions={signerOptions} tip={tip} /> )} diff --git a/packages/react-signer/src/TxSigned.tsx b/packages/react-signer/src/TxSigned.tsx index ce902b5c50..54016e2219 100644 --- a/packages/react-signer/src/TxSigned.tsx +++ b/packages/react-signer/src/TxSigned.tsx @@ -339,7 +339,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 }, getLedger, setQrState) + extractParams(api, senderInfo.signAddress, { nonce: -1, tip, withSignedTransaction: true, ...currentItem.signerOptions }, getLedger, setQrState) ]); queueSetTxStatus(currentItem.id, status);