From e23eef1a722e1a8e82cac5a576f6365094caec6e Mon Sep 17 00:00:00 2001 From: PDTnhah Date: Tue, 10 Dec 2024 15:56:12 +0700 Subject: [PATCH 1/6] [Issue-3895] Validate recipient when make XCM transfer --- .../src/core/logic-validation/transfer.ts | 65 +++++++++++++++---- .../src/koni/background/handlers/Extension.ts | 18 +++-- 2 files changed, 65 insertions(+), 18 deletions(-) diff --git a/packages/extension-base/src/core/logic-validation/transfer.ts b/packages/extension-base/src/core/logic-validation/transfer.ts index 57be57f2cd..845e560c79 100644 --- a/packages/extension-base/src/core/logic-validation/transfer.ts +++ b/packages/extension-base/src/core/logic-validation/transfer.ts @@ -137,37 +137,74 @@ export function validateXcmTransferRequest (destTokenInfo: _ChainAsset | undefin return [errors, keypair, transferValue]; } -export function additionalValidateXcmTransfer (originTokenInfo: _ChainAsset, destinationTokenInfo: _ChainAsset, sendingAmount: string, senderTransferable: string, receiverNativeBalance: string, destChainInfo: _ChainInfo, isSnowBridge = false): [TransactionWarning | undefined, TransactionError | undefined] { +export function additionalValidateXcmTransfer ( + originTokenInfo: _ChainAsset, + destinationTokenInfo: _ChainAsset, + sendingAmount: bigint, + senderTransferable: bigint, + destChainInfo: _ChainInfo, + isSendingTokenSufficient: boolean, + receiverNativeBalance?: bigint, + isSnowBridge = false +): [TransactionWarning | undefined, TransactionError | undefined] { const destMinAmount = _getTokenMinAmount(destinationTokenInfo); - const minSendingRequired = new BigN(destMinAmount).multipliedBy(XCM_MIN_AMOUNT_RATIO); + const minSendingRequired = BigInt(destMinAmount) * BigInt(XCM_MIN_AMOUNT_RATIO.toFixed()); let error: TransactionError | undefined; let warning: TransactionWarning | undefined; + const isSendingAmountEnough = sendingAmount > minSendingRequired; + const bnKeepAliveBalance = _isNativeToken(destinationTokenInfo) && receiverNativeBalance ? receiverNativeBalance + sendingAmount : receiverNativeBalance; + const isReceiverAmountPassED = isSnowBridge && bnKeepAliveBalance ? bnKeepAliveBalance > BigInt(_getChainExistentialDeposit(destChainInfo)) : true; + const remainingSendingTokenOfSenderEnoughED = !_isNativeToken(originTokenInfo) ? senderTransferable - sendingAmount > BigInt(_getTokenMinAmount(originTokenInfo)) : true; + // const isNativeTokenReceiverAmountPassED = isSnowBridge ? receiverNativeBalance + sendingAmount > BigInt(_getChainExistentialDeposit(destChainInfo)) : false; + // const isReceiverAmountPassED = isSnowBridge ? receiverNativeBalance > BigInt(_getChainExistentialDeposit(destChainInfo)) : true; + // Check sending token ED for receiver - if (new BigN(sendingAmount).lt(minSendingRequired)) { - const atLeastStr = formatNumber(minSendingRequired, destinationTokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: destinationTokenInfo.decimals || 6 }); + if (!isSendingAmountEnough) { + const atLeastStr = formatNumber(minSendingRequired.toString(), destinationTokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: destinationTokenInfo.decimals || 6 }); 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: originTokenInfo.symbol } })); } // check native token ED on dest chain for receiver - const bnKeepAliveBalance = _isNativeToken(destinationTokenInfo) ? new BigN(receiverNativeBalance).plus(sendingAmount) : new BigN(receiverNativeBalance); - - if (isSnowBridge && bnKeepAliveBalance.lt(_getChainExistentialDeposit(destChainInfo))) { - const { decimals, symbol } = _getChainNativeTokenBasicInfo(destChainInfo); - const atLeastStr = formatNumber(_getChainExistentialDeposit(destChainInfo), decimals || 0, balanceFormatter, { maxNumberFormat: 6 }); + if (_isNativeToken(destinationTokenInfo) || !isSendingTokenSufficient) { + if (!isReceiverAmountPassED) { + const { decimals, symbol } = _getChainNativeTokenBasicInfo(destChainInfo); + const atLeastStr = formatNumber(_getChainExistentialDeposit(destChainInfo), decimals || 0, balanceFormatter, { maxNumberFormat: 6 }); - error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t(' Insufficient {{symbol}} on {{chain}} to cover min balance ({{amount}} {{symbol}})', { replace: { amount: atLeastStr, symbol, chain: destChainInfo.name } })); + error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t(' Insufficient {{symbol}} on {{chain}} to cover min balance ({{amount}} {{symbol}})', { replace: { amount: atLeastStr, symbol, chain: destChainInfo.name } })); + } } // Check ed for sender - if (!_isNativeToken(originTokenInfo)) { - if (new BigN(senderTransferable).minus(sendingAmount).lt(_getTokenMinAmount(originTokenInfo))) { - warning = new TransactionWarning(BasicTxWarningCode.NOT_ENOUGH_EXISTENTIAL_DEPOSIT); - } + if (!remainingSendingTokenOfSenderEnoughED) { + warning = new TransactionWarning(BasicTxWarningCode.NOT_ENOUGH_EXISTENTIAL_DEPOSIT); } + // // Check sending token ED for receiver + // if (new BigN(sendingAmount).lt(minSendingRequired)) { + // const atLeastStr = formatNumber(minSendingRequired, destinationTokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: destinationTokenInfo.decimals || 6 }); + // + // 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: originTokenInfo.symbol } })); + // } + // + // // check native token ED on dest chain for receiver + // + // if (isSnowBridge && bnKeepAliveBalance.lt(_getChainExistentialDeposit(destChainInfo))) { + // const { decimals, symbol } = _getChainNativeTokenBasicInfo(destChainInfo); + // const atLeastStr = formatNumber(_getChainExistentialDeposit(destChainInfo), decimals || 0, balanceFormatter, { maxNumberFormat: 6 }); + // + // error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t(' Insufficient {{symbol}} on {{chain}} to cover min balance ({{amount}} {{symbol}})', { replace: { amount: atLeastStr, symbol, chain: destChainInfo.name } })); + // } + // + // // Check ed for sender + // if (!_isNativeToken(originTokenInfo)) { + // if (new BigN(senderTransferable).minus(sendingAmount).lt(_getTokenMinAmount(originTokenInfo))) { + // warning = new TransactionWarning(BasicTxWarningCode.NOT_ENOUGH_EXISTENTIAL_DEPOSIT); + // } + // } + return [warning, error]; } diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index bd00d7aab4..a9d9bffe75 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -1498,17 +1498,27 @@ export default class KoniExtension { extrinsic = await funcCreateExtrinsic(params); additionalValidator = async (inputTransaction: SWTransactionResponse): Promise => { - const { value: senderTransferable } = await this.getAddressTransferableBalance({ address: from, networkKey: originNetworkKey, token: originTokenInfo.slug }); + const { value: _senderTransferable } = await this.getAddressTransferableBalance({ address: from, networkKey: originNetworkKey, token: originTokenInfo.slug }); const isSnowBridge = _isSnowBridgeXcm(chainInfoMap[originNetworkKey], chainInfoMap[destinationNetworkKey]); - let recipientNativeBalance = '0'; + let receiverNativeBalance: bigint | undefined; + let isSendingTokenSufficient = false; + + const sendingAmount = BigInt(value); + const senderTransferable = BigInt(_senderTransferable); if (isSnowBridge) { const { value } = await this.getAddressTransferableBalance({ address: to, networkKey: destinationNetworkKey, extrinsicType: ExtrinsicType.TRANSFER_BALANCE }); - recipientNativeBalance = value; + receiverNativeBalance = BigInt(value); + } + + if (_isChainSubstrateCompatible(chainInfoMap[destinationNetworkKey])) { + const substrateApi = this.#koniState.getSubstrateApi(destinationNetworkKey); + + isSendingTokenSufficient = await this.isSufficientToken(destinationTokenInfo, substrateApi); } - const [warning, error] = additionalValidateXcmTransfer(originTokenInfo, destinationTokenInfo, value, senderTransferable, recipientNativeBalance, chainInfoMap[destinationNetworkKey], isSnowBridge); + const [warning, error] = additionalValidateXcmTransfer(originTokenInfo, destinationTokenInfo, sendingAmount, senderTransferable, chainInfoMap[destinationNetworkKey], isSendingTokenSufficient, receiverNativeBalance, isSnowBridge); error && inputTransaction.errors.push(error); warning && inputTransaction.warnings.push(warning); From 131afe4bcec7d3acf735dc3c56565bba096d2617 Mon Sep 17 00:00:00 2001 From: PDTnhah Date: Thu, 12 Dec 2024 16:40:04 +0700 Subject: [PATCH 2/6] [Issue-3725] Fix: Refactor code --- .../src/core/logic-validation/transfer.ts | 2 +- .../src/koni/background/handlers/Extension.ts | 41 +++++++++++++------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/packages/extension-base/src/core/logic-validation/transfer.ts b/packages/extension-base/src/core/logic-validation/transfer.ts index 845e560c79..3a73149902 100644 --- a/packages/extension-base/src/core/logic-validation/transfer.ts +++ b/packages/extension-base/src/core/logic-validation/transfer.ts @@ -76,7 +76,7 @@ export function additionalValidateTransferForRecipient ( const isReceiverAliveByNativeToken = receiverSystemAccountInfo ? _isAccountActive(receiverSystemAccountInfo) : false; const isReceivingAmountPassED = receiverSendingTokenKeepAliveBalance + transferAmount >= sendingTokenMinAmount; - if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN) { + if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN || (extrinsicType === ExtrinsicType.TRANSFER_XCM && !_isNativeToken(sendingTokenInfo))) { if (!remainingSendingTokenOfSenderEnoughED) { const warning = new TransactionWarning(BasicTxWarningCode.NOT_ENOUGH_EXISTENTIAL_DEPOSIT); diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index a9d9bffe75..0a28c2cc82 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 { additionalValidateTransferForRecipient, additionalValidateXcmTransfer, validateTransferRequest, validateXcmTransferRequest } from '@subwallet/extension-base/core/logic-validation/transfer'; +import { additionalValidateTransferForRecipient, 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'; @@ -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, _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, _isChainSubstrateCompatible, _isChainTonCompatible, _isCustomAsset, _isLocalToken, _isMantaZkAsset, _isNativeToken, _isPureEvmChain, _isTokenEvmSmartContract, _isTokenTransferredByEvm, _isTokenTransferredByTon } from '@subwallet/extension-base/services/chain-service/utils'; +import { _getAssetDecimals, _getAssetSymbol, _getChainNativeTokenBasicInfo, _getChainNativeTokenSlug, _getContractAddressOfToken, _getEvmChainId, _getTokenOnChainAssetId, _getXcmAssetMultilocation, _isAssetSmartContractNft, _isBridgedToken, _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'; @@ -1451,6 +1451,10 @@ export default class KoniExtension { const originTokenInfo = this.#koniState.getAssetBySlug(tokenSlug); const destinationTokenInfo = this.#koniState.getXcmEqualAssetByChain(destinationNetworkKey, tokenSlug); + + const destinationNativeTokenInfo = this.#koniState.getNativeTokenInfo(destinationNetworkKey); + const destinationNativeTokenSlug: string = destinationNativeTokenInfo.slug; + const [errors, fromKeyPair] = validateXcmTransferRequest(destinationTokenInfo, from, value); let extrinsic: SubmittableExtrinsic<'promise'> | TransactionConfig | null = null; @@ -1498,18 +1502,31 @@ export default class KoniExtension { extrinsic = await funcCreateExtrinsic(params); additionalValidator = async (inputTransaction: SWTransactionResponse): Promise => { - const { value: _senderTransferable } = await this.getAddressTransferableBalance({ address: from, networkKey: originNetworkKey, token: originTokenInfo.slug }); - const isSnowBridge = _isSnowBridgeXcm(chainInfoMap[originNetworkKey], chainInfoMap[destinationNetworkKey]); - let receiverNativeBalance: bigint | undefined; let isSendingTokenSufficient = false; + let receiverSystemAccountInfo: FrameSystemAccountInfo | undefined; - const sendingAmount = BigInt(value); + const { value: _senderTransferable } = await this.getAddressTransferableBalance({ address: from, networkKey: originNetworkKey, token: originTokenInfo.slug }); const senderTransferable = BigInt(_senderTransferable); - if (isSnowBridge) { - const { value } = await this.getAddressTransferableBalance({ address: to, networkKey: destinationNetworkKey, extrinsicType: ExtrinsicType.TRANSFER_BALANCE }); + // const isSnowBridge = _isSnowBridgeXcm(chainInfoMap[originNetworkKey], chainInfoMap[destinationNetworkKey]); + + const sendingAmount = BigInt(value); + + const extrinsicType = ExtrinsicType.TRANSFER_XCM; + + const { value: _receiverDestinationTokenKeepAliveBalance } = await this.getAddressTransferableBalance({ address: to, networkKey: destinationNetworkKey, token: destinationTokenInfo.slug, extrinsicType: ExtrinsicType.TRANSFER_BALANCE }); + const receiverDestinationTokenKeepAliveBalance = BigInt(_receiverDestinationTokenKeepAliveBalance); + + // if (isSnowBridge) { + // const { value } = await this.getAddressTransferableBalance({ address: to, networkKey: destinationNetworkKey, extrinsicType: ExtrinsicType.TRANSFER_BALANCE }); + // + // receiverNativeBalance = BigInt(value); + // } + + if (!_isNativeToken(destinationNativeTokenInfo)) { + const _receiverNativeTotal = await this.getAddressTotalBalance({ address: to, networkKey: destinationNetworkKey, token: destinationNativeTokenSlug, extrinsicType: ExtrinsicType.TRANSFER_BALANCE }); - receiverNativeBalance = BigInt(value); + receiverSystemAccountInfo = _receiverNativeTotal.metadata as FrameSystemAccountInfo; } if (_isChainSubstrateCompatible(chainInfoMap[destinationNetworkKey])) { @@ -1518,10 +1535,10 @@ export default class KoniExtension { isSendingTokenSufficient = await this.isSufficientToken(destinationTokenInfo, substrateApi); } - const [warning, error] = additionalValidateXcmTransfer(originTokenInfo, destinationTokenInfo, sendingAmount, senderTransferable, chainInfoMap[destinationNetworkKey], isSendingTokenSufficient, receiverNativeBalance, isSnowBridge); + const [warning, error] = additionalValidateTransferForRecipient(destinationTokenInfo, destinationNativeTokenInfo, extrinsicType, receiverDestinationTokenKeepAliveBalance, sendingAmount, senderTransferable, receiverSystemAccountInfo, isSendingTokenSufficient); - error && inputTransaction.errors.push(error); - warning && inputTransaction.warnings.push(warning); + warning.length && inputTransaction.warnings.push(...warning); + error.length && inputTransaction.errors.push(...error); }; eventsHandler = (eventEmitter: TransactionEmitter) => { From e1b6483b98d553042988ff55fe4ab7738cf6ba02 Mon Sep 17 00:00:00 2001 From: nampc Date: Thu, 12 Dec 2024 18:43:19 +0700 Subject: [PATCH 3/6] [Issue 3895] refactor: adjust some minor things --- .../src/core/logic-validation/transfer.ts | 78 +------------------ .../src/koni/background/handlers/Extension.ts | 20 +++-- 2 files changed, 18 insertions(+), 80 deletions(-) diff --git a/packages/extension-base/src/core/logic-validation/transfer.ts b/packages/extension-base/src/core/logic-validation/transfer.ts index 3a73149902..b1d1f402e1 100644 --- a/packages/extension-base/src/core/logic-validation/transfer.ts +++ b/packages/extension-base/src/core/logic-validation/transfer.ts @@ -5,13 +5,13 @@ 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 { LEDGER_SIGNING_COMPATIBLE_MAP, SIGNING_COMPATIBLE_MAP } 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 { _getAssetDecimals, _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'; @@ -75,8 +75,9 @@ export function additionalValidateTransferForRecipient ( const remainingSendingTokenOfSenderEnoughED = senderSendingTokenTransferable ? senderSendingTokenTransferable - transferAmount >= sendingTokenMinAmount : false; const isReceiverAliveByNativeToken = receiverSystemAccountInfo ? _isAccountActive(receiverSystemAccountInfo) : false; const isReceivingAmountPassED = receiverSendingTokenKeepAliveBalance + transferAmount >= sendingTokenMinAmount; + const isReceivingNonNativeToken = extrinsicType === ExtrinsicType.TRANSFER_TOKEN || (extrinsicType === ExtrinsicType.TRANSFER_XCM && !_isNativeToken(sendingTokenInfo)); - if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN || (extrinsicType === ExtrinsicType.TRANSFER_XCM && !_isNativeToken(sendingTokenInfo))) { + if (isReceivingNonNativeToken) { if (!remainingSendingTokenOfSenderEnoughED) { const warning = new TransactionWarning(BasicTxWarningCode.NOT_ENOUGH_EXISTENTIAL_DEPOSIT); @@ -137,77 +138,6 @@ export function validateXcmTransferRequest (destTokenInfo: _ChainAsset | undefin return [errors, keypair, transferValue]; } -export function additionalValidateXcmTransfer ( - originTokenInfo: _ChainAsset, - destinationTokenInfo: _ChainAsset, - sendingAmount: bigint, - senderTransferable: bigint, - destChainInfo: _ChainInfo, - isSendingTokenSufficient: boolean, - receiverNativeBalance?: bigint, - isSnowBridge = false -): [TransactionWarning | undefined, TransactionError | undefined] { - const destMinAmount = _getTokenMinAmount(destinationTokenInfo); - const minSendingRequired = BigInt(destMinAmount) * BigInt(XCM_MIN_AMOUNT_RATIO.toFixed()); - - let error: TransactionError | undefined; - let warning: TransactionWarning | undefined; - - const isSendingAmountEnough = sendingAmount > minSendingRequired; - const bnKeepAliveBalance = _isNativeToken(destinationTokenInfo) && receiverNativeBalance ? receiverNativeBalance + sendingAmount : receiverNativeBalance; - const isReceiverAmountPassED = isSnowBridge && bnKeepAliveBalance ? bnKeepAliveBalance > BigInt(_getChainExistentialDeposit(destChainInfo)) : true; - const remainingSendingTokenOfSenderEnoughED = !_isNativeToken(originTokenInfo) ? senderTransferable - sendingAmount > BigInt(_getTokenMinAmount(originTokenInfo)) : true; - // const isNativeTokenReceiverAmountPassED = isSnowBridge ? receiverNativeBalance + sendingAmount > BigInt(_getChainExistentialDeposit(destChainInfo)) : false; - // const isReceiverAmountPassED = isSnowBridge ? receiverNativeBalance > BigInt(_getChainExistentialDeposit(destChainInfo)) : true; - - // Check sending token ED for receiver - if (!isSendingAmountEnough) { - const atLeastStr = formatNumber(minSendingRequired.toString(), destinationTokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: destinationTokenInfo.decimals || 6 }); - - 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: originTokenInfo.symbol } })); - } - - // check native token ED on dest chain for receiver - if (_isNativeToken(destinationTokenInfo) || !isSendingTokenSufficient) { - if (!isReceiverAmountPassED) { - const { decimals, symbol } = _getChainNativeTokenBasicInfo(destChainInfo); - const atLeastStr = formatNumber(_getChainExistentialDeposit(destChainInfo), decimals || 0, balanceFormatter, { maxNumberFormat: 6 }); - - error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t(' Insufficient {{symbol}} on {{chain}} to cover min balance ({{amount}} {{symbol}})', { replace: { amount: atLeastStr, symbol, chain: destChainInfo.name } })); - } - } - - // Check ed for sender - if (!remainingSendingTokenOfSenderEnoughED) { - warning = new TransactionWarning(BasicTxWarningCode.NOT_ENOUGH_EXISTENTIAL_DEPOSIT); - } - - // // Check sending token ED for receiver - // if (new BigN(sendingAmount).lt(minSendingRequired)) { - // const atLeastStr = formatNumber(minSendingRequired, destinationTokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: destinationTokenInfo.decimals || 6 }); - // - // 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: originTokenInfo.symbol } })); - // } - // - // // check native token ED on dest chain for receiver - // - // if (isSnowBridge && bnKeepAliveBalance.lt(_getChainExistentialDeposit(destChainInfo))) { - // const { decimals, symbol } = _getChainNativeTokenBasicInfo(destChainInfo); - // const atLeastStr = formatNumber(_getChainExistentialDeposit(destChainInfo), decimals || 0, balanceFormatter, { maxNumberFormat: 6 }); - // - // error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t(' Insufficient {{symbol}} on {{chain}} to cover min balance ({{amount}} {{symbol}})', { replace: { amount: atLeastStr, symbol, chain: destChainInfo.name } })); - // } - // - // // Check ed for sender - // if (!_isNativeToken(originTokenInfo)) { - // if (new BigN(senderTransferable).minus(sendingAmount).lt(_getTokenMinAmount(originTokenInfo))) { - // warning = new TransactionWarning(BasicTxWarningCode.NOT_ENOUGH_EXISTENTIAL_DEPOSIT); - // } - // } - - return [warning, error]; -} - export function checkSupportForFeature (validationResponse: SWTransactionResponse, blockedFeaturesList: string[], chainInfo: _ChainInfo) { const extrinsicType = validationResponse.extrinsicType; const chain = validationResponse.chain; diff --git a/packages/extension-base/src/koni/background/handlers/Extension.ts b/packages/extension-base/src/koni/background/handlers/Extension.ts index 0a28c2cc82..f57532e6ac 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -1467,6 +1467,7 @@ export default class KoniExtension { const isAvailBridgeFromAvail = isAvailChainBridge(originNetworkKey) && _isPureEvmChain(chainInfoMap[destinationNetworkKey]); const isSnowBridgeEvmTransfer = _isPureEvmChain(chainInfoMap[originNetworkKey]) && _isSnowBridgeXcm(chainInfoMap[originNetworkKey], chainInfoMap[destinationNetworkKey]) && !isAvailBridgeFromEvm; const isPolygonBridgeTransfer = _isPolygonChainBridge(originNetworkKey, destinationNetworkKey); + const extrinsicType = ExtrinsicType.TRANSFER_XCM; let additionalValidator: undefined | ((inputTransaction: SWTransactionResponse) => Promise); let eventsHandler: undefined | ((eventEmitter: TransactionEmitter) => void); @@ -1512,9 +1513,7 @@ export default class KoniExtension { const sendingAmount = BigInt(value); - const extrinsicType = ExtrinsicType.TRANSFER_XCM; - - const { value: _receiverDestinationTokenKeepAliveBalance } = await this.getAddressTransferableBalance({ address: to, networkKey: destinationNetworkKey, token: destinationTokenInfo.slug, extrinsicType: ExtrinsicType.TRANSFER_BALANCE }); + const { value: _receiverDestinationTokenKeepAliveBalance } = await this.getAddressTotalBalance({ address: to, networkKey: destinationNetworkKey, token: destinationTokenInfo.slug, extrinsicType }); const receiverDestinationTokenKeepAliveBalance = BigInt(_receiverDestinationTokenKeepAliveBalance); // if (isSnowBridge) { @@ -1524,7 +1523,7 @@ export default class KoniExtension { // } if (!_isNativeToken(destinationNativeTokenInfo)) { - const _receiverNativeTotal = await this.getAddressTotalBalance({ address: to, networkKey: destinationNetworkKey, token: destinationNativeTokenSlug, extrinsicType: ExtrinsicType.TRANSFER_BALANCE }); + const _receiverNativeTotal = await this.getAddressTotalBalance({ address: to, networkKey: destinationNetworkKey, token: destinationNativeTokenSlug, extrinsicType }); receiverSystemAccountInfo = _receiverNativeTotal.metadata as FrameSystemAccountInfo; } @@ -1535,7 +1534,16 @@ export default class KoniExtension { isSendingTokenSufficient = await this.isSufficientToken(destinationTokenInfo, substrateApi); } - const [warning, error] = additionalValidateTransferForRecipient(destinationTokenInfo, destinationNativeTokenInfo, extrinsicType, receiverDestinationTokenKeepAliveBalance, sendingAmount, senderTransferable, receiverSystemAccountInfo, isSendingTokenSufficient); + const [warning, error] = additionalValidateTransferForRecipient( + destinationTokenInfo, + destinationNativeTokenInfo, + extrinsicType, + receiverDestinationTokenKeepAliveBalance, + sendingAmount, + senderTransferable, // different from sendingTokenInfo being passed in + receiverSystemAccountInfo, + isSendingTokenSufficient + ); warning.length && inputTransaction.warnings.push(...warning); error.length && inputTransaction.errors.push(...error); @@ -1575,7 +1583,7 @@ export default class KoniExtension { chain: originNetworkKey, transaction: extrinsic, data: inputData, - extrinsicType: ExtrinsicType.TRANSFER_XCM, + extrinsicType, chainType: !isSnowBridgeEvmTransfer && !isAvailBridgeFromEvm && !isPolygonBridgeTransfer ? ChainType.SUBSTRATE : ChainType.EVM, transferNativeAmount: _isNativeToken(originTokenInfo) ? value : '0', ignoreWarnings, From b61ffab9d0b355de3bcce98e7a0e25114d57ab3b Mon Sep 17 00:00:00 2001 From: PDTnhah Date: Tue, 24 Dec 2024 15:55:35 +0700 Subject: [PATCH 4/6] [Issue-3915] Fix bug --- .../extension-base/src/koni/background/handlers/Extension.ts | 2 +- 1 file changed, 1 insertion(+), 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 f57532e6ac..ba4ebbd598 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -1522,7 +1522,7 @@ export default class KoniExtension { // receiverNativeBalance = BigInt(value); // } - if (!_isNativeToken(destinationNativeTokenInfo)) { + if (!_isNativeToken(destinationTokenInfo)) { const _receiverNativeTotal = await this.getAddressTotalBalance({ address: to, networkKey: destinationNetworkKey, token: destinationNativeTokenSlug, extrinsicType }); receiverSystemAccountInfo = _receiverNativeTotal.metadata as FrameSystemAccountInfo; From 9da099fdf83f413b9167ed05671b7fb8a145ad96 Mon Sep 17 00:00:00 2001 From: PDTnhah Date: Tue, 24 Dec 2024 15:55:35 +0700 Subject: [PATCH 5/6] [Issue-3895] Fix bug --- .../extension-base/src/koni/background/handlers/Extension.ts | 2 +- 1 file changed, 1 insertion(+), 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 f57532e6ac..ba4ebbd598 100644 --- a/packages/extension-base/src/koni/background/handlers/Extension.ts +++ b/packages/extension-base/src/koni/background/handlers/Extension.ts @@ -1522,7 +1522,7 @@ export default class KoniExtension { // receiverNativeBalance = BigInt(value); // } - if (!_isNativeToken(destinationNativeTokenInfo)) { + if (!_isNativeToken(destinationTokenInfo)) { const _receiverNativeTotal = await this.getAddressTotalBalance({ address: to, networkKey: destinationNetworkKey, token: destinationNativeTokenSlug, extrinsicType }); receiverSystemAccountInfo = _receiverNativeTotal.metadata as FrameSystemAccountInfo; From c0a736a92b5107ba0edce94f1da50dabbe6eabe4 Mon Sep 17 00:00:00 2001 From: PDTnhah Date: Thu, 26 Dec 2024 17:02:36 +0700 Subject: [PATCH 6/6] [Issue-3895] Refactor logic validation for XCM --- .../src/core/logic-validation/transfer.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/extension-base/src/core/logic-validation/transfer.ts b/packages/extension-base/src/core/logic-validation/transfer.ts index b1d1f402e1..243f4f1deb 100644 --- a/packages/extension-base/src/core/logic-validation/transfer.ts +++ b/packages/extension-base/src/core/logic-validation/transfer.ts @@ -5,7 +5,7 @@ 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 } from '@subwallet/extension-base/constants'; +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'; @@ -67,14 +67,16 @@ export function additionalValidateTransferForRecipient ( isSendingTokenSufficient?: boolean ): [TransactionWarning[], TransactionError[]] { const sendingTokenMinAmount = BigInt(_getTokenMinAmount(sendingTokenInfo)); + const sendingTokenMinAmountXCM = BigInt(new BigN(_getTokenMinAmount(sendingTokenInfo)).multipliedBy(XCM_MIN_AMOUNT_RATIO).toString()); const nativeTokenMinAmount = _getTokenMinAmount(nativeTokenInfo); + const minSendingRequired = extrinsicType !== ExtrinsicType.TRANSFER_XCM ? sendingTokenMinAmount : sendingTokenMinAmountXCM; const warnings: TransactionWarning[] = []; const errors: TransactionError[] = []; const remainingSendingTokenOfSenderEnoughED = senderSendingTokenTransferable ? senderSendingTokenTransferable - transferAmount >= sendingTokenMinAmount : false; const isReceiverAliveByNativeToken = receiverSystemAccountInfo ? _isAccountActive(receiverSystemAccountInfo) : false; - const isReceivingAmountPassED = receiverSendingTokenKeepAliveBalance + transferAmount >= sendingTokenMinAmount; + const isReceivingAmountPassED = receiverSendingTokenKeepAliveBalance + transferAmount >= minSendingRequired; const isReceivingNonNativeToken = extrinsicType === ExtrinsicType.TRANSFER_TOKEN || (extrinsicType === ExtrinsicType.TRANSFER_XCM && !_isNativeToken(sendingTokenInfo)); if (isReceivingNonNativeToken) { @@ -99,10 +101,11 @@ export function additionalValidateTransferForRecipient ( const atLeast = sendingTokenMinAmount - receiverSendingTokenKeepAliveBalance; const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6 }); + const atLeastAmountStr = extrinsicType === ExtrinsicType.TRANSFER_XCM ? (Number(atLeastStr) * XCM_MIN_AMOUNT_RATIO).toString() : atLeastStr; 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 } }) + t('You must transfer at least {{amount}} {{symbol}} to avoid losing funds on the recipient account. Increase amount and try again', { replace: { amount: atLeastAmountStr, symbol: sendingTokenInfo.symbol } }) ); errors.push(error); @@ -113,10 +116,11 @@ export function additionalValidateTransferForRecipient ( const atLeast = sendingTokenMinAmount - receiverSendingTokenKeepAliveBalance; const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6 }); + const atLeastAmountStr = extrinsicType === ExtrinsicType.TRANSFER_XCM ? (Number(atLeastStr) * XCM_MIN_AMOUNT_RATIO).toString() : atLeastStr; 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 } }) + t('You must transfer at least {{amount}} {{symbol}} to keep the recipient account alive. Increase amount and try again', { replace: { amount: atLeastAmountStr, symbol: sendingTokenInfo.symbol } }) ); errors.push(error);