From af14f519f507fafbcad2eac6ffaf0afaff2fd4ae Mon Sep 17 00:00:00 2001 From: tunghp2002 Date: Wed, 9 Oct 2024 18:25:43 +0700 Subject: [PATCH 01/11] [Add] Extension - Add check balance condition --- .../src/core/logic-validation/transfer.ts | 21 +++++++++++------ .../src/core/substrate/system-pallet.ts | 2 +- .../src/koni/background/handlers/Extension.ts | 18 ++++++++++----- .../src/services/balance-service/index.ts | 23 +++++++++++++++++-- .../src/types/transaction/error.ts | 1 + 5 files changed, 49 insertions(+), 16 deletions(-) diff --git a/packages/extension-base/src/core/logic-validation/transfer.ts b/packages/extension-base/src/core/logic-validation/transfer.ts index 78ea7e4f83..f739f8337a 100644 --- a/packages/extension-base/src/core/logic-validation/transfer.ts +++ b/packages/extension-base/src/core/logic-validation/transfer.ts @@ -6,7 +6,7 @@ import { TransactionError } from '@subwallet/extension-base/background/errors/Tr import { _Address, AmountData, ExtrinsicDataTypeMap, ExtrinsicType, FeeData } from '@subwallet/extension-base/background/KoniTypes'; import { TransactionWarning } from '@subwallet/extension-base/background/warnings/TransactionWarning'; import { LEDGER_SIGNING_COMPATIBLE_MAP, SIGNING_COMPATIBLE_MAP, XCM_MIN_AMOUNT_RATIO } from '@subwallet/extension-base/constants'; -import { _canAccountBeReaped } from '@subwallet/extension-base/core/substrate/system-pallet'; +import { _canAccountBeReaped, _isAccountActive } from '@subwallet/extension-base/core/substrate/system-pallet'; import { FrameSystemAccountInfo } from '@subwallet/extension-base/core/substrate/types'; import { isBounceableAddress } from '@subwallet/extension-base/services/balance-service/helpers/subscribe/ton/utils'; import { _TRANSFER_CHAIN_GROUP } from '@subwallet/extension-base/services/chain-service/constants'; @@ -56,7 +56,7 @@ export function validateTransferRequest (tokenInfo: _ChainAsset, from: _Address, return [errors, keypair, transferValue]; } -export function additionalValidateTransfer (tokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverTransferTokenFreeBalance: string, transferAmount: string, senderTransferTokenTransferable?: string, receiverNativeTransferable?: string): [TransactionWarning[], TransactionError[]] { +export function additionalValidateTransfer (tokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverTransferTokenTotalBalance: string, transferAmount: string, senderTransferTokenTransferable?: string, _receiverNativeTotal?: string, isReceiverActive?: unknown): [TransactionWarning[], TransactionError[]] { const minAmount = _getTokenMinAmount(tokenInfo); const nativeMinAmount = _getTokenMinAmount(nativeTokenInfo); const warnings: TransactionWarning[] = []; @@ -72,17 +72,24 @@ export function additionalValidateTransfer (tokenInfo: _ChainAsset, nativeTokenI } // Check ed for receiver before sending - if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN && receiverNativeTransferable) { - if (new BigN(receiverNativeTransferable).lt(nativeMinAmount)) { - const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('The recipient account has {{amount}} {{nativeSymbol}} which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: receiverNativeTransferable, nativeSymbol: nativeTokenInfo.symbol, localSymbol: tokenInfo.symbol } })); + if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN && _receiverNativeTotal) { + if (new BigN(_receiverNativeTotal).lt(nativeMinAmount) && new BigN(nativeMinAmount).gt(0)) { + const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('The recipient account has {{amount}} {{nativeSymbol}} which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: _receiverNativeTotal, nativeSymbol: nativeTokenInfo.symbol, localSymbol: tokenInfo.symbol } })); errors.push(error); } } + // Check if receiver's account is active + if (isReceiverActive && _isAccountActive(isReceiverActive as FrameSystemAccountInfo)) { + const error = new TransactionError(TransferTxErrorType.RECEIVER_ACCOUNT_INACTIVE, t('The recipient account may be inactive. Change recipient account and try again')); + + errors.push(error); + } + // Check ed for receiver after sending - if (new BigN(receiverTransferTokenFreeBalance).plus(transferAmount).lt(minAmount)) { - const atLeast = new BigN(minAmount).minus(receiverTransferTokenFreeBalance).plus((tokenInfo.decimals || 0) === 0 ? 0 : 1); + if (new BigN(receiverTransferTokenTotalBalance).plus(transferAmount).lt(minAmount)) { + const atLeast = new BigN(minAmount).minus(receiverTransferTokenTotalBalance).plus((tokenInfo.decimals || 0) === 0 ? 0 : 1); const atLeastStr = formatNumber(atLeast, tokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: tokenInfo.decimals || 6 }); diff --git a/packages/extension-base/src/core/substrate/system-pallet.ts b/packages/extension-base/src/core/substrate/system-pallet.ts index d1b2c12e1e..57fa499955 100644 --- a/packages/extension-base/src/core/substrate/system-pallet.ts +++ b/packages/extension-base/src/core/substrate/system-pallet.ts @@ -24,7 +24,7 @@ export function _canAccountBeReaped (accountInfo: FrameSystemAccountInfo): boole } export function _isAccountActive (accountInfo: FrameSystemAccountInfo): boolean { - return accountInfo.providers === 0 && accountInfo.consumers === 0; + return accountInfo.consumers === 0 && accountInfo.providers === 0 && accountInfo.sufficients === 0; } export function _getSystemPalletTotalBalance (accountInfo: FrameSystemAccountInfo): bigint { diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index be6fd11e00..e4806a6632 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -1376,22 +1376,24 @@ export default class KoniExtension { const additionalValidator = async (inputTransaction: SWTransactionResponse): Promise => { let senderTransferTokenTransferable: string | undefined; - let receiverNativeTransferable: string | undefined; + let receiverNativeTotal: string | undefined; + let isReceiverActive: unknown; // Check ed for sender if (!isTransferNativeToken) { - const [_senderTransferTokenTransferable, _receiverNativeTransferable] = await Promise.all([ + const [_senderTransferTokenTransferable, _receiverNativeTotal] = await Promise.all([ this.getAddressTransferableBalance({ address: from, networkKey, token: tokenSlug, extrinsicType }), - this.getAddressTransferableBalance({ address: to, networkKey, token: nativeTokenSlug, extrinsicType: ExtrinsicType.TRANSFER_BALANCE }) + this.getAddressTotalBalance({ address: to, networkKey, token: nativeTokenSlug, extrinsicType: ExtrinsicType.TRANSFER_BALANCE }) ]); senderTransferTokenTransferable = _senderTransferTokenTransferable.value; - receiverNativeTransferable = _receiverNativeTransferable.value; + receiverNativeTotal = _receiverNativeTotal.value; + isReceiverActive = _receiverNativeTotal.metadata; } - const { value: receiverTransferTokenTransferable } = await this.getAddressTransferableBalance({ address: to, networkKey, token: tokenSlug, extrinsicType }); // todo: shouldn't be just transferable, locked also counts + const { value: receiverTransferTokenTransferable } = await this.getAddressTotalBalance({ address: to, networkKey, token: tokenSlug, extrinsicType }); // todo: shouldn't be just transferable, locked also counts - const [warnings, errors] = additionalValidateTransfer(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverTransferTokenTransferable, transferAmount.value, senderTransferTokenTransferable, receiverNativeTransferable); + const [warnings, errors] = additionalValidateTransfer(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverTransferTokenTransferable, transferAmount.value, senderTransferTokenTransferable, receiverNativeTotal, isReceiverActive); warnings.length && inputTransaction.warnings.push(...warnings); errors.length && inputTransaction.errors.push(...errors); @@ -1652,6 +1654,10 @@ export default class KoniExtension { return await this.#koniState.balanceService.getTransferableBalance(address, networkKey, token, extrinsicType); } + private async getAddressTotalBalance ({ address, extrinsicType, networkKey, token }: RequestFreeBalance): Promise { + return await this.#koniState.balanceService.getTotalBalance(address, networkKey, token, extrinsicType); + } + private async getMaxTransferable ({ address, destChain, isXcmTransfer, networkKey, token }: RequestMaxTransferable): Promise { const tokenInfo = token ? this.#koniState.chainService.getAssetBySlug(token) : this.#koniState.chainService.getNativeTokenInfo(networkKey); diff --git a/packages/extension-base/src/services/balance-service/index.ts b/packages/extension-base/src/services/balance-service/index.ts index df03477d33..7e8bda4c4b 100644 --- a/packages/extension-base/src/services/balance-service/index.ts +++ b/packages/extension-base/src/services/balance-service/index.ts @@ -17,6 +17,7 @@ import { addLazy, createPromiseHandler, isAccountAll, PromiseHandler, waitTimeou import { getKeypairTypeByAddress } from '@subwallet/keyring'; import { EthereumKeypairTypes, SubstrateKeypairTypes } from '@subwallet/keyring/types'; import keyring from '@subwallet/ui-keyring'; +import BigN from 'bignumber.js'; import { t } from 'i18next'; import { BehaviorSubject } from 'rxjs'; @@ -189,7 +190,7 @@ export class BalanceService implements StoppableServiceInterface { } /** Subscribe token free balance of an address on chain */ - public async subscribeTransferableBalance (address: string, chain: string, tokenSlug: string | undefined, extrinsicType?: ExtrinsicType, callback?: (rs: AmountData) => void): Promise<[() => void, AmountData]> { + public async subscribeBalance (address: string, chain: string, tokenSlug: string | undefined, balanceType: 'transferable' | 'total', extrinsicType?: ExtrinsicType, callback?: (rs: AmountData) => void): Promise<[() => void, AmountData]> { const chainInfo = this.state.chainService.getChainInfoByKey(chain); const chainState = this.state.chainService.getChainStateByKey(chain); @@ -218,10 +219,14 @@ export class BalanceService implements StoppableServiceInterface { unsub = subscribeBalance([address], [chain], [tSlug], assetMap, chainInfoMap, substrateApiMap, evmApiMap, tonApiMap, (result) => { const rs = result[0]; + const value = balanceType === 'total' + ? new BigN(rs.free).plus(new BigN(rs.locked)).toString() + : rs.free; + if (rs.tokenSlug === tSlug) { hasError = false; const balance: AmountData = { - value: rs.free, + value, decimals: tokenInfo.decimals || 0, symbol: tokenInfo.symbol, metadata: rs.metadata @@ -247,6 +252,14 @@ export class BalanceService implements StoppableServiceInterface { }); } + public async subscribeTransferableBalance (address: string, chain: string, tokenSlug: string | undefined, extrinsicType?: ExtrinsicType, callback?: (rs: AmountData) => void): Promise<[() => void, AmountData]> { + return this.subscribeBalance(address, chain, tokenSlug, 'transferable', extrinsicType, callback); + } + + public async subscribeTotalBalance (address: string, chain: string, tokenSlug: string | undefined, extrinsicType?: ExtrinsicType, callback?: (rs: AmountData) => void): Promise<[() => void, AmountData]> { + return this.subscribeBalance(address, chain, tokenSlug, 'total', extrinsicType, callback); + } + /** * @public * @async @@ -264,6 +277,12 @@ export class BalanceService implements StoppableServiceInterface { return balance; } + public async getTotalBalance (address: string, chain: string, tokenSlug?: string, extrinsicType?: ExtrinsicType): Promise { + const [, balance] = await this.subscribeTotalBalance(address, chain, tokenSlug, extrinsicType); + + return balance; + } + /** Remove balance from the subject object by addresses */ public removeBalanceByAddresses (addresses: string[]) { this.balanceMap.removeBalanceItems([...addresses, ALL_ACCOUNT_KEY]); diff --git a/packages/extension-base/src/types/transaction/error.ts b/packages/extension-base/src/types/transaction/error.ts index a0c00d6101..d86b8f1794 100644 --- a/packages/extension-base/src/types/transaction/error.ts +++ b/packages/extension-base/src/types/transaction/error.ts @@ -42,6 +42,7 @@ export enum TransferTxErrorType { INVALID_TOKEN = 'INVALID_TOKEN', TRANSFER_ERROR = 'TRANSFER_ERROR', RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT = 'RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT', + RECEIVER_ACCOUNT_INACTIVE = 'RECEIVER_ACCOUNT_INACTIVE' } export type TransactionErrorType = BasicTxErrorType | TransferTxErrorType | StakingTxErrorType | YieldValidationStatus | SwapErrorType; From 19d6c3a138f7f944cc384b95cd817389366c3708 Mon Sep 17 00:00:00 2001 From: PDTnhah Date: Thu, 21 Nov 2024 17:52:11 +0700 Subject: [PATCH 02/11] [Issue - 3713] Extension - Fix bug validating recipient balance when sending Substrate token --- .../src/background/KoniTypes.ts | 5 ++ .../src/core/logic-validation/transfer.ts | 53 ++++++++++++------- .../src/koni/background/handlers/Extension.ts | 28 ++++++++-- .../src/services/chain-service/constants.ts | 1 + 4 files changed, 64 insertions(+), 23 deletions(-) diff --git a/packages/extension-base/src/background/KoniTypes.ts b/packages/extension-base/src/background/KoniTypes.ts index 87e0bb4acb..9f857e77a4 100644 --- a/packages/extension-base/src/background/KoniTypes.ts +++ b/packages/extension-base/src/background/KoniTypes.ts @@ -616,6 +616,11 @@ export interface BasicTokenInfo { symbol: string; } +export interface SufficientMetadata { + isSufficient: boolean, + minBalance: number +} + export interface AmountData extends BasicTokenInfo { value: string; metadata?: unknown; diff --git a/packages/extension-base/src/core/logic-validation/transfer.ts b/packages/extension-base/src/core/logic-validation/transfer.ts index 0e15a3a43d..56bfa13d47 100644 --- a/packages/extension-base/src/core/logic-validation/transfer.ts +++ b/packages/extension-base/src/core/logic-validation/transfer.ts @@ -1,7 +1,7 @@ // Copyright 2019-2022 @subwallet/extension-base authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { _ChainAsset, _ChainInfo } from '@subwallet/chain-list/types'; +import { _AssetType, _ChainAsset, _ChainInfo } from '@subwallet/chain-list/types'; import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; import { _Address, AmountData, ExtrinsicDataTypeMap, ExtrinsicType, FeeData } from '@subwallet/extension-base/background/KoniTypes'; import { TransactionWarning } from '@subwallet/extension-base/background/warnings/TransactionWarning'; @@ -56,35 +56,50 @@ export function validateTransferRequest (tokenInfo: _ChainAsset, from: _Address, return [errors, keypair, transferValue]; } -export function additionalValidateTransfer (tokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverTransferTokenTotalBalance: string, transferAmount: string, senderTransferTokenTransferable?: string, _receiverNativeTotal?: string, isReceiverActive?: unknown): [TransactionWarning[], TransactionError[]] { +export function additionalValidateTransfer (tokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverTransferTokenTotalBalance: string, transferAmount: string, senderTransferTokenTransferable?: string, _receiverNativeTotal?: string, isReceiverActive?: unknown, isSufficient?: boolean): [TransactionWarning[], TransactionError[]] { const minAmount = _getTokenMinAmount(tokenInfo); const nativeMinAmount = _getTokenMinAmount(nativeTokenInfo); const warnings: TransactionWarning[] = []; const errors: TransactionError[] = []; - // Check ed of not native token for sender after sending - if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN && senderTransferTokenTransferable) { - if (new BigN(senderTransferTokenTransferable).minus(transferAmount).lt(minAmount)) { - const warning = new TransactionWarning(BasicTxWarningCode.NOT_ENOUGH_EXISTENTIAL_DEPOSIT); + if (tokenInfo.assetType !== _AssetType.NATIVE) { + // Check ed of not native token for sender after sending + if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN && senderTransferTokenTransferable) { + if (new BigN(senderTransferTokenTransferable).minus(transferAmount).lt(minAmount)) { + const warning = new TransactionWarning(BasicTxWarningCode.NOT_ENOUGH_EXISTENTIAL_DEPOSIT); - warnings.push(warning); + warnings.push(warning); + } } - } - // Check ed for receiver before sending - if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN && _receiverNativeTotal) { - if (new BigN(_receiverNativeTotal).lt(nativeMinAmount) && new BigN(nativeMinAmount).gt(0)) { - const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('The recipient account has {{amount}} {{nativeSymbol}} which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: _receiverNativeTotal, nativeSymbol: nativeTokenInfo.symbol, localSymbol: tokenInfo.symbol } })); + if (!isSufficient && new BigN(nativeMinAmount).gt(0)) { + // Check ed for receiver before sending + // if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN && _receiverNativeTotal) { + // if (new BigN(_receiverNativeTotal).lt(nativeMinAmount)) { + // const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('The recipient account has {{amount}} {{nativeSymbol}} which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: _receiverNativeTotal, nativeSymbol: nativeTokenInfo.symbol, localSymbol: tokenInfo.symbol } })); + // + // errors.push(error); + // } + // } + + // Check if receiver's account is active + if (isReceiverActive && _isAccountActive(isReceiverActive as FrameSystemAccountInfo)) { + const error = new TransactionError(TransferTxErrorType.RECEIVER_ACCOUNT_INACTIVE, t('The recipient account may be inactive. Change recipient account and try again')); + + errors.push(error); + } - errors.push(error); - } - } + // Check ed for receiver after sending + if (new BigN(receiverTransferTokenTotalBalance).plus(transferAmount).lt(minAmount)) { + const atLeast = new BigN(minAmount).minus(receiverTransferTokenTotalBalance).plus((tokenInfo.decimals || 0) === 0 ? 0 : 1); - // Check if receiver's account is active - if (isReceiverActive && _isAccountActive(isReceiverActive as FrameSystemAccountInfo)) { - const error = new TransactionError(TransferTxErrorType.RECEIVER_ACCOUNT_INACTIVE, t('The recipient account may be inactive. Change recipient account and try again')); + const atLeastStr = formatNumber(atLeast, tokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: tokenInfo.decimals || 6 }); - errors.push(error); + const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: tokenInfo.symbol } })); + + errors.push(error); + } + } } // Check ed for receiver after sending diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index b0ed2bd012..dc76ff4569 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -7,7 +7,7 @@ import { _AssetRef, _AssetType, _ChainAsset, _ChainInfo, _MultiChainAsset } from import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; import { withErrorLog } from '@subwallet/extension-base/background/handlers/helpers'; import { createSubscription } from '@subwallet/extension-base/background/handlers/subscriptions'; -import { AccountExternalError, AddressBookInfo, AmountData, AmountDataWithId, AssetSetting, AssetSettingUpdateReq, BondingOptionParams, BrowserConfirmationType, CampaignBanner, CampaignData, CampaignDataType, ChainType, CronReloadRequest, CrowdloanJson, ExternalRequestPromiseStatus, ExtrinsicType, KeyringState, MantaPayEnableMessage, MantaPayEnableParams, MantaPayEnableResponse, MantaPaySyncState, MetadataItem, NftCollection, NftJson, NftTransactionRequest, NftTransactionResponse, PriceJson, RequestAccountCreateExternalV2, RequestAccountCreateHardwareMultiple, RequestAccountCreateHardwareV2, RequestAccountCreateWithSecretKey, RequestAccountExportPrivateKey, RequestAddInjectedAccounts, RequestApproveConnectWalletSession, RequestApproveWalletConnectNotSupport, RequestAuthorization, RequestAuthorizationBlock, RequestAuthorizationPerAccount, RequestAuthorizationPerSite, RequestAuthorizeApproveV2, RequestBondingSubmit, RequestCameraSettings, RequestCampaignBannerComplete, RequestChangeEnableChainPatrol, RequestChangeLanguage, RequestChangeMasterPassword, RequestChangePriceCurrency, RequestChangeShowBalance, RequestChangeShowZeroBalance, RequestChangeTimeAutoLock, RequestConfirmationComplete, RequestConfirmationCompleteTon, RequestConnectWalletConnect, RequestCrowdloanContributions, RequestDeleteContactAccount, RequestDisconnectWalletConnectSession, RequestEditContactAccount, RequestFindRawMetadata, RequestForgetSite, RequestFreeBalance, RequestGetTransaction, RequestKeyringExportMnemonic, RequestMaxTransferable, RequestMigratePassword, RequestParseEvmContractInput, RequestParseTransactionSubstrate, RequestPassPhishingPage, RequestQrParseRLP, RequestQrSignEvm, RequestQrSignSubstrate, RequestRejectConnectWalletSession, RequestRejectExternalRequest, RequestRejectWalletConnectNotSupport, RequestRemoveInjectedAccounts, RequestResetWallet, RequestResolveExternalRequest, RequestSaveAppConfig, RequestSaveBrowserConfig, RequestSaveOSConfig, RequestSaveRecentAccount, RequestSettingsType, RequestSigningApprovePasswordV2, RequestStakePoolingBonding, RequestStakePoolingUnbonding, RequestSubscribeHistory, RequestSubstrateNftSubmitTransaction, RequestTuringCancelStakeCompound, RequestTuringStakeCompound, RequestUnbondingSubmit, RequestUnlockKeyring, RequestUnlockType, ResolveAddressToDomainRequest, ResolveDomainRequest, ResponseAccountCreateWithSecretKey, ResponseAccountExportPrivateKey, ResponseChangeMasterPassword, ResponseFindRawMetadata, ResponseKeyringExportMnemonic, ResponseMigratePassword, ResponseNftImport, ResponseParseEvmContractInput, ResponseParseTransactionSubstrate, ResponseQrParseRLP, ResponseQrSignEvm, ResponseQrSignSubstrate, ResponseRejectExternalRequest, ResponseResetWallet, ResponseResolveExternalRequest, ResponseSubscribeHistory, ResponseUnlockKeyring, ShowCampaignPopupRequest, StakingJson, StakingRewardJson, StakingType, ThemeNames, TransactionHistoryItem, TransactionResponse, ValidateNetworkRequest, ValidateNetworkResponse, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; +import { AccountExternalError, AddressBookInfo, AmountData, AmountDataWithId, AssetSetting, AssetSettingUpdateReq, BondingOptionParams, BrowserConfirmationType, CampaignBanner, CampaignData, CampaignDataType, ChainType, CronReloadRequest, CrowdloanJson, ExternalRequestPromiseStatus, ExtrinsicType, KeyringState, MantaPayEnableMessage, MantaPayEnableParams, MantaPayEnableResponse, MantaPaySyncState, MetadataItem, NftCollection, NftJson, NftTransactionRequest, NftTransactionResponse, PriceJson, RequestAccountCreateExternalV2, RequestAccountCreateHardwareMultiple, RequestAccountCreateHardwareV2, RequestAccountCreateWithSecretKey, RequestAccountExportPrivateKey, RequestAddInjectedAccounts, RequestApproveConnectWalletSession, RequestApproveWalletConnectNotSupport, RequestAuthorization, RequestAuthorizationBlock, RequestAuthorizationPerAccount, RequestAuthorizationPerSite, RequestAuthorizeApproveV2, RequestBondingSubmit, RequestCameraSettings, RequestCampaignBannerComplete, RequestChangeEnableChainPatrol, RequestChangeLanguage, RequestChangeMasterPassword, RequestChangePriceCurrency, RequestChangeShowBalance, RequestChangeShowZeroBalance, RequestChangeTimeAutoLock, RequestConfirmationComplete, RequestConfirmationCompleteTon, RequestConnectWalletConnect, RequestCrowdloanContributions, RequestDeleteContactAccount, RequestDisconnectWalletConnectSession, RequestEditContactAccount, RequestFindRawMetadata, RequestForgetSite, RequestFreeBalance, RequestGetTransaction, RequestKeyringExportMnemonic, RequestMaxTransferable, RequestMigratePassword, RequestParseEvmContractInput, RequestParseTransactionSubstrate, RequestPassPhishingPage, RequestQrParseRLP, RequestQrSignEvm, RequestQrSignSubstrate, RequestRejectConnectWalletSession, RequestRejectExternalRequest, RequestRejectWalletConnectNotSupport, RequestRemoveInjectedAccounts, RequestResetWallet, RequestResolveExternalRequest, RequestSaveAppConfig, RequestSaveBrowserConfig, RequestSaveOSConfig, RequestSaveRecentAccount, RequestSettingsType, RequestSigningApprovePasswordV2, RequestStakePoolingBonding, RequestStakePoolingUnbonding, RequestSubscribeHistory, RequestSubstrateNftSubmitTransaction, RequestTuringCancelStakeCompound, RequestTuringStakeCompound, RequestUnbondingSubmit, RequestUnlockKeyring, RequestUnlockType, ResolveAddressToDomainRequest, ResolveDomainRequest, ResponseAccountCreateWithSecretKey, ResponseAccountExportPrivateKey, ResponseChangeMasterPassword, ResponseFindRawMetadata, ResponseKeyringExportMnemonic, ResponseMigratePassword, ResponseNftImport, ResponseParseEvmContractInput, ResponseParseTransactionSubstrate, ResponseQrParseRLP, ResponseQrSignEvm, ResponseQrSignSubstrate, ResponseRejectExternalRequest, ResponseResetWallet, ResponseResolveExternalRequest, ResponseSubscribeHistory, ResponseUnlockKeyring, ShowCampaignPopupRequest, StakingJson, StakingRewardJson, StakingType, SufficientMetadata, ThemeNames, TransactionHistoryItem, TransactionResponse, ValidateNetworkRequest, ValidateNetworkResponse, ValidatorInfo } from '@subwallet/extension-base/background/KoniTypes'; import { AccountAuthType, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountExport, RequestAuthorizeCancel, RequestAuthorizeReject, RequestCurrentAccountAddress, RequestMetadataApprove, RequestMetadataReject, RequestSigningApproveSignature, RequestSigningCancel, RequestTypes, ResponseAccountExport, ResponseAuthorizeList, ResponseType, SigningRequest, WindowOpenParams } from '@subwallet/extension-base/background/types'; import { TransactionWarning } from '@subwallet/extension-base/background/warnings/TransactionWarning'; import { ALL_ACCOUNT_KEY, LATEST_SESSION, XCM_FEE_RATIO } from '@subwallet/extension-base/constants'; @@ -32,7 +32,7 @@ import { createTransferExtrinsic, getTransferMockTxFee } from '@subwallet/extens import { createTonTransaction } from '@subwallet/extension-base/services/balance-service/transfer/ton-transfer'; import { createAvailBridgeExtrinsicFromAvail, createAvailBridgeTxFromEth, createSnowBridgeExtrinsic, createXcmExtrinsic, CreateXcmExtrinsicProps, FunctionCreateXcmExtrinsic, getXcmMockTxFee } from '@subwallet/extension-base/services/balance-service/transfer/xcm'; import { getClaimTxOnAvail, getClaimTxOnEthereum, isAvailChainBridge } from '@subwallet/extension-base/services/balance-service/transfer/xcm/availBridge'; -import { _API_OPTIONS_CHAIN_GROUP, _DEFAULT_MANTA_ZK_CHAIN, _MANTA_ZK_CHAIN_GROUP, _ZK_ASSET_PREFIX } from '@subwallet/extension-base/services/chain-service/constants'; +import { _API_OPTIONS_CHAIN_GROUP, _DEFAULT_MANTA_ZK_CHAIN, _MANTA_ZK_CHAIN_GROUP, _ZK_ASSET_PREFIX, SUFFICIENT_CHAIN } from '@subwallet/extension-base/services/chain-service/constants'; import { _ChainApiStatus, _ChainConnectionStatus, _ChainState, _NetworkUpsertParams, _ValidateCustomAssetRequest, _ValidateCustomAssetResponse, EnableChainParams, EnableMultiChainParams } from '@subwallet/extension-base/services/chain-service/types'; import { _getAssetDecimals, _getAssetSymbol, _getChainNativeTokenBasicInfo, _getContractAddressOfToken, _getEvmChainId, _isAssetSmartContractNft, _isChainEvmCompatible, _isChainTonCompatible, _isCustomAsset, _isLocalToken, _isMantaZkAsset, _isNativeToken, _isPureEvmChain, _isTokenEvmSmartContract, _isTokenTransferredByEvm, _isTokenTransferredByTon } from '@subwallet/extension-base/services/chain-service/utils'; import { _NotificationInfo, NotificationSetup } from '@subwallet/extension-base/services/inapp-notification-service/interfaces'; @@ -69,6 +69,7 @@ import { t } from 'i18next'; import { combineLatest, Subject } from 'rxjs'; import { TransactionConfig } from 'web3-core'; +import { ApiPromise } from '@polkadot/api'; import { SubmittableExtrinsic } from '@polkadot/api/types'; import { Metadata, TypeRegistry } from '@polkadot/types'; import { ChainProperties } from '@polkadot/types/interfaces'; @@ -1401,9 +1402,12 @@ export default class KoniExtension { isReceiverActive = _receiverNativeTotal.metadata; } - const { value: receiverTransferTokenTransferable } = await this.getAddressTotalBalance({ address: to, networkKey, token: tokenSlug, extrinsicType }); // todo: shouldn't be just transferable, locked also counts + const { value: receiverTransferTokenTotalBalance } = await this.getAddressTotalBalance({ address: to, networkKey, token: tokenSlug, extrinsicType }); // todo: shouldn't be just transferable, locked also counts - const [warnings, errors] = additionalValidateTransfer(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverTransferTokenTransferable, transferAmount.value, senderTransferTokenTransferable, receiverNativeTotal, isReceiverActive); + const substrateApi = this.#koniState.getSubstrateApi(networkKey).api; + const isSufficient = await this.isSufficientToken(transferTokenInfo, substrateApi); + + const [warnings, errors] = additionalValidateTransfer(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverTransferTokenTotalBalance, transferAmount.value, senderTransferTokenTransferable, receiverNativeTotal, isReceiverActive, isSufficient); warnings.length && inputTransaction.warnings.push(...warnings); errors.length && inputTransaction.errors.push(...errors); @@ -1674,6 +1678,22 @@ export default class KoniExtension { } } + private async isSufficientToken (tokenInfo: _ChainAsset, api: ApiPromise): Promise { + let metadata: SufficientMetadata; + + if (SUFFICIENT_CHAIN.includes(tokenInfo.originChain) && tokenInfo.assetType !== _AssetType.NATIVE) { + const assetId = tokenInfo?.metadata?.assetId; + + const _metadata = await api.query.assets.asset(assetId); + + metadata = _metadata.toPrimitive() as unknown as SufficientMetadata; + } else { + return false; + } + + return metadata.isSufficient; + } + private async deleteCustomAsset (assetSlug: string) { const assetInfo = this.#koniState.getAssetBySlug(assetSlug); diff --git a/packages/extension-base/src/services/chain-service/constants.ts b/packages/extension-base/src/services/chain-service/constants.ts index 04b580d536..db98a68865 100644 --- a/packages/extension-base/src/services/chain-service/constants.ts +++ b/packages/extension-base/src/services/chain-service/constants.ts @@ -258,6 +258,7 @@ export const _XCM_CHAIN_GROUP = { xcmPallet: ['polkadot', 'kusama', 'rococo'] // default is xTokens pallet }; +export const SUFFICIENT_CHAIN = ['astar', 'calamari', 'parallel', 'darwinia2', 'crabParachain', 'pangolin', 'statemint', 'moonriver', 'shiden', 'moonbeam', 'statemine', 'liberland', 'dentnet', 'phala', 'crust', 'dbcchain', 'rococo_assethub']; export const _XCM_TYPE = { RP: `${_SubstrateChainType.RELAYCHAIN}-${_SubstrateChainType.PARACHAIN}`, // DMP From 5d0021dae8cede8010de0a3b9c5f7349454fd5a2 Mon Sep 17 00:00:00 2001 From: nampc Date: Sat, 23 Nov 2024 12:42:34 +0700 Subject: [PATCH 03/11] [Issue 3713] fix: update validating token transfer --- .../src/core/logic-validation/transfer.ts | 208 ++++++++++++------ .../src/koni/background/handlers/Extension.ts | 4 +- 2 files changed, 147 insertions(+), 65 deletions(-) diff --git a/packages/extension-base/src/core/logic-validation/transfer.ts b/packages/extension-base/src/core/logic-validation/transfer.ts index 56bfa13d47..77f52b7c17 100644 --- a/packages/extension-base/src/core/logic-validation/transfer.ts +++ b/packages/extension-base/src/core/logic-validation/transfer.ts @@ -1,29 +1,58 @@ // Copyright 2019-2022 @subwallet/extension-base authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { _AssetType, _ChainAsset, _ChainInfo } from '@subwallet/chain-list/types'; -import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; -import { _Address, AmountData, ExtrinsicDataTypeMap, ExtrinsicType, FeeData } from '@subwallet/extension-base/background/KoniTypes'; -import { TransactionWarning } from '@subwallet/extension-base/background/warnings/TransactionWarning'; -import { LEDGER_SIGNING_COMPATIBLE_MAP, SIGNING_COMPATIBLE_MAP, XCM_MIN_AMOUNT_RATIO } from '@subwallet/extension-base/constants'; -import { _canAccountBeReaped, _isAccountActive } from '@subwallet/extension-base/core/substrate/system-pallet'; -import { FrameSystemAccountInfo } from '@subwallet/extension-base/core/substrate/types'; -import { isBounceableAddress } from '@subwallet/extension-base/services/balance-service/helpers/subscribe/ton/utils'; -import { _TRANSFER_CHAIN_GROUP } from '@subwallet/extension-base/services/chain-service/constants'; -import { _EvmApi, _TonApi } from '@subwallet/extension-base/services/chain-service/types'; -import { _getChainExistentialDeposit, _getChainNativeTokenBasicInfo, _getContractAddressOfToken, _getTokenMinAmount, _isNativeToken, _isTokenEvmSmartContract, _isTokenTonSmartContract } from '@subwallet/extension-base/services/chain-service/utils'; -import { calculateGasFeeParams } from '@subwallet/extension-base/services/fee-service/utils'; -import { isSubstrateTransaction, isTonTransaction } from '@subwallet/extension-base/services/transaction-service/helpers'; -import { OptionalSWTransaction, SWTransactionInput, SWTransactionResponse } from '@subwallet/extension-base/services/transaction-service/types'; -import { AccountSignMode, BasicTxErrorType, BasicTxWarningCode, TransferTxErrorType } from '@subwallet/extension-base/types'; -import { balanceFormatter, formatNumber, pairToAccount } from '@subwallet/extension-base/utils'; -import { isTonAddress } from '@subwallet/keyring'; -import { KeyringPair } from '@subwallet/keyring/types'; -import { keyring } from '@subwallet/ui-keyring'; +import {_ChainAsset, _ChainInfo} from '@subwallet/chain-list/types'; +import {TransactionError} from '@subwallet/extension-base/background/errors/TransactionError'; +import { + _Address, + AmountData, + ExtrinsicDataTypeMap, + ExtrinsicType, + FeeData +} from '@subwallet/extension-base/background/KoniTypes'; +import {TransactionWarning} from '@subwallet/extension-base/background/warnings/TransactionWarning'; +import { + LEDGER_SIGNING_COMPATIBLE_MAP, + SIGNING_COMPATIBLE_MAP, + XCM_MIN_AMOUNT_RATIO +} from '@subwallet/extension-base/constants'; +import {_canAccountBeReaped, _isAccountActive} from '@subwallet/extension-base/core/substrate/system-pallet'; +import {FrameSystemAccountInfo} from '@subwallet/extension-base/core/substrate/types'; +import {isBounceableAddress} from '@subwallet/extension-base/services/balance-service/helpers/subscribe/ton/utils'; +import {_TRANSFER_CHAIN_GROUP} from '@subwallet/extension-base/services/chain-service/constants'; +import {_EvmApi, _TonApi} from '@subwallet/extension-base/services/chain-service/types'; +import { + _getAssetDecimals, + _getAssetSymbol, + _getChainExistentialDeposit, + _getChainNativeTokenBasicInfo, + _getContractAddressOfToken, + _getTokenMinAmount, + _isNativeToken, + _isTokenEvmSmartContract, + _isTokenTonSmartContract +} from '@subwallet/extension-base/services/chain-service/utils'; +import {calculateGasFeeParams} from '@subwallet/extension-base/services/fee-service/utils'; +import {isSubstrateTransaction, isTonTransaction} from '@subwallet/extension-base/services/transaction-service/helpers'; +import { + OptionalSWTransaction, + SWTransactionInput, + SWTransactionResponse +} from '@subwallet/extension-base/services/transaction-service/types'; +import { + AccountSignMode, + BasicTxErrorType, + BasicTxWarningCode, + TransferTxErrorType +} from '@subwallet/extension-base/types'; +import {balanceFormatter, formatNumber, pairToAccount} from '@subwallet/extension-base/utils'; +import {isTonAddress} from '@subwallet/keyring'; +import {KeyringPair} from '@subwallet/keyring/types'; +import {keyring} from '@subwallet/ui-keyring'; import BigN from 'bignumber.js'; -import { t } from 'i18next'; +import {t} from 'i18next'; -import { isEthereumAddress } from '@polkadot/util-crypto'; +import {isEthereumAddress} from '@polkadot/util-crypto'; // normal transfer export function validateTransferRequest (tokenInfo: _ChainAsset, from: _Address, to: _Address, value: string | undefined, transferAll: boolean | undefined): [TransactionError[], KeyringPair | undefined, BigN | undefined] { @@ -56,62 +85,115 @@ export function validateTransferRequest (tokenInfo: _ChainAsset, from: _Address, return [errors, keypair, transferValue]; } -export function additionalValidateTransfer (tokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverTransferTokenTotalBalance: string, transferAmount: string, senderTransferTokenTransferable?: string, _receiverNativeTotal?: string, isReceiverActive?: unknown, isSufficient?: boolean): [TransactionWarning[], TransactionError[]] { - const minAmount = _getTokenMinAmount(tokenInfo); - const nativeMinAmount = _getTokenMinAmount(nativeTokenInfo); +export function additionalValidateTransferForRecipient (sendingTokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverSendingTokenKeepAliveBalance: bigint, transferAmount: bigint, senderSendingTokenTransferable: bigint, _receiverNativeTotal?: string, isReceiverActive?: unknown, isSufficient?: boolean): [TransactionWarning[], TransactionError[]] { + const sendingTokenMinAmount = BigInt(_getTokenMinAmount(sendingTokenInfo)); + const nativeTokenMinAmount = _getTokenMinAmount(nativeTokenInfo); + const warnings: TransactionWarning[] = []; const errors: TransactionError[] = []; - if (tokenInfo.assetType !== _AssetType.NATIVE) { - // Check ed of not native token for sender after sending - if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN && senderTransferTokenTransferable) { - if (new BigN(senderTransferTokenTransferable).minus(transferAmount).lt(minAmount)) { - const warning = new TransactionWarning(BasicTxWarningCode.NOT_ENOUGH_EXISTENTIAL_DEPOSIT); + const remainingSendingTokenOfSenderEnoughED = senderSendingTokenTransferable - transferAmount >= sendingTokenMinAmount; + const isReceiverAliveByNativeToken = isReceiverActive && _isAccountActive(isReceiverActive as FrameSystemAccountInfo); + const isReceivingAmountPassED = receiverSendingTokenKeepAliveBalance + transferAmount >= sendingTokenMinAmount; + const isReceiverAliveAfterSending = isSufficient ? isReceivingAmountPassED : isReceiverAliveByNativeToken; - warnings.push(warning); - } - } + if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN) { + if (!remainingSendingTokenOfSenderEnoughED) { + const warning = new TransactionWarning(BasicTxWarningCode.NOT_ENOUGH_EXISTENTIAL_DEPOSIT); - if (!isSufficient && new BigN(nativeMinAmount).gt(0)) { - // Check ed for receiver before sending - // if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN && _receiverNativeTotal) { - // if (new BigN(_receiverNativeTotal).lt(nativeMinAmount)) { - // const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('The recipient account has {{amount}} {{nativeSymbol}} which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: _receiverNativeTotal, nativeSymbol: nativeTokenInfo.symbol, localSymbol: tokenInfo.symbol } })); - // - // errors.push(error); - // } - // } - - // Check if receiver's account is active - if (isReceiverActive && _isAccountActive(isReceiverActive as FrameSystemAccountInfo)) { - const error = new TransactionError(TransferTxErrorType.RECEIVER_ACCOUNT_INACTIVE, t('The recipient account may be inactive. Change recipient account and try again')); - - errors.push(error); - } + warnings.push(warning); + } - // Check ed for receiver after sending - if (new BigN(receiverTransferTokenTotalBalance).plus(transferAmount).lt(minAmount)) { - const atLeast = new BigN(minAmount).minus(receiverTransferTokenTotalBalance).plus((tokenInfo.decimals || 0) === 0 ? 0 : 1); + if (!isReceiverAliveAfterSending) { + const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, + t('The recipient account has {{amount}} {{nativeSymbol}} which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: _receiverNativeTotal, nativeSymbol: nativeTokenInfo.symbol, localSymbol: tokenInfo.symbol } }) + ); - const atLeastStr = formatNumber(atLeast, tokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: tokenInfo.decimals || 6 }); + errors.push(error); + } + } - const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: tokenInfo.symbol } })); + if (!isReceivingAmountPassED) { + const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, + t('The recipient account has {{amount}} {{nativeSymbol}} which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: _receiverNativeTotal, nativeSymbol: nativeTokenInfo.symbol, localSymbol: tokenInfo.symbol } }) + ); - errors.push(error); - } - } + errors.push(error); } - // Check ed for receiver after sending - if (new BigN(receiverTransferTokenTotalBalance).plus(transferAmount).lt(minAmount)) { - const atLeast = new BigN(minAmount).minus(receiverTransferTokenTotalBalance).plus((tokenInfo.decimals || 0) === 0 ? 0 : 1); - const atLeastStr = formatNumber(atLeast, tokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: tokenInfo.decimals || 6 }); - const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: tokenInfo.symbol } })); - errors.push(error); - } + + + + + + + + + + + // if (transferAmount + receiverSendingTokenKeepAliveBalance < sendingTokenMinAmount) { + // const atLeast = sendingTokenMinAmount - receiverSendingTokenKeepAliveBalance; + // + // const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6 }); + // + // const error = new TransactionError( + // TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, + // t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: _getAssetSymbol(sendingTokenInfo) } }) + // ); + // + // errors.push(error); + // } + + // if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN) { + // // if (senderSendingTokenTransferable - transferAmount < sendingTokenMinAmount) { + // // const warning = new TransactionWarning(BasicTxWarningCode.NOT_ENOUGH_EXISTENTIAL_DEPOSIT); + // // + // // warnings.push(warning); + // // } + // + // if (!isSufficient && new BigN(nativeTokenMinAmount).gt(0)) { + // // Check ed for receiver before sending + // // if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN && _receiverNativeTotal) { + // // if (new BigN(_receiverNativeTotal).lt(nativeMinAmount)) { + // // const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('The recipient account has {{amount}} {{nativeSymbol}} which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: _receiverNativeTotal, nativeSymbol: nativeTokenInfo.symbol, localSymbol: tokenInfo.symbol } })); + // // + // // errors.push(error); + // // } + // // } + // + // // Check if receiver's account is active + // if () { + // const error = new TransactionError(TransferTxErrorType.RECEIVER_ACCOUNT_INACTIVE, t('The recipient account may be inactive. Change recipient account and try again')); + // + // errors.push(error); + // } + // + // // Check ed for receiver after sending + // if (new BigN(receiverSendingTokenKeepAliveBalance).plus(transferAmount).lt(sendingTokenMinAmount)) { + // const atLeast = new BigN(sendingTokenMinAmount).minus(receiverSendingTokenKeepAliveBalance).plus((sendingTokenInfo.decimals || 0) === 0 ? 0 : 1); + // + // const atLeastStr = formatNumber(atLeast, sendingTokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: sendingTokenInfo.decimals || 6 }); + // + // const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } })); + // + // errors.push(error); + // } + // } + // } + // + // // Check ed for receiver after sending + // if (new BigN(receiverSendingTokenKeepAliveBalance).plus(transferAmount).lt(sendingTokenMinAmount)) { + // const atLeast = new BigN(sendingTokenMinAmount).minus(receiverSendingTokenKeepAliveBalance).plus((sendingTokenInfo.decimals || 0) === 0 ? 0 : 1); + // + // const atLeastStr = formatNumber(atLeast, sendingTokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: sendingTokenInfo.decimals || 6 }); + // + // const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } })); + // + // errors.push(error); + // } return [warnings, errors]; } diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index dc76ff4569..534eae632d 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -11,7 +11,7 @@ import { AccountExternalError, AddressBookInfo, AmountData, AmountDataWithId, As import { AccountAuthType, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountExport, RequestAuthorizeCancel, RequestAuthorizeReject, RequestCurrentAccountAddress, RequestMetadataApprove, RequestMetadataReject, RequestSigningApproveSignature, RequestSigningCancel, RequestTypes, ResponseAccountExport, ResponseAuthorizeList, ResponseType, SigningRequest, WindowOpenParams } from '@subwallet/extension-base/background/types'; import { TransactionWarning } from '@subwallet/extension-base/background/warnings/TransactionWarning'; import { ALL_ACCOUNT_KEY, LATEST_SESSION, XCM_FEE_RATIO } from '@subwallet/extension-base/constants'; -import { additionalValidateTransfer, additionalValidateXcmTransfer, validateTransferRequest, validateXcmTransferRequest } from '@subwallet/extension-base/core/logic-validation/transfer'; +import { additionalValidateTransferForRecipient, additionalValidateXcmTransfer, validateTransferRequest, validateXcmTransferRequest } from '@subwallet/extension-base/core/logic-validation/transfer'; import { _isSnowBridgeXcm } from '@subwallet/extension-base/core/substrate/xcm-parser'; import { ALLOWED_PATH } from '@subwallet/extension-base/defaults'; import { getERC20SpendingApprovalTx } from '@subwallet/extension-base/koni/api/contract-handler/evm/web3'; @@ -1407,7 +1407,7 @@ export default class KoniExtension { const substrateApi = this.#koniState.getSubstrateApi(networkKey).api; const isSufficient = await this.isSufficientToken(transferTokenInfo, substrateApi); - const [warnings, errors] = additionalValidateTransfer(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverTransferTokenTotalBalance, transferAmount.value, senderTransferTokenTransferable, receiverNativeTotal, isReceiverActive, isSufficient); + const [warnings, errors] = additionalValidateTransferForRecipient(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverTransferTokenTotalBalance, transferAmount.value, senderTransferTokenTransferable, receiverNativeTotal, isReceiverActive, isSufficient); warnings.length && inputTransaction.warnings.push(...warnings); errors.length && inputTransaction.errors.push(...errors); From c31383e9a79b5dd494e4332f04a9f4efc69ee8f5 Mon Sep 17 00:00:00 2001 From: PDTnhah Date: Tue, 26 Nov 2024 18:15:27 +0700 Subject: [PATCH 04/11] [Issue-3713] Refactor: validation logic for transfer --- .../src/core/logic-validation/transfer.ts | 116 +++++++----------- .../src/koni/background/handlers/Extension.ts | 13 +- 2 files changed, 51 insertions(+), 78 deletions(-) diff --git a/packages/extension-base/src/core/logic-validation/transfer.ts b/packages/extension-base/src/core/logic-validation/transfer.ts index 77f52b7c17..7d842ac50a 100644 --- a/packages/extension-base/src/core/logic-validation/transfer.ts +++ b/packages/extension-base/src/core/logic-validation/transfer.ts @@ -1,58 +1,29 @@ // Copyright 2019-2022 @subwallet/extension-base authors & contributors // SPDX-License-Identifier: Apache-2.0 -import {_ChainAsset, _ChainInfo} from '@subwallet/chain-list/types'; -import {TransactionError} from '@subwallet/extension-base/background/errors/TransactionError'; -import { - _Address, - AmountData, - ExtrinsicDataTypeMap, - ExtrinsicType, - FeeData -} from '@subwallet/extension-base/background/KoniTypes'; -import {TransactionWarning} from '@subwallet/extension-base/background/warnings/TransactionWarning'; -import { - LEDGER_SIGNING_COMPATIBLE_MAP, - SIGNING_COMPATIBLE_MAP, - XCM_MIN_AMOUNT_RATIO -} from '@subwallet/extension-base/constants'; -import {_canAccountBeReaped, _isAccountActive} from '@subwallet/extension-base/core/substrate/system-pallet'; -import {FrameSystemAccountInfo} from '@subwallet/extension-base/core/substrate/types'; -import {isBounceableAddress} from '@subwallet/extension-base/services/balance-service/helpers/subscribe/ton/utils'; -import {_TRANSFER_CHAIN_GROUP} from '@subwallet/extension-base/services/chain-service/constants'; -import {_EvmApi, _TonApi} from '@subwallet/extension-base/services/chain-service/types'; -import { - _getAssetDecimals, - _getAssetSymbol, - _getChainExistentialDeposit, - _getChainNativeTokenBasicInfo, - _getContractAddressOfToken, - _getTokenMinAmount, - _isNativeToken, - _isTokenEvmSmartContract, - _isTokenTonSmartContract -} from '@subwallet/extension-base/services/chain-service/utils'; -import {calculateGasFeeParams} from '@subwallet/extension-base/services/fee-service/utils'; -import {isSubstrateTransaction, isTonTransaction} from '@subwallet/extension-base/services/transaction-service/helpers'; -import { - OptionalSWTransaction, - SWTransactionInput, - SWTransactionResponse -} from '@subwallet/extension-base/services/transaction-service/types'; -import { - AccountSignMode, - BasicTxErrorType, - BasicTxWarningCode, - TransferTxErrorType -} from '@subwallet/extension-base/types'; -import {balanceFormatter, formatNumber, pairToAccount} from '@subwallet/extension-base/utils'; -import {isTonAddress} from '@subwallet/keyring'; -import {KeyringPair} from '@subwallet/keyring/types'; -import {keyring} from '@subwallet/ui-keyring'; +import { _ChainAsset, _ChainInfo } from '@subwallet/chain-list/types'; +import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError'; +import { _Address, AmountData, ExtrinsicDataTypeMap, ExtrinsicType, FeeData } from '@subwallet/extension-base/background/KoniTypes'; +import { TransactionWarning } from '@subwallet/extension-base/background/warnings/TransactionWarning'; +import { LEDGER_SIGNING_COMPATIBLE_MAP, SIGNING_COMPATIBLE_MAP, XCM_MIN_AMOUNT_RATIO } from '@subwallet/extension-base/constants'; +import { _canAccountBeReaped, _isAccountActive } from '@subwallet/extension-base/core/substrate/system-pallet'; +import { FrameSystemAccountInfo } from '@subwallet/extension-base/core/substrate/types'; +import { isBounceableAddress } from '@subwallet/extension-base/services/balance-service/helpers/subscribe/ton/utils'; +import { _TRANSFER_CHAIN_GROUP } from '@subwallet/extension-base/services/chain-service/constants'; +import { _EvmApi, _TonApi } from '@subwallet/extension-base/services/chain-service/types'; +import { _getAssetDecimals, _getChainExistentialDeposit, _getChainNativeTokenBasicInfo, _getContractAddressOfToken, _getTokenMinAmount, _isNativeToken, _isTokenEvmSmartContract, _isTokenTonSmartContract } from '@subwallet/extension-base/services/chain-service/utils'; +import { calculateGasFeeParams } from '@subwallet/extension-base/services/fee-service/utils'; +import { isSubstrateTransaction, isTonTransaction } from '@subwallet/extension-base/services/transaction-service/helpers'; +import { OptionalSWTransaction, SWTransactionInput, SWTransactionResponse } from '@subwallet/extension-base/services/transaction-service/types'; +import { AccountSignMode, BasicTxErrorType, BasicTxWarningCode, TransferTxErrorType } from '@subwallet/extension-base/types'; +import { balanceFormatter, formatNumber, pairToAccount } from '@subwallet/extension-base/utils'; +import { isTonAddress } from '@subwallet/keyring'; +import { KeyringPair } from '@subwallet/keyring/types'; +import { keyring } from '@subwallet/ui-keyring'; import BigN from 'bignumber.js'; -import {t} from 'i18next'; +import { t } from 'i18next'; -import {isEthereumAddress} from '@polkadot/util-crypto'; +import { isEthereumAddress } from '@polkadot/util-crypto'; // normal transfer export function validateTransferRequest (tokenInfo: _ChainAsset, from: _Address, to: _Address, value: string | undefined, transferAll: boolean | undefined): [TransactionError[], KeyringPair | undefined, BigN | undefined] { @@ -85,17 +56,17 @@ export function validateTransferRequest (tokenInfo: _ChainAsset, from: _Address, return [errors, keypair, transferValue]; } -export function additionalValidateTransferForRecipient (sendingTokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverSendingTokenKeepAliveBalance: bigint, transferAmount: bigint, senderSendingTokenTransferable: bigint, _receiverNativeTotal?: string, isReceiverActive?: unknown, isSufficient?: boolean): [TransactionWarning[], TransactionError[]] { +export function additionalValidateTransferForRecipient (sendingTokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverSendingTokenKeepAliveBalance: bigint, transferAmount: bigint, senderSendingTokenTransferable?: bigint, _receiverNativeTotal?: string, isReceiverActive?: unknown, isSufficient?: boolean): [TransactionWarning[], TransactionError[]] { const sendingTokenMinAmount = BigInt(_getTokenMinAmount(sendingTokenInfo)); - const nativeTokenMinAmount = _getTokenMinAmount(nativeTokenInfo); + // const nativeTokenMinAmount = _getTokenMinAmount(nativeTokenInfo); const warnings: TransactionWarning[] = []; const errors: TransactionError[] = []; - const remainingSendingTokenOfSenderEnoughED = senderSendingTokenTransferable - transferAmount >= sendingTokenMinAmount; - const isReceiverAliveByNativeToken = isReceiverActive && _isAccountActive(isReceiverActive as FrameSystemAccountInfo); + const remainingSendingTokenOfSenderEnoughED = senderSendingTokenTransferable && senderSendingTokenTransferable - transferAmount >= sendingTokenMinAmount; + const isReceiverAliveByNativeToken = isReceiverActive && !_isAccountActive(isReceiverActive as FrameSystemAccountInfo); const isReceivingAmountPassED = receiverSendingTokenKeepAliveBalance + transferAmount >= sendingTokenMinAmount; - const isReceiverAliveAfterSending = isSufficient ? isReceivingAmountPassED : isReceiverAliveByNativeToken; + // const isReceiverAliveAfterSending = isSufficient ? isReceivingAmountPassED : isReceiverAliveByNativeToken; if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN) { if (!remainingSendingTokenOfSenderEnoughED) { @@ -104,35 +75,34 @@ export function additionalValidateTransferForRecipient (sendingTokenInfo: _Chain warnings.push(warning); } - if (!isReceiverAliveAfterSending) { + if (!isReceiverAliveByNativeToken && !isSufficient) { const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, - t('The recipient account has {{amount}} {{nativeSymbol}} which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: _receiverNativeTotal, nativeSymbol: nativeTokenInfo.symbol, localSymbol: tokenInfo.symbol } }) + t('The recipient account has {{amount}} {{nativeSymbol}} which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: _receiverNativeTotal, nativeSymbol: nativeTokenInfo.symbol, localSymbol: sendingTokenInfo.symbol } }) ); errors.push(error); } + // else if (!isReceivingAmountPassED && isSufficient) { + // const atLeast = sendingTokenMinAmount - receiverSendingTokenKeepAliveBalance; + // + // const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6 }); + // + // const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } })); + // + // errors.push(error); + // } } if (!isReceivingAmountPassED) { - const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, - t('The recipient account has {{amount}} {{nativeSymbol}} which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: _receiverNativeTotal, nativeSymbol: nativeTokenInfo.symbol, localSymbol: tokenInfo.symbol } }) - ); - - errors.push(error); - } - - - - - - - - - - + console.log('type', typeof (receiverSendingTokenKeepAliveBalance)); + const atLeast = sendingTokenMinAmount - receiverSendingTokenKeepAliveBalance; + const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6 }); + const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } })); + errors.push(error); + } // if (transferAmount + receiverSendingTokenKeepAliveBalance < sendingTokenMinAmount) { // const atLeast = sendingTokenMinAmount - receiverSendingTokenKeepAliveBalance; diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index 534eae632d..c142f9390d 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -1386,28 +1386,31 @@ export default class KoniExtension { const transferNativeAmount = isTransferNativeToken ? transferAmount.value : '0'; const additionalValidator = async (inputTransaction: SWTransactionResponse): Promise => { - let senderTransferTokenTransferable: string | undefined; + let senderSendingTokenTransferable: bigint | undefined; let receiverNativeTotal: string | undefined; let isReceiverActive: unknown; // Check ed for sender if (!isTransferNativeToken) { - const [_senderTransferTokenTransferable, _receiverNativeTotal] = await Promise.all([ + const [_senderSendingTokenTransferable, _receiverNativeTotal] = await Promise.all([ this.getAddressTransferableBalance({ address: from, networkKey, token: tokenSlug, extrinsicType }), this.getAddressTotalBalance({ address: to, networkKey, token: nativeTokenSlug, extrinsicType: ExtrinsicType.TRANSFER_BALANCE }) ]); - senderTransferTokenTransferable = _senderTransferTokenTransferable.value; + senderSendingTokenTransferable = BigInt(_senderSendingTokenTransferable.value); receiverNativeTotal = _receiverNativeTotal.value; isReceiverActive = _receiverNativeTotal.metadata; } - const { value: receiverTransferTokenTotalBalance } = await this.getAddressTotalBalance({ address: to, networkKey, token: tokenSlug, extrinsicType }); // todo: shouldn't be just transferable, locked also counts + const { value: _receiverSendingTokenKeepAliveBalance } = await this.getAddressTotalBalance({ address: to, networkKey, token: tokenSlug, extrinsicType }); // todo: shouldn't be just transferable, locked also counts + const receiverSendingTokenKeepAliveBalance = BigInt(_receiverSendingTokenKeepAliveBalance); + + const amount = BigInt(transferAmount.value); const substrateApi = this.#koniState.getSubstrateApi(networkKey).api; const isSufficient = await this.isSufficientToken(transferTokenInfo, substrateApi); - const [warnings, errors] = additionalValidateTransferForRecipient(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverTransferTokenTotalBalance, transferAmount.value, senderTransferTokenTransferable, receiverNativeTotal, isReceiverActive, isSufficient); + const [warnings, errors] = additionalValidateTransferForRecipient(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverSendingTokenKeepAliveBalance, amount, senderSendingTokenTransferable, receiverNativeTotal, isReceiverActive, isSufficient); warnings.length && inputTransaction.warnings.push(...warnings); errors.length && inputTransaction.errors.push(...errors); From 70036882547ccbbe9408ca817b1e1310ce5bee4a Mon Sep 17 00:00:00 2001 From: PDTnhah Date: Tue, 26 Nov 2024 18:17:49 +0700 Subject: [PATCH 05/11] [Issue-3713] Refactor: validation logic for transfer --- .../src/core/logic-validation/transfer.ts | 73 ------------------- 1 file changed, 73 deletions(-) diff --git a/packages/extension-base/src/core/logic-validation/transfer.ts b/packages/extension-base/src/core/logic-validation/transfer.ts index 7d842ac50a..1a4d827b9c 100644 --- a/packages/extension-base/src/core/logic-validation/transfer.ts +++ b/packages/extension-base/src/core/logic-validation/transfer.ts @@ -58,7 +58,6 @@ export function validateTransferRequest (tokenInfo: _ChainAsset, from: _Address, export function additionalValidateTransferForRecipient (sendingTokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverSendingTokenKeepAliveBalance: bigint, transferAmount: bigint, senderSendingTokenTransferable?: bigint, _receiverNativeTotal?: string, isReceiverActive?: unknown, isSufficient?: boolean): [TransactionWarning[], TransactionError[]] { const sendingTokenMinAmount = BigInt(_getTokenMinAmount(sendingTokenInfo)); - // const nativeTokenMinAmount = _getTokenMinAmount(nativeTokenInfo); const warnings: TransactionWarning[] = []; const errors: TransactionError[] = []; @@ -66,7 +65,6 @@ export function additionalValidateTransferForRecipient (sendingTokenInfo: _Chain const remainingSendingTokenOfSenderEnoughED = senderSendingTokenTransferable && senderSendingTokenTransferable - transferAmount >= sendingTokenMinAmount; const isReceiverAliveByNativeToken = isReceiverActive && !_isAccountActive(isReceiverActive as FrameSystemAccountInfo); const isReceivingAmountPassED = receiverSendingTokenKeepAliveBalance + transferAmount >= sendingTokenMinAmount; - // const isReceiverAliveAfterSending = isSufficient ? isReceivingAmountPassED : isReceiverAliveByNativeToken; if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN) { if (!remainingSendingTokenOfSenderEnoughED) { @@ -82,19 +80,9 @@ export function additionalValidateTransferForRecipient (sendingTokenInfo: _Chain errors.push(error); } - // else if (!isReceivingAmountPassED && isSufficient) { - // const atLeast = sendingTokenMinAmount - receiverSendingTokenKeepAliveBalance; - // - // const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6 }); - // - // const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } })); - // - // errors.push(error); - // } } if (!isReceivingAmountPassED) { - console.log('type', typeof (receiverSendingTokenKeepAliveBalance)); const atLeast = sendingTokenMinAmount - receiverSendingTokenKeepAliveBalance; const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6 }); @@ -104,67 +92,6 @@ export function additionalValidateTransferForRecipient (sendingTokenInfo: _Chain errors.push(error); } - // if (transferAmount + receiverSendingTokenKeepAliveBalance < sendingTokenMinAmount) { - // const atLeast = sendingTokenMinAmount - receiverSendingTokenKeepAliveBalance; - // - // const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6 }); - // - // const error = new TransactionError( - // TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, - // t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: _getAssetSymbol(sendingTokenInfo) } }) - // ); - // - // errors.push(error); - // } - - // if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN) { - // // if (senderSendingTokenTransferable - transferAmount < sendingTokenMinAmount) { - // // const warning = new TransactionWarning(BasicTxWarningCode.NOT_ENOUGH_EXISTENTIAL_DEPOSIT); - // // - // // warnings.push(warning); - // // } - // - // if (!isSufficient && new BigN(nativeTokenMinAmount).gt(0)) { - // // Check ed for receiver before sending - // // if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN && _receiverNativeTotal) { - // // if (new BigN(_receiverNativeTotal).lt(nativeMinAmount)) { - // // const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('The recipient account has {{amount}} {{nativeSymbol}} which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: _receiverNativeTotal, nativeSymbol: nativeTokenInfo.symbol, localSymbol: tokenInfo.symbol } })); - // // - // // errors.push(error); - // // } - // // } - // - // // Check if receiver's account is active - // if () { - // const error = new TransactionError(TransferTxErrorType.RECEIVER_ACCOUNT_INACTIVE, t('The recipient account may be inactive. Change recipient account and try again')); - // - // errors.push(error); - // } - // - // // Check ed for receiver after sending - // if (new BigN(receiverSendingTokenKeepAliveBalance).plus(transferAmount).lt(sendingTokenMinAmount)) { - // const atLeast = new BigN(sendingTokenMinAmount).minus(receiverSendingTokenKeepAliveBalance).plus((sendingTokenInfo.decimals || 0) === 0 ? 0 : 1); - // - // const atLeastStr = formatNumber(atLeast, sendingTokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: sendingTokenInfo.decimals || 6 }); - // - // const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } })); - // - // errors.push(error); - // } - // } - // } - // - // // Check ed for receiver after sending - // if (new BigN(receiverSendingTokenKeepAliveBalance).plus(transferAmount).lt(sendingTokenMinAmount)) { - // const atLeast = new BigN(sendingTokenMinAmount).minus(receiverSendingTokenKeepAliveBalance).plus((sendingTokenInfo.decimals || 0) === 0 ? 0 : 1); - // - // const atLeastStr = formatNumber(atLeast, sendingTokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: sendingTokenInfo.decimals || 6 }); - // - // const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } })); - // - // errors.push(error); - // } - return [warnings, errors]; } From c823e7f59c99afe374d10625de5d6d43c3dae2d5 Mon Sep 17 00:00:00 2001 From: PDTnhah Date: Thu, 28 Nov 2024 15:01:19 +0700 Subject: [PATCH 06/11] [Issue-3713] Update message error --- .../src/core/logic-validation/transfer.ts | 19 ++++++++++++++++--- .../src/koni/background/handlers/Extension.ts | 4 +--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/extension-base/src/core/logic-validation/transfer.ts b/packages/extension-base/src/core/logic-validation/transfer.ts index 1a4d827b9c..2134d7f8c0 100644 --- a/packages/extension-base/src/core/logic-validation/transfer.ts +++ b/packages/extension-base/src/core/logic-validation/transfer.ts @@ -56,8 +56,9 @@ export function validateTransferRequest (tokenInfo: _ChainAsset, from: _Address, return [errors, keypair, transferValue]; } -export function additionalValidateTransferForRecipient (sendingTokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverSendingTokenKeepAliveBalance: bigint, transferAmount: bigint, senderSendingTokenTransferable?: bigint, _receiverNativeTotal?: string, isReceiverActive?: unknown, isSufficient?: boolean): [TransactionWarning[], TransactionError[]] { +export function additionalValidateTransferForRecipient (sendingTokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverSendingTokenKeepAliveBalance: bigint, transferAmount: bigint, senderSendingTokenTransferable?: bigint, isReceiverActive?: unknown, isSufficient?: boolean): [TransactionWarning[], TransactionError[]] { const sendingTokenMinAmount = BigInt(_getTokenMinAmount(sendingTokenInfo)); + const nativeTokenMinAmount = _getTokenMinAmount(nativeTokenInfo); const warnings: TransactionWarning[] = []; const errors: TransactionError[] = []; @@ -74,12 +75,24 @@ export function additionalValidateTransferForRecipient (sendingTokenInfo: _Chain } if (!isReceiverAliveByNativeToken && !isSufficient) { + const balanceKeepAlive = formatNumber(nativeTokenMinAmount, _getAssetDecimals(nativeTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(nativeTokenInfo) || 6 }); + const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, - t('The recipient account has {{amount}} {{nativeSymbol}} which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: _receiverNativeTotal, nativeSymbol: nativeTokenInfo.symbol, localSymbol: sendingTokenInfo.symbol } }) + t('The recipient account has less than {{amount}} {{nativeSymbol}}, which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: balanceKeepAlive, nativeSymbol: nativeTokenInfo.symbol, localSymbol: sendingTokenInfo.symbol } }) ); errors.push(error); } + + if (!isReceivingAmountPassED) { + const atLeast = sendingTokenMinAmount - receiverSendingTokenKeepAliveBalance; + + const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6 }); + + const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to avoid losing funds on the recipient account. Increase amount and try again', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } })); + + errors.push(error); + } } if (!isReceivingAmountPassED) { @@ -87,7 +100,7 @@ export function additionalValidateTransferForRecipient (sendingTokenInfo: _Chain const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6 }); - const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } })); + const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the recipient account alive. Increase amount and try again', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } })); errors.push(error); } diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index df90673369..aff9dce002 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -1388,7 +1388,6 @@ export default class KoniExtension { const additionalValidator = async (inputTransaction: SWTransactionResponse): Promise => { let senderSendingTokenTransferable: bigint | undefined; - let receiverNativeTotal: string | undefined; let isReceiverActive: unknown; // Check ed for sender @@ -1399,7 +1398,6 @@ export default class KoniExtension { ]); senderSendingTokenTransferable = BigInt(_senderSendingTokenTransferable.value); - receiverNativeTotal = _receiverNativeTotal.value; isReceiverActive = _receiverNativeTotal.metadata; } @@ -1411,7 +1409,7 @@ export default class KoniExtension { const substrateApi = this.#koniState.getSubstrateApi(networkKey).api; const isSufficient = await this.isSufficientToken(transferTokenInfo, substrateApi); - const [warnings, errors] = additionalValidateTransferForRecipient(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverSendingTokenKeepAliveBalance, amount, senderSendingTokenTransferable, receiverNativeTotal, isReceiverActive, isSufficient); + const [warnings, errors] = additionalValidateTransferForRecipient(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverSendingTokenKeepAliveBalance, amount, senderSendingTokenTransferable, isReceiverActive, isSufficient); warnings.length && inputTransaction.warnings.push(...warnings); errors.length && inputTransaction.errors.push(...errors); From a98f1cb3b52e20b859d62b49dfe63125eedc15ca Mon Sep 17 00:00:00 2001 From: nampc Date: Fri, 29 Nov 2024 15:20:34 +0700 Subject: [PATCH 07/11] [Issue 3713] fix: refactor code --- .../src/core/logic-validation/transfer.ts | 30 ++++++++++++++----- .../src/core/substrate/system-pallet.ts | 2 +- .../src/koni/background/handlers/Extension.ts | 7 +++-- .../src/services/balance-service/index.ts | 20 ++++++++++--- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/packages/extension-base/src/core/logic-validation/transfer.ts b/packages/extension-base/src/core/logic-validation/transfer.ts index 2134d7f8c0..a0234ce650 100644 --- a/packages/extension-base/src/core/logic-validation/transfer.ts +++ b/packages/extension-base/src/core/logic-validation/transfer.ts @@ -56,15 +56,24 @@ export function validateTransferRequest (tokenInfo: _ChainAsset, from: _Address, return [errors, keypair, transferValue]; } -export function additionalValidateTransferForRecipient (sendingTokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverSendingTokenKeepAliveBalance: bigint, transferAmount: bigint, senderSendingTokenTransferable?: bigint, isReceiverActive?: unknown, isSufficient?: boolean): [TransactionWarning[], TransactionError[]] { +export function additionalValidateTransferForRecipient ( + sendingTokenInfo: _ChainAsset, + nativeTokenInfo: _ChainAsset, + extrinsicType: ExtrinsicType, + receiverSendingTokenKeepAliveBalance: bigint, + transferAmount: bigint, + senderSendingTokenTransferable?: bigint, + receiverSystemAccountInfo?: FrameSystemAccountInfo, + isSendingTokenSufficient?: boolean +): [TransactionWarning[], TransactionError[]] { const sendingTokenMinAmount = BigInt(_getTokenMinAmount(sendingTokenInfo)); const nativeTokenMinAmount = _getTokenMinAmount(nativeTokenInfo); const warnings: TransactionWarning[] = []; const errors: TransactionError[] = []; - const remainingSendingTokenOfSenderEnoughED = senderSendingTokenTransferable && senderSendingTokenTransferable - transferAmount >= sendingTokenMinAmount; - const isReceiverAliveByNativeToken = isReceiverActive && !_isAccountActive(isReceiverActive as FrameSystemAccountInfo); + const remainingSendingTokenOfSenderEnoughED = senderSendingTokenTransferable ? senderSendingTokenTransferable - transferAmount >= sendingTokenMinAmount : false; + const isReceiverAliveByNativeToken = _isAccountActive(receiverSystemAccountInfo as FrameSystemAccountInfo); const isReceivingAmountPassED = receiverSendingTokenKeepAliveBalance + transferAmount >= sendingTokenMinAmount; if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN) { @@ -74,10 +83,11 @@ export function additionalValidateTransferForRecipient (sendingTokenInfo: _Chain warnings.push(warning); } - if (!isReceiverAliveByNativeToken && !isSufficient) { + if (!isReceiverAliveByNativeToken && !isSendingTokenSufficient) { const balanceKeepAlive = formatNumber(nativeTokenMinAmount, _getAssetDecimals(nativeTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(nativeTokenInfo) || 6 }); - const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, + const error = new TransactionError( + TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('The recipient account has less than {{amount}} {{nativeSymbol}}, which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: balanceKeepAlive, nativeSymbol: nativeTokenInfo.symbol, localSymbol: sendingTokenInfo.symbol } }) ); @@ -89,7 +99,10 @@ export function additionalValidateTransferForRecipient (sendingTokenInfo: _Chain const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6 }); - const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to avoid losing funds on the recipient account. Increase amount and try again', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } })); + const error = new TransactionError( + TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, + t('You must transfer at least {{amount}} {{symbol}} to avoid losing funds on the recipient account. Increase amount and try again', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } }) + ); errors.push(error); } @@ -100,7 +113,10 @@ export function additionalValidateTransferForRecipient (sendingTokenInfo: _Chain const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6 }); - const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the recipient account alive. Increase amount and try again', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } })); + const error = new TransactionError( + TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, + t('You must transfer at least {{amount}} {{symbol}} to keep the recipient account alive. Increase amount and try again', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } }) + ); errors.push(error); } diff --git a/packages/extension-base/src/core/substrate/system-pallet.ts b/packages/extension-base/src/core/substrate/system-pallet.ts index 57fa499955..0d5fb46685 100644 --- a/packages/extension-base/src/core/substrate/system-pallet.ts +++ b/packages/extension-base/src/core/substrate/system-pallet.ts @@ -24,7 +24,7 @@ export function _canAccountBeReaped (accountInfo: FrameSystemAccountInfo): boole } export function _isAccountActive (accountInfo: FrameSystemAccountInfo): boolean { - return accountInfo.consumers === 0 && accountInfo.providers === 0 && accountInfo.sufficients === 0; + return !(accountInfo.consumers === 0 && accountInfo.providers === 0 && accountInfo.sufficients === 0); } export function _getSystemPalletTotalBalance (accountInfo: FrameSystemAccountInfo): bigint { diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index aff9dce002..162993eb77 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -77,6 +77,7 @@ import { ChainProperties } from '@polkadot/types/interfaces'; import { Registry, SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types'; import { assert, hexStripPrefix, hexToU8a, isAscii, isHex, u8aToHex } from '@polkadot/util'; import { decodeAddress, isEthereumAddress } from '@polkadot/util-crypto'; +import {FrameSystemAccountInfo} from "@subwallet/extension-base/core/substrate/types"; export function isJsonPayload (value: SignerPayloadJSON | SignerPayloadRaw): value is SignerPayloadJSON { return (value as SignerPayloadJSON).genesisHash !== undefined; @@ -1388,7 +1389,7 @@ export default class KoniExtension { const additionalValidator = async (inputTransaction: SWTransactionResponse): Promise => { let senderSendingTokenTransferable: bigint | undefined; - let isReceiverActive: unknown; + let receiverSystemAccountInfo: FrameSystemAccountInfo | undefined; // Check ed for sender if (!isTransferNativeToken) { @@ -1398,7 +1399,7 @@ export default class KoniExtension { ]); senderSendingTokenTransferable = BigInt(_senderSendingTokenTransferable.value); - isReceiverActive = _receiverNativeTotal.metadata; + receiverSystemAccountInfo = _receiverNativeTotal.metadata as FrameSystemAccountInfo; } const { value: _receiverSendingTokenKeepAliveBalance } = await this.getAddressTotalBalance({ address: to, networkKey, token: tokenSlug, extrinsicType }); // todo: shouldn't be just transferable, locked also counts @@ -1409,7 +1410,7 @@ export default class KoniExtension { const substrateApi = this.#koniState.getSubstrateApi(networkKey).api; const isSufficient = await this.isSufficientToken(transferTokenInfo, substrateApi); - const [warnings, errors] = additionalValidateTransferForRecipient(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverSendingTokenKeepAliveBalance, amount, senderSendingTokenTransferable, isReceiverActive, isSufficient); + const [warnings, errors] = additionalValidateTransferForRecipient(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverSendingTokenKeepAliveBalance, amount, senderSendingTokenTransferable, receiverSystemAccountInfo, isSufficient); warnings.length && inputTransaction.warnings.push(...warnings); errors.length && inputTransaction.errors.push(...errors); diff --git a/packages/extension-base/src/services/balance-service/index.ts b/packages/extension-base/src/services/balance-service/index.ts index d588226aec..bfef7eee7c 100644 --- a/packages/extension-base/src/services/balance-service/index.ts +++ b/packages/extension-base/src/services/balance-service/index.ts @@ -190,7 +190,14 @@ export class BalanceService implements StoppableServiceInterface { } /** Subscribe token free balance of an address on chain */ - public async subscribeBalance (address: string, chain: string, tokenSlug: string | undefined, balanceType: 'transferable' | 'total', extrinsicType?: ExtrinsicType, callback?: (rs: AmountData) => void): Promise<[() => void, AmountData]> { + public async subscribeBalance ( + address: string, + chain: string, + tokenSlug: string | undefined, + balanceType: 'transferable' | 'total' | 'keepAlive' = 'transferable', + extrinsicType?: ExtrinsicType, + callback?: (rs: AmountData) => void + ): Promise<[() => void, AmountData]> { const chainInfo = this.state.chainService.getChainInfoByKey(chain); const chainState = this.state.chainService.getChainStateByKey(chain); @@ -219,9 +226,14 @@ export class BalanceService implements StoppableServiceInterface { unsub = subscribeBalance([address], [chain], [tSlug], assetMap, chainInfoMap, substrateApiMap, evmApiMap, tonApiMap, (result) => { const rs = result[0]; - const value = balanceType === 'total' - ? new BigN(rs.free).plus(new BigN(rs.locked)).toString() - : rs.free; + let value: string; + switch (balanceType) { + case 'total': + value = new BigN(rs.free).plus(new BigN(rs.locked)).toString(); + break; + default: + value = rs.free; + } if (rs.tokenSlug === tSlug && rs.state !== APIItemState.PENDING) { hasError = false; From 5b97d3c218f9df93704a375599d5d5df70ac2c78 Mon Sep 17 00:00:00 2001 From: nampc Date: Fri, 29 Nov 2024 16:04:21 +0700 Subject: [PATCH 08/11] [Issue 3713] fix: eslint --- .../extension-base/src/koni/background/handlers/Extension.ts | 2 +- packages/extension-base/src/services/balance-service/index.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index 162993eb77..d769218950 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -12,6 +12,7 @@ import { AccountAuthType, AuthorizeRequest, MessageTypes, MetadataRequest, Reque import { TransactionWarning } from '@subwallet/extension-base/background/warnings/TransactionWarning'; import { ALL_ACCOUNT_KEY, LATEST_SESSION, XCM_FEE_RATIO } from '@subwallet/extension-base/constants'; import { additionalValidateTransferForRecipient, additionalValidateXcmTransfer, validateTransferRequest, validateXcmTransferRequest } from '@subwallet/extension-base/core/logic-validation/transfer'; +import { FrameSystemAccountInfo } from '@subwallet/extension-base/core/substrate/types'; import { _isSnowBridgeXcm } from '@subwallet/extension-base/core/substrate/xcm-parser'; import { ALLOWED_PATH } from '@subwallet/extension-base/defaults'; import { getERC20SpendingApprovalTx } from '@subwallet/extension-base/koni/api/contract-handler/evm/web3'; @@ -77,7 +78,6 @@ import { ChainProperties } from '@polkadot/types/interfaces'; import { Registry, SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types'; import { assert, hexStripPrefix, hexToU8a, isAscii, isHex, u8aToHex } from '@polkadot/util'; import { decodeAddress, isEthereumAddress } from '@polkadot/util-crypto'; -import {FrameSystemAccountInfo} from "@subwallet/extension-base/core/substrate/types"; export function isJsonPayload (value: SignerPayloadJSON | SignerPayloadRaw): value is SignerPayloadJSON { return (value as SignerPayloadJSON).genesisHash !== undefined; diff --git a/packages/extension-base/src/services/balance-service/index.ts b/packages/extension-base/src/services/balance-service/index.ts index bfef7eee7c..2d7703ec73 100644 --- a/packages/extension-base/src/services/balance-service/index.ts +++ b/packages/extension-base/src/services/balance-service/index.ts @@ -227,6 +227,7 @@ export class BalanceService implements StoppableServiceInterface { const rs = result[0]; let value: string; + switch (balanceType) { case 'total': value = new BigN(rs.free).plus(new BigN(rs.locked)).toString(); From ea9be2f5f0fef69c00a803ee3c1dbe7dd1d9d441 Mon Sep 17 00:00:00 2001 From: PDTnhah Date: Tue, 3 Dec 2024 18:53:11 +0700 Subject: [PATCH 09/11] [Issue-3713] Refactor code --- .../src/core/logic-validation/transfer.ts | 2 +- .../src/koni/background/handlers/Extension.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/extension-base/src/core/logic-validation/transfer.ts b/packages/extension-base/src/core/logic-validation/transfer.ts index a0234ce650..57be57f2cd 100644 --- a/packages/extension-base/src/core/logic-validation/transfer.ts +++ b/packages/extension-base/src/core/logic-validation/transfer.ts @@ -73,7 +73,7 @@ export function additionalValidateTransferForRecipient ( const errors: TransactionError[] = []; const remainingSendingTokenOfSenderEnoughED = senderSendingTokenTransferable ? senderSendingTokenTransferable - transferAmount >= sendingTokenMinAmount : false; - const isReceiverAliveByNativeToken = _isAccountActive(receiverSystemAccountInfo as FrameSystemAccountInfo); + const isReceiverAliveByNativeToken = receiverSystemAccountInfo ? _isAccountActive(receiverSystemAccountInfo) : false; const isReceivingAmountPassED = receiverSendingTokenKeepAliveBalance + transferAmount >= sendingTokenMinAmount; if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN) { diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index d769218950..9371017b27 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -1391,6 +1391,10 @@ export default class KoniExtension { let senderSendingTokenTransferable: bigint | undefined; let receiverSystemAccountInfo: FrameSystemAccountInfo | undefined; + if (chainInfo.evmInfo) { + return undefined; + } + // Check ed for sender if (!isTransferNativeToken) { const [_senderSendingTokenTransferable, _receiverNativeTotal] = await Promise.all([ @@ -1408,9 +1412,9 @@ export default class KoniExtension { const amount = BigInt(transferAmount.value); const substrateApi = this.#koniState.getSubstrateApi(networkKey).api; - const isSufficient = await this.isSufficientToken(transferTokenInfo, substrateApi); + const isSendingTokenSufficient = await this.isSufficientToken(transferTokenInfo, substrateApi); - const [warnings, errors] = additionalValidateTransferForRecipient(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverSendingTokenKeepAliveBalance, amount, senderSendingTokenTransferable, receiverSystemAccountInfo, isSufficient); + const [warnings, errors] = additionalValidateTransferForRecipient(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverSendingTokenKeepAliveBalance, amount, senderSendingTokenTransferable, receiverSystemAccountInfo, isSendingTokenSufficient); warnings.length && inputTransaction.warnings.push(...warnings); errors.length && inputTransaction.errors.push(...errors); @@ -1684,13 +1688,13 @@ export default class KoniExtension { } } - private async isSufficientToken (tokenInfo: _ChainAsset, api: ApiPromise): Promise { + private async isSufficientToken (tokenInfo: _ChainAsset, substrateApi: ApiPromise): Promise { let metadata: SufficientMetadata; if (SUFFICIENT_CHAIN.includes(tokenInfo.originChain) && tokenInfo.assetType !== _AssetType.NATIVE) { const assetId = tokenInfo?.metadata?.assetId; - const _metadata = await api.query.assets.asset(assetId); + const _metadata = await substrateApi.query.assets.asset(assetId); metadata = _metadata.toPrimitive() as unknown as SufficientMetadata; } else { From 0ab4af8a59dd732d30129bb1c75cca90f98b5fa7 Mon Sep 17 00:00:00 2001 From: S2kael Date: Fri, 6 Dec 2024 15:38:44 +0700 Subject: [PATCH 10/11] [Issue-3713] Fix cannot transfer TON --- .../extension-base/src/koni/background/handlers/Extension.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index 9371017b27..4cafa62748 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -36,7 +36,7 @@ import { getClaimTxOnAvail, getClaimTxOnEthereum, isAvailChainBridge } from '@su import { _isPolygonChainBridge, getClaimPolygonBridge, isClaimedPolygonBridge } from '@subwallet/extension-base/services/balance-service/transfer/xcm/polygonBridge'; import { _API_OPTIONS_CHAIN_GROUP, _DEFAULT_MANTA_ZK_CHAIN, _MANTA_ZK_CHAIN_GROUP, _ZK_ASSET_PREFIX, SUFFICIENT_CHAIN } from '@subwallet/extension-base/services/chain-service/constants'; import { _ChainApiStatus, _ChainConnectionStatus, _ChainState, _NetworkUpsertParams, _ValidateCustomAssetRequest, _ValidateCustomAssetResponse, EnableChainParams, EnableMultiChainParams } from '@subwallet/extension-base/services/chain-service/types'; -import { _getAssetDecimals, _getAssetSymbol, _getChainNativeTokenBasicInfo, _getContractAddressOfToken, _getEvmChainId, _isAssetSmartContractNft, _isChainEvmCompatible, _isChainTonCompatible, _isCustomAsset, _isLocalToken, _isMantaZkAsset, _isNativeToken, _isPureEvmChain, _isTokenEvmSmartContract, _isTokenTransferredByEvm, _isTokenTransferredByTon } from '@subwallet/extension-base/services/chain-service/utils'; +import { _getAssetDecimals, _getAssetSymbol, _getChainNativeTokenBasicInfo, _getContractAddressOfToken, _getEvmChainId, _isAssetSmartContractNft, _isChainEvmCompatible, _isChainSubstrateCompatible, _isChainTonCompatible, _isCustomAsset, _isLocalToken, _isMantaZkAsset, _isNativeToken, _isPureEvmChain, _isTokenEvmSmartContract, _isTokenTransferredByEvm, _isTokenTransferredByTon } from '@subwallet/extension-base/services/chain-service/utils'; import { _NotificationInfo, NotificationSetup } from '@subwallet/extension-base/services/inapp-notification-service/interfaces'; import { AppBannerData, AppConfirmationData, AppPopupData } from '@subwallet/extension-base/services/mkt-campaign-service/types'; import { EXTENSION_REQUEST_URL } from '@subwallet/extension-base/services/request-service/constants'; @@ -1391,7 +1391,7 @@ export default class KoniExtension { let senderSendingTokenTransferable: bigint | undefined; let receiverSystemAccountInfo: FrameSystemAccountInfo | undefined; - if (chainInfo.evmInfo) { + if (!_isChainSubstrateCompatible(chainInfo)) { return undefined; } From 10d9db6ae245718e39bb12d4990d72bf63861243 Mon Sep 17 00:00:00 2001 From: nampc Date: Fri, 6 Dec 2024 16:28:49 +0700 Subject: [PATCH 11/11] [Issue 3713] fix: minor bugs --- .../src/koni/background/handlers/Extension.ts | 26 ++++++++++++------- .../src/services/balance-service/index.ts | 2 +- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index 9371017b27..fea718562d 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -35,8 +35,8 @@ import { createAvailBridgeExtrinsicFromAvail, createAvailBridgeTxFromEth, create import { getClaimTxOnAvail, getClaimTxOnEthereum, isAvailChainBridge } from '@subwallet/extension-base/services/balance-service/transfer/xcm/availBridge'; import { _isPolygonChainBridge, getClaimPolygonBridge, isClaimedPolygonBridge } from '@subwallet/extension-base/services/balance-service/transfer/xcm/polygonBridge'; import { _API_OPTIONS_CHAIN_GROUP, _DEFAULT_MANTA_ZK_CHAIN, _MANTA_ZK_CHAIN_GROUP, _ZK_ASSET_PREFIX, SUFFICIENT_CHAIN } from '@subwallet/extension-base/services/chain-service/constants'; -import { _ChainApiStatus, _ChainConnectionStatus, _ChainState, _NetworkUpsertParams, _ValidateCustomAssetRequest, _ValidateCustomAssetResponse, EnableChainParams, EnableMultiChainParams } from '@subwallet/extension-base/services/chain-service/types'; -import { _getAssetDecimals, _getAssetSymbol, _getChainNativeTokenBasicInfo, _getContractAddressOfToken, _getEvmChainId, _isAssetSmartContractNft, _isChainEvmCompatible, _isChainTonCompatible, _isCustomAsset, _isLocalToken, _isMantaZkAsset, _isNativeToken, _isPureEvmChain, _isTokenEvmSmartContract, _isTokenTransferredByEvm, _isTokenTransferredByTon } from '@subwallet/extension-base/services/chain-service/utils'; +import { _ChainApiStatus, _ChainConnectionStatus, _ChainState, _NetworkUpsertParams, _SubstrateAdapterQueryArgs, _SubstrateApi, _ValidateCustomAssetRequest, _ValidateCustomAssetResponse, EnableChainParams, EnableMultiChainParams } from '@subwallet/extension-base/services/chain-service/types'; +import { _getAssetDecimals, _getAssetSymbol, _getChainNativeTokenBasicInfo, _getContractAddressOfToken, _getEvmChainId, _getTokenOnChainAssetId, _getXcmAssetMultilocation, _isAssetSmartContractNft, _isBridgedToken, _isChainEvmCompatible, _isChainTonCompatible, _isCustomAsset, _isLocalToken, _isMantaZkAsset, _isNativeToken, _isPureEvmChain, _isTokenEvmSmartContract, _isTokenTransferredByEvm, _isTokenTransferredByTon } from '@subwallet/extension-base/services/chain-service/utils'; import { _NotificationInfo, NotificationSetup } from '@subwallet/extension-base/services/inapp-notification-service/interfaces'; import { AppBannerData, AppConfirmationData, AppPopupData } from '@subwallet/extension-base/services/mkt-campaign-service/types'; import { EXTENSION_REQUEST_URL } from '@subwallet/extension-base/services/request-service/constants'; @@ -71,11 +71,10 @@ import { t } from 'i18next'; import { combineLatest, Subject } from 'rxjs'; import { TransactionConfig } from 'web3-core'; -import { ApiPromise } from '@polkadot/api'; import { SubmittableExtrinsic } from '@polkadot/api/types'; import { Metadata, TypeRegistry } from '@polkadot/types'; import { ChainProperties } from '@polkadot/types/interfaces'; -import { Registry, SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types'; +import { AnyJson, Registry, SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types'; import { assert, hexStripPrefix, hexToU8a, isAscii, isHex, u8aToHex } from '@polkadot/util'; import { decodeAddress, isEthereumAddress } from '@polkadot/util-crypto'; @@ -1411,7 +1410,7 @@ export default class KoniExtension { const amount = BigInt(transferAmount.value); - const substrateApi = this.#koniState.getSubstrateApi(networkKey).api; + const substrateApi = this.#koniState.getSubstrateApi(networkKey); const isSendingTokenSufficient = await this.isSufficientToken(transferTokenInfo, substrateApi); const [warnings, errors] = additionalValidateTransferForRecipient(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverSendingTokenKeepAliveBalance, amount, senderSendingTokenTransferable, receiverSystemAccountInfo, isSendingTokenSufficient); @@ -1688,15 +1687,24 @@ export default class KoniExtension { } } - private async isSufficientToken (tokenInfo: _ChainAsset, substrateApi: ApiPromise): Promise { + private async isSufficientToken (tokenInfo: _ChainAsset, substrateApi: _SubstrateApi): Promise { let metadata: SufficientMetadata; if (SUFFICIENT_CHAIN.includes(tokenInfo.originChain) && tokenInfo.assetType !== _AssetType.NATIVE) { - const assetId = tokenInfo?.metadata?.assetId; + const assetId = _isBridgedToken(tokenInfo) ? _getXcmAssetMultilocation(tokenInfo) : _getTokenOnChainAssetId(tokenInfo); - const _metadata = await substrateApi.query.assets.asset(assetId); + const queryParams: _SubstrateAdapterQueryArgs = { + section: 'query', + module: 'foreignAssets', + method: 'asset', + args: [assetId] + }; + + if (!_isBridgedToken(tokenInfo)) { + queryParams.module = 'assets'; + } - metadata = _metadata.toPrimitive() as unknown as SufficientMetadata; + metadata = (await substrateApi.makeRpcQuery(queryParams)) as unknown as SufficientMetadata; } else { return false; } diff --git a/packages/extension-base/src/services/balance-service/index.ts b/packages/extension-base/src/services/balance-service/index.ts index 2d7703ec73..9db8df64e1 100644 --- a/packages/extension-base/src/services/balance-service/index.ts +++ b/packages/extension-base/src/services/balance-service/index.ts @@ -230,7 +230,7 @@ export class BalanceService implements StoppableServiceInterface { switch (balanceType) { case 'total': - value = new BigN(rs.free).plus(new BigN(rs.locked)).toString(); + value = new BigN(rs.free).plus(new BigN(rs.locked)).toFixed(); break; default: value = rs.free;