diff --git a/changelogs/3.2.9.txt b/changelogs/3.2.9.txt
new file mode 100644
index 00000000..619f4cd5
--- /dev/null
+++ b/changelogs/3.2.9.txt
@@ -0,0 +1 @@
+Bug fixes and performance improvements
diff --git a/package-lock.json b/package-lock.json
index dfd013c2..5cdad20b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "mytonwallet",
- "version": "3.2.8",
+ "version": "3.2.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mytonwallet",
- "version": "3.2.8",
+ "version": "3.2.9",
"license": "GPL-3.0-or-later",
"dependencies": {
"@awesome-cordova-plugins/core": "6.9.0",
diff --git a/package.json b/package.json
index 3845f85b..72bc65fc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mytonwallet",
- "version": "3.2.8",
+ "version": "3.2.9",
"description": "The most feature-rich web wallet and browser extension for TON – with support of multi-accounts, tokens (jettons), NFT, TON DNS, TON Sites, TON Proxy, and TON Magic.",
"main": "index.js",
"scripts": {
diff --git a/public/version.txt b/public/version.txt
index f092941a..e650c01d 100644
--- a/public/version.txt
+++ b/public/version.txt
@@ -1 +1 @@
-3.2.8
+3.2.9
diff --git a/src/api/chains/ton/transactions.ts b/src/api/chains/ton/transactions.ts
index 4f3060fe..e5043369 100644
--- a/src/api/chains/ton/transactions.ts
+++ b/src/api/chains/ton/transactions.ts
@@ -37,6 +37,7 @@ import { parseAccountId } from '../../../util/account';
import { bigintMultiplyToNumber } from '../../../util/bigint';
import { compareActivities } from '../../../util/compareActivities';
import { fromDecimal, toDecimal } from '../../../util/decimals';
+import { getDieselTokenAmount, isDieselAvailable } from '../../../util/fee/transferFee';
import { buildCollectionByKey, omit, pick } from '../../../util/iteratees';
import { logDebug, logDebugError } from '../../../util/logs';
import { updatePoisoningCache } from '../../../util/poisoningHash';
@@ -104,7 +105,6 @@ const PENDING_DIESEL_TIMEOUT = 15 * 60 * 1000; // 15 min
const DIESEL_NOT_AVAILABLE: ApiFetchEstimateDieselResult = {
status: 'not-available',
- amount: { token: 0n, stars: 0n },
nativeAmount: 0n,
remainingFee: 0n,
realFee: 0n,
@@ -277,10 +277,10 @@ export async function checkTransactionDraft(
tokenBalance: balance,
});
- if (result.diesel.status === 'not-available') {
- isEnoughBalance = canTransferGasfully && amount <= balance;
+ if (isDieselAvailable(result.diesel)) {
+ isEnoughBalance = amount + getDieselTokenAmount(result.diesel) <= balance;
} else {
- isEnoughBalance = amount + result.diesel.amount.token <= balance;
+ isEnoughBalance = canTransferGasfully && amount <= balance;
}
}
@@ -1338,20 +1338,21 @@ async function getDiesel({
);
const diesel: ApiFetchEstimateDieselResult = {
status: rawDiesel.status,
- amount: rawDiesel.status !== 'stars-fee'
- ? { token: fromDecimal(rawDiesel.amount ?? '0', token.decimals), stars: 0n }
- : { token: 0n, stars: fromDecimal(rawDiesel.amount ?? '0', 0) },
+ amount: rawDiesel.amount === undefined
+ ? undefined
+ : fromDecimal(rawDiesel.amount, rawDiesel.status === 'stars-fee' ? 0 : token.decimals),
nativeAmount: toncoinNeeded,
remainingFee: toncoinBalance,
realFee: fee.realFee,
};
- if (diesel.status === 'not-available') {
+ const tokenAmount = getDieselTokenAmount(diesel);
+ if (tokenAmount === 0n) {
return diesel;
}
tokenBalance ??= await getTokenBalanceWithMintless(network, address, tokenAddress);
- const canPayDiesel = tokenBalance >= diesel.amount.token;
+ const canPayDiesel = tokenBalance >= tokenAmount;
const isAwaitingNotExpiredPrevious = Boolean(
rawDiesel.pendingCreatedAt
&& Date.now() - new Date(rawDiesel.pendingCreatedAt).getTime() < PENDING_DIESEL_TIMEOUT,
diff --git a/src/api/chains/ton/types.ts b/src/api/chains/ton/types.ts
index 6b4a70d2..3c060915 100644
--- a/src/api/chains/ton/types.ts
+++ b/src/api/chains/ton/types.ts
@@ -118,21 +118,16 @@ export type ApiFetchEstimateDieselResult = {
status: DieselStatus;
/**
* The amount of the diesel itself. It will be sent together with the actual transfer. None of this will return back
- * as the excess. Charged on top of the transferred amount. The token and stars amounts can't be non-zero
- * simultaneously. Warning: the values can be zeros simultaneously, e.g. when the status is 'pending-previous'.
+ * as the excess. Undefined means that
+ * gasless transfer is not available, and the diesel shouldn't be shown as the fee; nevertheless, the status should
+ * be displayed by the UI.
+ *
+ * - If the status is not 'stars-fee', the value is measured in the transferred token and charged on top of the
+ * transferred amount.
+ * - If the status is 'stars-fee', the value is measured in Telegram stars, and the BigInt assumes 0 decimal places
+ * (i.e. the number is equal to the visible number of stars).
*/
- amount: {
- /** Measured in the transferred token */
- token: bigint;
- /**
- * Measured in Telegram stars. The BigInt assumes 0 decimal places (i.e. the number is equal to the visible number
- * of stars).
- */
- stars: 0n;
- } | {
- token: 0n;
- stars: bigint;
- };
+ amount?: bigint;
/**
* The native token amount covered by the diesel. Guaranteed to be > 0.
*/
@@ -143,7 +138,7 @@ export type ApiFetchEstimateDieselResult = {
*/
remainingFee: bigint;
/**
- * An approximate fee that will be actually spent. The difference between `nativeAmount+nativeRemainder` and this
+ * An approximate fee that will be actually spent. The difference between `nativeAmount+remainingFee` and this
* number is called "excess" and will be returned back to the wallet. Measured in the native token.
*/
realFee: bigint;
diff --git a/src/components/main/sections/Card/CustomCardBackground.module.scss b/src/components/main/sections/Card/CustomCardBackground.module.scss
index 73d224b7..81bfdbda 100644
--- a/src/components/main/sections/Card/CustomCardBackground.module.scss
+++ b/src/components/main/sections/Card/CustomCardBackground.module.scss
@@ -94,14 +94,14 @@
padding: 1.5px !important;
background-image: radial-gradient(30.47% 83.28% at 79.45% 3.2%, #FFFFFF 0%, rgba(255, 255, 255, 0) 100%),
- linear-gradient(258.65deg, #141518 33.29%, #292929 48.38%) !important;
+ linear-gradient(258.65deg, #141518 33.29%, #292929 48.38%) !important;
}
:global(.MtwCard__platinum)::before,
:global(.MtwCard__gold)::before,
:global(.MtwCard__silver)::before {
- background-image: radial-gradient(30.42% 83.05% at 79.4% 3.33%, #FFFFFF 0%, rgba(255, 255, 255, 0) 100%),
- linear-gradient(258.63deg, #141518 33.33%, #292929 48.39%) !important;
+ background-image: radial-gradient(23.98% 49.81% at 73.98% 0.37%, #FFFFFF 0%, rgba(255, 255, 255, 0) 100%),
+ linear-gradient(258.61deg, rgba(140, 148, 176, 0.5) 33.38%, rgba(186, 188, 194, 0.85) 48.39%) !important;
}
.shadow {
diff --git a/src/components/swap/SwapInitial.tsx b/src/components/swap/SwapInitial.tsx
index 9550f530..d5930c2b 100644
--- a/src/components/swap/SwapInitial.tsx
+++ b/src/components/swap/SwapInitial.tsx
@@ -26,10 +26,9 @@ import { selectCurrentAccount, selectIsMultichainAccount, selectSwapTokens } fro
import { bigintDivideToNumber, bigintMax } from '../../util/bigint';
import buildClassName from '../../util/buildClassName';
import { vibrate } from '../../util/capacitor';
-import { findChainConfig, getChainConfig } from '../../util/chain';
+import { findChainConfig } from '../../util/chain';
import { fromDecimal, toDecimal } from '../../util/decimals';
import { formatCurrency } from '../../util/formatNumber';
-import { getIsNativeToken } from '../../util/tokens';
import { ANIMATED_STICKERS_PATHS } from '../ui/helpers/animatedAssets';
import { isBackgroundModeActive } from '../../hooks/useBackgroundMode';
@@ -150,7 +149,6 @@ function SwapInitial({
);
const nativeBalance = nativeUserTokenIn?.amount ?? 0n;
const isNativeIn = currentTokenInSlug && currentTokenInSlug === nativeTokenInSlug;
- const chainConfigIn = nativeUserTokenIn ? getChainConfig(nativeUserTokenIn.chain as ApiChain) : undefined;
const isTonIn = tokenIn?.chain === 'ton';
const amountInBigint = amountIn && tokenIn ? fromDecimal(amountIn, tokenIn.decimals) : 0n;
@@ -160,16 +158,8 @@ function SwapInitial({
const networkFeeBigint = (() => {
let value = 0n;
- if (!chainConfigIn) {
- return value;
- }
-
if (Number(networkFee) > 0) {
value = fromDecimal(networkFee, nativeUserTokenIn?.decimals);
- } else if (swapType === SwapType.OnChain) {
- value = chainConfigIn?.gas.maxSwap ?? 0n;
- } else if (swapType === SwapType.CrosschainFromWallet) {
- value = getIsNativeToken(tokenInSlug) ? chainConfigIn.gas.maxTransfer : chainConfigIn.gas.maxTransferToken;
}
return value;
@@ -222,7 +212,7 @@ function SwapInitial({
amountIn
&& tokenIn
&& amountInBigint > 0
- && amountInBigint <= balanceIn,
+ && amountInBigint <= maxAmount,
) || (tokenIn && !nativeTokenInSlug);
const isEnoughFee = swapType !== SwapType.CrosschainToWallet
diff --git a/src/components/transfer/Transfer.module.scss b/src/components/transfer/Transfer.module.scss
index 8674e6e9..2b3ac1e1 100644
--- a/src/components/transfer/Transfer.module.scss
+++ b/src/components/transfer/Transfer.module.scss
@@ -545,11 +545,6 @@ textarea.inputStatic {
margin-bottom: 2rem;
}
-.transitionSlide {
- height: auto;
- min-height: 100%;
-}
-
.infoBox, .burnWarning, .error {
align-self: center;
diff --git a/src/components/transfer/TransferInitial.tsx b/src/components/transfer/TransferInitial.tsx
index 265e8b49..ffa1816d 100644
--- a/src/components/transfer/TransferInitial.tsx
+++ b/src/components/transfer/TransferInitial.tsx
@@ -29,7 +29,9 @@ import { readClipboardContent } from '../../util/clipboard';
import { SECOND } from '../../util/dateFormat';
import { fromDecimal, toBig, toDecimal } from '../../util/decimals';
import dns from '../../util/dns';
-import { explainApiTransferFee, getMaxTransferAmount } from '../../util/fee/transferFee';
+import {
+ explainApiTransferFee, getMaxTransferAmount, isBalanceSufficientForTransfer,
+} from '../../util/fee/transferFee';
import { formatCurrency, getShortCurrencySymbol } from '../../util/formatNumber';
import { isValidAddressOrDomain } from '../../util/isValidAddressOrDomain';
import { debounce } from '../../util/schedulers';
@@ -198,11 +200,7 @@ function TransferInitial({
return tokens?.find((token) => !token.tokenAddress && token.chain === chain);
}, [tokens, chain])!;
- const { status: dieselStatus, amount: dieselAmount } = diesel ?? {};
- const skipNextFeeEstimate = useRef(false);
-
const isToncoin = tokenSlug === TONCOIN.slug;
- const isToncoinFullBalance = isToncoin && balance === amount;
const shouldDisableClearButton = !toAddress && !amount && !(comment || binPayload) && !shouldEncrypt
&& !(nfts?.length && isStatic);
@@ -217,11 +215,6 @@ function TransferInitial({
const withAddressClearButton = !!toAddress.length;
const shortBaseSymbol = getShortCurrencySymbol(baseCurrency);
- const additionalAmount = amount && isToncoin ? amount : 0n;
- const isEnoughNativeCoin = isToncoinFullBalance
- ? (fee !== undefined && fee < nativeToken.amount)
- : (fee !== undefined && (fee + additionalAmount) <= nativeToken.amount);
-
const isNativeToken = getIsNativeToken(tokenSlug);
const explainedFee = useMemo(
() => explainApiTransferFee({
@@ -229,18 +222,17 @@ function TransferInitial({
}),
[fee, realFee, diesel, chain, isNativeToken],
);
- const isGaslessWithStars = dieselStatus === 'stars-fee';
- const isDieselAvailable = dieselStatus === 'available' || isGaslessWithStars;
- const isDieselNotAuthorized = dieselStatus === 'not-authorized';
- const withDiesel = explainedFee.isGasless;
- const isEnoughDiesel = withDiesel && amount && balance && dieselAmount
- ? isGaslessWithStars
- ? true
- : balance - amount >= dieselAmount.token
- : undefined;
- const isInsufficientFee = (fee !== undefined && !isEnoughNativeCoin && !isDieselAvailable)
- || (withDiesel && !isEnoughDiesel);
- const isAmountGiven = amount !== undefined;
+
+ // Note: this constant has 3 distinct meaningful values
+ const isEnoughBalance = isBalanceSufficientForTransfer({
+ tokenBalance: balance,
+ nativeTokenBalance: nativeToken.amount,
+ transferAmount: isNftTransfer ? NFT_TRANSFER_AMOUNT : amount,
+ fullFee: explainedFee.fullFee?.terms,
+ canTransferFullBalance: explainedFee.canTransferFullBalance,
+ });
+
+ const isAmountMissing = !isNftTransfer && !amount;
const isDisabledDebounce = useRef(false);
@@ -251,7 +243,8 @@ function TransferInitial({
canTransferFullBalance: explainedFee.canTransferFullBalance,
});
- const authorizeDieselInterval = isDieselNotAuthorized && isDieselAuthorizationStarted && tokenSlug && !isToncoin
+ const isDieselNotAuthorized = diesel?.status === 'not-authorized';
+ const authorizeDieselInterval = isDieselNotAuthorized && isDieselAuthorizationStarted
? AUTHORIZE_DIESEL_INTERVAL_MS
: undefined;
@@ -260,9 +253,7 @@ function TransferInitial({
);
const updateDieselState = useLastCallback(() => {
- if (tokenSlug) {
- fetchTransferDieselState({ tokenSlug });
- }
+ fetchTransferDieselState({ tokenSlug });
});
useInterval(updateDieselState, authorizeDieselInterval);
@@ -301,8 +292,7 @@ function TransferInitial({
// Note: this effect doesn't watch amount changes mainly because it's tricky to program a fee recalculation avoidance
// when the amount changes due to a fee change. And it's not needed because the fee doesn't depend on the amount.
useEffect(() => {
- if (!(isAmountGiven || nfts?.length) || !isAddressValid || skipNextFeeEstimate.current) {
- skipNextFeeEstimate.current = false;
+ if (isAmountMissing || !isAddressValid) {
return;
}
@@ -330,7 +320,7 @@ function TransferInitial({
isDisabledDebounce.current = false;
runFunction();
}
- }, [isAmountGiven, binPayload, comment, isAddressValid, isNftTransfer, nfts, stateInit, toAddress, tokenSlug]);
+ }, [isAmountMissing, binPayload, comment, isAddressValid, isNftTransfer, nfts, stateInit, toAddress, tokenSlug]);
const handleTokenChange = useLastCallback((slug: string) => {
changeTransferToken({ tokenSlug: slug });
@@ -504,22 +494,23 @@ function TransferInitial({
setTransferComment({ comment: trimStringByMaxBytes(value, COMMENT_MAX_SIZE_BYTES) });
});
+ const hasToAddressError = toAddress.length > 0 && !isAddressValid;
+ const isAmountGreaterThanBalance = !isNftTransfer && balance !== undefined && amount !== undefined
+ && amount > balance;
+ const hasAmountError = !isNftTransfer && ((amount ?? 0) < 0 || isEnoughBalance === false);
+ const isInsufficientFee = isEnoughBalance === false && !isAmountGreaterThanBalance;
const isCommentRequired = Boolean(toAddress) && isMemoRequired;
const hasCommentError = isCommentRequired && !comment;
- const requiredAmount = isNftTransfer ? NFT_TRANSFER_AMOUNT : amount;
- const isInsufficientBalance = balance !== undefined && amount !== undefined && amount > balance;
- const hasToAddressError = toAddress.length > 0 && !isAddressValid;
- const hasAmountError = Boolean(amount) && (amount < 0 || isInsufficientBalance || isInsufficientFee);
- const canSubmit = Boolean(tokenSlug && isAddressValid && requiredAmount && balance && requiredAmount > 0
- && requiredAmount <= balance && !hasAmountError
- && (isEnoughNativeCoin || isEnoughDiesel || isDieselNotAuthorized) && !hasCommentError
- && (!isNftTransfer || Boolean(nfts?.length)));
+ const canSubmit = isDieselNotAuthorized || Boolean(
+ isAddressValid && !isAmountMissing && !hasAmountError && isEnoughBalance && !hasCommentError
+ && !(isNftTransfer && !nfts?.length),
+ );
const handleSubmit = useLastCallback((e) => {
e.preventDefault();
- if (withDiesel && dieselStatus === 'not-authorized') {
+ if (isDieselNotAuthorized) {
authorizeDiesel();
return;
}
@@ -530,19 +521,16 @@ function TransferInitial({
vibrate();
- // Removes an excessive loading animation appearing in the Confirm button of the NFT transfer confirmation modal
- skipNextFeeEstimate.current = true;
-
submitTransferInitial({
- tokenSlug: tokenSlug!,
- amount: isNftTransfer ? NFT_TRANSFER_AMOUNT : amount!,
+ tokenSlug,
+ amount: amount ?? 0n,
toAddress,
comment,
binPayload,
shouldEncrypt,
nftAddresses: isNftTransfer ? nfts!.map(({ address }) => address) : undefined,
- withDiesel,
- isGaslessWithStars,
+ withDiesel: explainedFee.isGasless,
+ isGaslessWithStars: diesel?.status === 'stars-fee',
stateInit,
});
});
@@ -643,7 +631,7 @@ function TransferInitial({
let content: TeactNode = ' ';
if (amount) {
- if (isInsufficientBalance) {
+ if (isAmountGreaterThanBalance) {
transitionKey = 1;
content = {lang('Insufficient balance')};
} else if (isInsufficientFee) {
@@ -812,28 +800,25 @@ function TransferInitial({
const withButton = isQrScannerSupported || withPasteButton || withAddressClearButton;
function renderButtonText() {
- if (withDiesel && !isDieselAvailable) {
- if (dieselStatus === 'pending-previous') {
- return lang('Awaiting Previous Fee');
- } else {
- return lang('Authorize %token% Fee', { token: symbol! });
- }
- } else {
- return lang('$send_token_symbol', isNftTransfer ? 'NFT' : symbol || 'TON');
+ if (diesel?.status === 'not-authorized') {
+ return lang('Authorize %token% Fee', { token: symbol! });
+ }
+ if (diesel?.status === 'pending-previous' && isInsufficientFee) {
+ return lang('Awaiting Previous Fee');
}
+ return lang('$send_token_symbol', isNftTransfer ? 'NFT' : symbol || 'TON');
}
const shouldIgnoreErrors = isAddressBookOpen && shouldRenderSuggestions;
function renderFee() {
- const shouldShowFull = isInsufficientFee && !isInsufficientBalance;
let terms: FeeTerms | undefined;
let precision: FeePrecision = 'exact';
if (isNftTransfer) {
// NFT fee estimation is not supported yet. It will be added later.
} else if (amount) {
- const actualFee = shouldShowFull ? explainedFee.fullFee : explainedFee.realFee;
+ const actualFee = isInsufficientFee ? explainedFee.fullFee : explainedFee.realFee;
if (actualFee) {
({ terms, precision } = actualFee);
}
diff --git a/src/components/transfer/TransferModal.tsx b/src/components/transfer/TransferModal.tsx
index f3680196..97c61110 100644
--- a/src/components/transfer/TransferModal.tsx
+++ b/src/components/transfer/TransferModal.tsx
@@ -234,7 +234,7 @@ function TransferModal({
(Date.now());
+
+export function closeModal() {
+ setModalCloseSignal(Date.now());
+}
+
function Modal({
dialogRef,
title,
@@ -119,6 +126,12 @@ function Modal({
}
}, [isOpen, isCompact]);
+ useEffect(() => {
+ if (IS_DELEGATED_BOTTOM_SHEET || !isOpen) return undefined;
+
+ return getModalCloseSignal.subscribe(onClose);
+ }, [isOpen, onClose]);
+
useEffect(
() => (isOpen ? captureKeyboardListeners({
onEsc: { handler: onClose, shouldPreventDefault: IS_EXTENSION },
diff --git a/src/config.ts b/src/config.ts
index c210293a..fdcc654d 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -249,22 +249,12 @@ export const CHAIN_CONFIG = {
isDnsSupported: true,
addressRegex: /^([-\w_]{48}|0:[\da-h]{64})$/i,
nativeToken: TONCOIN,
- gas: {
- maxSwap: 400_000_000n, // 0.4 TON
- maxTransfer: 15_000_000n, // 0.015 TON
- maxTransferToken: 60_000_000n, // 0.06 TON
- },
},
tron: {
isMemoSupported: false,
isDnsSupported: false,
addressRegex: /^T[1-9A-HJ-NP-Za-km-z]{33}$/,
nativeToken: TRX,
- gas: {
- maxSwap: undefined,
- maxTransfer: 1_000_000n, // 1 TRX
- maxTransferToken: 30_000_000n, // 30 TRX
- },
mainnet: {
apiUrl: TRON_MAINNET_API_URL,
usdtAddress: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
@@ -405,7 +395,9 @@ export const INIT_SWAP_ASSETS: Record = {
};
export const DEFAULT_TRX_SWAP_FIRST_TOKEN_SLUG = TONCOIN.slug;
+export const DEFAULT_SWAP_FISRT_TOKEN_SLUG = TONCOIN.slug;
export const DEFAULT_SWAP_SECOND_TOKEN_SLUG = TON_USDT_SLUG;
+export const DEFAULT_TRANSFER_TOKEN_SLUG = TONCOIN.slug;
export const DEFAULT_CEX_SWAP_SECOND_TOKEN_SLUG = TRC20_USDT_MAINNET_SLUG;
export const SWAP_DEX_LABELS: Record = {
dedust: 'DeDust',
diff --git a/src/global/actions/api/swap.ts b/src/global/actions/api/swap.ts
index acc7e675..aa545aa0 100644
--- a/src/global/actions/api/swap.ts
+++ b/src/global/actions/api/swap.ts
@@ -26,6 +26,7 @@ import {
import {
DEFAULT_FEE,
+ DEFAULT_SWAP_FISRT_TOKEN_SLUG,
DEFAULT_SWAP_SECOND_TOKEN_SLUG,
IS_CAPACITOR,
TONCOIN,
@@ -232,9 +233,17 @@ addActionHandler('startSwap', async (global, actions, payload) => {
addActionHandler('setDefaultSwapParams', (global, actions, payload) => {
let { tokenInSlug: requiredTokenInSlug, tokenOutSlug: requiredTokenOutSlug } = payload ?? {};
+ const { withResetAmount } = payload ?? {};
- requiredTokenInSlug = requiredTokenInSlug || TONCOIN.slug;
+ requiredTokenInSlug = requiredTokenInSlug || DEFAULT_SWAP_FISRT_TOKEN_SLUG;
requiredTokenOutSlug = requiredTokenOutSlug || DEFAULT_SWAP_SECOND_TOKEN_SLUG;
+ if (
+ global.currentSwap.tokenInSlug === requiredTokenInSlug
+ && global.currentSwap.tokenOutSlug === requiredTokenOutSlug
+ && !withResetAmount
+ ) {
+ return;
+ }
global = updateCurrentSwap(global, {
tokenInSlug: requiredTokenInSlug,
@@ -247,6 +256,7 @@ addActionHandler('setDefaultSwapParams', (global, actions, payload) => {
amountOutMin: '0',
inputSource: SwapInputSource.In,
isDexLabelChanged: undefined,
+ ...(withResetAmount ? { amountIn: undefined, amountOut: undefined } : undefined),
});
setGlobal(global);
});
diff --git a/src/global/actions/api/transfer.ts b/src/global/actions/api/transfer.ts
index 87a5fe2d..a128ffe9 100644
--- a/src/global/actions/api/transfer.ts
+++ b/src/global/actions/api/transfer.ts
@@ -5,6 +5,7 @@ import { TransferState } from '../../types';
import { IS_CAPACITOR, NFT_BATCH_SIZE } from '../../../config';
import { vibrateOnError, vibrateOnSuccess } from '../../../util/capacitor';
+import { getDieselTokenAmount } from '../../../util/fee/transferFee';
import { callActionInNative } from '../../../util/multitab';
import { IS_DELEGATING_BOTTOM_SHEET } from '../../../util/windowEnvironment';
import { callApi } from '../../../api';
@@ -263,7 +264,7 @@ addActionHandler('submitTransferPassword', async (global, actions, { password })
shouldEncrypt,
isBase64Data: Boolean(binPayload),
withDiesel,
- dieselAmount: diesel?.amount.token,
+ dieselAmount: diesel && getDieselTokenAmount(diesel),
stateInit,
isGaslessWithStars,
};
diff --git a/src/global/actions/ui/initial.ts b/src/global/actions/ui/initial.ts
index 60959e5a..80550eb0 100644
--- a/src/global/actions/ui/initial.ts
+++ b/src/global/actions/ui/initial.ts
@@ -5,7 +5,12 @@ import { ApiCommonError, ApiTransactionDraftError, ApiTransactionError } from '.
import { AppState } from '../../types';
import {
- DEFAULT_SWAP_SECOND_TOKEN_SLUG, IS_CAPACITOR, IS_EXTENSION, TONCOIN,
+ DEFAULT_SWAP_FISRT_TOKEN_SLUG,
+ DEFAULT_SWAP_SECOND_TOKEN_SLUG,
+ DEFAULT_TRANSFER_TOKEN_SLUG,
+ IS_CAPACITOR,
+ IS_EXTENSION,
+ TONCOIN,
} from '../../../config';
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
import { parseAccountId } from '../../../util/account';
@@ -168,8 +173,31 @@ addActionHandler('selectToken', (global, actions, { slug } = {}) => {
actions.changeTransferToken({ tokenSlug: slug });
}
} else {
- actions.setDefaultSwapParams({ tokenInSlug: undefined, tokenOutSlug: undefined });
- actions.changeTransferToken({ tokenSlug: TONCOIN.slug });
+ const currentActivityToken = global.byAccountId[global.currentAccountId!].currentTokenSlug;
+
+ const isDefaultFirstTokenOutSwap = global.currentSwap.tokenOutSlug === DEFAULT_SWAP_FISRT_TOKEN_SLUG
+ && global.currentSwap.tokenInSlug === DEFAULT_SWAP_SECOND_TOKEN_SLUG;
+
+ const shouldResetSwap = global.currentSwap.tokenOutSlug === currentActivityToken
+ && (
+ (
+ global.currentSwap.tokenInSlug === DEFAULT_SWAP_FISRT_TOKEN_SLUG
+ && global.currentSwap.tokenOutSlug !== DEFAULT_SWAP_SECOND_TOKEN_SLUG
+ )
+ || isDefaultFirstTokenOutSwap
+ );
+
+ if (shouldResetSwap) {
+ actions.setDefaultSwapParams({ tokenInSlug: undefined, tokenOutSlug: undefined, withResetAmount: true });
+ }
+
+ const shouldResetTransfer = (global.currentTransfer.tokenSlug === currentActivityToken
+ && global.currentTransfer.tokenSlug !== DEFAULT_TRANSFER_TOKEN_SLUG)
+ && !global.currentTransfer.nfts?.length;
+
+ if (shouldResetTransfer) {
+ actions.changeTransferToken({ tokenSlug: DEFAULT_TRANSFER_TOKEN_SLUG, withResetAmount: true });
+ }
}
return updateCurrentAccountState(global, { currentTokenSlug: slug });
diff --git a/src/global/actions/ui/misc.ts b/src/global/actions/ui/misc.ts
index beb11ca6..68dbad95 100644
--- a/src/global/actions/ui/misc.ts
+++ b/src/global/actions/ui/misc.ts
@@ -75,6 +75,7 @@ import { switchAccount } from '../api/auth';
import { reportAppLockActivityEvent } from '../../../components/AppLocked';
import { getCardNftImageUrl } from '../../../components/main/sections/Card/helpers/getCardNftImageUrl';
+import { closeModal } from '../../../components/ui/Modal';
const OPEN_LEDGER_TAB_DELAY = 500;
const APP_VERSION_URL = IS_ANDROID_APP ? `${IS_PRODUCTION ? PRODUCTION_URL : BETA_URL}/version.txt` : 'version.txt';
@@ -819,6 +820,20 @@ addActionHandler('clearAccentColorFromNft', (global) => {
});
});
+addActionHandler('closeAnyModal', () => {
+ if (IS_DELEGATED_BOTTOM_SHEET) {
+ callActionInMain('closeAnyModal');
+ return;
+ }
+
+ closeModal();
+});
+
+addActionHandler('closeAllEntities', (global, actions) => {
+ actions.closeAnyModal();
+ actions.closeMediaViewer();
+});
+
async function connectLedgerAndGetHardwareState() {
const ledgerApi = await import('../../../util/ledger');
let newHardwareState;
diff --git a/src/global/actions/ui/transfer.ts b/src/global/actions/ui/transfer.ts
index 55838031..52b04984 100644
--- a/src/global/actions/ui/transfer.ts
+++ b/src/global/actions/ui/transfer.ts
@@ -27,12 +27,18 @@ addActionHandler('startTransfer', (global, actions, payload) => {
}
});
-addActionHandler('changeTransferToken', (global, actions, { tokenSlug }) => {
+addActionHandler('changeTransferToken', (global, actions, { tokenSlug, withResetAmount }) => {
const { amount, tokenSlug: currentTokenSlug } = global.currentTransfer;
+ if (tokenSlug === currentTokenSlug && !withResetAmount) {
+ return;
+ }
+
const currentToken = currentTokenSlug ? global.tokenInfo.bySlug[currentTokenSlug] : undefined;
const newToken = global.tokenInfo.bySlug[tokenSlug];
- if (amount && currentToken?.decimals !== newToken?.decimals) {
+ if (withResetAmount) {
+ global = updateCurrentTransfer(global, { amount: undefined });
+ } else if (amount && currentToken?.decimals !== newToken?.decimals) {
global = updateCurrentTransfer(global, {
amount: fromDecimal(toDecimal(amount, currentToken?.decimals), newToken?.decimals),
});
@@ -43,6 +49,7 @@ addActionHandler('changeTransferToken', (global, actions, { tokenSlug }) => {
fee: undefined,
realFee: undefined,
diesel: undefined,
+ nfts: undefined,
}));
});
diff --git a/src/global/initialState.ts b/src/global/initialState.ts
index 992a50e1..c4c6e5dc 100644
--- a/src/global/initialState.ts
+++ b/src/global/initialState.ts
@@ -13,10 +13,10 @@ import {
ANIMATION_LEVEL_DEFAULT,
DEFAULT_SLIPPAGE_VALUE,
DEFAULT_STAKING_STATE,
+ DEFAULT_TRANSFER_TOKEN_SLUG,
INIT_SWAP_ASSETS,
THEME_DEFAULT,
TOKEN_INFO,
- TONCOIN,
} from '../config';
import { IS_IOS_APP, USER_AGENT_LANG_CODE } from '../util/windowEnvironment';
@@ -37,7 +37,7 @@ export const INITIAL_STATE: GlobalState = {
currentTransfer: {
state: TransferState.None,
- tokenSlug: TONCOIN.slug,
+ tokenSlug: DEFAULT_TRANSFER_TOKEN_SLUG,
},
currentSwap: {
diff --git a/src/global/types.ts b/src/global/types.ts
index 0a560a08..620d85f5 100644
--- a/src/global/types.ts
+++ b/src/global/types.ts
@@ -763,7 +763,7 @@ export interface ActionPayloads {
binPayload?: string;
stateInit?: string;
} | undefined;
- changeTransferToken: { tokenSlug: string };
+ changeTransferToken: { tokenSlug: string; withResetAmount?: boolean };
fetchTransferFee: {
tokenSlug: string;
toAddress: string;
@@ -847,6 +847,8 @@ export interface ActionPayloads {
};
closeHideNftModal: undefined;
+ closeAnyModal: undefined;
+ closeAllEntities: undefined;
submitSignature: { password: string };
clearSignatureError: undefined;
cancelSignature: undefined;
@@ -982,7 +984,7 @@ export interface ActionPayloads {
toAddress?: string;
} | undefined;
cancelSwap: { shouldReset?: boolean } | undefined;
- setDefaultSwapParams: { tokenInSlug?: string; tokenOutSlug?: string } | undefined;
+ setDefaultSwapParams: { tokenInSlug?: string; tokenOutSlug?: string; withResetAmount?: boolean } | undefined;
switchSwapTokens: undefined;
setSwapTokenIn: { tokenSlug: string };
setSwapTokenOut: { tokenSlug: string };
diff --git a/src/util/capacitor/notifications.ts b/src/util/capacitor/notifications.ts
index 414e7078..f1901587 100644
--- a/src/util/capacitor/notifications.ts
+++ b/src/util/capacitor/notifications.ts
@@ -79,7 +79,12 @@ export async function initNotificationsWithGlobal(global: GlobalState) {
}
function handlePushNotificationActionPerformed(notification: ActionPerformed) {
- const { showAnyAccountTx, showAnyAccountTokenActivity, openAnyAccountStakingInfo } = getActions();
+ const {
+ showAnyAccountTx,
+ showAnyAccountTokenActivity,
+ openAnyAccountStakingInfo,
+ closeAllEntities,
+ } = getActions();
const global = getGlobal();
const notificationData = notification.notification.data as MessageData;
const { action, address } = notificationData;
@@ -93,6 +98,7 @@ function handlePushNotificationActionPerformed(notification: ActionPerformed) {
const network = 'mainnet';
+ closeAllEntities();
if (action === 'nativeTx' || action === 'swap') {
const { txId } = notificationData;
showAnyAccountTx({ accountId, txId, network });
diff --git a/src/util/fee/transferFee.ts b/src/util/fee/transferFee.ts
index 07903aa2..9f0d853a 100644
--- a/src/util/fee/transferFee.ts
+++ b/src/util/fee/transferFee.ts
@@ -39,7 +39,8 @@ type ExplainedTransferFee = {
canTransferFullBalance: boolean;
};
-type ApiFeeWithDiesel = ApiFee & { diesel: ApiFetchEstimateDieselResult };
+type AvailableDiesel = ApiFetchEstimateDieselResult & { amount: bigint };
+type ApiFeeWithDiesel = ApiFee & { diesel: AvailableDiesel };
type MaxTransferAmountInput = {
/** The wallet balance of the transferred token. Undefined means that it's unknown. */
@@ -52,6 +53,13 @@ type MaxTransferAmountInput = {
canTransferFullBalance: boolean;
};
+type BalanceSufficientForTransferInput = Omit & {
+ /** The wallet balance of the native token of the transfer chain. Undefined means that it's unknown. */
+ nativeTokenBalance: bigint | undefined;
+ /** The transferred amount. Undefined means that it's unknown. */
+ transferAmount: bigint | undefined;
+};
+
/**
* Converts the transfer fee data returned from API into data that is ready to be displayed in the transfer form UI.
*/
@@ -89,9 +97,38 @@ export function getMaxTransferAmount({
return bigintMax(tokenBalance - fee, 0n);
}
+/**
+ * Decides whether the balance is sufficient to transfer the amount and pay the fees.
+ * Returns undefined when it can't be calculated because of insufficient input data.
+ */
+export function isBalanceSufficientForTransfer({
+ tokenBalance,
+ nativeTokenBalance,
+ transferAmount,
+ fullFee,
+ canTransferFullBalance,
+}: BalanceSufficientForTransferInput) {
+ if (transferAmount === undefined || tokenBalance === undefined || nativeTokenBalance === undefined || !fullFee) {
+ return undefined;
+ }
+
+ const isFullTokenTransfer = transferAmount === tokenBalance && canTransferFullBalance;
+ const tokenRequiredAmount = (fullFee.token ?? 0n) + (isFullTokenTransfer ? 0n : transferAmount);
+ const nativeTokenRequiredAmount = fullFee.native ?? 0n;
+
+ return tokenRequiredAmount <= tokenBalance && nativeTokenRequiredAmount <= nativeTokenBalance;
+}
+
+export function isDieselAvailable(diesel: ApiFetchEstimateDieselResult): diesel is AvailableDiesel {
+ return diesel.status !== 'not-available' && diesel.amount !== undefined;
+}
+
+export function getDieselTokenAmount(diesel: ApiFetchEstimateDieselResult) {
+ return diesel.status === 'stars-fee' ? 0n : (diesel.amount ?? 0n);
+}
+
function shouldUseDiesel(input: ApiFee): input is ApiFeeWithDiesel {
- return input.diesel !== undefined
- && input.diesel.status !== 'not-available';
+ return input.diesel !== undefined && isDieselAvailable(input.diesel);
}
/**
@@ -127,10 +164,9 @@ function explainGasfullTransferFee(input: ApiFee) {
function explainGaslessTransferFee({ diesel }: ApiFeeWithDiesel) {
const isStarsDiesel = diesel.status === 'stars-fee';
const dieselKey = isStarsDiesel ? 'stars' : 'token';
- const dieselAmount = diesel.amount[dieselKey];
- const realFeeInDiesel = convertFee(diesel.realFee, diesel.nativeAmount, dieselAmount);
+ const realFeeInDiesel = convertFee(diesel.realFee, diesel.nativeAmount, diesel.amount);
// Cover as much displayed real fee as possible with diesel, because in the excess it will return as the native token.
- const dieselRealFee = bigintMin(dieselAmount, realFeeInDiesel);
+ const dieselRealFee = bigintMin(diesel.amount, realFeeInDiesel);
// Cover the remaining real fee with the native token.
const nativeRealFee = bigintMax(0n, diesel.realFee - diesel.nativeAmount);
@@ -140,8 +176,7 @@ function explainGaslessTransferFee({ diesel }: ApiFeeWithDiesel) {
fullFee: {
precision: 'lessThan',
terms: {
- token: diesel.amount.token,
- stars: diesel.amount.stars,
+ [dieselKey]: diesel.amount,
native: diesel.remainingFee,
},
},