;
@@ -53,8 +55,9 @@ export interface PayloadRequest {
export interface UserProperties {
application: string;
walletType: string;
- account?: any;
- wallet?: any;
+ accounts?: Account[];
+ activeAccount?: Account;
+ network?: Network;
version?: string | undefined;
platform?: string | undefined;
}
diff --git a/apps/extension/src/libs/hooks.ts b/apps/extension/src/libs/hooks.ts
index 48ec904fd..40ef024be 100644
--- a/apps/extension/src/libs/hooks.ts
+++ b/apps/extension/src/libs/hooks.ts
@@ -1,8 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { AppKey } from '@tonkeeper/core/dist/Keys';
import { IStorage } from '@tonkeeper/core/dist/Storage';
-import { AccountState } from '@tonkeeper/core/dist/entries/account';
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
import { throttle } from '@tonkeeper/core/dist/utils/common';
import { Analytics, AnalyticsGroup, toWalletType } from '@tonkeeper/uikit/dist/hooks/analytics';
import { Amplitude } from '@tonkeeper/uikit/dist/hooks/analytics/amplitude';
@@ -11,6 +9,7 @@ import { useEffect } from 'react';
import { v4 as uuidv4 } from 'uuid';
import { extensionType } from './appSdk';
import { AptabaseExtension } from './aptabase-extension';
+import { Account } from "@tonkeeper/core/dist/entries/account";
export const useAppWidth = () => {
useEffect(() => {
@@ -34,12 +33,12 @@ export const useAppWidth = () => {
export const useAnalytics = (
storage: IStorage,
- account?: AccountState,
- wallet?: WalletState | null,
+ activeAccount?: Account,
+ accounts?: Account[],
version?: string
) => {
return useQuery(
- [QueryKey.analytics],
+ [QueryKey.analytics, activeAccount, accounts],
async () => {
let userId: string | undefined = undefined;
if (extensionType === 'FireFox') {
@@ -59,10 +58,14 @@ export const useAnalytics = (
new Amplitude(process.env.REACT_APP_AMPLITUDE!, userId)
);
- tracker.init(extensionType ?? 'Extension', toWalletType(wallet), account, wallet);
+ tracker.init({
+ application: extensionType ?? 'Extension',
+ walletType: toWalletType(activeAccount?.activeTonWallet),
+ activeAccount: activeAccount!,
+ accounts: accounts!});
return tracker;
},
- { enabled: account != null }
+ { enabled: accounts != null && activeAccount !== undefined }
);
};
diff --git a/apps/extension/src/libs/service/bachgroundAptabaseService.ts b/apps/extension/src/libs/service/bachgroundAptabaseService.ts
index 62de4a541..0f4ad8dda 100644
--- a/apps/extension/src/libs/service/bachgroundAptabaseService.ts
+++ b/apps/extension/src/libs/service/bachgroundAptabaseService.ts
@@ -18,8 +18,8 @@ export class AptabaseExtensionService {
this.user_properties['application'] = params.application;
this.user_properties['walletType'] = params.walletType;
this.user_properties['network'] =
- params.wallet?.network === Network.TESTNET ? 'testnet' : 'mainnet';
- this.user_properties['accounts'] = params.account!.publicKeys.length;
+ params.network === Network.TESTNET ? 'testnet' : 'mainnet';
+ this.user_properties['accounts'] = params.accounts?.length || 0;
this.user_properties['version'] = params.version;
this.user_properties['platform'] = params.platform;
};
diff --git a/apps/extension/src/libs/service/dApp/tonConnectService.ts b/apps/extension/src/libs/service/dApp/tonConnectService.ts
index ad9876246..d3c150d01 100644
--- a/apps/extension/src/libs/service/dApp/tonConnectService.ts
+++ b/apps/extension/src/libs/service/dApp/tonConnectService.ts
@@ -19,8 +19,8 @@ import {
openNotificationPopUp,
} from './notificationService';
import { waitApprove } from './utils';
-import { accountSelectWallet } from "@tonkeeper/core/dist/service/accountService";
import { TonConnectError } from "@tonkeeper/core/dist/entries/exception";
+import { accountsStorage } from "@tonkeeper/core/dist/service/accountsStorage";
const storage = new ExtensionStorage();
@@ -106,7 +106,16 @@ export const tonConnectTransaction = async (
);
}
- await accountSelectWallet(storage, connection.wallet.publicKey);
+ const accounts = await accountsStorage(storage).getAccounts();
+ const accToActivate = accounts.find(a => a.allTonWallets.some(w => w.id === connection.wallet.rawAddress));
+
+ if (accToActivate) {
+ accToActivate.setActiveTonWallet(connection.wallet.rawAddress);
+ await accountsStorage(storage).updateAccountInState(accToActivate);
+ await accountsStorage(storage).setActiveAccountId(accToActivate.id);
+
+ }
+
await delay(200);
await cancelOpenedNotification();
diff --git a/apps/extension/src/libs/service/dApp/utils.ts b/apps/extension/src/libs/service/dApp/utils.ts
index 62ff7e848..740101271 100644
--- a/apps/extension/src/libs/service/dApp/utils.ts
+++ b/apps/extension/src/libs/service/dApp/utils.ts
@@ -8,11 +8,7 @@
import { TonConnectError } from '@tonkeeper/core/dist/entries/exception';
import {
CONNECT_EVENT_ERROR_CODES,
- TonConnectAccount,
} from '@tonkeeper/core/dist/entries/tonConnect';
-import { getAccountConnection } from '@tonkeeper/core/dist/service/tonConnect/connectionService';
-import { getCurrentWallet } from '@tonkeeper/core/dist/service/wallet/storeService';
-import { IStorage } from '@tonkeeper/core/dist/Storage';
import { backgroundEventsEmitter, PayloadRequest } from '../../event';
export const waitApprove = (id: number, popupId?: number) => {
diff --git a/apps/extension/task/webpack.config.js b/apps/extension/task/webpack.config.js
index 76f5927f8..c3c048b7d 100644
--- a/apps/extension/task/webpack.config.js
+++ b/apps/extension/task/webpack.config.js
@@ -18,7 +18,9 @@ module.exports = [
resolve: {
extensions: ['.tsx', '.ts', '.js'],
fallback: {
- buffer: require.resolve('buffer/')
+ buffer: require.resolve('buffer/'),
+ 'process/browser': require.resolve('process/browser'),
+ stream: require.resolve('stream-browserify')
}
},
output: {
diff --git a/apps/twa/src/App.tsx b/apps/twa/src/App.tsx
index 6a7cc0207..7dd68790c 100644
--- a/apps/twa/src/App.tsx
+++ b/apps/twa/src/App.tsx
@@ -1,6 +1,7 @@
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
import { Network, getApiConfig } from '@tonkeeper/core/dist/entries/network';
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
+import { WalletVersion } from "@tonkeeper/core/dist/entries/wallet";
+import { Account } from "@tonkeeper/core/dist/entries/account";
import { InnerBody, useWindowsScroll } from '@tonkeeper/uikit/dist/components/Body';
import { CopyNotification } from '@tonkeeper/uikit/dist/components/CopyNotification';
import { Footer, FooterGlobalStyle } from '@tonkeeper/uikit/dist/components/Footer';
@@ -20,7 +21,6 @@ import { SybHeaderGlobalStyle } from '@tonkeeper/uikit/dist/components/SubHeader
import {
AppContext,
IAppContext,
- WalletStateContext
} from '@tonkeeper/uikit/dist/hooks/appContext';
import {
AfterImportAction,
@@ -37,12 +37,8 @@ import { SDKProvider } from '@tma.js/sdk-react';
import { AmplitudeAnalyticsContext, useTrackLocation } from '@tonkeeper/uikit/dist/hooks/amplitude';
import { useLock } from '@tonkeeper/uikit/dist/hooks/lock';
import { UnlockNotification } from '@tonkeeper/uikit/dist/pages/home/UnlockNotification';
-import { useAccountState } from '@tonkeeper/uikit/dist/state/account';
-import { useUserFiat } from '@tonkeeper/uikit/dist/state/fiat';
-import { useAuthState } from '@tonkeeper/uikit/dist/state/password';
-import { useSwapMobileNotification } from '@tonkeeper/uikit/dist/state/swap/useSwapMobileNotification';
-import { useTonendpoint, useTonenpointConfig } from '@tonkeeper/uikit/dist/state/tonendpoint';
-import { useActiveWallet } from '@tonkeeper/uikit/dist/state/wallet';
+import { useTonendpoint, useTonenpointConfig } from "@tonkeeper/uikit/dist/state/tonendpoint";
+import { useActiveAccountQuery, useAccountsStateQuery, useActiveTonNetwork } from "@tonkeeper/uikit/dist/state/wallet";
import { defaultTheme } from '@tonkeeper/uikit/dist/styles/defaultTheme';
import { Container, GlobalStyle } from '@tonkeeper/uikit/dist/styles/globalStyle';
import { lightTheme } from '@tonkeeper/uikit/dist/styles/lightTheme';
@@ -59,6 +55,12 @@ import { SwapScreen } from './components/swap/SwapNotification';
import { TwaSendNotification } from './components/transfer/SendNotifications';
import { TwaAppSdk } from './libs/appSdk';
import { useAnalytics, useTwaAppViewport } from './libs/hooks';
+import { useUserFiat } from "@tonkeeper/uikit/dist/state/fiat";
+import { useUserLanguage } from "@tonkeeper/uikit/dist/state/language";
+import { useSwapMobileNotification } from "@tonkeeper/uikit/dist/state/swap/useSwapMobileNotification";
+import { useDevSettings } from "@tonkeeper/uikit/dist/state/dev";
+import { ModalsRoot } from "@tonkeeper/uikit/dist/components/ModalsRoot";
+import { useDebuggingTools } from "@tonkeeper/uikit/dist/hooks/useDebuggingTools";
const Initialize = React.lazy(() => import('@tonkeeper/uikit/dist/pages/import/Initialize'));
const ImportRouter = React.lazy(() => import('@tonkeeper/uikit/dist/pages/import'));
@@ -217,42 +219,36 @@ const seeIfShowQrScanner = (platform: TwaPlatform): boolean => {
};
export const Loader: FC<{ sdk: TwaAppSdk }> = ({ sdk }) => {
- const { data: activeWallet } = useActiveWallet();
+ const { data: activeAccount, isLoading: activeWalletLoading } = useActiveAccountQuery();
+ const { data: accounts, isLoading: isWalletsLoading } = useAccountsStateQuery();
+ const { data: lang, isLoading: isLangLoading } = useUserLanguage();
const { data: fiat } = useUserFiat();
+ const { data: devSettings } = useDevSettings();
const lock = useLock(sdk);
- const { data: account } = useAccountState();
- const { data: auth } = useAuthState();
+ const network = useActiveTonNetwork();
const tonendpoint = useTonendpoint({
- targetEnv: TARGET_ENV,
- build: sdk.version,
- network: activeWallet?.network,
- lang: activeWallet?.lang
- });
+ targetEnv: TARGET_ENV,
+ build: sdk.version,
+ network,
+ lang
+}
+ );
const { data: config } = useTonenpointConfig(tonendpoint);
const navigate = useNavigate();
- const { data: tracker } = useAnalytics(account, activeWallet, sdk.version);
-
- if (
- auth === undefined ||
- account === undefined ||
- config === undefined ||
- lock === undefined ||
- fiat === undefined
- ) {
+ const { data: tracker } = useAnalytics(activeAccount || undefined, accounts, network, sdk.version);
+
+ if (isWalletsLoading || activeWalletLoading || isLangLoading || config === undefined || lock === undefined || fiat === undefined || !devSettings) {
return ;
}
const showQrScan = seeIfShowQrScanner(sdk.launchParams.platform);
- const network = activeWallet?.network ?? Network.MAINNET;
const context: IAppContext = {
api: getApiConfig(config, network),
- auth,
fiat,
- account,
config,
tonendpoint,
standalone: true,
@@ -263,7 +259,8 @@ export const Loader: FC<{ sdk: TwaAppSdk }> = ({ sdk }) => {
hideBrowser: true,
hideSigner: !showQrScan,
hideKeystone: !showQrScan,
- hideQrScanner: !showQrScan
+ hideQrScanner: !showQrScan,
+ defaultWalletVersion: WalletVersion.V5R1
};
return (
@@ -274,12 +271,13 @@ export const Loader: FC<{ sdk: TwaAppSdk }> = ({ sdk }) => {
>
+
{showQrScan && }
@@ -308,7 +306,7 @@ const InitPages: FC<{ sdk: TwaAppSdk }> = ({ sdk }) => {
}>
- } />
+ } />
} />
@@ -318,13 +316,14 @@ const InitPages: FC<{ sdk: TwaAppSdk }> = ({ sdk }) => {
const Content: FC<{
sdk: TwaAppSdk;
- activeWallet?: WalletState | null;
+ activeAccount?: Account | null;
lock: boolean;
showQrScan: boolean;
-}> = ({ activeWallet, lock, showQrScan, sdk }) => {
+}> = ({ activeAccount, lock, showQrScan, sdk }) => {
const location = useLocation();
useWindowsScroll();
useTrackLocation();
+ useDebuggingTools();
if (lock) {
return (
@@ -334,21 +333,21 @@ const Content: FC<{
);
}
- if (!activeWallet || location.pathname.startsWith(AppRoute.import)) {
+ if (!activeAccount || location.pathname.startsWith(AppRoute.import)) {
return ;
}
return (
-
-
- } />
- } />
-
-
-
-
-
-
+ <>
+
+ } />
+ } />
+
+
+
+
+
+ >
);
};
diff --git a/apps/twa/src/libs/hooks.ts b/apps/twa/src/libs/hooks.ts
index 0f97ac58c..335c1add3 100644
--- a/apps/twa/src/libs/hooks.ts
+++ b/apps/twa/src/libs/hooks.ts
@@ -1,8 +1,8 @@
import { useQuery } from '@tanstack/react-query';
+import { Account } from "@tonkeeper/core/dist/entries/account";
+import { Network } from "@tonkeeper/core/dist/entries/network";
+import { Analytics, AnalyticsGroup, toWalletType } from "@tonkeeper/uikit/dist/hooks/analytics";
import { Viewport } from '@tma.js/sdk';
-import { AccountState } from '@tonkeeper/core/dist/entries/account';
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
-import { Analytics, AnalyticsGroup, toWalletType } from '@tonkeeper/uikit/dist/hooks/analytics';
import { AptabaseWeb } from '@tonkeeper/uikit/dist/hooks/analytics/aptabase-web';
import { Gtag } from '@tonkeeper/uikit/dist/hooks/analytics/gtag';
import { QueryKey } from '@tonkeeper/uikit/dist/libs/queryKey';
@@ -67,27 +67,29 @@ export const useTwaAppViewport = (setAppHeight: boolean, sdk: TwaAppSdk) => {
}, [sdk]);
};
-export const useAnalytics = (
- account?: AccountState,
- wallet?: WalletState | null,
- version?: string
-) => {
+export const useAnalytics = (activeAccount?: Account, accounts?: Account[], network?: Network, version?: string) => {
return useQuery(
- [QueryKey.analytics],
+ [QueryKey.analytics, activeAccount, accounts, network],
async () => {
- const tracker = new AnalyticsGroup(
- new AptabaseWeb(
- import.meta.env.VITE_APP_APTABASE_HOST,
- import.meta.env.VITE_APP_APTABASE,
- version
- ),
- new Gtag(import.meta.env.VITE_APP_MEASUREMENT_ID)
- );
-
- tracker.init('Twa', toWalletType(wallet), account, wallet);
+ const tracker = new AnalyticsGroup(
+ new AptabaseWeb(
+ import.meta.env.VITE_APP_APTABASE_HOST,
+ import.meta.env.VITE_APP_APTABASE,
+ version
+ ),
+ new Gtag(import.meta.env.VITE_APP_MEASUREMENT_ID)
+ );
+
+ tracker.init({
+ application:'Twa',
+ walletType: toWalletType(activeAccount?.activeTonWallet),
+ activeAccount: activeAccount!,
+ accounts: accounts!,
+ network
+ });
return tracker;
},
- { enabled: account != null }
+ { enabled: accounts != null && activeAccount !== undefined }
);
};
diff --git a/apps/twa/src/libs/twaNotification.ts b/apps/twa/src/libs/twaNotification.ts
index 613255bdf..0fc712d01 100644
--- a/apps/twa/src/libs/twaNotification.ts
+++ b/apps/twa/src/libs/twaNotification.ts
@@ -1,7 +1,7 @@
import { MiniApp, retrieveLaunchParams } from '@tma.js/sdk';
import { NotificationService } from '@tonkeeper/core/dist/AppSdk';
+import { TonWalletStandard } from "@tonkeeper/core/dist/entries/wallet";
import { APIConfig } from '@tonkeeper/core/dist/entries/apis';
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
import {
toTonProofItem,
tonConnectProofPayload
@@ -31,7 +31,7 @@ export class TwaNotification implements NotificationService {
private getTonConnectProof = async (
api: APIConfig,
- wallet: WalletState,
+ wallet: TonWalletStandard,
signTonConnect: (bufferToSign: Buffer) => Promise
) => {
const domain = 'https://twa.tonkeeper.com/';
@@ -40,7 +40,7 @@ export class TwaNotification implements NotificationService {
const proofPayload = tonConnectProofPayload(
timestamp,
domain,
- wallet.active.rawAddress,
+ wallet.rawAddress,
payload
);
const stateInit = walletStateInitFromState(wallet);
@@ -49,7 +49,7 @@ export class TwaNotification implements NotificationService {
subscribe = async (
api: APIConfig,
- wallet: WalletState,
+ wallet: TonWalletStandard,
signTonConnect: (bufferToSign: Buffer) => Promise
) => {
try {
@@ -62,7 +62,7 @@ export class TwaNotification implements NotificationService {
await twaApi.subscribeToAccountEvents({
subscribeToAccountEventsRequest: {
twaInitData: this.twaInitData,
- address: wallet.active.rawAddress,
+ address: wallet.rawAddress,
proof
}
});
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 202cd3392..a44bd5084 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -1,7 +1,7 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { localizationText } from '@tonkeeper/core/dist/entries/language';
-import { Network, getApiConfig } from '@tonkeeper/core/dist/entries/network';
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
+import { getApiConfig } from '@tonkeeper/core/dist/entries/network';
+import { WalletVersion } from "@tonkeeper/core/dist/entries/wallet";
import { InnerBody, useWindowsScroll } from '@tonkeeper/uikit/dist/components/Body';
import { CopyNotification } from '@tonkeeper/uikit/dist/components/CopyNotification';
import { Footer, FooterGlobalStyle } from '@tonkeeper/uikit/dist/components/Footer';
@@ -22,7 +22,7 @@ import {
EditFavoriteNotification
} from '@tonkeeper/uikit/dist/components/transfer/FavoriteNotification';
import { AmplitudeAnalyticsContext, useTrackLocation } from '@tonkeeper/uikit/dist/hooks/amplitude';
-import { AppContext, WalletStateContext } from '@tonkeeper/uikit/dist/hooks/appContext';
+import { AppContext, IAppContext } from "@tonkeeper/uikit/dist/hooks/appContext";
import {
AfterImportAction,
AppSdkContext,
@@ -37,11 +37,9 @@ import { UnlockNotification } from '@tonkeeper/uikit/dist/pages/home/UnlockNotif
import Initialize, { InitializeContainer } from '@tonkeeper/uikit/dist/pages/import/Initialize';
import { useKeyboardHeight } from '@tonkeeper/uikit/dist/pages/import/hooks';
import { UserThemeProvider } from '@tonkeeper/uikit/dist/providers/UserThemeProvider';
-import { useAccountState } from '@tonkeeper/uikit/dist/state/account';
import { useUserFiat } from '@tonkeeper/uikit/dist/state/fiat';
-import { useAuthState } from '@tonkeeper/uikit/dist/state/password';
-import { useTonendpoint, useTonenpointConfig } from '@tonkeeper/uikit/dist/state/tonendpoint';
-import { useActiveWallet } from '@tonkeeper/uikit/dist/state/wallet';
+import { useTonendpoint, useTonenpointConfig } from "@tonkeeper/uikit/dist/state/tonendpoint";
+import { useActiveAccountQuery, useAccountsStateQuery, useActiveTonNetwork } from "@tonkeeper/uikit/dist/state/wallet";
import { Container, GlobalStyle } from '@tonkeeper/uikit/dist/styles/globalStyle';
import React, { FC, PropsWithChildren, Suspense, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -49,6 +47,11 @@ import { BrowserRouter, Route, Routes, useLocation, useNavigate } from 'react-ro
import styled, { css } from 'styled-components';
import { BrowserAppSdk } from './libs/appSdk';
import { useAnalytics, useAppHeight, useAppWidth } from './libs/hooks';
+import { useUserLanguage } from "@tonkeeper/uikit/dist/state/language";
+import { useDevSettings } from "@tonkeeper/uikit/dist/state/dev";
+import { ModalsRoot } from "@tonkeeper/uikit/dist/components/ModalsRoot";
+import { Account } from "@tonkeeper/core/dist/entries/account";
+import { useDebuggingTools } from "@tonkeeper/uikit/dist/hooks/useDebuggingTools";
const ImportRouter = React.lazy(() => import('@tonkeeper/uikit/dist/pages/import'));
const Settings = React.lazy(() => import('@tonkeeper/uikit/dist/pages/settings'));
@@ -178,8 +181,12 @@ const Wrapper = styled(FullSizeWrapper)<{ standalone: boolean }>`
`;
export const Loader: FC = () => {
- const { data: activeWallet } = useActiveWallet();
+ const network = useActiveTonNetwork();
+ const { data: activeAccount, isLoading: activeWalletLoading } = useActiveAccountQuery();
+ const { data: accounts, isLoading: isWalletsLoading } = useAccountsStateQuery();
+ const { data: lang, isLoading: isLangLoading } = useUserLanguage();
const { data: fiat } = useUserFiat();
+ const { data: devSettings } = useDevSettings();
const [ios, standalone] = useMemo(() => {
return [sdk.isIOs(), sdk.isStandalone()] as const;
@@ -187,56 +194,54 @@ export const Loader: FC = () => {
const lock = useLock(sdk);
const { i18n } = useTranslation();
- const { data: account } = useAccountState();
- const { data: auth } = useAuthState();
const tonendpoint = useTonendpoint({
targetEnv: TARGET_ENV,
build: sdk.version,
- network: activeWallet?.network,
- lang: activeWallet?.lang
+ network,
+ lang
});
const { data: config } = useTonenpointConfig(tonendpoint);
const navigate = useNavigate();
useAppHeight();
- const { data: tracker } = useAnalytics(account, activeWallet, sdk.version);
+ const { data: tracker } = useAnalytics(activeAccount || undefined, accounts, sdk.version);
useEffect(() => {
if (
- activeWallet &&
- activeWallet.lang &&
- i18n.language !== localizationText(activeWallet.lang)
+ activeAccount &&
+ lang &&
+ i18n.language !== localizationText(lang)
) {
- i18n.reloadResources([localizationText(activeWallet.lang)]).then(() =>
- i18n.changeLanguage(localizationText(activeWallet.lang))
+ i18n.reloadResources([localizationText(lang)]).then(() =>
+ i18n.changeLanguage(localizationText(lang))
);
}
- }, [activeWallet, i18n]);
+ }, [activeAccount, i18n]);
if (
- auth === undefined ||
- account === undefined ||
+ isWalletsLoading ||
+ activeWalletLoading ||
+ isLangLoading ||
config === undefined ||
lock === undefined ||
- fiat === undefined
+ fiat === undefined ||
+ !devSettings
) {
return ;
}
- const network = activeWallet?.network ?? Network.MAINNET;
- const context = {
+ const context: IAppContext = {
api: getApiConfig(config, network),
- auth,
fiat,
- account,
config,
tonendpoint,
standalone,
extension: false,
proFeatures: false,
- ios
+ ios,
+ defaultWalletVersion: WalletVersion.V5R1
};
return (
@@ -246,11 +251,12 @@ export const Loader: FC = () => {
value={() => navigate(AppRoute.home, { replace: true })}
>
-
+
>}>
+
@@ -259,15 +265,16 @@ export const Loader: FC = () => {
};
export const Content: FC<{
- activeWallet?: WalletState | null;
+ activeAccount?: Account | null;
lock: boolean;
standalone: boolean;
-}> = ({ activeWallet, lock, standalone }) => {
+}> = ({ activeAccount, lock, standalone }) => {
const location = useLocation();
useWindowsScroll();
useAppWidth(standalone);
useKeyboardHeight();
useTrackLocation();
+ useDebuggingTools();
if (lock) {
return (
@@ -296,7 +303,7 @@ export const Content: FC<{
);
}
- if (!activeWallet || location.pathname.startsWith(AppRoute.import)) {
+ if (!activeAccount || location.pathname.startsWith(AppRoute.import)) {
return (
}>
@@ -304,7 +311,7 @@ export const Content: FC<{
}
+ element={}
/>
} />
@@ -316,78 +323,76 @@ export const Content: FC<{
return (
-
-
- }>
-
-
- }
- />
- }>
-
-
- }
- />
+
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+ }>
+
+
+ }
+ />
+
}>
-
+ }>
+
}
/>
-
- }>
-
+
+
+
+
+ } />
+
+
+
+ }>
+
- }
- />
-
-
-
-
- } />
-
-
-
- }>
-
-
-
- >
- }
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ >
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/apps/web/src/libs/hooks.ts b/apps/web/src/libs/hooks.ts
index 580b290f3..247f8a6d6 100644
--- a/apps/web/src/libs/hooks.ts
+++ b/apps/web/src/libs/hooks.ts
@@ -1,12 +1,12 @@
import { useQuery } from '@tanstack/react-query';
-import { AccountState } from '@tonkeeper/core/dist/entries/account';
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
+import { Account } from "@tonkeeper/core/dist/entries/account";
import { throttle } from '@tonkeeper/core/dist/utils/common';
import { Analytics, AnalyticsGroup, toWalletType } from '@tonkeeper/uikit/dist/hooks/analytics';
import { AptabaseWeb } from '@tonkeeper/uikit/dist/hooks/analytics/aptabase-web';
import { Gtag } from '@tonkeeper/uikit/dist/hooks/analytics/gtag';
import { QueryKey } from '@tonkeeper/uikit/dist/libs/queryKey';
import { useEffect } from 'react';
+import { useActiveTonNetwork } from "@tonkeeper/uikit/dist/state/wallet";
export const useAppHeight = () => {
useEffect(() => {
@@ -49,12 +49,13 @@ export const useAppWidth = (standalone: boolean) => {
};
export const useAnalytics = (
- account?: AccountState,
- wallet?: WalletState | null,
+ activeAccount?: Account,
+ accounts?: Account[],
version?: string
) => {
+ const network = useActiveTonNetwork();
return useQuery(
- [QueryKey.analytics],
+ [QueryKey.analytics, network],
async () => {
const tracker = new AnalyticsGroup(
new AptabaseWeb(
@@ -65,10 +66,17 @@ export const useAnalytics = (
new Gtag(import.meta.env.VITE_APP_MEASUREMENT_ID)
);
- tracker.init('Web', toWalletType(wallet), account, wallet);
+ tracker.init({
+ application: 'Web',
+ walletType: toWalletType(activeAccount?.activeTonWallet),
+ activeAccount: activeAccount!,
+ accounts: accounts!,
+ network
+ }
+ );
return tracker;
},
- { enabled: account != null }
+ { enabled: accounts != null && activeAccount !== undefined }
);
};
diff --git a/packages/core/src/AppSdk.ts b/packages/core/src/AppSdk.ts
index 0e689da04..83afedfde 100644
--- a/packages/core/src/AppSdk.ts
+++ b/packages/core/src/AppSdk.ts
@@ -3,20 +3,14 @@ import { APIConfig } from './entries/apis';
import { BLOCKCHAIN_NAME } from './entries/crypto';
import { EventEmitter, IEventEmitter } from './entries/eventEmitter';
import { NFT } from './entries/nft';
-import { AuthState } from './entries/password';
import { FavoriteSuggestion, LatestSuggestion } from './entries/suggestion';
-import { WalletState } from './entries/wallet';
import { TonTransferParams } from './service/deeplinkingService';
import { KeystoneMessageType, KeystonePathInfo } from './service/keystone/types';
import { LedgerTransaction } from './service/ledger/connector';
+import { TonWalletStandard } from './entries/wallet';
export type GetPasswordType = 'confirm' | 'unlock';
-export type GetPasswordParams = {
- auth: AuthState;
- type?: GetPasswordType;
-};
-
export type TransferInitParams = {
transfer?: TonTransferParams;
asset?: string;
@@ -38,7 +32,7 @@ export interface UIEvents {
scan: void;
resize: void;
navigate: void;
- getPassword: GetPasswordParams;
+ getPassword: void;
signer: string;
ledger: { path: number[]; transaction: LedgerTransaction };
keystone: { message: Buffer; messageType: KeystoneMessageType; pathInfo?: KeystonePathInfo };
@@ -74,7 +68,7 @@ export interface TouchId {
export interface NotificationService {
subscribe: (
api: APIConfig,
- wallet: WalletState,
+ wallet: TonWalletStandard,
signTonConnect: (bufferToSign: Buffer) => Promise
) => Promise;
unsubscribe: (address?: string) => Promise;
diff --git a/packages/core/src/Keys.ts b/packages/core/src/Keys.ts
index d168a724c..53502a0dd 100644
--- a/packages/core/src/Keys.ts
+++ b/packages/core/src/Keys.ts
@@ -1,15 +1,30 @@
export enum AppKey {
- ACCOUNT = 'account',
- WALLET = 'wallet',
+ /**
+ * @deprecated
+ */
+ DEPRECATED_ACCOUNT = 'account',
+ /**
+ * @deprecated
+ */
+ DEPRECATED_WALLET = 'wallet',
+ /**
+ * @deprecated
+ */
+ DEPRECATED_MNEMONIC = 'mnemonic',
+
+ ACCOUNTS = 'accounts',
+ ACTIVE_ACCOUNT_ID = 'active_account_id',
WALLET_CONFIG = 'wallet_config',
- MNEMONIC = 'mnemonic',
+ GLOBAL_PREFERENCES_CONFIG = 'global_preferences_config',
THEME = 'theme',
UI_PREFERENCES = 'ui_preferences',
MULTI_SEND_LISTS = 'multi_send_lists',
FIAT = 'fiat',
+ LANGUAGE = 'language',
+ DEV_SETTINGS = 'dev_settings',
- GLOBAL_AUTH_STATE = 'password',
+ DEPRECATED_GLOBAL_AUTH_STATE = 'password',
LOCK = 'lock',
TOUCH_ID = 'touch_id',
COUNTRY = 'country',
diff --git a/packages/core/src/entries/account.ts b/packages/core/src/entries/account.ts
index 472933433..efa235aec 100644
--- a/packages/core/src/entries/account.ts
+++ b/packages/core/src/entries/account.ts
@@ -1,8 +1,406 @@
-export interface AccountState {
+// eslint-disable-next-line max-classes-per-file
+import { AuthKeychain, AuthPassword, AuthSigner, AuthSignerDeepLink } from './password';
+import { KeystonePathInfo } from '../service/keystone/types';
+import { DerivationItem, TonWalletStandard, WalletId } from './wallet';
+
+/**
+ * @deprecated
+ */
+export interface DeprecatedAccountState {
publicKeys: string[];
activePublicKey?: string;
}
-export const defaultAccountState: AccountState = {
- publicKeys: []
-};
+export type AccountId = string;
+
+export interface IAccount {
+ id: AccountId;
+ name: string;
+ emoji: string;
+
+ get allTonWallets(): TonWalletStandard[];
+ get activeDerivationTonWallets(): TonWalletStandard[];
+ get activeTonWallet(): TonWalletStandard;
+
+ getTonWallet(id: WalletId): TonWalletStandard | undefined;
+ updateTonWallet(wallet: TonWalletStandard): void;
+ addTonWalletToActiveDerivation(wallet: TonWalletStandard): void;
+ removeTonWalletFromActiveDerivation(walletId: WalletId): void;
+ setActiveTonWallet(walletId: WalletId): void;
+}
+
+export class Clonable {
+ clone() {
+ const cloned = structuredClone(this);
+ Object.setPrototypeOf(cloned, Object.getPrototypeOf(this));
+ return cloned as this;
+ }
+}
+
+export class AccountTonMnemonic extends Clonable implements IAccount {
+ public readonly type = 'mnemonic';
+
+ get allTonWallets() {
+ return this.tonWallets;
+ }
+
+ get activeDerivationTonWallets() {
+ return this.tonWallets;
+ }
+
+ get activeTonWallet() {
+ return this.tonWallets.find(w => w.id === this.activeTonWalletId)!;
+ }
+
+ /**
+ * @param id ton public key hex string without 0x corresponding to the mnemonic
+ */
+ constructor(
+ public readonly id: AccountId,
+ public name: string,
+ public emoji: string,
+ public auth: AuthPassword | AuthKeychain,
+ public activeTonWalletId: WalletId,
+ public tonWallets: TonWalletStandard[]
+ ) {
+ super();
+ }
+
+ getTonWallet(id: WalletId) {
+ return this.allTonWallets.find(w => w.id === id);
+ }
+
+ updateTonWallet(wallet: TonWalletStandard) {
+ const index = this.tonWallets.findIndex(w => w.id === wallet.id)!;
+ if (index === -1) {
+ throw new Error('Wallet not found');
+ }
+ this.tonWallets[index] = wallet;
+ }
+
+ addTonWalletToActiveDerivation(wallet: TonWalletStandard) {
+ const walletExists = this.tonWallets.findIndex(w => w.id === wallet.id);
+ if (walletExists === -1) {
+ this.tonWallets = this.tonWallets.concat(wallet);
+ } else {
+ this.tonWallets[walletExists] = wallet;
+ }
+ }
+
+ removeTonWalletFromActiveDerivation(walletId: WalletId) {
+ if (this.tonWallets.length === 1) {
+ throw new Error('Cannot remove last wallet');
+ }
+
+ this.tonWallets = this.tonWallets.filter(w => w.id !== walletId);
+ if (this.activeTonWalletId === walletId) {
+ this.activeTonWalletId = this.tonWallets[0].id;
+ }
+ }
+
+ setActiveTonWallet(walletId: WalletId) {
+ if (this.tonWallets.every(w => w.id !== walletId)) {
+ throw new Error('Wallet not found');
+ }
+ this.activeTonWalletId = walletId;
+ }
+}
+
+export class AccountLedger extends Clonable implements IAccount {
+ public readonly type = 'ledger';
+
+ get allTonWallets() {
+ return this.derivations.flatMap(d => d.tonWallets);
+ }
+
+ get activeDerivationTonWallets() {
+ return this.activeDerivation.tonWallets;
+ }
+
+ get activeDerivation() {
+ return this.derivations.find(d => this.activeDerivationIndex === d.index)!;
+ }
+
+ get activeTonWallet() {
+ const activeDerivation = this.activeDerivation;
+ return this.activeDerivationTonWallets.find(
+ w => w.id === activeDerivation.activeTonWalletId
+ )!;
+ }
+
+ get derivations(): DerivationItem[] {
+ return this.addedDerivationsIndexes.map(
+ index => this.allAvailableDerivations.find(d => d.index === index)!
+ );
+ }
+
+ /**
+ * @param id index 0 derivation ton public key hex string without 0x
+ */
+ constructor(
+ public readonly id: AccountId,
+ public name: string,
+ public emoji: string,
+ public activeDerivationIndex: number,
+ public addedDerivationsIndexes: number[],
+ public readonly allAvailableDerivations: DerivationItem[]
+ ) {
+ super();
+
+ if (
+ addedDerivationsIndexes.some(index =>
+ allAvailableDerivations.every(d => d.index !== index)
+ )
+ ) {
+ throw new Error('Derivations not found');
+ }
+
+ if (!addedDerivationsIndexes.includes(activeDerivationIndex)) {
+ throw new Error('Active derivation not found');
+ }
+
+ this.addedDerivationsIndexes = [...new Set(addedDerivationsIndexes)];
+ }
+
+ getTonWallet(id: WalletId) {
+ return this.allTonWallets.find(w => w.id === id);
+ }
+
+ updateTonWallet(wallet: TonWalletStandard) {
+ for (const derivation of this.derivations) {
+ const index = derivation.tonWallets.findIndex(w => w.id === wallet.id);
+ if (index !== -1) {
+ derivation.tonWallets[index] = wallet;
+ return;
+ }
+ }
+
+ throw new Error('Derivation not found');
+ }
+
+ addTonWalletToActiveDerivation(wallet: TonWalletStandard) {
+ const walletExists = this.activeDerivation.tonWallets.findIndex(w => w.id === wallet.id);
+ if (walletExists === -1) {
+ this.activeDerivation.tonWallets = this.activeDerivation.tonWallets.concat(wallet);
+ } else {
+ this.activeDerivation.tonWallets[walletExists] = wallet;
+ }
+ }
+
+ removeTonWalletFromActiveDerivation(walletId: WalletId) {
+ if (this.activeDerivation.tonWallets.length === 1) {
+ throw new Error('Cannot remove last wallet');
+ }
+
+ this.activeDerivation.tonWallets = this.activeDerivation.tonWallets.filter(
+ w => w.id !== walletId
+ );
+ if (this.activeDerivation.activeTonWalletId === walletId) {
+ this.activeDerivation.activeTonWalletId = this.activeDerivation.tonWallets[0].id;
+ }
+ }
+
+ setActiveTonWallet(walletId: WalletId) {
+ for (const derivation of this.derivations) {
+ const walletInDerivation = derivation.tonWallets.some(w => w.id === walletId);
+ if (walletInDerivation) {
+ derivation.activeTonWalletId = walletId;
+ this.activeDerivationIndex = derivation.index;
+ return;
+ }
+ }
+
+ throw new Error('Derivation not found');
+ }
+
+ setActiveDerivationIndex(index: number) {
+ if (!this.addedDerivationsIndexes.includes(index)) {
+ throw new Error('Derivation not found');
+ }
+
+ this.activeDerivationIndex = index;
+ }
+
+ setAddedDerivationsIndexes(addedDerivationsIndexes: number[]) {
+ if (addedDerivationsIndexes.length === 0) {
+ throw new Error('Cant set empty derivations');
+ }
+ if (
+ addedDerivationsIndexes.some(index =>
+ this.allAvailableDerivations.every(d => d.index !== index)
+ )
+ ) {
+ throw new Error('Derivations not found');
+ }
+ this.addedDerivationsIndexes = [...new Set(addedDerivationsIndexes)];
+ if (!this.addedDerivationsIndexes.includes(this.activeDerivationIndex)) {
+ this.activeDerivationIndex = this.addedDerivationsIndexes[0];
+ }
+ }
+}
+
+export class AccountKeystone extends Clonable implements IAccount {
+ public readonly type = 'keystone';
+
+ get allTonWallets() {
+ return [this.tonWallet];
+ }
+
+ get activeDerivationTonWallets() {
+ return [this.tonWallet];
+ }
+
+ get activeTonWallet() {
+ return this.tonWallet;
+ }
+
+ /**
+ * @param id ton public key hex string without 0x
+ */
+ constructor(
+ public readonly id: AccountId,
+ public name: string,
+ public emoji: string,
+ public readonly pathInfo: KeystonePathInfo | undefined,
+ public tonWallet: TonWalletStandard
+ ) {
+ super();
+ }
+
+ getTonWallet(id: WalletId) {
+ return this.allTonWallets.find(w => w.id === id);
+ }
+
+ updateTonWallet(wallet: TonWalletStandard) {
+ this.tonWallet = wallet;
+ }
+
+ addTonWalletToActiveDerivation() {
+ throw new Error('Cannot add ton wallet to keystone account');
+ }
+
+ removeTonWalletFromActiveDerivation() {
+ throw new Error('Cannot remove ton wallet from keystone account');
+ }
+
+ setActiveTonWallet(walletId: WalletId) {
+ if (walletId !== this.tonWallet.id) {
+ throw new Error('Cannot add ton wallet to keystone account');
+ }
+ }
+}
+
+export class AccountTonOnly extends Clonable implements IAccount {
+ public readonly type = 'ton-only';
+
+ get allTonWallets() {
+ return this.tonWallets;
+ }
+
+ get activeDerivationTonWallets() {
+ return this.tonWallets;
+ }
+
+ get activeTonWallet() {
+ return this.tonWallets.find(w => w.id === this.activeTonWalletId)!;
+ }
+
+ /**
+ * @param id ton public key hex string without 0x
+ */
+ constructor(
+ public readonly id: AccountId,
+ public name: string,
+ public emoji: string,
+ public readonly auth: AuthSigner | AuthSignerDeepLink,
+ public activeTonWalletId: WalletId,
+ public tonWallets: TonWalletStandard[]
+ ) {
+ super();
+ }
+
+ getTonWallet(id: WalletId) {
+ return this.allTonWallets.find(w => w.id === id);
+ }
+
+ updateTonWallet(wallet: TonWalletStandard) {
+ const index = this.tonWallets.findIndex(w => w.id === wallet.id)!;
+ if (index === -1) {
+ throw new Error('Wallet not found');
+ }
+ this.tonWallets[index] = wallet;
+ }
+
+ addTonWalletToActiveDerivation(wallet: TonWalletStandard) {
+ const walletExists = this.tonWallets.findIndex(w => w.id === wallet.id);
+ if (walletExists === -1) {
+ this.tonWallets = this.tonWallets.concat(wallet);
+ } else {
+ this.tonWallets[walletExists] = wallet;
+ }
+ }
+
+ removeTonWalletFromActiveDerivation(walletId: WalletId) {
+ if (this.tonWallets.length === 1) {
+ throw new Error('Cannot remove last wallet');
+ }
+
+ this.tonWallets = this.tonWallets.filter(w => w.id !== walletId);
+ if (this.activeTonWalletId === walletId) {
+ this.activeTonWalletId = this.tonWallets[0].id;
+ }
+ }
+
+ setActiveTonWallet(walletId: WalletId) {
+ if (this.tonWallets.every(w => w.id !== walletId)) {
+ throw new Error('Wallet not found');
+ }
+
+ this.activeTonWalletId = walletId;
+ }
+}
+
+export type Account = AccountTonMnemonic | AccountLedger | AccountKeystone | AccountTonOnly;
+
+export type AccountsState = Account[];
+
+export const defaultAccountState: AccountsState = [];
+
+export function serializeAccount(account: Account): string {
+ return JSON.stringify(account);
+}
+
+const prototypes = {
+ mnemonic: AccountTonMnemonic.prototype,
+ ledger: AccountLedger.prototype,
+ keystone: AccountKeystone.prototype,
+ 'ton-only': AccountTonOnly.prototype
+} as const;
+
+export function bindAccountToClass(accountStruct: Account): void {
+ Object.setPrototypeOf(accountStruct, prototypes[accountStruct.type]);
+}
+
+export function getWalletById(
+ accounts: Account[],
+ walletId: WalletId
+): TonWalletStandard | undefined {
+ for (const account of accounts || []) {
+ const wallet = account.getTonWallet(walletId);
+ if (wallet) {
+ return wallet;
+ }
+ }
+}
+
+export function getAccountByWalletById(
+ accounts: Account[],
+ walletId: WalletId
+): Account | undefined {
+ for (const account of accounts || []) {
+ const wallet = account.getTonWallet(walletId);
+ if (wallet) {
+ return account;
+ }
+ }
+}
diff --git a/packages/core/src/entries/network.ts b/packages/core/src/entries/network.ts
index 046c2ca1c..2ef914b32 100644
--- a/packages/core/src/entries/network.ts
+++ b/packages/core/src/entries/network.ts
@@ -40,3 +40,4 @@ export const getApiConfig = (config: TonendpointConfig, network?: Network, TonCo
tronApi: getTronClient(network)
};
};
+
diff --git a/packages/core/src/entries/password.ts b/packages/core/src/entries/password.ts
index f8fce908f..0ee8f96e3 100644
--- a/packages/core/src/entries/password.ts
+++ b/packages/core/src/entries/password.ts
@@ -1,25 +1,23 @@
-import { KeystonePathInfo } from "../service/keystone/types";
+import { KeystonePathInfo } from '../service/keystone/types';
export type AuthState =
- | AuthNone
| AuthPassword
- | WebAuthn
- | KeychainPassword
+ | AuthKeychain
| AuthSigner
| AuthSignerDeepLink
| AuthLedger
| AuthKeystone;
-export interface AuthNone {
- kind: 'none';
-}
-
export interface AuthPassword {
kind: 'password';
+ encryptedMnemonic: string;
}
-export interface KeychainPassword {
+export interface AuthKeychain {
kind: 'keychain';
+
+ // currently eq to publicKey
+ keychainStoreKey: string;
}
export interface AuthSigner {
@@ -27,7 +25,7 @@ export interface AuthSigner {
}
export interface AuthSignerDeepLink {
- kind: 'signer-deeplink';
+ kind: 'signer-deeplink';
}
export interface AuthLedger {
@@ -40,11 +38,35 @@ export interface AuthKeystone {
info?: KeystonePathInfo;
}
-export interface WebAuthn {
- kind: 'webauthn';
- type: 'largeBlob' | 'credBlob' | 'userHandle';
- credentialId: string;
- transports?: AuthenticatorTransport[];
+/**
+ * @deprecated
+ */
+export type DeprecatedAuthState =
+ | DeprecatedAuthNone
+ | DeprecatedAuthPassword
+ | DeprecatedKeychainPassword
+ | AuthSigner
+ | AuthSignerDeepLink
+ | AuthLedger
+ | AuthKeystone;
+
+/**
+ * @deprecated
+ */
+export interface DeprecatedAuthNone {
+ kind: 'none';
}
-export const defaultAuthState: AuthState = { kind: 'none' };
+/**
+ * @deprecated
+ */
+export interface DeprecatedAuthPassword {
+ kind: 'password';
+}
+
+/**
+ * @deprecated
+ */
+export interface DeprecatedKeychainPassword {
+ kind: 'keychain';
+}
diff --git a/packages/core/src/entries/wallet.ts b/packages/core/src/entries/wallet.ts
index 72aa9203a..01a12d7b6 100644
--- a/packages/core/src/entries/wallet.ts
+++ b/packages/core/src/entries/wallet.ts
@@ -1,6 +1,6 @@
import { Language } from './language';
import { Network } from './network';
-import { AuthState } from './password';
+import { DeprecatedAuthState } from './password';
import { WalletProxy } from './proxy';
export enum WalletVersion {
@@ -8,18 +8,47 @@ export enum WalletVersion {
V3R2 = 1,
V4R1 = 2,
V4R2 = 3,
- V5beta = 4,
+ V5_BETA = 4,
V5R1 = 5
}
+export function sortWalletsByVersion(
+ w1: { version: WalletVersion },
+ w2: { version: WalletVersion }
+) {
+ if (w1.version < w2.version) {
+ return 1;
+ }
+ if (w1.version > w2.version) {
+ return -1;
+ }
+ return 0;
+}
+
+export function sortDerivationsByIndex(w1: { index: number }, w2: { index: number }) {
+ if (w1.index < w2.index) {
+ return -1;
+ }
+ if (w1.index > w2.index) {
+ return 1;
+ }
+ return 0;
+}
+
+export const isW5Version = (version: WalletVersion) => {
+ return version === WalletVersion.V5_BETA || version === WalletVersion.V5R1;
+};
+
export const WalletVersions = [
WalletVersion.V3R1,
WalletVersion.V3R2,
WalletVersion.V4R2,
- WalletVersion.V5beta,
+ WalletVersion.V5_BETA,
WalletVersion.V5R1
];
+export const backwardCompatibilityOnlyWalletVersions = [WalletVersion.V5_BETA];
+
export const walletVersionText = (version: WalletVersion) => {
switch (version) {
case WalletVersion.V3R1:
@@ -28,7 +57,7 @@ export const walletVersionText = (version: WalletVersion) => {
return 'v3R2';
case WalletVersion.V4R2:
return 'v4R2';
- case WalletVersion.V5beta:
+ case WalletVersion.V5_BETA:
return 'W5 beta';
case WalletVersion.V5R1:
return 'W5';
@@ -37,7 +66,10 @@ export const walletVersionText = (version: WalletVersion) => {
}
};
-export interface WalletAddress {
+/**
+ * @deprecated
+ */
+export interface DeprecatedWalletAddress {
friendlyAddress: string;
rawAddress: string;
version: WalletVersion;
@@ -50,10 +82,13 @@ export interface WalletVoucher {
voucher: string;
}
-export interface WalletState {
+/**
+ * @deprecated, use WalletsState instead
+ */
+export interface DeprecatedWalletState {
publicKey: string;
- active: WalletAddress;
- auth?: AuthState;
+ active: DeprecatedWalletAddress;
+ auth?: DeprecatedAuthState;
name?: string;
emoji: string;
@@ -74,7 +109,30 @@ export interface WalletState {
tron?: TronWalletStorage;
}
-export interface ActiveWalletConfig {
+export type WalletId = string;
+
+export type TonContract = {
+ id: WalletId;
+ rawAddress: string; // rawAddress
+};
+
+export type TonWalletStandard = TonContract & {
+ publicKey: string;
+ version: WalletVersion;
+};
+
+export type DerivationItem = {
+ index: number;
+ activeTonWalletId: WalletId;
+ tonWallets: TonWalletStandard[];
+ // tronWallets: never;
+};
+
+export function isStandardTonWallet(wallet: TonContract): wallet is TonWalletStandard {
+ return 'version' in wallet && 'publicKey' in wallet;
+}
+
+export interface TonWalletConfig {
pinnedTokens: string[];
hiddenTokens: string[];
pinnedNfts: string[];
@@ -83,6 +141,15 @@ export interface ActiveWalletConfig {
spamNfts: string[];
}
+export const defaultPreferencesConfig: TonWalletConfig = {
+ pinnedTokens: [],
+ hiddenTokens: [],
+ pinnedNfts: [],
+ hiddenNfts: [],
+ trustedNfts: [],
+ spamNfts: []
+};
+
export interface TronWalletStorage {
ownerWalletAddress: string;
walletByChain: Record;
diff --git a/packages/core/src/service/accountService.ts b/packages/core/src/service/accountService.ts
deleted file mode 100644
index 2dc08a516..000000000
--- a/packages/core/src/service/accountService.ts
+++ /dev/null
@@ -1,195 +0,0 @@
-import { AccountState, defaultAccountState } from '../entries/account';
-import { AuthState } from '../entries/password';
-import { WalletState } from '../entries/wallet';
-import { AppKey } from '../Keys';
-import { IStorage } from '../Storage';
-import { encrypt } from './cryptoService';
-import { getWalletMnemonic, validateWalletMnemonic } from './mnemonicService';
-import { getWalletStateOrDie } from './wallet/storeService';
-
-export const getAccountState = async (storage: IStorage) => {
- const state = await storage.get(AppKey.ACCOUNT);
- return state ?? defaultAccountState;
-};
-
-const accountAppendWallet = async (account: AccountState, publicKey: string) => {
- return {
- publicKeys: account.publicKeys.includes(publicKey)
- ? account.publicKeys
- : account.publicKeys.concat([publicKey]),
- activePublicKey: publicKey
- };
-};
-
-export const addWalletWithCustomAuthState = async (storage: IStorage, state: WalletState) => {
- return addWalletsWithCustomAuthState(storage, [state]);
-};
-
-export const preventDuplicatedWallet = async (storage: IStorage, state: WalletState) => {
- const account = await getAccountState(storage);
- if (account.publicKeys.includes(state.publicKey)) {
- throw new Error('Wallet already exist');
- }
-};
-
-export const addWalletsWithCustomAuthState = async (
- storage: IStorage,
- states: WalletState[],
- options?: {
- activePublicKey?: string;
- keepName?: boolean;
- }
-) => {
- const account = await getAccountState(storage);
-
- const pksToConcat = states
- .filter(s => !account.publicKeys.includes(s.publicKey))
- .map(s => s.publicKey);
- const updatedAccount = {
- publicKeys: account.publicKeys.concat(pksToConcat),
- activePublicKey: options?.activePublicKey ?? states[0].publicKey
- };
-
- const walletsUpdates = states.reduce((acc, s) => {
- let name = s.name;
- if (!options?.keepName) {
- name = account.publicKeys.includes(s.publicKey) ? undefined : s.name;
- }
-
- if (!('auth' in s)) {
- throw new Error('Missing wallet auth state.');
- }
-
- return { ...acc, [`${AppKey.WALLET}_${s.publicKey}`]: { ...s, name } };
- }, {});
-
- await storage.setBatch({
- [AppKey.ACCOUNT]: updatedAccount,
- ...walletsUpdates
- });
-};
-
-export const addWalletWithGlobalAuthState = async (
- storage: IStorage,
- state: WalletState,
- auth: AuthState,
- encryptedMnemonic?: string
-) => {
- const account = await getAccountState(storage);
- const updatedAccount = await accountAppendWallet(account, state.publicKey);
- if (account.publicKeys.includes(state.publicKey)) {
- await storage.setBatch({
- [AppKey.ACCOUNT]: updatedAccount,
- [AppKey.GLOBAL_AUTH_STATE]: auth,
- [`${AppKey.WALLET}_${state.publicKey}`]: { ...state, name: undefined }
- });
- } else {
- const data = {
- [AppKey.ACCOUNT]: updatedAccount,
- [AppKey.GLOBAL_AUTH_STATE]: auth,
- [`${AppKey.WALLET}_${state.publicKey}`]: state
- };
-
- if (encryptedMnemonic) {
- Object.assign(data, { [`${AppKey.MNEMONIC}_${state.publicKey}`]: encryptedMnemonic });
- }
-
- await storage.setBatch(data);
- }
-};
-
-export const accountSelectWallet = async (storage: IStorage, publicKey: string) => {
- const account = await getAccountState(storage);
- const updated = {
- publicKeys: account.publicKeys,
- activePublicKey: publicKey
- };
- await storage.set(AppKey.ACCOUNT, updated);
-};
-
-export const accountLogOutWallet = async (
- storage: IStorage,
- publicKey: string,
- removeRemove = false
-) => {
- if (removeRemove) {
- //await deleteWalletBackup(tonApi, publicKey);
- }
-
- const account = await getAccountState(storage);
-
- const publicKeys = account.publicKeys.filter(key => key !== publicKey);
- const updatedAccount = {
- publicKeys,
- activePublicKey: publicKeys.length > 0 ? publicKeys[0] : undefined
- };
-
- if (updatedAccount.publicKeys.length === 0) {
- await storage.setBatch({
- [AppKey.ACCOUNT]: null,
- [AppKey.GLOBAL_AUTH_STATE]: null,
- [`${AppKey.WALLET}_${publicKey}`]: null,
- [`${AppKey.MNEMONIC}_${publicKey}`]: null
- });
- } else {
- await storage.setBatch({
- [AppKey.ACCOUNT]: updatedAccount,
- [`${AppKey.WALLET}_${publicKey}`]: null,
- [`${AppKey.MNEMONIC}_${publicKey}`]: null
- });
- }
-};
-
-export const accountChangePassword = async (
- storage: IStorage,
- options: { old: string; password: string; confirm: string }
-) => {
- const account = await getAccountState(storage);
-
- const isValid = await validateWalletMnemonic(storage, account.publicKeys[0], options.old);
- if (!isValid) {
- return 'invalid-old';
- }
- const error = accountValidatePassword(options.password, options.confirm);
- if (error) {
- return error;
- }
-
- const updated = {} as Record;
- for (const publicKey of account.publicKeys) {
- const mnemonic = await getWalletMnemonic(storage, publicKey, options.old);
- updated[`${AppKey.MNEMONIC}_${publicKey}`] = await encrypt(
- mnemonic.join(' '),
- options.password
- );
- }
- await storage.setBatch(updated);
-};
-
-export const MinPasswordLength = 6;
-
-export const accountValidatePassword = (password: string, confirm: string) => {
- if (password.length < MinPasswordLength) {
- return 'invalid-password';
- }
- if (password !== confirm) {
- return 'invalid-confirm';
- }
- return undefined;
-};
-
-export const getWalletWithGlobalAuth = async (storage: IStorage) => {
- const account = await getAccountState(storage);
- if (account.publicKeys.length === 0) {
- throw new Error('Missing wallets');
- }
-
- for (const key of account.publicKeys) {
- const wallet = await getWalletStateOrDie(storage, key);
- if (wallet.auth === undefined) {
- return key;
- }
- }
-
- throw new Error('Missing wallet with global auth.');
-};
diff --git a/packages/core/src/service/accountsStorage.ts b/packages/core/src/service/accountsStorage.ts
new file mode 100644
index 000000000..02f75de2f
--- /dev/null
+++ b/packages/core/src/service/accountsStorage.ts
@@ -0,0 +1,265 @@
+import { AppKey } from '../Keys';
+import { IStorage } from '../Storage';
+import { DeprecatedWalletState, TonWalletStandard, WalletId } from '../entries/wallet';
+
+import {
+ Account,
+ AccountId,
+ AccountKeystone,
+ AccountLedger,
+ AccountsState,
+ AccountTonMnemonic,
+ AccountTonOnly,
+ defaultAccountState,
+ bindAccountToClass
+} from '../entries/account';
+
+import { DeprecatedAccountState } from '../entries/account';
+import { AuthState, DeprecatedAuthState } from '../entries/password';
+import { assertUnreachable, notNullish } from '../utils/types';
+import { getFallbackAccountEmoji } from './walletService';
+
+export class AccountsStorage {
+ constructor(private storage: IStorage) {}
+
+ getAccounts = async () => {
+ let state = await this.storage.get(AppKey.ACCOUNTS);
+ if (!state) {
+ state = await migrateToAccountsState(this.storage);
+ if (state) {
+ await this.setAccounts(state);
+ }
+ } else {
+ state.forEach(bindAccountToClass);
+ }
+ return state ?? defaultAccountState;
+ };
+
+ setAccounts = async (state: AccountsState) => {
+ await this.storage.set(AppKey.ACCOUNTS, state);
+ };
+
+ getActiveAccountId = async () => {
+ let state = await this.storage.get(AppKey.ACTIVE_ACCOUNT_ID);
+ if (!state) {
+ state = await this.migrateToActiveAccountIdState();
+ if (state !== null) {
+ await this.setActiveAccountId(state);
+ }
+ }
+ return state ?? null;
+ };
+
+ getActiveAccount = async (): Promise => {
+ const id = await this.getActiveAccountId();
+ if (id !== null) {
+ const state = await this.getAccounts();
+ return state.find(a => a.id === id) || null;
+ }
+ return null;
+ };
+
+ getAccount = async (id: AccountId): Promise => {
+ const state = await this.getAccounts();
+ return state.find(a => a.id === id) || null;
+ };
+
+ setActiveAccountId = async (activeAccountId: AccountId | null) => {
+ const accounts = await this.getAccounts();
+ if (accounts.every(a => a.id !== activeAccountId)) {
+ throw new Error('Account not found');
+ }
+ await this.storage.set(AppKey.ACTIVE_ACCOUNT_ID, activeAccountId);
+ };
+
+ addAccountToState = async (account: Account) => {
+ await this.addAccountsToState([account]);
+ };
+
+ addAccountsToState = async (accounts: Account[]) => {
+ const state = await this.getAccounts();
+ accounts.forEach(account => {
+ const existingAccIndex = state.findIndex(a => a.id === account.id);
+ if (existingAccIndex !== -1) {
+ state[existingAccIndex] = account;
+ return;
+ }
+ state.push(account);
+ });
+ await this.setAccounts(state);
+ };
+
+ /**
+ * Replace found wallets with same id in state and replace them with new ones with no array order changes
+ */
+ updateAccountsInState = async (accounts: Account[]) => {
+ const state = await this.getAccounts();
+
+ for (let i = 0; i < state.length; i++) {
+ const account = accounts.find(a => a.id === state[i].id);
+ if (!account) {
+ continue;
+ }
+
+ state[i] = account;
+ }
+
+ await this.setAccounts(state);
+ };
+
+ /**
+ * Replace found wallet with same id in state and replace it with new one with no array order changes
+ */
+ updateAccountInState = async (account: Account) => {
+ return this.updateAccountsInState([account]);
+ };
+
+ removeAccountsFromState = async (ids: AccountId[]) => {
+ const state = await this.getAccounts();
+ const activeAccountId = await this.getActiveAccountId();
+
+ const newState = state.filter(w => !ids.includes(w.id));
+
+ if (activeAccountId !== null && ids.includes(activeAccountId)) {
+ await this.setActiveAccountId(newState[0]?.id || null);
+ }
+
+ await this.setAccounts(newState);
+ };
+
+ removeAccountFromState = async (id: AccountId) => {
+ return this.removeAccountsFromState([id]);
+ };
+
+ async getNewAccountNameAndEmoji(accountId: AccountId) {
+ const existingAccounts = await this.getAccounts();
+ const existingAccount = existingAccounts.find(a => a.id === accountId);
+ const name = existingAccount?.name || 'Account ' + (existingAccounts.length + 1);
+ const emoji = existingAccount?.emoji || getFallbackAccountEmoji(accountId);
+ return { name, emoji };
+ }
+
+ private migrateToActiveAccountIdState = async (): Promise => {
+ const state = await this.storage.get(AppKey.DEPRECATED_ACCOUNT);
+ if (!state || !state.activePublicKey) {
+ return null;
+ }
+
+ const accounts = await this.getAccounts();
+ return (
+ accounts.find(a => a.allTonWallets.some(w => w.publicKey === state.activePublicKey))
+ ?.id || null
+ );
+ };
+}
+
+export const accountsStorage = (storage: IStorage): AccountsStorage => new AccountsStorage(storage);
+
+async function migrateToAccountsState(storage: IStorage): Promise {
+ const state = await storage.get(AppKey.DEPRECATED_ACCOUNT);
+ if (!state) {
+ return null;
+ }
+
+ const accounts: (Account | null)[] = await Promise.all(
+ state.publicKeys.map(async (pk, index) => {
+ const w = await storage.get(`${AppKey.DEPRECATED_WALLET}_${pk}`);
+ if (!w) {
+ return null;
+ }
+
+ let auth: AuthState;
+ let walletAuth = w.auth;
+ if (!walletAuth) {
+ walletAuth =
+ (await storage.get(AppKey.DEPRECATED_GLOBAL_AUTH_STATE)) ??
+ undefined;
+ }
+
+ if (!walletAuth) {
+ console.error('Wallet without auth detected', w.active.friendlyAddress);
+ return null;
+ }
+
+ if (walletAuth.kind === 'none') {
+ console.error('NONE AUTH detected for wallet', w.active.friendlyAddress);
+ return null;
+ }
+
+ if (walletAuth.kind === 'password') {
+ const encryptedMnemonic = await storage.get(
+ `${AppKey.DEPRECATED_MNEMONIC}_${pk}`
+ );
+
+ if (!encryptedMnemonic) {
+ console.error('Wallet without mnemonic detected', w.active.friendlyAddress);
+ return null;
+ }
+
+ auth = {
+ kind: walletAuth.kind,
+ encryptedMnemonic
+ };
+ } else if (walletAuth.kind === 'keychain') {
+ auth = {
+ kind: 'keychain',
+ keychainStoreKey: w.publicKey
+ };
+ } else {
+ auth = walletAuth;
+ }
+
+ const name = w.name || 'Account ' + (index + 1);
+ const emoji = w.emoji;
+
+ const tonWallet: TonWalletStandard = {
+ id: w.active.rawAddress,
+ publicKey: w.publicKey,
+ version: w.active.version,
+ rawAddress: w.active.rawAddress
+ };
+
+ const authKind = auth.kind;
+ switch (authKind) {
+ case 'password':
+ case 'keychain':
+ return new AccountTonMnemonic(
+ w.publicKey,
+ name,
+ emoji,
+ auth,
+ w.active.rawAddress,
+ [tonWallet]
+ );
+ case 'signer':
+ case 'signer-deeplink':
+ return new AccountTonOnly(w.publicKey, name, emoji, auth, w.active.rawAddress, [
+ tonWallet
+ ]);
+
+ case 'keystone':
+ return new AccountKeystone(w.publicKey, name, emoji, auth.info, tonWallet);
+
+ case 'ledger':
+ return new AccountLedger(
+ w.publicKey,
+ name,
+ emoji,
+ auth.accountIndex,
+ [auth.accountIndex],
+ [
+ {
+ index: auth.accountIndex,
+ activeTonWalletId: tonWallet.rawAddress,
+ tonWallets: [tonWallet]
+ }
+ ]
+ );
+ default:
+ assertUnreachable(authKind);
+ }
+ })
+ );
+
+ return accounts.filter(notNullish);
+}
diff --git a/packages/core/src/service/devStorage.ts b/packages/core/src/service/devStorage.ts
new file mode 100644
index 000000000..fbcd909e2
--- /dev/null
+++ b/packages/core/src/service/devStorage.ts
@@ -0,0 +1,24 @@
+import { IStorage } from '../Storage';
+import { Network } from '../entries/network';
+import { AppKey } from '../Keys';
+
+export interface DevSettings {
+ tonNetwork: Network;
+}
+
+const defaultDevSettings: DevSettings = {
+ tonNetwork: Network.MAINNET
+};
+
+export const getDevSettings = async (storage: IStorage) => {
+ const settings = await storage.get(AppKey.DEV_SETTINGS);
+ return {
+ ...defaultDevSettings,
+ ...settings
+ };
+};
+
+export const setDevSettings = async (storage: IStorage, settings: Partial) => {
+ const current = await getDevSettings(storage);
+ await storage.set(AppKey.DEV_SETTINGS, { ...current, ...settings });
+};
diff --git a/packages/core/src/service/ledger/transfer.ts b/packages/core/src/service/ledger/transfer.ts
index 15f411e1c..e242d3d52 100644
--- a/packages/core/src/service/ledger/transfer.ts
+++ b/packages/core/src/service/ledger/transfer.ts
@@ -1,4 +1,4 @@
-import { WalletState } from '../../entries/wallet';
+import { AccountLedger } from '../../entries/account';
import { TonRecipientData } from '../../entries/send';
import BigNumber from 'bignumber.js';
import {
@@ -10,7 +10,6 @@ import {
} from '../transfer/common';
import { Address, Cell } from '@ton/core';
import { getLedgerAccountPathByIndex } from './utils';
-import { AuthLedger } from '../../entries/password';
import { walletContractFromState } from '../wallet/contractService';
import { AssetAmount } from '../../entries/crypto/asset/asset-amount';
import { TonAsset } from '../../entries/crypto/asset/ton-asset';
@@ -21,13 +20,14 @@ import { LedgerSigner } from '../../entries/signer';
export const createLedgerTonTransfer = async (
timestamp: number,
seqno: number,
- walletState: WalletState,
+ account: AccountLedger,
recipient: TonRecipientData,
weiAmount: BigNumber,
isMax: boolean,
signer: LedgerSigner
) => {
- const path = getLedgerAccountPathByIndex((walletState.auth as AuthLedger).accountIndex);
+ const path = getLedgerAccountPathByIndex(account.activeDerivationIndex);
+ const walletState = account.activeTonWallet;
const contract = walletContractFromState(walletState);
const transfer = await signer(path, {
@@ -48,7 +48,7 @@ export const createLedgerTonTransfer = async (
export const createLedgerJettonTransfer = async (
timestamp: number,
seqno: number,
- walletState: WalletState,
+ account: AccountLedger,
recipientAddress: string,
amount: AssetAmount,
jettonWalletAddress: string,
@@ -56,8 +56,9 @@ export const createLedgerJettonTransfer = async (
signer: LedgerSigner
) => {
const jettonAmount = BigInt(amount.stringWeiAmount);
- const path = getLedgerAccountPathByIndex((walletState.auth as AuthLedger).accountIndex);
- const contract = walletContractFromState(walletState);
+ const path = getLedgerAccountPathByIndex(account.activeDerivationIndex);
+ const wallet = account.activeTonWallet;
+ const contract = walletContractFromState(wallet);
const transfer = await signer(path, {
to: Address.parse(jettonWalletAddress),
@@ -72,7 +73,7 @@ export const createLedgerJettonTransfer = async (
queryId: getTonkeeperQueryId(),
amount: jettonAmount,
destination: Address.parse(recipientAddress),
- responseDestination: Address.parse(walletState.active.rawAddress),
+ responseDestination: Address.parse(wallet.rawAddress),
forwardAmount: jettonTransferForwardAmount,
forwardPayload,
customPayload: null
@@ -85,14 +86,15 @@ export const createLedgerJettonTransfer = async (
export const createLedgerNftTransfer = async (
timestamp: number,
seqno: number,
- walletState: WalletState,
+ account: AccountLedger,
recipientAddress: string,
nftAddress: string,
nftTransferAmount: bigint,
forwardPayload: Cell | null,
signer: LedgerSigner
) => {
- const path = getLedgerAccountPathByIndex((walletState.auth as AuthLedger).accountIndex);
+ const path = getLedgerAccountPathByIndex(account.activeDerivationIndex);
+ const walletState = account.activeTonWallet;
const contract = walletContractFromState(walletState);
const transfer = await signer(path, {
@@ -106,7 +108,7 @@ export const createLedgerNftTransfer = async (
type: 'nft-transfer',
queryId: getTonkeeperQueryId(),
newOwner: Address.parse(recipientAddress),
- responseDestination: Address.parse(walletState.active.rawAddress),
+ responseDestination: Address.parse(walletState.rawAddress),
forwardAmount: nftTransferForwardAmount,
forwardPayload,
customPayload: null
diff --git a/packages/core/src/service/mnemonicService.ts b/packages/core/src/service/mnemonicService.ts
index 161f6dd77..a7a078f58 100644
--- a/packages/core/src/service/mnemonicService.ts
+++ b/packages/core/src/service/mnemonicService.ts
@@ -1,14 +1,9 @@
import { mnemonicValidate } from '@ton/crypto';
-import { AppKey } from '../Keys';
-import { IStorage } from '../Storage';
import { decrypt } from './cryptoService';
+import { AuthPassword } from '../entries/password';
-export const getWalletMnemonic = async (storage: IStorage, publicKey: string, password: string) => {
- const encryptedMnemonic = await storage.get(`${AppKey.MNEMONIC}_${publicKey}`);
- if (!encryptedMnemonic) {
- throw new Error('Wallet mnemonic not fount');
- }
- const mnemonic = (await decrypt(encryptedMnemonic, password)).split(' ');
+export const decryptWalletMnemonic = async (state: { auth: AuthPassword }, password: string) => {
+ const mnemonic = (await decrypt(state.auth.encryptedMnemonic, password)).split(' ');
const isValid = await mnemonicValidate(mnemonic);
if (!isValid) {
throw new Error('Wallet mnemonic not valid');
@@ -16,16 +11,3 @@ export const getWalletMnemonic = async (storage: IStorage, publicKey: string, pa
return mnemonic;
};
-
-export const validateWalletMnemonic = async (
- storage: IStorage,
- publicKey: string,
- password: string
-) => {
- try {
- await getWalletMnemonic(storage, publicKey, password);
- return true;
- } catch (e) {
- return false;
- }
-};
diff --git a/packages/core/src/service/passwordService.ts b/packages/core/src/service/passwordService.ts
new file mode 100644
index 000000000..5d74ec787
--- /dev/null
+++ b/packages/core/src/service/passwordService.ts
@@ -0,0 +1,79 @@
+import { IStorage } from '../Storage';
+import { decrypt, encrypt } from './cryptoService';
+import { mnemonicValidate } from '@ton/crypto';
+import { decryptWalletMnemonic } from './mnemonicService';
+import { AccountsStorage } from './accountsStorage';
+import { AuthPassword } from '../entries/password';
+import { AccountTonMnemonic } from '../entries/account';
+
+export class PasswordStorage {
+ private readonly accountsStorage: AccountsStorage;
+
+ constructor(storage: IStorage) {
+ this.accountsStorage = new AccountsStorage(storage);
+ }
+
+ async getIsPasswordSet() {
+ const wallets = await this.getPasswordAuthAccounts();
+ return wallets.length > 0;
+ }
+
+ async isPasswordValid(password: string): Promise {
+ try {
+ const accToCheck = (await this.getPasswordAuthAccounts())[0];
+ if (!accToCheck) {
+ throw new Error('None wallet has a password auth');
+ }
+
+ const mnemonic = (
+ await decrypt((accToCheck.auth as AuthPassword).encryptedMnemonic, password)
+ ).split(' ');
+ return await mnemonicValidate(mnemonic);
+ } catch (e) {
+ console.error(e);
+ return false;
+ }
+ }
+
+ async checkPassword(password: string): Promise {
+ const isValid = await this.isPasswordValid(password);
+ if (!isValid) {
+ throw new Error('Invalid password');
+ }
+ }
+
+ async updatePassword(oldPassword: string, newPassword: string): Promise {
+ const accounts = await this.getPasswordAuthAccounts();
+
+ const updatedAccounts = await Promise.all(
+ accounts.map(async acc => {
+ const mnemonic = await decryptWalletMnemonic(
+ acc as { auth: AuthPassword },
+ oldPassword
+ );
+ (acc.auth as AuthPassword).encryptedMnemonic = await encrypt(
+ mnemonic.join(' '),
+ newPassword
+ );
+ return acc.clone();
+ })
+ );
+
+ await this.accountsStorage.updateAccountsInState(updatedAccounts);
+ }
+
+ private async getPasswordAuthAccounts(): Promise {
+ const accounts = await this.accountsStorage.getAccounts();
+ return accounts.filter(
+ a => a.type === 'mnemonic' && a.auth.kind === 'password'
+ ) as AccountTonMnemonic[];
+ }
+}
+
+export const MinPasswordLength = 6;
+
+export function validatePassword(password: string) {
+ return password.length >= MinPasswordLength;
+}
+
+export const passwordStorage = (storage: IStorage): PasswordStorage => new PasswordStorage(storage);
diff --git a/packages/core/src/service/proService.ts b/packages/core/src/service/proService.ts
index 5b7b65820..2352bd5f2 100644
--- a/packages/core/src/service/proService.ts
+++ b/packages/core/src/service/proService.ts
@@ -11,7 +11,7 @@ import { FiatCurrencies } from '../entries/fiat';
import { Language, localizationText } from '../entries/language';
import { ProState, ProSubscription, ProSubscriptionInvalid } from '../entries/pro';
import { RecipientData, TonRecipientData } from '../entries/send';
-import { WalletState } from '../entries/wallet';
+import { isStandardTonWallet, TonWalletStandard, WalletVersion } from '../entries/wallet';
import { AccountsApi } from '../tonApiV2';
import {
FiatCurrencies as FiatCurrenciesGenerated,
@@ -31,7 +31,7 @@ import { loginViaTG } from './telegramOauth';
import { createTonProofItem, tonConnectProofPayload } from './tonConnect/connectService';
import { getServerTime } from './transfer/common';
import { walletStateInitFromState } from './wallet/contractService';
-import { getWalletState } from './wallet/storeService';
+import { accountsStorage } from './accountsStorage';
export const setBackupState = async (storage: IStorage, state: ProSubscription) => {
await storage.set(AppKey.PRO_BACKUP, state);
@@ -42,16 +42,20 @@ export const getBackupState = async (storage: IStorage) => {
return backup ?? toEmptySubscription();
};
-export const getProState = async (storage: IStorage, wallet: WalletState): Promise => {
+export const getProState = async (
+ storage: IStorage,
+ wallet: TonWalletStandard
+): Promise => {
try {
return await loadProState(storage, wallet);
} catch (e) {
+ console.error(e);
return {
subscription: toEmptySubscription(),
hasWalletAuthCookie: false,
wallet: {
publicKey: wallet.publicKey,
- rawAddress: wallet.active.rawAddress
+ rawAddress: wallet.rawAddress
}
};
}
@@ -65,24 +69,51 @@ const toEmptySubscription = (): ProSubscriptionInvalid => {
};
};
+export const walletVersionFromProServiceDTO = (value: string) => {
+ switch (value.toUpperCase()) {
+ case 'V3R1':
+ return WalletVersion.V3R1;
+ case 'V3R2':
+ return WalletVersion.V3R2;
+ case 'V4R2':
+ return WalletVersion.V4R2;
+ case 'V5_BETA':
+ return WalletVersion.V5_BETA;
+ case 'V5R1':
+ return WalletVersion.V5R1;
+ default:
+ throw new Error('Unsupported version');
+ }
+};
+
export const loadProState = async (
storage: IStorage,
- fallbackWallet: WalletState
+ fallbackWallet: TonWalletStandard
): Promise => {
const user = await ProServiceService.proServiceGetUserInfo();
let wallet = {
publicKey: fallbackWallet.publicKey,
- rawAddress: fallbackWallet.active.rawAddress
+ rawAddress: fallbackWallet.rawAddress
};
- if (user.pub_key) {
- const actualWallet = await getWalletState(storage, user.pub_key);
+ if (user.pub_key && user.version) {
+ const wallets = (await accountsStorage(storage).getAccounts()).flatMap(
+ a => a.allTonWallets
+ );
+ const actualWallet = wallets
+ .filter(isStandardTonWallet)
+ .find(
+ w =>
+ w.publicKey === user.pub_key &&
+ user.version &&
+ w.version === walletVersionFromProServiceDTO(user.version)
+ );
if (!actualWallet) {
throw new Error('Unknown wallet');
}
wallet = {
publicKey: actualWallet.publicKey,
- rawAddress: actualWallet.active.rawAddress
+ rawAddress: actualWallet.rawAddress
};
}
@@ -131,19 +162,14 @@ export const checkAuthCookie = async () => {
export const authViaTonConnect = async (
api: APIConfig,
- wallet: WalletState,
+ wallet: TonWalletStandard,
signProof: (bufferToSing: Buffer) => Promise
) => {
const domain = 'https://tonkeeper.com/';
const { payload } = await ProServiceService.proServiceAuthGeneratePayload();
const timestamp = await getServerTime(api);
- const proofPayload = tonConnectProofPayload(
- timestamp,
- domain,
- wallet.active.rawAddress,
- payload
- );
+ const proofPayload = tonConnectProofPayload(timestamp, domain, wallet.rawAddress, payload);
const stateInit = walletStateInitFromState(wallet);
const proof = createTonProofItem(
await signProof(proofPayload.bufferToSign),
@@ -152,7 +178,7 @@ export const authViaTonConnect = async (
);
const result = await ProServiceService.proServiceTonConnectAuth({
- address: wallet.active.rawAddress,
+ address: wallet.rawAddress,
proof: {
timestamp: proof.timestamp,
domain: proof.domain.value,
diff --git a/packages/core/src/service/signerService.ts b/packages/core/src/service/signerService.ts
index 09e9dee38..cbe7cc620 100644
--- a/packages/core/src/service/signerService.ts
+++ b/packages/core/src/service/signerService.ts
@@ -3,7 +3,7 @@ import queryString from 'query-string';
import { IAppSdk } from '../AppSdk';
import { AppKey } from '../Keys';
import { APIConfig } from '../entries/apis';
-import { WalletState, WalletVersion } from '../entries/wallet';
+import { isW5Version, TonWalletStandard, WalletVersion } from '../entries/wallet';
import { BlockchainApi } from '../tonApiV2';
import { externalMessage, getWalletSeqNo } from './transfer/common';
import { walletContractFromState } from './wallet/contractService';
@@ -34,7 +34,7 @@ const walletVersionText = (version: WalletVersion) => {
return 'v3r2';
case WalletVersion.V4R2:
return 'v4r2';
- case WalletVersion.V5beta:
+ case WalletVersion.V5_BETA:
return 'v5beta';
case WalletVersion.V5R1:
return 'v5r1';
@@ -66,7 +66,7 @@ export const storeTransactionAndCreateDeepLink = async (
export const publishSignerMessage = async (
sdk: IAppSdk,
api: APIConfig,
- walletState: WalletState,
+ walletState: TonWalletStandard,
signatureHex: string
) => {
const messageBase64 = await sdk.storage.get(AppKey.SIGNER_MESSAGE);
@@ -74,15 +74,12 @@ export const publishSignerMessage = async (
throw new Error('missing message');
}
const contract = walletContractFromState(walletState);
- const seqno = await getWalletSeqNo(api, walletState.active.rawAddress);
+ const seqno = await getWalletSeqNo(api, walletState.rawAddress);
const signature = Buffer.from(signatureHex, 'hex');
const message = Cell.fromBase64(messageBase64).asSlice();
const transfer = beginCell();
- if (
- walletState.active.version === WalletVersion.V5beta ||
- walletState.active.version === WalletVersion.V5R1
- ) {
+ if (isW5Version(walletState.version)) {
transfer.storeSlice(message).storeBuffer(signature);
} else {
transfer.storeBuffer(signature).storeSlice(message);
diff --git a/packages/core/src/service/suggestionService.ts b/packages/core/src/service/suggestionService.ts
index 13bee652e..60e11b241 100644
--- a/packages/core/src/service/suggestionService.ts
+++ b/packages/core/src/service/suggestionService.ts
@@ -2,11 +2,10 @@ import { IAppSdk } from '../AppSdk';
import { APIConfig } from '../entries/apis';
import { BLOCKCHAIN_NAME } from '../entries/crypto';
import { FavoriteSuggestion, LatestSuggestion } from '../entries/suggestion';
-import { WalletState } from '../entries/wallet';
+import { TonWalletStandard } from '../entries/wallet';
import { AppKey } from '../Keys';
import { IStorage } from '../Storage';
import { AccountsApi } from '../tonApiV2';
-import { TronApi } from '../tronApi';
export const getHiddenSuggestions = async (storage: IStorage, publicKey: string) => {
const result = await storage.get(`${AppKey.HIDDEN_SUGGESTIONS}_${publicKey}`);
@@ -42,9 +41,9 @@ export const deleteFavoriteSuggestion = async (
storage.set(`${AppKey.FAVOURITES}_${publicKey}`, items);
};
-const getTronSuggestionsList = async (
+/*const getTronSuggestionsList = async (
api: APIConfig,
- wallet: WalletState,
+ wallet: DeprecatedWalletState,
seeIfAddressIsAdded: (list: LatestSuggestion[], address: string) => boolean
) => {
const list = [] as LatestSuggestion[];
@@ -70,17 +69,17 @@ const getTronSuggestionsList = async (
}
return list;
-};
+};*/
const getTonSuggestionsList = async (
api: APIConfig,
- wallet: WalletState,
+ wallet: TonWalletStandard,
seeIfAddressIsAdded: (list: LatestSuggestion[], address: string) => boolean
) => {
const list = [] as LatestSuggestion[];
const items = await new AccountsApi(api.tonApiV2).getAccountEvents({
- accountId: wallet.active.rawAddress,
+ accountId: wallet.rawAddress,
limit: 100,
subjectOnly: true
});
@@ -90,7 +89,7 @@ const getTonSuggestionsList = async (
if (!tonTransferEvent) return;
const recipient = event.actions.find(
- item => item.tonTransfer?.recipient.address !== wallet.active.rawAddress
+ item => item.tonTransfer?.recipient.address !== wallet.rawAddress
);
if (!recipient) return;
@@ -112,7 +111,7 @@ const getTonSuggestionsList = async (
export const getSuggestionsList = async (
sdk: IAppSdk,
api: APIConfig,
- wallet: WalletState,
+ wallet: TonWalletStandard,
acceptBlockchains: BLOCKCHAIN_NAME[] = [BLOCKCHAIN_NAME.TON, BLOCKCHAIN_NAME.TRON]
) => {
const favorites = await getFavoriteSuggestions(sdk.storage, wallet.publicKey);
@@ -129,8 +128,8 @@ export const getSuggestionsList = async (
switch (name) {
case BLOCKCHAIN_NAME.TON:
return getTonSuggestionsList(api, wallet, seeIfAddressIsAdded);
- case BLOCKCHAIN_NAME.TRON:
- return getTronSuggestionsList(api, wallet, seeIfAddressIsAdded);
+ /* case BLOCKCHAIN_NAME.TRON:
+ return getTronSuggestionsList(api, wallet, seeIfAddressIsAdded);*/
default:
throw new Error('Unexpected chain');
}
diff --git a/packages/core/src/service/tonConnect/connectService.ts b/packages/core/src/service/tonConnect/connectService.ts
index 6e9bc89ee..d9ceb4c42 100644
--- a/packages/core/src/service/tonConnect/connectService.ts
+++ b/packages/core/src/service/tonConnect/connectService.ts
@@ -1,9 +1,7 @@
import { Address, beginCell, storeStateInit } from '@ton/core';
import { getSecureRandomBytes, keyPairFromSeed, sha256_sync } from '@ton/crypto';
import queryString from 'query-string';
-import { AppKey } from '../../Keys';
import { IStorage } from '../../Storage';
-import { AccountState } from '../../entries/account';
import { TonConnectError } from '../../entries/exception';
import { Network } from '../../entries/network';
import {
@@ -22,17 +20,19 @@ import {
TonConnectAccount,
TonProofItemReplySuccess
} from '../../entries/tonConnect';
-import { WalletState } from '../../entries/wallet';
+import { TonWalletStandard } from '../../entries/wallet';
import { walletContractFromState } from '../wallet/contractService';
-import { getWalletState } from '../wallet/storeService';
import {
AccountConnection,
- TonConnectParams,
disconnectAccountConnection,
- getAccountConnection,
- saveAccountConnection
+ getTonWalletConnections,
+ saveAccountConnection,
+ TonConnectParams
} from './connectionService';
import { SessionCrypto } from './protocol';
+import { accountsStorage } from '../accountsStorage';
+import { getDevSettings } from '../devStorage';
+import { Account } from '../../entries/account';
export function parseTonConnect(options: { url: string }): TonConnectParams | string {
try {
@@ -101,7 +101,7 @@ export const getManifest = async (request: ConnectRequest) => {
// TODO: get fetch from context
const response = await getManifestResponse(request.manifestUrl);
- if (response.status != 200) {
+ if (response.status !== 200) {
throw new Error(`Failed to load Manifest: ${response.status}`);
}
@@ -171,12 +171,10 @@ export const getDappConnection = async (
storage: IStorage,
origin: string,
account?: TonConnectAccount
-): Promise<{ wallet: WalletState; connection: AccountConnection } | undefined> => {
+): Promise<{ wallet: TonWalletStandard; connection: AccountConnection } | undefined> => {
const appConnections = await getAppConnections(storage);
if (account) {
- const walletState = appConnections.find(
- c => c.wallet.active.rawAddress === account?.address
- );
+ const walletState = appConnections.find(c => c.wallet.rawAddress === account?.address);
const connection = walletState?.connections.find(item => item.webViewUrl === origin);
if (walletState && connection) {
return { wallet: walletState.wallet, connection };
@@ -198,34 +196,31 @@ export const getDappConnection = async (
export const getAppConnections = async (
storage: IStorage
-): Promise<{ wallet: WalletState; connections: AccountConnection[] }[]> => {
- const state = await storage.get(AppKey.ACCOUNT);
-
- if (!state) {
+): Promise<{ wallet: TonWalletStandard; connections: AccountConnection[] }[]> => {
+ const accounts = await accountsStorage(storage).getAccounts();
+ if (!accounts.length) {
throw new TonConnectError(
'Missing active wallet',
CONNECT_EVENT_ERROR_CODES.UNKNOWN_APP_ERROR
);
}
- const wallets = (
- await Promise.all(state.publicKeys.map(pk => getWalletState(storage, pk)))
- ).filter(Boolean) as WalletState[];
-
return Promise.all(
- wallets.map(async wallet => {
- const walletConnections = await getAccountConnection(storage, wallet);
- return { wallet, connections: walletConnections };
- })
+ accounts
+ .flatMap(a => a.allTonWallets)
+ .map(async wallet => {
+ const walletConnections = await getTonWalletConnections(storage, wallet);
+ return { wallet, connections: walletConnections };
+ })
);
};
export const checkWalletConnectionOrDie = async (options: {
storage: IStorage;
- wallet: WalletState;
+ wallet: TonWalletStandard;
webViewUrl: string;
}) => {
- const connections = await getAccountConnection(options.storage, options.wallet);
+ const connections = await getTonWalletConnections(options.storage, options.wallet);
console.log(connections);
@@ -249,15 +244,15 @@ export const tonReConnectRequest = async (
CONNECT_EVENT_ERROR_CODES.BAD_REQUEST_ERROR
);
}
- return [toTonAddressItemReply(connection.wallet)];
+ return [toTonAddressItemReply(connection.wallet, (await getDevSettings(storage)).tonNetwork)];
};
-export const toTonAddressItemReply = (wallet: WalletState) => {
+export const toTonAddressItemReply = (wallet: TonWalletStandard, network: Network) => {
const contract = walletContractFromState(wallet);
const result: TonAddressItemReply = {
name: 'ton_addr',
address: contract.address.toRawString(),
- network: (wallet.network || Network.MAINNET).toString(),
+ network: network.toString(),
walletStateInit: beginCell()
.storeWritable(storeStateInit(contract.init))
.endCell()
@@ -326,19 +321,15 @@ export const tonConnectProofPayload = (
export const toTonProofItemReply = async (options: {
storage: IStorage;
- wallet: WalletState;
+ account: Account;
signTonConnect: (bufferToSign: Buffer) => Promise;
proof: ConnectProofPayload;
}): Promise => {
- let signHash = true;
- if (options.wallet.auth) {
- signHash = options.wallet.auth.kind !== 'keystone';
- }
- const result: TonProofItemReplySuccess = {
+ const signHash = options.account.type !== 'keystone';
+ return {
name: 'ton_proof',
proof: await toTonProofItem(options.signTonConnect, options.proof, signHash)
};
- return result;
};
export const createTonProofItem = (
@@ -361,7 +352,7 @@ export const createTonProofItem = (
export const toTonProofItem = async (
signTonConnect: (bufferToSign: Buffer) => Promise,
proof: ConnectProofPayload,
- signHash: boolean = true,
+ signHash = true,
stateInit?: string
) => {
const signature = await signTonConnect(signHash ? proof.bufferToSign : proof.messageBuffer);
@@ -381,7 +372,7 @@ export const tonDisconnectRequest = async (options: { storage: IStorage; webView
export const saveWalletTonConnect = async (options: {
storage: IStorage;
- wallet: WalletState;
+ wallet: TonWalletStandard;
manifest: DAppManifest;
params: TonConnectParams;
replyItems: ConnectItemReply[];
diff --git a/packages/core/src/service/tonConnect/connectionService.ts b/packages/core/src/service/tonConnect/connectionService.ts
index 39d6ce2c7..6023778b7 100644
--- a/packages/core/src/service/tonConnect/connectionService.ts
+++ b/packages/core/src/service/tonConnect/connectionService.ts
@@ -1,8 +1,8 @@
-import { Network } from '../../entries/network';
import { ConnectRequest, DAppManifest, KeyPair } from '../../entries/tonConnect';
-import { WalletState } from '../../entries/wallet';
+import { TonWalletStandard } from '../../entries/wallet';
import { AppKey } from '../../Keys';
import { IStorage } from '../../Storage';
+import { getDevSettings } from '../devStorage';
export interface TonConnectParams {
protocolVersion: number;
@@ -18,35 +18,42 @@ export interface AccountConnection {
webViewUrl?: string;
}
-export const getAccountConnection = async (
+export const getTonWalletConnections = async (
storage: IStorage,
- wallet: Pick
+ wallet: Pick
) => {
- const result = await storage.get(
- `${AppKey.CONNECTIONS}_${wallet.publicKey}_${wallet.network ?? Network.MAINNET}`
+ const network = (await getDevSettings(storage)).tonNetwork;
+
+ let result = await storage.get(
+ `${AppKey.CONNECTIONS}_${wallet.id}_${network}`
);
- return result ?? [];
+
+ if (!result) {
+ result = await migrateAccountConnections(storage, wallet);
+ await setAccountConnection(storage, wallet, result);
+ }
+
+ return result;
};
export const setAccountConnection = async (
storage: IStorage,
- wallet: Pick,
+ wallet: Pick,
items: AccountConnection[]
) => {
- await storage.set(
- `${AppKey.CONNECTIONS}_${wallet.publicKey}_${wallet.network ?? Network.MAINNET}`,
- items
- );
+ const network = (await getDevSettings(storage)).tonNetwork;
+
+ await storage.set(`${AppKey.CONNECTIONS}_${wallet.id}_${network}`, items);
};
export const saveAccountConnection = async (options: {
storage: IStorage;
- wallet: WalletState;
+ wallet: TonWalletStandard;
manifest: DAppManifest;
params: TonConnectParams;
webViewUrl?: string;
}): Promise => {
- let connections = await getAccountConnection(options.storage, options.wallet);
+ let connections = await getTonWalletConnections(options.storage, options.wallet);
const old = connections.find(item => item.manifest.url === options.manifest.url);
if (old) {
@@ -68,10 +75,10 @@ export const saveAccountConnection = async (options: {
*/
export const disconnectAccountConnection = async (options: {
storage: IStorage;
- wallet: WalletState;
+ wallet: TonWalletStandard;
webViewUrl: string;
}) => {
- let connections = await getAccountConnection(options.storage, options.wallet);
+ let connections = await getTonWalletConnections(options.storage, options.wallet);
connections = connections.filter(item => item.webViewUrl !== options.webViewUrl);
@@ -83,12 +90,24 @@ export const disconnectAccountConnection = async (options: {
*/
export const disconnectAppConnection = async (options: {
storage: IStorage;
- wallet: WalletState;
+ wallet: Pick;
clientSessionId: string;
}) => {
- let connections = await getAccountConnection(options.storage, options.wallet);
+ let connections = await getTonWalletConnections(options.storage, options.wallet);
connections = connections.filter(item => item.clientSessionId !== options.clientSessionId);
await setAccountConnection(options.storage, options.wallet, connections);
};
+
+async function migrateAccountConnections(
+ storage: IStorage,
+ wallet: Pick
+) {
+ const network = (await getDevSettings(storage)).tonNetwork;
+ const oldConnections = await storage.get(
+ `${AppKey.CONNECTIONS}_${wallet.publicKey}_${network}`
+ );
+
+ return oldConnections ?? [];
+}
diff --git a/packages/core/src/service/transfer/common.ts b/packages/core/src/service/transfer/common.ts
index f9aedc010..67ca4ae81 100644
--- a/packages/core/src/service/transfer/common.ts
+++ b/packages/core/src/service/transfer/common.ts
@@ -14,10 +14,11 @@ import nacl from 'tweetnacl';
import { APIConfig } from '../../entries/apis';
import { TonRecipient, TransferEstimationEvent } from '../../entries/send';
import { BaseSigner } from '../../entries/signer';
-import { WalletState } from '../../entries/wallet';
-import { NotEnoughBalanceError } from '../../errors/NotEnoughBalanceError';
+import { TonWalletStandard } from '../../entries/wallet';
import { Account, AccountsApi, LiteServerApi, WalletApi } from '../../tonApiV2';
-import { WalletContract, walletContractFromState } from '../wallet/contractService';
+import { walletContractFromState } from '../wallet/contractService';
+import { NotEnoughBalanceError } from '../../errors/NotEnoughBalanceError';
+import { WalletContract } from '../wallet/contractService';
export enum SendMode {
CARRY_ALL_REMAINING_BALANCE = 128,
@@ -76,11 +77,11 @@ export const getWalletSeqNo = async (api: APIConfig, accountId: string) => {
return seqno;
};
-export const getWalletBalance = async (api: APIConfig, walletState: WalletState) => {
+export const getWalletBalance = async (api: APIConfig, walletState: TonWalletStandard) => {
const wallet = await new AccountsApi(api.tonApiV2).getAccount({
- accountId: walletState.active.rawAddress
+ accountId: walletState.rawAddress
});
- const seqno = await getWalletSeqNo(api, walletState.active.rawAddress);
+ const seqno = await getWalletSeqNo(api, walletState.rawAddress);
return [wallet, seqno] as const;
};
@@ -97,7 +98,7 @@ export const seeIfTimeError = (e: unknown): e is Error => {
export const createTransferMessage = async (
wallet: {
seqno: number;
- state: WalletState;
+ state: TonWalletStandard;
signer: BaseSigner;
timestamp: number;
},
@@ -136,7 +137,7 @@ signEstimateMessage.type = 'cell' as const;
export async function getKeyPairAndSeqno(options: {
api: APIConfig;
- walletState: WalletState;
+ walletState: TonWalletStandard;
fee: TransferEstimationEvent;
amount: BigNumber;
}) {
diff --git a/packages/core/src/service/transfer/jettonService.ts b/packages/core/src/service/transfer/jettonService.ts
index 933a34e66..5a9658109 100644
--- a/packages/core/src/service/transfer/jettonService.ts
+++ b/packages/core/src/service/transfer/jettonService.ts
@@ -5,7 +5,7 @@ import { AssetAmount } from '../../entries/crypto/asset/asset-amount';
import { TonAsset } from '../../entries/crypto/asset/ton-asset';
import { TonRecipientData, TransferEstimationEvent } from '../../entries/send';
import { CellSigner, Signer } from '../../entries/signer';
-import { WalletState } from '../../entries/wallet';
+import { TonWalletStandard } from '../../entries/wallet';
import { BlockchainApi, EmulationApi } from '../../tonApiV2';
import { createLedgerJettonTransfer } from '../ledger/transfer';
import { walletContractFromState } from '../wallet/contractService';
@@ -20,6 +20,7 @@ import {
SendMode,
signEstimateMessage
} from './common';
+import { Account } from '../../entries/account';
export const jettonTransferAmount = toNano(0.1);
export const jettonTransferForwardAmount = BigInt(1);
@@ -47,7 +48,7 @@ export const jettonTransferBody = (params: {
const createJettonTransfer = async (
timestamp: number,
seqno: number,
- walletState: WalletState,
+ walletState: TonWalletStandard,
recipientAddress: string,
amount: AssetAmount,
jettonWalletAddress: string,
@@ -60,7 +61,7 @@ const createJettonTransfer = async (
queryId: getTonkeeperQueryId(),
jettonAmount,
toAddress: Address.parse(recipientAddress),
- responseAddress: Address.parse(walletState.active.rawAddress),
+ responseAddress: Address.parse(walletState.rawAddress),
forwardAmount: jettonTransferForwardAmount,
forwardPayload
});
@@ -86,7 +87,7 @@ const createJettonTransfer = async (
export const estimateJettonTransfer = async (
api: APIConfig,
- walletState: WalletState,
+ walletState: TonWalletStandard,
recipient: TonRecipientData,
amount: AssetAmount,
jettonWalletAddress: string
@@ -117,7 +118,7 @@ export const estimateJettonTransfer = async (
export const sendJettonTransfer = async (
api: APIConfig,
- walletState: WalletState,
+ account: Account,
recipient: TonRecipientData,
amount: AssetAmount,
jettonWalletAddress: string,
@@ -130,23 +131,37 @@ export const sendJettonTransfer = async (
.multipliedBy(-1)
.plus(jettonTransferAmount.toString());
+ const walletState = account.activeTonWallet;
const [wallet, seqno] = await getWalletBalance(api, walletState);
checkWalletBalanceOrDie(total, wallet);
let buffer: Buffer;
- const params = [
- timestamp,
- seqno,
- walletState,
- recipient.toAccount.address,
- amount,
- jettonWalletAddress,
- recipient.comment ? comment(recipient.comment) : null
- ] as const;
+
if (signer.type === 'ledger') {
- buffer = await createLedgerJettonTransfer(...params, signer);
+ if (account.type !== 'ledger') {
+ throw new Error(`Unexpected account type: ${account.type}`);
+ }
+ buffer = await createLedgerJettonTransfer(
+ timestamp,
+ seqno,
+ account,
+ recipient.toAccount.address,
+ amount,
+ jettonWalletAddress,
+ recipient.comment ? comment(recipient.comment) : null,
+ signer
+ );
} else {
- buffer = await createJettonTransfer(...params, signer);
+ buffer = await createJettonTransfer(
+ timestamp,
+ seqno,
+ walletState,
+ recipient.toAccount.address,
+ amount,
+ jettonWalletAddress,
+ recipient.comment ? comment(recipient.comment) : null,
+ signer
+ );
}
await new BlockchainApi(api.tonApiV2).sendBlockchainMessage({
diff --git a/packages/core/src/service/transfer/multiSendService.ts b/packages/core/src/service/transfer/multiSendService.ts
index b949e4d30..73e085946 100644
--- a/packages/core/src/service/transfer/multiSendService.ts
+++ b/packages/core/src/service/transfer/multiSendService.ts
@@ -2,7 +2,7 @@ import { Address, comment, internal } from '@ton/core';
import BigNumber from 'bignumber.js';
import { APIConfig } from '../../entries/apis';
import { CellSigner } from '../../entries/signer';
-import { WalletState, WalletVersion } from '../../entries/wallet';
+import { TonWalletStandard, WalletVersion } from '../../entries/wallet';
import { BlockchainApi, EmulationApi } from '../../tonApiV2';
import { unShiftedDecimals } from '../../utils/balance';
import { walletContractFromState } from '../wallet/contractService';
@@ -32,7 +32,7 @@ export type TransferMessage = {
export const MAX_ALLOWED_WALLET_MSGS = {
[WalletVersion.V5R1]: 255,
- [WalletVersion.V5beta]: 255,
+ [WalletVersion.V5_BETA]: 255,
[WalletVersion.V4R2]: 4,
[WalletVersion.V4R1]: 4,
[WalletVersion.V3R2]: 4,
@@ -51,7 +51,7 @@ const checkMaxAllowedMessagesInMultiTransferOrDie = (
export const estimateTonMultiTransfer = async (
api: APIConfig,
- walletState: WalletState,
+ walletState: TonWalletStandard,
transferMessages: TransferMessage[]
) => {
const timestamp = await getServerTime(api);
@@ -60,10 +60,7 @@ export const estimateTonMultiTransfer = async (
const [wallet, seqno] = await getWalletBalance(api, walletState);
checkWalletBalanceOrDie(total, wallet);
- checkMaxAllowedMessagesInMultiTransferOrDie(
- transferMessages.length,
- walletState.active.version
- );
+ checkMaxAllowedMessagesInMultiTransferOrDie(transferMessages.length, walletState.version);
const cell = await createTonMultiTransfer(
timestamp,
@@ -84,7 +81,7 @@ export const estimateTonMultiTransfer = async (
export const sendTonMultiTransfer = async (
api: APIConfig,
- walletState: WalletState,
+ walletState: TonWalletStandard,
transferMessages: TransferMessage[],
feeEstimate: BigNumber,
signer: CellSigner
@@ -95,10 +92,7 @@ export const sendTonMultiTransfer = async (
const [wallet, seqno] = await getWalletBalance(api, walletState);
checkWalletBalanceOrDie(total.plus(feeEstimate), wallet);
- checkMaxAllowedMessagesInMultiTransferOrDie(
- transferMessages.length,
- walletState.active.version
- );
+ checkMaxAllowedMessagesInMultiTransferOrDie(transferMessages.length, walletState.version);
const cell = await createTonMultiTransfer(
timestamp,
@@ -118,7 +112,7 @@ export const sendTonMultiTransfer = async (
const createTonMultiTransfer = async (
timestamp: number,
seqno: number,
- walletState: WalletState,
+ walletState: TonWalletStandard,
transferMessages: TransferMessage[],
signer: CellSigner
) => {
@@ -144,7 +138,7 @@ const createTonMultiTransfer = async (
export const estimateJettonMultiTransfer = async (
api: APIConfig,
- walletState: WalletState,
+ walletState: TonWalletStandard,
jettonWalletAddress: string,
transferMessages: TransferMessage[]
) => {
@@ -156,10 +150,7 @@ export const estimateJettonMultiTransfer = async (
wallet
);
- checkMaxAllowedMessagesInMultiTransferOrDie(
- transferMessages.length,
- walletState.active.version
- );
+ checkMaxAllowedMessagesInMultiTransferOrDie(transferMessages.length, walletState.version);
const cell = await createJettonMultiTransfer(
timestamp,
@@ -182,7 +173,7 @@ export const estimateJettonMultiTransfer = async (
export const sendJettonMultiTransfer = async (
api: APIConfig,
- walletState: WalletState,
+ walletState: TonWalletStandard,
jettonWalletAddress: string,
transferMessages: TransferMessage[],
feeEstimate: BigNumber,
@@ -192,10 +183,7 @@ export const sendJettonMultiTransfer = async (
const [wallet, seqno] = await getWalletBalance(api, walletState);
- checkMaxAllowedMessagesInMultiTransferOrDie(
- transferMessages.length,
- walletState.active.version
- );
+ checkMaxAllowedMessagesInMultiTransferOrDie(transferMessages.length, walletState.version);
const attachValue = feeEstimate.div(transferMessages.length).plus(unShiftedDecimals(0.05));
checkWalletBalanceOrDie(attachValue.multipliedBy(transferMessages.length), wallet);
@@ -243,7 +231,7 @@ export const sendJettonMultiTransfer = async (
const createJettonMultiTransfer = async (
timestamp: number,
seqno: number,
- walletState: WalletState,
+ walletState: TonWalletStandard,
jettonWalletAddress: string,
transferMessages: TransferMessage[],
attachValue: BigNumber,
@@ -265,7 +253,7 @@ const createJettonMultiTransfer = async (
queryId: getTonkeeperQueryId(),
jettonAmount: BigInt(msg.weiAmount.toFixed(0)),
toAddress: Address.parse(msg.to),
- responseAddress: Address.parse(walletState.active.rawAddress),
+ responseAddress: Address.parse(walletState.rawAddress),
forwardAmount: jettonTransferForwardAmount,
forwardPayload: msg.comment ? comment(msg.comment) : null
})
@@ -285,7 +273,7 @@ export type NftTransferMessage = {
export const createNftMultiTransfer = async (
timestamp: number,
seqno: number,
- walletState: WalletState,
+ walletState: TonWalletStandard,
transferMessages: NftTransferMessage[],
attachValue: BigNumber,
signer: CellSigner
@@ -305,7 +293,7 @@ export const createNftMultiTransfer = async (
body: nftTransferBody({
queryId: getTonkeeperQueryId(),
newOwnerAddress: Address.parse(msg.to),
- responseAddress: Address.parse(walletState.active.rawAddress),
+ responseAddress: Address.parse(walletState.rawAddress),
forwardAmount: nftTransferForwardAmount,
forwardPayload: msg.comment ? comment(msg.comment) : null
})
@@ -318,7 +306,7 @@ export const createNftMultiTransfer = async (
export const sendNftMultiTransfer = async (
api: APIConfig,
- walletState: WalletState,
+ walletState: TonWalletStandard,
transferMessages: NftTransferMessage[],
feeEstimate: BigNumber,
signer: CellSigner
@@ -327,10 +315,7 @@ export const sendNftMultiTransfer = async (
const [wallet, seqno] = await getWalletBalance(api, walletState);
- checkMaxAllowedMessagesInMultiTransferOrDie(
- transferMessages.length,
- walletState.active.version
- );
+ checkMaxAllowedMessagesInMultiTransferOrDie(transferMessages.length, walletState.version);
const attachValue = feeEstimate.div(transferMessages.length).plus(unShiftedDecimals(0.03));
checkWalletBalanceOrDie(attachValue.multipliedBy(transferMessages.length), wallet);
diff --git a/packages/core/src/service/transfer/nftService.ts b/packages/core/src/service/transfer/nftService.ts
index 176ee951c..e32732546 100644
--- a/packages/core/src/service/transfer/nftService.ts
+++ b/packages/core/src/service/transfer/nftService.ts
@@ -3,7 +3,7 @@ import BigNumber from 'bignumber.js';
import { APIConfig } from '../../entries/apis';
import { TonRecipientData, TransferEstimationEvent } from '../../entries/send';
import { CellSigner, Signer } from '../../entries/signer';
-import { WalletState } from '../../entries/wallet';
+import { TonWalletStandard } from '../../entries/wallet';
import { BlockchainApi, EmulationApi, NftItem } from '../../tonApiV2';
import { createLedgerNftTransfer } from '../ledger/transfer';
import {
@@ -16,6 +16,7 @@ import {
getWalletBalance,
signEstimateMessage
} from './common';
+import { Account } from '../../entries/account';
const initNftTransferAmount = toNano('1');
export const nftTransferForwardAmount = BigInt('1');
@@ -68,7 +69,7 @@ const nftLinkBody = (params: { queryId: bigint; linkToAddress: string }) => {
const createNftTransfer = (
timestamp: number,
seqno: number,
- walletState: WalletState,
+ walletState: TonWalletStandard,
recipientAddress: string,
nftAddress: string,
nftTransferAmount: bigint,
@@ -78,7 +79,7 @@ const createNftTransfer = (
const body = nftTransferBody({
queryId: getTonkeeperQueryId(),
newOwnerAddress: Address.parse(recipientAddress),
- responseAddress: Address.parse(walletState.active.rawAddress),
+ responseAddress: Address.parse(walletState.rawAddress),
forwardAmount: nftTransferForwardAmount,
forwardPayload
});
@@ -91,7 +92,7 @@ const createNftTransfer = (
export const estimateNftTransfer = async (
api: APIConfig,
- walletState: WalletState,
+ walletState: TonWalletStandard,
recipient: TonRecipientData,
nftItem: NftItem
): Promise => {
@@ -120,7 +121,7 @@ export const estimateNftTransfer = async (
export const sendNftTransfer = async (
api: APIConfig,
- walletState: WalletState,
+ account: Account,
recipient: TonRecipientData,
nftItem: NftItem,
fee: TransferEstimationEvent,
@@ -139,27 +140,39 @@ export const sendNftTransfer = async (
throw new Error(`Unexpected nft transfer amount: ${nftTransferAmount.toString()}`);
}
+ const walletState = account.activeTonWallet;
const [wallet, seqno] = await getWalletBalance(api, walletState);
checkWalletBalanceOrDie(total, wallet);
- const params = [
- timestamp,
- seqno,
- walletState,
- recipient.toAccount.address,
- nftItem.address,
- BigInt(nftTransferAmount.toString()),
- recipient.comment ? comment(recipient.comment) : null
- ] as const;
-
let buffer: Buffer;
switch (signer.type) {
case 'cell': {
- buffer = await createNftTransfer(...params, signer);
+ buffer = await createNftTransfer(
+ timestamp,
+ seqno,
+ walletState,
+ recipient.toAccount.address,
+ nftItem.address,
+ BigInt(nftTransferAmount.toString()),
+ recipient.comment ? comment(recipient.comment) : null,
+ signer
+ );
break;
}
case 'ledger': {
- buffer = await createLedgerNftTransfer(...params, signer);
+ if (account.type !== 'ledger') {
+ throw new Error(`Unexpected account type: ${account.type}`);
+ }
+ buffer = await createLedgerNftTransfer(
+ timestamp,
+ seqno,
+ account,
+ recipient.toAccount.address,
+ nftItem.address,
+ BigInt(nftTransferAmount.toString()),
+ recipient.comment ? comment(recipient.comment) : null,
+ signer
+ );
break;
}
}
@@ -171,14 +184,16 @@ export const sendNftTransfer = async (
export const sendNftRenew = async (options: {
api: APIConfig;
- walletState: WalletState;
+ account: Account;
nftAddress: string;
fee: TransferEstimationEvent;
signer: CellSigner;
amount: BigNumber;
}) => {
+ const walletState = options.account.activeTonWallet;
+
const timestamp = await getServerTime(options.api);
- const { seqno } = await getKeyPairAndSeqno(options);
+ const { seqno } = await getKeyPairAndSeqno({ ...options, walletState });
const body = nftRenewBody({ queryId: getTonkeeperQueryId() });
@@ -186,7 +201,7 @@ export const sendNftRenew = async (options: {
{
timestamp,
seqno,
- state: options.walletState,
+ state: walletState,
signer: options.signer
},
{ to: options.nftAddress, value: options.amount, body }
@@ -199,7 +214,7 @@ export const sendNftRenew = async (options: {
export const estimateNftRenew = async (options: {
api: APIConfig;
- walletState: WalletState;
+ walletState: TonWalletStandard;
nftAddress: string;
amount: BigNumber;
}) => {
@@ -224,15 +239,16 @@ export const estimateNftRenew = async (options: {
export const sendNftLink = async (options: {
api: APIConfig;
- walletState: WalletState;
+ account: Account;
nftAddress: string;
linkToAddress: string;
fee: TransferEstimationEvent;
signer: CellSigner;
amount: BigNumber;
}) => {
+ const walletState = options.account.activeTonWallet;
const timestamp = await getServerTime(options.api);
- const { seqno } = await getKeyPairAndSeqno(options);
+ const { seqno } = await getKeyPairAndSeqno({ ...options, walletState });
const body = nftLinkBody({ ...options, queryId: getTonkeeperQueryId() });
@@ -240,7 +256,7 @@ export const sendNftLink = async (options: {
{
timestamp,
seqno,
- state: options.walletState,
+ state: walletState,
signer: options.signer
},
{ to: options.nftAddress, value: options.amount, body }
@@ -253,7 +269,7 @@ export const sendNftLink = async (options: {
export const estimateNftLink = async (options: {
api: APIConfig;
- walletState: WalletState;
+ walletState: TonWalletStandard;
nftAddress: string;
linkToAddress: string;
amount: BigNumber;
diff --git a/packages/core/src/service/transfer/tonService.ts b/packages/core/src/service/transfer/tonService.ts
index 0df2fed9b..241101ef7 100644
--- a/packages/core/src/service/transfer/tonService.ts
+++ b/packages/core/src/service/transfer/tonService.ts
@@ -6,7 +6,7 @@ import { AssetAmount } from '../../entries/crypto/asset/asset-amount';
import { TonRecipientData, TransferEstimationEvent } from '../../entries/send';
import { CellSigner, Signer } from '../../entries/signer';
import { TonConnectTransactionPayload } from '../../entries/tonConnect';
-import { WalletState } from '../../entries/wallet';
+import { TonWalletStandard } from '../../entries/wallet';
import { AccountsApi, BlockchainApi, EmulationApi } from '../../tonApiV2';
import { createLedgerTonTransfer } from '../ledger/transfer';
import { walletContractFromState } from '../wallet/contractService';
@@ -24,8 +24,8 @@ import {
signEstimateMessage
} from './common';
import { getLedgerAccountPathByIndex } from '../ledger/utils';
-import { AuthLedger } from '../../entries/password';
import { LedgerError } from '../../errors/LedgerError';
+import { Account } from '../../entries/account';
export type EstimateData = {
accountEvent: TransferEstimationEvent;
@@ -47,7 +47,7 @@ export const toStateInit = (
const createTonTransfer = async (
timestamp: number,
seqno: number,
- walletState: WalletState,
+ walletState: TonWalletStandard,
recipient: TonRecipientData,
weiAmount: BigNumber,
isMax: boolean,
@@ -76,19 +76,23 @@ const createTonTransfer = async (
const createTonConnectTransfer = async (
timestamp: number,
seqno: number,
- walletState: WalletState,
+ account: Account,
params: TonConnectTransactionPayload,
signer: Signer
) => {
+ const walletState = account.activeTonWallet;
const contract = walletContractFromState(walletState);
if (signer.type === 'ledger') {
if (params.messages.length !== 1) {
throw new Error('Ledger signer does not support multiple messages');
}
+ if (account.type !== 'ledger') {
+ throw new Error('Ledger signer can only be used with ledger accounts');
+ }
const message = params.messages[0];
- const path = getLedgerAccountPathByIndex((walletState.auth as AuthLedger).accountIndex);
+ const path = getLedgerAccountPathByIndex(account.activeDerivationIndex);
let transfer: Cell;
try {
@@ -141,7 +145,7 @@ const createTonConnectTransfer = async (
export const estimateTonTransfer = async (
api: APIConfig,
- walletState: WalletState,
+ walletState: TonWalletStandard,
recipient: TonRecipientData,
weiAmount: BigNumber,
isMax: boolean
@@ -175,11 +179,11 @@ export type ConnectTransferError = { kind: 'not-enough-balance' } | { kind: unde
export const tonConnectTransferError = async (
api: APIConfig,
- walletState: WalletState,
+ walletState: TonWalletStandard,
params: TonConnectTransactionPayload
): Promise => {
const wallet = await new AccountsApi(api.tonApiV2).getAccount({
- accountId: walletState.active.rawAddress
+ accountId: walletState.rawAddress
});
const total = params.messages.reduce(
@@ -196,17 +200,17 @@ export const tonConnectTransferError = async (
export const estimateTonConnectTransfer = async (
api: APIConfig,
- walletState: WalletState,
+ account: Account,
params: TonConnectTransactionPayload
): Promise => {
const timestamp = await getServerTime(api);
- const [wallet, seqno] = await getWalletBalance(api, walletState);
+ const [wallet, seqno] = await getWalletBalance(api, account.activeTonWallet);
checkWalletPositiveBalanceOrDie(wallet);
const cell = await createTonConnectTransfer(
timestamp,
seqno,
- walletState,
+ account,
params,
signEstimateMessage
);
@@ -222,14 +226,14 @@ export const estimateTonConnectTransfer = async (
export const sendTonConnectTransfer = async (
api: APIConfig,
- walletState: WalletState,
+ account: Account,
params: TonConnectTransactionPayload,
signer: Signer
) => {
const timestamp = await getServerTime(api);
- const seqno = await getWalletSeqNo(api, walletState.active.rawAddress);
+ const seqno = await getWalletSeqNo(api, account.activeTonWallet.rawAddress);
- const external = await createTonConnectTransfer(timestamp, seqno, walletState, params, signer);
+ const external = await createTonConnectTransfer(timestamp, seqno, account, params, signer);
const boc = external.toString('base64');
@@ -242,7 +246,7 @@ export const sendTonConnectTransfer = async (
export const sendTonTransfer = async (
api: APIConfig,
- walletState: WalletState,
+ account: Account,
recipient: TonRecipientData,
amount: AssetAmount,
isMax: boolean,
@@ -253,17 +257,36 @@ export const sendTonTransfer = async (
const total = new BigNumber(fee.event.extra).multipliedBy(-1).plus(amount.weiAmount);
- const [wallet, seqno] = await getWalletBalance(api, walletState);
+ const wallet = account.activeTonWallet;
+ const [tonapiWallet, seqno] = await getWalletBalance(api, wallet);
if (!isMax) {
- checkWalletBalanceOrDie(total, wallet);
+ checkWalletBalanceOrDie(total, tonapiWallet);
}
let buffer: Buffer;
- const params = [timestamp, seqno, walletState, recipient, amount.weiAmount, isMax] as const;
if (signer.type === 'ledger') {
- buffer = await createLedgerTonTransfer(...params, signer);
+ if (account.type !== 'ledger') {
+ throw new Error(`Unexpected account type: ${account.type}`);
+ }
+ buffer = await createLedgerTonTransfer(
+ timestamp,
+ seqno,
+ account,
+ recipient,
+ amount.weiAmount,
+ isMax,
+ signer
+ );
} else {
- buffer = await createTonTransfer(...params, signer);
+ buffer = await createTonTransfer(
+ timestamp,
+ seqno,
+ wallet,
+ recipient,
+ amount.weiAmount,
+ isMax,
+ signer
+ );
}
await new BlockchainApi(api.tonApiV2).sendBlockchainMessage({
diff --git a/packages/core/src/service/tron/tronService.ts b/packages/core/src/service/tron/tronService.ts
index 451775d1f..118ec7200 100644
--- a/packages/core/src/service/tron/tronService.ts
+++ b/packages/core/src/service/tron/tronService.ts
@@ -1,11 +1,8 @@
import { hmac_sha512, mnemonicToPrivateKey } from '@ton/crypto';
import { ethers } from 'ethers';
-import { IStorage } from '../../Storage';
import { Network } from '../../entries/network';
import { Factories, TronChain, WalletImplementations } from '../../entries/tron';
-import { TronWalletState, TronWalletStorage, WalletState } from '../../entries/wallet';
-import { Configuration, TronApi } from '../../tronApi';
-import { setWalletState } from '../wallet/storeService';
+import { TronWalletState, TronWalletStorage } from '../../entries/wallet';
import { calculateCreate2 } from './addressCalculation';
import { TronAddress } from './tronUtils';
@@ -29,10 +26,10 @@ const getOwnerAddress = async (mnemonic: string[]): Promise => {
return TronAddress.hexToBase58(wallet.address);
};
-export const getTronWallet = async (
+/*export const getTronWallet = async (
tronApi: Configuration,
mnemonic: string[],
- wallet: WalletState
+ wallet: DeprecatedWalletState
): Promise => {
const ownerWalletAddress = await getOwnerAddress(mnemonic);
@@ -52,17 +49,17 @@ export const getTronWallet = async (
export const importTronWallet = async (
storage: IStorage,
tronApi: Configuration,
- wallet: WalletState,
+ wallet: DeprecatedWalletState,
mnemonic: string[]
): Promise => {
const tron = await getTronWallet(tronApi, mnemonic, wallet);
const updated = { ...wallet, tron };
- await setWalletState(storage, updated);
+ // await setWalletState(storage, updated);
return tron;
-};
+};*/
export const getTronWalletState = (tron: TronWalletStorage, network?: Network): TronWalletState => {
const chainId = network !== Network.TESTNET ? TronChain.MAINNET : TronChain.NILE;
diff --git a/packages/core/src/service/tron/tronTransferService.ts b/packages/core/src/service/tron/tronTransferService.ts
index c2f4afbb6..180a98dd7 100644
--- a/packages/core/src/service/tron/tronTransferService.ts
+++ b/packages/core/src/service/tron/tronTransferService.ts
@@ -1,17 +1,14 @@
-import BigNumber from 'bignumber.js';
import { ethers } from 'ethers';
import { AssetAmount } from '../../entries/crypto/asset/asset-amount';
-import { toTronAsset } from '../../entries/crypto/asset/constants';
import { TronAsset } from '../../entries/crypto/asset/tron-asset';
-import { RecipientData, TransferEstimation, TronRecipient } from '../../entries/send';
-import { TronWalletStorage, WalletState } from '../../entries/wallet';
+import { TronRecipient } from '../../entries/send';
+import { TronWalletStorage } from '../../entries/wallet';
import {
Configuration,
EstimatePayload,
PublishPayload,
RequestData,
- TronApi,
- TronBalances
+ TronApi
} from '../../tronApi';
import { hashRequest } from './tronEncoding';
import { getPrivateKey } from './tronService';
@@ -72,13 +69,13 @@ export async function sendTronTransfer(
});
}
-async function estimateTronFee({
+/*async function estimateTronFee({
wallet,
tronApi,
address,
amount
}: {
- wallet: WalletState;
+ wallet: DeprecatedWalletState;
tronApi: Configuration;
address: TronRecipient;
amount: AssetAmount;
@@ -91,8 +88,9 @@ async function estimateTronFee({
});
return payload.request.fee;
-}
+}*/
+/*
export async function estimateTron({
recipient,
amount,
@@ -105,7 +103,7 @@ export async function estimateTron({
amount: AssetAmount;
isMax: boolean;
tronApi: Configuration;
- wallet: WalletState;
+ wallet: DeprecatedWalletState;
balances: TronBalances | undefined;
}): Promise> {
if (isMax) {
@@ -144,3 +142,4 @@ export async function estimateTron({
return { fee, payload };
}
+*/
diff --git a/packages/core/src/service/wallet/configService.ts b/packages/core/src/service/wallet/configService.ts
index f29323fe1..78827e8c8 100644
--- a/packages/core/src/service/wallet/configService.ts
+++ b/packages/core/src/service/wallet/configService.ts
@@ -2,9 +2,9 @@ import { Address } from '@ton/core';
import { AppKey } from '../../Keys';
import { IStorage } from '../../Storage';
import { Network } from '../../entries/network';
-import { ActiveWalletConfig } from '../../entries/wallet';
+import { TonWalletConfig } from '../../entries/wallet';
-const defaultConfig: ActiveWalletConfig = {
+const defaultConfig: TonWalletConfig = {
pinnedNfts: [],
hiddenNfts: [],
pinnedTokens: [],
@@ -15,7 +15,7 @@ const defaultConfig: ActiveWalletConfig = {
const migration = async (storage: IStorage, address: string, network: Network | undefined) => {
const raw = Address.parse(address).toRawString();
- const config = await storage.get(
+ const config = await storage.get(
`${AppKey.WALLET_CONFIG}_${raw}_${network ?? Network.MAINNET}`
);
if (config != null) {
@@ -30,7 +30,7 @@ export const getActiveWalletConfig = async (
network: Network | undefined
) => {
const formatted = Address.parse(address).toString({ testOnly: network === Network.TESTNET });
- let config = await storage.get(`${AppKey.WALLET_CONFIG}_${formatted}`);
+ let config = await storage.get(`${AppKey.WALLET_CONFIG}_${formatted}`);
if (!config) {
config = await migration(storage, address, network);
@@ -45,7 +45,7 @@ export const setActiveWalletConfig = async (
storage: IStorage,
address: string,
network: Network | undefined,
- config: ActiveWalletConfig
+ config: TonWalletConfig
) => {
const formatted = Address.parse(address).toString({ testOnly: network === Network.TESTNET });
await storage.set(`${AppKey.WALLET_CONFIG}_${formatted}`, config);
diff --git a/packages/core/src/service/wallet/contractService.ts b/packages/core/src/service/wallet/contractService.ts
index d1894b867..5a8d47ee9 100644
--- a/packages/core/src/service/wallet/contractService.ts
+++ b/packages/core/src/service/wallet/contractService.ts
@@ -5,11 +5,11 @@ import { WalletContractV4 } from '@ton/ton/dist/wallets/WalletContractV4';
import { WalletContractV5Beta } from '@ton/ton/dist/wallets/WalletContractV5Beta';
import { WalletContractV5R1 } from '@ton/ton/dist/wallets/WalletContractV5R1';
import { Network } from '../../entries/network';
-import { WalletState, WalletVersion } from '../../entries/wallet';
+import { TonWalletStandard, WalletVersion } from '../../entries/wallet';
-export const walletContractFromState = (wallet: WalletState) => {
+export const walletContractFromState = (wallet: TonWalletStandard) => {
const publicKey = Buffer.from(wallet.publicKey, 'hex');
- return walletContract(publicKey, wallet.active.version, wallet.network);
+ return walletContract(publicKey, wallet.version);
};
const workchain = 0;
@@ -19,7 +19,7 @@ export type WalletContract = ReturnType;
export const walletContract = (
publicKey: Buffer,
version: WalletVersion,
- network: Network | undefined
+ network = Network.MAINNET
) => {
switch (version) {
case WalletVersion.V3R1:
@@ -30,7 +30,7 @@ export const walletContract = (
throw new Error('Unsupported wallet contract version - v4R1');
case WalletVersion.V4R2:
return WalletContractV4.create({ workchain, publicKey });
- case WalletVersion.V5beta:
+ case WalletVersion.V5_BETA:
return WalletContractV5Beta.create({
walletId: {
networkGlobalId: network
@@ -42,7 +42,7 @@ export const walletContract = (
}
};
-export const walletStateInitFromState = (wallet: WalletState) => {
+export const walletStateInitFromState = (wallet: TonWalletStandard) => {
const contract = walletContractFromState(wallet);
return beginCell()
diff --git a/packages/core/src/service/wallet/storeService.ts b/packages/core/src/service/wallet/storeService.ts
deleted file mode 100644
index c7d6cdb04..000000000
--- a/packages/core/src/service/wallet/storeService.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { Address } from '@ton/core';
-import { AppKey } from '../../Keys';
-import { IStorage } from '../../Storage';
-import { AccountState } from '../../entries/account';
-import { TonConnectError } from '../../entries/exception';
-import { Network } from '../../entries/network';
-import { CONNECT_EVENT_ERROR_CODES } from '../../entries/tonConnect';
-import { WalletState } from '../../entries/wallet';
-import { emojis } from '../../utils/emojis';
-
-export const getWalletState = async (storage: IStorage, publicKey: string) => {
- const state = await storage.get(`${AppKey.WALLET}_${publicKey}`);
-
- if (state) {
- state.active.friendlyAddress = Address.parse(state.active.friendlyAddress).toString({
- testOnly: state.network === Network.TESTNET
- });
-
- state.emoji ||= getFallbackWalletEmoji(state.publicKey);
- }
-
- return state;
-};
-
-export const getWalletStateOrDie = async (storage: IStorage, publicKey: string) => {
- const state = await getWalletState(storage, publicKey);
- if (!state) {
- throw new Error(`Missing wallet state: ${publicKey}`);
- }
- return state;
-};
-
-export const setWalletState = (storage: IStorage, state: WalletState) => {
- return storage.set(`${AppKey.WALLET}_${state.publicKey}`, state);
-};
-
-export const getCurrentWallet = async (storage: IStorage) => {
- const state = await storage.get(AppKey.ACCOUNT);
-
- if (!state || !state.activePublicKey) {
- throw new TonConnectError(
- 'Missing active wallet',
- CONNECT_EVENT_ERROR_CODES.UNKNOWN_APP_ERROR
- );
- }
-
- const wallet = await getWalletState(storage, state.activePublicKey);
-
- if (!wallet) {
- throw new TonConnectError(
- 'Missing wallet state',
- CONNECT_EVENT_ERROR_CODES.UNKNOWN_APP_ERROR
- );
- }
-
- return wallet;
-};
-
-export function getFallbackWalletEmoji(publicKey: string) {
- const index = Number('0x' + publicKey.slice(-6)) % emojis.length;
- return emojis[index];
-}
diff --git a/packages/core/src/service/walletService.ts b/packages/core/src/service/walletService.ts
index 11d415b6c..701f758c4 100644
--- a/packages/core/src/service/walletService.ts
+++ b/packages/core/src/service/walletService.ts
@@ -3,54 +3,83 @@ import { parseTonAccount } from '@keystonehq/keystone-sdk/dist/wallet/hdKey';
import { Address } from '@ton/core';
import { mnemonicToPrivateKey } from '@ton/crypto';
import { WalletContractV4 } from '@ton/ton/dist/wallets/WalletContractV4';
-import { WalletContractV5R1 } from '@ton/ton/dist/wallets/WalletContractV5R1';
import queryString from 'query-string';
-import { AppKey } from '../Keys';
-import { IStorage } from '../Storage';
import { APIConfig } from '../entries/apis';
import { Network } from '../entries/network';
-import { AuthState } from '../entries/password';
-import { WalletAddress, WalletState, WalletVersion, WalletVersions } from '../entries/wallet';
+import { AuthKeychain, AuthPassword } from '../entries/password';
+import { sortWalletsByVersion, WalletVersion, WalletVersions } from '../entries/wallet';
import { WalletApi } from '../tonApiV2';
-import { TonendpointConfig } from '../tonkeeperApi/tonendpoint';
-import { encrypt } from './cryptoService';
import { walletContract } from './wallet/contractService';
-import { getFallbackWalletEmoji, getWalletStateOrDie, setWalletState } from './wallet/storeService';
+import { emojis } from '../utils/emojis';
+import {
+ AccountKeystone,
+ AccountLedger,
+ AccountTonMnemonic,
+ AccountTonOnly
+} from '../entries/account';
+import { IStorage } from '../Storage';
+import { accountsStorage } from './accountsStorage';
-export const createNewWalletState = async (
- api: APIConfig,
+export const createStandardTonAccountByMnemonic = async (
+ appContext: { api: APIConfig; defaultWalletVersion: WalletVersion },
+ storage: IStorage,
mnemonic: string[],
- config: TonendpointConfig,
- name?: string
+ options: {
+ versions?: WalletVersion[];
+ network?: Network;
+ auth: AuthPassword | Omit;
+ }
) => {
const keyPair = await mnemonicToPrivateKey(mnemonic);
const publicKey = keyPair.publicKey.toString('hex');
- const active = await findWalletAddress(api, publicKey, config.flags?.disable_v5r1 ?? true);
+ let tonWallets: { rawAddress: string; version: WalletVersion }[] = [];
+ if (options.versions) {
+ tonWallets = options.versions
+ .map(v => getWalletAddress(publicKey, v))
+ .map(i => ({
+ rawAddress: i.address.toRawString(),
+ version: i.version
+ }));
+ } else {
+ tonWallets = [await findWalletAddress(appContext, publicKey)];
+ }
- const state: WalletState = {
- publicKey,
- active,
- revision: 0,
- name,
- emoji: getFallbackWalletEmoji(publicKey)
- };
+ let walletAuth: AuthPassword | AuthKeychain;
+ if (options.auth.kind === 'keychain') {
+ walletAuth = {
+ kind: 'keychain',
+ keychainStoreKey: publicKey
+ };
+ } else {
+ walletAuth = options.auth;
+ }
- // state.tron = await getTronWallet(api.tronApi, mnemonic, state).catch(() => undefined);
+ const { name, emoji } = await accountsStorage(storage).getNewAccountNameAndEmoji(publicKey);
- return state;
-};
+ const walletIdToActivate = tonWallets.slice().sort(sortWalletsByVersion)[0].rawAddress;
-export const encryptWalletMnemonic = async (mnemonic: string[], password: string) => {
- return encrypt(mnemonic.join(' '), password);
+ return new AccountTonMnemonic(
+ publicKey,
+ name,
+ emoji,
+ walletAuth,
+ walletIdToActivate,
+ tonWallets.map(w => ({
+ id: w.rawAddress,
+ publicKey,
+ version: w.version,
+ rawAddress: w.rawAddress
+ }))
+ );
};
const versionMap: Record = {
wallet_v3r1: WalletVersion.V3R1,
wallet_v3r2: WalletVersion.V3R2,
wallet_v4r2: WalletVersion.V4R2,
- wallet_v5_beta: WalletVersion.V5beta,
+ wallet_v5_beta: WalletVersion.V5_BETA,
wallet_v5r1: WalletVersion.V5R1
};
@@ -66,9 +95,12 @@ const findWalletVersion = (interfaces?: string[]): WalletVersion => {
throw new Error('Unexpected wallet version');
};
-const findWalletAddress = async (api: APIConfig, publicKey: string, disable_v5r1: boolean) => {
+const findWalletAddress = async (
+ appContext: { api: APIConfig; defaultWalletVersion: WalletVersion },
+ publicKey: string
+): Promise<{ rawAddress: string; version: WalletVersion }> => {
try {
- const result = await new WalletApi(api.tonApiV2).getWalletsByPublicKey({
+ const result = await new WalletApi(appContext.api.tonApiV2).getWalletsByPublicKey({
publicKey: publicKey
});
@@ -82,57 +114,34 @@ const findWalletAddress = async (api: APIConfig, publicKey: string, disable_v5r1
.sort((one, two) => two.balance - one.balance);
if (activeWallet) {
- const wallet: WalletAddress = {
+ return {
rawAddress: activeWallet.address,
- friendlyAddress: Address.parse(activeWallet.address).toString(),
version: findWalletVersion(activeWallet.interfaces)
};
-
- return wallet;
}
} catch (e) {
// eslint-disable-next-line no-console
console.warn(e);
}
- if (disable_v5r1) {
- const contact = WalletContractV4.create({
- workchain: 0,
- publicKey: Buffer.from(publicKey, 'hex')
- });
- const wallet: WalletAddress = {
- rawAddress: contact.address.toRawString(),
- friendlyAddress: contact.address.toString(),
- version: WalletVersion.V4R2
- };
- return wallet;
- } else {
- const contact = WalletContractV5R1.create({
- workChain: 0,
- publicKey: Buffer.from(publicKey, 'hex')
- });
- const wallet: WalletAddress = {
- rawAddress: contact.address.toRawString(),
- friendlyAddress: contact.address.toString(),
- version: WalletVersion.V5R1
- };
-
- return wallet;
- }
+ const contact = walletContract(Buffer.from(publicKey, 'hex'), appContext.defaultWalletVersion);
+ return {
+ rawAddress: contact.address.toRawString(),
+ version: appContext.defaultWalletVersion
+ };
};
export const getWalletAddress = (
- publicKey: Buffer,
+ publicKey: Buffer | string,
version: WalletVersion,
network?: Network
-): WalletAddress => {
+): { address: Address; version: WalletVersion } => {
+ if (typeof publicKey === 'string') {
+ publicKey = Buffer.from(publicKey, 'hex');
+ }
const { address } = walletContract(publicKey, version, network);
return {
- rawAddress: address.toRawString(),
- friendlyAddress: address.toString({
- testOnly: network === Network.TESTNET,
- bounceable: false
- }),
+ address,
version
};
};
@@ -140,7 +149,7 @@ export const getWalletAddress = (
export const getWalletsAddresses = (
publicKey: Buffer | string,
network?: Network
-): Record<(typeof WalletVersions)[number], WalletAddress> => {
+): Record<(typeof WalletVersions)[number], { address: Address; version: WalletVersion }> => {
if (typeof publicKey === 'string') {
publicKey = Buffer.from(publicKey, 'hex');
}
@@ -150,51 +159,14 @@ export const getWalletsAddresses = (
version,
getWalletAddress(publicKey as Buffer, version, network)
])
- ) as Record<(typeof WalletVersions)[number], WalletAddress>;
-};
-
-export const updateWalletVersion = async (
- storage: IStorage,
- wallet: WalletState,
- version: WalletVersion
-) => {
- const updated: WalletState = {
- ...wallet,
- revision: wallet.revision + 1,
- active: getWalletAddress(Buffer.from(wallet.publicKey, 'hex'), version, wallet.network)
- };
- await setWalletState(storage, updated);
+ ) as Record<(typeof WalletVersions)[number], { address: Address; version: WalletVersion }>;
};
-export const updateWalletProperty = async (
+export const accountBySignerQr = async (
+ appContext: { api: APIConfig; defaultWalletVersion: WalletVersion },
storage: IStorage,
- wallet: WalletState,
- props: Partial<
- Pick<
- WalletState,
- | 'name'
- | 'hiddenJettons'
- | 'shownJettons'
- | 'orderJettons'
- | 'lang'
- | 'network'
- | 'emoji'
- >
- >
-) => {
- const updated: WalletState = {
- ...wallet,
- ...props,
- revision: wallet.revision + 1
- };
- await setWalletState(storage, updated);
-};
-
-export const walletStateFromSignerQr = async (
- api: APIConfig,
- qrCode: string,
- config: TonendpointConfig
-) => {
+ qrCode: string
+): Promise => {
if (!qrCode.startsWith('tonkeeper://signer')) {
throw new Error('Unexpected QR code');
}
@@ -212,102 +184,115 @@ export const walletStateFromSignerQr = async (
const publicKey = pk;
- const active = await findWalletAddress(api, publicKey, config.flags?.disable_v5r1 ?? true);
+ // TODO support multiple wallets versions configuration
+ const active = await findWalletAddress(appContext, publicKey);
- const state: WalletState = {
- publicKey,
- active,
- revision: 0,
- name: name ? name : undefined,
- auth: { kind: 'signer' },
- emoji: getFallbackWalletEmoji(publicKey)
- };
+ const { name: fallbackName, emoji } = await accountsStorage(storage).getNewAccountNameAndEmoji(
+ publicKey
+ );
- return state;
+ return new AccountTonOnly(
+ publicKey,
+ name || fallbackName,
+ emoji,
+ { kind: 'signer' },
+ active.rawAddress,
+ [
+ {
+ id: active.rawAddress,
+ publicKey,
+ version: active.version,
+ rawAddress: active.rawAddress
+ }
+ ]
+ );
};
-export const walletStateFromSignerDeepLink = async (
- api: APIConfig,
+export const accountBySignerDeepLink = async (
+ appContext: { api: APIConfig; defaultWalletVersion: WalletVersion },
+ storage: IStorage,
publicKey: string,
- name: string | null,
- config: TonendpointConfig
-) => {
- const active = await findWalletAddress(api, publicKey, config.flags?.disable_v5r1 ?? true);
-
- const state: WalletState = {
- publicKey,
- active,
- revision: 0,
- name: name ? name : undefined,
- auth: { kind: 'signer-deeplink' },
- emoji: getFallbackWalletEmoji(publicKey)
- };
-
- return state;
-};
+ name: string | null
+): Promise => {
+ const active = await findWalletAddress(appContext, publicKey);
-export const walletStateFromLedger = (walletInfo: {
- address: string;
- publicKey: Buffer;
- accountIndex: number;
-}) => {
- const address = Address.parse(walletInfo.address);
- const publicKey = walletInfo.publicKey.toString('hex');
+ const { name: fallbackName, emoji } = await accountsStorage(storage).getNewAccountNameAndEmoji(
+ publicKey
+ );
- const state: WalletState = {
+ return new AccountTonOnly(
publicKey,
- active: {
- friendlyAddress: address.toString({ bounceable: false }),
- rawAddress: address.toRawString(),
- version: WalletVersion.V4R2
- },
- revision: 0,
- name: `Ledger ${walletInfo.accountIndex + 1}`,
- auth: { kind: 'ledger', accountIndex: walletInfo.accountIndex },
- emoji: getFallbackWalletEmoji(publicKey)
- };
+ name || fallbackName,
+ emoji,
+ { kind: 'signer-deeplink' },
+ active.rawAddress,
+ [
+ {
+ id: active.rawAddress,
+ publicKey,
+ version: active.version,
+ rawAddress: active.rawAddress
+ }
+ ]
+ );
+};
- return state;
+export const accountByLedger = (
+ accountId: string,
+ walletsIndexesToAdd: number[],
+ walletsInfo: {
+ address: string;
+ publicKey: Buffer;
+ accountIndex: number;
+ version: WalletVersion;
+ }[],
+ name: string,
+ emoji: string
+): AccountLedger => {
+ return new AccountLedger(
+ accountId,
+ name,
+ emoji,
+ walletsIndexesToAdd[0],
+ walletsIndexesToAdd,
+ walletsInfo.map(item => ({
+ index: item.accountIndex,
+ activeTonWalletId: item.address,
+ tonWallets: [
+ {
+ id: item.address,
+ publicKey: item.publicKey.toString('hex'),
+ version: item.version,
+ rawAddress: item.address
+ }
+ ]
+ }))
+ );
};
-export const walletStateFromKeystone = (ur: UR) => {
+export const accountByKeystone = async (ur: UR, storage: IStorage): Promise => {
const account = parseTonAccount(ur);
const contact = WalletContractV4.create({
workchain: 0,
publicKey: Buffer.from(account.publicKey, 'hex')
});
- const wallet: WalletAddress = {
- rawAddress: contact.address.toRawString(),
- friendlyAddress: contact.address.toString(),
- version: WalletVersion.V4R2
- };
const pathInfo =
account.path && account.xfp ? { path: account.path, mfp: account.xfp } : undefined;
- const state: WalletState = {
- publicKey: account.publicKey,
- active: wallet,
- revision: 0,
- name: account.name ?? `Keystone`,
- auth: { kind: 'keystone', info: pathInfo },
- emoji: getFallbackWalletEmoji(account.publicKey)
- };
+ const { name: fallbackName, emoji } = await accountsStorage(storage).getNewAccountNameAndEmoji(
+ account.publicKey
+ );
- return state;
+ return new AccountKeystone(account.publicKey, account.name || fallbackName, emoji, pathInfo, {
+ id: contact.address.toRawString(),
+ publicKey: account.publicKey,
+ version: WalletVersion.V4R2,
+ rawAddress: contact.address.toRawString()
+ });
};
-export const getWalletAuthState = async (storage: IStorage, publicKey: string) => {
- const wallet = await getWalletStateOrDie(storage, publicKey);
-
- if (wallet.auth) {
- return wallet.auth;
- }
-
- const globalAuth = await storage.get(AppKey.GLOBAL_AUTH_STATE);
- if (!globalAuth) {
- throw new Error('Missing Auth');
- }
-
- return globalAuth;
-};
+export function getFallbackAccountEmoji(publicKey: string) {
+ const index = Number('0x' + publicKey.slice(-6)) % emojis.length;
+ return emojis[index];
+}
diff --git a/packages/core/src/tonkeeperApi/tonendpoint.ts b/packages/core/src/tonkeeperApi/tonendpoint.ts
index e55527b6f..0d542af6b 100644
--- a/packages/core/src/tonkeeperApi/tonendpoint.ts
+++ b/packages/core/src/tonkeeperApi/tonendpoint.ts
@@ -19,7 +19,7 @@ interface BootOptions {
type TonendpointResponse = { success: false } | { success: true; data: Data };
export interface TonendpointConfig {
- flags?: { disable_v5r1: boolean; [key: string]: boolean };
+ flags?: { [key: string]: boolean };
tonendpoint: string;
tonApiKey?: string;
@@ -69,7 +69,7 @@ const defaultTonendpoint = 'https://api.tonkeeper.com'; // 'http://localhost:13
export const defaultTonendpointConfig: TonendpointConfig = {
tonendpoint: defaultTonendpoint,
tonEndpoint: '',
- flags: { disable_v5r1: true }
+ flags: {}
};
const defaultFetch: FetchAPI = (input, init) => window.fetch(input, init);
@@ -190,7 +190,7 @@ export const getServerConfig = async (tonendpoint: Tonendpoint): Promise {
}
};
-export const formatAddress = (value: string, network?: Network, bounceable = false) => {
- return Address.parse(value).toString({
+export const formatAddress = (value: string | Address, network?: Network, bounceable = false) => {
+ return (typeof value === 'string' ? Address.parse(value) : value).toString({
testOnly: network === Network.TESTNET,
bounceable
});
diff --git a/packages/core/src/utils/types.ts b/packages/core/src/utils/types.ts
index ddc3477c5..9ecff3106 100644
--- a/packages/core/src/utils/types.ts
+++ b/packages/core/src/utils/types.ts
@@ -11,3 +11,5 @@ export type NonNullableFields = {
export function notNullish(x: T | null | undefined): x is T {
return x !== null && x !== undefined;
}
+
+export type AllOrNone = Required | Partial>;
diff --git a/packages/locales/src/tonkeeper-web/bg.json b/packages/locales/src/tonkeeper-web/bg.json
index 70080b1b4..1ea113d9b 100644
--- a/packages/locales/src/tonkeeper-web/bg.json
+++ b/packages/locales/src/tonkeeper-web/bg.json
@@ -1,6 +1,7 @@
{
"about_tonkeeper_pro" : "За Tonkeeper Pro",
"actionTitle" : "Отворете портфейла",
+ "add" : "Добави",
"add_dns_address" : "Добавете адрес на портфейл, който домейн % ще свърже.",
"all_assets_jettons" : "Всички активи",
"appExtensionDescription" : "Вашият разширителен портфейл на The Open Network",
@@ -45,6 +46,7 @@
"Enable_storing_config" : "Разрешаване на съхранението на конфигурацията",
"enter_password" : "Въведете парола",
"export_dot_csv" : "Експортиране .CSV",
+ "hide" : "Скрий",
"history_spam_nft" : "Спам NFT",
"I_have_a_backup_copy_of_recovery_phrase" : "Имам резервно копие на фразата за възстановяване",
"import_csv" : "Импортиране на CSV",
@@ -131,6 +133,7 @@
"nft_on_sale_text" : "NFT е на продажба в момента.\nЗа да го прехвърлите, първо трябва да го изтеглите от продажба.",
"no_connected_apps" : "Няма свързани приложения",
"Old_password" : "Текуща парола",
+ "open" : "Отворете",
"operation_not_supported" : "Операцията не е налична за тези портфейли. Изберете друг портфейл и опитайте отново.",
"page_header_history" : "История",
"page_header_purchases" : "Покупки",
@@ -217,6 +220,7 @@
"Unexpected_QR_Code" : "Неочакван QR код",
"Unlock" : "Отключи",
"update" : "Актуализация",
+ "update_ledger_error" : "Уверете се, че софтуерът на Ledger е актуализиран",
"upload_file" : "Качване на файл",
"wallet_address" : "Адрес на портфейл",
"wallet_aside_collectibles" : "Колекционерски предмети",
@@ -227,5 +231,6 @@
"wallet_aside_tokens" : "Токени",
"wallet_multi_send" : "Мулти Изпращане",
"Wallet_name" : "Име на портфейл",
- "wallet_sell" : "Продажба"
+ "wallet_sell" : "Продажба",
+ "wallet_version_and_tokens" : ", токени"
}
\ No newline at end of file
diff --git a/packages/locales/src/tonkeeper-web/en.json b/packages/locales/src/tonkeeper-web/en.json
index 84b4079e1..e6ab72e73 100644
--- a/packages/locales/src/tonkeeper-web/en.json
+++ b/packages/locales/src/tonkeeper-web/en.json
@@ -172,6 +172,7 @@
"save" : "Save",
"settings_collectibles_list" : "Collectibles",
"settings_connected_apps" : "Connected Apps",
+ "settings_ledger_indexes" : "Ledger subwallets",
"start_trial_notification_description" : "Telegram connection is required solely for the purpose of verification that you are not a bot.",
"start_trial_notification_heading" : "Connect Telegram to Pro for Free",
"swap_balance" : "Balance",
diff --git a/packages/locales/src/tonkeeper-web/ru-RU.json b/packages/locales/src/tonkeeper-web/ru-RU.json
index 5eafae00c..e3e4e4591 100644
--- a/packages/locales/src/tonkeeper-web/ru-RU.json
+++ b/packages/locales/src/tonkeeper-web/ru-RU.json
@@ -163,6 +163,7 @@
"save" : "Сохранить",
"settings_collectibles_list" : "Коллекции",
"settings_connected_apps" : "Подключенные приложения",
+ "settings_ledger_indexes" : "Подкошельки леджер",
"start_trial_notification_description" : "Подключение к Telegram необходимо только для проверки, что вы не бот.",
"start_trial_notification_heading" : "Подключите Telegram для пробного периода Pro",
"swap_balance" : "Баланс",
diff --git a/packages/uikit/src/components/BackButton.tsx b/packages/uikit/src/components/BackButton.tsx
index acda81044..d109bb690 100644
--- a/packages/uikit/src/components/BackButton.tsx
+++ b/packages/uikit/src/components/BackButton.tsx
@@ -30,14 +30,17 @@ export const useNativeBackButton = (sdk: IAppSdk, onClick: () => void) => {
}, [sdk, onClick]);
};
-export const BackButtonBlock: FC<{ onClick: () => void }> = ({ onClick }) => {
+export const BackButtonBlock: FC<{ onClick: () => void; className?: string }> = ({
+ onClick,
+ className
+}) => {
const sdk = useAppSdk();
useNativeBackButton(sdk, onClick);
if (sdk.nativeBackButton) {
return <>>;
} else {
return (
-
+
diff --git a/packages/uikit/src/components/Header.tsx b/packages/uikit/src/components/Header.tsx
index 21bd08f2b..b8d3fa714 100644
--- a/packages/uikit/src/components/Header.tsx
+++ b/packages/uikit/src/components/Header.tsx
@@ -2,12 +2,16 @@ import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
import { FC, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import styled, { createGlobalStyle, css } from 'styled-components';
-import { useAppContext, useWalletContext } from '../hooks/appContext';
import { useTranslation } from '../hooks/translation';
import { AppRoute, SettingsRoute } from '../libs/routes';
-import { useMutateActiveWallet } from '../state/account';
import { useUserCountry } from '../state/country';
-import { useWalletState } from '../state/wallet';
+import {
+ useActiveWallet,
+ useAccountsState,
+ useMutateActiveTonWallet,
+ useActiveTonNetwork,
+ useActiveAccount
+} from '../state/wallet';
import { DropDown } from './DropDown';
import { DoneIcon, DownIcon, PlusIcon, SettingsIcon } from './Icon';
import { ColumnText, Divider } from './Layout';
@@ -17,6 +21,13 @@ import { ScanButton } from './connect/ScanButton';
import { ImportNotification } from './create/ImportNotification';
import { SkeletonText } from './shared/Skeleton';
import { WalletEmoji } from './shared/emoji/WalletEmoji';
+import {
+ sortDerivationsByIndex,
+ sortWalletsByVersion,
+ TonWalletStandard
+} from '@tonkeeper/core/dist/entries/wallet';
+import { Account } from '@tonkeeper/core/dist/entries/account';
+import { AccountAndWalletBadgesGroup } from './account/AccountBadge';
const Block = styled.div<{
center?: boolean;
@@ -97,6 +108,7 @@ const Icon = styled.span`
padding-left: 0.5rem;
color: ${props => props.theme.accentBlue};
display: flex;
+ margin-left: auto;
`;
const Row = styled.div`
@@ -114,39 +126,48 @@ const Row = styled.div`
}
`;
+const ListItemPayloadStyled = styled(ListItemPayload)`
+ justify-content: flex-start;
+`;
+
+const ColumnTextStyled = styled(ColumnText)`
+ flex-grow: 0;
+`;
+
+const DropDownContainerStyle = createGlobalStyle`
+ .header-dd-container {
+ margin-left: -135px;
+ width: 270px;
+ }
+`;
+
const WalletRow: FC<{
- activePublicKey?: string;
- publicKey: string;
- index: number;
+ account: Account;
+ wallet: TonWalletStandard;
onClose: () => void;
-}> = ({ activePublicKey, publicKey, index, onClose }) => {
- const { mutate } = useMutateActiveWallet();
- const { t } = useTranslation();
- const { data: wallet } = useWalletState(publicKey);
- const address = wallet
- ? toShortValue(formatAddress(wallet.active.rawAddress, wallet.network))
- : undefined;
+}> = ({ account, wallet, onClose }) => {
+ const network = useActiveTonNetwork();
+ const { mutate } = useMutateActiveTonWallet();
+ const address = toShortValue(formatAddress(wallet.rawAddress, network));
+ const activeWallet = useActiveWallet();
return (
{
- mutate(publicKey);
+ mutate(wallet.id);
onClose();
}}
>
-
- {wallet && }
-
- {activePublicKey === publicKey ? (
+
+
+
+
+ {activeWallet?.id === wallet.id ? (
) : undefined}
-
+
);
};
@@ -156,10 +177,36 @@ const DropDownPayload: FC<{ onClose: () => void; onCreate: () => void }> = ({
onCreate
}) => {
const navigate = useNavigate();
- const { account } = useAppContext();
const { t } = useTranslation();
+ const accountsWallets: { wallet: TonWalletStandard; account: Account }[] =
+ useAccountsState().flatMap(a => {
+ if (a.type === 'ledger') {
+ return a.derivations
+ .slice()
+ .sort(sortDerivationsByIndex)
+ .map(
+ d =>
+ ({
+ wallet: d.tonWallets.find(w => w.id === d.activeTonWalletId)!,
+ account: a
+ } as { wallet: TonWalletStandard; account: Account })
+ );
+ }
+
+ return a.allTonWallets
+ .slice()
+ .sort(sortWalletsByVersion)
+ .map(w => ({
+ wallet: w,
+ account: a
+ }));
+ });
+
+ if (!accountsWallets) {
+ return null;
+ }
- if (account.publicKeys.length === 1) {
+ if (accountsWallets.length === 1) {
return (
{
@@ -176,12 +223,11 @@ const DropDownPayload: FC<{ onClose: () => void; onCreate: () => void }> = ({
} else {
return (
<>
- {account.publicKeys.map((publicKey, index) => (
+ {accountsWallets.map(({ wallet, account }) => (
))}
@@ -202,30 +248,35 @@ const DropDownPayload: FC<{ onClose: () => void; onCreate: () => void }> = ({
}
};
+const TitleStyled = styled(Title)`
+ align-items: center;
+`;
+
export const Header: FC<{ showQrScan?: boolean }> = ({ showQrScan = true }) => {
- const { t } = useTranslation();
- const wallet = useWalletContext();
+ const account = useActiveAccount();
const [isOpen, setOpen] = useState(false);
- const { account } = useAppContext();
- const shouldShowIcon = account.publicKeys.length > 1;
+ const accounts = useAccountsState();
+ const shouldShowIcon = accounts.length > 1;
return (
+
(
setOpen(true)} />
)}
+ containerClassName="header-dd-container"
>
-
- {shouldShowIcon && }
- {wallet.name ? wallet.name : t('wallet_title')}
+
+ {shouldShowIcon && }
+ {account.name}
-
+
{showQrScan && }
diff --git a/packages/uikit/src/components/Icon.tsx b/packages/uikit/src/components/Icon.tsx
index 97293635f..8b4702a99 100644
--- a/packages/uikit/src/components/Icon.tsx
+++ b/packages/uikit/src/components/Icon.tsx
@@ -1933,3 +1933,29 @@ export const BlockIcon: FC<{ className?: string }> = ({ className }) => {
);
};
+
+export const GearIconEmpty: FC<{ className?: string }> = ({ className }) => {
+ return (
+
+ );
+};
diff --git a/packages/uikit/src/components/ModalsRoot.tsx b/packages/uikit/src/components/ModalsRoot.tsx
new file mode 100644
index 000000000..5688d2605
--- /dev/null
+++ b/packages/uikit/src/components/ModalsRoot.tsx
@@ -0,0 +1,11 @@
+import { WalletVersionSettingsNotification } from './modals/WalletVersionSettingsNotification';
+import { LedgerIndexesSettingsNotification } from './modals/LedgerIndexesSettingsNotification';
+
+export const ModalsRoot = () => {
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/packages/uikit/src/components/PairKeystoneNotification.tsx b/packages/uikit/src/components/PairKeystoneNotification.tsx
index 2f3f1ab46..da413caf8 100644
--- a/packages/uikit/src/components/PairKeystoneNotification.tsx
+++ b/packages/uikit/src/components/PairKeystoneNotification.tsx
@@ -4,7 +4,7 @@ import { IAppSdk } from '@tonkeeper/core/dist/AppSdk';
import { KeystoneMessageType, KeystonePathInfo } from '@tonkeeper/core/dist/service/keystone/types';
import { constructKeystoneSignRequest } from '@tonkeeper/core/dist/service/keystone/ur';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
-import { useAppContext, useWalletContext } from '../hooks/appContext';
+import { useAppContext } from '../hooks/appContext';
import { useAppSdk } from '../hooks/appSdk';
import { useKeystoneScanner } from '../hooks/keystoneScanner';
import { useNavigate } from '../hooks/navigate';
@@ -13,6 +13,8 @@ import { Notification, NotificationBlock } from './Notification';
import { Button } from './fields/Button';
import { Background, HeaderBlock } from './home/AccountView';
import { KeystoneAnimatedQRCode } from './home/qrCodeView';
+import { useActiveWallet } from '../state/wallet';
+import { formatAddress } from '@tonkeeper/core/dist/utils/common';
export const SignerContent: FC<{
sdk: IAppSdk;
@@ -24,7 +26,7 @@ export const SignerContent: FC<{
onClose: () => void;
handleResult: (result: string) => void;
}> = ({ sdk, transactionParams, onClose, handleResult }) => {
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const { t } = useTranslation();
const { extension } = useAppContext();
@@ -41,7 +43,7 @@ export const SignerContent: FC<{
return constructKeystoneSignRequest(
transactionParams.message,
transactionParams.messageType,
- wallet.active.friendlyAddress,
+ formatAddress(wallet.rawAddress),
transactionParams.pathInfo
);
}, [transactionParams]);
diff --git a/packages/uikit/src/components/PairSignerNotification.tsx b/packages/uikit/src/components/PairSignerNotification.tsx
index e4087fdbb..894676b0c 100644
--- a/packages/uikit/src/components/PairSignerNotification.tsx
+++ b/packages/uikit/src/components/PairSignerNotification.tsx
@@ -1,7 +1,7 @@
import { IAppSdk } from '@tonkeeper/core/dist/AppSdk';
import { createTransferQr } from '@tonkeeper/core/dist/service/signerService';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
-import { useAppContext, useWalletContext } from '../hooks/appContext';
+import { useAppContext } from '../hooks/appContext';
import { useAppSdk } from '../hooks/appSdk';
import { useNavigate } from '../hooks/navigate';
import { useScanner } from '../hooks/scanner';
@@ -10,6 +10,8 @@ import { Notification, NotificationBlock } from './Notification';
import { Button } from './fields/Button';
import { Background, HeaderBlock } from './home/AccountView';
import { AnimatedQrCode } from './home/qrCodeView';
+import { useActiveWallet } from '../state/wallet';
+import { isStandardTonWallet } from '@tonkeeper/core/dist/entries/wallet';
export const SignerContent: FC<{
sdk: IAppSdk;
@@ -17,7 +19,7 @@ export const SignerContent: FC<{
onClose: () => void;
onSubmit: (result: string) => void;
}> = ({ sdk, boc, onClose, onSubmit }) => {
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const { t } = useTranslation();
const { extension } = useAppContext();
@@ -26,7 +28,10 @@ export const SignerContent: FC<{
const openScanner = useScanner(null, onSubmit);
const message = useMemo(() => {
- return createTransferQr(wallet.publicKey, wallet.active.version, boc);
+ if (!isStandardTonWallet(wallet)) {
+ throw new Error('Unexpected wallet');
+ }
+ return createTransferQr(wallet.publicKey, wallet.version, boc);
}, [wallet, boc]);
return (
diff --git a/packages/uikit/src/components/ScrollContainer.tsx b/packages/uikit/src/components/ScrollContainer.tsx
new file mode 100644
index 000000000..0c9ea51b3
--- /dev/null
+++ b/packages/uikit/src/components/ScrollContainer.tsx
@@ -0,0 +1,30 @@
+import styled, { css } from 'styled-components';
+
+export const ScrollContainer = styled.div<{ thumbColor?: string }>`
+ overflow: auto;
+
+ ${p =>
+ (p.theme.os === 'windows' || p.theme.os === 'linux') &&
+ css`
+ &:hover {
+ &::-webkit-scrollbar-thumb {
+ background-color: ${p.thumbColor || p.theme.backgroundPage};
+ }
+ }
+
+ &::-webkit-scrollbar {
+ -webkit-appearance: none;
+ width: 5px;
+ background-color: transparent;
+ }
+
+ &::-webkit-scrollbar-track {
+ background-color: transparent;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ border-radius: 4px;
+ background-color: transparent;
+ }
+ `}
+`;
diff --git a/packages/uikit/src/components/Skeleton.tsx b/packages/uikit/src/components/Skeleton.tsx
index 062efc1bb..5f2a2f7f0 100644
--- a/packages/uikit/src/components/Skeleton.tsx
+++ b/packages/uikit/src/components/Skeleton.tsx
@@ -2,7 +2,7 @@ import React, { FC, useEffect } from 'react';
import styled from 'styled-components';
import { useAppSdk } from '../hooks/appSdk';
import { InnerBody } from './Body';
-import {ActivityHeader, BrowserHeader, SettingsHeader} from './Header';
+import { ActivityHeader, BrowserHeader, SettingsHeader } from './Header';
import { ActionsRow } from './home/Actions';
import { BalanceSkeleton } from './home/Balance';
import { CoinInfoSkeleton } from './jettons/Info';
@@ -11,8 +11,8 @@ import { ListBlock, ListItem, ListItemPayload } from './List';
import { SubHeader } from './SubHeader';
import { H3 } from './Text';
import { SkeletonImage, SkeletonText } from './shared/Skeleton';
-import {randomIntFromInterval} from "../libs/common";
-import {RecommendationsPageBodySkeleton} from "./skeletons/BrowserSkeletons";
+import { randomIntFromInterval } from '../libs/common';
+import { RecommendationsPageBodySkeleton } from './skeletons/BrowserSkeletons';
export const SkeletonSubHeader = React.memo(() => {
return } />;
@@ -41,7 +41,7 @@ const ListItemBlock = styled.div`
width: 100%;
`;
-export const SkeletonListPayload = React.memo(() => {
+export const SkeletonListPayloadWithImage = React.memo(() => {
return (
@@ -55,13 +55,45 @@ export const SkeletonListPayload = React.memo(() => {
);
});
-export const SkeletonList: FC<{
+export const SkeletonListWithImages: FC<{
size?: number;
margin?: boolean;
fullWidth?: boolean;
}> = React.memo(({ size = 1, margin, fullWidth }) => {
return (
+ {Array(size)
+ .fill(null)
+ .map((_, index) => (
+
+
+
+ ))}
+
+ );
+});
+
+export const SkeletonListPayload = React.memo(() => {
+ return (
+
+
+ }
+ secondary={}
+ >
+
+
+ );
+});
+
+export const SkeletonList: FC<{
+ size?: number;
+ margin?: boolean;
+ fullWidth?: boolean;
+ className?: string;
+}> = React.memo(({ size = 1, margin, fullWidth, className }) => {
+ return (
+
{Array(size)
.fill(null)
.map((_, index) => (
@@ -113,10 +145,10 @@ export const ActivitySkeletonPage = React.memo(() => {
-
-
-
-
+
+
+
+
>
@@ -167,7 +199,7 @@ export const CoinHistorySkeleton = React.memo(() => {
-
+
);
});
@@ -209,7 +241,7 @@ export const HomeSkeleton = React.memo(() => {
{/* */}
-
+
>
);
});
diff --git a/packages/uikit/src/components/account/AccountAndWalletInfo.tsx b/packages/uikit/src/components/account/AccountAndWalletInfo.tsx
new file mode 100644
index 000000000..5873e3136
--- /dev/null
+++ b/packages/uikit/src/components/account/AccountAndWalletInfo.tsx
@@ -0,0 +1,66 @@
+import { Body2 } from '../Text';
+import { WalletEmoji } from '../shared/emoji/WalletEmoji';
+import { Dot } from '../Dot';
+import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
+import { Account } from '@tonkeeper/core/dist/entries/account';
+import { useActiveAccount, useActiveTonNetwork } from '../../state/wallet';
+import { FC } from 'react';
+import { TonWalletStandard, WalletId } from '@tonkeeper/core/dist/entries/wallet';
+import { AccountAndWalletBadgesGroup } from './AccountBadge';
+import { useTranslation } from '../../hooks/translation';
+import styled from 'styled-components';
+import type { AllOrNone } from '@tonkeeper/core/dist/utils/types';
+
+const WalletInfoStyled = styled.div`
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ color: ${p => p.theme.textSecondary};
+`;
+
+const NameText = styled(Body2)`
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+`;
+
+const AddressText = styled(Body2)`
+ flex-shrink: 0;
+ color: ${p => p.theme.textTertiary};
+`;
+
+export const AccountAndWalletInfo: FC<
+ AllOrNone<{ account: Account; walletId: WalletId }>
+> = props => {
+ const { t } = useTranslation();
+ let account: Account = useActiveAccount();
+ let wallet: TonWalletStandard = account.activeTonWallet;
+ const network = useActiveTonNetwork();
+
+ if ('account' in props && props.account) {
+ account = props.account;
+ wallet = account.getTonWallet(props.walletId)!;
+ }
+
+ return (
+
+
+ {t('confirmSendModal_wallet')}
+ {account.name}
+
+
+ {account.allTonWallets.length > 1 ? (
+ <>
+
+
+ {toShortValue(formatAddress(wallet.rawAddress, network))}
+
+
+ >
+ ) : (
+
+ )}
+
+ );
+};
diff --git a/packages/uikit/src/components/account/AccountBadge.tsx b/packages/uikit/src/components/account/AccountBadge.tsx
new file mode 100644
index 000000000..25ab45dbc
--- /dev/null
+++ b/packages/uikit/src/components/account/AccountBadge.tsx
@@ -0,0 +1,137 @@
+import { FC, PropsWithChildren } from 'react';
+import { Account } from '@tonkeeper/core/dist/entries/account';
+import { Badge } from '../shared';
+import { WalletId, WalletVersion, walletVersionText } from '@tonkeeper/core/dist/entries/wallet';
+import styled from 'styled-components';
+
+export const AccountBadge: FC<
+ PropsWithChildren<{
+ accountType: Account['type'];
+ size?: 's' | 'm';
+ className?: string;
+ }>
+> = ({ accountType, size = 'm', className, children }) => {
+ if (accountType === 'ledger') {
+ return (
+
+ {children || 'Ledger'}
+
+ );
+ }
+
+ if (accountType === 'ton-only') {
+ return (
+
+ {children || 'Signer'}
+
+ );
+ }
+
+ if (accountType === 'keystone') {
+ return (
+
+ {children || 'Keystone'}
+
+ );
+ }
+
+ return null;
+};
+
+export const WalletVersionBadge: FC<{
+ walletVersion: WalletVersion;
+ size?: 's' | 'm';
+ className?: string;
+}> = ({ walletVersion, size = 'm', className }) => {
+ return (
+
+ {walletVersionText(walletVersion)}
+
+ );
+};
+
+export const WalletIndexBadge: FC<
+ PropsWithChildren<{
+ size?: 's' | 'm';
+ className?: string;
+ }>
+> = ({ size = 'm', className, children }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+const Container = styled.div`
+ flex-shrink: 0;
+ display: flex;
+
+ & > *:nth-child(2) {
+ margin-left: 3px;
+ }
+`;
+
+export const AccountAndWalletBadgesGroup: FC<{
+ account: Account;
+ walletId: WalletId;
+ size?: 's' | 'm';
+ className?: string;
+}> = ({ account, walletId, className, size }) => {
+ if (account.type === 'ledger') {
+ const derivation = account.derivations.find(d => d.tonWallets.some(w => w.id === walletId));
+ return (
+
+
+ Ledger
+
+ {account.derivations.length > 1 && !!derivation && (
+ #{derivation.index + 1}
+ )}
+
+ );
+ }
+
+ if (account.type === 'ton-only') {
+ const wallet = account.tonWallets.find(w => w.id === walletId);
+ return (
+
+
+ Signer
+
+ {account.tonWallets.length > 1 && !!wallet && (
+
+ )}
+
+ );
+ }
+
+ if (account.type === 'keystone') {
+ return ;
+ }
+
+ if (account.type === 'mnemonic' && account.tonWallets.length > 1) {
+ const wallet = account.tonWallets.find(w => w.id === walletId);
+ if (wallet) {
+ return (
+
+ );
+ }
+ }
+
+ return null;
+};
diff --git a/packages/uikit/src/components/activity/ActivityGroup.tsx b/packages/uikit/src/components/activity/ActivityGroup.tsx
index 23c868021..e5d6d261f 100644
--- a/packages/uikit/src/components/activity/ActivityGroup.tsx
+++ b/packages/uikit/src/components/activity/ActivityGroup.tsx
@@ -2,14 +2,15 @@ import { InfiniteData } from '@tanstack/react-query';
import { AccountEvents } from '@tonkeeper/core/dist/tonApiV2';
import { TronEvents } from '@tonkeeper/core/dist/tronApi';
import React, { FC, useMemo, useState } from 'react';
-import { GenericActivityGroup } from '../../state/activity';
+import { formatActivityDate, GenericActivityGroup, getActivityTitle } from '../../state/activity';
import { MixedActivity, getMixedActivityGroups } from '../../state/mixedActivity';
-import { CoinHistorySkeleton, HistoryBlock, SkeletonList } from '../Skeleton';
-import { ActivityBlock } from './ActivityLayout';
+import { CoinHistorySkeleton, HistoryBlock, SkeletonListWithImages } from '../Skeleton';
+import { Group, List, Title } from './ActivityLayout';
import { ActionData, ActivityNotification } from './ton/ActivityNotification';
import { TonActivityEvents } from './ton/TonActivityEvents';
import { TronActionData, TronActivityNotification } from './tron/ActivityNotification';
import { TronActivityEvents } from './tron/TronActivityEvents';
+import { useTranslation } from '../../hooks/translation';
export const ActivityList: FC<{
isFetched: boolean;
@@ -27,7 +28,7 @@ export const ActivityList: FC<{
return (
- {isFetchingNextPage && }
+ {isFetchingNextPage && }
);
};
@@ -37,35 +38,41 @@ export const MixedActivityGroup: FC<{
}> = ({ items }) => {
const [tonAction, seTonAction] = useState(undefined);
const [tronAction, setTronAction] = useState(undefined);
+ const { i18n } = useTranslation();
return (
<>
- {
- if (event.kind === 'tron') {
- return (
-
- );
- }
- if (event.kind === 'ton') {
- return (
-
- );
- }
- return <>>;
- }}
- />
+ {items.map(([eventKey, events]) => {
+ return (
+
+
+ {getActivityTitle(i18n.language, eventKey, events[0].timestamp)}
+
+ {events.map(({ timestamp, event, key }) => {
+ const date = formatActivityDate(i18n.language, eventKey, timestamp);
+ return (
+
+ {event.kind === 'tron' ? (
+
+ ) : event.kind === 'ton' ? (
+
+ ) : null}
+
+ );
+ })}
+
+ );
+ })}
seTonAction(undefined)} />
{
);
};
-
-export function ActivityBlock({
- groups,
- RenderItem
-}: {
- groups: GenericActivityGroup[];
- RenderItem: (props: { event: T; date: string; timestamp: number }) => React.ReactElement;
-}) {
- const { i18n } = useTranslation();
- return (
- <>
- {groups.map(([eventKey, events]) => {
- return (
-
-
- {getActivityTitle(i18n.language, eventKey, events[0].timestamp)}
-
- {events.map(({ timestamp, event, key }) => {
- const date = formatActivityDate(i18n.language, eventKey, timestamp);
- return (
-
-
-
- );
- })}
-
- );
- })}
- >
- );
-}
diff --git a/packages/uikit/src/components/activity/NotificationCommon.tsx b/packages/uikit/src/components/activity/NotificationCommon.tsx
index 6bec3b1b0..ad6a11718 100644
--- a/packages/uikit/src/components/activity/NotificationCommon.tsx
+++ b/packages/uikit/src/components/activity/NotificationCommon.tsx
@@ -13,7 +13,7 @@ import { formatDecimals } from '@tonkeeper/core/dist/utils/balance';
import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
import { FC, PropsWithChildren, useMemo } from 'react';
import styled from 'styled-components';
-import { useAppContext, useWalletContext } from '../../hooks/appContext';
+import { useAppContext } from '../../hooks/appContext';
import { useAppSdk } from '../../hooks/appSdk';
import { formatFiatCurrency, useCoinFullBalance } from '../../hooks/balance';
import { useTranslation } from '../../hooks/translation';
@@ -25,6 +25,7 @@ import { ListItem, ListItemPayload } from '../List';
import { Body1, H2, Label1 } from '../Text';
import { Button } from '../fields/Button';
import { hexToRGBA } from '../../libs/css';
+import { useActiveTonNetwork } from '../../state/wallet';
export const Title = styled(H2)<{ secondary?: boolean; tertiary?: boolean }>`
display: flex;
@@ -163,7 +164,7 @@ export const ActionRecipientDetails: FC<{ recipient: AccountAddress; bounced?: b
}) => {
const { t } = useTranslation();
const sdk = useAppSdk();
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
return (
<>
@@ -176,7 +177,7 @@ export const ActionRecipientDetails: FC<{ recipient: AccountAddress; bounced?: b
)}
>
@@ -185,7 +186,7 @@ export const ActionRecipientDetails: FC<{ recipient: AccountAddress; bounced?: b
export const ActionPoolDetails: FC<{ pool: AccountAddress }> = ({ pool }) => {
const { t } = useTranslation();
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
return (
<>
@@ -195,7 +196,7 @@ export const ActionPoolDetails: FC<{ pool: AccountAddress }> = ({ pool }) => {
>
@@ -222,7 +223,7 @@ export const ActionSenderDetails: FC<{ sender: AccountAddress; bounced?: boolean
}) => {
const { t } = useTranslation();
const sdk = useAppSdk();
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
return (
<>
@@ -235,7 +236,7 @@ export const ActionSenderDetails: FC<{ sender: AccountAddress; bounced?: boolean
)}
>
@@ -245,9 +246,9 @@ export const ActionSenderDetails: FC<{ sender: AccountAddress; bounced?: boolean
export const ActionBeneficiaryDetails: FC<{ beneficiary: AccountAddress }> = ({ beneficiary }) => {
const { t } = useTranslation();
const sdk = useAppSdk();
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
- const address = formatAddress(beneficiary.address, wallet.network, true);
+ const address = formatAddress(beneficiary.address, network, true);
return (
<>
{beneficiary.name && (
@@ -302,8 +303,8 @@ export const ActionDeployerAddress: FC<{ address?: string }> = ({ address }) =>
};
export const ActionDeployerDetails: FC<{ deployer: string }> = ({ deployer }) => {
- const wallet = useWalletContext();
- return ;
+ const network = useActiveTonNetwork();
+ return ;
};
export const ActionFeeDetails: FC<{
@@ -435,9 +436,9 @@ export const TronActionDetailsBlock: FC>
event,
children
}) => {
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
const url =
- wallet.network === Network.TESTNET
+ network === Network.TESTNET
? 'https://nile.tronscan.org/#/transaction/%s'
: 'https://tronscan.org/#/transaction/%s';
return (
diff --git a/packages/uikit/src/components/activity/ton/ActivityActionDetails.tsx b/packages/uikit/src/components/activity/ton/ActivityActionDetails.tsx
index 4e4d7b147..6b5e8fbd2 100644
--- a/packages/uikit/src/components/activity/ton/ActivityActionDetails.tsx
+++ b/packages/uikit/src/components/activity/ton/ActivityActionDetails.tsx
@@ -2,7 +2,6 @@ import { CryptoCurrency } from '@tonkeeper/core/dist/entries/crypto';
import { AccountEvent, ActionStatusEnum, TonTransferAction } from '@tonkeeper/core/dist/tonApiV2';
import { formatDecimals } from '@tonkeeper/core/dist/utils/balance';
import { FC } from 'react';
-import { useWalletContext } from '../../../hooks/appContext';
import { useFormatCoinValue } from '../../../hooks/balance';
import { useTranslation } from '../../../hooks/translation';
import { useFormatFiat, useRate } from '../../../state/rates';
@@ -26,6 +25,7 @@ import {
Title
} from '../NotificationCommon';
import { ActionData } from './ActivityNotification';
+import { useActiveWallet } from '../../../state/wallet';
const TonTransferActionContent: FC<{
tonTransfer: TonTransferAction;
@@ -34,11 +34,11 @@ const TonTransferActionContent: FC<{
isScam: boolean;
status?: ActionStatusEnum;
}> = ({ tonTransfer, timestamp, event, isScam, status }) => {
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const { data } = useRate(CryptoCurrency.TON);
const { fiatAmount } = useFormatFiat(data, formatDecimals(tonTransfer.amount));
- const kind = tonTransfer.recipient.address === wallet.active.rawAddress ? 'received' : 'send';
+ const kind = tonTransfer.recipient.address === wallet.rawAddress ? 'received' : 'send';
return (
@@ -116,8 +116,7 @@ export const AuctionBidActionDetails: FC = ({ action, timestamp, eve
};
export const DomainRenewActionDetails: FC = ({ action, timestamp, event }) => {
- const { t } = useTranslation();
- const { domainRenew, simplePreview } = action;
+ const { domainRenew } = action;
if (!domainRenew) {
return ;
diff --git a/packages/uikit/src/components/activity/ton/ContractDeployAction.tsx b/packages/uikit/src/components/activity/ton/ContractDeployAction.tsx
index aa217df87..80debbcf3 100644
--- a/packages/uikit/src/components/activity/ton/ContractDeployAction.tsx
+++ b/packages/uikit/src/components/activity/ton/ContractDeployAction.tsx
@@ -1,7 +1,6 @@
import { Action } from '@tonkeeper/core/dist/tonApiV2';
import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
import React, { FC } from 'react';
-import { useWalletContext } from '../../../hooks/appContext';
import { useTranslation } from '../../../hooks/translation';
import { ListBlock } from '../../List';
import { ContractDeployActivityAction, WalletDeployActivityAction } from '../ActivityActionLayout';
@@ -19,6 +18,7 @@ import {
} from '../NotificationCommon';
import { ActionData } from './ActivityNotification';
import { NftComment } from './NftActivity';
+import { useActiveTonNetwork } from '../../../state/wallet';
export const ContractDeployActionDetails: FC = ({ action, timestamp, event }) => {
const { t } = useTranslation();
@@ -54,7 +54,7 @@ export const ContractDeployAction: FC<{
}> = ({ action, date }) => {
const { t } = useTranslation();
const { contractDeploy } = action;
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
if (!contractDeploy) {
return ;
}
@@ -62,7 +62,7 @@ export const ContractDeployAction: FC<{
const address = toShortValue(
formatAddress(
contractDeploy.address,
- wallet.network,
+ network,
!interfaces.some(value => value.includes('wallet'))
)
);
diff --git a/packages/uikit/src/components/activity/ton/JettonActivity.tsx b/packages/uikit/src/components/activity/ton/JettonActivity.tsx
index 282e77577..c769abfbb 100644
--- a/packages/uikit/src/components/activity/ton/JettonActivity.tsx
+++ b/packages/uikit/src/components/activity/ton/JettonActivity.tsx
@@ -1,7 +1,6 @@
import { Action } from '@tonkeeper/core/dist/tonApiV2';
import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
import React, { FC } from 'react';
-import { useWalletContext } from '../../../hooks/appContext';
import { useFormatCoinValue } from '../../../hooks/balance';
import { useTranslation } from '../../../hooks/translation';
import { FailedNote, ReceiveActivityAction, SendActivityAction } from '../ActivityActionLayout';
@@ -19,6 +18,7 @@ import {
} from '../CommonAction';
import { toDexName } from '../NotificationCommon';
import { useSwapValue } from './JettonNotifications';
+import { useActiveTonNetwork, useActiveWallet } from '../../../state/wallet';
export interface JettonActionProps {
action: Action;
@@ -26,7 +26,8 @@ export interface JettonActionProps {
}
export const JettonTransferAction: FC<{ action: Action; date: string }> = ({ action, date }) => {
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
+ const network = useActiveTonNetwork();
const { jettonTransfer } = action;
const format = useFormatCoinValue();
@@ -37,7 +38,7 @@ export const JettonTransferAction: FC<{ action: Action; date: string }> = ({ act
const isScam = jettonTransfer.jetton.verification === 'blacklist';
- if (jettonTransfer.sender?.address === wallet.active.rawAddress) {
+ if (jettonTransfer.sender?.address === wallet.rawAddress) {
return (
= ({ act
toShortValue(
formatAddress(
jettonTransfer.recipient?.address ?? jettonTransfer.recipientsWallet,
- wallet.network
+ network
)
)
}
@@ -68,7 +69,7 @@ export const JettonTransferAction: FC<{ action: Action; date: string }> = ({ act
toShortValue(
formatAddress(
jettonTransfer.sender?.address ?? jettonTransfer.sendersWallet,
- wallet.network
+ network
)
)
}
@@ -123,7 +124,7 @@ export const JettonBurnAction: FC = ({ action, date }) => {
const { t } = useTranslation();
const { jettonBurn } = action;
const format = useFormatCoinValue();
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
if (!jettonBurn) {
return ;
@@ -137,9 +138,7 @@ export const JettonBurnAction: FC = ({ action, date }) => {
title={t('transactions_burned')}
amount={<>- {format(jettonBurn.amount, jettonBurn.jetton.decimals)}>}
entry={jettonBurn.jetton.symbol}
- address={toShortValue(
- formatAddress(jettonBurn.jetton.address, wallet.network, true)
- )}
+ address={toShortValue(formatAddress(jettonBurn.jetton.address, network, true))}
date={date}
/>
@@ -151,7 +150,7 @@ export const JettonMintAction: FC = ({ action, date }) => {
const { t } = useTranslation();
const { jettonMint } = action;
const format = useFormatCoinValue();
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
if (!jettonMint) {
return ;
@@ -165,9 +164,7 @@ export const JettonMintAction: FC = ({ action, date }) => {
title={t('transaction_type_mint')}
amount={<>+ {format(jettonMint.amount, jettonMint.jetton.decimals)}>}
entry={jettonMint.jetton.symbol}
- address={toShortValue(
- formatAddress(jettonMint.jetton.address, wallet.network, true)
- )}
+ address={toShortValue(formatAddress(jettonMint.jetton.address, network, true))}
date={date}
green
/>
diff --git a/packages/uikit/src/components/activity/ton/JettonNotifications.tsx b/packages/uikit/src/components/activity/ton/JettonNotifications.tsx
index 123bd8827..a62c85df5 100644
--- a/packages/uikit/src/components/activity/ton/JettonNotifications.tsx
+++ b/packages/uikit/src/components/activity/ton/JettonNotifications.tsx
@@ -10,7 +10,6 @@ import {
} from '@tonkeeper/core/dist/tonApiV2';
import { formatDecimals } from '@tonkeeper/core/dist/utils/balance';
import { FC, useMemo } from 'react';
-import { useWalletContext } from '../../../hooks/appContext';
import { useFormatCoinValue } from '../../../hooks/balance';
import { useTranslation } from '../../../hooks/translation';
import { useFormatFiat, useRate } from '../../../state/rates';
@@ -27,6 +26,7 @@ import {
Title
} from '../NotificationCommon';
import { ActionData } from './ActivityNotification';
+import { useActiveWallet } from '../../../state/wallet';
const JettonTransferActionContent: FC<{
jettonTransfer: JettonTransferAction;
@@ -35,14 +35,14 @@ const JettonTransferActionContent: FC<{
isScam: boolean;
status?: ActionStatusEnum;
}> = ({ jettonTransfer, timestamp, event, isScam, status }) => {
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const { data } = useRate(Address.parse(jettonTransfer.jetton.address).toString());
const { fiatAmount } = useFormatFiat(
data,
formatDecimals(jettonTransfer.amount, jettonTransfer.jetton.decimals)
);
const blacklist = jettonTransfer.jetton.verification === 'blacklist';
- const kind = jettonTransfer.sender?.address === wallet.active.rawAddress ? 'send' : 'received';
+ const kind = jettonTransfer.sender?.address === wallet.rawAddress ? 'send' : 'received';
return (
diff --git a/packages/uikit/src/components/activity/ton/NftActivity.tsx b/packages/uikit/src/components/activity/ton/NftActivity.tsx
index 0485830bd..14bd7a0d5 100644
--- a/packages/uikit/src/components/activity/ton/NftActivity.tsx
+++ b/packages/uikit/src/components/activity/ton/NftActivity.tsx
@@ -3,10 +3,9 @@ import { formatDecimals } from '@tonkeeper/core/dist/utils/balance';
import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
import React, { FC, useState } from 'react';
import styled, { css } from 'styled-components';
-import { useWalletContext } from '../../../hooks/appContext';
import { useAppSdk } from '../../../hooks/appSdk';
import { useTranslation } from '../../../hooks/translation';
-import { useNftItemData } from '../../../state/wallet';
+import { useActiveTonNetwork, useActiveWallet } from '../../../state/wallet';
import { InfoCircleIcon, VerificationIcon } from '../../Icon';
import { ListBlock } from '../../List';
import { Body1, Body2 } from '../../Text';
@@ -44,7 +43,8 @@ import {
useIsSpamNft,
useIsUnverifiedNft,
useMarkNftAsSpam,
- useMarkNftAsTrusted
+ useMarkNftAsTrusted,
+ useNftItemData
} from '../../../state/nft';
const NftBlock = styled.div`
@@ -141,13 +141,14 @@ export const NftItemTransferAction: FC<{
date: string;
}> = ({ action, date }) => {
const { t } = useTranslation();
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
+ const network = useActiveTonNetwork();
const { nftItemTransfer } = action;
if (!nftItemTransfer) {
return ;
}
- if (nftItemTransfer.recipient?.address === wallet.active.rawAddress) {
+ if (nftItemTransfer.recipient?.address === wallet.rawAddress) {
return (
@@ -161,7 +162,7 @@ export const NftItemTransferAction: FC<{
toShortValue(
formatAddress(
nftItemTransfer.sender?.address ?? nftItemTransfer.nft,
- wallet.network,
+ network,
!nftItemTransfer.sender?.address
)
)
@@ -189,7 +190,7 @@ export const NftItemTransferAction: FC<{
toShortValue(
formatAddress(
nftItemTransfer.recipient?.address ?? nftItemTransfer.nft,
- wallet.network,
+ network,
!nftItemTransfer.recipient?.address
)
)
@@ -376,7 +377,7 @@ const NftActivityHeader: FC<{
};
export const NftItemTransferActionDetails: FC = ({ action, timestamp, event }) => {
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const { nftItemTransfer } = action;
const { data } = useNftItemData(nftItemTransfer?.nft);
@@ -385,8 +386,7 @@ export const NftItemTransferActionDetails: FC = ({ action, timestamp
return ;
}
- const kind =
- nftItemTransfer.recipient?.address === wallet.active.rawAddress ? 'received' : 'send';
+ const kind = nftItemTransfer.recipient?.address === wallet.rawAddress ? 'received' : 'send';
return (
diff --git a/packages/uikit/src/components/activity/ton/StakeActivity.tsx b/packages/uikit/src/components/activity/ton/StakeActivity.tsx
index 22a30bdd2..de6e78900 100644
--- a/packages/uikit/src/components/activity/ton/StakeActivity.tsx
+++ b/packages/uikit/src/components/activity/ton/StakeActivity.tsx
@@ -2,12 +2,12 @@ import { CryptoCurrency } from '@tonkeeper/core/dist/entries/crypto';
import { Action } from '@tonkeeper/core/dist/tonApiV2';
import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
import React, { FC } from 'react';
-import { useWalletContext } from '../../../hooks/appContext';
import { useFormatCoinValue } from '../../../hooks/balance';
import { useTranslation } from '../../../hooks/translation';
import { FailedNote } from '../ActivityActionLayout';
import { ActivityIcon, ReceiveIcon, SentIcon } from '../ActivityIcons';
import { ColumnLayout, ErrorAction, ListItemGrid } from '../CommonAction';
+import { useActiveTonNetwork } from '../../../state/wallet';
export const DepositStakeAction: FC<{
action: Action;
@@ -15,7 +15,7 @@ export const DepositStakeAction: FC<{
}> = ({ action, date }) => {
const { t } = useTranslation();
const { depositStake } = action;
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
const format = useFormatCoinValue();
if (!depositStake) {
@@ -33,7 +33,7 @@ export const DepositStakeAction: FC<{
entry={CryptoCurrency.TON}
address={
depositStake.pool.name ??
- toShortValue(formatAddress(depositStake.pool.address, wallet.network, true))
+ toShortValue(formatAddress(depositStake.pool.address, network, true))
}
date={date}
/>
@@ -48,7 +48,7 @@ export const WithdrawStakeAction: FC<{
}> = ({ action, date }) => {
const { t } = useTranslation();
const { withdrawStake } = action;
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
const format = useFormatCoinValue();
if (!withdrawStake) {
return ;
@@ -65,7 +65,7 @@ export const WithdrawStakeAction: FC<{
green
address={
withdrawStake.pool.name ??
- toShortValue(formatAddress(withdrawStake.pool.address, wallet.network, true))
+ toShortValue(formatAddress(withdrawStake.pool.address, network, true))
}
date={date}
/>
@@ -80,7 +80,7 @@ export const WithdrawRequestStakeAction: FC<{
}> = ({ action, date }) => {
const { t } = useTranslation();
const { withdrawStakeRequest } = action;
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
const format = useFormatCoinValue();
if (!withdrawStakeRequest) {
return ;
@@ -101,9 +101,7 @@ export const WithdrawRequestStakeAction: FC<{
entry={withdrawStakeRequest.amount ? CryptoCurrency.TON : ''}
address={
withdrawStakeRequest.pool.name ??
- toShortValue(
- formatAddress(withdrawStakeRequest.pool.address, wallet.network, true)
- )
+ toShortValue(formatAddress(withdrawStakeRequest.pool.address, network, true))
}
date={date}
/>
diff --git a/packages/uikit/src/components/activity/ton/SubscribeAction.tsx b/packages/uikit/src/components/activity/ton/SubscribeAction.tsx
index c9d770c30..3cbc29efe 100644
--- a/packages/uikit/src/components/activity/ton/SubscribeAction.tsx
+++ b/packages/uikit/src/components/activity/ton/SubscribeAction.tsx
@@ -1,7 +1,6 @@
import { Action } from '@tonkeeper/core/dist/tonApiV2';
import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
import React, { FC } from 'react';
-import { useWalletContext } from '../../../hooks/appContext';
import { useTranslation } from '../../../hooks/translation';
import { ListBlock } from '../../List';
import { FailedDetail } from '../ActivityDetailsLayout';
@@ -17,6 +16,7 @@ import {
Title
} from '../NotificationCommon';
import { ActionData } from './ActivityNotification';
+import { useActiveTonNetwork } from '../../../state/wallet';
export const UnSubscribeActionDetails: FC = ({ action, timestamp, event }) => {
const { t } = useTranslation();
@@ -69,7 +69,7 @@ export const SubscribeActionDetails: FC = ({ action, timestamp, even
export const UnSubscribeAction: FC<{ action: Action; date: string }> = ({ action, date }) => {
const { t } = useTranslation();
const { unSubscribe } = action;
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
if (!unSubscribe) {
return ;
@@ -84,7 +84,7 @@ export const UnSubscribeAction: FC<{ action: Action; date: string }> = ({ action
entry="-"
address={
unSubscribe.beneficiary.name ??
- toShortValue(formatAddress(unSubscribe.beneficiary.address, wallet.network))
+ toShortValue(formatAddress(unSubscribe.beneficiary.address, network))
}
date={date}
/>
@@ -95,7 +95,7 @@ export const UnSubscribeAction: FC<{ action: Action; date: string }> = ({ action
export const SubscribeAction: FC<{ action: Action; date: string }> = ({ action, date }) => {
const { t } = useTranslation();
const { subscribe } = action;
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
if (!subscribe) {
return ;
@@ -111,7 +111,7 @@ export const SubscribeAction: FC<{ action: Action; date: string }> = ({ action,
entry="-"
address={
subscribe.beneficiary.name ??
- toShortValue(formatAddress(subscribe.beneficiary.address, wallet.network))
+ toShortValue(formatAddress(subscribe.beneficiary.address, network))
}
date={date}
/>
diff --git a/packages/uikit/src/components/activity/ton/TonActivityAction.tsx b/packages/uikit/src/components/activity/ton/TonActivityAction.tsx
index ad2f32bac..3c8799ea6 100644
--- a/packages/uikit/src/components/activity/ton/TonActivityAction.tsx
+++ b/packages/uikit/src/components/activity/ton/TonActivityAction.tsx
@@ -8,7 +8,6 @@ import {
ContractDeployIcon,
SentIcon
} from '../../../components/activity/ActivityIcons';
-import { useWalletContext } from '../../../hooks/appContext';
import { useFormatCoinValue } from '../../../hooks/balance';
import { useTranslation } from '../../../hooks/translation';
import { FailedNote, ReceiveActivityAction, SendActivityAction } from '../ActivityActionLayout';
@@ -37,14 +36,16 @@ import {
WithdrawStakeAction
} from './StakeActivity';
import { SubscribeAction, UnSubscribeAction } from './SubscribeAction';
+import { useActiveTonNetwork, useActiveWallet } from '../../../state/wallet';
const TonTransferAction: FC<{
action: Action;
date: string;
isScam: boolean;
}> = ({ action, date, isScam }) => {
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const { tonTransfer } = action;
+ const network = useActiveTonNetwork();
const format = useFormatCoinValue();
@@ -52,13 +53,13 @@ const TonTransferAction: FC<{
return ;
}
- if (tonTransfer.recipient.address === wallet.active.rawAddress) {
+ if (tonTransfer.recipient.address === wallet.rawAddress) {
return (
= ({ action, date }) => {
const { t } = useTranslation();
const { smartContractExec } = action;
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const format = useFormatCoinValue();
+ const network = useActiveTonNetwork();
if (!smartContractExec) {
return ;
}
- if (seeIfAddressEqual(smartContractExec.contract.address, wallet.active.rawAddress)) {
+ if (seeIfAddressEqual(smartContractExec.contract.address, wallet.rawAddress)) {
return (
@@ -109,7 +111,7 @@ export const SmartContractExecAction: FC<{
green
entry={CryptoCurrency.TON}
address={toShortValue(
- formatAddress(smartContractExec.contract.address, wallet.network)
+ formatAddress(smartContractExec.contract.address, network)
)}
date={date}
/>
@@ -127,7 +129,7 @@ export const SmartContractExecAction: FC<{
amount={<>- {format(smartContractExec.tonAttached)}>}
entry={CryptoCurrency.TON}
address={toShortValue(
- formatAddress(smartContractExec.contract.address, wallet.network, true)
+ formatAddress(smartContractExec.contract.address, network, true)
)}
date={date}
/>
@@ -143,7 +145,7 @@ const AuctionBidAction: FC<{
}> = ({ action, date }) => {
const { t } = useTranslation();
const { auctionBid } = action;
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
const format = useFormatCoinValue();
if (!auctionBid) {
@@ -166,7 +168,7 @@ const AuctionBidAction: FC<{
{(auctionBid.auctionType as string) !== ''
? auctionBid.auctionType
: toShortValue(
- formatAddress(auctionBid.auction.address, wallet.network, true)
+ formatAddress(auctionBid.auction.address, network, true)
)}
{date}
@@ -181,10 +183,7 @@ const DomainRenewAction: FC<{
action: Action;
date: string;
}> = ({ action, date }) => {
- const { t } = useTranslation();
const { domainRenew, simplePreview } = action;
- const wallet = useWalletContext();
- const format = useFormatCoinValue();
if (!domainRenew) {
return ;
diff --git a/packages/uikit/src/components/connect/TonConnectNotification.tsx b/packages/uikit/src/components/connect/TonConnectNotification.tsx
index 83ee88a77..7135ddcc6 100644
--- a/packages/uikit/src/components/connect/TonConnectNotification.tsx
+++ b/packages/uikit/src/components/connect/TonConnectNotification.tsx
@@ -4,12 +4,9 @@ import {
ConnectRequest,
DAppManifest
} from '@tonkeeper/core/dist/entries/tonConnect';
-import { walletVersionText } from '@tonkeeper/core/dist/entries/wallet';
import { getManifest } from '@tonkeeper/core/dist/service/tonConnect/connectService';
-import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
import React, { FC, useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
-import { useWalletContext } from '../../hooks/appContext';
import { useAppSdk } from '../../hooks/appSdk';
import { useTranslation } from '../../hooks/translation';
import { TxConfirmationCustomError } from '../../libs/errors/TxConfirmationCustomError';
@@ -21,14 +18,17 @@ import { Body2, Body3, H2, Label2 } from '../Text';
import { Button } from '../fields/Button';
import { ResultButton } from '../transfer/common';
import { useConnectTonConnectAppMutation } from '../../state/tonConnect';
+import { AccountAndWalletInfo } from '../account/AccountAndWalletInfo';
const Title = styled(H2)`
text-align: center;
user-select: none;
`;
const SubTitle = styled(Body2)`
- margin-ton: 4px;
- display: block;
+ margin-top: 4px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
color: ${props => props.theme.textSecondary};
text-align: center;
user-select: none;
@@ -42,10 +42,6 @@ const Notes = styled(Body3)`
max-width: 300px;
`;
-const Address = styled.span`
- color: ${props => props.theme.textTertiary};
-`;
-
const ImageRow = styled.div`
display: flex;
width: 100%;
@@ -84,8 +80,6 @@ const ConnectContent: FC<{
const sdk = useAppSdk();
const [done, setDone] = useState(false);
- const wallet = useWalletContext();
-
const { t } = useTranslation();
useEffect(() => {
@@ -109,8 +103,6 @@ const ConnectContent: FC<{
}
};
- const address = formatAddress(wallet.active.rawAddress, wallet.network);
-
let shortUrl = manifest.url;
try {
shortUrl = new URL(manifest.url).hostname;
@@ -132,8 +124,7 @@ const ConnectContent: FC<{
{t('ton_login_title_web').replace('%{name}', shortUrl)}
{t('ton_login_caption').replace('%{name}', getDomain(manifest.name))}{' '}
- {toShortValue(address)}{' '}
- {walletVersionText(wallet.active.version)}
+
diff --git a/packages/uikit/src/components/connect/TonConnectSubscription.tsx b/packages/uikit/src/components/connect/TonConnectSubscription.tsx
index 5508e1851..4e2298f33 100644
--- a/packages/uikit/src/components/connect/TonConnectSubscription.tsx
+++ b/packages/uikit/src/components/connect/TonConnectSubscription.tsx
@@ -7,9 +7,7 @@ import {
import { subscribeTonConnect } from '@tonkeeper/core/dist/service/tonConnect/httpBridge';
import { useCallback, useEffect, useState } from 'react';
import { useSendNotificationAnalytics } from '../../hooks/amplitude';
-import { useWalletContext } from '../../hooks/appContext';
import { useAppSdk } from '../../hooks/appSdk';
-import { useMutateActiveWallet } from '../../state/account';
import {
tonConnectAppManuallyDisconnected$,
useAppTonConnectConnections,
@@ -19,6 +17,8 @@ import {
import { TonTransactionNotification } from './TonTransactionNotification';
import { SendTransactionAppRequest, useResponseSendMutation } from './connectHook';
+import { useActiveWallet, useMutateActiveTonWallet } from '../../state/wallet';
+
const useUnSupportMethodMutation = () => {
return useMutation(replyBadRequestResponse);
};
@@ -27,7 +27,7 @@ const TonConnectSubscription = () => {
const [request, setRequest] = useState(undefined);
const sdk = useAppSdk();
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const { data: appConnections } = useAppTonConnectConnections();
const { data: lastEventId } = useTonConnectLastEventId();
@@ -36,7 +36,7 @@ const TonConnectSubscription = () => {
const { mutateAsync: responseSendAsync } = useResponseSendMutation();
useSendNotificationAnalytics(request?.connection?.manifest);
- const { mutateAsync: setActiveWallet } = useMutateActiveWallet();
+ const { mutateAsync: setActiveWallet } = useMutateActiveTonWallet();
useEffect(() => {
const handleMessage = (params: TonConnectAppRequest) => {
@@ -60,7 +60,7 @@ const TonConnectSubscription = () => {
);
if (walletToActivate) {
- setActiveWallet(walletToActivate.wallet.publicKey).then(() =>
+ setActiveWallet(walletToActivate.wallet.rawAddress).then(() =>
setTimeout(() => {
setRequest(value);
}, 100)
@@ -83,7 +83,7 @@ const TonConnectSubscription = () => {
(async () => {
if (notifications && appConnections) {
try {
- const enable = await notifications.subscribed(wallet.active.rawAddress);
+ const enable = await notifications.subscribed(wallet.rawAddress);
if (enable) {
for (const connection of appConnections.flatMap(i => i.connections)) {
await notifications.subscribeTonConnect(
diff --git a/packages/uikit/src/components/connect/TonTransactionNotification.tsx b/packages/uikit/src/components/connect/TonTransactionNotification.tsx
index 0dab2e85a..28cb51330 100644
--- a/packages/uikit/src/components/connect/TonTransactionNotification.tsx
+++ b/packages/uikit/src/components/connect/TonTransactionNotification.tsx
@@ -7,13 +7,12 @@ import {
sendTonConnectTransfer,
tonConnectTransferError
} from '@tonkeeper/core/dist/service/transfer/tonService';
-import { toShortValue } from '@tonkeeper/core/dist/utils/common';
import { FC, useCallback, useEffect } from 'react';
import styled, { css } from 'styled-components';
-import { useAppContext, useWalletContext } from '../../hooks/appContext';
+import { useAppContext } from '../../hooks/appContext';
import { useAppSdk } from '../../hooks/appSdk';
import { useTranslation } from '../../hooks/translation';
-import { QueryKey } from '../../libs/queryKey';
+import { anyOfKeysParts, QueryKey } from '../../libs/queryKey';
import { getSigner } from '../../state/mnemonic';
import { useCheckTouchId } from '../../state/password';
import { CheckmarkCircleIcon, ErrorIcon, ExclamationMarkCircleIcon } from '../Icon';
@@ -26,13 +25,14 @@ import {
NotificationHeaderPortal,
NotificationTitleRow
} from '../Notification';
-import { SkeletonList } from '../Skeleton';
-import { Body2, H2, Label2, Label3 } from '../Text';
+import { SkeletonListWithImages } from '../Skeleton';
+import { H2, Label2, Label3 } from '../Text';
import { Button } from '../fields/Button';
-import { WalletEmoji } from '../shared/emoji/WalletEmoji';
import { ResultButton } from '../transfer/common';
import { EmulationList } from './EstimationLayout';
+import { useActiveStandardTonWallet, useAccountsState, useActiveAccount } from '../../state/wallet';
import { LedgerError } from '@tonkeeper/core/dist/errors/LedgerError';
+import { AccountAndWalletInfo } from '../account/AccountAndWalletInfo';
const ButtonGap = styled.div`
${props =>
@@ -56,20 +56,20 @@ const ButtonRowStyled = styled.div`
`;
const useSendMutation = (params: TonConnectTransactionPayload, waitInvalidation?: boolean) => {
- const wallet = useWalletContext();
+ const account = useActiveAccount();
const sdk = useAppSdk();
const { api } = useAppContext();
const client = useQueryClient();
const { mutateAsync: checkTouchId } = useCheckTouchId();
return useMutation(async () => {
- const signer = await getSigner(sdk, wallet.publicKey, checkTouchId);
+ const signer = await getSigner(sdk, account.id, checkTouchId);
- const boc = await sendTonConnectTransfer(api, wallet, params, signer);
+ const boc = await sendTonConnectTransfer(api, account, params, signer);
- const invalidationPromise = client.invalidateQueries({
- predicate: query => query.queryKey.includes(wallet.active.rawAddress)
- });
+ const invalidationPromise = client.invalidateQueries(
+ anyOfKeysParts(account.id, account.activeTonWallet.id)
+ );
if (waitInvalidation) {
await invalidationPromise;
}
@@ -82,7 +82,7 @@ const NotificationSkeleton: FC<{ handleClose: (result?: string) => void }> = ({
return (
-
+
@@ -244,12 +244,12 @@ const ConnectContent: FC<{
const useEstimation = (params: TonConnectTransactionPayload, errorFetched: boolean) => {
const { api } = useAppContext();
- const wallet = useWalletContext();
+ const account = useActiveAccount();
return useQuery(
[QueryKey.estimate, params],
async () => {
- const accountEvent = await estimateTonConnectTransfer(api, wallet, params);
+ const accountEvent = await estimateTonConnectTransfer(api, account, params);
return { accountEvent };
},
{ enabled: errorFetched }
@@ -258,7 +258,7 @@ const useEstimation = (params: TonConnectTransactionPayload, errorFetched: boole
const useTransactionError = (params: TonConnectTransactionPayload) => {
const { api } = useAppContext();
- const wallet = useWalletContext();
+ const wallet = useActiveStandardTonWallet();
return useQuery([QueryKey.estimate, 'error', params], async () => {
return tonConnectTransferError(api, wallet, params);
@@ -269,20 +269,7 @@ const NotificationTitleRowStyled = styled(NotificationTitleRow)`
align-items: flex-start;
`;
-const WalletInfoStyled = styled.div`
- display: flex;
- align-items: center;
- gap: 4px;
- color: ${p => p.theme.textSecondary};
-
- > ${Body2} {
- overflow: hidden;
- text-overflow: ellipsis;
- }
-`;
-
const NotificationTitleWithWalletName: FC<{ onClose: () => void }> = ({ onClose }) => {
- const wallet = useWalletContext();
const { t } = useTranslation();
return (
@@ -291,17 +278,7 @@ const NotificationTitleWithWalletName: FC<{ onClose: () => void }> = ({ onClose
{t('txActions_signRaw_title')}
-
-
- {t('confirmSendModal_wallet')}
- {wallet.name ?? toShortValue(wallet.active.friendlyAddress)}
-
-
-
+
@@ -315,12 +292,12 @@ export const TonTransactionNotification: FC<{
waitInvalidation?: boolean;
}> = ({ params, handleClose, waitInvalidation }) => {
const { t } = useTranslation();
- const { account } = useAppContext();
+ const wallets = useAccountsState();
const Content = useCallback(() => {
if (!params) return undefined;
return (
<>
- {account.publicKeys.length > 1 && (
+ {wallets.length > 1 && (
handleClose()} />
)}
>
);
- }, [origin, params, handleClose, account.publicKeys.length]);
+ }, [origin, params, handleClose, wallets.length]);
return (
<>
handleClose()}
- title={account.publicKeys.length > 1 ? undefined : t('txActions_signRaw_title')}
+ title={wallets.length > 1 ? undefined : t('txActions_signRaw_title')}
hideButton
>
{Content}
diff --git a/packages/uikit/src/components/connect/connectHook.ts b/packages/uikit/src/components/connect/connectHook.ts
index cf4577540..9e310ba3a 100644
--- a/packages/uikit/src/components/connect/connectHook.ts
+++ b/packages/uikit/src/components/connect/connectHook.ts
@@ -17,10 +17,11 @@ import {
TonConnectParams
} from '@tonkeeper/core/dist/service/tonConnect/connectionService';
import { sendEventToBridge } from '@tonkeeper/core/dist/service/tonConnect/httpBridge';
-import { useWalletContext } from '../../hooks/appContext';
import { useAppSdk } from '../../hooks/appSdk';
import { useTranslation } from '../../hooks/translation';
import { QueryKey } from '../../libs/queryKey';
+import { useActiveWallet } from '../../state/wallet';
+import { isStandardTonWallet } from '@tonkeeper/core/dist/entries/wallet';
export const useGetConnectInfo = () => {
const sdk = useAppSdk();
@@ -71,11 +72,16 @@ export interface AppConnectionProps {
export const useResponseConnectionMutation = () => {
const sdk = useAppSdk();
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const client = useQueryClient();
return useMutation(
async ({ params, replyItems, manifest }) => {
+ if (!isStandardTonWallet(wallet)) {
+ console.error('Attempt to connect to non standard ton wallet');
+ return;
+ }
+
if (replyItems && manifest) {
const response = await saveWalletTonConnect({
storage: sdk.storage,
diff --git a/packages/uikit/src/components/create/ChangePassword.tsx b/packages/uikit/src/components/create/ChangePassword.tsx
index 2466c46e0..ab529f7c1 100644
--- a/packages/uikit/src/components/create/ChangePassword.tsx
+++ b/packages/uikit/src/components/create/ChangePassword.tsx
@@ -1,5 +1,4 @@
import { useMutation } from '@tanstack/react-query';
-import { accountChangePassword } from '@tonkeeper/core/dist/service/accountService';
import React, { FC, useCallback, useState } from 'react';
import styled from 'styled-components';
import { useAppSdk } from '../../hooks/appSdk';
@@ -7,6 +6,8 @@ import { useTranslation } from '../../hooks/translation';
import { Button } from '../fields/Button';
import { Input } from '../fields/Input';
import { Notification, NotificationBlock } from '../Notification';
+import { usePasswordStorage } from '../../hooks/useStorage';
+import { validatePassword } from '@tonkeeper/core/dist/service/passwordService';
const Block = styled.div`
display: flex;
@@ -19,27 +20,40 @@ const Block = styled.div`
const useUpdatePassword = () => {
const sdk = useAppSdk();
const { t } = useTranslation();
+ const passwordStorage = usePasswordStorage();
return useMutation<
- string | undefined,
+ 'invalid-old' | 'invalid-confirm' | 'invalid-password' | undefined,
Error,
{ old: string; password: string; confirm: string }
- >(async options => {
- const error = await accountChangePassword(sdk.storage, options);
- if (error === undefined) {
- sdk.uiEvents.emit('copy', {
- method: 'copy',
- id: Date.now(),
- params: t('PasswordChanged')
- });
+ >(async ({ old, password, confirm }) => {
+ const isValidOld = await passwordStorage.isPasswordValid(old);
+ if (!isValidOld) {
+ return 'invalid-old';
}
- return error;
+
+ if (!validatePassword(password)) {
+ return 'invalid-password';
+ }
+ if (password !== confirm) {
+ return 'invalid-confirm';
+ }
+
+ await passwordStorage.updatePassword(old, password);
+
+ sdk.uiEvents.emit('copy', {
+ method: 'copy',
+ id: Date.now(),
+ params: t('PasswordChanged')
+ });
});
};
const ChangePasswordContent: FC<{ handleClose: () => void }> = ({ handleClose }) => {
const { t } = useTranslation();
- const [error, setError] = useState(undefined);
+ const [error, setError] = useState<
+ 'invalid-old' | 'invalid-confirm' | 'invalid-password' | undefined
+ >(undefined);
const { mutateAsync, isLoading, reset } = useUpdatePassword();
diff --git a/packages/uikit/src/components/create/ChoseLedgerIndexes.tsx b/packages/uikit/src/components/create/ChoseLedgerIndexes.tsx
new file mode 100644
index 000000000..8ba9a3f59
--- /dev/null
+++ b/packages/uikit/src/components/create/ChoseLedgerIndexes.tsx
@@ -0,0 +1,137 @@
+import { ListBlock, ListItem, ListItemPayload } from '../List';
+import styled from 'styled-components';
+import { Body1, Body2, H2, Label1 } from '../Text';
+import { useTranslation } from '../../hooks/translation';
+import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
+import React, { FC, useState } from 'react';
+import { useTonWalletsBalances } from '../../state/wallet';
+import { SkeletonList } from '../Skeleton';
+import { toFormattedTonBalance } from '../../hooks/balance';
+import { Checkbox } from '../fields/Checkbox';
+import { Button } from '../fields/Button';
+import { ChevronLeftIcon } from '../Icon';
+import { RoundedButton } from '../fields/RoundedButton';
+import { AccountLedger } from '@tonkeeper/core/dist/entries/account';
+
+const Wrapper = styled.div`
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const BackButtonContainer = styled.div`
+ padding: 8px;
+ margin-bottom: 24px;
+ margin-right: auto;
+`;
+
+const Body1Styled = styled(Body1)`
+ margin-top: 4px;
+ margin-bottom: 32px;
+ color: ${p => p.theme.textSecondary};
+`;
+
+const SkeletonListStyled = styled(SkeletonList)`
+ width: 100%;
+`;
+
+const ListBlockStyled = styled(ListBlock)`
+ width: 100%;
+`;
+
+const TextContainer = styled.span`
+ flex-direction: column;
+ display: flex;
+ align-items: flex-start;
+`;
+
+const Body2Secondary = styled(Body2)`
+ color: ${props => props.theme.textSecondary};
+`;
+
+const SubmitBlock = styled.div`
+ padding: 16px 0 32px;
+ flex: 1;
+ display: flex;
+ align-items: flex-end;
+ width: 100%;
+`;
+
+export const ChoseLedgerIndexes: FC<{
+ account: AccountLedger;
+ onSubmit: (indexes: number[]) => void;
+ onBack: () => void;
+ isLoading?: boolean;
+}> = ({ account, onSubmit, onBack, isLoading }) => {
+ const { t } = useTranslation();
+
+ const { data: balances } = useTonWalletsBalances(
+ account.allAvailableDerivations.map(
+ d => d.tonWallets.find(w => w.id === d.activeTonWalletId)!.rawAddress
+ )
+ );
+ const [checkedIndexes, setCheckedIndexes] = useState(account.addedDerivationsIndexes);
+
+ const toggleIndex = (index: number, isChecked: boolean) => {
+ setCheckedIndexes(state =>
+ isChecked ? state.concat(index) : state.filter(i => i !== index)
+ );
+ };
+
+ return (
+
+
+
+
+
+
+ {t('choose_wallets_title')}
+ {t('choose_wallets_subtitle')}
+ {!balances ? (
+
+ ) : (
+ <>
+
+ {balances.map((balance, index) => {
+ const derivationIndex = account.allAvailableDerivations[index].index;
+ return (
+
+
+
+ # {derivationIndex + 1}
+
+ {toShortValue(formatAddress(balance.address))}
+ ·
+ {toFormattedTonBalance(balance.tonBalance)} TON
+ {balance.hasJettons &&
+ t('wallet_version_and_tokens')}
+
+
+
+ toggleIndex(derivationIndex, isChecked)
+ }
+ />
+
+
+ );
+ })}
+
+
+
+
+ >
+ )}
+
+ );
+};
diff --git a/packages/uikit/src/components/create/ChoseWalletVersions.tsx b/packages/uikit/src/components/create/ChoseWalletVersions.tsx
new file mode 100644
index 000000000..2c024ac97
--- /dev/null
+++ b/packages/uikit/src/components/create/ChoseWalletVersions.tsx
@@ -0,0 +1,160 @@
+import { ListBlock, ListItem, ListItemPayload } from '../List';
+import styled from 'styled-components';
+import { Body1, Body2, H2, Label1 } from '../Text';
+import { useTranslation } from '../../hooks/translation';
+import {
+ WalletVersion,
+ WalletVersions,
+ walletVersionText
+} from '@tonkeeper/core/dist/entries/wallet';
+import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
+import React, { FC, useEffect, useLayoutEffect, useState } from 'react';
+import { useAccountState, useStandardTonWalletVersions } from '../../state/wallet';
+import { SkeletonList } from '../Skeleton';
+import { toFormattedTonBalance } from '../../hooks/balance';
+import { Checkbox } from '../fields/Checkbox';
+import { Button } from '../fields/Button';
+import { mnemonicToWalletKey } from '@ton/crypto';
+import { ChevronLeftIcon } from '../Icon';
+import { RoundedButton } from '../fields/RoundedButton';
+import { useAppContext } from '../../hooks/appContext';
+
+const Wrapper = styled.div`
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const BackButtonContainer = styled.div`
+ padding: 8px;
+ margin-bottom: 24px;
+ margin-right: auto;
+`;
+
+const Body1Styled = styled(Body1)`
+ margin-top: 4px;
+ margin-bottom: 32px;
+ color: ${p => p.theme.textSecondary};
+`;
+
+const SkeletonListStyled = styled(SkeletonList)`
+ width: 100%;
+`;
+
+const ListBlockStyled = styled(ListBlock)`
+ width: 100%;
+`;
+
+const TextContainer = styled.span`
+ flex-direction: column;
+ display: flex;
+ align-items: flex-start;
+`;
+
+const Body2Secondary = styled(Body2)`
+ color: ${props => props.theme.textSecondary};
+`;
+
+const SubmitBlock = styled.div`
+ padding: 16px 0 32px;
+ flex: 1;
+ display: flex;
+ align-items: flex-end;
+ width: 100%;
+`;
+
+export const ChoseWalletVersions: FC<{
+ mnemonic: string[];
+ onSubmit: (versions: WalletVersion[]) => void;
+ onBack: () => void;
+ isLoading?: boolean;
+}> = ({ mnemonic, onSubmit, onBack, isLoading }) => {
+ const { t } = useTranslation();
+ const { defaultWalletVersion } = useAppContext();
+
+ const [publicKey, setPublicKey] = useState(undefined);
+ const { data: wallets } = useStandardTonWalletVersions(publicKey);
+ const [checkedVersions, setCheckedVersions] = useState([]);
+ const accountState = useAccountState(publicKey);
+
+ useEffect(() => {
+ mnemonicToWalletKey(mnemonic).then(keypair =>
+ setPublicKey(keypair.publicKey.toString('hex'))
+ );
+ }, [mnemonic]);
+
+ useLayoutEffect(() => {
+ if (wallets) {
+ if (accountState) {
+ return setCheckedVersions(accountState.allTonWallets.map(w => w.version));
+ }
+
+ const versionsToCheck = wallets
+ .filter(w => w.tonBalance || w.hasJettons)
+ .map(w => w.version);
+ if (!versionsToCheck.length) {
+ versionsToCheck.push(defaultWalletVersion);
+ }
+ setCheckedVersions(versionsToCheck);
+ }
+ }, [wallets, accountState]);
+
+ const toggleVersion = (version: WalletVersion, isChecked: boolean) => {
+ setCheckedVersions(state =>
+ isChecked ? state.concat(version) : state.filter(i => i !== version)
+ );
+ };
+
+ return (
+
+
+
+
+
+
+ {t('choose_wallets_title')}
+ {t('choose_wallets_subtitle')}
+ {!wallets ? (
+
+ ) : (
+ <>
+
+ {wallets.map(wallet => (
+
+
+
+ {walletVersionText(wallet.version)}
+
+ {toShortValue(formatAddress(wallet.address))}
+ ·
+ {toFormattedTonBalance(wallet.tonBalance)} TON
+ {wallet.hasJettons && t('wallet_version_and_tokens')}
+
+
+
+ toggleVersion(wallet.version, isChecked)
+ }
+ />
+
+
+ ))}
+
+
+
+
+ >
+ )}
+
+ );
+};
diff --git a/packages/uikit/src/components/create/CreateAuth.tsx b/packages/uikit/src/components/create/CreateAuth.tsx
deleted file mode 100644
index 657b678c1..000000000
--- a/packages/uikit/src/components/create/CreateAuth.tsx
+++ /dev/null
@@ -1,178 +0,0 @@
-import { useMutation } from '@tanstack/react-query';
-import { AppKey } from '@tonkeeper/core/dist/Keys';
-import { AuthNone, AuthPassword, AuthState } from '@tonkeeper/core/dist/entries/password';
-import { MinPasswordLength } from '@tonkeeper/core/dist/service/accountService';
-import React, { FC, useEffect, useRef, useState } from 'react';
-import styled from 'styled-components';
-import { useAppSdk } from '../../hooks/appSdk';
-import { useTranslation } from '../../hooks/translation';
-import { CenterContainer } from '../Layout';
-import { H2 } from '../Text';
-import { Button } from '../fields/Button';
-import { Input } from '../fields/Input';
-
-const Block = styled.form`
- display: flex;
- text-align: center;
- gap: 1rem;
- flex-direction: column;
-`;
-
-const useSetNoneAuthMutation = () => {
- const sdk = useAppSdk();
- return useMutation(async () => {
- const state: AuthNone = {
- kind: 'none'
- };
- await sdk.storage.set(AppKey.GLOBAL_AUTH_STATE, state);
- });
-};
-
-const SelectAuthType: FC<{
- onSelect: (value: AuthState['kind']) => void;
- isLoading: boolean;
-}> = ({ onSelect, isLoading }) => {
- const { t } = useTranslation();
-
- return (
-
-
-
-
- );
-};
-
-const useCreatePassword = () => {
- const sdk = useAppSdk();
-
- return useMutation(
- async ({ password, confirm }) => {
- if (password.length < MinPasswordLength) {
- sdk.hapticNotification('error');
- return 'password';
- }
- if (password !== confirm) {
- sdk.hapticNotification('error');
- return 'confirm';
- }
-
- const state: AuthPassword = {
- kind: 'password'
- };
- await sdk.storage.set(AppKey.GLOBAL_AUTH_STATE, state);
- }
- );
-};
-
-const FillPassword: FC<{
- afterCreate: (password: string) => void;
- isLoading?: boolean;
-}> = ({ afterCreate, isLoading }) => {
- const { t } = useTranslation();
-
- const { mutateAsync, isLoading: isCreating, reset } = useCreatePassword();
-
- const ref = useRef(null);
-
- const [error, setError] = useState(undefined);
-
- const [password, setPassword] = useState('');
- const [confirm, setConfirm] = useState('');
-
- const onCreate: React.FormEventHandler = async e => {
- e.stopPropagation();
- e.preventDefault();
- reset();
- const result = await mutateAsync({ password, confirm });
- if (result === undefined) {
- return afterCreate(password);
- } else {
- setError(result);
- }
- };
-
- useEffect(() => {
- if (ref.current) {
- ref.current.focus();
- }
- }, [ref]);
-
- return (
-
-
- {t('Create_password')}
- {
- setError(undefined);
- setPassword(value);
- }}
- isValid={error == null}
- helpText={error === 'confirm' ? t('PasswordDoNotMatch') : t('MinPassword')}
- />
-
- {
- setError(undefined);
- setConfirm(value);
- }}
- isValid={error !== 'confirm'}
- />
-
-
-
-
- );
-};
-
-export const CreateAuthState: FC<{
- afterCreate: (password?: string) => void;
- isLoading?: boolean;
-}> = ({ afterCreate, isLoading }) => {
- const [authType, setAuthType] = useState('password');
-
- const { mutateAsync: setNoneAuth, isLoading: isNoneLoading } = useSetNoneAuthMutation();
-
- const onSelect = async (_authType: AuthState['kind']) => {
- if (_authType === 'none') {
- await setNoneAuth();
- afterCreate();
- } else {
- setAuthType(_authType);
- }
- };
-
- if (authType === undefined) {
- return ;
- } else if (authType === 'password') {
- return ;
- } else {
- return <>TODO: WithAuthn case >;
- }
-};
diff --git a/packages/uikit/src/components/create/CreatePassword.tsx b/packages/uikit/src/components/create/CreatePassword.tsx
new file mode 100644
index 000000000..462829f6a
--- /dev/null
+++ b/packages/uikit/src/components/create/CreatePassword.tsx
@@ -0,0 +1,96 @@
+import React, { FC, useEffect, useRef, useState } from 'react';
+import styled from 'styled-components';
+import { useAppSdk } from '../../hooks/appSdk';
+import { useTranslation } from '../../hooks/translation';
+import { CenterContainer } from '../Layout';
+import { H2 } from '../Text';
+import { Button } from '../fields/Button';
+import { Input } from '../fields/Input';
+import { validatePassword } from '@tonkeeper/core/dist/service/passwordService';
+
+const Block = styled.form`
+ display: flex;
+ text-align: center;
+ gap: 1rem;
+ flex-direction: column;
+`;
+
+export const CreatePassword: FC<{
+ afterCreate: (password: string) => void;
+ isLoading?: boolean;
+ className?: string;
+}> = ({ afterCreate, isLoading, className }) => {
+ const { t } = useTranslation();
+ const sdk = useAppSdk();
+
+ const ref = useRef(null);
+
+ const [error, setError] = useState(undefined);
+
+ const [password, setPassword] = useState('');
+ const [confirm, setConfirm] = useState('');
+
+ const onCreate: React.FormEventHandler = async e => {
+ e.stopPropagation();
+ e.preventDefault();
+ if (!validatePassword(password)) {
+ sdk.hapticNotification('error');
+ return setError('password');
+ }
+ if (password !== confirm) {
+ sdk.hapticNotification('error');
+ return setError('confirm');
+ }
+
+ return afterCreate(password);
+ };
+
+ useEffect(() => {
+ if (ref.current) {
+ ref.current.focus();
+ }
+ }, [ref]);
+
+ return (
+
+
+ {t('Create_password')}
+ {
+ setError(undefined);
+ setPassword(value);
+ }}
+ isValid={error == null}
+ helpText={error === 'confirm' ? t('PasswordDoNotMatch') : t('MinPassword')}
+ />
+
+ {
+ setError(undefined);
+ setConfirm(value);
+ }}
+ isValid={error !== 'confirm'}
+ />
+
+
+
+
+ );
+};
diff --git a/packages/uikit/src/components/create/WalletName.tsx b/packages/uikit/src/components/create/WalletName.tsx
index 40769c19b..708c0db45 100644
--- a/packages/uikit/src/components/create/WalletName.tsx
+++ b/packages/uikit/src/components/create/WalletName.tsx
@@ -1,12 +1,6 @@
-import { useMutation, useQueryClient } from '@tanstack/react-query';
-import { AccountState } from '@tonkeeper/core/dist/entries/account';
-import { getWalletState } from '@tonkeeper/core/dist/service/wallet/storeService';
-import { updateWalletProperty } from '@tonkeeper/core/dist/service/walletService';
import React, { FC, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
-import { useAppSdk } from '../../hooks/appSdk';
import { useTranslation } from '../../hooks/translation';
-import { QueryKey } from '../../libs/queryKey';
import { CenterContainer } from '../Layout';
import { Body2, H2 } from '../Text';
import { Button } from '../fields/Button';
@@ -26,71 +20,36 @@ const Body = styled(Body2)`
color: ${props => props.theme.textSecondary};
`;
-const useUpdateNameMutation = () => {
- const sdk = useAppSdk();
- const client = useQueryClient();
-
- return useMutation(
- async ({ name, emoji, account }) => {
- if (name.length < 3) {
- throw new Error('Missing name');
- }
-
- if (!account.activePublicKey) {
- throw new Error('Missing activePublicKey');
- }
- const wallet = await getWalletState(sdk.storage, account.activePublicKey);
- if (!wallet) {
- throw new Error('Missing wallet');
- }
-
- await updateWalletProperty(sdk.storage, wallet, { name, emoji });
- await client.invalidateQueries([QueryKey.account]);
- return account;
- }
- );
-};
-
-export const UpdateWalletName: FC<
- | {
- account: AccountState;
- onUpdate: (account: AccountState) => void;
- walletEmoji: string;
- }
- | {
- walletEmoji: string;
- submitHandler: ({ name, emoji }: { name: string; emoji: string }) => void;
- }
-> = props => {
+export const UpdateWalletName: FC<{
+ walletEmoji: string;
+ name?: string;
+ submitHandler: ({ name, emoji }: { name: string; emoji: string }) => void;
+ isLoading?: boolean;
+}> = ({ walletEmoji, submitHandler, name: nameProp, isLoading }) => {
const { t } = useTranslation();
const ref = useRef(null);
- const { mutateAsync, isError, isLoading, reset } = useUpdateNameMutation();
-
useEffect(() => {
if (ref.current) {
ref.current.focus();
}
}, [ref.current]);
- const [name, setName] = useState('');
- const [emoji, setEmoji] = useState(props.walletEmoji);
+ const [name, setName] = useState(nameProp || '');
+ const [emoji, setEmoji] = useState(walletEmoji);
const onSubmit: React.FormEventHandler = async e => {
e.preventDefault();
- if ('submitHandler' in props) {
- props.submitHandler({ name, emoji });
- } else {
- props.onUpdate(await mutateAsync({ name, emoji, account: props.account }));
- }
+ submitHandler({ name, emoji });
};
const onChange = (value: string) => {
- reset();
setName(value);
};
+ const isValid = name.length >= 3;
+
return (
@@ -104,8 +63,7 @@ export const UpdateWalletName: FC<
value={name}
onChange={onChange}
label={t('Wallet_name')}
- disabled={isLoading}
- isValid={!isError}
+ isValid={isValid}
rightElement={emoji ? : null}
/>
@@ -115,9 +73,9 @@ export const UpdateWalletName: FC<
fullWidth
marginTop
primary
- loading={isLoading}
- disabled={isLoading}
+ disabled={!isValid}
type="submit"
+ loading={isLoading}
>
{t('add_edit_favorite_save')}
diff --git a/packages/uikit/src/components/create/Words.tsx b/packages/uikit/src/components/create/Words.tsx
index 50b72198c..1e96ff035 100644
--- a/packages/uikit/src/components/create/Words.tsx
+++ b/packages/uikit/src/components/create/Words.tsx
@@ -251,7 +251,7 @@ export const Check: FC<{
mnemonic: string[];
onBack: () => void;
onConfirm: () => void;
- isLoading: boolean;
+ isLoading?: boolean;
}> = ({ onBack, onConfirm, mnemonic, isLoading }) => {
const { t, i18n } = useTranslation();
@@ -359,7 +359,7 @@ const focusInput = (current: HTMLDivElement | null, index: number) => {
};
export const ImportWords: FC<{
- isLoading: boolean;
+ isLoading?: boolean;
onMnemonic: (mnemonic: string[]) => void;
}> = ({ isLoading, onMnemonic }) => {
const sdk = useAppSdk();
diff --git a/packages/uikit/src/components/dashboard/DashboardTable.tsx b/packages/uikit/src/components/dashboard/DashboardTable.tsx
index d9dbd43f4..e70e49555 100644
--- a/packages/uikit/src/components/dashboard/DashboardTable.tsx
+++ b/packages/uikit/src/components/dashboard/DashboardTable.tsx
@@ -6,8 +6,7 @@ import { useDashboardData } from '../../state/dashboard/useDashboardData';
import { DashboardCellAddress, DashboardColumnType } from '@tonkeeper/core/dist/entries/dashboard';
import { Skeleton } from '../shared/Skeleton';
import { DashboardCell } from './columns/DashboardCell';
-import { useWalletsState } from '../../state/wallet';
-import { Network } from '@tonkeeper/core/dist/entries/network';
+import { useAccountsState } from '../../state/wallet';
const TableStyled = styled.table`
width: 100%;
@@ -101,10 +100,8 @@ const isNumericColumn = (columnType: DashboardColumnType): boolean => {
export const DashboardTable: FC<{ className?: string }> = ({ className }) => {
const { data: columns } = useDashboardColumnsAsForm();
const { data: dashboardData } = useDashboardData();
- const { data: wallets, isFetched: isWalletsFetched } = useWalletsState();
- const mainnetPubkeys = wallets
- ?.filter(w => w && w.network !== Network.TESTNET)
- .map(w => w!.publicKey);
+ const wallets = useAccountsState();
+ const mainnetIds = wallets?.map(w => w!.id);
const [isResizing, setIsResizing] = useState(false);
const [hoverOnColumn, setHoverOnColumn] = useState(undefined);
@@ -159,7 +156,7 @@ export const DashboardTable: FC<{ className?: string }> = ({ className }) => {
return hoverOnColumn !== undefined && hoverOnColumn >= i && hoverOnColumn <= i + 1;
};
- if (!columns || !isWalletsFetched) {
+ if (!columns) {
return null;
}
@@ -224,7 +221,7 @@ export const DashboardTable: FC<{ className?: string }> = ({ className }) => {
))}
))
- : (mainnetPubkeys || [1, 2, 3]).map(key => (
+ : (mainnetIds || [1, 2, 3]).map(key => (
{selectedColumns.map((col, colIndex) => (
diff --git a/packages/uikit/src/components/desktop/aside/AsideHeader.tsx b/packages/uikit/src/components/desktop/aside/AsideHeader.tsx
index d76a35732..21a2e03af 100644
--- a/packages/uikit/src/components/desktop/aside/AsideHeader.tsx
+++ b/packages/uikit/src/components/desktop/aside/AsideHeader.tsx
@@ -2,14 +2,14 @@ import styled from 'styled-components';
import { FC, useRef, useState } from 'react';
import { WalletEmoji } from '../../shared/emoji/WalletEmoji';
import { Body3, Label2 } from '../../Text';
-import { useAppContext } from '../../../hooks/appContext';
-import { useWalletState } from '../../../state/wallet';
+import { useActiveAccount } from '../../../state/wallet';
import { useTranslation } from '../../../hooks/translation';
import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
import { useAsideActiveRoute } from '../../../hooks/desktop/useAsideActiveRoute';
import { useAppSdk } from '../../../hooks/appSdk';
import { CopyIcon, DoneIcon } from '../../Icon';
import { Transition } from 'react-transition-group';
+import { AccountAndWalletBadgesGroup } from '../../account/AccountBadge';
const HeaderContainer = styled.div<{ width: number }>`
box-sizing: border-box;
@@ -63,14 +63,14 @@ const DoneIconStyled = styled(DoneIcon)`
export const AsideHeader: FC<{ width: number }> = ({ width }) => {
const { t } = useTranslation();
- const { account } = useAppContext();
- const { data: wallet } = useWalletState(account.activePublicKey!);
+ const account = useActiveAccount();
+ const activeWallet = account.activeTonWallet;
const route = useAsideActiveRoute();
const [copied, setIsCopied] = useState(false);
const sdk = useAppSdk();
const [hovered, setHovered] = useState(false);
- const address = wallet ? formatAddress(wallet.active.rawAddress) : '';
+ const address = formatAddress(activeWallet.rawAddress);
const timeoutRef = useRef | undefined>(undefined);
@@ -98,12 +98,17 @@ export const AsideHeader: FC<{ width: number }> = ({ width }) => {
onMouseOver={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
- {wallet && !route && (
+ {!route && (
<>
- {wallet.name || t('wallet_title')}
+ {account.name || t('wallet_title')}
{toShortValue(address)}
+
= ({ width }) => {
-
+
>
)}
diff --git a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx
index dbc17e0e5..104358a3a 100644
--- a/packages/uikit/src/components/desktop/aside/AsideMenu.tsx
+++ b/packages/uikit/src/components/desktop/aside/AsideMenu.tsx
@@ -8,17 +8,35 @@ import { useTranslation } from '../../../hooks/translation';
import { useIsScrolled } from '../../../hooks/useIsScrolled';
import { scrollToTop } from '../../../libs/common';
import { AppProRoute, AppRoute } from '../../../libs/routes';
-import { useMutateActiveWallet } from '../../../state/account';
import { useMutateUserUIPreferences, useUserUIPreferences } from '../../../state/theme';
-import { useWalletState } from '../../../state/wallet';
+import {
+ useAccountsState,
+ useActiveTonNetwork,
+ useMutateActiveTonWallet,
+ useActiveAccount
+} from '../../../state/wallet';
import { fallbackRenderOver } from '../../Error';
-import { GlobeIcon, PlusIcon, SlidersIcon, StatsIcon } from '../../Icon';
+import { GearIconEmpty, GlobeIcon, PlusIcon, SlidersIcon, StatsIcon } from '../../Icon';
import { Label2 } from '../../Text';
import { ImportNotification } from '../../create/ImportNotification';
import { AsideMenuItem } from '../../shared/AsideItem';
import { WalletEmoji } from '../../shared/emoji/WalletEmoji';
import { AsideHeader } from './AsideHeader';
import { SubscriptionInfo } from './SubscriptionInfo';
+import { Account } from '@tonkeeper/core/dist/entries/account';
+import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
+import {
+ sortDerivationsByIndex,
+ sortWalletsByVersion,
+ WalletId
+} from '@tonkeeper/core/dist/entries/wallet';
+import { assertUnreachable } from '@tonkeeper/core/dist/utils/types';
+import { IconButtonTransparentBackground } from '../../fields/IconButton';
+import { useWalletVersionSettingsNotification } from '../../modals/WalletVersionSettingsNotification';
+import { useIsHovered } from '../../../hooks/useIsHovered';
+import { ScrollContainer } from '../../ScrollContainer';
+import { AccountBadge, WalletIndexBadge, WalletVersionBadge } from '../../account/AccountBadge';
+import { useLedgerIndexesSettingsNotification } from '../../modals/LedgerIndexesSettingsNotification';
const AsideContainer = styled.div<{ width: number }>`
display: flex;
@@ -54,10 +72,6 @@ const AsideContentContainer = styled.div`
padding: 0.5rem 0.5rem 0;
`;
-const ScrollContainer = styled.div`
- overflow: auto;
-`;
-
const DividerStyled = styled.div<{ isHidden?: boolean }>`
opacity: ${p => (p.isHidden ? 0 : 1)};
height: 1px;
@@ -92,18 +106,45 @@ const SubscriptionInfoStyled = styled(SubscriptionInfo)`
padding: 6px 16px 6px 8px;
`;
-export const AsideMenuAccount: FC<{ publicKey: string; isSelected: boolean }> = ({
- publicKey,
+const AsideMenuSubItem = styled(AsideMenuItem)`
+ padding-left: 36px;
+`;
+
+const AccountBadgeStyled = styled(AccountBadge)`
+ margin-left: -4px;
+`;
+
+const WalletVersionBadgeStyled = styled(WalletVersionBadge)`
+ margin-left: -4px;
+`;
+
+const WalletIndexBadgeStyled = styled(WalletIndexBadge)`
+ margin-left: -4px;
+`;
+
+const GearIconButtonStyled = styled(IconButtonTransparentBackground)<{ isShown: boolean }>`
+ margin-left: auto;
+ margin-right: -10px;
+ flex-shrink: 0;
+ padding-left: 0;
+
+ opacity: ${p => (p.isShown ? 1 : 0)};
+ transition: opacity 0.15s ease-in-out;
+`;
+
+export const AsideMenuAccount: FC<{ account: Account; isSelected: boolean }> = ({
+ account,
isSelected
}) => {
- const { t } = useTranslation();
- const { data: wallet } = useWalletState(publicKey);
- const { mutateAsync } = useMutateActiveWallet();
+ const { onOpen: openWalletVersionSettings } = useWalletVersionSettingsNotification();
+ const { onOpen: openLedgerIndexesSettings } = useLedgerIndexesSettingsNotification();
+ const network = useActiveTonNetwork();
+ const { mutateAsync: setActiveWallet } = useMutateActiveTonWallet();
const navigate = useNavigate();
const location = useLocation();
- const { account } = useAppContext();
- const shouldShowIcon = account.publicKeys.length > 1;
+ const accounts = useAccountsState();
+ const shouldShowIcon = accounts.length > 1;
const handleNavigateHome = useCallback(() => {
const navigateHomeFromRoutes = [AppProRoute.dashboard, AppRoute.settings, AppRoute.browser];
@@ -114,30 +155,179 @@ export const AsideMenuAccount: FC<{ publicKey: string; isSelected: boolean }> =
}
}, [location.pathname]);
- const onClick = useCallback(() => {
- mutateAsync(publicKey).then(handleNavigateHome);
- }, [publicKey, mutateAsync, handleNavigateHome]);
+ const { isHovered, ref } = useIsHovered();
- if (!wallet) {
+ const onClickWallet = (walletId: WalletId) =>
+ setActiveWallet(walletId).then(handleNavigateHome);
+
+ if (!account) {
return null;
}
- const name = wallet.name ? wallet.name : t('wallet_title');
+ if (account.type === 'mnemonic') {
+ const sortedWallets = account.tonWallets.slice().sort(sortWalletsByVersion);
+ return (
+ <>
+ onClickWallet(sortedWallets[0].id)}
+ ref={ref}
+ >
+ {shouldShowIcon && (
+
+ )}
+ {account.name}
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ openWalletVersionSettings({ accountId: account.id });
+ }}
+ isShown={isHovered}
+ >
+
+
+
+ {sortedWallets.length > 1 &&
+ sortedWallets.map(wallet => (
+ onClickWallet(wallet.id)}
+ >
+
+ {toShortValue(formatAddress(wallet.rawAddress, network))}
+
+
+
+ ))}
+ >
+ );
+ }
+
+ if (account.type === 'ledger') {
+ const sortedDerivations = account.derivations.slice().sort(sortDerivationsByIndex);
+ return (
+ <>
+ onClickWallet(sortedDerivations[0].activeTonWalletId)}
+ ref={ref}
+ >
+ {shouldShowIcon && (
+
+ )}
+ {account.name}
+
- return (
-
- {shouldShowIcon && (
-
- )}
- {name}
-
- );
+ {/*show settings only for non-legacy added ledger accounts*/}
+ {account.allAvailableDerivations.length > 1 && (
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ openLedgerIndexesSettings({ accountId: account.id });
+ }}
+ isShown={isHovered}
+ >
+
+
+ )}
+
+ {sortedDerivations.length > 1 &&
+ sortedDerivations.map(derivation => {
+ const wallet = derivation.tonWallets.find(
+ w => w.id === derivation.activeTonWalletId
+ )!;
+
+ return (
+ onClickWallet(derivation.activeTonWalletId)}
+ >
+
+ {toShortValue(formatAddress(wallet.rawAddress, network))}
+
+
+ {'#' + (derivation.index + 1)}
+
+
+ );
+ })}
+ >
+ );
+ }
+
+ if (account.type === 'ton-only') {
+ const sortedWallets = account.tonWallets.slice().sort(sortWalletsByVersion);
+ return (
+ <>
+ onClickWallet(account.activeTonWallet.id)}
+ ref={ref}
+ >
+ {shouldShowIcon && (
+
+ )}
+ {account.name}
+
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ openWalletVersionSettings({ accountId: account.id });
+ }}
+ isShown={isHovered}
+ >
+
+
+
+ {sortedWallets.length > 1 &&
+ sortedWallets.map(wallet => (
+ onClickWallet(wallet.id)}
+ >
+
+ {toShortValue(formatAddress(wallet.rawAddress, network))}
+
+
+
+ ))}
+ >
+ );
+ }
+
+ if (account.type === 'keystone') {
+ return (
+ onClickWallet(account.activeTonWallet.id)}
+ ref={ref}
+ >
+ {shouldShowIcon && (
+
+ )}
+ {account.name}
+
+
+ );
+ }
+
+ assertUnreachable(account);
};
const AsideMenuPayload: FC<{ className?: string }> = ({ className }) => {
const { t } = useTranslation();
const [isOpenImport, setIsOpenImport] = useState(false);
- const { account, proFeatures } = useAppContext();
+ const { proFeatures } = useAppContext();
+ const accounts = useAccountsState();
+ const activeAccount = useActiveAccount();
const navigate = useNavigate();
const location = useLocation();
const { ref, closeBottom } = useIsScrolled();
@@ -209,15 +399,11 @@ const AsideMenuPayload: FC<{ className?: string }> = ({ className }) => {
{t('aside_dashboard')}
)}
- {account.publicKeys.map(publicKey => (
+ {accounts.map(account => (
))}
diff --git a/packages/uikit/src/components/desktop/aside/PreferencesAsideMenu.tsx b/packages/uikit/src/components/desktop/aside/PreferencesAsideMenu.tsx
index c47773745..5621d7e3c 100644
--- a/packages/uikit/src/components/desktop/aside/PreferencesAsideMenu.tsx
+++ b/packages/uikit/src/components/desktop/aside/PreferencesAsideMenu.tsx
@@ -20,7 +20,7 @@ import { AppRoute, SettingsRoute } from '../../../libs/routes';
import { useTranslation } from '../../../hooks/translation';
import { useAppSdk } from '../../../hooks/appSdk';
import { useAppContext } from '../../../hooks/appContext';
-import { DeleteAllNotification } from '../../settings/LogOutNotification';
+import { DeleteAllNotification } from '../../settings/DeleteAccountNotification';
import React from 'react';
import { useDisclosure } from '../../../hooks/useDisclosure';
import { capitalize, getCountryName, getLanguageName } from '../../../libs/common';
@@ -29,6 +29,7 @@ import { Skeleton } from '../../shared/Skeleton';
import { useProState } from '../../../state/pro';
import { availableThemes, useUserUIPreferences } from '../../../state/theme';
import { hexToRGBA } from '../../../libs/css';
+import { useAccountsState } from '../../../state/wallet';
const PreferencesAsideContainer = styled.div`
width: fit-content;
@@ -83,13 +84,14 @@ export const PreferencesAsideMenu = () => {
const isCoinPageOpened = location.pathname.startsWith(AppRoute.coins);
const sdk = useAppSdk();
- const { config, account } = useAppContext();
+ const { config } = useAppContext();
const { isOpen, onClose, onOpen } = useDisclosure();
const { data: countryData } = useCountrySetting();
const country = countryData === null ? t('auto') : countryData;
const { data: proState } = useProState();
const { data: uiPreferences } = useUserUIPreferences();
const { fiat } = useAppContext();
+ const wallets = useAccountsState();
return (
@@ -233,7 +235,7 @@ export const PreferencesAsideMenu = () => {
- {account.publicKeys.length > 1
+ {wallets.length > 1
? t('preferences_aside_sign_out_all')
: t('preferences_aside_sign_out')}
diff --git a/packages/uikit/src/components/desktop/aside/WalletAsideMenu.tsx b/packages/uikit/src/components/desktop/aside/WalletAsideMenu.tsx
index 673fb5107..1282aa7e1 100644
--- a/packages/uikit/src/components/desktop/aside/WalletAsideMenu.tsx
+++ b/packages/uikit/src/components/desktop/aside/WalletAsideMenu.tsx
@@ -88,14 +88,6 @@ export const WalletAsideMenu = () => {
)}
-
- {({ isActive }) => (
-
-
- NOT Vouchers
-
- )}
-
{({ isActive }) => (
diff --git a/packages/uikit/src/components/desktop/header/DesktopHeader.tsx b/packages/uikit/src/components/desktop/header/DesktopHeader.tsx
index c15d973d5..19718b2dc 100644
--- a/packages/uikit/src/components/desktop/header/DesktopHeader.tsx
+++ b/packages/uikit/src/components/desktop/header/DesktopHeader.tsx
@@ -8,16 +8,19 @@ import { useTranslation } from '../../../hooks/translation';
import { useDisclosure } from '../../../hooks/useDisclosure';
import { usePreFetchRates } from '../../../state/rates';
import { useTonendpointBuyMethods } from '../../../state/tonendpoint';
-import { useWalletTotalBalance } from '../../../state/wallet';
import { fallbackRenderOver } from '../../Error';
-import { ArrowDownIcon, ArrowUpIcon, PlusIcon, PlusIconSmall } from "../../Icon";
-import { Num2 } from '../../Text';
+import { ArrowDownIcon, ArrowUpIcon, PlusIconSmall } from '../../Icon';
+import { Body2Class, Num2 } from '../../Text';
import { Button } from '../../fields/Button';
import { IconButton } from '../../fields/IconButton';
import { Link } from 'react-router-dom';
-import { AppProRoute } from '../../../libs/routes';
+import { AppProRoute, AppRoute, SettingsRoute } from '../../../libs/routes';
import { BuyNotification } from '../../home/BuyAction';
import { Skeleton } from '../../shared/Skeleton';
+import { useWalletTotalBalance } from '../../../state/asset';
+import { hexToRGBA } from '../../../libs/css';
+import { useActiveTonNetwork } from '../../../state/wallet';
+import { Network } from '@tonkeeper/core/dist/entries/network';
const DesktopHeaderStyled = styled.div`
padding-left: 1rem;
@@ -96,6 +99,26 @@ const LinkStyled = styled(Link)`
text-decoration: unset;
`;
+const TestnetBadge = styled(Link)`
+ background: ${p => hexToRGBA(p.theme.accentRed, 0.16)};
+ color: ${p => p.theme.accentRed};
+ padding: 4px 8px;
+ border-radius: ${p => p.theme.corner2xSmall};
+ border: none;
+ text-transform: uppercase;
+ margin-left: 10px;
+ margin-right: auto;
+ text-decoration: none;
+
+ transition: background 0.15s ease-in-out;
+
+ &:hover {
+ background: ${p => hexToRGBA(p.theme.accentRed, 0.36)};
+ }
+
+ ${Body2Class};
+`;
+
const DesktopHeaderPayload = () => {
usePreFetchRates();
const { fiat } = useAppContext();
@@ -104,6 +127,7 @@ const DesktopHeaderPayload = () => {
const { isOpen, onClose, onOpen } = useDisclosure();
const { data: buy } = useTonendpointBuyMethods();
const { t } = useTranslation();
+ const network = useActiveTonNetwork();
return (
@@ -114,7 +138,9 @@ const DesktopHeaderPayload = () => {
{formatFiatCurrency(fiat, balance || 0)}
)}
-
+ {network === Network.TESTNET && (
+ Testnet
+ )}
= ({ account, fallbackAddress }) => {
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
const { t } = useTranslation();
return (
@@ -111,9 +111,9 @@ export const HistoryCellAccount: FC<{
{account?.name
? account.name
: account?.address
- ? toShortValue(formatAddress(account.address, wallet.network))
+ ? toShortValue(formatAddress(account.address, network))
: fallbackAddress
- ? toShortValue(formatAddress(fallbackAddress, wallet.network))
+ ? toShortValue(formatAddress(fallbackAddress, network))
: t('transactions_unknown')}
);
diff --git a/packages/uikit/src/components/desktop/history/ton/JettonDesktopActions.tsx b/packages/uikit/src/components/desktop/history/ton/JettonDesktopActions.tsx
index 7f9c94d51..292f7f5b4 100644
--- a/packages/uikit/src/components/desktop/history/ton/JettonDesktopActions.tsx
+++ b/packages/uikit/src/components/desktop/history/ton/JettonDesktopActions.tsx
@@ -15,9 +15,9 @@ import { eqAddresses } from '@tonkeeper/core/dist/utils/address';
import { CryptoCurrency } from '@tonkeeper/core/dist/entries/crypto';
import styled from 'styled-components';
import { ChevronRightIcon, FireIcon, SparkIcon, SwapIcon } from '../../../Icon';
-import { useWalletContext } from '../../../../hooks/appContext';
import { useTranslation } from '../../../../hooks/translation';
import { toDexName } from '../../../activity/NotificationCommon';
+import { useActiveWallet } from '../../../../state/wallet';
type SwapAsset = {
amount: string | number;
@@ -66,14 +66,14 @@ export const JettonTransferDesktopAction: FC<{
action: Action;
isScam: boolean;
}> = ({ action, isScam }) => {
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const { jettonTransfer } = action;
if (!jettonTransfer) {
return ;
}
- if (eqAddresses(wallet.active.rawAddress, jettonTransfer.sender?.address)) {
+ if (eqAddresses(wallet.rawAddress, jettonTransfer.sender?.address)) {
return (
<>
diff --git a/packages/uikit/src/components/desktop/history/ton/NftDesktopActions.tsx b/packages/uikit/src/components/desktop/history/ton/NftDesktopActions.tsx
index 98d9434a7..8d753fbe9 100644
--- a/packages/uikit/src/components/desktop/history/ton/NftDesktopActions.tsx
+++ b/packages/uikit/src/components/desktop/history/ton/NftDesktopActions.tsx
@@ -11,8 +11,7 @@ import {
ErrorRow,
HistoryCellActionGeneric
} from './HistoryCell';
-import { useWalletContext } from '../../../../hooks/appContext';
-import { useNftCollectionData, useNftItemData } from '../../../../state/wallet';
+import { useActiveWallet } from '../../../../state/wallet';
import styled, { css } from 'styled-components';
import { Body2 } from '../../../Text';
import { Skeleton } from '../../../shared/Skeleton';
@@ -20,7 +19,7 @@ import { useFormatCoinValue } from '../../../../hooks/balance';
import { ChevronRightIcon, CoinsIcon } from '../../../Icon';
import { useTranslation } from '../../../../hooks/translation';
import { ContractDeployIcon } from '../../../activity/ActivityIcons';
-import { useIsSpamNft, useIsUnverifiedNft } from '../../../../state/nft';
+import { useIsSpamNft, useIsUnverifiedNft, useNftCollectionData, useNftItemData } from "../../../../state/nft";
const NftImage = styled.img<{ isUnverified?: boolean }>`
width: 20px;
@@ -130,7 +129,7 @@ export const NftTransferDesktopAction: FC<{
isScam: boolean;
}> = ({ action, isScam }) => {
const { t } = useTranslation();
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const { nftItemTransfer } = action;
const { data: nftInfo } = useNftItemData(action.nftItemTransfer?.nft || '');
@@ -142,7 +141,7 @@ export const NftTransferDesktopAction: FC<{
return ;
}
- if (eqAddresses(wallet.active.rawAddress, nftItemTransfer.sender?.address)) {
+ if (eqAddresses(wallet.rawAddress, nftItemTransfer.sender?.address)) {
return (
<>
diff --git a/packages/uikit/src/components/desktop/history/ton/SmartContractExecDesktopAction.tsx b/packages/uikit/src/components/desktop/history/ton/SmartContractExecDesktopAction.tsx
index ec5c3c4ce..771e116c1 100644
--- a/packages/uikit/src/components/desktop/history/ton/SmartContractExecDesktopAction.tsx
+++ b/packages/uikit/src/components/desktop/history/ton/SmartContractExecDesktopAction.tsx
@@ -1,6 +1,5 @@
import React, { FC } from 'react';
import { Action } from '@tonkeeper/core/dist/tonApiV2';
-import { useWalletContext } from '../../../../hooks/appContext';
import {
ActionRow,
ErrorRow,
@@ -13,12 +12,13 @@ import { eqAddresses } from '@tonkeeper/core/dist/utils/address';
import { CryptoCurrency } from '@tonkeeper/core/dist/entries/crypto';
import { CodeIcon } from '../../../Icon';
import { useTranslation } from '../../../../hooks/translation';
+import { useActiveWallet } from '../../../../state/wallet';
export const SmartContractExecDesktopAction: FC<{
action: Action;
isScam: boolean;
}> = ({ action, isScam }) => {
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const { smartContractExec } = action;
const { t } = useTranslation();
@@ -43,9 +43,7 @@ export const SmartContractExecDesktopAction: FC<{
decimals={9}
isFailed={action.status === 'failed'}
isSpam={isScam}
- isNegative={
- !eqAddresses(smartContractExec.contract.address, wallet.active.rawAddress)
- }
+ isNegative={!eqAddresses(smartContractExec.contract.address, wallet.rawAddress)}
/>
>
diff --git a/packages/uikit/src/components/desktop/history/ton/TonTransferDesktopAction.tsx b/packages/uikit/src/components/desktop/history/ton/TonTransferDesktopAction.tsx
index 6fe5ea545..28a995da9 100644
--- a/packages/uikit/src/components/desktop/history/ton/TonTransferDesktopAction.tsx
+++ b/packages/uikit/src/components/desktop/history/ton/TonTransferDesktopAction.tsx
@@ -12,20 +12,20 @@ import {
HistoryCellAccount,
ErrorRow
} from './HistoryCell';
-import { useWalletContext } from '../../../../hooks/appContext';
+import { useActiveWallet } from '../../../../state/wallet';
export const TonTransferDesktopAction: FC<{
action: Action;
isScam: boolean;
}> = ({ action, isScam }) => {
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const { tonTransfer } = action;
if (!tonTransfer) {
return ;
}
- if (eqAddresses(tonTransfer.recipient.address, wallet.active.rawAddress)) {
+ if (eqAddresses(tonTransfer.recipient.address, wallet.rawAddress)) {
return (
<>
diff --git a/packages/uikit/src/components/desktop/multi-send/MultiSendConfirmNotification.tsx b/packages/uikit/src/components/desktop/multi-send/MultiSendConfirmNotification.tsx
index ba2ab6ef7..890f263b2 100644
--- a/packages/uikit/src/components/desktop/multi-send/MultiSendConfirmNotification.tsx
+++ b/packages/uikit/src/components/desktop/multi-send/MultiSendConfirmNotification.tsx
@@ -10,8 +10,6 @@ import { useRate } from '../../../state/rates';
import { useAppContext } from '../../../hooks/appContext';
import { formatFiatCurrency, useFormatCoinValue } from '../../../hooks/balance';
import { ListBlock, ListItem } from '../../List';
-import { useWalletState } from '../../../state/wallet';
-import { WalletEmoji } from '../../shared/emoji/WalletEmoji';
import { useTranslation } from '../../../hooks/translation';
import { useEstimateMultiTransfer } from '../../../hooks/blockchain/useEstimateMultiTransferFee';
import { Skeleton } from '../../shared/Skeleton';
@@ -31,6 +29,7 @@ import { useDisclosure } from '../../../hooks/useDisclosure';
import { MultiSendReceiversNotification } from './MultiSendReceiversNotification';
import { NotEnoughBalanceError } from '@tonkeeper/core/dist/errors/NotEnoughBalanceError';
import { TON_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants';
+import { AccountAndWalletInfo } from '../../account/AccountAndWalletInfo';
const ConfirmWrapper = styled.div`
display: flex;
@@ -96,12 +95,6 @@ const ListItemStyled = styled(ListItem)`
}
`;
-const WalletNameStyled = styled.div`
- display: flex;
- align-items: center;
- gap: 6px;
-`;
-
const RecipientsContainer = styled.div`
display: flex;
flex-direction: column;
@@ -165,9 +158,6 @@ const MultiSendConfirmContent: FC<{
willBeSentBN?.multipliedBy(rate?.prices || 0)
);
- const { account } = useAppContext();
- const { data: wallet } = useWalletState(account.activePublicKey!);
-
const {
mutateAsync: estimate,
isLoading: estimateLoading,
@@ -201,16 +191,7 @@ const MultiSendConfirmContent: FC<{
{t('send_screen_steps_comfirm_wallet')}
- {wallet && (
-
-
- {wallet.name || t('wallet_title')}
-
- )}
+
{t('recipients')}
diff --git a/packages/uikit/src/components/desktop/multi-send/MultiSendTable.tsx b/packages/uikit/src/components/desktop/multi-send/MultiSendTable.tsx
index 60b69b378..f26226c30 100644
--- a/packages/uikit/src/components/desktop/multi-send/MultiSendTable.tsx
+++ b/packages/uikit/src/components/desktop/multi-send/MultiSendTable.tsx
@@ -2,19 +2,17 @@ import { Address } from '@ton/core';
import { TON_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants';
import { TonAsset, isTon } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset';
import { DnsRecipient, TonRecipient } from '@tonkeeper/core/dist/entries/send';
-import { WalletVersion } from '@tonkeeper/core/dist/entries/wallet';
+import { isW5Version } from '@tonkeeper/core/dist/entries/wallet';
import { arrayToCsvString } from '@tonkeeper/core/dist/service/parserService';
import { MAX_ALLOWED_WALLET_MSGS } from '@tonkeeper/core/dist/service/transfer/multiSendService';
import { shiftedDecimals } from '@tonkeeper/core/dist/utils/balance';
-import { getDecimalSeparator } from '@tonkeeper/core/dist/utils/formatting';
-import { removeGroupSeparator } from '@tonkeeper/core/dist/utils/send';
import BigNumber from 'bignumber.js';
import { FC, useEffect, useState } from 'react';
import { Controller, FormProvider, useFieldArray, useForm, useFormContext } from 'react-hook-form';
import { ControllerRenderProps } from 'react-hook-form/dist/types/controller';
import { Link, useBlocker, useNavigate } from 'react-router-dom';
import styled, { css } from 'styled-components';
-import { useAppContext, useWalletContext } from '../../../hooks/appContext';
+import { useAppContext } from '../../../hooks/appContext';
import { formatter } from '../../../hooks/balance';
import { useTranslation } from '../../../hooks/translation';
import {
@@ -51,6 +49,9 @@ import { SaveListNotification } from './SaveListNotification';
import { UpdateListNotification } from './UpdateListNotification';
import { ImportListNotification } from './import-list/ImportListNotification';
import { getWillBeMultiSendValue } from './utils';
+import { removeGroupSeparator } from '@tonkeeper/core/dist/utils/send';
+import { getDecimalSeparator } from '@tonkeeper/core/dist/utils/formatting';
+import { useActiveWallet } from '../../../state/wallet';
const FormHeadingWrapper = styled.div`
display: flex;
@@ -318,9 +319,9 @@ const MultiSendAddMore: FC<{
}> = ({ onAdd, fieldsNumber }) => {
const { t } = useTranslation();
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
- if (fieldsNumber < MAX_ALLOWED_WALLET_MSGS[wallet.active.version]) {
+ if (fieldsNumber < MAX_ALLOWED_WALLET_MSGS[wallet.version]) {
return (
{isLinkedWithAnotherWallet && !isLoading && (
diff --git a/packages/uikit/src/components/nft/NftAction.tsx b/packages/uikit/src/components/nft/NftAction.tsx
index eafb5be10..73e3b0f7f 100644
--- a/packages/uikit/src/components/nft/NftAction.tsx
+++ b/packages/uikit/src/components/nft/NftAction.tsx
@@ -3,13 +3,13 @@ import { NFT, isNFTDNS } from '@tonkeeper/core/dist/entries/nft';
import { NftItem } from '@tonkeeper/core/dist/tonApiV2';
import { FC } from 'react';
import styled from 'styled-components';
-import { useWalletContext } from '../../hooks/appContext';
import { useAppSdk } from '../../hooks/appSdk';
import { useTranslation } from '../../hooks/translation';
import { Body2 } from '../Text';
import { Button } from '../fields/Button';
import { LinkNft } from './LinkNft';
import { RenewNft } from './RenewNft';
+import { useActiveWallet } from '../../state/wallet';
const getMarketplaceUrl = (nftItem: NftItem) => {
const { marketplace } = nftItem.metadata;
@@ -48,7 +48,7 @@ const ActionTransfer: FC<{
}> = ({ nftItem }) => {
const sdk = useAppSdk();
const { t } = useTranslation();
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
return (
<>
@@ -57,8 +57,7 @@ const ActionTransfer: FC<{
size="large"
fullWidth
disabled={
- nftItem.sale !== undefined ||
- nftItem.owner?.address !== wallet.active.rawAddress
+ nftItem.sale !== undefined || nftItem.owner?.address !== wallet.rawAddress
}
onClick={e => {
e.preventDefault();
diff --git a/packages/uikit/src/components/nft/NftDetails.tsx b/packages/uikit/src/components/nft/NftDetails.tsx
index 12f6a3526..4e6575d81 100644
--- a/packages/uikit/src/components/nft/NftDetails.tsx
+++ b/packages/uikit/src/components/nft/NftDetails.tsx
@@ -3,15 +3,16 @@ import { NftItem } from '@tonkeeper/core/dist/tonApiV2';
import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
import React, { FC } from 'react';
import styled from 'styled-components';
-import { useAppContext, useWalletContext } from '../../hooks/appContext';
+import { useAppContext } from '../../hooks/appContext';
import { useAppSdk } from '../../hooks/appSdk';
import { useDateFormat } from '../../hooks/dateFormat';
import { useTranslation } from '../../hooks/translation';
-import { useNftDNSExpirationDate, useNftItemData } from '../../state/wallet';
+import { useActiveTonNetwork, useActiveWallet } from '../../state/wallet';
import { SpinnerIcon } from '../Icon';
import { ListBlock, ListItem, ListItemPayload } from '../List';
import { Body1, H3, Label1 } from '../Text';
import { NFTKind } from './NftAction';
+import { useNftDNSExpirationDate, useNftItemData } from '../../state/nft';
const Block = styled.div`
width: 100%;
@@ -34,7 +35,6 @@ const RightText = styled(Body1)`
`;
export const NftDetails: FC<{ nftItem: NftItem; kind: NFTKind }> = React.memo(({ nftItem }) => {
- const wallet = useWalletContext();
const { t } = useTranslation();
const { data } = useNftItemData(nftItem.address);
const { data: expirationDate, isLoading: isExpirationDateLoading } =
@@ -52,8 +52,9 @@ export const NftDetails: FC<{ nftItem: NftItem; kind: NFTKind }> = React.memo(({
const owner = item.owner?.address;
const address = Address.parse(item.address).toString();
+ const network = useActiveTonNetwork();
const url = config.NFTOnExplorerUrl ?? 'https://tonviewer.com/nft/%s';
- const nftAddress = formatAddress(address, wallet.network, true);
+ const nftAddress = formatAddress(address, network, true);
return (
@@ -65,12 +66,10 @@ export const NftDetails: FC<{ nftItem: NftItem; kind: NFTKind }> = React.memo(({
{owner && (
- sdk.copyToClipboard(formatAddress(owner, wallet.network))}
- >
+ sdk.copyToClipboard(formatAddress(owner, network))}>
{t('nft_owner_address')}
- {toShortValue(formatAddress(owner, wallet.network))}
+ {toShortValue(formatAddress(owner, network))}
)}
diff --git a/packages/uikit/src/components/nft/NftHeader.tsx b/packages/uikit/src/components/nft/NftHeader.tsx
index a7ad6f3b0..c3ed41489 100644
--- a/packages/uikit/src/components/nft/NftHeader.tsx
+++ b/packages/uikit/src/components/nft/NftHeader.tsx
@@ -3,7 +3,7 @@ import React, { FC } from 'react';
import styled, { css } from 'styled-components';
import { VerificationIcon } from '../Icon';
import { Body2, Body3, Label2 } from '../Text';
-import { useActiveWalletConfig } from '../../state/wallet';
+import { useActiveTonWalletConfig } from '../../state/wallet';
import { useTranslation } from '../../hooks/translation';
import { SpamBadge } from '../activity/NotificationCommon';
@@ -59,7 +59,7 @@ const IconBody = styled.span`
export const NftCollectionBody3: FC<{ nft: NftItem }> = React.memo(({ nft }) => {
const { t } = useTranslation();
- const { data } = useActiveWalletConfig();
+ const { data } = useActiveTonWalletConfig();
const isTrusted = data?.trustedNfts.includes(nft.collection?.address || nft.address);
const isSuspicious = nft.trust !== TrustType.Whitelist;
diff --git a/packages/uikit/src/components/nft/NftView.tsx b/packages/uikit/src/components/nft/NftView.tsx
index 72d2478af..e7b6bba28 100644
--- a/packages/uikit/src/components/nft/NftView.tsx
+++ b/packages/uikit/src/components/nft/NftView.tsx
@@ -2,7 +2,7 @@ import { NFT } from '@tonkeeper/core/dist/entries/nft';
import React, { FC, useMemo, useRef } from 'react';
import styled from 'styled-components';
import { useTranslation } from '../../hooks/translation';
-import { useActiveWalletConfig, useNftCollectionData } from '../../state/wallet';
+import { useActiveTonWalletConfig } from '../../state/wallet';
import {
BlockIcon,
ChevronDownIcon,
@@ -21,7 +21,7 @@ import { NftDetails } from './NftDetails';
import { Image, NftBlock } from './Nfts';
import { TrustType } from '@tonkeeper/core/dist/tonApiV2';
import { Button } from '../fields/Button';
-import { useHideNft, useMarkNftAsSpam, useMarkNftAsTrusted } from '../../state/nft';
+import { useHideNft, useMarkNftAsSpam, useMarkNftAsTrusted, useNftCollectionData } from "../../state/nft";
import { UnverifiedNftNotification } from './UnverifiedNftNotification';
import { useDisclosure } from '../../hooks/useDisclosure';
import { DropDown } from '../DropDown';
@@ -130,7 +130,7 @@ export const NftPreview: FC<{
const { mutate: markNftAsTrusted, isLoading: markNftAsTrustedLoading } = useMarkNftAsTrusted();
const { mutateAsync: hideNft } = useHideNft();
- const { data } = useActiveWalletConfig();
+ const { data } = useActiveTonWalletConfig();
const isSuspicious = nftItem.trust !== TrustType.Whitelist;
const isTrusted = !!data?.trustedNfts.includes(nftItem.collection?.address || nftItem.address);
diff --git a/packages/uikit/src/components/nft/Nfts.tsx b/packages/uikit/src/components/nft/Nfts.tsx
index 966934dee..bc3b4b9c2 100644
--- a/packages/uikit/src/components/nft/Nfts.tsx
+++ b/packages/uikit/src/components/nft/Nfts.tsx
@@ -5,9 +5,9 @@ import styled, { css } from 'styled-components';
import { AppSelectionContext, useAppContext } from '../../hooks/appContext';
import { useAppSdk } from '../../hooks/appSdk';
import { toDaysLeft } from '../../hooks/dateFormat';
-import { useNftDNSExpirationDate } from '../../state/wallet';
import { FireBadgeIcon, SaleIcon } from '../Icon';
import { NftCollectionBody3, NftHeaderLabel2 } from './NftHeader';
+import { useNftDNSExpirationDate } from "../../state/nft";
const Grid = styled.div`
display: grid;
diff --git a/packages/uikit/src/components/nft/RenewNft.tsx b/packages/uikit/src/components/nft/RenewNft.tsx
index 4d5a61603..0323afeee 100644
--- a/packages/uikit/src/components/nft/RenewNft.tsx
+++ b/packages/uikit/src/components/nft/RenewNft.tsx
@@ -16,12 +16,12 @@ import { toDaysLeft, useDateFormat } from '../../hooks/dateFormat';
import { useTranslation } from '../../hooks/translation';
import { useNotification } from '../../hooks/useNotification';
import { useQueryChangeWait } from '../../hooks/useQueryChangeWait';
-import { useNftDNSExpirationDate } from '../../state/wallet';
import { Notification } from '../Notification';
import { Body2 } from '../Text';
import { Button } from '../fields/Button';
import { ConfirmView, ConfirmViewButtons, ConfirmViewButtonsSlot } from '../transfer/ConfirmView';
import { ConfirmAndCancelMainButton } from '../transfer/common';
+import { useNftDNSExpirationDate } from "../../state/nft";
const RenewDNSBlock = styled.div`
width: 100%;
diff --git a/packages/uikit/src/components/settings/AccountSettings.tsx b/packages/uikit/src/components/settings/AccountSettings.tsx
index eff4ee61f..dc63a8e4c 100644
--- a/packages/uikit/src/components/settings/AccountSettings.tsx
+++ b/packages/uikit/src/components/settings/AccountSettings.tsx
@@ -1,11 +1,11 @@
import { walletVersionText } from '@tonkeeper/core/dist/entries/wallet';
import { useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
-import { useAppContext, useWalletContext } from '../../hooks/appContext';
+import { useAppContext } from '../../hooks/appContext';
import { useTranslation } from '../../hooks/translation';
import { SettingsRoute, relative, WalletSettingsRoute } from '../../libs/routes';
import { useJettonList } from '../../state/jetton';
-import { LogOutWalletNotification } from './LogOutNotification';
+import { DeleteAccountNotification } from './DeleteAccountNotification';
import {
AppsIcon,
ListOfTokensIcon,
@@ -17,26 +17,21 @@ import {
WalletsIcon
} from './SettingsIcons';
import { SettingsItem, SettingsList } from './SettingsList';
-import { useWalletNftList } from '../../state/wallet';
+import { useActiveWallet, useAccountsState, useActiveAccount } from '../../state/wallet';
+import { useWalletNftList } from '../../state/nft';
const SingleAccountSettings = () => {
- const [logout, setLogout] = useState(false);
const { t } = useTranslation();
const navigate = useNavigate();
- const wallet = useWalletContext();
+ const account = useActiveAccount();
+ const wallet = account.activeTonWallet;
const { data: jettons } = useJettonList();
const { data: nft } = useWalletNftList();
const { proFeatures } = useAppContext();
const mainItems = useMemo(() => {
- const items: SettingsItem[] = [
- // {
- // name: t('Subscriptions'),
- // icon: ,
- // action: () => navigate(relative(SettingsRoute.subscriptions)),
- // },
- ];
+ const items: SettingsItem[] = [];
- if (wallet.auth == null) {
+ if (account.type === 'mnemonic') {
items.push({
name: t('settings_recovery_phrase'),
icon: ,
@@ -44,11 +39,22 @@ const SingleAccountSettings = () => {
});
}
- items.push({
- name: t('settings_wallet_version'),
- icon: walletVersionText(wallet.active.version),
- action: () => navigate(relative(SettingsRoute.version))
- });
+ if (account.type === 'mnemonic' || account.type === 'ton-only') {
+ items.push({
+ name: t('settings_wallet_version'),
+ icon: walletVersionText(wallet.version),
+ action: () => navigate(relative(SettingsRoute.version))
+ });
+ }
+
+ // check available derivations length to filter and keep only non-legacy added ledger accounts
+ if (account.type === 'ledger' && account.allAvailableDerivations.length > 1) {
+ items.push({
+ name: t('settings_ledger_indexes'),
+ icon: `# ${account.activeDerivationIndex + 1}`,
+ action: () => navigate(relative(SettingsRoute.ledgerIndexes))
+ });
+ }
if (proFeatures) {
items.unshift({
@@ -84,22 +90,13 @@ const SingleAccountSettings = () => {
icon: ,
action: () => navigate(relative(WalletSettingsRoute.connectedApps))
});
- items.push({
- name: t('settings_reset'),
- icon: ,
- action: () => setLogout(true)
- });
return items;
- }, [t, navigate, wallet, jettons, nft]);
+ }, [t, navigate, account, jettons, nft]);
return (
<>
- setLogout(false)}
- />
>
);
};
@@ -107,11 +104,14 @@ const SingleAccountSettings = () => {
const MultipleAccountSettings = () => {
const { t } = useTranslation();
const navigate = useNavigate();
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const { data: jettons } = useJettonList();
const { data: nft } = useWalletNftList();
const { proFeatures } = useAppContext();
+ const account = useActiveAccount();
+
+ const [deleteAccount, setDeleteAccount] = useState(false);
const accountItems = useMemo(() => {
const items: SettingsItem[] = [
@@ -141,7 +141,7 @@ const MultipleAccountSettings = () => {
const mainItems = useMemo(() => {
const items: SettingsItem[] = [];
- if (wallet.auth == null) {
+ if (account.type === 'mnemonic') {
items.push({
name: t('settings_recovery_phrase'),
icon: ,
@@ -149,11 +149,22 @@ const MultipleAccountSettings = () => {
});
}
- items.push({
- name: t('settings_wallet_version'),
- icon: walletVersionText(wallet.active.version),
- action: () => navigate(relative(SettingsRoute.version))
- });
+ if (account.type === 'mnemonic' || account.type === 'ton-only') {
+ items.push({
+ name: t('settings_wallet_version'),
+ icon: walletVersionText(wallet.version),
+ action: () => navigate(relative(SettingsRoute.version))
+ });
+ }
+
+ // check available derivations length to filter and keep only non-legacy added ledger accounts
+ if (account.type === 'ledger' && account.allAvailableDerivations.length > 1) {
+ items.push({
+ name: t('settings_ledger_indexes'),
+ icon: `# ${account.activeDerivationIndex + 1}`,
+ action: () => navigate(relative(SettingsRoute.ledgerIndexes))
+ });
+ }
if (jettons?.balances.length) {
items.push({
@@ -181,6 +192,11 @@ const MultipleAccountSettings = () => {
icon: ,
action: () => navigate(relative(WalletSettingsRoute.connectedApps))
});
+ items.push({
+ name: t('Delete_wallet_data'),
+ icon: ,
+ action: () => setDeleteAccount(true)
+ });
return items;
}, [t, navigate, wallet, jettons, nft]);
@@ -188,14 +204,18 @@ const MultipleAccountSettings = () => {
<>
+ setDeleteAccount(false)}
+ />
>
);
};
export const AccountSettings = () => {
- const { account } = useAppContext();
+ const accounts = useAccountsState();
- if (account.publicKeys.length > 1) {
+ if (accounts.length > 1) {
return ;
} else {
return ;
diff --git a/packages/uikit/src/components/settings/ClearSettings.tsx b/packages/uikit/src/components/settings/ClearSettings.tsx
index 2952cf77b..9429c3e2d 100644
--- a/packages/uikit/src/components/settings/ClearSettings.tsx
+++ b/packages/uikit/src/components/settings/ClearSettings.tsx
@@ -1,20 +1,20 @@
import React, { useMemo, useState } from 'react';
-import { useAppContext } from '../../hooks/appContext';
import { useTranslation } from '../../hooks/translation';
-import { DeleteAllNotification } from './LogOutNotification';
+import { DeleteAllNotification } from './DeleteAccountNotification';
import { DeleteAccountIcon } from './SettingsIcons';
import { SettingsList } from './SettingsList';
+import { useAccountsState } from '../../state/wallet';
export const ClearSettings = () => {
const { t } = useTranslation();
- const { account } = useAppContext();
+ const wallets = useAccountsState();
const [open, setOpen] = useState(false);
const deleteItems = useMemo(() => {
return [
{
name:
- account.publicKeys.length > 1
+ wallets.length > 1
? t('Delete_all_accounts_and_logout')
: t('settings_delete_account'),
icon: ,
diff --git a/packages/uikit/src/components/settings/LogOutNotification.tsx b/packages/uikit/src/components/settings/DeleteAccountNotification.tsx
similarity index 60%
rename from packages/uikit/src/components/settings/LogOutNotification.tsx
rename to packages/uikit/src/components/settings/DeleteAccountNotification.tsx
index b87b68784..25874e156 100644
--- a/packages/uikit/src/components/settings/LogOutNotification.tsx
+++ b/packages/uikit/src/components/settings/DeleteAccountNotification.tsx
@@ -1,19 +1,19 @@
import { useQueryClient } from '@tanstack/react-query';
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
import { FC, useCallback, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import styled from 'styled-components';
import { useTranslation } from '../../hooks/translation';
import { AppRoute, SettingsRoute } from '../../libs/routes';
-import { useMutateDeleteAll } from '../../state/account';
+import { useMutateDeleteAll } from '../../state/wallet';
import { useMutateLogOut } from '../../state/wallet';
import { Notification } from '../Notification';
import { Body1, H2, Label1, Label2 } from '../Text';
import { Button } from '../fields/Button';
import { Checkbox } from '../fields/Checkbox';
import { DisclaimerBlock } from '../home/BuyItemNotification';
+import { Account, AccountId } from '@tonkeeper/core/dist/entries/account';
-const NotificationBlock = styled.form`
+const NotificationBlock = styled.div`
display: flex;
flex-direction: column;
align-items: center;
@@ -36,103 +36,18 @@ const DisclaimerLink = styled(Label1)`
margin-left: 2.35rem;
`;
-const LotOutContent: FC<{
- onClose: (action: () => void) => void;
- publicKey: string;
- isKeystone: boolean;
-}> = ({ onClose, publicKey, isKeystone }) => {
- const navigate = useNavigate();
- const { t } = useTranslation();
- const [checked, setChecked] = useState(false);
- const { mutateAsync, isLoading } = useMutateLogOut(publicKey);
-
- return (
-
-
- {t('settings_reset_alert_title')}
-
- {t(
- isKeystone
- ? 'Delete_keystone_wallet_data_description'
- : 'settings_reset_alert_caption'
- )}
-
-
-
- {!isKeystone && (
-
-
-
- {t('I_have_a_backup_copy_of_recovery_phrase')}
-
-
-
- onClose(() =>
- navigate(
- AppRoute.settings + SettingsRoute.recovery + '/' + publicKey
- )
- )
- }
- >
- {t('Back_up_now')}
-
-
- )}
- {isKeystone && }
-
-
- );
-};
-
-export const LogOutWalletNotification: FC<{
- wallet?: WalletState;
- handleClose: () => void;
-}> = ({ wallet, handleClose }) => {
- const Content = useCallback(
- (afterClose: (action: () => void) => void) => {
- if (!wallet) return undefined;
- return (
-
- );
- },
- [wallet]
- );
-
- return (
-
- {Content}
-
- );
-};
-
const DeleteContent: FC<{
onClose: (action: () => void) => void;
- publicKey: string;
+ accountId: AccountId;
isKeystone: boolean;
-}> = ({ onClose, publicKey, isKeystone }) => {
+}> = ({ onClose, accountId, isKeystone }) => {
const navigate = useNavigate();
const { t } = useTranslation();
const [checked, setChecked] = useState(false);
- const { mutateAsync, isLoading } = useMutateLogOut(publicKey, true);
+ const { mutateAsync, isLoading } = useMutateLogOut();
const onDelete = async () => {
- await mutateAsync();
+ await mutateAsync(accountId);
onClose(() => navigate(AppRoute.home));
};
@@ -160,7 +75,7 @@ const DeleteContent: FC<{
onClick={() =>
onClose(() =>
navigate(
- AppRoute.settings + SettingsRoute.recovery + '/' + publicKey
+ AppRoute.settings + SettingsRoute.recovery + '/' + accountId
)
)
}
@@ -176,6 +91,7 @@ const DeleteContent: FC<{
fullWidth
loading={isLoading}
onClick={onDelete}
+ type="button"
>
{t('Delete_wallet_data')}
@@ -183,26 +99,26 @@ const DeleteContent: FC<{
);
};
-export const DeleteWalletNotification: FC<{
- wallet?: WalletState;
+export const DeleteAccountNotification: FC<{
+ account?: Account;
handleClose: () => void;
-}> = ({ wallet, handleClose }) => {
+}> = ({ account, handleClose }) => {
const Content = useCallback(
(afterClose: (action: () => void) => void) => {
- if (!wallet) return undefined;
+ if (!account) return undefined;
return (
);
},
- [wallet]
+ [account]
);
return (
-
+
{Content}
);
@@ -250,6 +166,7 @@ const DeleteAllContent: FC<{ onClose: (action: () => void) => void }> = ({ onClo
fullWidth
loading={isLoading}
onClick={onDelete}
+ type="button"
>
{t('Delete_wallet_data')}
diff --git a/packages/uikit/src/components/settings/ProSettings.tsx b/packages/uikit/src/components/settings/ProSettings.tsx
index e708eb689..cc3f6316b 100644
--- a/packages/uikit/src/components/settings/ProSettings.tsx
+++ b/packages/uikit/src/components/settings/ProSettings.tsx
@@ -4,23 +4,25 @@ import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
import { ProServiceTier } from '@tonkeeper/core/src/tonConsoleApi';
import { FC, PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
-import { WalletStateContext } from '../../hooks/appContext';
import { useNotifyError } from '../../hooks/appSdk';
import { useFormatCoinValue } from '../../hooks/balance';
import { useEstimateTransfer } from '../../hooks/blockchain/useEstimateTransfer';
import { useSendTransfer } from '../../hooks/blockchain/useSendTransfer';
import { useTranslation } from '../../hooks/translation';
-import { useAccountState } from '../../state/account';
import {
ConfirmState,
useCreateInvoiceMutation,
useProLogout,
useProPlans,
useProState,
- useSelectWalletMutation,
+ useSelectWalletForProMutation,
useWaitInvoiceMutation
} from '../../state/pro';
-import { useWalletState } from '../../state/wallet';
+import {
+ useAccountAndWalletByWalletId,
+ useAccountsState,
+ useActiveTonNetwork
+} from '../../state/wallet';
import { InnerBody } from '../Body';
import { SubscriptionStatus } from '../desktop/aside/SubscriptionInfo';
import { Button } from '../fields/Button';
@@ -33,6 +35,14 @@ import { Notification } from '../Notification';
import { SubHeader } from '../SubHeader';
import { Body1, Label1, Title } from '../Text';
import { ConfirmView } from '../transfer/ConfirmView';
+import {
+ backwardCompatibilityOnlyWalletVersions,
+ sortWalletsByVersion,
+ TonWalletStandard
+} from '@tonkeeper/core/dist/entries/wallet';
+import { AccountTonMnemonic, Account } from '@tonkeeper/core/dist/entries/account';
+import { WalletEmoji } from '../shared/emoji/WalletEmoji';
+import { WalletVersionBadge } from '../account/AccountBadge';
const Block = styled.div`
display: flex;
@@ -56,19 +66,35 @@ const Description = styled(Body1)`
margin-bottom: 16px;
`;
-const WalletItem: FC<{ publicKey: string }> = ({ publicKey }) => {
- const { t } = useTranslation();
- const { data: wallet } = useWalletState(publicKey);
+const WalletEmojiStyled = styled(WalletEmoji)`
+ margin-left: 3px;
+ display: inline-flex;
+`;
- const address = wallet
- ? toShortValue(formatAddress(wallet.active.rawAddress, wallet.network))
- : undefined;
+const WalletBadgeStyled = styled(WalletVersionBadge)`
+ margin-left: 3px;
+ display: inline-block;
+`;
+
+const WalletItem: FC<{ account: Account; wallet: TonWalletStandard }> = ({ account, wallet }) => {
+ const network = useActiveTonNetwork();
+ const address = toShortValue(formatAddress(wallet.rawAddress, network));
return (
+ {account.name}
+
+ >
+ }
+ secondary={
+ <>
+ {address}
+
+ >
+ }
/>
);
};
@@ -80,26 +106,31 @@ const SelectLabel = styled(Label1)`
const SelectWallet: FC<{ onClose: () => void }> = ({ onClose }) => {
const { t } = useTranslation();
- const { data: accounts } = useAccountState();
- const { mutateAsync, error } = useSelectWalletMutation();
+ const { mutateAsync, error } = useSelectWalletForProMutation();
useNotifyError(error);
-
- if (!accounts) return <>>;
+ const accounts = useAccountsState().filter(
+ acc => acc.type === 'mnemonic'
+ ) as AccountTonMnemonic[];
return (
<>
{t('select_wallet_for_authorization')}
- {accounts.publicKeys.map(publicKey => (
- mutateAsync(publicKey).then(() => onClose())}
- >
-
-
-
-
- ))}
+ {accounts.flatMap(account =>
+ account.allTonWallets
+ .filter(w => !backwardCompatibilityOnlyWalletVersions.includes(w.version))
+ .sort(sortWalletsByVersion)
+ .map(wallet => (
+ mutateAsync(wallet.id).then(() => onClose())}
+ >
+
+
+
+
+ ))
+ )}
>
);
@@ -116,11 +147,17 @@ const ProWallet: FC<{
onClick: () => void;
disabled?: boolean;
}> = ({ data, onClick, disabled }) => {
+ const { account, wallet } = useAccountAndWalletByWalletId(data.wallet.rawAddress)!;
+
+ if (!account || !wallet) {
+ return null;
+ }
+
return (
!disabled && onClick()}>
-
+
@@ -179,19 +216,17 @@ const ConfirmNotification: FC<{
const content = useCallback(() => {
if (!state) return <>>;
return (
-
- {
- if (confirmed) {
- waitResult(state);
- setTimeout(() => onClose(true), 3000);
- } else {
- onClose();
- }
- }}
- />
-
+ {
+ if (confirmed) {
+ waitResult(state);
+ setTimeout(() => onClose(true), 3000);
+ } else {
+ onClose();
+ }
+ }}
+ />
);
}, [state]);
diff --git a/packages/uikit/src/components/settings/ThemeSettings.tsx b/packages/uikit/src/components/settings/ThemeSettings.tsx
index c45a23f5b..a9b259a12 100644
--- a/packages/uikit/src/components/settings/ThemeSettings.tsx
+++ b/packages/uikit/src/components/settings/ThemeSettings.tsx
@@ -44,18 +44,6 @@ export const ThemeSettings = () => {
icon: ,
action: () => navigate(relative(SettingsRoute.country))
});
-
- // TODO: REMOVE:
- items.push({
- name: i18n.language === 'ru' ? 'Обновление адреса' : 'Address Update',
- icon: 'EQ » UQ',
- action: () =>
- sdk.openPage(
- i18n.language === 'ru'
- ? 'https://t.me/tonkeeper_ru/65'
- : 'https://t.me/tonkeeper_news/49'
- )
- });
return items;
}, [t, i18n.enable, navigate, fiat]);
diff --git a/packages/uikit/src/components/settings/nft/NFTSettingsContent.tsx b/packages/uikit/src/components/settings/nft/NFTSettingsContent.tsx
index 0b71d8be8..03d4f41be 100644
--- a/packages/uikit/src/components/settings/nft/NFTSettingsContent.tsx
+++ b/packages/uikit/src/components/settings/nft/NFTSettingsContent.tsx
@@ -2,13 +2,13 @@ import { FC, useMemo, useState } from 'react';
import styled from 'styled-components';
import { ChevronRightIcon, MinusIcon, PlusIcon } from '../../../components/Icon';
import { ListBlock, ListItemElement, ListItemPayload } from '../../../components/List';
-import { SkeletonList } from '../../../components/Skeleton';
+import { SkeletonListWithImages } from '../../../components/Skeleton';
import { Body2, H3, Label1 } from '../../../components/Text';
import { useTranslation } from '../../../hooks/translation';
-import { useActiveWalletConfig, useWalletNftList } from '../../../state/wallet';
+import { useActiveTonWalletConfig } from '../../../state/wallet';
import { IconButton } from '../../../components/fields/IconButton';
import { BorderSmallResponsive } from '../../../components/shared/Styles';
-import { isSpamNft, useHideNft, useMakeNftVisible, useMarkNftAsTrusted } from '../../../state/nft';
+import { isSpamNft, useHideNft, useMakeNftVisible, useMarkNftAsTrusted, useWalletNftList } from "../../../state/nft";
import { SettingsNFTCollection, SettingsSingleNFT } from './models';
import { SpamNftInfoNotification } from './SpamNftInfoNotification';
import { Image } from '../../../components/shared/Image';
@@ -117,7 +117,7 @@ export const NFTSettingsContent = () => {
>();
const { data: nfts } = useWalletNftList();
- const { data: config } = useActiveWalletConfig();
+ const { data: config } = useActiveTonWalletConfig();
const collections: (SettingsNFTCollection | SettingsSingleNFT)[] = useMemo(() => {
if (!config || !nfts) return [];
@@ -181,7 +181,7 @@ export const NFTSettingsContent = () => {
const { mutate: trustNft } = useMarkNftAsTrusted();
if (!nfts || !config) {
- return ;
+ return ;
}
const onCloseSpamNftInfo = (confirmNotSpam?: boolean) => {
diff --git a/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx b/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx
index 131ec7254..5a6cd55b2 100644
--- a/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx
+++ b/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx
@@ -1,36 +1,36 @@
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
-import { formatAddress } from '@tonkeeper/core/dist/utils/common';
+import { Account } from '@tonkeeper/core/dist/entries/account';
import React, { FC, useCallback, useState } from 'react';
import { useTranslation } from '../../../hooks/translation';
-import { useMutateRenameWallet } from '../../../state/wallet';
+import { useMutateRenameAccount } from '../../../state/wallet';
import { Notification, NotificationBlock } from '../../Notification';
import { Button } from '../../fields/Button';
import { Input } from '../../fields/Input';
import { WalletEmoji } from '../../shared/emoji/WalletEmoji';
import { EmojisList } from '../../shared/emoji/EmojisList';
+import { useAccountLabel } from '../../../hooks/accountUtils';
const RenameWalletContent: FC<{
- wallet: WalletState;
+ account: Account;
afterClose: (action: () => void) => void;
animationTime?: number;
-}> = ({ animationTime, afterClose, wallet }) => {
+}> = ({ animationTime, afterClose, account }) => {
const { t } = useTranslation();
- const { mutateAsync, isLoading, isError } = useMutateRenameWallet(wallet);
+ const { mutateAsync, isLoading, isError } = useMutateRenameAccount();
- const [name, setName] = useState(wallet.name ?? '');
- const [emoji, setEmoji] = useState(wallet.emoji ?? '');
+ const [name, setName] = useState(account.name);
+ const [emoji, setEmoji] = useState(account.emoji);
const onSubmit: React.FormEventHandler = async e => {
e.preventDefault();
- await mutateAsync({ name, emoji });
+ await mutateAsync({ id: account.id, name, emoji });
afterClose(() => null);
};
- const address = formatAddress(wallet.active.rawAddress, wallet.network);
+ const label = useAccountLabel(account);
return (
-
+
void;
-}> = ({ wallet, handleClose }) => {
+}> = ({ account, handleClose }) => {
const { t } = useTranslation();
const Content = useCallback(
(afterClose: (action: () => void) => void) => {
- if (!wallet) return undefined;
+ if (!account) return undefined;
return (
-
+
);
},
- [wallet]
+ [account]
);
return (
-
+
{Content}
);
diff --git a/packages/uikit/src/components/shared/AsideItem.tsx b/packages/uikit/src/components/shared/AsideItem.tsx
index 56e953490..fe4c9ad5f 100644
--- a/packages/uikit/src/components/shared/AsideItem.tsx
+++ b/packages/uikit/src/components/shared/AsideItem.tsx
@@ -1,4 +1,5 @@
import styled from 'styled-components';
+import { hexToRGBA } from '../../libs/css';
export const AsideMenuItem = styled.button<{ isSelected: boolean }>`
background: ${p => (p.isSelected ? p.theme.backgroundContentTint : p.theme.backgroundContent)};
@@ -21,6 +22,6 @@ export const AsideMenuItem = styled.button<{ isSelected: boolean }>`
transition: background-color 0.15s ease-in-out;
&:hover {
- background: ${p => p.theme.backgroundContentTint};
+ background: ${p => hexToRGBA(p.theme.backgroundContentTint, 0.56)};
}
`;
diff --git a/packages/uikit/src/components/shared/Badge.tsx b/packages/uikit/src/components/shared/Badge.tsx
index 82e2d19eb..b9fc6ba86 100644
--- a/packages/uikit/src/components/shared/Badge.tsx
+++ b/packages/uikit/src/components/shared/Badge.tsx
@@ -1,26 +1,58 @@
import { FC, PropsWithChildren } from 'react';
-import styled from 'styled-components';
+import styled, { css } from 'styled-components';
import { hexToRGBA } from '../../libs/css';
-const BadgeStyled = styled.div<{ color: string; display: string }>`
+const BadgeStyled = styled.div<{
+ color: string;
+ display: string;
+ size: 'm' | 's';
+ background?: string;
+}>`
display: ${p => p.display};
- padding: 3px 5px;
+ flex-shrink: 0;
+
+ ${p =>
+ p.size === 'm'
+ ? css`
+ padding: 3px 5px;
+ border-radius: ${p.theme.corner3xSmall};
+ font-weight: 600;
+ font-size: 10px;
+ line-height: 14px;
+ `
+ : css`
+ padding: 2px 4px;
+ border-radius: 3px;
+ font-size: 9px;
+ font-style: normal;
+ font-weight: 510;
+ line-height: 12px;
+ `}
+
color: ${p => p.theme[p.color]};
- border-radius: ${p => p.theme.corner3xSmall};
- background-color: ${p => hexToRGBA(p.theme[p.color], 0.16)};
+ background-color: ${p =>
+ p.background ? p.theme[p.background] : hexToRGBA(p.theme[p.color], 0.16)};
text-transform: uppercase;
font-style: normal;
- font-weight: 600;
- font-size: 10px;
- line-height: 14px;
`;
export const Badge: FC<
- PropsWithChildren<{ className?: string; color?: string; display?: string }>
-> = ({ color, className, children, display = 'block' }) => {
+ PropsWithChildren<{
+ className?: string;
+ color?: string;
+ display?: string;
+ size?: 'm' | 's';
+ background?: string;
+ }>
+> = ({ color, className, children, display = 'block', size = 'm' }) => {
return (
-
+
{children}
);
diff --git a/packages/uikit/src/components/transfer/ConfirmListItem.tsx b/packages/uikit/src/components/transfer/ConfirmListItem.tsx
index 44879924d..e71118136 100644
--- a/packages/uikit/src/components/transfer/ConfirmListItem.tsx
+++ b/packages/uikit/src/components/transfer/ConfirmListItem.tsx
@@ -3,7 +3,6 @@ import { BLOCKCHAIN_NAME, CryptoCurrency } from '@tonkeeper/core/dist/entries/cr
import { RecipientData, isTonRecipientData } from '@tonkeeper/core/dist/entries/send';
import { toShortValue } from '@tonkeeper/core/dist/utils/common';
import { FC } from 'react';
-import { useWalletContext } from '../../hooks/appContext';
import { useAppSdk } from '../../hooks/appSdk';
import { useTranslation } from '../../hooks/translation';
import { ColumnText } from '../Layout';
@@ -11,6 +10,7 @@ import { ListItem, ListItemPayload } from '../List';
import { Label1 } from '../Text';
import { getRecipientAddress } from './amountView/AmountViewUI';
import { Label } from './common';
+import { useActiveTonNetwork } from '../../state/wallet';
export const cropName = (name: string) => {
return name.length > 19 ? toShortValue(name, 8) : name;
@@ -46,8 +46,8 @@ const RecipientItemAddress: FC<{ address: string }> = ({ address }) => {
export const RecipientListItem: FC<{ recipient: RecipientData }> = ({ recipient }) => {
const { address } = recipient;
- const wallet = useWalletContext();
- const addrValue = getRecipientAddress(recipient, wallet);
+ const network = useActiveTonNetwork();
+ const addrValue = getRecipientAddress(recipient, network);
if ('isFavorite' in address && address.isFavorite) {
if (address.blockchain === BLOCKCHAIN_NAME.TRON) {
diff --git a/packages/uikit/src/components/transfer/RecipientView.tsx b/packages/uikit/src/components/transfer/RecipientView.tsx
index c5d090890..e00cb301b 100644
--- a/packages/uikit/src/components/transfer/RecipientView.tsx
+++ b/packages/uikit/src/components/transfer/RecipientView.tsx
@@ -11,7 +11,7 @@ import {
} from '@tonkeeper/core/dist/utils/common';
import React, { FC, useEffect, useMemo, useRef, useState } from 'react';
import styled from 'styled-components';
-import { useAppContext, useWalletContext } from '../../hooks/appContext';
+import { useAppContext } from '../../hooks/appContext';
import { useAppSdk } from '../../hooks/appSdk';
import { openIosKeyboard } from '../../hooks/ios';
import { useTranslation } from '../../hooks/translation';
@@ -32,6 +32,7 @@ import { TextArea } from '../fields/Input';
import { InputWithScanner } from '../fields/InputWithScanner';
import { ShowAddress, useShowAddress } from './ShowAddress';
import { SuggestionList } from './SuggestionList';
+import { useActiveTonNetwork } from '../../state/wallet';
const Warning = styled(Body2)`
user-select: none;
@@ -135,7 +136,7 @@ export const RecipientView: FC<{
}) => {
const sdk = useAppSdk();
const [submitted, setSubmit] = useState(false);
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
const { t } = useTranslation();
const { standalone, ios } = useAppContext();
const ref = useRef(null);
@@ -242,7 +243,7 @@ export const RecipientView: FC<{
if (recipient.blockchain === BLOCKCHAIN_NAME.TRON) {
return recipient.address;
} else {
- return formatAddress(recipient.address, wallet.network);
+ return formatAddress(recipient.address, network);
}
}
@@ -251,7 +252,7 @@ export const RecipientView: FC<{
}
return recipient.address;
- }, [recipient]);
+ }, [recipient, network]);
const showAddress = useShowAddress(ref, formatted, toAccount);
@@ -293,7 +294,7 @@ export const RecipientView: FC<{
const onSelect = async (item: Suggestion) => {
if (item.blockchain === BLOCKCHAIN_NAME.TON) {
- item.address = formatAddress(item.address, wallet.network);
+ item.address = formatAddress(item.address, network);
}
setAddress(item);
ref.current?.focus();
diff --git a/packages/uikit/src/components/transfer/ShowAddress.tsx b/packages/uikit/src/components/transfer/ShowAddress.tsx
index b1a49422e..3d2594d06 100644
--- a/packages/uikit/src/components/transfer/ShowAddress.tsx
+++ b/packages/uikit/src/components/transfer/ShowAddress.tsx
@@ -2,9 +2,9 @@ import { Account } from '@tonkeeper/core/dist/tonApiV2';
import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
import React, { FC, PropsWithChildren, useEffect, useState } from 'react';
import styled from 'styled-components';
-import { useWalletContext } from '../../hooks/appContext';
import useTextWidth from '../../hooks/textWidth';
import { Body1 } from '../Text';
+import { useActiveTonNetwork, useActiveWallet } from '../../state/wallet';
interface ShowAddressProps {
inputTextWidth: number;
@@ -16,7 +16,7 @@ export const useShowAddress = (
value: string,
toAccount?: Account
) => {
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
const address = toAccount?.address ?? undefined;
const [showAddress, setShowAddress] = useState(undefined);
@@ -41,12 +41,12 @@ export const useShowAddress = (
setShowAddress({
inputTextWidth,
addressTextWidth,
- value: toShortValue(formatAddress(toAccount.address, wallet.network))
+ value: toShortValue(formatAddress(toAccount.address, network))
});
} else {
setShowAddress(undefined);
}
- }, [ref.current, toAccount, inputTextWidth, addressTextWidth]);
+ }, [ref.current, toAccount, inputTextWidth, addressTextWidth, network]);
return showAddress;
};
diff --git a/packages/uikit/src/components/transfer/SuggestionAddress.tsx b/packages/uikit/src/components/transfer/SuggestionAddress.tsx
index 4a6ad2631..cc085c88b 100644
--- a/packages/uikit/src/components/transfer/SuggestionAddress.tsx
+++ b/packages/uikit/src/components/transfer/SuggestionAddress.tsx
@@ -2,21 +2,21 @@ import { BLOCKCHAIN_NAME } from '@tonkeeper/core/dist/entries/crypto';
import { Suggestion } from '@tonkeeper/core/dist/entries/suggestion';
import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
import React, { FC, useMemo } from 'react';
-import { useWalletContext } from '../../hooks/appContext';
import { useAppSdk } from '../../hooks/appSdk';
import { useTranslation } from '../../hooks/translation';
import { ListBlock, ListItem, ListItemPayload } from '../List';
import { Label1 } from '../Text';
import { Label } from './common';
+import { useActiveTonNetwork } from '../../state/wallet';
export const useSuggestionAddress = (item: Suggestion) => {
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
return useMemo(() => {
return item.blockchain === BLOCKCHAIN_NAME.TRON
? item.address
- : formatAddress(item.address, wallet.network);
- }, [item]);
+ : formatAddress(item.address, network);
+ }, [item, network]);
};
export const SuggestionAddress: FC<{ item: Suggestion }> = ({ item }) => {
diff --git a/packages/uikit/src/components/transfer/SuggestionList.tsx b/packages/uikit/src/components/transfer/SuggestionList.tsx
index c7bdb9d23..d435f249f 100644
--- a/packages/uikit/src/components/transfer/SuggestionList.tsx
+++ b/packages/uikit/src/components/transfer/SuggestionList.tsx
@@ -14,7 +14,7 @@ import {
import { toShortValue } from '@tonkeeper/core/dist/utils/common';
import { FC } from 'react';
import styled from 'styled-components';
-import { useAppContext, useWalletContext } from '../../hooks/appContext';
+import { useAppContext } from '../../hooks/appContext';
import { useAppSdk } from '../../hooks/appSdk';
import { useTranslation } from '../../hooks/translation';
import { QueryKey } from '../../libs/queryKey';
@@ -22,9 +22,10 @@ import { DropDown } from '../DropDown';
import { EllipsisIcon, StarIcon } from '../Icon';
import { ColumnText } from '../Layout';
import { ListBlock, ListItem, ListItemPayload } from '../List';
-import { SkeletonList } from '../Skeleton';
+import { SkeletonListWithImages } from '../Skeleton';
import { Label1 } from '../Text';
import { useSuggestionAddress } from './SuggestionAddress';
+import { useActiveStandardTonWallet } from '../../state/wallet';
const Label = styled(Label1)`
user-select: none;
@@ -36,10 +37,10 @@ const Label = styled(Label1)`
const useLatestSuggestion = (acceptBlockchains?: BLOCKCHAIN_NAME[]) => {
const sdk = useAppSdk();
const { api } = useAppContext();
- const wallet = useWalletContext();
+ const wallet = useActiveStandardTonWallet();
return useQuery(
- [wallet.active.rawAddress, QueryKey.activity, 'suggestions', acceptBlockchains],
+ [wallet.rawAddress, QueryKey.activity, 'suggestions', acceptBlockchains],
() => getSuggestionsList(sdk, api, wallet, acceptBlockchains),
{ keepPreviousData: true }
);
@@ -67,16 +68,12 @@ const getLatestDate = (language: string, timestamp: number) => {
const useDeleteFavorite = (item: FavoriteSuggestion) => {
const sdk = useAppSdk();
- const wallet = useWalletContext();
+ const wallet = useActiveStandardTonWallet();
const queryClient = useQueryClient();
return useMutation(async () => {
await deleteFavoriteSuggestion(sdk.storage, wallet.publicKey, item.address);
- await queryClient.invalidateQueries([
- wallet.active.rawAddress,
- QueryKey.activity,
- 'suggestions'
- ]);
+ await queryClient.invalidateQueries([wallet.rawAddress, QueryKey.activity, 'suggestions']);
});
};
@@ -159,16 +156,12 @@ const FavoriteItem: FC<{
const useHideSuggestion = (item: LatestSuggestion) => {
const sdk = useAppSdk();
- const wallet = useWalletContext();
+ const wallet = useActiveStandardTonWallet();
const queryClient = useQueryClient();
return useMutation(async () => {
await hideSuggestions(sdk.storage, wallet.publicKey, item.address);
- await queryClient.invalidateQueries([
- wallet.active.rawAddress,
- QueryKey.activity,
- 'suggestions'
- ]);
+ await queryClient.invalidateQueries([wallet.rawAddress, QueryKey.activity, 'suggestions']);
});
};
@@ -244,7 +237,7 @@ export const SuggestionList: FC<{
return (
<>
-
+
>
);
}
diff --git a/packages/uikit/src/components/transfer/amountView/AmountViewUI.tsx b/packages/uikit/src/components/transfer/amountView/AmountViewUI.tsx
index 978091ad8..26b68497e 100644
--- a/packages/uikit/src/components/transfer/amountView/AmountViewUI.tsx
+++ b/packages/uikit/src/components/transfer/amountView/AmountViewUI.tsx
@@ -1,16 +1,17 @@
import { RecipientData, isTonRecipientData } from '@tonkeeper/core/dist/entries/send';
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
+import { Network } from '@tonkeeper/core/dist/entries/network';
import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common';
import { getDecimalSeparator, getNotDecimalSeparator } from '@tonkeeper/core/dist/utils/formatting';
import { isNumeric, removeGroupSeparator, seeIfLargeTail } from '@tonkeeper/core/dist/utils/send';
import BigNumber from 'bignumber.js';
import { FC } from 'react';
import styled from 'styled-components';
-import { useAppContext, useWalletContext } from '../../../hooks/appContext';
+import { useAppContext } from '../../../hooks/appContext';
import { formatter } from '../../../hooks/balance';
import { Body1, Body2, H3, Label2, Num2 } from '../../Text';
import { cropName } from '../ConfirmListItem';
import { AmountState } from './amountState';
+import { useActiveTonNetwork } from '../../../state/wallet';
export const Center = styled.div`
text-align: center;
@@ -159,18 +160,18 @@ export const RecipientName: FC<{ recipient: RecipientData }> = ({ recipient }) =
return <>>;
};
-export const getRecipientAddress = (recipient: RecipientData, wallet: WalletState) => {
+export const getRecipientAddress = (recipient: RecipientData, network: Network) => {
if (isTonRecipientData(recipient)) {
if ('dns' in recipient.address) {
- return formatAddress(recipient.toAccount.address, wallet.network);
+ return formatAddress(recipient.toAccount.address, network);
}
}
return recipient.address.address;
};
export const RecipientAddress: FC<{ recipient: RecipientData }> = ({ recipient }) => {
- const wallet = useWalletContext();
- const address = getRecipientAddress(recipient, wallet);
+ const network = useActiveTonNetwork();
+ const address = getRecipientAddress(recipient, network);
return {toShortValue(address)};
};
diff --git a/packages/uikit/src/components/transfer/common.tsx b/packages/uikit/src/components/transfer/common.tsx
index eac284e2d..c1068cdbe 100644
--- a/packages/uikit/src/components/transfer/common.tsx
+++ b/packages/uikit/src/components/transfer/common.tsx
@@ -8,7 +8,7 @@ import { seeIfBalanceError, seeIfTimeError } from '@tonkeeper/core/dist/service/
import { Account, JettonsBalances } from '@tonkeeper/core/dist/tonApiV2';
import React, { FC, PropsWithChildren } from 'react';
import styled, { css, useTheme } from 'styled-components';
-import { useAppContext, useWalletContext } from '../../hooks/appContext';
+import { useAppContext } from '../../hooks/appContext';
import { useTranslation } from '../../hooks/translation';
import { ChevronLeftIcon } from '../Icon';
import { NotificationCancelButton, NotificationTitleBlock } from '../Notification';
@@ -17,6 +17,7 @@ import { RoundedButton, ButtonMock } from '../fields/RoundedButton';
import { Button } from '../fields/Button';
import { Center, Title } from './amountView/AmountViewUI';
import { AmountState } from './amountView/amountState';
+import { useIsActiveWalletLedger } from '../../state/ledger';
export const duration = 300;
export const timingFunction = 'ease-in-out';
@@ -221,8 +222,7 @@ export type ConfirmMainButtonProps = (props: {
export const ConfirmMainButton: ConfirmMainButtonProps = ({ isLoading, isDisabled, onClick }) => {
const { t } = useTranslation();
- const { auth } = useWalletContext();
- const isLedger = auth?.kind === 'ledger';
+ const isLedger = useIsActiveWalletLedger();
return (
)}
- sdk.copyToClipboard(formatAddress(nftItem.address, wallet.network, true))
- }
+ onClick={() => sdk.copyToClipboard(formatAddress(nftItem.address, network, true))}
>
-
- {toShortValue(formatAddress(nftItem.address, wallet.network, true))}
-
+ {toShortValue(formatAddress(nftItem.address, network, true))}
diff --git a/packages/uikit/src/components/transfer/nft/ConfirmNftView.tsx b/packages/uikit/src/components/transfer/nft/ConfirmNftView.tsx
index e16ff3291..0df28c91a 100644
--- a/packages/uikit/src/components/transfer/nft/ConfirmNftView.tsx
+++ b/packages/uikit/src/components/transfer/nft/ConfirmNftView.tsx
@@ -5,7 +5,7 @@ import {
} from '@tonkeeper/core/dist/service/transfer/nftService';
import { NftItem } from '@tonkeeper/core/dist/tonApiV2';
import React, { FC, useState } from 'react';
-import { useAppContext, useWalletContext } from '../../../hooks/appContext';
+import { useAppContext } from '../../../hooks/appContext';
import { useAppSdk } from '../../../hooks/appSdk';
import { useTranslation } from '../../../hooks/translation';
import { Gap } from '../../Layout';
@@ -33,6 +33,11 @@ import {
ConfirmViewDetailsRecipient
} from '../ConfirmView';
import { NftDetailsBlock } from './Common';
+import {
+ useActiveAccount,
+ useActiveStandardTonWallet,
+ useInvalidateActiveWalletQueries
+} from '../../../state/wallet';
const assetAmount = new AssetAmount({
asset: TON_ASSET,
@@ -43,7 +48,7 @@ const useNftTransferEstimation = (nftItem: NftItem, data?: TonRecipientData) =>
const { t } = useTranslation();
const sdk = useAppSdk();
const { api } = useAppContext();
- const wallet = useWalletContext();
+ const wallet = useActiveStandardTonWallet();
const client = useQueryClient();
return useQuery, Error>(
@@ -73,26 +78,26 @@ const useSendNft = (
const { t } = useTranslation();
const sdk = useAppSdk();
const { api } = useAppContext();
- const wallet = useWalletContext();
+ const account = useActiveAccount();
const client = useQueryClient();
const track2 = useTransactionAnalytics();
const { mutateAsync: checkTouchId } = useCheckTouchId();
+ const { mutateAsync: invalidateAccountQueries } = useInvalidateActiveWalletQueries();
return useMutation(async () => {
if (!fee) return false;
- const signer = await getSigner(sdk, wallet.publicKey, checkTouchId).catch(() => null);
+ const signer = await getSigner(sdk, account.id, checkTouchId).catch(() => null);
if (signer === null) return false;
track2('send-nft');
try {
- await sendNftTransfer(api, wallet, recipient, nftItem, fee, signer);
+ await sendNftTransfer(api, account, recipient, nftItem, fee, signer);
} catch (e) {
await notifyError(client, sdk, t, e);
}
- await client.invalidateQueries([wallet.active.rawAddress]);
- await client.invalidateQueries();
+ await invalidateAccountQueries();
return true;
});
};
diff --git a/packages/uikit/src/components/transfer/nft/hooks.ts b/packages/uikit/src/components/transfer/nft/hooks.ts
index 74cd23b73..cc582fc0f 100644
--- a/packages/uikit/src/components/transfer/nft/hooks.ts
+++ b/packages/uikit/src/components/transfer/nft/hooks.ts
@@ -1,21 +1,22 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { checkWalletPositiveBalanceOrDie } from '@tonkeeper/core/dist/service/transfer/common';
import { AccountsApi } from '@tonkeeper/core/dist/tonApiV2';
-import { useAppContext, useWalletContext } from '../../../hooks/appContext';
+import { useAppContext } from '../../../hooks/appContext';
import { useAppSdk } from '../../../hooks/appSdk';
import { useTranslation } from '../../../hooks/translation';
import { notifyError } from '../common';
+import { useActiveWallet } from '../../../state/wallet';
export const useMinimalBalance = () => {
const sdk = useAppSdk();
const { api } = useAppContext();
- const walletState = useWalletContext();
+ const walletState = useActiveWallet();
const { t } = useTranslation();
const client = useQueryClient();
return useMutation(async () => {
const wallet = await new AccountsApi(api.tonApiV2).getAccount({
- accountId: walletState.active.rawAddress
+ accountId: walletState.rawAddress
});
try {
checkWalletPositiveBalanceOrDie(wallet);
diff --git a/packages/uikit/src/desktop-pages/history/DesktopHistoryPage.tsx b/packages/uikit/src/desktop-pages/history/DesktopHistoryPage.tsx
index 103e92c4a..64f1ccbfa 100644
--- a/packages/uikit/src/desktop-pages/history/DesktopHistoryPage.tsx
+++ b/packages/uikit/src/desktop-pages/history/DesktopHistoryPage.tsx
@@ -3,21 +3,23 @@ import { AccountsApi } from '@tonkeeper/core/dist/tonApiV2';
import { FC, Suspense, useMemo, useRef } from 'react';
import styled from 'styled-components';
import { ActivitySkeletonPage } from '../../components/Skeleton';
-import { Body2, Label2 } from '../../components/Text';
+import { useAppContext } from '../../hooks/appContext';
+import { useFetchNext } from '../../hooks/useFetchNext';
+import { QueryKey } from '../../libs/queryKey';
+import { getMixedActivity } from '../../state/mixedActivity';
import EmptyActivity from '../../components/activity/EmptyActivity';
import {
DesktopViewHeader,
DesktopViewPageLayout
} from '../../components/desktop/DesktopViewLayout';
import { DesktopHistory } from '../../components/desktop/history/DesktopHistory';
-import { useAppContext, useWalletContext } from '../../hooks/appContext';
import { useAppSdk } from '../../hooks/appSdk';
import { useTranslation } from '../../hooks/translation';
-import { useFetchNext } from '../../hooks/useFetchNext';
import { useIsScrolled } from '../../hooks/useIsScrolled';
import { mergeRefs } from '../../libs/common';
-import { QueryKey } from '../../libs/queryKey';
-import { getMixedActivity } from '../../state/mixedActivity';
+import { useActiveWallet } from '../../state/wallet';
+import { Body2, Label2 } from '../../components/Text';
+import { formatAddress } from '@tonkeeper/core/dist/utils/common';
const HistoryPageWrapper = styled(DesktopViewPageLayout)`
overflow: auto;
@@ -47,7 +49,7 @@ const ExplorerButton = styled.button`
`;
export const DesktopHistoryPage: FC = () => {
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const sdk = useAppSdk();
const { api, standalone, config } = useAppContext();
const { t } = useTranslation();
@@ -61,10 +63,10 @@ export const DesktopHistoryPage: FC = () => {
isFetchingNextPage: isTonFetchingNextPage,
data: tonEvents
} = useInfiniteQuery({
- queryKey: [wallet.active.rawAddress, QueryKey.activity, 'all'],
+ queryKey: [wallet.rawAddress, QueryKey.activity, 'all'],
queryFn: ({ pageParam = undefined }) =>
new AccountsApi(api.tonApiV2).getAccountEvents({
- accountId: wallet.active.rawAddress,
+ accountId: wallet.rawAddress,
limit: 20,
beforeLt: pageParam,
subjectOnly: true
@@ -104,7 +106,7 @@ export const DesktopHistoryPage: FC = () => {
? sdk.openPage(
config.accountExplorer.replace(
'%s',
- wallet.active.friendlyAddress
+ formatAddress(wallet.rawAddress)
)
)
: undefined
diff --git a/packages/uikit/src/desktop-pages/nft/DesktopCollectables.tsx b/packages/uikit/src/desktop-pages/nft/DesktopCollectables.tsx
index 8cd56072c..4477bec0d 100644
--- a/packages/uikit/src/desktop-pages/nft/DesktopCollectables.tsx
+++ b/packages/uikit/src/desktop-pages/nft/DesktopCollectables.tsx
@@ -1,5 +1,4 @@
import { NftsList } from '../../components/nft/Nfts';
-import { useWalletFilteredNftList } from '../../state/wallet';
import styled from 'styled-components';
import { Body2, Label2 } from '../../components/Text';
import { Button } from '../../components/fields/Button';
@@ -15,6 +14,7 @@ import { KnownNFTDnsCollections } from '../../components/nft/NftView';
import { useMemo } from 'react';
import { SlidersIcon } from '../../components/Icon';
import { IconButtonTransparentBackground } from '../../components/fields/IconButton';
+import { useWalletFilteredNftList } from "../../state/nft";
const gap = '10px';
const maxColumnsNumber = 4;
diff --git a/packages/uikit/src/desktop-pages/nft/DesktopDns.tsx b/packages/uikit/src/desktop-pages/nft/DesktopDns.tsx
index 7520cd6eb..26e99b7b1 100644
--- a/packages/uikit/src/desktop-pages/nft/DesktopDns.tsx
+++ b/packages/uikit/src/desktop-pages/nft/DesktopDns.tsx
@@ -1,5 +1,4 @@
import { NftsList } from '../../components/nft/Nfts';
-import { useWalletFilteredNftList } from '../../state/wallet';
import styled from 'styled-components';
import { Body2, Label2 } from '../../components/Text';
import { Button } from '../../components/fields/Button';
@@ -15,6 +14,7 @@ import { useMemo } from 'react';
import { KnownNFTDnsCollections } from '../../components/nft/NftView';
import { SlidersIcon } from '../../components/Icon';
import { IconButtonTransparentBackground } from '../../components/fields/IconButton';
+import { useWalletFilteredNftList } from "../../state/nft";
const gap = '10px';
const maxColumnsNumber = 4;
diff --git a/packages/uikit/src/desktop-pages/notcoin/NotcoinPage.tsx b/packages/uikit/src/desktop-pages/notcoin/NotcoinPage.tsx
deleted file mode 100644
index 366b3e378..000000000
--- a/packages/uikit/src/desktop-pages/notcoin/NotcoinPage.tsx
+++ /dev/null
@@ -1,413 +0,0 @@
-import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
-import { Address, SendMode, beginCell, internal, toNano } from '@ton/core';
-import { APIConfig } from '@tonkeeper/core/dist/entries/apis';
-import { CellSigner, Signer } from '@tonkeeper/core/dist/entries/signer';
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
-import {
- checkWalletBalanceOrDie,
- externalMessage,
- getServerTime,
- getTTL,
- getTonkeeperQueryId,
- getWalletBalance,
- getWalletSeqNo,
- signEstimateMessage
-} from '@tonkeeper/core/dist/service/transfer/common';
-import { walletContractFromState } from '@tonkeeper/core/dist/service/wallet/contractService';
-import { AccountsApi, BlockchainApi, EmulationApi, NftItem } from '@tonkeeper/core/dist/tonApiV2';
-import { TonendpointConfig } from '@tonkeeper/core/dist/tonkeeperApi/tonendpoint';
-import { unShiftedDecimals } from '@tonkeeper/core/dist/utils/balance';
-import { delay } from '@tonkeeper/core/dist/utils/common';
-import { FC, useEffect, useRef, useState } from 'react';
-import { styled } from 'styled-components';
-import { NotCoinIcon, SpinnerIcon } from '../../components/Icon';
-import { SkeletonList } from '../../components/Skeleton';
-import { Body1, Body2, Label2 } from '../../components/Text';
-import { ImportNotification } from '../../components/create/ImportNotification';
-import {
- DesktopViewHeader,
- DesktopViewPageLayout
-} from '../../components/desktop/DesktopViewLayout';
-import { Button } from '../../components/fields/Button';
-import { useAppContext, useWalletContext } from '../../hooks/appContext';
-import { useAppSdk, useToast } from '../../hooks/appSdk';
-import { useDateTimeFormat } from '../../hooks/useDateTimeFormat';
-import { getSigner } from '../../state/mnemonic';
-import { useCheckTouchId } from '../../state/password';
-import { chooseAddress } from './address';
-
-const useVouchers = () => {
- const wallet = useWalletContext();
- const { api } = useAppContext();
-
- const limit = 1000;
-
- const getItems = async (offset: number) => {
- const items = await new AccountsApi(api.tonApiV2).getAccountNftItems({
- accountId: wallet.active.rawAddress,
- collection: 'EQDmkj65Ab_m0aZaW8IpKw4kYqIgITw_HRstYEkVQ6NIYCyW',
- limit: limit,
- offset: offset
- });
-
- return items.nftItems;
- };
-
- return useQuery(['notcoin', 'length', wallet.active.rawAddress], async () => {
- const result: NftItem[] = [];
- let page: NftItem[] = [];
- let offset = 0;
- do {
- console.log('loading', offset);
- page = await getItems(offset);
- offset += page.length;
- result.push(...page);
- } while (page.length === limit);
- return result;
- });
-};
-
-export const confirmWalletSeqNo = async (
- activeWallet: string,
- api: APIConfig,
- currentSeqNo: number
-) => {
- let walletSeqNo: number = currentSeqNo - 1;
- do {
- await delay(4000);
-
- try {
- walletSeqNo = await getWalletSeqNo(api, activeWallet);
- console.log('wait seqno', currentSeqNo, walletSeqNo);
- } catch (e) {
- console.error(e);
- }
- } while (walletSeqNo <= currentSeqNo);
-};
-
-const getNotcoinBurnAddress = (nftAddress: string, config: TonendpointConfig) => {
- const burnAddresses = config.notcoin_burn_addresses ?? [];
-
- const { match } = chooseAddress(
- Address.parse(nftAddress),
- burnAddresses.map(item => Address.parse(item))
- );
-
- if (match) {
- return match;
- }
-
- var item = burnAddresses[Math.floor(Math.random() * burnAddresses.length)];
- return Address.parse(item);
-};
-
-const checkBurnDate = async (api: APIConfig, config: TonendpointConfig) => {
- const burnTimestamp = config.notcoin_burn_date
- ? config.notcoin_burn_date
- : Date.now() / 1000 + 300;
- const nowTimestamp = await getServerTime(api);
-
- if (burnTimestamp > nowTimestamp) {
- return [false, burnTimestamp] as const;
- }
-
- return [true, burnTimestamp] as const;
-};
-
-const createNftMultiTransfer = async (
- timestamp: number,
- seqno: number,
- walletState: WalletState,
- chunk: NftItem[],
- config: TonendpointConfig,
- signer: CellSigner
-) => {
- const contract = walletContractFromState(walletState);
-
- const transfer = await contract.createTransferAndSignRequestAsync({
- seqno,
- signer,
- timeout: getTTL(timestamp),
- sendMode: SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS,
- messages: chunk.map(nft => {
- return internal({
- to: Address.parse(nft.address),
- bounce: true,
- value: toNano('0.12'),
- body: beginCell()
- .storeUint(0x5fcc3d14, 32) // transfer op
- .storeUint(getTonkeeperQueryId(), 64)
- .storeAddress(getNotcoinBurnAddress(nft.address, config))
- .storeAddress(Address.parse(walletState.active.rawAddress))
- .storeBit(false)
- .storeCoins(toNano('0.05'))
- .storeBit(false)
- .storeUint(0x5fec6642, 32)
- .storeUint(nft.index, 64)
- .endCell()
- });
- })
- });
-
- return externalMessage(contract, seqno, transfer).toBoc();
-};
-
-const sendNftMultiTransfer = async (
- api: APIConfig,
- walletState: WalletState,
- chunk: NftItem[],
- config: TonendpointConfig,
- signer: CellSigner
-) => {
- const timestamp = await getServerTime(api);
-
- const [wallet, seqno] = await getWalletBalance(api, walletState);
-
- checkWalletBalanceOrDie(unShiftedDecimals(0.07).multipliedBy(chunk.length), wallet);
-
- const estimationCell = await createNftMultiTransfer(
- timestamp,
- seqno,
- walletState,
- chunk,
- config,
- signEstimateMessage
- );
-
- const res = await new EmulationApi(api.tonApiV2).emulateMessageToAccountEvent({
- ignoreSignatureCheck: true,
- accountId: wallet.address,
- decodeMessageRequest: { boc: estimationCell.toString('base64') }
- });
-
- if (res.actions.some(action => action.status !== 'ok')) {
- throw new Error('NFT transfer estimation failed');
- }
-
- const cell = await createNftMultiTransfer(timestamp, seqno, walletState, chunk, config, signer);
-
- await new BlockchainApi(api.tonApiV2).sendBlockchainMessage({
- sendBlockchainMessageRequest: { boc: cell.toString('base64') }
- });
- return true;
-};
-
-const useBurnMutation = () => {
- const wallet = useWalletContext();
- const { api } = useAppContext();
-
- return useMutation<
- boolean,
- Error,
- { signer: Signer | null; chunk: NftItem[]; config: TonendpointConfig }
- >(async ({ signer, chunk, config }) => {
- if (signer === null) {
- throw new Error('Unable to sign transaction.');
- }
-
- const seqno = await getWalletSeqNo(api, wallet.active.rawAddress);
-
- console.log('send', chunk);
-
- await sendNftMultiTransfer(api, wallet, chunk, config, signer as CellSigner);
-
- await confirmWalletSeqNo(wallet.active.rawAddress, api, seqno);
-
- return true;
- });
-};
-
-const NotFound = styled.div`
- display: flex;
- align-items: center;
- flex-direction: column;
- gap: 1rem;
-`;
-
-const NotFoundBlock = () => {
- const [isOpenImport, setIsOpenImport] = useState(false);
- return (
-
-
-
- Wallet don't have NOT Vouchers
-
- Please cancel any existing auctions for your vouchers
-
- setIsOpenImport(true)}>
- Import another wallet
-
-
-
- );
-};
-
-const Wrapper = styled.div`
- padding: 1rem;
-`;
-
-const Center = styled(Body1)`
- display: flex;
- align-items: center;
- gap: 1rem;
-`;
-
-const TgLink = styled.a`
- color: ${p => p.theme.accentBlue};
- cursor: pointer;
- text-decoration: underline;
-`;
-
-const BodyCenter = styled(Body2)`
- text-align: center;
- max-width: 435px;
-`;
-
-const BurnBlock: FC<{ data: NftItem[] | undefined }> = ({ data }) => {
- const { api, tonendpoint } = useAppContext();
-
- const [burning, setBurning] = useState(false);
- const [ok, setIsOk] = useState(false);
- const [left, setLeft] = useState(0);
-
- const mutation = useBurnMutation();
- const toast = useToast();
-
- const { mutateAsync: checkTouchId } = useCheckTouchId();
- const wallet = useWalletContext();
-
- const process = useRef(true);
- const sdk = useAppSdk();
- const client = useQueryClient();
- const formatDate = useDateTimeFormat();
-
- useEffect(() => {
- return () => {
- process.current = false;
- client.invalidateQueries(['notcoin']);
- };
- }, []);
-
- const onBurn = async () => {
- const config = await tonendpoint.boot();
- const [allow, time] = await checkBurnDate(api, config);
- if (!allow) {
- toast(
- `Burning NOT Vouchers will be available soon! ${formatDate(new Date(time * 1000), {
- day: 'numeric',
- month: 'short',
- year: 'numeric',
- inputUnit: 'seconds'
- })}`
- );
- return;
- }
-
- setLeft(data?.length ?? 0);
- setBurning(true);
-
- if (!data) return;
- const signer: Signer | null = await getSigner(sdk, wallet.publicKey, checkTouchId).catch(
- () => null
- );
-
- const chunkSize = 4;
- for (let i = 0; i < data.length; i += chunkSize) {
- const chunk = data.slice(i, i + chunkSize);
- try {
- if (process.current) {
- await mutation.mutateAsync({ signer, chunk, config });
- setLeft(l => l - chunkSize);
- }
- } catch (e) {
- toast(e instanceof Error ? e.message : 'Unexpected error.');
- }
- }
- setIsOk(true);
- };
-
- if (!data) {
- return ;
- }
-
- if (ok) {
- return (
-
-
- Finish
-
- You burned all NOT Vouchers. Notcoin will deposit to your wallet address{' '}
- {
- e.stopPropagation();
- sdk.openPage(`https://tonviewer.com/${wallet.active.friendlyAddress}`);
- }}
- >
- Check Tonviewer!
-
-
-
- );
- }
- if (burning) {
- return (
-
-
-
-
- Burning, {left} vouchers left
-
- Please wait and don't close this page
-
- Wallet should have enough TON for transfer NFTs - 0.07 TON per voucher.
-
-
- );
- }
-
- if (data.length === 0) {
- return ;
- }
-
- return (
-
-
- The wallet contains {data.length} vouchers
-
- The process will take approximately{' '}
- {Math.round((Math.ceil(data.length / 4) * 30) / 60)} min
-
-
- Please ensure that the wallet has sufficient TON to transfer NFTs - 0.07 TON per
- voucher. The wallet will initiate multiple transactions to transfer the NFT vouchers
- to a Burning Smart Contracts. More Details{' '}
- {
- e.stopPropagation();
- sdk.openPage('https://t.me/notcoin_bot');
- }}
- >
- Notcoin Bot
-
- .
-
-
- Burn NOT Vouchers
-
-
- );
-};
-
-export const NotcoinPage = () => {
- const { data } = useVouchers();
- return (
-
-
- Burn NOT Vouchers
-
-
-
-
-
- );
-};
diff --git a/packages/uikit/src/desktop-pages/notcoin/address.ts b/packages/uikit/src/desktop-pages/notcoin/address.ts
deleted file mode 100644
index 2ee819a5e..000000000
--- a/packages/uikit/src/desktop-pages/notcoin/address.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { Address } from '@ton/core';
-
-function findMatchingBits(a: number, b: number, start_from: number) {
- let bitPos = start_from;
- let keepGoing = true;
- do {
- const bitCount = bitPos + 1;
- const mask = (1 << bitCount) - 1;
- const shift = 8 - bitCount;
- if (((a >> shift) & mask) == ((b >> shift) & mask)) {
- bitPos++;
- } else {
- keepGoing = false;
- }
- } while (keepGoing && bitPos < 7);
-
- return bitPos;
-}
-
-export function chooseAddress(user: Address, contracts: Address[]) {
- const maxBytes = 32;
- let byteIdx = 0;
- let bitIdx = 0;
- let bestMatch: Address | undefined;
-
- if (user.workChain !== 0) {
- throw new TypeError(`Only basechain user address allowed:${user}`);
- }
- for (let testContract of contracts) {
- if (testContract.workChain !== 0) {
- throw new TypeError(`Only basechain deposit address allowed:${testContract}`);
- }
- if (byteIdx >= maxBytes) {
- break;
- }
- if (
- byteIdx == 0 ||
- testContract.hash.subarray(0, byteIdx).equals(user.hash.subarray(0, byteIdx))
- ) {
- let keepGoing = true;
- do {
- if (keepGoing && testContract.hash[byteIdx] == user.hash[byteIdx]) {
- bestMatch = testContract;
- byteIdx++;
- bitIdx = 0;
- if (byteIdx == maxBytes) {
- break;
- }
- } else {
- keepGoing = false;
- if (bitIdx < 7) {
- const resIdx = findMatchingBits(
- user.hash[byteIdx],
- testContract.hash[byteIdx],
- bitIdx
- );
- if (resIdx > bitIdx) {
- bitIdx = resIdx;
- bestMatch = testContract;
- }
- }
- }
- } while (keepGoing);
- }
- }
- return {
- match: bestMatch,
- prefixLength: byteIdx * 8 + bitIdx
- };
-}
diff --git a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx
index 1d4b31b78..a19f05f31 100644
--- a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx
+++ b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsPage.tsx
@@ -15,13 +15,13 @@ import {
DesktopViewHeader,
DesktopViewPageLayout
} from '../../components/desktop/DesktopViewLayout';
-import { LogOutWalletNotification } from '../../components/settings/LogOutNotification';
+import { DeleteAccountNotification } from '../../components/settings/DeleteAccountNotification';
import { RenameWalletNotification } from '../../components/settings/wallet-name/WalletNameNotification';
import { WalletEmoji } from '../../components/shared/emoji/WalletEmoji';
-import { useWalletContext } from '../../hooks/appContext';
import { useTranslation } from '../../hooks/translation';
import { useDisclosure } from '../../hooks/useDisclosure';
import { AppRoute, WalletSettingsRoute } from '../../libs/routes';
+import { useActiveAccount } from '../../state/wallet';
const SettingsListBlock = styled.div`
padding: 0.5rem 0;
@@ -55,9 +55,16 @@ const LinkStyled = styled(Link)`
export const DesktopWalletSettingsPage = () => {
const { t } = useTranslation();
- const wallet = useWalletContext();
+ const account = useActiveAccount();
const { isOpen: isRenameOpen, onClose: onRenameClose, onOpen: onRenameOpen } = useDisclosure();
- const { isOpen: isLogoutOpen, onClose: onLogoutClose, onOpen: onLogoutOpen } = useDisclosure();
+ const { isOpen: isDeleteOpen, onClose: onDeleteClose, onOpen: onDeleteOpen } = useDisclosure();
+
+ const canChangeVersion = account.type === 'mnemonic' || account.type === 'ton-only';
+
+ // check available derivations length to filter and keep only non-legacy added ledger accounts
+ const canChangeLedgerIndex =
+ account.type === 'ledger' && account.allAvailableDerivations.length > 1;
+ const activeWallet = account.activeTonWallet;
return (
@@ -66,30 +73,45 @@ export const DesktopWalletSettingsPage = () => {
-
+
- {wallet.name || t('wallet_title')}
+ {account.name || t('wallet_title')}
{t('customize')}
-
-
-
- {t('settings_backup_seed')}
-
-
-
-
-
-
- {t('settings_wallet_version')}
- {walletVersionText(wallet.active.version)}
-
-
-
+ {account.type === 'mnemonic' && (
+
+
+
+ {t('settings_backup_seed')}
+
+
+ )}
+ {canChangeVersion && (
+
+
+
+
+ {t('settings_wallet_version')}
+ {walletVersionText(activeWallet.version)}
+
+
+
+ )}
+ {canChangeLedgerIndex && (
+
+
+
+
+ {t('settings_ledger_indexes')}
+ # {account.activeDerivationIndex + 1}
+
+
+
+ )}
@@ -111,20 +133,20 @@ export const DesktopWalletSettingsPage = () => {
-
+
- {t('preferences_aside_sign_out')}
+ {t('Delete_wallet_data')}
-
);
diff --git a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx
index f55d8be1f..0f73498e2 100644
--- a/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx
+++ b/packages/uikit/src/desktop-pages/settings/DesktopWalletSettingsRouting.tsx
@@ -1,12 +1,13 @@
import { Outlet, Route, Routes } from 'react-router-dom';
import { WalletSettingsRoute } from '../../libs/routes';
import { ActiveRecovery, Recovery } from '../../pages/settings/Recovery';
-import { WalletVersion } from '../../pages/settings/Version';
+import { WalletVersionPage } from '../../pages/settings/Version';
import { JettonsSettings } from '../../pages/settings/Jettons';
import styled from 'styled-components';
import { DesktopWalletSettingsPage } from './DesktopWalletSettingsPage';
import { DesktopConnectedAppsSettings } from './DesktopConnectedAppsSettings';
import { DesktopNftSettings } from './DesktopNftSettings';
+import { LedgerIndexesPage } from '../../pages/settings/LedgerIndexes';
const OldSettingsLayoutWrapper = styled.div`
padding-top: 64px;
@@ -26,10 +27,11 @@ export const DesktopWalletSettingsRouting = () => {
}>
- } />
+ } />
} />
- } />
+ } />
+ } />
} />
= {};
@@ -9,21 +10,21 @@ export class AptabaseWeb implements Analytics {
init(this.key, { appVersion, host: this.host });
}
- init = (
- application: string,
- walletType: string,
- account?: any,
- wallet?: any,
- version?: string | undefined,
- platform?: string | undefined
- ) => {
- this.user_properties['application'] = application;
- this.user_properties['walletType'] = walletType;
- this.user_properties['network'] =
- wallet?.network === Network.TESTNET ? 'testnet' : 'mainnet';
- this.user_properties['accounts'] = account!.publicKeys.length;
- this.user_properties['version'] = version;
- this.user_properties['platform'] = platform;
+ init = (params: {
+ application: string;
+ walletType: string;
+ activeAccount: Account;
+ accounts: Account[];
+ network?: Network;
+ version?: string;
+ platform?: string;
+ }) => {
+ this.user_properties.application = params.application;
+ this.user_properties.walletType = params.walletType;
+ this.user_properties.network = params.network === Network.TESTNET ? 'testnet' : 'mainnet';
+ this.user_properties.accounts = params.accounts?.length ?? 0;
+ this.user_properties.version = params.version;
+ this.user_properties.platform = params.platform;
};
pageView = (location: string) => {
diff --git a/packages/uikit/src/hooks/analytics/google.ts b/packages/uikit/src/hooks/analytics/google.ts
index fb4e2ea72..eca575587 100644
--- a/packages/uikit/src/hooks/analytics/google.ts
+++ b/packages/uikit/src/hooks/analytics/google.ts
@@ -1,13 +1,13 @@
import { AppKey } from '@tonkeeper/core/dist/Keys';
import { IStorage } from '@tonkeeper/core/dist/Storage';
-import { AccountState } from '@tonkeeper/core/dist/entries/account';
import { Network } from '@tonkeeper/core/dist/entries/network';
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
+import { Account } from '@tonkeeper/core/dist/entries/account';
import { v4 as uuidv4 } from 'uuid';
import { Analytics } from '.';
export class GoogleAnalytics4 implements Analytics {
private user_properties: Record = {};
+
private clientId: string | undefined;
constructor(
@@ -30,22 +30,23 @@ export class GoogleAnalytics4 implements Analytics {
}
}
- init(
- application: string,
- walletType: string,
- account?: AccountState,
- wallet?: WalletState | null,
- version?: string,
- platform?: string
- ) {
- this.user_properties['application'] = { value: application };
- this.user_properties['walletType'] = { value: walletType };
- this.user_properties['network'] = {
- value: wallet?.network === Network.TESTNET ? 'testnet' : 'mainnet'
+ init(params: {
+ application: string;
+ walletType: string;
+ activeAccount: Account;
+ accounts: Account[];
+ network?: Network;
+ version?: string;
+ platform?: string;
+ }) {
+ this.user_properties.application = { value: params.application };
+ this.user_properties.walletType = { value: params.walletType };
+ this.user_properties.network = {
+ value: params.network === Network.TESTNET ? 'testnet' : 'mainnet'
};
- this.user_properties['accounts'] = { value: account!.publicKeys.length };
- this.user_properties['version'] = { value: version };
- this.user_properties['platform'] = { value: platform };
+ this.user_properties.accounts = { value: params.accounts?.length || 0 };
+ this.user_properties.version = { value: params.version };
+ this.user_properties.platform = { value: params.platform };
}
pageView(location: string) {
diff --git a/packages/uikit/src/hooks/analytics/gtag.ts b/packages/uikit/src/hooks/analytics/gtag.ts
index 397c53f16..c2f6ba771 100644
--- a/packages/uikit/src/hooks/analytics/gtag.ts
+++ b/packages/uikit/src/hooks/analytics/gtag.ts
@@ -1,6 +1,5 @@
-import { AccountState } from '@tonkeeper/core/dist/entries/account';
import { Network } from '@tonkeeper/core/dist/entries/network';
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
+import { Account } from '@tonkeeper/core/dist/entries/account';
import ReactGA from 'react-ga4';
import { Analytics } from '.';
@@ -9,21 +8,22 @@ export class Gtag implements Analytics {
ReactGA.initialize(this.measurementId);
}
- init(
- application: string,
- walletType: string,
- account?: AccountState,
- wallet?: WalletState | null,
- version?: string,
- platform?: string
- ) {
+ init(params: {
+ application: string;
+ walletType: string;
+ activeAccount: Account;
+ accounts: Account[];
+ network?: Network;
+ version?: string;
+ platform?: string;
+ }) {
ReactGA.gtag('set', 'user_properties', {
- application,
- walletType,
- network: wallet?.network === Network.TESTNET ? 'testnet' : 'mainnet',
- accounts: account!.publicKeys.length,
- version,
- platform
+ application: params.application,
+ walletType: params.walletType,
+ network: params.network === Network.TESTNET ? 'testnet' : 'mainnet',
+ accounts: params.accounts.length,
+ version: params.version,
+ platform: params.version
});
}
diff --git a/packages/uikit/src/hooks/analytics/index.ts b/packages/uikit/src/hooks/analytics/index.ts
index 3f8613a70..7e7ce6c62 100644
--- a/packages/uikit/src/hooks/analytics/index.ts
+++ b/packages/uikit/src/hooks/analytics/index.ts
@@ -1,17 +1,23 @@
-import { AccountState } from '@tonkeeper/core/dist/entries/account';
-import { WalletState, walletVersionText } from '@tonkeeper/core/dist/entries/wallet';
+import {
+ isStandardTonWallet,
+ walletVersionText,
+ TonWalletStandard
+} from '@tonkeeper/core/dist/entries/wallet';
+import { Account } from '@tonkeeper/core/dist/entries/account';
+import { Network } from '@tonkeeper/core/dist/entries/network';
export interface Analytics {
pageView: (location: string) => void;
- init: (
- application: string,
- walletType: string,
- account?: AccountState,
- wallet?: WalletState | null,
- version?: string,
- platform?: string
- ) => void;
+ init: (params: {
+ application: string;
+ walletType: string;
+ activeAccount: Account;
+ accounts: Account[];
+ network?: Network;
+ version?: string;
+ platform?: string;
+ }) => void;
track: (name: string, params: Record) => Promise;
}
@@ -21,31 +27,32 @@ export class AnalyticsGroup implements Analytics {
constructor(...items: Analytics[]) {
this.analytics = items;
}
+
pageView(location: string) {
this.analytics.forEach(c => c.pageView(location));
}
- init(
- application: string,
- walletType: string,
- account?: AccountState,
- wallet?: WalletState | null,
- version?: string,
- platform?: string
- ) {
- this.analytics.forEach(c =>
- c.init(application, walletType, account, wallet, version, platform)
- );
+ init(params: {
+ application: string;
+ walletType: string;
+ activeAccount: Account;
+ accounts: Account[];
+ network?: Network;
+ version?: string;
+ platform?: string;
+ }) {
+ this.analytics.forEach(c => c.init(params));
}
async track(name: string, params: Record) {
- return await Promise.all(this.analytics.map(c => c.track(name, params))).then(
- () => undefined
- );
+ return Promise.all(this.analytics.map(c => c.track(name, params))).then(() => undefined);
}
}
-export const toWalletType = (wallet?: WalletState | null): string => {
+export const toWalletType = (wallet?: TonWalletStandard | null): string => {
if (!wallet) return 'new-user';
- return walletVersionText(wallet.active.version);
+ if (!isStandardTonWallet(wallet)) {
+ return 'multisend';
+ }
+ return walletVersionText(wallet.version);
};
diff --git a/packages/uikit/src/hooks/appContext.ts b/packages/uikit/src/hooks/appContext.ts
index 30e70b44b..9a02fd994 100644
--- a/packages/uikit/src/hooks/appContext.ts
+++ b/packages/uikit/src/hooks/appContext.ts
@@ -1,21 +1,17 @@
-import { AccountState, defaultAccountState } from '@tonkeeper/core/dist/entries/account';
import { APIConfig } from '@tonkeeper/core/dist/entries/apis';
import { FiatCurrencies } from '@tonkeeper/core/dist/entries/fiat';
-import { AuthState, defaultAuthState } from '@tonkeeper/core/dist/entries/password';
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
import { Configuration as ConfigurationV2 } from '@tonkeeper/core/dist/tonApiV2';
import {
+ defaultTonendpointConfig,
Tonendpoint,
- TonendpointConfig,
- defaultTonendpointConfig
+ TonendpointConfig
} from '@tonkeeper/core/dist/tonkeeperApi/tonendpoint';
import { Configuration as TronConfiguration } from '@tonkeeper/core/dist/tronApi';
import React, { useContext } from 'react';
+import { WalletVersion } from '@tonkeeper/core/dist/entries/wallet';
export interface IAppContext {
api: APIConfig;
- account: AccountState;
- auth: AuthState;
fiat: FiatCurrencies;
config: TonendpointConfig;
tonendpoint: Tonendpoint;
@@ -33,6 +29,7 @@ export interface IAppContext {
tgAuthBotId: string;
stonfiReferralAddress: string;
};
+ defaultWalletVersion: WalletVersion;
}
export const AppContext = React.createContext({
@@ -40,8 +37,6 @@ export const AppContext = React.createContext({
tonApiV2: new ConfigurationV2(),
tronApi: new TronConfiguration()
},
- account: defaultAccountState,
- auth: defaultAuthState,
fiat: FiatCurrencies.USD,
config: defaultTonendpointConfig,
tonendpoint: new Tonendpoint({ targetEnv: 'web' }, {}),
@@ -49,17 +44,12 @@ export const AppContext = React.createContext({
extension: false,
ios: false,
proFeatures: false,
- hideQrScanner: false
+ hideQrScanner: false,
+ defaultWalletVersion: WalletVersion.V5R1
});
export const useAppContext = () => {
return useContext(AppContext);
};
-export const WalletStateContext = React.createContext(undefined!);
-
-export const useWalletContext = () => {
- return useContext(WalletStateContext);
-};
-
export const AppSelectionContext = React.createContext(null);
diff --git a/packages/uikit/src/hooks/balance.ts b/packages/uikit/src/hooks/balance.ts
index c630ee661..6355478e3 100644
--- a/packages/uikit/src/hooks/balance.ts
+++ b/packages/uikit/src/hooks/balance.ts
@@ -1,6 +1,6 @@
import { FiatCurrencies } from '@tonkeeper/core/dist/entries/fiat';
import { AmountFormatter } from '@tonkeeper/core/dist/utils/AmountFormatter';
-import { formatDecimals } from '@tonkeeper/core/dist/utils/balance';
+import { formatDecimals, shiftedDecimals } from "@tonkeeper/core/dist/utils/balance";
import { getDecimalSeparator, getGroupSeparator } from '@tonkeeper/core/dist/utils/formatting';
import BigNumber from 'bignumber.js';
import { useCallback, useMemo } from 'react';
@@ -12,6 +12,10 @@ export const formatter = new AmountFormatter({
})
});
+export const toFormattedTonBalance = (weiBalance: number) => {
+ return formatter.format(shiftedDecimals(weiBalance, 9));
+};
+
export const useCoinFullBalance = (balance: number | string, decimals: string | number = 9) => {
return useMemo(
() =>
diff --git a/packages/uikit/src/hooks/blockchain/nft/useAreNftActionsDisabled.ts b/packages/uikit/src/hooks/blockchain/nft/useAreNftActionsDisabled.ts
index 592b70865..718ade3f3 100644
--- a/packages/uikit/src/hooks/blockchain/nft/useAreNftActionsDisabled.ts
+++ b/packages/uikit/src/hooks/blockchain/nft/useAreNftActionsDisabled.ts
@@ -1,11 +1,9 @@
import { NFT } from '@tonkeeper/core/dist/entries/nft';
import { seeIfAddressEqual } from '@tonkeeper/core/dist/utils/common';
-import { useWalletContext } from '../../appContext';
+import { useActiveWallet } from '../../../state/wallet';
export function useAreNftActionsDisabled(nft: NFT) {
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
- return (
- nft.sale !== undefined || !seeIfAddressEqual(wallet.active.rawAddress, nft.owner?.address)
- );
+ return nft.sale !== undefined || !seeIfAddressEqual(wallet.rawAddress, nft.owner?.address);
}
diff --git a/packages/uikit/src/hooks/blockchain/useEstimateMultiTransferFee.ts b/packages/uikit/src/hooks/blockchain/useEstimateMultiTransferFee.ts
index 8b2d62d7f..6897a347a 100644
--- a/packages/uikit/src/hooks/blockchain/useEstimateMultiTransferFee.ts
+++ b/packages/uikit/src/hooks/blockchain/useEstimateMultiTransferFee.ts
@@ -10,12 +10,13 @@ import {
import { AccountEvent } from '@tonkeeper/core/dist/tonApiV2';
import BigNumber from 'bignumber.js';
import { useJettonList } from '../../state/jetton';
-import { useAppContext, useWalletContext } from '../appContext';
+import { useAppContext } from '../appContext';
import { MultiSendFormTokenized, multiSendFormToTransferMessages } from './useSendMultiTransfer';
+import { useActiveStandardTonWallet } from '../../state/wallet';
export function useEstimateMultiTransfer() {
const { api } = useAppContext();
- const wallet = useWalletContext();
+ const wallet = useActiveStandardTonWallet();
const { data: jettons } = useJettonList();
return useMutation<
diff --git a/packages/uikit/src/hooks/blockchain/useEstimateTonFee.ts b/packages/uikit/src/hooks/blockchain/useEstimateTonFee.ts
index f5072fe5d..7cf382c36 100644
--- a/packages/uikit/src/hooks/blockchain/useEstimateTonFee.ts
+++ b/packages/uikit/src/hooks/blockchain/useEstimateTonFee.ts
@@ -4,14 +4,15 @@ import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amo
import { TON_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants';
import { TonAsset } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset';
import { TransferEstimation } from '@tonkeeper/core/dist/entries/send';
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
+import { TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet';
import { EmulationApi } from '@tonkeeper/core/dist/tonApiV2';
import { Omit } from 'react-beautiful-dnd';
-import { useAppContext, useWalletContext } from '../appContext';
+import { useAppContext } from '../appContext';
+import { useActiveStandardTonWallet } from '../../state/wallet';
export type ContractCallerParams = {
api: APIConfig;
- walletState: WalletState;
+ walletState: TonWalletStandard;
};
export function useEstimateTonFee(
@@ -27,7 +28,7 @@ export function useEstimateTonFee(
args: Omit
) {
const { api } = useAppContext();
- const walletState = useWalletContext();
+ const walletState = useActiveStandardTonWallet();
return useQuery, Error>(
queryKey,
@@ -36,7 +37,7 @@ export function useEstimateTonFee(
const event = await new EmulationApi(api.tonApiV2).emulateMessageToAccountEvent({
ignoreSignatureCheck: true,
- accountId: walletState.active.rawAddress,
+ accountId: walletState.rawAddress,
decodeMessageRequest: { boc }
});
diff --git a/packages/uikit/src/hooks/blockchain/useEstimateTransfer.ts b/packages/uikit/src/hooks/blockchain/useEstimateTransfer.ts
index 33c8ad5a3..b9fa194bd 100644
--- a/packages/uikit/src/hooks/blockchain/useEstimateTransfer.ts
+++ b/packages/uikit/src/hooks/blockchain/useEstimateTransfer.ts
@@ -5,26 +5,24 @@ import { Asset, isTonAsset } from '@tonkeeper/core/dist/entries/crypto/asset/ass
import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount';
import { TON_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants';
import { TonAsset } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset';
-import { TronAsset } from '@tonkeeper/core/dist/entries/crypto/asset/tron-asset';
import {
RecipientData,
TonRecipientData,
TransferEstimation,
TransferEstimationEvent
} from '@tonkeeper/core/dist/entries/send';
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
+import { TonWalletStandard } from '@tonkeeper/core/dist/entries/wallet';
import { estimateJettonTransfer } from '@tonkeeper/core/dist/service/transfer/jettonService';
import { estimateTonTransfer } from '@tonkeeper/core/dist/service/transfer/tonService';
-import { estimateTron } from '@tonkeeper/core/dist/service/tron/tronTransferService';
import { JettonsBalances } from '@tonkeeper/core/dist/tonApiV2';
import { notifyError } from '../../components/transfer/common';
import { QueryKey } from '../../libs/queryKey';
import { useJettonList } from '../../state/jetton';
import { DefaultRefetchInterval } from '../../state/tonendpoint';
-import { useTronBalances } from '../../state/tron/tron';
-import { useAppContext, useWalletContext } from '../appContext';
+import { useAppContext } from '../appContext';
import { useAppSdk } from '../appSdk';
import { useTranslation } from '../translation';
+import { useActiveStandardTonWallet } from '../../state/wallet';
async function estimateTon({
recipient,
@@ -38,7 +36,7 @@ async function estimateTon({
amount: AssetAmount;
isMax: boolean;
api: APIConfig;
- wallet: WalletState;
+ wallet: TonWalletStandard;
jettons: JettonsBalances | undefined;
}): Promise> {
let payload: TransferEstimationEvent;
@@ -75,10 +73,10 @@ export function useEstimateTransfer(
const { t } = useTranslation();
const sdk = useAppSdk();
const { api } = useAppContext();
- const wallet = useWalletContext();
+ const wallet = useActiveStandardTonWallet();
const client = useQueryClient();
const { data: jettons } = useJettonList();
- const { data: balances } = useTronBalances();
+ /*const { data: balances } = useTronBalances();*/
return useQuery, Error>(
[QueryKey.estimate, recipient, amount],
@@ -94,14 +92,16 @@ export function useEstimateTransfer(
jettons
});
} else {
- return await estimateTron({
+ /* return await estimateTron({
amount: amount as AssetAmount,
tronApi: api.tronApi,
wallet,
recipient,
isMax,
balances
- });
+ });*/
+
+ throw new Error('Tron is not supported');
}
} catch (e) {
await notifyError(client, sdk, t, e);
diff --git a/packages/uikit/src/hooks/blockchain/useExecuteTonContract.ts b/packages/uikit/src/hooks/blockchain/useExecuteTonContract.ts
index bda62166b..ac84ddc41 100644
--- a/packages/uikit/src/hooks/blockchain/useExecuteTonContract.ts
+++ b/packages/uikit/src/hooks/blockchain/useExecuteTonContract.ts
@@ -2,20 +2,21 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { APIConfig } from '@tonkeeper/core/dist/entries/apis';
import { CellSigner } from '@tonkeeper/core/dist/entries/signer';
import { TransferEstimationEvent } from '@tonkeeper/core/dist/entries/send';
-import { WalletState } from '@tonkeeper/core/dist/entries/wallet';
+import { Account } from '@tonkeeper/core/dist/entries/account';
import { Omit } from 'react-beautiful-dnd';
import { notifyError } from '../../components/transfer/common';
import { getSigner } from '../../state/mnemonic';
import { AmplitudeTransactionType, useTransactionAnalytics } from '../amplitude';
-import { useAppContext, useWalletContext } from '../appContext';
+import { useAppContext } from '../appContext';
import { useAppSdk } from '../appSdk';
import { useTranslation } from '../translation';
import { TxConfirmationCustomError } from '../../libs/errors/TxConfirmationCustomError';
import { useCheckTouchId } from '../../state/password';
+import { useActiveAccount, useInvalidateActiveWalletQueries } from '../../state/wallet';
export type ContractExecutorParams = {
api: APIConfig;
- walletState: WalletState;
+ account: Account;
signer: CellSigner;
fee: TransferEstimationEvent;
};
@@ -33,17 +34,18 @@ export function useExecuteTonContract(
const { t } = useTranslation();
const sdk = useAppSdk();
const { api } = useAppContext();
- const walletState = useWalletContext();
+ const account = useActiveAccount();
const client = useQueryClient();
const track2 = useTransactionAnalytics();
const { mutateAsync: checkTouchId } = useCheckTouchId();
+ const { mutateAsync: invalidateAccountQueries } = useInvalidateActiveWalletQueries();
return useMutation(async () => {
if (!args.fee) {
return false;
}
- const signer = await getSigner(sdk, walletState.publicKey, checkTouchId).catch(() => null);
+ const signer = await getSigner(sdk, account.id, checkTouchId).catch(() => null);
if (signer?.type !== 'cell') {
throw new TxConfirmationCustomError(t('ledger_operation_not_supported'));
}
@@ -54,7 +56,7 @@ export function useExecuteTonContract(
try {
await executor({
api,
- walletState,
+ account,
signer,
...args
} as Args);
@@ -62,7 +64,7 @@ export function useExecuteTonContract(
await notifyError(client, sdk, t, e);
}
- await client.invalidateQueries([walletState.active.rawAddress]);
+ await invalidateAccountQueries();
await client.invalidateQueries();
return true;
});
diff --git a/packages/uikit/src/hooks/blockchain/useSendMultiTransfer.ts b/packages/uikit/src/hooks/blockchain/useSendMultiTransfer.ts
index 3e782ccce..6b452ea9f 100644
--- a/packages/uikit/src/hooks/blockchain/useSendMultiTransfer.ts
+++ b/packages/uikit/src/hooks/blockchain/useSendMultiTransfer.ts
@@ -15,9 +15,10 @@ import { useJettonList } from '../../state/jetton';
import { getSigner } from '../../state/mnemonic';
import { useCheckTouchId } from '../../state/password';
import { useTransactionAnalytics } from '../amplitude';
-import { useAppContext, useWalletContext } from '../appContext';
+import { useAppContext } from '../appContext';
import { useAppSdk } from '../appSdk';
import { useTranslation } from '../translation';
+import { useActiveAccount } from '../../state/wallet';
export type MultiSendFormTokenized = {
rows: {
@@ -42,7 +43,7 @@ export function useSendMultiTransfer() {
const { t } = useTranslation();
const sdk = useAppSdk();
const { api } = useAppContext();
- const wallet = useWalletContext();
+ const account = useActiveAccount();
const client = useQueryClient();
const track2 = useTransactionAnalytics();
const { data: jettons } = useJettonList();
@@ -53,7 +54,8 @@ export function useSendMultiTransfer() {
Error,
{ form: MultiSendFormTokenized; asset: TonAsset; feeEstimation: BigNumber }
>(async ({ form, asset, feeEstimation }) => {
- const signer = await getSigner(sdk, wallet.publicKey, checkTouchId).catch(() => null);
+ const wallet = account.activeTonWallet;
+ const signer = await getSigner(sdk, account.id, checkTouchId).catch(() => null);
if (signer === null) return false;
try {
if (signer.type !== 'cell') {
@@ -88,7 +90,7 @@ export function useSendMultiTransfer() {
}
await client.invalidateQueries({
- predicate: query => query.queryKey.includes(wallet.active.rawAddress)
+ predicate: query => query.queryKey.includes(wallet.id)
});
return true;
});
diff --git a/packages/uikit/src/hooks/blockchain/useSendTransfer.ts b/packages/uikit/src/hooks/blockchain/useSendTransfer.ts
index 5c169d720..e8c4e56c3 100644
--- a/packages/uikit/src/hooks/blockchain/useSendTransfer.ts
+++ b/packages/uikit/src/hooks/blockchain/useSendTransfer.ts
@@ -17,9 +17,10 @@ import { useJettonList } from '../../state/jetton';
import { getSigner } from '../../state/mnemonic';
import { useCheckTouchId } from '../../state/password';
import { useTransactionAnalytics } from '../amplitude';
-import { useAppContext, useWalletContext } from '../appContext';
+import { useAppContext } from '../appContext';
import { useAppSdk } from '../appSdk';
import { useTranslation } from '../translation';
+import { useActiveAccount, useInvalidateActiveWalletQueries } from '../../state/wallet';
export function useSendTransfer(
recipient: T extends TonAsset ? TonRecipientData : TronRecipientData,
@@ -30,14 +31,15 @@ export function useSendTransfer(
const { t } = useTranslation();
const sdk = useAppSdk();
const { api } = useAppContext();
- const wallet = useWalletContext();
+ const account = useActiveAccount();
const client = useQueryClient();
const track2 = useTransactionAnalytics();
const { data: jettons } = useJettonList();
const { mutateAsync: checkTouchId } = useCheckTouchId();
+ const { mutateAsync: invalidateAccountQueries } = useInvalidateActiveWalletQueries();
return useMutation(async () => {
- const signer = await getSigner(sdk, wallet.publicKey, checkTouchId).catch(() => null);
+ const signer = await getSigner(sdk, account.id, checkTouchId).catch(() => null);
if (signer === null) return false;
try {
if (isTonAsset(amount.asset)) {
@@ -45,7 +47,7 @@ export function useSendTransfer(
track2('send-ton');
await sendTonTransfer(
api,
- wallet,
+ account,
recipient as TonRecipientData,
amount,
isMax,
@@ -61,7 +63,7 @@ export function useSendTransfer(
)!;
await sendJettonTransfer(
api,
- wallet,
+ account,
recipient as TonRecipientData,
amount as AssetAmount,
jettonInfo!.walletAddress.address,
@@ -87,9 +89,7 @@ export function useSendTransfer(
await notifyError(client, sdk, t, e);
}
- await client.invalidateQueries({
- predicate: query => query.queryKey.includes(wallet.active.rawAddress)
- });
+ await invalidateAccountQueries();
return true;
});
}
diff --git a/packages/uikit/src/hooks/blockchain/useTonRecipient.ts b/packages/uikit/src/hooks/blockchain/useTonRecipient.ts
index 22d3fb08f..6398f64ab 100644
--- a/packages/uikit/src/hooks/blockchain/useTonRecipient.ts
+++ b/packages/uikit/src/hooks/blockchain/useTonRecipient.ts
@@ -3,13 +3,13 @@ import { TonRecipientData } from '@tonkeeper/core/dist/entries/send';
import { formatAddress } from '@tonkeeper/core/dist/utils/common';
import { useEffect, useMemo, useRef } from 'react';
import { useGetToAccount } from '../../components/transfer/RecipientView';
-import { useWalletContext } from '../appContext';
+import { useActiveTonNetwork } from '../../state/wallet';
export function useTonRecipient(address: string): {
recipient: TonRecipientData;
isLoading: boolean;
} {
- const wallet = useWalletContext();
+ const network = useActiveTonNetwork();
const isFirstRender = useRef(true);
const { isLoading, data: toAccount, mutate: mutateRecipient } = useGetToAccount();
useEffect(() => {
@@ -19,14 +19,14 @@ export function useTonRecipient(address: string): {
const recipient = useMemo(
() => ({
address: {
- address: formatAddress(address, wallet.network, true),
+ address: formatAddress(address, network, true),
blockchain: BLOCKCHAIN_NAME.TON
} as const,
comment: '',
done: false,
toAccount: toAccount!
}),
- [toAccount]
+ [toAccount, network]
);
return {
diff --git a/packages/uikit/src/hooks/useDebuggingTools.ts b/packages/uikit/src/hooks/useDebuggingTools.ts
new file mode 100644
index 000000000..bfa30c244
--- /dev/null
+++ b/packages/uikit/src/hooks/useDebuggingTools.ts
@@ -0,0 +1,25 @@
+import { useAppSdk } from './appSdk';
+import { AccountsState } from '@tonkeeper/core/dist/entries/account';
+import { accountsStorage } from '@tonkeeper/core/dist/service/accountsStorage';
+
+export const useDebuggingTools = () => {
+ const sdk = useAppSdk();
+ if (typeof window !== 'undefined') {
+ const activityKey =
+ 'I UNDERSTAND THAT BY DOING THIS I MAY LOSE ALL MY FUNDS/Я ПОНИМАЮ, ЧТО ПОДЕЛАЯ ТАК, Я МОГУ ПОТЕРЯТЬ ВСЕ СВОИ СРЕДСТВА';
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ window.kdt = {
+ checkKey() {
+ return this.key && this.key === activityKey;
+ },
+ clearNewWalletsStorage() {
+ if (!this.checkKey()) {
+ console.error('ERR: method is not supported');
+ return;
+ }
+ accountsStorage(sdk.storage).setAccounts(null as unknown as AccountsState);
+ }
+ };
+ }
+};
diff --git a/packages/uikit/src/hooks/useIsHovered.ts b/packages/uikit/src/hooks/useIsHovered.ts
new file mode 100644
index 000000000..24450a094
--- /dev/null
+++ b/packages/uikit/src/hooks/useIsHovered.ts
@@ -0,0 +1,15 @@
+import { useCallback, useRef, useState } from 'react';
+import { useEventListener } from './useEventListener';
+
+export const useIsHovered = () => {
+ const [isHovered, setIsHovered] = useState(false);
+ const ref = useRef(null);
+
+ const handleMouseEnter = useCallback(() => setIsHovered(true), []);
+ const handleMouseLeave = useCallback(() => setIsHovered(false), []);
+
+ useEventListener('mouseenter', handleMouseEnter, ref);
+ useEventListener('mouseleave', handleMouseLeave, ref);
+
+ return { isHovered, ref };
+};
diff --git a/packages/uikit/src/hooks/useStorage.ts b/packages/uikit/src/hooks/useStorage.ts
new file mode 100644
index 000000000..da047076c
--- /dev/null
+++ b/packages/uikit/src/hooks/useStorage.ts
@@ -0,0 +1,13 @@
+import { useAppSdk } from './appSdk';
+import { passwordStorage } from '@tonkeeper/core/dist/service/passwordService';
+import { accountsStorage } from '@tonkeeper/core/dist/service/accountsStorage';
+
+export const useAccountsStorage = () => {
+ const sdk = useAppSdk();
+ return accountsStorage(sdk.storage);
+};
+
+export const usePasswordStorage = () => {
+ const sdk = useAppSdk();
+ return passwordStorage(sdk.storage);
+};
diff --git a/packages/uikit/src/libs/queryKey.ts b/packages/uikit/src/libs/queryKey.ts
index 05ddf1950..21cdfb2e4 100644
--- a/packages/uikit/src/libs/queryKey.ts
+++ b/packages/uikit/src/libs/queryKey.ts
@@ -1,3 +1,5 @@
+import { InvalidateQueryFilters } from '@tanstack/react-query';
+
export enum QueryKey {
account = 'account',
wallet = 'wallet',
@@ -20,6 +22,9 @@ export enum QueryKey {
system = 'system',
syncDate = 'syncDate',
analytics = 'analytics',
+ language = 'language',
+ walletVersions = 'walletVersions',
+ globalPreferencesConfig = 'globalPreferencesConfig',
tonConnectConnection = 'tonConnectConnection',
tonConnectLastEventId = 'tonConnectLastEventId',
@@ -60,3 +65,9 @@ export enum TonkeeperApiKey {
stock,
fiat
}
+
+export function anyOfKeysParts(...keys: string[]): InvalidateQueryFilters {
+ return {
+ predicate: q => q.queryKey.some(element => keys.includes(element as string))
+ };
+}
diff --git a/packages/uikit/src/libs/routes.ts b/packages/uikit/src/libs/routes.ts
index a7d672c7b..e5d8e1f04 100644
--- a/packages/uikit/src/libs/routes.ts
+++ b/packages/uikit/src/libs/routes.ts
@@ -11,7 +11,6 @@ export enum AppRoute {
signer = '/signer',
publish = '/publish',
swap = '/swap',
- notcoin = '/notcoin',
home = '/'
}
@@ -43,6 +42,7 @@ export enum SettingsRoute {
account = '/account',
recovery = '/recovery',
version = '/version',
+ ledgerIndexes = '/ledger-indexes',
jettons = '/jettons',
nft = '/nft',
security = '/security',
@@ -55,6 +55,7 @@ export enum WalletSettingsRoute {
index = '/',
recovery = '/recovery',
version = '/version',
+ ledgerIndexes = '/ledger-indexes',
jettons = '/jettons',
nft = '/nft',
connectedApps = '/connected-apps'
diff --git a/packages/uikit/src/libs/web.ts b/packages/uikit/src/libs/web.ts
new file mode 100644
index 000000000..ba4472eb9
--- /dev/null
+++ b/packages/uikit/src/libs/web.ts
@@ -0,0 +1,23 @@
+export function getUserOS() {
+ if (navigator.userAgent.includes('Win')) {
+ return 'windows';
+ }
+ if (navigator.userAgent.includes('Mac')) {
+ return 'mac';
+ }
+ if (navigator.userAgent.includes('Linux')) {
+ return 'linux';
+ }
+ if (navigator.userAgent.includes('Android')) {
+ return 'android';
+ }
+ if (
+ navigator.userAgent.includes('iPhone') ||
+ navigator.userAgent.includes('iPad') ||
+ navigator.userAgent.includes('iPod')
+ ) {
+ return 'ios';
+ }
+
+ return undefined;
+}
diff --git a/packages/uikit/src/pages/activity/Activity.tsx b/packages/uikit/src/pages/activity/Activity.tsx
index 6ef46683a..6924833a4 100644
--- a/packages/uikit/src/pages/activity/Activity.tsx
+++ b/packages/uikit/src/pages/activity/Activity.tsx
@@ -3,18 +3,19 @@ import { AccountsApi } from '@tonkeeper/core/dist/tonApiV2';
import React, { FC, Suspense, useMemo, useRef } from 'react';
import { InnerBody } from '../../components/Body';
import { ActivityHeader } from '../../components/Header';
-import { ActivitySkeletonPage, SkeletonList } from '../../components/Skeleton';
+import { ActivitySkeletonPage, SkeletonListWithImages } from '../../components/Skeleton';
import { MixedActivityGroup } from '../../components/activity/ActivityGroup';
-import { useAppContext, useWalletContext } from '../../hooks/appContext';
+import { useAppContext } from '../../hooks/appContext';
import { useFetchNext } from '../../hooks/useFetchNext';
import { QueryKey } from '../../libs/queryKey';
import { getMixedActivityGroups } from '../../state/mixedActivity';
+import { useActiveWallet } from '../../state/wallet';
const EmptyActivity = React.lazy(() => import('../../components/activity/EmptyActivity'));
const Activity: FC = () => {
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const { api, standalone } = useAppContext();
const ref = useRef(null);
@@ -26,10 +27,10 @@ const Activity: FC = () => {
isFetchingNextPage: isTonFetchingNextPage,
data: tonEvents
} = useInfiniteQuery({
- queryKey: [wallet.active.rawAddress, QueryKey.activity, 'all'],
+ queryKey: [wallet.rawAddress, QueryKey.activity, 'all'],
queryFn: ({ pageParam = undefined }) =>
new AccountsApi(api.tonApiV2).getAccountEvents({
- accountId: wallet.active.rawAddress,
+ accountId: wallet.rawAddress,
limit: 20,
beforeLt: pageParam,
subjectOnly: true
@@ -81,7 +82,7 @@ const Activity: FC = () => {
- {isFetchingNextPage && }
+ {isFetchingNextPage && }
>
);
diff --git a/packages/uikit/src/pages/coin/Jetton.tsx b/packages/uikit/src/pages/coin/Jetton.tsx
index 4ed3ccaab..93d6284dd 100644
--- a/packages/uikit/src/pages/coin/Jetton.tsx
+++ b/packages/uikit/src/pages/coin/Jetton.tsx
@@ -12,7 +12,7 @@ import { ActionsRow } from '../../components/home/Actions';
import { ReceiveAction } from '../../components/home/ReceiveAction';
import { CoinInfo } from '../../components/jettons/Info';
import { SendAction } from '../../components/transfer/SendActionButton';
-import { useAppContext, useWalletContext } from '../../hooks/appContext';
+import { useAppContext } from '../../hooks/appContext';
import { useFormatBalance } from '../../hooks/balance';
import { useFetchNext } from '../../hooks/useFetchNext';
import { JettonKey, QueryKey } from '../../libs/queryKey';
@@ -21,19 +21,20 @@ import { useFormatFiat, useRate } from '../../state/rates';
import { SwapAction } from '../../components/home/SwapAction';
import { tonAssetAddressToString } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset';
import { useAllSwapAssets } from '../../state/swap/useSwapAssets';
+import { useActiveWallet } from '../../state/wallet';
const JettonHistory: FC<{ balance: JettonBalance; innerRef: React.RefObject }> = ({
balance,
innerRef
}) => {
const { api, standalone } = useAppContext();
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const { isFetched, hasNextPage, data, isFetchingNextPage, fetchNextPage } = useInfiniteQuery({
queryKey: [balance.walletAddress.address, QueryKey.activity, JettonKey.history],
queryFn: ({ pageParam = undefined }) =>
new AccountsApi(api.tonApiV2).getAccountJettonHistoryByID({
- accountId: wallet.active.rawAddress,
+ accountId: wallet.rawAddress,
jettonId: balance.jetton.address,
limit: 20,
beforeLt: pageParam
diff --git a/packages/uikit/src/pages/coin/Ton.tsx b/packages/uikit/src/pages/coin/Ton.tsx
index 8b634bf4b..184020f37 100644
--- a/packages/uikit/src/pages/coin/Ton.tsx
+++ b/packages/uikit/src/pages/coin/Ton.tsx
@@ -10,14 +10,14 @@ import { SubHeader } from '../../components/SubHeader';
import { ActivityList } from '../../components/activity/ActivityGroup';
import { HomeActions } from '../../components/home/TonActions';
import { CoinInfo } from '../../components/jettons/Info';
-import { useAppContext, useWalletContext } from '../../hooks/appContext';
+import { useAppContext } from '../../hooks/appContext';
import { useFormatBalance } from '../../hooks/balance';
import { useTranslation } from '../../hooks/translation';
import { useFetchNext } from '../../hooks/useFetchNext';
import { QueryKey } from '../../libs/queryKey';
import { useFormatFiat, useRate } from '../../state/rates';
import { groupAndFilterTonActivityItems } from '../../state/ton/tonActivity';
-import { useWalletAccountInfo } from '../../state/wallet';
+import { useActiveWallet, useWalletAccountInfo } from '../../state/wallet';
const TonHeader: FC<{ info: Account }> = ({ info: { balance } }) => {
const { t } = useTranslation();
@@ -46,13 +46,13 @@ export const TonPage = () => {
const { data: info } = useWalletAccountInfo();
const { api, standalone } = useAppContext();
- const wallet = useWalletContext();
+ const wallet = useActiveWallet();
const { fetchNextPage, hasNextPage, isFetchingNextPage, data, isFetched } = useInfiniteQuery({
- queryKey: [wallet.active.rawAddress, QueryKey.activity, 'ton'],
+ queryKey: [wallet.rawAddress, QueryKey.activity, 'ton'],
queryFn: ({ pageParam = undefined }) =>
new AccountsApi(api.tonApiV2).getAccountEvents({
- accountId: wallet.active.rawAddress,
+ accountId: wallet.rawAddress,
limit: 20,
beforeLt: pageParam,
subjectOnly: true
diff --git a/packages/uikit/src/pages/coin/Tron.tsx b/packages/uikit/src/pages/coin/Tron.tsx
index b2def38bb..454bee17a 100644
--- a/packages/uikit/src/pages/coin/Tron.tsx
+++ b/packages/uikit/src/pages/coin/Tron.tsx
@@ -1,7 +1,6 @@
-import { useInfiniteQuery } from '@tanstack/react-query';
import { BLOCKCHAIN_NAME } from '@tonkeeper/core/dist/entries/crypto';
import { TronWalletState } from '@tonkeeper/core/dist/entries/wallet';
-import { TronApi, TronBalance } from '@tonkeeper/core/dist/tronApi';
+import { TronBalance } from '@tonkeeper/core/dist/tronApi';
import { formatDecimals } from '@tonkeeper/core/dist/utils/balance';
import React, { FC, useEffect, useMemo, useRef } from 'react';
import { Route, Routes, useNavigate, useParams } from 'react-router-dom';
@@ -10,15 +9,11 @@ import { InnerBody } from '../../components/Body';
import { CoinSkeletonPage } from '../../components/Skeleton';
import { SubHeader } from '../../components/SubHeader';
import { Body2 } from '../../components/Text';
-import { ActivityList } from '../../components/activity/ActivityGroup';
import { ActionsRow } from '../../components/home/Actions';
import { ReceiveAction } from '../../components/home/ReceiveAction';
import { CoinInfo } from '../../components/jettons/Info';
import { SendAction } from '../../components/transfer/SendActionButton';
-import { useAppContext, useWalletContext } from '../../hooks/appContext';
import { useFormatBalance } from '../../hooks/balance';
-import { useFetchNext } from '../../hooks/useFetchNext';
-import { QueryKey } from '../../libs/queryKey';
import { AppRoute } from '../../libs/routes';
import { useFormatFiat, useRate } from '../../state/rates';
import { useTronBalance, useTronWalletState } from '../../state/tron/tron';
@@ -37,8 +32,8 @@ const TronActivity: FC<{
tron: TronWalletState;
innerRef: React.RefObject;
}> = ({ tron, innerRef }) => {
- const wallet = useWalletContext();
- const {
+ return null;
+ /*const {
standalone,
api: { tronApi }
} = useAppContext();
@@ -61,7 +56,7 @@ const TronActivity: FC<{
isFetchingNextPage={isFetchingNextPage}
tronEvents={data}
/>
- );
+ );*/
};
const Layout = styled.div`
diff --git a/packages/uikit/src/pages/home/Home.tsx b/packages/uikit/src/pages/home/Home.tsx
index 897ad4d80..def1baeb4 100644
--- a/packages/uikit/src/pages/home/Home.tsx
+++ b/packages/uikit/src/pages/home/Home.tsx
@@ -9,7 +9,8 @@ import { TabsView } from '../../components/home/TabsView';
import { HomeActions } from '../../components/home/TonActions';
import { useAssets } from '../../state/home';
import { usePreFetchRates } from '../../state/rates';
-import { useWalletFilteredNftList } from '../../state/wallet';
+
+import { useWalletFilteredNftList } from "../../state/nft";
const HomeAssets: FC<{
assets: AssetData;
diff --git a/packages/uikit/src/pages/home/MainColumn.tsx b/packages/uikit/src/pages/home/MainColumn.tsx
index 9522c0d78..29b9eefbd 100644
--- a/packages/uikit/src/pages/home/MainColumn.tsx
+++ b/packages/uikit/src/pages/home/MainColumn.tsx
@@ -13,7 +13,8 @@ import { useTranslation } from '../../hooks/translation';
import { scrollToTop } from '../../libs/common';
import { AppRoute } from '../../libs/routes';
import { useAssets } from '../../state/home';
-import { useWalletFilteredNftList } from '../../state/wallet';
+
+import { useWalletFilteredNftList } from "../../state/nft";
const MainColumnSkeleton = memo(() => {
const sdk = useAppSdk();
diff --git a/packages/uikit/src/pages/home/Unlock.tsx b/packages/uikit/src/pages/home/Unlock.tsx
index bb747f28e..d0e4f5607 100644
--- a/packages/uikit/src/pages/home/Unlock.tsx
+++ b/packages/uikit/src/pages/home/Unlock.tsx
@@ -1,15 +1,13 @@
import { useMutation } from '@tanstack/react-query';
-import { getWalletWithGlobalAuth } from '@tonkeeper/core/dist/service/accountService';
-import { validateWalletMnemonic } from '@tonkeeper/core/dist/service/mnemonicService';
import React, { FC, useEffect, useRef, useState } from 'react';
import styled, { css } from 'styled-components';
import { TonkeeperIcon } from '../../components/Icon';
import { Button, ButtonRow } from '../../components/fields/Button';
import { Input } from '../../components/fields/Input';
-import { useAppContext } from '../../hooks/appContext';
import { useAppSdk } from '../../hooks/appSdk';
import { useTranslation } from '../../hooks/translation';
-import { useMutateDeleteAll } from '../../state/account';
+import { useIsPasswordSet, useMutateDeleteAll, useAccountsState } from '../../state/wallet';
+import { passwordStorage } from '@tonkeeper/core/dist/service/passwordService';
const Block = styled.form<{ minHeight?: string }>`
display: flex;
@@ -44,11 +42,9 @@ const useMutateUnlock = () => {
const sdk = useAppSdk();
return useMutation(async password => {
- const publicKey = await getWalletWithGlobalAuth(sdk.storage);
-
- const isValid = await validateWalletMnemonic(sdk.storage, publicKey, password);
+ const isValid = await passwordStorage(sdk.storage).isPasswordValid(password);
if (!isValid) {
- throw new Error('Mnemonic not valid');
+ throw new Error('Password not valid');
}
sdk.uiEvents.emit('unlock');
});
@@ -57,7 +53,7 @@ const useMutateUnlock = () => {
export const PasswordUnlock: FC<{ minHeight?: string }> = ({ minHeight }) => {
const sdk = useAppSdk();
const { t } = useTranslation();
- const { account } = useAppContext();
+ const wallets = useAccountsState();
const ref = useRef(null);
const { mutate: mutateLogOut, isLoading: isLogOutLoading } = useMutateDeleteAll();
@@ -84,7 +80,7 @@ export const PasswordUnlock: FC<{ minHeight?: string }> = ({ minHeight }) => {
const onLogOut = async () => {
const confirm = await sdk.confirm(
- t(account.publicKeys.length > 1 ? 'logout_on_unlock_many' : 'logout_on_unlock_one')
+ t(wallets.length > 1 ? 'logout_on_unlock_many' : 'logout_on_unlock_one')
);
if (confirm) {
await mutateLogOut();
@@ -136,9 +132,9 @@ export const PasswordUnlock: FC<{ minHeight?: string }> = ({ minHeight }) => {
};
export const Unlock = () => {
- const { auth } = useAppContext();
+ const isPasswordSet = useIsPasswordSet();
- if (auth.kind === 'password') {
+ if (isPasswordSet) {
return ;
} else {
return Other auth ;
diff --git a/packages/uikit/src/pages/home/UnlockNotification.tsx b/packages/uikit/src/pages/home/UnlockNotification.tsx
index 2ecb26a76..aa5eba7a8 100644
--- a/packages/uikit/src/pages/home/UnlockNotification.tsx
+++ b/packages/uikit/src/pages/home/UnlockNotification.tsx
@@ -1,11 +1,5 @@
import { useMutation } from '@tanstack/react-query';
-import { GetPasswordParams, IAppSdk, KeyboardParams } from '@tonkeeper/core/dist/AppSdk';
-import { AuthState } from '@tonkeeper/core/dist/entries/password';
-import {
- MinPasswordLength,
- getWalletWithGlobalAuth
-} from '@tonkeeper/core/dist/service/accountService';
-import { validateWalletMnemonic } from '@tonkeeper/core/dist/service/mnemonicService';
+import { IAppSdk, KeyboardParams } from '@tonkeeper/core/dist/AppSdk';
import { debounce } from '@tonkeeper/core/dist/utils/common';
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useLocation } from 'react-router-dom';
@@ -15,6 +9,9 @@ import { Button, ButtonRow } from '../../components/fields/Button';
import { Input } from '../../components/fields/Input';
import { hideIosKeyboard, openIosKeyboard } from '../../hooks/ios';
import { useTranslation } from '../../hooks/translation';
+import { passwordStorage, validatePassword } from '@tonkeeper/core/dist/service/passwordService';
+import { useIsPasswordSet } from '../../state/wallet';
+import { CreatePassword } from '../../components/create/CreatePassword';
const Block = styled.form<{ padding: number }>`
display: flex;
@@ -30,14 +27,21 @@ const Block = styled.form<{ padding: number }>`
}
`;
+const CreatePasswordStyled = styled(CreatePassword)<{ padding: number }>`
+ @media (max-width: 440px) {
+ padding-bottom: ${props => props.padding}px;
+ }
+`;
+
export const useMutateUnlock = (sdk: IAppSdk, requestId?: number) => {
+ const isPasswordSet = useIsPasswordSet();
return useMutation(async password => {
- const publicKey = await getWalletWithGlobalAuth(sdk.storage);
-
- const isValid = await validateWalletMnemonic(sdk.storage, publicKey, password);
- if (!isValid) {
- sdk.hapticNotification('error');
- throw new Error('Mnemonic not valid');
+ if (isPasswordSet) {
+ const isValid = await passwordStorage(sdk.storage).isPasswordValid(password);
+ if (!isValid) {
+ sdk.hapticNotification('error');
+ throw new Error('Password not valid');
+ }
}
sdk.uiEvents.emit('response', {
@@ -125,7 +129,7 @@ export const PasswordUnlock: FC<{
primary
fullWidth
type="submit"
- disabled={password.length < MinPasswordLength}
+ disabled={!validatePassword(password)}
loading={isLoading}
>
{t('confirm')}
@@ -141,7 +145,6 @@ export const UnlockNotification: FC<{ sdk: IAppSdk; usePadding?: boolean }> = ({
}) => {
const { t } = useTranslation();
const [padding, setPadding] = useState(0);
- const [auth, setAuth] = useState(undefined);
const [requestId, setId] = useState(undefined);
const setRequest = useMemo(() => {
@@ -151,10 +154,11 @@ export const UnlockNotification: FC<{ sdk: IAppSdk; usePadding?: boolean }> = ({
const { mutateAsync, isLoading, isError, reset } = useMutateUnlock(sdk, requestId);
const close = useCallback(() => {
- setAuth(undefined);
setId(undefined);
}, []);
+ const isPasswordSet = useIsPasswordSet();
+
const onSubmit = async (password: string) => {
reset();
try {
@@ -189,15 +193,9 @@ export const UnlockNotification: FC<{ sdk: IAppSdk; usePadding?: boolean }> = ({
);
};
- const handler = (options: {
- method: 'getPassword';
- id?: number | undefined;
- params: GetPasswordParams;
- }) => {
+ const handler = (options: { method: 'getPassword'; id?: number | undefined }) => {
openIosKeyboard('text', 'password');
- setAuth(options.params?.auth);
-
setRequest(options.id);
};
@@ -211,7 +209,17 @@ export const UnlockNotification: FC<{ sdk: IAppSdk; usePadding?: boolean }> = ({
}, [sdk]);
const Content = useCallback(() => {
- if (!auth || !requestId) return undefined;
+ if (!requestId) return undefined;
+
+ if (!isPasswordSet) {
+ return (
+
+ );
+ }
return (
= ({
padding={usePadding ? padding : 0}
/>
);
- }, [sdk, auth, requestId, padding, onCancel, onSubmit]);
+ }, [sdk, requestId, padding, onCancel, onSubmit, isPasswordSet, isLoading, isError]);
return (
= ({ listOfAuth }) => {
+const Create = () => {
const sdk = useAppSdk();
const { t } = useTranslation();
- const { config } = useAppContext();
- const {
- mutateAsync: checkPasswordAndCreateWalletAsync,
- isLoading: isConfirmLoading,
- reset
- } = useAddWalletMutation();
+ const { defaultWalletVersion } = useAppContext();
+ const { mutateAsync: createWalletsAsync, isLoading: isCreateWalletLoading } =
+ useCreateAccountMnemonic();
+ const { mutateAsync: renameWallet, isLoading: renameLoading } = useMutateRenameAccount();
- const { data: wallet } = useActiveWallet();
- const [mnemonic, setMnemonic] = useState([]);
- const [account, setAccount] = useState(undefined);
+ const [mnemonic, setMnemonic] = useState();
+ const [createdAccount, setCreatedAccount] = useState(undefined);
- const [create, setCreate] = useState(false);
- const [open, setOpen] = useState(false);
- const [check, setCheck] = useState(false);
- const [checked, setChecked] = useState(false);
- const [hasPassword, setHasPassword] = useState(false);
- const [passNotifications, setPassNotification] = useState(false);
+ const [creatingAnimationPassed, setCreatingAnimationPassed] = useState(false);
+ const [infoPagePassed, setInfoPagePassed] = useState(false);
+ const [wordsPagePassed, setWordsPagePassed] = useState(false);
+ const [editNamePagePassed, setEditNamePagePassed] = useState(false);
+ const [notificationsSubscribePagePassed, setPassNotification] = useState(false);
useEffect(() => {
setTimeout(() => {
@@ -47,22 +41,22 @@ const Create: FC<{ listOfAuth: AuthState['kind'][] }> = ({ listOfAuth }) => {
}, []);
useEffect(() => {
- if (mnemonic.length) {
+ if (mnemonic) {
setTimeout(() => {
- setCreate(true);
+ setCreatingAnimationPassed(true);
}, 1500);
}
}, [mnemonic]);
- if (mnemonic.length === 0) {
+ if (!mnemonic) {
return } title={t('create_wallet_generating')} />;
}
- if (!create) {
+ if (!creatingAnimationPassed) {
return } title={t('create_wallet_generated')} />;
}
- if (!open) {
+ if (!infoPagePassed) {
return (
= ({ listOfAuth }) => {
title={t('create_wallet_title')}
description={t('create_wallet_caption')}
button={
- setOpen(true)}>
+ setInfoPagePassed(true)}
+ >
{t('continue')}
}
@@ -78,64 +78,56 @@ const Create: FC<{ listOfAuth: AuthState['kind'][] }> = ({ listOfAuth }) => {
);
}
- if (!check) {
+ if (!wordsPagePassed) {
return (
setOpen(false)}
- onCheck={() => setCheck(true)}
+ onBack={() => setInfoPagePassed(false)}
+ onCheck={() => setWordsPagePassed(true)}
/>
);
}
- if (!checked) {
+ if (!createdAccount) {
return (
setCheck(false)}
- onConfirm={() =>
- checkPasswordAndCreateWalletAsync({ mnemonic, listOfAuth }).then(state => {
- setChecked(true);
- if (state === false) {
- setHasPassword(false);
- } else {
- setHasPassword(true);
- setAccount(state);
- }
- })
- }
- isLoading={isConfirmLoading}
+ onBack={() => setWordsPagePassed(false)}
+ onConfirm={() => {
+ createWalletsAsync({
+ mnemonic,
+ versions: [defaultWalletVersion],
+ selectAccount: true
+ }).then(setCreatedAccount);
+ }}
+ isLoading={isCreateWalletLoading}
/>
);
}
- if (!hasPassword) {
+ if (!editNamePagePassed) {
return (
- {
- reset();
- checkPasswordAndCreateWalletAsync({ mnemonic, password }).then(state => {
- if (state !== false) {
- setHasPassword(true);
- setAccount(state);
- }
+ {
+ renameWallet({
+ id: createdAccount.id,
+ ...val
+ }).then(newAcc => {
+ setEditNamePagePassed(true);
+ setCreatedAccount(newAcc);
});
}}
- isLoading={isConfirmLoading}
+ walletEmoji={createdAccount.emoji}
+ isLoading={renameLoading}
/>
);
}
- if (account && account.publicKeys.length > 1 && wallet && wallet.name == null) {
- return (
-
- );
- }
-
- if (sdk.notifications && !passNotifications) {
+ if (sdk.notifications && !notificationsSubscribePagePassed) {
return (
setPassNotification(true)}
/>
diff --git a/packages/uikit/src/pages/import/Import.tsx b/packages/uikit/src/pages/import/Import.tsx
index e9f6401bd..75af66e71 100644
--- a/packages/uikit/src/pages/import/Import.tsx
+++ b/packages/uikit/src/pages/import/Import.tsx
@@ -1,78 +1,76 @@
-import { AccountState } from '@tonkeeper/core/dist/entries/account';
-import { AuthState } from '@tonkeeper/core/dist/entries/password';
-import { FC, useState } from 'react';
-import { CreateAuthState } from '../../components/create/CreateAuth';
+import React, { useState } from 'react';
import { UpdateWalletName } from '../../components/create/WalletName';
import { ImportWords } from '../../components/create/Words';
import { useAppSdk } from '../../hooks/appSdk';
-import { useActiveWallet } from '../../state/wallet';
-import { FinalView, useAddWalletMutation } from './Password';
+import { FinalView } from './Password';
import { Subscribe } from './Subscribe';
+import { useCreateAccountMnemonic, useMutateRenameAccount } from '../../state/wallet';
+import { ChoseWalletVersions } from '../../components/create/ChoseWalletVersions';
+import { AccountTonMnemonic } from '@tonkeeper/core/dist/entries/account';
-const Import: FC<{ listOfAuth: AuthState['kind'][] }> = ({ listOfAuth }) => {
+const Import = () => {
const sdk = useAppSdk();
- const [mnemonic, setMnemonic] = useState([]);
- const [account, setAccount] = useState(undefined);
- const [hasPassword, setHasPassword] = useState(false);
- const [passNotifications, setPassNotification] = useState(false);
+ const [mnemonic, setMnemonic] = useState();
+ const [createdAccount, setCreatedAccount] = useState(undefined);
- const { data: wallet } = useActiveWallet();
+ const [editNamePagePassed, setEditNamePagePassed] = useState(false);
+ const [notificationsSubscribePagePassed, setNotificationsSubscribePagePassed] = useState(false);
+ const { mutateAsync: renameAccount, isLoading: renameLoading } =
+ useMutateRenameAccount();
- const {
- mutateAsync: checkPasswordAndCreateWalletAsync,
- isLoading: isConfirmLoading,
- reset
- } = useAddWalletMutation();
+ const { mutateAsync: createWalletsAsync, isLoading: isCreatingWallets } =
+ useCreateAccountMnemonic();
- if (mnemonic.length === 0) {
+ if (!mnemonic) {
+ return ;
+ }
+
+ if (!createdAccount) {
return (
- {
- checkPasswordAndCreateWalletAsync({ mnemonic: m, listOfAuth }).then(state => {
- setMnemonic(m);
- if (state === false) {
- setHasPassword(false);
- } else {
- setHasPassword(true);
- setAccount(state);
- }
- });
+ {
+ createWalletsAsync({
+ mnemonic,
+ versions,
+ selectAccount: true
+ }).then(setCreatedAccount);
+ }}
+ onBack={() => {
+ setCreatedAccount(undefined);
+ setMnemonic(undefined);
}}
+ isLoading={isCreatingWallets}
/>
);
}
- if (!hasPassword) {
+ if (!editNamePagePassed) {
return (
- {
- reset();
- checkPasswordAndCreateWalletAsync({ mnemonic, password }).then(state => {
- if (state !== false) {
- setHasPassword(true);
- setAccount(state);
- }
+ {
+ renameAccount({
+ id: createdAccount.id,
+ ...val
+ }).then(newAcc => {
+ setEditNamePagePassed(true);
+ setCreatedAccount(newAcc);
});
}}
- isLoading={isConfirmLoading}
+ walletEmoji={createdAccount.emoji}
+ isLoading={renameLoading}
/>
);
}
- if (account && account.publicKeys.length > 1 && wallet && wallet.name == null) {
- return (
- |