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"