From 6e04303938d2bab3c98ed50ae295461b44a373b7 Mon Sep 17 00:00:00 2001 From: aidan-starke Date: Mon, 7 Nov 2022 16:15:17 +1300 Subject: [PATCH 1/5] Add mapping for chains with seperate fee asset --- packages/apps-config/src/settings/feeAssets.ts | 13 +++++++++++++ packages/apps-config/src/settings/index.ts | 1 + packages/apps-config/src/settings/types.ts | 7 +++++++ 3 files changed, 21 insertions(+) create mode 100644 packages/apps-config/src/settings/feeAssets.ts diff --git a/packages/apps-config/src/settings/feeAssets.ts b/packages/apps-config/src/settings/feeAssets.ts new file mode 100644 index 00000000000..930eca04d01 --- /dev/null +++ b/packages/apps-config/src/settings/feeAssets.ts @@ -0,0 +1,13 @@ +// Copyright 2017-2022 @polkadot/apps-config authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { ChainSpecName, FeeAsset } from './types'; + +// A mapping of chains to their fee asset if different to the native token +export const feeAssets: Record = { + root: { + assetId: 2, + decimals: 6, + symbol: 'XRP' + } +}; diff --git a/packages/apps-config/src/settings/index.ts b/packages/apps-config/src/settings/index.ts index ed1b88ccdee..c0622ada9fc 100644 --- a/packages/apps-config/src/settings/index.ts +++ b/packages/apps-config/src/settings/index.ts @@ -2,5 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 export * from './ethereumChains'; +export * from './feeAssets'; export * from './languages'; export * from './ss58'; diff --git a/packages/apps-config/src/settings/types.ts b/packages/apps-config/src/settings/types.ts index 164b36cb09e..5c2574f487e 100644 --- a/packages/apps-config/src/settings/types.ts +++ b/packages/apps-config/src/settings/types.ts @@ -21,3 +21,10 @@ export interface LinkOption extends Option { paraId?: number; textBy: string; } + +export type ChainSpecName = string; +export interface FeeAsset { + assetId: number; + symbol: string; + decimals: number; +} From bde41d98d1a4a4ebb8a5838dbb91400fd39067b3 Mon Sep 17 00:00:00 2001 From: aidan-starke Date: Mon, 7 Nov 2022 16:15:41 +1300 Subject: [PATCH 2/5] Display fee / fee error based on fee asset --- packages/react-hooks/src/index.ts | 1 + .../react-hooks/src/useFeeAssetBalance.ts | 40 +++++++++++++++++++ packages/react-signer/src/PaymentInfo.tsx | 28 +++++++------ 3 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 packages/react-hooks/src/useFeeAssetBalance.ts diff --git a/packages/react-hooks/src/index.ts b/packages/react-hooks/src/index.ts index 4a30ebbd3cb..2873a7d82f6 100644 --- a/packages/react-hooks/src/index.ts +++ b/packages/react-hooks/src/index.ts @@ -30,6 +30,7 @@ export { useEventChanges } from './useEventChanges'; export { useEventTrigger } from './useEventTrigger'; export { useExtrinsicTrigger } from './useExtrinsicTrigger'; export { useFavorites } from './useFavorites'; +export { useFeeAssetBalance } from './useFeeAssetBalance'; export { useFormField } from './useFormField'; export { useIncrement } from './useIncrement'; export { useInflation } from './useInflation'; diff --git a/packages/react-hooks/src/useFeeAssetBalance.ts b/packages/react-hooks/src/useFeeAssetBalance.ts new file mode 100644 index 00000000000..1639a01f4d0 --- /dev/null +++ b/packages/react-hooks/src/useFeeAssetBalance.ts @@ -0,0 +1,40 @@ +// Copyright 2017-2022 @polkadot/react-signer authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { FeeAsset } from '@polkadot/apps-config/settings/types'; +import type { Option } from '@polkadot/types'; +import type { PalletAssetsAssetAccount } from '@polkadot/types/lookup'; +import type { BN } from '@polkadot/util'; + +import { useEffect, useState } from 'react'; + +import { feeAssets } from '@polkadot/apps-config'; +import { useApi, useCall } from '@polkadot/react-hooks'; + +export const useFeeAssetBalance = (accountId: string | null): [ + FeeAsset, + BN | null +] => { + const { api } = useApi(); + const [feeBalance, setFeeBalance] = useState(null); + const feeAsset = feeAssets[api.runtimeVersion.specName.toString()]; + const assetsAccount = useCall>(api.query.assets.account, [feeAsset?.assetId, accountId]); + + useEffect(() => { + if (!assetsAccount) { + return; + } + + if (assetsAccount.isSome) { + const { balance } = assetsAccount.unwrap(); + + setFeeBalance(balance); + } + + if (assetsAccount.isNone) { + setFeeBalance(api.registry.createType('Balance', 0)); + } + }, [api, assetsAccount]); + + return [feeAsset, feeBalance]; +}; diff --git a/packages/react-signer/src/PaymentInfo.tsx b/packages/react-signer/src/PaymentInfo.tsx index abfe49d5d6d..97fe4840888 100644 --- a/packages/react-signer/src/PaymentInfo.tsx +++ b/packages/react-signer/src/PaymentInfo.tsx @@ -10,7 +10,7 @@ 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 { useApi, useCall, useFeeAssetBalance, useIsMountedRef } from '@polkadot/react-hooks'; import { formatBalance, nextTick } from '@polkadot/util'; import { useTranslation } from './translate'; @@ -31,39 +31,43 @@ function PaymentInfo ({ accountId, className = '', extrinsic }: Props): React.Re const balances = useCall(api.derive.balances?.all, [accountId]); const mountedRef = useIsMountedRef(); + const [feeAsset, feeAssetBalance] = useFeeAssetBalance(accountId); + useEffect((): void => { accountId && extrinsic && api.call.transactionPaymentApi && - nextTick(async (): Promise => { - try { - const info = await extrinsic.paymentInfo(accountId); + nextTick(async (): Promise => { + try { + const info = await extrinsic.paymentInfo(accountId); - mountedRef.current && setDispatchInfo(info); - } catch (error) { - console.error(error); - } - }); + mountedRef.current && setDispatchInfo(info); + } catch (error) { + console.error(error); + } + }); }, [api, accountId, extrinsic, mountedRef]); if (!dispatchInfo || !extrinsic) { return null; } - const isFeeError = api.consts.balances && !api.tx.balances?.transfer.is(extrinsic) && balances?.accountId.eq(accountId) && ( + const isFeeError = !feeAsset && api.consts.balances && !api.tx.balances?.transfer.is(extrinsic) && balances?.accountId.eq(accountId) && ( balances.availableBalance.lte(dispatchInfo.partialFee) || balances.freeBalance.sub(dispatchInfo.partialFee).lte(api.consts.balances.existentialDeposit) ); + const isAssetFeeError = api.query.assets && feeAssetBalance?.lte(dispatchInfo.partialFee); + return ( <> - Fees of {formatBalance(dispatchInfo.partialFee, { withSiFull: true })} will be applied to the submission + Fees of {formatBalance(dispatchInfo.partialFee, { withSiFull: true, withUnit: feeAsset?.symbol })} will be applied to the submission } /> - {isFeeError && ( + {(isFeeError || isAssetFeeError) && ( ('The account does not have enough free funds (excluding locked/bonded/reserved) available to cover the transaction fees without dropping the balance below the account existential amount.')} /> )} From e135d8ffc6c9a1d6fd8facc1d529d229dee00254 Mon Sep 17 00:00:00 2001 From: aidan-starke Date: Tue, 8 Nov 2022 06:56:50 +1300 Subject: [PATCH 3/5] Cleanup --- packages/react-signer/src/PaymentInfo.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/react-signer/src/PaymentInfo.tsx b/packages/react-signer/src/PaymentInfo.tsx index 97fe4840888..5087aed6ff5 100644 --- a/packages/react-signer/src/PaymentInfo.tsx +++ b/packages/react-signer/src/PaymentInfo.tsx @@ -35,15 +35,15 @@ function PaymentInfo ({ accountId, className = '', extrinsic }: Props): React.Re useEffect((): void => { accountId && extrinsic && api.call.transactionPaymentApi && - nextTick(async (): Promise => { - try { - const info = await extrinsic.paymentInfo(accountId); + nextTick(async (): Promise => { + try { + const info = await extrinsic.paymentInfo(accountId); - mountedRef.current && setDispatchInfo(info); - } catch (error) { - console.error(error); - } - }); + mountedRef.current && setDispatchInfo(info); + } catch (error) { + console.error(error); + } + }); }, [api, accountId, extrinsic, mountedRef]); if (!dispatchInfo || !extrinsic) { @@ -55,7 +55,7 @@ function PaymentInfo ({ accountId, className = '', extrinsic }: Props): React.Re balances.freeBalance.sub(dispatchInfo.partialFee).lte(api.consts.balances.existentialDeposit) ); - const isAssetFeeError = api.query.assets && feeAssetBalance?.lte(dispatchInfo.partialFee); + const isAssetFeeError = feeAsset && feeAssetBalance?.lte(dispatchInfo.partialFee); return ( <> From 014c2d35e7d5f59bbdf74cc8f5dae964a1b09986 Mon Sep 17 00:00:00 2001 From: aidan-starke Date: Tue, 8 Nov 2022 07:11:30 +1300 Subject: [PATCH 4/5] Use `feeAsset?.decimals` --- packages/react-signer/src/PaymentInfo.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/react-signer/src/PaymentInfo.tsx b/packages/react-signer/src/PaymentInfo.tsx index 5087aed6ff5..3bd12b14122 100644 --- a/packages/react-signer/src/PaymentInfo.tsx +++ b/packages/react-signer/src/PaymentInfo.tsx @@ -27,12 +27,11 @@ interface Props { function PaymentInfo ({ accountId, className = '', extrinsic }: Props): React.ReactElement | null { const { t } = useTranslation(); const { api } = useApi(); + const [feeAsset, feeAssetBalance] = useFeeAssetBalance(accountId); const [dispatchInfo, setDispatchInfo] = useState(null); const balances = useCall(api.derive.balances?.all, [accountId]); const mountedRef = useIsMountedRef(); - const [feeAsset, feeAssetBalance] = useFeeAssetBalance(accountId); - useEffect((): void => { accountId && extrinsic && api.call.transactionPaymentApi && nextTick(async (): Promise => { @@ -55,7 +54,7 @@ function PaymentInfo ({ accountId, className = '', extrinsic }: Props): React.Re balances.freeBalance.sub(dispatchInfo.partialFee).lte(api.consts.balances.existentialDeposit) ); - const isAssetFeeError = feeAsset && feeAssetBalance?.lte(dispatchInfo.partialFee); + const isFeeAssetError = feeAsset && feeAssetBalance?.lte(dispatchInfo.partialFee); return ( <> @@ -63,11 +62,11 @@ function PaymentInfo ({ accountId, className = '', extrinsic }: Props): React.Re className={className} summary={ - Fees of {formatBalance(dispatchInfo.partialFee, { withSiFull: true, withUnit: feeAsset?.symbol })} will be applied to the submission + Fees of {formatBalance(dispatchInfo.partialFee, { decimals: feeAsset?.decimals, withSiFull: true, withUnit: feeAsset?.symbol })} will be applied to the submission } /> - {(isFeeError || isAssetFeeError) && ( + {(isFeeError || isFeeAssetError) && ( ('The account does not have enough free funds (excluding locked/bonded/reserved) available to cover the transaction fees without dropping the balance below the account existential amount.')} /> )} From e8996f23ef560f5b47dcc39488258d67981b60fc Mon Sep 17 00:00:00 2001 From: aidan-starke Date: Tue, 8 Nov 2022 07:54:11 +1300 Subject: [PATCH 5/5] Prevent error if no assets pallet --- packages/react-hooks/src/useFeeAssetBalance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-hooks/src/useFeeAssetBalance.ts b/packages/react-hooks/src/useFeeAssetBalance.ts index 1639a01f4d0..6bf26ca5a31 100644 --- a/packages/react-hooks/src/useFeeAssetBalance.ts +++ b/packages/react-hooks/src/useFeeAssetBalance.ts @@ -18,7 +18,7 @@ export const useFeeAssetBalance = (accountId: string | null): [ const { api } = useApi(); const [feeBalance, setFeeBalance] = useState(null); const feeAsset = feeAssets[api.runtimeVersion.specName.toString()]; - const assetsAccount = useCall>(api.query.assets.account, [feeAsset?.assetId, accountId]); + const assetsAccount = useCall>(api.query.assets?.account, [feeAsset?.assetId, accountId]); useEffect(() => { if (!assetsAccount) {