From 7acda0f77dec44bdfa4f2611e88a7355cc0ddaa7 Mon Sep 17 00:00:00 2001 From: mytonwalletorg Date: Tue, 15 Oct 2024 15:45:55 +0200 Subject: [PATCH] v3.0.30 --- changelogs/3.0.30.txt | 1 + package-lock.json | 4 +- package.json | 2 +- public/version.txt | 2 +- src/api/chains/ton/polling.ts | 15 ++- src/api/chains/ton/tokens.ts | 7 +- src/api/chains/ton/transactions.ts | 7 +- src/api/methods/polling.ts | 7 +- src/api/types/misc.ts | 1 + src/api/types/updates.ts | 4 +- src/assets/logoDark.svg | 1 + src/assets/logoLight.svg | 1 + src/components/auth/Auth.module.scss | 4 +- src/components/auth/Auth.tsx | 13 +- src/components/auth/AuthStart.tsx | 12 +- src/components/auth/MnemonicList.tsx | 10 +- .../main/modals/AddAccountModal.module.scss | 12 +- src/components/main/modals/BackupModal.tsx | 9 +- .../main/modals/QrScannerModal.module.scss | 2 +- .../main/sections/Content/Token.module.scss | 3 + src/components/settings/Settings.tsx | 9 +- src/components/settings/SettingsAbout.tsx | 16 ++- src/components/swap/Swap.module.scss | 3 + src/components/swap/SwapModal.tsx | 11 +- src/components/transfer/Transfer.module.scss | 4 + src/components/transfer/TransferInitial.tsx | 36 +++--- src/components/ui/Dropdown.tsx | 5 +- src/components/ui/DropdownMenu.tsx | 4 +- src/components/ui/Modal.module.scss | 11 +- src/global/actions/api/swap.ts | 116 ++++++++---------- src/global/actions/api/wallet.ts | 14 ++- src/global/actions/ui/initial.ts | 4 + src/global/actions/ui/misc.ts | 2 +- src/global/reducers/misc.ts | 11 +- src/global/types.ts | 2 + 35 files changed, 232 insertions(+), 133 deletions(-) create mode 100644 changelogs/3.0.30.txt create mode 100644 src/assets/logoDark.svg create mode 100644 src/assets/logoLight.svg diff --git a/changelogs/3.0.30.txt b/changelogs/3.0.30.txt new file mode 100644 index 00000000..619f4cd5 --- /dev/null +++ b/changelogs/3.0.30.txt @@ -0,0 +1 @@ +Bug fixes and performance improvements diff --git a/package-lock.json b/package-lock.json index 517efd30..4aa98e38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mytonwallet", - "version": "3.0.29", + "version": "3.0.30", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mytonwallet", - "version": "3.0.29", + "version": "3.0.30", "license": "GPL-3.0-or-later", "dependencies": { "@awesome-cordova-plugins/core": "6.8.0", diff --git a/package.json b/package.json index d0844226..4522dcb8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mytonwallet", - "version": "3.0.29", + "version": "3.0.30", "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 92eba66b..f9d0e255 100644 --- a/public/version.txt +++ b/public/version.txt @@ -1 +1 @@ -3.0.29 +3.0.30 diff --git a/src/api/chains/ton/polling.ts b/src/api/chains/ton/polling.ts index 6145a2d7..ecfdb460 100644 --- a/src/api/chains/ton/polling.ts +++ b/src/api/chains/ton/polling.ts @@ -27,7 +27,7 @@ import { import { getStakingCommonCache } from '../../common/cache'; import { isAlive, isUpdaterAlive } from '../../common/helpers'; import { processNftUpdates, updateAccountNfts } from '../../common/nft'; -import { addTokens } from '../../common/tokens'; +import { addTokens, getTokensCache } from '../../common/tokens'; import { txCallbacks } from '../../common/txCallbacks'; import { hexToBytes } from '../../common/utils'; import { FIRST_TRANSACTIONS_LIMIT, SEC } from '../../constants'; @@ -144,25 +144,34 @@ async function setupBalanceBasedPolling( if (isToncoinBalanceChanged || (doubleCheckTokensTime && doubleCheckTokensTime < Date.now())) { doubleCheckTokensTime = isToncoinBalanceChanged ? Date.now() + DOUBLE_CHECK_TOKENS_PAUSE : undefined; tokenBalances = await getAccountTokenBalances(accountId).catch(logAndRescue); + const slugsWithBalances = new Set(tokenBalances?.map(({ slug }) => slug)); + const tokensCache = getTokensCache(); + const mintlessZeroBalances = Object.fromEntries( + Object.values(tokensCache) + .filter((token) => token.customPayloadApiUrl && !slugsWithBalances.has(token.slug)) + .map(({ slug }) => [slug, undefined]), + ); throwErrorIfUpdaterNotAlive(onUpdate, accountId); if (tokenBalances) { const tokens = tokenBalances.filter(Boolean).map(({ token }) => token); await addTokens(tokens, onUpdate); + const cachedTokenBalances = cache?.tokenBalances || {}; tokenBalances.forEach(({ slug, balance: tokenBalance }) => { - const cachedBalance = cache?.tokenBalances && cache.tokenBalances[slug]; + const cachedBalance = cachedTokenBalances[slug]; if (cachedBalance === tokenBalance) return; changedTokenSlugs.push(slug); balancesToUpdate[slug] = tokenBalance; }); + Object.assign(balancesToUpdate, mintlessZeroBalances); lastBalanceCache[accountId] = { ...lastBalanceCache[accountId], tokenBalances: Object.fromEntries(tokenBalances.map( - ({ slug, balance: tokenBalance }) => [slug, tokenBalance], + ({ slug, balance: tokenBalance }) => [slug, tokenBalance || 0n], )), }; } diff --git a/src/api/chains/ton/tokens.ts b/src/api/chains/ton/tokens.ts index b0f4c709..1476d150 100644 --- a/src/api/chains/ton/tokens.ts +++ b/src/api/chains/ton/tokens.ts @@ -266,8 +266,11 @@ export async function getMintlessParams(options: { if (!isMintlessClaimed) { const data = await fetchMintlessTokenWalletData(token.customPayloadApiUrl!, fromAddress); + const isExpired = data + ? Date.now() > new Date(Number(data.compressed_info.expired_at) * 1000).getTime() + : true; - if (data) { + if (data && !isExpired) { customPayload = data.custom_payload; mintlessTokenBalance = BigInt(data.compressed_info.amount); @@ -301,6 +304,8 @@ async function fetchMintlessTokenWalletData(customPayloadApiUrl: string, address state_init: string; compressed_info: { amount: string; + start_from: string; + expired_at: string; }; } | undefined; } diff --git a/src/api/chains/ton/transactions.ts b/src/api/chains/ton/transactions.ts index bcc0740d..9bd95c19 100644 --- a/src/api/chains/ton/transactions.ts +++ b/src/api/chains/ton/transactions.ts @@ -113,6 +113,7 @@ export async function checkTransactionDraft( tokenAddress, shouldEncrypt, isBase64Data, + stateInit: stateInitString, } = options; let { toAddress, amount, data } = options; @@ -131,7 +132,7 @@ export async function checkTransactionDraft( const { isInitialized } = await getContractInfo(network, toAddress); - if (options.stateInit && !isBase64Data) { + if (stateInitString && !isBase64Data) { return { ...result, error: ApiTransactionDraftError.StateInitWithoutBin, @@ -140,9 +141,9 @@ export async function checkTransactionDraft( let stateInit; - if (options.stateInit) { + if (stateInitString) { try { - stateInit = Cell.fromBase64(options.stateInit); + stateInit = Cell.fromBase64(stateInitString); } catch { return { ...result, diff --git a/src/api/methods/polling.ts b/src/api/methods/polling.ts index 2491350b..4358fe97 100644 --- a/src/api/methods/polling.ts +++ b/src/api/methods/polling.ts @@ -37,7 +37,7 @@ export async function initPolling(_onUpdate: OnApiUpdate) { Promise.allSettled([ tryUpdateKnownAddresses(), tryUpdateTokens(_onUpdate, true), - tryLoadSwapTokens(_onUpdate), + tryUpdateSwapTokens(_onUpdate), tryUpdateStakingCommonData(), ]).then(() => resolveDataPreloadPromise()); @@ -74,6 +74,7 @@ export async function setupLongBackendPolling() { tryUpdateKnownAddresses(), tryUpdateStakingCommonData(), tryUpdateConfig(localOnUpdate), + tryUpdateSwapTokens(localOnUpdate), ]); } } @@ -124,7 +125,7 @@ export async function tryUpdateTokens(localOnUpdate?: OnApiUpdate, isFirstRun?: } } -export async function tryLoadSwapTokens(localOnUpdate?: OnApiUpdate) { +export async function tryUpdateSwapTokens(localOnUpdate?: OnApiUpdate) { if (!localOnUpdate) { localOnUpdate = onUpdate; } @@ -153,7 +154,7 @@ export async function tryLoadSwapTokens(localOnUpdate?: OnApiUpdate) { tokens, }); } catch (err) { - logDebugError('tryLoadSwapTokens', err); + logDebugError('tryUpdateSwapTokens', err); } } diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts index a8af60b4..2be4a722 100644 --- a/src/api/types/misc.ts +++ b/src/api/types/misc.ts @@ -182,6 +182,7 @@ export enum ApiLiquidUnstakeMode { export type ApiLoyaltyType = 'black' | 'platinum' | 'gold' | 'silver' | 'standard'; export type ApiBalanceBySlug = Record; +export type ApiMaybeBalanceBySlug = Record; export type ApiWalletInfo = { address: string; diff --git a/src/api/types/updates.ts b/src/api/types/updates.ts index ade3657b..15883805 100644 --- a/src/api/types/updates.ts +++ b/src/api/types/updates.ts @@ -5,11 +5,11 @@ import type { ApiStakingCommonData, ApiSwapAsset, ApiVestingInfo } from './backe import type { ApiAnyDisplayError } from './errors'; import type { ApiBackendStakingState, - ApiBalanceBySlug, ApiBaseCurrency, ApiChain, ApiCountryCode, ApiDappTransfer, + ApiMaybeBalanceBySlug, ApiNft, ApiStakingState, ApiTokenWithPrice, @@ -21,7 +21,7 @@ import type { ApiDapp, ApiTonWallet } from './storage'; export type ApiUpdateBalances = { type: 'updateBalances'; accountId: string; - balancesToUpdate: ApiBalanceBySlug; + balancesToUpdate: ApiMaybeBalanceBySlug; }; export type ApiUpdateNewActivities = { diff --git a/src/assets/logoDark.svg b/src/assets/logoDark.svg new file mode 100644 index 00000000..696e76ce --- /dev/null +++ b/src/assets/logoDark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/logoLight.svg b/src/assets/logoLight.svg new file mode 100644 index 00000000..dccb15c8 --- /dev/null +++ b/src/assets/logoLight.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/auth/Auth.module.scss b/src/components/auth/Auth.module.scss index 40ac5520..b3807856 100644 --- a/src/components/auth/Auth.module.scss +++ b/src/components/auth/Auth.module.scss @@ -70,8 +70,8 @@ .logo { display: block !important; - width: 12.5rem; - height: 12.5rem; + width: 10rem; + height: 10rem; margin: 5rem 0 0 0; transition: opacity 1s ease, transform 350ms ease-out !important; diff --git a/src/components/auth/Auth.tsx b/src/components/auth/Auth.tsx index 7abfc2a3..cad51cb7 100644 --- a/src/components/auth/Auth.tsx +++ b/src/components/auth/Auth.tsx @@ -2,7 +2,7 @@ import React, { memo, useState } from '../../lib/teact/teact'; import { getActions } from '../../lib/teact/teactn'; import { withGlobal } from '../../global'; -import type { GlobalState } from '../../global/types'; +import type { GlobalState, Theme } from '../../global/types'; import { AuthState } from '../../global/types'; import { pick } from '../../util/iteratees'; @@ -33,7 +33,7 @@ import styles from './Auth.module.scss'; type StateProps = Pick; +)> & { theme: Theme }; const RENDER_COUNT = Object.keys(AuthState).length / 2; @@ -46,6 +46,7 @@ const Auth = ({ mnemonicCheckIndexes, isBackupModalOpen, method, + theme, }: StateProps) => { const { closeAbout, @@ -150,7 +151,7 @@ const Auth = ({ /> ); case AuthState.about: - return ; + return ; } } @@ -180,8 +181,12 @@ const Auth = ({ }; export default memo(withGlobal((global): StateProps => { - return pick(global.auth, [ + const authProps = pick(global.auth, [ 'state', 'biometricsStep', 'error', 'mnemonic', 'mnemonicCheckIndexes', 'isLoading', 'method', 'isBackupModalOpen', ]); + return { + ...authProps, + theme: global.settings.theme, + }; })(Auth)); diff --git a/src/components/auth/AuthStart.tsx b/src/components/auth/AuthStart.tsx index a6f611ee..f7dd07f3 100644 --- a/src/components/auth/AuthStart.tsx +++ b/src/components/auth/AuthStart.tsx @@ -1,11 +1,14 @@ import React, { memo } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; +import { type Theme } from '../../global/types'; + import { APP_NAME } from '../../config'; import renderText from '../../global/helpers/renderText'; import buildClassName from '../../util/buildClassName'; import { IS_LEDGER_SUPPORTED } from '../../util/windowEnvironment'; +import useAppTheme from '../../hooks/useAppTheme'; import useFlag from '../../hooks/useFlag'; import useLang from '../../hooks/useLang'; import useShowTransition from '../../hooks/useShowTransition'; @@ -14,14 +17,16 @@ import Button from '../ui/Button'; import styles from './Auth.module.scss'; -import logoPath from '../../assets/logo.svg'; +import logoDarkPath from '../../assets/logoDark.svg'; +import logoLightPath from '../../assets/logoLight.svg'; interface StateProps { hasAccounts?: boolean; isLoading?: boolean; + theme: Theme; } -function AuthStart({ hasAccounts, isLoading }: StateProps) { +function AuthStart({ hasAccounts, isLoading, theme }: StateProps) { const { startCreatingWallet, startImportingWallet, @@ -31,6 +36,8 @@ function AuthStart({ hasAccounts, isLoading }: StateProps) { } = getActions(); const lang = useLang(); + const appTheme = useAppTheme(theme); + const logoPath = appTheme === 'light' ? logoLightPath : logoDarkPath; const [isLogoReady, markLogoReady] = useFlag(); const { transitionClassNames } = useShowTransition(isLogoReady, undefined, undefined, 'slow'); @@ -96,5 +103,6 @@ export default memo(withGlobal((global): StateProps => { return { hasAccounts: Boolean(global.currentAccountId), isLoading: global.auth.isLoading, + theme: global.settings.theme, }; })(AuthStart)); diff --git a/src/components/auth/MnemonicList.tsx b/src/components/auth/MnemonicList.tsx index c45de746..cdf79a4f 100644 --- a/src/components/auth/MnemonicList.tsx +++ b/src/components/auth/MnemonicList.tsx @@ -16,7 +16,7 @@ type OwnProps = { isActive?: boolean; mnemonic?: string[]; onClose: NoneToVoidFunction; - onNext: NoneToVoidFunction; + onNext?: NoneToVoidFunction; }; function MnemonicList({ @@ -47,9 +47,11 @@ function MnemonicList({ ))} -
- -
+ {onNext && ( +
+ +
+ )} ); diff --git a/src/components/main/modals/AddAccountModal.module.scss b/src/components/main/modals/AddAccountModal.module.scss index d30863f6..9f00c836 100644 --- a/src/components/main/modals/AddAccountModal.module.scss +++ b/src/components/main/modals/AddAccountModal.module.scss @@ -92,8 +92,10 @@ margin-bottom: 1rem; @include respond-below(xs) { - @supports (margin-bottom: max(var(--safe-area-bottom), 1.5rem)) { - margin-bottom: max(var(--safe-area-bottom), 1.5rem); + :global(html:not(.is-android-app)) & { + @supports (margin-bottom: max(var(--safe-area-bottom), 1.5rem)) { + margin-bottom: max(var(--safe-area-bottom), 1.5rem); + } } } } @@ -122,8 +124,10 @@ text-align: center; @include respond-below(xs) { - @supports (margin-bottom: max(var(--safe-area-bottom), 1.5rem)) { - margin-bottom: max(var(--safe-area-bottom), 1.5rem); + :global(html:not(.is-android-app)) & { + @supports (margin-bottom: max(var(--safe-area-bottom), 1.5rem)) { + margin-bottom: max(var(--safe-area-bottom), 1.5rem); + } } } } diff --git a/src/components/main/modals/BackupModal.tsx b/src/components/main/modals/BackupModal.tsx index d7fbbd85..f30b0f08 100644 --- a/src/components/main/modals/BackupModal.tsx +++ b/src/components/main/modals/BackupModal.tsx @@ -5,6 +5,7 @@ import { getActions, withGlobal } from '../../../global'; import { IS_CAPACITOR, MNEMONIC_COUNT } from '../../../config'; import { selectMnemonicForCheck } from '../../../global/actions/api/auth'; +import { selectCurrentAccountState } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import { vibrateOnError, vibrateOnSuccess } from '../../../util/capacitor'; import isMnemonicPrivateKey from '../../../util/isMnemonicPrivateKey'; @@ -33,6 +34,7 @@ type OwnProps = { type StateProps = { currentAccountId?: string; + isBackupRequired?: boolean; }; enum SLIDES { @@ -43,7 +45,7 @@ enum SLIDES { } function BackupModal({ - isOpen, currentAccountId, onClose, + isOpen, currentAccountId, onClose, isBackupRequired, }: OwnProps & StateProps) { const { setIsBackupRequired, setIsPinAccepted, clearIsPinAccepted } = getActions(); @@ -159,7 +161,7 @@ function BackupModal({ ) : ( ); @@ -205,5 +207,6 @@ function BackupModal({ } export default memo(withGlobal((global): StateProps => { - return { currentAccountId: global.currentAccountId }; + const { isBackupRequired } = selectCurrentAccountState(global) || {}; + return { currentAccountId: global.currentAccountId, isBackupRequired }; })(BackupModal)); diff --git a/src/components/main/modals/QrScannerModal.module.scss b/src/components/main/modals/QrScannerModal.module.scss index 4afc04e9..4ca7be44 100644 --- a/src/components/main/modals/QrScannerModal.module.scss +++ b/src/components/main/modals/QrScannerModal.module.scss @@ -100,7 +100,7 @@ } .closeButton { - top: 1rem; + top: max(var(--safe-area-top), 1rem); right: 1rem; width: 1.875rem !important; diff --git a/src/components/main/sections/Content/Token.module.scss b/src/components/main/sections/Content/Token.module.scss index ccbc68bb..76755cf5 100644 --- a/src/components/main/sections/Content/Token.module.scss +++ b/src/components/main/sections/Content/Token.module.scss @@ -201,6 +201,9 @@ .tokenIcon { margin: 0 0.625rem 0 0; + .container:hover > & { + --color-background-first: var(--color-interactive-item-hover); + } } .vestingIcon, diff --git a/src/components/settings/Settings.tsx b/src/components/settings/Settings.tsx index c094d8b1..5a7b2d7f 100644 --- a/src/components/settings/Settings.tsx +++ b/src/components/settings/Settings.tsx @@ -642,7 +642,14 @@ function Settings({ /> ); case SettingsState.About: - return ; + return ( + + ); case SettingsState.Disclaimer: return ( void; isInsideModal?: boolean; + theme: Theme; } -function SettingsAbout({ isActive, handleBackClick, isInsideModal }: OwnProps) { +function SettingsAbout({ + isActive, handleBackClick, isInsideModal, theme, +}: OwnProps) { const lang = useLang(); useHistoryBack({ @@ -38,6 +45,9 @@ function SettingsAbout({ isActive, handleBackClick, isInsideModal }: OwnProps) { isScrolled, } = useScrolledState(); + const appTheme = useAppTheme(theme); + const logoPath = appTheme === 'light' ? logoLightPath : logoDarkPath; + return (
{isInsideModal ? ( @@ -64,7 +74,7 @@ function SettingsAbout({ isActive, handleBackClick, isInsideModal }: OwnProps) { )} onScroll={isInsideModal ? handleContentScroll : undefined} > - {lang('Logo')} + {lang('Logo')}

{APP_NAME} {APP_VERSION} {APP_ENV_MARKER} diff --git a/src/components/swap/Swap.module.scss b/src/components/swap/Swap.module.scss index 8688fd2c..deecd074 100644 --- a/src/components/swap/Swap.module.scss +++ b/src/components/swap/Swap.module.scss @@ -150,6 +150,9 @@ .tokenIcon { --color-background-first: var(--color-input-button-background); + .tokenSelector:hover > & { + --color-background-first: var(--color-input-button-background-hover); + } } .tokenContent { diff --git a/src/components/swap/SwapModal.tsx b/src/components/swap/SwapModal.tsx index 8cad8412..b1c8f989 100644 --- a/src/components/swap/SwapModal.tsx +++ b/src/components/swap/SwapModal.tsx @@ -59,6 +59,7 @@ function SwapModal({ payoutAddress, payinExtraId, isSettingsModalOpen, + networkFee, }, swapTokens, activityById, @@ -93,10 +94,11 @@ function SwapModal({ const [renderedTransactionAmountOut, setRenderedTransactionAmountOut] = useState(amountOut); const [renderedTransactionTokenIn, setRenderedTransactionTokenIn] = useState(tokenIn); const [renderedTransactionTokenOut, setRenderedTransactionTokenOut] = useState(tokenOut); + const [renderedNetworkFee, setRenderedNetworkFee] = useState(networkFee); const [renderedActivity, setRenderedActivity] = useState(); useEffect(() => { - if (!isOpen || !activityById || !activityId) { + if (!isOpen || !activityId || !activityById?.[activityId]) { setRenderedActivity(undefined); return; } @@ -117,6 +119,7 @@ function SwapModal({ setRenderedTransactionAmountOut(amountOut); setRenderedTransactionTokenIn(tokenIn); setRenderedTransactionTokenOut(tokenOut); + setRenderedNetworkFee(networkFee); setRenderedSwapType(swapType); if (swapType === SwapType.OnChain) { @@ -249,9 +252,9 @@ function SwapModal({ ); case SwapState.Complete: { - const networkFee = renderedActivity && 'networkFee' in renderedActivity + const networkFeeValue = renderedActivity && 'networkFee' in renderedActivity ? renderedActivity.networkFee - : undefined; + : renderedNetworkFee; return ( { + if (isMaxAmountSelected && prevDieselAmount !== dieselAmount && maxAmount! > 0) { + isUpdatingAmountDueToMaxChange.current = true; + + setMaxAmountSelected(false); + setPrevDieselAmount(dieselAmount); + setTransferAmount({ amount: maxAmount }); + } + }, [ + dieselAmount, maxAmount, isMaxAmountSelected, prevDieselAmount, withDiesel, balance, isGaslessWithStars, + ]); + useEffect(() => { if ( !toAddress @@ -388,21 +400,12 @@ function TransferInitial({ tokenSlug, ]); - useEffect(() => { - if (isMaxAmountSelected && prevDieselAmount !== dieselAmount) { - isUpdatingAmountDueToMaxChange.current = true; - - setMaxAmountSelected(false); - setPrevDieselAmount(dieselAmount); - setTransferAmount({ amount: maxAmount }); - } - }, [ - dieselAmount, maxAmount, isMaxAmountSelected, prevDieselAmount, withDiesel, balance, isGaslessWithStars, - ]); - const handleTokenChange = useLastCallback( (slug: string) => { + if (slug === tokenSlug) return; + changeTransferToken({ tokenSlug: slug }); + setMaxAmountSelected(false); if (slug === STAKED_TOKEN_SLUG) { showDialog({ title: lang('Warning!'), @@ -569,9 +572,10 @@ function TransferInitial({ } vibrate(); - - setMaxAmountSelected(true); - setTransferAmount({ amount: maxAmount }); + if (maxAmount! > 0) { + setMaxAmountSelected(true); + setTransferAmount({ amount: maxAmount }); + } }); const handleCommentChange = useLastCallback((value) => { @@ -606,6 +610,7 @@ function TransferInitial({ amount: isNftTransfer ? NFT_TRANSFER_AMOUNT : amount!, toAddress, comment, + binPayload, shouldEncrypt, nftAddresses: isNftTransfer ? nfts!.map(({ address }) => address) : undefined, withDiesel, @@ -836,6 +841,7 @@ function TransferInitial({ items={dropDownItems} selectedValue={tokenSlug} className={styles.tokenDropdown} + itemNameClassName={styles.tokenDropdownItem} onChange={handleTokenChange} /> ); diff --git a/src/components/ui/Dropdown.tsx b/src/components/ui/Dropdown.tsx index 8ddad597..d25c3485 100644 --- a/src/components/ui/Dropdown.tsx +++ b/src/components/ui/Dropdown.tsx @@ -27,6 +27,7 @@ interface OwnProps { selectedValue?: string; items: DropdownItem[]; className?: string; + itemNameClassName?: string; theme?: 'light'; arrow?: 'caret' | 'chevron'; menuPosition?: 'top' | 'bottom'; @@ -45,6 +46,7 @@ function Dropdown({ items, selectedValue, className, + itemNameClassName, theme, arrow = DEFAULT_ARROW, menuPosition, @@ -115,7 +117,7 @@ function Dropdown({ aria-hidden /> )} - + {shouldTranslateOptions ? lang(selectedItem!.name) : selectedItem!.name} {withMenu && } @@ -130,6 +132,7 @@ function Dropdown({ items={items} shouldTranslateOptions={shouldTranslateOptions} selectedValue={selectedValue} + itemNameClassName={itemNameClassName} onSelect={onChange} onClose={closeMenu} /> diff --git a/src/components/ui/DropdownMenu.tsx b/src/components/ui/DropdownMenu.tsx index 6e5f75f8..2e69033c 100644 --- a/src/components/ui/DropdownMenu.tsx +++ b/src/components/ui/DropdownMenu.tsx @@ -26,6 +26,7 @@ interface OwnProps { buttonClassName?: string; iconClassName?: string; fontIconClassName?: string; + itemNameClassName?: string; shouldCleanup?: boolean; onSelect?: (value: string) => void; onClose: NoneToVoidFunction; @@ -47,6 +48,7 @@ function DropdownMenu({ buttonClassName, iconClassName, fontIconClassName, + itemNameClassName, shouldCleanup, onSelect, onClose, @@ -105,7 +107,7 @@ function DropdownMenu({ aria-hidden /> )} - + {shouldTranslateOptions ? lang(item.name) : item.name} {item.description && ( diff --git a/src/components/ui/Modal.module.scss b/src/components/ui/Modal.module.scss index 29fab0a8..ef2aacb2 100644 --- a/src/components/ui/Modal.module.scss +++ b/src/components/ui/Modal.module.scss @@ -154,6 +154,13 @@ } } + // Fix transparent navigation bar in Capacitor app for Android. + :global(html.is-android-app) &:not(.forceBottomSheet) .dialog { + max-height: 100%; + padding-top: var(--safe-area-top); + padding-bottom: var(--safe-area-bottom); + } + :global(html.is-native-bottom-sheet) & { .container { align-items: flex-start; @@ -477,7 +484,9 @@ } @supports (padding-bottom: var(--safe-area-bottom)) { - padding-bottom: max(var(--safe-area-bottom), 1rem); + :global(html:not(.is-android-app)) & { + padding-bottom: max(var(--safe-area-bottom), 1rem); + } :global(.is-native-bottom-sheet) & { padding-bottom: max(var(--safe-area-bottom, 1rem), 1rem); diff --git a/src/global/actions/api/swap.ts b/src/global/actions/api/swap.ts index f6aefb29..5d7553e4 100644 --- a/src/global/actions/api/swap.ts +++ b/src/global/actions/api/swap.ts @@ -240,7 +240,6 @@ addActionHandler('submitSwap', async (global, actions, { password }) => { } global = getGlobal(); - const currentSwapId = global.currentSwap.swapId; if (IS_CAPACITOR) { global = setIsPinAccepted(global); } @@ -258,10 +257,10 @@ addActionHandler('submitSwap', async (global, actions, { password }) => { const buildResult = await callApi( 'swapBuildTransfer', global.currentAccountId!, password, swapBuildRequest, ); + global = getGlobal(); if (!buildResult || 'error' in buildResult) { actions.showError({ error: buildResult?.error }); - global = getGlobal(); if (IS_CAPACITOR) { global = clearIsPinAccepted(global); void vibrateOnError(); @@ -286,6 +285,20 @@ addActionHandler('submitSwap', async (global, actions, { password }) => { txIds: [], }; + global = updateCurrentSwap(global, { + tokenInSlug: undefined, + tokenOutSlug: undefined, + amountIn: undefined, + amountOut: undefined, + isLoading: false, + state: SwapState.Complete, + activityId: buildSwapId(buildResult.id), + }); + setGlobal(global); + if (IS_CAPACITOR) { + void vibrateOnSuccess(); + } + const result = await callApi( 'swapSubmit', global.currentAccountId!, @@ -295,38 +308,15 @@ addActionHandler('submitSwap', async (global, actions, { password }) => { swapBuildRequest.shouldTryDiesel, ); - global = getGlobal(); - if (!result || 'error' in result) { - global = updateCurrentSwap(global, { - isLoading: false, - }); if (IS_CAPACITOR) { + global = getGlobal(); global = clearIsPinAccepted(global); + setGlobal(global); void vibrateOnError(); } - setGlobal(global); - actions.showError({ error: result?.error }); - return; - } - if (currentSwapId !== global.currentSwap.swapId) { - setGlobal(global); - return; - } - - global = updateCurrentSwap(global, { - tokenInSlug: undefined, - tokenOutSlug: undefined, - amountIn: undefined, - amountOut: undefined, - isLoading: false, - state: SwapState.Complete, - activityId: buildSwapId(buildResult.id), - }); - setGlobal(global); - if (IS_CAPACITOR) { - void vibrateOnSuccess(); + actions.showError({ error: result?.error }); } }); @@ -399,44 +389,6 @@ addActionHandler('submitSwapCex', async (global, actions, { password }) => { return; } - let transferResult: ((ApiSubmitTransferResult | ApiSubmitTransferWithDieselResult) & { txId?: string }) | undefined; - - const transferOptions: ApiSubmitTransferOptions = { - password, - accountId: global.currentAccountId!, - fee: fromDecimal(swapItem.swap.networkFee, tokenIn.decimals), - amount: fromDecimal(swapItem.swap.fromAmount, tokenIn.decimals), - toAddress: swapItem.swap.cex!.payinAddress, - tokenAddress: isMutlichainAccount && shouldSendTransaction - ? tokenIn.tokenAddress - : undefined, - }; - - if (shouldSendTransaction) { - await pause(WAIT_FOR_CHANGELLY); - - if (shouldSendTonTransaction) { - transferResult = await callApi('submitTransfer', 'ton', transferOptions, false); - } else if (shouldSendTronTransaction) { - transferResult = await callApi('submitTransfer', 'tron', transferOptions, false); - } - - global = getGlobal(); - } - - if (shouldSendTransaction && (!transferResult || 'error' in transferResult)) { - global = updateCurrentSwap(global, { - isLoading: false, - }); - if (IS_CAPACITOR) { - global = clearIsPinAccepted(global); - void vibrateOnError(); - } - setGlobal(global); - actions.showError({ error: transferResult?.error }); - return; - } - global = updateCurrentSwap(global, { isLoading: false, state: shouldSendTokenToExternalWallet ? SwapState.Complete : SwapState.WaitTokens, @@ -449,6 +401,38 @@ addActionHandler('submitSwapCex', async (global, actions, { password }) => { if (IS_CAPACITOR) { void vibrateOnSuccess(); } + + if (shouldSendTransaction) { + global = getGlobal(); + const transferOptions: ApiSubmitTransferOptions = { + password, + accountId: global.currentAccountId!, + fee: fromDecimal(swapItem.swap.networkFee, tokenIn.decimals), + amount: fromDecimal(swapItem.swap.fromAmount, tokenIn.decimals), + toAddress: swapItem.swap.cex!.payinAddress, + tokenAddress: isMutlichainAccount ? tokenIn.tokenAddress : undefined, + }; + + await pause(WAIT_FOR_CHANGELLY); + + let transferResult: ((ApiSubmitTransferResult | ApiSubmitTransferWithDieselResult) & { txId?: string }) | undefined; + + if (shouldSendTonTransaction) { + transferResult = await callApi('submitTransfer', 'ton', transferOptions, false); + } else if (shouldSendTronTransaction) { + transferResult = await callApi('submitTransfer', 'tron', transferOptions, false); + } + + if (!transferResult || 'error' in transferResult) { + if (IS_CAPACITOR) { + global = getGlobal(); + global = clearIsPinAccepted(global); + void vibrateOnError(); + setGlobal(global); + } + actions.showError({ error: transferResult?.error }); + } + } }); addActionHandler('switchSwapTokens', (global) => { diff --git a/src/global/actions/api/wallet.ts b/src/global/actions/api/wallet.ts index 8dee7b10..db763020 100644 --- a/src/global/actions/api/wallet.ts +++ b/src/global/actions/api/wallet.ts @@ -140,7 +140,16 @@ addActionHandler('submitTransferInitial', async (global, actions, payload) => { } const { - tokenSlug, toAddress, amount, comment, shouldEncrypt, nftAddresses, withDiesel, stateInit, isGaslessWithStars, + tokenSlug, + toAddress, + amount, + comment, + shouldEncrypt, + nftAddresses, + withDiesel, + stateInit, + isGaslessWithStars, + binPayload, } = payload; setGlobal(updateSendingLoading(global, true)); @@ -161,9 +170,10 @@ addActionHandler('submitTransferInitial', async (global, actions, payload) => { tokenAddress, toAddress, amount, - data: comment, + data: binPayload ?? comment, shouldEncrypt, stateInit, + isBase64Data: Boolean(binPayload), isGaslessWithStars, }); } diff --git a/src/global/actions/ui/initial.ts b/src/global/actions/ui/initial.ts index a5a237d2..d23120a0 100644 --- a/src/global/actions/ui/initial.ts +++ b/src/global/actions/ui/initial.ts @@ -20,6 +20,7 @@ import switchAnimationLevel from '../../../util/switchAnimationLevel'; import switchTheme, { setStatusBarStyle } from '../../../util/switchTheme'; import { IS_ANDROID, + IS_ANDROID_APP, IS_DELEGATED_BOTTOM_SHEET, IS_ELECTRON, IS_IOS, @@ -50,6 +51,9 @@ addActionHandler('init', (_, actions) => { documentElement.classList.add('is-ios'); } else if (IS_ANDROID) { documentElement.classList.add('is-android'); + if (IS_ANDROID_APP) { + documentElement.classList.add('is-android-app'); + } } else if (IS_MAC_OS) { documentElement.classList.add('is-macos'); } else if (IS_WINDOWS) { diff --git a/src/global/actions/ui/misc.ts b/src/global/actions/ui/misc.ts index 3c89f08c..fa1b6609 100644 --- a/src/global/actions/ui/misc.ts +++ b/src/global/actions/ui/misc.ts @@ -566,7 +566,7 @@ addActionHandler('changeBaseCurrency', async (global, actions, { currency }) => await Promise.all([ callApi('tryUpdateTokens'), - callApi('tryLoadSwapTokens'), + callApi('tryUpdateSwapTokens'), ]); }); diff --git a/src/global/reducers/misc.ts b/src/global/reducers/misc.ts index 241a8854..19aca222 100644 --- a/src/global/reducers/misc.ts +++ b/src/global/reducers/misc.ts @@ -1,5 +1,5 @@ import type { - ApiBalanceBySlug, ApiChain, ApiSwapAsset, ApiTokenWithPrice, + ApiChain, ApiMaybeBalanceBySlug, ApiSwapAsset, ApiTokenWithPrice, } from '../../api/types'; import type { Account, AccountState, GlobalState } from '../types'; @@ -107,7 +107,7 @@ export function renameAccount(global: GlobalState, accountId: string, title: str export function updateBalances( global: GlobalState, accountId: string, - balancesToUpdate: ApiBalanceBySlug, + balancesToUpdate: ApiMaybeBalanceBySlug, ): GlobalState { if (Object.keys(balancesToUpdate).length === 0) { return global; @@ -118,6 +118,13 @@ export function updateBalances( const updatedBalancesBySlug = { ...(balances?.bySlug || {}) }; for (const [slug, balance] of Object.entries(balancesToUpdate)) { + if (balance === undefined) { + if (updatedBalancesBySlug[slug]) { + updatedBalancesBySlug[slug] = 0n; + } + continue; + } + updatedBalancesBySlug[slug] = balance; } diff --git a/src/global/types.ts b/src/global/types.ts index 9cbd4cd2..45f53fbf 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -734,6 +734,8 @@ export interface ActionPayloads { shouldEncrypt?: boolean; nftAddresses?: string[]; withDiesel?: boolean; + isBase64Data?: boolean; + binPayload?: string; isGaslessWithStars?: boolean; stateInit?: string; };