diff --git a/packages/@core-js/src/service/transactionService.ts b/packages/@core-js/src/service/transactionService.ts index 5ee4dbdc3..cb4ee7d1a 100644 --- a/packages/@core-js/src/service/transactionService.ts +++ b/packages/@core-js/src/service/transactionService.ts @@ -12,11 +12,13 @@ import { import { Address as AddressFormatter } from '../formatters/Address'; import { OpCodes, WalletContract } from './contractService'; import { SignRawMessage } from '@tonkeeper/mobile/src/core/ModalContainer/NFTOperations/TxRequest.types'; +import { tk } from '@tonkeeper/mobile/src/wallet'; export type AnyAddress = string | Address | AddressFormatter; export interface TransferParams { seqno: number; + timeout?: number; sendMode?: number; secretKey: Buffer; messages: MessageRelaxed[]; @@ -35,7 +37,7 @@ export function tonAddress(address: AnyAddress) { export class TransactionService { public static TTL = 5 * 60; - private static getTimeout() { + public static getTimeout() { return Math.floor(Date.now() / 1e3) + TransactionService.TTL; } @@ -163,7 +165,7 @@ export class TransactionService { static createTransfer(contract, transferParams: TransferParams) { const transfer = contract.createTransfer({ - timeout: TransactionService.getTimeout(), + timeout: transferParams.timeout ?? TransactionService.getTimeout(), seqno: transferParams.seqno, secretKey: transferParams.secretKey, sendMode: diff --git a/packages/@core-js/src/utils/tonProof.ts b/packages/@core-js/src/utils/tonProof.ts index 02869087d..43f2b0b1f 100644 --- a/packages/@core-js/src/utils/tonProof.ts +++ b/packages/@core-js/src/utils/tonProof.ts @@ -4,6 +4,7 @@ import nacl from 'tweetnacl'; import naclUtils from 'tweetnacl-util'; const { createHash } = require('react-native-crypto'); import { Address } from '../formatters/Address'; +import { getRawTimeFromLiteserverSafely } from '@tonkeeper/shared/utils/blockchain'; export interface TonProofArgs { address: string; @@ -22,7 +23,7 @@ export async function createTonProof({ }: TonProofArgs) { try { const address = Address.parse(_addr).toRaw(); - const timestamp = Math.floor(Date.now() / 1000); + const timestamp = await getRawTimeFromLiteserverSafely(); const timestampBuffer = new Int64LE(timestamp).toBuffer(); const domainBuffer = Buffer.from(domain); diff --git a/packages/mobile/android/app/build.gradle b/packages/mobile/android/app/build.gradle index 86deb18c9..0cc75381b 100644 --- a/packages/mobile/android/app/build.gradle +++ b/packages/mobile/android/app/build.gradle @@ -92,7 +92,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 433 - versionName "4.4.0" + versionName "4.4.1" missingDimensionStrategy 'react-native-camera', 'general' missingDimensionStrategy 'store', 'play' } diff --git a/packages/mobile/android/app/src/main/java/com/ton_keeper/MainApplication.java b/packages/mobile/android/app/src/main/java/com/ton_keeper/MainApplication.java index 6def30c19..dac4f818d 100644 --- a/packages/mobile/android/app/src/main/java/com/ton_keeper/MainApplication.java +++ b/packages/mobile/android/app/src/main/java/com/ton_keeper/MainApplication.java @@ -20,6 +20,9 @@ import java.util.List; +import java.lang.reflect.Field; +import android.database.CursorWindow; + public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = @@ -69,6 +72,18 @@ public void onCreate() { } ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); ApplicationLifecycleDispatcher.onApplicationCreate(this); + + // https://github.com/react-native-async-storage/async-storage/issues/537 + try { + Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize"); + field.setAccessible(true); + field.set(null, 300 * 1024 * 1024); + } catch (Exception e) { + if (BuildConfig.DEBUG) { + e.printStackTrace(); + } + } + } @Override diff --git a/packages/mobile/android/app/src/main/res/values/styles.xml b/packages/mobile/android/app/src/main/res/values/styles.xml index a023143f9..508711c27 100644 --- a/packages/mobile/android/app/src/main/res/values/styles.xml +++ b/packages/mobile/android/app/src/main/res/values/styles.xml @@ -23,4 +23,9 @@ @color/alert_text @color/alert_text + \ No newline at end of file diff --git a/packages/mobile/android/gradle.properties b/packages/mobile/android/gradle.properties index 70eb1b545..e9c5769d0 100644 --- a/packages/mobile/android/gradle.properties +++ b/packages/mobile/android/gradle.properties @@ -54,3 +54,4 @@ expo.webp.animated=false # Enable network inspector EX_DEV_CLIENT_NETWORK_INSPECTOR=true +AsyncStorage_db_size_in_MB=100 diff --git a/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj b/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj index 360b519f9..110e82934 100644 --- a/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj +++ b/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj @@ -1298,7 +1298,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.4.0; + MARKETING_VERSION = 4.4.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1332,7 +1332,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.4.0; + MARKETING_VERSION = 4.4.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 85b3016d8..00531a408 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -125,6 +125,7 @@ "react-native-sse": "^1.1.0", "react-native-svg": "^13.10.0", "react-native-svg-transformer": "^0.14.3", + "react-native-system-navigation-bar": "^2.6.4", "react-native-tweetnacl": "^1.0.5", "react-native-url-polyfill": "^2.0.0", "react-native-video": "^5.2.0", diff --git a/packages/mobile/src/blockchain/wallet.ts b/packages/mobile/src/blockchain/wallet.ts index 4c57219d9..0eb668ae2 100644 --- a/packages/mobile/src/blockchain/wallet.ts +++ b/packages/mobile/src/blockchain/wallet.ts @@ -30,6 +30,7 @@ import { NetworkOverloadedError, emulateBoc, sendBoc, + getTimeoutFromLiteserverSafely, } from '@tonkeeper/shared/utils/blockchain'; import { OperationEnum, TonAPI, TypeEnum } from '@tonkeeper/core/src/TonAPI'; import { setBalanceForEmulation } from '@tonkeeper/shared/utils/wallet'; @@ -45,6 +46,7 @@ const TonWeb = require('tonweb'); export const inscriptionTransferAmount = '0.05'; interface JettonTransferParams { + timeout?: number; seqno: number; jettonWalletAddress: string; recipient: Account; @@ -358,6 +360,7 @@ export class TonWallet { } createJettonTransfer({ + timeout, seqno, jettonWalletAddress, recipient, @@ -382,6 +385,7 @@ export class TonWallet { const jettonAmount = BigInt(amountNano); return TransactionService.createTransfer(contract, { + timeout, seqno, secretKey, messages: [ @@ -417,7 +421,10 @@ export class TonWallet { throw new Error(t('send_get_wallet_info_error')); } + const timeout = await getTimeoutFromLiteserverSafely(); + const boc = this.createJettonTransfer({ + timeout, seqno, jettonWalletAddress, recipient, @@ -484,7 +491,10 @@ export class TonWallet { ? tk.wallet.battery.excessesAccount : tk.wallet.address.ton.raw; + const timeout = await getTimeoutFromLiteserverSafely(); + const boc = this.createJettonTransfer({ + timeout, seqno, jettonWalletAddress, recipient, @@ -534,9 +544,6 @@ export class TonWallet { if (e instanceof NetworkOverloadedError) { throw e; } - if (!store.getState().main.isTimeSynced) { - throw new Error('wrong_time'); - } throw new Error(t('send_publish_tx_error')); } @@ -568,7 +575,11 @@ export class TonWallet { allowedDestinations: lockupConfig?.allowed_destinations, }, ); + + const timeout = await getTimeoutFromLiteserverSafely(); + return TransactionService.createTransfer(contract, { + timeout, seqno, secretKey, sendMode, @@ -747,9 +758,6 @@ export class TonWallet { if (e instanceof NetworkOverloadedError) { throw e; } - if (!store.getState().main.isTimeSynced) { - throw new Error('wrong_time'); - } throw new Error(t('send_publish_tx_error')); } diff --git a/packages/mobile/src/core/DAppBrowser/DAppBrowser.tsx b/packages/mobile/src/core/DAppBrowser/DAppBrowser.tsx index 7fae81bef..c23197bc9 100644 --- a/packages/mobile/src/core/DAppBrowser/DAppBrowser.tsx +++ b/packages/mobile/src/core/DAppBrowser/DAppBrowser.tsx @@ -1,6 +1,6 @@ import { useDeeplinking } from '$libs/deeplinking'; import { openDAppsSearch } from '$navigation'; -import { getCorrectUrl, getSearchQuery, getUrlWithoutTonProxy } from '$utils'; +import { getCorrectUrl, getSearchQuery, getUrlWithoutTonProxy, isIOS } from '$utils'; import React, { FC, memo, useCallback, useState } from 'react'; import { Linking, StatusBar, useWindowDimensions } from 'react-native'; import { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; @@ -16,7 +16,7 @@ import { useDAppBridge } from './hooks/useDAppBridge'; import { useWallet } from '@tonkeeper/shared/hooks'; import { Address } from '@tonkeeper/shared/Address'; import { config } from '$config'; -import { Screen, isIOS } from '@tonkeeper/uikit'; +import { Screen, isAndroid, useTheme } from '@tonkeeper/uikit'; export interface DAppBrowserProps { url: string; @@ -144,9 +144,11 @@ const DAppBrowserComponent: FC = (props) => { openDAppsSearch(initialQuery, openUrl); }, [currentUrl, initialUrl, openUrl]); + const theme = useTheme(); + return ( - {isIOS ? : null} + = ({ route }) => { const isWatchOnly = wallet && wallet.isWatchOnly; const fiatCurrency = useWalletCurrency(); - const shouldShowChart = jettonPrice.fiat !== 0; const shouldExcludeChartPeriods = config.get('exclude_jetton_chart_periods'); const nav = useNavigation(); - const showSwap = useSwapStore((s) => !!s.assets[jetton.jettonAddress], shallow); + const shouldShowChart = jettonPrice.fiat !== 0; + const showSwap = jettonPrice.fiat !== 0; const handleSend = useCallback(() => { trackEvent(Events.SendOpen, { from: SendAnalyticsFrom.TokenScreen }); diff --git a/packages/mobile/src/core/ModalContainer/NFTOperations/Modals/SignRawModal.tsx b/packages/mobile/src/core/ModalContainer/NFTOperations/Modals/SignRawModal.tsx index 6552d6b32..1db31d942 100644 --- a/packages/mobile/src/core/ModalContainer/NFTOperations/Modals/SignRawModal.tsx +++ b/packages/mobile/src/core/ModalContainer/NFTOperations/Modals/SignRawModal.tsx @@ -1,6 +1,6 @@ import React, { memo, useEffect, useMemo } from 'react'; import { NFTOperationFooter, useNFTOperationState } from '../NFTOperationFooter'; -import { SignRawParams, TxBodyOptions } from '../TXRequest.types'; +import { SignRawMessage, SignRawParams, TxBodyOptions } from '../TXRequest.types'; import { useUnlockVault } from '../useUnlockVault'; import { calculateMessageTransferAmount, delay } from '$utils'; import { debugLog } from '$utils/debugLog'; @@ -54,7 +54,11 @@ import { JettonTransferAction, NftItemTransferAction } from 'tonapi-sdk-js'; import { TokenDetailsParams } from '../../../../components/TokenDetails/TokenDetails'; import { ModalStackRouteNames } from '$navigation'; import { CanceledActionError } from '$core/Send/steps/ConfirmStep/ActionErrors'; -import { emulateBoc, sendBoc } from '@tonkeeper/shared/utils/blockchain'; +import { + emulateBoc, + getTimeoutFromLiteserverSafely, + sendBoc, +} from '@tonkeeper/shared/utils/blockchain'; import { openAboutRiskAmountModal } from '@tonkeeper/shared/modals/AboutRiskAmountModal'; import { toNano } from '@ton/core'; import BigNumber from 'bignumber.js'; @@ -115,7 +119,10 @@ export const SignRawModal = memo((props) => { vault.workchain, ); + const timeout = await getTimeoutFromLiteserverSafely(); + const boc = TransactionService.createTransfer(contract, { + timeout, messages: TransactionService.parseSignRawMessages( params.messages, isBattery ? tk.wallet.battery.excessesAccount : undefined, @@ -352,6 +359,10 @@ export const SignRawModal = memo((props) => { ); }); +function isValidMessage(message: SignRawMessage): boolean { + return Address.isValid(message.address) && new BigNumber(message.amount).gt('0'); +} + export const openSignRawModal = async ( params: SignRawParams, options: TxBodyOptions, @@ -370,6 +381,10 @@ export const openSignRawModal = async ( try { Toast.loading(); + if (!params.messages.every((mes) => isValidMessage(mes))) { + throw new Error('Invalid message'); + } + if (isTonConnect) { await TonConnectRemoteBridge.closeOtherTransactions(); } @@ -383,7 +398,9 @@ export const openSignRawModal = async ( let consequences: MessageConsequences | null = null; let isBattery = false; try { + const timeout = await getTimeoutFromLiteserverSafely(); const boc = TransactionService.createTransfer(contract, { + timeout, messages: TransactionService.parseSignRawMessages(params.messages), seqno: await getWalletSeqno(wallet), secretKey: Buffer.alloc(64), diff --git a/packages/mobile/src/core/ModalContainer/TimeNotSynced/TimeNotSynced.tsx b/packages/mobile/src/core/ModalContainer/TimeNotSynced/TimeNotSynced.tsx deleted file mode 100644 index 9d97d9978..000000000 --- a/packages/mobile/src/core/ModalContainer/TimeNotSynced/TimeNotSynced.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { memo, useCallback, useEffect } from 'react'; -import { t } from '@tonkeeper/shared/i18n'; -import { Modal } from '@tonkeeper/uikit'; -import { push } from '$navigation/imperative'; -import { SheetActions, useNavigation } from '@tonkeeper/router'; -import { MainDB } from '$database'; -import { mainActions } from '$store/main'; -import { useDispatch } from 'react-redux'; -import { Button, Icon, Text } from '$uikit'; -import * as S from './TimeNotSynced.style'; -import { Linking, Platform } from 'react-native'; -import { Base64, delay } from '$utils'; - -export const TimeNotSyncedModal = memo(() => { - const dispatch = useDispatch(); - const nav = useNavigation(); - - useEffect(() => { - MainDB.setTimeSyncedDismissed(false); - dispatch(mainActions.setTimeSyncedDismissed(false)); - }, []); - - const handleOpenSettings = useCallback(async () => { - nav.goBack(); - await delay(400); - if (Platform.OS === 'ios') { - return Linking.openURL(Base64.decodeToStr('QXBwLXByZWZzOnJvb3Q=')); - } - Linking.sendIntent('android.settings.DATE_SETTINGS'); - }, []); - - return ( - - - - - - - {t('txActions.signRaw.wrongTime.title')} - - - {t('txActions.signRaw.wrongTime.description')} - - - - - - - - - - ); -}); - -export const openTimeNotSyncedModal = async () => { - push('SheetsProvider', { - $$action: SheetActions.ADD, - component: TimeNotSyncedModal, - path: 'TimeNotSynced', - }); - - return true; -}; diff --git a/packages/mobile/src/core/NFTSend/NFTSend.tsx b/packages/mobile/src/core/NFTSend/NFTSend.tsx index ba62d509b..af60b187d 100644 --- a/packages/mobile/src/core/NFTSend/NFTSend.tsx +++ b/packages/mobile/src/core/NFTSend/NFTSend.tsx @@ -35,7 +35,11 @@ import { delay } from '$utils'; import { Toast } from '$store'; import axios from 'axios'; import { useUnlockVault } from '$core/ModalContainer/NFTOperations/useUnlockVault'; -import { emulateBoc, sendBoc } from '@tonkeeper/shared/utils/blockchain'; +import { + emulateBoc, + getTimeoutFromLiteserverSafely, + sendBoc, +} from '@tonkeeper/shared/utils/blockchain'; import { checkIsInsufficient, openInsufficientFundsModal, @@ -150,7 +154,9 @@ export const NFTSend: FC = (props) => { wallet.config.workchain, ); + const timeout = await getTimeoutFromLiteserverSafely(); const boc = TransactionService.createTransfer(contract, { + timeout, messages: nftTransferMessages, seqno: await getWalletSeqno(), secretKey: Buffer.alloc(64), @@ -312,7 +318,9 @@ export const NFTSend: FC = (props) => { vault.workchain, ); + const timeout = await getTimeoutFromLiteserverSafely(); const boc = TransactionService.createTransfer(contract, { + timeout, messages: nftTransferMessages, seqno: await getWalletSeqno(), sendMode: 3, diff --git a/packages/mobile/src/core/ScanQR/ScanQR.tsx b/packages/mobile/src/core/ScanQR/ScanQR.tsx index 4b9bc9cd4..fc237042c 100644 --- a/packages/mobile/src/core/ScanQR/ScanQR.tsx +++ b/packages/mobile/src/core/ScanQR/ScanQR.tsx @@ -22,9 +22,11 @@ import { triggerSelection, } from '$utils'; import { BottomButtonWrap, BottomButtonWrapHelper } from '$shared/components'; -import { useTheme } from '$hooks/useTheme'; import { useNavigation } from '@tonkeeper/router'; import { t } from '@tonkeeper/shared/i18n'; +import SystemNavigationBar from 'react-native-system-navigation-bar'; +import { DarkTheme } from '@tonkeeper/uikit/src/styles/themes/dark'; +import { useTheme } from '@tonkeeper/uikit'; export const ScanQR: FC = ({ route }) => { const nav = useNavigation(); @@ -37,6 +39,22 @@ export const ScanQR: FC = ({ route }) => { const [isCameraBlocked, setCameraBlocked] = useState(false); const [isHasPermission, setHasPermissions] = useState(false); + useEffect(() => { + SystemNavigationBar.setNavigationColor( + DarkTheme.backgroundPageAlternate, + 'light', + 'navigation', + ); + + return () => { + SystemNavigationBar.setNavigationColor( + theme.backgroundPageAlternate, + theme.isDark ? 'light' : 'dark', + 'navigation', + ); + }; + }, [theme.backgroundPageAlternate, theme.isDark]); + useEffect(() => { const permissionName = Platform.select({ android: PERMISSIONS.ANDROID.CAMERA, diff --git a/packages/mobile/src/core/Swap/Swap.style.ts b/packages/mobile/src/core/Swap/Swap.style.ts deleted file mode 100644 index 47c7e52d8..000000000 --- a/packages/mobile/src/core/Swap/Swap.style.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Opacity } from '$shared/constants'; -import styled from '$styled'; -import { hNs, ns } from '$utils'; -import WebView from 'react-native-webview'; - -export const Container = styled.View` - flex: 1; - position: relative; - background: ${({ theme }) => theme.colors.backgroundPrimary}; -`; - -export const Browser = styled(WebView)` - flex: 1; - background: ${({ theme }) => theme.colors.backgroundPrimary}; -`; - -export const Overlay = styled.View` - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: ${({ theme }) => theme.colors.backgroundPrimary}; - padding: 19.5px 16px; - align-items: flex-end; -`; - -export const BackButtonContainer = styled.TouchableOpacity.attrs({ - activeOpacity: Opacity.ForSmall, -})``; - -export const BackButton = styled.View` - background: ${({ theme }) => theme.colors.buttonSecondaryBackground}; - height: ${hNs(32)}px; - width: ${ns(32)}px; - border-radius: ${ns(32 / 2)}px; - align-items: center; - justify-content: center; -`; diff --git a/packages/mobile/src/core/Swap/Swap.tsx b/packages/mobile/src/core/Swap/Swap.tsx index 4d03b21cf..6d0f338c9 100644 --- a/packages/mobile/src/core/Swap/Swap.tsx +++ b/packages/mobile/src/core/Swap/Swap.tsx @@ -3,20 +3,20 @@ import { StonfiInjectedObject } from './types'; import { openSignRawModal } from '$core/ModalContainer/NFTOperations/Modals/SignRawModal'; import { getTimeSec } from '$utils/getTimeSec'; import { useNavigation } from '@tonkeeper/router'; -import * as S from './Swap.style'; -import { Icon } from '$uikit'; -import { getCorrectUrl, getDomainFromURL } from '$utils'; +import { getCorrectUrl, getDomainFromURL, isAndroid } from '$utils'; import { logEvent } from '@amplitude/analytics-browser'; -import { checkIsTimeSynced } from '$navigation/hooks/useDeeplinkingResolvers'; import { useWebViewBridge } from '$hooks/jsBridge'; import { useWallet } from '@tonkeeper/shared/hooks'; import { config } from '$config'; import { tk } from '$wallet'; import { ShouldStartLoadRequest } from 'react-native-webview/lib/WebViewTypes'; -import { Linking } from 'react-native'; +import { Linking, StatusBar } from 'react-native'; import { useDeeplinking } from '$libs/deeplinking'; import DeviceInfo from 'react-native-device-info'; import { BatterySupportedTransaction } from '$wallet/managers/BatteryManager'; +import { Icon, Modal, Steezy, TouchableOpacity, View } from '@tonkeeper/uikit'; +import { Opacity } from '$shared/constants'; +import WebView from 'react-native-webview'; interface Props { jettonAddress?: string; @@ -69,12 +69,6 @@ export const Swap: FC = (props) => { new Promise((resolve, reject) => { const { valid_until } = request; - if (!checkIsTimeSynced()) { - reject(); - - return; - } - if (valid_until < getTimeSec()) { reject(); @@ -147,37 +141,74 @@ export const Swap: FC = (props) => { [deeplinking, openUrl], ); + const webViewStyle = Steezy.useStyle(styles.webView); + return ( - - - {overlayVisible ? ( - - - - - - - - ) : null} - + + + + + {overlayVisible ? ( + + + + + + + + ) : null} + + ); }; + +const styles = Steezy.create(({ colors, safeArea }) => ({ + container: { + flex: 1, + paddingTop: isAndroid ? safeArea.top : 0, + backgroundColor: colors.backgroundPage, + }, + webView: { + flex: 1, + backgroundColor: colors.backgroundPage, + }, + overlay: { + position: 'absolute', + top: isAndroid ? safeArea.top : 0, + left: 0, + right: 0, + bottom: 0, + paddingVertical: 19.5, + paddingHorizontal: 16, + alignItems: 'flex-end', + backgroundColor: colors.backgroundPage, + }, + backButton: { + backgroundColor: colors.buttonSecondaryBackground, + height: 32, + width: 32, + borderRadius: 32 / 2, + alignItems: 'center', + justifyContent: 'center', + }, +})); diff --git a/packages/mobile/src/core/TonConnect/TonConnectModal.tsx b/packages/mobile/src/core/TonConnect/TonConnectModal.tsx index 4b1b3d098..e3d371c29 100644 --- a/packages/mobile/src/core/TonConnect/TonConnectModal.tsx +++ b/packages/mobile/src/core/TonConnect/TonConnectModal.tsx @@ -174,7 +174,7 @@ export const TonConnectModal = (props: TonConnectModalProps) => { const { replyBuilder, requestPromise } = props; - const replyItems = replyBuilder.createReplyItems( + const replyItems = await replyBuilder.createReplyItems( address, privateKey, publicKey, diff --git a/packages/mobile/src/database/main.ts b/packages/mobile/src/database/main.ts index cef1554a7..2a34eb4b3 100644 --- a/packages/mobile/src/database/main.ts +++ b/packages/mobile/src/database/main.ts @@ -2,25 +2,6 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import { LogItem } from '$store/main/interface'; -export class MainDB { - static async timeSyncedDismissedTimestamp(): Promise { - const timeSyncedDismissed = await AsyncStorage.getItem('timeSyncedDismissed'); - return ( - !!timeSyncedDismissed && - timeSyncedDismissed !== 'false' && - parseFloat(timeSyncedDismissed) - ); - } - - static async setTimeSyncedDismissed(isDismissed: false | number) { - if (isDismissed) { - await AsyncStorage.setItem('timeSyncedDismissed', isDismissed.toString()); - } else { - await AsyncStorage.setItem('timeSyncedDismissed', 'false'); - } - } -} - export async function getHiddenNotifications(): Promise { const raw = await AsyncStorage.getItem('mainnet_default_hidden_internal_notifications'); diff --git a/packages/mobile/src/navigation/AppNavigator.tsx b/packages/mobile/src/navigation/AppNavigator.tsx index e75fa7b00..9af6fec11 100644 --- a/packages/mobile/src/navigation/AppNavigator.tsx +++ b/packages/mobile/src/navigation/AppNavigator.tsx @@ -7,7 +7,7 @@ import { setNavigationRef, onNavigationReady } from './imperative'; import { AppStack } from './MainStack'; import { mainSelector } from '$store/main'; import { ProvidersWithoutNavigation } from './Providers'; -import { isAndroid, useTheme } from '@tonkeeper/uikit'; +import { useTheme } from '@tonkeeper/uikit'; export const AppNavigator: FC = () => { const theme = useTheme(); @@ -40,8 +40,9 @@ export const AppNavigator: FC = () => { > diff --git a/packages/mobile/src/navigation/MainStack/MainStack.tsx b/packages/mobile/src/navigation/MainStack/MainStack.tsx index d09f04240..291e471b3 100644 --- a/packages/mobile/src/navigation/MainStack/MainStack.tsx +++ b/packages/mobile/src/navigation/MainStack/MainStack.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { FC, useEffect } from 'react'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { MainStackParamList } from './MainStack.interface'; @@ -42,6 +42,7 @@ import { tk } from '$wallet'; import { MigrationStack } from '$navigation/MigrationStack'; import { useTonPriceUpdater } from '$hooks/useTonPriceUpdater'; import { SettingsStack } from '$navigation/SettingsStack/SettingsStack'; +import SystemNavigationBar from 'react-native-system-navigation-bar'; const Stack = createNativeStackNavigator(); @@ -74,6 +75,14 @@ export const MainStack: FC = () => { const isMigrated = useExternalState(tk.migrationStore, (state) => state.isMigrated); + useEffect(() => { + SystemNavigationBar.setNavigationColor( + theme.colors.backgroundPageAlternate, + theme.isDark ? 'light' : 'dark', + 'navigation', + ); + }, [theme.colors.backgroundPageAlternate, theme.isDark]); + const renderRoot = () => { if (hasWallet) { if (showLockScreen) { diff --git a/packages/mobile/src/navigation/MainStack/TabStack/TabStack.tsx b/packages/mobile/src/navigation/MainStack/TabStack/TabStack.tsx index 3f1bcfd49..5520f2e17 100644 --- a/packages/mobile/src/navigation/MainStack/TabStack/TabStack.tsx +++ b/packages/mobile/src/navigation/MainStack/TabStack/TabStack.tsx @@ -80,7 +80,7 @@ export const TabStack: FC = () => { ) : ( diff --git a/packages/mobile/src/navigation/ModalStack.tsx b/packages/mobile/src/navigation/ModalStack.tsx index 25f0917f3..c6ae51d20 100644 --- a/packages/mobile/src/navigation/ModalStack.tsx +++ b/packages/mobile/src/navigation/ModalStack.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { memo, useEffect } from 'react'; import { createModalStackNavigator } from '@tonkeeper/router'; import { NFT } from '$core/NFT/NFT'; import { SignRawModal } from '$core/ModalContainer/NFTOperations/Modals/SignRawModal'; @@ -33,9 +33,38 @@ import { ReceiveInscriptionModal } from '@tonkeeper/shared/modals/ReceiveInscrip import { CustomizeWallet } from '$core/CustomizeWallet/CustomizeWallet'; import { TokenDetails } from '../components/TokenDetails/TokenDetails'; import { BackupWarningModal, ExchangeModal, LogoutWarningModal } from '$modals'; +import { ThemeProvider, useTheme } from '@tonkeeper/uikit'; +import { BlueTheme } from '@tonkeeper/uikit/src/styles/themes/blue'; +import SystemNavigationBar from 'react-native-system-navigation-bar'; const Stack = createModalStackNavigator(ProvidersWithNavigation); +const SwapWithTheme = memo(() => { + const theme = useTheme(); + + useEffect(() => { + SystemNavigationBar.setNavigationColor( + BlueTheme.backgroundPageAlternate, + 'light', + 'navigation', + ); + + return () => { + SystemNavigationBar.setNavigationColor( + theme.backgroundPageAlternate, + theme.isDark ? 'light' : 'dark', + 'navigation', + ); + }; + }, [theme.backgroundPageAlternate, theme.isDark]); + + return ( + + + + ); +}); + export const ModalStack = React.memo(() => ( @@ -71,7 +100,7 @@ export const ModalStack = React.memo(() => ( - + { return store.getState().wallet.wallet; }; -const getIsTimeSynced = () => { - return store.getState().main.isTimeSynced; -}; - const getExpiresSec = () => { return getTimeSec() + 10 * 60; }; -export function checkIsTimeSynced() { - if (!getIsTimeSynced()) { - openTimeNotSyncedModal(); - return false; - } - return true; -} - export function useDeeplinkingResolvers() { const deeplinking = useDeeplinking(); const dispatch = useDispatch(); @@ -529,10 +516,6 @@ export function useDeeplinkingResolvers() { const txBody = txRequest.body as any; const isSignRaw = isSignRawParams(txBody?.params); - if (!checkIsTimeSynced()) { - return Toast.hide(); - } - if ( txBody.expires_sec < getTimeSec() || (isSignRaw && txBody.params.valid_until < getTimeSec()) diff --git a/packages/mobile/src/store/main/index.ts b/packages/mobile/src/store/main/index.ts index 069ee45fb..5f0e85cc6 100644 --- a/packages/mobile/src/store/main/index.ts +++ b/packages/mobile/src/store/main/index.ts @@ -8,8 +8,6 @@ import { SetAccentAction, SetLogsAction, SetNotificationsAction, - SetTimeSyncedAction, - SetTimeSyncedDismissedAction, SetTonCustomIcon, SetUnlockedAction, UpdateBadHostsAction, @@ -19,8 +17,6 @@ import { walletWalletSelector } from '$store/wallet'; const initialState: MainState = { isInitiating: true, - isTimeSynced: true, - timeSyncedDismissedTimestamp: false, badHosts: [], isBadHostsDismissed: false, internalNotifications: [], @@ -45,11 +41,6 @@ export const { actions, reducer } = createSlice({ state.isUnlocked = action.payload; }, - getTimeSynced() {}, - setTimeSynced(state, action: SetTimeSyncedAction) { - state.isTimeSynced = action.payload; - }, - updateBadHosts(state, action: UpdateBadHostsAction) { if (JSON.stringify(state.badHosts) !== JSON.stringify(action.payload)) { state.isBadHostsDismissed = false; @@ -61,10 +52,6 @@ export const { actions, reducer } = createSlice({ state.isBadHostsDismissed = true; }, - setTimeSyncedDismissed(state, action: SetTimeSyncedDismissedAction) { - state.timeSyncedDismissedTimestamp = action.payload; - }, - loadNotifications() {}, setNotifications(state, action: SetNotificationsAction) { @@ -137,8 +124,3 @@ export const accentTonIconSelector = createSelector( customIconSelector, (wallet, tonCustomIcon) => (wallet ? tonCustomIcon : null), ); - -export const isTimeSyncedSelector = createSelector( - mainSelector, - (state) => state.isTimeSynced, -); diff --git a/packages/mobile/src/store/main/interface.ts b/packages/mobile/src/store/main/interface.ts index a20656d74..f75c5a435 100644 --- a/packages/mobile/src/store/main/interface.ts +++ b/packages/mobile/src/store/main/interface.ts @@ -11,8 +11,6 @@ export interface LogItem { export interface MainState { isInitiating: boolean; - isTimeSynced: boolean; - timeSyncedDismissedTimestamp: false | number; badHosts: string[]; isBadHostsDismissed: boolean; internalNotifications: InternalNotificationModel[]; @@ -23,8 +21,6 @@ export interface MainState { tonCustomIcon: AccentNFTIcon | null; } -export type SetTimeSyncedAction = PayloadAction; -export type SetTimeSyncedDismissedAction = PayloadAction; export type UpdateBadHostsAction = PayloadAction; export type SetNotificationsAction = PayloadAction; export type HideNotificationAction = PayloadAction; diff --git a/packages/mobile/src/store/main/sagas.ts b/packages/mobile/src/store/main/sagas.ts index 4c27c14c9..1e5ca05ae 100644 --- a/packages/mobile/src/store/main/sagas.ts +++ b/packages/mobile/src/store/main/sagas.ts @@ -11,7 +11,6 @@ import { getHiddenNotifications, getSavedLogs, hideNotification, - MainDB, setSavedLogs, } from '$database'; import { HideNotificationAction } from '$store/main/interface'; @@ -20,7 +19,6 @@ import { InternalNotificationModel } from '$store/models'; import { initStats, trackEvent, trackFirstLaunch } from '$utils/stats'; import { favoritesActions } from '$store/favorites'; -import { useSwapStore } from '$store/zustand/swap'; import { tk } from '$wallet'; import { config } from '$config'; @@ -35,8 +33,6 @@ function* initWorker() { } export function* initHandler() { - const timeSyncedDismissed = yield call(MainDB.timeSyncedDismissedTimestamp); - initStats(); trackFirstLaunch(); @@ -44,24 +40,14 @@ export function* initHandler() { yield call([tk, 'init']); - yield put( - batchActions( - mainActions.endInitiating(), - mainActions.setTimeSyncedDismissed(timeSyncedDismissed), - ), - ); + yield put(batchActions(mainActions.endInitiating())); const logs = yield call(getSavedLogs); yield put(mainActions.setLogs(logs)); - if (tk.wallet) { - useSwapStore.getState().actions.fetchAssets(); - } - yield put(mainActions.loadNotifications()); yield put(favoritesActions.loadSuggests()); - yield put(mainActions.getTimeSynced()); SplashScreen.hideAsync(); } @@ -99,27 +85,6 @@ function* hideNotificationWorker(action: HideNotificationAction) { } catch (e) {} } -function* getTimeSyncedWorker() { - try { - const endpoint = `${config.get('tonapiV2Endpoint')}/v2/liteserver/get_time`; - - const response = yield call(axios.get, endpoint, { - headers: { Authorization: `Bearer ${config.get('tonApiV2Key')}` }, - }); - const time = response?.data?.time; - const isSynced = Math.abs(Date.now() - time * 1000) <= 7000; - - if (isSynced) { - yield call(MainDB.setTimeSyncedDismissed, false); - yield put(mainActions.setTimeSyncedDismissed(false)); - } - - yield put(mainActions.setTimeSynced(isSynced)); - } catch (e) { - console.log(e); - } -} - function* addLogWorker() { try { const { logs } = yield select(mainSelector); @@ -130,7 +95,6 @@ function* addLogWorker() { export function* mainSaga() { yield all([ takeLatest(mainActions.init, initWorker), - takeLatest(mainActions.getTimeSynced, getTimeSyncedWorker), takeLatest(mainActions.loadNotifications, loadNotificationsWorker), takeLatest(mainActions.hideNotification, hideNotificationWorker), takeLatest(mainActions.addLog, addLogWorker), diff --git a/packages/mobile/src/store/wallet/sagas.ts b/packages/mobile/src/store/wallet/sagas.ts index eb0ae288b..e55951e47 100644 --- a/packages/mobile/src/store/wallet/sagas.ts +++ b/packages/mobile/src/store/wallet/sagas.ts @@ -22,7 +22,6 @@ import { WalletGetUnlockedVaultAction, } from '$store/wallet/interface'; -import { MainDB } from '$database'; import { Toast, useAddressUpdateStore, useConnectedAppsStore } from '$store'; import { t } from '@tonkeeper/shared/i18n'; import { getChainName } from '$shared/dynamicConfig'; @@ -351,8 +350,6 @@ function* sendCoinsWorker(action: SendCoinsAction) { e && debugLog(e.message); if (e && e.message === 'wrong_time') { - MainDB.setTimeSyncedDismissed(false); - yield put(mainActions.setTimeSyncedDismissed(false)); Alert.alert( t('send_sending_wrong_time_title'), t('send_sending_wrong_time_description'), diff --git a/packages/mobile/src/store/zustand/swap/index.ts b/packages/mobile/src/store/zustand/swap/index.ts deleted file mode 100644 index 66e4bd997..000000000 --- a/packages/mobile/src/store/zustand/swap/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './types'; -export * from './useSwapStore'; diff --git a/packages/mobile/src/store/zustand/swap/types.ts b/packages/mobile/src/store/zustand/swap/types.ts deleted file mode 100644 index ccd576489..000000000 --- a/packages/mobile/src/store/zustand/swap/types.ts +++ /dev/null @@ -1,46 +0,0 @@ -export interface ISwapAsset { - address: string; - symbol: string; -} - -export type SwapAssets = { - [key: string]: ISwapAsset; -}; - -export interface ISwapStore { - assets: SwapAssets; - actions: { - fetchAssets: () => Promise; - }; -} - -export interface StonFiItem { - address: string; //"EQCSqjXUUfo7txZVeIpiB5ObyJ_dBOOdtXQNBIwvjMefNpF0" - apy_1d: string; //"0.010509024542116885" - apy_7d: string; // "1.090410672685333" - apy_30d: string; // "1.090410672685333" - collected_token0_protocol_fee: string; //"309131" - collected_token1_protocol_fee: string; // "111845809" - deprecated: boolean; //false - lp_fee: string; //"20" - lp_total_supply: string; //"209838035" - protocol_fee: string; // "10" - protocol_fee_address: string; // "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c" - ref_fee: string; // "10" - reserve0: string; // "9998902465" - reserve1: string; // "4489590433195" - router_address: string; // "EQB3ncyBUTjZUA5EnFKR5_EnOMI9V1tTEAAPaiU71gc4TiUt" - token0_address: string; // "EQB-MPwrd1G6WKNkLz_VnV6WqBDd142KMQv-g1O-8QUA3728" - token1_address: string; // "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c" -} - -export interface StonFiAsset { - contract_address: string; //"EQCcLAW537KnRg_aSPrnQJoyYjOZkzqYp6FVmRUvN1crSazV" - decimals: number; //9 - default_symbol: boolean; //false - deprecated: boolean; //false - display_name: string; //"Ambra" - image_url: string; //"https://asset.ston.fi/img/EQCcLAW537KnRg_aSPrnQJoyYjOZkzqYp6FVmRUvN1crSazV" - kind: string; //"JETTON" - symbol: string; //"AMBR" -} diff --git a/packages/mobile/src/store/zustand/swap/useSwapStore.ts b/packages/mobile/src/store/zustand/swap/useSwapStore.ts deleted file mode 100644 index baa56267a..000000000 --- a/packages/mobile/src/store/zustand/swap/useSwapStore.ts +++ /dev/null @@ -1,82 +0,0 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { create } from 'zustand'; -import { createJSONStorage, persist } from 'zustand/middleware'; -import { ISwapStore, StonFiAsset, StonFiItem, SwapAssets } from './types'; - -const StonFiTon = 'EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c'; - -const initialState: Omit = { - assets: {}, -}; - -export const useSwapStore = create( - persist( - (set) => ({ - ...initialState, - actions: { - fetchAssets: async () => { - try { - const assets = await fetch('https://app.ston.fi/rpc', { - method: 'POST', - body: JSON.stringify({ - jsonrpc: '2.0', - id: Date.now(), - method: 'asset.list', - }), - headers: { 'content-type': 'application/json' }, - }); - - const result = await fetch('https://app.ston.fi/rpc', { - method: 'POST', - body: JSON.stringify({ - jsonrpc: '2.0', - id: Date.now(), - method: 'pool.list', - }), - headers: { 'content-type': 'application/json' }, - }); - - const data: StonFiItem[] = (await result.json()).result.pools; - const assetList: StonFiAsset[] = (await assets.json()).result.assets; - - const items = data.reduce((acc, item) => { - if (!acc[item.token0_address] && item.token0_address !== StonFiTon) { - const asset = assetList.find( - (a) => a.contract_address === item.token0_address, - ); - - if (asset) { - acc[item.token0_address] = { - address: item.token0_address, - symbol: asset.symbol, - }; - } - } - if (!acc[item.token1_address] && item.token1_address !== StonFiTon) { - const asset = assetList.find( - (a) => a.contract_address === item.token1_address, - ); - - if (asset) { - acc[item.token1_address] = { - address: item.token1_address, - symbol: asset.symbol, - }; - } - } - - return acc; - }, {} as SwapAssets); - - set({ assets: items }); - } catch {} - }, - }, - }), - { - name: 'swap', - storage: createJSONStorage(() => AsyncStorage), - partialize: ({ assets }) => ({ assets } as ISwapStore), - }, - ), -); diff --git a/packages/mobile/src/tabs/Wallet/WalletScreen.tsx b/packages/mobile/src/tabs/Wallet/WalletScreen.tsx index 38e128140..01c51b5d3 100644 --- a/packages/mobile/src/tabs/Wallet/WalletScreen.tsx +++ b/packages/mobile/src/tabs/Wallet/WalletScreen.tsx @@ -50,6 +50,7 @@ export const WalletScreen = memo(({ navigation }) => { useEffect(() => { const timer = setTimeout(() => { dispatch(mainActions.mainStackInited()); + dispatch(mainActions.setUnlocked(true)); }, 500); return () => clearTimeout(timer); }, [dispatch]); diff --git a/packages/mobile/src/tabs/Wallet/components/WalletContentList.tsx b/packages/mobile/src/tabs/Wallet/components/WalletContentList.tsx index bed257603..cce75bff8 100644 --- a/packages/mobile/src/tabs/Wallet/components/WalletContentList.tsx +++ b/packages/mobile/src/tabs/Wallet/components/WalletContentList.tsx @@ -1,10 +1,8 @@ import React, { memo } from 'react'; -import { Screen, View, List, ListSeparator } from '@tonkeeper/uikit'; +import { Screen, View, List, ListSeparator, RefreshControl } from '@tonkeeper/uikit'; import { Steezy } from '$styles'; -import { RefreshControl } from 'react-native'; import { ListItemRate } from './ListItemRate'; import { TonIcon } from '@tonkeeper/uikit'; -import { useTheme } from '$hooks/useTheme'; import { HideableAmount } from '$core/HideableAmount/HideableAmount'; import { Text } from '@tonkeeper/uikit'; import { CellItemToRender } from '../content-providers/utils/types'; @@ -110,8 +108,6 @@ function ItemSeparatorComponent() { } export const WalletContentList = memo((props) => { - const theme = useTheme(); - return ( ((props) => { } /> diff --git a/packages/mobile/src/tabs/Wallet/hooks/useInternalNotifications.ts b/packages/mobile/src/tabs/Wallet/hooks/useInternalNotifications.ts index 5aed6aa05..84b806604 100644 --- a/packages/mobile/src/tabs/Wallet/hooks/useInternalNotifications.ts +++ b/packages/mobile/src/tabs/Wallet/hooks/useInternalNotifications.ts @@ -2,7 +2,6 @@ import { usePrevious } from '$hooks/usePrevious'; import { mainActions, mainSelector } from '$store/main'; import { InternalNotificationProps } from '$uikit/InternalNotification/InternalNotification.interface'; import { useNetInfo } from '@react-native-community/netinfo'; -import { MainDB } from '$database'; import { useEffect, useMemo, useState } from 'react'; import { Linking } from 'react-native'; import { useDispatch, useSelector } from 'react-redux'; @@ -31,13 +30,8 @@ export const useInternalNotifications = () => { } }, [netInfo.isConnected, prevNetInfo.isConnected, dispatch]); - const { - badHosts, - isBadHostsDismissed, - internalNotifications, - timeSyncedDismissedTimestamp, - isTimeSynced, - } = useSelector(mainSelector); + const { badHosts, isBadHostsDismissed, internalNotifications } = + useSelector(mainSelector); const addressUpdateDismissed = useAddressUpdateStore((s) => s.dismissed); const shouldShowAddressUpdate = useFlag('address_style_notice'); @@ -77,20 +71,6 @@ export const useInternalNotifications = () => { mode: 'danger', onClose: () => dispatch(mainActions.dismissBadHosts()), }); - } else if ( - !isTimeSynced && - (!timeSyncedDismissedTimestamp || - timeSyncedDismissedTimestamp < Date.now() - 7 * 24 * 60 * 60 * 1000) - ) { - result.push({ - title: t('notify_incorrect_time_err_title'), - caption: t('notify_incorrect_time_err_caption'), - mode: 'tertiary', - onClose: () => { - MainDB.setTimeSyncedDismissed(Date.now()); - dispatch(mainActions.setTimeSyncedDismissed(Date.now())); - }, - }); } if (wallet && !addressUpdateDismissed && shouldShowAddressUpdate) { @@ -139,8 +119,6 @@ export const useInternalNotifications = () => { netInfo.isConnected, badHosts, isBadHostsDismissed, - isTimeSynced, - timeSyncedDismissedTimestamp, wallet, addressUpdateDismissed, shouldShowAddressUpdate, diff --git a/packages/mobile/src/tonconnect/ConnectReplyBuilder.ts b/packages/mobile/src/tonconnect/ConnectReplyBuilder.ts index 3db9575d2..93bebf8e4 100644 --- a/packages/mobile/src/tonconnect/ConnectReplyBuilder.ts +++ b/packages/mobile/src/tonconnect/ConnectReplyBuilder.ts @@ -11,9 +11,9 @@ import nacl from 'tweetnacl'; import TonWeb from 'tonweb'; import { Buffer } from 'buffer'; import { getDomainFromURL } from '$utils'; -import { getTimeSec } from '$utils/getTimeSec'; import { Int64LE } from 'int64-buffer'; import { DAppManifest } from './models'; +import { getRawTimeFromLiteserverSafely } from '@tonkeeper/shared/utils/blockchain'; const { createHash } = require('react-native-crypto'); @@ -31,13 +31,13 @@ export class ConnectReplyBuilder { return getChainName() === 'mainnet' ? CHAIN.MAINNET : CHAIN.TESTNET; } - private createTonProofItem( + private async createTonProofItem( address: string, secretKey: Uint8Array, payload: string, - ): TonProofItemReply { + ): Promise { try { - const timestamp = getTimeSec(); + const timestamp = await getRawTimeFromLiteserverSafely(); const timestampBuffer = new Int64LE(timestamp).toBuffer(); const domain = getDomainFromURL(this.manifest.url); @@ -102,36 +102,41 @@ export class ConnectReplyBuilder { } } - createReplyItems( + async createReplyItems( addr: string, privateKey: Uint8Array, publicKey: Uint8Array, walletStateInit: string, isTestnet: boolean, - ): ConnectItemReply[] { + ): Promise { const address = new TonWeb.utils.Address(addr).toString(false, true, true); - const replyItems = this.request.items.map((requestItem): ConnectItemReply => { - switch (requestItem.name) { + const replyItems: ConnectItemReply[] = []; + for (const item of this.request.items) { + switch (item.name) { case 'ton_addr': - return { + replyItems.push({ name: 'ton_addr', address, network: isTestnet ? CHAIN.TESTNET : CHAIN.MAINNET, publicKey: Buffer.from(publicKey).toString('hex'), walletStateInit, - }; + }); + break; case 'ton_proof': - return this.createTonProofItem(address, privateKey, requestItem.payload); + replyItems.push( + await this.createTonProofItem(address, privateKey, item.payload), + ); + break; default: - return { - name: (requestItem as ConnectItem).name, + replyItems.push({ + name: (item as ConnectItem).name, error: { code: 400 }, - } as unknown as ConnectItemReply; + } as unknown as ConnectItemReply); } - }); + } return replyItems; } diff --git a/packages/mobile/src/tonconnect/TonConnect.ts b/packages/mobile/src/tonconnect/TonConnect.ts index 1d71495aa..c34ecc890 100644 --- a/packages/mobile/src/tonconnect/TonConnect.ts +++ b/packages/mobile/src/tonconnect/TonConnect.ts @@ -2,7 +2,6 @@ import { openSignRawModal } from '$core/ModalContainer/NFTOperations/Modals/Sign import { SignRawParams } from '$core/ModalContainer/NFTOperations/TXRequest.types'; import { TonConnectModalResponse } from '$core/TonConnect/models'; import { openTonConnect } from '$core/TonConnect/TonConnectModal'; -import { checkIsTimeSynced } from '$navigation/hooks/useDeeplinkingResolvers'; import { findConnectedAppByClientSessionId, findConnectedAppByUrl, @@ -321,15 +320,6 @@ class TonConnectService { }; const boc = await new Promise(async (resolve, reject) => { - if (!checkIsTimeSynced()) { - return reject( - new SendTransactionError( - request.id, - SEND_TRANSACTION_ERROR_CODES.USER_REJECTS_ERROR, - 'Wallet declined the request', - ), - ); - } const openModalResult = await openSignRawModal( txParams, { diff --git a/packages/mobile/src/uikit/NavBar/NavBar.tsx b/packages/mobile/src/uikit/NavBar/NavBar.tsx index 45e09a64b..6d8579b6f 100644 --- a/packages/mobile/src/uikit/NavBar/NavBar.tsx +++ b/packages/mobile/src/uikit/NavBar/NavBar.tsx @@ -61,7 +61,7 @@ export const NavBar: FC = (props) => { }, [onGoBack]); const top = useMemo(() => { - if (isModal) { + if (isModal && isIOS) { return 0; } else { return topInset; diff --git a/packages/mobile/src/utils/proof.ts b/packages/mobile/src/utils/proof.ts index 80e66e51f..91f0120cc 100644 --- a/packages/mobile/src/utils/proof.ts +++ b/packages/mobile/src/utils/proof.ts @@ -1,4 +1,3 @@ -import { getTimeSec } from '$utils/getTimeSec'; import { Int64LE } from 'int64-buffer'; import { Buffer } from 'buffer'; import nacl from 'tweetnacl'; @@ -7,6 +6,7 @@ const { createHash } = require('react-native-crypto'); import { ConnectApi, Configuration } from '@tonkeeper/core/src/legacy'; import { Address } from '@tonkeeper/core'; import { config } from '$config'; +import { getRawTimeFromLiteserverSafely } from '@tonkeeper/shared/utils/blockchain'; export interface TonProofArgs { address: string; @@ -37,7 +37,7 @@ export async function createTonProof({ if (!payload) { payload = (await connectApi.getTonConnectPayload()).payload; } - const timestamp = getTimeSec(); + const timestamp = await getRawTimeFromLiteserverSafely(); const timestampBuffer = new Int64LE(timestamp).toBuffer(); const domainBuffer = Buffer.from(domain); diff --git a/packages/mobile/src/utils/stats.ts b/packages/mobile/src/utils/stats.ts index ae3f9b379..efb4106c2 100644 --- a/packages/mobile/src/utils/stats.ts +++ b/packages/mobile/src/utils/stats.ts @@ -1,5 +1,4 @@ import { config } from '$config'; -import { init, logEvent } from '@amplitude/analytics-browser'; import AsyncStorage from '@react-native-async-storage/async-storage'; import Aptabase from '@aptabase/react-native'; import DeviceInfo from 'react-native-device-info'; @@ -19,20 +18,6 @@ export function initStats() { appVersion: DeviceInfo.getVersion(), }); } - init(config.get('amplitudeKey'), '-', { - minIdLength: 1, - deviceId: '-', - trackingOptions: { - ipAddress: false, - deviceModel: true, - language: false, - osName: true, - osVersion: true, - platform: true, - adid: false, - carrier: false, - }, - }); TrakingEnabled = true; } @@ -48,7 +33,6 @@ export async function trackEvent(name: string, params: any = {}) { Object.assign(params, { firebase_user_id: DeviceInfo.getUniqueId() }), ); } - logEvent(name, params); } catch (e) {} } diff --git a/packages/mobile/src/wallet/models/ActivityModel/ActivityModel.ts b/packages/mobile/src/wallet/models/ActivityModel/ActivityModel.ts index d37629b8f..4b24b3470 100644 --- a/packages/mobile/src/wallet/models/ActivityModel/ActivityModel.ts +++ b/packages/mobile/src/wallet/models/ActivityModel/ActivityModel.ts @@ -11,7 +11,11 @@ import { ActionItem, AnyActionItem, } from './ActivityModelTypes'; -import { AccountEvent, ActionStatusEnum } from '@tonkeeper/core/src/TonAPI'; +import { + AccountEvent, + ActionStatusEnum, + JettonVerificationType, +} from '@tonkeeper/core/src/TonAPI'; import { toLowerCaseFirstLetter } from '@tonkeeper/uikit'; import { Address } from '@tonkeeper/core'; import { TronEvent } from '@tonkeeper/core/src/TronAPI/TronAPIGenerated'; @@ -167,7 +171,10 @@ export class ActivityModel { type: ActionAmountType.Jetton, jettonAddress: payload.jetton.address, decimals: payload.jetton.decimals, - symbol: payload.jetton.symbol, + symbol: + payload.jetton.verification === JettonVerificationType.Blacklist + ? 'FAKE' + : payload.jetton.symbol, value: payload.amount, }; case ActionType.NftPurchase: diff --git a/packages/mobile/src/wallet/models/JettonBalanceModel/JettonBalanceModel.ts b/packages/mobile/src/wallet/models/JettonBalanceModel/JettonBalanceModel.ts index e934e3323..7fd14180b 100644 --- a/packages/mobile/src/wallet/models/JettonBalanceModel/JettonBalanceModel.ts +++ b/packages/mobile/src/wallet/models/JettonBalanceModel/JettonBalanceModel.ts @@ -1,5 +1,5 @@ import { Address, AmountFormatter } from '@tonkeeper/core'; -import { JettonBalance } from '@tonkeeper/core/src/TonAPI'; +import { JettonBalance, JettonVerificationType } from '@tonkeeper/core/src/TonAPI'; import { JettonMetadata, JettonVerification } from './types'; export class JettonBalanceModel { @@ -28,5 +28,9 @@ export class JettonBalanceModel { this.walletAddress = new Address(jettonBalance.wallet_address.address).toFriendly(); this.verification = jettonBalance.jetton .verification as unknown as JettonVerification; + + if (jettonBalance.jetton.verification === JettonVerificationType.Blacklist) { + this.metadata.symbol = 'FAKE'; + } } } diff --git a/packages/mobile/src/wallet/utils.ts b/packages/mobile/src/wallet/utils.ts index cb63ce07e..00a2edc07 100644 --- a/packages/mobile/src/wallet/utils.ts +++ b/packages/mobile/src/wallet/utils.ts @@ -8,6 +8,7 @@ export const createTonApiInstance = (isTestnet = false) => { return new TonAPI({ baseHeaders: () => ({ Authorization: `Bearer ${config.get('tonApiV2Key', isTestnet)}`, + 'Cache-Control': 'no-cache', }), baseUrl: () => config.get('tonapiIOEndpoint', isTestnet), }); diff --git a/packages/shared/utils/blockchain.ts b/packages/shared/utils/blockchain.ts index 342843c02..3edb8942d 100644 --- a/packages/shared/utils/blockchain.ts +++ b/packages/shared/utils/blockchain.ts @@ -3,6 +3,7 @@ import { tk } from '@tonkeeper/mobile/src/wallet'; import { ContentType, ServiceStatus } from '@tonkeeper/core/src/TonAPI'; import { TransactionService } from '@tonkeeper/core'; import { t } from '../i18n'; +import { Alert } from 'react-native'; export class NetworkOverloadedError extends Error {} @@ -66,3 +67,21 @@ export async function emulateBoc( return { emulateResult, battery: false }; } } + +export async function getRawTimeFromLiteserverSafely(): Promise { + try { + const res = await tk.wallet.tonapi.liteserver.getRawTime({ + headers: { + 'Cache-Control': 'no-cache', + }, + cache: 'no-cache', + }); + return res.time; + } catch (e) { + return Math.floor(Date.now() / 1e3); + } +} + +export async function getTimeoutFromLiteserverSafely() { + return (await getRawTimeFromLiteserverSafely()) + TransactionService.TTL; +} diff --git a/packages/uikit/src/containers/Modal/ScreenModal/ScreenModalHeader.tsx b/packages/uikit/src/containers/Modal/ScreenModal/ScreenModalHeader.tsx index 65b18a1ec..9309e2051 100644 --- a/packages/uikit/src/containers/Modal/ScreenModal/ScreenModalHeader.tsx +++ b/packages/uikit/src/containers/Modal/ScreenModal/ScreenModalHeader.tsx @@ -7,7 +7,7 @@ import { memo } from 'react'; import { isString } from '../../../utils/strings'; import { useNavigation } from '@tonkeeper/router'; import { StatusBar } from 'react-native'; -import { isIOS } from '../../../utils'; +import { isAndroid, isIOS } from '../../../utils'; export interface ScreenModalHeaderProps { children?: React.ReactNode; @@ -43,8 +43,9 @@ export const ScreenModalHeader = memo((props) => { ); }); -const styles = Steezy.create(({ colors }) => ({ +const styles = Steezy.create(({ colors, safeArea }) => ({ container: { + marginTop: isAndroid ? safeArea.top : 0, height: 64, justifyContent: 'center', alignItems: 'center', diff --git a/packages/uikit/src/containers/Modal/SheetModal/SheetModal.tsx b/packages/uikit/src/containers/Modal/SheetModal/SheetModal.tsx index b8593cccd..e9e9664b1 100644 --- a/packages/uikit/src/containers/Modal/SheetModal/SheetModal.tsx +++ b/packages/uikit/src/containers/Modal/SheetModal/SheetModal.tsx @@ -78,7 +78,7 @@ export const SheetModal = memo( }); }, []); - const topInset = !isAndroid ? StatusBarHeight + safeArea.top : 0; + const topInset = !isAndroid ? StatusBarHeight + safeArea.top : safeArea.top; const handleIndexChange = useCallback( (index: number) => { diff --git a/yarn.lock b/yarn.lock index a9f54b67d..c70ff53a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12772,6 +12772,11 @@ react-native-svg@^13.10.0: css-select "^5.1.0" css-tree "^1.1.3" +react-native-system-navigation-bar@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/react-native-system-navigation-bar/-/react-native-system-navigation-bar-2.6.4.tgz#34edee7051dea01531ff2be95dc14f9fa8a540b7" + integrity sha512-4pysgADW53PiuHv+2glzNLJnHSxqDszZvLoitLFI5os4D+gCDfxmR36VSET4EnXkzSf8X9mbeFkHYDypDHJyZA== + react-native-tcp@^3.3.0: version "3.3.2" resolved "https://registry.yarnpkg.com/react-native-tcp/-/react-native-tcp-3.3.2.tgz#b38c153039acac89294caa4991689c003ec62dce"