diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-alert.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-alert.vue new file mode 100644 index 000000000..046f53424 --- /dev/null +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/components/send-alert.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/packages/extension/src/providers/ethereum/ui/send-transaction/index.vue b/packages/extension/src/providers/ethereum/ui/send-transaction/index.vue index 5d638b745..a00efafc0 100644 --- a/packages/extension/src/providers/ethereum/ui/send-transaction/index.vue +++ b/packages/extension/src/providers/ethereum/ui/send-transaction/index.vue @@ -103,18 +103,7 @@ @gas-type-changed="selectFee" /> - +
@@ -143,7 +132,7 @@ import SendContactsList from '@/providers/common/ui/send-transaction/send-contac import AssetsSelectList from '@action/views/assets-select-list/index.vue'; import NftSelectList from '@/providers/common/ui/send-transaction/nft-select-list/index.vue'; import SendTokenSelect from './components/send-token-select.vue'; -import SendAlert from '@/providers/common/ui/send-transaction/send-alert.vue'; +import SendAlert from './components/send-alert.vue'; import SendNftSelect from '@/providers/common/ui/send-transaction/send-nft-select.vue'; import SendInputAmount from '@/providers/common/ui/send-transaction/send-input-amount.vue'; import SendFeeSelect from '@/providers/common/ui/send-transaction/send-fee-select.vue'; @@ -169,6 +158,7 @@ import erc721 from '../../libs/abi/erc721'; import erc1155 from '../../libs/abi/erc1155'; import { SendTransactionDataType, VerifyTransactionParams } from '../types'; import { + formatFiatValue, formatFloatingPointValue, isNumericPositive, } from '@/libs/utils/number-formatter'; @@ -217,11 +207,19 @@ const accountAssets = ref([]); const selectedAsset = ref>(loadingAsset); const amount = ref(''); const isEstimateValid = ref(true); +const hasValidDecimals = computed(() => { + return isValidDecimals(sendAmount.value, selectedAsset.value.decimals!); +}); +const hasPositiveSendAmount = computed(() => { + return isNumericPositive(sendAmount.value); +}); const hasEnoughBalance = computed(() => { - if (!isValidDecimals(sendAmount.value, selectedAsset.value.decimals!)) { + if (!hasValidDecimals.value) { + return false; + } + if (!hasPositiveSendAmount.value) { return false; } - // check if valid sendAmount.value if (!isNumericPositive(sendAmount.value)) { return false; @@ -341,7 +339,11 @@ const Tx = computed(() => { return tx; }); -const nativeBalanceAfterTransaction = computed(() => { +/** + * Native balance after the transaction in the base unit of the + * native currency (eg in WETH, Lamports, Satoshis, ...) + */ +const nativeBalanceAfterTransactionInBaseUnits = computed(() => { if ( isSendToken.value && nativeBalance.value && @@ -390,6 +392,71 @@ const nativeBalanceAfterTransaction = computed(() => { return toBN(0); }); +/** + * Native balance after the transaction in the human unit of the + * native currency (eg in ETH, SOL, BTC, ...) + */ +const nativeBalanceAfterTransactionInHumanUnits = computed(() => { + return fromBase( + nativeBalanceAfterTransactionInBaseUnits.value.abs().toString(), + props.network.decimals, + ); +}); + +const nativeCurrencyUsdPrice = computed(() => { + return accountAssets.value[0]?.price || '0'; +}); + +const balanceAfterInUsd = computed(() => { + return new BigNumber( + nativeBalanceAfterTransactionInHumanUnits.value.toString(), + ) + .times(nativeCurrencyUsdPrice.value ?? '0') + .toFixed(); +}); + +const errorMsg = computed(() => { + if (!hasValidDecimals.value) { + return `Too many decimals.`; + } + + if (!hasPositiveSendAmount.value) { + return `Invalid amount.`; + } + + if ( + !hasEnoughBalance.value && + nativeBalanceAfterTransactionInBaseUnits.value.isNeg() + ) { + return `Not enough funds. You are + ~${formatFloatingPointValue(nativeBalanceAfterTransactionInHumanUnits.value).value} + ${props.network.currencyName} ($ ${ + formatFiatValue(balanceAfterInUsd.value).value + }) short.`; + } + + if (!props.network.isAddress(addressTo.value) && addressTo.value !== '') { + return `Invalid to address.`; + } + + if ( + isSendToken.value && + !isValidDecimals(sendAmount.value, selectedAsset.value.decimals!) + ) { + return `Invalid decimals for ${selectedAsset.value.symbol}.`; + } + + if (!isSendToken.value && !selectedNft.value.id) { + return `Invalid NFT selected.`; + } + + if (new BigNumber(sendAmount.value).gt(assetMaxValue.value)) { + return `Amount exceeds maximum value.`; + } + + return ''; +}); + const setTransactionFees = (tx: Transaction) => { return tx .getGasCosts() @@ -479,7 +546,7 @@ const sendButtonTitle = computed(() => { const isValidSend = computed(() => { if (!isInputsValid.value) return false; - if (nativeBalanceAfterTransaction.value.isNeg()) return false; + if (nativeBalanceAfterTransactionInBaseUnits.value.isNeg()) return false; if (!isEstimateValid.value) return false; return true; }); diff --git a/packages/extension/src/providers/solana/ui/send-transaction/index.vue b/packages/extension/src/providers/solana/ui/send-transaction/index.vue index 1f59aeda9..38c6a9594 100644 --- a/packages/extension/src/providers/solana/ui/send-transaction/index.vue +++ b/packages/extension/src/providers/solana/ui/send-transaction/index.vue @@ -140,6 +140,7 @@ import { VerifyTransactionParams, SendTransactionDataType } from '../types'; import { formatFloatingPointValue, formatFiatValue, + isNumericPositive, } from '@/libs/utils/number-formatter'; import { routes as RouterNames } from '@/ui/action/router'; import getUiPath from '@/libs/utils/get-ui-path'; @@ -172,6 +173,7 @@ import { import getPriorityFees from '../libs/get-priority-fees'; import bs58 from 'bs58'; import SolanaAPI from '@/providers/solana/libs/api'; +import { ComputedRefSymbol } from '@vue/reactivity'; const props = defineProps({ network: { @@ -211,8 +213,17 @@ const amount = ref(''); const isEstimateValid = ref(true); const storageFee = ref(0); const SolTx = ref(); -const hasEnoughBalance = computed(() => { - if (!isValidDecimals(sendAmount.value, selectedAsset.value.decimals!)) { +const hasValidDecimals = computed((): boolean => { + return isValidDecimals(sendAmount.value, selectedAsset.value.decimals!); +}); +const hasPositiveSendAmount = computed(() => { + return isNumericPositive(sendAmount.value); +}); +const hasEnoughBalance = computed((): boolean => { + if (!hasValidDecimals.value) { + return false; + } + if (!hasPositiveSendAmount.value) { return false; } return toBN(selectedAsset.value.balance ?? '0').gte( @@ -276,7 +287,11 @@ const TxInfo = computed(() => { }; }); -const nativeBalanceAfterTransaction = computed(() => { +/** + * Native balance after the transaction in the base unit of the + * native currency (eg in WETH, Lamports, Satoshis, ...) + */ +const nativeBalanceAfterTransactionInBaseUnits = computed(() => { if ( isSendToken.value && nativeBalance.value && @@ -320,47 +335,71 @@ const nativeBalanceAfterTransaction = computed(() => { return toBN(0); }); -const nativeValue = computed(() => { +/** + * Native balance after the transaction in the human unit of the + * native currency (eg in ETH, SOL, BTC, ...) + */ +const nativeBalanceAfterTransactionInHumanUnits = computed(() => { return fromBase( - nativeBalanceAfterTransaction.value.toString(), + nativeBalanceAfterTransactionInBaseUnits.value.abs().toString(), props.network.decimals, ); }); -const nativePrice = computed(() => { +const nativeCurrencyUsdPrice = computed(() => { return accountAssets.value[0]?.price || '0'; }); -const priceDifference = computed(() => { - return new BigNumber(nativeValue.value) - .times(nativePrice.value ?? '0') +const balanceAfterInUsd = computed(() => { + return new BigNumber( + nativeBalanceAfterTransactionInHumanUnits.value.toString(), + ) + .times(nativeCurrencyUsdPrice.value ?? '0') .toFixed(); }); const errorMsg = computed(() => { - if (hasEnoughBalance.value && nativeBalanceAfterTransaction.value.isNeg()) { + if (!hasValidDecimals.value) { + return `Too many decimals.`; + } + + if (!hasPositiveSendAmount.value) { + return `Invalid amount.`; + } + + if ( + !hasEnoughBalance.value && + nativeBalanceAfterTransactionInBaseUnits.value.isNeg() + ) { return `Not enough funds. You are - ~${formatFloatingPointValue(nativeValue.value).value} + ~${formatFloatingPointValue(nativeBalanceAfterTransactionInHumanUnits.value).value} ${props.network.currencyName} ($ ${ - formatFiatValue(priceDifference.value).value + formatFiatValue(balanceAfterInUsd.value).value }) short.`; } + if ( !props.network.isAddress(getAddress(addressTo.value)) && addressTo.value !== '' - ) + ) { return `Invalid to address.`; + } + if ( isSendToken.value && !isValidDecimals(sendAmount.value, selectedAsset.value.decimals!) ) { return `Invalid decimals for ${selectedAsset.value.symbol}.`; } + if (!isSendToken.value && !selectedNft.value.id) { return `Invalid NFT selected.`; } - if (new BigNumber(sendAmount.value).gt(assetMaxValue.value)) + + if (new BigNumber(sendAmount.value).gt(assetMaxValue.value)) { return `Amount exceeds maximum value.`; + } + return ''; }); @@ -418,7 +457,7 @@ const sendButtonTitle = computed(() => { const isValidSend = computed(() => { if (!isInputsValid.value) return false; - if (nativeBalanceAfterTransaction.value.isNeg()) return false; + if (nativeBalanceAfterTransactionInBaseUnits.value.isNeg()) return false; if (!isEstimateValid.value) return false; if (gasCostValues.value.ECONOMY.nativeValue === '0') return false; return true; diff --git a/packages/extension/src/ui/action/views/swap/views/swap-best-offer/components/swap-best-offer-block/components/best-offer-warning.vue b/packages/extension/src/ui/action/views/swap/views/swap-best-offer/components/swap-best-offer-block/components/best-offer-warning.vue index 2f1597081..890f48586 100644 --- a/packages/extension/src/ui/action/views/swap/views/swap-best-offer/components/swap-best-offer-block/components/best-offer-warning.vue +++ b/packages/extension/src/ui/action/views/swap/views/swap-best-offer/components/swap-best-offer-block/components/best-offer-warning.vue @@ -15,6 +15,8 @@